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