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");
188 /* This next line and the similar ones below are sketchily
189 * guessed to fix #2885. I think maybe that problems occur
190 * because with gtk_rc_get_style (to quote its docs) "no
191 * refcount is added to the returned style". So I've switched
192 * this to use Glib::wrap with take_copy == true, which requires
193 * all the nasty casts and calls to plain-old-C GTK.
195 * At worst I think this causes a memory leak; at least it appears
198 * I could be wrong about any or all of the above.
200 fatal_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (fatal_widget
.gobj())), true));
202 fatal_ptag
->property_font_desc().set_value((*fatal_style
)->get_font());
203 fatal_ptag
->property_foreground_gdk().set_value((*fatal_style
)->get_fg(STATE_ACTIVE
));
204 fatal_ptag
->property_background_gdk().set_value((*fatal_style
)->get_bg(STATE_ACTIVE
));
205 fatal_mtag
->property_font_desc().set_value((*fatal_style
)->get_font());
206 fatal_mtag
->property_foreground_gdk().set_value((*fatal_style
)->get_fg(STATE_NORMAL
));
207 fatal_mtag
->property_background_gdk().set_value((*fatal_style
)->get_bg(STATE_NORMAL
));
209 error_widget
.set_name ("ErrorMessage");
211 error_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (error_widget
.gobj())), true));
213 error_ptag
->property_font_desc().set_value((*error_style
)->get_font());
214 error_ptag
->property_foreground_gdk().set_value((*error_style
)->get_fg(STATE_ACTIVE
));
215 error_ptag
->property_background_gdk().set_value((*error_style
)->get_bg(STATE_ACTIVE
));
216 error_mtag
->property_font_desc().set_value((*error_style
)->get_font());
217 error_mtag
->property_foreground_gdk().set_value((*error_style
)->get_fg(STATE_NORMAL
));
218 error_mtag
->property_background_gdk().set_value((*error_style
)->get_bg(STATE_NORMAL
));
220 warning_widget
.set_name ("WarningMessage");
221 delete warning_style
;
222 warning_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (warning_widget
.gobj())), true));
224 warning_ptag
->property_font_desc().set_value((*warning_style
)->get_font());
225 warning_ptag
->property_foreground_gdk().set_value((*warning_style
)->get_fg(STATE_ACTIVE
));
226 warning_ptag
->property_background_gdk().set_value((*warning_style
)->get_bg(STATE_ACTIVE
));
227 warning_mtag
->property_font_desc().set_value((*warning_style
)->get_font());
228 warning_mtag
->property_foreground_gdk().set_value((*warning_style
)->get_fg(STATE_NORMAL
));
229 warning_mtag
->property_background_gdk().set_value((*warning_style
)->get_bg(STATE_NORMAL
));
231 info_widget
.set_name ("InfoMessage");
233 info_style
= new Glib::RefPtr
<Style
> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget
*> (info_widget
.gobj())), true));
235 info_ptag
->property_font_desc().set_value((*info_style
)->get_font());
236 info_ptag
->property_foreground_gdk().set_value((*info_style
)->get_fg(STATE_ACTIVE
));
237 info_ptag
->property_background_gdk().set_value((*info_style
)->get_bg(STATE_ACTIVE
));
238 info_mtag
->property_font_desc().set_value((*info_style
)->get_font());
239 info_mtag
->property_foreground_gdk().set_value((*info_style
)->get_fg(STATE_NORMAL
));
240 info_mtag
->property_background_gdk().set_value((*info_style
)->get_bg(STATE_NORMAL
));
246 UI::run (Receiver
&old_receiver
)
253 /* stop the old receiver (text/console) once we hit the first idle */
255 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver
, &Receiver::hangup
), false));
275 UIRequest
*req
= get_request (Quit
);
284 static bool idle_quit ()
293 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
296 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit
));
301 UI::touch_display (Touchable
*display
)
303 UIRequest
*req
= get_request (TouchDisplay
);
309 req
->display
= display
;
315 UI::set_tip (Widget
&w
, const gchar
*tip
)
317 set_tip(&w
, tip
, "");
321 UI::set_tip (Widget
&w
, const std::string
& tip
)
323 set_tip(&w
, tip
.c_str(), "");
327 UI::set_tip (Widget
*w
, const gchar
*tip
, const gchar
*hlp
)
329 UIRequest
*req
= get_request (SetTip
);
331 std::string
msg(tip
);
333 Glib::RefPtr
<Gtk::Action
> action
= w
->get_action();
336 bool has_key
= ActionManager::lookup_entry(action
->get_accel_path(), key
);
337 if (has_key
&& key
.get_abbrev() != "") {
338 msg
.append("\n\n Key: ").append(key
.get_abbrev());
347 req
->msg
= msg
.c_str();
354 UI::set_state (Widget
*w
, StateType state
)
356 UIRequest
*req
= get_request (StateChange
);
362 req
->new_state
= state
;
369 UI::idle_add (int (*func
)(void *), void *arg
)
371 UIRequest
*req
= get_request (AddIdle
);
377 req
->function
= func
;
383 /* END abstract_ui interfaces */
385 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
386 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
387 * is called when that trackable is destroyed.
389 PBD::EventLoop::InvalidationRecord
*
390 __invalidator (sigc::trackable
& trackable
, const char* file
, int line
)
392 PBD::EventLoop::InvalidationRecord
* ir
= new PBD::EventLoop::InvalidationRecord
;
397 trackable
.add_destroy_notify_callback (ir
, PBD::EventLoop::invalidate_request
);
403 UI::do_request (UIRequest
* req
)
405 if (req
->type
== ErrorMessage
) {
407 process_error_message (req
->chn
, req
->msg
);
408 free (const_cast<char*>(req
->msg
)); /* it was strdup'ed */
409 req
->msg
= 0; /* don't free it again in the destructor */
411 } else if (req
->type
== Quit
) {
415 } else if (req
->type
== CallSlot
) {
417 if (getenv ("DEBUG_THREADED_SIGNALS")) {
418 cerr
<< "call slot for " << name() << endl
;
423 } else if (req
->type
== TouchDisplay
) {
425 req
->display
->touch ();
426 if (req
->display
->delete_after_touch()) {
430 } else if (req
->type
== StateChange
) {
432 req
->widget
->set_state (req
->new_state
);
434 } else if (req
->type
== SetTip
) {
436 #ifdef GTK_NEW_TOOLTIP_API
437 /* even if the installed GTK is up to date,
438 at present (November 2008) our included
439 version of gtkmm is not. so use the GTK
440 API that we've verified has the right function.
442 gtk_widget_set_tooltip_text (req
->widget
->gobj(), req
->msg
);
444 tips
->set_tip (*req
->widget
, req
->msg
, "");
449 error
<< "GtkUI: unknown request type "
455 /*======================================================================
457 ======================================================================*/
460 UI::receive (Transmitter::Channel chn
, const char *str
)
462 if (caller_is_ui_thread()) {
463 process_error_message (chn
, str
);
465 UIRequest
* req
= get_request (ErrorMessage
);
472 req
->msg
= strdup (str
);
478 #define OLD_STYLE_ERRORS 1
481 UI::process_error_message (Transmitter::Channel chn
, const char *str
)
484 RefPtr
<TextBuffer::Tag
> ptag
;
485 RefPtr
<TextBuffer::Tag
> mtag
;
488 bool fatal_received
= false;
489 #ifndef OLD_STYLE_ERRORS
490 PopUp
* popup
= new PopUp (WIN_POS_CENTER
, 0, true);
494 case Transmitter::Fatal
:
495 prefix
= "[FATAL]: ";
499 fatal_received
= true;
501 case Transmitter::Error
:
503 prefix
= "[ERROR]: ";
508 popup
->set_name ("ErrorMessage");
509 popup
->set_text (str
);
514 case Transmitter::Info
:
521 popup
->set_name ("InfoMessage");
522 popup
->set_text (str
);
528 case Transmitter::Warning
:
530 prefix
= "[WARNING]: ";
535 popup
->set_name ("WarningMessage");
536 popup
->set_text (str
);
542 /* no choice but to use text/console output here */
543 cerr
<< "programmer error in UI::check_error_messages (channel = " << chn
<< ")\n";
547 errors
->text().get_buffer()->begin_user_action();
549 if (fatal_received
) {
553 display_message (prefix
, prefix_len
, ptag
, mtag
, str
);
555 if (!errors
->is_visible() && chn
!= Transmitter::Info
) {
560 errors
->text().get_buffer()->end_user_action();
566 if (!errors
->is_visible()) {
567 errors
->set_position (WIN_POS_MOUSE
);
575 UI::display_message (const char *prefix
, gint
/*prefix_len*/, RefPtr
<TextBuffer::Tag
> ptag
, RefPtr
<TextBuffer::Tag
> mtag
, const char *msg
)
577 RefPtr
<TextBuffer
> buffer (errors
->text().get_buffer());
579 buffer
->insert_with_tag(buffer
->end(), prefix
, ptag
);
580 buffer
->insert_with_tag(buffer
->end(), msg
, mtag
);
581 buffer
->insert_with_tag(buffer
->end(), "\n", mtag
);
583 errors
->scroll_to_bottom ();
587 UI::handle_fatal (const char *message
)
590 Label
label (message
);
591 Button
quit (_("Press To Exit"));
594 win
.set_default_size (400, 100);
596 WindowTitle
title(Glib::get_application_name());
597 title
+= ": Fatal Error";
598 win
.set_title (title
.get_string());
600 win
.set_position (WIN_POS_MOUSE
);
601 win
.set_border_width (12);
603 win
.get_vbox()->pack_start (label
, true, true);
604 hpacker
.pack_start (quit
, true, false);
605 win
.get_vbox()->pack_start (hpacker
, false, false);
607 quit
.signal_clicked().connect(mem_fun(*this,&UI::quit
));
610 win
.set_modal (true);
618 UI::popup_error (const string
& text
)
622 if (!caller_is_ui_thread()) {
623 error
<< "non-UI threads can't use UI::popup_error"
628 pup
= new PopUp (WIN_POS_MOUSE
, 0, true);
629 pup
->set_text (text
);
635 int gdk_quartz_in_carbon_menu_event_handler ();
643 /* as of february 11th 2008, gtk/osx has a problem in that mac menu events
644 are handled using Carbon with an "internal" event handling system that
645 doesn't pass things back to the glib/gtk main loop. this makes
646 gtk_main_iteration() block if we call it while in a menu event handler
647 because glib gets confused and thinks there are two threads running
650 this hack (relies on code in gtk2_ardour/sync-menu.c) works
654 if (gdk_quartz_in_carbon_menu_event_handler()) {
658 if (!caller_is_ui_thread()) {
659 error
<< "non-UI threads cannot call UI::flush_pending()"
664 gtk_main_iteration();
666 while (gtk_events_pending()) {
667 gtk_main_iteration();
672 UI::just_hide_it (GdkEventAny */
*ev*/
, Window
*win
)
679 UI::get_color (const string
& prompt
, bool& picked
, const Gdk::Color
* initial
)
683 ColorSelectionDialog
color_dialog (prompt
);
685 color_dialog
.set_modal (true);
686 color_dialog
.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done
), false));
687 color_dialog
.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done
), true));
688 color_dialog
.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted
));
691 color_dialog
.get_colorsel()->set_current_color (*initial
);
694 color_dialog
.show_all ();
695 color_picked
= false;
700 color_dialog
.hide_all ();
703 Gdk::Color f_rgba
= color_dialog
.get_colorsel()->get_current_color ();
704 color
.set_red(f_rgba
.get_red());
705 color
.set_green(f_rgba
.get_green());
706 color
.set_blue(f_rgba
.get_blue());
715 UI::color_selection_done (bool status
)
717 color_picked
= status
;
722 UI::color_selection_deleted (GdkEventAny */
*ev*/
)