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