From 37877fbdc2b2ffb479fe261eb9bb3d45c32453f8 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 19 Jan 2022 01:59:05 +0100 Subject: [PATCH] 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',