use -r argument with JACK if realtime is not requested in engine dialog (also applied...
[ArdourMidi.git] / libs / gtkmm2ext / gtk_ui.cc
blob6474f1828d3201d11d707191406d19308a593abf
1 /*
2 Copyright (C) 1999-2005 Paul Barton-Davis
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.
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.
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.
18 $Id$
21 #include <cmath>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <unistd.h>
25 #include <cerrno>
26 #include <climits>
27 #include <cctype>
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>
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>
43 #include "i18n.h"
45 using namespace Gtkmm2ext;
46 using namespace Gtk;
47 using namespace Glib;
48 using namespace PBD;
49 using std::map;
51 UI *UI::theGtkUI = 0;
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();
60 #include "pbd/abstract_ui.cc" /* instantiate the template */
62 UI::UI (string namestr, int *argc, char ***argv)
63 : AbstractUI<UIRequest> (namestr)
65 theMain = new Main (argc, argv);
66 #ifndef GTK_NEW_TOOLTIP_API
67 tips = new Tooltips;
68 #endif
70 _active = false;
72 if (!theGtkUI) {
73 theGtkUI = this;
74 } else {
75 fatal << "duplicate UI requested" << endmsg;
76 /* NOTREACHED */
79 /* the GUI event loop runs in the main thread of the app,
80 which is assumed to have called this.
83 run_loop_thread = Thread::self();
85 /* store "this" as the UI-for-thread of this thread, same argument
86 as for previous line.
89 set_event_loop_for_thread (this);
91 /* attach our request source to the default main context */
93 request_channel.ios()->attach (MainContext::get_default());
95 errors = new TextViewer (800,600);
96 errors->text().set_editable (false);
97 errors->text().set_name ("ErrorText");
99 Glib::set_application_name(namestr);
101 WindowTitle title(Glib::get_application_name());
102 title += _("Log");
103 errors->set_title (title.get_string());
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);
109 //load_rcfile (rcfile);
112 UI::~UI ()
117 bool
118 UI::caller_is_ui_thread ()
120 return Thread::self() == run_loop_thread;
124 UI::load_rcfile (string path, bool themechange)
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...
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;
137 if (path.length() == 0) {
138 return -1;
141 if (access (path.c_str(), R_OK)) {
142 error << "UI: couldn't find rc file \""
143 << path
144 << '"'
145 << endmsg;
146 return -1;
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();
154 if (themechange) {
155 return 0; //Don't continue on every time there is a theme change
158 /* have to pack widgets into a toplevel window so that styles will stick */
160 Window temp_window (WINDOW_TOPLEVEL);
161 temp_window.ensure_style ();
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());
171 box.pack_start (fatal_widget);
172 box.pack_start (error_widget);
173 box.pack_start (warning_widget);
174 box.pack_start (info_widget);
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();
185 fatal_widget.set_name ("FatalMessage");
186 delete fatal_style;
188 /* This next line and the similar ones below are sketchily
189 * guessed to fix #2885. I think maybe that problems occur
190 * because with gtk_rc_get_style (to quote its docs) "no
191 * refcount is added to the returned style". So I've switched
192 * this to use Glib::wrap with take_copy == true, which requires
193 * all the nasty casts and calls to plain-old-C GTK.
195 * At worst I think this causes a memory leak; at least it appears
196 * to fix the bug.
198 * I could be wrong about any or all of the above.
200 fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
202 fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
203 fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
204 fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
205 fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
206 fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
207 fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
209 error_widget.set_name ("ErrorMessage");
210 delete error_style;
211 error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
213 error_ptag->property_font_desc().set_value((*error_style)->get_font());
214 error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
215 error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
216 error_mtag->property_font_desc().set_value((*error_style)->get_font());
217 error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
218 error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
220 warning_widget.set_name ("WarningMessage");
221 delete warning_style;
222 warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
224 warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
225 warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
226 warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
227 warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
228 warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
229 warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
231 info_widget.set_name ("InfoMessage");
232 delete info_style;
233 info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
235 info_ptag->property_font_desc().set_value((*info_style)->get_font());
236 info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
237 info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
238 info_mtag->property_font_desc().set_value((*info_style)->get_font());
239 info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
240 info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
242 return 0;
245 void
246 UI::run (Receiver &old_receiver)
248 listen_to (error);
249 listen_to (info);
250 listen_to (warning);
251 listen_to (fatal);
253 /* stop the old receiver (text/console) once we hit the first idle */
255 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
257 starting ();
258 _active = true;
259 theMain->run ();
260 _active = false;
261 stopping ();
262 hangup ();
263 return;
266 bool
267 UI::running ()
269 return _active;
272 void
273 UI::quit ()
275 UIRequest *req = get_request (Quit);
277 if (req == 0) {
278 return;
281 send_request (req);
284 static bool idle_quit ()
286 Main::quit ();
287 return true;
290 void
291 UI::do_quit ()
293 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
294 Main::quit ();
295 } else {
296 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
300 void
301 UI::touch_display (Touchable *display)
303 UIRequest *req = get_request (TouchDisplay);
305 if (req == 0) {
306 return;
309 req->display = display;
311 send_request (req);
314 void
315 UI::set_tip (Widget &w, const gchar *tip)
317 set_tip(&w, tip, "");
320 void
321 UI::set_tip (Widget &w, const std::string& tip)
323 set_tip(&w, tip.c_str(), "");
326 void
327 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
329 UIRequest *req = get_request (SetTip);
331 std::string msg(tip);
333 Glib::RefPtr<Gtk::Action> action = w->get_action();
334 if (action) {
335 Gtk::AccelKey key;
336 bool has_key = ActionManager::lookup_entry(action->get_accel_path(), key);
337 if (has_key && key.get_abbrev() != "") {
338 msg.append("\n\n Key: ").append(key.get_abbrev());
342 if (req == 0) {
343 return;
346 req->widget = w;
347 req->msg = msg.c_str();
348 req->msg2 = hlp;
350 send_request (req);
353 void
354 UI::set_state (Widget *w, StateType state)
356 UIRequest *req = get_request (StateChange);
358 if (req == 0) {
359 return;
362 req->new_state = state;
363 req->widget = w;
365 send_request (req);
368 void
369 UI::idle_add (int (*func)(void *), void *arg)
371 UIRequest *req = get_request (AddIdle);
373 if (req == 0) {
374 return;
377 req->function = func;
378 req->arg = arg;
380 send_request (req);
383 /* END abstract_ui interfaces */
385 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
386 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
387 * is called when that trackable is destroyed.
389 PBD::EventLoop::InvalidationRecord*
390 __invalidator (sigc::trackable& trackable, const char* file, int line)
392 PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
394 ir->file = file;
395 ir->line = line;
397 trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
399 return ir;
402 void
403 UI::do_request (UIRequest* req)
405 if (req->type == ErrorMessage) {
407 process_error_message (req->chn, req->msg);
408 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
409 req->msg = 0; /* don't free it again in the destructor */
411 } else if (req->type == Quit) {
413 do_quit ();
415 } else if (req->type == CallSlot) {
416 #ifndef NDEBUG
417 if (getenv ("DEBUG_THREADED_SIGNALS")) {
418 cerr << "call slot for " << name() << endl;
420 #endif
421 req->the_slot ();
423 } else if (req->type == TouchDisplay) {
425 req->display->touch ();
426 if (req->display->delete_after_touch()) {
427 delete req->display;
430 } else if (req->type == StateChange) {
432 req->widget->set_state (req->new_state);
434 } else if (req->type == SetTip) {
436 #ifdef GTK_NEW_TOOLTIP_API
437 /* even if the installed GTK is up to date,
438 at present (November 2008) our included
439 version of gtkmm is not. so use the GTK
440 API that we've verified has the right function.
442 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
443 #else
444 tips->set_tip (*req->widget, req->msg, "");
445 #endif
447 } else {
449 error << "GtkUI: unknown request type "
450 << (int) req->type
451 << endmsg;
455 /*======================================================================
456 Error Display
457 ======================================================================*/
459 void
460 UI::receive (Transmitter::Channel chn, const char *str)
462 if (caller_is_ui_thread()) {
463 process_error_message (chn, str);
464 } else {
465 UIRequest* req = get_request (ErrorMessage);
467 if (req == 0) {
468 return;
471 req->chn = chn;
472 req->msg = strdup (str);
474 send_request (req);
478 #define OLD_STYLE_ERRORS 1
480 void
481 UI::process_error_message (Transmitter::Channel chn, const char *str)
483 RefPtr<Style> style;
484 RefPtr<TextBuffer::Tag> ptag;
485 RefPtr<TextBuffer::Tag> mtag;
486 const char *prefix;
487 size_t prefix_len;
488 bool fatal_received = false;
489 #ifndef OLD_STYLE_ERRORS
490 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
491 #endif
493 switch (chn) {
494 case Transmitter::Fatal:
495 prefix = "[FATAL]: ";
496 ptag = fatal_ptag;
497 mtag = fatal_mtag;
498 prefix_len = 9;
499 fatal_received = true;
500 break;
501 case Transmitter::Error:
502 #if OLD_STYLE_ERRORS
503 prefix = "[ERROR]: ";
504 ptag = error_ptag;
505 mtag = error_mtag;
506 prefix_len = 9;
507 #else
508 popup->set_name ("ErrorMessage");
509 popup->set_text (str);
510 popup->touch ();
511 return;
512 #endif
513 break;
514 case Transmitter::Info:
515 #if OLD_STYLE_ERRORS
516 prefix = "[INFO]: ";
517 ptag = info_ptag;
518 mtag = info_mtag;
519 prefix_len = 8;
520 #else
521 popup->set_name ("InfoMessage");
522 popup->set_text (str);
523 popup->touch ();
524 return;
525 #endif
527 break;
528 case Transmitter::Warning:
529 #if OLD_STYLE_ERRORS
530 prefix = "[WARNING]: ";
531 ptag = warning_ptag;
532 mtag = warning_mtag;
533 prefix_len = 11;
534 #else
535 popup->set_name ("WarningMessage");
536 popup->set_text (str);
537 popup->touch ();
538 return;
539 #endif
540 break;
541 default:
542 /* no choice but to use text/console output here */
543 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
544 ::exit (1);
547 errors->text().get_buffer()->begin_user_action();
549 if (fatal_received) {
550 handle_fatal (str);
551 } else {
553 display_message (prefix, prefix_len, ptag, mtag, str);
555 if (!errors->is_visible() && chn != Transmitter::Info) {
556 toggle_errors();
560 errors->text().get_buffer()->end_user_action();
563 void
564 UI::toggle_errors ()
566 if (!errors->is_visible()) {
567 errors->set_position (WIN_POS_MOUSE);
568 errors->show ();
569 } else {
570 errors->hide ();
574 void
575 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
577 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
579 buffer->insert_with_tag(buffer->end(), prefix, ptag);
580 buffer->insert_with_tag(buffer->end(), msg, mtag);
581 buffer->insert_with_tag(buffer->end(), "\n", mtag);
583 errors->scroll_to_bottom ();
586 void
587 UI::handle_fatal (const char *message)
589 Dialog win;
590 Label label (message);
591 Button quit (_("Press To Exit"));
592 HBox hpacker;
594 win.set_default_size (400, 100);
596 WindowTitle title(Glib::get_application_name());
597 title += ": Fatal Error";
598 win.set_title (title.get_string());
600 win.set_position (WIN_POS_MOUSE);
601 win.set_border_width (12);
603 win.get_vbox()->pack_start (label, true, true);
604 hpacker.pack_start (quit, true, false);
605 win.get_vbox()->pack_start (hpacker, false, false);
607 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
609 win.show_all ();
610 win.set_modal (true);
612 theMain->run ();
614 _exit (1);
617 void
618 UI::popup_error (const string& text)
620 PopUp *pup;
622 if (!caller_is_ui_thread()) {
623 error << "non-UI threads can't use UI::popup_error"
624 << endmsg;
625 return;
628 pup = new PopUp (WIN_POS_MOUSE, 0, true);
629 pup->set_text (text);
630 pup->touch ();
633 #ifdef GTKOSX
634 extern "C" {
635 int gdk_quartz_in_carbon_menu_event_handler ();
637 #endif
639 void
640 UI::flush_pending ()
642 #ifdef GTKOSX
643 /* as of february 11th 2008, gtk/osx has a problem in that mac menu events
644 are handled using Carbon with an "internal" event handling system that
645 doesn't pass things back to the glib/gtk main loop. this makes
646 gtk_main_iteration() block if we call it while in a menu event handler
647 because glib gets confused and thinks there are two threads running
648 g_main_poll_func().
650 this hack (relies on code in gtk2_ardour/sync-menu.c) works
651 around that.
654 if (gdk_quartz_in_carbon_menu_event_handler()) {
655 return;
657 #endif
658 if (!caller_is_ui_thread()) {
659 error << "non-UI threads cannot call UI::flush_pending()"
660 << endmsg;
661 return;
664 gtk_main_iteration();
666 while (gtk_events_pending()) {
667 gtk_main_iteration();
671 bool
672 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
674 win->hide ();
675 return true;
678 Gdk::Color
679 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
681 Gdk::Color color;
683 ColorSelectionDialog color_dialog (prompt);
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));
690 if (initial) {
691 color_dialog.get_colorsel()->set_current_color (*initial);
694 color_dialog.show_all ();
695 color_picked = false;
696 picked = false;
698 Main::run();
700 color_dialog.hide_all ();
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());
708 picked = true;
711 return color;
714 void
715 UI::color_selection_done (bool status)
717 color_picked = status;
718 Main::quit ();
721 bool
722 UI::color_selection_deleted (GdkEventAny */*ev*/)
724 Main::quit ();
725 return true;