Add Editor/UI Lua Scripting and Action/Callback Script
[ardour.git] / gtk2_ardour / ardour_ui2.cc
1 /*
2     Copyright (C) 1999 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <unistd.h>
27 #include <cerrno>
28 #include <iostream>
29 #include <cmath>
30
31 #include <sigc++/bind.h>
32 #include "pbd/error.h"
33 #include "pbd/basename.h"
34 #include "pbd/fastlog.h"
35
36 #include "gtkmm2ext/cairocell.h"
37 #include "gtkmm2ext/utils.h"
38 #include "gtkmm2ext/click_box.h"
39 #include "gtkmm2ext/window_title.h"
40
41 #include "ardour/profile.h"
42 #include "ardour/session.h"
43 #include "ardour/types.h"
44
45 #include "ardour_ui.h"
46 #include "keyboard.h"
47 #include "public_editor.h"
48 #include "audio_clock.h"
49 #include "actions.h"
50 #include "main_clock.h"
51 #include "utils.h"
52 #include "theme_manager.h"
53 #include "midi_tracer.h"
54 #include "shuttle_control.h"
55 #include "global_port_matrix.h"
56 #include "location_ui.h"
57 #include "rc_option_editor.h"
58 #include "time_info_box.h"
59
60 #include "i18n.h"
61
62 using namespace std;
63 using namespace ARDOUR;
64 using namespace PBD;
65 using namespace Gtkmm2ext;
66 using namespace Gtk;
67 using namespace Glib;
68 using namespace ARDOUR_UI_UTILS;
69
70
71 void
72 ARDOUR_UI::setup_tooltips ()
73 {
74         set_tip (roll_button, _("Play from playhead"));
75         set_tip (stop_button, _("Stop playback"));
76         set_tip (rec_button, _("Toggle record"));
77         set_tip (play_selection_button, _("Play range/selection"));
78         set_tip (goto_start_button, _("Go to start of session"));
79         set_tip (goto_end_button, _("Go to end of session"));
80         set_tip (auto_loop_button, _("Play loop range"));
81         set_tip (midi_panic_button, _("MIDI Panic\nSend note off and reset controller messages on all MIDI channels"));
82         set_tip (auto_return_button, _("Return to last playback start when stopped"));
83         set_tip (follow_edits_button, _("Playhead follows range selections and edits"));
84         set_tip (auto_input_button, _("Be sensible about input monitoring"));
85         set_tip (click_button, _("Enable/Disable audio click"));
86         set_tip (solo_alert_button, _("When active, something is soloed.\nClick to de-solo everything"));
87         set_tip (auditioning_alert_button, _("When active, auditioning is taking place.\nClick to stop the audition"));
88         set_tip (feedback_alert_button, _("When active, there is a feedback loop."));
89         set_tip (primary_clock, _("<b>Primary Clock</b> right-click to set display mode. Click to edit, click+drag a digit or mouse-over+scroll wheel to modify.\nText edits: right-to-left overwrite <tt>Esc</tt>: cancel; <tt>Enter</tt>: confirm; postfix the edit with '+' or '-' to enter delta times.\n"));
90         set_tip (secondary_clock, _("<b>Secondary Clock</b> right-click to set display mode. Click to edit, click+drag a digit or mouse-over+scroll wheel to modify.\nText edits: right-to-left overwrite <tt>Esc</tt>: cancel; <tt>Enter</tt>: confirm; postfix the edit with '+' or '-' to enter delta times.\n"));
91         set_tip (editor_meter_peak_display, _("Reset All Peak Indicators"));
92         set_tip (error_alert_button, _("Show Error Log and acknowledge warnings"));
93
94         synchronize_sync_source_and_video_pullup ();
95
96         editor->setup_tooltips ();
97 }
98
99 bool
100 ARDOUR_UI::status_bar_button_press (GdkEventButton* ev)
101 {
102         bool handled = false;
103
104         switch (ev->button) {
105         case 1:
106                 status_bar_label.set_text ("");
107                 handled = true;
108                 break;
109         default:
110                 break;
111         }
112
113         return handled;
114 }
115
116 void
117 ARDOUR_UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
118 {
119         string text;
120
121         UI::display_message (prefix, prefix_len, ptag, mtag, msg);
122
123         ArdourLogLevel ll = LogLevelNone;
124
125         if (strcmp (prefix, _("[ERROR]: ")) == 0) {
126                 text = "<span color=\"red\" weight=\"bold\">";
127                 ll = LogLevelError;
128         } else if (strcmp (prefix, _("[WARNING]: ")) == 0) {
129                 text = "<span color=\"yellow\" weight=\"bold\">";
130                 ll = LogLevelWarning;
131         } else if (strcmp (prefix, _("[INFO]: ")) == 0) {
132                 text = "<span color=\"green\" weight=\"bold\">";
133                 ll = LogLevelInfo;
134         } else {
135                 text = "<span color=\"white\" weight=\"bold\">???";
136         }
137
138         _log_not_acknowledged = std::max(_log_not_acknowledged, ll);
139
140 #ifdef TOP_MENUBAR
141         text += prefix;
142         text += "</span>";
143         text += msg;
144
145         status_bar_label.set_markup (text);
146 #endif
147 }
148
149 XMLNode*
150 ARDOUR_UI::tearoff_settings (const char* name) const
151 {
152         XMLNode* ui_node = Config->extra_xml(X_("UI"));
153
154         if (ui_node) {
155                 XMLNode* tearoff_node = ui_node->child (X_("Tearoffs"));
156                 if (tearoff_node) {
157                         XMLNode* mnode = tearoff_node->child (name);
158                         return mnode;
159                 }
160         }
161
162         return 0;
163 }
164
165 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
166
167 void
168 ARDOUR_UI::setup_transport ()
169 {
170         RefPtr<Action> act;
171
172         transport_hbox.set_border_width (PX_SCALE(3));
173         transport_hbox.set_spacing (PX_SCALE(3));
174
175         transport_base.set_name ("TransportBase");
176         transport_base.add (transport_hbox);
177
178         transport_frame.set_shadow_type (SHADOW_OUT);
179         transport_frame.set_name ("BaseFrame");
180         transport_frame.add (transport_base);
181
182         auto_return_button.set_text(_("Auto Return"));
183
184         follow_edits_button.set_text(_("Follow Edits"));
185
186 //      auto_input_button.set_text (_("Auto Input"));
187
188         click_button.set_icon (ArdourIcon::TransportMetronom);
189
190         act = ActionManager::get_action ("Transport", "ToggleClick");
191         click_button.set_related_action (act);
192         click_button.signal_button_press_event().connect (sigc::mem_fun (*this, &ARDOUR_UI::click_button_clicked), false);
193
194         auto_return_button.set_name ("transport option button");
195         follow_edits_button.set_name ("transport option button");
196         auto_input_button.set_name ("transport option button");
197
198         /* these have to provide a clear indication of active state */
199
200         click_button.set_name ("transport button");
201         sync_button.set_name ("transport active option button");
202
203         stop_button.set_active (true);
204
205         goto_start_button.set_icon (ArdourIcon::TransportStart);
206         goto_end_button.set_icon (ArdourIcon::TransportEnd);
207         roll_button.set_icon (ArdourIcon::TransportPlay);
208         stop_button.set_icon (ArdourIcon::TransportStop);
209         play_selection_button.set_icon (ArdourIcon::TransportRange);
210         auto_loop_button.set_icon (ArdourIcon::TransportLoop);
211         rec_button.set_icon (ArdourIcon::RecButton);
212         midi_panic_button.set_icon (ArdourIcon::TransportPanic);
213
214         act = ActionManager::get_action (X_("Transport"), X_("Stop"));
215         stop_button.set_related_action (act);
216         act = ActionManager::get_action (X_("Transport"), X_("Roll"));
217         roll_button.set_related_action (act);
218         act = ActionManager::get_action (X_("Transport"), X_("Record"));
219         rec_button.set_related_action (act);
220         act = ActionManager::get_action (X_("Transport"), X_("GotoStart"));
221         goto_start_button.set_related_action (act);
222         act = ActionManager::get_action (X_("Transport"), X_("GotoEnd"));
223         goto_end_button.set_related_action (act);
224         act = ActionManager::get_action (X_("Transport"), X_("Loop"));
225         auto_loop_button.set_related_action (act);
226         act = ActionManager::get_action (X_("Transport"), X_("PlaySelection"));
227         play_selection_button.set_related_action (act);
228         act = ActionManager::get_action (X_("MIDI"), X_("panic"));
229         midi_panic_button.set_related_action (act);
230         act = ActionManager::get_action (X_("Transport"), X_("ToggleExternalSync"));
231         sync_button.set_related_action (act);
232
233         /* clocks, etc. */
234
235         ARDOUR_UI::Clock.connect (sigc::mem_fun (primary_clock, &AudioClock::set));
236         ARDOUR_UI::Clock.connect (sigc::mem_fun (secondary_clock, &AudioClock::set));
237
238         primary_clock->ValueChanged.connect (sigc::mem_fun(*this, &ARDOUR_UI::primary_clock_value_changed));
239         secondary_clock->ValueChanged.connect (sigc::mem_fun(*this, &ARDOUR_UI::secondary_clock_value_changed));
240         big_clock->ValueChanged.connect (sigc::mem_fun(*this, &ARDOUR_UI::big_clock_value_changed));
241
242         act = ActionManager::get_action ("Transport", "ToggleAutoReturn");
243         auto_return_button.set_related_action (act);
244         act = ActionManager::get_action (X_("Transport"), X_("ToggleFollowEdits"));
245         follow_edits_button.set_related_action (act);
246         act = ActionManager::get_action ("Transport", "ToggleAutoInput");
247         auto_input_button.set_related_action (act);
248
249         /* alerts */
250
251         /* CANNOT sigc::bind these to clicked or toggled, must use pressed or released */
252
253         solo_alert_button.set_name ("rude solo");
254         act = ActionManager::get_action (X_("Main"), X_("cancel-solo"));
255         solo_alert_button.set_related_action (act);
256         auditioning_alert_button.set_name ("rude audition");
257         auditioning_alert_button.signal_button_press_event().connect (sigc::mem_fun(*this,&ARDOUR_UI::audition_alert_press), false);
258         feedback_alert_button.set_name ("feedback alert");
259         feedback_alert_button.signal_button_press_event().connect (sigc::mem_fun (*this, &ARDOUR_UI::feedback_alert_press), false);
260         error_alert_button.set_name ("error alert");
261         error_alert_button.signal_button_release_event().connect (sigc::mem_fun(*this,&ARDOUR_UI::error_alert_press), false);
262         act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
263         error_alert_button.set_related_action(act);
264         error_alert_button.set_fallthrough_to_parent(true);
265
266         alert_box.set_homogeneous (true);
267         alert_box.set_spacing (PX_SCALE(2));
268         alert_box.pack_start (solo_alert_button, true, true);
269         alert_box.pack_start (auditioning_alert_button, true, true);
270         alert_box.pack_start (feedback_alert_button, true, true);
271
272         /* all transport buttons should be the same size vertically and
273          * horizontally
274          */
275
276         Glib::RefPtr<SizeGroup> transport_button_size_group = SizeGroup::create (SIZE_GROUP_BOTH);
277         transport_button_size_group->add_widget (goto_start_button);
278         transport_button_size_group->add_widget (goto_end_button);
279         transport_button_size_group->add_widget (auto_loop_button);
280         transport_button_size_group->add_widget (rec_button);
281         transport_button_size_group->add_widget (play_selection_button);
282         transport_button_size_group->add_widget (roll_button);
283         transport_button_size_group->add_widget (stop_button);
284
285         /* the icon for this has an odd aspect ratio, so fatten up the button */
286         midi_panic_button.set_size_request (PX_SCALE(25), -1);
287         goto_start_button.set_size_request (PX_SCALE(28), PX_SCALE(44));
288         click_button.set_size_request (PX_SCALE(32), PX_SCALE(44));
289
290
291         HBox* tbox1 = manage (new HBox);
292         HBox* tbox2 = manage (new HBox);
293         HBox* tbox = manage (new HBox);
294
295         VBox* vbox1 = manage (new VBox);
296         VBox* vbox2 = manage (new VBox);
297
298         Alignment* a1 = manage (new Alignment);
299         Alignment* a2 = manage (new Alignment);
300
301         tbox1->set_spacing (PX_SCALE(2));
302         tbox2->set_spacing (PX_SCALE(2));
303         tbox->set_spacing (PX_SCALE(2));
304
305         if (!Profile->get_trx()) {
306                 tbox1->pack_start (midi_panic_button, true, true, 5);
307                 tbox1->pack_start (click_button, true, true, 5);
308         }
309
310         tbox1->pack_start (goto_start_button, true, true);
311         tbox1->pack_start (goto_end_button, true, true);
312         tbox1->pack_start (auto_loop_button, true, true);
313
314         if (!Profile->get_trx()) {
315                 tbox2->pack_start (play_selection_button, true, true);
316         }
317         tbox2->pack_start (roll_button, true, true);
318         tbox2->pack_start (stop_button, true, true);
319         tbox2->pack_start (rec_button, true, true, 5);
320
321         vbox1->pack_start (*tbox1, true, true);
322         vbox2->pack_start (*tbox2, true, true);
323
324         a1->add (*vbox1);
325         a1->set (0.5, 0.5, 0.0, 1.0);
326         a2->add (*vbox2);
327         a2->set (0.5, 0.5, 0.0, 1.0);
328
329         tbox->pack_start (*a1, false, false);
330         tbox->pack_start (*a2, false, false);
331
332         HBox* clock_box = manage (new HBox);
333
334         clock_box->pack_start (*primary_clock, false, false);
335         if (!ARDOUR::Profile->get_small_screen() && !ARDOUR::Profile->get_trx()) {
336                 clock_box->pack_start (*secondary_clock, false, false);
337         }
338         clock_box->set_spacing (PX_SCALE(3));
339
340         shuttle_box = manage (new ShuttleControl);
341         shuttle_box->show ();
342
343         VBox* transport_vbox = manage (new VBox);
344         transport_vbox->set_name ("TransportBase");
345         transport_vbox->set_border_width (0);
346         transport_vbox->set_spacing (PX_SCALE(3));
347         transport_vbox->pack_start (*tbox, true, true, 0);
348
349         if (!Profile->get_trx()) {
350                 transport_vbox->pack_start (*shuttle_box, false, false, 0);
351         }
352
353         time_info_box = manage (new TimeInfoBox);
354
355         transport_hbox.pack_start (*transport_vbox, false, true);
356
357         /* transport related toggle controls */
358
359         VBox* auto_box = manage (new VBox);
360         auto_box->set_homogeneous (true);
361         auto_box->set_spacing (PX_SCALE(2));
362         auto_box->pack_start (sync_button, true, true);
363         if (!ARDOUR::Profile->get_trx()) {
364                 auto_box->pack_start (follow_edits_button, true, true);
365                 auto_box->pack_start (auto_return_button, true, true);
366         }
367
368         if (!ARDOUR::Profile->get_trx()) {
369                 transport_hbox.pack_start (*auto_box, false, false);
370         }
371         transport_hbox.pack_start (*clock_box, true, true);
372
373         if (ARDOUR::Profile->get_trx()) {
374                 transport_hbox.pack_start (*auto_box, false, false);
375         }
376
377         if (!ARDOUR::Profile->get_trx()) {
378                 transport_hbox.pack_start (*time_info_box, false, false);
379         }
380
381         if (!ARDOUR::Profile->get_trx()) {
382                 transport_hbox.pack_start (alert_box, false, false);
383                 transport_hbox.pack_start (meter_box, false, false);
384                 transport_hbox.pack_start (editor_meter_peak_display, false, false);
385         }
386
387         /* desensitize */
388
389         set_transport_sensitivity (false);
390 }
391 #undef PX_SCALE
392
393 void
394 ARDOUR_UI::soloing_changed (bool onoff)
395 {
396         if (solo_alert_button.get_active() != onoff) {
397                 solo_alert_button.set_active (onoff);
398         }
399 }
400
401 void
402 ARDOUR_UI::_auditioning_changed (bool onoff)
403 {
404         auditioning_alert_button.set_active (onoff);
405         set_transport_sensitivity (!onoff);
406 }
407
408 void
409 ARDOUR_UI::auditioning_changed (bool onoff)
410 {
411         UI::instance()->call_slot (MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::_auditioning_changed, this, onoff));
412 }
413
414 bool
415 ARDOUR_UI::audition_alert_press (GdkEventButton*)
416 {
417         if (_session) {
418                 _session->cancel_audition();
419         }
420         return true;
421 }
422
423 bool
424 ARDOUR_UI::feedback_alert_press (GdkEventButton *)
425 {
426         return true;
427 }
428
429 bool
430 ARDOUR_UI::error_alert_press (GdkEventButton* ev)
431 {
432         bool do_toggle = true;
433         if (ev->button == 1) {
434                 if (_log_not_acknowledged == LogLevelError) {
435                         // just acknowledge the error, don't hide the log if it's already visible
436                         RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
437                         Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
438                         if (tact && tact->get_active()) {
439                                 do_toggle = false;
440                         }
441                 }
442                 _log_not_acknowledged = LogLevelNone;
443                 error_blink (false); // immediate acknowledge
444         }
445         // maybe fall through to to button toggle
446         return !do_toggle;
447 }
448
449 void
450 ARDOUR_UI::solo_blink (bool onoff)
451 {
452         if (_session == 0) {
453                 return;
454         }
455
456         if (_session->soloing() || _session->listening()) {
457                 if (onoff) {
458                         solo_alert_button.set_active (true);
459                 } else {
460                         solo_alert_button.set_active (false);
461                 }
462         } else {
463                 solo_alert_button.set_active (false);
464         }
465 }
466
467 void
468 ARDOUR_UI::sync_blink (bool onoff)
469 {
470         if (_session == 0 || !_session->config.get_external_sync()) {
471                 /* internal sync */
472                 sync_button.set_active (false);
473                 return;
474         }
475
476         if (!_session->transport_locked()) {
477                 /* not locked, so blink on and off according to the onoff argument */
478
479                 if (onoff) {
480                         sync_button.set_active (true);
481                 } else {
482                         sync_button.set_active (false);
483                 }
484         } else {
485                 /* locked */
486                 sync_button.set_active (true);
487         }
488 }
489
490 void
491 ARDOUR_UI::audition_blink (bool onoff)
492 {
493         if (_session == 0) {
494                 return;
495         }
496
497         if (_session->is_auditioning()) {
498                 if (onoff) {
499                         auditioning_alert_button.set_active (true);
500                 } else {
501                         auditioning_alert_button.set_active (false);
502                 }
503         } else {
504                 auditioning_alert_button.set_active (false);
505         }
506 }
507
508 void
509 ARDOUR_UI::feedback_blink (bool onoff)
510 {
511         if (_feedback_exists) {
512                 if (onoff) {
513                         feedback_alert_button.set_active (true);
514                 } else {
515                         feedback_alert_button.set_active (false);
516                 }
517         } else {
518                 feedback_alert_button.set_active (false);
519         }
520 }
521
522 void
523 ARDOUR_UI::error_blink (bool onoff)
524 {
525         switch (_log_not_acknowledged) {
526                 case LogLevelError:
527                         // blink
528                         if (onoff) {
529                                 error_alert_button.set_custom_led_color(0xff0000ff); // bright red
530                         } else {
531                                 error_alert_button.set_custom_led_color(0x880000ff); // dark red
532                         }
533                         break;
534                 case LogLevelWarning:
535                         error_alert_button.set_custom_led_color(0xccaa00ff); // yellow
536                         break;
537                 case LogLevelInfo:
538                         error_alert_button.set_custom_led_color(0x88cc00ff); // lime green
539                         break;
540                 default:
541                         error_alert_button.set_custom_led_color(0x333333ff); // gray
542                         break;
543         }
544 }
545
546
547
548 void
549 ARDOUR_UI::set_transport_sensitivity (bool yn)
550 {
551         ActionManager::set_sensitive (ActionManager::transport_sensitive_actions, yn);
552         shuttle_box->set_sensitive (yn);
553 }
554
555 void
556 ARDOUR_UI::editor_realized ()
557 {
558         boost::function<void (string)> pc (boost::bind (&ARDOUR_UI::parameter_changed, this, _1));
559         Config->map_parameters (pc);
560
561         UIConfiguration::instance().reset_dpi ();
562 }
563
564 void
565 ARDOUR_UI::maximise_editing_space ()
566 {
567         if (editor) {
568                 editor->maximise_editing_space ();
569         }
570 }
571
572 void
573 ARDOUR_UI::restore_editing_space ()
574 {
575         if (editor) {
576                 editor->restore_editing_space ();
577         }
578 }
579
580 void
581 ARDOUR_UI::show_ui_prefs ()
582 {
583         if (rc_option_editor) {
584                 show_tabbable (rc_option_editor);
585                 rc_option_editor->set_current_page (_("GUI"));
586         }
587 }
588
589 bool
590 ARDOUR_UI::click_button_clicked (GdkEventButton* ev)
591 {
592         if (ev->button != 3) {
593                 /* this handler is just for button-3 clicks */
594                 return false;
595         }
596
597         show_tabbable (rc_option_editor);
598         rc_option_editor->set_current_page (_("Misc"));
599         return true;
600 }
601
602 void
603 ARDOUR_UI::toggle_follow_edits ()
604 {
605         RefPtr<Action> act = ActionManager::get_action (X_("Transport"), X_("ToggleFollowEdits"));
606         assert (act);
607
608         RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic (act);
609         assert (tact);
610
611         UIConfiguration::instance().set_follow_edits (tact->get_active ());
612 }
613
614 void
615 ARDOUR_UI::update_title ()
616 {
617         if (_session) {
618                 bool dirty = _session->dirty();
619
620                 string session_name;
621
622                 if (_session->snap_name() != _session->name()) {
623                         session_name = _session->snap_name();
624                 } else {
625                         session_name = _session->name();
626                 }
627
628                 if (dirty) {
629                         session_name = "*" + session_name;
630                 }
631
632                 WindowTitle title (session_name);
633                 title += Glib::get_application_name();
634                 _main_window.set_title (title.get_string());
635         } else {
636                 WindowTitle title (Glib::get_application_name());
637                 _main_window.set_title (title.get_string());
638         }
639
640 }
641