track-color chooser works
[ardour.git] / libs / gtkmm2ext / gtk_ui.cc
1 /*
2     Copyright (C) 1999-2005 Paul Barton-Davis 
3
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.
8
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.
13
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.
17
18     $Id$
19 */
20
21 #include <cmath>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <unistd.h>
25 #include <cerrno>
26 #include <climits>
27 #include <cctype>
28
29 #include <gtkmm.h>
30 #include <pbd/error.h>
31 #include <pbd/touchable.h>
32 #include <pbd/failed_constructor.h>
33 #include <pbd/pthread_utils.h>
34
35 #include <gtkmm2ext/gtk_ui.h>
36 #include <gtkmm2ext/textviewer.h>
37 #include <gtkmm2ext/popup.h>
38 #include <gtkmm2ext/utils.h>
39
40 #include "i18n.h"
41
42 using namespace Gtkmm2ext;
43 using namespace Gtk;
44 using namespace Glib;
45 using std::map;
46
47 pthread_t UI::gui_thread;
48 UI       *UI::theGtkUI = 0;
49
50 UI::UI (string name, int *argc, char ***argv, string rcfile) 
51         : _ui_name (name)
52 {
53         theMain = new Main (argc, argv);
54         tips = new Tooltips;
55
56         if (pthread_key_create (&thread_request_buffer_key, 0)) {
57                 cerr << _("cannot create thread request buffer key") << endl;
58                 throw failed_constructor();
59         }
60
61         PBD::ThreadCreated.connect (mem_fun (*this, &UI::register_thread));
62
63         _ok = false;
64         _active = false;
65
66         if (!theGtkUI) {
67                 theGtkUI = this;
68                 gui_thread = pthread_self ();
69         } else {
70                 fatal << "duplicate UI requested" << endmsg;
71                 /* NOTREACHED */
72         }
73
74         if (setup_signal_pipe ()) {
75                 return;
76         }
77
78         errors = new TextViewer (850,100);
79         errors->text().set_editable (false); 
80         errors->text().set_name ("ErrorText");
81
82         string title;
83         title = _ui_name;
84         title += ": Log";
85         errors->set_title (title);
86
87         errors->dismiss_button().set_name ("ErrorLogCloseButton");
88         errors->signal_delete_event().connect (bind (ptr_fun (just_hide_it), (Window *) errors));
89
90         register_thread (pthread_self(), X_("GUI"));
91
92         load_rcfile (rcfile);
93
94         _ok = true;
95 }
96
97 UI::~UI ()
98 {
99         close (signal_pipe[0]);
100         close (signal_pipe[1]);
101 }
102
103 int
104 UI::load_rcfile (string path)
105 {
106         if (path.length() == 0) {
107                 return -1;
108         }
109
110         if (access (path.c_str(), R_OK)) {
111                 error << "UI: couldn't find rc file \"" 
112                       << path
113                       << '"'
114                       << endmsg;
115                 return -1;
116         }
117         
118         RC rc (path.c_str());
119
120         /* have to pack widgets into a toplevel window so that styles will stick */
121
122         Window temp_window (WINDOW_TOPLEVEL);
123         HBox box;
124         Label a_widget1;
125         Label a_widget2;
126         Label a_widget3;
127         Label a_widget4;
128         RefPtr<Gtk::Style> style;
129         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
130
131         box.pack_start (a_widget1);
132         box.pack_start (a_widget2);
133         box.pack_start (a_widget3);
134         box.pack_start (a_widget4);
135
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();
144
145         a_widget1.set_name ("FatalMessage");
146         a_widget1.ensure_style ();
147         style = a_widget1.get_style();
148
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));
155
156         a_widget2.set_name ("ErrorMessage");
157         a_widget2.ensure_style ();
158         style = a_widget2.get_style();
159
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));
166
167         a_widget3.set_name ("WarningMessage");
168         a_widget3.ensure_style ();
169         style = a_widget3.get_style();
170
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));
177
178         a_widget4.set_name ("InfoMessage");
179         a_widget4.ensure_style ();
180         style = a_widget4.get_style();
181
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));
188
189         return 0;
190 }
191
192 void
193 UI::run (Receiver &old_receiver)
194 {
195         listen_to (error);
196         listen_to (info);
197         listen_to (warning);
198         listen_to (fatal);
199
200         old_receiver.hangup ();
201         starting ();
202         _active = true; 
203         theMain->run ();
204         _active = false;
205         stopping ();
206         hangup ();
207         return;
208 }
209
210 bool
211 UI::running ()
212 {
213         return _active;
214 }
215
216 void
217 UI::kill ()
218 {
219         if (_active) {
220                 pthread_kill (gui_thread, SIGKILL);
221         } 
222 }
223
224 void
225 UI::quit ()
226 {
227         request (Quit);
228 }
229
230 static bool idle_quit ()
231 {
232         cerr << "idle quit, level = " << Main::level() << endl;
233         Main::quit ();
234         return true;
235 }
236
237 void
238 UI::do_quit ()
239 {
240         Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
241 }
242
243 int
244 UI::set_quit_context()
245 {
246         return setjmp (quit_context);
247 }
248
249 void
250 UI::touch_display (Touchable *display)
251 {
252         Request *req = get_request (TouchDisplay);
253
254         if (req == 0) {
255                 return;
256         }
257
258         req->display = display;
259
260         send_request (req);
261 }       
262
263 void
264 UI::call_slot (sigc::slot<void> slot)
265 {
266         Request *req = get_request (CallSlot);
267
268         if (req == 0) {
269                 return;
270         }
271
272         req->slot = slot;
273
274         send_request (req);
275 }       
276
277 void
278 UI::call_slot_locked (sigc::slot<void> slot)
279 {
280         if (caller_is_gui_thread()) {
281                 call_slot (slot);
282                 return;
283         }
284
285         Request *req = get_request (CallSlotLocked);
286
287         if (req == 0) {
288                 return;
289         }
290
291         req->slot = slot;
292
293         pthread_mutex_init (&req->slot_lock, NULL);
294         pthread_cond_init (&req->slot_cond, NULL);
295         pthread_mutex_lock (&req->slot_lock);
296
297         send_request (req);
298
299         pthread_cond_wait (&req->slot_cond, &req->slot_lock);
300         pthread_mutex_unlock (&req->slot_lock);
301
302         delete req;
303 }       
304
305 void
306 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
307 {
308         Request *req = get_request (SetTip);
309
310         if (req == 0) {
311                 return;
312         }
313
314         req->widget = w;
315         req->msg = tip;
316         req->msg2 = hlp;
317
318         send_request (req);
319 }
320
321 void
322 UI::set_state (Widget *w, StateType state)
323 {
324         Request *req = get_request (StateChange);
325         
326         if (req == 0) {
327                 return;
328         }
329
330         req->new_state = state;
331         req->widget = w;
332
333         send_request (req);
334 }
335
336 void
337 UI::idle_add (int (*func)(void *), void *arg)
338 {
339         Request *req = get_request (AddIdle);
340
341         if (req == 0) {
342                 return;
343         }
344
345         req->function = func;
346         req->arg = arg;
347
348         send_request (req);
349 }
350
351 void
352 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
353 {
354         Request *req = get_request (AddTimeout);
355
356         if (req == 0) {
357                 return;
358         }
359
360         req->function = func;
361         req->arg = arg;
362         req->timeout = timeout;
363
364         send_request (req);
365 }
366
367 /* END abstract_ui interfaces */
368
369 /* Handling requests */
370
371 void
372 UI::register_thread (pthread_t thread_id, string name)
373 {
374         RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
375
376         {
377                 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
378                 request_buffers[thread_id] = b;
379         }
380
381         pthread_setspecific (thread_request_buffer_key, b);
382 }
383
384 UI::Request::Request()
385 {
386
387 }
388
389 UI::Request*
390 UI::get_request (RequestType rt)
391 {
392         RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
393
394         if (rbuf == 0) {
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())
398                      << endl;
399                 abort ();
400         }
401         
402         RingBufferNPT<Request>::rw_vector vec;
403         
404         rbuf->get_write_vector (&vec);
405
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())
409                              << endl;
410                         return 0;
411                 } else {
412                         vec.buf[1]->type = rt;
413                         return vec.buf[1];
414                 }
415         } else {
416                 vec.buf[0]->type = rt;
417                 return vec.buf[0];
418         }
419 }
420
421 int
422 UI::setup_signal_pipe ()
423 {
424         /* setup the pipe that other threads send us notifications/requests
425            through.
426         */
427
428         if (pipe (signal_pipe)) {
429                 error << "UI: cannot create error signal pipe ("
430                       << std::strerror (errno) << ")" 
431                       << endmsg;
432
433                 return -1;
434         }
435
436         if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
437                 error << "UI: cannot set O_NONBLOCK on "
438                          "signal read pipe ("
439                       << std::strerror (errno) << ")"
440                       << endmsg;
441                 return -1;
442         }
443
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) 
448                       << ")" 
449                       << endmsg;
450                 return -1;
451         }
452
453         /* add the pipe to the select/poll loop that GDK does */
454
455         gdk_input_add (signal_pipe[0],
456                        GDK_INPUT_READ,
457                        UI::signal_pipe_callback,
458                        this);
459
460         return 0;
461 }
462
463 void
464 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
465 {
466         char buf[256];
467         
468         /* flush (nonblocking) pipe */
469         
470         while (read (fd, buf, 256) > 0);
471         
472         ((UI *) arg)->handle_ui_requests ();
473 }
474
475 void
476 UI::handle_ui_requests ()
477 {
478         RequestBufferMap::iterator i;
479
480         request_buffer_map_lock.lock ();
481
482         for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
483
484                 RingBufferNPT<Request>::rw_vector vec;
485
486                 while (true) {
487
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.
496                         */
497
498                         i->second->get_read_vector (&vec);
499
500                         if (vec.len[0] == 0) {
501                                 break;
502                         } else {
503                                 /* copy constructor does a deep
504                                    copy of the Request object,
505                                    unlike Ringbuffer::read()
506                                 */
507                                 Request req (*vec.buf[0]);
508                                 i->second->increment_read_ptr (1);
509                                 request_buffer_map_lock.unlock ();
510                                 do_request (&req);
511                                 request_buffer_map_lock.lock ();
512                         } 
513                 }
514         }
515
516         request_buffer_map_lock.unlock ();
517 }
518
519 void
520 UI::do_request (Request* req)
521 {
522         switch (req->type) {
523         case ErrorMessage:
524                 process_error_message (req->chn, req->msg);
525                 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
526                 break;
527                 
528         case Quit:
529                 do_quit ();
530                 break;
531                 
532         case CallSlot:
533                 req->slot ();
534                 break;
535
536         case CallSlotLocked:
537                 pthread_mutex_lock (&req->slot_lock);
538                 req->slot ();
539                 pthread_cond_signal (&req->slot_cond);
540                 pthread_mutex_unlock (&req->slot_lock);
541                 break;
542                 
543         case TouchDisplay:
544                 req->display->touch ();
545                 if (req->display->delete_after_touch()) {
546                         delete req->display;
547                 }
548                 break;
549                 
550         case StateChange:
551                 req->widget->set_state (req->new_state);
552                 break;
553                 
554         case SetTip:
555                 /* XXX need to figure out how this works */
556                 break;
557                 
558         case AddIdle:
559                 gtk_idle_add (req->function, req->arg);
560                 break;
561                 
562         case AddTimeout:
563                 gtk_timeout_add (req->timeout, req->function, req->arg);
564                 break;
565                 
566         default:
567                 error << "UI: unknown request type "
568                       << (int) req->type
569                       << endmsg;
570         }              
571 }
572
573 void
574 UI::send_request (Request *req)
575 {
576         if (instance() == 0) {
577                 return; /* XXX is this the right thing to do ? */
578         }
579         
580         if (caller_is_gui_thread()) {
581                 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
582                 do_request (req);
583         } else {        
584                 const char c = 0;
585                 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
586
587                 if (rbuf == 0) {
588                         /* can't use the error system to report this, because this
589                            thread isn't registered!
590                         */
591                         cerr << _("programming error: ")
592                              << string_compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
593                                          pthread_self())
594                              << endl;
595                         abort ();
596                 }
597                 
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);
601         }
602 }
603
604 void
605 UI::request (RequestType rt)
606 {
607         Request *req = get_request (rt);
608
609         if (req == 0) {
610                 return;
611         }
612
613         send_request (req);
614 }
615
616 /*======================================================================
617   Error Display
618   ======================================================================*/
619
620 void
621 UI::receive (Transmitter::Channel chn, const char *str)
622 {
623         if (caller_is_gui_thread()) {
624                 process_error_message (chn, str);
625         } else {
626                 Request* req = get_request (ErrorMessage);
627
628                 if (req == 0) {
629                         return;
630                 }
631
632                 req->chn = chn;
633                 req->msg = strdup (str);
634
635                 send_request (req);
636         }
637 }
638
639 #define OLD_STYLE_ERRORS 1
640
641 void
642 UI::process_error_message (Transmitter::Channel chn, const char *str)
643 {
644         RefPtr<Style> style;
645         RefPtr<TextBuffer::Tag> ptag;
646         RefPtr<TextBuffer::Tag> mtag;
647         char *prefix;
648         size_t prefix_len;
649         bool fatal_received = false;
650 #ifndef OLD_STYLE_ERRORS
651         PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
652 #endif
653
654         switch (chn) {
655         case Transmitter::Fatal:
656                 prefix = "[FATAL]: ";
657                 ptag = fatal_ptag;
658                 mtag = fatal_mtag;
659                 prefix_len = 9;
660                 fatal_received = true;
661                 break;
662         case Transmitter::Error:
663 #if OLD_STYLE_ERRORS
664                 prefix = "[ERROR]: ";
665                 ptag = error_ptag;
666                 mtag = error_mtag;
667                 prefix_len = 9;
668 #else
669                 popup->set_name ("ErrorMessage");
670                 popup->set_text (str);
671                 popup->touch ();
672                 return;
673 #endif
674                 break;
675         case Transmitter::Info:
676 #if OLD_STYLE_ERRORS    
677                 prefix = "[INFO]: ";
678                 ptag = info_ptag;
679                 mtag = info_mtag;
680                 prefix_len = 8;
681 #else
682                 popup->set_name ("InfoMessage");
683                 popup->set_text (str);
684                 popup->touch ();
685                 return;
686 #endif
687
688                 break;
689         case Transmitter::Warning:
690 #if OLD_STYLE_ERRORS
691                 prefix = "[WARNING]: ";
692                 ptag = warning_ptag;
693                 mtag = warning_mtag;
694                 prefix_len = 11;
695 #else
696                 popup->set_name ("WarningMessage");
697                 popup->set_text (str);
698                 popup->touch ();
699                 return;
700 #endif
701                 break;
702         default:
703                 /* no choice but to use text/console output here */
704                 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
705                 ::exit (1);
706         }
707         
708         errors->text().get_buffer()->begin_user_action();
709
710         if (fatal_received) {
711                 handle_fatal (str);
712         } else {
713                 
714                 display_message (prefix, prefix_len, ptag, mtag, str);
715                 
716                 if (!errors->is_visible()) {
717                         toggle_errors();
718                 }
719         }
720
721         errors->text().get_buffer()->end_user_action();
722 }
723
724 void
725 UI::toggle_errors ()
726 {
727         if (!errors->is_visible()) {
728                 errors->set_position (WIN_POS_MOUSE);
729                 errors->show ();
730         } else {
731                 errors->hide ();
732         }
733 }
734
735 void
736 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
737 {
738         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
739
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);
743
744         errors->scroll_to_bottom ();
745 }       
746
747 void
748 UI::handle_fatal (const char *message)
749 {
750         Window win (WINDOW_POPUP);
751         VBox packer;
752         Label label (message);
753         Button quit (_("Press To Exit"));
754
755         win.set_default_size (400, 100);
756         
757         string title;
758         title = _ui_name;
759         title += ": Fatal Error";
760         win.set_title (title);
761
762         win.set_position (WIN_POS_MOUSE);
763         win.add (packer);
764
765         packer.pack_start (label, true, true);
766         packer.pack_start (quit, false, false);
767         quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
768         
769         win.show_all ();
770         win.set_modal (true);
771
772         theMain->run ();
773         
774         exit (1);
775 }
776
777 void
778 UI::popup_error (const char *text)
779 {
780         PopUp *pup;
781
782         if (!caller_is_gui_thread()) {
783                 error << "non-UI threads can't use UI::popup_error" 
784                       << endmsg;
785                 return;
786         }
787         
788         pup = new PopUp (WIN_POS_MOUSE, 0, true);
789         pup->set_text (text);
790         pup->touch ();
791 }
792
793
794 void
795 UI::flush_pending ()
796 {
797         if (!caller_is_gui_thread()) {
798                 error << "non-UI threads cannot call UI::flush_pending()"
799                       << endmsg;
800                 return;
801         }
802
803         gtk_main_iteration();
804
805         while (gtk_events_pending()) {
806                 gtk_main_iteration();
807         }
808 }
809
810 bool
811 UI::just_hide_it (GdkEventAny *ev, Window *win)
812 {
813         win->hide_all ();
814         return true;
815 }
816
817 Gdk::Color
818 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
819 {
820         Gdk::Color color;
821
822         ColorSelectionDialog color_dialog (prompt);
823
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));
828
829         if (initial) {
830                 color_dialog.get_colorsel()->set_current_color (*initial);
831         }
832
833         color_dialog.show_all ();
834         color_picked = false;
835         picked = false;
836
837         Main::run();
838
839         color_dialog.hide_all ();
840
841         if (color_picked) {
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());
846
847                 picked = true;
848         }
849
850         return color;
851 }
852
853 void
854 UI::color_selection_done (bool status)
855 {
856         color_picked = status;
857         Main::quit ();
858 }
859
860 bool
861 UI::color_selection_deleted (GdkEventAny *ev)
862 {
863         Main::quit ();
864         return true;
865 }