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;
68 /** Add an element to a menu, settings its sensitivity.
69 * @param m Menu to add to.
70 * @param e Element to add.
71 * @param s true to make sensitive, false to make insensitive
74 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
78 m.back().set_sensitive (false);
84 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
90 /* xpm2rgb copied from nixieclock, which bore the legend:
92 nixieclock - a nixie desktop timepiece
93 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
95 and was released under the GPL.
99 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
101 static long vals[256], val;
102 uint32_t t, x, y, colors, cpp;
104 unsigned char *savergb, *rgb;
108 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
109 error << string_compose (_("bad XPM header %1"), xpm[0])
114 savergb = rgb = (unsigned char*) malloc (h * w * 3);
116 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
117 for (t = 0; t < colors; ++t) {
118 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
122 // COLORMAP -> RGB CONVERSION
123 // Get low 3 bytes from vals[]
127 for (y = h-1; y > 0; --y) {
129 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
130 val = vals[(int)*p++];
131 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
132 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
133 *(rgb+0) = val & 0xff; // 0:R
141 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
143 static long vals[256], val;
144 uint32_t t, x, y, colors, cpp;
146 unsigned char *savergb, *rgb;
151 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
152 error << string_compose (_("bad XPM header %1"), xpm[0])
157 savergb = rgb = (unsigned char*) malloc (h * w * 4);
159 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
161 if (strstr (xpm[1], "None")) {
162 sscanf (xpm[1], "%c", &transparent);
169 for (; t < colors; ++t) {
170 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
174 // COLORMAP -> RGB CONVERSION
175 // Get low 3 bytes from vals[]
179 for (y = h-1; y > 0; --y) {
183 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
185 if (transparent && (*p++ == transparent)) {
193 *(rgb+3) = alpha; // 3: alpha
194 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
195 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
196 *(rgb+0) = val & 0xff; // 0:R
203 Pango::FontDescription
204 get_font_for_style (string widgetname)
206 Gtk::Window window (WINDOW_TOPLEVEL);
208 Glib::RefPtr<Gtk::Style> style;
211 foobar.set_name (widgetname);
212 foobar.ensure_style();
214 style = foobar.get_style ();
216 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
218 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
222 /* layout inherited its font description from a PangoContext */
224 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
225 pfd = pango_context_get_font_description (ctxt);
226 return Pango::FontDescription (pfd); /* make a copy */
229 return Pango::FontDescription (pfd); /* make a copy */
233 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
235 /* In GTK+2, styles aren't set up correctly if the widget is not
236 attached to a toplevel window that has a screen pointer.
239 static Gtk::Window* window = 0;
242 window = new Window (WINDOW_TOPLEVEL);
249 foo.set_name (style);
252 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
256 r = rc->fg[state].red / 257;
257 g = rc->fg[state].green / 257;
258 b = rc->fg[state].blue / 257;
260 /* what a hack ... "a" is for "active" */
261 if (state == Gtk::STATE_NORMAL && rgba) {
262 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
264 } else if (attr == "bg") {
266 r = rc->bg[state].red / 257;
267 g = rc->bg[state].green / 257;
268 b = rc->bg[state].blue / 257;
269 } else if (attr == "base") {
270 r = rc->base[state].red / 257;
271 g = rc->base[state].green / 257;
272 b = rc->base[state].blue / 257;
273 } else if (attr == "text") {
274 r = rc->text[state].red / 257;
275 g = rc->text[state].green / 257;
276 b = rc->text[state].blue / 257;
279 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
284 if (state == Gtk::STATE_NORMAL && rgba) {
285 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
287 return (uint32_t) RGB_TO_UINT(r,g,b);
292 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
294 static Gtk::Window* window = 0;
295 assert (r && g && b);
298 window = new Window (WINDOW_TOPLEVEL);
305 foo.set_name (style);
308 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
311 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
315 *r = rc->fg[state].red / 65535.0;
316 *g = rc->fg[state].green / 65535.0;
317 *b = rc->fg[state].blue / 65535.0;
318 } else if (attr == "bg") {
319 *r = rc->bg[state].red / 65535.0;
320 *g = rc->bg[state].green / 65535.0;
321 *b = rc->bg[state].blue / 65535.0;
322 } else if (attr == "base") {
323 *r = rc->base[state].red / 65535.0;
324 *g = rc->base[state].green / 65535.0;
325 *b = rc->base[state].blue / 65535.0;
326 } else if (attr == "text") {
327 *r = rc->text[state].red / 65535.0;
328 *g = rc->text[state].green / 65535.0;
329 *b = rc->text[state].blue / 65535.0;
339 set_color (Gdk::Color& c, int rgb)
341 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
345 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
347 PublicEditor& ed (PublicEditor::instance());
349 if (!key_press_focus_accelerator_handler (*win, ev)) {
351 /* early key press in pre-main-window-dialogs, no editor yet */
354 return ed.on_key_press_event(ev);
361 forward_key_press (GdkEventKey* ev)
363 return PublicEditor::instance().on_key_press_event(ev);
367 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
369 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
370 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
371 GdkKeymapKey *keymapkey = NULL;
374 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
375 if (n_keys !=1) { g_free(keymapkey); return false;}
378 ev.type = GDK_KEY_PRESS;
379 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
380 ev.send_event = FALSE;
385 ev.string = const_cast<gchar*> ("");
386 ev.hardware_keycode = keymapkey[0].keycode;
387 ev.group = keymapkey[0].group;
390 forward_key_press(&ev);
391 ev.type = GDK_KEY_RELEASE;
392 return forward_key_press(&ev);
396 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
398 GtkWindow* win = window.gobj();
399 GtkWidget* focus = gtk_window_get_focus (win);
400 bool special_handling_of_unmodified_accelerators = false;
401 bool allow_activating = true;
402 /* consider all relevant modifiers but not LOCK or SHIFT */
403 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
406 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
407 special_handling_of_unmodified_accelerators = true;
412 /* at one time this appeared to be necessary. As of July 2012, it does not
413 appear to be. if it ever is necessar, figure out if it should apply
417 if (Keyboard::some_magic_widget_has_focus ()) {
418 allow_activating = false;
424 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",
428 special_handling_of_unmodified_accelerators,
429 Keyboard::some_magic_widget_has_focus(),
433 /* This exists to allow us to override the way GTK handles
434 key events. The normal sequence is:
436 a) event is delivered to a GtkWindow
437 b) accelerators/mnemonics are activated
438 c) if (b) didn't handle the event, propagate to
439 the focus widget and/or focus chain
441 The problem with this is that if the accelerators include
442 keys without modifiers, such as the space bar or the
443 letter "e", then pressing the key while typing into
444 a text entry widget results in the accelerator being
445 activated, instead of the desired letter appearing
448 There is no good way of fixing this, but this
449 represents a compromise. The idea is that
450 key events involving modifiers (not Shift)
451 get routed into the activation pathway first, then
452 get propagated to the focus widget if necessary.
454 If the key event doesn't involve modifiers,
455 we deliver to the focus widget first, thus allowing
456 it to get "normal text" without interference
459 Of course, this can also be problematic: if there
460 is a widget with focus, then it will swallow
461 all "normal text" accelerators.
464 if (!special_handling_of_unmodified_accelerators) {
466 /* XXX note that for a brief moment, the conditional above
467 * included "|| (ev->state & mask)" so as to enforce the
468 * implication of special_handling_of_UNMODIFIED_accelerators.
469 * however, this forces any key that GTK doesn't allow and that
470 * we have an alternative (see next comment) for to be
471 * automatically sent through the accel groups activation
472 * pathway, which prevents individual widgets & canvas items
473 * from ever seeing it if is used by a key binding.
475 * specifically, this hid Ctrl-down-arrow from MIDI region
476 * views because it is also bound to an action.
478 * until we have a robust, clean binding system, this
479 * quirk will have to remain in place.
482 /* pretend that certain key events that GTK does not allow
483 to be used as accelerators are actually something that
484 it does allow. but only where there are no modifiers.
487 uint32_t fakekey = ev->keyval;
489 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
490 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
491 ev->keyval, fakekey));
493 GdkModifierType mod = GdkModifierType (ev->state);
495 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
497 /* GTK on OS X is currently (February 2012) setting both
498 the Meta and Mod2 bits in the event modifier state if
499 the Command key is down.
501 gtk_accel_groups_activate() does not invoke any of the logic
502 that gtk_window_activate_key() will that sorts out that stupid
503 state of affairs, and as a result it fails to find a match
504 for the key event and the current set of accelerators.
506 to fix this, if the meta bit is set, remove the mod2 bit
507 from the modifier. this assumes that our bindings use Primary
508 which will have set the meta bit in the accelerator entry.
510 if (mod & GDK_META_MASK) {
511 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
515 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
516 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
522 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
524 /* no special handling or there are modifiers in effect: accelerate first */
526 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
527 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
528 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
530 if (allow_activating) {
531 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
532 if (gtk_window_activate_key (win, ev)) {
533 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
537 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
540 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
542 return gtk_window_propagate_key_event (win, ev);
545 /* no modifiers, propagate first */
547 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
549 if (!gtk_window_propagate_key_event (win, ev)) {
550 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
551 if (allow_activating) {
552 return gtk_window_activate_key (win, ev);
554 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
558 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
562 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
566 Glib::RefPtr<Gdk::Pixbuf>
567 get_xpm (std::string name)
569 if (!xpm_map[name]) {
571 SearchPath spath(ARDOUR::ardour_data_search_path());
573 spath.add_subdirectory_to_paths("pixmaps");
575 std::string data_file_path;
577 if(!find_file_in_search_path (spath, name, data_file_path)) {
578 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
582 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
583 } catch(const Glib::Error& e) {
584 warning << "Caught Glib::Error: " << e.what() << endmsg;
588 return xpm_map[name];
592 get_icon_path (const char* cname)
597 SearchPath spath(ARDOUR::ardour_data_search_path());
599 spath.add_subdirectory_to_paths("icons");
601 std::string data_file_path;
603 if (!find_file_in_search_path (spath, name, data_file_path)) {
604 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
607 return data_file_path;
610 Glib::RefPtr<Gdk::Pixbuf>
611 get_icon (const char* cname)
613 Glib::RefPtr<Gdk::Pixbuf> img;
615 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
616 } catch (const Gdk::PixbufError &e) {
617 cerr << "Caught PixbufError: " << e.what() << endl;
619 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
626 longest (vector<string>& strings)
628 if (strings.empty()) {
632 vector<string>::iterator longest = strings.begin();
633 string::size_type longest_length = (*longest).length();
635 vector<string>::iterator i = longest;
638 while (i != strings.end()) {
640 string::size_type len = (*i).length();
642 if (len > longest_length) {
644 longest_length = len;
654 key_is_legal_for_numeric_entry (guint keyval)
656 /* we assume that this does not change over the life of the process
659 static int comma_decimal = -1;
664 if (comma_decimal < 0) {
665 std::lconv* lc = std::localeconv();
666 if (strchr (lc->decimal_point, ',') != 0) {
678 case GDK_decimalpoint:
679 case GDK_KP_Separator:
709 case GDK_KP_Subtract:
738 set_pango_fontsize ()
740 long val = ARDOUR::Config->get_font_scale();
742 /* FT2 rendering - used by GnomeCanvas, sigh */
744 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
746 /* Cairo rendering, in case there is any */
748 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
754 long val = ARDOUR::Config->get_font_scale();
755 set_pango_fontsize ();
758 gtk_settings_set_long_property (gtk_settings_get_default(),
759 "gtk-xft-dpi", val, "ardour");
760 DPIReset();//Emit Signal
764 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
766 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
767 Gdk::Rectangle monitor_rect;
768 screen->get_monitor_geometry (0, monitor_rect);
770 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
771 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
773 window->resize (w, h);
777 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
779 escape_underscores (string const & s)
782 string::size_type const N = s.length ();
784 for (string::size_type i = 0; i < N; ++i) {
795 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
797 escape_angled_brackets (string const & s)
800 boost::replace_all (o, "<", "<");
801 boost::replace_all (o, ">", ">");
806 unique_random_color (list<Gdk::Color>& used_colors)
814 h = fmod (random(), 360.0);
815 s = (random() % 65535) / 65535.0;
816 v = (random() % 65535) / 65535.0;
818 s = min (0.5, s); /* not too saturated */
819 v = max (0.9, v); /* not too bright */
820 newcolor.set_hsv (h, s, v);
822 if (used_colors.size() == 0) {
823 used_colors.push_back (newcolor);
827 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
829 float rdelta, bdelta, gdelta;
831 rdelta = newcolor.get_red() - c.get_red();
832 bdelta = newcolor.get_blue() - c.get_blue();
833 gdelta = newcolor.get_green() - c.get_green();
835 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
836 /* different enough */
837 used_colors.push_back (newcolor);
842 /* XXX need throttle here to make sure we don't spin for ever */
847 rate_as_string (float r)
850 if (fmod (r, 1000.0f)) {
851 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
853 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);