--- gigedit/trunk/src/gigedit/mainwindow.cpp 2015/06/12 17:57:52 2773 +++ gigedit/trunk/src/gigedit/mainwindow.cpp 2020/02/16 18:39:53 3749 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2015 Andreas Persson + * Copyright (C) 2006-2020 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,17 +20,25 @@ #include #include +#include "compat.h" + #include #include #include #include +#include #include #include #include -#include +#if HAS_GTKMM_STOCK +# include +#endif #include #include -#include +#if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 89) +# include +#endif +#include #if GTKMM_MAJOR_VERSION < 3 #include "wrapLabel.hh" #endif @@ -39,7 +47,11 @@ #include "compat.h" #include -#include +#ifdef LIBSNDFILE_HEADER_FILE +# include LIBSNDFILE_HEADER_FILE(sndfile.h) +#else +# include +#endif #include #include "mainwindow.h" @@ -50,18 +62,46 @@ #include "ReferencesView.h" #include "../../gfx/status_attached.xpm" #include "../../gfx/status_detached.xpm" +#include "gfx/builtinpix.h" +#include "MacroEditor.h" +#include "MacrosSetup.h" +#if defined(__APPLE__) +# include "MacHelper.h" +#endif +static 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 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(); + + this->file = NULL; + // set_border_width(5); -// set_default_size(400, 200); + if (!Settings::singleton()->autoRestoreWindowDimension) { +#if GTKMM_MAJOR_VERSION >= 3 + set_default_size(1010, -1); +#else + set_default_size(915, -1); +#endif + set_position(Gtk::WIN_POS_CENTER); + } add(m_VBox); @@ -71,8 +111,13 @@ // m_TreeView.set_reorderable(); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + m_TreeView.signal_button_press_event().connect( + sigc::mem_fun(*this, &MainWindow::on_button_release)); +#else m_TreeView.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &MainWindow::on_button_release)); +#endif // Add the TreeView tab, inside a ScrolledWindow, with the button underneath: m_ScrolledWindow.add(m_TreeView); @@ -85,10 +130,20 @@ m_ScrolledWindowScripts.add(m_TreeViewScripts); m_ScrolledWindowScripts.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - +#if GTKMM_MAJOR_VERSION < 3 m_TreeViewNotebook.set_size_request(300); +#endif + + m_searchLabel.set_text(Glib::ustring(" ") + _("Filter:")); + m_searchField.pack_start(m_searchLabel, Gtk::PACK_SHRINK); + m_searchField.pack_start(m_searchText); + m_searchField.set_spacing(5); + + m_left_vbox.pack_start(m_TreeViewNotebook); + m_left_vbox.pack_start(m_searchField, Gtk::PACK_SHRINK); + + m_HPaned.add1(m_left_vbox); - m_HPaned.add1(m_TreeViewNotebook); dimreg_hbox.add(dimreg_label); dimreg_hbox.add(dimreg_all_regions); dimreg_hbox.add(dimreg_all_dimregs); @@ -96,6 +151,58 @@ 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); +#if HAS_GTKMM_ALIGNMENT + imageNoSample.set_alignment(Gtk::ALIGN_END); + labelNoSample.set_alignment(Gtk::ALIGN_START); +#else + imageNoSample.set_halign(Gtk::ALIGN_END); + labelNoSample.set_halign(Gtk::ALIGN_START); +#endif + legend_hbox.add(imageNoSample); + legend_hbox.add(labelNoSample); + + imageMissingSample.set(yellowDot); +#if HAS_GTKMM_ALIGNMENT + imageMissingSample.set_alignment(Gtk::ALIGN_END); + labelMissingSample.set_alignment(Gtk::ALIGN_START); +#else + imageMissingSample.set_halign(Gtk::ALIGN_END); + labelMissingSample.set_halign(Gtk::ALIGN_START); +#endif + legend_hbox.add(imageMissingSample); + legend_hbox.add(labelMissingSample); + + imageLooped.set(blackLoop); +#if HAS_GTKMM_ALIGNMENT + imageLooped.set_alignment(Gtk::ALIGN_END); + labelLooped.set_alignment(Gtk::ALIGN_START); +#else + imageLooped.set_halign(Gtk::ALIGN_END); + labelLooped.set_halign(Gtk::ALIGN_START); +#endif + legend_hbox.add(imageLooped); + legend_hbox.add(labelLooped); + + imageSomeLoops.set(grayLoop); +#if HAS_GTKMM_ALIGNMENT + imageSomeLoops.set_alignment(Gtk::ALIGN_END); + labelSomeLoops.set_alignment(Gtk::ALIGN_START); +#else + imageSomeLoops.set_halign(Gtk::ALIGN_END); + labelSomeLoops.set_halign(Gtk::ALIGN_START); +#endif + legend_hbox.add(imageSomeLoops); + legend_hbox.add(labelSomeLoops); + +#if HAS_GTKMM_SHOW_ALL_CHILDREN + legend_hbox.show_all_children(); +#endif + } + 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.")); @@ -107,6 +214,45 @@ m_TreeViewNotebook.append_page(m_ScrolledWindow, _("Instruments")); m_TreeViewNotebook.append_page(m_ScrolledWindowScripts, _("Scripts")); +#if USE_GLIB_ACTION + m_actionGroup = Gio::SimpleActionGroup::create(); + m_actionGroup->add_action( + "New", sigc::mem_fun(*this, &MainWindow::on_action_file_new) + ); + m_actionGroup->add_action( + "Open", sigc::mem_fun(*this, &MainWindow::on_action_file_open) + ); + m_actionGroup->add_action( + "Save", sigc::mem_fun(*this, &MainWindow::on_action_file_save) + ); + m_actionGroup->add_action( + "SaveAs", sigc::mem_fun(*this, &MainWindow::on_action_file_save_as) + ); + m_actionGroup->add_action( + "Properties", sigc::mem_fun(*this, &MainWindow::on_action_file_properties) + ); + m_actionGroup->add_action( + "InstrProperties", sigc::mem_fun(*this, &MainWindow::show_instr_props) + ); + m_actionMIDIRules = m_actionGroup->add_action( + "MidiRules", sigc::mem_fun(*this, &MainWindow::show_midi_rules) + ); + m_actionGroup->add_action( + "ScriptSlots", sigc::mem_fun(*this, &MainWindow::show_script_slots) + ); + m_actionGroup->add_action( + "Quit", sigc::mem_fun(*this, &MainWindow::on_action_quit) + ); + m_actionGroup->add_action( + "MenuSample", sigc::mem_fun(*this, &MainWindow::show_samples_tab) + ); + m_actionGroup->add_action( + "MenuInstrument", sigc::mem_fun(*this, &MainWindow::show_intruments_tab) + ); + m_actionGroup->add_action( + "MenuScript", sigc::mem_fun(*this, &MainWindow::show_scripts_tab) + ); +#else actionGroup = Gtk::ActionGroup::create(); actionGroup->add(Gtk::Action::create("MenuFile", _("_File"))); @@ -156,13 +302,134 @@ 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("AssignScripts", _("Assign Script"))); actionGroup->add(Gtk::Action::create("MenuEdit", _("_Edit"))); +#endif + + 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 +#if USE_GLIB_ACTION + m_actionCopyDimRgn = m_actionGroup->add_action( + "CopyDimRgn", sigc::mem_fun(*this, &MainWindow::copy_selected_dimrgn) + ); + m_actionPasteDimRgn = m_actionGroup->add_action( + "PasteDimRgn", sigc::mem_fun(*this, &MainWindow::paste_copied_dimrgn) + ); + m_actionAdjustClipboard = m_actionGroup->add_action( + "AdjustClipboard", sigc::mem_fun(*this, &MainWindow::adjust_clipboard_content) + ); + m_actionGroup->add_action( + "SelectPrevInstr", sigc::mem_fun(*this, &MainWindow::select_prev_instrument) + ); + m_actionGroup->add_action( + "SelectNextInstr", sigc::mem_fun(*this, &MainWindow::select_next_instrument) + ); + m_actionGroup->add_action( + "SelectPrevRegion", sigc::mem_fun(*this, &MainWindow::select_prev_region) + ); + m_actionGroup->add_action( + "SelectNextRegion", sigc::mem_fun(*this, &MainWindow::select_next_region) + ); + m_actionGroup->add_action( + "SelectPrevDimRgnZone", sigc::mem_fun(*this, &MainWindow::select_prev_dim_rgn_zone) + ); + m_actionGroup->add_action( + "SelectNextDimRgnZone", sigc::mem_fun(*this, &MainWindow::select_next_dim_rgn_zone) + ); + m_actionGroup->add_action( + "SelectPrevDimension", sigc::mem_fun(*this, &MainWindow::select_prev_dimension) + ); + m_actionGroup->add_action( + "SelectNextDimension", sigc::mem_fun(*this, &MainWindow::select_next_dimension) + ); + m_actionGroup->add_action( + "SelectAddPrevDimRgnZone", sigc::mem_fun(*this, &MainWindow::select_add_prev_dim_rgn_zone) + ); + m_actionGroup->add_action( + "SelectAddNextDimRgnZone", sigc::mem_fun(*this, &MainWindow::select_add_next_dim_rgn_zone) + ); +#else + 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("SelectPrevInstr", + _("Select Previous Instrument")), + Gtk::AccelKey(GDK_KEY_Up, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::select_prev_instrument)); + + actionGroup->add(Gtk::Action::create("SelectNextInstr", + _("Select Next Instrument")), + Gtk::AccelKey(GDK_KEY_Down, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::select_next_instrument)); + + 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)); +#endif + +#if USE_GLIB_ACTION + m_actionToggleCopySampleUnity = m_actionGroup->add_action_bool("CopySampleUnity", true); + m_actionToggleCopySampleTune = m_actionGroup->add_action_bool("CopySampleTune", true); + m_actionToggleCopySampleLoop = m_actionGroup->add_action_bool("CopySampleLoop", true); +#else Glib::RefPtr toggle_action = Gtk::ToggleAction::create("CopySampleUnity", _("Copy Sample's _Unity Note")); toggle_action->set_active(true); @@ -177,20 +444,93 @@ Gtk::ToggleAction::create("CopySampleLoop", _("Copy Sample's _Loop Points")); toggle_action->set_active(true); actionGroup->add(toggle_action); +#endif + +#if USE_GLIB_ACTION + m_actionToggleStatusBar = + m_actionGroup->add_action_bool("Statusbar", sigc::mem_fun(*this, &MainWindow::on_action_view_status_bar), true); + m_actionToggleRestoreWinDim = + m_actionGroup->add_action_bool("AutoRestoreWinDim", sigc::mem_fun(*this, &MainWindow::on_auto_restore_win_dim), Settings::singleton()->autoRestoreWindowDimension); + m_actionInstrDoubleClickOpensProps = + m_actionGroup->add_action_bool( + "OpenInstrPropsByDoubleClick", + sigc::mem_fun(*this, &MainWindow::on_instr_double_click_opens_props), + Settings::singleton()->instrumentDoubleClickOpensProps + ); + m_actionToggleShowTooltips = m_actionGroup->add_action_bool( + "ShowTooltips", sigc::mem_fun(*this, &MainWindow::on_action_show_tooltips), + Settings::singleton()->showTooltips + ); + m_actionToggleSaveWithTempFile = + m_actionGroup->add_action_bool("SaveWithTemporaryFile", sigc::mem_fun(*this, &MainWindow::on_save_with_temporary_file), Settings::singleton()->saveWithTemporaryFile); + m_actionGroup->add_action("RefreshAll", sigc::mem_fun(*this, &MainWindow::on_action_refresh_all)); +#else + actionGroup->add(Gtk::Action::create("MenuMacro", _("_Macro"))); - actionGroup->add(Gtk::Action::create("MenuView", _("_View"))); + actionGroup->add(Gtk::Action::create("MenuView", _("Vie_w"))); toggle_action = Gtk::ToggleAction::create("Statusbar", _("_Statusbar")); toggle_action->set_active(true); actionGroup->add(toggle_action, 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("OpenInstrPropsByDoubleClick", _("Instrument Properties by Double Click")); + toggle_action->set_active(Settings::singleton()->instrumentDoubleClickOpensProps); + actionGroup->add(toggle_action, + sigc::mem_fun( + *this, &MainWindow::on_instr_double_click_opens_props)); + + toggle_action = + Gtk::ToggleAction::create("ShowTooltips", _("Tooltips for Beginners")); + toggle_action->set_active(Settings::singleton()->showTooltips); + actionGroup->add( + toggle_action, + sigc::mem_fun(*this, &MainWindow::on_action_show_tooltips) + ); + + 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) ); +#endif +#if USE_GLIB_ACTION + m_actionGroup->add_action( + "About", sigc::mem_fun(*this, &MainWindow::on_action_help_about) + ); + m_actionGroup->add_action( + "AddInstrument", sigc::mem_fun(*this, &MainWindow::on_action_add_instrument) + ); + m_actionGroup->add_action( + "DupInstrument", sigc::mem_fun(*this, &MainWindow::on_action_duplicate_instrument) + ); + m_actionGroup->add_action( + "MoveInstrument", sigc::mem_fun(*this, &MainWindow::on_action_move_instr) + ); + m_actionGroup->add_action( + "CombInstruments", sigc::mem_fun(*this, &MainWindow::on_action_combine_instruments) + ); + m_actionGroup->add_action( + "RemoveInstrument", sigc::mem_fun(*this, &MainWindow::on_action_remove_instrument) + ); +#else action = Gtk::Action::create("MenuHelp", Gtk::Stock::HELP); actionGroup->add(Gtk::Action::create("MenuHelp", action->property_label())); @@ -206,11 +546,35 @@ sigc::mem_fun(*this, &MainWindow::on_action_duplicate_instrument) ); actionGroup->add( + Gtk::Action::create("MoveInstrument", _("Move _Instrument To ...")), + Gtk::AccelKey(GDK_KEY_i, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::on_action_move_instr) + ); + actionGroup->add( + Gtk::Action::create("CombInstruments", _("_Combine Instruments ...")), + Gtk::AccelKey(GDK_KEY_j, primaryModifierKey), + sigc::mem_fun(*this, &MainWindow::on_action_combine_instruments) + ); + actionGroup->add( Gtk::Action::create("RemoveInstrument", Gtk::Stock::REMOVE), sigc::mem_fun(*this, &MainWindow::on_action_remove_instrument) ); +#endif - +#if USE_GLIB_ACTION + m_actionToggleWarnOnExtensions = m_actionGroup->add_action_bool( + "WarnUserOnExtensions", sigc::mem_fun(*this, &MainWindow::on_action_warn_user_on_extensions), + Settings::singleton()->warnUserOnExtensions + ); + m_actionToggleSyncSamplerSelection = m_actionGroup->add_action_bool( + "SyncSamplerInstrumentSelection", sigc::mem_fun(*this, &MainWindow::on_action_sync_sampler_instrument_selection), + Settings::singleton()->syncSamplerInstrumentSelection + ); + m_actionToggleMoveRootNoteWithRegion = m_actionGroup->add_action_bool( + "MoveRootNoteWithRegionMoved", sigc::mem_fun(*this, &MainWindow::on_action_move_root_note_with_region_moved), + Settings::singleton()->moveRootNoteWithRegionMoved + ); +#else actionGroup->add(Gtk::Action::create("MenuSettings", _("_Settings"))); toggle_action = @@ -236,8 +600,16 @@ toggle_action, sigc::mem_fun(*this, &MainWindow::on_action_move_root_note_with_region_moved) ); +#endif - +#if USE_GLIB_ACTION + m_actionGroup->add_action( + "CombineInstruments", sigc::mem_fun(*this, &MainWindow::on_action_combine_instruments) + ); + m_actionGroup->add_action( + "MergeFiles", sigc::mem_fun(*this, &MainWindow::on_action_merge_files) + ); +#else actionGroup->add(Gtk::Action::create("MenuTools", _("_Tools"))); actionGroup->add( @@ -249,9 +621,35 @@ Gtk::Action::create("MergeFiles", _("_Merge Files...")), sigc::mem_fun(*this, &MainWindow::on_action_merge_files) ); - +#endif // sample right-click popup actions +#if USE_GLIB_ACTION + m_actionSampleProperties = m_actionGroup->add_action( + "SampleProperties", sigc::mem_fun(*this, &MainWindow::on_action_sample_properties) + ); + m_actionAddSampleGroup = m_actionGroup->add_action( + "AddGroup", sigc::mem_fun(*this, &MainWindow::on_action_add_group) + ); + m_actionAddSample = m_actionGroup->add_action( + "AddSample", sigc::mem_fun(*this, &MainWindow::on_action_add_sample) + ); + m_actionRemoveSample = m_actionGroup->add_action( + "RemoveSample", sigc::mem_fun(*this, &MainWindow::on_action_remove_sample) + ); + m_actionGroup->add_action( + "RemoveUnusedSamples", sigc::mem_fun(*this, &MainWindow::on_action_remove_unused_samples) + ); + m_actionViewSampleRefs = m_actionGroup->add_action( + "ShowSampleRefs", sigc::mem_fun(*this, &MainWindow::on_action_view_references) + ); + m_actionReplaceSample = m_actionGroup->add_action( + "ReplaceSample", sigc::mem_fun(*this, &MainWindow::on_action_replace_sample) + ); + m_actionGroup->add_action( + "ReplaceAllSamplesInAllGroups", sigc::mem_fun(*this, &MainWindow::on_action_replace_all_samples_in_all_groups) + ); +#else actionGroup->add( Gtk::Action::create("SampleProperties", Gtk::Stock::PROPERTIES), sigc::mem_fun(*this, &MainWindow::on_action_sample_properties) @@ -286,8 +684,23 @@ _("Replace All Samples in All Groups...")), sigc::mem_fun(*this, &MainWindow::on_action_replace_all_samples_in_all_groups) ); +#endif // script right-click popup actions +#if USE_GLIB_ACTION + m_actionAddScriptGroup = m_actionGroup->add_action( + "AddScriptGroup", sigc::mem_fun(*this, &MainWindow::on_action_add_script_group) + ); + m_actionAddScript = m_actionGroup->add_action( + "AddScript", sigc::mem_fun(*this, &MainWindow::on_action_add_script) + ); + m_actionEditScript = m_actionGroup->add_action( + "EditScript", sigc::mem_fun(*this, &MainWindow::on_action_edit_script) + ); + m_actionRemoveScript = m_actionGroup->add_action( + "RemoveScript", sigc::mem_fun(*this, &MainWindow::on_action_remove_script) + ); +#else actionGroup->add( Gtk::Action::create("AddScriptGroup", _("Add _Group")), sigc::mem_fun(*this, &MainWindow::on_action_add_script_group) @@ -304,7 +717,407 @@ Gtk::Action::create("RemoveScript", Gtk::Stock::REMOVE), sigc::mem_fun(*this, &MainWindow::on_action_remove_script) ); +#endif +#if USE_GTKMM_BUILDER + insert_action_group("AppMenu", m_actionGroup); + + m_uiManager = Gtk::Builder::create(); + Glib::ustring ui_info = + "" + " " + " " + " _File" + "
" + " " + " New" + " AppMenu.New" + " " + " " + " Open" + " AppMenu.Open" + " " + "
" + "
" + " " + " Save" + " AppMenu.Save" + " " + " " + " Save As" + " AppMenu.SaveAs" + " " + "
" + "
" + " " + " Properties" + " AppMenu.Properties" + " " + "
" + "
" + " " + " Quit" + " AppMenu.Quit" + " " + "
" + "
" + " " + " Edit" + "
" + " " + " Copy Dimension Region" + " AppMenu.CopyDimRgn" + " " + " " + " Adjust Clipboard" + " AppMenu.AdjustClipboard" + " " + " " + " Paste Dimension Region" + " AppMenu.PasteDimRgn" + " " + "
" + " " + " Previous Instrument" + " AppMenu.SelectPrevInstr" + " " + " " + " Next Instrument" + " AppMenu.SelectNextInstr" + " " + "
" + " " + " Previous Region" + " AppMenu.SelectPrevRegion" + " " + " " + " Next Region" + " AppMenu.SelectNextRegion" + " " + "
" + " " + " Previous Dimension" + " AppMenu.SelectPrevDimension" + " " + " " + " Next Dimension" + " AppMenu.SelectNextDimension" + " " + " " + " Previous Dimension Region Zone" + " AppMenu.SelectPrevDimRgnZone" + " " + " " + " Next Dimension Region Zone" + " AppMenu.SelectNextDimRgnZone" + " " + " " + " Add Previous Dimension Region Zone" + " AppMenu.SelectAddPrevDimRgnZone" + " " + " " + " Add Next Dimension Region Zone" + " AppMenu.SelectAddNextDimRgnZone" + " " + "
" + " " + " Copy Sample Unity" + " AppMenu.CopySampleUnity" + " " + " " + " Copy Sample Tune" + " AppMenu.CopySampleTune" + " " + " " + " Copy Sample Loop" + " AppMenu.CopySampleLoop" + " " + "
" + "
" + " " + " Macro" + "
" + "
" + "
" + " " + " Sample" + "
" + " " + " Properties" + " AppMenu.SampleProperties" + " " + " " + " Add Group" + " AppMenu.AddGroup" + " " + " " + " Add Sample" + " AppMenu.AddSample" + " " + " " + " Show Sample References" + " AppMenu.ShowSampleRefs" + " " + " " + " Replace Sample" + " AppMenu.ReplaceSample" + " " + " " + " Replace all Samples in all Groups" + " AppMenu.ReplaceAllSamplesInAllGroups" + " " + "
" + "
" + " " + " Remove Sample" + " AppMenu.RemoveSample" + " " + " " + " Remove unused Samples" + " AppMenu.RemoveUnusedSamples" + " " + "
" + "
" + " " + " Instrument" + "
" + " " + " Properties" + " AppMenu.InstrProperties" + " " + " " + " MIDI Rules" + " AppMenu.MidiRules" + " " + " " + " Script Slots" + " AppMenu.ScriptSlots" + " " + "
" + " " + " Assign Scripts" + " " + "
" + " " + " Add Instrument" + " AppMenu.AddInstrument" + " " + " " + " Duplicate Instrument" + " AppMenu.DupInstrument" + " " + " " + " Move Instrument To ..." + " AppMenu.MoveInstrument" + " " + " " + " Combine Instrument" + " AppMenu.CombInstruments" + " " + "
" + "
" + " " + " Remove Instrument" + " AppMenu.RemoveInstrument" + " " + "
" + "
" + " " + " Script" + "
" + " " + " Add Script Group" + " AppMenu.AddScriptGroup" + " " + " " + " Add Script" + " AppMenu.AddScript" + " " + " " + " Edit Script" + " AppMenu.EditScript" + " " + "
" + "
" + " " + " Remove Script" + " AppMenu.RemoveScript" + " " + "
" + "
" + " " + " View" + "
" + " " + " Statusbar" + " AppMenu.Statusbar" + " " + " " + " Tooltips for Beginners" + " AppMenu.ShowTooltips" + " " + " " + " Auto restore Window Dimensions" + " AppMenu.AutoRestoreWinDim" + " " + " " + " Instrument Properties by Double Click" + " AppMenu.OpenInstrPropsByDoubleClick" + " " + "
" + "
" + " " + " Refresh All" + " AppMenu.RefreshAll" + " " + "
" + "
" + " " + " Tools" + "
" + " " + " Combine Instruments ..." + " AppMenu.CombineInstruments" + " " + " " + " Merge Files ..." + " AppMenu.MergeFiles" + " " + "
" + "
" + " " + " Settings" + "
" + " " + " Warning on Format Extensions" + " AppMenu.WarnUserOnExtensions" + " " + " " + " Synchronize Sampler Selection" + " AppMenu.SyncSamplerInstrumentSelection" + " " + " " + " Move Root Note with Region moved" + " AppMenu.MoveRootNoteWithRegionMoved" + " " + " " + " Save with temporary file" + " AppMenu.SaveWithTemporaryFile" + " " + "
" + "
" + " " + " Help" + "
" + " " + " About ..." + " AppMenu.About" + " " + "
" + "
" + "
" + // popups + " " + "
" + " " + " Instrument Properties" + " AppMenu.InstrProperties" + " " + " " + " MIDI Rules" + " AppMenu.MidiRules" + " " + " " + " Script Slots" + " AppMenu.ScriptSlots" + " " + " " + " Add Instrument" + " AppMenu.AddInstrument" + " " + " " + " Duplicate Instrument" + " AppMenu.DupInstrument" + " " + " " + " Move Instrument To ..." + " AppMenu.MoveInstrument" + " " + " " + " Combine Instruments" + " AppMenu.CombInstruments" + " " + "
" + "
" + " " + " Remove Instruments" + " AppMenu.RemoveInstrument" + " " + "
" + "
" + " " + "
" + " " + " Sample Properties" + " AppMenu.SampleProperties" + " " + " " + " Add Sample Group" + " AppMenu.AddGroup" + " " + " " + " Add Sample" + " AppMenu.AddSample" + " " + " " + " Show Sample References ..." + " AppMenu.ShowSampleRefs" + " " + " " + " Replace Sample" + " AppMenu.ReplaceSample" + " " + " " + " Replace all Samples ..." + " AppMenu.ReplaceAllSamplesInAllGroups" + " " + "
" + "
" + " " + " Remove Sample" + " AppMenu.RemoveSample" + " " + " " + " Remove unused Samples" + " AppMenu.RemoveUnusedSamples" + " " + "
" + "
" + " " + "
" + " " + " Add Script Group" + " AppMenu.AddScriptGroup" + " " + " " + " Add Script" + " AppMenu.AddScript" + " " + " " + " Edit Script" + " AppMenu.EditScript" + " " + "
" + "
" + " " + " Remove Script" + " AppMenu.RemoveScript" + " " + "
" + "
" + "
"; + m_uiManager->add_from_string(ui_info); +#else uiManager = Gtk::UIManager::create(); uiManager->insert_action_group(actionGroup); add_accel_group(uiManager->get_accel_group()); @@ -324,10 +1137,29 @@ " " " " " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " " " " " " " + " " + " " " " " " " " @@ -346,8 +1178,11 @@ " " " " " " + " " " " " " + " " + " " " " " " " " @@ -360,6 +1195,9 @@ " " " " " " + " " + " " + " " " " " " " " @@ -371,6 +1209,7 @@ " " " " " " + " " " " " " " " @@ -382,6 +1221,8 @@ " " " " " " + " " + " " " " " " " " @@ -405,7 +1246,25 @@ " " ""; uiManager->add_ui_from_string(ui_info); +#endif +#if USE_GTKMM_BUILDER + popup_menu = new Gtk::Menu( + Glib::RefPtr::cast_dynamic( + m_uiManager->get_object("PopupMenu") + ) + ); + sample_popup = new Gtk::Menu( + Glib::RefPtr::cast_dynamic( + m_uiManager->get_object("SamplePopupMenu") + ) + ); + script_popup = new Gtk::Menu( + Glib::RefPtr::cast_dynamic( + m_uiManager->get_object("ScriptPopupMenu") + ) + ); +#else popup_menu = dynamic_cast(uiManager->get_widget("/PopupMenu")); // Set tooltips for menu items (for some reason, setting a tooltip on the @@ -425,7 +1284,7 @@ { Gtk::MenuItem* item = dynamic_cast( uiManager->get_widget("/MenuBar/MenuEdit/CopySampleLoop")); - item->set_tooltip_text(_("Used when dragging a sample to a region's sample reference field. You may disable this for example if you want to replace an existing sample in a region with a new sample, but don't want that the region's current loop informations to be altered by this action.")); + item->set_tooltip_text(_("Used when dragging a sample to a region's sample reference field. You may disable this for example if you want to replace an existing sample in a region with a new sample, but don't want that the region's current loop information to be altered by this action.")); } { Gtk::MenuItem* item = dynamic_cast( @@ -440,7 +1299,7 @@ { 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.")); + item->set_tooltip_text(_("If checked, and when a region is moved by dragging it around on the virtual keyboard, the keyboard position dependent pitch will move exactly with the amount of semi tones the region was moved around.")); } { Gtk::MenuItem* item = dynamic_cast( @@ -458,6 +1317,16 @@ } { 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/MenuView/OpenInstrPropsByDoubleClick")); + item->set_tooltip_text(_("If checked, double clicking an instrument opens its properties dialog.")); + } + { + 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.")); } @@ -466,12 +1335,29 @@ uiManager->get_widget("/MenuBar/MenuTools/MergeFiles")); item->set_tooltip_text(_("Add instruments and samples of other .gig files to this .gig file.")); } +#endif - +#if USE_GTKMM_BUILDER + assign_scripts_menu = new Gtk::Menu( + Glib::RefPtr::cast_dynamic( + m_uiManager->get_object("AssignScripts") + ) + ); +#else instrument_menu = static_cast( uiManager->get_widget("/MenuBar/MenuInstrument/AllInstruments"))->get_submenu(); + assign_scripts_menu = static_cast( + uiManager->get_widget("/MenuBar/MenuInstrument/AssignScripts"))->get_submenu(); +#endif + +#if USE_GTKMM_BUILDER + Gtk::Widget* menuBar = NULL; + m_uiManager->get_widget("MenuBar", menuBar); +#else Gtk::Widget* menuBar = uiManager->get_widget("/MenuBar"); +#endif + m_VBox.pack_start(*menuBar, Gtk::PACK_SHRINK); m_VBox.pack_start(m_HPaned); m_VBox.pack_start(m_RegionChooser, Gtk::PACK_SHRINK); @@ -482,8 +1368,12 @@ set_file_is_shared(false); // Status Bar: +#if USE_GTKMM_BOX +# warning No status bar layout for GTKMM 4 yet +#else m_StatusBar.pack_start(m_AttachedStateLabel, Gtk::PACK_SHRINK); m_StatusBar.pack_start(m_AttachedStateImage, Gtk::PACK_SHRINK); +#endif m_StatusBar.show(); m_RegionChooser.signal_region_selected().connect( @@ -494,15 +1384,26 @@ // 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. Drag & drop to change the order of instruments.")); + m_refTreeModelFilter = Gtk::TreeModelFilter::create(m_refTreeModel); + m_refTreeModelFilter->set_visible_func( + sigc::mem_fun(*this, &MainWindow::instrument_row_visible) + ); + m_TreeView.set_model(m_refTreeModelFilter); + + m_TreeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); + m_TreeView.set_has_tooltip(true); + m_TreeView.signal_query_tooltip().connect( + sigc::mem_fun(*this, &MainWindow::onQueryTreeViewTooltip) + ); 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 @@ -525,6 +1426,7 @@ // 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); @@ -546,9 +1448,15 @@ ); } m_TreeViewSamples.set_headers_visible(true); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + m_TreeViewSamples.signal_button_press_event().connect( + sigc::mem_fun(*this, &MainWindow::on_sample_treeview_button_release) + ); +#else m_TreeViewSamples.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &MainWindow::on_sample_treeview_button_release) ); +#endif m_refSamplesTreeModel->signal_row_changed().connect( sigc::mem_fun(*this, &MainWindow::sample_name_changed) ); @@ -565,9 +1473,15 @@ // m_TreeViewScripts.set_reorderable(); m_TreeViewScripts.append_column_editable("Samples", m_ScriptsModel.m_col_name); m_TreeViewScripts.set_headers_visible(false); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + m_TreeViewScripts.signal_button_press_event().connect( + sigc::mem_fun(*this, &MainWindow::on_script_treeview_button_release) + ); +#else m_TreeViewScripts.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &MainWindow::on_script_treeview_button_release) ); +#endif //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) @@ -611,7 +1525,9 @@ sigc::mem_fun(*this, &MainWindow::file_changed)); instrumentProps.signal_changed().connect( sigc::mem_fun(*this, &MainWindow::file_changed)); - propDialog.signal_changed().connect( + sampleProps.signal_changed().connect( + sigc::mem_fun(*this, &MainWindow::file_changed)); + fileProps.signal_changed().connect( sigc::mem_fun(*this, &MainWindow::file_changed)); midiRules.signal_changed().connect( sigc::mem_fun(*this, &MainWindow::file_changed)); @@ -633,11 +1549,44 @@ sigc::mem_fun(*this, &MainWindow::select_sample) ); + dimreg_edit.editScriptSlotsButton.signal_clicked().connect( + sigc::mem_fun(*this, &MainWindow::show_script_slots) + ); + // simply sending the same signal (pair) to the sampler on 'patch' variable + // changes as the already existing signal (pair) when the user edits the + // script's source code, because the sampler would reload the source code + // and the 'patch' variables from the instrument on this signal anyway + dimreg_edit.scriptVars.signal_vars_to_be_changed.connect( + [this](gig::Instrument* instr) { + for (int i = 0; i < instr->ScriptSlotCount(); ++i) { + gig::Script* script = instr->GetScriptOfSlot(i); + signal_script_to_be_changed.emit(script); + } + } + ); + dimreg_edit.scriptVars.signal_vars_changed.connect( + [this](gig::Instrument* instr) { + for (int i = 0; i < instr->ScriptSlotCount(); ++i) { + gig::Script* script = instr->GetScriptOfSlot(i); + signal_script_changed.emit(script); + } + } + ); + dimreg_edit.scriptVars.signal_edit_script.connect( + [this](gig::Script* script) { + editScript(script); + } + ); + m_RegionChooser.signal_instrument_struct_to_be_changed().connect( sigc::hide( sigc::bind( file_structure_to_be_changed_signal.make_slot(), +#if SIGCXX_MAJOR_VERSION > 2 || (SIGCXX_MAJOR_VERSION == 2 && SIGCXX_MINOR_VERSION >= 8) + std::ref(this->file) +#else sigc::ref(this->file) +#endif ) ) ); @@ -645,7 +1594,11 @@ sigc::hide( sigc::bind( file_structure_changed_signal.make_slot(), +#if SIGCXX_MAJOR_VERSION > 2 || (SIGCXX_MAJOR_VERSION == 2 && SIGCXX_MINOR_VERSION >= 8) + std::ref(this->file) +#else sigc::ref(this->file) +#endif ) ) ); @@ -666,23 +1619,233 @@ dimreg_stereo.signal_toggled().connect( sigc::mem_fun(*this, &MainWindow::update_dimregs)); + m_searchText.signal_changed().connect( + sigc::mem_fun(*m_refTreeModelFilter.operator->(), &Gtk::TreeModelFilter::refilter) + ); + file = 0; file_is_changed = false; +#if HAS_GTKMM_SHOW_ALL_CHILDREN show_all_children(); +#endif // start with a new gig file by default on_action_file_new(); + m_TreeViewNotebook.signal_switch_page().connect( + sigc::mem_fun(*this, &MainWindow::on_notebook_tab_switched) + ); + // 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 + { +#if USE_GTKMM_BUILDER + menuMacro = new Gtk::Menu( + Glib::RefPtr::cast_dynamic( + m_uiManager->get_object("MenuMacro") + ) + ); +#else + Gtk::Menu* menuMacro = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuMacro") + )->get_submenu(); +#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("/macro_12", GDK_KEY_F13, noModifier); + Gtk::AccelMap::add_entry("/macro_13", GDK_KEY_F14, noModifier); + Gtk::AccelMap::add_entry("/macro_14", GDK_KEY_F15, noModifier); + Gtk::AccelMap::add_entry("/macro_15", GDK_KEY_F16, noModifier); + Gtk::AccelMap::add_entry("/macro_16", GDK_KEY_F17, noModifier); + Gtk::AccelMap::add_entry("/macro_17", GDK_KEY_F18, noModifier); + Gtk::AccelMap::add_entry("/macro_18", GDK_KEY_F19, noModifier); + Gtk::AccelMap::add_entry("/SetupMacros", 'm', primaryModifierKey); + + Glib::RefPtr accelGroup = this->get_accel_group(); + menuMacro->set_accel_group(accelGroup); + + updateMacroMenu(); + } + + // setup "Assign Scripts" keyboard accelerators + { + Gtk::AccelMap::add_entry("/script_0", GDK_KEY_F1, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_1", GDK_KEY_F2, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_2", GDK_KEY_F3, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_3", GDK_KEY_F4, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_4", GDK_KEY_F5, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_5", GDK_KEY_F6, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_6", GDK_KEY_F7, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_7", GDK_KEY_F8, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_8", GDK_KEY_F9, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_9", GDK_KEY_F10, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_10", GDK_KEY_F11, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_11", GDK_KEY_F12, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_12", GDK_KEY_F13, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_13", GDK_KEY_F14, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_14", GDK_KEY_F15, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_15", GDK_KEY_F16, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_16", GDK_KEY_F17, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_17", GDK_KEY_F18, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/script_18", GDK_KEY_F19, Gdk::SHIFT_MASK); + Gtk::AccelMap::add_entry("/DropAllScriptSlots", GDK_KEY_BackSpace, Gdk::SHIFT_MASK); + + Glib::RefPtr accelGroup = this->get_accel_group(); + assign_scripts_menu->set_accel_group(accelGroup); + } + + on_show_tooltips_changed(); + + Glib::signal_idle().connect_once( + sigc::mem_fun(*this, &MainWindow::bringToFront), + 200 + ); } MainWindow::~MainWindow() { } +void MainWindow::bringToFront() { + #if defined(__APPLE__) + macRaiseAppWindow(); + #endif + raise(); + present(); +} + +void MainWindow::updateMacroMenu() { +#if !USE_GTKMM_BUILDER + Gtk::Menu* menuMacro = dynamic_cast( + uiManager->get_widget("/MenuBar/MenuMacro") + )->get_submenu(); +#endif + + // 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"); + } + +#if HAS_GTKMM_SHOW_ALL_CHILDREN + menuMacro->show_all_children(); +#endif +} + +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(); +} + +//NOTE: the actual signal's first argument for argument 'page' is on some gtkmm version GtkNotebookPage* and on some Gtk::Widget*. Since we don't need that argument, it is simply void* here for now. +void MainWindow::on_notebook_tab_switched(void* page, guint page_num) { + bool isInstrumentsPage = (page_num == 1); + // so far we only support filtering for the instruments list, so hide the + // filter text entry field if another tab is selected + m_searchField.set_visible(isInstrumentsPage); +} + bool MainWindow::on_delete_event(GdkEventAny* event) { return !file_is_shared && file_is_changed && !close_confirmation_dialog(); @@ -702,8 +1865,10 @@ 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; + //NOTE: was const_iterator before, which did not compile with GTKMM4 development branch, probably going to be fixed before final GTKMM4 release though. + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[0]); if (it) { Gtk::TreeModel::Row row = *it; instrument = row[m_Columns.m_col_instr]; @@ -746,6 +1911,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() @@ -762,50 +1934,81 @@ void MainWindow::on_sel_change() { +#if !USE_GTKMM_BUILDER // 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(); + } } +#endif + + updateScriptListOfMenu(); - m_RegionChooser.set_instrument(get_instrument()); + gig::Instrument* instr = get_instrument(); + + m_RegionChooser.set_instrument(instr); + dimreg_edit.scriptVars.setInstrument(instr, true/*force update*/); if (Settings::singleton()->syncSamplerInstrumentSelection) { switch_sampler_instrument_signal.emit(get_instrument()); } } + +LoaderSaverBase::LoaderSaverBase(const Glib::ustring filename, gig::File* gig) : + filename(filename), gig(gig), +#ifdef GLIB_THREADS + thread(0), +#endif + progress(0.f) +{ +} + void loader_progress_callback(gig::progress_t* progress) { - Loader* loader = static_cast(progress->custom); + LoaderSaverBase* loader = static_cast(progress->custom); loader->progress_callback(progress->factor); } -void Loader::progress_callback(float fraction) +void LoaderSaverBase::progress_callback(float fraction) { { +#ifdef GLIB_THREADS Glib::Threads::Mutex::Lock lock(progressMutex); +#else + std::lock_guard lock(progressMutex); +#endif progress = fraction; } progress_dispatcher(); } -void Loader::thread_function() +#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 LoaderSaverBase::thread_function() { - printf("thread_function self=%x\n", Glib::Threads::Thread::self()); +#ifdef GLIB_THREADS + printf("thread_function self=%p\n", + static_cast(Glib::Threads::Thread::self())); +#else + std::cout << "thread_function self=" << std::this_thread::get_id() << "\n"; +#endif printf("Start %s\n", filename.c_str()); try { - RIFF::File* riff = new RIFF::File(filename); - gig = new gig::File(riff); gig::progress_t progress; progress.callback = loader_progress_callback; progress.custom = this; - gig->GetInstrument(0, &progress); + thread_function_sub(progress); printf("End\n"); finished_dispatcher(); } catch (RIFF::Exception e) { @@ -817,133 +2020,123 @@ } } -Loader::Loader(const char* filename) - : filename(filename), thread(0), progress(0.f) -{ -} - -void Loader::launch() +void LoaderSaverBase::launch() { +#ifdef GLIB_THREADS #ifdef OLD_THREADS - thread = Glib::Thread::create(sigc::mem_fun(*this, &Loader::thread_function), true); + thread = Glib::Thread::create(sigc::mem_fun(*this, &LoaderSaverBase::thread_function), true); +#else + thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &LoaderSaverBase::thread_function)); +#endif + printf("launch thread=%p\n", static_cast(thread)); #else - thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &Loader::thread_function)); + thread = std::thread([this](){ thread_function(); }); + std::cout << "launch thread=" << thread.get_id() << "\n"; #endif - printf("launch thread=%x\n", thread); } -float Loader::get_progress() +float LoaderSaverBase::get_progress() { - float res; - { - Glib::Threads::Mutex::Lock lock(progressMutex); - res = progress; - } - return res; +#ifdef GLIB_THREADS + Glib::Threads::Mutex::Lock lock(progressMutex); +#else + std::lock_guard lock(progressMutex); +#endif + return progress; } -Glib::Dispatcher& Loader::signal_progress() +Glib::Dispatcher& LoaderSaverBase::signal_progress() { return progress_dispatcher; } -Glib::Dispatcher& Loader::signal_finished() +Glib::Dispatcher& LoaderSaverBase::signal_finished() { return finished_dispatcher; } -Glib::Dispatcher& Loader::signal_error() +Glib::Dispatcher& LoaderSaverBase::signal_error() { return error_dispatcher; } -void saver_progress_callback(gig::progress_t* progress) -{ - Saver* saver = static_cast(progress->custom); - saver->progress_callback(progress->factor); +void LoaderSaverBase::join() { +#ifdef GLIB_THREADS + thread->join(); +#else + thread.join(); +#endif } -void Saver::progress_callback(float fraction) + +Loader::Loader(const char* filename) : + LoaderSaverBase(filename, 0) { - { - Glib::Threads::Mutex::Lock lock(progressMutex); - progress = fraction; - } - progress_dispatcher.emit(); } -void Saver::thread_function() +void Loader::thread_function_sub(gig::progress_t& progress) { - printf("thread_function self=%x\n", Glib::Threads::Thread::self()); - printf("Start %s\n", filename.c_str()); - try { - gig::progress_t progress; - progress.callback = saver_progress_callback; - progress.custom = this; - - // if no filename was provided, that means "save", if filename was provided means "save as" - if (filename.empty()) { - gig->Save(&progress); - } else { - gig->Save(filename, &progress); - } + RIFF::File* riff = new RIFF::File(filename); + gig = new gig::File(riff); - printf("End\n"); - finished_dispatcher.emit(); - } catch (RIFF::Exception e) { - error_message = e.Message; - error_dispatcher.emit(); - } catch (...) { - error_message = _("Unknown exception occurred"); - error_dispatcher.emit(); - } + gig->GetInstrument(0, &progress); } -Saver::Saver(gig::File* file, Glib::ustring filename) - : gig(file), filename(filename), thread(0), progress(0.f) + +Saver::Saver(gig::File* file, Glib::ustring filename) : + LoaderSaverBase(filename, file) { } -void Saver::launch() +void Saver::thread_function_sub(gig::progress_t& progress) { -#ifdef OLD_THREADS - thread = Glib::Thread::create(sigc::mem_fun(*this, &Saver::thread_function), true); + // if no filename was provided, that means "save", if filename was provided means "save as" + if (filename.empty()) { + 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 - thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &Saver::thread_function)); + throw RIFF::Exception("Could not replace original file with temporary file (unable to rename temp file): " + gig::String(strerror(errno))); #endif - printf("launch thread=%x\n", thread); -} - -float Saver::get_progress() -{ - float res; - { - Glib::Threads::Mutex::Lock lock(progressMutex); - res = progress; + } + } + } else { + gig->Save(filename, &progress); } - return res; } -Glib::Dispatcher& Saver::signal_progress() -{ - return progress_dispatcher; -} - -Glib::Dispatcher& Saver::signal_finished() -{ - return finished_dispatcher; -} - -Glib::Dispatcher& Saver::signal_error() -{ - return error_dispatcher; -} ProgressDialog::ProgressDialog(const Glib::ustring& title, Gtk::Window& parent) : Gtk::Dialog(title, parent, true) { +#if USE_GTKMM_BOX + get_content_area()->pack_start(progressBar); +#else get_vbox()->pack_start(progressBar); +#endif +#if HAS_GTKMM_SHOW_ALL_CHILDREN show_all_children(); +#endif resize(600,50); } @@ -956,10 +2149,12 @@ m_refTreeModel->clear(); m_refSamplesTreeModel->clear(); m_refScriptsTreeModel->clear(); +#if !USE_GTKMM_BUILDER // remove all entries from "Instrument" menu while (!instrument_menu->get_children().empty()) { remove_instrument_from_menu(0); } +#endif // free libgig's gig::File instance if (file && !file_is_shared) delete file; file = NULL; @@ -971,10 +2166,12 @@ m_refTreeModel->clear(); m_refSamplesTreeModel->clear(); m_refScriptsTreeModel->clear(); +#if !USE_GTKMM_BUILDER // remove all entries from "Instrument" menu while (!instrument_menu->get_children().empty()) { remove_instrument_from_menu(0); } +#endif if (!this->file) return; @@ -1008,8 +2205,13 @@ g_free(msg); dialog.set_secondary_text(_("If you close without saving, your changes will be lost.")); dialog.add_button(_("Close _Without Saving"), Gtk::RESPONSE_NO); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(file_has_name ? Gtk::Stock::SAVE : Gtk::Stock::SAVE_AS, Gtk::RESPONSE_YES); +#else + dialog.add_button(_("_OK"), Gtk::RESPONSE_OK); + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); +#endif dialog.set_default_response(Gtk::RESPONSE_YES); int response = dialog.run(); dialog.hide(); @@ -1040,7 +2242,11 @@ "used by the sampler until you tell the sampler explicitly to " "load it.")); dialog.add_button(_("_Yes, Detach"), Gtk::RESPONSE_YES); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); +#endif dialog.set_default_response(Gtk::RESPONSE_CANCEL); int response = dialog.run(); dialog.hide(); @@ -1054,8 +2260,13 @@ if (file_is_shared && !leaving_shared_mode_dialog()) return; Gtk::FileChooserDialog dialog(*this, _("Open file")); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + dialog.add_button(_("_Open"), Gtk::RESPONSE_OK); +#endif dialog.set_default_response(Gtk::RESPONSE_OK); #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 Gtk::FileFilter filter; @@ -1069,9 +2280,16 @@ dialog.set_current_folder(current_gig_dir); } if (dialog.run() == Gtk::RESPONSE_OK) { + dialog.hide(); 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()); +#ifdef GLIB_THREADS + printf("on_action_file_open self=%p\n", + static_cast(Glib::Threads::Thread::self())); +#else + std::cout << "on_action_file_open self=" << + std::this_thread::get_id() << "\n"; +#endif load_file(filename.c_str()); current_gig_dir = Glib::path_get_dirname(filename); } @@ -1086,7 +2304,9 @@ Glib::filename_display_basename(name) + "' ...", *this ); +#if HAS_GTKMM_SHOW_ALL_CHILDREN progress_dialog->show_all(); +#endif loader = new Loader(name); //FIXME: memory leak! loader->signal_progress().connect( sigc::mem_fun(*this, &MainWindow::on_loader_progress)); @@ -1120,12 +2340,14 @@ // make sure the selected item in the "instruments" tree view is // visible (scroll to it) m_TreeView.scroll_to_row(Gtk::TreePath(ToString(i))); +#if !USE_GTKMM_BUILDER // select item in instrument menu { const std::vector children = instrument_menu->get_children(); static_cast(children[i])->set_active(); } +#endif // update region chooser and dimension region chooser m_RegionChooser.set_instrument(instr); break; @@ -1140,14 +2362,22 @@ void MainWindow::on_loader_finished() { + loader->join(); printf("Loader finished!\n"); - printf("on_loader_finished self=%x\n", Glib::Threads::Thread::self()); +#ifdef GLIB_THREADS + printf("on_loader_finished self=%p\n", + static_cast(Glib::Threads::Thread::self())); +#else + std::cout << "on_loader_finished self=" << + std::this_thread::get_id() << "\n"; +#endif load_gig(loader->gig, loader->filename.c_str()); progress_dialog->hide(); } void MainWindow::on_loader_error() { + loader->join(); Glib::ustring txt = _("Could not load file: ") + loader->error_message; Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); @@ -1196,7 +2426,9 @@ Glib::filename_display_basename(this->filename) + "' ...", *this ); +#if HAS_GTKMM_SHOW_ALL_CHILDREN progress_dialog->show_all(); +#endif saver = new Saver(this->file); //FIXME: memory leak! saver->signal_progress().connect( sigc::mem_fun(*this, &MainWindow::on_saver_progress)); @@ -1216,6 +2448,7 @@ void MainWindow::on_saver_error() { + saver->join(); file_structure_changed_signal.emit(this->file); Glib::ustring txt = _("Could not save file: ") + saver->error_message; Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); @@ -1224,6 +2457,7 @@ void MainWindow::on_saver_finished() { + saver->join(); this->file = saver->gig; this->filename = saver->filename; current_gig_dir = Glib::path_get_dirname(filename); @@ -1249,8 +2483,13 @@ bool MainWindow::file_save_as() { Gtk::FileChooserDialog dialog(*this, _("Save as"), Gtk::FILE_CHOOSER_ACTION_SAVE); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + dialog.add_button(_("_Save"), Gtk::RESPONSE_OK); +#endif dialog.set_default_response(Gtk::RESPONSE_OK); dialog.set_do_overwrite_confirmation(); @@ -1279,9 +2518,11 @@ } // show warning in the dialog - Gtk::HBox descriptionArea; + 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; @@ -1298,10 +2539,17 @@ "same .gig file will end up in corrupted sample wave data!\n") ); descriptionArea.pack_start(description); +#if USE_GTKMM_BOX + dialog.get_content_area()->pack_start(descriptionArea, Gtk::PACK_SHRINK); +#else dialog.get_vbox()->pack_start(descriptionArea, Gtk::PACK_SHRINK); +#endif +#if HAS_GTKMM_SHOW_ALL_CHILDREN descriptionArea.show_all(); +#endif if (dialog.run() == Gtk::RESPONSE_OK) { + dialog.hide(); std::string filename = dialog.get_filename(); if (!Glib::str_has_suffix(filename, ".gig")) { filename += ".gig"; @@ -1313,7 +2561,9 @@ Glib::filename_display_basename(filename) + "' ...", *this ); +#if HAS_GTKMM_SHOW_ALL_CHILDREN progress_dialog->show_all(); +#endif saver = new Saver(file, filename); //FIXME: memory leak! saver->signal_progress().connect( @@ -1333,13 +2583,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")); @@ -1362,6 +2612,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: { @@ -1371,7 +2624,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; @@ -1391,7 +2644,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; @@ -1402,16 +2655,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; } } @@ -1425,8 +2678,8 @@ void MainWindow::on_action_file_properties() { - propDialog.show(); - propDialog.deiconify(); + fileProps.show(); + fileProps.deiconify(); } void MainWindow::on_action_warn_user_on_extensions() { @@ -1434,6 +2687,30 @@ !Settings::singleton()->warnUserOnExtensions; } +void MainWindow::on_action_show_tooltips() { + Settings::singleton()->showTooltips = + !Settings::singleton()->showTooltips; + + on_show_tooltips_changed(); +} + +void MainWindow::on_show_tooltips_changed() { + const bool b = Settings::singleton()->showTooltips; + + dimreg_label.set_has_tooltip(b); + dimreg_all_regions.set_has_tooltip(b); + dimreg_all_dimregs.set_has_tooltip(b); + dimreg_stereo.set_has_tooltip(b); + + // Not doing this here, we let onQueryTreeViewTooltip() handle this per cell + //m_TreeView.set_has_tooltip(b); + + m_TreeViewSamples.set_has_tooltip(b); + m_TreeViewScripts.set_has_tooltip(b); + + set_has_tooltip(b); +} + void MainWindow::on_action_sync_sampler_instrument_selection() { Settings::singleton()->syncSamplerInstrumentSelection = !Settings::singleton()->syncSamplerInstrumentSelection; @@ -1453,7 +2730,7 @@ dialog.set_name("Gigedit"); #endif dialog.set_version(VERSION); - dialog.set_copyright("Copyright (C) 2006-2015 Andreas Persson"); + dialog.set_copyright("Copyright (C) 2006-2019 Andreas Persson"); const std::string sComment = _("Built " __DATE__ "\nUsing ") + ::gig::libraryName() + " " + ::gig::libraryVersion() + "\n\n" + @@ -1464,15 +2741,16 @@ "backup your Gigasampler/GigaStudio files before editing them with " "this application.\n" "\n" - "Please report bugs to: http://bugs.linuxsampler.org" + "Please report bugs to: https://bugs.linuxsampler.org" ); dialog.set_comments(sComment.c_str()); - dialog.set_website("http://www.linuxsampler.org"); - dialog.set_website_label("http://www.linuxsampler.org"); + dialog.set_website("https://www.linuxsampler.org"); + dialog.set_website_label("https://www.linuxsampler.org"); + dialog.set_position(Gtk::WIN_POS_CENTER); dialog.run(); } -PropDialog::PropDialog() +FilePropDialog::FilePropDialog() : eFileFormat(_("File Format")), eName(_("Name")), eCreationDate(_("Creation date")), @@ -1490,13 +2768,23 @@ eSourceForm(_("Source form")), eCommissioned(_("Commissioned")), eSubject(_("Subject")), +#if HAS_GTKMM_STOCK quitButton(Gtk::Stock::CLOSE), +#else + quitButton(_("_Close")), +#endif 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); + connect(eFileFormat, &FilePropDialog::set_FileFormat); connect(eName, &DLS::Info::Name); connect(eCreationDate, &DLS::Info::CreationDate); connect(eComments, &DLS::Info::Comments); @@ -1532,30 +2820,44 @@ table.add(eCommissioned); table.add(eSubject); +#if USE_GTKMM_GRID + table.set_column_spacing(5); +#else table.set_col_spacings(5); +#endif + add(vbox); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + table.set_margin(5); +#else table.set_border_width(5); +#endif vbox.add(table); vbox.pack_start(buttonBox, Gtk::PACK_SHRINK); buttonBox.set_layout(Gtk::BUTTONBOX_END); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + buttonBox.set_margin(5); +#else buttonBox.set_border_width(5); +#endif buttonBox.show(); buttonBox.pack_start(quitButton); quitButton.set_can_default(); quitButton.grab_focus(); quitButton.signal_clicked().connect( - sigc::mem_fun(*this, &PropDialog::hide)); - eFileFormat.signal_value_changed().connect( - sigc::mem_fun(*this, &PropDialog::onFileFormatChanged)); + sigc::mem_fun(*this, &FilePropDialog::hide)); quitButton.show(); vbox.show(); +#if HAS_GTKMM_SHOW_ALL_CHILDREN show_all_children(); +#endif } -void PropDialog::set_file(gig::File* file) +void FilePropDialog::set_file(gig::File* file) { m_file = file; + update(file->pInfo); // update file format version combo box const std::string sGiga = "Gigasampler/GigaStudio v"; @@ -1563,25 +2865,24 @@ std::vector txts; std::vector values; txts.push_back(sGiga + "2"); values.push_back(2); - txts.push_back(sGiga + "3/v4"); values.push_back(3); - if (major != 2 && major != 3) { + txts.push_back(sGiga + "3"); values.push_back(3); + txts.push_back(sGiga + "4"); values.push_back(4); + if (major < 2 || major > 4) { txts.push_back(sGiga + ToString(major)); values.push_back(major); } std::vector texts; for (int i = 0; i < txts.size(); ++i) texts.push_back(txts[i].c_str()); texts.push_back(NULL); values.push_back(0); + + update_model++; eFileFormat.set_choices(&texts[0], &values[0]); eFileFormat.set_value(major); + update_model--; } -void PropDialog::onFileFormatChanged() { - const int major = eFileFormat.get_value(); - if (m_file) m_file->pVersion->major = major; -} - -void PropDialog::set_info(DLS::Info* info) +void FilePropDialog::set_FileFormat(int value) { - update(info); + m_file->pVersion->major = value; } @@ -1613,23 +2914,51 @@ } InstrumentProps::InstrumentProps() : +#if HAS_GTKMM_STOCK quitButton(Gtk::Stock::CLOSE), +#else + quitButton(_("_Close")), +#endif table(2,1), eName(_("Name")), eIsDrum(_("Is drum")), eMIDIBank(_("MIDI bank"), 0, 16383), eMIDIProgram(_("MIDI program")), - eAttenuation(_("Attenuation"), 0, 96, 0, 1), - eGainPlus6(_("Gain +6dB"), eAttenuation, -6), + eAttenuation(_("Attenuation (dB)"), -96, +96, 0, 1), eEffectSend(_("Effect send"), 0, 65535), eFineTune(_("Fine tune"), -8400, 8400), - ePitchbendRange(_("Pitchbend range"), 0, 12), + ePitchbendRange(_("Pitchbend range (halftones)"), 0, 48), ePianoReleaseMode(_("Piano release mode")), eDimensionKeyRangeLow(_("Keyswitching range low")), - eDimensionKeyRangeHigh(_("Keyswitching range high")) -{ + eDimensionKeyRangeHigh(_("Keyswitching range high")), + table2(2,1), + eName2(_("Name")), + eCreationDate(_("Creation date")), + eComments(_("Comments")), + eProduct(_("Product")), + eCopyright(_("Copyright")), + eArtists(_("Artists")), + eGenre(_("Genre")), + eKeywords(_("Keywords")), + eEngineer(_("Engineer")), + eTechnician(_("Technician")), + eSoftware(_("Software")), + eMedium(_("Medium")), + eSource(_("Source")), + eSourceForm(_("Source form")), + eCommissioned(_("Commissioned")), + eSubject(_("Subject")) +{ + if (!Settings::singleton()->autoRestoreWindowDimension) { + //set_default_size(470, 390); + set_position(Gtk::WIN_POS_MOUSE); + } + set_title(_("Instrument Properties")); + tabs.append_page(vbox[1], _("Settings")); + tabs.append_page(vbox[2], _("Info")); + eDimensionKeyRangeLow.set_tip( _("start of the keyboard area which should switch the " "\"keyswitching\" dimension") @@ -1644,7 +2973,6 @@ connect(eMIDIBank, &InstrumentProps::set_MIDIBank); connect(eMIDIProgram, &InstrumentProps::set_MIDIProgram); connect(eAttenuation, &gig::Instrument::Attenuation); - connect(eGainPlus6, &gig::Instrument::Attenuation); connect(eEffectSend, &gig::Instrument::EffectSend); connect(eFineTune, &gig::Instrument::FineTune); connect(ePitchbendRange, &gig::Instrument::PitchbendRange); @@ -1654,14 +2982,64 @@ eName.signal_value_changed().connect(sig_name_changed.make_slot()); + connect(eName2, &InstrumentProps::set_Name); + connectLambda(eCreationDate, [this](gig::String s) { + m->pInfo->CreationDate = s; + }); + connectLambda(eComments, [this](gig::String s) { + m->pInfo->Comments = s; + }); + connectLambda(eProduct, [this](gig::String s) { + m->pInfo->Product = s; + }); + connectLambda(eCopyright, [this](gig::String s) { + m->pInfo->Copyright = s; + }); + connectLambda(eArtists, [this](gig::String s) { + m->pInfo->Artists = s; + }); + connectLambda(eGenre, [this](gig::String s) { + m->pInfo->Genre = s; + }); + connectLambda(eKeywords, [this](gig::String s) { + m->pInfo->Keywords = s; + }); + connectLambda(eEngineer, [this](gig::String s) { + m->pInfo->Engineer = s; + }); + connectLambda(eTechnician, [this](gig::String s) { + m->pInfo->Technician = s; + }); + connectLambda(eSoftware, [this](gig::String s) { + m->pInfo->Software = s; + }); + connectLambda(eMedium, [this](gig::String s) { + m->pInfo->Medium = s; + }); + connectLambda(eSource, [this](gig::String s) { + m->pInfo->Source = s; + }); + connectLambda(eSourceForm, [this](gig::String s) { + m->pInfo->SourceForm = s; + }); + connectLambda(eCommissioned, [this](gig::String s) { + m->pInfo->Commissioned = s; + }); + connectLambda(eSubject, [this](gig::String s) { + m->pInfo->Subject = s; + }); + + // tab 1 +#if USE_GTKMM_GRID + table.set_column_spacing(5); +#else table.set_col_spacings(5); - +#endif table.add(eName); table.add(eIsDrum); table.add(eMIDIBank); table.add(eMIDIProgram); table.add(eAttenuation); - table.add(eGainPlus6); table.add(eEffectSend); table.add(eFineTune); table.add(ePitchbendRange); @@ -1669,13 +3047,47 @@ table.add(eDimensionKeyRangeLow); table.add(eDimensionKeyRangeHigh); - add(vbox); + // tab 2 +#if USE_GTKMM_GRID + table2.set_column_spacing(5); +#else + table2.set_col_spacings(5); +#endif + table2.add(eName2); + table2.add(eCreationDate); + table2.add(eComments); + table2.add(eProduct); + table2.add(eCopyright); + table2.add(eArtists); + table2.add(eGenre); + table2.add(eKeywords); + table2.add(eEngineer); + table2.add(eTechnician); + table2.add(eSoftware); + table2.add(eMedium); + table2.add(eSource); + table2.add(eSourceForm); + table2.add(eCommissioned); + table2.add(eSubject); + + add(vbox[0]); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + table.set_margin(5); +#else table.set_border_width(5); - vbox.pack_start(table); +#endif + vbox[1].pack_start(table); + vbox[2].pack_start(table2); table.show(); - vbox.pack_start(buttonBox, Gtk::PACK_SHRINK); + table2.show(); + vbox[0].pack_start(tabs); + vbox[0].pack_start(buttonBox, Gtk::PACK_SHRINK); buttonBox.set_layout(Gtk::BUTTONBOX_END); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + buttonBox.set_margin(5); +#else buttonBox.set_border_width(5); +#endif buttonBox.show(); buttonBox.pack_start(quitButton); quitButton.set_can_default(); @@ -1685,8 +3097,10 @@ sigc::mem_fun(*this, &InstrumentProps::hide)); quitButton.show(); - vbox.show(); + vbox[0].show(); +#if HAS_GTKMM_SHOW_ALL_CHILDREN show_all_children(); +#endif } void InstrumentProps::set_instrument(gig::Instrument* instrument) @@ -1694,10 +3108,318 @@ update(instrument); update_model++; + + // tab 1 eName.set_value(instrument->pInfo->Name); eIsDrum.set_value(instrument->IsDrum); eMIDIBank.set_value(instrument->MIDIBank); eMIDIProgram.set_value(instrument->MIDIProgram); + // tab 2 + eName2.set_value(instrument->pInfo->Name); + eCreationDate.set_value(instrument->pInfo->CreationDate); + eComments.set_value(instrument->pInfo->Comments); + eProduct.set_value(instrument->pInfo->Product); + eCopyright.set_value(instrument->pInfo->Copyright); + eArtists.set_value(instrument->pInfo->Artists); + eGenre.set_value(instrument->pInfo->Genre); + eKeywords.set_value(instrument->pInfo->Keywords); + eEngineer.set_value(instrument->pInfo->Engineer); + eTechnician.set_value(instrument->pInfo->Technician); + eSoftware.set_value(instrument->pInfo->Software); + eMedium.set_value(instrument->pInfo->Medium); + eSource.set_value(instrument->pInfo->Source); + eSourceForm.set_value(instrument->pInfo->SourceForm); + eCommissioned.set_value(instrument->pInfo->Commissioned); + eSubject.set_value(instrument->pInfo->Subject); + + update_model--; +} + + +SampleProps::SampleProps() : +#if HAS_GTKMM_STOCK + quitButton(Gtk::Stock::CLOSE), +#else + quitButton(_("_Close")), +#endif + table(2,1), + eName(_("Name")), + eUnityNote(_("Unity Note")), + eSampleGroup(_("Sample Group")), + eSampleFormatInfo(_("Sample Format")), + eSampleID("Sample ID"), + eChecksum("Wave Data CRC-32"), + eLoopsCount(_("Loops"), 0, 1), // we might support more than 1 loop in future + eLoopStart(_("Loop start position"), 0, 9999999), + eLoopLength(_("Loop size"), 0, 9999999), + eLoopType(_("Loop type")), + eLoopPlayCount(_("Playback count")), + table2(2,1), + eName2(_("Name")), + eCreationDate(_("Creation date")), + eComments(_("Comments")), + eProduct(_("Product")), + eCopyright(_("Copyright")), + eArtists(_("Artists")), + eGenre(_("Genre")), + eKeywords(_("Keywords")), + eEngineer(_("Engineer")), + eTechnician(_("Technician")), + eSoftware(_("Software")), + eMedium(_("Medium")), + eSource(_("Source")), + eSourceForm(_("Source form")), + eCommissioned(_("Commissioned")), + eSubject(_("Subject")) +{ + if (!Settings::singleton()->autoRestoreWindowDimension) { + //set_default_size(470, 390); + set_position(Gtk::WIN_POS_MOUSE); + } + + set_title(_("Sample Properties")); + + tabs.append_page(vbox[1], _("Settings")); + tabs.append_page(vbox[2], _("Info")); + + connect(eName, &SampleProps::set_Name); + connect(eUnityNote, &gig::Sample::MIDIUnityNote); + connect(eLoopsCount, &gig::Sample::Loops); + connectLambda(eLoopStart, [this](uint32_t start){ + m->LoopStart = start; + m->LoopEnd = start + m->LoopSize; + }); + connectLambda(eLoopLength, [this](uint32_t length){ + m->LoopSize = length; + m->LoopEnd = m->LoopStart + length; + }); + { + const char* choices[] = { _("normal"), _("bidirectional"), _("backward"), 0 }; + static const gig::loop_type_t values[] = { + gig::loop_type_normal, + gig::loop_type_bidirectional, + gig::loop_type_backward + }; + eLoopType.set_choices(choices, values); + } + connect(eLoopType, &gig::Sample::LoopType); + connect(eLoopPlayCount, &gig::Sample::LoopPlayCount); + + eName.signal_value_changed().connect(sig_name_changed.make_slot()); + + connect(eName2, &SampleProps::set_Name); + connectLambda(eCreationDate, [this](gig::String s) { + m->pInfo->CreationDate = s; + }); + connectLambda(eComments, [this](gig::String s) { + m->pInfo->Comments = s; + }); + connectLambda(eProduct, [this](gig::String s) { + m->pInfo->Product = s; + }); + connectLambda(eCopyright, [this](gig::String s) { + m->pInfo->Copyright = s; + }); + connectLambda(eArtists, [this](gig::String s) { + m->pInfo->Artists = s; + }); + connectLambda(eGenre, [this](gig::String s) { + m->pInfo->Genre = s; + }); + connectLambda(eKeywords, [this](gig::String s) { + m->pInfo->Keywords = s; + }); + connectLambda(eEngineer, [this](gig::String s) { + m->pInfo->Engineer = s; + }); + connectLambda(eTechnician, [this](gig::String s) { + m->pInfo->Technician = s; + }); + connectLambda(eSoftware, [this](gig::String s) { + m->pInfo->Software = s; + }); + connectLambda(eMedium, [this](gig::String s) { + m->pInfo->Medium = s; + }); + connectLambda(eSource, [this](gig::String s) { + m->pInfo->Source = s; + }); + connectLambda(eSourceForm, [this](gig::String s) { + m->pInfo->SourceForm = s; + }); + connectLambda(eCommissioned, [this](gig::String s) { + m->pInfo->Commissioned = s; + }); + connectLambda(eSubject, [this](gig::String s) { + m->pInfo->Subject = s; + }); + + // tab 1 +#if USE_GTKMM_GRID + table.set_column_spacing(5); +#else + table.set_col_spacings(5); +#endif + table.add(eName); + table.add(eUnityNote); + table.add(eSampleGroup); + table.add(eSampleFormatInfo); + table.add(eSampleID); + table.add(eChecksum); + table.add(eLoopsCount); + table.add(eLoopStart); + table.add(eLoopLength); + table.add(eLoopType); + table.add(eLoopPlayCount); + + // tab 2 +#if USE_GTKMM_GRID + table2.set_column_spacing(5); +#else + table2.set_col_spacings(5); +#endif + table2.add(eName2); + table2.add(eCreationDate); + table2.add(eComments); + table2.add(eProduct); + table2.add(eCopyright); + table2.add(eArtists); + table2.add(eGenre); + table2.add(eKeywords); + table2.add(eEngineer); + table2.add(eTechnician); + table2.add(eSoftware); + table2.add(eMedium); + table2.add(eSource); + table2.add(eSourceForm); + table2.add(eCommissioned); + table2.add(eSubject); + + add(vbox[0]); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + table.set_margin(5); +#else + table.set_border_width(5); +#endif + vbox[1].pack_start(table); + vbox[2].pack_start(table2); + table.show(); + table2.show(); + vbox[0].pack_start(tabs); + vbox[0].pack_start(buttonBox, Gtk::PACK_SHRINK); + buttonBox.set_layout(Gtk::BUTTONBOX_END); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + buttonBox.set_margin(5); +#else + buttonBox.set_border_width(5); +#endif + buttonBox.show(); + buttonBox.pack_start(quitButton); + quitButton.set_can_default(); + quitButton.grab_focus(); + + quitButton.signal_clicked().connect( + sigc::mem_fun(*this, &SampleProps::hide)); + + quitButton.show(); + vbox[0].show(); +#if HAS_GTKMM_SHOW_ALL_CHILDREN + show_all_children(); +#endif +} + +void SampleProps::set_sample(gig::Sample* sample) +{ + update(sample); + + update_model++; + + // tab 1 + eName.set_value(sample->pInfo->Name); + eUnityNote.set_value(sample->MIDIUnityNote); + // show sample group name + { + Glib::ustring s = "---"; + if (sample && sample->GetGroup()) + s = sample->GetGroup()->Name; + eSampleGroup.text.set_text(s); + } + // assemble sample format info string + { + Glib::ustring s; + if (sample) { + switch (sample->Channels) { + case 1: s = _("Mono"); break; + case 2: s = _("Stereo"); break; + default: + s = ToString(sample->Channels) + _(" audio channels"); + break; + } + s += " " + ToString(sample->BitDepth) + " Bits"; + s += " " + ToString(sample->SamplesPerSecond/1000) + "." + + ToString((sample->SamplesPerSecond%1000)/100) + " kHz"; + } else { + s = _("No sample assigned to this dimension region."); + } + eSampleFormatInfo.text.set_text(s); + } + // generate sample's memory address pointer string + { + Glib::ustring s; + if (sample) { + char buf[64] = {}; + snprintf(buf, sizeof(buf), "%p", sample); + s = buf; + } else { + s = "---"; + } + eSampleID.text.set_text(s); + } + // generate raw wave form data CRC-32 checksum string + { + Glib::ustring s = "---"; + if (sample) { + char buf[64] = {}; + snprintf(buf, sizeof(buf), "%x", sample->GetWaveDataCRC32Checksum()); + s = buf; + } + eChecksum.text.set_text(s); + } + eLoopsCount.set_value(sample->Loops); + eLoopStart.set_value(sample->LoopStart); + eLoopLength.set_value(sample->LoopSize); + eLoopType.set_value(sample->LoopType); + eLoopPlayCount.set_value(sample->LoopPlayCount); + // tab 2 + eName2.set_value(sample->pInfo->Name); + eCreationDate.set_value(sample->pInfo->CreationDate); + eComments.set_value(sample->pInfo->Comments); + eProduct.set_value(sample->pInfo->Product); + eCopyright.set_value(sample->pInfo->Copyright); + eArtists.set_value(sample->pInfo->Artists); + eGenre.set_value(sample->pInfo->Genre); + eKeywords.set_value(sample->pInfo->Keywords); + eEngineer.set_value(sample->pInfo->Engineer); + eTechnician.set_value(sample->pInfo->Technician); + eSoftware.set_value(sample->pInfo->Software); + eMedium.set_value(sample->pInfo->Medium); + eSource.set_value(sample->pInfo->Source); + eSourceForm.set_value(sample->pInfo->SourceForm); + eCommissioned.set_value(sample->pInfo->Commissioned); + eSubject.set_value(sample->pInfo->Subject); + + update_model--; +} + +void SampleProps::set_Name(const gig::String& name) +{ + m->pInfo->Name = name; +} + +void SampleProps::update_name() +{ + update_model++; + eName.set_value(m->pInfo->Name); update_model--; } @@ -1731,6 +3453,55 @@ } } +bool MainWindow::onQueryTreeViewTooltip(int x, int y, bool keyboardTip, const Glib::RefPtr& tooltip) { + Gtk::TreeModel::iterator iter; + if (!m_TreeView.get_tooltip_context_iter(x, y, keyboardTip, iter)) { + return false; + } + Gtk::TreeModel::Path path(iter); + Gtk::TreeModel::Row row = *iter; + Gtk::TreeViewColumn* pointedColumn = NULL; + // resolve the precise table column the mouse points to + { + Gtk::TreeModel::Path path; // unused + int cellX, cellY; // unused + m_TreeView.get_path_at_pos(x, y, path, pointedColumn, cellX, cellY); + } + Gtk::TreeViewColumn* scriptsColumn = m_TreeView.get_column(2); + if (pointedColumn == scriptsColumn) { // mouse hovers scripts column ... + // show the script(s) assigned to the hovered instrument as tooltip + tooltip->set_markup( row[m_Columns.m_col_tooltip] ); + m_TreeView.set_tooltip_cell(tooltip, &path, scriptsColumn, NULL); + } else { + // if beginners' tooltips is disabled then don't show the following one + if (!Settings::singleton()->showTooltips) + return false; + // yeah, a beginners tooltip + tooltip->set_text(_( + "Right click here for actions on instruments & MIDI Rules. " + "Drag & drop to change the order of instruments." + )); + m_TreeView.set_tooltip_cell(tooltip, &path, pointedColumn, NULL); + } + return true; +} + +static Glib::ustring scriptTooltipFor(gig::Instrument* instrument, int index) { + Glib::ustring name(gig_to_utf8(instrument->pInfo->Name)); + const int iScriptSlots = instrument->ScriptSlotCount(); + Glib::ustring tooltip = "(" + ToString(index) + ") “" + name + "”\n\n"; + if (!iScriptSlots) + tooltip += "No script assigned"; + else { + for (int i = 0; i < iScriptSlots; ++i) { + tooltip += "• " + ToString(i+1) + ". Script: “" + + instrument->GetScriptOfSlot(i)->Name + "”"; + if (i + 1 < iScriptSlots) tooltip += "\n\n"; + } + } + return tooltip; +} + void MainWindow::load_gig(gig::File* gig, const char* filename, bool isSharedInstrument) { file = 0; @@ -1744,23 +3515,31 @@ file_has_name = filename; file_is_changed = false; - propDialog.set_file(gig); - propDialog.set_info(gig->pInfo); + fileProps.set_file(gig); 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) : ""; + row[m_Columns.m_col_tooltip] = scriptTooltipFor(instrument, index); +#if !USE_GTKMM_BUILDER add_instrument_to_menu(name); +#endif } instrument_name_connection.unblock(); +#if !USE_GTKMM_BUILDER uiManager->get_widget("/MenuBar/MenuInstrument/AllInstruments")->show(); +#endif updateSampleRefCountMap(gig); @@ -1826,8 +3605,13 @@ { 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; + } + //NOTE: was const_iterator before, which did not compile with GTKMM4 development branch, probably going to be fixed before final GTKMM4 release though. + Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(rows[0]); if (it) { Gtk::TreeModel::Row row = *it; gig::Instrument* instrument = row[m_Columns.m_col_instr]; @@ -1844,7 +3628,8 @@ } else { instrumentProps.hide(); } - return it; + //NOTE: explicit boolean cast required for GTKMM4 development branch here + return it ? true : false; } void MainWindow::show_instr_props() @@ -1863,7 +3648,64 @@ gig::Instrument* instrument = row[m_Columns.m_col_instr]; Glib::ustring gigname(gig_to_utf8(instrument->pInfo->Name)); if (gigname != name) { + Gtk::TreeModel::Path path(*it); + const int index = path[0]; row[m_Columns.m_col_name] = gigname; + row[m_Columns.m_col_tooltip] = scriptTooltipFor(instrument, index); + } +} + +bool MainWindow::sample_props_set_sample() +{ + sampleProps.signal_name_changed().clear(); + + std::vector rows = m_TreeViewSamples.get_selection()->get_selected_rows(); + if (rows.empty()) { + sampleProps.hide(); + return false; + } + //NOTE: was const_iterator before, which did not compile with GTKMM4 development branch, probably going to be fixed before final GTKMM4 release though. + Gtk::TreeModel::iterator it = m_refSamplesTreeModel->get_iter(rows[0]); + if (it) { + Gtk::TreeModel::Row row = *it; + gig::Sample* sample = row[m_SamplesModel.m_col_sample]; + + sampleProps.set_sample(sample); + + // make sure sample tree is updated when user changes the + // sample name in sample properties window + sampleProps.signal_name_changed().connect( + sigc::bind( + sigc::mem_fun(*this, + &MainWindow::sample_name_changed_by_sample_props + ), it + ) + ); + } else { + sampleProps.hide(); + } + //NOTE: explicit boolean cast required for GTKMM4 development branch here + return it ? true : false; +} + +void MainWindow::show_sample_props() +{ + if (sample_props_set_sample()) { + sampleProps.show(); + sampleProps.deiconify(); + } +} + +void MainWindow::sample_name_changed_by_sample_props(Gtk::TreeModel::iterator& it) +{ + Gtk::TreeModel::Row row = *it; + Glib::ustring name = row[m_SamplesModel.m_col_name]; + + gig::Sample* sample = row[m_SamplesModel.m_col_sample]; + Glib::ustring gigname(gig_to_utf8(sample->pInfo->Name)); + if (gigname != name) { + Gtk::TreeModel::Path path(*it); + row[m_SamplesModel.m_col_name] = gigname; } } @@ -1880,8 +3722,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]; @@ -1889,15 +3732,81 @@ 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(); + + //NOTE: This is a big mess! Sometimes GTK requires m_TreeView.get_model(), here we need m_refTreeModelFilter->get_model(), otherwise accessing children below causes an error! + //Glib::RefPtr model = m_TreeView.get_model(); + Glib::RefPtr model = m_refTreeModelFilter->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) : ""; + row[m_Columns.m_col_tooltip] = scriptTooltipFor(pInstrument, i); + break; + } + + // causes the sampler to reload the instrument with the new script + on_sel_change(); +} + +void MainWindow::assignScript(gig::Script* pScript) { + if (!pScript) { + printf("assignScript() : !script\n"); + return; + } + printf("assignScript('%s')\n", pScript->Name.c_str()); + + gig::Instrument* pInstrument = get_instrument(); + if (!pInstrument) { + printf("!instrument\n"); + return; + } + + pInstrument->AddScriptSlot(pScript); + + onScriptSlotsModified(pInstrument); +} + +void MainWindow::dropAllScriptSlots() { + gig::Instrument* pInstrument = get_instrument(); + if (!pInstrument) { + printf("!instrument\n"); + return; + } + + const int iScriptSlots = pInstrument->ScriptSlotCount(); + for (int i = iScriptSlots - 1; i >= 0; --i) + pInstrument->RemoveScriptSlot(i); + + onScriptSlotsModified(pInstrument); +} + void MainWindow::on_action_refresh_all() { __refreshEntireGUI(); } void MainWindow::on_action_view_status_bar() { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleStatusBar->get_state(active); + // for some reason toggle state does not change automatically + active = !active; + m_actionToggleStatusBar->change_state(active); + if (active) + m_StatusBar.show(); + else + m_StatusBar.hide(); +#else Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuView/Statusbar")); if (!item) { @@ -1906,9 +3815,72 @@ } if (item->get_active()) m_StatusBar.show(); else m_StatusBar.hide(); +#endif +} + +void MainWindow::on_auto_restore_win_dim() { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleRestoreWinDim->get_state(active); + // for some reason toggle state does not change automatically + active = !active; + m_actionToggleRestoreWinDim->change_state(active); + Settings::singleton()->autoRestoreWindowDimension = active; +#else + 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(); +#endif +} + +void MainWindow::on_instr_double_click_opens_props() { +#if USE_GLIB_ACTION + bool active = false; + m_actionInstrDoubleClickOpensProps->get_state(active); + // for some reason toggle state does not change automatically + active = !active; + m_actionInstrDoubleClickOpensProps->change_state(active); + Settings::singleton()->instrumentDoubleClickOpensProps = active; +#else + Gtk::CheckMenuItem* item = + dynamic_cast(uiManager->get_widget("/MenuBar/MenuView/OpenInstrPropsByDoubleClick")); + if (!item) { + std::cerr << "/MenuBar/MenuView/OpenInstrPropsByDoubleClick == NULL\n"; + return; + } + Settings::singleton()->instrumentDoubleClickOpensProps = item->get_active(); +#endif +} + +void MainWindow::on_save_with_temporary_file() { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleSaveWithTempFile->get_state(active); + // for some reason toggle state does not change automatically + active = !active; + m_actionToggleSaveWithTempFile->change_state(active); + Settings::singleton()->saveWithTemporaryFile = active; +#else + 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(); +#endif } bool MainWindow::is_copy_samples_unity_note_enabled() const { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleCopySampleUnity->get_state(active); + return active; +#else Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuEdit/CopySampleUnity")); if (!item) { @@ -1916,9 +3888,15 @@ return true; } return item->get_active(); +#endif } bool MainWindow::is_copy_samples_fine_tune_enabled() const { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleCopySampleTune->get_state(active); + return active; +#else Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuEdit/CopySampleTune")); if (!item) { @@ -1926,9 +3904,15 @@ return true; } return item->get_active(); +#endif } bool MainWindow::is_copy_samples_loop_enabled() const { +#if USE_GLIB_ACTION + bool active = false; + m_actionToggleCopySampleLoop->get_state(active); + return active; +#else Gtk::CheckMenuItem* item = dynamic_cast(uiManager->get_widget("/MenuBar/MenuEdit/CopySampleLoop")); if (!item) { @@ -1936,15 +3920,24 @@ return true; } return item->get_active(); +#endif } -void MainWindow::on_button_release(GdkEventButton* button) -{ +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 +bool MainWindow::on_button_release(Gdk::EventButton& _button) { + GdkEventButton* button = _button.gobj(); +#else +void MainWindow::on_button_release(GdkEventButton* button) { +#endif if (button->type == GDK_2BUTTON_PRESS) { - show_instr_props(); + if (Settings::singleton()->instrumentDoubleClickOpensProps) + show_instr_props(); } else if (button->type == GDK_BUTTON_PRESS && button->button == 3) { // gig v2 files have no midi rules const bool bEnabled = !(file->pVersion && file->pVersion->major == 2); +#if USE_GTKMM_BUILDER + m_actionMIDIRules->property_enabled() = bEnabled; +#else static_cast( uiManager->get_widget("/MenuBar/MenuInstrument/MidiRules"))->set_sensitive( bEnabled @@ -1953,10 +3946,15 @@ uiManager->get_widget("/PopupMenu/MidiRules"))->set_sensitive( bEnabled ); +#endif popup_menu->popup(button->button, button->time); } +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + return false; +#endif } +#if !USE_GTKMM_BUILDER void MainWindow::on_instrument_selection_change(Gtk::RadioMenuItem* item) { if (item->get_active()) { const std::vector children = @@ -1971,21 +3969,92 @@ } } } +#endif + +void MainWindow::on_action_move_instr() { + gig::Instrument* instr = get_instrument(); + if (!instr) return; + + int currentIndex = getIndexOf(instr); + + Gtk::Dialog dialog(_("Move Instrument"), true /*modal*/); +#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 + Gtk::Adjustment adjustment( + currentIndex, + 0 /*min*/, file->CountInstruments() - 1 /*max*/ + ); + Gtk::SpinButton spinBox(adjustment); +#else + Gtk::SpinButton spinBox( + Gtk::Adjustment::create( + currentIndex, + 0 /*min*/, file->CountInstruments() - 1 /*max*/ + ) + ); +#endif +#if USE_GTKMM_BOX + dialog.get_content_area()->pack_start(spinBox); +#else + dialog.get_vbox()->pack_start(spinBox); +#endif +#if HAS_GTKMM_STOCK + Gtk::Button* okButton = dialog.add_button(Gtk::Stock::OK, 0); + dialog.add_button(Gtk::Stock::CANCEL, 1); +#else + Gtk::Button* okButton = dialog.add_button(_("_OK"), 0); + dialog.add_button(_("_Cancel"), 1); +#endif + okButton->set_sensitive(false); + // show the dialog at a reasonable screen position + dialog.set_position(Gtk::WIN_POS_MOUSE); + // only enable the 'OK' button if entered new index is not instrument's + // current index already + spinBox.signal_value_changed().connect([&]{ + okButton->set_sensitive( spinBox.get_value_as_int() != currentIndex ); + }); + // usability acceleration: if user hits enter key on the text entry field + // then auto trigger the 'OK' button + spinBox.signal_activate().connect([&]{ + if (okButton->get_sensitive()) + okButton->clicked(); + }); +#if HAS_GTKMM_SHOW_ALL_CHILDREN + dialog.show_all_children(); +#endif + if (!dialog.run()) { // 'OK' selected ... + int newIndex = spinBox.get_value_as_int(); + printf("MOVE TO %d\n", newIndex); + gig::Instrument* dst = file->GetInstrument(newIndex); + instr->MoveTo(dst); + __refreshEntireGUI(); + select_instrument(instr); + } +} void MainWindow::select_instrument(gig::Instrument* instrument) { if (!instrument) return; + //NOTE: This is a big mess! Sometimes GTK requires m_refTreeModelFilter->get_model(), here we need m_TreeView.get_model(), otherwise treeview selection below causes an error! Glib::RefPtr model = m_TreeView.get_model(); + //Glib::RefPtr model = m_refTreeModelFilter->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(); + +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + auto iterSel = model->children()[i].get_iter(); + m_TreeView.get_selection()->select(iterSel); +#else m_TreeView.get_selection()->select(model->children()[i]); - Gtk::TreePath path( - m_TreeView.get_selection()->get_selected() - ); - m_TreeView.scroll_to_row(path); +#endif + 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 } } @@ -1996,17 +4065,26 @@ gig::Region* pRegion = (gig::Region*) dimRgn->GetParent(); gig::Instrument* pInstrument = (gig::Instrument*) pRegion->GetParent(); + //NOTE: This is a big mess! Sometimes GTK requires m_refTreeModelFilter->get_model(), here we need m_TreeView.get_model(), otherwise treeview selection below causes an error! Glib::RefPtr model = m_TreeView.get_model(); + //Glib::RefPtr model = m_refTreeModelFilter->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(); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + auto iterSel = model->children()[i].get_iter(); + m_TreeView.get_selection()->select(iterSel); +#else m_TreeView.get_selection()->select(model->children()[i]); - Gtk::TreePath path( - m_TreeView.get_selection()->get_selected() - ); - m_TreeView.scroll_to_row(path); +#endif + 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 @@ -2032,63 +4110,120 @@ Gtk::TreeModel::Row rowSample = rowGroup.children()[s]; if (rowSample[m_SamplesModel.m_col_sample] == sample) { show_samples_tab(); + m_TreeViewSamples.get_selection()->unselect_all(); +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + auto iterSel = rowGroup.children()[s].get_iter(); + m_TreeViewSamples.get_selection()->select(iterSel); +#else m_TreeViewSamples.get_selection()->select(rowGroup.children()[s]); - Gtk::TreePath path( - m_TreeViewSamples.get_selection()->get_selected() - ); - m_TreeViewSamples.scroll_to_row(path); +#endif + std::vector rows = + m_TreeViewSamples.get_selection()->get_selected_rows(); + if (rows.empty()) return; + m_TreeViewSamples.scroll_to_row(rows[0]); return; } } } } +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 +bool MainWindow::on_sample_treeview_button_release(Gdk::EventButton& _button) { + GdkEventButton* button = _button.gobj(); +#else void MainWindow::on_sample_treeview_button_release(GdkEventButton* button) { +#endif 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); + } + } + }*/ + +#if !USE_GTKMM_BUILDER Gtk::Menu* sample_popup = dynamic_cast(uiManager->get_widget("/SamplePopupMenu")); +#endif + // 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++; } - - + +#if USE_GTKMM_BUILDER + m_actionSampleProperties->property_enabled() = (n == 1); + m_actionAddSample->property_enabled() = (n); + m_actionAddSampleGroup->property_enabled() = (file); + m_actionViewSampleRefs->property_enabled() = (nSamples == 1); + m_actionRemoveSample->property_enabled() = (n); + m_actionReplaceSample->property_enabled() = (nSamples == 1); +#else 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); +#endif // show sample popup sample_popup->popup(button->button, button->time); +#if !USE_GTKMM_BUILDER 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); +#endif } + +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + return false; +#endif } +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 +bool MainWindow::on_script_treeview_button_release(Gdk::EventButton& _button) { + GdkEventButton* button = _button.gobj(); +#else void MainWindow::on_script_treeview_button_release(GdkEventButton* button) { +#endif if (button->type == GDK_BUTTON_PRESS && button->button == 3) { +#if !USE_GTKMM_BUILDER Gtk::Menu* script_popup = dynamic_cast(uiManager->get_widget("/ScriptPopupMenu")); +#endif // update enabled/disabled state of sample popup items Glib::RefPtr sel = m_TreeViewScripts.get_selection(); Gtk::TreeModel::iterator it = sel->get_selected(); @@ -2099,6 +4234,12 @@ group_selected = row[m_ScriptsModel.m_col_group]; script_selected = row[m_ScriptsModel.m_col_script]; } +#if USE_GTKMM_BUILDER + m_actionAddScript->property_enabled() = (group_selected || script_selected); + m_actionAddScriptGroup->property_enabled() = (file); + m_actionEditScript->property_enabled() = (script_selected); + m_actionRemoveScript->property_enabled() = (group_selected || script_selected); +#else dynamic_cast(uiManager->get_widget("/ScriptPopupMenu/AddScript"))-> set_sensitive(group_selected || script_selected); dynamic_cast(uiManager->get_widget("/ScriptPopupMenu/AddScriptGroup"))-> @@ -2107,9 +4248,11 @@ set_sensitive(script_selected); dynamic_cast(uiManager->get_widget("/ScriptPopupMenu/RemoveScript"))-> set_sensitive(group_selected || script_selected); +#endif // show sample popup script_popup->popup(button->button, button->time); +#if !USE_GTKMM_BUILDER dynamic_cast(uiManager->get_widget("/MenuBar/MenuScript/AddScript"))-> set_sensitive(group_selected || script_selected); dynamic_cast(uiManager->get_widget("/MenuBar/MenuScript/AddScriptGroup"))-> @@ -2118,9 +4261,74 @@ set_sensitive(script_selected); dynamic_cast(uiManager->get_widget("/MenuBar/MenuScript/RemoveScript"))-> set_sensitive(group_selected || script_selected); +#endif } +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + return false; +#endif } +void MainWindow::updateScriptListOfMenu() { + // remove all entries from "Assign Script" menu + { + const std::vector children = assign_scripts_menu->get_children(); + for (int i = 0; i < children.size(); ++i) { + Gtk::Widget* child = children[i]; + assign_scripts_menu->remove(*child); + delete child; + } + } + + int iTotalScripts = 0; + + if (!file) goto noScripts; + + // add all configured macros as menu items to the "Macro" menu + for (int iGroup = 0; file->GetScriptGroup(iGroup); ++iGroup) { + gig::ScriptGroup* pGroup = file->GetScriptGroup(iGroup); + for (int iScript = 0; pGroup->GetScript(iScript); ++iScript, ++iTotalScripts) { + gig::Script* pScript = pGroup->GetScript(iScript); + std::string name = pScript->Name; + + Gtk::MenuItem* item = new Gtk::MenuItem(name); + item->signal_activate().connect( + sigc::bind( + sigc::mem_fun(*this, &MainWindow::assignScript), pScript + ) + ); + assign_scripts_menu->append(*item); + item->set_accel_path("/script_" + ToString(iTotalScripts)); + //item->set_tooltip_text(comment); + } + } + + noScripts: + + // if there are no macros configured at all, then show a dummy entry instead + if (!iTotalScripts) { + Gtk::MenuItem* item = new Gtk::MenuItem(_("No Scripts")); + item->set_sensitive(false); + assign_scripts_menu->append(*item); + } + + // add separator line to menu + assign_scripts_menu->append(*new Gtk::SeparatorMenuItem); + + { + Gtk::MenuItem* item = new Gtk::MenuItem(_("Unassign All Scripts")); + item->signal_activate().connect( + sigc::mem_fun(*this, &MainWindow::dropAllScriptSlots) + ); + assign_scripts_menu->append(*item); + item->set_accel_path("/DropAllScriptSlots"); + } + +#if HAS_GTKMM_SHOW_ALL_CHILDREN + assign_scripts_menu->show_all_children(); +#endif +} + +#if !USE_GTKMM_BUILDER Gtk::RadioMenuItem* MainWindow::add_instrument_to_menu( const Glib::ustring& name, int position) { @@ -2144,7 +4352,9 @@ item)); return item; } +#endif +#if !USE_GTKMM_BUILDER void MainWindow::remove_instrument_from_menu(int index) { const std::vector children = instrument_menu->get_children(); @@ -2152,6 +4362,7 @@ instrument_menu->remove(*child); delete child; } +#endif void MainWindow::add_instrument(gig::Instrument* instrument) { const Glib::ustring name(gig_to_utf8(instrument->pInfo->Name)); @@ -2160,14 +4371,18 @@ instrument_name_connection.block(); Gtk::TreeModel::iterator iterInstr = m_refTreeModel->append(); Gtk::TreeModel::Row rowInstr = *iterInstr; + const int index = m_refTreeModel->children().size() - 1; + rowInstr[m_Columns.m_col_nr] = index; rowInstr[m_Columns.m_col_name] = name; rowInstr[m_Columns.m_col_instr] = instrument; + rowInstr[m_Columns.m_col_scripts] = ""; + rowInstr[m_Columns.m_col_tooltip] = scriptTooltipFor(instrument, index); instrument_name_connection.unblock(); +#if !USE_GTKMM_BUILDER add_instrument_to_menu(name); - - m_TreeView.get_selection()->select(iterInstr); - +#endif + select_instrument(instrument); file_changed(); } @@ -2188,19 +4403,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() { @@ -2217,8 +4436,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 { @@ -2229,10 +4450,24 @@ if (instr) file->DeleteInstrument(instr); file_changed(); +#if !USE_GTKMM_BUILDER remove_instrument_from_menu(index); +#endif // 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; + gig::Instrument* instrument = row[m_Columns.m_col_instr]; + row[m_Columns.m_col_nr] = index; + row[m_Columns.m_col_tooltip] = scriptTooltipFor(instrument, index); + } + } #if GTKMM_MAJOR_VERSION < 3 // select another instrument (in gtk3 this is done @@ -2260,11 +4495,7 @@ } void MainWindow::on_action_sample_properties() { - //TODO: show a dialog where the selected sample's properties can be edited - Gtk::MessageDialog msg( - *this, _("Sorry, yet to be implemented!"), false, Gtk::MESSAGE_INFO - ); - msg.run(); + show_sample_props(); } void MainWindow::on_action_add_script_group() { @@ -2327,9 +4558,18 @@ if (!it) return; Gtk::TreeModel::Row row = *it; gig::Script* script = row[m_ScriptsModel.m_col_script]; - if (!script) return; + editScript(script); +} +void MainWindow::editScript(gig::Script* script) { + 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(); @@ -2408,7 +4648,9 @@ // 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; @@ -2426,8 +4668,13 @@ // show 'browse for file' dialog Gtk::FileChooserDialog dialog(*this, replace ? _("Replace Sample with") : _("Add Sample(s)")); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + dialog.add_button(_("_Open"), Gtk::RESPONSE_OK); +#endif 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 @@ -2470,13 +4717,14 @@ dialog.set_current_folder(current_sample_dir); } if (dialog.run() == Gtk::RESPONSE_OK) { + dialog.hide(); current_sample_dir = dialog.get_current_folder(); Glib::ustring error_files; std::vector filenames = dialog.get_filenames(); for (std::vector::iterator iter = filenames.begin(); iter != filenames.end(); ++iter) { printf("Adding sample %s\n",(*iter).c_str()); - // use libsndfile to retrieve file informations + // use libsndfile to retrieve file information SF_INFO info; info.format = 0; SNDFILE* hFile = sf_open((*iter).c_str(), SFM_READ, &info); @@ -2557,7 +4805,7 @@ 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 if (replace) { row[m_SamplesModel.m_col_name] = gig_to_utf8(sample->pInfo->Name); @@ -2617,17 +4865,32 @@ Gtk::Label description(str); description.set_line_wrap(); #endif - Gtk::HBox entryArea; + HBox entryArea; Gtk::Label entryLabel( _("Add filename extension: "), Gtk::ALIGN_START); Gtk::Entry postfixEntryBox; postfixEntryBox.set_text(".wav"); entryArea.pack_start(entryLabel); entryArea.pack_start(postfixEntryBox); +#if USE_GTKMM_BOX + dialog.get_content_area()->pack_start(description, Gtk::PACK_SHRINK); + dialog.get_content_area()->pack_start(entryArea, Gtk::PACK_SHRINK); +#else dialog.get_vbox()->pack_start(description, Gtk::PACK_SHRINK); dialog.get_vbox()->pack_start(entryArea, Gtk::PACK_SHRINK); +#endif description.show(); + +#if HAS_GTKMM_SHOW_ALL_CHILDREN entryArea.show_all(); +#else + entryArea.show(); +#endif + +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); +#endif dialog.add_button(_("Select"), Gtk::RESPONSE_OK); dialog.set_select_multiple(false); if (current_sample_dir != "") { @@ -2635,6 +4898,7 @@ } if (dialog.run() == Gtk::RESPONSE_OK) { + dialog.hide(); current_sample_dir = dialog.get_current_folder(); Glib::ustring error_files; std::string folder = dialog.get_filename(); @@ -2651,18 +4915,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); @@ -2671,7 +4931,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(); } @@ -2695,8 +4955,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]; @@ -2721,15 +4983,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(); @@ -2744,14 +5003,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(); @@ -2807,16 +5062,11 @@ gig::Sample* sample = *itSample; // remove sample from the .gig file file->DeleteSample(sample); - // if sample was just previously added, remove it fro 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 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) { @@ -2875,10 +5125,13 @@ gig::Instrument* src = NULL; { Glib::RefPtr sel = m_TreeView.get_selection(); - Gtk::TreeModel::iterator it = sel->get_selected(); - if (it) { - Gtk::TreeModel::Row row = *it; - src = row[m_Columns.m_col_instr]; + 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; @@ -2933,10 +5186,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, @@ -3041,6 +5297,10 @@ file_changed(); } } + // change name in the sample properties window + if (sampleProps.get_sample() == sample && sample) { + sampleProps.set_sample(sample); + } } void MainWindow::script_name_changed(const Gtk::TreeModel::Path& path, @@ -3076,6 +5336,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(); @@ -3087,6 +5353,7 @@ Gtk::TreeModel::Row row = *iter; Glib::ustring name = row[m_Columns.m_col_name]; +#if !USE_GTKMM_BUILDER // change name in instrument menu int index = path[0]; const std::vector children = instrument_menu->get_children(); @@ -3099,6 +5366,7 @@ item->set_active(); #endif } +#endif // change name in gig gig::Instrument* instrument = row[m_Columns.m_col_instr]; @@ -3115,10 +5383,56 @@ } } +bool MainWindow::instrument_row_visible(const Gtk::TreeModel::const_iterator& iter) { + if (!iter) + return true; + + Glib::ustring pattern = m_searchText.get_text().lowercase(); + trim(pattern); + if (pattern.empty()) return true; + +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24) + //HACK: on GTKMM4 development branch const_iterator cannot be easily converted to iterator, probably going to be fixed before final GTKMM4 release though. + Gtk::TreeModel::Row row = **(Gtk::TreeModel::iterator*)(&iter); +#else + Gtk::TreeModel::Row row = *iter; +#endif + Glib::ustring name = row[m_Columns.m_col_name]; + name = name.lowercase(); + + std::vector tokens = Glib::Regex::split_simple(" ", pattern); + for (int t = 0; t < tokens.size(); ++t) + if (name.find(tokens[t]) == Glib::ustring::npos) + return false; + + return true; +} + void MainWindow::on_action_combine_instruments() { CombineInstrumentsDialog* d = new CombineInstrumentsDialog(*this, file); + + // take over selection from instruments list view for the combine dialog's + // list view as pre-selection + std::set indeces; + { + Glib::RefPtr sel = m_TreeView.get_selection(); + 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; + int index = row[m_Columns.m_col_nr]; + indeces.insert(index); + } + } + } + d->setSelectedInstruments(indeces); + +#if HAS_GTKMM_SHOW_ALL_CHILDREN d->show_all(); - d->resize(500, 400); +#else + d->show(); +#endif d->run(); if (d->fileWasChanged()) { // update GUI with new instrument just created @@ -3129,7 +5443,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]; @@ -3140,7 +5456,11 @@ d->dimension_region_selected.connect( sigc::mem_fun(*this, &MainWindow::select_dimension_region) ); +#if HAS_GTKMM_SHOW_ALL_CHILDREN d->show_all(); +#else + d->show(); +#endif d->resize(500, 400); d->run(); delete d; @@ -3224,7 +5544,11 @@ Glib::filename_display_basename(this->filename) + "' ...", *this ); +#if HAS_GTKMM_SHOW_ALL_CHILDREN progress_dialog->show_all(); +#else + progress_dialog->show(); +#endif saver = new Saver(this->file); //FIXME: memory leak! saver->signal_progress().connect( sigc::mem_fun(*this, &MainWindow::on_saver_progress)); @@ -3251,7 +5575,11 @@ } Gtk::FileChooserDialog dialog(*this, _("Merge .gig files")); +#if HAS_GTKMM_STOCK dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); +#else + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); +#endif dialog.add_button(_("Merge"), Gtk::RESPONSE_OK); dialog.set_default_response(Gtk::RESPONSE_CANCEL); #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 @@ -3268,9 +5596,11 @@ dialog.set_select_multiple(true); // show warning in the file picker dialog - Gtk::HBox descriptionArea; + 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; @@ -3289,11 +5619,26 @@ "will accordingly be stored separately in the target .gig file!" )); descriptionArea.pack_start(description); +#if USE_GTKMM_BOX +# warning No description area implemented for dialog on GTKMM 3 +#else dialog.get_vbox()->pack_start(descriptionArea, Gtk::PACK_SHRINK); +#endif +#if HAS_GTKMM_SHOW_ALL_CHILDREN descriptionArea.show_all(); +#else + descriptionArea.show(); +#endif if (dialog.run() == Gtk::RESPONSE_OK) { - printf("on_action_merge_files self=%x\n", Glib::Threads::Thread::self()); + dialog.hide(); +#ifdef GLIB_THREADS + printf("on_action_merge_files self=%p\n", + static_cast(Glib::Threads::Thread::self())); +#else + std::cout << "on_action_merge_files self=" << + std::this_thread::get_id() << "\n"; +#endif std::vector filenames = dialog.get_filenames(); // merge the selected files to the currently open .gig file @@ -3325,9 +5670,13 @@ } { +#if USE_GTKMM_BUILDER + m_actionToggleSyncSamplerSelection->property_enabled() = b; +#else Gtk::MenuItem* item = dynamic_cast( uiManager->get_widget("/MenuBar/MenuSettings/SyncSamplerInstrumentSelection")); if (item) item->set_sensitive(b); +#endif } } @@ -3375,6 +5724,209 @@ m_TreeViewNotebook.set_current_page(2); } +void MainWindow::select_instrument_by_dir(int dir) { + if (!file) return; + gig::Instrument* pInstrument = get_instrument(); + if (!pInstrument) { + select_instrument( file->GetInstrument(0) ); + return; + } + for (int i = 0; file->GetInstrument(i); ++i) { + if (file->GetInstrument(i) == pInstrument) { + select_instrument( file->GetInstrument(i + dir) ); + return; + } + } +} + +void MainWindow::select_prev_instrument() { + select_instrument_by_dir(-1); +} + +void MainWindow::select_next_instrument() { + select_instrument_by_dir(1); +} + +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(); +#if USE_GTKMM_BUILDER + m_actionCopyDimRgn->property_enabled() = bDimensionRegionCopyIsPossible; +#else + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/CopyDimRgn") + )->set_sensitive(bDimensionRegionCopyIsPossible); +#endif +} + +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 +void MainWindow::on_clipboard_owner_change(Gdk::EventOwnerChange& event) { +#else +void MainWindow::on_clipboard_owner_change(GdkEventOwnerChange* event) { +#endif + 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(); + +#if USE_GTKMM_BUILDER + m_actionPasteDimRgn->property_enabled() = bDimensionRegionPasteIsPossible; + m_actionAdjustClipboard->property_enabled() = bDimensionRegionPasteIsPossible; +#else + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/PasteDimRgn") + )->set_sensitive(bDimensionRegionPasteIsPossible); + + static_cast( + uiManager->get_widget("/MenuBar/MenuEdit/AdjustClipboard") + )->set_sensitive(bDimensionRegionPasteIsPossible); +#endif +} + sigc::signal& MainWindow::signal_file_structure_to_be_changed() { return file_structure_to_be_changed_signal; }