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.
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>
45 using namespace Gtkmm2ext;
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
75 fatal << "duplicate UI requested" << endmsg;
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
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());
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);
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) {
141 if (access (path.c_str(), R_OK)) {
142 error << "UI: couldn't find rc file \""
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();
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 ();
166 Label warning_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");
187 fatal_style = new Glib::RefPtr<Style>(rc.get_style(fatal_widget));
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));
196 error_widget.set_name ("ErrorMessage");
198 error_style = new Glib::RefPtr<Style>(rc.get_style(error_widget));
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));
207 warning_widget.set_name ("WarningMessage");
208 delete warning_style;
209 warning_style = new Glib::RefPtr<Style>(rc.get_style(warning_widget));
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));
218 info_widget.set_name ("InfoMessage");
220 info_style = new Glib::RefPtr<Style>(rc.get_style(info_widget));
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));
233 UI::run (Receiver &old_receiver)
240 /* stop the old receiver (text/console) once we hit the first idle */
242 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
262 UIRequest *req = get_request (Quit);
271 static bool idle_quit ()
280 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
283 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
288 UI::touch_display (Touchable *display)
290 UIRequest *req = get_request (TouchDisplay);
296 req->display = display;
302 UI::set_tip (Widget &w, const gchar *tip)
304 set_tip(&w, tip, "");
308 UI::set_tip (Widget &w, const std::string& tip)
310 set_tip(&w, tip.c_str(), "");
314 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
316 UIRequest *req = get_request (SetTip);
318 std::string msg(tip);
320 Glib::RefPtr<Gtk::Action> action = w->get_action();
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());
334 req->msg = msg.c_str();
341 UI::set_state (Widget *w, StateType state)
343 UIRequest *req = get_request (StateChange);
349 req->new_state = state;
356 UI::idle_add (int (*func)(void *), void *arg)
358 UIRequest *req = get_request (AddIdle);
364 req->function = func;
370 /* END abstract_ui interfaces */
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.
376 PBD::EventLoop::InvalidationRecord*
377 __invalidator (sigc::trackable& trackable, const char* file, int line)
379 PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
384 trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
390 UI::do_request (UIRequest* req)
392 if (req->type == ErrorMessage) {
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 */
398 } else if (req->type == Quit) {
402 } else if (req->type == CallSlot) {
404 if (getenv ("DEBUG_THREADED_SIGNALS")) {
405 cerr << "call slot for " << name() << endl;
410 } else if (req->type == TouchDisplay) {
412 req->display->touch ();
413 if (req->display->delete_after_touch()) {
417 } else if (req->type == StateChange) {
419 req->widget->set_state (req->new_state);
421 } else if (req->type == SetTip) {
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.
429 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
431 tips->set_tip (*req->widget, req->msg, "");
436 error << "GtkUI: unknown request type "
442 /*======================================================================
444 ======================================================================*/
447 UI::receive (Transmitter::Channel chn, const char *str)
449 if (caller_is_ui_thread()) {
450 process_error_message (chn, str);
452 UIRequest* req = get_request (ErrorMessage);
459 req->msg = strdup (str);
465 #define OLD_STYLE_ERRORS 1
468 UI::process_error_message (Transmitter::Channel chn, const char *str)
471 RefPtr<TextBuffer::Tag> ptag;
472 RefPtr<TextBuffer::Tag> mtag;
475 bool fatal_received = false;
476 #ifndef OLD_STYLE_ERRORS
477 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
481 case Transmitter::Fatal:
482 prefix = "[FATAL]: ";
486 fatal_received = true;
488 case Transmitter::Error:
490 prefix = "[ERROR]: ";
495 popup->set_name ("ErrorMessage");
496 popup->set_text (str);
501 case Transmitter::Info:
508 popup->set_name ("InfoMessage");
509 popup->set_text (str);
515 case Transmitter::Warning:
517 prefix = "[WARNING]: ";
522 popup->set_name ("WarningMessage");
523 popup->set_text (str);
529 /* no choice but to use text/console output here */
530 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
534 errors->text().get_buffer()->begin_user_action();
536 if (fatal_received) {
540 display_message (prefix, prefix_len, ptag, mtag, str);
542 if (!errors->is_visible() && chn != Transmitter::Info) {
547 errors->text().get_buffer()->end_user_action();
553 if (!errors->is_visible()) {
554 errors->set_position (WIN_POS_MOUSE);
562 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
564 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
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);
570 errors->scroll_to_bottom ();
574 UI::handle_fatal (const char *message)
577 Label label (message);
578 Button quit (_("Press To Exit"));
581 win.set_default_size (400, 100);
583 WindowTitle title(Glib::get_application_name());
584 title += ": Fatal Error";
585 win.set_title (title.get_string());
587 win.set_position (WIN_POS_MOUSE);
588 win.set_border_width (12);
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);
594 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
597 win.set_modal (true);
605 UI::popup_error (const string& text)
609 if (!caller_is_ui_thread()) {
610 error << "non-UI threads can't use UI::popup_error"
615 pup = new PopUp (WIN_POS_MOUSE, 0, true);
616 pup->set_text (text);
622 int gdk_quartz_in_carbon_menu_event_handler ();
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
637 this hack (relies on code in gtk2_ardour/sync-menu.c) works
641 if (gdk_quartz_in_carbon_menu_event_handler()) {
645 if (!caller_is_ui_thread()) {
646 error << "non-UI threads cannot call UI::flush_pending()"
651 gtk_main_iteration();
653 while (gtk_events_pending()) {
654 gtk_main_iteration();
659 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
666 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
670 ColorSelectionDialog color_dialog (prompt);
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));
678 color_dialog.get_colorsel()->set_current_color (*initial);
681 color_dialog.show_all ();
682 color_picked = false;
687 color_dialog.hide_all ();
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());
702 UI::color_selection_done (bool status)
704 color_picked = status;
709 UI::color_selection_deleted (GdkEventAny */*ev*/)