Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / libs / gtkmm2ext / gtk_ui.cc
blobbebe6c09a9012800b16e6e9ea374961079a9d35f
1 /*
2 Copyright (C) 1999-2005 Paul Barton-Davis
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.
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.
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.
18 $Id$
21 #include <cmath>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <unistd.h>
25 #include <cerrno>
26 #include <climits>
27 #include <cctype>
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>
36 #include <gtkmm2ext/application.h>
37 #include <gtkmm2ext/gtk_ui.h>
38 #include <gtkmm2ext/textviewer.h>
39 #include <gtkmm2ext/popup.h>
40 #include <gtkmm2ext/utils.h>
41 #include <gtkmm2ext/window_title.h>
42 #include <gtkmm2ext/actions.h>
44 #include "i18n.h"
46 using namespace Gtkmm2ext;
47 using namespace Gtk;
48 using namespace Glib;
49 using namespace PBD;
50 using std::map;
52 UI *UI::theGtkUI = 0;
54 BaseUI::RequestType Gtkmm2ext::NullMessage = BaseUI::new_request_type();
55 BaseUI::RequestType Gtkmm2ext::ErrorMessage = BaseUI::new_request_type();
56 BaseUI::RequestType Gtkmm2ext::TouchDisplay = BaseUI::new_request_type();
57 BaseUI::RequestType Gtkmm2ext::StateChange = BaseUI::new_request_type();
58 BaseUI::RequestType Gtkmm2ext::SetTip = BaseUI::new_request_type();
59 BaseUI::RequestType Gtkmm2ext::AddIdle = BaseUI::new_request_type();
60 BaseUI::RequestType Gtkmm2ext::AddTimeout = BaseUI::new_request_type();
62 #include "pbd/abstract_ui.cc" /* instantiate the template */
64 UI::UI (string namestr, int *argc, char ***argv)
65 : AbstractUI<UIRequest> (namestr)
67 theMain = new Main (argc, argv);
68 #ifndef GTK_NEW_TOOLTIP_API
69 tips = new Tooltips;
70 #endif
72 _active = false;
74 if (!theGtkUI) {
75 theGtkUI = this;
76 } else {
77 fatal << "duplicate UI requested" << endmsg;
78 /* NOTREACHED */
81 /* the GUI event loop runs in the main thread of the app,
82 which is assumed to have called this.
85 run_loop_thread = Thread::self();
87 /* store "this" as the UI-for-thread of this thread, same argument
88 as for previous line.
91 set_event_loop_for_thread (this);
93 /* attach our request source to the default main context */
95 request_channel.ios()->attach (MainContext::get_default());
97 errors = new TextViewer (800,600);
98 errors->text().set_editable (false);
99 errors->text().set_name ("ErrorText");
100 errors->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Editor/toggle-log-window")));
102 Glib::set_application_name(namestr);
104 WindowTitle title(Glib::get_application_name());
105 title += _("Log");
106 errors->set_title (title.get_string());
108 errors->dismiss_button().set_name ("ErrorLogCloseButton");
109 errors->signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), (Window *) errors));
110 errors->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
112 //load_rcfile (rcfile);
114 /* instantiate the Application singleton */
116 Application::instance();
119 UI::~UI ()
124 bool
125 UI::caller_is_ui_thread ()
127 return Thread::self() == run_loop_thread;
131 UI::load_rcfile (string path, bool themechange)
133 /* Yes, pointers to Glib::RefPtr. If these are not kept around,
134 * a segfault somewhere deep in the wonderfully robust glib will result.
135 * This does not occur if wiget.get_style is used instead of rc.get_style below,
136 * except that doesn't actually work...
139 static Glib::RefPtr<Style>* fatal_style = 0;
140 static Glib::RefPtr<Style>* error_style = 0;
141 static Glib::RefPtr<Style>* warning_style = 0;
142 static Glib::RefPtr<Style>* info_style = 0;
144 if (path.length() == 0) {
145 return -1;
148 if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
149 error << "UI: couldn't find rc file \""
150 << path
151 << '"'
152 << endmsg;
153 return -1;
156 RC rc (path.c_str());
157 //this is buggy in gtkmm for some reason, so use C
158 //RC::reset_styles (Gtk::Settings::get_default());
159 gtk_rc_reset_styles (gtk_settings_get_default());
161 theme_changed.emit();
163 if (themechange) {
164 return 0; //Don't continue on every time there is a theme change
167 /* have to pack widgets into a toplevel window so that styles will stick */
169 Window temp_window (WINDOW_TOPLEVEL);
170 temp_window.ensure_style ();
172 HBox box;
173 Label fatal_widget;
174 Label error_widget;
175 Label warning_widget;
176 Label info_widget;
177 RefPtr<Gtk::Style> style;
178 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
180 box.pack_start (fatal_widget);
181 box.pack_start (error_widget);
182 box.pack_start (warning_widget);
183 box.pack_start (info_widget);
185 error_ptag = buffer->create_tag();
186 error_mtag = buffer->create_tag();
187 fatal_ptag = buffer->create_tag();
188 fatal_mtag = buffer->create_tag();
189 warning_ptag = buffer->create_tag();
190 warning_mtag = buffer->create_tag();
191 info_ptag = buffer->create_tag();
192 info_mtag = buffer->create_tag();
194 fatal_widget.set_name ("FatalMessage");
195 delete fatal_style;
197 /* This next line and the similar ones below are sketchily
198 * guessed to fix #2885. I think maybe that problems occur
199 * because with gtk_rc_get_style (to quote its docs) "no
200 * refcount is added to the returned style". So I've switched
201 * this to use Glib::wrap with take_copy == true, which requires
202 * all the nasty casts and calls to plain-old-C GTK.
204 * At worst I think this causes a memory leak; at least it appears
205 * to fix the bug.
207 * I could be wrong about any or all of the above.
209 fatal_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (fatal_widget.gobj())), true));
211 fatal_ptag->property_font_desc().set_value((*fatal_style)->get_font());
212 fatal_ptag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_ACTIVE));
213 fatal_ptag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_ACTIVE));
214 fatal_mtag->property_font_desc().set_value((*fatal_style)->get_font());
215 fatal_mtag->property_foreground_gdk().set_value((*fatal_style)->get_fg(STATE_NORMAL));
216 fatal_mtag->property_background_gdk().set_value((*fatal_style)->get_bg(STATE_NORMAL));
218 error_widget.set_name ("ErrorMessage");
219 delete error_style;
220 error_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (error_widget.gobj())), true));
222 error_ptag->property_font_desc().set_value((*error_style)->get_font());
223 error_ptag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_ACTIVE));
224 error_ptag->property_background_gdk().set_value((*error_style)->get_bg(STATE_ACTIVE));
225 error_mtag->property_font_desc().set_value((*error_style)->get_font());
226 error_mtag->property_foreground_gdk().set_value((*error_style)->get_fg(STATE_NORMAL));
227 error_mtag->property_background_gdk().set_value((*error_style)->get_bg(STATE_NORMAL));
229 warning_widget.set_name ("WarningMessage");
230 delete warning_style;
231 warning_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (warning_widget.gobj())), true));
233 warning_ptag->property_font_desc().set_value((*warning_style)->get_font());
234 warning_ptag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_ACTIVE));
235 warning_ptag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_ACTIVE));
236 warning_mtag->property_font_desc().set_value((*warning_style)->get_font());
237 warning_mtag->property_foreground_gdk().set_value((*warning_style)->get_fg(STATE_NORMAL));
238 warning_mtag->property_background_gdk().set_value((*warning_style)->get_bg(STATE_NORMAL));
240 info_widget.set_name ("InfoMessage");
241 delete info_style;
242 info_style = new Glib::RefPtr<Style> (Glib::wrap (gtk_rc_get_style (reinterpret_cast<GtkWidget*> (info_widget.gobj())), true));
244 info_ptag->property_font_desc().set_value((*info_style)->get_font());
245 info_ptag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_ACTIVE));
246 info_ptag->property_background_gdk().set_value((*info_style)->get_bg(STATE_ACTIVE));
247 info_mtag->property_font_desc().set_value((*info_style)->get_font());
248 info_mtag->property_foreground_gdk().set_value((*info_style)->get_fg(STATE_NORMAL));
249 info_mtag->property_background_gdk().set_value((*info_style)->get_bg(STATE_NORMAL));
251 return 0;
254 void
255 UI::run (Receiver &old_receiver)
257 listen_to (error);
258 listen_to (info);
259 listen_to (warning);
260 listen_to (fatal);
262 /* stop the old receiver (text/console) once we hit the first idle */
264 Glib::signal_idle().connect (bind_return (mem_fun (old_receiver, &Receiver::hangup), false));
266 starting ();
267 _active = true;
268 theMain->run ();
269 _active = false;
270 stopping ();
271 hangup ();
272 return;
275 bool
276 UI::running ()
278 return _active;
281 void
282 UI::quit ()
284 UIRequest *req = get_request (Quit);
286 if (req == 0) {
287 return;
290 send_request (req);
293 static bool idle_quit ()
295 Main::quit ();
296 return true;
299 void
300 UI::do_quit ()
302 if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
303 Main::quit ();
304 } else {
305 Glib::signal_idle().connect (sigc::ptr_fun (idle_quit));
309 void
310 UI::touch_display (Touchable *display)
312 UIRequest *req = get_request (TouchDisplay);
314 if (req == 0) {
315 return;
318 req->display = display;
320 send_request (req);
323 void
324 UI::set_tip (Widget &w, const gchar *tip)
326 set_tip(&w, tip, "");
329 void
330 UI::set_tip (Widget &w, const std::string& tip)
332 set_tip(&w, tip.c_str(), "");
335 void
336 UI::set_tip (Widget *w, const gchar *tip, const gchar *hlp)
338 UIRequest *req = get_request (SetTip);
340 std::string msg(tip);
342 Glib::RefPtr<Gtk::Action> action = w->get_action();
343 if (action) {
344 Gtk::AccelKey key;
345 ustring ap = action->get_accel_path();
346 if (!ap.empty()) {
347 bool has_key = ActionManager::lookup_entry(ap, key);
348 if (has_key && key.get_abbrev() != "") {
349 msg.append("\n\n Key: ").append(key.get_abbrev());
354 if (req == 0) {
355 return;
358 req->widget = w;
359 req->msg = msg.c_str();
360 req->msg2 = hlp;
362 send_request (req);
365 void
366 UI::set_state (Widget *w, StateType state)
368 UIRequest *req = get_request (StateChange);
370 if (req == 0) {
371 return;
374 req->new_state = state;
375 req->widget = w;
377 send_request (req);
380 void
381 UI::idle_add (int (*func)(void *), void *arg)
383 UIRequest *req = get_request (AddIdle);
385 if (req == 0) {
386 return;
389 req->function = func;
390 req->arg = arg;
392 send_request (req);
395 /* END abstract_ui interfaces */
397 /** Create a PBD::EventLoop::InvalidationRecord and attach a callback
398 * to a given sigc::trackable so that PBD::EventLoop::invalidate_request
399 * is called when that trackable is destroyed.
401 PBD::EventLoop::InvalidationRecord*
402 __invalidator (sigc::trackable& trackable, const char* file, int line)
404 PBD::EventLoop::InvalidationRecord* ir = new PBD::EventLoop::InvalidationRecord;
406 ir->file = file;
407 ir->line = line;
409 trackable.add_destroy_notify_callback (ir, PBD::EventLoop::invalidate_request);
411 return ir;
414 void
415 UI::do_request (UIRequest* req)
417 if (req->type == ErrorMessage) {
419 process_error_message (req->chn, req->msg);
420 free (const_cast<char*>(req->msg)); /* it was strdup'ed */
421 req->msg = 0; /* don't free it again in the destructor */
423 } else if (req->type == Quit) {
425 do_quit ();
427 } else if (req->type == CallSlot) {
428 #ifndef NDEBUG
429 if (getenv ("DEBUG_THREADED_SIGNALS")) {
430 cerr << "call slot for " << name() << endl;
432 #endif
433 req->the_slot ();
435 } else if (req->type == TouchDisplay) {
437 req->display->touch ();
438 if (req->display->delete_after_touch()) {
439 delete req->display;
442 } else if (req->type == StateChange) {
444 req->widget->set_state (req->new_state);
446 } else if (req->type == SetTip) {
448 #ifdef GTK_NEW_TOOLTIP_API
449 /* even if the installed GTK is up to date,
450 at present (November 2008) our included
451 version of gtkmm is not. so use the GTK
452 API that we've verified has the right function.
454 gtk_widget_set_tooltip_text (req->widget->gobj(), req->msg);
455 #else
456 tips->set_tip (*req->widget, req->msg, "");
457 #endif
459 } else {
461 error << "GtkUI: unknown request type "
462 << (int) req->type
463 << endmsg;
467 /*======================================================================
468 Error Display
469 ======================================================================*/
471 void
472 UI::receive (Transmitter::Channel chn, const char *str)
474 if (caller_is_ui_thread()) {
475 process_error_message (chn, str);
476 } else {
477 UIRequest* req = get_request (ErrorMessage);
479 if (req == 0) {
480 return;
483 req->chn = chn;
484 req->msg = strdup (str);
486 send_request (req);
490 #define OLD_STYLE_ERRORS 1
492 void
493 UI::process_error_message (Transmitter::Channel chn, const char *str)
495 RefPtr<Style> style;
496 RefPtr<TextBuffer::Tag> ptag;
497 RefPtr<TextBuffer::Tag> mtag;
498 const char *prefix;
499 size_t prefix_len;
500 bool fatal_received = false;
501 #ifndef OLD_STYLE_ERRORS
502 PopUp* popup = new PopUp (WIN_POS_CENTER, 0, true);
503 #endif
505 switch (chn) {
506 case Transmitter::Fatal:
507 prefix = "[FATAL]: ";
508 ptag = fatal_ptag;
509 mtag = fatal_mtag;
510 prefix_len = 9;
511 fatal_received = true;
512 break;
513 case Transmitter::Error:
514 #if OLD_STYLE_ERRORS
515 prefix = "[ERROR]: ";
516 ptag = error_ptag;
517 mtag = error_mtag;
518 prefix_len = 9;
519 #else
520 popup->set_name ("ErrorMessage");
521 popup->set_text (str);
522 popup->touch ();
523 return;
524 #endif
525 break;
526 case Transmitter::Info:
527 #if OLD_STYLE_ERRORS
528 prefix = "[INFO]: ";
529 ptag = info_ptag;
530 mtag = info_mtag;
531 prefix_len = 8;
532 #else
533 popup->set_name ("InfoMessage");
534 popup->set_text (str);
535 popup->touch ();
536 return;
537 #endif
539 break;
540 case Transmitter::Warning:
541 #if OLD_STYLE_ERRORS
542 prefix = "[WARNING]: ";
543 ptag = warning_ptag;
544 mtag = warning_mtag;
545 prefix_len = 11;
546 #else
547 popup->set_name ("WarningMessage");
548 popup->set_text (str);
549 popup->touch ();
550 return;
551 #endif
552 break;
553 default:
554 /* no choice but to use text/console output here */
555 cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n";
556 ::exit (1);
559 errors->text().get_buffer()->begin_user_action();
561 if (fatal_received) {
562 handle_fatal (str);
563 } else {
565 display_message (prefix, prefix_len, ptag, mtag, str);
567 if (!errors->is_visible() && chn != Transmitter::Info) {
568 show_errors ();
572 errors->text().get_buffer()->end_user_action();
575 void
576 UI::show_errors ()
578 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
579 if (!act) {
580 return;
583 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
584 if (tact) {
585 tact->set_active ();
589 void
590 UI::toggle_errors ()
592 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("toggle-log-window"));
593 if (!act) {
594 return;
597 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
599 if (tact->get_active()) {
600 errors->set_position (WIN_POS_MOUSE);
601 errors->show ();
602 } else {
603 errors->hide ();
607 void
608 UI::display_message (const char *prefix, gint /*prefix_len*/, RefPtr<TextBuffer::Tag> ptag, RefPtr<TextBuffer::Tag> mtag, const char *msg)
610 RefPtr<TextBuffer> buffer (errors->text().get_buffer());
612 buffer->insert_with_tag(buffer->end(), prefix, ptag);
613 buffer->insert_with_tag(buffer->end(), msg, mtag);
614 buffer->insert_with_tag(buffer->end(), "\n", mtag);
616 errors->scroll_to_bottom ();
619 void
620 UI::handle_fatal (const char *message)
622 Dialog win;
623 Label label (message);
624 Button quit (_("Press To Exit"));
625 HBox hpacker;
627 win.set_default_size (400, 100);
629 WindowTitle title(Glib::get_application_name());
630 title += ": Fatal Error";
631 win.set_title (title.get_string());
633 win.set_position (WIN_POS_MOUSE);
634 win.set_border_width (12);
636 win.get_vbox()->pack_start (label, true, true);
637 hpacker.pack_start (quit, true, false);
638 win.get_vbox()->pack_start (hpacker, false, false);
640 quit.signal_clicked().connect(mem_fun(*this,&UI::quit));
642 win.show_all ();
643 win.set_modal (true);
645 theMain->run ();
647 _exit (1);
650 void
651 UI::popup_error (const string& text)
653 PopUp *pup;
655 if (!caller_is_ui_thread()) {
656 error << "non-UI threads can't use UI::popup_error"
657 << endmsg;
658 return;
661 pup = new PopUp (WIN_POS_MOUSE, 0, true);
662 pup->set_text (text);
663 pup->touch ();
667 void
668 UI::flush_pending ()
670 if (!caller_is_ui_thread()) {
671 error << "non-UI threads cannot call UI::flush_pending()"
672 << endmsg;
673 return;
676 gtk_main_iteration();
678 while (gtk_events_pending()) {
679 gtk_main_iteration();
683 bool
684 UI::just_hide_it (GdkEventAny */*ev*/, Window *win)
686 win->hide ();
687 return true;
690 Gdk::Color
691 UI::get_color (const string& prompt, bool& picked, const Gdk::Color* initial)
693 Gdk::Color color;
695 ColorSelectionDialog color_dialog (prompt);
697 color_dialog.set_modal (true);
698 color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), false));
699 color_dialog.get_ok_button()->signal_clicked().connect (bind (mem_fun (*this, &UI::color_selection_done), true));
700 color_dialog.signal_delete_event().connect (mem_fun (*this, &UI::color_selection_deleted));
702 if (initial) {
703 color_dialog.get_colorsel()->set_current_color (*initial);
706 color_dialog.show_all ();
707 color_picked = false;
708 picked = false;
710 Main::run();
712 color_dialog.hide_all ();
714 if (color_picked) {
715 Gdk::Color f_rgba = color_dialog.get_colorsel()->get_current_color ();
716 color.set_red(f_rgba.get_red());
717 color.set_green(f_rgba.get_green());
718 color.set_blue(f_rgba.get_blue());
720 picked = true;
723 return color;
726 void
727 UI::color_selection_done (bool status)
729 color_picked = status;
730 Main::quit ();
733 bool
734 UI::color_selection_deleted (GdkEventAny */*ev*/)
736 Main::quit ();
737 return true;