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