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 if (pthread_key_create (&thread_request_buffer_key, 0)) {
57 cerr << _("cannot create thread request buffer key") << endl;
58 throw failed_constructor();
61 PBD::ThreadCreated.connect (mem_fun (*this, &UI::register_thread));
68 gui_thread = pthread_self ();
70 fatal << "duplicate UI requested" << endmsg;
74 if (setup_signal_pipe ()) {
78 errors = new TextViewer (850,100);
79 errors->text().set_editable (false);
80 errors->text().set_name ("ErrorText");
85 errors->set_title (title);
87 errors->dismiss_button().set_name ("ErrorLogCloseButton");
88 errors->signal_delete_event().connect (bind (ptr_fun (just_hide_it), (Window *) errors));
90 register_thread (pthread_self(), X_("GUI"));
99 close (signal_pipe[0]);
100 close (signal_pipe[1]);
104 UI::load_rcfile (string path)
106 if (path.length() == 0) {
110 if (access (path.c_str(), R_OK)) {
111 error << "UI: couldn't find rc file \""
118 RC rc (path.c_str());
120 /* have to pack widgets into a toplevel window so that styles will stick */
122 Window temp_window (WINDOW_TOPLEVEL);
128 RefPtr<Gtk::Style> style;
129 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
131 box.pack_start (a_widget1);
132 box.pack_start (a_widget2);
133 box.pack_start (a_widget3);
134 box.pack_start (a_widget4);
136 error_ptag = buffer->create_tag();
137 error_mtag = buffer->create_tag();
138 fatal_ptag = buffer->create_tag();
139 fatal_mtag = buffer->create_tag();
140 warning_ptag = buffer->create_tag();
141 warning_mtag = buffer->create_tag();
142 info_ptag = buffer->create_tag();
143 info_mtag = buffer->create_tag();
145 a_widget1.set_name ("FatalMessage");
146 a_widget1.ensure_style ();
147 style = a_widget1.get_style();
149 fatal_ptag->property_font_desc().set_value(style->get_font());
150 fatal_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
151 fatal_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
152 fatal_mtag->property_font_desc().set_value(style->get_font());
153 fatal_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
154 fatal_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
156 a_widget2.set_name ("ErrorMessage");
157 a_widget2.ensure_style ();
158 style = a_widget2.get_style();
160 error_ptag->property_font_desc().set_value(style->get_font());
161 error_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
162 error_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
163 error_mtag->property_font_desc().set_value(style->get_font());
164 error_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
165 error_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
167 a_widget3.set_name ("WarningMessage");
168 a_widget3.ensure_style ();
169 style = a_widget3.get_style();
171 warning_ptag->property_font_desc().set_value(style->get_font());
172 warning_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
173 warning_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
174 warning_mtag->property_font_desc().set_value(style->get_font());
175 warning_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
176 warning_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
178 a_widget4.set_name ("InfoMessage");
179 a_widget4.ensure_style ();
180 style = a_widget4.get_style();
182 info_ptag->property_font_desc().set_value(style->get_font());
183 info_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
184 info_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
185 info_mtag->property_font_desc().set_value(style->get_font());
186 info_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
187 info_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
193 UI::run (Receiver &old_receiver)
200 old_receiver.hangup ();
220 pthread_kill (gui_thread, SIGKILL);
230 static bool idle_quit ()
232 cerr << "idle quit, level = " << Main::level() << endl;
240 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
244 UI::set_quit_context()
246 return setjmp (quit_context);
250 UI::touch_display (Touchable *display)
252 Request *req = get_request (TouchDisplay);
258 req->display = display;
264 UI::call_slot (sigc::slot<void> slot)
266 Request *req = get_request (CallSlot);
278 UI::call_slot_locked (sigc::slot<void> slot)
280 if (caller_is_gui_thread()) {
285 Request *req = get_request (CallSlotLocked);
293 pthread_mutex_init (&req->slot_lock, NULL);
294 pthread_cond_init (&req->slot_cond, NULL);
295 pthread_mutex_lock (&req->slot_lock);
299 pthread_cond_wait (&req->slot_cond, &req->slot_lock);
300 pthread_mutex_unlock (&req->slot_lock);
306 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
308 Request *req = get_request (SetTip);
322 UI::set_state (Widget *w, StateType state)
324 Request *req = get_request (StateChange);
330 req->new_state = state;
337 UI::idle_add (int (*func)(void *), void *arg)
339 Request *req = get_request (AddIdle);
345 req->function = func;
352 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
354 Request *req = get_request (AddTimeout);
360 req->function = func;
362 req->timeout = timeout;
367 /* END abstract_ui interfaces */
369 /* Handling requests */
372 UI::register_thread (pthread_t thread_id, string name)
374 RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
377 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
378 request_buffers[thread_id] = b;
381 pthread_setspecific (thread_request_buffer_key, b);
384 UI::Request::Request()
390 UI::get_request (RequestType rt)
392 RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
395 /* Cannot happen, but if it does we can't use the error reporting mechanism */
396 cerr << _("programming error: ")
397 << string_compose (X_("no GUI request buffer found for thread %1"), pthread_self())
402 RingBufferNPT<Request>::rw_vector vec;
404 rbuf->get_write_vector (&vec);
406 if (vec.len[0] == 0) {
407 if (vec.len[1] == 0) {
408 cerr << string_compose (X_("no space in GUI request buffer for thread %1"), pthread_self())
412 vec.buf[1]->type = rt;
416 vec.buf[0]->type = rt;
422 UI::setup_signal_pipe ()
424 /* setup the pipe that other threads send us notifications/requests
428 if (pipe (signal_pipe)) {
429 error << "UI: cannot create error signal pipe ("
430 << std::strerror (errno) << ")"
436 if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
437 error << "UI: cannot set O_NONBLOCK on "
439 << std::strerror (errno) << ")"
444 if (fcntl (signal_pipe[1], F_SETFL, O_NONBLOCK)) {
445 error << "UI: cannot set O_NONBLOCK on "
446 "signal write pipe ("
447 << std::strerror (errno)
453 /* add the pipe to the select/poll loop that GDK does */
455 gdk_input_add (signal_pipe[0],
457 UI::signal_pipe_callback,
464 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
468 /* flush (nonblocking) pipe */
470 while (read (fd, buf, 256) > 0);
472 ((UI *) arg)->handle_ui_requests ();
476 UI::handle_ui_requests ()
478 RequestBufferMap::iterator i;
480 request_buffer_map_lock.lock ();
482 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
484 RingBufferNPT<Request>::rw_vector vec;
488 /* we must process requests 1 by 1 because
489 the request may run a recursive main
490 event loop that will itself call
491 handle_ui_requests. when we return
492 from the request handler, we cannot
493 expect that the state of queued requests
494 is even remotely consistent with
495 the condition before we called it.
498 i->second->get_read_vector (&vec);
500 if (vec.len[0] == 0) {
503 /* copy constructor does a deep
504 copy of the Request object,
505 unlike Ringbuffer::read()
507 Request req (*vec.buf[0]);
508 i->second->increment_read_ptr (1);
509 request_buffer_map_lock.unlock ();
511 request_buffer_map_lock.lock ();
516 request_buffer_map_lock.unlock ();
520 UI::do_request (Request* req)
524 process_error_message (req->chn, req->msg);
525 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
537 pthread_mutex_lock (&req->slot_lock);
539 pthread_cond_signal (&req->slot_cond);
540 pthread_mutex_unlock (&req->slot_lock);
544 req->display->touch ();
545 if (req->display->delete_after_touch()) {
551 req->widget->set_state (req->new_state);
555 /* XXX need to figure out how this works */
559 gtk_idle_add (req->function, req->arg);
563 gtk_timeout_add (req->timeout, req->function, req->arg);
567 error << "UI: unknown request type "
574 UI::send_request (Request *req)
576 if (instance() == 0) {
577 return; /* XXX is this the right thing to do ? */
580 if (caller_is_gui_thread()) {
581 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
585 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
588 /* can't use the error system to report this, because this
589 thread isn't registered!
591 cerr << _("programming error: ")
592 << string_compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
598 // cerr << "thread " << pthread_self() << " sent request " << req << " type = " << req->type << endl;
599 rbuf->increment_write_ptr (1);
600 write (signal_pipe[1], &c, 1);
605 UI::request (RequestType rt)
607 Request *req = get_request (rt);
616 /*======================================================================
618 ======================================================================*/
621 UI::receive (Transmitter::Channel chn, const char *str)
623 if (caller_is_gui_thread()) {
624 process_error_message (chn, str);
626 Request* req = get_request (ErrorMessage);
633 req->msg = strdup (str);
639 #define OLD_STYLE_ERRORS 1
642 UI::process_error_message (Transmitter::Channel chn, const char *str)
645 RefPtr<TextBuffer::Tag> ptag;
646 RefPtr<TextBuffer::Tag> mtag;
649 bool fatal_received = false;
650 #ifndef OLD_STYLE_ERRORS
651 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
655 case Transmitter::Fatal:
656 prefix = "[FATAL]: ";
660 fatal_received = true;
662 case Transmitter::Error:
664 prefix = "[ERROR]: ";
669 popup->set_name ("ErrorMessage");
670 popup->set_text (str);
675 case Transmitter::Info:
682 popup->set_name ("InfoMessage");
683 popup->set_text (str);
689 case Transmitter::Warning:
691 prefix = "[WARNING]: ";
696 popup->set_name ("WarningMessage");
697 popup->set_text (str);
703 /* no choice but to use text/console output here */
704 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
708 errors->text().get_buffer()->begin_user_action();
710 if (fatal_received) {
714 display_message (prefix, prefix_len, ptag, mtag, str);
716 if (!errors->is_visible()) {
721 errors->text().get_buffer()->end_user_action();
727 if (!errors->is_visible()) {
728 errors->set_position (WIN_POS_MOUSE);
736 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
738 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
740 buffer->insert_with_tag(buffer->end(), prefix, ptag);
741 buffer->insert_with_tag(buffer->end(), msg, mtag);
742 buffer->insert_with_tag(buffer->end(), "\n", mtag);
744 errors->scroll_to_bottom ();
748 UI::handle_fatal (const char *message)
750 Window win (WINDOW_POPUP);
752 Label label (message);
753 Button quit (_("Press To Exit"));
755 win.set_default_size (400, 100);
759 title += ": Fatal Error";
760 win.set_title (title);
762 win.set_position (WIN_POS_MOUSE);
765 packer.pack_start (label, true, true);
766 packer.pack_start (quit, false, false);
767 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
770 win.set_modal (true);
778 UI::popup_error (const char *text)
782 if (!caller_is_gui_thread()) {
783 error << "non-UI threads can't use UI::popup_error"
788 pup = new PopUp (WIN_POS_MOUSE, 0, true);
789 pup->set_text (text);
797 if (!caller_is_gui_thread()) {
798 error << "non-UI threads cannot call UI::flush_pending()"
803 gtk_main_iteration();
805 while (gtk_events_pending()) {
806 gtk_main_iteration();
811 UI::just_hide_it (GdkEventAny *ev, Window *win)
818 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
822 ColorSelectionDialog color_dialog (prompt);
824 color_dialog.set_modal (true);
825 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
826 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
827 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
830 color_dialog.get_colorsel()->set_current_color (*initial);
833 color_dialog.show_all ();
834 color_picked = false;
839 color_dialog.hide_all ();
842 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
843 color.set_red(f_rgba.get_red());
844 color.set_green(f_rgba.get_green());
845 color.set_blue(f_rgba.get_blue());
854 UI::color_selection_done (bool status)
856 color_picked = status;
861 UI::color_selection_deleted (GdkEventAny *ev)