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