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>
46 using namespace Gtkmm2ext;
54 BaseUI::RequestType Gtkmm2ext::NullMessage = BaseUI::new_request_type();
55 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
56 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
57 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
58 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
59 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
60 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
62 #include "pbd/abstract_ui.cc" /* instantiate the template */
64 UI::UI (string namestr, int *argc, char ***argv)
65 : AbstractUI<UIRequest> (namestr)
67 theMain = new Main (argc, argv);
68 #ifndef GTK_NEW_TOOLTIP_API
77 fatal << "duplicate UI requested" << endmsg;
81 /* the GUI event loop runs in the main thread of the app,
82 which is assumed to have called this.
85 run_loop_thread = Thread::self();
87 /* store "this" as the UI-for-thread of this thread, same argument
91 set_event_loop_for_thread (this);
93 /* attach our request source to the default main context */
95 request_channel.ios()->attach (MainContext::get_default());
97 errors = new TextViewer (800,600);
98 errors->text().set_editable (false);
99 errors->text().set_name ("ErrorText");
100 errors->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Editor/toggle-log-window")));
102 Glib::set_application_name(namestr);
104 WindowTitle title(Glib::get_application_name());
106 errors->set_title (title.get_string());
108 errors->dismiss_button().set_name ("ErrorLogCloseButton");
109 errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
110 errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
112 //load_rcfile (rcfile);
114 /* instantiate the Application singleton */
116 Application::instance();
125 UI::caller_is_ui_thread ()
127 return Thread::self() == run_loop_thread;
131 UI::load_rcfile (string path, bool themechange)
133 /* Yes, pointers to Glib::RefPtr. If these are not kept around,
134 * a segfault somewhere deep in the wonderfully robust glib will result.
135 * This does not occur if wiget.get_style is used instead of rc.get_style below,
136 * except that doesn't actually work...
139 static Glib::RefPtr<Style>* fatal_style = 0;
140 static Glib::RefPtr<Style>* error_style = 0;
141 static Glib::RefPtr<Style>* warning_style = 0;
142 static Glib::RefPtr<Style>* info_style = 0;
144 if (path.length() == 0) {
148 if (access (path.c_str(), R_OK)) {
149 error << "UI: couldn't find rc file \""
156 vector<string> files;
157 files.push_back(path.c_str());
158 RC::set_default_files(files);
159 RC::reparse_all (Gtk::Settings::get_default(), true);
160 theme_changed.emit();
163 return 0; //Don't continue on every time there is a theme change
166 /* have to pack widgets into a toplevel window so that styles will stick */
168 Window temp_window (WINDOW_TOPLEVEL);
169 temp_window.ensure_style ();
174 Label warning_widget;
176 RefPtr<Gtk::Style> style;
177 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
179 box.pack_start (fatal_widget);
180 box.pack_start (error_widget);
181 box.pack_start (warning_widget);
182 box.pack_start (info_widget);
184 error_ptag = buffer->create_tag();
185 error_mtag = buffer->create_tag();
186 fatal_ptag = buffer->create_tag();
187 fatal_mtag = buffer->create_tag();
188 warning_ptag = buffer->create_tag();
189 warning_mtag = buffer->create_tag();
190 info_ptag = buffer->create_tag();
191 info_mtag = buffer->create_tag();
193 fatal_widget.set_name ("FatalMessage");
196 /* This next line and the similar ones below are sketchily
197 * guessed to fix #2885. I think maybe that problems occur
198 * because with gtk_rc_get_style (to quote its docs) "no
199 * refcount is added to the returned style". So I've switched
200 * this to use Glib::wrap with take_copy == true, which requires
201 * all the nasty casts and calls to plain-old-C GTK.
203 * At worst I think this causes a memory leak; at least it appears
206 * I could be wrong about any or all of the above.
208 fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
210 fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
211 fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
212 fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
213 fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
214 fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
215 fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
217 error_widget.set_name ("ErrorMessage");
219 error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
221 error_ptag->property_font_desc().set_value((*error_style)->get_font());
222 error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
223 error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
224 error_mtag->property_font_desc().set_value((*error_style)->get_font());
225 error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
226 error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
228 warning_widget.set_name ("WarningMessage");
229 delete warning_style;
230 warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
232 warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
233 warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
234 warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
235 warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
236 warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
237 warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
239 info_widget.set_name ("InfoMessage");
241 info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
243 info_ptag->property_font_desc().set_value((*info_style)->get_font());
244 info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
245 info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
246 info_mtag->property_font_desc().set_value((*info_style)->get_font());
247 info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
248 info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
254 UI::run (Receiver &old_receiver)
261 /* stop the old receiver (text/console) once we hit the first idle */
263 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
283 UIRequest *req = get_request (Quit);
292 static bool idle_quit ()
301 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
304 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
309 UI::touch_display (Touchable *display)
311 UIRequest *req = get_request (TouchDisplay);
317 req->display = display;
323 UI::set_tip (Widget &w, const gchar *tip)
325 set_tip(&w, tip, "");
329 UI::set_tip (Widget &w, const std::string& tip)
331 set_tip(&w, tip.c_str(), "");
335 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
337 UIRequest *req = get_request (SetTip);
339 std::string msg(tip);
341 Glib::RefPtr<Gtk::Action> action = w->get_action();
344 ustring ap = action->get_accel_path();
346 bool has_key = ActionManager::lookup_entry(ap, key);
347 if (has_key && key.get_abbrev() != "") {
348 msg.append("\n\n Key: ").append(key.get_abbrev());
358 req->msg = msg.c_str();
365 UI::set_state (Widget *w, StateType state)
367 UIRequest *req = get_request (StateChange);
373 req->new_state = state;
380 UI::idle_add (int (*func)(void *), void *arg)
382 UIRequest *req = get_request (AddIdle);
388 req->function = func;
394 /* END abstract_ui interfaces */
396 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
397 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
398 * is called when that trackable is destroyed.
400 PBD::EventLoop::InvalidationRecord*
401 __invalidator (sigc::trackable& trackable, const char* file, int line)
403 PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
408 trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
414 UI::do_request (UIRequest* req)
416 if (req->type == ErrorMessage) {
418 process_error_message (req->chn, req->msg);
419 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
420 req->msg = 0; /* don't free it again in the destructor */
422 } else if (req->type == Quit) {
426 } else if (req->type == CallSlot) {
428 if (getenv ("DEBUG_THREADED_SIGNALS")) {
429 cerr << "call slot for " << name() << endl;
434 } else if (req->type == TouchDisplay) {
436 req->display->touch ();
437 if (req->display->delete_after_touch()) {
441 } else if (req->type == StateChange) {
443 req->widget->set_state (req->new_state);
445 } else if (req->type == SetTip) {
447 #ifdef GTK_NEW_TOOLTIP_API
448 /* even if the installed GTK is up to date,
449 at present (November 2008) our included
450 version of gtkmm is not. so use the GTK
451 API that we've verified has the right function.
453 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
455 tips->set_tip (*req->widget, req->msg, "");
460 error << "GtkUI: unknown request type "
466 /*======================================================================
468 ======================================================================*/
471 UI::receive (Transmitter::Channel chn, const char *str)
473 if (caller_is_ui_thread()) {
474 process_error_message (chn, str);
476 UIRequest* req = get_request (ErrorMessage);
483 req->msg = strdup (str);
489 #define OLD_STYLE_ERRORS 1
492 UI::process_error_message (Transmitter::Channel chn, const char *str)
495 RefPtr<TextBuffer::Tag> ptag;
496 RefPtr<TextBuffer::Tag> mtag;
499 bool fatal_received = false;
500 #ifndef OLD_STYLE_ERRORS
501 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
505 case Transmitter::Fatal:
506 prefix = "[FATAL]: ";
510 fatal_received = true;
512 case Transmitter::Error:
514 prefix = "[ERROR]: ";
519 popup->set_name ("ErrorMessage");
520 popup->set_text (str);
525 case Transmitter::Info:
532 popup->set_name ("InfoMessage");
533 popup->set_text (str);
539 case Transmitter::Warning:
541 prefix = "[WARNING]: ";
546 popup->set_name ("WarningMessage");
547 popup->set_text (str);
553 /* no choice but to use text/console output here */
554 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
558 errors->text().get_buffer()->begin_user_action();
560 if (fatal_received) {
564 display_message (prefix, prefix_len, ptag, mtag, str);
566 if (!errors->is_visible() && chn != Transmitter::Info) {
571 errors->text().get_buffer()->end_user_action();
577 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
582 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
584 if (tact->get_active()) {
585 errors->set_position (WIN_POS_MOUSE);
593 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
595 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
597 buffer->insert_with_tag(buffer->end(), prefix, ptag);
598 buffer->insert_with_tag(buffer->end(), msg, mtag);
599 buffer->insert_with_tag(buffer->end(), "\n", mtag);
601 errors->scroll_to_bottom ();
605 UI::handle_fatal (const char *message)
608 Label label (message);
609 Button quit (_("Press To Exit"));
612 win.set_default_size (400, 100);
614 WindowTitle title(Glib::get_application_name());
615 title += ": Fatal Error";
616 win.set_title (title.get_string());
618 win.set_position (WIN_POS_MOUSE);
619 win.set_border_width (12);
621 win.get_vbox()->pack_start (label, true, true);
622 hpacker.pack_start (quit, true, false);
623 win.get_vbox()->pack_start (hpacker, false, false);
625 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
628 win.set_modal (true);
636 UI::popup_error (const string& text)
640 if (!caller_is_ui_thread()) {
641 error << "non-UI threads can't use UI::popup_error"
646 pup = new PopUp (WIN_POS_MOUSE, 0, true);
647 pup->set_text (text);
655 if (!caller_is_ui_thread()) {
656 error << "non-UI threads cannot call UI::flush_pending()"
661 gtk_main_iteration();
663 while (gtk_events_pending()) {
664 gtk_main_iteration();
669 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
676 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
680 ColorSelectionDialog color_dialog (prompt);
682 color_dialog.set_modal (true);
683 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
684 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
685 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
688 color_dialog.get_colorsel()->set_current_color (*initial);
691 color_dialog.show_all ();
692 color_picked = false;
697 color_dialog.hide_all ();
700 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
701 color.set_red(f_rgba.get_red());
702 color.set_green(f_rgba.get_green());
703 color.set_blue(f_rgba.get_blue());
712 UI::color_selection_done (bool status)
714 color_picked = status;
719 UI::color_selection_deleted (GdkEventAny */*ev*/)