fix incorrect accumulation of export video options each time the dialog is used
[ardour.git] / gtk2_ardour / transport_masters_dialog.cc
index 66409b87fa79a0ea440f3cb4a67e3ff0257be82e..33d0ce5ca4fa0b4ba7c0e823f494bc7fa1cd29ba 100644 (file)
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 */
+#include <gtkmm/stock.h>
 
 #include "pbd/enumwriter.h"
+#include "pbd/i18n.h"
 
 #include "temporal/time.h"
 
@@ -32,9 +34,9 @@
 #include "gtkmm2ext/gui_thread.h"
 
 #include "ardour_ui.h"
+#include "floating_text_entry.h"
 #include "transport_masters_dialog.h"
 
-#include "pbd/i18n.h"
 
 using namespace std;
 using namespace Gtk;
@@ -44,36 +46,45 @@ using namespace PBD;
 using namespace ArdourWidgets;
 
 TransportMastersWidget::TransportMastersWidget ()
-       : table (4, 9)
+       : table (4, 13)
+       , add_button (_("Add a new Transport Master"))
 {
        pack_start (table, PACK_EXPAND_WIDGET, 12);
-
-       col_title[0].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Name")));
-       col_title[0].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Name")));
-       col_title[1].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Type")));
-       col_title[2].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Format")));
-       col_title[3].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Current")));
-       col_title[4].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Timestamp")));
-       col_title[5].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Delta")));
-       col_title[6].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Collect")));
-       col_title[7].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Use")));
-       col_title[8].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Data Source")));
-       col_title[9].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Accept")));
-       col_title[10].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Clock Synced")));
-       col_title[11].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("29.97/30")));
-
-       set_tooltip (col_title[11], _("<b>When enabled</b> the external timecode source is assumed to use 29.97 fps instead of 30000/1001.\n"
-                                    "SMPTE 12M-1999 specifies 29.97df as 30000/1001. The spec further mentions that "
-                                    "drop-sample timecode has an accumulated error of -86ms over a 24-hour period.\n"
-                                    "Drop-sample timecode would compensate exactly for a NTSC color frame rate of 30 * 0.9990 (ie 29.970000). "
-                                    "That is not the actual rate. However, some vendors use that rate - despite it being against the specs - "
-                                    "because the variant of using exactly 29.97 fps has zero timecode drift.\n"
+       pack_start (add_button, FALSE, FALSE);
+
+       add_button.signal_clicked ().connect (sigc::mem_fun (*this, &TransportMastersWidget::add_master));
+
+       col_title[0].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Use")));
+       col_title[1].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Name")));
+       col_title[2].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Type")));
+       col_title[3].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Format/\nBPM")));
+       col_title[4].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Current")));
+       col_title[5].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Last")));
+       col_title[6].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Timestamp")));
+       col_title[7].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Delta")));
+       col_title[8].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Collect")));
+       col_title[9].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Data Source")));
+       col_title[10].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Active\nCommands")));
+       col_title[11].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Clock\nSynced")));
+       col_title[12].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("29.97/\n30")));
+       col_title[13].set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Remove")));
+
+       set_tooltip (col_title[12], _("<b>When enabled</b> the external timecode source is assumed to use 29.97 fps instead of 30000/1001.\n"
+                                     "SMPTE 12M-1999 specifies 29.97df as 30000/1001. The spec further mentions that "
+                                     "drop-sample timecode has an accumulated error of -86ms over a 24-hour period.\n"
+                                     "Drop-sample timecode would compensate exactly for a NTSC color frame rate of 30 * 0.9990 (ie 29.970000). "
+                                     "That is not the actual rate. However, some vendors use that rate - despite it being against the specs - "
+                                     "because the variant of using exactly 29.97 fps has zero timecode drift.\n"
                             ));
 
+       set_tooltip (col_title[11], string_compose (_("<b>When enabled</b> the external timecode source is assumed to be sample-clock synced to the audio interface\n"
+                                                     "being used by %1."), PROGRAM_NAME));
 
        table.set_spacings (6);
 
        TransportMasterManager::instance().CurrentChanged.connect (current_connection, invalidator (*this), boost::bind (&TransportMastersWidget::current_changed, this, _1, _2), gui_context());
+       TransportMasterManager::instance().Added.connect (add_connection, invalidator (*this), boost::bind (&TransportMastersWidget::rebuild, this), gui_context());
+       TransportMasterManager::instance().Removed.connect (remove_connection, invalidator (*this), boost::bind (&TransportMastersWidget::rebuild, this), gui_context());
 
        rebuild ();
 }
@@ -85,6 +96,12 @@ TransportMastersWidget::~TransportMastersWidget ()
        }
 }
 
+void
+TransportMastersWidget::set_transport_master (boost::shared_ptr<TransportMaster> tm)
+{
+       _session->request_sync_source (tm);
+}
+
 void
 TransportMastersWidget::current_changed (boost::shared_ptr<TransportMaster> old_master, boost::shared_ptr<TransportMaster> new_master)
 {
@@ -96,6 +113,35 @@ TransportMastersWidget::current_changed (boost::shared_ptr<TransportMaster> old_
        }
 }
 
+void
+TransportMastersWidget::add_master ()
+{
+       AddTransportMasterDialog d;
+
+       d.present ();
+       string name;
+
+       while (name.empty()) {
+
+               int r = d.run ();
+
+               switch (r) {
+               case RESPONSE_ACCEPT:
+                       name = d.get_name();
+                       break;
+               default:
+                       return;
+               }
+       }
+
+       d.hide ();
+
+       if (TransportMasterManager::instance().add (d.get_type(), name)) {
+               MessageDialog msg (_("New transport master not added - check error log for details"));
+               msg.run ();
+       }
+}
+
 void
 TransportMastersWidget::rebuild ()
 {
@@ -108,26 +154,19 @@ TransportMastersWidget::rebuild ()
        }
 
        rows.clear ();
-       table.resize (masters.size()+1, 12);
-
-       table.attach (col_title[0], 0, 1, 0, 1);
-       table.attach (col_title[1], 1, 2, 0, 1);
-       table.attach (col_title[2], 2, 3, 0, 1);
-       table.attach (col_title[3], 3, 4, 0, 1);
-       table.attach (col_title[4], 4, 5, 0, 1);
-       table.attach (col_title[5], 5, 6, 0, 1);
-       table.attach (col_title[6], 6, 7, 0, 1);
-       table.attach (col_title[7], 7, 8, 0, 1);
-       table.attach (col_title[8], 8, 9, 0, 1);
-       table.attach (col_title[9], 9, 10, 0, 1);
-       table.attach (col_title[10], 10, 11, 0, 1);
-       table.attach (col_title[11], 11, 12, 0, 1);
+       table.resize (masters.size()+1, 14);
+
+       for (size_t col = 0; col < sizeof (col_title) / sizeof (col_title[0]); ++col) {
+               table.attach (col_title[col], col, col+1, 0, 1);
+       }
 
        uint32_t n = 1;
 
+       Gtk::RadioButtonGroup use_button_group;
+
        for (TransportMasterManager::TransportMasters::const_iterator m = masters.begin(); m != masters.end(); ++m, ++n) {
 
-               Row* r = new Row;
+               Row* r = new Row (*this);
                rows.push_back (r);
 
                r->tm = *m;
@@ -140,29 +179,46 @@ TransportMastersWidget::rebuild ()
                        r->use_button.set_active (true);
                }
 
-               table.attach (r->type, 0, 1, n, n+1);
-               table.attach (r->label, 1, 2, n, n+1);
-               table.attach (r->format, 2, 3, n, n+1);
-               table.attach (r->current, 3, 4, n, n+1);
-               table.attach (r->timestamp, 4, 5, n, n+1);
-               table.attach (r->delta, 5, 6, n, n+1);
-               table.attach (r->collect_button, 6, 7, n, n+1);
-               table.attach (r->use_button, 7, 8, n, n+1);
-               table.attach (r->port_combo, 8, 9, n, n+1);
-               table.attach (r->request_options, 9, 10, n, n+1);
+               int col = 0;
+
+               r->label_box.add (r->label);
+
+               table.attach (r->use_button, col, col+1, n, n+1); ++col;
+               table.attach (r->label_box, col, col+1, n, n+1); ++col;
+               table.attach (r->type, col, col+1, n, n+1); ++col;
+               table.attach (r->format, col, col+1, n, n+1); ++col;
+               table.attach (r->current, col, col+1, n, n+1); ++col;
+               table.attach (r->last, col, col+1, n, n+1); ++col;
+               table.attach (r->timestamp, col, col+1, n, n+1); ++col;
+               table.attach (r->delta, col, col+1, n, n+1); ++col;
+               table.attach (r->collect_button, col, col+1, n, n+1); ++col;
+               table.attach (r->port_combo, col, col+1, n, n+1); ++col;
+               table.attach (r->request_options, col, col+1, n, n+1); ++col;
 
                boost::shared_ptr<TimecodeTransportMaster> ttm (boost::dynamic_pointer_cast<TimecodeTransportMaster> (r->tm));
 
                if (ttm) {
-                       table.attach (r->sclock_synced_button, 10, 11, n, n+1);
-                       table.attach (r->fr2997_button, 11, 12, n, n+1);
+                       table.attach (r->sclock_synced_button, col, col+1, n, n+1); ++col;
+                       table.attach (r->fr2997_button, col, col+1, n, n+1); ++col;
                        r->fr2997_button.signal_toggled().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::fr2997_button_toggled));
+               } else {
+                       col += 2;
+               }
+
+               if (r->tm->removeable()) {
+                       table.attach (r->remove_button, col, col+1, n, n+1); ++col;
+               } else {
+                       col++;
                }
 
+               table.show_all ();
+
+               r->label_box.signal_button_press_event().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::name_press));
                r->port_combo.signal_changed().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::port_choice_changed));
                r->use_button.signal_toggled().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::use_button_toggled));
                r->collect_button.signal_toggled().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::collect_button_toggled));
                r->request_options.signal_button_press_event().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::request_option_press), false);
+               r->remove_button.signal_clicked().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::remove_clicked));
 
                if (ttm) {
                        r->sclock_synced_button.signal_toggled().connect (sigc::mem_fun (*r, &TransportMastersWidget::Row::sync_button_toggled));
@@ -184,12 +240,56 @@ TransportMastersWidget::rebuild ()
        }
 }
 
-TransportMastersWidget::Row::Row ()
-       : request_option_menu (0)
+TransportMastersWidget::Row::Row (TransportMastersWidget& p)
+       : parent (p)
+       , request_option_menu (0)
+       , remove_button (X_("x"))
+       , name_editor (0)
+       , save_when (0)
        , ignore_active_change (false)
 {
 }
 
+bool
+TransportMastersWidget::Row::name_press (GdkEventButton* ev)
+{
+       if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) {
+               Gtk::Window* toplevel = dynamic_cast<Gtk::Window*> (label.get_toplevel());
+               if (!toplevel) {
+                       return false;
+               }
+               name_editor = new FloatingTextEntry (toplevel, tm->name());
+               name_editor->use_text.connect (sigc::mem_fun (*this, &TransportMastersWidget::Row::name_edited));
+               name_editor->show ();
+               return true;
+       }
+       return false;
+}
+
+bool
+TransportMastersWidget::idle_remove (TransportMastersWidget::Row* row)
+{
+       TransportMasterManager::instance().remove (row->tm->name());
+       return false;
+}
+
+void
+TransportMastersWidget::Row::remove_clicked ()
+{
+       /* have to do this via an idle callback, because it will destroy the
+          widget from which this callback was initiated.
+       */
+       Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (parent, &TransportMastersWidget::idle_remove), this));
+}
+
+void
+TransportMastersWidget::Row::name_edited (string str, int ignored)
+{
+       tm->set_name (str);
+       /* floating text entry deletes itself */
+       name_editor = 0;
+}
+
 void
 TransportMastersWidget::Row::prop_change (PropertyChange what_changed)
 {
@@ -221,7 +321,7 @@ void
 TransportMastersWidget::Row::use_button_toggled ()
 {
        if (use_button.get_active()) {
-               Config->set_sync_source (tm->type());
+               parent.set_transport_master (tm);
        }
 }
 
@@ -374,38 +474,72 @@ TransportMastersWidget::Row::update (Session* s, samplepos_t now)
 
        samplepos_t pos;
        double speed;
+       samplepos_t most_recent;
+       samplepos_t when;
        stringstream ss;
        Time t;
+       Time l;
        boost::shared_ptr<TimecodeTransportMaster> ttm;
        boost::shared_ptr<MIDIClock_TransportMaster> mtm;
 
        if (s) {
 
-               if (tm->speed_and_position (speed, pos, now)) {
+               if (tm->speed_and_position (speed, pos, most_recent, when, now)) {
 
                        sample_to_timecode (pos, t, false, false, 25, false, AudioEngine::instance()->sample_rate(), 100, false, 0);
+                       sample_to_timecode (most_recent, l, false, false, 25, false, AudioEngine::instance()->sample_rate(), 100, false, 0);
 
                        if ((ttm = boost::dynamic_pointer_cast<TimecodeTransportMaster> (tm))) {
                                format.set_text (timecode_format_name (ttm->apparent_timecode_format()));
+                               last.set_text (Timecode::timecode_format_time (l));
                        } else if ((mtm = boost::dynamic_pointer_cast<MIDIClock_TransportMaster> (tm))) {
                                char buf[8];
                                snprintf (buf, sizeof (buf), "%.1f", mtm->bpm());
                                format.set_text (buf);
+                               last.set_text ("");
                        } else {
                                format.set_text ("");
+                               last.set_text ("");
                        }
                        current.set_text (Timecode::timecode_format_time (t));
-                       timestamp.set_markup (tm->position_string());
                        delta.set_markup (tm->delta_string ());
 
+                       char gap[32];
+                       const float seconds = (when - now) / (float) AudioEngine::instance()->sample_rate();
+                       if (abs (seconds) < 1.0) {
+                               snprintf (gap, sizeof (gap), "%.3fs", seconds);
+                       } else if (abs (seconds) < 4.0) {
+                               snprintf (gap, sizeof (gap), "%ds", (int) floor (seconds));
+                       } else {
+                               snprintf (gap, sizeof (gap), "%s", _(">4s ago"));
+                       }
+                       timestamp.set_text (gap);
+                       save_when = when;
+
+               } else {
+
+                       if (save_when) {
+                               char gap[32];
+
+                               const float seconds = (when - now) / (float) AudioEngine::instance()->sample_rate();
+                               if (abs (seconds) < 1.0) {
+                                       snprintf (gap, sizeof (gap), "%.3fs", seconds);
+                               } else if (abs (seconds) < 4.0) {
+                                       snprintf (gap, sizeof (gap), "%ds", (int) floor (seconds));
+                               } else {
+                                       snprintf (gap, sizeof (gap), "%s", _(">4s ago"));
+                               }
+                               timestamp.set_text (gap);
+                               save_when = when;
+                       }
+                       delta.set_text ("");
+                       current.set_text ("");
                }
        }
-
-       populate_port_combo ();
 }
 
 void
-TransportMastersWidget::update (samplepos_t audible)
+TransportMastersWidget::update (samplepos_t /* audible */)
 {
        samplepos_t now = AudioEngine::instance()->sample_time ();
 
@@ -438,7 +572,6 @@ TransportMastersWindow::TransportMastersWindow ()
 void
 TransportMastersWindow::on_realize ()
 {
-       std::cerr << "TMD realized!\n";
        ArdourWindow::on_realize ();
        /* (try to) ensure that resizing is possible and the window can be moved (and closed) */
        get_window()->set_decorations (Gdk::DECOR_BORDER | Gdk::DECOR_RESIZEH | Gdk::DECOR_TITLE | Gdk::DECOR_MENU);
@@ -452,3 +585,61 @@ TransportMastersWindow::set_session (ARDOUR::Session* s)
        ArdourWindow::set_session (s);
        w.set_session (s);
 }
+
+TransportMastersWidget::AddTransportMasterDialog::AddTransportMasterDialog ()
+       : ArdourDialog (_("Add Transport Master"), true, false)
+       , name_label (_("Name"))
+       , type_label (_("Type"))
+{
+       name_hbox.set_spacing (6);
+       name_hbox.pack_start (name_label, false, false);
+       name_hbox.pack_start (name_entry, true, true);
+
+       type_hbox.set_spacing (6);
+       type_hbox.pack_start (type_label, false, false);
+       type_hbox.pack_start (type_combo, true, true);
+
+       vector<string> s;
+
+       s.push_back (X_("MTC"));
+       s.push_back (X_("LTC"));
+       s.push_back (X_("MIDI Clock"));
+
+       set_popdown_strings (type_combo, s);
+       type_combo.set_active_text (X_("LTC"));
+
+       get_vbox()->pack_start (name_hbox, false, false);
+       get_vbox()->pack_start (type_hbox, false, false);
+
+       add_button (_("Cancel"), RESPONSE_CANCEL);
+       add_button (_("Add"), RESPONSE_ACCEPT);
+
+       name_entry.show ();
+       type_combo.show ();
+       name_label.show ();
+       type_label.show ();
+       name_hbox.show ();
+       type_hbox.show ();
+
+       name_entry.signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &Gtk::Dialog::response), Gtk::RESPONSE_ACCEPT));
+}
+
+string
+TransportMastersWidget::AddTransportMasterDialog::get_name () const
+{
+       return name_entry.get_text ();
+}
+
+SyncSource
+TransportMastersWidget::AddTransportMasterDialog::get_type() const
+{
+       string t = type_combo.get_active_text ();
+
+       if (t == X_("MTC")) {
+               return MTC;
+       } else if (t == X_("MIDI Clock")) {
+               return MIDIClock;
+       }
+
+       return LTC;
+}