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