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
35 #include <gtkmm/window.h>
36 #include <gtkmm/combo.h>
37 #include <gtkmm/label.h>
38 #include <gtkmm/paned.h>
39 #include <gtk/gtkpaned.h>
40 #include <boost/algorithm/string.hpp>
42 #include "pbd/file_utils.h"
44 #include <gtkmm2ext/utils.h>
45 #include "ardour/rc_configuration.h"
46 #include "ardour/filesystem_paths.h"
47 #include "canvas/item.h"
49 #include "ardour_ui.h"
51 #include "public_editor.h"
55 #include "rgb_macros.h"
56 #include "gui_thread.h"
62 using Gtkmm2ext::Keyboard;
64 sigc::signal<void> DPIReset;
67 /** Add an element to a menu, settings its sensitivity.
68 * @param m Menu to add to.
69 * @param e Element to add.
70 * @param s true to make sensitive, false to make insensitive
73 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
77 m.back().set_sensitive (false);
83 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
89 /* xpm2rgb copied from nixieclock, which bore the legend:
91 nixieclock - a nixie desktop timepiece
92 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
94 and was released under the GPL.
98 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
100 static long vals[256], val;
101 uint32_t t, x, y, colors, cpp;
103 unsigned char *savergb, *rgb;
107 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
108 error << string_compose (_("bad XPM header %1"), xpm[0])
113 savergb = rgb = (unsigned char*) malloc (h * w * 3);
115 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
116 for (t = 0; t < colors; ++t) {
117 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
121 // COLORMAP -> RGB CONVERSION
122 // Get low 3 bytes from vals[]
126 for (y = h-1; y > 0; --y) {
128 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
129 val = vals[(int)*p++];
130 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
131 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
132 *(rgb+0) = val & 0xff; // 0:R
140 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
142 static long vals[256], val;
143 uint32_t t, x, y, colors, cpp;
145 unsigned char *savergb, *rgb;
150 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
151 error << string_compose (_("bad XPM header %1"), xpm[0])
156 savergb = rgb = (unsigned char*) malloc (h * w * 4);
158 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
160 if (strstr (xpm[1], "None")) {
161 sscanf (xpm[1], "%c", &transparent);
168 for (; t < colors; ++t) {
169 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
173 // COLORMAP -> RGB CONVERSION
174 // Get low 3 bytes from vals[]
178 for (y = h-1; y > 0; --y) {
182 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
184 if (transparent && (*p++ == transparent)) {
192 *(rgb+3) = alpha; // 3: alpha
193 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
194 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
195 *(rgb+0) = val & 0xff; // 0:R
202 Pango::FontDescription
203 get_font_for_style (string widgetname)
205 Gtk::Window window (WINDOW_TOPLEVEL);
207 Glib::RefPtr<Gtk::Style> style;
210 foobar.set_name (widgetname);
211 foobar.ensure_style();
213 style = foobar.get_style ();
215 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
217 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
221 /* layout inherited its font description from a PangoContext */
223 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
224 pfd = pango_context_get_font_description (ctxt);
225 return Pango::FontDescription (pfd); /* make a copy */
228 return Pango::FontDescription (pfd); /* make a copy */
232 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
234 /* In GTK+2, styles aren't set up correctly if the widget is not
235 attached to a toplevel window that has a screen pointer.
238 static Gtk::Window* window = 0;
241 window = new Window (WINDOW_TOPLEVEL);
248 foo.set_name (style);
251 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
255 r = rc->fg[state].red / 257;
256 g = rc->fg[state].green / 257;
257 b = rc->fg[state].blue / 257;
259 /* what a hack ... "a" is for "active" */
260 if (state == Gtk::STATE_NORMAL && rgba) {
261 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
263 } else if (attr == "bg") {
265 r = rc->bg[state].red / 257;
266 g = rc->bg[state].green / 257;
267 b = rc->bg[state].blue / 257;
268 } else if (attr == "base") {
269 r = rc->base[state].red / 257;
270 g = rc->base[state].green / 257;
271 b = rc->base[state].blue / 257;
272 } else if (attr == "text") {
273 r = rc->text[state].red / 257;
274 g = rc->text[state].green / 257;
275 b = rc->text[state].blue / 257;
278 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
283 if (state == Gtk::STATE_NORMAL && rgba) {
284 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
286 return (uint32_t) RGB_TO_UINT(r,g,b);
291 set_color (Gdk::Color& c, int rgb)
293 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
297 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
299 if (!key_press_focus_accelerator_handler (*win, ev)) {
300 return PublicEditor::instance().on_key_press_event(ev);
307 forward_key_press (GdkEventKey* ev)
309 return PublicEditor::instance().on_key_press_event(ev);
313 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
315 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
316 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
317 GdkKeymapKey *keymapkey = NULL;
320 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
321 if (n_keys !=1) { g_free(keymapkey); return false;}
324 ev.type = GDK_KEY_PRESS;
325 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
326 ev.send_event = FALSE;
331 ev.string = (gchar*) "";
332 ev.hardware_keycode = keymapkey[0].keycode;
333 ev.group = keymapkey[0].group;
336 forward_key_press(&ev);
337 ev.type = GDK_KEY_RELEASE;
338 return forward_key_press(&ev);
342 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
344 GtkWindow* win = window.gobj();
345 GtkWidget* focus = gtk_window_get_focus (win);
346 bool special_handling_of_unmodified_accelerators = false;
347 bool allow_activating = true;
348 /* consider all relevant modifiers but not LOCK or SHIFT */
349 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
352 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
353 special_handling_of_unmodified_accelerators = true;
358 /* at one time this appeared to be necessary. As of July 2012, it does not
359 appear to be. if it ever is necessar, figure out if it should apply
363 if (Keyboard::some_magic_widget_has_focus ()) {
364 allow_activating = false;
370 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",
374 special_handling_of_unmodified_accelerators,
375 Keyboard::some_magic_widget_has_focus(),
379 /* This exists to allow us to override the way GTK handles
380 key events. The normal sequence is:
382 a) event is delivered to a GtkWindow
383 b) accelerators/mnemonics are activated
384 c) if (b) didn't handle the event, propagate to
385 the focus widget and/or focus chain
387 The problem with this is that if the accelerators include
388 keys without modifiers, such as the space bar or the
389 letter "e", then pressing the key while typing into
390 a text entry widget results in the accelerator being
391 activated, instead of the desired letter appearing
394 There is no good way of fixing this, but this
395 represents a compromise. The idea is that
396 key events involving modifiers (not Shift)
397 get routed into the activation pathway first, then
398 get propagated to the focus widget if necessary.
400 If the key event doesn't involve modifiers,
401 we deliver to the focus widget first, thus allowing
402 it to get "normal text" without interference
405 Of course, this can also be problematic: if there
406 is a widget with focus, then it will swallow
407 all "normal text" accelerators.
410 if (!special_handling_of_unmodified_accelerators) {
412 /* XXX note that for a brief moment, the conditional above
413 * included "|| (ev->state & mask)" so as to enforce the
414 * implication of special_handling_of_UNMODIFIED_accelerators.
415 * however, this forces any key that GTK doesn't allow and that
416 * we have an alternative (see next comment) for to be
417 * automatically sent through the accel groups activation
418 * pathway, which prevents individual widgets & canvas items
419 * from ever seeing it if is used by a key binding.
421 * specifically, this hid Ctrl-down-arrow from MIDI region
422 * views because it is also bound to an action.
424 * until we have a robust, clean binding system, this
425 * quirk will have to remain in place.
428 /* pretend that certain key events that GTK does not allow
429 to be used as accelerators are actually something that
430 it does allow. but only where there are no modifiers.
433 uint32_t fakekey = ev->keyval;
435 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
436 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
437 ev->keyval, fakekey));
439 GdkModifierType mod = GdkModifierType (ev->state);
441 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
443 /* GTK on OS X is currently (February 2012) setting both
444 the Meta and Mod2 bits in the event modifier state if
445 the Command key is down.
447 gtk_accel_groups_activate() does not invoke any of the logic
448 that gtk_window_activate_key() will that sorts out that stupid
449 state of affairs, and as a result it fails to find a match
450 for the key event and the current set of accelerators.
452 to fix this, if the meta bit is set, remove the mod2 bit
453 from the modifier. this assumes that our bindings use Primary
454 which will have set the meta bit in the accelerator entry.
456 if (mod & GDK_META_MASK) {
457 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
461 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
462 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
468 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
470 /* no special handling or there are modifiers in effect: accelerate first */
472 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
473 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
474 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
476 if (allow_activating) {
477 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
478 if (gtk_window_activate_key (win, ev)) {
479 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
483 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
486 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
488 return gtk_window_propagate_key_event (win, ev);
491 /* no modifiers, propagate first */
493 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
495 if (!gtk_window_propagate_key_event (win, ev)) {
496 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
497 if (allow_activating) {
498 return gtk_window_activate_key (win, ev);
500 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
504 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
508 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
512 Glib::RefPtr<Gdk::Pixbuf>
513 get_xpm (std::string name)
515 if (!xpm_map[name]) {
517 SearchPath spath(ARDOUR::ardour_data_search_path());
519 spath.add_subdirectory_to_paths("pixmaps");
521 std::string data_file_path;
523 if(!find_file_in_search_path (spath, name, data_file_path)) {
524 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
528 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
529 } catch(const Glib::Error& e) {
530 warning << "Caught Glib::Error: " << e.what() << endmsg;
534 return xpm_map[name];
538 get_icon_path (const char* cname)
543 SearchPath spath(ARDOUR::ardour_data_search_path());
545 spath.add_subdirectory_to_paths("icons");
547 std::string data_file_path;
549 if (!find_file_in_search_path (spath, name, data_file_path)) {
550 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
553 return data_file_path;
556 Glib::RefPtr<Gdk::Pixbuf>
557 get_icon (const char* cname)
559 Glib::RefPtr<Gdk::Pixbuf> img;
561 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
562 } catch (const Gdk::PixbufError &e) {
563 cerr << "Caught PixbufError: " << e.what() << endl;
565 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
572 longest (vector<string>& strings)
574 if (strings.empty()) {
578 vector<string>::iterator longest = strings.begin();
579 string::size_type longest_length = (*longest).length();
581 vector<string>::iterator i = longest;
584 while (i != strings.end()) {
586 string::size_type len = (*i).length();
588 if (len > longest_length) {
590 longest_length = len;
600 key_is_legal_for_numeric_entry (guint keyval)
602 /* we assume that this does not change over the life of the process
605 static int comma_decimal = -1;
610 if (comma_decimal < 0) {
611 std::lconv* lc = std::localeconv();
612 if (strchr (lc->decimal_point, ',') != 0) {
624 case GDK_decimalpoint:
625 case GDK_KP_Separator:
655 case GDK_KP_Subtract:
684 set_pango_fontsize ()
686 long val = ARDOUR::Config->get_font_scale();
688 /* FT2 rendering - used by GnomeCanvas, sigh */
690 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
692 /* Cairo rendering, in case there is any */
694 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
700 long val = ARDOUR::Config->get_font_scale();
701 set_pango_fontsize ();
704 gtk_settings_set_long_property (gtk_settings_get_default(),
705 "gtk-xft-dpi", val, "ardour");
706 DPIReset();//Emit Signal
710 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
712 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
713 Gdk::Rectangle monitor_rect;
714 screen->get_monitor_geometry (0, monitor_rect);
716 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
717 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
719 window->resize (w, h);
723 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
725 escape_underscores (string const & s)
728 string::size_type const N = s.length ();
730 for (string::size_type i = 0; i < N; ++i) {
741 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
743 escape_angled_brackets (string const & s)
746 boost::replace_all (o, "<", "<");
747 boost::replace_all (o, ">", ">");
752 unique_random_color (list<Gdk::Color>& used_colors)
758 /* avoid neon/glowing tones by limiting them to the
759 "inner section" (paler) of a color wheel/circle.
762 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
764 newcolor.set_red (random() % max_saturation);
765 newcolor.set_blue (random() % max_saturation);
766 newcolor.set_green (random() % max_saturation);
768 if (used_colors.size() == 0) {
769 used_colors.push_back (newcolor);
773 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
775 float rdelta, bdelta, gdelta;
777 rdelta = newcolor.get_red() - c.get_red();
778 bdelta = newcolor.get_blue() - c.get_blue();
779 gdelta = newcolor.get_green() - c.get_green();
781 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
782 used_colors.push_back (newcolor);
787 /* XXX need throttle here to make sure we don't spin for ever */