/[svn]/gigedit/trunk/src/gigedit/mainwindow.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/mainwindow.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1582 - (show annotations) (download)
Sat Dec 8 12:28:53 2007 UTC (16 years, 3 months ago) by persson
File size: 63771 byte(s)
* fixed file properties dialog - properties can now be edited

1 /*
2 * Copyright (C) 2006, 2007 Andreas Persson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2, or (at
7 * your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include <iostream>
21
22 #include <gtkmm/filechooserdialog.h>
23 #include <gtkmm/messagedialog.h>
24 #include <gtkmm/stock.h>
25 #include <gtkmm/targetentry.h>
26 #include <gtkmm/main.h>
27 #include <gtkmm/toggleaction.h>
28
29 #include "global.h"
30
31 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 6) || GTKMM_MAJOR_VERSION > 2
32 #define ABOUT_DIALOG
33 #include <gtkmm/aboutdialog.h>
34 #endif
35
36 #if (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION < 6) || GLIBMM_MAJOR_VERSION < 2
37 namespace Glib {
38 Glib::ustring filename_display_basename(const std::string& filename)
39 {
40 gchar* gstr = g_path_get_basename(filename.c_str());
41 Glib::ustring str(gstr);
42 g_free(gstr);
43 return Glib::filename_to_utf8(str);
44 }
45 }
46 #endif
47
48 #include <stdio.h>
49 #include <sndfile.h>
50
51 #include "mainwindow.h"
52
53 #include "../../gfx/status_attached.xpm"
54 #include "../../gfx/status_detached.xpm"
55
56 template<class T> inline std::string ToString(T o) {
57 std::stringstream ss;
58 ss << o;
59 return ss.str();
60 }
61
62 Table::Table(int x, int y) : Gtk::Table(x, y), rowno(0) { }
63
64 void Table::add(BoolEntry& boolentry)
65 {
66 attach(boolentry.widget, 0, 2, rowno, rowno + 1,
67 Gtk::FILL, Gtk::SHRINK);
68 rowno++;
69 }
70
71 void Table::add(BoolEntryPlus6& boolentry)
72 {
73 attach(boolentry.widget, 0, 2, rowno, rowno + 1,
74 Gtk::FILL, Gtk::SHRINK);
75 rowno++;
76 }
77
78 void Table::add(LabelWidget& prop)
79 {
80 attach(prop.label, 1, 2, rowno, rowno + 1,
81 Gtk::FILL, Gtk::SHRINK);
82 attach(prop.widget, 2, 3, rowno, rowno + 1,
83 Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK);
84 rowno++;
85 }
86
87 MainWindow::MainWindow() :
88 dimreg_label(_("Changes apply to:")),
89 dimreg_all_regions(_("all regions")),
90 dimreg_all_dimregs(_("all dimension splits")),
91 dimreg_stereo(_("both channels"))
92 {
93 // set_border_width(5);
94 // set_default_size(400, 200);
95
96
97 add(m_VBox);
98
99 // Handle selection
100 Glib::RefPtr<Gtk::TreeSelection> tree_sel_ref = m_TreeView.get_selection();
101 tree_sel_ref->signal_changed().connect(
102 sigc::mem_fun(*this, &MainWindow::on_sel_change));
103
104 // m_TreeView.set_reorderable();
105
106 m_TreeView.signal_button_press_event().connect_notify(
107 sigc::mem_fun(*this, &MainWindow::on_button_release));
108
109 // Add the TreeView tab, inside a ScrolledWindow, with the button underneath:
110 m_ScrolledWindow.add(m_TreeView);
111 // m_ScrolledWindow.set_size_request(200, 600);
112 m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
113
114 m_ScrolledWindowSamples.add(m_TreeViewSamples);
115 m_ScrolledWindowSamples.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
116
117
118 m_TreeViewNotebook.set_size_request(300);
119
120 m_HPaned.add1(m_TreeViewNotebook);
121 dimreg_hbox.add(dimreg_label);
122 dimreg_hbox.add(dimreg_all_regions);
123 dimreg_hbox.add(dimreg_all_dimregs);
124 dimreg_stereo.set_active();
125 dimreg_hbox.add(dimreg_stereo);
126 dimreg_vbox.add(dimreg_edit);
127 dimreg_vbox.pack_start(dimreg_hbox, Gtk::PACK_SHRINK);
128 m_HPaned.add2(dimreg_vbox);
129
130
131 m_TreeViewNotebook.append_page(m_ScrolledWindowSamples, "Samples");
132 m_TreeViewNotebook.append_page(m_ScrolledWindow, "Instruments");
133
134
135 actionGroup = Gtk::ActionGroup::create();
136
137 actionGroup->add(Gtk::Action::create("MenuFile", _("_File")));
138 actionGroup->add(Gtk::Action::create("New", Gtk::Stock::NEW),
139 sigc::mem_fun(
140 *this, &MainWindow::on_action_file_new));
141 Glib::RefPtr<Gtk::Action> action =
142 Gtk::Action::create("Open", Gtk::Stock::OPEN);
143 action->property_label() = action->property_label() + "...";
144 actionGroup->add(action,
145 sigc::mem_fun(
146 *this, &MainWindow::on_action_file_open));
147 actionGroup->add(Gtk::Action::create("Save", Gtk::Stock::SAVE),
148 sigc::mem_fun(
149 *this, &MainWindow::on_action_file_save));
150 action = Gtk::Action::create("SaveAs", Gtk::Stock::SAVE_AS);
151 action->property_label() = action->property_label() + "...";
152 actionGroup->add(action,
153 Gtk::AccelKey("<shift><control>s"),
154 sigc::mem_fun(
155 *this, &MainWindow::on_action_file_save_as));
156 actionGroup->add(Gtk::Action::create("Properties",
157 Gtk::Stock::PROPERTIES),
158 sigc::mem_fun(
159 *this, &MainWindow::on_action_file_properties));
160 actionGroup->add(Gtk::Action::create("InstrProperties",
161 Gtk::Stock::PROPERTIES),
162 sigc::mem_fun(
163 *this, &MainWindow::show_instr_props));
164 actionGroup->add(Gtk::Action::create("Quit", Gtk::Stock::QUIT),
165 sigc::mem_fun(
166 *this, &MainWindow::on_action_quit));
167 actionGroup->add(Gtk::Action::create("MenuInstrument", _("_Instrument")));
168
169 actionGroup->add(Gtk::Action::create("MenuView", _("_View")));
170 Glib::RefPtr<Gtk::ToggleAction> toggle_action =
171 Gtk::ToggleAction::create("Statusbar", _("_Statusbar"));
172 toggle_action->set_active(true);
173 actionGroup->add(toggle_action,
174 sigc::mem_fun(
175 *this, &MainWindow::on_action_view_status_bar));
176
177 action = Gtk::Action::create("MenuHelp", Gtk::Stock::HELP);
178 actionGroup->add(Gtk::Action::create("MenuHelp",
179 action->property_label()));
180 #ifdef ABOUT_DIALOG
181 actionGroup->add(Gtk::Action::create("About", Gtk::Stock::ABOUT),
182 sigc::mem_fun(
183 *this, &MainWindow::on_action_help_about));
184 #endif
185 actionGroup->add(
186 Gtk::Action::create("AddInstrument", _("Add _Instrument")),
187 sigc::mem_fun(*this, &MainWindow::on_action_add_instrument)
188 );
189 actionGroup->add(
190 Gtk::Action::create("RemoveInstrument", Gtk::Stock::REMOVE),
191 sigc::mem_fun(*this, &MainWindow::on_action_remove_instrument)
192 );
193
194 // sample right-click popup actions
195 actionGroup->add(
196 Gtk::Action::create("SampleProperties", Gtk::Stock::PROPERTIES),
197 sigc::mem_fun(*this, &MainWindow::on_action_sample_properties)
198 );
199 actionGroup->add(
200 Gtk::Action::create("AddGroup", _("Add _Group")),
201 sigc::mem_fun(*this, &MainWindow::on_action_add_group)
202 );
203 actionGroup->add(
204 Gtk::Action::create("AddSample", _("Add _Sample(s)")),
205 sigc::mem_fun(*this, &MainWindow::on_action_add_sample)
206 );
207 actionGroup->add(
208 Gtk::Action::create("RemoveSample", Gtk::Stock::REMOVE),
209 sigc::mem_fun(*this, &MainWindow::on_action_remove_sample)
210 );
211
212 uiManager = Gtk::UIManager::create();
213 uiManager->insert_action_group(actionGroup);
214 add_accel_group(uiManager->get_accel_group());
215
216 Glib::ustring ui_info =
217 "<ui>"
218 " <menubar name='MenuBar'>"
219 " <menu action='MenuFile'>"
220 " <menuitem action='New'/>"
221 " <menuitem action='Open'/>"
222 " <separator/>"
223 " <menuitem action='Save'/>"
224 " <menuitem action='SaveAs'/>"
225 " <separator/>"
226 " <menuitem action='Properties'/>"
227 " <separator/>"
228 " <menuitem action='Quit'/>"
229 " </menu>"
230 " <menu action='MenuInstrument'>"
231 " </menu>"
232 " <menu action='MenuView'>"
233 " <menuitem action='Statusbar'/>"
234 " </menu>"
235 #ifdef ABOUT_DIALOG
236 " <menu action='MenuHelp'>"
237 " <menuitem action='About'/>"
238 " </menu>"
239 #endif
240 " </menubar>"
241 " <popup name='PopupMenu'>"
242 " <menuitem action='InstrProperties'/>"
243 " <menuitem action='AddInstrument'/>"
244 " <separator/>"
245 " <menuitem action='RemoveInstrument'/>"
246 " </popup>"
247 " <popup name='SamplePopupMenu'>"
248 " <menuitem action='SampleProperties'/>"
249 " <menuitem action='AddGroup'/>"
250 " <menuitem action='AddSample'/>"
251 " <separator/>"
252 " <menuitem action='RemoveSample'/>"
253 " </popup>"
254 "</ui>";
255 uiManager->add_ui_from_string(ui_info);
256
257 popup_menu = dynamic_cast<Gtk::Menu*>(uiManager->get_widget("/PopupMenu"));
258
259 Gtk::Widget* menuBar = uiManager->get_widget("/MenuBar");
260 m_VBox.pack_start(*menuBar, Gtk::PACK_SHRINK);
261 m_VBox.pack_start(m_HPaned);
262 m_VBox.pack_start(m_RegionChooser, Gtk::PACK_SHRINK);
263 m_VBox.pack_start(m_DimRegionChooser, Gtk::PACK_SHRINK);
264 m_VBox.pack_start(m_StatusBar, Gtk::PACK_SHRINK);
265
266 // Status Bar:
267 m_StatusBar.pack_start(m_AttachedStateLabel, Gtk::PACK_SHRINK);
268 m_StatusBar.pack_start(m_AttachedStateImage, Gtk::PACK_SHRINK);
269 m_StatusBar.show();
270
271 m_RegionChooser.signal_region_selected().connect(
272 sigc::mem_fun(*this, &MainWindow::region_changed) );
273 m_DimRegionChooser.signal_dimregion_selected().connect(
274 sigc::mem_fun(*this, &MainWindow::dimreg_changed) );
275
276
277 // Create the Tree model:
278 m_refTreeModel = Gtk::ListStore::create(m_Columns);
279 m_TreeView.set_model(m_refTreeModel);
280 m_refTreeModel->signal_row_changed().connect(
281 sigc::mem_fun(*this, &MainWindow::instrument_name_changed)
282 );
283
284 // Add the TreeView's view columns:
285 m_TreeView.append_column_editable("Instrument", m_Columns.m_col_name);
286 m_TreeView.set_headers_visible(false);
287
288 // create samples treeview (including its data model)
289 m_refSamplesTreeModel = SamplesTreeStore::create(m_SamplesModel);
290 m_TreeViewSamples.set_model(m_refSamplesTreeModel);
291 // m_TreeViewSamples.set_reorderable();
292 m_TreeViewSamples.append_column_editable("Samples", m_SamplesModel.m_col_name);
293 m_TreeViewSamples.set_headers_visible(false);
294 m_TreeViewSamples.signal_button_press_event().connect_notify(
295 sigc::mem_fun(*this, &MainWindow::on_sample_treeview_button_release)
296 );
297 m_refSamplesTreeModel->signal_row_changed().connect(
298 sigc::mem_fun(*this, &MainWindow::sample_name_changed)
299 );
300
301 // establish drag&drop between samples tree view and dimension region 'Sample' text entry
302 std::list<Gtk::TargetEntry> drag_target_gig_sample;
303 drag_target_gig_sample.push_back( Gtk::TargetEntry("gig::Sample") );
304 m_TreeViewSamples.drag_source_set(drag_target_gig_sample);
305 m_TreeViewSamples.signal_drag_begin().connect(
306 sigc::mem_fun(*this, &MainWindow::on_sample_treeview_drag_begin)
307 );
308 m_TreeViewSamples.signal_drag_data_get().connect(
309 sigc::mem_fun(*this, &MainWindow::on_sample_treeview_drag_data_get)
310 );
311 dimreg_edit.wSample->drag_dest_set(drag_target_gig_sample);
312 dimreg_edit.wSample->signal_drag_data_received().connect(
313 sigc::mem_fun(*this, &MainWindow::on_sample_label_drop_drag_data_received)
314 );
315 dimreg_edit.signal_dimreg_changed().connect(
316 sigc::hide(sigc::mem_fun(*this, &MainWindow::file_changed)));
317 m_RegionChooser.signal_instrument_changed().connect(
318 sigc::mem_fun(*this, &MainWindow::file_changed));
319 m_DimRegionChooser.signal_region_changed().connect(
320 sigc::mem_fun(*this, &MainWindow::file_changed));
321 instrumentProps.signal_instrument_changed().connect(
322 sigc::mem_fun(*this, &MainWindow::file_changed));
323 propDialog.signal_info_changed().connect(
324 sigc::mem_fun(*this, &MainWindow::file_changed));
325
326 dimreg_edit.signal_dimreg_to_be_changed().connect(
327 dimreg_to_be_changed_signal.make_slot());
328 dimreg_edit.signal_dimreg_changed().connect(
329 dimreg_changed_signal.make_slot());
330 dimreg_edit.signal_sample_ref_changed().connect(
331 sample_ref_changed_signal.make_slot());
332
333 m_RegionChooser.signal_instrument_struct_to_be_changed().connect(
334 sigc::hide(
335 sigc::bind(
336 file_structure_to_be_changed_signal.make_slot(),
337 sigc::ref(this->file)
338 )
339 )
340 );
341 m_RegionChooser.signal_instrument_struct_changed().connect(
342 sigc::hide(
343 sigc::bind(
344 file_structure_changed_signal.make_slot(),
345 sigc::ref(this->file)
346 )
347 )
348 );
349 m_RegionChooser.signal_region_to_be_changed().connect(
350 region_to_be_changed_signal.make_slot());
351 m_RegionChooser.signal_region_changed_signal().connect(
352 region_changed_signal.make_slot());
353
354 dimreg_all_regions.signal_toggled().connect(
355 sigc::mem_fun(*this, &MainWindow::update_dimregs));
356 dimreg_all_dimregs.signal_toggled().connect(
357 sigc::mem_fun(*this, &MainWindow::dimreg_all_dimregs_toggled));
358 dimreg_stereo.signal_toggled().connect(
359 sigc::mem_fun(*this, &MainWindow::update_dimregs));
360
361 file = 0;
362 file_is_changed = false;
363 set_file_is_shared(false);
364
365 show_all_children();
366
367 // start with a new gig file by default
368 on_action_file_new();
369 }
370
371 MainWindow::~MainWindow()
372 {
373 }
374
375 bool MainWindow::on_delete_event(GdkEventAny* event)
376 {
377 return !file_is_shared && file_is_changed && !close_confirmation_dialog();
378 }
379
380 void MainWindow::on_action_quit()
381 {
382 if (!file_is_shared && file_is_changed && !close_confirmation_dialog()) return;
383 hide();
384 }
385
386 void MainWindow::region_changed()
387 {
388 m_DimRegionChooser.set_region(m_RegionChooser.get_region());
389 }
390
391 gig::Instrument* MainWindow::get_instrument()
392 {
393 gig::Instrument* instrument = 0;
394 Glib::RefPtr<Gtk::TreeSelection> tree_sel_ref = m_TreeView.get_selection();
395
396 Gtk::TreeModel::iterator it = tree_sel_ref->get_selected();
397 if (it) {
398 Gtk::TreeModel::Row row = *it;
399 instrument = row[m_Columns.m_col_instr];
400 }
401 return instrument;
402 }
403
404 void MainWindow::add_region_to_dimregs(gig::Region* region, bool stereo, bool all_dimregs)
405 {
406 if (all_dimregs) {
407 for (int i = 0 ; i < region->DimensionRegions ; i++) {
408 if (region->pDimensionRegions[i]) {
409 dimreg_edit.dimregs.insert(region->pDimensionRegions[i]);
410 }
411 }
412 } else {
413 m_DimRegionChooser.get_dimregions(region, stereo, dimreg_edit.dimregs);
414 }
415 }
416
417 void MainWindow::update_dimregs()
418 {
419 dimreg_edit.dimregs.clear();
420 bool all_regions = dimreg_all_regions.get_active();
421 bool stereo = dimreg_stereo.get_active();
422 bool all_dimregs = dimreg_all_dimregs.get_active();
423
424 if (all_regions) {
425 gig::Instrument* instrument = get_instrument();
426 if (instrument) {
427 for (gig::Region* region = instrument->GetFirstRegion() ;
428 region ;
429 region = instrument->GetNextRegion()) {
430 add_region_to_dimregs(region, stereo, all_dimregs);
431 }
432 }
433 } else {
434 gig::Region* region = m_RegionChooser.get_region();
435 if (region) {
436 add_region_to_dimregs(region, stereo, all_dimregs);
437 }
438 }
439 }
440
441 void MainWindow::dimreg_all_dimregs_toggled()
442 {
443 dimreg_stereo.set_sensitive(!dimreg_all_dimregs.get_active());
444 update_dimregs();
445 }
446
447 void MainWindow::dimreg_changed()
448 {
449 update_dimregs();
450 dimreg_edit.set_dim_region(m_DimRegionChooser.get_dimregion());
451 }
452
453 void MainWindow::on_sel_change()
454 {
455 m_RegionChooser.set_instrument(get_instrument());
456 }
457
458 void loader_progress_callback(gig::progress_t* progress)
459 {
460 Loader* loader = static_cast<Loader*>(progress->custom);
461 loader->progress_callback(progress->factor);
462 }
463
464 void Loader::progress_callback(float fraction)
465 {
466 {
467 Glib::Mutex::Lock lock(progressMutex);
468 progress = fraction;
469 }
470 progress_dispatcher();
471 }
472
473 void Loader::thread_function()
474 {
475 printf("thread_function self=%x\n", Glib::Thread::self());
476 printf("Start %s\n", filename);
477 RIFF::File* riff = new RIFF::File(filename);
478 gig = new gig::File(riff);
479 gig::progress_t progress;
480 progress.callback = loader_progress_callback;
481 progress.custom = this;
482
483 gig->GetInstrument(0, &progress);
484 printf("End\n");
485 finished_dispatcher();
486 }
487
488 Loader::Loader(const char* filename)
489 : thread(0), filename(filename)
490 {
491 }
492
493 void Loader::launch()
494 {
495 thread = Glib::Thread::create(sigc::mem_fun(*this, &Loader::thread_function), true);
496 printf("launch thread=%x\n", thread);
497 }
498
499 float Loader::get_progress()
500 {
501 float res;
502 {
503 Glib::Mutex::Lock lock(progressMutex);
504 res = progress;
505 }
506 return res;
507 }
508
509 Glib::Dispatcher& Loader::signal_progress()
510 {
511 return progress_dispatcher;
512 }
513
514 Glib::Dispatcher& Loader::signal_finished()
515 {
516 return finished_dispatcher;
517 }
518
519 LoadDialog::LoadDialog(const Glib::ustring& title, Gtk::Window& parent)
520 : Gtk::Dialog(title, parent, true)
521 {
522 get_vbox()->pack_start(progressBar);
523 show_all_children();
524 }
525
526 // Clear all GUI elements / controls. This method is typically called
527 // before a new .gig file is to be created or to be loaded.
528 void MainWindow::__clear() {
529 // remove all entries from "Instrument" menu
530 Gtk::MenuItem* instrument_menu =
531 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/MenuBar/MenuInstrument"));
532 instrument_menu->hide();
533 for (int i = 0; i < instrument_menu->get_submenu()->items().size(); i++) {
534 delete &instrument_menu->get_submenu()->items()[i];
535 }
536 instrument_menu->get_submenu()->items().clear();
537 // forget all samples that ought to be imported
538 m_SampleImportQueue.clear();
539 // clear the samples and instruments tree views
540 m_refTreeModel->clear();
541 m_refSamplesTreeModel->clear();
542 // free libgig's gig::File instance
543 if (file && !file_is_shared) delete file;
544 file = NULL;
545 set_file_is_shared(false);
546 }
547
548 void MainWindow::on_action_file_new()
549 {
550 if (!file_is_shared && file_is_changed && !close_confirmation_dialog()) return;
551
552 if (file_is_shared && !leaving_shared_mode_dialog()) return;
553
554 // clear all GUI elements
555 __clear();
556 // create a new .gig file (virtually yet)
557 gig::File* pFile = new gig::File;
558 // already add one new instrument by default
559 gig::Instrument* pInstrument = pFile->AddInstrument();
560 pInstrument->pInfo->Name = "Unnamed Instrument";
561 // update GUI with that new gig::File
562 load_gig(pFile, 0 /*no file name yet*/);
563 }
564
565 bool MainWindow::close_confirmation_dialog()
566 {
567 gchar* msg = g_strdup_printf(_("Save changes to \"%s\" before closing?"),
568 Glib::filename_display_basename(filename).c_str());
569 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
570 g_free(msg);
571 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 6) || GTKMM_MAJOR_VERSION > 2
572 dialog.set_secondary_text(_("If you close without saving, your changes will be lost."));
573 #endif
574 dialog.add_button(_("Close _Without Saving"), Gtk::RESPONSE_NO);
575 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
576 dialog.add_button(file_has_name ? Gtk::Stock::SAVE : Gtk::Stock::SAVE_AS, Gtk::RESPONSE_YES);
577 dialog.set_default_response(Gtk::RESPONSE_YES);
578 int response = dialog.run();
579 dialog.hide();
580 if (response == Gtk::RESPONSE_YES) return file_save();
581 return response != Gtk::RESPONSE_CANCEL;
582 }
583
584 bool MainWindow::leaving_shared_mode_dialog() {
585 Glib::ustring msg = _("Detach from sampler and proceed working stand-alone?");
586 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
587 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 6) || GTKMM_MAJOR_VERSION > 2
588 dialog.set_secondary_text(
589 _("If you proceed to work on another instrument file, it won't be "
590 "used by the sampler until you tell the sampler explicitly to "
591 "load it.")
592 );
593 #endif
594 dialog.add_button(_("_Yes, Detach"), Gtk::RESPONSE_YES);
595 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
596 dialog.set_default_response(Gtk::RESPONSE_CANCEL);
597 int response = dialog.run();
598 dialog.hide();
599 return response == Gtk::RESPONSE_YES;
600 }
601
602 void MainWindow::on_action_file_open()
603 {
604 if (!file_is_shared && file_is_changed && !close_confirmation_dialog()) return;
605
606 if (file_is_shared && !leaving_shared_mode_dialog()) return;
607
608 Gtk::FileChooserDialog dialog(*this, _("Open file"));
609 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
610 dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
611 dialog.set_default_response(Gtk::RESPONSE_OK);
612 Gtk::FileFilter filter;
613 filter.add_pattern("*.gig");
614 dialog.set_filter(filter);
615 if (current_dir != "") {
616 dialog.set_current_folder(current_dir);
617 }
618 if (dialog.run() == Gtk::RESPONSE_OK) {
619 std::string filename = dialog.get_filename();
620 printf("filename=%s\n", filename.c_str());
621 printf("on_action_file_open self=%x\n", Glib::Thread::self());
622 load_file(filename.c_str());
623 current_dir = Glib::path_get_dirname(filename);
624 }
625 }
626
627 void MainWindow::load_file(const char* name)
628 {
629 __clear();
630 load_dialog = new LoadDialog("Loading...", *this);
631 load_dialog->show_all();
632 loader = new Loader(strdup(name));
633 loader->signal_progress().connect(
634 sigc::mem_fun(*this, &MainWindow::on_loader_progress));
635 loader->signal_finished().connect(
636 sigc::mem_fun(*this, &MainWindow::on_loader_finished));
637 loader->launch();
638 }
639
640 void MainWindow::load_instrument(gig::Instrument* instr) {
641 if (!instr) {
642 Glib::ustring txt = "Provided instrument is NULL!\n";
643 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
644 msg.run();
645 Gtk::Main::quit();
646 }
647 // clear all GUI elements
648 __clear();
649 // load the instrument
650 gig::File* pFile = (gig::File*) instr->GetParent();
651 load_gig(pFile, 0 /*file name*/, true /*shared instrument*/);
652 //TODO: automatically select the given instrument
653 }
654
655 void MainWindow::on_loader_progress()
656 {
657 load_dialog->set_fraction(loader->get_progress());
658 }
659
660 void MainWindow::on_loader_finished()
661 {
662 printf("Loader finished!\n");
663 printf("on_loader_finished self=%x\n", Glib::Thread::self());
664 load_gig(loader->gig, loader->filename);
665 load_dialog->hide();
666 }
667
668 void MainWindow::on_action_file_save()
669 {
670 file_save();
671 }
672
673 bool MainWindow::check_if_savable()
674 {
675 if (!file) return false;
676
677 if (!file->GetFirstSample()) {
678 Gtk::MessageDialog(*this, _("The file could not be saved "
679 "because it contains no samples"),
680 false, Gtk::MESSAGE_ERROR).run();
681 return false;
682 }
683
684 for (gig::Instrument* instrument = file->GetFirstInstrument() ; instrument ;
685 instrument = file->GetNextInstrument()) {
686 if (!instrument->GetFirstRegion()) {
687 Gtk::MessageDialog(*this, _("The file could not be saved "
688 "because there are instruments "
689 "that have no regions"),
690 false, Gtk::MESSAGE_ERROR).run();
691 return false;
692 }
693 }
694 return true;
695 }
696
697 bool MainWindow::file_save()
698 {
699 if (!check_if_savable()) return false;
700 if (!file_is_shared && !file_has_name) return file_save_as();
701
702 std::cout << "Saving file\n" << std::flush;
703 file_structure_to_be_changed_signal.emit(this->file);
704 try {
705 file->Save();
706 if (file_is_changed) {
707 set_title(get_title().substr(1));
708 file_is_changed = false;
709 }
710 } catch (RIFF::Exception e) {
711 file_structure_changed_signal.emit(this->file);
712 Glib::ustring txt = _("Could not save file: ") + e.Message;
713 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
714 msg.run();
715 return false;
716 }
717 std::cout << "Saving file done\n" << std::flush;
718 __import_queued_samples();
719 file_structure_changed_signal.emit(this->file);
720 return true;
721 }
722
723 void MainWindow::on_action_file_save_as()
724 {
725 if (!check_if_savable()) return;
726 file_save_as();
727 }
728
729 bool MainWindow::file_save_as()
730 {
731 Gtk::FileChooserDialog dialog(*this, _("Save as"), Gtk::FILE_CHOOSER_ACTION_SAVE);
732 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
733 dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
734 dialog.set_default_response(Gtk::RESPONSE_OK);
735
736 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 8) || GTKMM_MAJOR_VERSION > 2
737 dialog.set_do_overwrite_confirmation();
738 // TODO: an overwrite dialog for gtkmm < 2.8
739 #endif
740 Gtk::FileFilter filter;
741 filter.add_pattern("*.gig");
742 dialog.set_filter(filter);
743
744 if (Glib::path_is_absolute(filename)) {
745 dialog.set_filename(filename);
746 } else if (current_dir != "") {
747 dialog.set_current_folder(current_dir);
748 }
749 dialog.set_current_name(Glib::filename_display_basename(filename));
750
751 if (dialog.run() == Gtk::RESPONSE_OK) {
752 file_structure_to_be_changed_signal.emit(this->file);
753 try {
754 std::string filename = dialog.get_filename();
755 if (!Glib::str_has_suffix(filename, ".gig")) {
756 filename += ".gig";
757 }
758 printf("filename=%s\n", filename.c_str());
759 file->Save(filename);
760 this->filename = filename;
761 current_dir = Glib::path_get_dirname(filename);
762 set_title(Glib::filename_display_basename(filename));
763 file_has_name = true;
764 file_is_changed = false;
765 } catch (RIFF::Exception e) {
766 file_structure_changed_signal.emit(this->file);
767 Glib::ustring txt = _("Could not save file: ") + e.Message;
768 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
769 msg.run();
770 return false;
771 }
772 __import_queued_samples();
773 file_structure_changed_signal.emit(this->file);
774 return true;
775 }
776 return false;
777 }
778
779 // actually write the sample(s)' data to the gig file
780 void MainWindow::__import_queued_samples() {
781 std::cout << "Starting sample import\n" << std::flush;
782 Glib::ustring error_files;
783 printf("Samples to import: %d\n", m_SampleImportQueue.size());
784 for (std::list<SampleImportItem>::iterator iter = m_SampleImportQueue.begin();
785 iter != m_SampleImportQueue.end(); ) {
786 printf("Importing sample %s\n",(*iter).sample_path.c_str());
787 SF_INFO info;
788 info.format = 0;
789 SNDFILE* hFile = sf_open((*iter).sample_path.c_str(), SFM_READ, &info);
790 try {
791 if (!hFile) throw std::string("could not open file");
792 // determine sample's bit depth
793 int bitdepth;
794 switch (info.format & 0xff) {
795 case SF_FORMAT_PCM_S8:
796 case SF_FORMAT_PCM_16:
797 case SF_FORMAT_PCM_U8:
798 bitdepth = 16;
799 break;
800 case SF_FORMAT_PCM_24:
801 case SF_FORMAT_PCM_32:
802 case SF_FORMAT_FLOAT:
803 case SF_FORMAT_DOUBLE:
804 bitdepth = 24;
805 break;
806 default:
807 sf_close(hFile); // close sound file
808 throw std::string("format not supported"); // unsupported subformat (yet?)
809 }
810
811 const int bufsize = 10000;
812 switch (bitdepth) {
813 case 16: {
814 short* buffer = new short[bufsize * info.channels];
815 sf_count_t cnt = info.frames;
816 while (cnt) {
817 // libsndfile does the conversion for us (if needed)
818 int n = sf_readf_short(hFile, buffer, bufsize);
819 // write from buffer directly (physically) into .gig file
820 iter->gig_sample->Write(buffer, n);
821 cnt -= n;
822 }
823 delete[] buffer;
824 break;
825 }
826 case 24: {
827 int* srcbuf = new int[bufsize * info.channels];
828 uint8_t* dstbuf = new uint8_t[bufsize * 3 * info.channels];
829 sf_count_t cnt = info.frames;
830 while (cnt) {
831 // libsndfile returns 32 bits, convert to 24
832 int n = sf_readf_int(hFile, srcbuf, bufsize);
833 int j = 0;
834 for (int i = 0 ; i < n * info.channels ; i++) {
835 dstbuf[j++] = srcbuf[i] >> 8;
836 dstbuf[j++] = srcbuf[i] >> 16;
837 dstbuf[j++] = srcbuf[i] >> 24;
838 }
839 // write from buffer directly (physically) into .gig file
840 iter->gig_sample->Write(dstbuf, n);
841 cnt -= n;
842 }
843 delete[] srcbuf;
844 delete[] dstbuf;
845 break;
846 }
847 }
848 // cleanup
849 sf_close(hFile);
850 // on success we remove the sample from the import queue,
851 // otherwise keep it, maybe it works the next time ?
852 std::list<SampleImportItem>::iterator cur = iter;
853 ++iter;
854 m_SampleImportQueue.erase(cur);
855 } catch (std::string what) {
856 // remember the files that made trouble (and their cause)
857 if (error_files.size()) error_files += "\n";
858 error_files += (*iter).sample_path += " (" + what + ")";
859 ++iter;
860 }
861 }
862 // show error message box when some sample(s) could not be imported
863 if (error_files.size()) {
864 Glib::ustring txt = _("Could not import the following sample(s):\n") + error_files;
865 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
866 msg.run();
867 }
868 }
869
870 void MainWindow::on_action_file_properties()
871 {
872 propDialog.show();
873 propDialog.deiconify();
874 }
875
876 void MainWindow::on_action_help_about()
877 {
878 #ifdef ABOUT_DIALOG
879 Gtk::AboutDialog dialog;
880 dialog.set_version(VERSION);
881 dialog.set_copyright("Copyright (C) 2006,2007 Andreas Persson");
882 dialog.set_comments(
883 "Released under the GNU General Public License.\n"
884 "\n"
885 "Please notice that this is still a very young instrument editor. "
886 "So better backup your Gigasampler files before editing them with "
887 "this application.\n"
888 "\n"
889 "Please report bugs to: http://bugs.linuxsampler.org"
890 );
891 dialog.set_website("http://www.linuxsampler.org");
892 dialog.set_website_label("http://www.linuxsampler.org");
893 dialog.run();
894 #endif
895 }
896
897 PropDialog::PropDialog()
898 : table(2,1),
899 eName("Name"),
900 eCreationDate("Creation date"),
901 eComments("Comments"),
902 eProduct("Product"),
903 eCopyright("Copyright"),
904 eArtists("Artists"),
905 eGenre("Genre"),
906 eKeywords("Keywords"),
907 eEngineer("Engineer"),
908 eTechnician("Technician"),
909 eSoftware("Software"),
910 eMedium("Medium"),
911 eSource("Source"),
912 eSourceForm("Source form"),
913 eCommissioned("Commissioned"),
914 eSubject("Subject"),
915 quitButton(Gtk::Stock::CLOSE),
916 update_model(0)
917 {
918 set_title("File Properties");
919 eName.set_width_chars(50);
920
921 connect(eName, &DLS::Info::Name);
922 connect(eCreationDate, &DLS::Info::CreationDate);
923 connect(eComments, &DLS::Info::Comments);
924 connect(eProduct, &DLS::Info::Product);
925 connect(eCopyright, &DLS::Info::Copyright);
926 connect(eArtists, &DLS::Info::Artists);
927 connect(eGenre, &DLS::Info::Genre);
928 connect(eKeywords, &DLS::Info::Keywords);
929 connect(eEngineer, &DLS::Info::Engineer);
930 connect(eTechnician, &DLS::Info::Technician);
931 connect(eSoftware, &DLS::Info::Software);
932 connect(eMedium, &DLS::Info::Medium);
933 connect(eSource, &DLS::Info::Source);
934 connect(eSourceForm, &DLS::Info::SourceForm);
935 connect(eCommissioned, &DLS::Info::Commissioned);
936 connect(eSubject, &DLS::Info::Subject);
937
938 table.add(eName);
939 table.add(eCreationDate);
940 table.add(eComments);
941 table.add(eProduct);
942 table.add(eCopyright);
943 table.add(eArtists);
944 table.add(eGenre);
945 table.add(eKeywords);
946 table.add(eEngineer);
947 table.add(eTechnician);
948 table.add(eSoftware);
949 table.add(eMedium);
950 table.add(eSource);
951 table.add(eSourceForm);
952 table.add(eCommissioned);
953 table.add(eSubject);
954
955 table.set_col_spacings(5);
956 add(vbox);
957 table.set_border_width(5);
958 vbox.add(table);
959 vbox.pack_start(buttonBox, Gtk::PACK_SHRINK);
960 buttonBox.set_layout(Gtk::BUTTONBOX_END);
961 buttonBox.set_border_width(5);
962 buttonBox.show();
963 buttonBox.pack_start(quitButton);
964 quitButton.set_flags(Gtk::CAN_DEFAULT);
965 quitButton.grab_focus();
966 quitButton.signal_clicked().connect(
967 sigc::mem_fun(*this, &PropDialog::hide));
968
969 quitButton.show();
970 vbox.show();
971 show_all_children();
972 }
973
974 void PropDialog::set_info(DLS::Info* info)
975 {
976 this->info = info;
977 update_model++;
978 eName.set_value(info->Name);
979 eCreationDate.set_value(info->CreationDate);
980 eComments.set_value(info->Comments);
981 eProduct.set_value(info->Product);
982 eCopyright.set_value(info->Copyright);
983 eArtists.set_value(info->Artists);
984 eGenre.set_value(info->Genre);
985 eKeywords.set_value(info->Keywords);
986 eEngineer.set_value(info->Engineer);
987 eTechnician.set_value(info->Technician);
988 eSoftware.set_value(info->Software);
989 eMedium.set_value(info->Medium);
990 eSource.set_value(info->Source);
991 eSourceForm.set_value(info->SourceForm);
992 eCommissioned.set_value(info->Commissioned);
993 eSubject.set_value(info->Subject);
994 update_model--;
995 }
996
997 sigc::signal<void>& PropDialog::signal_info_changed()
998 {
999 return info_changed;
1000 }
1001
1002 void InstrumentProps::set_IsDrum(bool value)
1003 {
1004 instrument->IsDrum = value;
1005 }
1006
1007 void InstrumentProps::set_MIDIBank(uint16_t value)
1008 {
1009 instrument->MIDIBank = value;
1010 }
1011
1012 void InstrumentProps::set_MIDIProgram(uint32_t value)
1013 {
1014 instrument->MIDIProgram = value;
1015 }
1016
1017 void InstrumentProps::set_DimensionKeyRange_low(uint8_t value)
1018 {
1019 instrument->DimensionKeyRange.low = value;
1020 if (value > instrument->DimensionKeyRange.high) {
1021 eDimensionKeyRangeHigh.set_value(value);
1022 }
1023 }
1024
1025 void InstrumentProps::set_DimensionKeyRange_high(uint8_t value)
1026 {
1027 instrument->DimensionKeyRange.high = value;
1028 if (value < instrument->DimensionKeyRange.low) {
1029 eDimensionKeyRangeLow.set_value(value);
1030 }
1031 }
1032
1033 InstrumentProps::InstrumentProps()
1034 : table(2,1),
1035 quitButton(Gtk::Stock::CLOSE),
1036 eName("Name"),
1037 eIsDrum("Is drum"),
1038 eMIDIBank("MIDI bank", 0, 16383),
1039 eMIDIProgram("MIDI program"),
1040 eAttenuation("Attenuation", 0, 96, 0, 1),
1041 eGainPlus6("Gain +6dB", eAttenuation, -6),
1042 eEffectSend("Effect send", 0, 65535),
1043 eFineTune("Fine tune", -8400, 8400),
1044 ePitchbendRange("Pitchbend range", 0, 12),
1045 ePianoReleaseMode("Piano release mode"),
1046 eDimensionKeyRangeLow("Dimension key range low"),
1047 eDimensionKeyRangeHigh("Dimension key range high"),
1048 update_model(0)
1049 {
1050 set_title("Instrument Properties");
1051
1052 connect(eIsDrum, &InstrumentProps::set_IsDrum);
1053 connect(eMIDIBank, &InstrumentProps::set_MIDIBank);
1054 connect(eMIDIProgram, &InstrumentProps::set_MIDIProgram);
1055 connect(eAttenuation, &gig::Instrument::Attenuation);
1056 connect(eGainPlus6, &gig::Instrument::Attenuation);
1057 connect(eEffectSend, &gig::Instrument::EffectSend);
1058 connect(eFineTune, &gig::Instrument::FineTune);
1059 connect(ePitchbendRange, &gig::Instrument::PitchbendRange);
1060 connect(ePianoReleaseMode, &gig::Instrument::PianoReleaseMode);
1061 connect(eDimensionKeyRangeLow,
1062 &InstrumentProps::set_DimensionKeyRange_low);
1063 connect(eDimensionKeyRangeHigh,
1064 &InstrumentProps::set_DimensionKeyRange_high);
1065
1066 table.set_col_spacings(5);
1067
1068 table.add(eName);
1069 table.add(eIsDrum);
1070 table.add(eMIDIBank);
1071 table.add(eMIDIProgram);
1072 table.add(eAttenuation);
1073 table.add(eGainPlus6);
1074 table.add(eEffectSend);
1075 table.add(eFineTune);
1076 table.add(ePitchbendRange);
1077 table.add(ePianoReleaseMode);
1078 table.add(eDimensionKeyRangeLow);
1079 table.add(eDimensionKeyRangeHigh);
1080
1081 add(vbox);
1082 table.set_border_width(5);
1083 vbox.pack_start(table);
1084 table.show();
1085 vbox.pack_start(buttonBox, Gtk::PACK_SHRINK);
1086 buttonBox.set_layout(Gtk::BUTTONBOX_END);
1087 buttonBox.set_border_width(5);
1088 buttonBox.show();
1089 buttonBox.pack_start(quitButton);
1090 quitButton.set_flags(Gtk::CAN_DEFAULT);
1091 quitButton.grab_focus();
1092
1093 quitButton.signal_clicked().connect(
1094 sigc::mem_fun(*this, &InstrumentProps::hide));
1095
1096 quitButton.show();
1097 vbox.show();
1098 show_all_children();
1099 }
1100
1101 void InstrumentProps::set_instrument(gig::Instrument* instrument)
1102 {
1103 this->instrument = instrument;
1104
1105 update_model++;
1106 eName.set_value(instrument->pInfo->Name);
1107 eIsDrum.set_value(instrument->IsDrum);
1108 eMIDIBank.set_value(instrument->MIDIBank);
1109 eMIDIProgram.set_value(instrument->MIDIProgram);
1110 eAttenuation.set_value(instrument->Attenuation);
1111 eGainPlus6.set_value(instrument->Attenuation);
1112 eEffectSend.set_value(instrument->EffectSend);
1113 eFineTune.set_value(instrument->FineTune);
1114 ePitchbendRange.set_value(instrument->PitchbendRange);
1115 ePianoReleaseMode.set_value(instrument->PianoReleaseMode);
1116 eDimensionKeyRangeLow.set_value(instrument->DimensionKeyRange.low);
1117 eDimensionKeyRangeHigh.set_value(instrument->DimensionKeyRange.high);
1118 update_model--;
1119 }
1120
1121 sigc::signal<void>& InstrumentProps::signal_instrument_changed()
1122 {
1123 return instrument_changed;
1124 }
1125
1126 void MainWindow::file_changed()
1127 {
1128 if (file && !file_is_changed) {
1129 set_title("*" + get_title());
1130 file_is_changed = true;
1131 }
1132 }
1133
1134 void MainWindow::load_gig(gig::File* gig, const char* filename, bool isSharedInstrument)
1135 {
1136 file = 0;
1137 set_file_is_shared(isSharedInstrument);
1138
1139 this->filename = filename ? filename : _("Unsaved Gig File");
1140 set_title(Glib::filename_display_basename(this->filename));
1141 file_has_name = filename;
1142 file_is_changed = false;
1143
1144 propDialog.set_info(gig->pInfo);
1145
1146 Gtk::MenuItem* instrument_menu =
1147 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/MenuBar/MenuInstrument"));
1148
1149 int instrument_index = 0;
1150 Gtk::RadioMenuItem::Group instrument_group;
1151 for (gig::Instrument* instrument = gig->GetFirstInstrument() ; instrument ;
1152 instrument = gig->GetNextInstrument()) {
1153 Gtk::TreeModel::iterator iter = m_refTreeModel->append();
1154 Gtk::TreeModel::Row row = *iter;
1155 row[m_Columns.m_col_name] = instrument->pInfo->Name.c_str();
1156 row[m_Columns.m_col_instr] = instrument;
1157 // create a menu item for this instrument
1158 Gtk::RadioMenuItem* item =
1159 new Gtk::RadioMenuItem(instrument_group, instrument->pInfo->Name.c_str());
1160 instrument_menu->get_submenu()->append(*item);
1161 item->signal_activate().connect(
1162 sigc::bind(
1163 sigc::mem_fun(*this, &MainWindow::on_instrument_selection_change),
1164 instrument_index
1165 )
1166 );
1167 instrument_index++;
1168 }
1169 instrument_menu->show();
1170 instrument_menu->get_submenu()->show_all_children();
1171
1172 for (gig::Group* group = gig->GetFirstGroup(); group; group = gig->GetNextGroup()) {
1173 if (group->Name != "") {
1174 Gtk::TreeModel::iterator iterGroup = m_refSamplesTreeModel->append();
1175 Gtk::TreeModel::Row rowGroup = *iterGroup;
1176 rowGroup[m_SamplesModel.m_col_name] = group->Name.c_str();
1177 rowGroup[m_SamplesModel.m_col_group] = group;
1178 rowGroup[m_SamplesModel.m_col_sample] = NULL;
1179 for (gig::Sample* sample = group->GetFirstSample();
1180 sample; sample = group->GetNextSample()) {
1181 Gtk::TreeModel::iterator iterSample =
1182 m_refSamplesTreeModel->append(rowGroup.children());
1183 Gtk::TreeModel::Row rowSample = *iterSample;
1184 rowSample[m_SamplesModel.m_col_name] = sample->pInfo->Name.c_str();
1185 rowSample[m_SamplesModel.m_col_sample] = sample;
1186 rowSample[m_SamplesModel.m_col_group] = NULL;
1187 }
1188 }
1189 }
1190
1191 file = gig;
1192
1193 // select the first instrument
1194 Glib::RefPtr<Gtk::TreeSelection> tree_sel_ref = m_TreeView.get_selection();
1195 tree_sel_ref->select(Gtk::TreePath("0"));
1196 }
1197
1198 void MainWindow::show_instr_props()
1199 {
1200 gig::Instrument* instrument = get_instrument();
1201 if (instrument)
1202 {
1203 instrumentProps.set_instrument(instrument);
1204 instrumentProps.show();
1205 instrumentProps.deiconify();
1206 }
1207 }
1208
1209 void MainWindow::on_action_view_status_bar() {
1210 Gtk::CheckMenuItem* item =
1211 dynamic_cast<Gtk::CheckMenuItem*>(uiManager->get_widget("/MenuBar/MenuView/Statusbar"));
1212 if (!item) {
1213 std::cerr << "/MenuBar/MenuView/Statusbar == NULL\n";
1214 return;
1215 }
1216 if (item->get_active()) m_StatusBar.show();
1217 else m_StatusBar.hide();
1218 }
1219
1220 void MainWindow::on_button_release(GdkEventButton* button)
1221 {
1222 if (button->type == GDK_2BUTTON_PRESS) {
1223 show_instr_props();
1224 } else if (button->type == GDK_BUTTON_PRESS && button->button == 3) {
1225 popup_menu->popup(button->button, button->time);
1226 }
1227 }
1228
1229 void MainWindow::on_instrument_selection_change(int index) {
1230 m_RegionChooser.set_instrument(file->GetInstrument(index));
1231 }
1232
1233 void MainWindow::on_sample_treeview_button_release(GdkEventButton* button) {
1234 if (button->type == GDK_BUTTON_PRESS && button->button == 3) {
1235 Gtk::Menu* sample_popup =
1236 dynamic_cast<Gtk::Menu*>(uiManager->get_widget("/SamplePopupMenu"));
1237 // update enabled/disabled state of sample popup items
1238 Glib::RefPtr<Gtk::TreeSelection> sel = m_TreeViewSamples.get_selection();
1239 Gtk::TreeModel::iterator it = sel->get_selected();
1240 bool group_selected = false;
1241 bool sample_selected = false;
1242 if (it) {
1243 Gtk::TreeModel::Row row = *it;
1244 group_selected = row[m_SamplesModel.m_col_group];
1245 sample_selected = row[m_SamplesModel.m_col_sample];
1246 }
1247 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/SamplePopupMenu/SampleProperties"))->
1248 set_sensitive(group_selected || sample_selected);
1249 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/SamplePopupMenu/AddSample"))->
1250 set_sensitive(group_selected || sample_selected);
1251 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/SamplePopupMenu/AddGroup"))->
1252 set_sensitive(file);
1253 dynamic_cast<Gtk::MenuItem*>(uiManager->get_widget("/SamplePopupMenu/RemoveSample"))->
1254 set_sensitive(group_selected || sample_selected);
1255 // show sample popup
1256 sample_popup->popup(button->button, button->time);
1257 }
1258 }
1259
1260 void MainWindow::on_action_add_instrument() {
1261 static int __instrument_indexer = 0;
1262 if (!file) return;
1263 gig::Instrument* instrument = file->AddInstrument();
1264 __instrument_indexer++;
1265 instrument->pInfo->Name =
1266 "Unnamed Instrument " + ToString(__instrument_indexer);
1267 // update instrument tree view
1268 Gtk::TreeModel::iterator iterInstr = m_refTreeModel->append();
1269 Gtk::TreeModel::Row rowInstr = *iterInstr;
1270 rowInstr[m_Columns.m_col_name] = instrument->pInfo->Name.c_str();
1271 rowInstr[m_Columns.m_col_instr] = instrument;
1272 file_changed();
1273 }
1274
1275 void MainWindow::on_action_remove_instrument() {
1276 if (!file) return;
1277 if (file_is_shared) {
1278 Gtk::MessageDialog msg(
1279 *this,
1280 _("You cannot delete an instrument from this file, since it's "
1281 "currently used by the sampler."),
1282 false, Gtk::MESSAGE_INFO
1283 );
1284 msg.run();
1285 return;
1286 }
1287
1288 Glib::RefPtr<Gtk::TreeSelection> sel = m_TreeView.get_selection();
1289 Gtk::TreeModel::iterator it = sel->get_selected();
1290 if (it) {
1291 Gtk::TreeModel::Row row = *it;
1292 gig::Instrument* instr = row[m_Columns.m_col_instr];
1293 try {
1294 // remove instrument from the gig file
1295 if (instr) file->DeleteInstrument(instr);
1296 // remove respective row from instruments tree view
1297 m_refTreeModel->erase(it);
1298 file_changed();
1299 } catch (RIFF::Exception e) {
1300 Gtk::MessageDialog msg(*this, e.Message.c_str(), false, Gtk::MESSAGE_ERROR);
1301 msg.run();
1302 }
1303 }
1304 }
1305
1306 void MainWindow::on_action_sample_properties() {
1307 //TODO: show a dialog where the selected sample's properties can be edited
1308 Gtk::MessageDialog msg(
1309 *this, "Sorry, yet to be implemented!", false, Gtk::MESSAGE_INFO
1310 );
1311 msg.run();
1312 }
1313
1314 void MainWindow::on_action_add_group() {
1315 static int __sample_indexer = 0;
1316 if (!file) return;
1317 gig::Group* group = file->AddGroup();
1318 group->Name = "Unnamed Group";
1319 if (__sample_indexer) group->Name += " " + ToString(__sample_indexer);
1320 __sample_indexer++;
1321 // update sample tree view
1322 Gtk::TreeModel::iterator iterGroup = m_refSamplesTreeModel->append();
1323 Gtk::TreeModel::Row rowGroup = *iterGroup;
1324 rowGroup[m_SamplesModel.m_col_name] = group->Name.c_str();
1325 rowGroup[m_SamplesModel.m_col_sample] = NULL;
1326 rowGroup[m_SamplesModel.m_col_group] = group;
1327 file_changed();
1328 }
1329
1330 void MainWindow::on_action_add_sample() {
1331 if (!file) return;
1332 // get selected group
1333 Glib::RefPtr<Gtk::TreeSelection> sel = m_TreeViewSamples.get_selection();
1334 Gtk::TreeModel::iterator it = sel->get_selected();
1335 if (!it) return;
1336 Gtk::TreeModel::Row row = *it;
1337 gig::Group* group = row[m_SamplesModel.m_col_group];
1338 if (!group) { // not a group, but a sample is selected (probably)
1339 gig::Sample* sample = row[m_SamplesModel.m_col_sample];
1340 if (!sample) return;
1341 it = row.parent(); // resolve parent (that is the sample's group)
1342 if (!it) return;
1343 row = *it;
1344 group = row[m_SamplesModel.m_col_group];
1345 if (!group) return;
1346 }
1347 // show 'browse for file' dialog
1348 Gtk::FileChooserDialog dialog(*this, _("Add Sample(s)"));
1349 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1350 dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
1351 dialog.set_select_multiple(true);
1352 Gtk::FileFilter soundfilter; // matches all file types supported by libsndfile
1353 const char* const supportedFileTypes[] = {
1354 "*.wav", "*.WAV", "*.aiff", "*.AIFF", "*.aifc", "*.AIFC", "*.snd",
1355 "*.SND", "*.au", "*.AU", "*.paf", "*.PAF", "*.iff", "*.IFF",
1356 "*.svx", "*.SVX", "*.sf", "*.SF", "*.voc", "*.VOC", "*.w64",
1357 "*.W64", "*.pvf", "*.PVF", "*.xi", "*.XI", "*.htk", "*.HTK",
1358 "*.caf", "*.CAF", NULL
1359 };
1360 for (int i = 0; supportedFileTypes[i]; i++)
1361 soundfilter.add_pattern(supportedFileTypes[i]);
1362 soundfilter.set_name("Sound Files");
1363 Gtk::FileFilter allpassfilter; // matches every file
1364 allpassfilter.add_pattern("*.*");
1365 allpassfilter.set_name("All Files");
1366 dialog.add_filter(soundfilter);
1367 dialog.add_filter(allpassfilter);
1368 if (dialog.run() == Gtk::RESPONSE_OK) {
1369 Glib::ustring error_files;
1370 Glib::SListHandle<Glib::ustring> filenames = dialog.get_filenames();
1371 for (Glib::SListHandle<Glib::ustring>::iterator iter = filenames.begin();
1372 iter != filenames.end(); ++iter) {
1373 printf("Adding sample %s\n",(*iter).c_str());
1374 // use libsndfile to retrieve file informations
1375 SF_INFO info;
1376 info.format = 0;
1377 SNDFILE* hFile = sf_open((*iter).c_str(), SFM_READ, &info);
1378 try {
1379 if (!hFile) throw std::string("could not open file");
1380 int bitdepth;
1381 switch (info.format & 0xff) {
1382 case SF_FORMAT_PCM_S8:
1383 case SF_FORMAT_PCM_16:
1384 case SF_FORMAT_PCM_U8:
1385 bitdepth = 16;
1386 break;
1387 case SF_FORMAT_PCM_24:
1388 case SF_FORMAT_PCM_32:
1389 case SF_FORMAT_FLOAT:
1390 case SF_FORMAT_DOUBLE:
1391 bitdepth = 24;
1392 break;
1393 default:
1394 sf_close(hFile); // close sound file
1395 throw std::string("format not supported"); // unsupported subformat (yet?)
1396 }
1397 // add a new sample to the .gig file
1398 gig::Sample* sample = file->AddSample();
1399 // file name without path
1400 Glib::ustring filename = Glib::filename_display_basename(*iter);
1401 // remove file extension if there is one
1402 for (int i = 0; supportedFileTypes[i]; i++) {
1403 if (Glib::str_has_suffix(filename, supportedFileTypes[i] + 1)) {
1404 filename.erase(filename.length() - strlen(supportedFileTypes[i] + 1));
1405 break;
1406 }
1407 }
1408 sample->pInfo->Name = filename;
1409 sample->Channels = info.channels;
1410 sample->BitDepth = bitdepth;
1411 sample->FrameSize = bitdepth / 8/*1 byte are 8 bits*/ * info.channels;
1412 sample->SamplesPerSecond = info.samplerate;
1413 sample->AverageBytesPerSecond = sample->FrameSize * sample->SamplesPerSecond;
1414 sample->BlockAlign = sample->FrameSize;
1415 sample->SamplesTotal = info.frames;
1416
1417 SF_INSTRUMENT instrument;
1418 if (sf_command(hFile, SFC_GET_INSTRUMENT,
1419 &instrument, sizeof(instrument)) != SF_FALSE)
1420 {
1421 sample->MIDIUnityNote = instrument.basenote;
1422
1423 #if HAVE_SF_INSTRUMENT_LOOPS
1424 if (instrument.loop_count && instrument.loops[0].mode != SF_LOOP_NONE) {
1425 sample->Loops = 1;
1426
1427 switch (instrument.loops[0].mode) {
1428 case SF_LOOP_FORWARD:
1429 sample->LoopType = gig::loop_type_normal;
1430 break;
1431 case SF_LOOP_BACKWARD:
1432 sample->LoopType = gig::loop_type_backward;
1433 break;
1434 case SF_LOOP_ALTERNATING:
1435 sample->LoopType = gig::loop_type_bidirectional;
1436 break;
1437 }
1438 sample->LoopStart = instrument.loops[0].start;
1439 sample->LoopEnd = instrument.loops[0].end;
1440 sample->LoopPlayCount = instrument.loops[0].count;
1441 sample->LoopSize = sample->LoopEnd - sample->LoopStart + 1;
1442 }
1443 #endif
1444 }
1445
1446 // schedule resizing the sample (which will be done
1447 // physically when File::Save() is called)
1448 sample->Resize(info.frames);
1449 // make sure sample is part of the selected group
1450 group->AddSample(sample);
1451 // schedule that physical resize and sample import
1452 // (data copying), performed when "Save" is requested
1453 SampleImportItem sched_item;
1454 sched_item.gig_sample = sample;
1455 sched_item.sample_path = *iter;
1456 m_SampleImportQueue.push_back(sched_item);
1457 // add sample to the tree view
1458 Gtk::TreeModel::iterator iterSample =
1459 m_refSamplesTreeModel->append(row.children());
1460 Gtk::TreeModel::Row rowSample = *iterSample;
1461 rowSample[m_SamplesModel.m_col_name] = filename;
1462 rowSample[m_SamplesModel.m_col_sample] = sample;
1463 rowSample[m_SamplesModel.m_col_group] = NULL;
1464 // close sound file
1465 sf_close(hFile);
1466 file_changed();
1467 } catch (std::string what) { // remember the files that made trouble (and their cause)
1468 if (error_files.size()) error_files += "\n";
1469 error_files += *iter += " (" + what + ")";
1470 }
1471 }
1472 // show error message box when some file(s) could not be opened / added
1473 if (error_files.size()) {
1474 Glib::ustring txt = _("Could not add the following sample(s):\n") + error_files;
1475 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
1476 msg.run();
1477 }
1478 }
1479 }
1480
1481 void MainWindow::on_action_remove_sample() {
1482 if (!file) return;
1483 Glib::RefPtr<Gtk::TreeSelection> sel = m_TreeViewSamples.get_selection();
1484 Gtk::TreeModel::iterator it = sel->get_selected();
1485 if (it) {
1486 Gtk::TreeModel::Row row = *it;
1487 gig::Group* group = row[m_SamplesModel.m_col_group];
1488 gig::Sample* sample = row[m_SamplesModel.m_col_sample];
1489 Glib::ustring name = row[m_SamplesModel.m_col_name];
1490 try {
1491 // remove group or sample from the gig file
1492 if (group) {
1493 // temporarily remember the samples that bolong to
1494 // that group (we need that to clean the queue)
1495 std::list<gig::Sample*> members;
1496 for (gig::Sample* pSample = group->GetFirstSample();
1497 pSample; pSample = group->GetNextSample()) {
1498 members.push_back(pSample);
1499 }
1500 // notify everybody that we're going to remove these samples
1501 samples_to_be_removed_signal.emit(members);
1502 // delete the group in the .gig file including the
1503 // samples that belong to the group
1504 file->DeleteGroup(group);
1505 // notify that we're done with removal
1506 samples_removed_signal.emit();
1507 // if sample(s) were just previously added, remove
1508 // them from the import queue
1509 for (std::list<gig::Sample*>::iterator member = members.begin();
1510 member != members.end(); ++member) {
1511 for (std::list<SampleImportItem>::iterator iter = m_SampleImportQueue.begin();
1512 iter != m_SampleImportQueue.end(); ++iter) {
1513 if ((*iter).gig_sample == *member) {
1514 printf("Removing previously added sample '%s' from group '%s'\n",
1515 (*iter).sample_path.c_str(), name.c_str());
1516 m_SampleImportQueue.erase(iter);
1517 break;
1518 }
1519 }
1520 }
1521 file_changed();
1522 } else if (sample) {
1523 // notify everybody that we're going to remove this sample
1524 std::list<gig::Sample*> lsamples;
1525 lsamples.push_back(sample);
1526 samples_to_be_removed_signal.emit(lsamples);
1527 // remove sample from the .gig file
1528 file->DeleteSample(sample);
1529 // notify that we're done with removal
1530 samples_removed_signal.emit();
1531 // if sample was just previously added, remove it from
1532 // the import queue
1533 for (std::list<SampleImportItem>::iterator iter = m_SampleImportQueue.begin();
1534 iter != m_SampleImportQueue.end(); ++iter) {
1535 if ((*iter).gig_sample == sample) {
1536 printf("Removing previously added sample '%s'\n",
1537 (*iter).sample_path.c_str());
1538 m_SampleImportQueue.erase(iter);
1539 break;
1540 }
1541 }
1542 dimreg_changed();
1543 file_changed();
1544 }
1545 // remove respective row(s) from samples tree view
1546 m_refSamplesTreeModel->erase(it);
1547 } catch (RIFF::Exception e) {
1548 // pretend we're done with removal (i.e. to avoid dead locks)
1549 samples_removed_signal.emit();
1550 // show error message
1551 Gtk::MessageDialog msg(*this, e.Message.c_str(), false, Gtk::MESSAGE_ERROR);
1552 msg.run();
1553 }
1554 }
1555 }
1556
1557 // For some reason drag_data_get gets called two times for each
1558 // drag'n'drop (at least when target is an Entry). This work-around
1559 // makes sure the code in drag_data_get and drop_drag_data_received is
1560 // only executed once, as drag_begin only gets called once.
1561 void MainWindow::on_sample_treeview_drag_begin(const Glib::RefPtr<Gdk::DragContext>& context)
1562 {
1563 first_call_to_drag_data_get = true;
1564 }
1565
1566 void MainWindow::on_sample_treeview_drag_data_get(const Glib::RefPtr<Gdk::DragContext>&,
1567 Gtk::SelectionData& selection_data, guint, guint)
1568 {
1569 if (!first_call_to_drag_data_get) return;
1570 first_call_to_drag_data_get = false;
1571
1572 // get selected sample
1573 gig::Sample* sample = NULL;
1574 Glib::RefPtr<Gtk::TreeSelection> sel = m_TreeViewSamples.get_selection();
1575 Gtk::TreeModel::iterator it = sel->get_selected();
1576 if (it) {
1577 Gtk::TreeModel::Row row = *it;
1578 sample = row[m_SamplesModel.m_col_sample];
1579 }
1580 // pass the gig::Sample as pointer
1581 selection_data.set(selection_data.get_target(), 0/*unused*/, (const guchar*)&sample,
1582 sizeof(sample)/*length of data in bytes*/);
1583 }
1584
1585 void MainWindow::on_sample_label_drop_drag_data_received(
1586 const Glib::RefPtr<Gdk::DragContext>& context, int, int,
1587 const Gtk::SelectionData& selection_data, guint, guint time)
1588 {
1589 gig::Sample* sample = *((gig::Sample**) selection_data.get_data());
1590
1591 if (sample && selection_data.get_length() == sizeof(gig::Sample*)) {
1592 std::cout << "Drop received sample \"" <<
1593 sample->pInfo->Name << "\"" << std::endl;
1594 // drop success
1595 context->drop_reply(true, time);
1596
1597 //TODO: we should better move most of the following code to DimRegionEdit::set_sample()
1598
1599 // notify everybody that we're going to alter the region
1600 gig::Region* region = m_RegionChooser.get_region();
1601 region_to_be_changed_signal.emit(region);
1602
1603 // find the samplechannel dimension
1604 gig::dimension_def_t* stereo_dimension = 0;
1605 for (int i = 0 ; i < region->Dimensions ; i++) {
1606 if (region->pDimensionDefinitions[i].dimension ==
1607 gig::dimension_samplechannel) {
1608 stereo_dimension = &region->pDimensionDefinitions[i];
1609 break;
1610 }
1611 }
1612 bool channels_changed = false;
1613 if (sample->Channels == 1 && stereo_dimension) {
1614 // remove the samplechannel dimension
1615 region->DeleteDimension(stereo_dimension);
1616 channels_changed = true;
1617 region_changed();
1618 }
1619 dimreg_edit.set_sample(sample);
1620
1621 if (sample->Channels == 2 && !stereo_dimension) {
1622 // add samplechannel dimension
1623 gig::dimension_def_t dim;
1624 dim.dimension = gig::dimension_samplechannel;
1625 dim.bits = 1;
1626 dim.zones = 2;
1627 region->AddDimension(&dim);
1628 channels_changed = true;
1629 region_changed();
1630 }
1631 if (channels_changed) {
1632 // unmap all samples with wrong number of channels
1633 // TODO: maybe there should be a warning dialog for this
1634 for (int i = 0 ; i < region->DimensionRegions ; i++) {
1635 gig::DimensionRegion* d = region->pDimensionRegions[i];
1636 if (d->pSample && d->pSample->Channels != sample->Channels) {
1637 gig::Sample* oldref = d->pSample;
1638 d->pSample = NULL;
1639 sample_ref_changed_signal.emit(oldref, NULL);
1640 }
1641 }
1642 }
1643
1644 // notify we're done with altering
1645 region_changed_signal.emit(region);
1646
1647 file_changed();
1648
1649 return;
1650 }
1651 // drop failed
1652 context->drop_reply(false, time);
1653 }
1654
1655 void MainWindow::sample_name_changed(const Gtk::TreeModel::Path& path,
1656 const Gtk::TreeModel::iterator& iter) {
1657 if (!iter) return;
1658 Gtk::TreeModel::Row row = *iter;
1659 Glib::ustring name = row[m_SamplesModel.m_col_name];
1660 gig::Group* group = row[m_SamplesModel.m_col_group];
1661 gig::Sample* sample = row[m_SamplesModel.m_col_sample];
1662 if (group) {
1663 if (group->Name != name) {
1664 group->Name = name;
1665 printf("group name changed\n");
1666 file_changed();
1667 }
1668 } else if (sample) {
1669 if (sample->pInfo->Name != name.raw()) {
1670 sample->pInfo->Name = name.raw();
1671 printf("sample name changed\n");
1672 file_changed();
1673 }
1674 }
1675 }
1676
1677 void MainWindow::instrument_name_changed(const Gtk::TreeModel::Path& path,
1678 const Gtk::TreeModel::iterator& iter) {
1679 if (!iter) return;
1680 Gtk::TreeModel::Row row = *iter;
1681 Glib::ustring name = row[m_Columns.m_col_name];
1682 gig::Instrument* instrument = row[m_Columns.m_col_instr];
1683 if (instrument && instrument->pInfo->Name != name.raw()) {
1684 instrument->pInfo->Name = name.raw();
1685 file_changed();
1686 }
1687 }
1688
1689 void MainWindow::set_file_is_shared(bool b) {
1690 this->file_is_shared = b;
1691
1692 if (file_is_shared) {
1693 m_AttachedStateLabel.set_label(_("live-mode"));
1694 m_AttachedStateImage.set(
1695 Gdk::Pixbuf::create_from_xpm_data(status_attached_xpm)
1696 );
1697 } else {
1698 m_AttachedStateLabel.set_label(_("stand-alone"));
1699 m_AttachedStateImage.set(
1700 Gdk::Pixbuf::create_from_xpm_data(status_detached_xpm)
1701 );
1702 }
1703 }
1704
1705 sigc::signal<void, gig::File*>& MainWindow::signal_file_structure_to_be_changed() {
1706 return file_structure_to_be_changed_signal;
1707 }
1708
1709 sigc::signal<void, gig::File*>& MainWindow::signal_file_structure_changed() {
1710 return file_structure_changed_signal;
1711 }
1712
1713 sigc::signal<void, std::list<gig::Sample*> >& MainWindow::signal_samples_to_be_removed() {
1714 return samples_to_be_removed_signal;
1715 }
1716
1717 sigc::signal<void>& MainWindow::signal_samples_removed() {
1718 return samples_removed_signal;
1719 }
1720
1721 sigc::signal<void, gig::Region*>& MainWindow::signal_region_to_be_changed() {
1722 return region_to_be_changed_signal;
1723 }
1724
1725 sigc::signal<void, gig::Region*>& MainWindow::signal_region_changed() {
1726 return region_changed_signal;
1727 }
1728
1729 sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& MainWindow::signal_sample_ref_changed() {
1730 return sample_ref_changed_signal;
1731 }
1732
1733 sigc::signal<void, gig::DimensionRegion*>& MainWindow::signal_dimreg_to_be_changed() {
1734 return dimreg_to_be_changed_signal;
1735 }
1736
1737 sigc::signal<void, gig::DimensionRegion*>& MainWindow::signal_dimreg_changed() {
1738 return dimreg_changed_signal;
1739 }

  ViewVC Help
Powered by ViewVC