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"
32 #include <gtkmm/window.h>
33 #include <gtkmm/combo.h>
34 #include <gtkmm/label.h>
35 #include <gtkmm/paned.h>
36 #include <gtk/gtkpaned.h>
37 #include <boost/algorithm/string.hpp>
39 #include "pbd/file_utils.h"
41 #include <gtkmm2ext/utils.h>
43 #include "ardour/filesystem_paths.h"
45 #include "canvas/item.h"
46 #include "canvas/utils.h"
49 #include "public_editor.h"
53 #include "rgb_macros.h"
54 #include "gui_thread.h"
55 #include "ui_config.h"
61 using Gtkmm2ext::Keyboard;
63 namespace ARDOUR_UI_UTILS {
64 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 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
82 m.back().set_sensitive (false);
88 ARDOUR_UI_UTILS::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 ARDOUR_UI_UTILS::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 ARDOUR_UI_UTILS::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 /** Returns a Pango::FontDescription given a string describing the font.
209 * If the returned FontDescription does not specify a family, then
210 * the family is set to "Sans". This mirrors GTK's behaviour in
213 * Some environments will force Pango to specify the family
214 * even if it was not specified in the string describing the font.
215 * Such environments should be left unaffected by this function,
216 * since the font family will be left alone.
218 * There may be other similar font specification enforcement
219 * that we might add here later.
221 Pango::FontDescription
222 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
224 Pango::FontDescription fd (name);
226 if (fd.get_family().empty()) {
227 fd.set_family ("Sans");
233 Pango::FontDescription
234 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
236 Gtk::Window window (WINDOW_TOPLEVEL);
238 Glib::RefPtr<Gtk::Style> style;
241 foobar.set_name (widgetname);
242 foobar.ensure_style();
244 style = foobar.get_style ();
246 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
248 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
252 /* layout inherited its font description from a PangoContext */
254 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
255 pfd = pango_context_get_font_description (ctxt);
256 return Pango::FontDescription (pfd); /* make a copy */
259 return Pango::FontDescription (pfd); /* make a copy */
263 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
265 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
268 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
272 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
274 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
277 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
281 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
283 /* since alpha value is not available from a Gdk::Color, it is
284 hardcoded as 0xff (aka 255 or 1.0)
287 const uint32_t r = c.get_red_p () * 255.0;
288 const uint32_t g = c.get_green_p () * 255.0;
289 const uint32_t b = c.get_blue_p () * 255.0;
290 const uint32_t a = 0xff;
292 return RGBA_TO_UINT (r,g,b,a);
297 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
300 if (!key_press_focus_accelerator_handler (*win, ev)) {
301 if (!PublicEditor::_instance) {
302 /* early key press in pre-main-window-dialogs, no editor yet */
305 PublicEditor& ed (PublicEditor::instance());
306 return ed.on_key_press_event(ev);
313 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
315 return PublicEditor::instance().on_key_press_event(ev);
319 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
321 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
322 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
323 GdkKeymapKey *keymapkey = NULL;
326 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
327 if (n_keys !=1) { g_free(keymapkey); return false;}
330 ev.type = GDK_KEY_PRESS;
331 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
332 ev.send_event = FALSE;
337 ev.string = const_cast<gchar*> ("");
338 ev.hardware_keycode = keymapkey[0].keycode;
339 ev.group = keymapkey[0].group;
342 forward_key_press(&ev);
343 ev.type = GDK_KEY_RELEASE;
344 return forward_key_press(&ev);
348 show_gdk_event_state (int state)
351 if (state & GDK_SHIFT_MASK) {
354 if (state & GDK_LOCK_MASK) {
357 if (state & GDK_CONTROL_MASK) {
360 if (state & GDK_MOD1_MASK) {
363 if (state & GDK_MOD2_MASK) {
366 if (state & GDK_MOD3_MASK) {
369 if (state & GDK_MOD4_MASK) {
372 if (state & GDK_MOD5_MASK) {
375 if (state & GDK_BUTTON1_MASK) {
378 if (state & GDK_BUTTON2_MASK) {
381 if (state & GDK_BUTTON3_MASK) {
384 if (state & GDK_BUTTON4_MASK) {
387 if (state & GDK_BUTTON5_MASK) {
390 if (state & GDK_SUPER_MASK) {
393 if (state & GDK_HYPER_MASK) {
396 if (state & GDK_META_MASK) {
399 if (state & GDK_RELEASE_MASK) {
406 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
408 GtkWindow* win = window.gobj();
409 GtkWidget* focus = gtk_window_get_focus (win);
410 bool special_handling_of_unmodified_accelerators = false;
411 bool allow_activating = true;
412 /* consider all relevant modifiers but not LOCK or SHIFT */
413 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
414 GdkModifierType modifier = GdkModifierType (ev->state);
415 modifier = GdkModifierType (modifier & gtk_accelerator_get_default_mod_mask());
416 Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator(modifier);
419 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
420 special_handling_of_unmodified_accelerators = true;
425 /* at one time this appeared to be necessary. As of July 2012, it does not
426 appear to be. if it ever is necessar, figure out if it should apply
430 if (Keyboard::some_magic_widget_has_focus ()) {
431 allow_activating = false;
437 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 (%8) Key event: code = %2 state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
440 show_gdk_event_state (ev->state),
441 special_handling_of_unmodified_accelerators,
442 Keyboard::some_magic_widget_has_focus(),
445 (focus ? gtk_widget_get_name (focus) : "no focus widget")));
447 /* This exists to allow us to override the way GTK handles
448 key events. The normal sequence is:
450 a) event is delivered to a GtkWindow
451 b) accelerators/mnemonics are activated
452 c) if (b) didn't handle the event, propagate to
453 the focus widget and/or focus chain
455 The problem with this is that if the accelerators include
456 keys without modifiers, such as the space bar or the
457 letter "e", then pressing the key while typing into
458 a text entry widget results in the accelerator being
459 activated, instead of the desired letter appearing
462 There is no good way of fixing this, but this
463 represents a compromise. The idea is that
464 key events involving modifiers (not Shift)
465 get routed into the activation pathway first, then
466 get propagated to the focus widget if necessary.
468 If the key event doesn't involve modifiers,
469 we deliver to the focus widget first, thus allowing
470 it to get "normal text" without interference
473 Of course, this can also be problematic: if there
474 is a widget with focus, then it will swallow
475 all "normal text" accelerators.
478 if (!special_handling_of_unmodified_accelerators) {
481 /* XXX note that for a brief moment, the conditional above
482 * included "|| (ev->state & mask)" so as to enforce the
483 * implication of special_handling_of_UNMODIFIED_accelerators.
484 * however, this forces any key that GTK doesn't allow and that
485 * we have an alternative (see next comment) for to be
486 * automatically sent through the accel groups activation
487 * pathway, which prevents individual widgets & canvas items
488 * from ever seeing it if is used by a key binding.
490 * specifically, this hid Ctrl-down-arrow from MIDI region
491 * views because it is also bound to an action.
493 * until we have a robust, clean binding system, this
494 * quirk will have to remain in place.
497 /* pretend that certain key events that GTK does not allow
498 to be used as accelerators are actually something that
499 it does allow. but only where there are no modifiers.
502 uint32_t fakekey = ev->keyval;
504 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
505 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
506 ev->keyval, fakekey));
508 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tmodified modifier was %1\n", show_gdk_event_state (modifier)));
510 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, modifier)) {
511 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
517 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
519 /* no special handling or there are modifiers in effect: accelerate first */
521 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
522 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
523 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
525 if (allow_activating) {
526 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
527 if (gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier)) {
528 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
532 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
535 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
537 return gtk_window_propagate_key_event (win, ev);
540 /* no modifiers, propagate first */
542 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
544 if (!gtk_window_propagate_key_event (win, ev)) {
545 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
546 if (allow_activating) {
547 return gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier);
549 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
553 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
557 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
561 Glib::RefPtr<Gdk::Pixbuf>
562 ARDOUR_UI_UTILS::get_xpm (std::string name)
564 if (!xpm_map[name]) {
566 Searchpath spath(ARDOUR::ardour_data_search_path());
568 spath.add_subdirectory_to_paths("pixmaps");
570 std::string data_file_path;
572 if(!find_file (spath, name, data_file_path)) {
573 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
577 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
578 } catch(const Glib::Error& e) {
579 warning << "Caught Glib::Error: " << e.what() << endmsg;
583 return xpm_map[name];
587 ARDOUR_UI_UTILS::get_icon_sets ()
589 Searchpath spath(ARDOUR::ardour_data_search_path());
590 spath.add_subdirectory_to_paths ("icons");
593 r.push_back (_("default"));
595 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
597 vector<string> entries;
599 get_paths (entries, *s, false, false);
601 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
602 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
603 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
612 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
614 std::string data_file_path;
621 Searchpath spath(ARDOUR::ardour_data_search_path());
623 if (!icon_set.empty() && icon_set != _("default")) {
625 /* add "icons/icon_set" but .. not allowed to add both of these at once */
626 spath.add_subdirectory_to_paths ("icons");
627 spath.add_subdirectory_to_paths (icon_set);
629 find_file (spath, name, data_file_path);
631 spath.add_subdirectory_to_paths ("icons");
632 find_file (spath, name, data_file_path);
635 if (is_image && data_file_path.empty()) {
637 if (!icon_set.empty() && icon_set != _("default")) {
638 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
641 Searchpath def (ARDOUR::ardour_data_search_path());
642 def.add_subdirectory_to_paths ("icons");
644 if (!find_file (def, name, data_file_path)) {
645 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
646 abort(); /*NOTREACHED*/
650 return data_file_path;
653 Glib::RefPtr<Gdk::Pixbuf>
654 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
656 Glib::RefPtr<Gdk::Pixbuf> img;
658 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
659 } catch (const Gdk::PixbufError &e) {
660 cerr << "Caught PixbufError: " << e.what() << endl;
662 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
668 namespace ARDOUR_UI_UTILS {
669 Glib::RefPtr<Gdk::Pixbuf>
670 get_icon (const char* cname)
672 Glib::RefPtr<Gdk::Pixbuf> img;
674 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
675 } catch (const Gdk::PixbufError &e) {
676 cerr << "Caught PixbufError: " << e.what() << endl;
678 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
686 ARDOUR_UI_UTILS::longest (vector<string>& strings)
688 if (strings.empty()) {
692 vector<string>::iterator longest = strings.begin();
693 string::size_type longest_length = (*longest).length();
695 vector<string>::iterator i = longest;
698 while (i != strings.end()) {
700 string::size_type len = (*i).length();
702 if (len > longest_length) {
704 longest_length = len;
714 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
716 /* we assume that this does not change over the life of the process
719 static int comma_decimal = -1;
724 if (comma_decimal < 0) {
725 std::lconv* lc = std::localeconv();
726 if (strchr (lc->decimal_point, ',') != 0) {
738 case GDK_decimalpoint:
739 case GDK_KP_Separator:
769 case GDK_KP_Subtract:
799 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
801 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
802 Gdk::Rectangle monitor_rect;
803 screen->get_monitor_geometry (0, monitor_rect);
805 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
806 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
808 window->resize (w, h);
812 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
814 ARDOUR_UI_UTILS::escape_underscores (string const & s)
817 string::size_type const N = s.length ();
819 for (string::size_type i = 0; i < N; ++i) {
830 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
832 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
835 boost::replace_all (o, "<", "<");
836 boost::replace_all (o, ">", ">");
841 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
849 h = fmod (random(), 360.0);
850 s = (random() % 65535) / 65535.0;
851 v = (random() % 65535) / 65535.0;
853 s = min (0.5, s); /* not too saturated */
854 v = max (0.9, v); /* not too bright */
855 newcolor.set_hsv (h, s, v);
857 if (used_colors.size() == 0) {
858 used_colors.push_back (newcolor);
862 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
864 float rdelta, bdelta, gdelta;
866 rdelta = newcolor.get_red() - c.get_red();
867 bdelta = newcolor.get_blue() - c.get_blue();
868 gdelta = newcolor.get_green() - c.get_green();
870 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
871 /* different enough */
872 used_colors.push_back (newcolor);
877 /* XXX need throttle here to make sure we don't spin for ever */
882 ARDOUR_UI_UTILS::rate_as_string (float r)
885 if (fmod (r, 1000.0f)) {
886 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
888 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
894 ARDOUR_UI_UTILS::windows_overlap (Gtk::Window *a, Gtk::Window *b)
900 if (a->get_screen() == b->get_screen()) {
904 a->get_position (ex, ey);
905 a->get_size (ew, eh);
906 b->get_position (mx, my);
907 b->get_size (mw, mh);
923 if (gdk_rectangle_intersect (&e, &m, &r)) {