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