/[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 1661 - (show annotations) (download)
Sun Feb 3 14:10:47 2008 UTC (16 years, 1 month ago) by schoenebeck
File size: 64872 byte(s)
* implemented alternative behavior for the virtual MIDI keyboard
  (selectable by combobox below the keyboard)
* show absolute velocity value of note-on & note-off events below
  the virtual MIDI keyboard

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

  ViewVC Help
Powered by ViewVC