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>
44 using namespace Gtkmm2ext;
50 pthread_t UI::gui_thread;
53 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
54 BaseUI::RequestType Gtkmm2ext::Quit = BaseUI::new_request_type();
55 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
56 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
57 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
58 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
59 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
61 #include <pbd/abstract_ui.cc> /* instantiate the template */
64 UI::UI (string namestr, int *argc, char ***argv)
65 : AbstractUI<UIRequest> (namestr, true)
67 theMain = new Main (argc, argv);
68 #ifndef GTK_NEW_TOOLTIP_API
76 gui_thread = pthread_self ();
78 fatal << "duplicate UI requested" << endmsg;
82 /* add the pipe to the select/poll loop that GDK does */
84 gdk_input_add (signal_pipe[0],
86 UI::signal_pipe_callback,
89 errors = new TextViewer (850,100);
90 errors->text().set_editable (false);
91 errors->text().set_name ("ErrorText");
93 Glib::set_application_name(namestr);
95 WindowTitle title(Glib::get_application_name());
97 errors->set_title (title.get_string());
99 errors->dismiss_button().set_name ("ErrorLogCloseButton");
100 errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
101 errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
103 register_thread (pthread_self(), X_("GUI"));
105 //load_rcfile (rcfile);
114 UI::caller_is_ui_thread ()
116 return pthread_equal (gui_thread, pthread_self());
120 UI::load_rcfile (string path, bool themechange)
122 if (path.length() == 0) {
126 if (access (path.c_str(), R_OK)) {
127 error << "UI: couldn't find rc file \""
134 RC rc (path.c_str());
135 // RC::reset_styles (Gtk::Settings::get_default());
136 gtk_rc_reset_styles (gtk_settings_get_default());
137 theme_changed.emit();
140 return 0; //Don't continue on every time there is a theme change
143 /* have to pack widgets into a toplevel window so that styles will stick */
145 Window temp_window (WINDOW_TOPLEVEL);
151 RefPtr<Gtk::Style> style;
152 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
154 box.pack_start (a_widget1);
155 box.pack_start (a_widget2);
156 box.pack_start (a_widget3);
157 box.pack_start (a_widget4);
159 error_ptag = buffer->create_tag();
160 error_mtag = buffer->create_tag();
161 fatal_ptag = buffer->create_tag();
162 fatal_mtag = buffer->create_tag();
163 warning_ptag = buffer->create_tag();
164 warning_mtag = buffer->create_tag();
165 info_ptag = buffer->create_tag();
166 info_mtag = buffer->create_tag();
168 a_widget1.set_name ("FatalMessage");
169 a_widget1.ensure_style ();
170 style = a_widget1.get_style();
172 fatal_ptag->property_font_desc().set_value(style->get_font());
173 fatal_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
174 fatal_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
175 fatal_mtag->property_font_desc().set_value(style->get_font());
176 fatal_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
177 fatal_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
179 a_widget2.set_name ("ErrorMessage");
180 a_widget2.ensure_style ();
181 style = a_widget2.get_style();
183 error_ptag->property_font_desc().set_value(style->get_font());
184 error_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
185 error_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
186 error_mtag->property_font_desc().set_value(style->get_font());
187 error_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
188 error_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
190 a_widget3.set_name ("WarningMessage");
191 a_widget3.ensure_style ();
192 style = a_widget3.get_style();
194 warning_ptag->property_font_desc().set_value(style->get_font());
195 warning_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
196 warning_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
197 warning_mtag->property_font_desc().set_value(style->get_font());
198 warning_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
199 warning_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
201 a_widget4.set_name ("InfoMessage");
202 a_widget4.ensure_style ();
203 style = a_widget4.get_style();
205 info_ptag->property_font_desc().set_value(style->get_font());
206 info_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
207 info_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
208 info_mtag->property_font_desc().set_value(style->get_font());
209 info_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
210 info_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
216 UI::run (Receiver &old_receiver)
223 old_receiver.hangup ();
243 pthread_kill (gui_thread, SIGKILL);
250 UIRequest *req = get_request (Quit);
259 static bool idle_quit ()
268 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
271 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
276 UI::touch_display (Touchable *display)
278 UIRequest *req = get_request (TouchDisplay);
284 req->display = display;
290 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
292 UIRequest *req = get_request (SetTip);
306 UI::set_state (Widget *w, StateType state)
308 UIRequest *req = get_request (StateChange);
314 req->new_state = state;
321 UI::idle_add (int (*func)(void *), void *arg)
323 UIRequest *req = get_request (AddIdle);
329 req->function = func;
335 /* END abstract_ui interfaces */
338 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
342 /* flush (nonblocking) pipe */
344 while (read (fd, buf, 256) > 0) {}
346 ((UI *) arg)->handle_ui_requests ();
350 UI::do_request (UIRequest* req)
352 if (req->type == ErrorMessage) {
354 process_error_message (req->chn, req->msg);
355 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
356 req->msg = 0; /* don't free it again in the destructor */
358 } else if (req->type == Quit) {
362 } else if (req->type == CallSlot) {
366 } else if (req->type == TouchDisplay) {
368 req->display->touch ();
369 if (req->display->delete_after_touch()) {
373 } else if (req->type == StateChange) {
375 req->widget->set_state (req->new_state);
377 } else if (req->type == SetTip) {
379 #ifdef GTK_NEW_TOOLTIP_API
380 /* even if the installed GTK is up to date,
381 at present (November 2008) our included
382 version of gtkmm is not. so use the GTK
383 API that we've verified has the right function.
385 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
387 tips->set_tip (*req->widget, req->msg, "");
392 error << "GtkUI: unknown request type "
398 /*======================================================================
400 ======================================================================*/
403 UI::receive (Transmitter::Channel chn, const char *str)
405 if (caller_is_ui_thread()) {
406 process_error_message (chn, str);
408 UIRequest* req = get_request (ErrorMessage);
415 req->msg = strdup (str);
421 #define OLD_STYLE_ERRORS 1
424 UI::process_error_message (Transmitter::Channel chn, const char *str)
427 RefPtr<TextBuffer::Tag> ptag;
428 RefPtr<TextBuffer::Tag> mtag;
431 bool fatal_received = false;
432 #ifndef OLD_STYLE_ERRORS
433 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
437 case Transmitter::Fatal:
438 prefix = "[FATAL]: ";
442 fatal_received = true;
444 case Transmitter::Error:
446 prefix = "[ERROR]: ";
451 popup->set_name ("ErrorMessage");
452 popup->set_text (str);
457 case Transmitter::Info:
464 popup->set_name ("InfoMessage");
465 popup->set_text (str);
471 case Transmitter::Warning:
473 prefix = "[WARNING]: ";
478 popup->set_name ("WarningMessage");
479 popup->set_text (str);
485 /* no choice but to use text/console output here */
486 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
490 errors->text().get_buffer()->begin_user_action();
492 if (fatal_received) {
496 display_message (prefix, prefix_len, ptag, mtag, str);
498 if (!errors->is_visible()) {
503 errors->text().get_buffer()->end_user_action();
509 if (!errors->is_visible()) {
510 errors->set_position (WIN_POS_MOUSE);
518 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
520 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
522 buffer->insert_with_tag(buffer->end(), prefix, ptag);
523 buffer->insert_with_tag(buffer->end(), msg, mtag);
524 buffer->insert_with_tag(buffer->end(), "\n", mtag);
526 errors->scroll_to_bottom ();
530 UI::handle_fatal (const char *message)
532 Window win (WINDOW_POPUP);
534 Label label (message);
535 Button quit (_("Press To Exit"));
537 win.set_default_size (400, 100);
541 title += ": Fatal Error";
542 win.set_title (title);
544 win.set_position (WIN_POS_MOUSE);
547 packer.pack_start (label, true, true);
548 packer.pack_start (quit, false, false);
549 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
552 win.set_modal (true);
560 UI::popup_error (const char *text)
564 if (!caller_is_ui_thread()) {
565 error << "non-UI threads can't use UI::popup_error"
570 pup = new PopUp (WIN_POS_MOUSE, 0, true);
571 pup->set_text (text);
577 int gdk_quartz_in_carbon_menu_event_handler ();
585 /* as of february 11th 2008, gtk/osx has a problem in that mac menu events
586 are handled using Carbon with an "internal" event handling system that
587 doesn't pass things back to the glib/gtk main loop. this makes
588 gtk_main_iteration() block if we call it while in a menu event handler
589 because glib gets confused and thinks there are two threads running
592 this hack (relies on code in gtk2_ardour/sync-menu.c) works
596 if (gdk_quartz_in_carbon_menu_event_handler()) {
600 if (!caller_is_ui_thread()) {
601 error << "non-UI threads cannot call UI::flush_pending()"
606 gtk_main_iteration();
608 while (gtk_events_pending()) {
609 gtk_main_iteration();
614 UI::just_hide_it (GdkEventAny *ev, Window *win)
621 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
625 ColorSelectionDialog color_dialog (prompt);
627 color_dialog.set_modal (true);
628 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
629 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
630 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
633 color_dialog.get_colorsel()->set_current_color (*initial);
636 color_dialog.show_all ();
637 color_picked = false;
642 color_dialog.hide_all ();
645 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
646 color.set_red(f_rgba.get_red());
647 color.set_green(f_rgba.get_green());
648 color.set_blue(f_rgba.get_blue());
657 UI::color_selection_done (bool status)
659 color_picked = status;
664 UI::color_selection_deleted (GdkEventAny *ev)