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 (!Glib::file_test (path
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
149 error
<< "UI: couldn't find rc file \""
156 RC
rc (path
.c_str());
157 //this is buggy in gtkmm for some reason, so use C
158 //RC::reset_styles (Gtk::Settings::get_default());
159 gtk_rc_reset_styles (gtk_settings_get_default());
161 theme_changed
.emit();
164 return 0; //Don't continue on every time there is a theme change
167 /* have to pack widgets into a toplevel window so that styles will stick */
169 Window
temp_window (WINDOW_TOPLEVEL
);
170 temp_window
.ensure_style ();
175 Label warning_widget
;
177 RefPtr
<Gtk::Style
> style
;
178 RefPtr
<TextBuffer
> buffer (errors
->text().get_buffer());
180 box
.pack_start (fatal_widget
);
181 box
.pack_start (error_widget
);
182 box
.pack_start (warning_widget
);
183 box
.pack_start (info_widget
);
185 error_ptag
= buffer
->create_tag();
186 error_mtag
= buffer
->create_tag();
187 fatal_ptag
= buffer
->create_tag();
188 fatal_mtag
= buffer
->create_tag();
189 warning_ptag
= buffer
->create_tag();
190 warning_mtag
= buffer
->create_tag();
191 info_ptag
= buffer
->create_tag();
192 info_mtag
= buffer
->create_tag();
194 fatal_widget
.set_name ("FatalMessage");
197 /* This next line and the similar ones below are sketchily
198 * guessed to fix #2885. I think maybe that problems occur
199 * because with gtk_rc_get_style (to quote its docs) "no
200 * refcount is added to the returned style". So I've switched
201 * this to use Glib::wrap with take_copy == true, which requires
202 * all the nasty casts and calls to plain-old-C GTK.
204 * At worst I think this causes a memory leak; at least it appears
207 * I could be wrong about any or all of the above.
209 fatal_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (fatal_widget
.gobj())), true));
211 fatal_ptag
->property_font_desc().set_value((*fatal_style
)->get_font());
212 fatal_ptag
->property_foreground_gdk().set_value((*fatal_style
)->get_fg(STATE_ACTIVE
));
213 fatal_ptag
->property_background_gdk().set_value((*fatal_style
)->get_bg(STATE_ACTIVE
));
214 fatal_mtag
->property_font_desc().set_value((*fatal_style
)->get_font());
215 fatal_mtag
->property_foreground_gdk().set_value((*fatal_style
)->get_fg(STATE_NORMAL
));
216 fatal_mtag
->property_background_gdk().set_value((*fatal_style
)->get_bg(STATE_NORMAL
));
218 error_widget
.set_name ("ErrorMessage");
220 error_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (error_widget
.gobj())), true));
222 error_ptag
->property_font_desc().set_value((*error_style
)->get_font());
223 error_ptag
->property_foreground_gdk().set_value((*error_style
)->get_fg(STATE_ACTIVE
));
224 error_ptag
->property_background_gdk().set_value((*error_style
)->get_bg(STATE_ACTIVE
));
225 error_mtag
->property_font_desc().set_value((*error_style
)->get_font());
226 error_mtag
->property_foreground_gdk().set_value((*error_style
)->get_fg(STATE_NORMAL
));
227 error_mtag
->property_background_gdk().set_value((*error_style
)->get_bg(STATE_NORMAL
));
229 warning_widget
.set_name ("WarningMessage");
230 delete warning_style
;
231 warning_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (warning_widget
.gobj())), true));
233 warning_ptag
->property_font_desc().set_value((*warning_style
)->get_font());
234 warning_ptag
->property_foreground_gdk().set_value((*warning_style
)->get_fg(STATE_ACTIVE
));
235 warning_ptag
->property_background_gdk().set_value((*warning_style
)->get_bg(STATE_ACTIVE
));
236 warning_mtag
->property_font_desc().set_value((*warning_style
)->get_font());
237 warning_mtag
->property_foreground_gdk().set_value((*warning_style
)->get_fg(STATE_NORMAL
));
238 warning_mtag
->property_background_gdk().set_value((*warning_style
)->get_bg(STATE_NORMAL
));
240 info_widget
.set_name ("InfoMessage");
242 info_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (info_widget
.gobj())), true));
244 info_ptag
->property_font_desc().set_value((*info_style
)->get_font());
245 info_ptag
->property_foreground_gdk().set_value((*info_style
)->get_fg(STATE_ACTIVE
));
246 info_ptag
->property_background_gdk().set_value((*info_style
)->get_bg(STATE_ACTIVE
));
247 info_mtag
->property_font_desc().set_value((*info_style
)->get_font());
248 info_mtag
->property_foreground_gdk().set_value((*info_style
)->get_fg(STATE_NORMAL
));
249 info_mtag
->property_background_gdk().set_value((*info_style
)->get_bg(STATE_NORMAL
));
255 UI::run (Receiver
&old_receiver
)
262 /* stop the old receiver (text/console) once we hit the first idle */
264 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver
, &Receiver::hangup
), false));
284 UIRequest
*req
= get_request (Quit
);
293 static bool idle_quit ()
302 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
305 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit
));
310 UI::touch_display (Touchable
*display
)
312 UIRequest
*req
= get_request (TouchDisplay
);
318 req
->display
= display
;
324 UI::set_tip (Widget
&w
, const gchar
*tip
)
326 set_tip(&w
, tip
, "");
330 UI::set_tip (Widget
&w
, const std::string
& tip
)
332 set_tip(&w
, tip
.c_str(), "");
336 UI::set_tip (Widget
*w
, const gchar
*tip
, const gchar
*hlp
)
338 UIRequest
*req
= get_request (SetTip
);
340 std::string
msg(tip
);
342 Glib::RefPtr
<Gtk::Action
> action
= w
->get_action();
345 ustring ap
= action
->get_accel_path();
347 bool has_key
= ActionManager::lookup_entry(ap
, key
);
348 if (has_key
&& key
.get_abbrev() != "") {
349 msg
.append("\n\n Key: ").append(key
.get_abbrev());
359 req
->msg
= msg
.c_str();
366 UI::set_state (Widget
*w
, StateType state
)
368 UIRequest
*req
= get_request (StateChange
);
374 req
->new_state
= state
;
381 UI::idle_add (int (*func
)(void *), void *arg
)
383 UIRequest
*req
= get_request (AddIdle
);
389 req
->function
= func
;
395 /* END abstract_ui interfaces */
397 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
398 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
399 * is called when that trackable is destroyed.
401 PBD::EventLoop::InvalidationRecord
*
402 __invalidator (sigc::trackable
& trackable
, const char* file
, int line
)
404 PBD::EventLoop::InvalidationRecord
* ir
= new PBD::EventLoop::InvalidationRecord
;
409 trackable
.add_destroy_notify_callback (ir
, PBD::EventLoop::invalidate_request
);
415 UI::do_request (UIRequest
* req
)
417 if (req
->type
== ErrorMessage
) {
419 process_error_message (req
->chn
, req
->msg
);
420 free (const_cast<char*>(req
->msg
)); /* it was strdup'ed */
421 req
->msg
= 0; /* don't free it again in the destructor */
423 } else if (req
->type
== Quit
) {
427 } else if (req
->type
== CallSlot
) {
429 if (getenv ("DEBUG_THREADED_SIGNALS")) {
430 cerr
<< "call slot for " << name() << endl
;
435 } else if (req
->type
== TouchDisplay
) {
437 req
->display
->touch ();
438 if (req
->display
->delete_after_touch()) {
442 } else if (req
->type
== StateChange
) {
444 req
->widget
->set_state (req
->new_state
);
446 } else if (req
->type
== SetTip
) {
448 #ifdef GTK_NEW_TOOLTIP_API
449 /* even if the installed GTK is up to date,
450 at present (November 2008) our included
451 version of gtkmm is not. so use the GTK
452 API that we've verified has the right function.
454 gtk_widget_set_tooltip_text (req
->widget
->gobj(), req
->msg
);
456 tips
->set_tip (*req
->widget
, req
->msg
, "");
461 error
<< "GtkUI: unknown request type "
467 /*======================================================================
469 ======================================================================*/
472 UI::receive (Transmitter::Channel chn
, const char *str
)
474 if (caller_is_ui_thread()) {
475 process_error_message (chn
, str
);
477 UIRequest
* req
= get_request (ErrorMessage
);
484 req
->msg
= strdup (str
);
490 #define OLD_STYLE_ERRORS 1
493 UI::process_error_message (Transmitter::Channel chn
, const char *str
)
496 RefPtr
<TextBuffer::Tag
> ptag
;
497 RefPtr
<TextBuffer::Tag
> mtag
;
500 bool fatal_received
= false;
501 #ifndef OLD_STYLE_ERRORS
502 PopUp
* popup
= new PopUp (WIN_POS_CENTER
, 0, true);
506 case Transmitter::Fatal
:
507 prefix
= "[FATAL]: ";
511 fatal_received
= true;
513 case Transmitter::Error
:
515 prefix
= "[ERROR]: ";
520 popup
->set_name ("ErrorMessage");
521 popup
->set_text (str
);
526 case Transmitter::Info
:
533 popup
->set_name ("InfoMessage");
534 popup
->set_text (str
);
540 case Transmitter::Warning
:
542 prefix
= "[WARNING]: ";
547 popup
->set_name ("WarningMessage");
548 popup
->set_text (str
);
554 /* no choice but to use text/console output here */
555 cerr
<< "programmer error in UI::check_error_messages (channel = " << chn
<< ")\n";
559 errors
->text().get_buffer()->begin_user_action();
561 if (fatal_received
) {
565 display_message (prefix
, prefix_len
, ptag
, mtag
, str
);
567 if (!errors
->is_visible() && chn
!= Transmitter::Info
) {
572 errors
->text().get_buffer()->end_user_action();
578 Glib::RefPtr
<Action
> act
= ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
583 Glib::RefPtr
<ToggleAction
> tact
= Glib::RefPtr
<ToggleAction
>::cast_dynamic (act
);
592 Glib::RefPtr
<Action
> act
= ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
597 Glib::RefPtr
<ToggleAction
> tact
= Glib::RefPtr
<ToggleAction
>::cast_dynamic (act
);
599 if (tact
->get_active()) {
600 errors
->set_position (WIN_POS_MOUSE
);
608 UI::display_message (const char *prefix
, gint
/*prefix_len*/, RefPtr
<TextBuffer::Tag
> ptag
, RefPtr
<TextBuffer::Tag
> mtag
, const char *msg
)
610 RefPtr
<TextBuffer
> buffer (errors
->text().get_buffer());
612 buffer
->insert_with_tag(buffer
->end(), prefix
, ptag
);
613 buffer
->insert_with_tag(buffer
->end(), msg
, mtag
);
614 buffer
->insert_with_tag(buffer
->end(), "\n", mtag
);
616 errors
->scroll_to_bottom ();
620 UI::handle_fatal (const char *message
)
623 Label
label (message
);
624 Button
quit (_("Press To Exit"));
627 win
.set_default_size (400, 100);
629 WindowTitle
title(Glib::get_application_name());
630 title
+= ": Fatal Error";
631 win
.set_title (title
.get_string());
633 win
.set_position (WIN_POS_MOUSE
);
634 win
.set_border_width (12);
636 win
.get_vbox()->pack_start (label
, true, true);
637 hpacker
.pack_start (quit
, true, false);
638 win
.get_vbox()->pack_start (hpacker
, false, false);
640 quit
.signal_clicked().connect(mem_fun(*this,&UI::quit
));
643 win
.set_modal (true);
651 UI::popup_error (const string
& text
)
653 if (!caller_is_ui_thread()) {
654 error
<< "non-UI threads can't use UI::popup_error"
659 MessageDialog
msg (text
);
660 msg
.set_title (string_compose (_("I'm sorry %1, I can't do that"), g_get_user_name()));
661 msg
.set_wmclass (X_("error"), name());
662 msg
.set_position (WIN_POS_MOUSE
);
669 if (!caller_is_ui_thread()) {
670 error
<< "non-UI threads cannot call UI::flush_pending()"
675 gtk_main_iteration();
677 while (gtk_events_pending()) {
678 gtk_main_iteration();
683 UI::just_hide_it (GdkEventAny */
*ev*/
, Window
*win
)
690 UI::get_color (const string
& prompt
, bool& picked
, const Gdk::Color
* initial
)
694 ColorSelectionDialog
color_dialog (prompt
);
696 color_dialog
.set_modal (true);
697 color_dialog
.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done
), false));
698 color_dialog
.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done
), true));
699 color_dialog
.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted
));
702 color_dialog
.get_colorsel()->set_current_color (*initial
);
705 color_dialog
.show_all ();
706 color_picked
= false;
711 color_dialog
.hide_all ();
714 Gdk::Color f_rgba
= color_dialog
.get_colorsel()->get_current_color ();
715 color
.set_red(f_rgba
.get_red());
716 color
.set_green(f_rgba
.get_green());
717 color
.set_blue(f_rgba
.get_blue());
726 UI::color_selection_done (bool status
)
728 color_picked
= status
;
733 UI::color_selection_deleted (GdkEventAny */
*ev*/
)