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