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);
237 UI::touch_display (Touchable *display)
239 Request *req = get_request (TouchDisplay);
245 req->display = display;
251 UI::call_slot (sigc::slot<void> slot)
253 Request *req = get_request (CallSlot);
265 UI::call_slot_locked (sigc::slot<void> slot)
267 if (caller_is_gui_thread()) {
272 Request *req = get_request (CallSlotLocked);
280 pthread_mutex_init (&req->slot_lock, NULL);
281 pthread_cond_init (&req->slot_cond, NULL);
282 pthread_mutex_lock (&req->slot_lock);
286 pthread_cond_wait (&req->slot_cond, &req->slot_lock);
287 pthread_mutex_unlock (&req->slot_lock);
293 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
295 Request *req = get_request (SetTip);
309 UI::set_state (Widget *w, StateType state)
311 Request *req = get_request (StateChange);
317 req->new_state = state;
324 UI::idle_add (int (*func)(void *), void *arg)
326 Request *req = get_request (AddIdle);
332 req->function = func;
339 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
341 Request *req = get_request (AddTimeout);
347 req->function = func;
349 req->timeout = timeout;
354 /* END abstract_ui interfaces */
356 /* Handling requests */
359 UI::register_thread (pthread_t thread_id, string name)
361 RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
364 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
365 request_buffers[thread_id] = b;
368 pthread_setspecific (thread_request_buffer_key, b);
371 UI::Request::Request()
377 UI::get_request (RequestType rt)
379 RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
382 /* Cannot happen, but if it does we can't use the error reporting mechanism */
383 cerr << _("programming error: ")
384 << string_compose (X_("no GUI request buffer found for thread %1"), pthread_self())
389 RingBufferNPT<Request>::rw_vector vec;
391 rbuf->get_write_vector (&vec);
393 if (vec.len[0] == 0) {
394 if (vec.len[1] == 0) {
395 cerr << string_compose (X_("no space in GUI request buffer for thread %1"), pthread_self())
399 vec.buf[1]->type = rt;
403 vec.buf[0]->type = rt;
409 UI::setup_signal_pipe ()
411 /* setup the pipe that other threads send us notifications/requests
415 if (pipe (signal_pipe)) {
416 error << "UI: cannot create error signal pipe ("
417 << std::strerror (errno) << ")"
423 if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
424 error << "UI: cannot set O_NONBLOCK on "
426 << std::strerror (errno) << ")"
431 if (fcntl (signal_pipe[1], F_SETFL, O_NONBLOCK)) {
432 error << "UI: cannot set O_NONBLOCK on "
433 "signal write pipe ("
434 << std::strerror (errno)
440 /* add the pipe to the select/poll loop that GDK does */
442 gdk_input_add (signal_pipe[0],
444 UI::signal_pipe_callback,
451 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
455 /* flush (nonblocking) pipe */
457 while (read (fd, buf, 256) > 0);
459 ((UI *) arg)->handle_ui_requests ();
463 UI::handle_ui_requests ()
465 RequestBufferMap::iterator i;
467 request_buffer_map_lock.lock ();
469 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
471 RingBufferNPT<Request>::rw_vector vec;
475 /* we must process requests 1 by 1 because
476 the request may run a recursive main
477 event loop that will itself call
478 handle_ui_requests. when we return
479 from the request handler, we cannot
480 expect that the state of queued requests
481 is even remotely consistent with
482 the condition before we called it.
485 i->second->get_read_vector (&vec);
487 if (vec.len[0] == 0) {
490 /* copy constructor does a deep
491 copy of the Request object,
492 unlike Ringbuffer::read()
494 Request req (*vec.buf[0]);
495 i->second->increment_read_ptr (1);
496 request_buffer_map_lock.unlock ();
498 request_buffer_map_lock.lock ();
503 request_buffer_map_lock.unlock ();
507 UI::do_request (Request* req)
511 process_error_message (req->chn, req->msg);
512 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
524 pthread_mutex_lock (&req->slot_lock);
526 pthread_cond_signal (&req->slot_cond);
527 pthread_mutex_unlock (&req->slot_lock);
531 req->display->touch ();
532 if (req->display->delete_after_touch()) {
538 req->widget->set_state (req->new_state);
542 /* XXX need to figure out how this works */
546 gtk_idle_add (req->function, req->arg);
550 gtk_timeout_add (req->timeout, req->function, req->arg);
554 error << "UI: unknown request type "
561 UI::send_request (Request *req)
563 if (instance() == 0) {
564 return; /* XXX is this the right thing to do ? */
567 if (caller_is_gui_thread()) {
568 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
572 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
575 /* can't use the error system to report this, because this
576 thread isn't registered!
578 cerr << _("programming error: ")
579 << string_compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
585 // cerr << "thread " << pthread_self() << " sent request " << req << " type = " << req->type << endl;
586 rbuf->increment_write_ptr (1);
587 write (signal_pipe[1], &c, 1);
592 UI::request (RequestType rt)
594 Request *req = get_request (rt);
603 /*======================================================================
605 ======================================================================*/
608 UI::receive (Transmitter::Channel chn, const char *str)
610 if (caller_is_gui_thread()) {
611 process_error_message (chn, str);
613 Request* req = get_request (ErrorMessage);
620 req->msg = strdup (str);
626 #define OLD_STYLE_ERRORS 1
629 UI::process_error_message (Transmitter::Channel chn, const char *str)
632 RefPtr<TextBuffer::Tag> ptag;
633 RefPtr<TextBuffer::Tag> mtag;
636 bool fatal_received = false;
637 #ifndef OLD_STYLE_ERRORS
638 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
642 case Transmitter::Fatal:
643 prefix = "[FATAL]: ";
647 fatal_received = true;
649 case Transmitter::Error:
651 prefix = "[ERROR]: ";
656 popup->set_name ("ErrorMessage");
657 popup->set_text (str);
662 case Transmitter::Info:
669 popup->set_name ("InfoMessage");
670 popup->set_text (str);
676 case Transmitter::Warning:
678 prefix = "[WARNING]: ";
683 popup->set_name ("WarningMessage");
684 popup->set_text (str);
690 /* no choice but to use text/console output here */
691 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
695 errors->text().get_buffer()->begin_user_action();
697 if (fatal_received) {
701 display_message (prefix, prefix_len, ptag, mtag, str);
703 if (!errors->is_visible()) {
708 errors->text().get_buffer()->end_user_action();
714 if (!errors->is_visible()) {
715 errors->set_position (WIN_POS_MOUSE);
723 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
725 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
727 buffer->insert_with_tag(buffer->end(), prefix, ptag);
728 buffer->insert_with_tag(buffer->end(), msg, mtag);
729 buffer->insert_with_tag(buffer->end(), "\n", mtag);
731 errors->scroll_to_bottom ();
735 UI::handle_fatal (const char *message)
737 Window win (WINDOW_POPUP);
739 Label label (message);
740 Button quit (_("Press To Exit"));
742 win.set_default_size (400, 100);
746 title += ": Fatal Error";
747 win.set_title (title);
749 win.set_position (WIN_POS_MOUSE);
752 packer.pack_start (label, true, true);
753 packer.pack_start (quit, false, false);
754 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
757 win.set_modal (true);
765 UI::popup_error (const char *text)
769 if (!caller_is_gui_thread()) {
770 error << "non-UI threads can't use UI::popup_error"
775 pup = new PopUp (WIN_POS_MOUSE, 0, true);
776 pup->set_text (text);
784 if (!caller_is_gui_thread()) {
785 error << "non-UI threads cannot call UI::flush_pending()"
790 gtk_main_iteration();
792 while (gtk_events_pending()) {
793 gtk_main_iteration();
798 UI::just_hide_it (GdkEventAny *ev, Window *win)
805 UI::get_color (const string& prompt, bool& picked, Gdk::Color* initial)
809 ColorSelectionDialog color_dialog (prompt);
811 color_dialog.set_modal (true);
812 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
813 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
814 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
817 color_dialog.get_colorsel()->set_current_color (*initial);
820 color_dialog.show_all ();
821 color_picked = false;
826 color_dialog.hide_all ();
829 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
830 color.set_red(f_rgba.get_red());
831 color.set_green(f_rgba.get_green());
832 color.set_blue(f_rgba.get_blue());
841 UI::color_selection_done (bool status)
843 color_picked = status;
848 UI::color_selection_deleted (GdkEventAny *ev)