From 41ade3b024cae2c8f953959f51f54970034a0137 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 19 Jan 2022 09:06:38 -0700 Subject: [PATCH 01/10] add a distinct Marker type enum for cue markers Plan is to change the shape before this is considered done; for now it uses the same shape as a regular marker --- gtk2_ardour/editor_markers.cc | 2 +- gtk2_ardour/marker.cc | 16 ++++++++++++++++ gtk2_ardour/marker.h | 3 ++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gtk2_ardour/editor_markers.cc b/gtk2_ardour/editor_markers.cc index 25ef61eee0..00afe7f0f5 100644 --- a/gtk2_ardour/editor_markers.cc +++ b/gtk2_ardour/editor_markers.cc @@ -122,7 +122,7 @@ Editor::add_new_location_internal (Location* location) lam->start = new ArdourMarker (*this, *cd_marker_group, color, location->name(), ArdourMarker::Mark, location->start()); group = cd_marker_group; } else if (location->is_cue_marker() && ruler_cue_marker_action->get_active()) { - lam->start = new ArdourMarker (*this, *cue_marker_group, color, location->name(), ArdourMarker::Mark, location->start()); + lam->start = new ArdourMarker (*this, *cue_marker_group, color, location->name(), ArdourMarker::Cue, location->start()); group = cue_marker_group; } else { lam->start = new ArdourMarker (*this, *marker_group, color, location->name(), ArdourMarker::Mark, location->start()); diff --git a/gtk2_ardour/marker.cc b/gtk2_ardour/marker.cc index 910fc2e79c..9628320a7f 100644 --- a/gtk2_ardour/marker.cc +++ b/gtk2_ardour/marker.cc @@ -167,6 +167,8 @@ ArdourMarker::ArdourMarker (PublicEditor& ed, ArdourCanvas::Item& parent, guint3 * \ | * MH,MH * + * Cue: + * ben: put your shape here :) */ switch (type) { @@ -268,6 +270,20 @@ ArdourMarker::ArdourMarker (PublicEditor& ed, ArdourCanvas::Item& parent, guint3 _label_offset = 0.0; break; + case Cue: + /* ben: new shape needed here */ + points = new ArdourCanvas::Points (); + + points->push_back (ArdourCanvas::Duple (0.0, 0.0)); + points->push_back (ArdourCanvas::Duple ( M6, 0.0)); + points->push_back (ArdourCanvas::Duple ( M6, MH * .4)); + points->push_back (ArdourCanvas::Duple ( M3, MH)); + points->push_back (ArdourCanvas::Duple (0.0, MH * .4)); + points->push_back (ArdourCanvas::Duple (0.0, 0.0)); + + _shift = 3; + _label_offset = 10.0; + break; } _position = pos; diff --git a/gtk2_ardour/marker.h b/gtk2_ardour/marker.h index dd67b14780..23fc4c7b8f 100644 --- a/gtk2_ardour/marker.h +++ b/gtk2_ardour/marker.h @@ -66,7 +66,8 @@ public: LoopEnd, PunchIn, PunchOut, - RegionCue + RegionCue, + Cue }; From 9886d6c19edbf6acdc0f098c8d4cbb61909fdcb1 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 17:12:05 +0100 Subject: [PATCH 02/10] Allow to disable clang_compilation_database See also af69061644a and b8e1cd53c. This can be useful in some cases where running a dry-run build will fail. e.g. with --freedesktop files that are not generated before the actual build. --- wscript | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wscript b/wscript index 20b6cd0b2c..5ba4e247a9 100644 --- a/wscript +++ b/wscript @@ -807,6 +807,8 @@ def options(opt): help='Compile with -rdynamic -- allow obtaining backtraces from within Ardour') opt.add_option('--no-carbon', action='store_true', default=False, dest='nocarbon', help='Compile without support for AU Plugins with only CARBON UI (needed for 64bit)') + opt.add_option('--no-compile-database', action='store_true', default=False, dest='clang_compile_db', + help='Do not call clang_compilation_database to write compile_commands.json prior to build') opt.add_option('--boost-sp-debug', action='store_true', default=False, dest='boost_sp_debug', help='Compile with Boost shared pointer debugging') opt.add_option('--debug-symbols', action='store_true', default=False, dest='debug_symbols', @@ -935,7 +937,7 @@ def configure(conf): conf.load('compiler_cxx') if Options.options.dist_target == 'mingw': conf.load('winres') - else: + elif not Options.options.clang_compile_db: conf.load('clang_compilation_database') if Options.options.dist_target == 'msvc': From 5427cec8212e6a13361cac29181906a84983bcaf Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 18 Jan 2022 02:55:43 +0100 Subject: [PATCH 03/10] Separate RegionList into a case-class for reusing in on the TriggerPage --- gtk2_ardour/editor.cc | 3 - gtk2_ardour/editor_regions.cc | 1095 ++----------------------------- gtk2_ardour/editor_regions.h | 182 +---- gtk2_ardour/region_list_base.cc | 821 +++++++++++++++++++++++ gtk2_ardour/region_list_base.h | 244 +++++++ gtk2_ardour/wscript | 1 + 6 files changed, 1153 insertions(+), 1193 deletions(-) create mode 100644 gtk2_ardour/region_list_base.cc create mode 100644 gtk2_ardour/region_list_base.h diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 19e03334ec..9b6791889f 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -2581,7 +2581,6 @@ Editor::set_state (const XMLNode& node, int version) XMLNodeList children = node.children (); for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) { selection->set_state (**i, Stateful::current_state_version); - _regions->set_state (**i); _locations->set_state (**i); } @@ -2683,7 +2682,6 @@ Editor::get_state () node->set_property (X_("show-touched-automation"), _show_touched_automation); node->add_child_nocopy (selection->get_state ()); - node->add_child_nocopy (_regions->get_state ()); node->set_property ("nudge-clock-value", nudge_clock->current_duration()); @@ -6215,7 +6213,6 @@ Editor::session_going_away () /* rip everything out of the list displays */ - _regions->clear (); _sources->clear (); _routes->clear (); _route_groups->clear (); diff --git a/gtk2_ardour/editor_regions.cc b/gtk2_ardour/editor_regions.cc index 16637ffc58..3f946dc1c2 100644 --- a/gtk2_ardour/editor_regions.cc +++ b/gtk2_ardour/editor_regions.cc @@ -2,7 +2,7 @@ * Copyright (C) 2009-2012 Carl Hetherington * Copyright (C) 2009-2012 David Robillard * Copyright (C) 2009-2018 Paul Davis - * Copyright (C) 2013-2019 Robin Gareus + * Copyright (C) 2013-2021 Robin Gareus * Copyright (C) 2015-2016 Tim Mayberry * Copyright (C) 2016 Nick Mainsbridge * Copyright (C) 2018-2019 Ben Loftis @@ -23,457 +23,91 @@ */ #include -#include -#include -#include +#include #include -#include "pbd/basename.h" -#include "pbd/enumwriter.h" - -#include "ardour/audiofilesource.h" -#include "ardour/audioregion.h" -#include "ardour/profile.h" -#include "ardour/region_factory.h" +#include "ardour/region.h" #include "ardour/session.h" -#include "ardour/session_playlist.h" -#include "ardour/silentfilesource.h" - -#include "gtkmm2ext/treeutils.h" -#include "gtkmm2ext/utils.h" #include "widgets/choice.h" -#include "widgets/tooltips.h" -#include "actions.h" #include "ardour_ui.h" -#include "audio_clock.h" -#include "editing.h" -#include "editing_convert.h" #include "editor.h" -#include "editor_drag.h" #include "editor_regions.h" #include "gui_thread.h" #include "keyboard.h" -#include "main_clock.h" #include "region_view.h" -#include "ui_config.h" #include "utils.h" #include "pbd/i18n.h" using namespace std; using namespace ARDOUR; -using namespace ArdourWidgets; -using namespace ARDOUR_UI_UTILS; using namespace PBD; using namespace Gtk; -using namespace Glib; -using namespace Editing; -using namespace Temporal; using Gtkmm2ext::Keyboard; -//#define SHOW_REGION_EXTRAS - EditorRegions::EditorRegions (Editor* e) - : EditorComponent (e) - , old_focus (0) - , name_editable (0) - , tags_editable (0) - , _menu (0) - , _no_redisplay (false) + : EditorComponent (e) { - _display.set_size_request (100, -1); - _display.set_rules_hint (true); - _display.set_name ("RegionList"); - _display.set_fixed_height_mode (true); - _display.set_reorderable (false); + init (); - /* Try to prevent single mouse presses from initiating edits. - * This relies on a hack in gtktreeview.c:gtk_treeview_button_press() */ - _display.set_data ("mouse-edits-require-mod1", (gpointer)0x1); + _change_connection = _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &EditorRegions::selection_changed)); - _model = TreeStore::create (_columns); - _model->set_sort_column (0, SORT_ASCENDING); - - /* column widths */ - int bbt_width, date_width, chan_width, check_width, height; - - Glib::RefPtr layout = _display.create_pango_layout (X_ ("000|000|000")); - Gtkmm2ext::get_pixel_size (layout, bbt_width, height); - - Glib::RefPtr layout2 = _display.create_pango_layout (X_ ("2099-10-10 10:10:30")); - Gtkmm2ext::get_pixel_size (layout2, date_width, height); - - Glib::RefPtr layout3 = _display.create_pango_layout (X_ ("Chans ")); - Gtkmm2ext::get_pixel_size (layout3, chan_width, height); - - check_width = 20; - - TreeViewColumn* col_name = manage (new TreeViewColumn ("", _columns.name)); - col_name->set_fixed_width (120); - col_name->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_chans = manage (new TreeViewColumn ("", _columns.channels)); - col_chans->set_fixed_width (chan_width); - col_chans->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_tags = manage (new TreeViewColumn ("", _columns.tags)); - col_tags->set_fixed_width (date_width); - col_tags->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_start = manage (new TreeViewColumn ("", _columns.start)); - col_start->set_fixed_width (bbt_width); - col_start->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_end = manage (new TreeViewColumn ("", _columns.end)); - col_end->set_fixed_width (bbt_width); - col_end->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_length = manage (new TreeViewColumn ("", _columns.length)); - col_length->set_fixed_width (bbt_width); - col_length->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_sync = manage (new TreeViewColumn ("", _columns.sync)); - col_sync->set_fixed_width (bbt_width); - col_sync->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_fadein = manage (new TreeViewColumn ("", _columns.fadein)); - col_fadein->set_fixed_width (bbt_width); - col_fadein->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_fadeout = manage (new TreeViewColumn ("", _columns.fadeout)); - col_fadeout->set_fixed_width (bbt_width); - col_fadeout->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_locked = manage (new TreeViewColumn ("", _columns.locked)); - col_locked->set_fixed_width (check_width); - col_locked->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_glued = manage (new TreeViewColumn ("", _columns.glued)); - col_glued->set_fixed_width (check_width); - col_glued->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_muted = manage (new TreeViewColumn ("", _columns.muted)); - col_muted->set_fixed_width (check_width); - col_muted->set_sizing (TREE_VIEW_COLUMN_FIXED); - TreeViewColumn* col_opaque = manage (new TreeViewColumn ("", _columns.opaque)); - col_opaque->set_fixed_width (check_width); - col_opaque->set_sizing (TREE_VIEW_COLUMN_FIXED); - - _display.append_column (*col_name); - _display.append_column (*col_chans); - _display.append_column (*col_tags); - _display.append_column (*col_start); - _display.append_column (*col_length); - _display.append_column (*col_locked); - _display.append_column (*col_glued); - _display.append_column (*col_muted); - _display.append_column (*col_opaque); - -#ifdef SHOW_REGION_EXTRAS - _display.append_column (*col_end); - _display.append_column (*col_sync); - _display.append_column (*col_fadein); - _display.append_column (*col_fadeout); -#endif - - TreeViewColumn* col; - Gtk::Label* l; - - struct ColumnInfo { - int index; - int sort_idx; - Gtk::AlignmentEnum al; - const char* label; - const char* tooltip; - } ci[] = { - /* clang-format off */ - { 0, 0, ALIGN_LEFT, _("Name"), _("Region name") }, - { 1, 1, ALIGN_LEFT, _("# Ch"), _("# Channels in the region") }, - { 2, 2, ALIGN_LEFT, _("Tags"), _("Tags") }, - { 3, 16, ALIGN_RIGHT, _("Start"), _("Position of start of region") }, - { 4, 4, ALIGN_RIGHT, _("Length"), _("Length of the region") }, - { 5, -1, ALIGN_CENTER, S_("Lock|L"), _("Region position locked?") }, - { 6, -1, ALIGN_CENTER, S_("Glued|G"), _("Region position glued to Bars|Beats time?") }, - { 7, -1, ALIGN_CENTER, S_("Mute|M"), _("Region muted?") }, - { 8, -1, ALIGN_CENTER, S_("Opaque|O"), _("Region opaque (blocks regions below it from being heard)?") }, -#ifdef SHOW_REGION_EXTRAS - { 9, 5, ALIGN_RIGHT, _("End"), _("Position of end of region") }, - { 10, -1, ALIGN_RIGHT, _("Sync"), _("Position of region sync point, relative to start of the region") }, - { 11,-1, ALIGN_RIGHT, _("Fade In"), _("Length of region fade-in (units: secondary clock), () if disabled") }, - { 12,-1, ALIGN_RIGHT, _("Fade Out"), _("Length of region fade-out (units: secondary clock), () if disabled") }, -#endif - { -1,-1, ALIGN_CENTER, 0, 0 } - }; - /* clang-format on */ - - for (int i = 0; ci[i].index >= 0; ++i) { - col = _display.get_column (ci[i].index); - - /* add the label */ - l = manage (new Label (ci[i].label)); - l->set_alignment (ci[i].al); - set_tooltip (*l, ci[i].tooltip); - col->set_widget (*l); - l->show (); - - col->set_sort_column (ci[i].sort_idx); - - col->set_expand (false); - - /* this sets the alignment of the column header... */ - col->set_alignment (ci[i].al); - - /* ...and this sets the alignment for the data cells */ - CellRendererText* renderer = dynamic_cast (_display.get_column_cell_renderer (i)); - if (renderer) { - renderer->property_xalign () = (ci[i].al == ALIGN_RIGHT ? 1.0 : (ci[i].al == ALIGN_LEFT ? 0.0 : 0.5)); - } - } - - _display.set_model (_model); - - _display.set_headers_visible (true); - _display.set_rules_hint (); - - if (UIConfiguration::instance ().get_use_tooltips ()) { - /* show path as the row tooltip */ - _display.set_tooltip_column (13); /* path */ - } - _display.get_selection ()->set_select_function (sigc::mem_fun (*this, &EditorRegions::selection_filter)); - - /* Name cell: make editable */ - CellRendererText* region_name_cell = dynamic_cast (_display.get_column_cell_renderer (0)); - region_name_cell->property_editable () = true; - region_name_cell->signal_edited ().connect (sigc::mem_fun (*this, &EditorRegions::name_edit)); - region_name_cell->signal_editing_started ().connect (sigc::mem_fun (*this, &EditorRegions::name_editing_started)); - - /* Region Name: color turns red if source is missing. */ - TreeViewColumn* tv_col = _display.get_column (0); - CellRendererText* renderer = dynamic_cast (_display.get_column_cell_renderer (0)); - tv_col->add_attribute (renderer->property_text (), _columns.name); - tv_col->add_attribute (renderer->property_foreground_gdk (), _columns.color_); - tv_col->set_expand (true); - - /* Tags cell: make editable */ - CellRendererText* region_tags_cell = dynamic_cast (_display.get_column_cell_renderer (2)); - region_tags_cell->property_editable () = true; - region_tags_cell->signal_edited ().connect (sigc::mem_fun (*this, &EditorRegions::tag_edit)); - region_tags_cell->signal_editing_started ().connect (sigc::mem_fun (*this, &EditorRegions::tag_editing_started)); - - /* checkbox cells */ - int check_start_col = 5; - - CellRendererToggle* locked_cell = dynamic_cast (_display.get_column_cell_renderer (check_start_col++)); - locked_cell->property_activatable () = true; - locked_cell->signal_toggled ().connect (sigc::mem_fun (*this, &EditorRegions::locked_changed)); - - CellRendererToggle* glued_cell = dynamic_cast (_display.get_column_cell_renderer (check_start_col++)); - glued_cell->property_activatable () = true; - glued_cell->signal_toggled ().connect (sigc::mem_fun (*this, &EditorRegions::glued_changed)); - - CellRendererToggle* muted_cell = dynamic_cast (_display.get_column_cell_renderer (check_start_col++)); - muted_cell->property_activatable () = true; - muted_cell->signal_toggled ().connect (sigc::mem_fun (*this, &EditorRegions::muted_changed)); - - CellRendererToggle* opaque_cell = dynamic_cast (_display.get_column_cell_renderer (check_start_col)); - opaque_cell->property_activatable () = true; - opaque_cell->signal_toggled ().connect (sigc::mem_fun (*this, &EditorRegions::opaque_changed)); - - _display.get_selection ()->set_mode (SELECTION_MULTIPLE); _display.add_object_drag (_columns.region.index (), "x-ardour/region.erl", TARGET_SAME_APP); _display.set_drag_column (_columns.name.index ()); - /* setup DnD handling */ - - _scroller.add (_display); - _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC); - - _display.signal_button_press_event ().connect (sigc::mem_fun (*this, &EditorRegions::button_press), false); - _change_connection = _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &EditorRegions::selection_changed)); - - _scroller.signal_key_press_event ().connect (sigc::mem_fun (*this, &EditorRegions::key_press), false); - _scroller.signal_focus_in_event ().connect (sigc::mem_fun (*this, &EditorRegions::focus_in), false); - _scroller.signal_focus_out_event ().connect (sigc::mem_fun (*this, &EditorRegions::focus_out)); - - _display.signal_enter_notify_event ().connect (sigc::mem_fun (*this, &EditorRegions::enter_notify), false); - _display.signal_leave_notify_event ().connect (sigc::mem_fun (*this, &EditorRegions::leave_notify), false); - - ARDOUR_UI::instance ()->primary_clock->mode_changed.connect (sigc::mem_fun (*this, &EditorRegions::clock_format_changed)); - - e->EditorFreeze.connect (editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::freeze_tree_model, this), gui_context ()); - e->EditorThaw.connect (editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::thaw_tree_model, this), gui_context ()); -} - -bool -EditorRegions::focus_in (GdkEventFocus*) -{ - Window* win = dynamic_cast (_scroller.get_toplevel ()); - - if (win) { - old_focus = win->get_focus (); - } else { - old_focus = 0; - } - - name_editable = 0; - tags_editable = 0; - - /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */ - return true; -} - -bool -EditorRegions::focus_out (GdkEventFocus*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - name_editable = 0; - tags_editable = 0; - - return false; -} - -bool -EditorRegions::enter_notify (GdkEventCrossing*) -{ - if (name_editable || tags_editable) { - return true; - } - - Keyboard::magic_widget_grab_focus (); - return false; -} - -bool -EditorRegions::leave_notify (GdkEventCrossing*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - Keyboard::magic_widget_drop_focus (); - return false; + e->EditorFreeze.connect (_editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::freeze_tree_model, this), gui_context ()); + e->EditorThaw.connect (_editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::thaw_tree_model, this), gui_context ()); } void -EditorRegions::set_session (ARDOUR::Session* s) +EditorRegions::init () { - SessionHandlePtr::set_session (s); + add_name_column (); + setup_col (append_col (_columns.channels, "Chans "), 1, ALIGN_LEFT, _("# Ch"), _("# Channels in the region")); + add_tag_column (); - ARDOUR::Region::RegionsPropertyChanged.connect (region_property_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::regions_changed, this, _1, _2), gui_context ()); - ARDOUR::RegionFactory::CheckNewRegion.connect (check_new_region_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::add_region, this, _1), gui_context ()); + int cb_width = 24; + int bbt_width, height; - redisplay (); -} + Glib::RefPtr layout = _display.create_pango_layout (X_("000|000|000")); + Gtkmm2ext::get_pixel_size (layout, bbt_width, height); -void -EditorRegions::add_region (boost::shared_ptr region) -{ - if (!region || !_session) { - return; - } + TreeViewColumn* tvc; - /* whole-file regions are shown in the Source List */ - if (region->whole_file ()) { - return; - } + tvc = append_col (_columns.start, bbt_width); + setup_col (tvc, 16, ALIGN_RIGHT, _("Start"), _("Position of start of region")); + tvc = append_col (_columns.length, bbt_width); + setup_col (tvc, 4, ALIGN_RIGHT, _("Length"), _("Length of the region")); - /* we only show files-on-disk. - * if there's some other kind of region, we ignore it (for now) - */ - boost::shared_ptr fs = boost::dynamic_pointer_cast (region->source()); - if (!fs || fs->empty()) { - return; - } + tvc = append_col (_columns.locked, cb_width); + setup_col (tvc, -1, ALIGN_CENTER, S_("Lock|L"), _("Region position locked?")); + setup_toggle (tvc, sigc::mem_fun (*this, &EditorRegions::locked_changed)); - PropertyChange pc; - boost::shared_ptr rl (new RegionList); - rl->push_back (region); - regions_changed (rl, pc); -} + tvc = append_col (_columns.glued, cb_width); + setup_col (tvc, -1, ALIGN_CENTER, S_("Glued|G"), _("Region position glued to Bars|Beats time?")); + setup_toggle (tvc, sigc::mem_fun (*this, &EditorRegions::glued_changed)); -void -EditorRegions::destroy_region (boost::shared_ptr region) -{ - //UNTESTED - //At the time of writing, the only way to remove regions is "cleanup" - //by definition, "cleanup" only removes regions that aren't on the timeline - //so this would be a no-op anyway - //perhaps someday we will allow users to manually destroy regions. - RegionRowMap::iterator map_it = region_row_map.find (region); - if (map_it != region_row_map.end ()) { - region_row_map.erase (map_it); - _model->erase (map_it->second); - } -} + tvc = append_col (_columns.muted, cb_width); + setup_col (tvc, -1, ALIGN_CENTER, S_("Mute|M"), _("Region muted?")); + setup_toggle (tvc, sigc::mem_fun (*this, &EditorRegions::muted_changed)); -void -EditorRegions::remove_unused_regions () -{ - vector choices; - string prompt; + tvc = append_col (_columns.opaque, cb_width); + setup_col (tvc, -1, ALIGN_CENTER, S_("Opaque|O"), _("Region opaque (blocks regions below it from being heard)?")); + setup_toggle (tvc, sigc::mem_fun (*this, &EditorRegions::opaque_changed)); - if (!_session) { - return; - } - - prompt = _ ("Do you really want to remove unused regions?" - "\n(This is destructive and cannot be undone)"); - - choices.push_back (_ ("No, do nothing.")); - choices.push_back (_ ("Yes, remove.")); - - ArdourWidgets::Choice prompter (_ ("Remove unused regions"), prompt, choices); - - if (prompter.run () == 1) { - _no_redisplay = true; - _session->cleanup_regions (); - _no_redisplay = false; - redisplay (); - } -} - -void -EditorRegions::regions_changed (boost::shared_ptr rl, const PropertyChange& what_changed) -{ - bool freeze = rl->size () > 2; - if (freeze) { - freeze_tree_model (); - } - for (RegionList::const_iterator i = rl->begin (); i != rl->end(); ++i) { - boost::shared_ptr r = *i; - - RegionRowMap::iterator map_it = region_row_map.find (r); - - boost::shared_ptr pl = r->playlist (); - if (!(pl && _session && _session->playlist_is_active (pl))) { - /* this region is not on an active playlist - * maybe it got deleted, or whatever */ - if (map_it != region_row_map.end ()) { - Gtk::TreeModel::iterator r = map_it->second; - region_row_map.erase (map_it); - _model->erase (r); - } - break; - } - - if (map_it != region_row_map.end ()) { - /* found the region, update its row properties */ - TreeModel::Row row = *(map_it->second); - populate_row (r, row, what_changed); - - } else { - /* new region, add it to the list */ - TreeModel::iterator iter = _model->append (); - TreeModel::Row row = *iter; - region_row_map.insert (pair, Gtk::TreeModel::iterator> (r, iter)); - - /* set the properties that don't change */ - row[_columns.region] = r; - - /* now populate the properties that might change... */ - populate_row (r, row, PropertyChange ()); - } - } - if (freeze) { - thaw_tree_model (); - } +#ifdef SHOW_REGION_EXTRAS + tvc = append_col (_columns.end, bbt_width); + setup_col (tvc, 5, ALIGN_RIGHT, _("End"), _("Position of end of region")); + tvc = append_col (_columns.sync, bbt_width); + setup_col (tvc, -1, ALIGN_RIGHT, _("Sync"), _("Position of region sync point, relative to start of the region")); + tvc = append_col (_columns.fadein, bbt_width); + setup_col (tvc, -1, ALIGN_RIGHT, _("Fade In"), _("Length of region fade-in (units: secondary clock, () if disabled")); + tvc = append_col (_columns.fadeout, bbt_width); + setup_col (tvc, -1, ALIGN_RIGHT, _("Fade out"), _("Length of region fade-out (units: secondary clock, () if disabled")); +#endif } void @@ -526,425 +160,14 @@ EditorRegions::set_selected (RegionSelection& regions) } } -void -EditorRegions::redisplay () -{ - if (_no_redisplay || !_session) { - return; - } - - /* store sort column id and type for later */ - _model->get_sort_column_id (_sort_col_id, _sort_type); - - _display.set_model (Glib::RefPtr (0)); - _model->clear (); - /* Disable sorting to gain performance */ - _model->set_sort_column (-2, SORT_ASCENDING); - - region_row_map.clear (); - - RegionFactory::foreach_region (sigc::mem_fun (*this, &EditorRegions::add_region)); - - _model->set_sort_column (_sort_col_id, _sort_type); // re-enabale sorting - _display.set_model (_model); -} - -void -EditorRegions::update_row (boost::shared_ptr region) -{ - if (!region || !_session) { - return; - } - - RegionRowMap::iterator it; - - it = region_row_map.find (region); - - if (it != region_row_map.end ()) { - PropertyChange c; - TreeModel::iterator j = it->second; - populate_row (region, (*j), c); - } -} - -void -EditorRegions::clock_format_changed () -{ - if (!_session) { - return; - } - - PropertyChange change; - change.add (ARDOUR::Properties::start); - change.add (ARDOUR::Properties::length); - change.add (ARDOUR::Properties::sync_position); - change.add (ARDOUR::Properties::fade_in); - change.add (ARDOUR::Properties::fade_out); - - RegionRowMap::iterator i; - - for (i = region_row_map.begin (); i != region_row_map.end (); ++i) { - TreeModel::iterator j = i->second; - - boost::shared_ptr region = (*j)[_columns.region]; - - populate_row (region, (*j), change); - } -} - -void -EditorRegions::format_position (timepos_t const & p, char* buf, size_t bufsize, bool onoff) -{ - Temporal::BBT_Time bbt; - Timecode::Time timecode; - samplepos_t pos (p.samples()); - - if (pos < 0) { - error << string_compose (_ ("EditorRegions::format_position: negative timecode position: %1"), pos) << endmsg; - snprintf (buf, bufsize, "invalid"); - return; - } - - switch (ARDOUR_UI::instance ()->primary_clock->mode ()) { - case AudioClock::BBT: - bbt = Temporal::TempoMap::use()->bbt_at (p); - if (onoff) { - snprintf (buf, bufsize, "%03d|%02d|%04d", bbt.bars, bbt.beats, bbt.ticks); - } else { - snprintf (buf, bufsize, "(%03d|%02d|%04d)", bbt.bars, bbt.beats, bbt.ticks); - } - break; - - case AudioClock::MinSec: - samplepos_t left; - int hrs; - int mins; - float secs; - - left = pos; - hrs = (int)floor (left / (_session->sample_rate () * 60.0f * 60.0f)); - left -= (samplecnt_t)floor (hrs * _session->sample_rate () * 60.0f * 60.0f); - mins = (int)floor (left / (_session->sample_rate () * 60.0f)); - left -= (samplecnt_t)floor (mins * _session->sample_rate () * 60.0f); - secs = left / (float)_session->sample_rate (); - if (onoff) { - snprintf (buf, bufsize, "%02d:%02d:%06.3f", hrs, mins, secs); - } else { - snprintf (buf, bufsize, "(%02d:%02d:%06.3f)", hrs, mins, secs); - } - break; - - case AudioClock::Seconds: - if (onoff) { - snprintf (buf, bufsize, "%.1f", pos / (float)_session->sample_rate ()); - } else { - snprintf (buf, bufsize, "(%.1f)", pos / (float)_session->sample_rate ()); - } - break; - - case AudioClock::Samples: - if (onoff) { - snprintf (buf, bufsize, "%" PRId64, pos); - } else { - snprintf (buf, bufsize, "(%" PRId64 ")", pos); - } - break; - - case AudioClock::Timecode: - default: - _session->timecode_time (pos, timecode); - if (onoff) { - snprintf (buf, bufsize, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); - } else { - snprintf (buf, bufsize, "(%02d:%02d:%02d:%02d)", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); - } - break; - } -} - -void -EditorRegions::populate_row (boost::shared_ptr region, TreeModel::Row const& row, PBD::PropertyChange const& what_changed) -{ - /* the grid is most interested in the regions that are *visible* in the editor. - * this is a convenient place to flag changes to the grid cache, on a visible region */ - PropertyChange grid_interests; - grid_interests.add (ARDOUR::Properties::length); - grid_interests.add (ARDOUR::Properties::sync_position); - - if (what_changed.contains (grid_interests)) { - _editor->mark_region_boundary_cache_dirty (); - } - - { - Gdk::Color c; - bool missing_source = boost::dynamic_pointer_cast (region->source ()) != NULL; - if (missing_source) { - set_color_from_rgba (c, UIConfiguration::instance ().color ("region list missing source")); - } else { - set_color_from_rgba (c, UIConfiguration::instance ().color ("region list whole file")); - } - row[_columns.color_] = c; - } - - boost::shared_ptr audioregion = boost::dynamic_pointer_cast (region); - - PropertyChange c; - const bool all = what_changed == c; - - if (all || what_changed.contains (Properties::length)) { - populate_row_position (region, row); - } - if (all || what_changed.contains (Properties::start) || what_changed.contains (Properties::sync_position)) { - populate_row_sync (region, row); - } - if (all || what_changed.contains (Properties::fade_in)) { - populate_row_fade_in (region, row, audioregion); - } - if (all || what_changed.contains (Properties::fade_out)) { - populate_row_fade_out (region, row, audioregion); - } - if (all || what_changed.contains (Properties::locked)) { - populate_row_locked (region, row); - } - if (all || what_changed.contains (Properties::time_domain)) { - populate_row_glued (region, row); - } - if (all || what_changed.contains (Properties::muted)) { - populate_row_muted (region, row); - } - if (all || what_changed.contains (Properties::opaque)) { - populate_row_opaque (region, row); - } - if (all || what_changed.contains (Properties::length)) { - populate_row_end (region, row); - populate_row_length (region, row); - } - if (all) { - populate_row_source (region, row); - } - if (all || what_changed.contains (Properties::name) || what_changed.contains (Properties::tags)) { - populate_row_name (region, row); - } -} - -#if 0 - if (audioRegion && fades_in_seconds) { - - samplepos_t left; - int mins; - int millisecs; - - left = audioRegion->fade_in()->back()->when; - mins = (int) floor (left / (_session->sample_rate() * 60.0f)); - left -= (samplepos_t) floor (mins * _session->sample_rate() * 60.0f); - millisecs = (int) floor ((left * 1000.0f) / _session->sample_rate()); - - if (audioRegion->fade_in()->back()->when >= _session->sample_rate()) { - sprintf (fadein_str, "%01dM %01dmS", mins, millisecs); - } else { - sprintf (fadein_str, "%01dmS", millisecs); - } - - left = audioRegion->fade_out()->back()->when; - mins = (int) floor (left / (_session->sample_rate() * 60.0f)); - left -= (samplepos_t) floor (mins * _session->sample_rate() * 60.0f); - millisecs = (int) floor ((left * 1000.0f) / _session->sample_rate()); - - if (audioRegion->fade_out()->back()->when >= _session->sample_rate()) { - sprintf (fadeout_str, "%01dM %01dmS", mins, millisecs); - } else { - sprintf (fadeout_str, "%01dmS", millisecs); - } - } -#endif - -void -EditorRegions::populate_row_length (boost::shared_ptr region, TreeModel::Row const& row) -{ - char buf[16]; - - if (ARDOUR_UI::instance ()->primary_clock->mode () == AudioClock::BBT) { - TempoMap::SharedPtr map (TempoMap::use()); - Temporal::BBT_Time bbt; /* uninitialized until full duration works */ - // Temporal::BBT_Time bbt = map->bbt_duration_at (region->position(), region->length()); - snprintf (buf, sizeof (buf), "%03d|%02d|%04d", bbt.bars, bbt.beats, bbt.ticks); - } else { - format_position (timepos_t (region->length ()), buf, sizeof (buf)); - } - - row[_columns.length] = buf; -} - -void -EditorRegions::populate_row_end (boost::shared_ptr region, TreeModel::Row const& row) -{ -#ifndef SHOW_REGION_EXTRAS - return; -#endif - - if (region->last_sample () >= region->first_sample ()) { - char buf[16]; - format_position (region->nt_last (), buf, sizeof (buf)); - row[_columns.end] = buf; - } else { - row[_columns.end] = "empty"; - } -} - -void -EditorRegions::populate_row_position (boost::shared_ptr region, TreeModel::Row const& row) -{ - row[_columns.position] = region->position (); - - char buf[16]; - format_position (region->position (), buf, sizeof (buf)); - row[_columns.start] = buf; -} - -void -EditorRegions::populate_row_sync (boost::shared_ptr region, TreeModel::Row const& row) -{ -#ifndef SHOW_REGION_EXTRAS - return; -#endif - if (region->sync_position () == region->position ()) { - row[_columns.sync] = _ ("Start"); - } else if (region->sync_position () == (region->last_sample ())) { - row[_columns.sync] = _ ("End"); - } else { - char buf[16]; - format_position (region->sync_position (), buf, sizeof (buf)); - row[_columns.sync] = buf; - } -} - -void -EditorRegions::populate_row_fade_in (boost::shared_ptr region, TreeModel::Row const& row, boost::shared_ptr audioregion) -{ -#ifndef SHOW_REGION_EXTRAS - return; -#endif - if (!audioregion) { - row[_columns.fadein] = ""; - } else { - char buf[32]; - format_position (audioregion->fade_in ()->back ()->when, buf, sizeof (buf), audioregion->fade_in_active ()); - row[_columns.fadein] = buf; - } -} - -void -EditorRegions::populate_row_fade_out (boost::shared_ptr region, TreeModel::Row const& row, boost::shared_ptr audioregion) -{ -#ifndef SHOW_REGION_EXTRAS - return; -#endif - if (!audioregion) { - row[_columns.fadeout] = ""; - } else { - char buf[32]; - format_position (audioregion->fade_out ()->back ()->when, buf, sizeof (buf), audioregion->fade_out_active ()); - row[_columns.fadeout] = buf; - } -} - -void -EditorRegions::populate_row_locked (boost::shared_ptr region, TreeModel::Row const& row) -{ - row[_columns.locked] = region->locked (); -} - -void -EditorRegions::populate_row_glued (boost::shared_ptr region, TreeModel::Row const& row) -{ - if (region->position_time_domain () == Temporal::BeatTime) { - row[_columns.glued] = true; - } else { - row[_columns.glued] = false; - } -} - -void -EditorRegions::populate_row_muted (boost::shared_ptr region, TreeModel::Row const& row) -{ - row[_columns.muted] = region->muted (); -} - -void -EditorRegions::populate_row_opaque (boost::shared_ptr region, TreeModel::Row const& row) -{ - row[_columns.opaque] = region->opaque (); -} - -void -EditorRegions::populate_row_name (boost::shared_ptr region, TreeModel::Row const& row) -{ - row[_columns.name] = Gtkmm2ext::markup_escape_text (region->name ()); - - if (region->data_type() == DataType::MIDI) { - row[_columns.channels] = 0; /*TODO: some better recognition of midi regions*/ - } else { - row[_columns.channels] = region->sources().size(); - } - - row[_columns.tags] = region->tags (); -} - -void -EditorRegions::populate_row_source (boost::shared_ptr region, TreeModel::Row const& row) -{ - if (boost::dynamic_pointer_cast (region->source ())) { - row[_columns.path] = _ ("MISSING ") + Gtkmm2ext::markup_escape_text (region->source ()->name ()); - } else { - row[_columns.path] = Gtkmm2ext::markup_escape_text (region->source ()->name ()); - } -} - void EditorRegions::show_context_menu (int button, int time) { using namespace Gtk::Menu_Helpers; - Gtk::Menu* menu = dynamic_cast (ActionManager::get_widget (X_ ("/PopupRegionMenu"))); + Gtk::Menu* menu = dynamic_cast (ActionManager::get_widget (X_("/PopupRegionMenu"))); menu->popup (button, time); } -bool -EditorRegions::key_press (GdkEventKey* ev) -{ - TreeViewColumn* col; - - switch (ev->keyval) { - case GDK_Tab: - case GDK_ISO_Left_Tab: - - if (name_editable) { - name_editable->editing_done (); - name_editable = 0; - } - - if (tags_editable) { - tags_editable->editing_done (); - tags_editable = 0; - } - - col = _display.get_column (0); // select&focus on name column - - if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - treeview_select_previous (_display, _model, col); - } else { - treeview_select_next (_display, _model, col); - } - - return true; - break; - - default: - break; - } - - return false; -} - bool EditorRegions::button_press (GdkEventButton* ev) { @@ -978,7 +201,7 @@ EditorRegions::button_press (GdkEventButton* ev) } void -EditorRegions::selection_mapover (sigc::slot > sl) +EditorRegions::selection_mapover (sigc::slot> sl) { Glib::RefPtr selection = _display.get_selection (); TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows (); @@ -1005,118 +228,28 @@ EditorRegions::selection_mapover (sigc::slot > s } } -bool -EditorRegions::selection_filter (const RefPtr& model, const TreeModel::Path& path, bool already_selected) -{ - if (already_selected) { - /* deselecting path, if it is selected, is OK */ - return true; - } - - /* not possible to select rows that do not represent regions, like "Hidden" */ - TreeModel::iterator iter = model->get_iter (path); - if (iter) { - boost::shared_ptr r = (*iter)[_columns.region]; - if (!r) { - return false; - } - } - - return true; -} - void -EditorRegions::name_editing_started (CellEditable* ce, const Glib::ustring& path) +EditorRegions::regions_changed (boost::shared_ptr rl, const PropertyChange& what_changed) { - name_editable = ce; + /* the grid is most interested in the regions that are *visible* in the editor. + * this is a convenient place to flag changes to the grid cache, on a visible region */ + PropertyChange grid_interests; + grid_interests.add (ARDOUR::Properties::length); + grid_interests.add (ARDOUR::Properties::sync_position); - /* give it a special name */ - - Gtk::Entry* e = dynamic_cast (ce); - - if (e) { - e->set_name (X_ ("RegionNameEditorEntry")); - - TreeIter iter; - if ((iter = _model->get_iter (path))) { - boost::shared_ptr region = (*iter)[_columns.region]; - - if (region) { - e->set_text (region->name ()); - } - } - } -} - -void -EditorRegions::name_edit (const std::string& path, const std::string& new_text) -{ - name_editable = 0; - - boost::shared_ptr region; - TreeIter row_iter; - - if ((row_iter = _model->get_iter (path))) { - region = (*row_iter)[_columns.region]; - (*row_iter)[_columns.name] = new_text; + if (what_changed.contains (grid_interests)) { + _editor->mark_region_boundary_cache_dirty (); } - if (region) { - region->set_name (new_text); - - populate_row_name (region, (*row_iter)); - } -} - -void -EditorRegions::tag_editing_started (CellEditable* ce, const Glib::ustring& path) -{ - tags_editable = ce; - - /* give it a special name */ - - Gtk::Entry* e = dynamic_cast (ce); - - if (e) { - e->set_name (X_ ("RegionTagEditorEntry")); - - TreeIter iter; - if ((iter = _model->get_iter (path))) { - boost::shared_ptr region = (*iter)[_columns.region]; - - if (region) { - e->set_text (region->tags ()); - } - } - } -} - -void -EditorRegions::tag_edit (const std::string& path, const std::string& new_text) -{ - tags_editable = 0; - - boost::shared_ptr region; - TreeIter row_iter; - - if ((row_iter = _model->get_iter (path))) { - region = (*row_iter)[_columns.region]; - (*row_iter)[_columns.tags] = new_text; - } - - if (region) { - region->set_tags (new_text); - - populate_row_name (region, (*row_iter)); - } + RegionListBase::regions_changed (rl, what_changed); } /** @return Region that has been dragged out of the list, or 0 */ boost::shared_ptr EditorRegions::get_dragged_region () { - list > regions; - TreeView* source; + list> regions; + TreeView* source; _display.get_object_drag_data (regions, &source); if (regions.empty ()) { @@ -1126,17 +259,6 @@ EditorRegions::get_dragged_region () return regions.front (); } -void -EditorRegions::clear () -{ - _display.set_model (Glib::RefPtr (0)); - _model->clear (); - _display.set_model (_model); - - /* Clean up the maps */ - region_row_map.clear (); -} - boost::shared_ptr EditorRegions::get_single_selection () { @@ -1160,100 +282,27 @@ EditorRegions::get_single_selection () } void -EditorRegions::freeze_tree_model () +EditorRegions::remove_unused_regions () { - /* store sort column id and type for later */ - _model->get_sort_column_id (_sort_col_id, _sort_type); - _change_connection.block (true); - _display.set_model (Glib::RefPtr (0)); - _model->set_sort_column (-2, SORT_ASCENDING); //Disable sorting to gain performance -} + vector choices; + string prompt; -void -EditorRegions::thaw_tree_model () -{ - _model->set_sort_column (_sort_col_id, _sort_type); // re-enabale sorting - _display.set_model (_model); - _change_connection.block (false); -} - -void -EditorRegions::locked_changed (std::string const& path) -{ - TreeIter i = _model->get_iter (path); - if (i) { - boost::shared_ptr region = (*i)[_columns.region]; - if (region) { - region->set_locked (!(*i)[_columns.locked]); - } - } -} - -void -EditorRegions::glued_changed (std::string const& path) -{ - TreeIter i = _model->get_iter (path); - if (i) { - boost::shared_ptr region = (*i)[_columns.region]; - if (region) { - /* `glued' means MusicTime, and we're toggling here */ - region->set_position_time_domain ((*i)[_columns.glued] ? Temporal::AudioTime : Temporal::BeatTime); - } - } -} - -void -EditorRegions::muted_changed (std::string const& path) -{ - TreeIter i = _model->get_iter (path); - if (i) { - boost::shared_ptr region = (*i)[_columns.region]; - if (region) { - region->set_muted (!(*i)[_columns.muted]); - } - } -} - -void -EditorRegions::opaque_changed (std::string const& path) -{ - TreeIter i = _model->get_iter (path); - if (i) { - boost::shared_ptr region = (*i)[_columns.region]; - if (region) { - region->set_opaque (!(*i)[_columns.opaque]); - } - } -} - -XMLNode& -EditorRegions::get_state () const -{ - XMLNode* node = new XMLNode (X_ ("RegionList")); - - //TODO: save sort state? - // node->set_property (X_("sort-col"), _sort_type); - // node->set_property (X_("sort-asc"), _sort_type); - - return *node; -} - -void -EditorRegions::set_state (const XMLNode& node) -{ - bool changed = false; - - if (node.name () != X_ ("RegionList")) { + if (!_session) { return; } - if (changed) { + prompt = _("Do you really want to remove unused regions?" + "\n(This is destructive and cannot be undone)"); + + choices.push_back (_("No, do nothing.")); + choices.push_back (_("Yes, remove.")); + + ArdourWidgets::Choice prompter (_("Remove unused regions"), prompt, choices); + + if (prompter.run () == 1) { + _no_redisplay = true; + _session->cleanup_regions (); + _no_redisplay = false; redisplay (); } } - -RefPtr -EditorRegions::remove_unused_regions_action () const -{ - return ActionManager::get_action (X_ ("RegionList"), X_ ("removeUnusedRegions")); -} diff --git a/gtk2_ardour/editor_regions.h b/gtk2_ardour/editor_regions.h index f65a907e33..3eba9e1586 100644 --- a/gtk2_ardour/editor_regions.h +++ b/gtk2_ardour/editor_regions.h @@ -3,6 +3,7 @@ * Copyright (C) 2009-2011 David Robillard * Copyright (C) 2009-2018 Paul Davis * Copyright (C) 2018 Ben Loftis + * Copyright (C) 2021 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,188 +22,35 @@ #ifndef __gtk_ardour_editor_regions_h__ #define __gtk_ardour_editor_regions_h__ -#include - -#include -#include -#include -#include - #include "editor_component.h" +#include "region_list_base.h" +#include "region_selection.h" -class EditorRegions : public EditorComponent, public ARDOUR::SessionHandlePtr +class EditorRegions : public EditorComponent, public RegionListBase { public: - EditorRegions (Editor *); + EditorRegions (Editor*); - void set_session (ARDOUR::Session *); - - Gtk::Widget& widget () { - return _scroller; - } - - void clear (); - - void set_selected (RegionSelection &); - void selection_mapover (sigc::slot >); + void set_selected (RegionSelection&); + void selection_mapover (sigc::slot>); + void remove_unused_regions (); boost::shared_ptr get_dragged_region (); boost::shared_ptr get_single_selection (); - void redisplay (); - - void suspend_redisplay () { - _no_redisplay = true; + void unselect_all () + { + _display.get_selection ()->unselect_all (); } - void resume_redisplay () { - _no_redisplay = false; - redisplay (); - } - - void block_change_connection (bool b) { - _change_connection.block (b); - } - - void unselect_all () { - _display.get_selection()->unselect_all (); - } - - void remove_unused_regions (); - - XMLNode& get_state () const; - void set_state (const XMLNode &); +protected: + void regions_changed (boost::shared_ptr, PBD::PropertyChange const&); private: - - struct Columns : public Gtk::TreeModel::ColumnRecord { - Columns () { - add (name); - add (channels); - add (tags); - add (start); - add (length); - add (end); - add (sync); - add (fadein); - add (fadeout); - add (locked); - add (glued); - add (muted); - add (opaque); - add (path); - add (region); - add (color_); - add (position); - } - - Gtk::TreeModelColumn name; - Gtk::TreeModelColumn channels; - Gtk::TreeModelColumn tags; - Gtk::TreeModelColumn position; - Gtk::TreeModelColumn start; - Gtk::TreeModelColumn end; - Gtk::TreeModelColumn length; - Gtk::TreeModelColumn sync; - Gtk::TreeModelColumn fadein; - Gtk::TreeModelColumn fadeout; - Gtk::TreeModelColumn locked; - Gtk::TreeModelColumn glued; - Gtk::TreeModelColumn muted; - Gtk::TreeModelColumn opaque; - Gtk::TreeModelColumn path; - Gtk::TreeModelColumn > region; - Gtk::TreeModelColumn color_; - }; - - Columns _columns; - - Gtk::TreeModel::RowReference last_row; - - void freeze_tree_model (); - void thaw_tree_model (); - void regions_changed (boost::shared_ptr, PBD::PropertyChange const &); + void init (); void selection_changed (); - - sigc::connection _change_connection; - - int _sort_col_id; - Gtk::SortType _sort_type; - - bool selection_filter (const Glib::RefPtr& model, const Gtk::TreeModel::Path& path, bool yn); - - Gtk::Widget* old_focus; - - Gtk::CellEditable* name_editable; - void name_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void name_edit (const std::string&, const std::string&); - - - Gtk::CellEditable* tags_editable; - void tag_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void tag_edit (const std::string&, const std::string&); - - - void locked_changed (std::string const &); - void glued_changed (std::string const &); - void muted_changed (std::string const &); - void opaque_changed (std::string const &); - - bool key_press (GdkEventKey *); - bool button_press (GdkEventButton *); - - bool focus_in (GdkEventFocus*); - bool focus_out (GdkEventFocus*); - bool enter_notify (GdkEventCrossing*); - bool leave_notify (GdkEventCrossing*); - + bool button_press (GdkEventButton*); void show_context_menu (int button, int time); - - void format_position (Temporal::timepos_t const & pos, char* buf, size_t bufsize, bool onoff = true); - - void add_region (boost::shared_ptr); - void destroy_region (boost::shared_ptr); - - void populate_row (boost::shared_ptr, Gtk::TreeModel::Row const &, PBD::PropertyChange const &); - void populate_row_used (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_position (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_end (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_sync (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_fade_in (boost::shared_ptr region, Gtk::TreeModel::Row const& row, boost::shared_ptr); - void populate_row_fade_out (boost::shared_ptr region, Gtk::TreeModel::Row const& row, boost::shared_ptr); - void populate_row_locked (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_muted (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_glued (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_opaque (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_length (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_name (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - void populate_row_source (boost::shared_ptr region, Gtk::TreeModel::Row const& row); - - void update_row (boost::shared_ptr); - - void clock_format_changed (); - - Glib::RefPtr remove_unused_regions_action () const; - - Gtk::Menu* _menu; - Gtk::ScrolledWindow _scroller; - Gtk::Frame _frame; - - Gtkmm2ext::DnDTreeView > _display; - - Glib::RefPtr _model; - - bool _no_redisplay; - - typedef boost::unordered_map, Gtk::TreeModel::iterator> RegionRowMap; - - RegionRowMap region_row_map; - - PBD::ScopedConnection region_property_connection; - PBD::ScopedConnection check_new_region_connection; - - PBD::ScopedConnection editor_freeze_connection; - PBD::ScopedConnection editor_thaw_connection; }; #endif /* __gtk_ardour_editor_regions_h__ */ diff --git a/gtk2_ardour/region_list_base.cc b/gtk2_ardour/region_list_base.cc new file mode 100644 index 0000000000..7dfb5701b3 --- /dev/null +++ b/gtk2_ardour/region_list_base.cc @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2009-2012 Carl Hetherington + * Copyright (C) 2009-2012 David Robillard + * Copyright (C) 2009-2018 Paul Davis + * Copyright (C) 2013-2021 Robin Gareus + * Copyright (C) 2015-2016 Tim Mayberry + * Copyright (C) 2016 Nick Mainsbridge + * Copyright (C) 2018-2019 Ben Loftis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include "ardour/audiofilesource.h" +#include "ardour/audioregion.h" +#include "ardour/region_factory.h" +#include "ardour/session.h" +#include "ardour/session_playlist.h" +#include "ardour/silentfilesource.h" + +#include "gtkmm2ext/treeutils.h" +#include "gtkmm2ext/utils.h" + +#include "widgets/tooltips.h" + +#include "actions.h" +#include "ardour_ui.h" +#include "audio_clock.h" +#include "gui_thread.h" +#include "keyboard.h" +#include "main_clock.h" +#include "region_list_base.h" +#include "ui_config.h" +#include "utils.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace ARDOUR_UI_UTILS; +using namespace PBD; +using namespace Gtk; +using namespace Temporal; + +using Gtkmm2ext::Keyboard; + +RegionListBase::RegionListBase () + : _name_editable (0) + , _tags_editable (0) + , _old_focus (0) + , _no_redisplay (false) +{ + _display.set_size_request (100, -1); + _display.set_rules_hint (true); + _display.set_name ("RegionList"); + _display.set_fixed_height_mode (true); + _display.set_reorderable (false); + + /* Try to prevent single mouse presses from initiating edits. + * This relies on a hack in gtktreeview.c:gtk_treeview_button_press() */ + _display.set_data ("mouse-edits-require-mod1", (gpointer)0x1); + + _model = TreeStore::create (_columns); + _model->set_sort_column (0, SORT_ASCENDING); + + _display.set_model (_model); + + _display.set_headers_visible (true); + _display.set_rules_hint (); + + if (UIConfiguration::instance ().get_use_tooltips ()) { + /* show path as the row tooltip */ + _display.set_tooltip_column (13); /* path */ + } + + _display.get_selection ()->set_mode (SELECTION_MULTIPLE); + + _scroller.add (_display); + _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC); + + _display.signal_button_press_event ().connect (sigc::mem_fun (*this, &RegionListBase::button_press), false); + _display.signal_enter_notify_event ().connect (sigc::mem_fun (*this, &RegionListBase::enter_notify), false); + _display.signal_leave_notify_event ().connect (sigc::mem_fun (*this, &RegionListBase::leave_notify), false); + _scroller.signal_focus_in_event ().connect (sigc::mem_fun (*this, &RegionListBase::focus_in), false); + _scroller.signal_focus_out_event ().connect (sigc::mem_fun (*this, &RegionListBase::focus_out)); + _scroller.signal_key_press_event ().connect (sigc::mem_fun (*this, &RegionListBase::key_press), false); + + ARDOUR_UI::instance ()->primary_clock->mode_changed.connect (sigc::mem_fun (*this, &RegionListBase::clock_format_changed)); +} + +void +RegionListBase::setup_col (TreeViewColumn* col, int sort_idx, Gtk::AlignmentEnum al, const char* label, const char* tooltip) +{ + /* add the label */ + Gtk::Label* l = manage (new Label (label)); + l->set_alignment (al); + ArdourWidgets::set_tooltip (*l, tooltip); + col->set_widget (*l); + l->show (); + + col->set_sort_column (sort_idx); + col->set_expand (false); + + /* this sets the alignment of the column header... */ + col->set_alignment (al); + + /* ...and this sets the alignment for the data cells */ + CellRendererText* renderer = dynamic_cast (col->get_first_cell_renderer ()); + if (renderer) { + renderer->property_xalign () = (al == ALIGN_RIGHT ? 1.0 : (al == ALIGN_LEFT ? 0.0 : 0.5)); + } +} + +void +RegionListBase::setup_toggle (Gtk::TreeViewColumn* tvc, sigc::slot cb) +{ + CellRendererToggle* tc = dynamic_cast (tvc->get_first_cell_renderer ()); + tc->property_activatable () = true; + tc->signal_toggled ().connect (cb); +} + +void +RegionListBase::add_name_column () +{ + TreeViewColumn* tvc = append_col (_columns.name, 120); + setup_col (tvc, 0, ALIGN_LEFT, _("Name"), ("Region name")); + + /* Region Name: make editable */ + CellRendererText* region_name_cell = dynamic_cast (tvc->get_first_cell_renderer ()); + region_name_cell->property_editable () = true; + region_name_cell->signal_edited ().connect (sigc::mem_fun (*this, &RegionListBase::name_edit)); + region_name_cell->signal_editing_started ().connect (sigc::mem_fun (*this, &RegionListBase::name_editing_started)); + /* Region Name: color turns red if source is missing. */ + tvc->add_attribute (region_name_cell->property_text (), _columns.name); + tvc->add_attribute (region_name_cell->property_foreground_gdk (), _columns.color_); + tvc->set_expand (true); +} + +void +RegionListBase::add_tag_column () +{ + TreeViewColumn* tvc = append_col (_columns.tags, "2099-10-10 10:10:30"); + setup_col (tvc, 2, ALIGN_LEFT, _("Tags"), _("Tags")); + + /* Tags cell: make editable */ + CellRendererText* region_tags_cell = dynamic_cast (tvc->get_first_cell_renderer ()); + region_tags_cell->property_editable () = true; + region_tags_cell->signal_edited ().connect (sigc::mem_fun (*this, &RegionListBase::tag_edit)); + region_tags_cell->signal_editing_started ().connect (sigc::mem_fun (*this, &RegionListBase::tag_editing_started)); +} + +bool +RegionListBase::focus_in (GdkEventFocus*) +{ + Window* win = dynamic_cast (_scroller.get_toplevel ()); + + if (win) { + _old_focus = win->get_focus (); + } else { + _old_focus = 0; + } + + _tags_editable = 0; + _name_editable = 0; + + /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */ + return true; +} + +bool +RegionListBase::focus_out (GdkEventFocus*) +{ + if (_old_focus) { + _old_focus->grab_focus (); + _old_focus = 0; + } + + _tags_editable = 0; + _name_editable = 0; + + return false; +} + +bool +RegionListBase::enter_notify (GdkEventCrossing*) +{ + if (_name_editable || _tags_editable) { + return true; + } + + Keyboard::magic_widget_grab_focus (); + return false; +} + +bool +RegionListBase::leave_notify (GdkEventCrossing*) +{ + if (_old_focus) { + _old_focus->grab_focus (); + _old_focus = 0; + } + Keyboard::magic_widget_drop_focus (); + return false; +} + +void +RegionListBase::set_session (ARDOUR::Session* s) +{ + SessionHandlePtr::set_session (s); + + if (!s) { + clear (); + return; + } + + ARDOUR::Region::RegionsPropertyChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&RegionListBase::regions_changed, this, _1, _2), gui_context ()); + ARDOUR::RegionFactory::CheckNewRegion.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&RegionListBase::add_region, this, _1), gui_context ()); + + redisplay (); +} + +void +RegionListBase::add_region (boost::shared_ptr region) +{ + if (!region || !_session) { + return; + } + + /* whole-file regions are shown in the Source List */ + if (region->whole_file ()) { + return; + } + + /* we only show files-on-disk. + * if there's some other kind of region, we ignore it (for now) + */ + boost::shared_ptr fs = boost::dynamic_pointer_cast (region->source ()); + if (!fs || fs->empty ()) { + return; + } + + PropertyChange pc; + boost::shared_ptr rl (new RegionList); + rl->push_back (region); + regions_changed (rl, pc); +} + +void +RegionListBase::regions_changed (boost::shared_ptr rl, const PropertyChange& what_changed) +{ + bool freeze = rl->size () > 2; + if (freeze) { + freeze_tree_model (); + } + for (RegionList::const_iterator i = rl->begin (); i != rl->end (); ++i) { + boost::shared_ptr r = *i; + + RegionRowMap::iterator map_it = region_row_map.find (r); + + boost::shared_ptr pl = r->playlist (); + if (!(pl && _session && _session->playlist_is_active (pl))) { + /* this region is not on an active playlist + * maybe it got deleted, or whatever */ + if (map_it != region_row_map.end ()) { + Gtk::TreeModel::iterator r = map_it->second; + region_row_map.erase (map_it); + _model->erase (r); + } + break; + } + + if (map_it != region_row_map.end ()) { + /* found the region, update its row properties */ + TreeModel::Row row = *(map_it->second); + populate_row (r, row, what_changed); + } else { + /* new region, add it to the list */ + TreeModel::iterator iter = _model->append (); + TreeModel::Row row = *iter; + region_row_map.insert (pair, Gtk::TreeModel::iterator> (r, iter)); + + /* set the properties that don't change */ + row[_columns.region] = r; + + /* now populate the properties that might change... */ + populate_row (r, row, PropertyChange ()); + } + } + if (freeze) { + thaw_tree_model (); + } +} + +void +RegionListBase::redisplay () +{ + if (_no_redisplay || !_session) { + return; + } + + /* store sort column id and type for later */ + _model->get_sort_column_id (_sort_col_id, _sort_type); + + _display.set_model (Glib::RefPtr (0)); + _model->clear (); + /* Disable sorting to gain performance */ + _model->set_sort_column (-2, SORT_ASCENDING); + + region_row_map.clear (); + + RegionFactory::foreach_region (sigc::mem_fun (*this, &RegionListBase::add_region)); + + /* re-enabale sorting */ + _model->set_sort_column (_sort_col_id, _sort_type); + _display.set_model (_model); +} + +void +RegionListBase::clock_format_changed () +{ + if (!_session) { + return; + } + + PropertyChange change; + change.add (ARDOUR::Properties::start); + change.add (ARDOUR::Properties::length); + change.add (ARDOUR::Properties::sync_position); + change.add (ARDOUR::Properties::fade_in); + change.add (ARDOUR::Properties::fade_out); + + TreeModel::Children rows = _model->children (); + for (TreeModel::iterator i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr r = (*i)[_columns.region]; + populate_row (r, *i, change); + } +} + +void +RegionListBase::format_position (timepos_t const& p, char* buf, size_t bufsize, bool onoff) +{ + Temporal::BBT_Time bbt; + Timecode::Time timecode; + samplepos_t pos (p.samples ()); + + if (pos < 0) { + error << string_compose (_("RegionListBase::format_position: negative timecode position: %1"), pos) << endmsg; + snprintf (buf, bufsize, "invalid"); + return; + } + + switch (ARDOUR_UI::instance ()->primary_clock->mode ()) { + case AudioClock::BBT: + bbt = Temporal::TempoMap::use ()->bbt_at (p); + if (onoff) { + snprintf (buf, bufsize, "%03d|%02d|%04d", bbt.bars, bbt.beats, bbt.ticks); + } else { + snprintf (buf, bufsize, "(%03d|%02d|%04d)", bbt.bars, bbt.beats, bbt.ticks); + } + break; + + case AudioClock::MinSec: + samplepos_t left; + int hrs; + int mins; + float secs; + + left = pos; + hrs = (int)floor (left / (_session->sample_rate () * 60.0f * 60.0f)); + left -= (samplecnt_t)floor (hrs * _session->sample_rate () * 60.0f * 60.0f); + mins = (int)floor (left / (_session->sample_rate () * 60.0f)); + left -= (samplecnt_t)floor (mins * _session->sample_rate () * 60.0f); + secs = left / (float)_session->sample_rate (); + if (onoff) { + snprintf (buf, bufsize, "%02d:%02d:%06.3f", hrs, mins, secs); + } else { + snprintf (buf, bufsize, "(%02d:%02d:%06.3f)", hrs, mins, secs); + } + break; + + case AudioClock::Seconds: + if (onoff) { + snprintf (buf, bufsize, "%.1f", pos / (float)_session->sample_rate ()); + } else { + snprintf (buf, bufsize, "(%.1f)", pos / (float)_session->sample_rate ()); + } + break; + + case AudioClock::Samples: + if (onoff) { + snprintf (buf, bufsize, "%" PRId64, pos); + } else { + snprintf (buf, bufsize, "(%" PRId64 ")", pos); + } + break; + + case AudioClock::Timecode: + default: + _session->timecode_time (pos, timecode); + if (onoff) { + snprintf (buf, bufsize, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); + } else { + snprintf (buf, bufsize, "(%02d:%02d:%02d:%02d)", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); + } + break; + } +} + +void +RegionListBase::populate_row (boost::shared_ptr region, TreeModel::Row const& row, PBD::PropertyChange const& what_changed) +{ + assert (region); + + { + Gdk::Color c; + bool missing_source = boost::dynamic_pointer_cast (region->source ()) != NULL; + if (missing_source) { + set_color_from_rgba (c, UIConfiguration::instance ().color ("region list missing source")); + } else { + set_color_from_rgba (c, UIConfiguration::instance ().color ("region list whole file")); + } + row[_columns.color_] = c; + } + + boost::shared_ptr audioregion = boost::dynamic_pointer_cast (region); + + PropertyChange c; + const bool all = what_changed == c; + + if (all || what_changed.contains (Properties::length)) { + populate_row_position (region, row); + } + if (all || what_changed.contains (Properties::start) || what_changed.contains (Properties::sync_position)) { + populate_row_sync (region, row); + } + if (all || what_changed.contains (Properties::fade_in)) { + populate_row_fade_in (region, row, audioregion); + } + if (all || what_changed.contains (Properties::fade_out)) { + populate_row_fade_out (region, row, audioregion); + } + if (all || what_changed.contains (Properties::locked)) { + populate_row_locked (region, row); + } + if (all || what_changed.contains (Properties::time_domain)) { + populate_row_glued (region, row); + } + if (all || what_changed.contains (Properties::muted)) { + populate_row_muted (region, row); + } + if (all || what_changed.contains (Properties::opaque)) { + populate_row_opaque (region, row); + } + if (all || what_changed.contains (Properties::length)) { + populate_row_end (region, row); + populate_row_length (region, row); + } + if (all) { + populate_row_source (region, row); + } + if (all || what_changed.contains (Properties::name) || what_changed.contains (Properties::tags)) { + populate_row_name (region, row); + } +} + +void +RegionListBase::populate_row_length (boost::shared_ptr region, TreeModel::Row const& row) +{ + char buf[16]; + + if (ARDOUR_UI::instance ()->primary_clock->mode () == AudioClock::BBT) { + TempoMap::SharedPtr map (TempoMap::use ()); + Temporal::BBT_Time bbt; /* uninitialized until full duration works */ + // Temporal::BBT_Time bbt = map->bbt_duration_at (region->position(), region->length()); + snprintf (buf, sizeof (buf), "%03d|%02d|%04d", bbt.bars, bbt.beats, bbt.ticks); + } else { + format_position (timepos_t (region->length ()), buf, sizeof (buf)); + } + + row[_columns.length] = buf; +} + +void +RegionListBase::populate_row_end (boost::shared_ptr region, TreeModel::Row const& row) +{ +#ifndef SHOW_REGION_EXTRAS + return; +#endif + + if (region->last_sample () >= region->first_sample ()) { + char buf[16]; + format_position (region->nt_last (), buf, sizeof (buf)); + row[_columns.end] = buf; + } else { + row[_columns.end] = "empty"; + } +} + +void +RegionListBase::populate_row_position (boost::shared_ptr region, TreeModel::Row const& row) +{ + row[_columns.position] = region->position (); + + char buf[16]; + format_position (region->position (), buf, sizeof (buf)); + row[_columns.start] = buf; +} + +void +RegionListBase::populate_row_sync (boost::shared_ptr region, TreeModel::Row const& row) +{ +#ifndef SHOW_REGION_EXTRAS + return; +#endif + if (region->sync_position () == region->position ()) { + row[_columns.sync] = _("Start"); + } else if (region->sync_position () == (region->last_sample ())) { + row[_columns.sync] = _("End"); + } else { + char buf[16]; + format_position (region->sync_position (), buf, sizeof (buf)); + row[_columns.sync] = buf; + } +} + +void +RegionListBase::populate_row_fade_in (boost::shared_ptr region, TreeModel::Row const& row, boost::shared_ptr audioregion) +{ +#ifndef SHOW_REGION_EXTRAS + return; +#endif + if (!audioregion) { + row[_columns.fadein] = ""; + } else { + char buf[32]; + format_position (audioregion->fade_in ()->back ()->when, buf, sizeof (buf), audioregion->fade_in_active ()); + row[_columns.fadein] = buf; + } +} + +void +RegionListBase::populate_row_fade_out (boost::shared_ptr region, TreeModel::Row const& row, boost::shared_ptr audioregion) +{ +#ifndef SHOW_REGION_EXTRAS + return; +#endif + if (!audioregion) { + row[_columns.fadeout] = ""; + } else { + char buf[32]; + format_position (audioregion->fade_out ()->back ()->when, buf, sizeof (buf), audioregion->fade_out_active ()); + row[_columns.fadeout] = buf; + } +} + +void +RegionListBase::populate_row_locked (boost::shared_ptr region, TreeModel::Row const& row) +{ + row[_columns.locked] = region->locked (); +} + +void +RegionListBase::populate_row_glued (boost::shared_ptr region, TreeModel::Row const& row) +{ + if (region->position_time_domain () == Temporal::BeatTime) { + row[_columns.glued] = true; + } else { + row[_columns.glued] = false; + } +} + +void +RegionListBase::populate_row_muted (boost::shared_ptr region, TreeModel::Row const& row) +{ + row[_columns.muted] = region->muted (); +} + +void +RegionListBase::populate_row_opaque (boost::shared_ptr region, TreeModel::Row const& row) +{ + row[_columns.opaque] = region->opaque (); +} + +void +RegionListBase::populate_row_name (boost::shared_ptr region, TreeModel::Row const& row) +{ + row[_columns.name] = Gtkmm2ext::markup_escape_text (region->name ()); + + if (region->data_type () == DataType::MIDI) { + row[_columns.channels] = 0; /*TODO: some better recognition of midi regions*/ + } else { + row[_columns.channels] = region->sources ().size (); + } + + row[_columns.tags] = region->tags (); +} + +void +RegionListBase::populate_row_source (boost::shared_ptr region, TreeModel::Row const& row) +{ + if (boost::dynamic_pointer_cast (region->source ())) { + row[_columns.path] = _("MISSING ") + Gtkmm2ext::markup_escape_text (region->source ()->name ()); + } else { + row[_columns.path] = Gtkmm2ext::markup_escape_text (region->source ()->name ()); + } +} + +bool +RegionListBase::key_press (GdkEventKey* ev) +{ + TreeViewColumn* col; + + switch (ev->keyval) { + case GDK_Tab: + case GDK_ISO_Left_Tab: + + if (_name_editable) { + _name_editable->editing_done (); + _name_editable = 0; + } + + if (_tags_editable) { + _tags_editable->editing_done (); + _tags_editable = 0; + } + + col = _display.get_column (0); // select&focus on name column + + if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + treeview_select_previous (_display, _model, col); + } else { + treeview_select_next (_display, _model, col); + } + + return true; + break; + + default: + break; + } + + return false; +} + +void +RegionListBase::name_editing_started (CellEditable* ce, const Glib::ustring& path) +{ + _name_editable = ce; + + /* give it a special name */ + + Gtk::Entry* e = dynamic_cast (ce); + + if (e) { + e->set_name (X_("RegionNameEditorEntry")); + + TreeIter iter; + if ((iter = _model->get_iter (path))) { + boost::shared_ptr region = (*iter)[_columns.region]; + + if (region) { + e->set_text (region->name ()); + } + } + } +} + +void +RegionListBase::name_edit (const std::string& path, const std::string& new_text) +{ + _name_editable = 0; + + boost::shared_ptr region; + TreeIter row_iter; + + if ((row_iter = _model->get_iter (path))) { + region = (*row_iter)[_columns.region]; + (*row_iter)[_columns.name] = new_text; + } + + if (region) { + region->set_name (new_text); + + populate_row_name (region, (*row_iter)); + } +} + +void +RegionListBase::tag_editing_started (CellEditable* ce, const Glib::ustring& path) +{ + _tags_editable = ce; + + /* give it a special name */ + + Gtk::Entry* e = dynamic_cast (ce); + + if (e) { + e->set_name (X_("RegionTagEditorEntry")); + + TreeIter iter; + if ((iter = _model->get_iter (path))) { + boost::shared_ptr region = (*iter)[_columns.region]; + + if (region) { + e->set_text (region->tags ()); + } + } + } +} + +void +RegionListBase::tag_edit (const std::string& path, const std::string& new_text) +{ + _tags_editable = 0; + + boost::shared_ptr region; + TreeIter row_iter; + + if ((row_iter = _model->get_iter (path))) { + region = (*row_iter)[_columns.region]; + (*row_iter)[_columns.tags] = new_text; + } + + if (region) { + region->set_tags (new_text); + + populate_row_name (region, (*row_iter)); + } +} + +void +RegionListBase::clear () +{ + _display.set_model (Glib::RefPtr (0)); + _model->clear (); + _display.set_model (_model); + + /* Clean up the maps */ + region_row_map.clear (); +} + +void +RegionListBase::freeze_tree_model () +{ + /* store sort column id and type for later */ + _model->get_sort_column_id (_sort_col_id, _sort_type); + _change_connection.block (true); + _display.set_model (Glib::RefPtr (0)); + _model->set_sort_column (-2, SORT_ASCENDING); //Disable sorting to gain performance +} + +void +RegionListBase::thaw_tree_model () +{ + _model->set_sort_column (_sort_col_id, _sort_type); // re-enabale sorting + _display.set_model (_model); + _change_connection.block (false); +} + +void +RegionListBase::locked_changed (std::string const& path) +{ + TreeIter i = _model->get_iter (path); + if (i) { + boost::shared_ptr region = (*i)[_columns.region]; + if (region) { + region->set_locked (!(*i)[_columns.locked]); + } + } +} + +void +RegionListBase::glued_changed (std::string const& path) +{ + TreeIter i = _model->get_iter (path); + if (i) { + boost::shared_ptr region = (*i)[_columns.region]; + if (region) { + /* `glued' means MusicTime, and we're toggling here */ + region->set_position_time_domain ((*i)[_columns.glued] ? Temporal::AudioTime : Temporal::BeatTime); + } + } +} + +void +RegionListBase::muted_changed (std::string const& path) +{ + TreeIter i = _model->get_iter (path); + if (i) { + boost::shared_ptr region = (*i)[_columns.region]; + if (region) { + region->set_muted (!(*i)[_columns.muted]); + } + } +} + +void +RegionListBase::opaque_changed (std::string const& path) +{ + TreeIter i = _model->get_iter (path); + if (i) { + boost::shared_ptr region = (*i)[_columns.region]; + if (region) { + region->set_opaque (!(*i)[_columns.opaque]); + } + } +} diff --git a/gtk2_ardour/region_list_base.h b/gtk2_ardour/region_list_base.h new file mode 100644 index 0000000000..e6542def03 --- /dev/null +++ b/gtk2_ardour/region_list_base.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2009-2011 Carl Hetherington + * Copyright (C) 2009-2011 David Robillard + * Copyright (C) 2009-2018 Paul Davis + * Copyright (C) 2018 Ben Loftis + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef _gtk_ardour_region_list_base_h_ +#define _gtk_ardour_region_list_base_h_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "gtkmm2ext/utils.h" + +#include "pbd/properties.h" +#include "pbd/signals.h" + +#include "ardour/session_handle.h" +#include "ardour/types.h" + +#include "gtkmm2ext/dndtreeview.h" + +//#define SHOW_REGION_EXTRAS + +namespace ARDOUR +{ + class Region; + class AudioRegion; +} + +class RegionListBase : public ARDOUR::SessionHandlePtr +{ +public: + RegionListBase (); + + void set_session (ARDOUR::Session*); + + Gtk::Widget& widget () + { + return _scroller; + } + + void clear (); + + void redisplay (); + + void suspend_redisplay () + { + _no_redisplay = true; + } + + void resume_redisplay () + { + _no_redisplay = false; + redisplay (); + } + + void block_change_connection (bool b) + { + _change_connection.block (b); + } + + void unselect_all () + { + _display.get_selection ()->unselect_all (); + } + +protected: + struct Columns : public Gtk::TreeModel::ColumnRecord { + Columns () + { + add (name); // 0 + add (channels); // 1 + add (tags); // 2 + add (start); // 3 + add (length); // 3 + add (end); // 5 + add (sync); // 6 + add (fadein); // 7 + add (fadeout); // 8 + add (locked); // 9 + add (glued); // 10 + add (muted); // 11 + add (opaque); // 12 + add (path); // 13 + add (region); // 14 + add (color_); // 15 + add (position); // 16 + /* src-list */ + add (captd_for); // 17 + add (take_id); // 18 + add (natural_pos); // 19 + add (natural_s); // 20 + add (captd_xruns); + } + + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn channels; + Gtk::TreeModelColumn tags; + Gtk::TreeModelColumn position; + Gtk::TreeModelColumn start; + Gtk::TreeModelColumn end; + Gtk::TreeModelColumn length; + Gtk::TreeModelColumn sync; + Gtk::TreeModelColumn fadein; + Gtk::TreeModelColumn fadeout; + Gtk::TreeModelColumn locked; + Gtk::TreeModelColumn glued; + Gtk::TreeModelColumn muted; + Gtk::TreeModelColumn opaque; + Gtk::TreeModelColumn path; + Gtk::TreeModelColumn> region; + Gtk::TreeModelColumn color_; + Gtk::TreeModelColumn captd_for; + Gtk::TreeModelColumn take_id; + Gtk::TreeModelColumn natural_pos; + Gtk::TreeModelColumn natural_s; + Gtk::TreeModelColumn captd_xruns; + }; + + void add_name_column (); + void add_tag_column (); + + template + Gtk::TreeViewColumn* append_col (Gtk::TreeModelColumn const& col, int width) + { + Gtk::TreeViewColumn* c = manage (new Gtk::TreeViewColumn ("", col)); + c->set_fixed_width (width); + c->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED); + _display.append_column (*c); + return c; + } + + template + Gtk::TreeViewColumn* append_col (Gtk::TreeModelColumn const& col, std::string const& sizing_text) + { + int w, h; + Glib::RefPtr layout = _display.create_pango_layout (sizing_text); + Gtkmm2ext::get_pixel_size (layout, w, h); + return append_col (col, w); + } + + void setup_col (Gtk::TreeViewColumn*, int, Gtk::AlignmentEnum, const char*, const char*); + void setup_toggle (Gtk::TreeViewColumn*, sigc::slot); + + void freeze_tree_model (); + void thaw_tree_model (); + + virtual void regions_changed (boost::shared_ptr, PBD::PropertyChange const&); + + void name_editing_started (Gtk::CellEditable*, const Glib::ustring&); + void name_edit (const std::string&, const std::string&); + void tag_editing_started (Gtk::CellEditable*, const Glib::ustring&); + void tag_edit (const std::string&, const std::string&); + + void locked_changed (std::string const&); + void glued_changed (std::string const&); + void muted_changed (std::string const&); + void opaque_changed (std::string const&); + + bool key_press (GdkEventKey*); + + virtual bool button_press (GdkEventButton*) + { + return false; + } + + bool focus_in (GdkEventFocus*); + bool focus_out (GdkEventFocus*); + + bool enter_notify (GdkEventCrossing*); + bool leave_notify (GdkEventCrossing*); + + void format_position (Temporal::timepos_t const& pos, char* buf, size_t bufsize, bool onoff = true); + + void add_region (boost::shared_ptr); + + void populate_row (boost::shared_ptr, Gtk::TreeModel::Row const&, PBD::PropertyChange const&); + void populate_row_used (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_position (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_end (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_sync (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_fade_in (boost::shared_ptr region, Gtk::TreeModel::Row const& row, boost::shared_ptr); + void populate_row_fade_out (boost::shared_ptr region, Gtk::TreeModel::Row const& row, boost::shared_ptr); + void populate_row_locked (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_muted (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_glued (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_opaque (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_length (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_name (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + void populate_row_source (boost::shared_ptr region, Gtk::TreeModel::Row const& row); + + void clock_format_changed (); + + Columns _columns; + + int _sort_col_id; + Gtk::SortType _sort_type; + + Gtk::CellEditable* _name_editable; + Gtk::CellEditable* _tags_editable; + Gtk::Widget* _old_focus; + + Gtk::ScrolledWindow _scroller; + Gtk::Frame _frame; + + Gtkmm2ext::DnDTreeView> _display; + + Glib::RefPtr _model; + + bool _no_redisplay; + + typedef boost::unordered_map, Gtk::TreeModel::iterator> RegionRowMap; + + RegionRowMap region_row_map; + + sigc::connection _change_connection; + + PBD::ScopedConnection _editor_freeze_connection; + PBD::ScopedConnection _editor_thaw_connection; +}; + +#endif /* _gtk_ardour_region_list_base_h_ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 21ccd082b8..a8b9ecd9d0 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -250,6 +250,7 @@ gtk2_ardour_sources = [ 'region_editor.cc', 'region_gain_line.cc', 'region_layering_order_editor.cc', + 'region_list_base.cc', 'region_peak_cursor.cc', 'region_selection.cc', 'region_view.cc', From 0204ea1f24c4fdb9f75968539d3c8e1207d9e62c Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 18 Jan 2022 06:52:57 +0100 Subject: [PATCH 04/10] Unify Region Drag/Drop Identify Regions using PDB::ID. This allows dragging regions from almost anywhere to anywhere, without special cases. --- gtk2_ardour/editor.cc | 10 ------ gtk2_ardour/editor.h | 13 +------- gtk2_ardour/editor_canvas.cc | 9 ++---- gtk2_ardour/editor_canvas_events.cc | 50 +++++++++-------------------- gtk2_ardour/editor_regions.cc | 18 ----------- gtk2_ardour/editor_regions.h | 1 - gtk2_ardour/editor_sources.cc | 25 ++++++++------- gtk2_ardour/editor_sources.h | 8 ++--- gtk2_ardour/public_editor.h | 3 -- gtk2_ardour/region_list_base.cc | 20 ++++++++++++ gtk2_ardour/region_list_base.h | 2 ++ gtk2_ardour/trigger_page.cc | 8 ++--- gtk2_ardour/triggerbox_ui.cc | 15 --------- 13 files changed, 64 insertions(+), 118 deletions(-) diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 9b6791889f..b669e818f9 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -5875,16 +5875,6 @@ Editor::consider_auditioning (boost::shared_ptr region) last_audition_region = r; } -boost::shared_ptr -Editor::get_dragged_region_from_sidebar () -{ - boost::shared_ptr rv = _regions->get_dragged_region (); - if (!rv) { - rv = _sources->get_dragged_region (); - } - return rv; -} - void Editor::hide_a_region (boost::shared_ptr r) { diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 4c0606e26c..2386074b10 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -226,8 +226,6 @@ public: void hide_a_region (boost::shared_ptr); void show_a_region (boost::shared_ptr); - boost::shared_ptr get_dragged_region_from_sidebar (); - #ifdef USE_RUBBERBAND std::vector rb_opt_strings; int rb_current_opt; @@ -2123,16 +2121,7 @@ private: gint y, const Gtk::SelectionData& data, guint info, - guint time, - bool from_region_list); - - void drop_routes ( - const Glib::RefPtr& context, - gint x, - gint y, - const Gtk::SelectionData& data, - guint info, - guint time); + guint time); /* audio export */ diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc index 85e0183c12..ef29af4ac2 100644 --- a/gtk2_ardour/editor_canvas.cc +++ b/gtk2_ardour/editor_canvas.cc @@ -268,8 +268,7 @@ Editor::initialize_canvas () vector target_table; - target_table.push_back (TargetEntry ("x-ardour/region.erl", TARGET_SAME_APP)); // DnD from the region list will generate this target - target_table.push_back (TargetEntry ("x-ardour/region.esl", TARGET_SAME_APP)); // DnD from the source list will generate this target + target_table.push_back (TargetEntry ("x-ardour/region.pbdid", TARGET_SAME_APP)); target_table.push_back (TargetEntry ("text/uri-list")); target_table.push_back (TargetEntry ("text/plain")); target_table.push_back (TargetEntry ("application/x-rootwin-drop")); @@ -391,10 +390,8 @@ Editor::track_canvas_drag_data_received (const RefPtr& context if (!ARDOUR_UI_UTILS::engine_is_running ()) { return; } - if (data.get_target() == "x-ardour/region.erl") { - drop_regions (context, x, y, data, info, time, true); - } else if (data.get_target() == "x-ardour/region.esl") { - drop_regions (context, x, y, data, info, time, false); + if (data.get_target() == "x-ardour/region.pbdid") { + drop_regions (context, x, y, data, info, time); } else { drop_paths (context, x, y, data, info, time); } diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc index 31fcb4c538..0fd8b6393d 100644 --- a/gtk2_ardour/editor_canvas_events.cc +++ b/gtk2_ardour/editor_canvas_events.cc @@ -1210,39 +1210,27 @@ Editor::track_canvas_drag_motion (Glib::RefPtr const& context, if (can_drop) { - if (target == "x-ardour/region.erl") { - region = _regions->get_dragged_region (); - } else if (target == "x-ardour/region.esl") { - region = _sources->get_dragged_region (); - } + if (target == "x-ardour/region.pbdid") { +#if 0 + // TODO check drag_source::drag_data_get() -> SelectionData& region - if (region) { - - if (tv.first == 0 - && ( - boost::dynamic_pointer_cast (region) != 0 || - boost::dynamic_pointer_cast (region) != 0 - ) - ) - { + if (tv.first == 0 && region) { /* drop to drop-zone */ context->drag_status (context->get_suggested_action(), time); return true; } - if ((boost::dynamic_pointer_cast (region) != 0 && - dynamic_cast (tv.first) != 0) || - (boost::dynamic_pointer_cast (region) != 0 && - dynamic_cast (tv.first) != 0)) { - - /* audio to audio - OR - midi to midi - */ - + if ((boost::dynamic_pointer_cast (region) != 0 && dynamic_cast (tv.first) != 0) || + (boost::dynamic_pointer_cast (region) != 0 && dynamic_cast (tv.first) != 0)) { + /* audio to audio OR MIDI to MIDI */ context->drag_status (context->get_suggested_action(), time); return true; } +#else + /* region drop always works */ + context->drag_status (context->get_suggested_action(), time); +#endif + return true; } else { /* DND originating from outside ardour * @@ -1270,9 +1258,8 @@ Editor::track_canvas_drag_motion (Glib::RefPtr const& context, void Editor::drop_regions (const Glib::RefPtr& /*context*/, int x, int y, - const SelectionData& /*data*/, - guint /*info*/, guint /*time*/, - bool from_region_list) + const SelectionData& data, + guint /*info*/, guint /*time*/) { GdkEvent event; double px; @@ -1285,13 +1272,8 @@ Editor::drop_regions (const Glib::RefPtr& /*context*/, event.motion.state = Gdk::BUTTON1_MASK; samplepos_t const pos = window_event_sample (&event, &px, &py); - boost::shared_ptr region; - - if (from_region_list) { - region = _regions->get_dragged_region (); - } else { - region = _sources->get_dragged_region (); - } + PBD::ID rid (data.get_data_as_string ()); + boost::shared_ptr region = RegionFactory::region_by_id (rid); if (!region) { return; } diff --git a/gtk2_ardour/editor_regions.cc b/gtk2_ardour/editor_regions.cc index 3f946dc1c2..4ffe2adbbb 100644 --- a/gtk2_ardour/editor_regions.cc +++ b/gtk2_ardour/editor_regions.cc @@ -55,9 +55,6 @@ EditorRegions::EditorRegions (Editor* e) _change_connection = _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &EditorRegions::selection_changed)); - _display.add_object_drag (_columns.region.index (), "x-ardour/region.erl", TARGET_SAME_APP); - _display.set_drag_column (_columns.name.index ()); - e->EditorFreeze.connect (_editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::freeze_tree_model, this), gui_context ()); e->EditorThaw.connect (_editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorRegions::thaw_tree_model, this), gui_context ()); } @@ -244,21 +241,6 @@ EditorRegions::regions_changed (boost::shared_ptr rl, const Property RegionListBase::regions_changed (rl, what_changed); } -/** @return Region that has been dragged out of the list, or 0 */ -boost::shared_ptr -EditorRegions::get_dragged_region () -{ - list> regions; - TreeView* source; - _display.get_object_drag_data (regions, &source); - - if (regions.empty ()) { - return boost::shared_ptr (); - } - - return regions.front (); -} - boost::shared_ptr EditorRegions::get_single_selection () { diff --git a/gtk2_ardour/editor_regions.h b/gtk2_ardour/editor_regions.h index 3eba9e1586..2227646f5d 100644 --- a/gtk2_ardour/editor_regions.h +++ b/gtk2_ardour/editor_regions.h @@ -35,7 +35,6 @@ public: void selection_mapover (sigc::slot>); void remove_unused_regions (); - boost::shared_ptr get_dragged_region (); boost::shared_ptr get_single_selection (); void unselect_all () diff --git a/gtk2_ardour/editor_sources.cc b/gtk2_ardour/editor_sources.cc index 7499edeeda..86cf876112 100644 --- a/gtk2_ardour/editor_sources.cc +++ b/gtk2_ardour/editor_sources.cc @@ -227,11 +227,13 @@ EditorSources::EditorSources (Editor* e) tv_col->set_expand (true); _display.get_selection()->set_mode (SELECTION_MULTIPLE); - _display.add_object_drag (_columns.region.index (), "x-ardour/region.esl", TARGET_SAME_APP); + + /* Set up DnD Source */ + _display.add_object_drag (_columns.region.index (), "x-ardour/region.pbdid", TARGET_SAME_APP); _display.set_drag_column (_columns.name.index()); + _display.signal_drag_data_get ().connect (sigc::mem_fun (*this, &EditorSources::drag_data_get)); - /* setup DnD handling */ - + /* setup DnD Receive */ list source_list_target_table; source_list_target_table.push_back (TargetEntry ("text/plain")); @@ -949,20 +951,21 @@ EditorSources::drag_data_received (const RefPtr& context, } } -/** @return Region that has been dragged out of the list, or 0 */ -boost::shared_ptr -EditorSources::get_dragged_region () +void +EditorSources::drag_data_get (Glib::RefPtr const&, Gtk::SelectionData& data, guint, guint) { + if (data.get_target () != "x-ardour/region.pbdid") { + return; + } + list > regions; TreeView* region; _display.get_object_drag_data (regions, ®ion); - if (regions.empty()) { - return boost::shared_ptr (); + if (!regions.empty ()) { + assert (regions.size() == 1); + data.set (data.get_target (), regions.front()->id ().to_s ()); } - - assert (regions.size() == 1); - return regions.front (); } void diff --git a/gtk2_ardour/editor_sources.h b/gtk2_ardour/editor_sources.h index 2e5b9493bf..01ced48efe 100644 --- a/gtk2_ardour/editor_sources.h +++ b/gtk2_ardour/editor_sources.h @@ -25,6 +25,8 @@ #include #include +#include "gtkmm2ext/dndtreeview.h" + #include "editor_component.h" #include "selection.h" @@ -42,7 +44,6 @@ public: void clear (); - boost::shared_ptr get_dragged_region (); boost::shared_ptr get_single_selection (); void unselect_all () { @@ -132,9 +133,8 @@ private: void redisplay (); - void drag_data_received ( - Glib::RefPtr const &, gint, gint, Gtk::SelectionData const &, guint, guint - ); + void drag_data_get (Glib::RefPtr const&, Gtk::SelectionData&, guint, guint); + void drag_data_received (Glib::RefPtr const &, gint, gint, Gtk::SelectionData const &, guint, guint); Gtk::ScrolledWindow _scroller; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 323baf2bf5..d2c52305d4 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -203,9 +203,6 @@ public: */ virtual void consider_auditioning (boost::shared_ptr r) = 0; - /* Editor::_regions DnD */ - virtual boost::shared_ptr get_dragged_region_from_sidebar () = 0; - /* import dialogs -> ardour-ui ?! */ virtual void external_audio_dialog () = 0; virtual void session_import_dialog () = 0; diff --git a/gtk2_ardour/region_list_base.cc b/gtk2_ardour/region_list_base.cc index 7dfb5701b3..cc3b6ea89e 100644 --- a/gtk2_ardour/region_list_base.cc +++ b/gtk2_ardour/region_list_base.cc @@ -77,6 +77,10 @@ RegionListBase::RegionListBase () _model = TreeStore::create (_columns); _model->set_sort_column (0, SORT_ASCENDING); + _display.add_object_drag (_columns.region.index (), "x-ardour/region.pbdid", Gtk::TARGET_SAME_APP); + _display.set_drag_column (_columns.name.index ()); + _display.signal_drag_data_get ().connect (sigc::mem_fun (*this, &RegionListBase::drag_data_get)); + _display.set_model (_model); _display.set_headers_visible (true); @@ -217,6 +221,22 @@ RegionListBase::leave_notify (GdkEventCrossing*) return false; } +void +RegionListBase::drag_data_get (Glib::RefPtr const&, Gtk::SelectionData& data, guint, guint) +{ + if (data.get_target () != "x-ardour/region.pbdid") { + return; + } + + list> regions; + TreeView* source; + _display.get_object_drag_data (regions, &source); + + if (!regions.empty ()) { + data.set (data.get_target (), regions.front ()->id ().to_s ()); + } +} + void RegionListBase::set_session (ARDOUR::Session* s) { diff --git a/gtk2_ardour/region_list_base.h b/gtk2_ardour/region_list_base.h index e6542def03..8393c9b946 100644 --- a/gtk2_ardour/region_list_base.h +++ b/gtk2_ardour/region_list_base.h @@ -213,6 +213,8 @@ protected: void clock_format_changed (); + void drag_data_get (Glib::RefPtr const&, Gtk::SelectionData&, guint, guint); + Columns _columns; int _sort_col_id; diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index d31aa03ce5..ed4b12095c 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -108,8 +108,7 @@ TriggerPage::TriggerPage () _no_strips.signal_drag_data_received ().connect (sigc::mem_fun (*this, &TriggerPage::no_strip_drag_data_received)); std::vector target_table; - target_table.push_back (Gtk::TargetEntry ("x-ardour/region.erl", Gtk::TARGET_SAME_APP)); - target_table.push_back (Gtk::TargetEntry ("x-ardour/region.esl", Gtk::TARGET_SAME_APP)); + target_table.push_back (Gtk::TargetEntry ("x-ardour/region.pbdid", Gtk::TARGET_SAME_APP)); target_table.push_back (Gtk::TargetEntry ("text/uri-list")); target_table.push_back (Gtk::TargetEntry ("text/plain")); target_table.push_back (Gtk::TargetEntry ("application/x-rootwin-drop")); @@ -527,8 +526,9 @@ TriggerPage::no_strip_drag_motion (Glib::RefPtr const& context void TriggerPage::no_strip_drag_data_received (Glib::RefPtr const& context, int /*x*/, int y, Gtk::SelectionData const& data, guint /*info*/, guint time) { - if (data.get_target () == "x-ardour/region.erl" || data.get_target () == "x-ardour/region.esl") { - boost::shared_ptr region = PublicEditor::instance ().get_dragged_region_from_sidebar (); + if (data.get_target () == "x-ardour/region.pbdid") { + PBD::ID rid (data.get_data_as_string ()); + boost::shared_ptr region = RegionFactory::region_by_id (rid); boost::shared_ptr triggerbox; if (boost::dynamic_pointer_cast (region)) { diff --git a/gtk2_ardour/triggerbox_ui.cc b/gtk2_ardour/triggerbox_ui.cc index a6293e4dcc..52451a8809 100644 --- a/gtk2_ardour/triggerbox_ui.cc +++ b/gtk2_ardour/triggerbox_ui.cc @@ -855,8 +855,6 @@ TriggerBoxUI::TriggerBoxUI (ArdourCanvas::Item* parent, TriggerBox& tb) } std::vector target_table; - target_table.push_back (Gtk::TargetEntry ("x-ardour/region.erl", Gtk::TARGET_SAME_APP)); - target_table.push_back (Gtk::TargetEntry ("x-ardour/region.esl", Gtk::TARGET_SAME_APP)); target_table.push_back (Gtk::TargetEntry ("x-ardour/region.pbdid", Gtk::TARGET_SAME_APP)); target_table.push_back (Gtk::TargetEntry ("text/uri-list")); target_table.push_back (Gtk::TargetEntry ("text/plain")); @@ -986,21 +984,8 @@ TriggerBoxUI::drag_data_received (Glib::RefPtr const& context, context->drag_finish (false, false, time); return; } - if (data.get_target () == "x-ardour/region.erl" || data.get_target () == "x-ardour/region.esl") { - boost::shared_ptr region = PublicEditor::instance ().get_dragged_region_from_sidebar (); - if (region) { - _triggerbox.set_from_selection (n, region); - context->drag_finish (true, false, time); - } else { - context->drag_finish (false, false, time); - } - return; - } if (data.get_target () == "x-ardour/region.pbdid") { - /* Long term goal is to receive all information from another TriggerBox Slot, - * not just the region. - */ PBD::ID rid (data.get_data_as_string ()); boost::shared_ptr region = RegionFactory::region_by_id (rid); if (region) { From 37877fbdc2b2ffb479fe261eb9bb3d45c32453f8 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 01:59:05 +0100 Subject: [PATCH 05/10] Unify Source and RegionList abstraction --- gtk2_ardour/editor.cc | 1 - gtk2_ardour/editor_sources.cc | 898 ++++---------------------------- gtk2_ardour/editor_sources.h | 126 +---- gtk2_ardour/region_list_base.cc | 99 +++- gtk2_ardour/region_list_base.h | 13 +- gtk2_ardour/source_list_base.cc | 104 ++++ gtk2_ardour/source_list_base.h | 39 ++ gtk2_ardour/wscript | 1 + 8 files changed, 337 insertions(+), 944 deletions(-) create mode 100644 gtk2_ardour/source_list_base.cc create mode 100644 gtk2_ardour/source_list_base.h diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index b669e818f9..db80a3eda6 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -6203,7 +6203,6 @@ Editor::session_going_away () /* rip everything out of the list displays */ - _sources->clear (); _routes->clear (); _route_groups->clear (); diff --git a/gtk2_ardour/editor_sources.cc b/gtk2_ardour/editor_sources.cc index 86cf876112..8c678dab82 100644 --- a/gtk2_ardour/editor_sources.cc +++ b/gtk2_ardour/editor_sources.cc @@ -16,51 +16,32 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include #include -#include #include +#include -#include "pbd/basename.h" -#include "pbd/enumwriter.h" -#include "pbd/file_utils.h" - -#include "ardour/audioregion.h" -#include "ardour/source.h" #include "ardour/audiofilesource.h" -#include "ardour/silentfilesource.h" -#include "ardour/smf_source.h" #include "ardour/region_factory.h" #include "ardour/session.h" #include "ardour/session_directory.h" -#include "ardour/profile.h" - -#include "gtkmm2ext/treeutils.h" -#include "gtkmm2ext/utils.h" +#include "ardour/smf_source.h" +#include "ardour/source.h" #include "widgets/choice.h" -#include "widgets/tooltips.h" -#include "audio_clock.h" #include "context_menu_helper.h" -#include "editor.h" #include "editing.h" -#include "editing_convert.h" -#include "keyboard.h" -#include "ardour_ui.h" -#include "gui_thread.h" -#include "actions.h" -#include "region_view.h" -#include "utils.h" +#include "editor.h" #include "editor_drag.h" -#include "main_clock.h" +#include "editor_sources.h" +#include "gui_thread.h" +#include "keyboard.h" +#include "region_view.h" #include "ui_config.h" +#include "utils.h" #include "pbd/i18n.h" -#include "editor_sources.h" - using namespace std; using namespace ARDOUR; using namespace ArdourWidgets; @@ -68,170 +49,12 @@ using namespace ARDOUR_UI_UTILS; using namespace PBD; using namespace Gtk; using namespace Glib; -using namespace Editing; using Gtkmm2ext::Keyboard; -struct ColumnInfo { - int index; - const char* label; - const char* tooltip; -}; - EditorSources::EditorSources (Editor* e) : EditorComponent (e) - , old_focus (0) - , tags_editable (0) - , name_editable (0) { - _display.set_size_request (100, -1); - _display.set_rules_hint (true); - _display.set_name ("SourcesList"); - _display.set_fixed_height_mode (true); - - /* Try to prevent single mouse presses from initiating edits. - This relies on a hack in gtktreeview.c:gtk_treeview_button_press() - */ - _display.set_data ("mouse-edits-require-mod1", (gpointer) 0x1); - - _model = TreeStore::create (_columns); - _model->set_sort_column (0, SORT_ASCENDING); - - /* column widths */ - int bbt_width, date_width, chan_width, height; - - Glib::RefPtr layout = _display.create_pango_layout (X_("000|000|000")); - Gtkmm2ext::get_pixel_size (layout, bbt_width, height); - - Glib::RefPtr layout2 = _display.create_pango_layout (X_("2018-10-14 12:12:30")); - Gtkmm2ext::get_pixel_size (layout2, date_width, height); - - Glib::RefPtr layout3 = _display.create_pango_layout (X_("Chans ")); - Gtkmm2ext::get_pixel_size (layout3, chan_width, height); - - TreeViewColumn* col_name = manage (new TreeViewColumn ("", _columns.name)); - col_name->set_fixed_width (bbt_width*2); - col_name->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_name->set_sort_column(0); - - TreeViewColumn* col_chans = manage (new TreeViewColumn ("", _columns.channels)); - col_chans->set_fixed_width (chan_width); - col_chans->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_chans->set_sort_column(1); - - TreeViewColumn* captd_for = manage (new TreeViewColumn ("", _columns.captd_for)); - captd_for->set_fixed_width (date_width); - captd_for->set_sizing (TREE_VIEW_COLUMN_FIXED); - captd_for->set_sort_column(2); - - TreeViewColumn* col_tags = manage (new TreeViewColumn ("", _columns.tags)); - col_tags->set_fixed_width (date_width); - col_tags->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_tags->set_sort_column(3); - - TreeViewColumn* col_take_id = manage (new TreeViewColumn ("", _columns.take_id)); - col_take_id->set_fixed_width (date_width); - col_take_id->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_take_id->set_sort_column(4); - - TreeViewColumn* col_nat_pos = manage (new TreeViewColumn ("", _columns.natural_pos)); - col_nat_pos->set_fixed_width (bbt_width); - col_nat_pos->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_nat_pos->set_sort_column(9); - - TreeViewColumn* col_path = manage (new TreeViewColumn ("", _columns.path)); - col_path->set_fixed_width (bbt_width); - col_path->set_sizing (TREE_VIEW_COLUMN_FIXED); - col_path->set_sort_column(6); - - TreeViewColumn* captd_xruns = manage (new TreeViewColumn ("", _columns.captd_xruns)); - captd_xruns->set_fixed_width (chan_width * 1.25); - captd_xruns->set_sizing (TREE_VIEW_COLUMN_FIXED); - captd_xruns->set_sort_column(10); - - _display.append_column (*col_name); - _display.append_column (*col_chans); - _display.append_column (*captd_for); - _display.append_column (*captd_xruns); - _display.append_column (*col_tags); - _display.append_column (*col_take_id); - _display.append_column (*col_nat_pos); - _display.append_column (*col_path); - - TreeViewColumn* col; - Gtk::Label* l; - - ColumnInfo ci[] = { - { 0, _("Name"), _("Region name") }, - { 1, _("# Ch"), _("# Channels") }, - { 2, _("Captured For"), _("Original Track this was recorded on") }, - { 3, _("# Xruns"), _("Number of dropouts that occured during recording") }, - { 4, _("Tags"), _("Tags") }, - { 5, _("Take ID"), _("Take ID") }, - { 6, _("Orig Pos"), _("Original Position of the file on timeline, when it was recorded") }, - { 7, _("Path"), _("Path (folder) of the file location") }, - { -1, 0, 0 } - }; - - /* make Name and Path columns manually resizable */ - - _display.get_column (0)->set_resizable (true); - _display.get_column (5)->set_resizable (true); - - for (int i = 0; ci[i].index >= 0; ++i) { - col = _display.get_column (ci[i].index); - l = manage (new Label (ci[i].label)); - set_tooltip (*l, ci[i].tooltip); - col->set_widget (*l); - l->show (); - } - _display.set_model (_model); - - _display.set_headers_visible (true); - _display.set_rules_hint (); - - if (UIConfiguration::instance ().get_use_tooltips ()) { - /* show path as the row tooltip */ - _display.set_tooltip_column (6); /* path */ - } - - /* set the color of the name field */ - TreeViewColumn* tv_col = _display.get_column(0); - CellRendererText* renderer = dynamic_cast(_display.get_column_cell_renderer (0)); - tv_col->add_attribute(renderer->property_text(), _columns.name); - tv_col->add_attribute(renderer->property_foreground_gdk(), _columns.color_); - - /* Name cell: make editable */ - CellRendererText* region_name_cell = dynamic_cast(_display.get_column_cell_renderer (0)); - region_name_cell->property_editable() = true; - region_name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorSources::name_edit)); - region_name_cell->signal_editing_started().connect (sigc::mem_fun (*this, &EditorSources::name_editing_started)); - - /* Tags cell: make editable */ - CellRendererText* region_tags_cell = dynamic_cast(_display.get_column_cell_renderer (4)); - region_tags_cell->property_editable() = true; - region_tags_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorSources::tag_edit)); - region_tags_cell->signal_editing_started().connect (sigc::mem_fun (*this, &EditorSources::tag_editing_started)); - - /* right-align the Natural Pos column */ - TreeViewColumn* nat_col = _display.get_column(6); - nat_col->set_alignment (ALIGN_RIGHT); - renderer = dynamic_cast(_display.get_column_cell_renderer (6)); - if (renderer) { - renderer->property_xalign() = 1.0; - } - - /* the PATH field should expand when the pane is opened wider */ - tv_col = _display.get_column(7); - renderer = dynamic_cast(_display.get_column_cell_renderer (7)); - tv_col->add_attribute(renderer->property_text(), _columns.path); - tv_col->set_expand (true); - - _display.get_selection()->set_mode (SELECTION_MULTIPLE); - - /* Set up DnD Source */ - _display.add_object_drag (_columns.region.index (), "x-ardour/region.pbdid", TARGET_SAME_APP); - _display.set_drag_column (_columns.name.index()); - _display.signal_drag_data_get ().connect (sigc::mem_fun (*this, &EditorSources::drag_data_get)); + init (); /* setup DnD Receive */ list source_list_target_table; @@ -241,439 +64,75 @@ EditorSources::EditorSources (Editor* e) source_list_target_table.push_back (TargetEntry ("application/x-rootwin-drop")); _display.add_drop_targets (source_list_target_table); - _display.signal_drag_data_received().connect (sigc::mem_fun(*this, &EditorSources::drag_data_received)); + _display.signal_drag_data_received ().connect (sigc::mem_fun (*this, &EditorSources::drag_data_received)); - _scroller.add (_display); - _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC); + _change_connection = _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &EditorSources::selection_changed)); - _display.signal_button_press_event().connect (sigc::mem_fun(*this, &EditorSources::button_press), false); - _change_connection = _display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &EditorSources::selection_changed)); - - _scroller.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorSources::key_press), false); - _scroller.signal_focus_in_event().connect (sigc::mem_fun (*this, &EditorSources::focus_in), false); - _scroller.signal_focus_out_event().connect (sigc::mem_fun (*this, &EditorSources::focus_out)); - - _display.signal_enter_notify_event().connect (sigc::mem_fun (*this, &EditorSources::enter_notify), false); - _display.signal_leave_notify_event().connect (sigc::mem_fun (*this, &EditorSources::leave_notify), false); - - ARDOUR_UI::instance()->primary_clock->mode_changed.connect (sigc::mem_fun(*this, &EditorSources::clock_format_changed)); - - e->EditorFreeze.connect (editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::freeze_tree_model, this), gui_context()); - e->EditorThaw.connect (editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::thaw_tree_model, this), gui_context()); -} - -bool -EditorSources::focus_in (GdkEventFocus*) -{ - Window* win = dynamic_cast (_scroller.get_toplevel ()); - - if (win) { - old_focus = win->get_focus (); - } else { - old_focus = 0; - } - - tags_editable = 0; - name_editable = 0; - - /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */ - return true; -} - -bool -EditorSources::focus_out (GdkEventFocus*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - tags_editable = 0; - name_editable = 0; - - return false; -} - -bool -EditorSources::enter_notify (GdkEventCrossing*) -{ - if (tags_editable || name_editable) { - return true; - } - - Keyboard::magic_widget_grab_focus (); - return false; -} - -bool -EditorSources::leave_notify (GdkEventCrossing*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - Keyboard::magic_widget_drop_focus (); - return false; + e->EditorFreeze.connect (_editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::freeze_tree_model, this), gui_context ()); + e->EditorThaw.connect (_editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::thaw_tree_model, this), gui_context ()); } void -EditorSources::set_session (ARDOUR::Session* s) +EditorSources::init () { - SessionHandlePtr::set_session (s); + int bbt_width, date_width, height; - if (s) { - ARDOUR::Region::RegionsPropertyChanged.connect (source_property_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::regions_changed, this, _1, _2), gui_context ()); + Glib::RefPtr layout = _display.create_pango_layout (X_("000|000|000")); + Gtkmm2ext::get_pixel_size (layout, bbt_width, height); + Glib::RefPtr layout2 = _display.create_pango_layout (X_("2018-10-14 12:12:30")); + Gtkmm2ext::get_pixel_size (layout2, date_width, height); - ARDOUR::RegionFactory::CheckNewRegion.connect (add_source_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::add_source, this, _1), gui_context()); + add_name_column (); - s->SourceRemoved.connect (remove_source_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::remove_weak_source, this, _1), gui_context()); + setup_col (append_col (_columns.channels, "Chans "), 1, ALIGN_LEFT, _("# Ch"), _("# Channels in the region")); + setup_col (append_col (_columns.captd_for, date_width), 17, ALIGN_LEFT, _("Captured For"), _("Original Track this was recorded on")); + setup_col (append_col (_columns.captd_xruns, "1234567890"), 21, ALIGN_RIGHT, _("# Xruns"), _("Number of dropouts that occured during recording")); - redisplay(); + add_tag_column (); - } else { - source_property_connection.disconnect (); - add_source_connection.disconnect (); - remove_source_connection.disconnect (); - clear(); - } -} + setup_col (append_col (_columns.take_id, date_width), 18, ALIGN_LEFT, _("Take ID"), _("Take ID")); + setup_col (append_col (_columns.natural_pos, bbt_width), 20, ALIGN_RIGHT, _("Orig Pos"), _("Original Position of the file on timeline, when it was recorded")); -void -EditorSources::remove_weak_source (boost::weak_ptr src) -{ - boost::shared_ptr source = src.lock(); - if (source) { - remove_source (source); - } -} + TreeViewColumn* tvc = append_col (_columns.path, bbt_width); + setup_col (tvc, 13, ALIGN_LEFT, _("Path"), _("Path (folder) of the file location")); + tvc->set_expand (true); -void -EditorSources::remove_source (boost::shared_ptr source) -{ - TreeModel::iterator i; - TreeModel::Children rows = _model->children(); - for (i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr rr = (*i)[_columns.region]; - if (rr->source() == source) { - _model->erase(i); - break; - } - } -} - -void -EditorSources::remove_weak_region (boost::weak_ptr r) -{ - boost::shared_ptr region = r.lock(); - if (!region) { - return; - } - TreeModel::Children rows = _model->children(); - for (TreeModel::iterator i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr rr = (*i)[_columns.region]; - if (rr == region) { - _model->erase(i); - break; - } - } -} - -void -EditorSources::populate_row (TreeModel::Row row, boost::shared_ptr region) -{ - ENSURE_GUI_THREAD (*this, &ARDOUR_UI::record_state_changed, row, region); - - if (!region) { - return; - } - - boost::shared_ptr source = region->source(); // ToDo: is it OK to use only the first source? - - /* COLOR (for missing files) */ - Gdk::Color c; - bool missing_source = boost::dynamic_pointer_cast(source) != NULL; - if (missing_source) { - set_color_from_rgba (c, UIConfiguration::instance().color ("region list missing source")); - } else { - set_color_from_rgba (c, UIConfiguration::instance().color ("region list whole file")); - } - row[_columns.color_] = c; - - /* NAME */ - row[_columns.name] = region->name(); - - /* N CHANNELS */ - if (region->data_type() == DataType::MIDI) { - row[_columns.channels] = 0; /*TODO: some better recognition of midi regions*/ - } else { - row[_columns.channels] = region->sources().size(); - } - - /* CAPTURED FOR */ - row[_columns.captd_for] = source->captured_for(); - - /* CAPTURED DROPOUTS */ - row[_columns.captd_xruns] = source->n_captured_xruns(); - - /* TAGS */ - row[_columns.tags] = region->tags(); - - row[_columns.region] = region; - row[_columns.take_id] = source->take_id(); - - /* PATH */ - string pathstr = source->name(); - if (missing_source) { - pathstr = _("(MISSING) ") + Gtkmm2ext::markup_escape_text (source->name()); - } else { - /* is it a file? */ - boost::shared_ptr fs = boost::dynamic_pointer_cast(source); - if (!fs) { - pathstr = Gtkmm2ext::markup_escape_text (source->name()); // someday: sequence region(?) - } else { - /* audio file? */ - boost::shared_ptr afs = boost::dynamic_pointer_cast(source); - if (afs) { - const string audio_directory = _session->session_directory().sound_path(); - if (!PBD::path_is_within(audio_directory, fs->path())) { - pathstr = Gtkmm2ext::markup_escape_text (fs->path()); - } - } - /* midi file? */ - boost::shared_ptr mfs = boost::dynamic_pointer_cast(source); - if (mfs) { - const string midi_directory = _session->session_directory().midi_path(); - if (!PBD::path_is_within(midi_directory, fs->path())) { - pathstr = Gtkmm2ext::markup_escape_text (fs->path()); - } - } - } - } - - row[_columns.path] = pathstr; - - /* Natural Position (samples, an invisible column for sorting) */ - row[_columns.natural_s] = source->natural_position(); - - /* Natural Position (text representation) */ - if (source->have_natural_position()) { - char buf[64]; - format_position (source->natural_position(), buf, sizeof (buf)); - row[_columns.natural_pos] = buf; - } else { - row[_columns.natural_pos] = X_("--"); - } -} - -void -EditorSources::redisplay () -{ - remove_region_connections.drop_connections (); - _display.set_model (Glib::RefPtr(0)); - _model->clear (); - _model->set_sort_column (-2, SORT_ASCENDING); // Disable sorting to gain performance - - /* Ask the region factory to fill our list of whole-file regions */ - RegionFactory::foreach_region (sigc::mem_fun (*this, &EditorSources::add_source)); - - _model->set_sort_column (0, SORT_ASCENDING); // re-enable sorting - _display.set_model (_model); -} - -void -EditorSources::add_source (boost::shared_ptr region) -{ - if (!region || !_session) { - return; - } - - /* by definition, the Source List only shows whole-file regions - * this roughly equates to Source objects, but preserves the stereo-ness - * (or multichannel-ness) of a stereo source file. - */ - if (!region->whole_file ()) { - return; - } - - /* we only show files-on-disk. - * if there's some other kind of source, we ignore it (for now) - */ - boost::shared_ptr fs = boost::dynamic_pointer_cast (region->source()); - if (!fs) { - return; - } - - if (fs->empty()) { - /* MIDI sources are allowed to be empty */ - if (!boost::dynamic_pointer_cast (region->source())) { - return; - } - } - - region->DropReferences.connect (remove_region_connections, MISSING_INVALIDATOR, boost::bind (&EditorSources::remove_weak_region, this, boost::weak_ptr (region)), gui_context()); - - PropertyChange pc; - boost::shared_ptr rl (new RegionList); - rl->push_back (region); - regions_changed (rl, pc); -} - -void -EditorSources::regions_changed (boost::shared_ptr rl, PBD::PropertyChange const &) -{ - bool freeze = rl->size () > 2; - if (freeze) { - freeze_tree_model (); - } - - for (RegionList::const_iterator r = rl->begin (); r != rl->end(); ++r) { - boost::shared_ptr region = *r; - - if (!region->whole_file ()) { - /*this isn't on our list anyway; we can ignore it*/ - break; - } - - TreeModel::iterator i; - TreeModel::Children rows = _model->children(); - - for (i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr rr = (*i)[_columns.region]; - if (region == rr) { - populate_row(*i, region); - break; - } - } - - if (i == rows.end()) { - TreeModel::Row row = *(_model->append()); - populate_row (row, region); - } - } - - if (freeze) { - thaw_tree_model (); - } + /* make Name and Path columns manually resizable */ + _display.get_column (0)->set_resizable (true); + _display.get_column (5)->set_resizable (true); } void EditorSources::selection_changed () { + if (_display.get_selection ()->count_selected_rows () > 0) { + TreeIter iter; + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); - if (_display.get_selection()->count_selected_rows() > 0) { - - TreeIter iter; - TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows (); - - _editor->get_selection().clear_regions (); - - for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) { + _editor->get_selection ().clear_regions (); + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { if ((iter = _model->get_iter (*i))) { - /* highlight any regions in the editor that use this region's source */ boost::shared_ptr region = (*iter)[_columns.region]; - if (!region) continue; + if (!region) + continue; - boost::shared_ptr source = region->source(); + boost::shared_ptr source = region->source (); if (source) { - - set > regions; + set> regions; RegionFactory::get_regions_using_source (source, regions); - for (set >::iterator region = regions.begin(); region != regions.end(); region++) { + for (set>::iterator region = regions.begin (); region != regions.end (); region++) { _change_connection.block (true); _editor->set_selected_regionview_from_region_list (*region, Selection::Add); _change_connection.block (false); - } } } } } else { - _editor->get_selection().clear_regions (); - } - -} - -void -EditorSources::clock_format_changed () -{ - TreeModel::iterator i; - TreeModel::Children rows = _model->children(); - for (i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr rr = (*i)[_columns.region]; - populate_row(*i, rr); - } -} - -void -EditorSources::format_position (timepos_t const & pos, char* buf, size_t bufsize, bool onoff) -{ - Temporal::BBT_Time bbt; - Timecode::Time timecode; - - if (pos.is_negative ()) { - error << string_compose (_("EditorSources::format_position: negative timecode position: %1"), pos) << endmsg; - snprintf (buf, bufsize, "invalid"); - return; - } - - switch (ARDOUR_UI::instance()->primary_clock->mode ()) { - case AudioClock::BBT: - bbt = Temporal::TempoMap::use()->bbt_at (pos); - if (onoff) { - snprintf (buf, bufsize, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks); - } else { - snprintf (buf, bufsize, "(%03d|%02d|%04d)" , bbt.bars, bbt.beats, bbt.ticks); - } - break; - - case AudioClock::MinSec: - samplepos_t left; - int hrs; - int mins; - float secs; - - left = pos.samples(); - hrs = (int) floor (left / (_session->sample_rate() * 60.0f * 60.0f)); - left -= (samplecnt_t) floor (hrs * _session->sample_rate() * 60.0f * 60.0f); - mins = (int) floor (left / (_session->sample_rate() * 60.0f)); - left -= (samplecnt_t) floor (mins * _session->sample_rate() * 60.0f); - secs = left / (float) _session->sample_rate(); - if (onoff) { - snprintf (buf, bufsize, "%02d:%02d:%06.3f", hrs, mins, secs); - } else { - snprintf (buf, bufsize, "(%02d:%02d:%06.3f)", hrs, mins, secs); - } - break; - - case AudioClock::Seconds: - if (onoff) { - snprintf (buf, bufsize, "%.1f", pos.samples() / (float)_session->sample_rate()); - } else { - snprintf (buf, bufsize, "(%.1f)", pos.samples() / (float)_session->sample_rate()); - } - break; - - case AudioClock::Samples: - if (onoff) { - snprintf (buf, bufsize, "%" PRId64, pos.samples()); - } else { - snprintf (buf, bufsize, "(%" PRId64 ")", pos.samples()); - } - break; - - case AudioClock::Timecode: - default: - _session->timecode_time (pos.samples(), timecode); - if (onoff) { - snprintf (buf, bufsize, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); - } else { - snprintf (buf, bufsize, "(%02d:%02d:%02d:%02d)", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); - } - break; + _editor->get_selection ().clear_regions (); } } @@ -681,15 +140,15 @@ void EditorSources::show_context_menu (int button, int time) { using namespace Gtk::Menu_Helpers; - Gtk::Menu* menu = ARDOUR_UI_UTILS::shared_popup_menu (); - MenuList& items = menu->items(); + Gtk::Menu* menu = ARDOUR_UI_UTILS::shared_popup_menu (); + MenuList& items = menu->items (); #ifdef RECOVER_REGIONS_IS_WORKING - items.push_back(MenuElem(_("Recover the selected Sources to their original Track & Position"), - sigc::mem_fun(*this, &EditorSources::recover_selected_sources))); + items.push_back (MenuElem (_("Recover the selected Sources to their original Track & Position"), + sigc::mem_fun (*this, &EditorSources::recover_selected_sources))); #endif - items.push_back(MenuElem(_("Remove the selected Sources"), - sigc::mem_fun(*this, &EditorSources::remove_selected_sources))); - menu->popup(1, time); + items.push_back (MenuElem (_("Remove the selected Sources"), + sigc::mem_fun (*this, &EditorSources::remove_selected_sources))); + menu->popup (1, time); } void @@ -697,33 +156,32 @@ EditorSources::recover_selected_sources () { ARDOUR::RegionList to_be_recovered; - if (_display.get_selection()->count_selected_rows() > 0) { - - TreeIter iter; - TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows (); - for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) { + if (_display.get_selection ()->count_selected_rows () > 0) { + TreeIter iter; + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { if ((iter = _model->get_iter (*i))) { boost::shared_ptr region = (*iter)[_columns.region]; if (region) { - to_be_recovered.push_back(region); + to_be_recovered.push_back (region); } } } } /* ToDo */ - _editor->recover_regions(to_be_recovered); // this operation should be undo-able + _editor->recover_regions (to_be_recovered); // this operation should be undo-able } void EditorSources::remove_selected_sources () { vector choices; - string prompt; + string prompt; - prompt = _("Do you want to remove the selected Sources?" - "\nThis operation cannot be undone." - "\nThe source files will not actually be deleted until you execute Session->Cleanup."); + prompt = _("Do you want to remove the selected Sources?" + "\nThis operation cannot be undone." + "\nThe source files will not actually be deleted until you execute Session->Cleanup."); choices.push_back (_("No, do nothing.")); choices.push_back (_("Only remove the Regions that use these Sources.")); @@ -734,96 +192,65 @@ EditorSources::remove_selected_sources () int opt = prompter.run (); if (opt >= 1) { + std::list> to_be_removed; - std::list > to_be_removed; + if (_display.get_selection ()->count_selected_rows () > 0) { + TreeIter iter; + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); - if (_display.get_selection()->count_selected_rows() > 0) { - - TreeIter iter; - TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows (); - - _editor->get_selection().clear_regions (); - - for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) { + _editor->get_selection ().clear_regions (); + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { if ((iter = _model->get_iter (*i))) { - boost::shared_ptr region = (*iter)[_columns.region]; - if (!region) continue; + if (!region) + continue; - boost::shared_ptr source = region->source(); + boost::shared_ptr source = region->source (); if (source) { - set > regions; + set> regions; RegionFactory::get_regions_using_source (source, regions); - for (set >::iterator region = regions.begin(); region != regions.end(); region++) { + for (set>::iterator region = regions.begin (); region != regions.end (); region++) { _change_connection.block (true); _editor->set_selected_regionview_from_region_list (*region, Selection::Add); _change_connection.block (false); } - to_be_removed.push_back(source); + to_be_removed.push_back (source); } } - } - _editor->remove_regions( _editor->get_regions_from_selection_and_entered(), false /*can_ripple*/, false /*as_part_of_other_command*/); // this operation is undo-able + _editor->remove_regions (_editor->get_regions_from_selection_and_entered (), false /*can_ripple*/, false /*as_part_of_other_command*/); // this operation is undo-able - if (opt==2) { - for (std::list >::iterator i = to_be_removed.begin(); i != to_be_removed.end(); ++i) { - _session->remove_source(*i); // this operation is (currently) not undo-able + if (opt == 2) { + for (std::list>::iterator i = to_be_removed.begin (); i != to_be_removed.end (); ++i) { + _session->remove_source (*i); // this operation is (currently) not undo-able } } } } - } bool EditorSources::key_press (GdkEventKey* ev) { - TreeViewColumn *col; - switch (ev->keyval) { - case GDK_Tab: - case GDK_ISO_Left_Tab: + case GDK_BackSpace: + remove_selected_sources (); + return true; - if (tags_editable) { - tags_editable->editing_done (); - tags_editable = 0; - } - - if (name_editable) { - name_editable->editing_done (); - name_editable = 0; - } - - col = _display.get_column (1); // select&focus on tags column - - if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - treeview_select_previous (_display, _model, col); - } else { - treeview_select_next (_display, _model, col); - } - - return true; - break; - - case GDK_BackSpace: - remove_selected_sources(); - return true; - - default: - break; + default: + break; } - return false; + return RegionListBase::key_press (ev); } bool -EditorSources::button_press (GdkEventButton *ev) +EditorSources::button_press (GdkEventButton* ev) { if (Keyboard::is_context_menu_event (ev)) { show_context_menu (ev->button, ev->time); @@ -832,96 +259,6 @@ EditorSources::button_press (GdkEventButton *ev) return false; } -void -EditorSources::tag_editing_started (CellEditable* ce, const Glib::ustring& path) -{ - tags_editable = ce; - - /* give it a special name */ - - Gtk::Entry *e = dynamic_cast (ce); - - if (e) { - e->set_name (X_("SourceTagEditorEntry")); - - TreeIter iter; - if ((iter = _model->get_iter (path))) { - boost::shared_ptr region = (*iter)[_columns.region]; - - if(region) { - e->set_text(region->tags()); - } - } - } -} - -void -EditorSources::tag_edit (const std::string& path, const std::string& new_text) -{ - tags_editable = 0; - - boost::shared_ptr region; - TreeIter row_iter; - - if ((row_iter = _model->get_iter (path))) { - region = (*row_iter)[_columns.region]; - (*row_iter)[_columns.tags] = new_text; - } - - if (region) { - region->set_tags (new_text); - - _session->set_dirty(); // whole-file regions aren't in a playlist to catch property changes, so we need to explicitly set the session dirty - - populate_row ((*row_iter), region); - } -} - -void -EditorSources::name_editing_started (CellEditable* ce, const Glib::ustring& path) -{ - name_editable = ce; - - /* give it a special name */ - - Gtk::Entry *e = dynamic_cast (ce); - - if (e) { - e->set_name (X_("SourceNameEditorEntry")); - - TreeIter iter; - if ((iter = _model->get_iter (path))) { - boost::shared_ptr region = (*iter)[_columns.region]; - - if(region) { - e->set_text(region->name()); - } - } - } -} - -void -EditorSources::name_edit (const std::string& path, const std::string& new_text) -{ - name_editable = 0; - - boost::shared_ptr region; - TreeIter row_iter; - - if ((row_iter = _model->get_iter (path))) { - region = (*row_iter)[_columns.region]; - (*row_iter)[_columns.name] = new_text; - } - - if (region) { - region->set_name (new_text); - - _session->set_dirty(); // whole-file regions aren't in a playlist to catch property changes, so we need to explicitly set the session dirty - - populate_row ((*row_iter), region); - } -} - void EditorSources::drag_data_received (const RefPtr& context, int x, int y, @@ -945,44 +282,18 @@ EditorSources::drag_data_received (const RefPtr& context, _editor->do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, SrcBest, SMFTrackName, SMFTempoIgnore, pos); } else { - _editor->do_embed (paths, Editing::ImportDistinctFiles, ImportAsRegion, pos); + _editor->do_embed (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, pos); } context->drag_finish (true, false, dtime); } } -void -EditorSources::drag_data_get (Glib::RefPtr const&, Gtk::SelectionData& data, guint, guint) -{ - if (data.get_target () != "x-ardour/region.pbdid") { - return; - } - - list > regions; - TreeView* region; - _display.get_object_drag_data (regions, ®ion); - - if (!regions.empty ()) { - assert (regions.size() == 1); - data.set (data.get_target (), regions.front()->id ().to_s ()); - } -} - -void -EditorSources::clear () -{ - remove_region_connections.drop_connections (); - _display.set_model (Glib::RefPtr (0)); - _model->clear (); - _display.set_model (_model); -} - boost::shared_ptr EditorSources::get_single_selection () { - Glib::RefPtr selected = _display.get_selection(); + Glib::RefPtr selected = _display.get_selection (); - if (selected->count_selected_rows() != 1) { + if (selected->count_selected_rows () != 1) { return boost::shared_ptr (); } @@ -990,7 +301,7 @@ EditorSources::get_single_selection () /* only one row selected, so rows.begin() is it */ - TreeIter iter = _model->get_iter (*rows.begin()); + TreeIter iter = _model->get_iter (*rows.begin ()); if (!iter) { return boost::shared_ptr (); @@ -998,36 +309,3 @@ EditorSources::get_single_selection () return (*iter)[_columns.region]; } - -void -EditorSources::freeze_tree_model () -{ - /* store sort column id and type for later */ - _model->get_sort_column_id (_sort_col_id, _sort_type); - _change_connection.block (true); - _display.set_model (Glib::RefPtr(0)); - _model->set_sort_column (-2, SORT_ASCENDING); // Disable sorting to gain performance -} - -void -EditorSources::thaw_tree_model () -{ - _model->set_sort_column (_sort_col_id, _sort_type); // re-enabale sorting - _display.set_model (_model); - _change_connection.block (false); -} - -XMLNode & -EditorSources::get_state () const -{ - XMLNode* node = new XMLNode (X_("SourcesList")); - - //TODO: save sort state? - - return *node; -} - -void -EditorSources::set_state (const XMLNode & node) -{ -} diff --git a/gtk2_ardour/editor_sources.h b/gtk2_ardour/editor_sources.h index 01ced48efe..a7a2959e1c 100644 --- a/gtk2_ardour/editor_sources.h +++ b/gtk2_ardour/editor_sources.h @@ -18,137 +18,29 @@ #ifndef __gtk_ardour_editor_sources_h__ #define __gtk_ardour_editor_sources_h__ -#include - -#include -#include -#include -#include - -#include "gtkmm2ext/dndtreeview.h" - #include "editor_component.h" +#include "source_list_base.h" -#include "selection.h" - -class EditorSources : public EditorComponent, public ARDOUR::SessionHandlePtr +class EditorSources : public EditorComponent, public SourceListBase { public: - EditorSources (Editor *); - - void set_session (ARDOUR::Session *); - - Gtk::Widget& widget () { - return _scroller; - } - - void clear (); + EditorSources (Editor*); boost::shared_ptr get_single_selection (); - void unselect_all () { - _display.get_selection()->unselect_all (); - } - /* user actions */ void remove_selected_sources (); - void recover_selected_sources(); - - XMLNode& get_state () const; - void set_state (const XMLNode &); + void recover_selected_sources (); private: - - struct Columns : public Gtk::TreeModel::ColumnRecord { - Columns () { - add (name); - add (channels); - add (captd_for); - add (tags); - add (take_id); - add (natural_pos); - add (path); - add (color_); - add (region); - add (natural_s); - add (captd_xruns); - } - - Gtk::TreeModelColumn name; - Gtk::TreeModelColumn channels; - Gtk::TreeModelColumn captd_for; - Gtk::TreeModelColumn tags; - Gtk::TreeModelColumn > region; - Gtk::TreeModelColumn color_; - Gtk::TreeModelColumn natural_pos; - Gtk::TreeModelColumn path; - Gtk::TreeModelColumn take_id; - Gtk::TreeModelColumn natural_s; - Gtk::TreeModelColumn captd_xruns; - }; - - Columns _columns; - - Gtk::TreeModel::RowReference last_row; - - void freeze_tree_model (); - void thaw_tree_model (); - void regions_changed (boost::shared_ptr, PBD::PropertyChange const&); - void populate_row (Gtk::TreeModel::Row row, boost::shared_ptr region); - void selection_changed (); - - sigc::connection _change_connection; - - int _sort_col_id; - Gtk::SortType _sort_type; - - Gtk::Widget* old_focus; - - Gtk::CellEditable* tags_editable; - void tag_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void tag_edit (const std::string&, const std::string&); - - Gtk::CellEditable* name_editable; - void name_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void name_edit (const std::string&, const std::string&); - - bool key_press (GdkEventKey *); - bool button_press (GdkEventButton *); - - bool focus_in (GdkEventFocus*); - bool focus_out (GdkEventFocus*); - bool enter_notify (GdkEventCrossing*); - bool leave_notify (GdkEventCrossing*); - + void init (); + bool key_press (GdkEventKey*); + bool button_press (GdkEventButton*); void show_context_menu (int button, int time); - void format_position (Temporal::timepos_t const & pos, char* buf, size_t bufsize, bool onoff = true); + void selection_changed (); - void add_source (boost::shared_ptr); - void remove_source (boost::shared_ptr); - void remove_weak_region (boost::weak_ptr); - void remove_weak_source (boost::weak_ptr); - - void clock_format_changed (); - - void redisplay (); - - void drag_data_get (Glib::RefPtr const&, Gtk::SelectionData&, guint, guint); - void drag_data_received (Glib::RefPtr const &, gint, gint, Gtk::SelectionData const &, guint, guint); - - Gtk::ScrolledWindow _scroller; - - Gtkmm2ext::DnDTreeView > _display; - - Glib::RefPtr _model; - - PBD::ScopedConnection source_property_connection; - PBD::ScopedConnection add_source_connection; - PBD::ScopedConnection remove_source_connection; - PBD::ScopedConnectionList remove_region_connections; - - PBD::ScopedConnection editor_freeze_connection; - PBD::ScopedConnection editor_thaw_connection; + void drag_data_received (Glib::RefPtr const&, gint, gint, Gtk::SelectionData const&, guint, guint); }; #endif diff --git a/gtk2_ardour/region_list_base.cc b/gtk2_ardour/region_list_base.cc index cc3b6ea89e..e4dad5b116 100644 --- a/gtk2_ardour/region_list_base.cc +++ b/gtk2_ardour/region_list_base.cc @@ -25,12 +25,17 @@ #include #include +#include "pbd/file_utils.h" + #include "ardour/audiofilesource.h" #include "ardour/audioregion.h" +#include "ardour/midi_source.h" #include "ardour/region_factory.h" #include "ardour/session.h" +#include "ardour/session_directory.h" #include "ardour/session_playlist.h" #include "ardour/silentfilesource.h" +#include "ardour/smf_source.h" #include "gtkmm2ext/treeutils.h" #include "gtkmm2ext/utils.h" @@ -254,14 +259,32 @@ RegionListBase::set_session (ARDOUR::Session* s) } void -RegionListBase::add_region (boost::shared_ptr region) +RegionListBase::remove_weak_region (boost::weak_ptr r) { - if (!region || !_session) { + boost::shared_ptr region = r.lock (); + if (!region) { return; } + RegionRowMap::iterator map_it = region_row_map.find (region); + if (map_it != region_row_map.end ()) { + Gtk::TreeModel::iterator r_it = map_it->second; + region_row_map.erase (map_it); + _model->erase (r_it); + } +} + +bool +RegionListBase::list_region (boost::shared_ptr region) const +{ /* whole-file regions are shown in the Source List */ - if (region->whole_file ()) { + return !region->whole_file (); +} + +void +RegionListBase::add_region (boost::shared_ptr region) +{ + if (!region || !_session || !list_region (region)) { return; } @@ -269,10 +292,21 @@ RegionListBase::add_region (boost::shared_ptr region) * if there's some other kind of region, we ignore it (for now) */ boost::shared_ptr fs = boost::dynamic_pointer_cast (region->source ()); - if (!fs || fs->empty ()) { + if (!fs) { return; } + if (fs->empty ()) { + /* MIDI sources are allowed to be empty */ + if (!boost::dynamic_pointer_cast (region->source ())) { + return; + } + } + + if (region->whole_file ()) { + region->DropReferences.connect (_remove_region_connections, MISSING_INVALIDATOR, boost::bind (&RegionListBase::remove_weak_region, this, boost::weak_ptr (region)), gui_context ()); + } + PropertyChange pc; boost::shared_ptr rl (new RegionList); rl->push_back (region); @@ -289,16 +323,18 @@ RegionListBase::regions_changed (boost::shared_ptr rl, const Propert for (RegionList::const_iterator i = rl->begin (); i != rl->end (); ++i) { boost::shared_ptr r = *i; - RegionRowMap::iterator map_it = region_row_map.find (r); + RegionRowMap::iterator map_it = region_row_map.find (r); + boost::shared_ptr pl = r->playlist (); - boost::shared_ptr pl = r->playlist (); - if (!(pl && _session && _session->playlist_is_active (pl))) { + bool is_on_active_playlist = pl && _session && _session->playlist_is_active (pl); + + if (!((is_on_active_playlist || r->whole_file ()) && list_region (r))) { /* this region is not on an active playlist * maybe it got deleted, or whatever */ if (map_it != region_row_map.end ()) { - Gtk::TreeModel::iterator r = map_it->second; + Gtk::TreeModel::iterator r_it = map_it->second; region_row_map.erase (map_it); - _model->erase (r); + _model->erase (r_it); } break; } @@ -335,6 +371,8 @@ RegionListBase::redisplay () /* store sort column id and type for later */ _model->get_sort_column_id (_sort_col_id, _sort_type); + _remove_region_connections.drop_connections (); + _display.set_model (Glib::RefPtr (0)); _model->clear (); /* Disable sorting to gain performance */ @@ -495,6 +533,8 @@ RegionListBase::populate_row (boost::shared_ptr region, TreeModel::Row c if (all || what_changed.contains (Properties::name) || what_changed.contains (Properties::tags)) { populate_row_name (region, row); } + /* CAPTURED DROPOUTS */ + row[_columns.captd_xruns] = region->source ()->n_captured_xruns (); } void @@ -632,10 +672,44 @@ RegionListBase::populate_row_name (boost::shared_ptr region, TreeModel:: void RegionListBase::populate_row_source (boost::shared_ptr region, TreeModel::Row const& row) { - if (boost::dynamic_pointer_cast (region->source ())) { - row[_columns.path] = _("MISSING ") + Gtkmm2ext::markup_escape_text (region->source ()->name ()); + boost::shared_ptr source = region->source (); + if (boost::dynamic_pointer_cast (source)) { + row[_columns.path] = _("MISSING ") + Gtkmm2ext::markup_escape_text (source->name ()); } else { - row[_columns.path] = Gtkmm2ext::markup_escape_text (region->source ()->name ()); + row[_columns.path] = Gtkmm2ext::markup_escape_text (source->name ()); + + boost::shared_ptr fs = boost::dynamic_pointer_cast (source); + if (fs) { + boost::shared_ptr afs = boost::dynamic_pointer_cast (source); + if (afs) { + const string audio_directory = _session->session_directory ().sound_path (); + if (!PBD::path_is_within (audio_directory, fs->path ())) { + row[_columns.path] = Gtkmm2ext::markup_escape_text (fs->path ()); + } + } + boost::shared_ptr mfs = boost::dynamic_pointer_cast (source); + if (mfs) { + const string midi_directory = _session->session_directory ().midi_path (); + if (!PBD::path_is_within (midi_directory, fs->path ())) { + row[_columns.path] = Gtkmm2ext::markup_escape_text (fs->path ()); + } + } + } + } + + row[_columns.captd_for] = source->captured_for (); + row[_columns.take_id] = source->take_id (); + + /* Natural Position (samples, an invisible column for sorting) */ + row[_columns.natural_s] = source->natural_position (); + + /* Natural Position (text representation) */ + if (source->have_natural_position ()) { + char buf[64]; + format_position (source->natural_position (), buf, sizeof (buf)); + row[_columns.natural_pos] = buf; + } else { + row[_columns.natural_pos] = X_("--"); } } @@ -765,6 +839,7 @@ RegionListBase::tag_edit (const std::string& path, const std::string& new_text) void RegionListBase::clear () { + _remove_region_connections.drop_connections (); _display.set_model (Glib::RefPtr (0)); _model->clear (); _display.set_model (_model); diff --git a/gtk2_ardour/region_list_base.h b/gtk2_ardour/region_list_base.h index 8393c9b946..63ba72d310 100644 --- a/gtk2_ardour/region_list_base.h +++ b/gtk2_ardour/region_list_base.h @@ -166,21 +166,22 @@ protected: void freeze_tree_model (); void thaw_tree_model (); + void remove_weak_region (boost::weak_ptr); virtual void regions_changed (boost::shared_ptr, PBD::PropertyChange const&); void name_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void name_edit (const std::string&, const std::string&); void tag_editing_started (Gtk::CellEditable*, const Glib::ustring&); - void tag_edit (const std::string&, const std::string&); + + virtual void name_edit (const std::string&, const std::string&); + virtual void tag_edit (const std::string&, const std::string&); void locked_changed (std::string const&); void glued_changed (std::string const&); void muted_changed (std::string const&); void opaque_changed (std::string const&); - bool key_press (GdkEventKey*); - + virtual bool key_press (GdkEventKey*); virtual bool button_press (GdkEventButton*) { return false; @@ -215,6 +216,8 @@ protected: void drag_data_get (Glib::RefPtr const&, Gtk::SelectionData&, guint, guint); + virtual bool list_region (boost::shared_ptr) const; + Columns _columns; int _sort_col_id; @@ -241,6 +244,8 @@ protected: PBD::ScopedConnection _editor_freeze_connection; PBD::ScopedConnection _editor_thaw_connection; + + PBD::ScopedConnectionList _remove_region_connections; }; #endif /* _gtk_ardour_region_list_base_h_ */ diff --git a/gtk2_ardour/source_list_base.cc b/gtk2_ardour/source_list_base.cc new file mode 100644 index 0000000000..da6020f505 --- /dev/null +++ b/gtk2_ardour/source_list_base.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ardour/region.h" +#include "ardour/session.h" + +#include "gui_thread.h" +#include "source_list_base.h" + +#include "pbd/i18n.h" + +using namespace Gtk; + +SourceListBase::SourceListBase () +{ +} + +void +SourceListBase::set_session (ARDOUR::Session* s) +{ + if (s) { + s->SourceRemoved.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&SourceListBase::remove_weak_source, this, _1), gui_context ()); + } + RegionListBase::set_session (s); +} + +void +SourceListBase::remove_weak_source (boost::weak_ptr src) +{ + boost::shared_ptr source = src.lock (); + if (source) { + remove_source (source); + } +} + +void +SourceListBase::remove_source (boost::shared_ptr source) +{ + TreeModel::iterator i; + TreeModel::Children rows = _model->children (); + for (i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr rr = (*i)[_columns.region]; + if (rr->source () == source) { + RegionRowMap::iterator map_it = region_row_map.find (rr); + assert (map_it != region_row_map.end () && i == map_it->second); + region_row_map.erase (map_it); + _model->erase (i); + break; + } + } +} + +bool +SourceListBase::list_region (boost::shared_ptr region) const +{ + /* by definition, the Source List only shows whole-file regions + * this roughly equates to Source objects, but preserves the stereo-ness + * (or multichannel-ness) of a stereo source file. + */ + return region->whole_file (); +} + +void +SourceListBase::tag_edit (const std::string& path, const std::string& new_text) +{ + RegionListBase::tag_edit (path, new_text); + + TreeIter row_iter; + if ((row_iter = _model->get_iter (path))) { + boost::shared_ptr region = (*row_iter)[_columns.region]; + if (region) { + _session->set_dirty (); // whole-file regions aren't in a playlist to catch property changes, so we need to explicitly set the session dirty + } + } +} + +void +SourceListBase::name_edit (const std::string& path, const std::string& new_text) +{ + RegionListBase::name_edit (path, new_text); + + TreeIter row_iter; + if ((row_iter = _model->get_iter (path))) { + boost::shared_ptr region = (*row_iter)[_columns.region]; + if (region) { + _session->set_dirty (); // whole-file regions aren't in a playlist to catch property changes, so we need to explicitly set the session dirty + } + } +} diff --git a/gtk2_ardour/source_list_base.h b/gtk2_ardour/source_list_base.h new file mode 100644 index 0000000000..430944c57c --- /dev/null +++ b/gtk2_ardour/source_list_base.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef _gtk_ardour_source_list_base_h_ +#define _gtk_ardour_source_list_base_h_ + +#include "region_list_base.h" + +class SourceListBase : public RegionListBase +{ +public: + SourceListBase (); + void set_session (ARDOUR::Session*); + +protected: + void name_edit (const std::string&, const std::string&); + void tag_edit (const std::string&, const std::string&); + bool list_region (boost::shared_ptr) const; + +private: + void remove_source (boost::shared_ptr); + void remove_weak_source (boost::weak_ptr); +}; + +#endif /* _gtk_ardour_source_list_base_h_ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index a8b9ecd9d0..6ac4737260 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -280,6 +280,7 @@ gtk2_ardour_sources = [ 'sfdb_ui.cc', 'shuttle_control.cc', 'slot_properties_box.cc', + 'source_list_base.cc', 'soundcloud_export_selector.cc', 'splash.cc', 'speaker_dialog.cc', From c0ac99003b6e58e60bc03c64cde115adf7ce5535 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 18 Jan 2022 03:39:49 +0100 Subject: [PATCH 06/10] Add RegionList to TriggerPage --- gtk2_ardour/trigger_page.cc | 2 ++ gtk2_ardour/trigger_page.h | 2 ++ gtk2_ardour/trigger_region_list.cc | 30 ++++++++++++++++++++++++++++++ gtk2_ardour/trigger_region_list.h | 29 +++++++++++++++++++++++++++++ gtk2_ardour/wscript | 1 + 5 files changed, 64 insertions(+) create mode 100644 gtk2_ardour/trigger_region_list.cc create mode 100644 gtk2_ardour/trigger_region_list.h diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index ed4b12095c..73ecbaed61 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -125,6 +125,7 @@ TriggerPage::TriggerPage () _sidebar_vbox.pack_start (_sidebar_notebook); add_sidebar_page (_("Clips"), _trigger_clip_picker); + add_sidebar_page (_("Regions"), _trigger_region_list.widget ()); /* Upper pane ([slot | strips] | file browser) */ _pane_upper.add (_strip_group_box); @@ -264,6 +265,7 @@ TriggerPage::set_session (Session* s) _cue_box.set_session (s); _trigger_clip_picker.set_session (s); _master.set_session (s); + _trigger_region_list.set_session (s); if (!_session) { return; diff --git a/gtk2_ardour/trigger_page.h b/gtk2_ardour/trigger_page.h index 0ce68064d0..2379be211c 100644 --- a/gtk2_ardour/trigger_page.h +++ b/gtk2_ardour/trigger_page.h @@ -40,6 +40,7 @@ #include "midi_trigger_properties_box.h" #include "slot_properties_box.h" #include "trigger_clip_picker.h" +#include "trigger_region_list.h" #include "trigger_master.h" class TriggerStrip; @@ -103,6 +104,7 @@ private: Gtk::VBox _sidebar_vbox; Gtk::Notebook _sidebar_notebook; TriggerClipPicker _trigger_clip_picker; + TriggerRegionList _trigger_region_list; CueBoxWidget _cue_box; FittedCanvasWidget _master_widget; diff --git a/gtk2_ardour/trigger_region_list.cc b/gtk2_ardour/trigger_region_list.cc new file mode 100644 index 0000000000..e0b1a53545 --- /dev/null +++ b/gtk2_ardour/trigger_region_list.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "trigger_region_list.h" + +#include "pbd/i18n.h" + +using namespace Gtk; + +TriggerRegionList::TriggerRegionList () +{ + add_name_column (); + setup_col (append_col (_columns.channels, "Chans "), 1, ALIGN_LEFT, _("# Ch"), _("# Channels in the region")); + add_tag_column (); +} diff --git a/gtk2_ardour/trigger_region_list.h b/gtk2_ardour/trigger_region_list.h new file mode 100644 index 0000000000..7ee5d08d4a --- /dev/null +++ b/gtk2_ardour/trigger_region_list.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef _gtk_ardour_trigger_region_list_h_ +#define _gtk_ardour_trigger_region_list_h_ + +#include "region_list_base.h" + +class TriggerRegionList : public RegionListBase +{ +public: + TriggerRegionList (); +}; + +#endif /* _gtk_ardour_trigger_region_list_h_ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 6ac4737260..e0a9374dc8 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -314,6 +314,7 @@ gtk2_ardour_sources = [ 'transpose_dialog.cc', 'trigger_clip_picker.cc', 'trigger_page.cc', + 'trigger_region_list.cc', 'trigger_strip.cc', 'trigger_master.cc', 'trigger_ui.cc', From d4b436b4c5983b68e170021dda94a7d2a072f03e Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 02:42:09 +0100 Subject: [PATCH 07/10] Add SourceList to TriggerPage --- gtk2_ardour/trigger_page.cc | 2 ++ gtk2_ardour/trigger_page.h | 2 ++ gtk2_ardour/trigger_source_list.cc | 37 ++++++++++++++++++++++++++++++ gtk2_ardour/trigger_source_list.h | 29 +++++++++++++++++++++++ gtk2_ardour/wscript | 1 + 5 files changed, 71 insertions(+) create mode 100644 gtk2_ardour/trigger_source_list.cc create mode 100644 gtk2_ardour/trigger_source_list.h diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index 73ecbaed61..5d9fcb2a57 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -125,6 +125,7 @@ TriggerPage::TriggerPage () _sidebar_vbox.pack_start (_sidebar_notebook); add_sidebar_page (_("Clips"), _trigger_clip_picker); + add_sidebar_page (_("Sources"), _trigger_source_list.widget ()); add_sidebar_page (_("Regions"), _trigger_region_list.widget ()); /* Upper pane ([slot | strips] | file browser) */ @@ -265,6 +266,7 @@ TriggerPage::set_session (Session* s) _cue_box.set_session (s); _trigger_clip_picker.set_session (s); _master.set_session (s); + _trigger_source_list.set_session (s); _trigger_region_list.set_session (s); if (!_session) { diff --git a/gtk2_ardour/trigger_page.h b/gtk2_ardour/trigger_page.h index 2379be211c..468220d81d 100644 --- a/gtk2_ardour/trigger_page.h +++ b/gtk2_ardour/trigger_page.h @@ -41,6 +41,7 @@ #include "slot_properties_box.h" #include "trigger_clip_picker.h" #include "trigger_region_list.h" +#include "trigger_source_list.h" #include "trigger_master.h" class TriggerStrip; @@ -104,6 +105,7 @@ private: Gtk::VBox _sidebar_vbox; Gtk::Notebook _sidebar_notebook; TriggerClipPicker _trigger_clip_picker; + TriggerSourceList _trigger_source_list; TriggerRegionList _trigger_region_list; CueBoxWidget _cue_box; diff --git a/gtk2_ardour/trigger_source_list.cc b/gtk2_ardour/trigger_source_list.cc new file mode 100644 index 0000000000..38fb3004df --- /dev/null +++ b/gtk2_ardour/trigger_source_list.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ardour/region.h" +#include "ardour/session.h" + +#include "trigger_source_list.h" + +#include "pbd/i18n.h" + +using namespace Gtk; + +TriggerSourceList::TriggerSourceList () +{ + add_name_column (); + setup_col (append_col (_columns.channels, "Chans "), 1, ALIGN_LEFT, _("# Ch"), _("# Channels in the region")); + add_tag_column (); + + setup_col (append_col (_columns.captd_xruns, "1234567890"), 21, ALIGN_RIGHT, _("# Xruns"), _("Number of dropouts that occured during recording")); + setup_col (append_col (_columns.take_id, "2021-01-19 02:34:03"), 18, ALIGN_LEFT, _("Take ID"), _("Take ID")); + _display.get_column (0)->set_resizable (true); +} diff --git a/gtk2_ardour/trigger_source_list.h b/gtk2_ardour/trigger_source_list.h new file mode 100644 index 0000000000..f05385b26e --- /dev/null +++ b/gtk2_ardour/trigger_source_list.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef _gtk_ardour_trigger_source_list_h_ +#define _gtk_ardour_trigger_source_list_h_ + +#include "source_list_base.h" + +class TriggerSourceList : public SourceListBase +{ +public: + TriggerSourceList (); +}; + +#endif /* _gtk_ardour_trigger_source_list_h_ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index e0a9374dc8..2662d67a60 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -315,6 +315,7 @@ gtk2_ardour_sources = [ 'trigger_clip_picker.cc', 'trigger_page.cc', 'trigger_region_list.cc', + 'trigger_source_list.cc', 'trigger_strip.cc', 'trigger_master.cc', 'trigger_ui.cc', From 62a098811c4199b57409d851e23fe49f0d6bc404 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 04:53:37 +0100 Subject: [PATCH 08/10] Allow DnDTreeView drag w/o object reference --- libs/gtkmm2ext/gtkmm2ext/dndtreeview.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/gtkmm2ext/gtkmm2ext/dndtreeview.h b/libs/gtkmm2ext/gtkmm2ext/dndtreeview.h index b4f234d316..e7a5cbf762 100644 --- a/libs/gtkmm2ext/gtkmm2ext/dndtreeview.h +++ b/libs/gtkmm2ext/gtkmm2ext/dndtreeview.h @@ -136,13 +136,15 @@ class /*LIBGTKMM2EXT_API*/ DnDTreeView : public DnDTreeViewBase TreeView::on_drag_data_get (context, selection_data, info, time); - } else if (selection_data.get_target() == object_type) { + } else if (selection_data.get_target() == object_type && drag_data.data_column >= 0) { /* return a pointer to this object, which allows * the receiver to call on_drag_data_received() */ void *c = this; selection_data.set (8, (guchar*)&c, sizeof(void*)); + } else { + TreeView::on_drag_data_get (context, selection_data, info, time); } } @@ -178,7 +180,7 @@ class /*LIBGTKMM2EXT_API*/ DnDTreeView : public DnDTreeViewBase void get_object_drag_data (std::list& l, Gtk::TreeView** source) const { - if (drag_data.source == 0) { + if (drag_data.source == 0 || drag_data.data_column < 0) { return; } From 50bccb44d535e46d0590d698479de166b2e3c063 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 05:05:57 +0100 Subject: [PATCH 09/10] Add static meta-data for x-ardour/region.pbdid DnD During drag-motion callbacks the data to be dragged is n/a. However we like to discriminate if drop is possible. When dragging regions, the data-type of the region to be dragged is unknown, so different `x-ardour/region` targets are not an option, either. Until a better option is presented, a static global is used to set the data-type for region.pbdid drags. --- gtk2_ardour/editor_canvas_events.cc | 18 ++++---------- gtk2_ardour/public_editor.cc | 2 ++ gtk2_ardour/public_editor.h | 3 +++ gtk2_ardour/region_list_base.cc | 37 ++++++++++++++++++++++------- gtk2_ardour/region_list_base.h | 2 ++ gtk2_ardour/triggerbox_ui.cc | 14 +++++++++-- 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc index 0fd8b6393d..ee6b62f09b 100644 --- a/gtk2_ardour/editor_canvas_events.cc +++ b/gtk2_ardour/editor_canvas_events.cc @@ -1211,26 +1211,18 @@ Editor::track_canvas_drag_motion (Glib::RefPtr const& context, if (can_drop) { if (target == "x-ardour/region.pbdid") { -#if 0 - // TODO check drag_source::drag_data_get() -> SelectionData& region - - if (tv.first == 0 && region) { + if (tv.first == 0 && pbdid_dragged_dt != DataType::NIL) { /* drop to drop-zone */ - context->drag_status (context->get_suggested_action(), time); + context->drag_status (Gdk::ACTION_COPY, time); return true; } - if ((boost::dynamic_pointer_cast (region) != 0 && dynamic_cast (tv.first) != 0) || - (boost::dynamic_pointer_cast (region) != 0 && dynamic_cast (tv.first) != 0)) { + if ((pbdid_dragged_dt == DataType::AUDIO && dynamic_cast (tv.first) != 0) || + (pbdid_dragged_dt == DataType::MIDI && dynamic_cast (tv.first) != 0)) { /* audio to audio OR MIDI to MIDI */ - context->drag_status (context->get_suggested_action(), time); + context->drag_status (Gdk::ACTION_COPY, time); return true; } -#else - /* region drop always works */ - context->drag_status (context->get_suggested_action(), time); -#endif - return true; } else { /* DND originating from outside ardour * diff --git a/gtk2_ardour/public_editor.cc b/gtk2_ardour/public_editor.cc index c7321ed1ab..120241e4a8 100644 --- a/gtk2_ardour/public_editor.cc +++ b/gtk2_ardour/public_editor.cc @@ -31,6 +31,8 @@ const int PublicEditor::horizontal_spacing = 6; sigc::signal PublicEditor::DropDownKeys; +ARDOUR::DataType PublicEditor::pbdid_dragged_dt = ARDOUR::DataType::NIL; + PublicEditor::PublicEditor (Gtk::Widget& content) : Tabbable (content, _("Editor"), X_("editor")) { diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index d2c52305d4..efe65721e6 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -425,6 +425,9 @@ public: : action (a), target (tgt) {} }; + /* data-type of [region] object currently dragged with x-ardour/region.pbdid */ + static ARDOUR::DataType pbdid_dragged_dt; + std::map region_action_map; Glib::RefPtr editor_actions; diff --git a/gtk2_ardour/region_list_base.cc b/gtk2_ardour/region_list_base.cc index e4dad5b116..076a5db342 100644 --- a/gtk2_ardour/region_list_base.cc +++ b/gtk2_ardour/region_list_base.cc @@ -48,6 +48,7 @@ #include "gui_thread.h" #include "keyboard.h" #include "main_clock.h" +#include "public_editor.h" #include "region_list_base.h" #include "ui_config.h" #include "utils.h" @@ -82,8 +83,10 @@ RegionListBase::RegionListBase () _model = TreeStore::create (_columns); _model->set_sort_column (0, SORT_ASCENDING); - _display.add_object_drag (_columns.region.index (), "x-ardour/region.pbdid", Gtk::TARGET_SAME_APP); + _display.add_object_drag (-1, "x-ardour/region.pbdid", Gtk::TARGET_SAME_APP); _display.set_drag_column (_columns.name.index ()); + _display.signal_drag_begin ().connect (sigc::mem_fun (*this, &RegionListBase::drag_begin)); + _display.signal_drag_end ().connect (sigc::mem_fun (*this, &RegionListBase::drag_end)); _display.signal_drag_data_get ().connect (sigc::mem_fun (*this, &RegionListBase::drag_data_get)); _display.set_model (_model); @@ -226,19 +229,37 @@ RegionListBase::leave_notify (GdkEventCrossing*) return false; } +void +RegionListBase::drag_begin (Glib::RefPtr const&) +{ + if (_display.get_selection ()->count_selected_rows () == 0) { + PublicEditor::instance ().pbdid_dragged_dt = DataType::NIL; + } + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr region = (*_model->get_iter (*i))[_columns.region]; + PublicEditor::instance ().pbdid_dragged_dt = region->data_type (); + break; + } +} + +void +RegionListBase::drag_end (Glib::RefPtr const&) +{ + PublicEditor::instance ().pbdid_dragged_dt = DataType::NIL; +} + void RegionListBase::drag_data_get (Glib::RefPtr const&, Gtk::SelectionData& data, guint, guint) { if (data.get_target () != "x-ardour/region.pbdid") { return; } - - list> regions; - TreeView* source; - _display.get_object_drag_data (regions, &source); - - if (!regions.empty ()) { - data.set (data.get_target (), regions.front ()->id ().to_s ()); + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr region = (*_model->get_iter (*i))[_columns.region]; + data.set (data.get_target (), region->id ().to_s ()); + break; } } diff --git a/gtk2_ardour/region_list_base.h b/gtk2_ardour/region_list_base.h index 63ba72d310..38a7af5b10 100644 --- a/gtk2_ardour/region_list_base.h +++ b/gtk2_ardour/region_list_base.h @@ -214,6 +214,8 @@ protected: void clock_format_changed (); + void drag_begin (Glib::RefPtr const&); + void drag_end (Glib::RefPtr const&); void drag_data_get (Glib::RefPtr const&, Gtk::SelectionData&, guint, guint); virtual bool list_region (boost::shared_ptr) const; diff --git a/gtk2_ardour/triggerbox_ui.cc b/gtk2_ardour/triggerbox_ui.cc index 52451a8809..10630a8575 100644 --- a/gtk2_ardour/triggerbox_ui.cc +++ b/gtk2_ardour/triggerbox_ui.cc @@ -801,12 +801,21 @@ TriggerEntry::drag_begin (Glib::RefPtr const& context) /* ctx leaves scope, cr is destroyed, and pixmap surface is flush()ed */ } + boost::shared_ptr region = trigger ()->region (); + if (region) { + PublicEditor::instance ().pbdid_dragged_dt = region->data_type (); + } else { + PublicEditor::instance ().pbdid_dragged_dt = DataType::NIL; + } context->set_icon (pixmap->get_colormap (), pixmap, Glib::RefPtr (NULL), width / 2, height / 2); } void TriggerEntry::drag_end (Glib::RefPtr const&) { + if (_drag_active) { + PublicEditor::instance ().pbdid_dragged_dt = DataType::NIL; + } _drag_active = false; } @@ -942,8 +951,9 @@ TriggerBoxUI::slot_at_y (int y) const bool TriggerBoxUI::drag_motion (Glib::RefPtr const& context, int, int y, guint time) { - bool can_drop = true; - uint64_t n = slot_at_y (y); + bool can_drop = PublicEditor::instance ().pbdid_dragged_dt == _triggerbox.data_type (); + + uint64_t n = slot_at_y (y); if (n >= _slots.size ()) { assert (0); can_drop = false; From b51621a1ae74f7e7a4f443d577a77c8f2f828bd2 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 19:22:51 +0100 Subject: [PATCH 10/10] Fix assert() when using DnD to import MIDI files When importing a multi-track MIDI files with identical track-names, Ardour would create the same file for each track. Effectively overwriting an existing file. The following MIDI file would create the same file twice in `interchange/`, once for each MTrk. ``` MFile 1 2 240 MTrk 0 Meta SeqName "Foo Bar" 0 TimeSig 4/4 24 8 0 Tempo 666667 0 Meta TrkEnd TrkEnd MTrk 0 Meta TrkName "Foo Bar" 0 On ch=10 n=36 v=95 ... ``` --- gtk2_ardour/editor_canvas.cc | 4 ++-- gtk2_ardour/editor_sources.cc | 2 +- gtk2_ardour/trigger_page.cc | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc index ef29af4ac2..5384c32d57 100644 --- a/gtk2_ardour/editor_canvas.cc +++ b/gtk2_ardour/editor_canvas.cc @@ -432,7 +432,7 @@ Editor::drop_paths_part_two (const vector& paths, timepos_t const & p, d /* drop onto canvas background: create new tracks */ InstrumentSelector is; // instantiation builds instrument-list and sets default. - do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackName, SMFTempoIgnore, pos, is.selected_instrument(), false); + do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, SMFTrackNumber, SMFTempoIgnore, pos, is.selected_instrument(), false); if (UIConfiguration::instance().get_only_copy_imported_files() || copy) { do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack, @@ -450,7 +450,7 @@ Editor::drop_paths_part_two (const vector& paths, timepos_t const & p, d selection->set (tv); do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack, - SrcBest, SMFTrackName, SMFTempoIgnore, pos); + SrcBest, SMFTrackNumber, SMFTempoIgnore, pos); if (UIConfiguration::instance().get_only_copy_imported_files() || copy) { do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack, diff --git a/gtk2_ardour/editor_sources.cc b/gtk2_ardour/editor_sources.cc index 8c678dab82..be8b4fc50d 100644 --- a/gtk2_ardour/editor_sources.cc +++ b/gtk2_ardour/editor_sources.cc @@ -280,7 +280,7 @@ EditorSources::drag_data_received (const RefPtr& context, if (UIConfiguration::instance ().get_only_copy_imported_files () || copy) { _editor->do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, - SrcBest, SMFTrackName, SMFTempoIgnore, pos); + SrcBest, SMFTrackNumber, SMFTempoIgnore, pos); } else { _editor->do_embed (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, pos); } diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index 5d9fcb2a57..3092280ec4 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -602,8 +602,8 @@ TriggerPage::drop_paths_part_two (std::vector paths) InstrumentSelector is; // instantiation builds instrument-list and sets default. timepos_t pos (0); Editing::ImportDisposition disposition = Editing::ImportSerializeFiles; // or Editing::ImportDistinctFiles // TODO use drop modifier? config? - PublicEditor::instance().do_import (midi_paths, disposition, Editing::ImportAsTrigger, SrcBest, SMFTrackName, SMFTempoIgnore, pos, is.selected_instrument (), false); - PublicEditor::instance().do_import (audio_paths, disposition, Editing::ImportAsTrigger, SrcBest, SMFTrackName, SMFTempoIgnore, pos); + PublicEditor::instance().do_import (midi_paths, disposition, Editing::ImportAsTrigger, SrcBest, SMFTrackNumber, SMFTempoIgnore, pos, is.selected_instrument (), false); + PublicEditor::instance().do_import (audio_paths, disposition, Editing::ImportAsTrigger, SrcBest, SMFTrackNumber, SMFTempoIgnore, pos); } bool