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"
56 #include "ardour_dialog.h"
62 using Gtkmm2ext::Keyboard;
64 namespace ARDOUR_UI_UTILS {
65 sigc::signal<void> DPIReset;
68 #ifdef PLATFORM_WINDOWS
69 #define random() rand()
73 /** Add an element to a menu, settings its sensitivity.
74 * @param m Menu to add to.
75 * @param e Element to add.
76 * @param s true to make sensitive, false to make insensitive
79 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
83 m.back().set_sensitive (false);
89 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
95 /* xpm2rgb copied from nixieclock, which bore the legend:
97 nixieclock - a nixie desktop timepiece
98 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
100 and was released under the GPL.
104 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
106 static long vals[256], val;
107 uint32_t t, x, y, colors, cpp;
109 unsigned char *savergb, *rgb;
113 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
114 error << string_compose (_("bad XPM header %1"), xpm[0])
119 savergb = rgb = (unsigned char*) malloc (h * w * 3);
121 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
122 for (t = 0; t < colors; ++t) {
123 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
127 // COLORMAP -> RGB CONVERSION
128 // Get low 3 bytes from vals[]
132 for (y = h-1; y > 0; --y) {
134 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
135 val = vals[(int)*p++];
136 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
137 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
138 *(rgb+0) = val & 0xff; // 0:R
146 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
148 static long vals[256], val;
149 uint32_t t, x, y, colors, cpp;
151 unsigned char *savergb, *rgb;
156 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
157 error << string_compose (_("bad XPM header %1"), xpm[0])
162 savergb = rgb = (unsigned char*) malloc (h * w * 4);
164 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
166 if (strstr (xpm[1], "None")) {
167 sscanf (xpm[1], "%c", &transparent);
174 for (; t < colors; ++t) {
175 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
179 // COLORMAP -> RGB CONVERSION
180 // Get low 3 bytes from vals[]
184 for (y = h-1; y > 0; --y) {
188 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
190 if (transparent && (*p++ == transparent)) {
198 *(rgb+3) = alpha; // 3: alpha
199 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
200 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
201 *(rgb+0) = val & 0xff; // 0:R
208 /** Returns a Pango::FontDescription given a string describing the font.
210 * If the returned FontDescription does not specify a family, then
211 * the family is set to "Sans". This mirrors GTK's behaviour in
214 * Some environments will force Pango to specify the family
215 * even if it was not specified in the string describing the font.
216 * Such environments should be left unaffected by this function,
217 * since the font family will be left alone.
219 * There may be other similar font specification enforcement
220 * that we might add here later.
222 Pango::FontDescription
223 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
225 Pango::FontDescription fd (name);
227 if (fd.get_family().empty()) {
228 fd.set_family ("Sans");
234 Pango::FontDescription
235 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
237 Gtk::Window window (WINDOW_TOPLEVEL);
239 Glib::RefPtr<Gtk::Style> style;
242 foobar.set_name (widgetname);
243 foobar.ensure_style();
245 style = foobar.get_style ();
247 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
249 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
253 /* layout inherited its font description from a PangoContext */
255 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
256 pfd = pango_context_get_font_description (ctxt);
257 return Pango::FontDescription (pfd); /* make a copy */
260 return Pango::FontDescription (pfd); /* make a copy */
264 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
266 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
269 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
273 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
275 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
278 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
282 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
284 /* since alpha value is not available from a Gdk::Color, it is
285 hardcoded as 0xff (aka 255 or 1.0)
288 const uint32_t r = c.get_red_p () * 255.0;
289 const uint32_t g = c.get_green_p () * 255.0;
290 const uint32_t b = c.get_blue_p () * 255.0;
291 const uint32_t a = 0xff;
293 return RGBA_TO_UINT (r,g,b,a);
298 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
301 if (!key_press_focus_accelerator_handler (*win, ev)) {
302 if (!PublicEditor::_instance) {
303 /* early key press in pre-main-window-dialogs, no editor yet */
306 PublicEditor& ed (PublicEditor::instance());
307 return ed.on_key_press_event(ev);
314 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
316 return PublicEditor::instance().on_key_press_event(ev);
320 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
322 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
323 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
324 GdkKeymapKey *keymapkey = NULL;
327 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
328 if (n_keys !=1) { g_free(keymapkey); return false;}
331 ev.type = GDK_KEY_PRESS;
332 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
333 ev.send_event = FALSE;
338 ev.string = const_cast<gchar*> ("");
339 ev.hardware_keycode = keymapkey[0].keycode;
340 ev.group = keymapkey[0].group;
343 forward_key_press(&ev);
344 ev.type = GDK_KEY_RELEASE;
345 return forward_key_press(&ev);
349 show_gdk_event_state (int state)
352 if (state & GDK_SHIFT_MASK) {
355 if (state & GDK_LOCK_MASK) {
358 if (state & GDK_CONTROL_MASK) {
361 if (state & GDK_MOD1_MASK) {
364 if (state & GDK_MOD2_MASK) {
367 if (state & GDK_MOD3_MASK) {
370 if (state & GDK_MOD4_MASK) {
373 if (state & GDK_MOD5_MASK) {
376 if (state & GDK_BUTTON1_MASK) {
379 if (state & GDK_BUTTON2_MASK) {
382 if (state & GDK_BUTTON3_MASK) {
385 if (state & GDK_BUTTON4_MASK) {
388 if (state & GDK_BUTTON5_MASK) {
391 if (state & GDK_SUPER_MASK) {
394 if (state & GDK_HYPER_MASK) {
397 if (state & GDK_META_MASK) {
400 if (state & GDK_RELEASE_MASK) {
407 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
409 GtkWindow* win = window.gobj();
410 GtkWidget* focus = gtk_window_get_focus (win);
411 bool special_handling_of_unmodified_accelerators = false;
412 bool allow_activating = true;
413 /* consider all relevant modifiers but not LOCK or SHIFT */
414 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
415 GdkModifierType modifier = GdkModifierType (ev->state);
416 modifier = GdkModifierType (modifier & gtk_accelerator_get_default_mod_mask());
417 Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator(modifier);
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) {
482 /* XXX note that for a brief moment, the conditional above
483 * included "|| (ev->state & mask)" so as to enforce the
484 * implication of special_handling_of_UNMODIFIED_accelerators.
485 * however, this forces any key that GTK doesn't allow and that
486 * we have an alternative (see next comment) for to be
487 * automatically sent through the accel groups activation
488 * pathway, which prevents individual widgets & canvas items
489 * from ever seeing it if is used by a key binding.
491 * specifically, this hid Ctrl-down-arrow from MIDI region
492 * views because it is also bound to an action.
494 * until we have a robust, clean binding system, this
495 * quirk will have to remain in place.
498 /* pretend that certain key events that GTK does not allow
499 to be used as accelerators are actually something that
500 it does allow. but only where there are no modifiers.
503 uint32_t fakekey = ev->keyval;
505 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
506 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
507 ev->keyval, fakekey));
509 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tmodified modifier was %1\n", show_gdk_event_state (modifier)));
511 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, modifier)) {
512 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
518 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
520 /* no special handling or there are modifiers in effect: accelerate first */
522 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
523 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
524 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
526 if (allow_activating) {
527 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
528 if (gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier)) {
529 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
533 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
536 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
538 return gtk_window_propagate_key_event (win, ev);
541 /* no modifiers, propagate first */
543 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
545 if (!gtk_window_propagate_key_event (win, ev)) {
546 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
547 if (allow_activating) {
548 return gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier);
550 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
554 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
558 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
562 Glib::RefPtr<Gdk::Pixbuf>
563 ARDOUR_UI_UTILS::get_xpm (std::string name)
565 if (!xpm_map[name]) {
567 Searchpath spath(ARDOUR::ardour_data_search_path());
569 spath.add_subdirectory_to_paths("pixmaps");
571 std::string data_file_path;
573 if(!find_file (spath, name, data_file_path)) {
574 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
578 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
579 } catch(const Glib::Error& e) {
580 warning << "Caught Glib::Error: " << e.what() << endmsg;
584 return xpm_map[name];
588 ARDOUR_UI_UTILS::get_icon_sets ()
590 Searchpath spath(ARDOUR::ardour_data_search_path());
591 spath.add_subdirectory_to_paths ("icons");
594 r.push_back (_("default"));
596 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
598 vector<string> entries;
600 get_paths (entries, *s, false, false);
602 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
603 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
604 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
613 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
615 std::string data_file_path;
622 Searchpath spath(ARDOUR::ardour_data_search_path());
624 if (!icon_set.empty() && icon_set != _("default")) {
626 /* add "icons/icon_set" but .. not allowed to add both of these at once */
627 spath.add_subdirectory_to_paths ("icons");
628 spath.add_subdirectory_to_paths (icon_set);
630 find_file (spath, name, data_file_path);
632 spath.add_subdirectory_to_paths ("icons");
633 find_file (spath, name, data_file_path);
636 if (is_image && data_file_path.empty()) {
638 if (!icon_set.empty() && icon_set != _("default")) {
639 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
642 Searchpath def (ARDOUR::ardour_data_search_path());
643 def.add_subdirectory_to_paths ("icons");
645 if (!find_file (def, name, data_file_path)) {
646 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
647 abort(); /*NOTREACHED*/
651 return data_file_path;
654 Glib::RefPtr<Gdk::Pixbuf>
655 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
657 Glib::RefPtr<Gdk::Pixbuf> img;
659 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
660 } catch (const Gdk::PixbufError &e) {
661 cerr << "Caught PixbufError: " << e.what() << endl;
663 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
669 namespace ARDOUR_UI_UTILS {
670 Glib::RefPtr<Gdk::Pixbuf>
671 get_icon (const char* cname)
673 Glib::RefPtr<Gdk::Pixbuf> img;
675 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
676 } catch (const Gdk::PixbufError &e) {
677 cerr << "Caught PixbufError: " << e.what() << endl;
679 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
687 ARDOUR_UI_UTILS::longest (vector<string>& strings)
689 if (strings.empty()) {
693 vector<string>::iterator longest = strings.begin();
694 string::size_type longest_length = (*longest).length();
696 vector<string>::iterator i = longest;
699 while (i != strings.end()) {
701 string::size_type len = (*i).length();
703 if (len > longest_length) {
705 longest_length = len;
715 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
717 /* we assume that this does not change over the life of the process
720 static int comma_decimal = -1;
725 if (comma_decimal < 0) {
726 std::lconv* lc = std::localeconv();
727 if (strchr (lc->decimal_point, ',') != 0) {
739 case GDK_decimalpoint:
740 case GDK_KP_Separator:
770 case GDK_KP_Subtract:
800 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
802 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
803 Gdk::Rectangle monitor_rect;
804 screen->get_monitor_geometry (0, monitor_rect);
806 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
807 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
809 window->resize (w, h);
813 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
815 ARDOUR_UI_UTILS::escape_underscores (string const & s)
818 string::size_type const N = s.length ();
820 for (string::size_type i = 0; i < N; ++i) {
831 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
833 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
836 boost::replace_all (o, "<", "<");
837 boost::replace_all (o, ">", ">");
842 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
850 h = fmod (random(), 360.0);
851 s = (random() % 65535) / 65535.0;
852 v = (random() % 65535) / 65535.0;
854 s = min (0.5, s); /* not too saturated */
855 v = max (0.9, v); /* not too bright */
856 newcolor.set_hsv (h, s, v);
858 if (used_colors.size() == 0) {
859 used_colors.push_back (newcolor);
863 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
865 float rdelta, bdelta, gdelta;
867 rdelta = newcolor.get_red() - c.get_red();
868 bdelta = newcolor.get_blue() - c.get_blue();
869 gdelta = newcolor.get_green() - c.get_green();
871 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
872 /* different enough */
873 used_colors.push_back (newcolor);
878 /* XXX need throttle here to make sure we don't spin for ever */
883 ARDOUR_UI_UTILS::rate_as_string (float r)
886 if (fmod (r, 1000.0f)) {
887 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
889 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
895 ARDOUR_UI_UTILS::windows_overlap (Gtk::Window *a, Gtk::Window *b)
901 if (a->get_screen() == b->get_screen()) {
905 a->get_position (ex, ey);
906 a->get_size (ew, eh);
907 b->get_position (mx, my);
908 b->get_size (mw, mh);
924 if (gdk_rectangle_intersect (&e, &m, &r)) {
932 ARDOUR_UI_UTILS::overwrite_file_dialog (Gtk::Window& parent, string title, string text)
934 ArdourDialog dialog (parent, title, true);
937 dialog.get_vbox()->pack_start (label, true, true);
938 dialog.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
939 dialog.add_button (_("Overwrite"), Gtk::RESPONSE_ACCEPT);
942 switch (dialog.run()) {
943 case RESPONSE_ACCEPT:
945 case RESPONSE_CANCEL: