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