From 5427cec8212e6a13361cac29181906a84983bcaf Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 18 Jan 2022 02:55:43 +0100 Subject: [PATCH] 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',