/[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 1656 - (show annotations) (download)
Sat Feb 2 08:18:19 2008 UTC (16 years, 1 month ago) by schoenebeck
File size: 64493 byte(s)
* bugfix: key highlighting of active keys on the virtual keyboard is now
  working on multiple invocations from the sampler as well
* renamed misleading names regarding the gig format's "keyswitching"
  feature (the dimension is now displayed as "keyswitching" instead of
  "keyboard" in the dimregchooser widget and the two parameters for
  defining the actual keyswitching area on the keyboard in the instruments
  properties dialog are now called "Keyswitching range low/high" instead of
  "Dimension key range low/high")

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

  ViewVC Help
Powered by ViewVC