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