/* * 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 #include "ardour/audiofilesource.h" #include "ardour/region_factory.h" #include "ardour/session.h" #include "ardour/session_directory.h" #include "ardour/smf_source.h" #include "ardour/source.h" #include "widgets/choice.h" #include "context_menu_helper.h" #include "editing.h" #include "editor.h" #include "editor_drag.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" using namespace std; using namespace ARDOUR; using namespace ArdourWidgets; using namespace ARDOUR_UI_UTILS; using namespace PBD; using namespace Gtk; using namespace Glib; using Gtkmm2ext::Keyboard; EditorSources::EditorSources (Editor* e) : EditorComponent (e) { init (); /* setup DnD Receive */ list source_list_target_table; source_list_target_table.push_back (TargetEntry ("text/plain")); source_list_target_table.push_back (TargetEntry ("text/uri-list")); 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)); _change_connection = _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &EditorSources::selection_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 ()); } void EditorSources::init () { int bbt_width, date_width, height; Glib::RefPtr layout = _display.create_pango_layout (X_("000|000|000")); layout->get_pixel_size (bbt_width, height); Glib::RefPtr layout2 = _display.create_pango_layout (X_("2018-10-14 12:12:30")); layout2->get_pixel_size (date_width, height); add_name_column (); setup_col (append_col (_columns.channels, "Chans "), 1, ALIGN_START, _("# Ch"), _("# Channels in the region")); setup_col (append_col (_columns.captd_for, date_width), 17, ALIGN_START, _("Captured For"), _("Original Track this was recorded on")); setup_col (append_col (_columns.captd_xruns, "1234567890"), 21, ALIGN_END, _("# Xruns"), _("Number of dropouts that occurred during recording")); add_tag_column (); setup_col (append_col (_columns.take_id, date_width), 18, ALIGN_START, _("Take ID"), _("Take ID")); setup_col (append_col (_columns.natural_pos, bbt_width), 20, ALIGN_END, _("Orig Pos"), _("Original Position of the file on timeline, when it was recorded")); TreeViewColumn* tvc = append_col (_columns.path, bbt_width); setup_col (tvc, 13, ALIGN_START, _("Path"), _("Path (folder) of the file location")); tvc->set_expand (true); /* 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 (); _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 */ std::shared_ptr region = (*iter)[_columns.region]; if (!region) continue; std::shared_ptr source = region->source (); if (source) { set> regions; RegionFactory::get_regions_using_source (source, regions); for (set>::iterator region = regions.begin (); region != regions.end (); region++) { _change_connection.block (true); _editor->set_selected_regionview_from_region_list (*region, SelectionAdd); _change_connection.block (false); } } } } } else { _editor->get_selection ().clear_regions (); } } 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 (); #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))); #endif items.push_back (MenuElem (_("Remove the selected Sources"), sigc::mem_fun (*this, &EditorSources::remove_selected_sources))); menu->popup (button, time); } void 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 ((iter = _model->get_iter (*i))) { std::shared_ptr region = (*iter)[_columns.region]; if (region) { to_be_recovered.push_back (region); } } } } /* ToDo */ _editor->recover_regions (to_be_recovered); // this operation should be undo-able } void EditorSources::remove_selected_sources () { vector choices; 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."); choices.push_back (_("No, do nothing.")); choices.push_back (_("Only remove the Regions that use these Sources.")); choices.push_back (_("Yes, remove the Regions and Sources (cannot be undone!)")); Choice prompter (_("Remove selected Sources"), prompt, choices); int opt = prompter.run (); if (opt >= 1) { struct WeakPtrCompare { bool operator() (weak_ptr const& lhs, weak_ptr const& rhs) const { auto lptr = lhs.lock(), rptr = rhs.lock(); if (!rptr) return false; if (!lptr) return true; return lptr->id() < rptr->id(); } }; std::set, WeakPtrCompare> to_be_removed; 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) { if ((iter = _model->get_iter (*i))) { std::shared_ptr region = (*iter)[_columns.region]; if (!region) { continue; } for (auto const& source : region->sources ()) { set> regions; RegionFactory::get_regions_using_source (source, regions); for (auto const& region : regions) { _change_connection.block (true); /* Note: this ignores regions without RegionView. * When removing sources (opt == 2), * Playlist::region_going_away takes care of that * (Source::drop_references -> Region::source_deleted, * -> Region::drop_references). see f58f5bef55a5aa1 */ _editor->set_selected_regionview_from_region_list (region, SelectionAdd); _change_connection.block (false); } to_be_removed.insert (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 if (opt == 2) { for (auto const& s : to_be_removed) { _session->remove_source (s); // this operation is (currently) not undo-able } } } } } bool EditorSources::key_press (GdkEventKey* ev) { switch (ev->keyval) { case GDK_BackSpace: remove_selected_sources (); return true; default: break; } return RegionListBase::key_press (ev); } bool EditorSources::button_press (GdkEventButton* ev) { if (Keyboard::is_context_menu_event (ev)) { show_context_menu (ev->button, ev->time); return true; } return false; } void EditorSources::drag_data_received (const RefPtr& context, int x, int y, const SelectionData& data, guint info, guint dtime) { vector paths; if (data.get_target () == "GTK_TREE_MODEL_ROW") { /* something is being dragged over the source list */ _editor->_drags->abort (); _display.on_drag_data_received (context, x, y, data, info, dtime); return; } if (_session && convert_drop_to_paths (paths, data)) { timepos_t pos; bool copy = ((context->get_actions () & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY); if (UIConfiguration::instance ().get_only_copy_imported_files () || copy) { _editor->do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, SrcBest, SMFFileAndTrackName, SMFTempoIgnore, pos); } else { _editor->do_embed (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, pos); } context->drag_finish (true, false, dtime); } } std::shared_ptr EditorSources::get_single_selection () { Glib::RefPtr selected = _display.get_selection (); if (selected->count_selected_rows () != 1) { return std::shared_ptr (); } TreeView::Selection::ListHandle_Path rows = selected->get_selected_rows (); /* only one row selected, so rows.begin() is it */ TreeIter iter = _model->get_iter (*rows.begin ()); if (!iter) { return std::shared_ptr (); } return (*iter)[_columns.region]; }