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
34 #include <libart_lgpl/art_misc.h>
36 #include <gtkmm/window.h>
37 #include <gtkmm/combo.h>
38 #include <gtkmm/label.h>
39 #include <gtkmm/paned.h>
40 #include <gtk/gtkpaned.h>
41 #include <boost/algorithm/string.hpp>
43 #include "pbd/file_utils.h"
45 #include <gtkmm2ext/utils.h>
46 #include "ardour/rc_configuration.h"
48 #include "ardour/filesystem_paths.h"
50 #include "ardour_ui.h"
52 #include "public_editor.h"
56 #include "rgb_macros.h"
57 #include "canvas_impl.h"
58 #include "gui_thread.h"
64 using Gtkmm2ext::Keyboard;
66 sigc::signal<void> DPIReset;
69 /** Add an element to a menu, settings its sensitivity.
70 * @param m Menu to add to.
71 * @param e Element to add.
72 * @param s true to make sensitive, false to make insensitive
75 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
79 m.back().set_sensitive (false);
85 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
91 /* xpm2rgb copied from nixieclock, which bore the legend:
93 nixieclock - a nixie desktop timepiece
94 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
96 and was released under the GPL.
100 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
102 static long vals[256], val;
103 uint32_t t, x, y, colors, cpp;
105 unsigned char *savergb, *rgb;
109 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
110 error << string_compose (_("bad XPM header %1"), xpm[0])
115 savergb = rgb = (unsigned char*) malloc (h * w * 3);
117 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
118 for (t = 0; t < colors; ++t) {
119 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
123 // COLORMAP -> RGB CONVERSION
124 // Get low 3 bytes from vals[]
128 for (y = h-1; y > 0; --y) {
130 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
131 val = vals[(int)*p++];
132 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
133 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
134 *(rgb+0) = val & 0xff; // 0:R
142 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
144 static long vals[256], val;
145 uint32_t t, x, y, colors, cpp;
147 unsigned char *savergb, *rgb;
152 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
153 error << string_compose (_("bad XPM header %1"), xpm[0])
158 savergb = rgb = (unsigned char*) malloc (h * w * 4);
160 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
162 if (strstr (xpm[1], "None")) {
163 sscanf (xpm[1], "%c", &transparent);
170 for (; t < colors; ++t) {
171 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
175 // COLORMAP -> RGB CONVERSION
176 // Get low 3 bytes from vals[]
180 for (y = h-1; y > 0; --y) {
184 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
186 if (transparent && (*p++ == transparent)) {
194 *(rgb+3) = alpha; // 3: alpha
195 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
196 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
197 *(rgb+0) = val & 0xff; // 0:R
204 ArdourCanvas::Points*
205 get_canvas_points (string /*who*/, uint32_t npoints)
207 // cerr << who << ": wants " << npoints << " canvas points" << endl;
208 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
209 if (npoints > (uint32_t) gdk_screen_width() + 4) {
213 return new ArdourCanvas::Points (npoints);
216 Pango::FontDescription
217 get_font_for_style (string widgetname)
219 Gtk::Window window (WINDOW_TOPLEVEL);
221 Glib::RefPtr<Gtk::Style> style;
224 foobar.set_name (widgetname);
225 foobar.ensure_style();
227 style = foobar.get_style ();
229 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
231 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
235 /* layout inherited its font description from a PangoContext */
237 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
238 pfd = pango_context_get_font_description (ctxt);
239 return Pango::FontDescription (pfd); /* make a copy */
242 return Pango::FontDescription (pfd); /* make a copy */
246 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
248 /* In GTK+2, styles aren't set up correctly if the widget is not
249 attached to a toplevel window that has a screen pointer.
252 static Gtk::Window* window = 0;
255 window = new Window (WINDOW_TOPLEVEL);
262 foo.set_name (style);
265 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
269 r = rc->fg[state].red / 257;
270 g = rc->fg[state].green / 257;
271 b = rc->fg[state].blue / 257;
273 /* what a hack ... "a" is for "active" */
274 if (state == Gtk::STATE_NORMAL && rgba) {
275 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
277 } else if (attr == "bg") {
279 r = rc->bg[state].red / 257;
280 g = rc->bg[state].green / 257;
281 b = rc->bg[state].blue / 257;
282 } else if (attr == "base") {
283 r = rc->base[state].red / 257;
284 g = rc->base[state].green / 257;
285 b = rc->base[state].blue / 257;
286 } else if (attr == "text") {
287 r = rc->text[state].red / 257;
288 g = rc->text[state].green / 257;
289 b = rc->text[state].blue / 257;
292 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
297 if (state == Gtk::STATE_NORMAL && rgba) {
298 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
300 return (uint32_t) RGB_TO_UINT(r,g,b);
305 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
307 static Gtk::Window* window = 0;
308 assert (r && g && b);
311 window = new Window (WINDOW_TOPLEVEL);
318 foo.set_name (style);
321 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
324 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
328 *r = rc->fg[state].red / 65535.0;
329 *g = rc->fg[state].green / 65535.0;
330 *b = rc->fg[state].blue / 65535.0;
331 } else if (attr == "bg") {
332 *r = rc->bg[state].red / 65535.0;
333 *g = rc->bg[state].green / 65535.0;
334 *b = rc->bg[state].blue / 65535.0;
335 } else if (attr == "base") {
336 *r = rc->base[state].red / 65535.0;
337 *g = rc->base[state].green / 65535.0;
338 *b = rc->base[state].blue / 65535.0;
339 } else if (attr == "text") {
340 *r = rc->text[state].red / 65535.0;
341 *g = rc->text[state].green / 65535.0;
342 *b = rc->text[state].blue / 65535.0;
352 canvas_item_visible (ArdourCanvas::Item* item)
354 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
358 set_color (Gdk::Color& c, int rgb)
360 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
364 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
366 PublicEditor& ed (PublicEditor::instance());
368 if (!key_press_focus_accelerator_handler (*win, ev)) {
370 /* early key press in pre-main-window-dialogs, no editor yet */
373 return ed.on_key_press_event(ev);
380 forward_key_press (GdkEventKey* ev)
382 return PublicEditor::instance().on_key_press_event(ev);
386 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
388 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
389 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
390 GdkKeymapKey *keymapkey = NULL;
393 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
394 if (n_keys !=1) { g_free(keymapkey); return false;}
397 ev.type = GDK_KEY_PRESS;
398 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
399 ev.send_event = FALSE;
404 ev.string = (const gchar*) "";
405 ev.hardware_keycode = keymapkey[0].keycode;
406 ev.group = keymapkey[0].group;
409 forward_key_press(&ev);
410 ev.type = GDK_KEY_RELEASE;
411 return forward_key_press(&ev);
415 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
417 GtkWindow* win = window.gobj();
418 GtkWidget* focus = gtk_window_get_focus (win);
419 bool special_handling_of_unmodified_accelerators = false;
420 bool allow_activating = true;
421 /* consider all relevant modifiers but not LOCK or SHIFT */
422 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
425 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
426 special_handling_of_unmodified_accelerators = true;
431 /* at one time this appeared to be necessary. As of July 2012, it does not
432 appear to be. if it ever is necessar, figure out if it should apply
436 if (Keyboard::some_magic_widget_has_focus ()) {
437 allow_activating = false;
443 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",
447 special_handling_of_unmodified_accelerators,
448 Keyboard::some_magic_widget_has_focus(),
452 /* This exists to allow us to override the way GTK handles
453 key events. The normal sequence is:
455 a) event is delivered to a GtkWindow
456 b) accelerators/mnemonics are activated
457 c) if (b) didn't handle the event, propagate to
458 the focus widget and/or focus chain
460 The problem with this is that if the accelerators include
461 keys without modifiers, such as the space bar or the
462 letter "e", then pressing the key while typing into
463 a text entry widget results in the accelerator being
464 activated, instead of the desired letter appearing
467 There is no good way of fixing this, but this
468 represents a compromise. The idea is that
469 key events involving modifiers (not Shift)
470 get routed into the activation pathway first, then
471 get propagated to the focus widget if necessary.
473 If the key event doesn't involve modifiers,
474 we deliver to the focus widget first, thus allowing
475 it to get "normal text" without interference
478 Of course, this can also be problematic: if there
479 is a widget with focus, then it will swallow
480 all "normal text" accelerators.
483 if (!special_handling_of_unmodified_accelerators) {
485 /* XXX note that for a brief moment, the conditional above
486 * included "|| (ev->state & mask)" so as to enforce the
487 * implication of special_handling_of_UNMODIFIED_accelerators.
488 * however, this forces any key that GTK doesn't allow and that
489 * we have an alternative (see next comment) for to be
490 * automatically sent through the accel groups activation
491 * pathway, which prevents individual widgets & canvas items
492 * from ever seeing it if is used by a key binding.
494 * specifically, this hid Ctrl-down-arrow from MIDI region
495 * views because it is also bound to an action.
497 * until we have a robust, clean binding system, this
498 * quirk will have to remain in place.
501 /* pretend that certain key events that GTK does not allow
502 to be used as accelerators are actually something that
503 it does allow. but only where there are no modifiers.
506 uint32_t fakekey = ev->keyval;
508 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
509 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
510 ev->keyval, fakekey));
512 GdkModifierType mod = GdkModifierType (ev->state);
514 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
516 /* GTK on OS X is currently (February 2012) setting both
517 the Meta and Mod2 bits in the event modifier state if
518 the Command key is down.
520 gtk_accel_groups_activate() does not invoke any of the logic
521 that gtk_window_activate_key() will that sorts out that stupid
522 state of affairs, and as a result it fails to find a match
523 for the key event and the current set of accelerators.
525 to fix this, if the meta bit is set, remove the mod2 bit
526 from the modifier. this assumes that our bindings use Primary
527 which will have set the meta bit in the accelerator entry.
529 if (mod & GDK_META_MASK) {
530 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
534 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
535 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
541 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
543 /* no special handling or there are modifiers in effect: accelerate first */
545 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
546 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
547 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
549 if (allow_activating) {
550 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
551 if (gtk_window_activate_key (win, ev)) {
552 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
556 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
559 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
561 return gtk_window_propagate_key_event (win, ev);
564 /* no modifiers, propagate first */
566 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
568 if (!gtk_window_propagate_key_event (win, ev)) {
569 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
570 if (allow_activating) {
571 return gtk_window_activate_key (win, ev);
573 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
577 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
581 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
585 Glib::RefPtr<Gdk::Pixbuf>
586 get_xpm (std::string name)
588 if (!xpm_map[name]) {
590 Searchpath spath(ARDOUR::ardour_data_search_path());
592 spath.add_subdirectory_to_paths("pixmaps");
594 std::string data_file_path;
596 if(!find_file_in_search_path (spath, name, data_file_path)) {
597 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
601 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
602 } catch(const Glib::Error& e) {
603 warning << "Caught Glib::Error: " << e.what() << endmsg;
607 return xpm_map[name];
611 get_icon_path (const char* cname)
616 Searchpath spath(ARDOUR::ardour_data_search_path());
618 spath.add_subdirectory_to_paths("icons");
620 std::string data_file_path;
622 if (!find_file_in_search_path (spath, name, data_file_path)) {
623 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
626 return data_file_path;
629 Glib::RefPtr<Gdk::Pixbuf>
630 get_icon (const char* cname)
632 Glib::RefPtr<Gdk::Pixbuf> img;
634 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
635 } catch (const Gdk::PixbufError &e) {
636 cerr << "Caught PixbufError: " << e.what() << endl;
638 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
645 longest (vector<string>& strings)
647 if (strings.empty()) {
651 vector<string>::iterator longest = strings.begin();
652 string::size_type longest_length = (*longest).length();
654 vector<string>::iterator i = longest;
657 while (i != strings.end()) {
659 string::size_type len = (*i).length();
661 if (len > longest_length) {
663 longest_length = len;
673 key_is_legal_for_numeric_entry (guint keyval)
675 /* we assume that this does not change over the life of the process
678 static int comma_decimal = -1;
683 if (comma_decimal < 0) {
684 std::lconv* lc = std::localeconv();
685 if (strchr (lc->decimal_point, ',') != 0) {
697 case GDK_decimalpoint:
698 case GDK_KP_Separator:
728 case GDK_KP_Subtract:
757 set_pango_fontsize ()
759 long val = ARDOUR::Config->get_font_scale();
761 /* FT2 rendering - used by GnomeCanvas, sigh */
764 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
767 /* Cairo rendering, in case there is any */
769 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
775 long val = ARDOUR::Config->get_font_scale();
776 set_pango_fontsize ();
779 gtk_settings_set_long_property (gtk_settings_get_default(),
780 "gtk-xft-dpi", val, "ardour");
781 DPIReset();//Emit Signal
785 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
787 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
788 Gdk::Rectangle monitor_rect;
789 screen->get_monitor_geometry (0, monitor_rect);
791 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
792 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
794 window->resize (w, h);
798 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
800 escape_underscores (string const & s)
803 string::size_type const N = s.length ();
805 for (string::size_type i = 0; i < N; ++i) {
816 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
818 escape_angled_brackets (string const & s)
821 boost::replace_all (o, "<", "<");
822 boost::replace_all (o, ">", ">");
827 unique_random_color (list<Gdk::Color>& used_colors)
833 /* avoid neon/glowing tones by limiting them to the
834 "inner section" (paler) of a color wheel/circle.
837 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
839 newcolor.set_red (g_random_int() % max_saturation);
840 newcolor.set_blue (g_random_int() % max_saturation);
841 newcolor.set_green (g_random_int() % max_saturation);
843 if (used_colors.size() == 0) {
844 used_colors.push_back (newcolor);
848 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
850 float rdelta, bdelta, gdelta;
852 rdelta = newcolor.get_red() - c.get_red();
853 bdelta = newcolor.get_blue() - c.get_blue();
854 gdelta = newcolor.get_green() - c.get_green();
856 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
857 used_colors.push_back (newcolor);
862 /* XXX need throttle here to make sure we don't spin for ever */