2 Copyright (C) 2003 Paul Davis
4 This program is free software; you an 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.
21 #include "gtk2ardour-config.h"
24 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
25 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
36 #include <gtkmm/window.h>
37 #include <gtkmm/combo.h>
38 #include <gtkmm/label.h>
39 #include <gtkmm/paned.h>
40 #include <gtk/gtkpaned.h>
41 #include <boost/algorithm/string.hpp>
43 #include "pbd/file_utils.h"
45 #include <gtkmm2ext/utils.h>
46 #include "ardour/rc_configuration.h"
47 #include "ardour/filesystem_paths.h"
48 #include "canvas/item.h"
50 #include "ardour_ui.h"
52 #include "public_editor.h"
56 #include "rgb_macros.h"
57 #include "gui_thread.h"
63 using Gtkmm2ext::Keyboard;
65 sigc::signal<void> DPIReset;
67 #ifdef PLATFORM_WINDOWS
68 #define random() rand()
72 /** Add an element to a menu, settings its sensitivity.
73 * @param m Menu to add to.
74 * @param e Element to add.
75 * @param s true to make sensitive, false to make insensitive
78 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
82 m.back().set_sensitive (false);
88 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
94 /* xpm2rgb copied from nixieclock, which bore the legend:
96 nixieclock - a nixie desktop timepiece
97 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
99 and was released under the GPL.
103 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
105 static long vals[256], val;
106 uint32_t t, x, y, colors, cpp;
108 unsigned char *savergb, *rgb;
112 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
113 error << string_compose (_("bad XPM header %1"), xpm[0])
118 savergb = rgb = (unsigned char*) malloc (h * w * 3);
120 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
121 for (t = 0; t < colors; ++t) {
122 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
126 // COLORMAP -> RGB CONVERSION
127 // Get low 3 bytes from vals[]
131 for (y = h-1; y > 0; --y) {
133 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
134 val = vals[(int)*p++];
135 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
136 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
137 *(rgb+0) = val & 0xff; // 0:R
145 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
147 static long vals[256], val;
148 uint32_t t, x, y, colors, cpp;
150 unsigned char *savergb, *rgb;
155 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
156 error << string_compose (_("bad XPM header %1"), xpm[0])
161 savergb = rgb = (unsigned char*) malloc (h * w * 4);
163 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
165 if (strstr (xpm[1], "None")) {
166 sscanf (xpm[1], "%c", &transparent);
173 for (; t < colors; ++t) {
174 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
178 // COLORMAP -> RGB CONVERSION
179 // Get low 3 bytes from vals[]
183 for (y = h-1; y > 0; --y) {
187 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
189 if (transparent && (*p++ == transparent)) {
197 *(rgb+3) = alpha; // 3: alpha
198 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
199 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
200 *(rgb+0) = val & 0xff; // 0:R
207 Pango::FontDescription
208 get_font_for_style (string widgetname)
210 Gtk::Window window (WINDOW_TOPLEVEL);
212 Glib::RefPtr<Gtk::Style> style;
215 foobar.set_name (widgetname);
216 foobar.ensure_style();
218 style = foobar.get_style ();
220 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
222 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
226 /* layout inherited its font description from a PangoContext */
228 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
229 pfd = pango_context_get_font_description (ctxt);
230 return Pango::FontDescription (pfd); /* make a copy */
233 return Pango::FontDescription (pfd); /* make a copy */
237 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
239 /* In GTK+2, styles aren't set up correctly if the widget is not
240 attached to a toplevel window that has a screen pointer.
243 static Gtk::Window* window = 0;
246 window = new Window (WINDOW_TOPLEVEL);
253 foo.set_name (style);
256 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
260 r = rc->fg[state].red / 257;
261 g = rc->fg[state].green / 257;
262 b = rc->fg[state].blue / 257;
264 /* what a hack ... "a" is for "active" */
265 if (state == Gtk::STATE_NORMAL && rgba) {
266 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
268 } else if (attr == "bg") {
270 r = rc->bg[state].red / 257;
271 g = rc->bg[state].green / 257;
272 b = rc->bg[state].blue / 257;
273 } else if (attr == "base") {
274 r = rc->base[state].red / 257;
275 g = rc->base[state].green / 257;
276 b = rc->base[state].blue / 257;
277 } else if (attr == "text") {
278 r = rc->text[state].red / 257;
279 g = rc->text[state].green / 257;
280 b = rc->text[state].blue / 257;
283 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
288 if (state == Gtk::STATE_NORMAL && rgba) {
289 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
291 return (uint32_t) RGB_TO_UINT(r,g,b);
296 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
298 static Gtk::Window* window = 0;
299 assert (r && g && b);
302 window = new Window (WINDOW_TOPLEVEL);
309 foo.set_name (style);
312 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
315 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
319 *r = rc->fg[state].red / 65535.0;
320 *g = rc->fg[state].green / 65535.0;
321 *b = rc->fg[state].blue / 65535.0;
322 } else if (attr == "bg") {
323 *r = rc->bg[state].red / 65535.0;
324 *g = rc->bg[state].green / 65535.0;
325 *b = rc->bg[state].blue / 65535.0;
326 } else if (attr == "base") {
327 *r = rc->base[state].red / 65535.0;
328 *g = rc->base[state].green / 65535.0;
329 *b = rc->base[state].blue / 65535.0;
330 } else if (attr == "text") {
331 *r = rc->text[state].red / 65535.0;
332 *g = rc->text[state].green / 65535.0;
333 *b = rc->text[state].blue / 65535.0;
343 set_color (Gdk::Color& c, int rgb)
345 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
349 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
351 PublicEditor& ed (PublicEditor::instance());
353 if (!key_press_focus_accelerator_handler (*win, ev)) {
355 /* early key press in pre-main-window-dialogs, no editor yet */
358 return ed.on_key_press_event(ev);
365 forward_key_press (GdkEventKey* ev)
367 return PublicEditor::instance().on_key_press_event(ev);
371 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
373 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
374 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
375 GdkKeymapKey *keymapkey = NULL;
378 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
379 if (n_keys !=1) { g_free(keymapkey); return false;}
382 ev.type = GDK_KEY_PRESS;
383 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
384 ev.send_event = FALSE;
389 ev.string = const_cast<gchar*> ("");
390 ev.hardware_keycode = keymapkey[0].keycode;
391 ev.group = keymapkey[0].group;
394 forward_key_press(&ev);
395 ev.type = GDK_KEY_RELEASE;
396 return forward_key_press(&ev);
400 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
402 GtkWindow* win = window.gobj();
403 GtkWidget* focus = gtk_window_get_focus (win);
404 bool special_handling_of_unmodified_accelerators = false;
405 bool allow_activating = true;
406 /* consider all relevant modifiers but not LOCK or SHIFT */
407 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
410 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
411 special_handling_of_unmodified_accelerators = true;
416 /* at one time this appeared to be necessary. As of July 2012, it does not
417 appear to be. if it ever is necessar, figure out if it should apply
421 if (Keyboard::some_magic_widget_has_focus ()) {
422 allow_activating = false;
428 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 Key event: code = %2 state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
432 special_handling_of_unmodified_accelerators,
433 Keyboard::some_magic_widget_has_focus(),
437 /* This exists to allow us to override the way GTK handles
438 key events. The normal sequence is:
440 a) event is delivered to a GtkWindow
441 b) accelerators/mnemonics are activated
442 c) if (b) didn't handle the event, propagate to
443 the focus widget and/or focus chain
445 The problem with this is that if the accelerators include
446 keys without modifiers, such as the space bar or the
447 letter "e", then pressing the key while typing into
448 a text entry widget results in the accelerator being
449 activated, instead of the desired letter appearing
452 There is no good way of fixing this, but this
453 represents a compromise. The idea is that
454 key events involving modifiers (not Shift)
455 get routed into the activation pathway first, then
456 get propagated to the focus widget if necessary.
458 If the key event doesn't involve modifiers,
459 we deliver to the focus widget first, thus allowing
460 it to get "normal text" without interference
463 Of course, this can also be problematic: if there
464 is a widget with focus, then it will swallow
465 all "normal text" accelerators.
468 if (!special_handling_of_unmodified_accelerators) {
470 /* XXX note that for a brief moment, the conditional above
471 * included "|| (ev->state & mask)" so as to enforce the
472 * implication of special_handling_of_UNMODIFIED_accelerators.
473 * however, this forces any key that GTK doesn't allow and that
474 * we have an alternative (see next comment) for to be
475 * automatically sent through the accel groups activation
476 * pathway, which prevents individual widgets & canvas items
477 * from ever seeing it if is used by a key binding.
479 * specifically, this hid Ctrl-down-arrow from MIDI region
480 * views because it is also bound to an action.
482 * until we have a robust, clean binding system, this
483 * quirk will have to remain in place.
486 /* pretend that certain key events that GTK does not allow
487 to be used as accelerators are actually something that
488 it does allow. but only where there are no modifiers.
491 uint32_t fakekey = ev->keyval;
493 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
494 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
495 ev->keyval, fakekey));
497 GdkModifierType mod = GdkModifierType (ev->state);
499 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
501 /* GTK on OS X is currently (February 2012) setting both
502 the Meta and Mod2 bits in the event modifier state if
503 the Command key is down.
505 gtk_accel_groups_activate() does not invoke any of the logic
506 that gtk_window_activate_key() will that sorts out that stupid
507 state of affairs, and as a result it fails to find a match
508 for the key event and the current set of accelerators.
510 to fix this, if the meta bit is set, remove the mod2 bit
511 from the modifier. this assumes that our bindings use Primary
512 which will have set the meta bit in the accelerator entry.
514 if (mod & GDK_META_MASK) {
515 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
519 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
520 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
526 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
528 /* no special handling or there are modifiers in effect: accelerate first */
530 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
531 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
532 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
534 if (allow_activating) {
535 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
536 if (gtk_window_activate_key (win, ev)) {
537 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
541 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
544 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
546 return gtk_window_propagate_key_event (win, ev);
549 /* no modifiers, propagate first */
551 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
553 if (!gtk_window_propagate_key_event (win, ev)) {
554 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
555 if (allow_activating) {
556 return gtk_window_activate_key (win, ev);
558 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
562 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
566 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
570 Glib::RefPtr<Gdk::Pixbuf>
571 get_xpm (std::string name)
573 if (!xpm_map[name]) {
575 Searchpath spath(ARDOUR::ardour_data_search_path());
577 spath.add_subdirectory_to_paths("pixmaps");
579 std::string data_file_path;
581 if(!find_file_in_search_path (spath, name, data_file_path)) {
582 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
586 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
587 } catch(const Glib::Error& e) {
588 warning << "Caught Glib::Error: " << e.what() << endmsg;
592 return xpm_map[name];
596 get_icon_path (const char* cname)
601 Searchpath spath(ARDOUR::ardour_data_search_path());
603 spath.add_subdirectory_to_paths("icons");
605 std::string data_file_path;
607 if (!find_file_in_search_path (spath, name, data_file_path)) {
608 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
611 return data_file_path;
614 Glib::RefPtr<Gdk::Pixbuf>
615 get_icon (const char* cname)
617 Glib::RefPtr<Gdk::Pixbuf> img;
619 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
620 } catch (const Gdk::PixbufError &e) {
621 cerr << "Caught PixbufError: " << e.what() << endl;
623 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
630 longest (vector<string>& strings)
632 if (strings.empty()) {
636 vector<string>::iterator longest = strings.begin();
637 string::size_type longest_length = (*longest).length();
639 vector<string>::iterator i = longest;
642 while (i != strings.end()) {
644 string::size_type len = (*i).length();
646 if (len > longest_length) {
648 longest_length = len;
658 key_is_legal_for_numeric_entry (guint keyval)
660 /* we assume that this does not change over the life of the process
663 static int comma_decimal = -1;
668 if (comma_decimal < 0) {
669 std::lconv* lc = std::localeconv();
670 if (strchr (lc->decimal_point, ',') != 0) {
682 case GDK_decimalpoint:
683 case GDK_KP_Separator:
713 case GDK_KP_Subtract:
742 set_pango_fontsize ()
744 long val = ARDOUR::Config->get_font_scale();
746 /* FT2 rendering - used by GnomeCanvas, sigh */
748 #ifndef PLATFORM_WINDOWS
749 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
752 /* Cairo rendering, in case there is any */
754 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
760 long val = ARDOUR::Config->get_font_scale();
761 set_pango_fontsize ();
764 gtk_settings_set_long_property (gtk_settings_get_default(),
765 "gtk-xft-dpi", val, "ardour");
766 DPIReset();//Emit Signal
770 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
772 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
773 Gdk::Rectangle monitor_rect;
774 screen->get_monitor_geometry (0, monitor_rect);
776 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
777 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
779 window->resize (w, h);
783 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
785 escape_underscores (string const & s)
788 string::size_type const N = s.length ();
790 for (string::size_type i = 0; i < N; ++i) {
801 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
803 escape_angled_brackets (string const & s)
806 boost::replace_all (o, "<", "<");
807 boost::replace_all (o, ">", ">");
812 unique_random_color (list<Gdk::Color>& used_colors)
820 h = fmod (random(), 360.0);
821 s = (random() % 65535) / 65535.0;
822 v = (random() % 65535) / 65535.0;
824 s = min (0.5, s); /* not too saturated */
825 v = max (0.9, v); /* not too bright */
826 newcolor.set_hsv (h, s, v);
828 if (used_colors.size() == 0) {
829 used_colors.push_back (newcolor);
833 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
835 float rdelta, bdelta, gdelta;
837 rdelta = newcolor.get_red() - c.get_red();
838 bdelta = newcolor.get_blue() - c.get_blue();
839 gdelta = newcolor.get_green() - c.get_green();
841 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
842 /* different enough */
843 used_colors.push_back (newcolor);
848 /* XXX need throttle here to make sure we don't spin for ever */
853 rate_as_string (float r)
856 if (fmod (r, 1000.0f)) {
857 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
859 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);