--- gigedit/trunk/src/gigedit/mainwindow.cpp 2014/12/29 16:30:21 2683 +++ gigedit/trunk/src/gigedit/mainwindow.cpp 2017/05/26 22:10:16 3225 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2014 Andreas Persson + * Copyright (C) 2006-2017 Andreas Persson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -20,6 +20,14 @@ #include #include +#include +// threads.h must be included first to be able to build with +// G_DISABLE_DEPRECATED +#if (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION == 31 && GLIBMM_MICRO_VERSION >= 2) || \ + (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION > 31) || GLIBMM_MAJOR_VERSION > 2 +#include +#endif + #include #include #include @@ -31,6 +39,7 @@ #include #include #include +#include #if GTKMM_MAJOR_VERSION < 3 #include "wrapLabel.hh" #endif @@ -50,18 +59,30 @@ #include "ReferencesView.h" #include "../../gfx/status_attached.xpm" #include "../../gfx/status_detached.xpm" - +#include "gfx/builtinpix.h" +#include "MacroEditor.h" +#include "MacrosSetup.h" MainWindow::MainWindow() : m_DimRegionChooser(*this), dimreg_label(_("Changes apply to:")), dimreg_all_regions(_("all regions")), dimreg_all_dimregs(_("all dimension splits")), - dimreg_stereo(_("both channels")) + dimreg_stereo(_("both channels")), + labelLegend(_("Legend:")), + labelNoSample(_(" No Sample")), + labelMissingSample(_(" Missing some Sample(s)")), + labelLooped(_(" Looped")), + labelSomeLoops(_(" Some Loop(s)")) { + loadBuiltInPix(); + // set_border_width(5); -// set_default_size(400, 200); + if (!Settings::singleton()->autoRestoreWindowDimension) { + set_default_size(800, 600); + set_position(Gtk::WIN_POS_CENTER); + } add(m_VBox); @@ -96,6 +117,36 @@ dimreg_hbox.add(dimreg_stereo); dimreg_vbox.add(dimreg_edit); dimreg_vbox.pack_start(dimreg_hbox, Gtk::PACK_SHRINK); + { + legend_hbox.add(labelLegend); + + imageNoSample.set(redDot); + imageNoSample.set_alignment(Gtk::ALIGN_END); + labelNoSample.set_alignment(Gtk::ALIGN_START); + legend_hbox.add(imageNoSample); + legend_hbox.add(labelNoSample); + + imageMissingSample.set(yellowDot); + imageMissingSample.set_alignment(Gtk::ALIGN_END); + labelMissingSample.set_alignment(Gtk::ALIGN_START); + legend_hbox.add(imageMissingSample); + legend_hbox.add(labelMissingSample); + + imageLooped.set(blackLoop); + imageLooped.set_alignment(Gtk::ALIGN_END); + labelLooped.set_alignment(Gtk::ALIGN_START); + legend_hbox.add(imageLooped); + legend_hbox.add(labelLooped); + + imageSomeLoops.set(grayLoop); + imageSomeLoops.set_alignment(Gtk::ALIGN_END); + labelSomeLoops.set_alignment(Gtk::ALIGN_START); + legend_hbox.add(imageSomeLoops); + legend_hbox.add(labelSomeLoops); + + legend_hbox.show_all_children(); + } + dimreg_vbox.pack_start(legend_hbox, Gtk::PACK_SHRINK); m_HPaned.add2(dimreg_vbox); dimreg_label.set_tooltip_text(_("To automatically apply your changes above globally to the entire instrument, check all 3 check boxes on the right.")); @@ -156,13 +207,75 @@ sigc::mem_fun(*this, &MainWindow::show_intruments_tab) ); actionGroup->add( - Gtk::Action::create("MenuScript", _("S_cript")), + Gtk::Action::create("MenuScript", _("Scr_ipt")), sigc::mem_fun(*this, &MainWindow::show_scripts_tab) ); actionGroup->add(Gtk::Action::create("AllInstruments", _("_Select"))); actionGroup->add(Gtk::Action::create("MenuEdit", _("_Edit"))); + const Gdk::ModifierType primaryModifierKey = +#if defined(__APPLE__) + Gdk::META_MASK; // Cmd key on Mac +#else + Gdk::CONTROL_MASK; // Ctrl key on all other OSs +#endif + + actionGroup->add(Gtk::Action::create("CopyDimRgn", + _("Copy selected dimension region")), + Gtk::AccelKey(GDK_KEY_c, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::copy_selected_dimrgn)); + + actionGroup->add(Gtk::Action::create("PasteDimRgn", + _("Paste dimension region")), + Gtk::AccelKey(GDK_KEY_v, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::paste_copied_dimrgn)); + + actionGroup->add(Gtk::Action::create("AdjustClipboard", + _("Adjust Clipboard Content")), + Gtk::AccelKey(GDK_KEY_x, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::adjust_clipboard_content)); + + actionGroup->add(Gtk::Action::create("SelectPrevRegion", + _("Select Previous Region")), + Gtk::AccelKey(GDK_KEY_Left, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::select_prev_region)); + + actionGroup->add(Gtk::Action::create("SelectNextRegion", + _("Select Next Region")), + Gtk::AccelKey(GDK_KEY_Right, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::select_next_region)); + + actionGroup->add(Gtk::Action::create("SelectPrevDimRgnZone", + _("Select Previous Dimension Region Zone")), + Gtk::AccelKey(GDK_KEY_Left, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::select_prev_dim_rgn_zone)); + + actionGroup->add(Gtk::Action::create("SelectNextDimRgnZone", + _("Select Next Dimension Region Zone")), + Gtk::AccelKey(GDK_KEY_Right, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::select_next_dim_rgn_zone)); + + actionGroup->add(Gtk::Action::create("SelectPrevDimension", + _("Select Previous Dimension")), + Gtk::AccelKey(GDK_KEY_Up, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::select_prev_dimension)); + + actionGroup->add(Gtk::Action::create("SelectNextDimension", + _("Select Next Dimension")), + Gtk::AccelKey(GDK_KEY_Down, Gdk::MOD1_MASK), + sigc::mem_fun(*this, &MainWindow::select_next_dimension)); + + actionGroup->add(Gtk::Action::create("SelectAddPrevDimRgnZone", + _("Add Previous Dimension Region Zone to Selection")), + Gtk::AccelKey(GDK_KEY_Left, Gdk::MOD1_MASK | Gdk::SHIFT_MASK), + sigc::mem_fun(*this, &MainWindow::select_add_prev_dim_rgn_zone)); + + actionGroup->add(Gtk::Action::create("SelectAddNextDimRgnZone", + _("Add Next Dimension Region Zone to Selection")), + Gtk::AccelKey(GDK_KEY_Right, Gdk::MOD1_MASK | Gdk::SHIFT_MASK), + sigc::mem_fun(*this, &MainWindow::select_add_next_dim_rgn_zone)); + Glib::RefPtr toggle_action = Gtk::ToggleAction::create("CopySampleUnity", _("Copy Sample's _Unity Note")); toggle_action->set_active(true); @@ -179,7 +292,10 @@ actionGroup->add(toggle_action); - actionGroup->add(Gtk::Action::create("MenuView", _("_View"))); + actionGroup->add(Gtk::Action::create("MenuMacro", _("_Macro"))); + + + actionGroup->add(Gtk::Action::create("MenuView", _("Vie_w"))); toggle_action = Gtk::ToggleAction::create("Statusbar", _("_Statusbar")); toggle_action->set_active(true); @@ -187,6 +303,25 @@ sigc::mem_fun( *this, &MainWindow::on_action_view_status_bar)); + toggle_action = + Gtk::ToggleAction::create("AutoRestoreWinDim", _("_Auto Restore Window Dimension")); + toggle_action->set_active(Settings::singleton()->autoRestoreWindowDimension); + actionGroup->add(toggle_action, + sigc::mem_fun( + *this, &MainWindow::on_auto_restore_win_dim)); + + toggle_action = + Gtk::ToggleAction::create("SaveWithTemporaryFile", _("Save with _temporary file")); + toggle_action->set_active(Settings::singleton()->saveWithTemporaryFile); + actionGroup->add(toggle_action, + sigc::mem_fun( + *this, &MainWindow::on_save_with_temporary_file)); + + actionGroup->add( + Gtk::Action::create("RefreshAll", _("_Refresh All")), + sigc::mem_fun(*this, &MainWindow::on_action_refresh_all) + ); + action = Gtk::Action::create("MenuHelp", Gtk::Stock::HELP); actionGroup->add(Gtk::Action::create("MenuHelp", action->property_label())); @@ -217,6 +352,22 @@ sigc::mem_fun(*this, &MainWindow::on_action_warn_user_on_extensions) ); + toggle_action = + Gtk::ToggleAction::create("SyncSamplerInstrumentSelection", _("Synchronize sampler's instrument selection")); + toggle_action->set_active(Settings::singleton()->syncSamplerInstrumentSelection); + actionGroup->add( + toggle_action, + sigc::mem_fun(*this, &MainWindow::on_action_sync_sampler_instrument_selection) + ); + + toggle_action = + Gtk::ToggleAction::create("MoveRootNoteWithRegionMoved", _("Move root note with region moved")); + toggle_action->set_active(Settings::singleton()->moveRootNoteWithRegionMoved); + actionGroup->add( + toggle_action, + sigc::mem_fun(*this, &MainWindow::on_action_move_root_note_with_region_moved) + ); + actionGroup->add(Gtk::Action::create("MenuTools", _("_Tools"))); @@ -249,10 +400,19 @@ sigc::mem_fun(*this, &MainWindow::on_action_remove_sample) ); actionGroup->add( + Gtk::Action::create("RemoveUnusedSamples", _("Remove _Unused Samples")), + sigc::mem_fun(*this, &MainWindow::on_action_remove_unused_samples) + ); + actionGroup->add( Gtk::Action::create("ShowSampleRefs", _("Show References...")), sigc::mem_fun(*this, &MainWindow::on_action_view_references) ); actionGroup->add( + Gtk::Action::create("ReplaceSample", + _("Replace Sample...")), + sigc::mem_fun(*this, &MainWindow::on_action_replace_sample) + ); + actionGroup->add( Gtk::Action::create("ReplaceAllSamplesInAllGroups", _("Replace All Samples in All Groups...")), sigc::mem_fun(*this, &MainWindow::on_action_replace_all_samples_in_all_groups) @@ -295,18 +455,36 @@ " " " " " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " " " " " " " + " " + " " " " " " " " " " " " + " " " " " " " " + " " " " " " " " @@ -329,6 +507,9 @@ " " " " " " + " " + " " + " " " " " " " " @@ -336,6 +517,9 @@ " " " " " " + " " + " " + " " " " " " " " @@ -355,9 +539,11 @@ " " " " " " + " " " " " " " " + " " " " " " " " @@ -397,6 +583,35 @@ } { Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuSettings/SyncSamplerInstrumentSelection")); + item->set_tooltip_text(_("If checked, the sampler's current instrument will automatically be switched whenever another instrument was selected in gigedit (only available in live-mode).")); + } + { + Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuSettings/MoveRootNoteWithRegionMoved")); + item->set_tooltip_text(_("If checked, and when a region is moved by dragging it around on the virtual keyboard, the keybord position dependent pitch will move exactly with the amount of semi tones the region was moved around.")); + } + { + Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuSample/RemoveUnusedSamples")); + item->set_tooltip_text(_("Removes all samples that are not referenced by any instrument (i.e. red ones).")); + // copy tooltip to popup menu + Gtk::MenuItem* item2 = dynamic_cast( + uiManager->get_widget("/SamplePopupMenu/RemoveUnusedSamples")); + item2->set_tooltip_text(item->get_tooltip_text()); + } + { + Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuView/RefreshAll")); + item->set_tooltip_text(_("Reloads the currently open gig file and updates the entire graphical user interface.")); + } + { + Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuView/AutoRestoreWinDim")); + item->set_tooltip_text(_("If checked, size and position of all windows will be saved and automatically restored next time.")); + } + { + Gtk::MenuItem* item = dynamic_cast( uiManager->get_widget("/MenuBar/MenuTools/CombineInstruments")); item->set_tooltip_text(_("Create combi sounds out of individual sounds of this .gig file.")); } @@ -434,18 +649,40 @@ // Create the Tree model: m_refTreeModel = Gtk::ListStore::create(m_Columns); m_TreeView.set_model(m_refTreeModel); - m_TreeView.set_tooltip_text(_("Right click here for actions on instruments & MIDI Rules.")); + m_TreeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); + m_TreeView.set_tooltip_text(_("Right click here for actions on instruments & MIDI Rules. Drag & drop to change the order of instruments.")); instrument_name_connection = m_refTreeModel->signal_row_changed().connect( sigc::mem_fun(*this, &MainWindow::instrument_name_changed) ); // Add the TreeView's view columns: - m_TreeView.append_column_editable("Instrument", m_Columns.m_col_name); - m_TreeView.set_headers_visible(false); + m_TreeView.append_column(_("Nr"), m_Columns.m_col_nr); + m_TreeView.append_column_editable(_("Instrument"), m_Columns.m_col_name); + m_TreeView.append_column(_("Scripts"), m_Columns.m_col_scripts); + m_TreeView.set_headers_visible(true); + + // establish drag&drop within the instrument tree view, allowing to reorder + // the sequence of instruments within the gig file + { + std::vector drag_target_instrument; + drag_target_instrument.push_back(Gtk::TargetEntry("gig::Instrument")); + m_TreeView.drag_source_set(drag_target_instrument); + m_TreeView.drag_dest_set(drag_target_instrument); + m_TreeView.signal_drag_begin().connect( + sigc::mem_fun(*this, &MainWindow::on_instruments_treeview_drag_begin) + ); + m_TreeView.signal_drag_data_get().connect( + sigc::mem_fun(*this, &MainWindow::on_instruments_treeview_drag_data_get) + ); + m_TreeView.signal_drag_data_received().connect( + sigc::mem_fun(*this, &MainWindow::on_instruments_treeview_drop_drag_data_received) + ); + } // create samples treeview (including its data model) m_refSamplesTreeModel = SamplesTreeStore::create(m_SamplesModel); m_TreeViewSamples.set_model(m_refSamplesTreeModel); + m_TreeViewSamples.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); m_TreeViewSamples.set_tooltip_text(_("To actually use a sample, drag it from this list view to \"Sample\" -> \"Sample:\" on the region's settings pane on the right.\n\nRight click here for more actions on samples.")); // m_TreeViewSamples.set_reorderable(); m_TreeViewSamples.append_column_editable(_("Name"), m_SamplesModel.m_col_name); @@ -489,7 +726,7 @@ m_TreeViewScripts.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &MainWindow::on_script_treeview_button_release) ); - //FIXME: why the heck does this double click signal_row_activated() only fired while CTRL key is pressed ? + //FIXME: why the heck does this double click signal_row_activated() only fire while CTRL key is pressed ? m_TreeViewScripts.signal_row_activated().connect( sigc::mem_fun(*this, &MainWindow::script_double_clicked) ); @@ -526,6 +763,8 @@ sigc::hide(sigc::mem_fun(*this, &MainWindow::file_changed))); m_RegionChooser.signal_instrument_changed().connect( sigc::mem_fun(*this, &MainWindow::file_changed)); + m_RegionChooser.signal_instrument_changed().connect( + sigc::mem_fun(*this, &MainWindow::region_changed)); m_DimRegionChooser.signal_region_changed().connect( sigc::mem_fun(*this, &MainWindow::file_changed)); instrumentProps.signal_changed().connect( @@ -548,6 +787,10 @@ sigc::mem_fun(*this, &MainWindow::on_samples_to_be_removed) ); + dimreg_edit.signal_select_sample().connect( + sigc::mem_fun(*this, &MainWindow::select_sample) + ); + m_RegionChooser.signal_instrument_struct_to_be_changed().connect( sigc::hide( sigc::bind( @@ -591,13 +834,151 @@ // select 'Instruments' tab by default // (gtk allows this only if the tab childs are visible, thats why it's here) - m_TreeViewNotebook.set_current_page(1); + m_TreeViewNotebook.set_current_page(1); + + Gtk::Clipboard::get()->signal_owner_change().connect( + sigc::mem_fun(*this, &MainWindow::on_clipboard_owner_change) + ); + updateClipboardPasteAvailable(); + updateClipboardCopyAvailable(); + + // setup macros and their keyboard accelerators + { + Gtk::Menu* menuMacro = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuMacro") + )->get_submenu(); + + const Gdk::ModifierType primaryModifierKey = +#if defined(__APPLE__) + Gdk::META_MASK; // Cmd key on Mac +#else + Gdk::CONTROL_MASK; // Ctrl key on all other OSs +#endif + + const Gdk::ModifierType noModifier = (Gdk::ModifierType)0; + Gtk::AccelMap::add_entry("/macro_0", GDK_KEY_F1, noModifier); + Gtk::AccelMap::add_entry("/macro_1", GDK_KEY_F2, noModifier); + Gtk::AccelMap::add_entry("/macro_2", GDK_KEY_F3, noModifier); + Gtk::AccelMap::add_entry("/macro_3", GDK_KEY_F4, noModifier); + Gtk::AccelMap::add_entry("/macro_4", GDK_KEY_F5, noModifier); + Gtk::AccelMap::add_entry("/macro_5", GDK_KEY_F6, noModifier); + Gtk::AccelMap::add_entry("/macro_6", GDK_KEY_F7, noModifier); + Gtk::AccelMap::add_entry("/macro_7", GDK_KEY_F8, noModifier); + Gtk::AccelMap::add_entry("/macro_8", GDK_KEY_F9, noModifier); + Gtk::AccelMap::add_entry("/macro_9", GDK_KEY_F10, noModifier); + Gtk::AccelMap::add_entry("/macro_10", GDK_KEY_F11, noModifier); + Gtk::AccelMap::add_entry("/macro_11", GDK_KEY_F12, noModifier); + Gtk::AccelMap::add_entry("/SetupMacros", 'm', primaryModifierKey); + + Glib::RefPtr accelGroup = this->get_accel_group(); + menuMacro->set_accel_group(accelGroup); + + updateMacroMenu(); + } } MainWindow::~MainWindow() { } +void MainWindow::updateMacroMenu() { + Gtk::Menu* menuMacro = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuMacro") + )->get_submenu(); + + // remove all entries from "Macro" menu + { + const std::vector children = menuMacro->get_children(); + for (int i = 0; i < children.size(); ++i) { + Gtk::Widget* child = children[i]; + menuMacro->remove(*child); + delete child; + } + } + + // (re)load all macros from config file + try { + Settings::singleton()->loadMacros(m_macros); + } catch (Serialization::Exception e) { + std::cerr << "Exception while loading macros: " << e.Message << std::endl; + } catch (...) { + std::cerr << "Unknown exception while loading macros!" << std::endl; + } + + // add all configured macros as menu items to the "Macro" menu + for (int iMacro = 0; iMacro < m_macros.size(); ++iMacro) { + const Serialization::Archive& macro = m_macros[iMacro]; + std::string name = + macro.name().empty() ? + (std::string(_("Unnamed Macro")) + " " + ToString(iMacro+1)) : macro.name(); + Gtk::MenuItem* item = new Gtk::MenuItem(name); + item->signal_activate().connect( + sigc::bind( + sigc::mem_fun(*this, &MainWindow::onMacroSelected), iMacro + ) + ); + menuMacro->append(*item); + item->set_accel_path("/macro_" + ToString(iMacro)); + Glib::ustring comment = macro.comment(); + if (!comment.empty()) + item->set_tooltip_text(comment); + } + // if there are no macros configured at all, then show a dummy entry instead + if (m_macros.empty()) { + Gtk::MenuItem* item = new Gtk::MenuItem(_("No Macros")); + item->set_sensitive(false); + menuMacro->append(*item); + } + + // add separator line to menu + menuMacro->append(*new Gtk::SeparatorMenuItem); + + { + Gtk::MenuItem* item = new Gtk::MenuItem(_("Setup Macros ...")); + item->signal_activate().connect( + sigc::mem_fun(*this, &MainWindow::setupMacros) + ); + menuMacro->append(*item); + item->set_accel_path("/SetupMacros"); + } + + menuMacro->show_all_children(); +} + +void MainWindow::onMacroSelected(int iMacro) { + printf("onMacroSelected(%d)\n", iMacro); + if (iMacro < 0 || iMacro >= m_macros.size()) return; + Glib::ustring errorText; + try { + applyMacro(m_macros[iMacro]); + } catch (Serialization::Exception e) { + errorText = e.Message; + } catch (...) { + errorText = _("Unknown exception while applying macro"); + } + if (!errorText.empty()) { + Glib::ustring txt = _("Applying macro failed:\n") + errorText; + Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); + msg.run(); + } +} + +void MainWindow::setupMacros() { + MacrosSetup* setup = new MacrosSetup(); + gig::DimensionRegion* pDimRgn = m_DimRegionChooser.get_main_dimregion(); + setup->setMacros(m_macros, &m_serializationArchive, pDimRgn); + setup->signal_macros_changed().connect( + sigc::mem_fun(*this, &MainWindow::onMacrosSetupChanged) + ); + setup->show(); +} + +void MainWindow::onMacrosSetupChanged(const std::vector& macros) { + m_macros = macros; + Settings::singleton()->saveMacros(m_macros); + updateMacroMenu(); +} + bool MainWindow::on_delete_event(GdkEventAny* event) { return !file_is_shared && file_is_changed && !close_confirmation_dialog(); @@ -617,8 +998,9 @@ gig::Instrument* MainWindow::get_instrument() { gig::Instrument* instrument = 0; - Gtk::TreeModel::const_iterator it = - m_TreeView.get_selection()->get_selected(); + std::vector rows = m_TreeView.get_selection()->get_selected_rows(); + if (rows.empty()) return NULL; + Gtk::TreeModel::const_iterator it = m_refTreeModel->get_iter(rows[0]); if (it) { Gtk::TreeModel::Row row = *it; instrument = row[m_Columns.m_col_instr]; @@ -661,6 +1043,13 @@ add_region_to_dimregs(region, stereo, all_dimregs); } } + + m_RegionChooser.setModifyAllRegions(all_regions); + m_DimRegionChooser.setModifyAllRegions(all_regions); + m_DimRegionChooser.setModifyAllDimensionRegions(all_dimregs); + m_DimRegionChooser.setModifyBothChannels(stereo); + + updateClipboardCopyAvailable(); } void MainWindow::dimreg_all_dimregs_toggled() @@ -678,16 +1067,23 @@ void MainWindow::on_sel_change() { // select item in instrument menu - Gtk::TreeModel::iterator it = m_TreeView.get_selection()->get_selected(); - if (it) { - Gtk::TreePath path(it); - int index = path[0]; - const std::vector children = - instrument_menu->get_children(); - static_cast(children[index])->set_active(); + std::vector rows = m_TreeView.get_selection()->get_selected_rows(); + if (!rows.empty()) { + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[0]); + if (it) { + Gtk::TreePath path(it); + int index = path[0]; + const std::vector children = + instrument_menu->get_children(); + static_cast(children[index])->set_active(); + } } m_RegionChooser.set_instrument(get_instrument()); + + if (Settings::singleton()->syncSamplerInstrumentSelection) { + switch_sampler_instrument_signal.emit(get_instrument()); + } } void loader_progress_callback(gig::progress_t* progress) @@ -705,9 +1101,14 @@ progress_dispatcher(); } +#if defined(WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +// make sure stack is 16-byte aligned for SSE instructions +__attribute__((force_align_arg_pointer)) +#endif void Loader::thread_function() { - printf("thread_function self=%x\n", Glib::Threads::Thread::self()); + printf("thread_function self=%p\n", + static_cast(Glib::Threads::Thread::self())); printf("Start %s\n", filename.c_str()); try { RIFF::File* riff = new RIFF::File(filename); @@ -729,7 +1130,7 @@ } Loader::Loader(const char* filename) - : filename(filename), thread(0), progress(0.f) + : filename(filename), gig(0), thread(0), progress(0.f) { } @@ -740,7 +1141,7 @@ #else thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &Loader::thread_function)); #endif - printf("launch thread=%x\n", thread); + printf("launch thread=%p\n", static_cast(thread)); } float Loader::get_progress() @@ -783,9 +1184,14 @@ progress_dispatcher.emit(); } +#if defined(WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +// make sure stack is 16-byte aligned for SSE instructions +__attribute__((force_align_arg_pointer)) +#endif void Saver::thread_function() { - printf("thread_function self=%x\n", Glib::Threads::Thread::self()); + printf("thread_function self=%p\n", + static_cast(Glib::Threads::Thread::self())); printf("Start %s\n", filename.c_str()); try { gig::progress_t progress; @@ -794,7 +1200,34 @@ // if no filename was provided, that means "save", if filename was provided means "save as" if (filename.empty()) { - gig->Save(&progress); + if (!Settings::singleton()->saveWithTemporaryFile) { + // save directly over the existing .gig file + // (requires less disk space than solution below + // but may be slower) + gig->Save(&progress); + } else { + // save the file as separate temporary file first, + // then move the saved file over the old file + // (may result in performance speedup during save) + gig::String tmpname = filename + ".TMP"; + gig->Save(tmpname, &progress); + #if defined(WIN32) + if (!DeleteFile(filename.c_str())) { + throw RIFF::Exception("Could not replace original file with temporary file (unable to remove original file)."); + } + #else // POSIX ... + if (unlink(filename.c_str())) { + throw RIFF::Exception("Could not replace original file with temporary file (unable to remove original file): " + gig::String(strerror(errno))); + } + #endif + if (rename(tmpname.c_str(), filename.c_str())) { + #if defined(WIN32) + throw RIFF::Exception("Could not replace original file with temporary file (unable to rename temp file)."); + #else + throw RIFF::Exception("Could not replace original file with temporary file (unable to rename temp file): " + gig::String(strerror(errno))); + #endif + } + } } else { gig->Save(filename, &progress); } @@ -822,7 +1255,7 @@ #else thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &Saver::thread_function)); #endif - printf("launch thread=%x\n", thread); + printf("launch thread=%p\n", static_cast(thread)); } float Saver::get_progress() @@ -925,6 +1358,12 @@ int response = dialog.run(); dialog.hide(); + // user decided to exit app without saving + if (response == Gtk::RESPONSE_NO) return true; + + // user cancelled dialog, thus don't close app + if (response == Gtk::RESPONSE_CANCEL) return false; + // TODO: the following return valid is disabled and hard coded instead for // now, due to the fact that saving with progress bar is now implemented // asynchronously, as a result the app does not close automatically anymore @@ -976,7 +1415,8 @@ if (dialog.run() == Gtk::RESPONSE_OK) { std::string filename = dialog.get_filename(); printf("filename=%s\n", filename.c_str()); - printf("on_action_file_open self=%x\n", Glib::Threads::Thread::self()); + printf("on_action_file_open self=%p\n", + static_cast(Glib::Threads::Thread::self())); load_file(filename.c_str()); current_gig_dir = Glib::path_get_dirname(filename); } @@ -1046,7 +1486,8 @@ void MainWindow::on_loader_finished() { printf("Loader finished!\n"); - printf("on_loader_finished self=%x\n", Glib::Threads::Thread::self()); + printf("on_loader_finished self=%p\n", + static_cast(Glib::Threads::Thread::self())); load_gig(loader->gig, loader->filename.c_str()); progress_dialog->hide(); } @@ -1141,7 +1582,7 @@ file_structure_changed_signal.emit(this->file); - load_gig(this->file, this->filename.c_str()); + __refreshEntireGUI(); progress_dialog->hide(); } @@ -1186,7 +1627,9 @@ // show warning in the dialog Gtk::HBox descriptionArea; descriptionArea.set_spacing(15); - Gtk::Image warningIcon(Gtk::Stock::DIALOG_WARNING, Gtk::IconSize(Gtk::ICON_SIZE_DIALOG)); + Gtk::Image warningIcon; + warningIcon.set_from_icon_name("dialog-warning", + Gtk::IconSize(Gtk::ICON_SIZE_DIALOG)); descriptionArea.pack_start(warningIcon, Gtk::PACK_SHRINK); #if GTKMM_MAJOR_VERSION < 3 view::WrapLabel description; @@ -1238,13 +1681,13 @@ void MainWindow::__import_queued_samples() { std::cout << "Starting sample import\n" << std::flush; Glib::ustring error_files; - printf("Samples to import: %d\n", m_SampleImportQueue.size()); - for (std::list::iterator iter = m_SampleImportQueue.begin(); + printf("Samples to import: %d\n", int(m_SampleImportQueue.size())); + for (std::map::iterator iter = m_SampleImportQueue.begin(); iter != m_SampleImportQueue.end(); ) { - printf("Importing sample %s\n",(*iter).sample_path.c_str()); + printf("Importing sample %s\n",iter->second.sample_path.c_str()); SF_INFO info; info.format = 0; - SNDFILE* hFile = sf_open((*iter).sample_path.c_str(), SFM_READ, &info); + SNDFILE* hFile = sf_open(iter->second.sample_path.c_str(), SFM_READ, &info); sf_command(hFile, SFC_SET_SCALE_FLOAT_INT_READ, 0, SF_TRUE); try { if (!hFile) throw std::string(_("could not open file")); @@ -1267,6 +1710,9 @@ throw std::string(_("format not supported")); // unsupported subformat (yet?) } + // reset write position for sample + iter->first->SetPos(0); + const int bufsize = 10000; switch (bitdepth) { case 16: { @@ -1276,7 +1722,7 @@ // libsndfile does the conversion for us (if needed) int n = sf_readf_short(hFile, buffer, bufsize); // write from buffer directly (physically) into .gig file - iter->gig_sample->Write(buffer, n); + iter->first->Write(buffer, n); cnt -= n; } delete[] buffer; @@ -1296,7 +1742,7 @@ dstbuf[j++] = srcbuf[i] >> 24; } // write from buffer directly (physically) into .gig file - iter->gig_sample->Write(dstbuf, n); + iter->first->Write(dstbuf, n); cnt -= n; } delete[] srcbuf; @@ -1307,16 +1753,16 @@ // cleanup sf_close(hFile); // let the sampler re-cache the sample if needed - sample_changed_signal.emit(iter->gig_sample); + sample_changed_signal.emit(iter->first); // on success we remove the sample from the import queue, // otherwise keep it, maybe it works the next time ? - std::list::iterator cur = iter; + std::map::iterator cur = iter; ++iter; m_SampleImportQueue.erase(cur); } catch (std::string what) { // remember the files that made trouble (and their cause) if (!error_files.empty()) error_files += "\n"; - error_files += (*iter).sample_path += " (" + what + ")"; + error_files += iter->second.sample_path += " (" + what + ")"; ++iter; } } @@ -1339,6 +1785,16 @@ !Settings::singleton()->warnUserOnExtensions; } +void MainWindow::on_action_sync_sampler_instrument_selection() { + Settings::singleton()->syncSamplerInstrumentSelection = + !Settings::singleton()->syncSamplerInstrumentSelection; +} + +void MainWindow::on_action_move_root_note_with_region_moved() { + Settings::singleton()->moveRootNoteWithRegionMoved = + !Settings::singleton()->moveRootNoteWithRegionMoved; +} + void MainWindow::on_action_help_about() { Gtk::AboutDialog dialog; @@ -1348,7 +1804,7 @@ dialog.set_name("Gigedit"); #endif dialog.set_version(VERSION); - dialog.set_copyright("Copyright (C) 2006-2014 Andreas Persson"); + dialog.set_copyright("Copyright (C) 2006-2017 Andreas Persson"); const std::string sComment = _("Built " __DATE__ "\nUsing ") + ::gig::libraryName() + " " + ::gig::libraryVersion() + "\n\n" + @@ -1364,6 +1820,7 @@ dialog.set_comments(sComment.c_str()); dialog.set_website("http://www.linuxsampler.org"); dialog.set_website_label("http://www.linuxsampler.org"); + dialog.set_position(Gtk::WIN_POS_CENTER); dialog.run(); } @@ -1389,6 +1846,11 @@ table(2, 1), m_file(NULL) { + if (!Settings::singleton()->autoRestoreWindowDimension) { + set_default_size(470, 390); + set_position(Gtk::WIN_POS_MOUSE); + } + set_title(_("File Properties")); eName.set_width_chars(50); @@ -1523,6 +1985,11 @@ eDimensionKeyRangeLow(_("Keyswitching range low")), eDimensionKeyRangeHigh(_("Keyswitching range high")) { + if (!Settings::singleton()->autoRestoreWindowDimension) { + //set_default_size(470, 390); + set_position(Gtk::WIN_POS_MOUSE); + } + set_title(_("Instrument Properties")); eDimensionKeyRangeLow.set_tip( @@ -1631,7 +2098,10 @@ file = 0; set_file_is_shared(isSharedInstrument); - this->filename = filename ? filename : _("Unsaved Gig File"); + this->filename = + (filename && strlen(filename) > 0) ? + filename : (!gig->GetFileName().empty()) ? + gig->GetFileName() : _("Unsaved Gig File"); set_title(Glib::filename_display_basename(this->filename)); file_has_name = filename; file_is_changed = false; @@ -1640,14 +2110,18 @@ propDialog.set_info(gig->pInfo); instrument_name_connection.block(); + int index = 0; for (gig::Instrument* instrument = gig->GetFirstInstrument() ; instrument ; - instrument = gig->GetNextInstrument()) { + instrument = gig->GetNextInstrument(), ++index) { Glib::ustring name(gig_to_utf8(instrument->pInfo->Name)); + const int iScriptSlots = instrument->ScriptSlotCount(); Gtk::TreeModel::iterator iter = m_refTreeModel->append(); Gtk::TreeModel::Row row = *iter; + row[m_Columns.m_col_nr] = index; row[m_Columns.m_col_name] = name; row[m_Columns.m_col_instr] = instrument; + row[m_Columns.m_col_scripts] = iScriptSlots ? ToString(iScriptSlots) : ""; add_instrument_to_menu(name); } @@ -1718,8 +2192,12 @@ { instrumentProps.signal_name_changed().clear(); - Gtk::TreeModel::const_iterator it = - m_TreeView.get_selection()->get_selected(); + std::vector rows = m_TreeView.get_selection()->get_selected_rows(); + if (rows.empty()) { + instrumentProps.hide(); + return false; + } + Gtk::TreeModel::const_iterator it = m_refTreeModel->get_iter(rows[0]); if (it) { Gtk::TreeModel::Row row = *it; gig::Instrument* instrument = row[m_Columns.m_col_instr]; @@ -1772,8 +2250,9 @@ void MainWindow::show_script_slots() { if (!file) return; // get selected instrument - Glib::RefPtr sel = m_TreeView.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); + std::vector rows = m_TreeView.get_selection()->get_selected_rows(); + if (rows.empty()) return; + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[0]); if (!it) return; Gtk::TreeModel::Row row = *it; gig::Instrument* instrument = row[m_Columns.m_col_instr]; @@ -1781,10 +2260,30 @@ ScriptSlots* window = new ScriptSlots; window->setInstrument(instrument); + window->signal_script_slots_changed().connect( + sigc::mem_fun(*this, &MainWindow::onScriptSlotsModified) + ); //window->reparent(*this); window->show(); } +void MainWindow::onScriptSlotsModified(gig::Instrument* pInstrument) { + if (!pInstrument) return; + const int iScriptSlots = pInstrument->ScriptSlotCount(); + + Glib::RefPtr model = m_TreeView.get_model(); + for (int i = 0; i < model->children().size(); ++i) { + Gtk::TreeModel::Row row = model->children()[i]; + if (row[m_Columns.m_col_instr] != pInstrument) continue; + row[m_Columns.m_col_scripts] = iScriptSlots ? ToString(iScriptSlots) : ""; + break; + } +} + +void MainWindow::on_action_refresh_all() { + __refreshEntireGUI(); +} + void MainWindow::on_action_view_status_bar() { Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuView/Statusbar")); @@ -1796,6 +2295,26 @@ else m_StatusBar.hide(); } +void MainWindow::on_auto_restore_win_dim() { + Gtk::CheckMenuItem* item = + dynamic_cast(uiManager->get_widget("/MenuBar/MenuView/AutoRestoreWinDim")); + if (!item) { + std::cerr << "/MenuBar/MenuView/AutoRestoreWinDim == NULL\n"; + return; + } + Settings::singleton()->autoRestoreWindowDimension = item->get_active(); +} + +void MainWindow::on_save_with_temporary_file() { + Gtk::CheckMenuItem* item = + dynamic_cast(uiManager->get_widget("/MenuBar/MenuSettings/SaveWithTemporaryFile")); + if (!item) { + std::cerr << "/MenuBar/MenuSettings/SaveWithTemporaryFile == NULL\n"; + return; + } + Settings::singleton()->saveWithTemporaryFile = item->get_active(); +} + bool MainWindow::is_copy_samples_unity_note_enabled() const { Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuEdit/CopySampleUnity")); @@ -1860,45 +2379,140 @@ } } +void MainWindow::select_instrument(gig::Instrument* instrument) { + if (!instrument) return; + + Glib::RefPtr model = m_TreeView.get_model(); + for (int i = 0; i < model->children().size(); ++i) { + Gtk::TreeModel::Row row = model->children()[i]; + if (row[m_Columns.m_col_instr] == instrument) { + // select and show the respective instrument in the list view + show_intruments_tab(); + m_TreeView.get_selection()->unselect_all(); + m_TreeView.get_selection()->select(model->children()[i]); + std::vector rows = + m_TreeView.get_selection()->get_selected_rows(); + if (!rows.empty()) + m_TreeView.scroll_to_row(rows[0]); + on_sel_change(); // the regular instrument selection change callback + } + } +} + +/// Returns true if requested dimension region was successfully selected and scrolled to in the list view, false on error. +bool MainWindow::select_dimension_region(gig::DimensionRegion* dimRgn) { + gig::Region* pRegion = (gig::Region*) dimRgn->GetParent(); + gig::Instrument* pInstrument = (gig::Instrument*) pRegion->GetParent(); + + Glib::RefPtr model = m_TreeView.get_model(); + for (int i = 0; i < model->children().size(); ++i) { + Gtk::TreeModel::Row row = model->children()[i]; + if (row[m_Columns.m_col_instr] == pInstrument) { + // select and show the respective instrument in the list view + show_intruments_tab(); + m_TreeView.get_selection()->unselect_all(); + m_TreeView.get_selection()->select(model->children()[i]); + std::vector rows = + m_TreeView.get_selection()->get_selected_rows(); + if (!rows.empty()) + m_TreeView.scroll_to_row(rows[0]); + on_sel_change(); // the regular instrument selection change callback + + // select respective region in the region selector + m_RegionChooser.set_region(pRegion); + + // select and show the respective dimension region in the editor + //update_dimregs(); + if (!m_DimRegionChooser.select_dimregion(dimRgn)) return false; + //dimreg_edit.set_dim_region(dimRgn); + + return true; + } + } + + return false; +} + +void MainWindow::select_sample(gig::Sample* sample) { + Glib::RefPtr model = m_TreeViewSamples.get_model(); + for (int g = 0; g < model->children().size(); ++g) { + Gtk::TreeModel::Row rowGroup = model->children()[g]; + for (int s = 0; s < rowGroup.children().size(); ++s) { + Gtk::TreeModel::Row rowSample = rowGroup.children()[s]; + if (rowSample[m_SamplesModel.m_col_sample] == sample) { + show_samples_tab(); + m_TreeViewSamples.get_selection()->unselect_all(); + m_TreeViewSamples.get_selection()->select(rowGroup.children()[s]); + std::vector rows = + m_TreeViewSamples.get_selection()->get_selected_rows(); + if (rows.empty()) return; + m_TreeViewSamples.scroll_to_row(rows[0]); + return; + } + } + } +} + void MainWindow::on_sample_treeview_button_release(GdkEventButton* button) { if (button->type == GDK_BUTTON_PRESS && button->button == 3) { + // by default if Ctrl keys is pressed down, then a mouse right-click + // does not select the respective row, so we must assure this + // programmatically ... + /*{ + Gtk::TreeModel::Path path; + Gtk::TreeViewColumn* pColumn = NULL; + int cellX, cellY; + bool bSuccess = m_TreeViewSamples.get_path_at_pos( + (int)button->x, (int)button->y, + path, pColumn, cellX, cellY + ); + if (bSuccess) { + if (m_TreeViewSamples.get_selection()->count_selected_rows() <= 0) { + printf("not selected !!!\n"); + m_TreeViewSamples.get_selection()->select(path); + } + } + }*/ + Gtk::Menu* sample_popup = dynamic_cast(uiManager->get_widget("/SamplePopupMenu")); // update enabled/disabled state of sample popup items Glib::RefPtr sel = m_TreeViewSamples.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); - bool group_selected = false; - bool sample_selected = false; - if (it) { + std::vector rows = sel->get_selected_rows(); + const int n = rows.size(); + int nGroups = 0; + int nSamples = 0; + for (int r = 0; r < n; ++r) { + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[r]); + if (!it) continue; Gtk::TreeModel::Row row = *it; - group_selected = row[m_SamplesModel.m_col_group]; - sample_selected = row[m_SamplesModel.m_col_sample]; + if (row[m_SamplesModel.m_col_group]) nGroups++; + if (row[m_SamplesModel.m_col_sample]) nSamples++; } - - + dynamic_cast(uiManager->get_widget("/SamplePopupMenu/SampleProperties"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n == 1); dynamic_cast(uiManager->get_widget("/SamplePopupMenu/AddSample"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n); dynamic_cast(uiManager->get_widget("/SamplePopupMenu/AddGroup"))-> set_sensitive(file); dynamic_cast(uiManager->get_widget("/SamplePopupMenu/ShowSampleRefs"))-> - set_sensitive(sample_selected); + set_sensitive(nSamples == 1); dynamic_cast(uiManager->get_widget("/SamplePopupMenu/RemoveSample"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n); // show sample popup sample_popup->popup(button->button, button->time); dynamic_cast(uiManager->get_widget("/MenuBar/MenuSample/SampleProperties"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n == 1); dynamic_cast(uiManager->get_widget("/MenuBar/MenuSample/AddSample"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n); dynamic_cast(uiManager->get_widget("/MenuBar/MenuSample/AddGroup"))-> set_sensitive(file); dynamic_cast(uiManager->get_widget("/MenuBar/MenuSample/ShowSampleRefs"))-> - set_sensitive(sample_selected); + set_sensitive(nSamples == 1); dynamic_cast(uiManager->get_widget("/MenuBar/MenuSample/RemoveSample"))-> - set_sensitive(group_selected || sample_selected); + set_sensitive(n); } } @@ -1977,8 +2591,10 @@ instrument_name_connection.block(); Gtk::TreeModel::iterator iterInstr = m_refTreeModel->append(); Gtk::TreeModel::Row rowInstr = *iterInstr; + rowInstr[m_Columns.m_col_nr] = m_refTreeModel->children().size() - 1; rowInstr[m_Columns.m_col_name] = name; rowInstr[m_Columns.m_col_instr] = instrument; + rowInstr[m_Columns.m_col_scripts] = ""; instrument_name_connection.unblock(); add_instrument_to_menu(name); @@ -2005,19 +2621,23 @@ // retrieve the currently selected instrument // (being the original instrument to be duplicated) Glib::RefPtr sel = m_TreeView.get_selection(); - Gtk::TreeModel::iterator itSelection = sel->get_selected(); - if (!itSelection) return; - Gtk::TreeModel::Row row = *itSelection; - gig::Instrument* instrOrig = row[m_Columns.m_col_instr]; - if (!instrOrig) return; - - // duplicate the orginal instrument - gig::Instrument* instrNew = file->AddDuplicateInstrument(instrOrig); - instrNew->pInfo->Name = - instrOrig->pInfo->Name + - gig_from_utf8(Glib::ustring(" (") + _("Copy") + ")"); + std::vector rows = sel->get_selected_rows(); + for (int r = 0; r < rows.size(); ++r) { + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[r]); + if (it) { + Gtk::TreeModel::Row row = *it; + gig::Instrument* instrOrig = row[m_Columns.m_col_instr]; + if (instrOrig) { + // duplicate the orginal instrument + gig::Instrument* instrNew = file->AddDuplicateInstrument(instrOrig); + instrNew->pInfo->Name = + instrOrig->pInfo->Name + + gig_from_utf8(Glib::ustring(" (") + _("Copy") + ")"); - add_instrument(instrNew); + add_instrument(instrNew); + } + } + } } void MainWindow::on_action_remove_instrument() { @@ -2034,8 +2654,10 @@ } Glib::RefPtr sel = m_TreeView.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); - if (it) { + std::vector rows = sel->get_selected_rows(); + for (int r = rows.size() - 1; r >= 0; --r) { + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[r]); + if (!it) continue; Gtk::TreeModel::Row row = *it; gig::Instrument* instr = row[m_Columns.m_col_instr]; try { @@ -2050,6 +2672,16 @@ // remove row from instruments tree view m_refTreeModel->erase(it); + // update "Nr" column of all instrument rows + { + int index = 0; + for (Gtk::TreeModel::iterator it = m_refTreeModel->children().begin(); + it != m_refTreeModel->children().end(); ++it, ++index) + { + Gtk::TreeModel::Row row = *it; + row[m_Columns.m_col_nr] = index; + } + } #if GTKMM_MAJOR_VERSION < 3 // select another instrument (in gtk3 this is done @@ -2147,6 +2779,12 @@ if (!script) return; ScriptEditor* editor = new ScriptEditor; + editor->signal_script_to_be_changed.connect( + signal_script_to_be_changed.make_slot() + ); + editor->signal_script_changed.connect( + signal_script_changed.make_slot() + ); editor->setScript(script); //editor->reparent(*this); editor->show(); @@ -2212,28 +2850,42 @@ file_changed(); } +void MainWindow::on_action_replace_sample() { + add_or_replace_sample(true); +} + void MainWindow::on_action_add_sample() { + add_or_replace_sample(false); +} + +void MainWindow::add_or_replace_sample(bool replace) { if (!file) return; - // get selected group + + // get selected group (and probably selected sample) Glib::RefPtr sel = m_TreeViewSamples.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); + std::vector rows = sel->get_selected_rows(); + if (rows.empty()) return; + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[0]); if (!it) return; Gtk::TreeModel::Row row = *it; + gig::Sample* sample = NULL; gig::Group* group = row[m_SamplesModel.m_col_group]; if (!group) { // not a group, but a sample is selected (probably) - gig::Sample* sample = row[m_SamplesModel.m_col_sample]; - if (!sample) return; + if (replace) sample = row[m_SamplesModel.m_col_sample]; + if (!row[m_SamplesModel.m_col_sample]) return; it = row.parent(); // resolve parent (that is the sample's group) if (!it) return; - row = *it; - group = row[m_SamplesModel.m_col_group]; + if (!replace) row = *it; + group = (*it)[m_SamplesModel.m_col_group]; if (!group) return; } + if (replace && !sample) return; + // show 'browse for file' dialog - Gtk::FileChooserDialog dialog(*this, _("Add Sample(s)")); + Gtk::FileChooserDialog dialog(*this, replace ? _("Replace Sample with") : _("Add Sample(s)")); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); - dialog.set_select_multiple(true); + dialog.set_select_multiple(!replace); // allow multi audio file selection only when adding new samples, does not make sense when replacing a specific sample // matches all file types supported by libsndfile #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 @@ -2304,8 +2956,8 @@ sf_close(hFile); // close sound file throw std::string(_("format not supported")); // unsupported subformat (yet?) } - // add a new sample to the .gig file - gig::Sample* sample = file->AddSample(); + // add a new sample to the .gig file (if adding is requested actually) + if (!replace) sample = file->AddSample(); // file name without path Glib::ustring filename = Glib::filename_display_basename(*iter); // remove file extension if there is one @@ -2356,21 +3008,25 @@ // physically when File::Save() is called) sample->Resize(info.frames); // make sure sample is part of the selected group - group->AddSample(sample); + if (!replace) group->AddSample(sample); // schedule that physical resize and sample import // (data copying), performed when "Save" is requested SampleImportItem sched_item; sched_item.gig_sample = sample; sched_item.sample_path = *iter; - m_SampleImportQueue.push_back(sched_item); + m_SampleImportQueue[sample] = sched_item; // add sample to the tree view - Gtk::TreeModel::iterator iterSample = - m_refSamplesTreeModel->append(row.children()); - Gtk::TreeModel::Row rowSample = *iterSample; - rowSample[m_SamplesModel.m_col_name] = - gig_to_utf8(sample->pInfo->Name); - rowSample[m_SamplesModel.m_col_sample] = sample; - rowSample[m_SamplesModel.m_col_group] = NULL; + if (replace) { + row[m_SamplesModel.m_col_name] = gig_to_utf8(sample->pInfo->Name); + } else { + Gtk::TreeModel::iterator iterSample = + m_refSamplesTreeModel->append(row.children()); + Gtk::TreeModel::Row rowSample = *iterSample; + rowSample[m_SamplesModel.m_col_name] = + gig_to_utf8(sample->pInfo->Name); + rowSample[m_SamplesModel.m_col_sample] = sample; + rowSample[m_SamplesModel.m_col_group] = NULL; + } // close sound file sf_close(hFile); file_changed(); @@ -2381,7 +3037,11 @@ } // show error message box when some file(s) could not be opened / added if (!error_files.empty()) { - Glib::ustring txt = _("Could not add the following sample(s):\n") + error_files; + Glib::ustring txt = + (replace + ? _("Failed to replace sample with:\n") + : _("Could not add the following sample(s):\n")) + + error_files; Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } @@ -2448,18 +3108,14 @@ try { if (!hFile) throw std::string(_("could not open file")); - int bitdepth; switch (info.format & 0xff) { case SF_FORMAT_PCM_S8: case SF_FORMAT_PCM_16: case SF_FORMAT_PCM_U8: - bitdepth = 16; - break; case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: - bitdepth = 24; break; default: sf_close(hFile); @@ -2468,7 +3124,7 @@ SampleImportItem sched_item; sched_item.gig_sample = sample; sched_item.sample_path = filename; - m_SampleImportQueue.push_back(sched_item); + m_SampleImportQueue[sample] = sched_item; sf_close(hFile); file_changed(); } @@ -2492,8 +3148,10 @@ void MainWindow::on_action_remove_sample() { if (!file) return; Glib::RefPtr sel = m_TreeViewSamples.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); - if (it) { + std::vector rows = sel->get_selected_rows(); + for (int r = rows.size() - 1; r >= 0; --r) { + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[r]); + if (!it) continue; Gtk::TreeModel::Row row = *it; gig::Group* group = row[m_SamplesModel.m_col_group]; gig::Sample* sample = row[m_SamplesModel.m_col_sample]; @@ -2518,15 +3176,12 @@ // if sample(s) were just previously added, remove // them from the import queue for (std::list::iterator member = members.begin(); - member != members.end(); ++member) { - for (std::list::iterator iter = m_SampleImportQueue.begin(); - iter != m_SampleImportQueue.end(); ++iter) { - if ((*iter).gig_sample == *member) { - printf("Removing previously added sample '%s' from group '%s'\n", - (*iter).sample_path.c_str(), name.c_str()); - m_SampleImportQueue.erase(iter); - break; - } + member != members.end(); ++member) + { + if (m_SampleImportQueue.count(*member)) { + printf("Removing previously added sample '%s' from group '%s'\n", + m_SampleImportQueue[sample].sample_path.c_str(), name.c_str()); + m_SampleImportQueue.erase(*member); } } file_changed(); @@ -2541,14 +3196,10 @@ samples_removed_signal.emit(); // if sample was just previously added, remove it from // the import queue - for (std::list::iterator iter = m_SampleImportQueue.begin(); - iter != m_SampleImportQueue.end(); ++iter) { - if ((*iter).gig_sample == sample) { - printf("Removing previously added sample '%s'\n", - (*iter).sample_path.c_str()); - m_SampleImportQueue.erase(iter); - break; - } + if (m_SampleImportQueue.count(sample)) { + printf("Removing previously added sample '%s'\n", + m_SampleImportQueue[sample].sample_path.c_str()); + m_SampleImportQueue.erase(sample); } dimreg_changed(); file_changed(); @@ -2565,6 +3216,66 @@ } } +void MainWindow::on_action_remove_unused_samples() { + if (!file) return; + + // collect all samples that are not referenced by any instrument + std::list lsamples; + for (int iSample = 0; file->GetSample(iSample); ++iSample) { + gig::Sample* sample = file->GetSample(iSample); + bool isUsed = false; + for (gig::Instrument* instrument = file->GetFirstInstrument(); instrument; + instrument = file->GetNextInstrument()) + { + for (gig::Region* rgn = instrument->GetFirstRegion(); rgn; + rgn = instrument->GetNextRegion()) + { + for (int i = 0; i < 256; ++i) { + if (!rgn->pDimensionRegions[i]) continue; + if (rgn->pDimensionRegions[i]->pSample != sample) continue; + isUsed = true; + goto endOfRefSearch; + } + } + } + endOfRefSearch: + if (!isUsed) lsamples.push_back(sample); + } + + if (lsamples.empty()) return; + + // notify everybody that we're going to remove these samples + samples_to_be_removed_signal.emit(lsamples); + + // remove collected samples + try { + for (std::list::iterator itSample = lsamples.begin(); + itSample != lsamples.end(); ++itSample) + { + gig::Sample* sample = *itSample; + // remove sample from the .gig file + file->DeleteSample(sample); + // if sample was just previously added, remove it from the import queue + if (m_SampleImportQueue.count(sample)) { + printf("Removing previously added sample '%s'\n", + m_SampleImportQueue[sample].sample_path.c_str()); + m_SampleImportQueue.erase(sample); + } + } + } catch (RIFF::Exception e) { + // show error message + Gtk::MessageDialog msg(*this, e.Message.c_str(), false, Gtk::MESSAGE_ERROR); + msg.run(); + } + + // notify everybody that we're done with removal + samples_removed_signal.emit(); + + dimreg_changed(); + file_changed(); + __refreshEntireGUI(); +} + // see comment on on_sample_treeview_drag_begin() void MainWindow::on_scripts_treeview_drag_begin(const Glib::RefPtr& context) { @@ -2591,6 +3302,65 @@ sizeof(script)/*length of data in bytes*/); } +// see comment on on_sample_treeview_drag_begin() +void MainWindow::on_instruments_treeview_drag_begin(const Glib::RefPtr& context) +{ + first_call_to_drag_data_get = true; +} + +void MainWindow::on_instruments_treeview_drag_data_get(const Glib::RefPtr&, + Gtk::SelectionData& selection_data, guint, guint) +{ + if (!first_call_to_drag_data_get) return; + first_call_to_drag_data_get = false; + + // get selected source instrument + gig::Instrument* src = NULL; + { + Glib::RefPtr sel = m_TreeView.get_selection(); + std::vector rows = sel->get_selected_rows(); + if (!rows.empty()) { + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[0]); + if (it) { + Gtk::TreeModel::Row row = *it; + src = row[m_Columns.m_col_instr]; + } + } + } + if (!src) return; + + // pass the source gig::Instrument as pointer + selection_data.set(selection_data.get_target(), 0/*unused*/, (const guchar*)&src, + sizeof(src)/*length of data in bytes*/); +} + +void MainWindow::on_instruments_treeview_drop_drag_data_received( + const Glib::RefPtr& context, int x, int y, + const Gtk::SelectionData& selection_data, guint, guint time) +{ + gig::Instrument* src = *((gig::Instrument**) selection_data.get_data()); + if (!src || selection_data.get_length() != sizeof(gig::Instrument*)) + return; + + gig::Instrument* dst = NULL; + { + Gtk::TreeModel::Path path; + const bool found = m_TreeView.get_path_at_pos(x, y, path); + if (!found) return; + + Gtk::TreeModel::iterator iter = m_refTreeModel->get_iter(path); + if (!iter) return; + Gtk::TreeModel::Row row = *iter; + dst = row[m_Columns.m_col_instr]; + } + if (!dst) return; + + //printf("dragdrop received src=%s dst=%s\n", src->pInfo->Name.c_str(), dst->pInfo->Name.c_str()); + src->MoveTo(dst); + __refreshEntireGUI(); + select_instrument(src); +} + // For some reason drag_data_get gets called two times for each // drag'n'drop (at least when target is an Entry). This work-around // makes sure the code in drag_data_get and drop_drag_data_received is @@ -2609,10 +3379,13 @@ // get selected sample gig::Sample* sample = NULL; Glib::RefPtr sel = m_TreeViewSamples.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); - if (it) { - Gtk::TreeModel::Row row = *it; - sample = row[m_SamplesModel.m_col_sample]; + std::vector rows = sel->get_selected_rows(); + if (!rows.empty()) { + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[0]); + if (it) { + Gtk::TreeModel::Row row = *it; + sample = row[m_SamplesModel.m_col_sample]; + } } // pass the gig::Sample as pointer selection_data.set(selection_data.get_target(), 0/*unused*/, (const guchar*)&sample, @@ -2752,6 +3525,12 @@ if (!script) return; ScriptEditor* editor = new ScriptEditor; + editor->signal_script_to_be_changed.connect( + signal_script_to_be_changed.make_slot() + ); + editor->signal_script_changed.connect( + signal_script_changed.make_slot() + ); editor->setScript(script); //editor->reparent(*this); editor->show(); @@ -2794,7 +3573,6 @@ void MainWindow::on_action_combine_instruments() { CombineInstrumentsDialog* d = new CombineInstrumentsDialog(*this, file); d->show_all(); - d->resize(500, 400); d->run(); if (d->fileWasChanged()) { // update GUI with new instrument just created @@ -2805,7 +3583,9 @@ void MainWindow::on_action_view_references() { Glib::RefPtr sel = m_TreeViewSamples.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); + std::vector rows = sel->get_selected_rows(); + if (rows.empty()) return; + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[0]); if (!it) return; Gtk::TreeModel::Row row = *it; gig::Sample* sample = row[m_SamplesModel.m_col_sample]; @@ -2813,6 +3593,9 @@ ReferencesView* d = new ReferencesView(*this); d->setSample(sample); + d->dimension_region_selected.connect( + sigc::mem_fun(*this, &MainWindow::select_dimension_region) + ); d->show_all(); d->resize(500, 400); d->run(); @@ -2943,7 +3726,9 @@ // show warning in the file picker dialog Gtk::HBox descriptionArea; descriptionArea.set_spacing(15); - Gtk::Image warningIcon(Gtk::Stock::DIALOG_WARNING, Gtk::IconSize(Gtk::ICON_SIZE_DIALOG)); + Gtk::Image warningIcon; + warningIcon.set_from_icon_name("dialog-warning", + Gtk::IconSize(Gtk::ICON_SIZE_DIALOG)); descriptionArea.pack_start(warningIcon, Gtk::PACK_SHRINK); #if GTKMM_MAJOR_VERSION < 3 view::WrapLabel description; @@ -2966,7 +3751,8 @@ descriptionArea.show_all(); if (dialog.run() == Gtk::RESPONSE_OK) { - printf("on_action_merge_files self=%x\n", Glib::Threads::Thread::self()); + printf("on_action_merge_files self=%p\n", + static_cast(Glib::Threads::Thread::self())); std::vector filenames = dialog.get_filenames(); // merge the selected files to the currently open .gig file @@ -2978,7 +3764,7 @@ } // update GUI - __refreshEntireGUI(); + __refreshEntireGUI(); } } @@ -2996,6 +3782,12 @@ Gdk::Pixbuf::create_from_xpm_data(status_detached_xpm) ); } + + { + Gtk::MenuItem* item = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuSettings/SyncSamplerInstrumentSelection")); + if (item) item->set_sensitive(b); + } } void MainWindow::on_sample_ref_count_incremented(gig::Sample* sample, int offset) { @@ -3042,6 +3834,173 @@ m_TreeViewNotebook.set_current_page(2); } +void MainWindow::select_prev_region() { + m_RegionChooser.select_prev_region(); +} + +void MainWindow::select_next_region() { + m_RegionChooser.select_next_region(); +} + +void MainWindow::select_next_dim_rgn_zone() { + if (m_DimRegionChooser.has_focus()) return; // avoid conflict with key stroke handler of DimenionRegionChooser + m_DimRegionChooser.select_next_dimzone(); +} + +void MainWindow::select_prev_dim_rgn_zone() { + if (m_DimRegionChooser.has_focus()) return; // avoid conflict with key stroke handler of DimenionRegionChooser + m_DimRegionChooser.select_prev_dimzone(); +} + +void MainWindow::select_add_next_dim_rgn_zone() { + m_DimRegionChooser.select_next_dimzone(true); +} + +void MainWindow::select_add_prev_dim_rgn_zone() { + m_DimRegionChooser.select_prev_dimzone(true); +} + +void MainWindow::select_prev_dimension() { + if (m_DimRegionChooser.has_focus()) return; // avoid conflict with key stroke handler of DimenionRegionChooser + m_DimRegionChooser.select_prev_dimension(); +} + +void MainWindow::select_next_dimension() { + if (m_DimRegionChooser.has_focus()) return; // avoid conflict with key stroke handler of DimenionRegionChooser + m_DimRegionChooser.select_next_dimension(); +} + +#define CLIPBOARD_DIMENSIONREGION_TARGET \ + ("libgig.DimensionRegion." + m_serializationArchive.rawDataFormat()) + +void MainWindow::copy_selected_dimrgn() { + gig::DimensionRegion* pDimRgn = m_DimRegionChooser.get_main_dimregion(); + if (!pDimRgn) { + updateClipboardPasteAvailable(); + updateClipboardCopyAvailable(); + return; + } + + std::vector targets; + targets.push_back( Gtk::TargetEntry(CLIPBOARD_DIMENSIONREGION_TARGET) ); + + Glib::RefPtr clipboard = Gtk::Clipboard::get(); + clipboard->set( + targets, + sigc::mem_fun(*this, &MainWindow::on_clipboard_get), + sigc::mem_fun(*this, &MainWindow::on_clipboard_clear) + ); + + m_serializationArchive.serialize(pDimRgn); + + updateClipboardPasteAvailable(); +} + +void MainWindow::paste_copied_dimrgn() { + Glib::RefPtr clipboard = Gtk::Clipboard::get(); + clipboard->request_contents( + CLIPBOARD_DIMENSIONREGION_TARGET, + sigc::mem_fun(*this, &MainWindow::on_clipboard_received) + ); + updateClipboardPasteAvailable(); +} + +void MainWindow::adjust_clipboard_content() { + MacroEditor* editor = new MacroEditor(); + editor->setMacro(&m_serializationArchive, true); + editor->show(); +} + +void MainWindow::updateClipboardPasteAvailable() { + Glib::RefPtr clipboard = Gtk::Clipboard::get(); + clipboard->request_targets( + sigc::mem_fun(*this, &MainWindow::on_clipboard_received_targets) + ); +} + +void MainWindow::updateClipboardCopyAvailable() { + bool bDimensionRegionCopyIsPossible = m_DimRegionChooser.get_main_dimregion(); + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/CopyDimRgn") + )->set_sensitive(bDimensionRegionCopyIsPossible); +} + +void MainWindow::on_clipboard_owner_change(GdkEventOwnerChange* event) { + updateClipboardPasteAvailable(); +} + +void MainWindow::on_clipboard_get(Gtk::SelectionData& selection_data, guint /*info*/) { + const std::string target = selection_data.get_target(); + if (target == CLIPBOARD_DIMENSIONREGION_TARGET) { + selection_data.set( + CLIPBOARD_DIMENSIONREGION_TARGET, 8 /* "format": probably unused*/, + &m_serializationArchive.rawData()[0], + m_serializationArchive.rawData().size() + ); + } else { + std::cerr << "Clipboard: content for unknown target '" << target << "' requested\n"; + } +} + +void MainWindow::on_clipboard_clear() { + m_serializationArchive.clear(); + updateClipboardPasteAvailable(); + updateClipboardCopyAvailable(); +} + +//NOTE: Might throw exception !!! +void MainWindow::applyMacro(Serialization::Archive& macro) { + gig::DimensionRegion* pDimRgn = m_DimRegionChooser.get_main_dimregion(); + if (!pDimRgn) return; + + for (std::set::iterator itDimReg = dimreg_edit.dimregs.begin(); + itDimReg != dimreg_edit.dimregs.end(); ++itDimReg) + { + gig::DimensionRegion* pDimRgn = *itDimReg; + DimRegionChangeGuard(this, pDimRgn); + macro.deserialize(pDimRgn); + } + //region_changed() + file_changed(); + dimreg_changed(); +} + +void MainWindow::on_clipboard_received(const Gtk::SelectionData& selection_data) { + const std::string target = selection_data.get_target(); + if (target == CLIPBOARD_DIMENSIONREGION_TARGET) { + Glib::ustring errorText; + try { + m_serializationArchive.decode( + selection_data.get_data(), selection_data.get_length() + ); + applyMacro(m_serializationArchive); + } catch (Serialization::Exception e) { + errorText = e.Message; + } catch (...) { + errorText = _("Unknown exception while pasting DimensionRegion"); + } + if (!errorText.empty()) { + Glib::ustring txt = _("Pasting DimensionRegion failed:\n") + errorText; + Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); + msg.run(); + } + } +} + +void MainWindow::on_clipboard_received_targets(const std::vector& targets) { + const bool bDimensionRegionPasteIsPossible = + std::find(targets.begin(), targets.end(), + CLIPBOARD_DIMENSIONREGION_TARGET) != targets.end(); + + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/PasteDimRgn") + )->set_sensitive(bDimensionRegionPasteIsPossible); + + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/AdjustClipboard") + )->set_sensitive(bDimensionRegionPasteIsPossible); +} + sigc::signal& MainWindow::signal_file_structure_to_be_changed() { return file_structure_to_be_changed_signal; } @@ -3097,3 +4056,7 @@ sigc::signal& MainWindow::signal_keyboard_key_released() { return m_RegionChooser.signal_keyboard_key_released(); } + +sigc::signal& MainWindow::signal_switch_sampler_instrument() { + return switch_sampler_instrument_signal; +}