diff --git a/gtk2_ardour/editor_route_groups.cc b/gtk2_ardour/editor_route_groups.cc index f445ad9851..4042e3ac07 100644 --- a/gtk2_ardour/editor_route_groups.cc +++ b/gtk2_ardour/editor_route_groups.cc @@ -232,13 +232,14 @@ void EditorRouteGroups::recall() { } void EditorRouteGroups::write() { - camera->write(); + // camera->write(); } void EditorRouteGroups::load() { string path = Glib::build_filename(user_config_directory(-1), "snapshot.xml"); camera->load(path); MixerSnapshotDialog* m = new MixerSnapshotDialog(); + m->set_session(_session); m->run(); } diff --git a/gtk2_ardour/mixer_snapshot_dialog.cc b/gtk2_ardour/mixer_snapshot_dialog.cc index f08bb4a136..ff4d72ae95 100644 --- a/gtk2_ardour/mixer_snapshot_dialog.cc +++ b/gtk2_ardour/mixer_snapshot_dialog.cc @@ -1,25 +1,60 @@ #include +#include "ardour/filesystem_paths.h" +#include "ardour/session_state_utils.h" +#include "ardour/session_directory.h" + +#include +#include +#include + #include +#include "widgets/tooltips.h" +#include "widgets/choice.h" +#include "widgets/prompter.h" + #include "mixer_snapshot_dialog.h" +#include "pbd/basename.h" +#include "pbd/file_utils.h" +#include "pbd/gstdio_compat.h" #include "pbd/i18n.h" using namespace Gtk; using namespace PBD; using namespace std; +using namespace ARDOUR; +using namespace ArdourWidgets; + +struct ColumnInfo { + int index; + int sort_idx; + AlignmentEnum al; + const char* label; + const char* tooltip; +}; MixerSnapshotDialog::MixerSnapshotDialog() : ArdourDialog(_("this is a dialog"), true, false) { - - set_name("msdialog"); - Table* table = new Table(3, 3); - table->set_size_request(-1, 600); - table->attach (scroller, 0, 3, 0, 5); - get_vbox()->pack_start (*table); + global_model = Gtk::ListStore::create(_columns); + local_model = Gtk::ListStore::create(_columns); + bootstrap_display_and_model(global_display, global_model, true); + bootstrap_display_and_model(local_display, local_model, false); + + // global_display.set_focus_on_click(); + // local_display.set_focus_on_click(); + + // global_display.signal_cursor_changed().connect(sigc::mem_fun(*this, &MixerSnapshotDialog::callback)); + // local_display.signal_cursor_changed().connect(sigc::mem_fun(*this, &MixerSnapshotDialog::callback)); + + // global_display.get_selection()->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::selection_changed), true), false); + // local_display.get_selection()->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::selection_changed), false), false); + + global_display.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::button_press), true), false); + local_display.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::button_press), false), false); } MixerSnapshotDialog::~MixerSnapshotDialog() @@ -27,6 +62,266 @@ MixerSnapshotDialog::~MixerSnapshotDialog() } +void MixerSnapshotDialog::set_session(Session* s) +{ + if(s) + ArdourDialog::set_session(s); + + refill(); +} + + +bool MixerSnapshotDialog::button_press(GdkEventButton* ev, bool global) +{ + if (ev->type == GDK_2BUTTON_PRESS) { + + TreeModel::iterator iter; + if(global) { + iter = global_display.get_selection()->get_selected(); + } else { + iter = local_display.get_selection()->get_selected(); + } + + global_display.get_selection()->unselect_all(); + local_display.get_selection()->unselect_all(); + + if(iter) { + MixerSnapshot* s = (*iter)[_columns.snapshot]; + s->recall(); + return true; + } + } + return false; +} + + +bool MixerSnapshotDialog::bootstrap_display_and_model(Gtkmm2ext::DnDTreeView& display, Glib::RefPtr model, bool global) +{ + if(!model) { + return false; + } + + display.set_model(model); + + display.append_column(_("Fav"), _columns.favorite); + display.append_column(_("Name"), _columns.name); + display.append_column(_("# Tracks"), _columns.n_tracks); + display.append_column(_("# VCAs"), _columns.n_vcas); + display.append_column(_("# Groups"), _columns.n_groups); + display.append_column(_("Special Tracks"), _columns.has_specials); + display.append_column(_("Date"), _columns.date); + display.append_column(_("Version"), _columns.version); + + //newest snaps should be at the top + model->set_sort_column(6, SORT_DESCENDING); + + display.set_headers_visible(true); + display.set_headers_clickable(true); + display.set_reorderable(false); + display.set_rules_hint(true); + display.add_object_drag(_columns.name.index(), ""); + display.set_drag_column(_columns.name.index()); + + CellRendererToggle* fav_cell = dynamic_cast(display.get_column_cell_renderer(0)); + fav_cell->property_activatable() = true; + fav_cell->property_radio() = true; + fav_cell->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::fav_cell_action), global)); + + HBox* add_remove = manage(new HBox); + Button* btn_add = manage(new Button("New")); + Button* btn_del = manage(new Button("Delete")); + Button* btn_load = manage(new Button("New From Session")); + add_remove->pack_start(*btn_add, true, true); + add_remove->pack_start(*btn_del, true, true); + add_remove->pack_start(*btn_load, true, true); + + VBox* vbox = manage(new VBox); + vbox->set_homogeneous(); + vbox->pack_start(*add_remove); + vbox->set_size_request(800, -1); + + if(global) { + btn_add->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::new_snapshot), true)); + + global_scroller.set_border_width(10); + global_scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC); + global_scroller.add(global_display); + + Table* table = manage(new Table(3, 3)); + table->set_size_request(-1, 400); + table->attach(global_scroller, 0, 3, 0, 5 ); + table->attach(*vbox, 2, 3, 6, 8, FILL|EXPAND, FILL, 5, 5); + get_vbox()->pack_start(*table); + } else { + btn_add->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MixerSnapshotDialog::new_snapshot), false)); + + local_scroller.set_border_width(10); + local_scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC); + local_scroller.add(local_display); + + Table* table = manage(new Table(3, 3)); + table->set_size_request(-1, 400); + table->attach(local_scroller, 0, 3, 0, 5 ); + table->attach(*vbox, 2, 3, 6, 8, FILL|EXPAND, FILL, 5, 5); + get_vbox()->pack_start(*table); + } + + ColumnInfo ci[] = { + { 0, 0, ALIGN_CENTER, _("Favorite"), _("") }, + { 1, 1, ALIGN_LEFT, _("Name"), _("") }, + { 2, 2, ALIGN_CENTER, _("# Tracks"), _("") }, + { 3, 3, ALIGN_CENTER, _("# VCAs"), _("") }, + { 4, 4, ALIGN_CENTER, _("# Groups"), _("") }, + { 5, 5, ALIGN_CENTER, _("Special Tracks"), _("") }, + { 6, 8, ALIGN_LEFT, _("Date"), _("") }, + { 7, 7, ALIGN_LEFT, _("Version"), _("") }, + { -1,-1, ALIGN_CENTER, 0, 0 } + }; + + for (int i = 0; ci[i].index >= 0; ++i) { + ColumnInfo info = ci[i]; + + TreeViewColumn* col = display.get_column(info.index); + + Label* label = manage(new Label (info.label)); + label->set_alignment(info.al); + set_tooltip(*label, info.tooltip); + col->set_widget(*label); + label->show(); + + col->set_sort_column(info.sort_idx); + col->set_expand(false); + col->set_alignment(info.al); + + //...and this sets the alignment for the data cells + CellRendererText* rend = dynamic_cast(display.get_column_cell_renderer(info.index)); + if (rend) { + rend->property_xalign() = (info.al == ALIGN_RIGHT ? 1.0 : (info.al == ALIGN_LEFT ? 0.0 : 0.5)); + } + } + return true; +} + +void MixerSnapshotDialog::new_snapshot(bool global) +{ + MixerSnapshot* snap = new MixerSnapshot(_session); + + ArdourWidgets::Prompter prompter(true); + + string new_name; + + prompter.set_name("New Mixer Snapshot Prompter"); + prompter.set_title(_("Mixer Snapshot Name:")); + prompter.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT); + prompter.set_prompt(_("Set Mixer Snapshot Name")); + prompter.set_initial_text(snap->label); + + if (prompter.run() == RESPONSE_ACCEPT) { + prompter.get_result(new_name); + if (new_name.length() > 0) { + snap->label = new_name; + snap->snap(); + snap->write(global); + refill(); + } + } +} + +void MixerSnapshotDialog::refill() +{ + global_model->clear(); + + string global_directory = Glib::build_filename(user_config_directory(-1), "mixer_snapshots/"); + + vector files; + find_files_matching_pattern(files, global_directory, "*.xml"); + + for(vector::iterator i = files.begin(); i != files.end(); i++) { + string path = *(i); + string name = basename_nosuffix(*(i)); + + MixerSnapshot* snap = new MixerSnapshot(_session, path); + snap->label = name; + + TreeModel::Row row = *(global_model->append()); + if (name.length() > 48) { + name = name.substr (0, 48); + name.append("..."); + } + + row[_columns.name] = name; + row[_columns.favorite] = snap->favorite; + row[_columns.version] = snap->get_last_modified_with(); + row[_columns.n_tracks] = snap->get_routes().size(); + row[_columns.n_vcas] = snap->get_vcas().size(); + row[_columns.n_groups] = snap->get_groups().size();; + row[_columns.has_specials] = true; + + GStatBuf gsb; + g_stat(path.c_str(), &gsb); + Glib::DateTime gdt(Glib::DateTime::create_now_local(gsb.st_mtime)); + + row[_columns.timestamp] = gsb.st_mtime; + row[_columns.date] = gdt.format ("%F %H:%M");; + row[_columns.full_path] = path; + row[_columns.snapshot] = snap; + } + + local_model->clear(); + files.clear(); + + string local_directory = Glib::build_filename(_session->session_directory().root_path(), "mixer_snapshots/"); + find_files_matching_pattern(files, local_directory, "*.xml"); + + for(vector::iterator i = files.begin(); i != files.end(); i++) { + string path = *(i); + string name = basename_nosuffix(*(i)); + + MixerSnapshot* snap = new MixerSnapshot(_session, path); + snap->label = name; + + TreeModel::Row row = *(local_model->append()); + if (name.length() > 48) { + name = name.substr (0, 48); + name.append("..."); + } + + row[_columns.name] = name; + row[_columns.favorite] = snap->favorite; + row[_columns.version] = snap->get_last_modified_with(); + row[_columns.n_tracks] = snap->get_routes().size(); + row[_columns.n_vcas] = snap->get_vcas().size(); + row[_columns.n_groups] = snap->get_groups().size();; + row[_columns.has_specials] = true; + + GStatBuf gsb; + g_stat(path.c_str(), &gsb); + Glib::DateTime gdt(Glib::DateTime::create_now_local(gsb.st_mtime)); + + row[_columns.timestamp] = gsb.st_mtime; + row[_columns.date] = gdt.format ("%F %H:%M");; + row[_columns.full_path] = path; + row[_columns.snapshot] = snap; + } +} + +void MixerSnapshotDialog::fav_cell_action(const string& path, bool global) +{ + TreeModel::iterator iter; + if(global) + iter = global_model->get_iter(path); + else + iter = local_model->get_iter(path); + + if(iter) { + MixerSnapshot* snap = (*iter)[_columns.snapshot]; + snap->favorite = !snap->favorite; + (*iter)[_columns.favorite] = snap->favorite; + snap->write(global); + } + +} + int MixerSnapshotDialog::run() { show_all(); return 0; diff --git a/gtk2_ardour/mixer_snapshot_dialog.h b/gtk2_ardour/mixer_snapshot_dialog.h index 1dd0af6764..7fe5a017dc 100644 --- a/gtk2_ardour/mixer_snapshot_dialog.h +++ b/gtk2_ardour/mixer_snapshot_dialog.h @@ -45,12 +45,54 @@ class MixerSnapshotDialog : public ArdourDialog MixerSnapshotDialog(); ~MixerSnapshotDialog(); + void set_session(ARDOUR::Session*); + int run(); private: - Gtkmm2ext::DnDTreeView snap_display; - Gtk::ScrolledWindow scroller; + bool button_press(GdkEventButton*, bool); + void new_snapshot(bool); + bool bootstrap_display_and_model(Gtkmm2ext::DnDTreeView&, Glib::RefPtr, bool); + + void fav_cell_action(const std::string&, bool); + void refill(); + + struct MixerSnapshotColumns : public Gtk::TreeModel::ColumnRecord { + MixerSnapshotColumns () { + add (favorite); + add (name); + add (n_tracks); + add (n_vcas); + add (n_groups); + add (has_specials); + add (date); + add (version); + add (timestamp); + add (full_path); + add (snapshot); + } + Gtk::TreeModelColumn favorite; + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn version; + Gtk::TreeModelColumn n_tracks; + Gtk::TreeModelColumn n_vcas; + Gtk::TreeModelColumn n_groups; + Gtk::TreeModelColumn has_specials; + Gtk::TreeModelColumn date; + Gtk::TreeModelColumn timestamp; + Gtk::TreeModelColumn full_path; + Gtk::TreeModelColumn snapshot; + }; + + MixerSnapshotColumns _columns; + + Gtkmm2ext::DnDTreeView global_display; + Gtkmm2ext::DnDTreeView local_display; + Gtk::ScrolledWindow global_scroller; + Gtk::ScrolledWindow local_scroller; + Glib::RefPtr global_model; + Glib::RefPtr local_model; }; #endif diff --git a/libs/ardour/ardour/mixer_snapshot.h b/libs/ardour/ardour/mixer_snapshot.h index fa72fc8372..132fe108a7 100644 --- a/libs/ardour/ardour/mixer_snapshot.h +++ b/libs/ardour/ardour/mixer_snapshot.h @@ -42,7 +42,7 @@ class MixerSnapshot void snap(boost::shared_ptr); void recall(); void clear(); - void write(); + void write(bool); void load(std::string); struct State { @@ -62,10 +62,13 @@ class MixerSnapshot std::vector get_routes() {return route_states;}; std::vector get_groups() {return group_states;}; std::vector get_vcas() {return vca_states;}; + std::string get_last_modified_with() {return last_modified_with;}; int id; std::string label; std::time_t timestamp; + bool favorite; + bool has_specials; private: ARDOUR::Session* _session; @@ -74,7 +77,7 @@ class MixerSnapshot void load_from_session(std::string); void load_from_session(XMLNode&); - + std::string last_modified_with; std::vector route_states; std::vector group_states; std::vector vca_states; diff --git a/libs/ardour/mixer_snapshot.cc b/libs/ardour/mixer_snapshot.cc index 73953fd5dd..1f2a236d9c 100644 --- a/libs/ardour/mixer_snapshot.cc +++ b/libs/ardour/mixer_snapshot.cc @@ -6,6 +6,8 @@ #include "ardour/filename_extensions.h" #include "ardour/filesystem_paths.h" #include "ardour/session_state_utils.h" +#include "ardour/revision.h" +#include "ardour/session_directory.h" #include "pbd/i18n.h" #include "pbd/xml++.h" @@ -22,6 +24,8 @@ MixerSnapshot::MixerSnapshot(Session* s) : id(0) , label("snapshot") , timestamp(time(0)) + , favorite(false) + , last_modified_with("") { if(s) _session = s; @@ -31,11 +35,21 @@ MixerSnapshot::MixerSnapshot(Session* s, string file_path) : id(0) , label("snapshot") , timestamp(time(0)) + , favorite(false) + , last_modified_with("") { if(s) _session = s; - load_from_session(file_path); + if(Glib::file_test(file_path.c_str(), Glib::FILE_TEST_IS_DIR)) + load_from_session(file_path); + + string suffix = "." + PBD::get_suffix(file_path); + if(suffix == statefile_suffix) + load_from_session(file_path); + + if(suffix == ".xml") + load(file_path); } MixerSnapshot::~MixerSnapshot() @@ -192,6 +206,9 @@ void MixerSnapshot::reassign_masters(boost::shared_ptr slv, XMLNode no void MixerSnapshot::recall() { + if(!_session) + return; + _session->begin_reversible_command(_("mixer-snapshot recall")); //vcas @@ -245,11 +262,21 @@ void MixerSnapshot::recall() _session->commit_reversible_command(); } -void MixerSnapshot::write() -{ +void MixerSnapshot::write(bool global) +{ + if(empty()) + return; + XMLNode* node = new XMLNode("MixerSnapshot"); XMLNode* child; + child = node->add_child ("ProgramVersion"); + string modified_with = string_compose("%1 %2", PROGRAM_NAME, revision); + child->set_property("modified-with", modified_with); + + child = node->add_child ("Favorite"); + child->set_property("favorite", favorite); + child = node->add_child("Routes"); for(vector::iterator i = route_states.begin(); i != route_states.end(); i++) { child->add_child_copy((*i).node); @@ -265,10 +292,15 @@ void MixerSnapshot::write() child->add_child_copy((*i).node); } - string snap = Glib::build_filename(user_config_directory(-1), label + ".xml"); + string path = ""; + if(global) + path = Glib::build_filename(user_config_directory(-1), "mixer_snapshots/", label + ".xml"); + else + path = Glib::build_filename(_session->session_directory().root_path(), "mixer_snapshots/", label + ".xml"); + XMLTree tree; tree.set_root(node); - tree.write(snap.c_str()); + tree.write(path.c_str()); } void MixerSnapshot::load(string path) @@ -286,9 +318,23 @@ void MixerSnapshot::load(string path) return; } - XMLNode* route_node = find_named_node(*root, "Routes"); - XMLNode* group_node = find_named_node(*root, "Groups"); - XMLNode* vca_node = find_named_node(*root, "VCAS"); + XMLNode* version_node = find_named_node(*root, "ProgramVersion"); + XMLNode* fav_node = find_named_node(*root, "Favorite"); + XMLNode* route_node = find_named_node(*root, "Routes"); + XMLNode* group_node = find_named_node(*root, "Groups"); + XMLNode* vca_node = find_named_node(*root, "VCAS"); + + if(version_node) { + string version; + version_node->get_property(X_("modified-with"), version); + last_modified_with = version; + } + + if(fav_node) { + string fav; + fav_node->get_property(X_("favorite"), fav); + favorite = atoi(fav.c_str()); + } if(route_node) { XMLNodeList nlist = route_node->children();