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
32 #include <libart_lgpl/art_misc.h>
34 #include <gtkmm/window.h>
35 #include <gtkmm/combo.h>
36 #include <gtkmm/label.h>
37 #include <gtkmm/paned.h>
38 #include <gtk/gtkpaned.h>
39 #include <boost/algorithm/string.hpp>
41 #include "pbd/file_utils.h"
43 #include <gtkmm2ext/utils.h>
44 #include "ardour/rc_configuration.h"
46 #include "ardour/filesystem_paths.h"
48 #include "ardour_ui.h"
50 #include "public_editor.h"
54 #include "rgb_macros.h"
55 #include "canvas_impl.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 ArdourCanvas::Points*
203 get_canvas_points (string /*who*/, uint32_t npoints)
205 // cerr << who << ": wants " << npoints << " canvas points" << endl;
206 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
207 if (npoints > (uint32_t) gdk_screen_width() + 4) {
211 return new ArdourCanvas::Points (npoints);
214 Pango::FontDescription
215 get_font_for_style (string widgetname)
217 Gtk::Window window (WINDOW_TOPLEVEL);
219 Glib::RefPtr<Gtk::Style> style;
222 foobar.set_name (widgetname);
223 foobar.ensure_style();
225 style = foobar.get_style ();
227 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
229 PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj()));
233 /* layout inherited its font description from a PangoContext */
235 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
236 pfd = pango_context_get_font_description (ctxt);
237 return Pango::FontDescription (pfd); /* make a copy */
240 return Pango::FontDescription (pfd); /* make a copy */
244 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
246 /* In GTK+2, styles aren't set up correctly if the widget is not
247 attached to a toplevel window that has a screen pointer.
250 static Gtk::Window* window = 0;
253 window = new Window (WINDOW_TOPLEVEL);
260 foo.set_name (style);
263 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
267 r = rc->fg[state].red / 257;
268 g = rc->fg[state].green / 257;
269 b = rc->fg[state].blue / 257;
271 /* what a hack ... "a" is for "active" */
272 if (state == Gtk::STATE_NORMAL && rgba) {
273 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
275 } else if (attr == "bg") {
277 r = rc->bg[state].red / 257;
278 g = rc->bg[state].green / 257;
279 b = rc->bg[state].blue / 257;
280 } else if (attr == "base") {
281 r = rc->base[state].red / 257;
282 g = rc->base[state].green / 257;
283 b = rc->base[state].blue / 257;
284 } else if (attr == "text") {
285 r = rc->text[state].red / 257;
286 g = rc->text[state].green / 257;
287 b = rc->text[state].blue / 257;
290 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
295 if (state == Gtk::STATE_NORMAL && rgba) {
296 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
298 return (uint32_t) RGB_TO_UINT(r,g,b);
303 canvas_item_visible (ArdourCanvas::Item* item)
305 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
309 set_color (Gdk::Color& c, int rgb)
311 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
315 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
317 if (!key_press_focus_accelerator_handler (*win, ev)) {
318 return PublicEditor::instance().on_key_press_event(ev);
325 forward_key_press (GdkEventKey* ev)
327 return PublicEditor::instance().on_key_press_event(ev);
331 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
333 GtkWindow* win = window.gobj();
334 GtkWidget* focus = gtk_window_get_focus (win);
335 bool special_handling_of_unmodified_accelerators = false;
336 bool allow_activating = true;
337 /* consider all relevant modifiers but not LOCK or SHIFT */
338 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
341 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
342 special_handling_of_unmodified_accelerators = true;
347 /* at one time this appeared to be necessary. As of July 2012, it does not
348 appear to be. if it ever is necessar, figure out if it should apply
352 if (Keyboard::some_magic_widget_has_focus ()) {
353 allow_activating = false;
359 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",
363 special_handling_of_unmodified_accelerators,
364 Keyboard::some_magic_widget_has_focus(),
368 /* This exists to allow us to override the way GTK handles
369 key events. The normal sequence is:
371 a) event is delivered to a GtkWindow
372 b) accelerators/mnemonics are activated
373 c) if (b) didn't handle the event, propagate to
374 the focus widget and/or focus chain
376 The problem with this is that if the accelerators include
377 keys without modifiers, such as the space bar or the
378 letter "e", then pressing the key while typing into
379 a text entry widget results in the accelerator being
380 activated, instead of the desired letter appearing
383 There is no good way of fixing this, but this
384 represents a compromise. The idea is that
385 key events involving modifiers (not Shift)
386 get routed into the activation pathway first, then
387 get propagated to the focus widget if necessary.
389 If the key event doesn't involve modifiers,
390 we deliver to the focus widget first, thus allowing
391 it to get "normal text" without interference
394 Of course, this can also be problematic: if there
395 is a widget with focus, then it will swallow
396 all "normal text" accelerators.
399 if (!special_handling_of_unmodified_accelerators) {
401 /* XXX note that for a brief moment, the conditional above
402 * included "|| (ev->state & mask)" so as to enforce the
403 * implication of special_handling_of_UNMODIFIED_accelerators.
404 * however, this forces any key that GTK doesn't allow and that
405 * we have an alternative (see next comment) for to be
406 * automatically sent through the accel groups activation
407 * pathway, which prevents individual widgets & canvas items
408 * from ever seeing it if is used by a key binding.
410 * specifically, this hid Ctrl-down-arrow from MIDI region
411 * views because it is also bound to an action.
413 * until we have a robust, clean binding system, this
414 * quirk will have to remain in place.
417 /* pretend that certain key events that GTK does not allow
418 to be used as accelerators are actually something that
419 it does allow. but only where there are no modifiers.
422 uint32_t fakekey = ev->keyval;
424 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
425 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
426 ev->keyval, fakekey));
428 GdkModifierType mod = GdkModifierType (ev->state);
430 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
432 /* GTK on OS X is currently (February 2012) setting both
433 the Meta and Mod2 bits in the event modifier state if
434 the Command key is down.
436 gtk_accel_groups_activate() does not invoke any of the logic
437 that gtk_window_activate_key() will that sorts out that stupid
438 state of affairs, and as a result it fails to find a match
439 for the key event and the current set of accelerators.
441 to fix this, if the meta bit is set, remove the mod2 bit
442 from the modifier. this assumes that our bindings use Primary
443 which will have set the meta bit in the accelerator entry.
445 if (mod & GDK_META_MASK) {
446 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
450 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
451 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
457 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
459 /* no special handling or there are modifiers in effect: accelerate first */
461 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
463 if (allow_activating) {
464 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
465 if (gtk_window_activate_key (win, ev)) {
466 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
470 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
473 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
475 return gtk_window_propagate_key_event (win, ev);
478 /* no modifiers, propagate first */
480 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
482 if (!gtk_window_propagate_key_event (win, ev)) {
483 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
484 if (allow_activating) {
485 return gtk_window_activate_key (win, ev);
487 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
491 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
495 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
499 Glib::RefPtr<Gdk::Pixbuf>
500 get_xpm (std::string name)
502 if (!xpm_map[name]) {
504 SearchPath spath(ARDOUR::ardour_data_search_path());
506 spath.add_subdirectory_to_paths("pixmaps");
508 std::string data_file_path;
510 if(!find_file_in_search_path (spath, name, data_file_path)) {
511 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
515 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
516 } catch(const Glib::Error& e) {
517 warning << "Caught Glib::Error: " << e.what() << endmsg;
521 return xpm_map[name];
525 get_icon_path (const char* cname)
530 SearchPath spath(ARDOUR::ardour_data_search_path());
532 spath.add_subdirectory_to_paths("icons");
534 std::string data_file_path;
536 if (!find_file_in_search_path (spath, name, data_file_path)) {
537 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
540 return data_file_path;
543 Glib::RefPtr<Gdk::Pixbuf>
544 get_icon (const char* cname)
546 Glib::RefPtr<Gdk::Pixbuf> img;
548 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
549 } catch (const Gdk::PixbufError &e) {
550 cerr << "Caught PixbufError: " << e.what() << endl;
552 g_message("Caught ... ");
559 longest (vector<string>& strings)
561 if (strings.empty()) {
565 vector<string>::iterator longest = strings.begin();
566 string::size_type longest_length = (*longest).length();
568 vector<string>::iterator i = longest;
571 while (i != strings.end()) {
573 string::size_type len = (*i).length();
575 if (len > longest_length) {
577 longest_length = len;
587 key_is_legal_for_numeric_entry (guint keyval)
605 case GDK_KP_Subtract:
634 set_pango_fontsize ()
636 long val = ARDOUR::Config->get_font_scale();
638 /* FT2 rendering - used by GnomeCanvas, sigh */
640 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
642 /* Cairo rendering, in case there is any */
644 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
650 long val = ARDOUR::Config->get_font_scale();
651 set_pango_fontsize ();
654 gtk_settings_set_long_property (gtk_settings_get_default(),
655 "gtk-xft-dpi", val, "ardour");
656 DPIReset();//Emit Signal
660 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
662 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
663 Gdk::Rectangle monitor_rect;
664 screen->get_monitor_geometry (0, monitor_rect);
666 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
667 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
669 window->resize (w, h);
673 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
675 escape_underscores (string const & s)
678 string::size_type const N = s.length ();
680 for (string::size_type i = 0; i < N; ++i) {
691 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
693 escape_angled_brackets (string const & s)
696 boost::replace_all (o, "<", "<");
697 boost::replace_all (o, ">", ">");
702 unique_random_color (list<Gdk::Color>& used_colors)
708 /* avoid neon/glowing tones by limiting them to the
709 "inner section" (paler) of a color wheel/circle.
712 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
714 newcolor.set_red (random() % max_saturation);
715 newcolor.set_blue (random() % max_saturation);
716 newcolor.set_green (random() % max_saturation);
718 if (used_colors.size() == 0) {
719 used_colors.push_back (newcolor);
723 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
725 float rdelta, bdelta, gdelta;
727 rdelta = newcolor.get_red() - c.get_red();
728 bdelta = newcolor.get_blue() - c.get_blue();
729 gdelta = newcolor.get_green() - c.get_green();
731 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
732 used_colors.push_back (newcolor);
737 /* XXX need throttle here to make sure we don't spin for ever */