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 pixel_width (const string& str, Pango::FontDescription& font)
70 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
72 layout->set_font_description (font);
73 layout->set_text (str);
76 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
81 fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
84 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
85 string::size_type shorter_by = 0;
88 layout->set_font_description (font);
93 string::iterator last = ustr.end();
94 --last; /* now points at final entry */
98 while (!ustr.empty()) {
100 layout->set_text (txt);
103 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
105 if (width < pixel_width) {
106 actual_width = width;
113 if (with_ellipses && shorter_by > 3) {
124 /** Try to fit a string into a given horizontal space by ellipsizing it.
125 * @param cr Cairo context in which the text will be plotted.
127 * @param avail Available horizontal space.
128 * @return (Text, possibly ellipsized) and (horizontal size of text)
131 std::pair<std::string, double>
132 fit_to_pixels (cairo_t* cr, std::string name, double avail)
134 /* XXX hopefully there exists a more efficient way of doing this */
136 bool abbreviated = false;
140 cairo_text_extents_t ext;
141 cairo_text_extents (cr, name.c_str(), &ext);
143 if (ext.width < avail || name.length() <= 4) {
149 name = name.substr (0, name.length() - 4) + "...";
151 name = name.substr (0, name.length() - 3) + "...";
156 return std::make_pair (name, width);
160 /** Add an element to a menu, settings its sensitivity.
161 * @param m Menu to add to.
162 * @param e Element to add.
163 * @param s true to make sensitive, false to make insensitive
166 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
170 m.back().set_sensitive (false);
176 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
182 /* xpm2rgb copied from nixieclock, which bore the legend:
184 nixieclock - a nixie desktop timepiece
185 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
187 and was released under the GPL.
191 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
193 static long vals[256], val;
194 uint32_t t, x, y, colors, cpp;
196 unsigned char *savergb, *rgb;
200 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
201 error << string_compose (_("bad XPM header %1"), xpm[0])
206 savergb = rgb = (unsigned char*) malloc (h * w * 3);
208 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
209 for (t = 0; t < colors; ++t) {
210 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
214 // COLORMAP -> RGB CONVERSION
215 // Get low 3 bytes from vals[]
219 for (y = h-1; y > 0; --y) {
221 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
222 val = vals[(int)*p++];
223 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
224 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
225 *(rgb+0) = val & 0xff; // 0:R
233 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
235 static long vals[256], val;
236 uint32_t t, x, y, colors, cpp;
238 unsigned char *savergb, *rgb;
243 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
244 error << string_compose (_("bad XPM header %1"), xpm[0])
249 savergb = rgb = (unsigned char*) malloc (h * w * 4);
251 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
253 if (strstr (xpm[1], "None")) {
254 sscanf (xpm[1], "%c", &transparent);
261 for (; t < colors; ++t) {
262 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
266 // COLORMAP -> RGB CONVERSION
267 // Get low 3 bytes from vals[]
271 for (y = h-1; y > 0; --y) {
275 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
277 if (transparent && (*p++ == transparent)) {
285 *(rgb+3) = alpha; // 3: alpha
286 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
287 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
288 *(rgb+0) = val & 0xff; // 0:R
295 ArdourCanvas::Points*
296 get_canvas_points (string /*who*/, uint32_t npoints)
298 // cerr << who << ": wants " << npoints << " canvas points" << endl;
299 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
300 if (npoints > (uint32_t) gdk_screen_width() + 4) {
304 return new ArdourCanvas::Points (npoints);
307 Pango::FontDescription
308 get_font_for_style (string widgetname)
310 Gtk::Window window (WINDOW_TOPLEVEL);
312 Glib::RefPtr<Gtk::Style> style;
315 foobar.set_name (widgetname);
316 foobar.ensure_style();
318 style = foobar.get_style ();
320 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
322 PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
326 /* layout inherited its font description from a PangoContext */
328 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
329 pfd = pango_context_get_font_description (ctxt);
330 return Pango::FontDescription (pfd); /* make a copy */
333 return Pango::FontDescription (pfd); /* make a copy */
337 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
339 /* In GTK+2, styles aren't set up correctly if the widget is not
340 attached to a toplevel window that has a screen pointer.
343 static Gtk::Window* window = 0;
346 window = new Window (WINDOW_TOPLEVEL);
353 foo.set_name (style);
356 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
360 r = rc->fg[state].red / 257;
361 g = rc->fg[state].green / 257;
362 b = rc->fg[state].blue / 257;
364 /* what a hack ... "a" is for "active" */
365 if (state == Gtk::STATE_NORMAL && rgba) {
366 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
368 } else if (attr == "bg") {
370 r = rc->bg[state].red / 257;
371 g = rc->bg[state].green / 257;
372 b = rc->bg[state].blue / 257;
373 } else if (attr == "base") {
374 r = rc->base[state].red / 257;
375 g = rc->base[state].green / 257;
376 b = rc->base[state].blue / 257;
377 } else if (attr == "text") {
378 r = rc->text[state].red / 257;
379 g = rc->text[state].green / 257;
380 b = rc->text[state].blue / 257;
383 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
388 if (state == Gtk::STATE_NORMAL && rgba) {
389 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
391 return (uint32_t) RGB_TO_UINT(r,g,b);
397 color_from_style (string widget_style_name, int state, string attr)
401 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
402 widget_style_name.c_str(),
406 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
407 return Gdk::Color ("red");
411 return Gdk::Color (&style->fg[state]);
415 return Gdk::Color (&style->bg[state]);
418 if (attr == "light") {
419 return Gdk::Color (&style->light[state]);
422 if (attr == "dark") {
423 return Gdk::Color (&style->dark[state]);
427 return Gdk::Color (&style->mid[state]);
430 if (attr == "text") {
431 return Gdk::Color (&style->text[state]);
434 if (attr == "base") {
435 return Gdk::Color (&style->base[state]);
438 if (attr == "text_aa") {
439 return Gdk::Color (&style->text_aa[state]);
442 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
443 return Gdk::Color ("red");
446 Glib::RefPtr<Gdk::GC>
447 gc_from_style (string widget_style_name, int state, string attr)
451 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
452 widget_style_name.c_str(),
456 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
457 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
458 ret->set_rgb_fg_color(Gdk::Color("red"));
463 return Glib::wrap(style->fg_gc[state]);
467 return Glib::wrap(style->bg_gc[state]);
470 if (attr == "light") {
471 return Glib::wrap(style->light_gc[state]);
474 if (attr == "dark") {
475 return Glib::wrap(style->dark_gc[state]);
479 return Glib::wrap(style->mid_gc[state]);
482 if (attr == "text") {
483 return Glib::wrap(style->text_gc[state]);
486 if (attr == "base") {
487 return Glib::wrap(style->base_gc[state]);
490 if (attr == "text_aa") {
491 return Glib::wrap(style->text_aa_gc[state]);
494 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
495 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
496 ret->set_rgb_fg_color(Gdk::Color("red"));
502 canvas_item_visible (ArdourCanvas::Item* item)
504 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
508 set_color (Gdk::Color& c, int rgb)
510 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
514 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
516 if (!key_press_focus_accelerator_handler (*win, ev)) {
517 return PublicEditor::instance().on_key_press_event(ev);
524 forward_key_press (GdkEventKey* ev)
526 return PublicEditor::instance().on_key_press_event(ev);
530 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
532 GtkWindow* win = window.gobj();
533 GtkWidget* focus = gtk_window_get_focus (win);
534 bool special_handling_of_unmodified_accelerators = false;
535 bool allow_activating = true;
536 /* consider all relevant modifiers but not LOCK or SHIFT */
537 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
540 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
541 special_handling_of_unmodified_accelerators = true;
546 /* should this be universally true? */
547 if (Keyboard::some_magic_widget_has_focus ()) {
548 allow_activating = false;
553 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",
557 special_handling_of_unmodified_accelerators,
558 Keyboard::some_magic_widget_has_focus(),
562 /* This exists to allow us to override the way GTK handles
563 key events. The normal sequence is:
565 a) event is delivered to a GtkWindow
566 b) accelerators/mnemonics are activated
567 c) if (b) didn't handle the event, propagate to
568 the focus widget and/or focus chain
570 The problem with this is that if the accelerators include
571 keys without modifiers, such as the space bar or the
572 letter "e", then pressing the key while typing into
573 a text entry widget results in the accelerator being
574 activated, instead of the desired letter appearing
577 There is no good way of fixing this, but this
578 represents a compromise. The idea is that
579 key events involving modifiers (not Shift)
580 get routed into the activation pathway first, then
581 get propagated to the focus widget if necessary.
583 If the key event doesn't involve modifiers,
584 we deliver to the focus widget first, thus allowing
585 it to get "normal text" without interference
588 Of course, this can also be problematic: if there
589 is a widget with focus, then it will swallow
590 all "normal text" accelerators.
593 if (!special_handling_of_unmodified_accelerators) {
595 /* XXX note that for a brief moment, the conditional above
596 * included "|| (ev->state & mask)" so as to enforce the
597 * implication of special_handling_of_UNMODIFIED_accelerators.
598 * however, this forces any key that GTK doesn't allow and that
599 * we have an alternative (see next comment) for to be
600 * automatically sent through the accel groups activation
601 * pathway, which prevents individual widgets & canvas items
602 * from ever seeing it if is used by a key binding.
604 * specifically, this hid Ctrl-down-arrow from MIDI region
605 * views because it is also bound to an action.
607 * until we have a robust, clean binding system, this
608 * quirk will have to remain in place.
611 /* pretend that certain key events that GTK does not allow
612 to be used as accelerators are actually something that
613 it does allow. but only where there are no modifiers.
616 uint32_t fakekey = ev->keyval;
618 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
619 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
620 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
626 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
628 /* no special handling or there are modifiers in effect: accelerate first */
630 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
632 if (allow_activating) {
633 if (gtk_window_activate_key (win, ev)) {
637 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
640 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
642 return gtk_window_propagate_key_event (win, ev);
645 /* no modifiers, propagate first */
647 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
649 if (!gtk_window_propagate_key_event (win, ev)) {
650 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
651 if (allow_activating) {
652 return gtk_window_activate_key (win, ev);
654 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
658 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
662 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
666 Glib::RefPtr<Gdk::Pixbuf>
667 get_xpm (std::string name)
669 if (!xpm_map[name]) {
671 SearchPath spath(ARDOUR::ardour_search_path());
672 spath += ARDOUR::system_data_search_path();
674 spath.add_subdirectory_to_paths("pixmaps");
676 sys::path data_file_path;
678 if(!find_file_in_search_path (spath, name, data_file_path)) {
679 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
683 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
684 } catch(const Glib::Error& e) {
685 warning << "Caught Glib::Error: " << e.what() << endmsg;
689 return xpm_map[name];
693 get_icon_path (const char* cname)
698 SearchPath spath(ARDOUR::ardour_search_path());
699 spath += ARDOUR::system_data_search_path();
701 spath.add_subdirectory_to_paths("icons");
703 sys::path data_file_path;
705 if (!find_file_in_search_path (spath, name, data_file_path)) {
706 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
709 return data_file_path.to_string();
712 Glib::RefPtr<Gdk::Pixbuf>
713 get_icon (const char* cname)
715 Glib::RefPtr<Gdk::Pixbuf> img;
717 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
718 } catch (const Gdk::PixbufError &e) {
719 cerr << "Caught PixbufError: " << e.what() << endl;
721 g_message("Caught ... ");
728 longest (vector<string>& strings)
730 if (strings.empty()) {
734 vector<string>::iterator longest = strings.begin();
735 string::size_type longest_length = (*longest).length();
737 vector<string>::iterator i = longest;
740 while (i != strings.end()) {
742 string::size_type len = (*i).length();
744 if (len > longest_length) {
746 longest_length = len;
756 key_is_legal_for_numeric_entry (guint keyval)
774 case GDK_KP_Subtract:
803 set_pango_fontsize ()
805 long val = ARDOUR::Config->get_font_scale();
807 /* FT2 rendering - used by GnomeCanvas, sigh */
809 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
811 /* Cairo rendering, in case there is any */
813 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
819 long val = ARDOUR::Config->get_font_scale();
820 set_pango_fontsize ();
823 gtk_settings_set_long_property (gtk_settings_get_default(),
824 "gtk-xft-dpi", val, "ardour");
825 DPIReset();//Emit Signal
829 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
831 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
832 Gdk::Rectangle monitor_rect;
833 screen->get_monitor_geometry (0, monitor_rect);
835 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
836 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
838 window->resize (w, h);
842 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
844 escape_underscores (string const & s)
847 string::size_type const N = s.length ();
849 for (string::size_type i = 0; i < N; ++i) {
861 unique_random_color (list<Gdk::Color>& used_colors)
867 /* avoid neon/glowing tones by limiting them to the
868 "inner section" (paler) of a color wheel/circle.
871 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
873 newcolor.set_red (random() % max_saturation);
874 newcolor.set_blue (random() % max_saturation);
875 newcolor.set_green (random() % max_saturation);
877 if (used_colors.size() == 0) {
878 used_colors.push_back (newcolor);
882 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
884 float rdelta, bdelta, gdelta;
886 rdelta = newcolor.get_red() - c.get_red();
887 bdelta = newcolor.get_blue() - c.get_blue();
888 gdelta = newcolor.get_green() - c.get_green();
890 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
891 used_colors.push_back (newcolor);
896 /* XXX need throttle here to make sure we don't spin for ever */