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>
40 #include "pbd/file_utils.h"
42 #include <gtkmm2ext/utils.h>
43 #include "ardour/rc_configuration.h"
45 #include "ardour/filesystem_paths.h"
47 #include "ardour_ui.h"
49 #include "public_editor.h"
53 #include "rgb_macros.h"
54 #include "canvas_impl.h"
55 #include "gui_thread.h"
61 using Gtkmm2ext::Keyboard;
63 sigc::signal<void> DPIReset;
66 /** Add an element to a menu, settings its sensitivity.
67 * @param m Menu to add to.
68 * @param e Element to add.
69 * @param s true to make sensitive, false to make insensitive
72 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
76 m.back().set_sensitive (false);
82 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
88 /* xpm2rgb copied from nixieclock, which bore the legend:
90 nixieclock - a nixie desktop timepiece
91 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
93 and was released under the GPL.
97 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
99 static long vals[256], val;
100 uint32_t t, x, y, colors, cpp;
102 unsigned char *savergb, *rgb;
106 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
107 error << string_compose (_("bad XPM header %1"), xpm[0])
112 savergb = rgb = (unsigned char*) malloc (h * w * 3);
114 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
115 for (t = 0; t < colors; ++t) {
116 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
120 // COLORMAP -> RGB CONVERSION
121 // Get low 3 bytes from vals[]
125 for (y = h-1; y > 0; --y) {
127 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
128 val = vals[(int)*p++];
129 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
130 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
131 *(rgb+0) = val & 0xff; // 0:R
139 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
141 static long vals[256], val;
142 uint32_t t, x, y, colors, cpp;
144 unsigned char *savergb, *rgb;
149 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
150 error << string_compose (_("bad XPM header %1"), xpm[0])
155 savergb = rgb = (unsigned char*) malloc (h * w * 4);
157 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
159 if (strstr (xpm[1], "None")) {
160 sscanf (xpm[1], "%c", &transparent);
167 for (; t < colors; ++t) {
168 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
172 // COLORMAP -> RGB CONVERSION
173 // Get low 3 bytes from vals[]
177 for (y = h-1; y > 0; --y) {
181 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
183 if (transparent && (*p++ == transparent)) {
191 *(rgb+3) = alpha; // 3: alpha
192 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
193 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
194 *(rgb+0) = val & 0xff; // 0:R
201 ArdourCanvas::Points*
202 get_canvas_points (string /*who*/, uint32_t npoints)
204 // cerr << who << ": wants " << npoints << " canvas points" << endl;
205 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
206 if (npoints > (uint32_t) gdk_screen_width() + 4) {
210 return new ArdourCanvas::Points (npoints);
213 Pango::FontDescription
214 get_font_for_style (string widgetname)
216 Gtk::Window window (WINDOW_TOPLEVEL);
218 Glib::RefPtr<Gtk::Style> style;
221 foobar.set_name (widgetname);
222 foobar.ensure_style();
224 style = foobar.get_style ();
226 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
228 PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
232 /* layout inherited its font description from a PangoContext */
234 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
235 pfd = pango_context_get_font_description (ctxt);
236 return Pango::FontDescription (pfd); /* make a copy */
239 return Pango::FontDescription (pfd); /* make a copy */
243 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
245 /* In GTK+2, styles aren't set up correctly if the widget is not
246 attached to a toplevel window that has a screen pointer.
249 static Gtk::Window* window = 0;
252 window = new Window (WINDOW_TOPLEVEL);
259 foo.set_name (style);
262 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
266 r = rc->fg[state].red / 257;
267 g = rc->fg[state].green / 257;
268 b = rc->fg[state].blue / 257;
270 /* what a hack ... "a" is for "active" */
271 if (state == Gtk::STATE_NORMAL && rgba) {
272 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
274 } else if (attr == "bg") {
276 r = rc->bg[state].red / 257;
277 g = rc->bg[state].green / 257;
278 b = rc->bg[state].blue / 257;
279 } else if (attr == "base") {
280 r = rc->base[state].red / 257;
281 g = rc->base[state].green / 257;
282 b = rc->base[state].blue / 257;
283 } else if (attr == "text") {
284 r = rc->text[state].red / 257;
285 g = rc->text[state].green / 257;
286 b = rc->text[state].blue / 257;
289 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
294 if (state == Gtk::STATE_NORMAL && rgba) {
295 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
297 return (uint32_t) RGB_TO_UINT(r,g,b);
302 canvas_item_visible (ArdourCanvas::Item* item)
304 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
308 set_color (Gdk::Color& c, int rgb)
310 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
314 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
316 if (!key_press_focus_accelerator_handler (*win, ev)) {
317 return PublicEditor::instance().on_key_press_event(ev);
324 forward_key_press (GdkEventKey* ev)
326 return PublicEditor::instance().on_key_press_event(ev);
330 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
332 GtkWindow* win = window.gobj();
333 GtkWidget* focus = gtk_window_get_focus (win);
334 bool special_handling_of_unmodified_accelerators = false;
335 bool allow_activating = true;
336 /* consider all relevant modifiers but not LOCK or SHIFT */
337 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
340 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
341 special_handling_of_unmodified_accelerators = true;
346 /* should this be universally true? */
347 if (Keyboard::some_magic_widget_has_focus ()) {
348 allow_activating = false;
353 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",
357 special_handling_of_unmodified_accelerators,
358 Keyboard::some_magic_widget_has_focus(),
362 /* This exists to allow us to override the way GTK handles
363 key events. The normal sequence is:
365 a) event is delivered to a GtkWindow
366 b) accelerators/mnemonics are activated
367 c) if (b) didn't handle the event, propagate to
368 the focus widget and/or focus chain
370 The problem with this is that if the accelerators include
371 keys without modifiers, such as the space bar or the
372 letter "e", then pressing the key while typing into
373 a text entry widget results in the accelerator being
374 activated, instead of the desired letter appearing
377 There is no good way of fixing this, but this
378 represents a compromise. The idea is that
379 key events involving modifiers (not Shift)
380 get routed into the activation pathway first, then
381 get propagated to the focus widget if necessary.
383 If the key event doesn't involve modifiers,
384 we deliver to the focus widget first, thus allowing
385 it to get "normal text" without interference
388 Of course, this can also be problematic: if there
389 is a widget with focus, then it will swallow
390 all "normal text" accelerators.
393 if (!special_handling_of_unmodified_accelerators) {
395 /* XXX note that for a brief moment, the conditional above
396 * included "|| (ev->state & mask)" so as to enforce the
397 * implication of special_handling_of_UNMODIFIED_accelerators.
398 * however, this forces any key that GTK doesn't allow and that
399 * we have an alternative (see next comment) for to be
400 * automatically sent through the accel groups activation
401 * pathway, which prevents individual widgets & canvas items
402 * from ever seeing it if is used by a key binding.
404 * specifically, this hid Ctrl-down-arrow from MIDI region
405 * views because it is also bound to an action.
407 * until we have a robust, clean binding system, this
408 * quirk will have to remain in place.
411 /* pretend that certain key events that GTK does not allow
412 to be used as accelerators are actually something that
413 it does allow. but only where there are no modifiers.
416 uint32_t fakekey = ev->keyval;
418 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
419 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
420 ev->keyval, fakekey));
422 GdkModifierType mod = GdkModifierType (ev->state);
424 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
426 /* GTK on OS X is currently (February 2012) setting both
427 the Meta and Mod2 bits in the event modifier state if
428 the Command key is down.
430 gtk_accel_groups_activate() does not invoke any of the logic
431 that gtk_window_activate_key() will that sorts out that stupid
432 state of affairs, and as a result it fails to find a match
433 for the key event and the current set of accelerators.
435 to fix this, if the meta bit is set, remove the mod2 bit
436 from the modifier. this assumes that our bindings use Primary
437 which will have set the meta bit in the accelerator entry.
439 if (mod & GDK_META_MASK) {
440 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
444 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
445 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
451 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
453 /* no special handling or there are modifiers in effect: accelerate first */
455 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
457 if (allow_activating) {
458 if (gtk_window_activate_key (win, ev)) {
462 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
465 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
467 return gtk_window_propagate_key_event (win, ev);
470 /* no modifiers, propagate first */
472 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
474 if (!gtk_window_propagate_key_event (win, ev)) {
475 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
476 if (allow_activating) {
477 return gtk_window_activate_key (win, ev);
479 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
483 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
487 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
491 Glib::RefPtr<Gdk::Pixbuf>
492 get_xpm (std::string name)
494 if (!xpm_map[name]) {
496 SearchPath spath(ARDOUR::ardour_data_search_path());
498 spath.add_subdirectory_to_paths("pixmaps");
500 sys::path data_file_path;
502 if(!find_file_in_search_path (spath, name, data_file_path)) {
503 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
507 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
508 } catch(const Glib::Error& e) {
509 warning << "Caught Glib::Error: " << e.what() << endmsg;
513 return xpm_map[name];
517 get_icon_path (const char* cname)
522 SearchPath spath(ARDOUR::ardour_data_search_path());
524 spath.add_subdirectory_to_paths("icons");
526 sys::path data_file_path;
528 if (!find_file_in_search_path (spath, name, data_file_path)) {
529 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
532 return data_file_path.to_string();
535 Glib::RefPtr<Gdk::Pixbuf>
536 get_icon (const char* cname)
538 Glib::RefPtr<Gdk::Pixbuf> img;
540 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
541 } catch (const Gdk::PixbufError &e) {
542 cerr << "Caught PixbufError: " << e.what() << endl;
544 g_message("Caught ... ");
551 longest (vector<string>& strings)
553 if (strings.empty()) {
557 vector<string>::iterator longest = strings.begin();
558 string::size_type longest_length = (*longest).length();
560 vector<string>::iterator i = longest;
563 while (i != strings.end()) {
565 string::size_type len = (*i).length();
567 if (len > longest_length) {
569 longest_length = len;
579 key_is_legal_for_numeric_entry (guint keyval)
597 case GDK_KP_Subtract:
626 set_pango_fontsize ()
628 long val = ARDOUR::Config->get_font_scale();
630 /* FT2 rendering - used by GnomeCanvas, sigh */
632 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
634 /* Cairo rendering, in case there is any */
636 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
642 long val = ARDOUR::Config->get_font_scale();
643 set_pango_fontsize ();
646 gtk_settings_set_long_property (gtk_settings_get_default(),
647 "gtk-xft-dpi", val, "ardour");
648 DPIReset();//Emit Signal
652 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
654 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
655 Gdk::Rectangle monitor_rect;
656 screen->get_monitor_geometry (0, monitor_rect);
658 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
659 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
661 window->resize (w, h);
665 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
667 escape_underscores (string const & s)
670 string::size_type const N = s.length ();
672 for (string::size_type i = 0; i < N; ++i) {
684 unique_random_color (list<Gdk::Color>& used_colors)
690 /* avoid neon/glowing tones by limiting them to the
691 "inner section" (paler) of a color wheel/circle.
694 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
696 newcolor.set_red (random() % max_saturation);
697 newcolor.set_blue (random() % max_saturation);
698 newcolor.set_green (random() % max_saturation);
700 if (used_colors.size() == 0) {
701 used_colors.push_back (newcolor);
705 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
707 float rdelta, bdelta, gdelta;
709 rdelta = newcolor.get_red() - c.get_red();
710 bdelta = newcolor.get_blue() - c.get_blue();
711 gdelta = newcolor.get_green() - c.get_green();
713 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
714 used_colors.push_back (newcolor);
719 /* XXX need throttle here to make sure we don't spin for ever */