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 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(),
561 /* This exists to allow us to override the way GTK handles
562 key events. The normal sequence is:
564 a) event is delivered to a GtkWindow
565 b) accelerators/mnemonics are activated
566 c) if (b) didn't handle the event, propagate to
567 the focus widget and/or focus chain
569 The problem with this is that if the accelerators include
570 keys without modifiers, such as the space bar or the
571 letter "e", then pressing the key while typing into
572 a text entry widget results in the accelerator being
573 activated, instead of the desired letter appearing
576 There is no good way of fixing this, but this
577 represents a compromise. The idea is that
578 key events involving modifiers (not Shift)
579 get routed into the activation pathway first, then
580 get propagated to the focus widget if necessary.
582 If the key event doesn't involve modifiers,
583 we deliver to the focus widget first, thus allowing
584 it to get "normal text" without interference
587 Of course, this can also be problematic: if there
588 is a widget with focus, then it will swallow
589 all "normal text" accelerators.
592 if (!special_handling_of_unmodified_accelerators) {
594 /* XXX note that for a brief moment, the conditional above
595 * included "|| (ev->state & mask)" so as to enforce the
596 * implication of special_handling_of_UNMODIFIED_accelerators.
597 * however, this forces any key that GTK doesn't allow and that
598 * we have an alternative (see next comment) for to be
599 * automatically sent through the accel groups activation
600 * pathway, which prevents individual widgets & canvas items
601 * from ever seeing it if is used by a key binding.
603 * specifically, this hid Ctrl-down-arrow from MIDI region
604 * views because it is also bound to an action.
606 * until we have a robust, clean binding system, this
607 * quirk will have to remain in place.
610 /* pretend that certain key events that GTK does not allow
611 to be used as accelerators are actually something that
612 it does allow. but only where there are no modifiers.
615 uint32_t fakekey = ev->keyval;
617 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
618 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
619 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
625 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
627 /* no special handling or there are modifiers in effect: accelerate first */
629 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
631 if (allow_activating) {
632 if (gtk_window_activate_key (win, ev)) {
636 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
639 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
641 return gtk_window_propagate_key_event (win, ev);
644 /* no modifiers, propagate first */
646 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
648 if (!gtk_window_propagate_key_event (win, ev)) {
649 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
650 if (allow_activating) {
651 return gtk_window_activate_key (win, ev);
653 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
657 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
661 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
665 Glib::RefPtr<Gdk::Pixbuf>
666 get_xpm (std::string name)
668 if (!xpm_map[name]) {
670 SearchPath spath(ARDOUR::ardour_search_path());
671 spath += ARDOUR::system_data_search_path();
673 spath.add_subdirectory_to_paths("pixmaps");
675 sys::path data_file_path;
677 if(!find_file_in_search_path (spath, name, data_file_path)) {
678 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
682 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
683 } catch(const Glib::Error& e) {
684 warning << "Caught Glib::Error: " << e.what() << endmsg;
688 return xpm_map[name];
692 get_icon_path (const char* cname)
697 SearchPath spath(ARDOUR::ardour_search_path());
698 spath += ARDOUR::system_data_search_path();
700 spath.add_subdirectory_to_paths("icons");
702 sys::path data_file_path;
704 if (!find_file_in_search_path (spath, name, data_file_path)) {
705 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
708 return data_file_path.to_string();
711 Glib::RefPtr<Gdk::Pixbuf>
712 get_icon (const char* cname)
714 Glib::RefPtr<Gdk::Pixbuf> img;
716 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
717 } catch (const Gdk::PixbufError &e) {
718 cerr << "Caught PixbufError: " << e.what() << endl;
720 g_message("Caught ... ");
727 longest (vector<string>& strings)
729 if (strings.empty()) {
733 vector<string>::iterator longest = strings.begin();
734 string::size_type longest_length = (*longest).length();
736 vector<string>::iterator i = longest;
739 while (i != strings.end()) {
741 string::size_type len = (*i).length();
743 if (len > longest_length) {
745 longest_length = len;
755 key_is_legal_for_numeric_entry (guint keyval)
773 case GDK_KP_Subtract:
802 set_pango_fontsize ()
804 long val = ARDOUR::Config->get_font_scale();
806 /* FT2 rendering - used by GnomeCanvas, sigh */
808 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
810 /* Cairo rendering, in case there is any */
812 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
818 long val = ARDOUR::Config->get_font_scale();
819 set_pango_fontsize ();
822 gtk_settings_set_long_property (gtk_settings_get_default(),
823 "gtk-xft-dpi", val, "ardour");
824 DPIReset();//Emit Signal
828 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
830 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
831 Gdk::Rectangle monitor_rect;
832 screen->get_monitor_geometry (0, monitor_rect);
834 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
835 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
837 window->resize (w, h);
841 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
843 escape_underscores (string const & s)
846 string::size_type const N = s.length ();
848 for (string::size_type i = 0; i < N; ++i) {
860 unique_random_color (list<Gdk::Color>& used_colors)
866 /* avoid neon/glowing tones by limiting them to the
867 "inner section" (paler) of a color wheel/circle.
870 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
872 newcolor.set_red (random() % max_saturation);
873 newcolor.set_blue (random() % max_saturation);
874 newcolor.set_green (random() % max_saturation);
876 if (used_colors.size() == 0) {
877 used_colors.push_back (newcolor);
881 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
883 float rdelta, bdelta, gdelta;
885 rdelta = newcolor.get_red() - c.get_red();
886 bdelta = newcolor.get_blue() - c.get_blue();
887 gdelta = newcolor.get_green() - c.get_green();
889 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
890 used_colors.push_back (newcolor);
895 /* XXX need throttle here to make sure we don't spin for ever */