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