Playlist UI tweaks: Add (select, share, copy, steal) operations to the playlist selector (under Advanced... menu)

Use the same sorting for the menu list and playlist_selector items
Implement Copy, Share and Steal playlist actions. Need a RouteTimeAxis for this.
Rename persistent dialog buttons to make it clear you can Revert
This commit is contained in:
Ben Loftis 2020-09-11 13:10:30 -05:00
parent a51e51b4f2
commit 892d641987
5 changed files with 180 additions and 87 deletions

View file

@ -25,11 +25,13 @@
#include "ardour/audio_track.h" #include "ardour/audio_track.h"
#include "ardour/audioplaylist.h" #include "ardour/audioplaylist.h"
#include "ardour/midi_playlist.h" #include "ardour/midi_playlist.h"
#include "ardour/playlist_factory.h"
#include "ardour/session_playlist.h" #include "ardour/session_playlist.h"
#include <gtkmm2ext/gtk_ui.h> #include <gtkmm2ext/gtk_ui.h>
#include "public_editor.h"
#include "playlist_selector.h" #include "playlist_selector.h"
#include "route_ui.h" #include "route_ui.h"
#include "gui_thread.h" #include "gui_thread.h"
@ -45,7 +47,8 @@ using namespace PBD;
PlaylistSelector::PlaylistSelector () PlaylistSelector::PlaylistSelector ()
: ArdourDialog (_("Playlists")) : ArdourDialog (_("Playlists"))
{ {
rui = 0; _tav = 0;
_mode = plSelect;
set_name ("PlaylistSelectorWindow"); set_name ("PlaylistSelectorWindow");
set_modal(false); set_modal(false);
@ -55,6 +58,7 @@ PlaylistSelector::PlaylistSelector ()
model = TreeStore::create (columns); model = TreeStore::create (columns);
tree.set_model (model); tree.set_model (model);
tree.append_column (_("Playlists grouped by track"), columns.text); tree.append_column (_("Playlists grouped by track"), columns.text);
tree.set_headers_visible (false);
scroller.add (tree); scroller.add (tree);
scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC); scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
@ -64,21 +68,25 @@ PlaylistSelector::PlaylistSelector ()
get_vbox()->pack_start (scroller); get_vbox()->pack_start (scroller);
Button* close_btn = add_button (_("Close"), RESPONSE_CANCEL); get_vbox()->show_all();
Button* ok_btn = add_button (_("OK"), RESPONSE_OK);
Button* close_btn = add_button (_("Revert"), RESPONSE_CANCEL);
Button* ok_btn = add_button (_("Done"), RESPONSE_OK);
close_btn->signal_clicked().connect (sigc::mem_fun(*this, &PlaylistSelector::close_button_click)); close_btn->signal_clicked().connect (sigc::mem_fun(*this, &PlaylistSelector::close_button_click));
ok_btn->signal_clicked().connect (sigc::mem_fun(*this, &PlaylistSelector::ok_button_click)); ok_btn->signal_clicked().connect (sigc::mem_fun(*this, &PlaylistSelector::ok_button_click));
} }
void PlaylistSelector::set_rui(RouteUI* ruix) void PlaylistSelector::set_tav(RouteTimeAxisView* tavx, plMode mode)
{ {
if (rui == ruix) { _mode = mode;
if (_tav == tavx) {
return; return;
} }
rui = ruix; _tav = tavx;
boost::shared_ptr<Track> this_track = rui->track(); boost::shared_ptr<Track> this_track = _tav->track();
if (this_track) { if (this_track) {
this_track->PlaylistAdded.connect( this_track->PlaylistAdded.connect(
@ -109,6 +117,9 @@ PlaylistSelector::clear_map ()
delete x->second; delete x->second;
} }
trpl_map.clear (); trpl_map.clear ();
if (current_playlist) {
current_playlist.reset();
}
} }
bool bool
@ -124,10 +135,14 @@ PlaylistSelector::on_unmap_event (GdkEventAny* ev)
void void
PlaylistSelector::redisplay() PlaylistSelector::redisplay()
{ {
if (!_tav ) {
return;
}
vector<const char*> item; vector<const char*> item;
string str; string str;
set_title (string_compose (_("Playlist for %1"), rui->route()->name())); set_title (string_compose (_("Playlist for %1"), _tav->route()->name()));
clear_map (); clear_map ();
select_connection.disconnect (); select_connection.disconnect ();
@ -136,13 +151,9 @@ PlaylistSelector::redisplay()
_session->playlists()->foreach (this, &PlaylistSelector::add_playlist_to_map); _session->playlists()->foreach (this, &PlaylistSelector::add_playlist_to_map);
boost::shared_ptr<Track> this_track = rui->track(); boost::shared_ptr<Track> this_track = _tav->track();
TreeModel::Row others = *(model->append ()); boost::shared_ptr<Playlist> proxy;
others[columns.text] = _("Other tracks");
boost::shared_ptr<Playlist> proxy = others[columns.playlist];
proxy.reset ();
if (this_track->playlist()) { if (this_track->playlist()) {
current_playlist = this_track->playlist(); current_playlist = this_track->playlist();
@ -167,27 +178,32 @@ PlaylistSelector::redisplay()
bool have_selected = false; bool have_selected = false;
TreePath this_path; TreePath this_path;
if (tr == this_track) { //make a heading for each other track, if needed
if (tr != this_track && _mode != plSelect) {
row = *(model->prepend()); row = *(model->prepend());
row[columns.text] = nodename; row[columns.text] = nodename;
boost::shared_ptr<Playlist> proxy = row[columns.playlist]; boost::shared_ptr<Playlist> proxy = row[columns.playlist];
proxy.reset (); proxy.reset ();
} else {
row = *(model->append (others.children()));
row[columns.text] = nodename;
boost::shared_ptr<Playlist> proxy = row[columns.playlist];
proxy.reset ();
} }
/* Now insert all the playlists for this diskstream/track in a subtree */ /* Now insert all the playlists for this diskstream/track in a subtree */
vector<boost::shared_ptr<Playlist> > pls = *(x->second);
list<boost::shared_ptr<Playlist> >* pls = x->second; /* sort the playlists to match the order they appear in the track menu */
PlaylistSorterByID cmp;
sort (pls.begin(), pls.end(), cmp);
for (list<boost::shared_ptr<Playlist> >::iterator p = pls->begin(); p != pls->end(); ++p) { for (vector<boost::shared_ptr<Playlist> >::iterator p = pls.begin(); p != pls.end(); ++p) {
TreeModel::Row child_row; TreeModel::Row child_row;
if (tr == this_track && _mode==plSelect) {
child_row = *(model->append());
} else if (tr != this_track && _mode != plSelect) {
child_row = *(model->append (row.children())); child_row = *(model->append (row.children()));
}
if (child_row) {
child_row[columns.text] = (*p)->name(); child_row[columns.text] = (*p)->name();
child_row[columns.playlist] = *p; child_row[columns.playlist] = *p;
@ -196,22 +212,25 @@ PlaylistSelector::redisplay()
have_selected = true; have_selected = true;
} }
} }
}
if (have_selected) { if (have_selected) {
tree.get_selection()->select (selected_row); tree.get_selection()->select (selected_row);
} }
} }
if (_mode != plSelect) {
// Add unassigned (imported) playlists to the list // Add unassigned (imported) playlists to the list
list<boost::shared_ptr<Playlist> > unassigned; list<boost::shared_ptr<Playlist> > unassigned;
_session->playlists()->unassigned (unassigned); _session->playlists()->unassigned (unassigned);
if ( unassigned.begin() != unassigned.end() ) {
TreeModel::Row row; TreeModel::Row row;
TreeModel::Row selected_row; TreeModel::Row selected_row;
bool have_selected = false; bool have_selected = false;
TreePath this_path; TreePath this_path;
row = *(model->append (others.children())); row = *(model->append ());
row[columns.text] = _("Imported"); row[columns.text] = _("Imported");
proxy = row[columns.playlist]; proxy = row[columns.playlist];
proxy.reset (); proxy.reset ();
@ -232,6 +251,8 @@ PlaylistSelector::redisplay()
tree.get_selection()->select (selected_row); tree.get_selection()->select (selected_row);
} }
} }
}
} //if !plSelect
show_all (); show_all ();
select_connection = tree.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PlaylistSelector::selection_changed)); select_connection = tree.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PlaylistSelector::selection_changed));
@ -244,16 +265,16 @@ PlaylistSelector::add_playlist_to_map (boost::shared_ptr<Playlist> pl)
return; return;
} }
if (!rui) { if (!_tav) {
return; return;
} }
if (rui->is_midi_track ()) { if (_tav->is_midi_track ()) {
if (boost::dynamic_pointer_cast<MidiPlaylist> (pl) == 0) { if (boost::dynamic_pointer_cast<MidiPlaylist> (pl) == 0) {
return; return;
} }
} else { } else {
assert (rui->is_audio_track ()); assert (_tav->is_audio_track ());
if (boost::dynamic_pointer_cast<AudioPlaylist> (pl) == 0) { if (boost::dynamic_pointer_cast<AudioPlaylist> (pl) == 0) {
return; return;
} }
@ -262,7 +283,7 @@ PlaylistSelector::add_playlist_to_map (boost::shared_ptr<Playlist> pl)
TrackPlaylistMap::iterator x; TrackPlaylistMap::iterator x;
if ((x = trpl_map.find (pl->get_orig_track_id ())) == trpl_map.end()) { if ((x = trpl_map.find (pl->get_orig_track_id ())) == trpl_map.end()) {
x = trpl_map.insert (trpl_map.end(), make_pair (pl->get_orig_track_id(), new list<boost::shared_ptr<Playlist> >)); x = trpl_map.insert (trpl_map.end(), make_pair (pl->get_orig_track_id(), new vector<boost::shared_ptr<Playlist> >));
} }
x->second->push_back (pl); x->second->push_back (pl);
@ -277,17 +298,19 @@ PlaylistSelector::playlist_added()
void void
PlaylistSelector::close_button_click () PlaylistSelector::close_button_click ()
{ {
if (rui && current_playlist) { if (_tav && current_playlist) {
rui->track ()->use_playlist (rui->is_audio_track () ? DataType::AUDIO : DataType::MIDI, current_playlist); _tav->track ()->use_playlist (_tav->is_audio_track () ? DataType::AUDIO : DataType::MIDI, current_playlist);
} }
rui = 0; _tav = 0;
clear_map ();
hide (); hide ();
} }
void void
PlaylistSelector::ok_button_click() PlaylistSelector::ok_button_click()
{ {
rui = 0; _tav = 0;
clear_map ();
hide(); hide();
} }
@ -304,20 +327,36 @@ PlaylistSelector::selection_changed ()
TreeModel::iterator iter = tree.get_selection()->get_selected(); TreeModel::iterator iter = tree.get_selection()->get_selected();
if (!iter || rui == 0) { if (!iter || _tav == 0) {
/* nothing selected */ /* nothing selected */
return; return;
} }
if ((pl = ((*iter)[columns.playlist])) != 0) { if ((pl = ((*iter)[columns.playlist])) != 0) {
if (rui->is_audio_track () && boost::dynamic_pointer_cast<AudioPlaylist> (pl) == 0) { if (_tav->is_audio_track () && boost::dynamic_pointer_cast<AudioPlaylist> (pl) == 0) {
return; return;
} }
if (rui->is_midi_track () && boost::dynamic_pointer_cast<MidiPlaylist> (pl) == 0) { if (_tav->is_midi_track () && boost::dynamic_pointer_cast<MidiPlaylist> (pl) == 0) {
return; return;
} }
rui->track ()->use_playlist (rui->is_audio_track () ? DataType::AUDIO : DataType::MIDI, pl); switch (_mode) {
/* @Robin: I dont see a way to undo these playlist actions */
case plCopy: {
boost::shared_ptr<Playlist> playlist = PlaylistFactory::create (pl, string_compose ("%1.1", pl->name()));
/* playlist->reset_shares (); @Robin is this needed? */
_tav->track ()->use_playlist (_tav->is_audio_track () ? DataType::AUDIO : DataType::MIDI, playlist);
} break;
case plShare:
_tav->track ()->use_playlist (_tav->is_audio_track () ? DataType::AUDIO : DataType::MIDI, pl, false); /* share pl but do NOT set me as the owner */
break;
case plSteal:
_tav->track ()->use_playlist (_tav->is_audio_track () ? DataType::AUDIO : DataType::MIDI, pl); /* share the playlist and set ME as the owner */
break;
case plSelect:
_tav->use_playlist (NULL, pl); //call route_ui::use_playlist because it is group-aware
break;
}
} }
} }

View file

@ -41,24 +41,40 @@ namespace ARDOUR {
class RouteUI; class RouteUI;
struct PlaylistSorterByID {
bool operator() (boost::shared_ptr<ARDOUR::Playlist> a, boost::shared_ptr<ARDOUR::Playlist> b) const {
return a->sort_id() < b->sort_id();
}
};
class PlaylistSelector : public ArdourDialog class PlaylistSelector : public ArdourDialog
{ {
public: public:
PlaylistSelector (); PlaylistSelector ();
~PlaylistSelector (); ~PlaylistSelector ();
enum plMode {
plSelect,
plCopy,
plShare,
plSteal
};
void redisplay(); void redisplay();
void set_rui(RouteUI*); void set_tav(RouteTimeAxisView*, plMode in);
protected: protected:
bool on_unmap_event (GdkEventAny*); bool on_unmap_event (GdkEventAny*);
private: private:
typedef std::map<PBD::ID,std::list<boost::shared_ptr<ARDOUR::Playlist> >*> TrackPlaylistMap; typedef std::map<PBD::ID,std::vector<boost::shared_ptr<ARDOUR::Playlist> >*> TrackPlaylistMap;
Gtk::ScrolledWindow scroller; Gtk::ScrolledWindow scroller;
TrackPlaylistMap trpl_map; TrackPlaylistMap trpl_map;
RouteUI* rui;
RouteTimeAxisView* _tav;
plMode _mode;
sigc::connection select_connection; sigc::connection select_connection;
PBD::ScopedConnectionList signal_connections; PBD::ScopedConnectionList signal_connections;

View file

@ -1380,7 +1380,6 @@ RouteTimeAxisView::paste (samplepos_t pos, const Selection& selection, PasteCont
return true; return true;
} }
void void
RouteTimeAxisView::update_playlist_tip () RouteTimeAxisView::update_playlist_tip ()
{ {
@ -1411,14 +1410,6 @@ RouteTimeAxisView::update_playlist_tip ()
set_tooltip (playlist_button, _("Playlist") + std::string(": ") + Gtkmm2ext::markup_escape_text (track()->playlist()->name())); set_tooltip (playlist_button, _("Playlist") + std::string(": ") + Gtkmm2ext::markup_escape_text (track()->playlist()->name()));
} }
void
RouteTimeAxisView::show_playlist_selector ()
{
_editor.playlist_selector().set_rui(this);
_editor.playlist_selector().redisplay();
}
void void
RouteTimeAxisView::map_frozen () RouteTimeAxisView::map_frozen ()
{ {

View file

@ -1811,7 +1811,7 @@ void
RouteUI::set_route_active (bool a, bool apply_to_selection) RouteUI::set_route_active (bool a, bool apply_to_selection)
{ {
if (apply_to_selection) { if (apply_to_selection) {
ARDOUR_UI::instance()->the_editor().get_selection().tracks.foreach_route_ui (boost::bind (&RouteTimeAxisView::set_route_active, _1, a, false)); ARDOUR_UI::instance()->the_editor().get_selection().tracks.foreach_route_ui (boost::bind (&RouteUI::set_route_active, _1, a, false));
} else { } else {
_route->set_active (a, this); _route->set_active (a, this);
} }
@ -2552,7 +2552,7 @@ RouteUI::build_playlist_menu ()
vector<boost::shared_ptr<Playlist> > playlists_tr = _session->playlists()->playlists_for_track (tr); vector<boost::shared_ptr<Playlist> > playlists_tr = _session->playlists()->playlists_for_track (tr);
/* sort the playlists */ /* sort the playlists */
PlaylistSorter cmp; PlaylistSorterByID cmp;
sort (playlists_tr.begin(), playlists_tr.end(), cmp); sort (playlists_tr.begin(), playlists_tr.end(), cmp);
/* add the playlists to the menu */ /* add the playlists to the menu */
@ -2566,6 +2566,9 @@ RouteUI::build_playlist_menu ()
} }
} }
playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem(_("Select ..."), sigc::mem_fun(*this, &RouteUI::show_playlist_selector)));
playlist_items.push_back (SeparatorElem()); playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::rename_current_playlist))); playlist_items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::rename_current_playlist)));
playlist_items.push_back (SeparatorElem()); playlist_items.push_back (SeparatorElem());
@ -2576,8 +2579,8 @@ RouteUI::build_playlist_menu ()
} else { } else {
// Use a label which tells the user what is happening // Use a label which tells the user what is happening
playlist_items.push_back (MenuElem (_("New Take"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::new_playlists), this))); playlist_items.push_back (MenuElem (_("New Group Playlist"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::new_playlists), this)));
playlist_items.push_back (MenuElem (_("Copy Take"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::copy_playlists), this))); playlist_items.push_back (MenuElem (_("Copy Group Playlist"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::copy_playlists), this)));
} }
@ -2585,7 +2588,12 @@ RouteUI::build_playlist_menu ()
playlist_items.push_back (MenuElem (_("Clear Current"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::clear_playlists), this))); playlist_items.push_back (MenuElem (_("Clear Current"), sigc::bind(sigc::mem_fun(editor, &PublicEditor::clear_playlists), this)));
playlist_items.push_back (SeparatorElem()); playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem(_("Select from All..."), sigc::mem_fun(*this, &RouteUI::show_playlist_selector))); Menu* advanced_menu = manage (new Menu);
MenuList& advanced_items = advanced_menu->items();
advanced_items.push_back (MenuElem(_("Copy from ..."), sigc::mem_fun(*this, &RouteUI::show_playlist_copy_selector)));
advanced_items.push_back (MenuElem(_("Share with ..."), sigc::mem_fun(*this, &RouteUI::show_playlist_share_selector)));
advanced_items.push_back (MenuElem(_("Steal from ..."), sigc::mem_fun(*this, &RouteUI::show_playlist_steal_selector)));
playlist_items.push_back (MenuElem (_("Advanced"), *advanced_menu));
} }
void void
@ -2594,7 +2602,7 @@ RouteUI::use_playlist (RadioMenuItem *item, boost::weak_ptr<Playlist> wpl)
assert (is_track()); assert (is_track());
// exit if we were triggered by deactivating the old playlist // exit if we were triggered by deactivating the old playlist
if (!item->get_active()) { if (item && !item->get_active()) {
return; return;
} }
@ -2659,7 +2667,41 @@ RouteUI::use_playlist (RadioMenuItem *item, boost::weak_ptr<Playlist> wpl)
void void
RouteUI::show_playlist_selector () RouteUI::show_playlist_selector ()
{ {
ARDOUR_UI::instance()->the_editor().playlist_selector().show_for (this); RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (this);
if (rtv) {
ARDOUR_UI::instance()->the_editor().playlist_selector().set_tav(rtv, PlaylistSelector::plSelect);
ARDOUR_UI::instance()->the_editor().playlist_selector().redisplay ();
}
}
void
RouteUI::show_playlist_copy_selector ()
{
RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (this);
if (rtv) {
ARDOUR_UI::instance()->the_editor().playlist_selector().set_tav(rtv, PlaylistSelector::plCopy);
ARDOUR_UI::instance()->the_editor().playlist_selector().redisplay ();
}
}
void
RouteUI::show_playlist_share_selector ()
{
RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (this);
if (rtv) {
ARDOUR_UI::instance()->the_editor().playlist_selector().set_tav(rtv, PlaylistSelector::plShare);
ARDOUR_UI::instance()->the_editor().playlist_selector().redisplay ();
}
}
void
RouteUI::show_playlist_steal_selector ()
{
RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (this);
if (rtv) {
ARDOUR_UI::instance()->the_editor().playlist_selector().set_tav(rtv, PlaylistSelector::plSteal);
ARDOUR_UI::instance()->the_editor().playlist_selector().redisplay ();
}
} }
void void

View file

@ -161,6 +161,8 @@ public:
void use_new_playlist (bool prompt, std::vector<boost::shared_ptr<ARDOUR::Playlist> > const&, bool copy); void use_new_playlist (bool prompt, std::vector<boost::shared_ptr<ARDOUR::Playlist> > const&, bool copy);
void clear_playlist (); void clear_playlist ();
void use_playlist (Gtk::RadioMenuItem* item, boost::weak_ptr<ARDOUR::Playlist> wpl);
/* used by EditorRoutes */ /* used by EditorRoutes */
static Gtkmm2ext::ActiveState solo_active_state (boost::shared_ptr<ARDOUR::Stripable>); static Gtkmm2ext::ActiveState solo_active_state (boost::shared_ptr<ARDOUR::Stripable>);
static Gtkmm2ext::ActiveState solo_isolate_active_state (boost::shared_ptr<ARDOUR::Stripable>); static Gtkmm2ext::ActiveState solo_isolate_active_state (boost::shared_ptr<ARDOUR::Stripable>);
@ -239,9 +241,13 @@ protected:
std::string playlist_tip () const; std::string playlist_tip () const;
void build_playlist_menu (); void build_playlist_menu ();
void use_playlist (Gtk::RadioMenuItem* item, boost::weak_ptr<ARDOUR::Playlist> wpl);
Gtk::Menu* playlist_action_menu; Gtk::Menu* playlist_action_menu;
void show_playlist_selector ();
void show_playlist_copy_selector ();
void show_playlist_share_selector ();
void show_playlist_steal_selector ();
Gtk::CheckMenuItem* denormal_menu_item; Gtk::CheckMenuItem* denormal_menu_item;
static void set_showing_sends_to (boost::shared_ptr<ARDOUR::Route>); static void set_showing_sends_to (boost::shared_ptr<ARDOUR::Route>);
@ -285,7 +291,6 @@ private:
void set_sends_gain_to_zero (); void set_sends_gain_to_zero ();
void set_sends_gain_to_unity (); void set_sends_gain_to_unity ();
void show_playlist_selector ();
void rename_current_playlist (); void rename_current_playlist ();
void parameter_changed (std::string const&); void parameter_changed (std::string const&);