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 <gtkmm/window.h>
36 #include <gtkmm/combo.h>
37 #include <gtkmm/label.h>
38 #include <gtkmm/paned.h>
39 #include <gtk/gtkpaned.h>
40 #include <boost/algorithm/string.hpp>
42 #include "pbd/file_utils.h"
44 #include <gtkmm2ext/utils.h>
45 #include "ardour/rc_configuration.h"
46 #include "ardour/filesystem_paths.h"
47 #include "canvas/item.h"
49 #include "ardour_ui.h"
51 #include "public_editor.h"
55 #include "rgb_macros.h"
56 #include "gui_thread.h"
62 using Gtkmm2ext::Keyboard;
64 sigc::signal<void> DPIReset;
67 /** Add an element to a menu, settings its sensitivity.
68 * @param m Menu to add to.
69 * @param e Element to add.
70 * @param s true to make sensitive, false to make insensitive
73 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
77 m.back().set_sensitive (false);
83 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
89 /* xpm2rgb copied from nixieclock, which bore the legend:
91 nixieclock - a nixie desktop timepiece
92 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
94 and was released under the GPL.
98 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
100 static long vals[256], val;
101 uint32_t t, x, y, colors, cpp;
103 unsigned char *savergb, *rgb;
107 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
108 error << string_compose (_("bad XPM header %1"), xpm[0])
113 savergb = rgb = (unsigned char*) malloc (h * w * 3);
115 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
116 for (t = 0; t < colors; ++t) {
117 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
121 // COLORMAP -> RGB CONVERSION
122 // Get low 3 bytes from vals[]
126 for (y = h-1; y > 0; --y) {
128 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
129 val = vals[(int)*p++];
130 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
131 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
132 *(rgb+0) = val & 0xff; // 0:R
140 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
142 static long vals[256], val;
143 uint32_t t, x, y, colors, cpp;
145 unsigned char *savergb, *rgb;
150 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
151 error << string_compose (_("bad XPM header %1"), xpm[0])
156 savergb = rgb = (unsigned char*) malloc (h * w * 4);
158 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
160 if (strstr (xpm[1], "None")) {
161 sscanf (xpm[1], "%c", &transparent);
168 for (; t < colors; ++t) {
169 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
173 // COLORMAP -> RGB CONVERSION
174 // Get low 3 bytes from vals[]
178 for (y = h-1; y > 0; --y) {
182 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
184 if (transparent && (*p++ == transparent)) {
192 *(rgb+3) = alpha; // 3: alpha
193 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
194 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
195 *(rgb+0) = val & 0xff; // 0:R
202 Pango::FontDescription
203 get_font_for_style (string widgetname)
205 Gtk::Window window (WINDOW_TOPLEVEL);
207 Glib::RefPtr<Gtk::Style> style;
210 foobar.set_name (widgetname);
211 foobar.ensure_style();
213 style = foobar.get_style ();
215 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
217 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
221 /* layout inherited its font description from a PangoContext */
223 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
224 pfd = pango_context_get_font_description (ctxt);
225 return Pango::FontDescription (pfd); /* make a copy */
228 return Pango::FontDescription (pfd); /* make a copy */
232 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
234 /* In GTK+2, styles aren't set up correctly if the widget is not
235 attached to a toplevel window that has a screen pointer.
238 static Gtk::Window* window = 0;
241 window = new Window (WINDOW_TOPLEVEL);
248 foo.set_name (style);
251 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
255 r = rc->fg[state].red / 257;
256 g = rc->fg[state].green / 257;
257 b = rc->fg[state].blue / 257;
259 /* what a hack ... "a" is for "active" */
260 if (state == Gtk::STATE_NORMAL && rgba) {
261 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
263 } else if (attr == "bg") {
265 r = rc->bg[state].red / 257;
266 g = rc->bg[state].green / 257;
267 b = rc->bg[state].blue / 257;
268 } else if (attr == "base") {
269 r = rc->base[state].red / 257;
270 g = rc->base[state].green / 257;
271 b = rc->base[state].blue / 257;
272 } else if (attr == "text") {
273 r = rc->text[state].red / 257;
274 g = rc->text[state].green / 257;
275 b = rc->text[state].blue / 257;
278 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
283 if (state == Gtk::STATE_NORMAL && rgba) {
284 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
286 return (uint32_t) RGB_TO_UINT(r,g,b);
291 set_color (Gdk::Color& c, int rgb)
293 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
297 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
299 if (!key_press_focus_accelerator_handler (*win, ev)) {
300 return PublicEditor::instance().on_key_press_event(ev);
307 forward_key_press (GdkEventKey* ev)
309 return PublicEditor::instance().on_key_press_event(ev);
313 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
315 GtkWindow* win = window.gobj();
316 GtkWidget* focus = gtk_window_get_focus (win);
317 bool special_handling_of_unmodified_accelerators = false;
318 bool allow_activating = true;
319 /* consider all relevant modifiers but not LOCK or SHIFT */
320 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
323 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
324 special_handling_of_unmodified_accelerators = true;
329 /* at one time this appeared to be necessary. As of July 2012, it does not
330 appear to be. if it ever is necessar, figure out if it should apply
334 if (Keyboard::some_magic_widget_has_focus ()) {
335 allow_activating = false;
341 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",
345 special_handling_of_unmodified_accelerators,
346 Keyboard::some_magic_widget_has_focus(),
350 /* This exists to allow us to override the way GTK handles
351 key events. The normal sequence is:
353 a) event is delivered to a GtkWindow
354 b) accelerators/mnemonics are activated
355 c) if (b) didn't handle the event, propagate to
356 the focus widget and/or focus chain
358 The problem with this is that if the accelerators include
359 keys without modifiers, such as the space bar or the
360 letter "e", then pressing the key while typing into
361 a text entry widget results in the accelerator being
362 activated, instead of the desired letter appearing
365 There is no good way of fixing this, but this
366 represents a compromise. The idea is that
367 key events involving modifiers (not Shift)
368 get routed into the activation pathway first, then
369 get propagated to the focus widget if necessary.
371 If the key event doesn't involve modifiers,
372 we deliver to the focus widget first, thus allowing
373 it to get "normal text" without interference
376 Of course, this can also be problematic: if there
377 is a widget with focus, then it will swallow
378 all "normal text" accelerators.
381 if (!special_handling_of_unmodified_accelerators) {
383 /* XXX note that for a brief moment, the conditional above
384 * included "|| (ev->state & mask)" so as to enforce the
385 * implication of special_handling_of_UNMODIFIED_accelerators.
386 * however, this forces any key that GTK doesn't allow and that
387 * we have an alternative (see next comment) for to be
388 * automatically sent through the accel groups activation
389 * pathway, which prevents individual widgets & canvas items
390 * from ever seeing it if is used by a key binding.
392 * specifically, this hid Ctrl-down-arrow from MIDI region
393 * views because it is also bound to an action.
395 * until we have a robust, clean binding system, this
396 * quirk will have to remain in place.
399 /* pretend that certain key events that GTK does not allow
400 to be used as accelerators are actually something that
401 it does allow. but only where there are no modifiers.
404 uint32_t fakekey = ev->keyval;
406 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
407 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
408 ev->keyval, fakekey));
410 GdkModifierType mod = GdkModifierType (ev->state);
412 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
414 /* GTK on OS X is currently (February 2012) setting both
415 the Meta and Mod2 bits in the event modifier state if
416 the Command key is down.
418 gtk_accel_groups_activate() does not invoke any of the logic
419 that gtk_window_activate_key() will that sorts out that stupid
420 state of affairs, and as a result it fails to find a match
421 for the key event and the current set of accelerators.
423 to fix this, if the meta bit is set, remove the mod2 bit
424 from the modifier. this assumes that our bindings use Primary
425 which will have set the meta bit in the accelerator entry.
427 if (mod & GDK_META_MASK) {
428 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
432 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
433 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
439 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
441 /* no special handling or there are modifiers in effect: accelerate first */
443 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
445 if (allow_activating) {
446 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
447 if (gtk_window_activate_key (win, ev)) {
448 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
452 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
455 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
457 return gtk_window_propagate_key_event (win, ev);
460 /* no modifiers, propagate first */
462 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
464 if (!gtk_window_propagate_key_event (win, ev)) {
465 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
466 if (allow_activating) {
467 return gtk_window_activate_key (win, ev);
469 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
473 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
477 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
481 Glib::RefPtr<Gdk::Pixbuf>
482 get_xpm (std::string name)
484 if (!xpm_map[name]) {
486 SearchPath spath(ARDOUR::ardour_data_search_path());
488 spath.add_subdirectory_to_paths("pixmaps");
490 std::string data_file_path;
492 if(!find_file_in_search_path (spath, name, data_file_path)) {
493 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
497 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
498 } catch(const Glib::Error& e) {
499 warning << "Caught Glib::Error: " << e.what() << endmsg;
503 return xpm_map[name];
507 get_icon_path (const char* cname)
512 SearchPath spath(ARDOUR::ardour_data_search_path());
514 spath.add_subdirectory_to_paths("icons");
516 std::string data_file_path;
518 if (!find_file_in_search_path (spath, name, data_file_path)) {
519 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
522 return data_file_path;
525 Glib::RefPtr<Gdk::Pixbuf>
526 get_icon (const char* cname)
528 Glib::RefPtr<Gdk::Pixbuf> img;
530 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
531 } catch (const Gdk::PixbufError &e) {
532 cerr << "Caught PixbufError: " << e.what() << endl;
534 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
541 longest (vector<string>& strings)
543 if (strings.empty()) {
547 vector<string>::iterator longest = strings.begin();
548 string::size_type longest_length = (*longest).length();
550 vector<string>::iterator i = longest;
553 while (i != strings.end()) {
555 string::size_type len = (*i).length();
557 if (len > longest_length) {
559 longest_length = len;
569 key_is_legal_for_numeric_entry (guint keyval)
571 /* we assume that this does not change over the life of the process
574 static int comma_decimal = -1;
579 if (comma_decimal < 0) {
580 std::lconv* lc = std::localeconv();
581 if (strchr (lc->decimal_point, ',') != 0) {
593 case GDK_decimalpoint:
594 case GDK_KP_Separator:
624 case GDK_KP_Subtract:
653 set_pango_fontsize ()
655 long val = ARDOUR::Config->get_font_scale();
657 /* FT2 rendering - used by GnomeCanvas, sigh */
659 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
661 /* Cairo rendering, in case there is any */
663 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
669 long val = ARDOUR::Config->get_font_scale();
670 set_pango_fontsize ();
673 gtk_settings_set_long_property (gtk_settings_get_default(),
674 "gtk-xft-dpi", val, "ardour");
675 DPIReset();//Emit Signal
679 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
681 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
682 Gdk::Rectangle monitor_rect;
683 screen->get_monitor_geometry (0, monitor_rect);
685 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
686 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
688 window->resize (w, h);
692 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
694 escape_underscores (string const & s)
697 string::size_type const N = s.length ();
699 for (string::size_type i = 0; i < N; ++i) {
710 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
712 escape_angled_brackets (string const & s)
715 boost::replace_all (o, "<", "<");
716 boost::replace_all (o, ">", ">");
721 unique_random_color (list<Gdk::Color>& used_colors)
727 /* avoid neon/glowing tones by limiting them to the
728 "inner section" (paler) of a color wheel/circle.
731 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
733 newcolor.set_red (random() % max_saturation);
734 newcolor.set_blue (random() % max_saturation);
735 newcolor.set_green (random() % max_saturation);
737 if (used_colors.size() == 0) {
738 used_colors.push_back (newcolor);
742 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
744 float rdelta, bdelta, gdelta;
746 rdelta = newcolor.get_red() - c.get_red();
747 bdelta = newcolor.get_blue() - c.get_blue();
748 gdelta = newcolor.get_green() - c.get_green();
750 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
751 used_colors.push_back (newcolor);
756 /* XXX need throttle here to make sure we don't spin for ever */