more miscellaneous fixes to make things even nicer
[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 void
231 UI::do_quit ()
232 {
233         Main::quit();
234 }
235
236 void
237 UI::touch_display (Touchable *display)
238 {
239         Request *req = get_request (TouchDisplay);
240
241         if (req == 0) {
242                 return;
243         }
244
245         req->display = display;
246
247         send_request (req);
248 }       
249
250 void
251 UI::call_slot (sigc::slot<void> slot)
252 {
253         Request *req = get_request (CallSlot);
254
255         if (req == 0) {
256                 return;
257         }
258
259         req->slot = slot;
260
261         send_request (req);
262 }       
263
264 void
265 UI::call_slot_locked (sigc::slot<void> slot)
266 {
267         if (caller_is_gui_thread()) {
268                 call_slot (slot);
269                 return;
270         }
271
272         Request *req = get_request (CallSlotLocked);
273
274         if (req == 0) {
275                 return;
276         }
277
278         req->slot = slot;
279
280         pthread_mutex_init (&req->slot_lock, NULL);
281         pthread_cond_init (&req->slot_cond, NULL);
282         pthread_mutex_lock (&req->slot_lock);
283
284         send_request (req);
285
286         pthread_cond_wait (&req->slot_cond, &req->slot_lock);
287         pthread_mutex_unlock (&req->slot_lock);
288
289         delete req;
290 }       
291
292 void
293 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
294 {
295         Request *req = get_request (SetTip);
296
297         if (req == 0) {
298                 return;
299         }
300
301         req->widget = w;
302         req->msg = tip;
303         req->msg2 = hlp;
304
305         send_request (req);
306 }
307
308 void
309 UI::set_state (Widget *w, StateType state)
310 {
311         Request *req = get_request (StateChange);
312         
313         if (req == 0) {
314                 return;
315         }
316
317         req->new_state = state;
318         req->widget = w;
319
320         send_request (req);
321 }
322
323 void
324 UI::idle_add (int (*func)(void *), void *arg)
325 {
326         Request *req = get_request (AddIdle);
327
328         if (req == 0) {
329                 return;
330         }
331
332         req->function = func;
333         req->arg = arg;
334
335         send_request (req);
336 }
337
338 void
339 UI::timeout_add (unsigned int timeout, int (*func)(void *), void *arg)
340 {
341         Request *req = get_request (AddTimeout);
342
343         if (req == 0) {
344                 return;
345         }
346
347         req->function = func;
348         req->arg = arg;
349         req->timeout = timeout;
350
351         send_request (req);
352 }
353
354 /* END abstract_ui interfaces */
355
356 /* Handling requests */
357
358 void
359 UI::register_thread (pthread_t thread_id, string name)
360 {
361         RingBufferNPT<Request>* b = new RingBufferNPT<Request> (128);
362
363         {
364                 PBD::LockMonitor lm (request_buffer_map_lock, __LINE__, __FILE__);
365                 request_buffers[thread_id] = b;
366         }
367
368         pthread_setspecific (thread_request_buffer_key, b);
369 }
370
371 UI::Request::Request()
372 {
373
374 }
375
376 UI::Request*
377 UI::get_request (RequestType rt)
378 {
379         RingBufferNPT<Request>* rbuf = static_cast<RingBufferNPT<Request>* >(pthread_getspecific (thread_request_buffer_key));
380
381         if (rbuf == 0) {
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())
385                      << endl;
386                 abort ();
387         }
388         
389         RingBufferNPT<Request>::rw_vector vec;
390         
391         rbuf->get_write_vector (&vec);
392
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())
396                              << endl;
397                         return 0;
398                 } else {
399                         vec.buf[1]->type = rt;
400                         return vec.buf[1];
401                 }
402         } else {
403                 vec.buf[0]->type = rt;
404                 return vec.buf[0];
405         }
406 }
407
408 int
409 UI::setup_signal_pipe ()
410 {
411         /* setup the pipe that other threads send us notifications/requests
412            through.
413         */
414
415         if (pipe (signal_pipe)) {
416                 error << "UI: cannot create error signal pipe ("
417                       << std::strerror (errno) << ")" 
418                       << endmsg;
419
420                 return -1;
421         }
422
423         if (fcntl (signal_pipe[0], F_SETFL, O_NONBLOCK)) {
424                 error << "UI: cannot set O_NONBLOCK on "
425                          "signal read pipe ("
426                       << std::strerror (errno) << ")"
427                       << endmsg;
428                 return -1;
429         }
430
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) 
435                       << ")" 
436                       << endmsg;
437                 return -1;
438         }
439
440         /* add the pipe to the select/poll loop that GDK does */
441
442         gdk_input_add (signal_pipe[0],
443                        GDK_INPUT_READ,
444                        UI::signal_pipe_callback,
445                        this);
446
447         return 0;
448 }
449
450 void
451 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition cond)
452 {
453         char buf[256];
454         
455         /* flush (nonblocking) pipe */
456         
457         while (read (fd, buf, 256) > 0);
458         
459         ((UI *) arg)->handle_ui_requests ();
460 }
461
462 void
463 UI::handle_ui_requests ()
464 {
465         RequestBufferMap::iterator i;
466
467         request_buffer_map_lock.lock ();
468
469         for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
470
471                 RingBufferNPT<Request>::rw_vector vec;
472
473                 while (true) {
474
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.
483                         */
484
485                         i->second->get_read_vector (&vec);
486
487                         if (vec.len[0] == 0) {
488                                 break;
489                         } else {
490                                 /* copy constructor does a deep
491                                    copy of the Request object,
492                                    unlike Ringbuffer::read()
493                                 */
494                                 Request req (*vec.buf[0]);
495                                 i->second->increment_read_ptr (1);
496                                 request_buffer_map_lock.unlock ();
497                                 do_request (&req);
498                                 request_buffer_map_lock.lock ();
499                         } 
500                 }
501         }
502
503         request_buffer_map_lock.unlock ();
504 }
505
506 void
507 UI::do_request (Request* req)
508 {
509         switch (req->type) {
510         case ErrorMessage:
511                 process_error_message (req->chn, req->msg);
512                 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
513                 break;
514                 
515         case Quit:
516                 do_quit ();
517                 break;
518                 
519         case CallSlot:
520                 req->slot ();
521                 break;
522
523         case CallSlotLocked:
524                 pthread_mutex_lock (&req->slot_lock);
525                 req->slot ();
526                 pthread_cond_signal (&req->slot_cond);
527                 pthread_mutex_unlock (&req->slot_lock);
528                 break;
529                 
530         case TouchDisplay:
531                 req->display->touch ();
532                 if (req->display->delete_after_touch()) {
533                         delete req->display;
534                 }
535                 break;
536                 
537         case StateChange:
538                 req->widget->set_state (req->new_state);
539                 break;
540                 
541         case SetTip:
542                 /* XXX need to figure out how this works */
543                 break;
544                 
545         case AddIdle:
546                 gtk_idle_add (req->function, req->arg);
547                 break;
548                 
549         case AddTimeout:
550                 gtk_timeout_add (req->timeout, req->function, req->arg);
551                 break;
552                 
553         default:
554                 error << "UI: unknown request type "
555                       << (int) req->type
556                       << endmsg;
557         }              
558 }
559
560 void
561 UI::send_request (Request *req)
562 {
563         if (instance() == 0) {
564                 return; /* XXX is this the right thing to do ? */
565         }
566         
567         if (caller_is_gui_thread()) {
568                 // cerr << "GUI thread sent request " << req << " type = " << req->type << endl;
569                 do_request (req);
570         } else {        
571                 const char c = 0;
572                 RingBufferNPT<Request*>* rbuf = static_cast<RingBufferNPT<Request*> *> (pthread_getspecific (thread_request_buffer_key));
573
574                 if (rbuf == 0) {
575                         /* can't use the error system to report this, because this
576                            thread isn't registered!
577                         */
578                         cerr << _("programming error: ")
579                              << string_compose (X_("UI::send_request() called from %1, but no request buffer exists for that thread"),
580                                          pthread_self())
581                              << endl;
582                         abort ();
583                 }
584                 
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);
588         }
589 }
590
591 void
592 UI::request (RequestType rt)
593 {
594         Request *req = get_request (rt);
595
596         if (req == 0) {
597                 return;
598         }
599
600         send_request (req);
601 }
602
603 /*======================================================================
604   Error Display
605   ======================================================================*/
606
607 void
608 UI::receive (Transmitter::Channel chn, const char *str)
609 {
610         if (caller_is_gui_thread()) {
611                 process_error_message (chn, str);
612         } else {
613                 Request* req = get_request (ErrorMessage);
614
615                 if (req == 0) {
616                         return;
617                 }
618
619                 req->chn = chn;
620                 req->msg = strdup (str);
621
622                 send_request (req);
623         }
624 }
625
626 #define OLD_STYLE_ERRORS 1
627
628 void
629 UI::process_error_message (Transmitter::Channel chn, const char *str)
630 {
631         RefPtr<Style> style;
632         RefPtr<TextBuffer::Tag> ptag;
633         RefPtr<TextBuffer::Tag> mtag;
634         char *prefix;
635         size_t prefix_len;
636         bool fatal_received = false;
637 #ifndef OLD_STYLE_ERRORS
638         PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
639 #endif
640
641         switch (chn) {
642         case Transmitter::Fatal:
643                 prefix = "[FATAL]: ";
644                 ptag = fatal_ptag;
645                 mtag = fatal_mtag;
646                 prefix_len = 9;
647                 fatal_received = true;
648                 break;
649         case Transmitter::Error:
650 #if OLD_STYLE_ERRORS
651                 prefix = "[ERROR]: ";
652                 ptag = error_ptag;
653                 mtag = error_mtag;
654                 prefix_len = 9;
655 #else
656                 popup->set_name ("ErrorMessage");
657                 popup->set_text (str);
658                 popup->touch ();
659                 return;
660 #endif
661                 break;
662         case Transmitter::Info:
663 #if OLD_STYLE_ERRORS    
664                 prefix = "[INFO]: ";
665                 ptag = info_ptag;
666                 mtag = info_mtag;
667                 prefix_len = 8;
668 #else
669                 popup->set_name ("InfoMessage");
670                 popup->set_text (str);
671                 popup->touch ();
672                 return;
673 #endif
674
675                 break;
676         case Transmitter::Warning:
677 #if OLD_STYLE_ERRORS
678                 prefix = "[WARNING]: ";
679                 ptag = warning_ptag;
680                 mtag = warning_mtag;
681                 prefix_len = 11;
682 #else
683                 popup->set_name ("WarningMessage");
684                 popup->set_text (str);
685                 popup->touch ();
686                 return;
687 #endif
688                 break;
689         default:
690                 /* no choice but to use text/console output here */
691                 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
692                 ::exit (1);
693         }
694         
695         errors->text().get_buffer()->begin_user_action();
696
697         if (fatal_received) {
698                 handle_fatal (str);
699         } else {
700                 
701                 display_message (prefix, prefix_len, ptag, mtag, str);
702                 
703                 if (!errors->is_visible()) {
704                         toggle_errors();
705                 }
706         }
707
708         errors->text().get_buffer()->end_user_action();
709 }
710
711 void
712 UI::toggle_errors ()
713 {
714         if (!errors->is_visible()) {
715                 errors->set_position (WIN_POS_MOUSE);
716                 errors->show ();
717         } else {
718                 errors->hide ();
719         }
720 }
721
722 void
723 UI::display_message (const char *prefix, gint prefix_len, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
724 {
725         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
726
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);
730
731         errors->scroll_to_bottom ();
732 }       
733
734 void
735 UI::handle_fatal (const char *message)
736 {
737         Window win (WINDOW_POPUP);
738         VBox packer;
739         Label label (message);
740         Button quit (_("Press To Exit"));
741
742         win.set_default_size (400, 100);
743         
744         string title;
745         title = _ui_name;
746         title += ": Fatal Error";
747         win.set_title (title);
748
749         win.set_position (WIN_POS_MOUSE);
750         win.add (packer);
751
752         packer.pack_start (label, true, true);
753         packer.pack_start (quit, false, false);
754         quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
755         
756         win.show_all ();
757         win.set_modal (true);
758
759         theMain->run ();
760         
761         exit (1);
762 }
763
764 void
765 UI::popup_error (const char *text)
766 {
767         PopUp *pup;
768
769         if (!caller_is_gui_thread()) {
770                 error << "non-UI threads can't use UI::popup_error" 
771                       << endmsg;
772                 return;
773         }
774         
775         pup = new PopUp (WIN_POS_MOUSE, 0, true);
776         pup->set_text (text);
777         pup->touch ();
778 }
779
780
781 void
782 UI::flush_pending ()
783 {
784         if (!caller_is_gui_thread()) {
785                 error << "non-UI threads cannot call UI::flush_pending()"
786                       << endmsg;
787                 return;
788         }
789
790         gtk_main_iteration();
791
792         while (gtk_events_pending()) {
793                 gtk_main_iteration();
794         }
795 }
796
797 bool
798 UI::just_hide_it (GdkEventAny *ev, Window *win)
799 {
800         win->hide_all ();
801         return true;
802 }
803
804 Gdk::Color
805 UI::get_color (const string& prompt, bool& picked, Gdk::Color* initial)
806 {
807         Gdk::Color color;
808
809         ColorSelectionDialog color_dialog (prompt);
810
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));
815
816         if (initial) {
817                 color_dialog.get_colorsel()->set_current_color (*initial);
818         }
819
820         color_dialog.show_all ();
821         color_picked = false;
822         picked = false;
823
824         Main::run();
825
826         color_dialog.hide_all ();
827
828         if (color_picked) {
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());
833
834                 picked = true;
835         }
836
837         return color;
838 }
839
840 void
841 UI::color_selection_done (bool status)
842 {
843         color_picked = status;
844         Main::quit ();
845 }
846
847 bool
848 UI::color_selection_deleted (GdkEventAny *ev)
849 {
850         Main::quit ();
851         return true;
852 }