Make meters with different colour parameters actually work.
[ardour.git] / libs / gtkmm2ext / gtk_ui.cc
1 /*
2     Copyright (C) 1999-2005 Paul Barton-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     $Id$
19 */
20
21 #include <cmath>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <unistd.h>
25 #include <cerrno>
26 #include <climits>
27 #include <cctype>
28
29 #include <gtkmm.h>
30 #include <pbd/error.h>
31 #include <pbd/touchable.h>
32 #include <pbd/failed_constructor.h>
33 #include <pbd/pthread_utils.h>
34 #include <pbd/stacktrace.h>
35
36 #include <gtkmm2ext/application.h>
37 #include <gtkmm2ext/gtk_ui.h>
38 #include <gtkmm2ext/textviewer.h>
39 #include <gtkmm2ext/popup.h>
40 #include <gtkmm2ext/utils.h>
41 #include <gtkmm2ext/window_title.h>
42 #include <gtkmm2ext/actions.h>
43
44 #include "i18n.h"
45
46 using namespace Gtkmm2ext;
47 using namespace Gtk;
48 using namespace Glib;
49 using namespace PBD;
50 using std::map;
51
52 UI       *UI::theGtkUI = 0;
53
54 BaseUI::RequestType Gtkmm2ext::NullMessage = BaseUI::new_request_type();
55 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
56 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
57 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
58 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
59 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
60 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
61
62 #include "pbd/abstract_ui.cc"  /* instantiate the template */
63
64 UI::UI (string namestr, int *argc, char ***argv)
65         : AbstractUI<UIRequest> (namestr)
66 {
67         theMain = new Main (argc, argv);
68 #ifndef GTK_NEW_TOOLTIP_API
69         tips = new Tooltips;
70 #endif
71
72         _active = false;
73
74         if (!theGtkUI) {
75                 theGtkUI = this;
76         } else {
77                 fatal << "duplicate UI requested" << endmsg;
78                 /* NOTREACHED */
79         }
80
81         /* the GUI event loop runs in the main thread of the app,
82            which is assumed to have called this.
83         */
84
85         run_loop_thread = Thread::self();
86         
87         /* store "this" as the UI-for-thread of this thread, same argument
88            as for previous line.
89         */
90
91         set_event_loop_for_thread (this);
92
93         /* attach our request source to the default main context */
94
95         request_channel.ios()->attach (MainContext::get_default());
96
97         errors = new TextViewer (800,600);
98         errors->text().set_editable (false);
99         errors->text().set_name ("ErrorText");
100         errors->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Editor/toggle-log-window")));
101
102         Glib::set_application_name(namestr);
103
104         WindowTitle title(Glib::get_application_name());
105         title += _("Log");
106         errors->set_title (title.get_string());
107
108         errors->dismiss_button().set_name ("ErrorLogCloseButton");
109         errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
110         errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
111
112         //load_rcfile (rcfile);
113
114         /* instantiate the Application singleton */
115
116         Application::instance();
117 }
118
119 UI::~UI ()
120 {
121 }
122
123
124 bool
125 UI::caller_is_ui_thread ()
126 {
127         return Thread::self() == run_loop_thread;
128 }
129
130 int
131 UI::load_rcfile (string path, bool themechange)
132 {
133         /* Yes, pointers to Glib::RefPtr.  If these are not kept around,
134          * a segfault somewhere deep in the wonderfully robust glib will result.
135          * This does not occur if wiget.get_style is used instead of rc.get_style below,
136          * except that doesn't actually work... 
137          */
138         
139         static Glib::RefPtr<Style>* fatal_style   = 0;
140         static Glib::RefPtr<Style>* error_style   = 0;
141         static Glib::RefPtr<Style>* warning_style = 0;
142         static Glib::RefPtr<Style>* info_style    = 0;
143
144         if (path.length() == 0) {
145                 return -1;
146         }
147
148         if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
149                 error << "UI: couldn't find rc file \""
150                       << path
151                       << '"'
152                       << endmsg;
153                 return -1;
154         }
155
156         RC rc (path.c_str());
157         //this is buggy in gtkmm for some reason, so use C
158         //RC::reset_styles (Gtk::Settings::get_default());
159         gtk_rc_reset_styles (gtk_settings_get_default());
160
161         theme_changed.emit();
162
163         if (themechange) {
164                 return 0; //Don't continue on every time there is a theme change
165         }
166
167         /* have to pack widgets into a toplevel window so that styles will stick */
168
169         Window temp_window (WINDOW_TOPLEVEL);
170         temp_window.ensure_style ();
171
172         HBox box;
173         Label fatal_widget;
174         Label error_widget;
175         Label warning_widget;
176         Label info_widget;
177         RefPtr<Gtk::Style> style;
178         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
179
180         box.pack_start (fatal_widget);
181         box.pack_start (error_widget);
182         box.pack_start (warning_widget);
183         box.pack_start (info_widget);
184
185         error_ptag = buffer->create_tag();
186         error_mtag = buffer->create_tag();
187         fatal_ptag = buffer->create_tag();
188         fatal_mtag = buffer->create_tag();
189         warning_ptag = buffer->create_tag();
190         warning_mtag = buffer->create_tag();
191         info_ptag = buffer->create_tag();
192         info_mtag = buffer->create_tag();
193
194         fatal_widget.set_name ("FatalMessage");
195         delete fatal_style;
196
197         /* This next line and the similar ones below are sketchily
198          * guessed to fix #2885.  I think maybe that problems occur
199          * because with gtk_rc_get_style (to quote its docs) "no
200          * refcount is added to the returned style".  So I've switched
201          * this to use Glib::wrap with take_copy == true, which requires
202          * all the nasty casts and calls to plain-old-C GTK.
203          *
204          * At worst I think this causes a memory leak; at least it appears
205          * to fix the bug.
206          *
207          * I could be wrong about any or all of the above.
208          */
209         fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
210
211         fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
212         fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
213         fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
214         fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
215         fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
216         fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
217
218         error_widget.set_name ("ErrorMessage");
219         delete error_style;
220         error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
221
222         error_ptag->property_font_desc().set_value((*error_style)->get_font());
223         error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
224         error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
225         error_mtag->property_font_desc().set_value((*error_style)->get_font());
226         error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
227         error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
228
229         warning_widget.set_name ("WarningMessage");
230         delete warning_style;
231         warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
232
233         warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
234         warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
235         warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
236         warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
237         warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
238         warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
239
240         info_widget.set_name ("InfoMessage");
241         delete info_style;
242         info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
243
244         info_ptag->property_font_desc().set_value((*info_style)->get_font());
245         info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
246         info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
247         info_mtag->property_font_desc().set_value((*info_style)->get_font());
248         info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
249         info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
250
251         return 0;
252 }
253
254 void
255 UI::run (Receiver &old_receiver)
256 {
257         listen_to (error);
258         listen_to (info);
259         listen_to (warning);
260         listen_to (fatal);
261
262         /* stop the old receiver (text/console) once we hit the first idle */
263
264         Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
265
266         starting ();
267         _active = true;
268         theMain->run ();
269         _active = false;
270         stopping ();
271         hangup ();
272         return;
273 }
274
275 bool
276 UI::running ()
277 {
278         return _active;
279 }
280
281 void
282 UI::quit ()
283 {
284         UIRequest *req = get_request (Quit);
285
286         if (req == 0) {
287                 return;
288         }
289
290         send_request (req);
291 }
292
293 static bool idle_quit ()
294 {
295         Main::quit ();
296         return true;
297 }
298
299 void
300 UI::do_quit ()
301 {
302         if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
303                 Main::quit ();
304         } else {
305                 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
306         }
307 }
308
309 void
310 UI::touch_display (Touchable *display)
311 {
312         UIRequest *req = get_request (TouchDisplay);
313
314         if (req == 0) {
315                 return;
316         }
317
318         req->display = display;
319
320         send_request (req);
321 }
322
323 void
324 UI::set_tip (Widget &w, const gchar *tip)
325 {
326         set_tip(&w, tip, "");
327 }
328
329 void
330 UI::set_tip (Widget &w, const std::string& tip)
331 {
332         set_tip(&w, tip.c_str(), "");
333 }
334
335 void
336 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
337 {
338         UIRequest *req = get_request (SetTip);
339
340         std::string msg(tip);
341
342         Glib::RefPtr<Gtk::Action> action = w->get_action();
343         if (action) {
344                 Gtk::AccelKey key;
345                 ustring ap = action->get_accel_path();
346                 if (!ap.empty()) {
347                         bool has_key = ActionManager::lookup_entry(ap, key);
348                         if (has_key && key.get_abbrev() != "") {
349                                 msg.append("\n\n Key: ").append(key.get_abbrev());
350                         }
351                 }
352         }
353
354         if (req == 0) {
355                 return;
356         }
357
358         req->widget = w;
359         req->msg = msg.c_str();
360         req->msg2 = hlp;
361
362         send_request (req);
363 }
364
365 void
366 UI::set_state (Widget *w, StateType state)
367 {
368         UIRequest *req = get_request (StateChange);
369
370         if (req == 0) {
371                 return;
372         }
373
374         req->new_state = state;
375         req->widget = w;
376
377         send_request (req);
378 }
379
380 void
381 UI::idle_add (int (*func)(void *), void *arg)
382 {
383         UIRequest *req = get_request (AddIdle);
384
385         if (req == 0) {
386                 return;
387         }
388
389         req->function = func;
390         req->arg = arg;
391
392         send_request (req);
393 }
394
395 /* END abstract_ui interfaces */
396
397 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
398  *  to a given sigc::trackable so that PBD::EventLoop::invalidate_request
399  *  is called when that trackable is destroyed.
400  */
401 PBD::EventLoop::InvalidationRecord*
402 __invalidator (sigc::trackable& trackable, const char* file, int line)
403 {
404         PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
405
406         ir->file = file;
407         ir->line = line;
408
409         trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
410
411         return ir;
412 }
413
414 void
415 UI::do_request (UIRequest* req)
416 {
417         if (req->type == ErrorMessage) {
418
419                 process_error_message (req->chn, req->msg);
420                 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
421                 req->msg = 0; /* don't free it again in the destructor */
422
423         } else if (req->type == Quit) {
424
425                 do_quit ();
426
427         } else if (req->type == CallSlot) {
428 #ifndef NDEBUG
429                 if (getenv ("DEBUG_THREADED_SIGNALS")) {
430                         cerr << "call slot for " << name() << endl;
431                 }
432 #endif
433                 req->the_slot ();
434
435         } else if (req->type == TouchDisplay) {
436
437                 req->display->touch ();
438                 if (req->display->delete_after_touch()) {
439                         delete req->display;
440                 }
441
442         } else if (req->type == StateChange) {
443
444                 req->widget->set_state (req->new_state);
445
446         } else if (req->type == SetTip) {
447
448 #ifdef GTK_NEW_TOOLTIP_API
449                 /* even if the installed GTK is up to date,
450                    at present (November 2008) our included
451                    version of gtkmm is not. so use the GTK
452                    API that we've verified has the right function.
453                 */
454                 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
455 #else
456                 tips->set_tip (*req->widget, req->msg, "");
457 #endif
458
459         } else {
460
461                 error << "GtkUI: unknown request type "
462                       << (int) req->type
463                       << endmsg;
464         }
465 }
466
467 /*======================================================================
468   Error Display
469   ======================================================================*/
470
471 void
472 UI::receive (Transmitter::Channel chn, const char *str)
473 {
474         if (caller_is_ui_thread()) {
475                 process_error_message (chn, str);
476         } else {
477                 UIRequest* req = get_request (ErrorMessage);
478
479                 if (req == 0) {
480                         return;
481                 }
482
483                 req->chn = chn;
484                 req->msg = strdup (str);
485
486                 send_request (req);
487         }
488 }
489
490 #define OLD_STYLE_ERRORS 1
491
492 void
493 UI::process_error_message (Transmitter::Channel chn, const char *str)
494 {
495         RefPtr<Style> style;
496         RefPtr<TextBuffer::Tag> ptag;
497         RefPtr<TextBuffer::Tag> mtag;
498         const char *prefix;
499         size_t prefix_len;
500         bool fatal_received = false;
501 #ifndef OLD_STYLE_ERRORS
502         PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
503 #endif
504
505         switch (chn) {
506         case Transmitter::Fatal:
507                 prefix = "[FATAL]: ";
508                 ptag = fatal_ptag;
509                 mtag = fatal_mtag;
510                 prefix_len = 9;
511                 fatal_received = true;
512                 break;
513         case Transmitter::Error:
514 #if OLD_STYLE_ERRORS
515                 prefix = "[ERROR]: ";
516                 ptag = error_ptag;
517                 mtag = error_mtag;
518                 prefix_len = 9;
519 #else
520                 popup->set_name ("ErrorMessage");
521                 popup->set_text (str);
522                 popup->touch ();
523                 return;
524 #endif
525                 break;
526         case Transmitter::Info:
527 #if OLD_STYLE_ERRORS
528                 prefix = "[INFO]: ";
529                 ptag = info_ptag;
530                 mtag = info_mtag;
531                 prefix_len = 8;
532 #else
533                 popup->set_name ("InfoMessage");
534                 popup->set_text (str);
535                 popup->touch ();
536                 return;
537 #endif
538
539                 break;
540         case Transmitter::Warning:
541 #if OLD_STYLE_ERRORS
542                 prefix = "[WARNING]: ";
543                 ptag = warning_ptag;
544                 mtag = warning_mtag;
545                 prefix_len = 11;
546 #else
547                 popup->set_name ("WarningMessage");
548                 popup->set_text (str);
549                 popup->touch ();
550                 return;
551 #endif
552                 break;
553         default:
554                 /* no choice but to use text/console output here */
555                 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
556                 ::exit (1);
557         }
558
559         errors->text().get_buffer()->begin_user_action();
560
561         if (fatal_received) {
562                 handle_fatal (str);
563         } else {
564
565                 display_message (prefix, prefix_len, ptag, mtag, str);
566
567                 if (!errors->is_visible() && chn != Transmitter::Info) {
568                         show_errors ();
569                 }
570         }
571
572         errors->text().get_buffer()->end_user_action();
573 }
574
575 void
576 UI::show_errors ()
577 {
578         Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
579         if (!act) {
580                 return;
581         }
582
583         Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
584         if (tact) {
585                 tact->set_active ();
586         }
587 }
588
589 void
590 UI::toggle_errors ()
591 {
592         Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
593         if (!act) {
594                 return;
595         }
596
597         Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
598         
599         if (tact->get_active()) {
600                 errors->set_position (WIN_POS_MOUSE);
601                 errors->show ();
602         } else {
603                 errors->hide ();
604         }
605 }
606
607 void
608 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
609 {
610         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
611
612         buffer->insert_with_tag(buffer->end(), prefix, ptag);
613         buffer->insert_with_tag(buffer->end(), msg, mtag);
614         buffer->insert_with_tag(buffer->end(), "\n", mtag);
615
616         errors->scroll_to_bottom ();
617 }
618
619 void
620 UI::handle_fatal (const char *message)
621 {
622         Dialog win;
623         Label label (message);
624         Button quit (_("Press To Exit"));
625         HBox hpacker;
626
627         win.set_default_size (400, 100);
628
629         WindowTitle title(Glib::get_application_name());
630         title += ": Fatal Error";
631         win.set_title (title.get_string());
632
633         win.set_position (WIN_POS_MOUSE);
634         win.set_border_width (12);
635
636         win.get_vbox()->pack_start (label, true, true);
637         hpacker.pack_start (quit, true, false);
638         win.get_vbox()->pack_start (hpacker, false, false);
639
640         quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
641
642         win.show_all ();
643         win.set_modal (true);
644
645         theMain->run ();
646
647         _exit (1);
648 }
649
650 void
651 UI::popup_error (const string& text)
652 {
653         if (!caller_is_ui_thread()) {
654                 error << "non-UI threads can't use UI::popup_error"
655                       << endmsg;
656                 return;
657         }
658
659         MessageDialog msg (text);
660         msg.set_title (string_compose (_("I'm sorry %1, I can't do that"), g_get_user_name()));
661         msg.set_wmclass (X_("error"), name());
662         msg.set_position (WIN_POS_MOUSE);
663         msg.run ();
664 }
665
666 void
667 UI::flush_pending ()
668 {
669         if (!caller_is_ui_thread()) {
670                 error << "non-UI threads cannot call UI::flush_pending()"
671                       << endmsg;
672                 return;
673         }
674
675         gtk_main_iteration();
676
677         while (gtk_events_pending()) {
678                 gtk_main_iteration();
679         }
680 }
681
682 bool
683 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
684 {
685         win->hide ();
686         return true;
687 }
688
689 Gdk::Color
690 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
691 {
692         Gdk::Color color;
693
694         ColorSelectionDialog color_dialog (prompt);
695
696         color_dialog.set_modal (true);
697         color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
698         color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
699         color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
700
701         if (initial) {
702                 color_dialog.get_colorsel()->set_current_color (*initial);
703         }
704
705         color_dialog.show_all ();
706         color_picked = false;
707         picked = false;
708
709         Main::run();
710
711         color_dialog.hide_all ();
712
713         if (color_picked) {
714                 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
715                 color.set_red(f_rgba.get_red());
716                 color.set_green(f_rgba.get_green());
717                 color.set_blue(f_rgba.get_blue());
718
719                 picked = true;
720         }
721
722         return color;
723 }
724
725 void
726 UI::color_selection_done (bool status)
727 {
728         color_picked = status;
729         Main::quit ();
730 }
731
732 bool
733 UI::color_selection_deleted (GdkEventAny */*ev*/)
734 {
735         Main::quit ();
736         return true;
737 }