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 namespace ARDOUR_UI_UTILS {
68 sigc::signal<void> DPIReset;
71 #ifdef PLATFORM_WINDOWS
72 #define random() rand()
76 /** Add an element to a menu, settings its sensitivity.
77 * @param m Menu to add to.
78 * @param e Element to add.
79 * @param s true to make sensitive, false to make insensitive
82 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
86 m.back().set_sensitive (false);
92 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
98 /* xpm2rgb copied from nixieclock, which bore the legend:
100 nixieclock - a nixie desktop timepiece
101 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
103 and was released under the GPL.
107 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
109 static long vals[256], val;
110 uint32_t t, x, y, colors, cpp;
112 unsigned char *savergb, *rgb;
116 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
117 error << string_compose (_("bad XPM header %1"), xpm[0])
122 savergb = rgb = (unsigned char*) malloc (h * w * 3);
124 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
125 for (t = 0; t < colors; ++t) {
126 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
130 // COLORMAP -> RGB CONVERSION
131 // Get low 3 bytes from vals[]
135 for (y = h-1; y > 0; --y) {
137 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
138 val = vals[(int)*p++];
139 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
140 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
141 *(rgb+0) = val & 0xff; // 0:R
149 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
151 static long vals[256], val;
152 uint32_t t, x, y, colors, cpp;
154 unsigned char *savergb, *rgb;
159 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
160 error << string_compose (_("bad XPM header %1"), xpm[0])
165 savergb = rgb = (unsigned char*) malloc (h * w * 4);
167 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
169 if (strstr (xpm[1], "None")) {
170 sscanf (xpm[1], "%c", &transparent);
177 for (; t < colors; ++t) {
178 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
182 // COLORMAP -> RGB CONVERSION
183 // Get low 3 bytes from vals[]
187 for (y = h-1; y > 0; --y) {
191 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
193 if (transparent && (*p++ == transparent)) {
201 *(rgb+3) = alpha; // 3: alpha
202 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
203 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
204 *(rgb+0) = val & 0xff; // 0:R
211 /** Returns a Pango::FontDescription given a string describing the font.
213 * If the returned FontDescription does not specify a family, then
214 * the family is set to "Sans". This mirrors GTK's behaviour in
217 * Some environments will force Pango to specify the family
218 * even if it was not specified in the string describing the font.
219 * Such environments should be left unaffected by this function,
220 * since the font family will be left alone.
222 * There may be other similar font specification enforcement
223 * that we might add here later.
225 Pango::FontDescription
226 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
228 Pango::FontDescription fd (name);
230 if (fd.get_family().empty()) {
231 fd.set_family ("Sans");
237 Pango::FontDescription
238 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
240 Gtk::Window window (WINDOW_TOPLEVEL);
242 Glib::RefPtr<Gtk::Style> style;
245 foobar.set_name (widgetname);
246 foobar.ensure_style();
248 style = foobar.get_style ();
250 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
252 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
256 /* layout inherited its font description from a PangoContext */
258 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
259 pfd = pango_context_get_font_description (ctxt);
260 return Pango::FontDescription (pfd); /* make a copy */
263 return Pango::FontDescription (pfd); /* make a copy */
267 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
269 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
272 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
276 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
278 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
281 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
285 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
287 /* since alpha value is not available from a Gdk::Color, it is
288 hardcoded as 0xff (aka 255 or 1.0)
291 const uint32_t r = c.get_red_p () * 255.0;
292 const uint32_t g = c.get_green_p () * 255.0;
293 const uint32_t b = c.get_blue_p () * 255.0;
294 const uint32_t a = 0xff;
296 return RGBA_TO_UINT (r,g,b,a);
301 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
303 PublicEditor& ed (PublicEditor::instance());
305 if (!key_press_focus_accelerator_handler (*win, ev)) {
307 /* early key press in pre-main-window-dialogs, no editor yet */
310 return ed.on_key_press_event(ev);
317 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
319 return PublicEditor::instance().on_key_press_event(ev);
323 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
325 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
326 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
327 GdkKeymapKey *keymapkey = NULL;
330 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
331 if (n_keys !=1) { g_free(keymapkey); return false;}
334 ev.type = GDK_KEY_PRESS;
335 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
336 ev.send_event = FALSE;
341 ev.string = const_cast<gchar*> ("");
342 ev.hardware_keycode = keymapkey[0].keycode;
343 ev.group = keymapkey[0].group;
346 forward_key_press(&ev);
347 ev.type = GDK_KEY_RELEASE;
348 return forward_key_press(&ev);
352 show_gdk_event_state (int state)
355 if (state & GDK_SHIFT_MASK) {
358 if (state & GDK_LOCK_MASK) {
361 if (state & GDK_CONTROL_MASK) {
364 if (state & GDK_MOD1_MASK) {
367 if (state & GDK_MOD2_MASK) {
370 if (state & GDK_MOD3_MASK) {
373 if (state & GDK_MOD4_MASK) {
376 if (state & GDK_MOD5_MASK) {
379 if (state & GDK_BUTTON1_MASK) {
382 if (state & GDK_BUTTON2_MASK) {
385 if (state & GDK_BUTTON3_MASK) {
388 if (state & GDK_BUTTON4_MASK) {
391 if (state & GDK_BUTTON5_MASK) {
394 if (state & GDK_SUPER_MASK) {
397 if (state & GDK_HYPER_MASK) {
400 if (state & GDK_META_MASK) {
403 if (state & GDK_RELEASE_MASK) {
410 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
412 GtkWindow* win = window.gobj();
413 GtkWidget* focus = gtk_window_get_focus (win);
414 bool special_handling_of_unmodified_accelerators = false;
415 bool allow_activating = true;
416 /* consider all relevant modifiers but not LOCK or SHIFT */
417 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
420 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
421 special_handling_of_unmodified_accelerators = true;
426 /* at one time this appeared to be necessary. As of July 2012, it does not
427 appear to be. if it ever is necessar, figure out if it should apply
431 if (Keyboard::some_magic_widget_has_focus ()) {
432 allow_activating = false;
438 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",
441 show_gdk_event_state (ev->state),
442 special_handling_of_unmodified_accelerators,
443 Keyboard::some_magic_widget_has_focus(),
446 (focus ? gtk_widget_get_name (focus) : "no focus widget")));
448 /* This exists to allow us to override the way GTK handles
449 key events. The normal sequence is:
451 a) event is delivered to a GtkWindow
452 b) accelerators/mnemonics are activated
453 c) if (b) didn't handle the event, propagate to
454 the focus widget and/or focus chain
456 The problem with this is that if the accelerators include
457 keys without modifiers, such as the space bar or the
458 letter "e", then pressing the key while typing into
459 a text entry widget results in the accelerator being
460 activated, instead of the desired letter appearing
463 There is no good way of fixing this, but this
464 represents a compromise. The idea is that
465 key events involving modifiers (not Shift)
466 get routed into the activation pathway first, then
467 get propagated to the focus widget if necessary.
469 If the key event doesn't involve modifiers,
470 we deliver to the focus widget first, thus allowing
471 it to get "normal text" without interference
474 Of course, this can also be problematic: if there
475 is a widget with focus, then it will swallow
476 all "normal text" accelerators.
479 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 GdkModifierType mod = GdkModifierType (ev->state);
510 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
511 Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator(mod);
513 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tmodified modifier was %1\n", show_gdk_event_state (mod)));
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 name %7 string:%4 hardware_keycode:%5 group:%6\n",
528 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
530 if (allow_activating) {
531 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
532 if (gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, GdkModifierType (ev->state & mask))) {
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_accel_groups_activate (G_OBJECT(win), ev->keyval, GdkModifierType (ev->state & mask));
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 ARDOUR_UI_UTILS::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 (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 ARDOUR_UI_UTILS::get_icon_sets ()
594 Searchpath spath(ARDOUR::ardour_data_search_path());
595 spath.add_subdirectory_to_paths ("icons");
598 r.push_back (_("default"));
600 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
602 vector<string> entries;
604 get_paths (entries, *s, false, false);
606 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
607 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
608 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
617 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
619 std::string data_file_path;
626 Searchpath spath(ARDOUR::ardour_data_search_path());
628 if (!icon_set.empty() && icon_set != _("default")) {
630 /* add "icons/icon_set" but .. not allowed to add both of these at once */
631 spath.add_subdirectory_to_paths ("icons");
632 spath.add_subdirectory_to_paths (icon_set);
634 find_file (spath, name, data_file_path);
636 spath.add_subdirectory_to_paths ("icons");
637 find_file (spath, name, data_file_path);
640 if (is_image && data_file_path.empty()) {
642 if (!icon_set.empty() && icon_set != _("default")) {
643 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
646 Searchpath def (ARDOUR::ardour_data_search_path());
647 def.add_subdirectory_to_paths ("icons");
649 if (!find_file (def, name, data_file_path)) {
650 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
651 abort(); /*NOTREACHED*/
655 return data_file_path;
658 Glib::RefPtr<Gdk::Pixbuf>
659 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
661 Glib::RefPtr<Gdk::Pixbuf> img;
663 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
664 } catch (const Gdk::PixbufError &e) {
665 cerr << "Caught PixbufError: " << e.what() << endl;
667 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
673 namespace ARDOUR_UI_UTILS {
674 Glib::RefPtr<Gdk::Pixbuf>
675 get_icon (const char* cname)
677 Glib::RefPtr<Gdk::Pixbuf> img;
679 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
680 } catch (const Gdk::PixbufError &e) {
681 cerr << "Caught PixbufError: " << e.what() << endl;
683 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
691 ARDOUR_UI_UTILS::longest (vector<string>& strings)
693 if (strings.empty()) {
697 vector<string>::iterator longest = strings.begin();
698 string::size_type longest_length = (*longest).length();
700 vector<string>::iterator i = longest;
703 while (i != strings.end()) {
705 string::size_type len = (*i).length();
707 if (len > longest_length) {
709 longest_length = len;
719 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
721 /* we assume that this does not change over the life of the process
724 static int comma_decimal = -1;
729 if (comma_decimal < 0) {
730 std::lconv* lc = std::localeconv();
731 if (strchr (lc->decimal_point, ',') != 0) {
743 case GDK_decimalpoint:
744 case GDK_KP_Separator:
774 case GDK_KP_Subtract:
804 ARDOUR_UI_UTILS::set_pango_fontsize ()
806 long val = ARDOUR_UI::config()->get_font_scale();
808 /* FT2 rendering - used by GnomeCanvas, sigh */
810 #ifndef PLATFORM_WINDOWS
811 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
814 /* Cairo rendering, in case there is any */
816 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
820 ARDOUR_UI_UTILS::reset_dpi ()
822 long val = ARDOUR_UI::config()->get_font_scale();
823 set_pango_fontsize ();
826 gtk_settings_set_long_property (gtk_settings_get_default(),
827 "gtk-xft-dpi", val, "ardour");
828 DPIReset();//Emit Signal
832 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
834 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
835 Gdk::Rectangle monitor_rect;
836 screen->get_monitor_geometry (0, monitor_rect);
838 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
839 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
841 window->resize (w, h);
845 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
847 ARDOUR_UI_UTILS::escape_underscores (string const & s)
850 string::size_type const N = s.length ();
852 for (string::size_type i = 0; i < N; ++i) {
863 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
865 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
868 boost::replace_all (o, "<", "<");
869 boost::replace_all (o, ">", ">");
874 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
882 h = fmod (random(), 360.0);
883 s = (random() % 65535) / 65535.0;
884 v = (random() % 65535) / 65535.0;
886 s = min (0.5, s); /* not too saturated */
887 v = max (0.9, v); /* not too bright */
888 newcolor.set_hsv (h, s, v);
890 if (used_colors.size() == 0) {
891 used_colors.push_back (newcolor);
895 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
897 float rdelta, bdelta, gdelta;
899 rdelta = newcolor.get_red() - c.get_red();
900 bdelta = newcolor.get_blue() - c.get_blue();
901 gdelta = newcolor.get_green() - c.get_green();
903 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
904 /* different enough */
905 used_colors.push_back (newcolor);
910 /* XXX need throttle here to make sure we don't spin for ever */
915 ARDOUR_UI_UTILS::rate_as_string (float r)
918 if (fmod (r, 1000.0f)) {
919 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
921 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);