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