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