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 <libart_lgpl/art_misc.h>
37 #include <gtkmm/window.h>
38 #include <gtkmm/combo.h>
39 #include <gtkmm/label.h>
40 #include <gtkmm/paned.h>
41 #include <gtk/gtkpaned.h>
42 #include <boost/algorithm/string.hpp>
44 #include "pbd/file_utils.h"
46 #include <gtkmm2ext/utils.h>
47 #include "ardour/rc_configuration.h"
49 #include "ardour/filesystem_paths.h"
51 #include "ardour_ui.h"
53 #include "public_editor.h"
57 #include "rgb_macros.h"
58 #include "canvas_impl.h"
59 #include "gui_thread.h"
65 using Gtkmm2ext::Keyboard;
67 sigc::signal<void> DPIReset;
70 /** Add an element to a menu, settings its sensitivity.
71 * @param m Menu to add to.
72 * @param e Element to add.
73 * @param s true to make sensitive, false to make insensitive
76 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
80 m.back().set_sensitive (false);
86 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
92 /* xpm2rgb copied from nixieclock, which bore the legend:
94 nixieclock - a nixie desktop timepiece
95 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
97 and was released under the GPL.
101 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
103 static long vals[256], val;
104 uint32_t t, x, y, colors, cpp;
106 unsigned char *savergb, *rgb;
110 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
111 error << string_compose (_("bad XPM header %1"), xpm[0])
116 savergb = rgb = (unsigned char*) malloc (h * w * 3);
118 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
119 for (t = 0; t < colors; ++t) {
120 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
124 // COLORMAP -> RGB CONVERSION
125 // Get low 3 bytes from vals[]
129 for (y = h-1; y > 0; --y) {
131 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
132 val = vals[(int)*p++];
133 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
134 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
135 *(rgb+0) = val & 0xff; // 0:R
143 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
145 static long vals[256], val;
146 uint32_t t, x, y, colors, cpp;
148 unsigned char *savergb, *rgb;
153 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
154 error << string_compose (_("bad XPM header %1"), xpm[0])
159 savergb = rgb = (unsigned char*) malloc (h * w * 4);
161 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
163 if (strstr (xpm[1], "None")) {
164 sscanf (xpm[1], "%c", &transparent);
171 for (; t < colors; ++t) {
172 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
176 // COLORMAP -> RGB CONVERSION
177 // Get low 3 bytes from vals[]
181 for (y = h-1; y > 0; --y) {
185 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
187 if (transparent && (*p++ == transparent)) {
195 *(rgb+3) = alpha; // 3: alpha
196 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
197 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
198 *(rgb+0) = val & 0xff; // 0:R
205 ArdourCanvas::Points*
206 get_canvas_points (string /*who*/, uint32_t npoints)
208 // cerr << who << ": wants " << npoints << " canvas points" << endl;
209 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
210 if (npoints > (uint32_t) gdk_screen_width() + 4) {
214 return new ArdourCanvas::Points (npoints);
217 Pango::FontDescription
218 get_font_for_style (string widgetname)
220 Gtk::Window window (WINDOW_TOPLEVEL);
222 Glib::RefPtr<Gtk::Style> style;
225 foobar.set_name (widgetname);
226 foobar.ensure_style();
228 style = foobar.get_style ();
230 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
232 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
236 /* layout inherited its font description from a PangoContext */
238 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
239 pfd = pango_context_get_font_description (ctxt);
240 return Pango::FontDescription (pfd); /* make a copy */
243 return Pango::FontDescription (pfd); /* make a copy */
247 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
249 /* In GTK+2, styles aren't set up correctly if the widget is not
250 attached to a toplevel window that has a screen pointer.
253 static Gtk::Window* window = 0;
256 window = new Window (WINDOW_TOPLEVEL);
263 foo.set_name (style);
266 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
270 r = rc->fg[state].red / 257;
271 g = rc->fg[state].green / 257;
272 b = rc->fg[state].blue / 257;
274 /* what a hack ... "a" is for "active" */
275 if (state == Gtk::STATE_NORMAL && rgba) {
276 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
278 } else if (attr == "bg") {
280 r = rc->bg[state].red / 257;
281 g = rc->bg[state].green / 257;
282 b = rc->bg[state].blue / 257;
283 } else if (attr == "base") {
284 r = rc->base[state].red / 257;
285 g = rc->base[state].green / 257;
286 b = rc->base[state].blue / 257;
287 } else if (attr == "text") {
288 r = rc->text[state].red / 257;
289 g = rc->text[state].green / 257;
290 b = rc->text[state].blue / 257;
293 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
298 if (state == Gtk::STATE_NORMAL && rgba) {
299 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
301 return (uint32_t) RGB_TO_UINT(r,g,b);
306 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
308 static Gtk::Window* window = 0;
309 assert (r && g && b);
312 window = new Window (WINDOW_TOPLEVEL);
319 foo.set_name (style);
322 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
325 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
329 *r = rc->fg[state].red / 65535.0;
330 *g = rc->fg[state].green / 65535.0;
331 *b = rc->fg[state].blue / 65535.0;
332 } else if (attr == "bg") {
333 *r = rc->bg[state].red / 65535.0;
334 *g = rc->bg[state].green / 65535.0;
335 *b = rc->bg[state].blue / 65535.0;
336 } else if (attr == "base") {
337 *r = rc->base[state].red / 65535.0;
338 *g = rc->base[state].green / 65535.0;
339 *b = rc->base[state].blue / 65535.0;
340 } else if (attr == "text") {
341 *r = rc->text[state].red / 65535.0;
342 *g = rc->text[state].green / 65535.0;
343 *b = rc->text[state].blue / 65535.0;
353 canvas_item_visible (ArdourCanvas::Item* item)
355 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
359 set_color (Gdk::Color& c, int rgb)
361 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
365 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
367 PublicEditor& ed (PublicEditor::instance());
369 if (!key_press_focus_accelerator_handler (*win, ev)) {
371 /* early key press in pre-main-window-dialogs, no editor yet */
374 return ed.on_key_press_event(ev);
381 forward_key_press (GdkEventKey* ev)
383 return PublicEditor::instance().on_key_press_event(ev);
387 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
389 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
390 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
391 GdkKeymapKey *keymapkey = NULL;
394 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
395 if (n_keys !=1) { g_free(keymapkey); return false;}
398 ev.type = GDK_KEY_PRESS;
399 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
400 ev.send_event = FALSE;
405 ev.string = const_cast<gchar*> ("");
406 ev.hardware_keycode = keymapkey[0].keycode;
407 ev.group = keymapkey[0].group;
410 forward_key_press(&ev);
411 ev.type = GDK_KEY_RELEASE;
412 return forward_key_press(&ev);
416 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
418 GtkWindow* win = window.gobj();
419 GtkWidget* focus = gtk_window_get_focus (win);
420 bool special_handling_of_unmodified_accelerators = false;
421 bool allow_activating = true;
422 /* consider all relevant modifiers but not LOCK or SHIFT */
423 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
426 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
427 special_handling_of_unmodified_accelerators = true;
432 /* at one time this appeared to be necessary. As of July 2012, it does not
433 appear to be. if it ever is necessar, figure out if it should apply
437 if (Keyboard::some_magic_widget_has_focus ()) {
438 allow_activating = false;
444 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",
448 special_handling_of_unmodified_accelerators,
449 Keyboard::some_magic_widget_has_focus(),
453 /* This exists to allow us to override the way GTK handles
454 key events. The normal sequence is:
456 a) event is delivered to a GtkWindow
457 b) accelerators/mnemonics are activated
458 c) if (b) didn't handle the event, propagate to
459 the focus widget and/or focus chain
461 The problem with this is that if the accelerators include
462 keys without modifiers, such as the space bar or the
463 letter "e", then pressing the key while typing into
464 a text entry widget results in the accelerator being
465 activated, instead of the desired letter appearing
468 There is no good way of fixing this, but this
469 represents a compromise. The idea is that
470 key events involving modifiers (not Shift)
471 get routed into the activation pathway first, then
472 get propagated to the focus widget if necessary.
474 If the key event doesn't involve modifiers,
475 we deliver to the focus widget first, thus allowing
476 it to get "normal text" without interference
479 Of course, this can also be problematic: if there
480 is a widget with focus, then it will swallow
481 all "normal text" accelerators.
484 if (!special_handling_of_unmodified_accelerators) {
486 /* XXX note that for a brief moment, the conditional above
487 * included "|| (ev->state & mask)" so as to enforce the
488 * implication of special_handling_of_UNMODIFIED_accelerators.
489 * however, this forces any key that GTK doesn't allow and that
490 * we have an alternative (see next comment) for to be
491 * automatically sent through the accel groups activation
492 * pathway, which prevents individual widgets & canvas items
493 * from ever seeing it if is used by a key binding.
495 * specifically, this hid Ctrl-down-arrow from MIDI region
496 * views because it is also bound to an action.
498 * until we have a robust, clean binding system, this
499 * quirk will have to remain in place.
502 /* pretend that certain key events that GTK does not allow
503 to be used as accelerators are actually something that
504 it does allow. but only where there are no modifiers.
507 uint32_t fakekey = ev->keyval;
509 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
510 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
511 ev->keyval, fakekey));
513 GdkModifierType mod = GdkModifierType (ev->state);
515 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
517 /* GTK on OS X is currently (February 2012) setting both
518 the Meta and Mod2 bits in the event modifier state if
519 the Command key is down.
521 gtk_accel_groups_activate() does not invoke any of the logic
522 that gtk_window_activate_key() will that sorts out that stupid
523 state of affairs, and as a result it fails to find a match
524 for the key event and the current set of accelerators.
526 to fix this, if the meta bit is set, remove the mod2 bit
527 from the modifier. this assumes that our bindings use Primary
528 which will have set the meta bit in the accelerator entry.
530 if (mod & GDK_META_MASK) {
531 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
535 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
536 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
542 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
544 /* no special handling or there are modifiers in effect: accelerate first */
546 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
547 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
548 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
550 if (allow_activating) {
551 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
552 if (gtk_window_activate_key (win, ev)) {
553 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
557 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
560 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
562 return gtk_window_propagate_key_event (win, ev);
565 /* no modifiers, propagate first */
567 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
569 if (!gtk_window_propagate_key_event (win, ev)) {
570 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
571 if (allow_activating) {
572 return gtk_window_activate_key (win, ev);
574 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
578 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
582 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
586 Glib::RefPtr<Gdk::Pixbuf>
587 get_xpm (std::string name)
589 if (!xpm_map[name]) {
591 Searchpath spath(ARDOUR::ardour_data_search_path());
593 spath.add_subdirectory_to_paths("pixmaps");
595 std::string data_file_path;
597 if(!find_file_in_search_path (spath, name, data_file_path)) {
598 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
602 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
603 } catch(const Glib::Error& e) {
604 warning << "Caught Glib::Error: " << e.what() << endmsg;
608 return xpm_map[name];
612 get_icon_path (const char* cname)
617 Searchpath spath(ARDOUR::ardour_data_search_path());
619 spath.add_subdirectory_to_paths("icons");
621 std::string data_file_path;
623 if (!find_file_in_search_path (spath, name, data_file_path)) {
624 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
627 return data_file_path;
630 Glib::RefPtr<Gdk::Pixbuf>
631 get_icon (const char* cname)
633 Glib::RefPtr<Gdk::Pixbuf> img;
635 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
636 } catch (const Gdk::PixbufError &e) {
637 cerr << "Caught PixbufError: " << e.what() << endl;
639 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
646 longest (vector<string>& strings)
648 if (strings.empty()) {
652 vector<string>::iterator longest = strings.begin();
653 string::size_type longest_length = (*longest).length();
655 vector<string>::iterator i = longest;
658 while (i != strings.end()) {
660 string::size_type len = (*i).length();
662 if (len > longest_length) {
664 longest_length = len;
674 key_is_legal_for_numeric_entry (guint keyval)
676 /* we assume that this does not change over the life of the process
679 static int comma_decimal = -1;
684 if (comma_decimal < 0) {
685 std::lconv* lc = std::localeconv();
686 if (strchr (lc->decimal_point, ',') != 0) {
698 case GDK_decimalpoint:
699 case GDK_KP_Separator:
729 case GDK_KP_Subtract:
758 set_pango_fontsize ()
760 long val = ARDOUR::Config->get_font_scale();
762 /* FT2 rendering - used by GnomeCanvas, sigh */
765 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
768 /* Cairo rendering, in case there is any */
770 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
776 long val = ARDOUR::Config->get_font_scale();
777 set_pango_fontsize ();
780 gtk_settings_set_long_property (gtk_settings_get_default(),
781 "gtk-xft-dpi", val, "ardour");
782 DPIReset();//Emit Signal
786 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
788 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
789 Gdk::Rectangle monitor_rect;
790 screen->get_monitor_geometry (0, monitor_rect);
792 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
793 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
795 window->resize (w, h);
799 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
801 escape_underscores (string const & s)
804 string::size_type const N = s.length ();
806 for (string::size_type i = 0; i < N; ++i) {
817 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
819 escape_angled_brackets (string const & s)
822 boost::replace_all (o, "<", "<");
823 boost::replace_all (o, ">", ">");
828 unique_random_color (list<Gdk::Color>& used_colors)
834 /* avoid neon/glowing tones by limiting them to the
835 "inner section" (paler) of a color wheel/circle.
838 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
840 newcolor.set_red (g_random_int() % max_saturation);
841 newcolor.set_blue (g_random_int() % max_saturation);
842 newcolor.set_green (g_random_int() % max_saturation);
844 if (used_colors.size() == 0) {
845 used_colors.push_back (newcolor);
849 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
851 float rdelta, bdelta, gdelta;
853 rdelta = newcolor.get_red() - c.get_red();
854 bdelta = newcolor.get_blue() - c.get_blue();
855 gdelta = newcolor.get_green() - c.get_green();
857 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
858 used_colors.push_back (newcolor);
863 /* XXX need throttle here to make sure we don't spin for ever */
868 rate_as_string (float r)
871 if (fmod (r, 1000.0f)) {
872 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
874 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);