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