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/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>
47 using namespace Gtkmm2ext;
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();
63 #include "pbd/abstract_ui.cc" /* instantiate the template */
65 UI::UI (string namestr, int *argc, char ***argv)
66 : AbstractUI<UIRequest> (namestr)
68 theMain = new Main (argc, argv);
69 #ifndef GTK_NEW_TOOLTIP_API
78 fatal << "duplicate UI requested" << endmsg;
82 /* the GUI event loop runs in the main thread of the app,
83 which is assumed to have called this.
86 run_loop_thread = Thread::self();
88 /* store "this" as the UI-for-thread of this thread, same argument
92 set_event_loop_for_thread (this);
94 /* attach our request source to the default main context */
96 request_channel.ios()->attach (MainContext::get_default());
98 errors = new TextViewer (800,600);
99 errors->text().set_editable (false);
100 errors->text().set_name ("ErrorText");
101 errors->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Editor/toggle-log-window")));
103 Glib::set_application_name(namestr);
105 WindowTitle title(Glib::get_application_name());
107 errors->set_title (title.get_string());
109 errors->dismiss_button().set_name ("ErrorLogCloseButton");
110 errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
111 errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
113 //load_rcfile (rcfile);
115 /* instantiate the Application singleton */
117 Application::instance();
126 UI::caller_is_ui_thread ()
128 return Thread::self() == run_loop_thread;
132 UI::load_rcfile (string path, bool themechange)
134 /* Yes, pointers to Glib::RefPtr. If these are not kept around,
135 * a segfault somewhere deep in the wonderfully robust glib will result.
136 * This does not occur if wiget.get_style is used instead of rc.get_style below,
137 * except that doesn't actually work...
140 static Glib::RefPtr<Style>* fatal_style = 0;
141 static Glib::RefPtr<Style>* error_style = 0;
142 static Glib::RefPtr<Style>* warning_style = 0;
143 static Glib::RefPtr<Style>* info_style = 0;
145 if (path.length() == 0) {
149 if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
150 error << "UI: couldn't find rc file \""
157 RC rc (path.c_str());
158 //this is buggy in gtkmm for some reason, so use C
159 //RC::reset_styles (Gtk::Settings::get_default());
160 gtk_rc_reset_styles (gtk_settings_get_default());
162 theme_changed.emit();
165 return 0; //Don't continue on every time there is a theme change
168 /* have to pack widgets into a toplevel window so that styles will stick */
170 Window temp_window (WINDOW_TOPLEVEL);
171 temp_window.ensure_style ();
176 Label warning_widget;
178 RefPtr<Gtk::Style> style;
179 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
181 box.pack_start (fatal_widget);
182 box.pack_start (error_widget);
183 box.pack_start (warning_widget);
184 box.pack_start (info_widget);
186 error_ptag = buffer->create_tag();
187 error_mtag = buffer->create_tag();
188 fatal_ptag = buffer->create_tag();
189 fatal_mtag = buffer->create_tag();
190 warning_ptag = buffer->create_tag();
191 warning_mtag = buffer->create_tag();
192 info_ptag = buffer->create_tag();
193 info_mtag = buffer->create_tag();
195 fatal_widget.set_name ("FatalMessage");
198 /* This next line and the similar ones below are sketchily
199 * guessed to fix #2885. I think maybe that problems occur
200 * because with gtk_rc_get_style (to quote its docs) "no
201 * refcount is added to the returned style". So I've switched
202 * this to use Glib::wrap with take_copy == true, which requires
203 * all the nasty casts and calls to plain-old-C GTK.
205 * At worst I think this causes a memory leak; at least it appears
208 * I could be wrong about any or all of the above.
210 fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
212 fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
213 fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
214 fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
215 fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
216 fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
217 fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
219 error_widget.set_name ("ErrorMessage");
221 error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
223 error_ptag->property_font_desc().set_value((*error_style)->get_font());
224 error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
225 error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
226 error_mtag->property_font_desc().set_value((*error_style)->get_font());
227 error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
228 error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
230 warning_widget.set_name ("WarningMessage");
231 delete warning_style;
232 warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
234 warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
235 warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
236 warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
237 warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
238 warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
239 warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
241 info_widget.set_name ("InfoMessage");
243 info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
245 info_ptag->property_font_desc().set_value((*info_style)->get_font());
246 info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
247 info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
248 info_mtag->property_font_desc().set_value((*info_style)->get_font());
249 info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
250 info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
256 UI::run (Receiver &old_receiver)
263 /* stop the old receiver (text/console) once we hit the first idle */
265 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
285 UIRequest *req = get_request (Quit);
294 static bool idle_quit ()
303 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
306 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
311 UI::touch_display (Touchable *display)
313 UIRequest *req = get_request (TouchDisplay);
319 req->display = display;
325 UI::set_tip (Widget &w, const gchar *tip)
327 set_tip(&w, tip, "");
331 UI::set_tip (Widget &w, const std::string& tip)
333 set_tip(&w, tip.c_str(), "");
337 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
339 UIRequest *req = get_request (SetTip);
341 std::string msg(tip);
343 Glib::RefPtr<Gtk::Action> action = w->get_action();
346 Gtkmm2ext::Activatable* activatable;
347 if ((activatable = dynamic_cast<Gtkmm2ext::Activatable*>(w))) {
348 action = activatable->get_related_action();
354 ustring ap = action->get_accel_path();
356 bool has_key = ActionManager::lookup_entry(ap, key);
357 if (has_key && key.get_abbrev() != "") {
358 msg.append("\n\nKey: ").append(key.get_abbrev());
368 req->msg = msg.c_str();
375 UI::set_state (Widget *w, StateType state)
377 UIRequest *req = get_request (StateChange);
383 req->new_state = state;
390 UI::idle_add (int (*func)(void *), void *arg)
392 UIRequest *req = get_request (AddIdle);
398 req->function = func;
404 /* END abstract_ui interfaces */
406 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
407 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
408 * is called when that trackable is destroyed.
410 PBD::EventLoop::InvalidationRecord*
411 __invalidator (sigc::trackable& trackable, const char* file, int line)
413 PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
418 trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
424 UI::do_request (UIRequest* req)
426 if (req->type == ErrorMessage) {
428 process_error_message (req->chn, req->msg);
429 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
430 req->msg = 0; /* don't free it again in the destructor */
432 } else if (req->type == Quit) {
436 } else if (req->type == CallSlot) {
438 if (getenv ("DEBUG_THREADED_SIGNALS")) {
439 cerr << "call slot for " << name() << endl;
444 } else if (req->type == TouchDisplay) {
446 req->display->touch ();
447 if (req->display->delete_after_touch()) {
451 } else if (req->type == StateChange) {
453 req->widget->set_state (req->new_state);
455 } else if (req->type == SetTip) {
457 #ifdef GTK_NEW_TOOLTIP_API
458 /* even if the installed GTK is up to date,
459 at present (November 2008) our included
460 version of gtkmm is not. so use the GTK
461 API that we've verified has the right function.
463 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
465 tips->set_tip (*req->widget, req->msg, "");
470 error << "GtkUI: unknown request type "
476 /*======================================================================
478 ======================================================================*/
481 UI::receive (Transmitter::Channel chn, const char *str)
483 if (caller_is_ui_thread()) {
484 process_error_message (chn, str);
486 UIRequest* req = get_request (ErrorMessage);
493 req->msg = strdup (str);
499 #define OLD_STYLE_ERRORS 1
502 UI::process_error_message (Transmitter::Channel chn, const char *str)
505 RefPtr<TextBuffer::Tag> ptag;
506 RefPtr<TextBuffer::Tag> mtag;
509 bool fatal_received = false;
510 #ifndef OLD_STYLE_ERRORS
511 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
515 case Transmitter::Fatal:
516 prefix = "[FATAL]: ";
520 fatal_received = true;
522 case Transmitter::Error:
524 prefix = "[ERROR]: ";
529 popup->set_name ("ErrorMessage");
530 popup->set_text (str);
535 case Transmitter::Info:
542 popup->set_name ("InfoMessage");
543 popup->set_text (str);
549 case Transmitter::Warning:
551 prefix = "[WARNING]: ";
556 popup->set_name ("WarningMessage");
557 popup->set_text (str);
563 /* no choice but to use text/console output here */
564 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
568 errors->text().get_buffer()->begin_user_action();
570 if (fatal_received) {
574 display_message (prefix, prefix_len, ptag, mtag, str);
576 if (!errors->is_visible() && chn != Transmitter::Info) {
581 errors->text().get_buffer()->end_user_action();
587 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
592 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
601 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
606 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
608 if (tact->get_active()) {
609 errors->set_position (WIN_POS_MOUSE);
617 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
619 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
621 buffer->insert_with_tag(buffer->end(), prefix, ptag);
622 buffer->insert_with_tag(buffer->end(), msg, mtag);
623 buffer->insert_with_tag(buffer->end(), "\n", mtag);
625 errors->scroll_to_bottom ();
629 UI::handle_fatal (const char *message)
632 Label label (message);
633 Button quit (_("Press To Exit"));
636 win.set_default_size (400, 100);
638 WindowTitle title(Glib::get_application_name());
639 title += ": Fatal Error";
640 win.set_title (title.get_string());
642 win.set_position (WIN_POS_MOUSE);
643 win.set_border_width (12);
645 win.get_vbox()->pack_start (label, true, true);
646 hpacker.pack_start (quit, true, false);
647 win.get_vbox()->pack_start (hpacker, false, false);
649 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
652 win.set_modal (true);
660 UI::popup_error (const string& text)
662 if (!caller_is_ui_thread()) {
663 error << "non-UI threads can't use UI::popup_error"
668 MessageDialog msg (text);
669 msg.set_title (string_compose (_("I'm sorry %1, I can't do that"), g_get_user_name()));
670 msg.set_wmclass (X_("error"), name());
671 msg.set_position (WIN_POS_MOUSE);
678 if (!caller_is_ui_thread()) {
679 error << "non-UI threads cannot call UI::flush_pending()"
684 gtk_main_iteration();
686 while (gtk_events_pending()) {
687 gtk_main_iteration();
692 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
699 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
703 ColorSelectionDialog color_dialog (prompt);
705 color_dialog.set_modal (true);
706 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
707 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
708 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
711 color_dialog.get_colorsel()->set_current_color (*initial);
714 color_dialog.show_all ();
715 color_picked = false;
720 color_dialog.hide_all ();
723 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
724 color.set_red(f_rgba.get_red());
725 color.set_green(f_rgba.get_green());
726 color.set_blue(f_rgba.get_blue());
735 UI::color_selection_done (bool status)
737 color_picked = status;
742 UI::color_selection_deleted (GdkEventAny */*ev*/)