Fix some unused parameter warnings.
[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 #include <pbd/stacktrace.h>
35
36 #include <gtkmm2ext/gtk_ui.h>
37 #include <gtkmm2ext/textviewer.h>
38 #include <gtkmm2ext/popup.h>
39 #include <gtkmm2ext/utils.h>
40 #include <gtkmm2ext/window_title.h>
41
42 #include "i18n.h"
43
44 using namespace Gtkmm2ext;
45 using namespace Gtk;
46 using namespace Glib;
47 using namespace PBD;
48 using std::map;
49
50 pthread_t UI::gui_thread;
51 UI       *UI::theGtkUI = 0;
52
53 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
54 BaseUI::RequestType Gtkmm2ext::Quit = BaseUI::new_request_type();
55 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
56 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
57 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
58 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
59 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
60
61 #include <pbd/abstract_ui.cc>  /* instantiate the template */
62
63
64 UI::UI (string namestr, int *argc, char ***argv) 
65         : AbstractUI<UIRequest> (namestr, true)
66 {
67         theMain = new Main (argc, argv);
68 #ifndef GTK_NEW_TOOLTIP_API
69         tips = new Tooltips;
70 #endif
71
72         _active = false;
73
74         if (!theGtkUI) {
75                 theGtkUI = this;
76                 gui_thread = pthread_self ();
77         } else {
78                 fatal << "duplicate UI requested" << endmsg;
79                 /* NOTREACHED */
80         }
81
82         /* add the pipe to the select/poll loop that GDK does */
83
84         gdk_input_add (signal_pipe[0],
85                        GDK_INPUT_READ,
86                        UI::signal_pipe_callback,
87                        this);
88
89         errors = new TextViewer (850,100);
90         errors->text().set_editable (false); 
91         errors->text().set_name ("ErrorText");
92
93         Glib::set_application_name(namestr);
94
95         WindowTitle title(Glib::get_application_name());
96         title += _("Log");
97         errors->set_title (title.get_string());
98
99         errors->dismiss_button().set_name ("ErrorLogCloseButton");
100         errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
101         errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
102
103         register_thread (pthread_self(), X_("GUI"));
104
105         //load_rcfile (rcfile);
106 }
107
108 UI::~UI ()
109 {
110 }
111
112
113 bool
114 UI::caller_is_ui_thread ()
115 {
116         return pthread_equal (gui_thread, pthread_self());
117 }
118
119 int
120 UI::load_rcfile (string path, bool themechange)
121 {
122         if (path.length() == 0) {
123                 return -1;
124         }
125
126         if (access (path.c_str(), R_OK)) {
127                 error << "UI: couldn't find rc file \"" 
128                       << path
129                       << '"'
130                       << endmsg;
131                 return -1;
132         }
133         
134         RC rc (path.c_str());
135         // RC::reset_styles (Gtk::Settings::get_default());
136         gtk_rc_reset_styles (gtk_settings_get_default());
137         theme_changed.emit();
138
139         if (themechange) {
140                 return 0; //Don't continue on every time there is a theme change
141         }
142
143         /* have to pack widgets into a toplevel window so that styles will stick */
144
145         Window temp_window (WINDOW_TOPLEVEL);
146         HBox box;
147         Label a_widget1;
148         Label a_widget2;
149         Label a_widget3;
150         Label a_widget4;
151         RefPtr<Gtk::Style> style;
152         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
153
154         box.pack_start (a_widget1);
155         box.pack_start (a_widget2);
156         box.pack_start (a_widget3);
157         box.pack_start (a_widget4);
158
159         error_ptag = buffer->create_tag();
160         error_mtag = buffer->create_tag();
161         fatal_ptag = buffer->create_tag();
162         fatal_mtag = buffer->create_tag();
163         warning_ptag = buffer->create_tag();
164         warning_mtag = buffer->create_tag();
165         info_ptag = buffer->create_tag();
166         info_mtag = buffer->create_tag();
167
168         a_widget1.set_name ("FatalMessage");
169         a_widget1.ensure_style ();
170         style = a_widget1.get_style();
171
172         fatal_ptag->property_font_desc().set_value(style->get_font());
173         fatal_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
174         fatal_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
175         fatal_mtag->property_font_desc().set_value(style->get_font());
176         fatal_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
177         fatal_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
178
179         a_widget2.set_name ("ErrorMessage");
180         a_widget2.ensure_style ();
181         style = a_widget2.get_style();
182
183         error_ptag->property_font_desc().set_value(style->get_font());
184         error_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
185         error_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
186         error_mtag->property_font_desc().set_value(style->get_font());
187         error_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
188         error_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
189
190         a_widget3.set_name ("WarningMessage");
191         a_widget3.ensure_style ();
192         style = a_widget3.get_style();
193
194         warning_ptag->property_font_desc().set_value(style->get_font());
195         warning_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
196         warning_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
197         warning_mtag->property_font_desc().set_value(style->get_font());
198         warning_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
199         warning_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
200
201         a_widget4.set_name ("InfoMessage");
202         a_widget4.ensure_style ();
203         style = a_widget4.get_style();
204
205         info_ptag->property_font_desc().set_value(style->get_font());
206         info_ptag->property_foreground_gdk().set_value(style->get_fg(STATE_ACTIVE));
207         info_ptag->property_background_gdk().set_value(style->get_bg(STATE_ACTIVE));
208         info_mtag->property_font_desc().set_value(style->get_font());
209         info_mtag->property_foreground_gdk().set_value(style->get_fg(STATE_NORMAL));
210         info_mtag->property_background_gdk().set_value(style->get_bg(STATE_NORMAL));
211
212         return 0;
213 }
214
215 void
216 UI::run (Receiver &old_receiver)
217 {
218         listen_to (error);
219         listen_to (info);
220         listen_to (warning);
221         listen_to (fatal);
222
223         /* stop the old receiver (text/console) once we hit the first idle */
224
225         Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
226
227         starting ();
228         _active = true; 
229         theMain->run ();
230         _active = false;
231         stopping ();
232         hangup ();
233         return;
234 }
235
236 bool
237 UI::running ()
238 {
239         return _active;
240 }
241
242 void
243 UI::kill ()
244 {
245         if (_active) {
246                 pthread_kill (gui_thread, SIGKILL);
247         } 
248 }
249
250 void
251 UI::quit ()
252 {
253         UIRequest *req = get_request (Quit);
254
255         if (req == 0) {
256                 return;
257         }
258
259         send_request (req);
260 }
261
262 static bool idle_quit ()
263 {
264         Main::quit ();
265         return true;
266 }
267
268 void
269 UI::do_quit ()
270 {
271         if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
272                 Main::quit ();
273         } else {
274                 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
275         }
276 }
277
278 void
279 UI::touch_display (Touchable *display)
280 {
281         UIRequest *req = get_request (TouchDisplay);
282
283         if (req == 0) {
284                 return;
285         }
286
287         req->display = display;
288
289         send_request (req);
290 }       
291
292 void
293 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
294 {
295         UIRequest *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         UIRequest *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         UIRequest *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 /* END abstract_ui interfaces */
339
340 void
341 UI::signal_pipe_callback (void *arg, int fd, GdkInputCondition /*cond*/)
342 {
343         char buf[256];
344         
345         /* flush (nonblocking) pipe */
346         
347         while (read (fd, buf, 256) > 0) {}
348         
349         ((UI *) arg)->handle_ui_requests ();
350 }
351
352 void
353 UI::do_request (UIRequest* req)
354 {
355         if (req->type == ErrorMessage) {
356
357                 process_error_message (req->chn, req->msg);
358                 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
359                 req->msg = 0; /* don't free it again in the destructor */
360
361         } else if (req->type == Quit) {
362
363                 do_quit ();
364
365         } else if (req->type == CallSlot) {
366
367                 req->slot ();
368
369         } else if (req->type == TouchDisplay) {
370
371                 req->display->touch ();
372                 if (req->display->delete_after_touch()) {
373                         delete req->display;
374                 }
375
376         } else if (req->type == StateChange) {
377
378                 req->widget->set_state (req->new_state);
379
380         } else if (req->type == SetTip) {
381
382 #ifdef GTK_NEW_TOOLTIP_API
383                 /* even if the installed GTK is up to date,
384                    at present (November 2008) our included
385                    version of gtkmm is not. so use the GTK
386                    API that we've verified has the right function.
387                 */
388                 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
389 #else
390                 tips->set_tip (*req->widget, req->msg, "");
391 #endif
392
393         } else {
394
395                 error << "GtkUI: unknown request type "
396                       << (int) req->type
397                       << endmsg;
398         }              
399 }
400
401 /*======================================================================
402   Error Display
403   ======================================================================*/
404
405 void
406 UI::receive (Transmitter::Channel chn, const char *str)
407 {
408         if (caller_is_ui_thread()) {
409                 process_error_message (chn, str);
410         } else {
411                 UIRequest* req = get_request (ErrorMessage);
412
413                 if (req == 0) {
414                         return;
415                 }
416
417                 req->chn = chn;
418                 req->msg = strdup (str);
419
420                 send_request (req);
421         }
422 }
423
424 #define OLD_STYLE_ERRORS 1
425
426 void
427 UI::process_error_message (Transmitter::Channel chn, const char *str)
428 {
429         RefPtr<Style> style;
430         RefPtr<TextBuffer::Tag> ptag;
431         RefPtr<TextBuffer::Tag> mtag;
432         const char *prefix;
433         size_t prefix_len;
434         bool fatal_received = false;
435 #ifndef OLD_STYLE_ERRORS
436         PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
437 #endif
438
439         switch (chn) {
440         case Transmitter::Fatal:
441                 prefix = "[FATAL]: ";
442                 ptag = fatal_ptag;
443                 mtag = fatal_mtag;
444                 prefix_len = 9;
445                 fatal_received = true;
446                 break;
447         case Transmitter::Error:
448 #if OLD_STYLE_ERRORS
449                 prefix = "[ERROR]: ";
450                 ptag = error_ptag;
451                 mtag = error_mtag;
452                 prefix_len = 9;
453 #else
454                 popup->set_name ("ErrorMessage");
455                 popup->set_text (str);
456                 popup->touch ();
457                 return;
458 #endif
459                 break;
460         case Transmitter::Info:
461 #if OLD_STYLE_ERRORS    
462                 prefix = "[INFO]: ";
463                 ptag = info_ptag;
464                 mtag = info_mtag;
465                 prefix_len = 8;
466 #else
467                 popup->set_name ("InfoMessage");
468                 popup->set_text (str);
469                 popup->touch ();
470                 return;
471 #endif
472
473                 break;
474         case Transmitter::Warning:
475 #if OLD_STYLE_ERRORS
476                 prefix = "[WARNING]: ";
477                 ptag = warning_ptag;
478                 mtag = warning_mtag;
479                 prefix_len = 11;
480 #else
481                 popup->set_name ("WarningMessage");
482                 popup->set_text (str);
483                 popup->touch ();
484                 return;
485 #endif
486                 break;
487         default:
488                 /* no choice but to use text/console output here */
489                 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
490                 ::exit (1);
491         }
492         
493         errors->text().get_buffer()->begin_user_action();
494
495         if (fatal_received) {
496                 handle_fatal (str);
497         } else {
498                 
499                 display_message (prefix, prefix_len, ptag, mtag, str);
500                 
501                 if (!errors->is_visible()) {
502                         toggle_errors();
503                 }
504         }
505
506         errors->text().get_buffer()->end_user_action();
507 }
508
509 void
510 UI::toggle_errors ()
511 {
512         if (!errors->is_visible()) {
513                 errors->set_position (WIN_POS_MOUSE);
514                 errors->show ();
515         } else {
516                 errors->hide ();
517         }
518 }
519
520 void
521 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
522 {
523         RefPtr<TextBuffer> buffer (errors->text().get_buffer());
524
525         buffer->insert_with_tag(buffer->end(), prefix, ptag);
526         buffer->insert_with_tag(buffer->end(), msg, mtag);
527         buffer->insert_with_tag(buffer->end(), "\n", mtag);
528
529         errors->scroll_to_bottom ();
530 }       
531
532 void
533 UI::handle_fatal (const char *message)
534 {
535         Dialog win;
536         Label label (message);
537         Button quit (_("Press To Exit"));
538         HBox hpacker;
539
540         win.set_default_size (400, 100);
541         
542         string title;
543         title = name();
544         title += ": Fatal Error";
545         win.set_title (title);
546
547         win.set_position (WIN_POS_MOUSE);
548         win.set_border_width (12);
549
550         win.get_vbox()->pack_start (label, true, true);
551         hpacker.pack_start (quit, true, false);
552         win.get_vbox()->pack_start (hpacker, false, false);
553
554         quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
555         
556         win.show_all ();
557         win.set_modal (true);
558
559         theMain->run ();
560         
561         _exit (1);
562 }
563
564 void
565 UI::popup_error (const char *text)
566 {
567         PopUp *pup;
568
569         if (!caller_is_ui_thread()) {
570                 error << "non-UI threads can't use UI::popup_error" 
571                       << endmsg;
572                 return;
573         }
574         
575         pup = new PopUp (WIN_POS_MOUSE, 0, true);
576         pup->set_text (text);
577         pup->touch ();
578 }
579
580 #ifdef GTKOSX
581 extern "C" {
582         int gdk_quartz_in_carbon_menu_event_handler ();
583 }
584 #endif
585
586 void
587 UI::flush_pending ()
588 {
589 #ifdef GTKOSX
590         /* as of february 11th 2008, gtk/osx has a problem in that mac menu events
591            are handled using Carbon with an "internal" event handling system that 
592            doesn't pass things back to the glib/gtk main loop. this makes
593            gtk_main_iteration() block if we call it while in a menu event handler 
594            because glib gets confused and thinks there are two threads running
595            g_main_poll_func(). 
596
597            this hack (relies on code in gtk2_ardour/sync-menu.c) works
598            around that.
599         */
600
601         if (gdk_quartz_in_carbon_menu_event_handler()) {
602                 return;
603         }
604 #endif
605         if (!caller_is_ui_thread()) {
606                 error << "non-UI threads cannot call UI::flush_pending()"
607                       << endmsg;
608                 return;
609         }
610
611         gtk_main_iteration();
612
613         while (gtk_events_pending()) {
614                 gtk_main_iteration();
615         }
616 }
617
618 bool
619 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
620 {
621         win->hide ();
622         return true;
623 }
624
625 Gdk::Color
626 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
627 {
628         Gdk::Color color;
629
630         ColorSelectionDialog color_dialog (prompt);
631
632         color_dialog.set_modal (true);
633         color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
634         color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
635         color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
636
637         if (initial) {
638                 color_dialog.get_colorsel()->set_current_color (*initial);
639         }
640
641         color_dialog.show_all ();
642         color_picked = false;
643         picked = false;
644
645         Main::run();
646
647         color_dialog.hide_all ();
648
649         if (color_picked) {
650                 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
651                 color.set_red(f_rgba.get_red());
652                 color.set_green(f_rgba.get_green());
653                 color.set_blue(f_rgba.get_blue());
654
655                 picked = true;
656         }
657
658         return color;
659 }
660
661 void
662 UI::color_selection_done (bool status)
663 {
664         color_picked = status;
665         Main::quit ();
666 }
667
668 bool
669 UI::color_selection_deleted (GdkEventAny */*ev*/)
670 {
671         Main::quit ();
672         return true;
673 }