2 Copyright (C) 1999-2002 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;
45 pthread_t UI::gui_thread;
48 UI::UI (string name, int *argc, char ***argv, string rcfile)
51 theMain = new Gtk::Main (argc, argv);
52 tips = new Gtk::Tooltips;
54 if (pthread_key_create (&thread_request_buffer_key, 0)) {
55 cerr << _("cannot create thread request buffer key") << endl;
56 throw failed_constructor();
59 PBD::ThreadCreated.connect (mem_fun (*this, &UI::register_thread));
66 gui_thread = pthread_self ();
68 fatal << "duplicate UI requested" << endmsg;
72 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");
90 Glib::RefPtr<Gdk::Window> win(errors->get_window());
91 win->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH));
93 errors->signal_delete_event().connect (bind (ptr_fun (just_hide_it), (Gtk::Window *) errors));
95 register_thread (pthread_self(), X_("GUI"));
102 close (signal_pipe[0]);
103 close (signal_pipe[1]);
107 UI::load_rcfile (string path)
109 if (path.length() == 0) {
113 if (access (path.c_str(), R_OK)) {
114 error << "UI: couldn't find rc file \""
121 gtk_rc_parse (path.c_str());
123 Gtk::Label *a_widget1;
124 Gtk::Label *a_widget2;
125 Gtk::Label *a_widget3;
126 Gtk::Label *a_widget4;
128 a_widget1 = new Gtk::Label;
129 a_widget2 = new Gtk::Label;
130 a_widget3 = new Gtk::Label;
131 a_widget4 = new Gtk::Label;
133 a_widget1->set_name ("FatalMessage");
134 a_widget1->ensure_style ();
135 fatal_message_style = a_widget1->get_style();
137 a_widget2->set_name ("ErrorMessage");
138 a_widget2->ensure_style ();
139 error_message_style = a_widget2->get_style();
141 a_widget3->set_name ("WarningMessage");
142 a_widget3->ensure_style ();
143 warning_message_style = a_widget3->get_style();
145 a_widget4->set_name ("InfoMessage");
146 a_widget4->ensure_style ();
147 info_message_style = a_widget4->get_style();
158 UI::run (Receiver &old_receiver)
165 old_receiver.hangup ();
185 pthread_kill (gui_thread, SIGKILL);
202 UI::touch_display (Touchable *display)
204 Request *req = get_request (TouchDisplay);
210 req->display = display;
216 UI::call_slot (sigc::slot<void> slot)
218 Request *req = get_request (CallSlot);
230 UI::call_slot_locked (sigc::slot<void> slot)
232 if (caller_is_gui_thread()) {
237 Request *req = get_request (CallSlotLocked);
245 pthread_mutex_init (&req->slot_lock, NULL);
246 pthread_cond_init (&req->slot_cond, NULL);
247 pthread_mutex_lock (&req->slot_lock);
251 pthread_cond_wait (&req->slot_cond, &req->slot_lock);
252 pthread_mutex_unlock (&req->slot_lock);
258 UI::set_tip (Gtk::Widget *w, const gchar *tip, const gchar *hlp)
260 Request *req = get_request (SetTip);
274 UI::set_state (Gtk::Widget *w, Gtk::StateType state)
276 Request *req = get_request (StateChange);
282 req->new_state = state;
289 UI::idle_add (int (*func)(void *), void *arg)
291 Request *req = get_request (AddIdle);
297 req->function = func;
304 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
306 Request *req = get_request (AddTimeout);
312 req->function = func;
314 req->timeout = timeout;
319 /* END abstract_ui interfaces */
321 /* Handling requests */
324 UI::register_thread (pthread_t thread_id, string name)
326 RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
329 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
330 request_buffers[thread_id] = b;
333 pthread_setspecific (thread_request_buffer_key, b);
336 UI::Request::Request()
342 UI::get_request (RequestType rt)
344 RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
347 /* Cannot happen, but if it does we can't use the error reporting mechanism */
348 cerr << _("programming error: ")
349 << compose (X_("no GUI request buffer found for thread %1"), pthread_self())
354 RingBufferNPT<Request>::rw_vector vec;
356 rbuf->get_write_vector (&vec);
358 if (vec.len[0] == 0) {
359 if (vec.len[1] == 0) {
360 cerr << compose (X_("no space in GUI request buffer for thread %1"), pthread_self())
364 vec.buf[1]->type = rt;
368 vec.buf[0]->type = rt;
374 UI::setup_signal_pipe ()
376 /* setup the pipe that other threads send us notifications/requests
380 if (pipe (signal_pipe)) {
381 error << "UI: cannot create error signal pipe ("
382 << strerror (errno) << ")"
388 if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
389 error << "UI: cannot set O_NONBLOCK on "
391 << strerror (errno) << ")"
396 if (fcntl (signal_pipe[1], F_SETFL, O_NONBLOCK)) {
397 error << "UI: cannot set O_NONBLOCK on "
398 "signal write pipe ("
405 /* add the pipe to the select/poll loop that GDK does */
407 gdk_input_add (signal_pipe[0],
409 UI::signal_pipe_callback,
416 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
420 /* flush (nonblocking) pipe */
422 while (read (fd, buf, 256) > 0);
424 ((UI *) arg)->handle_ui_requests ();
428 UI::handle_ui_requests ()
430 RequestBufferMap::iterator i;
432 request_buffer_map_lock.lock ();
434 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
436 RingBufferNPT<Request>::rw_vector vec;
440 /* we must process requests 1 by 1 because
441 the request may run a recursive main
442 event loop that will itself call
443 handle_ui_requests. when we return
444 from the request handler, we cannot
445 expect that the state of queued requests
446 is even remotely consistent with
447 the condition before we called it.
450 i->second->get_read_vector (&vec);
452 if (vec.len[0] == 0) {
455 /* copy constructor does a deep
456 copy of the Request object,
457 unlike Ringbuffer::read()
459 Request req (*vec.buf[0]);
460 i->second->increment_read_ptr (1);
461 request_buffer_map_lock.unlock ();
463 request_buffer_map_lock.lock ();
468 request_buffer_map_lock.unlock ();
472 UI::do_request (Request* req)
476 process_error_message (req->chn, req->msg);
477 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
489 pthread_mutex_lock (&req->slot_lock);
491 pthread_cond_signal (&req->slot_cond);
492 pthread_mutex_unlock (&req->slot_lock);
496 req->display->touch ();
497 if (req->display->delete_after_touch()) {
503 req->widget->set_state (req->new_state);
507 /* XXX need to figure out how this works */
511 gtk_idle_add (req->function, req->arg);
515 gtk_timeout_add (req->timeout, req->function, req->arg);
519 error << "UI: unknown request type "
526 UI::send_request (Request *req)
528 if (instance() == 0) {
529 return; /* XXX is this the right thing to do ? */
532 if (caller_is_gui_thread()) {
533 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
537 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
540 /* can't use the error system to report this, because this
541 thread isn't registered!
543 cerr << _("programming error: ")
544 << compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
550 // cerr << "thread " << pthread_self() << " sent request " << req << " type = " << req->type << endl;
551 rbuf->increment_write_ptr (1);
552 write (signal_pipe[1], &c, 1);
557 UI::request (RequestType rt)
559 Request *req = get_request (rt);
568 /*======================================================================
570 ======================================================================*/
573 UI::receive (Transmitter::Channel chn, const char *str)
575 if (caller_is_gui_thread()) {
576 process_error_message (chn, str);
578 Request* req = get_request (ErrorMessage);
585 req->msg = strdup (str);
591 #define OLD_STYLE_ERRORS 1
594 UI::process_error_message (Transmitter::Channel chn, const char *str)
596 Glib::RefPtr<Gtk::Style> style;
599 bool fatal_received = false;
600 #ifndef OLD_STYLE_ERRORS
601 PopUp* popup = new PopUp (Gtk::WIN_POS_CENTER, 0, true);
605 case Transmitter::Fatal:
606 prefix = "[FATAL]: ";
607 style = fatal_message_style;
609 fatal_received = true;
611 case Transmitter::Error:
613 prefix = "[ERROR]: ";
614 style = error_message_style;
617 popup->set_name ("ErrorMessage");
618 popup->set_text (str);
623 case Transmitter::Info:
626 style = info_message_style;
629 popup->set_name ("InfoMessage");
630 popup->set_text (str);
636 case Transmitter::Warning:
638 prefix = "[WARNING]: ";
639 style = warning_message_style;
642 popup->set_name ("WarningMessage");
643 popup->set_text (str);
649 /* no choice but to use text/console output here */
650 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
654 errors->text().get_buffer()->begin_user_action();
656 if (fatal_received) {
660 display_message (prefix, prefix_len, style, str);
662 if (!errors->is_visible()) {
667 errors->text().get_buffer()->end_user_action();
673 if (!errors->is_visible()) {
674 errors->set_position (Gtk::WIN_POS_MOUSE);
682 UI::display_message (const char *prefix, gint prefix_len, Glib::RefPtr<Gtk::Style> style, const char *msg)
684 Glib::RefPtr<Gtk::TextBuffer::Tag> ptag = Gtk::TextBuffer::Tag::create();
685 Glib::RefPtr<Gtk::TextBuffer::Tag> mtag = Gtk::TextBuffer::Tag::create();
687 Pango::FontDescription pfont(style->get_font());
688 ptag->property_font_desc().set_value(pfont);
689 ptag->property_foreground_gdk().set_value(style->get_fg(Gtk::STATE_ACTIVE));
690 ptag->property_background_gdk().set_value(style->get_bg(Gtk::STATE_ACTIVE));
692 Pango::FontDescription mfont (style->get_font());
693 mtag->property_font_desc().set_value(mfont);
694 mtag->property_foreground_gdk().set_value(style->get_fg(Gtk::STATE_NORMAL));
695 mtag->property_background_gdk().set_value(style->get_bg(Gtk::STATE_NORMAL));
697 Glib::RefPtr<Gtk::TextBuffer> buffer (errors->text().get_buffer());
698 buffer->insert_with_tag(buffer->end(), prefix, ptag);
699 buffer->insert_with_tag(buffer->end(), msg, mtag);
700 buffer->insert_with_tag(buffer->end(), "\n", mtag);
702 errors->scroll_to_bottom ();
706 UI::handle_fatal (const char *message)
708 Gtk::Window win (Gtk::WINDOW_POPUP);
710 Gtk::Label label (message);
711 Gtk::Button quit (_("Press To Exit"));
713 win.set_default_size (400, 100);
717 title += ": Fatal Error";
718 win.set_title (title);
720 win.set_position (Gtk::WIN_POS_MOUSE);
723 packer.pack_start (label, true, true);
724 packer.pack_start (quit, false, false);
725 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
728 win.set_modal (true);
736 UI::popup_error (const char *text)
740 if (!caller_is_gui_thread()) {
741 error << "non-UI threads can't use UI::popup_error"
746 pup = new PopUp (Gtk::WIN_POS_MOUSE, 0, true);
747 pup->set_text (text);
755 if (!caller_is_gui_thread()) {
756 error << "non-UI threads cannot call UI::flush_pending()"
761 gtk_main_iteration();
763 while (gtk_events_pending()) {
764 gtk_main_iteration();
769 UI::just_hide_it (GdkEventAny *ev, Gtk::Window *win)
776 UI::get_color (const string& prompt, bool& picked, Gdk::Color* initial)
780 Gtk::ColorSelectionDialog color_dialog (prompt);
782 color_dialog.set_modal (true);
783 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
784 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
785 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
788 color_dialog.get_colorsel()->set_current_color (*initial);
791 color_dialog.show_all ();
792 color_picked = false;
797 color_dialog.hide_all ();
800 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
801 color.set_red(f_rgba.get_red());
802 color.set_green(f_rgba.get_green());
803 color.set_blue(f_rgba.get_blue());
812 UI::color_selection_done (bool status)
814 color_picked = status;
819 UI::color_selection_deleted (GdkEventAny *ev)