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/configuration.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((PangoLayout *)layout->gobj());
233 /* layout inherited its font description from a PangoContext */
235 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((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);
304 color_from_style (string widget_style_name, int state, string attr)
308 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
309 widget_style_name.c_str(),
313 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
314 return Gdk::Color ("red");
318 return Gdk::Color (&style->fg[state]);
322 return Gdk::Color (&style->bg[state]);
325 if (attr == "light") {
326 return Gdk::Color (&style->light[state]);
329 if (attr == "dark") {
330 return Gdk::Color (&style->dark[state]);
334 return Gdk::Color (&style->mid[state]);
337 if (attr == "text") {
338 return Gdk::Color (&style->text[state]);
341 if (attr == "base") {
342 return Gdk::Color (&style->base[state]);
345 if (attr == "text_aa") {
346 return Gdk::Color (&style->text_aa[state]);
349 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
350 return Gdk::Color ("red");
353 Glib::RefPtr<Gdk::GC>
354 gc_from_style (string widget_style_name, int state, string attr)
358 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
359 widget_style_name.c_str(),
363 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
364 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
365 ret->set_rgb_fg_color(Gdk::Color("red"));
370 return Glib::wrap(style->fg_gc[state]);
374 return Glib::wrap(style->bg_gc[state]);
377 if (attr == "light") {
378 return Glib::wrap(style->light_gc[state]);
381 if (attr == "dark") {
382 return Glib::wrap(style->dark_gc[state]);
386 return Glib::wrap(style->mid_gc[state]);
389 if (attr == "text") {
390 return Glib::wrap(style->text_gc[state]);
393 if (attr == "base") {
394 return Glib::wrap(style->base_gc[state]);
397 if (attr == "text_aa") {
398 return Glib::wrap(style->text_aa_gc[state]);
401 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
402 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
403 ret->set_rgb_fg_color(Gdk::Color("red"));
409 canvas_item_visible (ArdourCanvas::Item* item)
411 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
415 set_color (Gdk::Color& c, int rgb)
417 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
421 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
423 if (!key_press_focus_accelerator_handler (*win, ev)) {
424 return PublicEditor::instance().on_key_press_event(ev);
431 forward_key_press (GdkEventKey* ev)
433 return PublicEditor::instance().on_key_press_event(ev);
437 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
439 GtkWindow* win = window.gobj();
440 GtkWidget* focus = gtk_window_get_focus (win);
441 bool special_handling_of_unmodified_accelerators = false;
442 bool allow_activating = true;
443 /* consider all relevant modifiers but not LOCK or SHIFT */
444 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
447 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
448 special_handling_of_unmodified_accelerators = true;
453 /* should this be universally true? */
454 if (Keyboard::some_magic_widget_has_focus ()) {
455 allow_activating = false;
460 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",
464 special_handling_of_unmodified_accelerators,
465 Keyboard::some_magic_widget_has_focus(),
469 /* This exists to allow us to override the way GTK handles
470 key events. The normal sequence is:
472 a) event is delivered to a GtkWindow
473 b) accelerators/mnemonics are activated
474 c) if (b) didn't handle the event, propagate to
475 the focus widget and/or focus chain
477 The problem with this is that if the accelerators include
478 keys without modifiers, such as the space bar or the
479 letter "e", then pressing the key while typing into
480 a text entry widget results in the accelerator being
481 activated, instead of the desired letter appearing
484 There is no good way of fixing this, but this
485 represents a compromise. The idea is that
486 key events involving modifiers (not Shift)
487 get routed into the activation pathway first, then
488 get propagated to the focus widget if necessary.
490 If the key event doesn't involve modifiers,
491 we deliver to the focus widget first, thus allowing
492 it to get "normal text" without interference
495 Of course, this can also be problematic: if there
496 is a widget with focus, then it will swallow
497 all "normal text" accelerators.
500 if (!special_handling_of_unmodified_accelerators) {
502 /* XXX note that for a brief moment, the conditional above
503 * included "|| (ev->state & mask)" so as to enforce the
504 * implication of special_handling_of_UNMODIFIED_accelerators.
505 * however, this forces any key that GTK doesn't allow and that
506 * we have an alternative (see next comment) for to be
507 * automatically sent through the accel groups activation
508 * pathway, which prevents individual widgets & canvas items
509 * from ever seeing it if is used by a key binding.
511 * specifically, this hid Ctrl-down-arrow from MIDI region
512 * views because it is also bound to an action.
514 * until we have a robust, clean binding system, this
515 * quirk will have to remain in place.
518 /* pretend that certain key events that GTK does not allow
519 to be used as accelerators are actually something that
520 it does allow. but only where there are no modifiers.
523 uint32_t fakekey = ev->keyval;
525 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
526 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
527 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
533 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
535 /* no special handling or there are modifiers in effect: accelerate first */
537 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
539 if (allow_activating) {
540 if (gtk_window_activate_key (win, ev)) {
544 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
547 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
549 return gtk_window_propagate_key_event (win, ev);
552 /* no modifiers, propagate first */
554 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
556 if (!gtk_window_propagate_key_event (win, ev)) {
557 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
558 if (allow_activating) {
559 return gtk_window_activate_key (win, ev);
561 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
565 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
569 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
573 Glib::RefPtr<Gdk::Pixbuf>
574 get_xpm (std::string name)
576 if (!xpm_map[name]) {
578 SearchPath spath(ARDOUR::ardour_search_path());
579 spath += ARDOUR::system_data_search_path();
581 spath.add_subdirectory_to_paths("pixmaps");
583 sys::path data_file_path;
585 if(!find_file_in_search_path (spath, name, data_file_path)) {
586 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
590 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
591 } catch(const Glib::Error& e) {
592 warning << "Caught Glib::Error: " << e.what() << endmsg;
596 return xpm_map[name];
600 get_icon_path (const char* cname)
605 SearchPath spath(ARDOUR::ardour_search_path());
606 spath += ARDOUR::system_data_search_path();
608 spath.add_subdirectory_to_paths("icons");
610 sys::path data_file_path;
612 if (!find_file_in_search_path (spath, name, data_file_path)) {
613 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
616 return data_file_path.to_string();
619 Glib::RefPtr<Gdk::Pixbuf>
620 get_icon (const char* cname)
622 Glib::RefPtr<Gdk::Pixbuf> img;
624 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
625 } catch (const Gdk::PixbufError &e) {
626 cerr << "Caught PixbufError: " << e.what() << endl;
628 g_message("Caught ... ");
635 longest (vector<string>& strings)
637 if (strings.empty()) {
641 vector<string>::iterator longest = strings.begin();
642 string::size_type longest_length = (*longest).length();
644 vector<string>::iterator i = longest;
647 while (i != strings.end()) {
649 string::size_type len = (*i).length();
651 if (len > longest_length) {
653 longest_length = len;
663 key_is_legal_for_numeric_entry (guint keyval)
681 case GDK_KP_Subtract:
710 set_pango_fontsize ()
712 long val = ARDOUR::Config->get_font_scale();
714 /* FT2 rendering - used by GnomeCanvas, sigh */
716 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
718 /* Cairo rendering, in case there is any */
720 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
726 long val = ARDOUR::Config->get_font_scale();
727 set_pango_fontsize ();
730 gtk_settings_set_long_property (gtk_settings_get_default(),
731 "gtk-xft-dpi", val, "ardour");
732 DPIReset();//Emit Signal
736 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
738 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
739 Gdk::Rectangle monitor_rect;
740 screen->get_monitor_geometry (0, monitor_rect);
742 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
743 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
745 window->resize (w, h);
749 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
751 escape_underscores (string const & s)
754 string::size_type const N = s.length ();
756 for (string::size_type i = 0; i < N; ++i) {
768 unique_random_color (list<Gdk::Color>& used_colors)
774 /* avoid neon/glowing tones by limiting them to the
775 "inner section" (paler) of a color wheel/circle.
778 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
780 newcolor.set_red (random() % max_saturation);
781 newcolor.set_blue (random() % max_saturation);
782 newcolor.set_green (random() % max_saturation);
784 if (used_colors.size() == 0) {
785 used_colors.push_back (newcolor);
789 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
791 float rdelta, bdelta, gdelta;
793 rdelta = newcolor.get_red() - c.get_red();
794 bdelta = newcolor.get_blue() - c.get_blue();
795 gdelta = newcolor.get_green() - c.get_green();
797 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
798 used_colors.push_back (newcolor);
803 /* XXX need throttle here to make sure we don't spin for ever */