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