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"
49 #include "canvas/item.h"
50 #include "canvas/utils.h"
52 #include "ardour_ui.h"
54 #include "public_editor.h"
58 #include "rgb_macros.h"
59 #include "gui_thread.h"
65 using Gtkmm2ext::Keyboard;
67 sigc::signal<void> DPIReset;
69 #ifdef PLATFORM_WINDOWS
70 #define random() rand()
74 /** Add an element to a menu, settings its sensitivity.
75 * @param m Menu to add to.
76 * @param e Element to add.
77 * @param s true to make sensitive, false to make insensitive
80 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
84 m.back().set_sensitive (false);
90 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
96 /* xpm2rgb copied from nixieclock, which bore the legend:
98 nixieclock - a nixie desktop timepiece
99 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
101 and was released under the GPL.
105 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
107 static long vals[256], val;
108 uint32_t t, x, y, colors, cpp;
110 unsigned char *savergb, *rgb;
114 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
115 error << string_compose (_("bad XPM header %1"), xpm[0])
120 savergb = rgb = (unsigned char*) malloc (h * w * 3);
122 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
123 for (t = 0; t < colors; ++t) {
124 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
128 // COLORMAP -> RGB CONVERSION
129 // Get low 3 bytes from vals[]
133 for (y = h-1; y > 0; --y) {
135 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
136 val = vals[(int)*p++];
137 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
138 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
139 *(rgb+0) = val & 0xff; // 0:R
147 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
149 static long vals[256], val;
150 uint32_t t, x, y, colors, cpp;
152 unsigned char *savergb, *rgb;
157 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
158 error << string_compose (_("bad XPM header %1"), xpm[0])
163 savergb = rgb = (unsigned char*) malloc (h * w * 4);
165 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
167 if (strstr (xpm[1], "None")) {
168 sscanf (xpm[1], "%c", &transparent);
175 for (; t < colors; ++t) {
176 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
180 // COLORMAP -> RGB CONVERSION
181 // Get low 3 bytes from vals[]
185 for (y = h-1; y > 0; --y) {
189 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
191 if (transparent && (*p++ == transparent)) {
199 *(rgb+3) = alpha; // 3: alpha
200 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
201 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
202 *(rgb+0) = val & 0xff; // 0:R
209 Pango::FontDescription
210 get_font_for_style (string widgetname)
212 Gtk::Window window (WINDOW_TOPLEVEL);
214 Glib::RefPtr<Gtk::Style> style;
217 foobar.set_name (widgetname);
218 foobar.ensure_style();
220 style = foobar.get_style ();
222 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
224 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
228 /* layout inherited its font description from a PangoContext */
230 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
231 pfd = pango_context_get_font_description (ctxt);
232 return Pango::FontDescription (pfd); /* make a copy */
235 return Pango::FontDescription (pfd); /* make a copy */
239 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
241 /* In GTK+2, styles aren't set up correctly if the widget is not
242 attached to a toplevel window that has a screen pointer.
245 static Gtk::Window* window = 0;
248 window = new Window (WINDOW_TOPLEVEL);
255 foo.set_name (style);
258 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
262 r = rc->fg[state].red / 257;
263 g = rc->fg[state].green / 257;
264 b = rc->fg[state].blue / 257;
266 /* what a hack ... "a" is for "active" */
267 if (state == Gtk::STATE_NORMAL && rgba) {
268 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
270 } else if (attr == "bg") {
272 r = rc->bg[state].red / 257;
273 g = rc->bg[state].green / 257;
274 b = rc->bg[state].blue / 257;
275 } else if (attr == "base") {
276 r = rc->base[state].red / 257;
277 g = rc->base[state].green / 257;
278 b = rc->base[state].blue / 257;
279 } else if (attr == "text") {
280 r = rc->text[state].red / 257;
281 g = rc->text[state].green / 257;
282 b = rc->text[state].blue / 257;
285 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
290 if (state == Gtk::STATE_NORMAL && rgba) {
291 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
293 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
298 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
300 static Gtk::Window* window = 0;
301 assert (r && g && b);
304 window = new Window (WINDOW_TOPLEVEL);
311 foo.set_name (style);
314 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
317 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
321 *r = rc->fg[state].red / 65535.0;
322 *g = rc->fg[state].green / 65535.0;
323 *b = rc->fg[state].blue / 65535.0;
324 } else if (attr == "bg") {
325 *r = rc->bg[state].red / 65535.0;
326 *g = rc->bg[state].green / 65535.0;
327 *b = rc->bg[state].blue / 65535.0;
328 } else if (attr == "base") {
329 *r = rc->base[state].red / 65535.0;
330 *g = rc->base[state].green / 65535.0;
331 *b = rc->base[state].blue / 65535.0;
332 } else if (attr == "text") {
333 *r = rc->text[state].red / 65535.0;
334 *g = rc->text[state].green / 65535.0;
335 *b = rc->text[state].blue / 65535.0;
345 set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
347 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
350 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
354 set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
356 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
359 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
363 gdk_color_to_rgba (Gdk::Color const& c)
365 /* since alpha value is not available from a Gdk::Color, it is
366 hardcoded as 0xff (aka 255 or 1.0)
369 const uint32_t r = c.get_red_p () * 255.0;
370 const uint32_t g = c.get_green_p () * 255.0;
371 const uint32_t b = c.get_blue_p () * 255.0;
372 const uint32_t a = 0xff;
374 return RGBA_TO_UINT (r,g,b,a);
378 contrasting_text_color (uint32_t c)
381 ArdourCanvas::color_to_rgba (c, r, g, b, a);
383 const double black_r = 0.0;
384 const double black_g = 0.0;
385 const double black_b = 0.0;
387 const double white_r = 1.0;
388 const double white_g = 1.0;
389 const double white_b = 1.0;
391 /* Use W3C contrast guideline calculation */
393 double white_contrast = (max (r, white_r) - min (r, white_r)) +
394 (max (g, white_g) - min (g, white_g)) +
395 (max (b, white_b) - min (b, white_b));
397 double black_contrast = (max (r, black_r) - min (r, black_r)) +
398 (max (g, black_g) - min (g, black_g)) +
399 (max (b, black_b) - min (b, black_b));
401 if (white_contrast > black_contrast) {
403 return ArdourCanvas::rgba_to_color (1.0, 1.0, 1.0, 1.0);
406 return ArdourCanvas::rgba_to_color (0.0, 0.0, 0.0, 1.0);
411 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
413 PublicEditor& ed (PublicEditor::instance());
415 if (!key_press_focus_accelerator_handler (*win, ev)) {
417 /* early key press in pre-main-window-dialogs, no editor yet */
420 return ed.on_key_press_event(ev);
427 forward_key_press (GdkEventKey* ev)
429 return PublicEditor::instance().on_key_press_event(ev);
433 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
435 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
436 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
437 GdkKeymapKey *keymapkey = NULL;
440 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
441 if (n_keys !=1) { g_free(keymapkey); return false;}
444 ev.type = GDK_KEY_PRESS;
445 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
446 ev.send_event = FALSE;
451 ev.string = const_cast<gchar*> ("");
452 ev.hardware_keycode = keymapkey[0].keycode;
453 ev.group = keymapkey[0].group;
456 forward_key_press(&ev);
457 ev.type = GDK_KEY_RELEASE;
458 return forward_key_press(&ev);
462 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
464 GtkWindow* win = window.gobj();
465 GtkWidget* focus = gtk_window_get_focus (win);
466 bool special_handling_of_unmodified_accelerators = false;
467 bool allow_activating = true;
468 /* consider all relevant modifiers but not LOCK or SHIFT */
469 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
472 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
473 special_handling_of_unmodified_accelerators = true;
478 /* at one time this appeared to be necessary. As of July 2012, it does not
479 appear to be. if it ever is necessar, figure out if it should apply
483 if (Keyboard::some_magic_widget_has_focus ()) {
484 allow_activating = false;
490 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",
494 special_handling_of_unmodified_accelerators,
495 Keyboard::some_magic_widget_has_focus(),
499 /* This exists to allow us to override the way GTK handles
500 key events. The normal sequence is:
502 a) event is delivered to a GtkWindow
503 b) accelerators/mnemonics are activated
504 c) if (b) didn't handle the event, propagate to
505 the focus widget and/or focus chain
507 The problem with this is that if the accelerators include
508 keys without modifiers, such as the space bar or the
509 letter "e", then pressing the key while typing into
510 a text entry widget results in the accelerator being
511 activated, instead of the desired letter appearing
514 There is no good way of fixing this, but this
515 represents a compromise. The idea is that
516 key events involving modifiers (not Shift)
517 get routed into the activation pathway first, then
518 get propagated to the focus widget if necessary.
520 If the key event doesn't involve modifiers,
521 we deliver to the focus widget first, thus allowing
522 it to get "normal text" without interference
525 Of course, this can also be problematic: if there
526 is a widget with focus, then it will swallow
527 all "normal text" accelerators.
530 if (!special_handling_of_unmodified_accelerators) {
532 /* XXX note that for a brief moment, the conditional above
533 * included "|| (ev->state & mask)" so as to enforce the
534 * implication of special_handling_of_UNMODIFIED_accelerators.
535 * however, this forces any key that GTK doesn't allow and that
536 * we have an alternative (see next comment) for to be
537 * automatically sent through the accel groups activation
538 * pathway, which prevents individual widgets & canvas items
539 * from ever seeing it if is used by a key binding.
541 * specifically, this hid Ctrl-down-arrow from MIDI region
542 * views because it is also bound to an action.
544 * until we have a robust, clean binding system, this
545 * quirk will have to remain in place.
548 /* pretend that certain key events that GTK does not allow
549 to be used as accelerators are actually something that
550 it does allow. but only where there are no modifiers.
553 uint32_t fakekey = ev->keyval;
555 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
556 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
557 ev->keyval, fakekey));
559 GdkModifierType mod = GdkModifierType (ev->state);
561 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
563 /* GTK on OS X is currently (February 2012) setting both
564 the Meta and Mod2 bits in the event modifier state if
565 the Command key is down.
567 gtk_accel_groups_activate() does not invoke any of the logic
568 that gtk_window_activate_key() will that sorts out that stupid
569 state of affairs, and as a result it fails to find a match
570 for the key event and the current set of accelerators.
572 to fix this, if the meta bit is set, remove the mod2 bit
573 from the modifier. this assumes that our bindings use Primary
574 which will have set the meta bit in the accelerator entry.
576 if (mod & GDK_META_MASK) {
577 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
581 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
582 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
588 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
590 /* no special handling or there are modifiers in effect: accelerate first */
592 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
593 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
594 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
596 if (allow_activating) {
597 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
598 if (gtk_window_activate_key (win, ev)) {
599 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
603 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
606 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
608 return gtk_window_propagate_key_event (win, ev);
611 /* no modifiers, propagate first */
613 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
615 if (!gtk_window_propagate_key_event (win, ev)) {
616 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
617 if (allow_activating) {
618 return gtk_window_activate_key (win, ev);
620 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
624 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
628 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
632 Glib::RefPtr<Gdk::Pixbuf>
633 get_xpm (std::string name)
635 if (!xpm_map[name]) {
637 Searchpath spath(ARDOUR::ardour_data_search_path());
639 spath.add_subdirectory_to_paths("pixmaps");
641 std::string data_file_path;
643 if(!find_file_in_search_path (spath, name, data_file_path)) {
644 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
648 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
649 } catch(const Glib::Error& e) {
650 warning << "Caught Glib::Error: " << e.what() << endmsg;
654 return xpm_map[name];
660 Searchpath spath(ARDOUR::ardour_data_search_path());
661 spath.add_subdirectory_to_paths ("icons");
664 r.push_back (_("default"));
666 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
668 vector<string> entries;
670 get_files_in_directory (*s, entries);
672 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
676 /* ignore dotfiles, . and .. */
678 if (d.empty() || d[0] == '.') {
682 Glib::ustring path = Glib::build_filename (*s, *e);
684 if (Glib::file_test (path, Glib::FILE_TEST_IS_DIR)) {
685 r.push_back (Glib::filename_to_utf8 (*e));
694 get_icon_path (const char* cname, string icon_set)
696 std::string data_file_path;
700 Searchpath spath(ARDOUR::ardour_data_search_path());
702 if (!icon_set.empty() && icon_set != _("default")) {
703 string subdir = Glib::build_filename ("icons", icon_set);
704 spath.add_subdirectory_to_paths (subdir);
706 find_file_in_search_path (spath, name, data_file_path);
709 if (data_file_path.empty()) {
711 if (!icon_set.empty() && icon_set != _("default")) {
712 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
715 Searchpath def (ARDOUR::ardour_data_search_path());
716 def.add_subdirectory_to_paths ("icons");
718 if (!find_file_in_search_path (def, name, data_file_path)) {
719 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
724 return data_file_path;
727 Glib::RefPtr<Gdk::Pixbuf>
728 get_icon (const char* cname, string icon_set)
730 Glib::RefPtr<Gdk::Pixbuf> img;
732 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
733 } catch (const Gdk::PixbufError &e) {
734 cerr << "Caught PixbufError: " << e.what() << endl;
736 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
742 Glib::RefPtr<Gdk::Pixbuf>
743 get_icon (const char* cname)
745 Glib::RefPtr<Gdk::Pixbuf> img;
747 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
748 } catch (const Gdk::PixbufError &e) {
749 cerr << "Caught PixbufError: " << e.what() << endl;
751 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
758 longest (vector<string>& strings)
760 if (strings.empty()) {
764 vector<string>::iterator longest = strings.begin();
765 string::size_type longest_length = (*longest).length();
767 vector<string>::iterator i = longest;
770 while (i != strings.end()) {
772 string::size_type len = (*i).length();
774 if (len > longest_length) {
776 longest_length = len;
786 key_is_legal_for_numeric_entry (guint keyval)
788 /* we assume that this does not change over the life of the process
791 static int comma_decimal = -1;
796 if (comma_decimal < 0) {
797 std::lconv* lc = std::localeconv();
798 if (strchr (lc->decimal_point, ',') != 0) {
810 case GDK_decimalpoint:
811 case GDK_KP_Separator:
841 case GDK_KP_Subtract:
870 set_pango_fontsize ()
872 long val = ARDOUR::Config->get_font_scale();
874 /* FT2 rendering - used by GnomeCanvas, sigh */
876 #ifndef PLATFORM_WINDOWS
877 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
880 /* Cairo rendering, in case there is any */
882 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
888 long val = ARDOUR::Config->get_font_scale();
889 set_pango_fontsize ();
892 gtk_settings_set_long_property (gtk_settings_get_default(),
893 "gtk-xft-dpi", val, "ardour");
894 DPIReset();//Emit Signal
898 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
900 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
901 Gdk::Rectangle monitor_rect;
902 screen->get_monitor_geometry (0, monitor_rect);
904 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
905 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
907 window->resize (w, h);
911 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
913 escape_underscores (string const & s)
916 string::size_type const N = s.length ();
918 for (string::size_type i = 0; i < N; ++i) {
929 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
931 escape_angled_brackets (string const & s)
934 boost::replace_all (o, "<", "<");
935 boost::replace_all (o, ">", ">");
940 unique_random_color (list<Gdk::Color>& used_colors)
948 h = fmod (random(), 360.0);
949 s = (random() % 65535) / 65535.0;
950 v = (random() % 65535) / 65535.0;
952 s = min (0.5, s); /* not too saturated */
953 v = max (0.9, v); /* not too bright */
954 newcolor.set_hsv (h, s, v);
956 if (used_colors.size() == 0) {
957 used_colors.push_back (newcolor);
961 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
963 float rdelta, bdelta, gdelta;
965 rdelta = newcolor.get_red() - c.get_red();
966 bdelta = newcolor.get_blue() - c.get_blue();
967 gdelta = newcolor.get_green() - c.get_green();
969 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
970 /* different enough */
971 used_colors.push_back (newcolor);
976 /* XXX need throttle here to make sure we don't spin for ever */
981 rate_as_string (float r)
984 if (fmod (r, 1000.0f)) {
985 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
987 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);