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>
35 #include <gtkmm2ext/gtk_ui.h>
36 #include <gtkmm2ext/textviewer.h>
37 #include <gtkmm2ext/popup.h>
38 #include <gtkmm2ext/utils.h>
42 using namespace Gtkmm2ext;
47 pthread_t UI::gui_thread;
50 UI::UI (string name, int *argc, char ***argv, string rcfile)
53 theMain = new Main (argc, argv);
56 // allow run-time rebinding of accels
58 Settings::get_default()->property_gtk_can_change_accels() = true;
60 if (pthread_key_create (&thread_request_buffer_key, 0)) {
61 cerr << _("cannot create thread request buffer key") << endl;
62 throw failed_constructor();
65 PBD::ThreadCreated.connect (mem_fun (*this, &UI::register_thread));
72 gui_thread = pthread_self ();
74 fatal << "duplicate UI requested" << endmsg;
78 if (setup_signal_pipe ()) {
82 errors = new TextViewer (850,100);
83 errors->text().set_editable (false);
84 errors->text().set_name ("ErrorText");
89 errors->set_title (title);
91 errors->dismiss_button().set_name ("ErrorLogCloseButton");
92 errors->signal_delete_event().connect (bind (ptr_fun (just_hide_it), (Window *) errors));
94 register_thread (pthread_self(), X_("GUI"));
103 close (signal_pipe[0]);
104 close (signal_pipe[1]);
108 UI::load_rcfile (string path)
110 if (path.length() == 0) {
114 if (access (path.c_str(), R_OK)) {
115 error << "UI: couldn't find rc file \""
122 RC rc (path.c_str());
124 /* have to pack widgets into a toplevel window so that styles will stick */
126 Window temp_window (WINDOW_TOPLEVEL);
132 RefPtr<Gtk::Style> style;
133 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
135 box.pack_start (a_widget1);
136 box.pack_start (a_widget2);
137 box.pack_start (a_widget3);
138 box.pack_start (a_widget4);
140 error_ptag = buffer->create_tag();
141 error_mtag = buffer->create_tag();
142 fatal_ptag = buffer->create_tag();
143 fatal_mtag = buffer->create_tag();
144 warning_ptag = buffer->create_tag();
145 warning_mtag = buffer->create_tag();
146 info_ptag = buffer->create_tag();
147 info_mtag = buffer->create_tag();
149 a_widget1.set_name ("FatalMessage");
150 a_widget1.ensure_style ();
151 style = a_widget1.get_style();
153 fatal_ptag->property_font_desc().set_value(style->get_font());
154 fatal_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
155 fatal_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
156 fatal_mtag->property_font_desc().set_value(style->get_font());
157 fatal_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
158 fatal_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
160 a_widget2.set_name ("ErrorMessage");
161 a_widget2.ensure_style ();
162 style = a_widget2.get_style();
164 error_ptag->property_font_desc().set_value(style->get_font());
165 error_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
166 error_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
167 error_mtag->property_font_desc().set_value(style->get_font());
168 error_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
169 error_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
171 a_widget3.set_name ("WarningMessage");
172 a_widget3.ensure_style ();
173 style = a_widget3.get_style();
175 warning_ptag->property_font_desc().set_value(style->get_font());
176 warning_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
177 warning_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
178 warning_mtag->property_font_desc().set_value(style->get_font());
179 warning_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
180 warning_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
182 a_widget4.set_name ("InfoMessage");
183 a_widget4.ensure_style ();
184 style = a_widget4.get_style();
186 info_ptag->property_font_desc().set_value(style->get_font());
187 info_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
188 info_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
189 info_mtag->property_font_desc().set_value(style->get_font());
190 info_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
191 info_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
197 UI::run (Receiver &old_receiver)
204 old_receiver.hangup ();
224 pthread_kill (gui_thread, SIGKILL);
234 static bool idle_quit ()
243 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
247 UI::set_quit_context()
249 return setjmp (quit_context);
253 UI::touch_display (Touchable *display)
255 Request *req = get_request (TouchDisplay);
261 req->display = display;
267 UI::call_slot (sigc::slot<void> slot)
269 Request *req = get_request (CallSlot);
281 UI::call_slot_locked (sigc::slot<void> slot)
283 if (caller_is_gui_thread()) {
288 Request *req = get_request (CallSlotLocked);
296 pthread_mutex_init (&req->slot_lock, NULL);
297 pthread_cond_init (&req->slot_cond, NULL);
298 pthread_mutex_lock (&req->slot_lock);
302 pthread_cond_wait (&req->slot_cond, &req->slot_lock);
303 pthread_mutex_unlock (&req->slot_lock);
309 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
311 Request *req = get_request (SetTip);
325 UI::set_state (Widget *w, StateType state)
327 Request *req = get_request (StateChange);
333 req->new_state = state;
340 UI::idle_add (int (*func)(void *), void *arg)
342 Request *req = get_request (AddIdle);
348 req->function = func;
355 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
357 Request *req = get_request (AddTimeout);
363 req->function = func;
365 req->timeout = timeout;
370 /* END abstract_ui interfaces */
372 /* Handling requests */
375 UI::register_thread (pthread_t thread_id, string name)
377 RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
380 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
381 request_buffers[thread_id] = b;
384 pthread_setspecific (thread_request_buffer_key, b);
387 UI::Request::Request()
393 UI::get_request (RequestType rt)
395 RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
398 /* Cannot happen, but if it does we can't use the error reporting mechanism */
399 cerr << _("programming error: ")
400 << string_compose (X_("no GUI request buffer found for thread %1"), pthread_self())
405 RingBufferNPT<Request>::rw_vector vec;
407 rbuf->get_write_vector (&vec);
409 if (vec.len[0] == 0) {
410 if (vec.len[1] == 0) {
411 cerr << string_compose (X_("no space in GUI request buffer for thread %1"), pthread_self())
415 vec.buf[1]->type = rt;
419 vec.buf[0]->type = rt;
425 UI::setup_signal_pipe ()
427 /* setup the pipe that other threads send us notifications/requests
431 if (pipe (signal_pipe)) {
432 error << "UI: cannot create error signal pipe ("
433 << std::strerror (errno) << ")"
439 if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
440 error << "UI: cannot set O_NONBLOCK on "
442 << std::strerror (errno) << ")"
447 if (fcntl (signal_pipe[1], F_SETFL, O_NONBLOCK)) {
448 error << "UI: cannot set O_NONBLOCK on "
449 "signal write pipe ("
450 << std::strerror (errno)
456 /* add the pipe to the select/poll loop that GDK does */
458 gdk_input_add (signal_pipe[0],
460 UI::signal_pipe_callback,
467 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
471 /* flush (nonblocking) pipe */
473 while (read (fd, buf, 256) > 0);
475 ((UI *) arg)->handle_ui_requests ();
479 UI::handle_ui_requests ()
481 RequestBufferMap::iterator i;
483 request_buffer_map_lock.lock ();
485 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
487 RingBufferNPT<Request>::rw_vector vec;
491 /* we must process requests 1 by 1 because
492 the request may run a recursive main
493 event loop that will itself call
494 handle_ui_requests. when we return
495 from the request handler, we cannot
496 expect that the state of queued requests
497 is even remotely consistent with
498 the condition before we called it.
501 i->second->get_read_vector (&vec);
503 if (vec.len[0] == 0) {
506 /* copy constructor does a deep
507 copy of the Request object,
508 unlike Ringbuffer::read()
510 Request req (*vec.buf[0]);
511 i->second->increment_read_ptr (1);
512 request_buffer_map_lock.unlock ();
514 request_buffer_map_lock.lock ();
519 request_buffer_map_lock.unlock ();
523 UI::do_request (Request* req)
527 process_error_message (req->chn, req->msg);
528 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
540 pthread_mutex_lock (&req->slot_lock);
542 pthread_cond_signal (&req->slot_cond);
543 pthread_mutex_unlock (&req->slot_lock);
547 req->display->touch ();
548 if (req->display->delete_after_touch()) {
554 req->widget->set_state (req->new_state);
558 /* XXX need to figure out how this works */
562 gtk_idle_add (req->function, req->arg);
566 gtk_timeout_add (req->timeout, req->function, req->arg);
570 error << "UI: unknown request type "
577 UI::send_request (Request *req)
579 if (instance() == 0) {
580 return; /* XXX is this the right thing to do ? */
583 if (caller_is_gui_thread()) {
584 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
588 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
591 /* can't use the error system to report this, because this
592 thread isn't registered!
594 cerr << _("programming error: ")
595 << string_compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
601 // cerr << "thread " << pthread_self() << " sent request " << req << " type = " << req->type << endl;
602 rbuf->increment_write_ptr (1);
603 write (signal_pipe[1], &c, 1);
608 UI::request (RequestType rt)
610 Request *req = get_request (rt);
619 /*======================================================================
621 ======================================================================*/
624 UI::receive (Transmitter::Channel chn, const char *str)
626 if (caller_is_gui_thread()) {
627 process_error_message (chn, str);
629 Request* req = get_request (ErrorMessage);
636 req->msg = strdup (str);
642 #define OLD_STYLE_ERRORS 1
645 UI::process_error_message (Transmitter::Channel chn, const char *str)
648 RefPtr<TextBuffer::Tag> ptag;
649 RefPtr<TextBuffer::Tag> mtag;
652 bool fatal_received = false;
653 #ifndef OLD_STYLE_ERRORS
654 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
658 case Transmitter::Fatal:
659 prefix = "[FATAL]: ";
663 fatal_received = true;
665 case Transmitter::Error:
667 prefix = "[ERROR]: ";
672 popup->set_name ("ErrorMessage");
673 popup->set_text (str);
678 case Transmitter::Info:
685 popup->set_name ("InfoMessage");
686 popup->set_text (str);
692 case Transmitter::Warning:
694 prefix = "[WARNING]: ";
699 popup->set_name ("WarningMessage");
700 popup->set_text (str);
706 /* no choice but to use text/console output here */
707 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
711 errors->text().get_buffer()->begin_user_action();
713 if (fatal_received) {
717 display_message (prefix, prefix_len, ptag, mtag, str);
719 if (!errors->is_visible()) {
724 errors->text().get_buffer()->end_user_action();
730 if (!errors->is_visible()) {
731 errors->set_position (WIN_POS_MOUSE);
739 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
741 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
743 buffer->insert_with_tag(buffer->end(), prefix, ptag);
744 buffer->insert_with_tag(buffer->end(), msg, mtag);
745 buffer->insert_with_tag(buffer->end(), "\n", mtag);
747 errors->scroll_to_bottom ();
751 UI::handle_fatal (const char *message)
753 Window win (WINDOW_POPUP);
755 Label label (message);
756 Button quit (_("Press To Exit"));
758 win.set_default_size (400, 100);
762 title += ": Fatal Error";
763 win.set_title (title);
765 win.set_position (WIN_POS_MOUSE);
768 packer.pack_start (label, true, true);
769 packer.pack_start (quit, false, false);
770 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
773 win.set_modal (true);
781 UI::popup_error (const char *text)
785 if (!caller_is_gui_thread()) {
786 error << "non-UI threads can't use UI::popup_error"
791 pup = new PopUp (WIN_POS_MOUSE, 0, true);
792 pup->set_text (text);
800 if (!caller_is_gui_thread()) {
801 error << "non-UI threads cannot call UI::flush_pending()"
806 gtk_main_iteration();
808 while (gtk_events_pending()) {
809 gtk_main_iteration();
814 UI::just_hide_it (GdkEventAny *ev, Window *win)
821 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
825 ColorSelectionDialog color_dialog (prompt);
827 color_dialog.set_modal (true);
828 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
829 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
830 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
833 color_dialog.get_colorsel()->set_current_color (*initial);
836 color_dialog.show_all ();
837 color_picked = false;
842 color_dialog.hide_all ();
845 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
846 color.set_red(f_rgba.get_red());
847 color.set_green(f_rgba.get_green());
848 color.set_blue(f_rgba.get_blue());
857 UI::color_selection_done (bool status)
859 color_picked = status;
864 UI::color_selection_deleted (GdkEventAny *ev)