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 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
293 static Gtk::Window* window = 0;
294 assert (r && g && b);
297 window = new Window (WINDOW_TOPLEVEL);
304 foo.set_name (style);
307 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
310 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
314 *r = rc->fg[state].red / 65535.0;
315 *g = rc->fg[state].green / 65535.0;
316 *b = rc->fg[state].blue / 65535.0;
317 } else if (attr == "bg") {
318 *r = rc->bg[state].red / 65535.0;
319 *g = rc->bg[state].green / 65535.0;
320 *b = rc->bg[state].blue / 65535.0;
321 } else if (attr == "base") {
322 *r = rc->base[state].red / 65535.0;
323 *g = rc->base[state].green / 65535.0;
324 *b = rc->base[state].blue / 65535.0;
325 } else if (attr == "text") {
326 *r = rc->text[state].red / 65535.0;
327 *g = rc->text[state].green / 65535.0;
328 *b = rc->text[state].blue / 65535.0;
338 set_color (Gdk::Color& c, int rgb)
340 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
344 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
346 PublicEditor& ed (PublicEditor::instance());
348 if (!key_press_focus_accelerator_handler (*win, ev)) {
350 /* early key press in pre-main-window-dialogs, no editor yet */
353 return ed.on_key_press_event(ev);
360 forward_key_press (GdkEventKey* ev)
362 return PublicEditor::instance().on_key_press_event(ev);
366 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
368 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
369 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
370 GdkKeymapKey *keymapkey = NULL;
373 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
374 if (n_keys !=1) { g_free(keymapkey); return false;}
377 ev.type = GDK_KEY_PRESS;
378 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
379 ev.send_event = FALSE;
384 ev.string = const_cast<gchar*> ("");
385 ev.hardware_keycode = keymapkey[0].keycode;
386 ev.group = keymapkey[0].group;
389 forward_key_press(&ev);
390 ev.type = GDK_KEY_RELEASE;
391 return forward_key_press(&ev);
395 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
397 GtkWindow* win = window.gobj();
398 GtkWidget* focus = gtk_window_get_focus (win);
399 bool special_handling_of_unmodified_accelerators = false;
400 bool allow_activating = true;
401 /* consider all relevant modifiers but not LOCK or SHIFT */
402 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
405 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
406 special_handling_of_unmodified_accelerators = true;
411 /* at one time this appeared to be necessary. As of July 2012, it does not
412 appear to be. if it ever is necessar, figure out if it should apply
416 if (Keyboard::some_magic_widget_has_focus ()) {
417 allow_activating = false;
423 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",
427 special_handling_of_unmodified_accelerators,
428 Keyboard::some_magic_widget_has_focus(),
432 /* This exists to allow us to override the way GTK handles
433 key events. The normal sequence is:
435 a) event is delivered to a GtkWindow
436 b) accelerators/mnemonics are activated
437 c) if (b) didn't handle the event, propagate to
438 the focus widget and/or focus chain
440 The problem with this is that if the accelerators include
441 keys without modifiers, such as the space bar or the
442 letter "e", then pressing the key while typing into
443 a text entry widget results in the accelerator being
444 activated, instead of the desired letter appearing
447 There is no good way of fixing this, but this
448 represents a compromise. The idea is that
449 key events involving modifiers (not Shift)
450 get routed into the activation pathway first, then
451 get propagated to the focus widget if necessary.
453 If the key event doesn't involve modifiers,
454 we deliver to the focus widget first, thus allowing
455 it to get "normal text" without interference
458 Of course, this can also be problematic: if there
459 is a widget with focus, then it will swallow
460 all "normal text" accelerators.
463 if (!special_handling_of_unmodified_accelerators) {
465 /* XXX note that for a brief moment, the conditional above
466 * included "|| (ev->state & mask)" so as to enforce the
467 * implication of special_handling_of_UNMODIFIED_accelerators.
468 * however, this forces any key that GTK doesn't allow and that
469 * we have an alternative (see next comment) for to be
470 * automatically sent through the accel groups activation
471 * pathway, which prevents individual widgets & canvas items
472 * from ever seeing it if is used by a key binding.
474 * specifically, this hid Ctrl-down-arrow from MIDI region
475 * views because it is also bound to an action.
477 * until we have a robust, clean binding system, this
478 * quirk will have to remain in place.
481 /* pretend that certain key events that GTK does not allow
482 to be used as accelerators are actually something that
483 it does allow. but only where there are no modifiers.
486 uint32_t fakekey = ev->keyval;
488 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
489 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
490 ev->keyval, fakekey));
492 GdkModifierType mod = GdkModifierType (ev->state);
494 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
496 /* GTK on OS X is currently (February 2012) setting both
497 the Meta and Mod2 bits in the event modifier state if
498 the Command key is down.
500 gtk_accel_groups_activate() does not invoke any of the logic
501 that gtk_window_activate_key() will that sorts out that stupid
502 state of affairs, and as a result it fails to find a match
503 for the key event and the current set of accelerators.
505 to fix this, if the meta bit is set, remove the mod2 bit
506 from the modifier. this assumes that our bindings use Primary
507 which will have set the meta bit in the accelerator entry.
509 if (mod & GDK_META_MASK) {
510 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
514 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
515 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
521 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
523 /* no special handling or there are modifiers in effect: accelerate first */
525 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
526 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
527 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
529 if (allow_activating) {
530 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
531 if (gtk_window_activate_key (win, ev)) {
532 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
536 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
539 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
541 return gtk_window_propagate_key_event (win, ev);
544 /* no modifiers, propagate first */
546 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
548 if (!gtk_window_propagate_key_event (win, ev)) {
549 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
550 if (allow_activating) {
551 return gtk_window_activate_key (win, ev);
553 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
557 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
561 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
565 Glib::RefPtr<Gdk::Pixbuf>
566 get_xpm (std::string name)
568 if (!xpm_map[name]) {
570 SearchPath spath(ARDOUR::ardour_data_search_path());
572 spath.add_subdirectory_to_paths("pixmaps");
574 std::string data_file_path;
576 if(!find_file_in_search_path (spath, name, data_file_path)) {
577 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
581 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
582 } catch(const Glib::Error& e) {
583 warning << "Caught Glib::Error: " << e.what() << endmsg;
587 return xpm_map[name];
591 get_icon_path (const char* cname)
596 SearchPath spath(ARDOUR::ardour_data_search_path());
598 spath.add_subdirectory_to_paths("icons");
600 std::string data_file_path;
602 if (!find_file_in_search_path (spath, name, data_file_path)) {
603 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
606 return data_file_path;
609 Glib::RefPtr<Gdk::Pixbuf>
610 get_icon (const char* cname)
612 Glib::RefPtr<Gdk::Pixbuf> img;
614 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
615 } catch (const Gdk::PixbufError &e) {
616 cerr << "Caught PixbufError: " << e.what() << endl;
618 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
625 longest (vector<string>& strings)
627 if (strings.empty()) {
631 vector<string>::iterator longest = strings.begin();
632 string::size_type longest_length = (*longest).length();
634 vector<string>::iterator i = longest;
637 while (i != strings.end()) {
639 string::size_type len = (*i).length();
641 if (len > longest_length) {
643 longest_length = len;
653 key_is_legal_for_numeric_entry (guint keyval)
655 /* we assume that this does not change over the life of the process
658 static int comma_decimal = -1;
663 if (comma_decimal < 0) {
664 std::lconv* lc = std::localeconv();
665 if (strchr (lc->decimal_point, ',') != 0) {
677 case GDK_decimalpoint:
678 case GDK_KP_Separator:
708 case GDK_KP_Subtract:
737 set_pango_fontsize ()
739 long val = ARDOUR::Config->get_font_scale();
741 /* FT2 rendering - used by GnomeCanvas, sigh */
743 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
745 /* Cairo rendering, in case there is any */
747 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
753 long val = ARDOUR::Config->get_font_scale();
754 set_pango_fontsize ();
757 gtk_settings_set_long_property (gtk_settings_get_default(),
758 "gtk-xft-dpi", val, "ardour");
759 DPIReset();//Emit Signal
763 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
765 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
766 Gdk::Rectangle monitor_rect;
767 screen->get_monitor_geometry (0, monitor_rect);
769 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
770 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
772 window->resize (w, h);
776 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
778 escape_underscores (string const & s)
781 string::size_type const N = s.length ();
783 for (string::size_type i = 0; i < N; ++i) {
794 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
796 escape_angled_brackets (string const & s)
799 boost::replace_all (o, "<", "<");
800 boost::replace_all (o, ">", ">");
805 unique_random_color (list<Gdk::Color>& used_colors)
813 h = fmod (random(), 360.0);
814 s = (random() % 65535) / 65535.0;
815 v = (random() % 65535) / 65535.0;
817 s = min (0.5, s); /* not too saturated */
818 v = max (0.9, v); /* not too bright */
819 newcolor.set_hsv (h, s, v);
821 if (used_colors.size() == 0) {
822 used_colors.push_back (newcolor);
826 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
828 float rdelta, bdelta, gdelta;
830 rdelta = newcolor.get_red() - c.get_red();
831 bdelta = newcolor.get_blue() - c.get_blue();
832 gdelta = newcolor.get_green() - c.get_green();
834 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
835 /* different enough */
836 used_colors.push_back (newcolor);
841 /* XXX need throttle here to make sure we don't spin for ever */