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.
20 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
21 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
27 #include <libart_lgpl/art_misc.h>
29 #include <gtkmm/window.h>
30 #include <gtkmm/combo.h>
31 #include <gtkmm/label.h>
32 #include <gtkmm/paned.h>
33 #include <gtk/gtkpaned.h>
35 #include "pbd/file_utils.h"
37 #include <gtkmm2ext/utils.h>
38 #include "ardour/configuration.h"
39 #include "ardour/rc_configuration.h"
41 #include "ardour/filesystem_paths.h"
43 #include "ardour_ui.h"
44 #include "public_editor.h"
48 #include "rgb_macros.h"
49 #include "canvas_impl.h"
55 using Gtkmm2ext::Keyboard;
57 sigc::signal<void> DPIReset;
60 pixel_width (const ustring& str, Pango::FontDescription& font)
63 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
65 layout->set_font_description (font);
66 layout->set_text (str);
69 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
74 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
77 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
78 ustring::size_type shorter_by = 0;
81 layout->set_font_description (font);
86 ustring::iterator last = ustr.end();
87 --last; /* now points at final entry */
91 while (!ustr.empty()) {
93 layout->set_text (txt);
96 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
98 if (width < pixel_width) {
106 if (with_ellipses && shorter_by > 3) {
117 /** Try to fit a string into a given horizontal space by ellipsizing it.
118 * @param cr Cairo context in which the text will be plotted.
120 * @param avail Available horizontal space.
121 * @return (Text, possibly ellipsized) and (horizontal size of text)
124 std::pair<std::string, double>
125 fit_to_pixels (cairo_t* cr, std::string name, double avail)
127 /* XXX hopefully there exists a more efficient way of doing this */
129 bool abbreviated = false;
133 cairo_text_extents_t ext;
134 cairo_text_extents (cr, name.c_str(), &ext);
136 if (ext.width < avail || name.length() <= 4) {
142 name = name.substr (0, name.length() - 4) + "...";
144 name = name.substr (0, name.length() - 3) + "...";
149 return std::make_pair (name, width);
153 /** Add an element to a menu, settings its sensitivity.
154 * @param m Menu to add to.
155 * @param e Element to add.
156 * @param s true to make sensitive, false to make insensitive
159 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
163 m.back().set_sensitive (false);
169 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
175 /* xpm2rgb copied from nixieclock, which bore the legend:
177 nixieclock - a nixie desktop timepiece
178 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
180 and was released under the GPL.
184 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
186 static long vals[256], val;
187 uint32_t t, x, y, colors, cpp;
189 unsigned char *savergb, *rgb;
193 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
194 error << string_compose (_("bad XPM header %1"), xpm[0])
199 savergb = rgb = (unsigned char*) malloc (h * w * 3);
201 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
202 for (t = 0; t < colors; ++t) {
203 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
207 // COLORMAP -> RGB CONVERSION
208 // Get low 3 bytes from vals[]
212 for (y = h-1; y > 0; --y) {
214 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
215 val = vals[(int)*p++];
216 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
217 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
218 *(rgb+0) = val & 0xff; // 0:R
226 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
228 static long vals[256], val;
229 uint32_t t, x, y, colors, cpp;
231 unsigned char *savergb, *rgb;
236 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
237 error << string_compose (_("bad XPM header %1"), xpm[0])
242 savergb = rgb = (unsigned char*) malloc (h * w * 4);
244 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
246 if (strstr (xpm[1], "None")) {
247 sscanf (xpm[1], "%c", &transparent);
254 for (; t < colors; ++t) {
255 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
259 // COLORMAP -> RGB CONVERSION
260 // Get low 3 bytes from vals[]
264 for (y = h-1; y > 0; --y) {
268 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
270 if (transparent && (*p++ == transparent)) {
278 *(rgb+3) = alpha; // 3: alpha
279 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
280 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
281 *(rgb+0) = val & 0xff; // 0:R
288 ArdourCanvas::Points*
289 get_canvas_points (string /*who*/, uint32_t npoints)
291 // cerr << who << ": wants " << npoints << " canvas points" << endl;
292 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
293 if (npoints > (uint32_t) gdk_screen_width() + 4) {
297 return new ArdourCanvas::Points (npoints);
300 Pango::FontDescription*
301 get_font_for_style (string widgetname)
303 Gtk::Window window (WINDOW_TOPLEVEL);
305 Glib::RefPtr<Gtk::Style> style;
308 foobar.set_name (widgetname);
309 foobar.ensure_style();
311 style = foobar.get_style ();
313 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
315 PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
319 /* layout inherited its font description from a PangoContext */
321 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
322 pfd = pango_context_get_font_description (ctxt);
323 return new Pango::FontDescription (pfd, true); /* make a copy */
326 return new Pango::FontDescription (pfd, true); /* make a copy */
330 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
332 /* In GTK+2, styles aren't set up correctly if the widget is not
333 attached to a toplevel window that has a screen pointer.
336 static Gtk::Window* window = 0;
339 window = new Window (WINDOW_TOPLEVEL);
346 foo.set_name (style);
349 GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
353 r = waverc->fg[state].red / 257;
354 g = waverc->fg[state].green / 257;
355 b = waverc->fg[state].blue / 257;
357 /* what a hack ... "a" is for "active" */
358 if (state == Gtk::STATE_NORMAL && rgba) {
359 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
361 } else if (attr == "bg") {
363 r = waverc->bg[state].red / 257;
364 g = waverc->bg[state].green / 257;
365 b = waverc->bg[state].blue / 257;
366 } else if (attr == "base") {
367 r = waverc->base[state].red / 257;
368 g = waverc->base[state].green / 257;
369 b = waverc->base[state].blue / 257;
370 } else if (attr == "text") {
371 r = waverc->text[state].red / 257;
372 g = waverc->text[state].green / 257;
373 b = waverc->text[state].blue / 257;
376 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
381 if (state == Gtk::STATE_NORMAL && rgba) {
382 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
384 return (uint32_t) RGB_TO_UINT(r,g,b);
390 color_from_style (string widget_style_name, int state, string attr)
394 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
395 widget_style_name.c_str(),
399 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
400 return Gdk::Color ("red");
404 return Gdk::Color (&style->fg[state]);
408 return Gdk::Color (&style->bg[state]);
411 if (attr == "light") {
412 return Gdk::Color (&style->light[state]);
415 if (attr == "dark") {
416 return Gdk::Color (&style->dark[state]);
420 return Gdk::Color (&style->mid[state]);
423 if (attr == "text") {
424 return Gdk::Color (&style->text[state]);
427 if (attr == "base") {
428 return Gdk::Color (&style->base[state]);
431 if (attr == "text_aa") {
432 return Gdk::Color (&style->text_aa[state]);
435 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
436 return Gdk::Color ("red");
439 Glib::RefPtr<Gdk::GC>
440 gc_from_style (string widget_style_name, int state, string attr)
444 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
445 widget_style_name.c_str(),
449 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
450 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
451 ret->set_rgb_fg_color(Gdk::Color("red"));
456 return Glib::wrap(style->fg_gc[state]);
460 return Glib::wrap(style->bg_gc[state]);
463 if (attr == "light") {
464 return Glib::wrap(style->light_gc[state]);
467 if (attr == "dark") {
468 return Glib::wrap(style->dark_gc[state]);
472 return Glib::wrap(style->mid_gc[state]);
475 if (attr == "text") {
476 return Glib::wrap(style->text_gc[state]);
479 if (attr == "base") {
480 return Glib::wrap(style->base_gc[state]);
483 if (attr == "text_aa") {
484 return Glib::wrap(style->text_aa_gc[state]);
487 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
488 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
489 ret->set_rgb_fg_color(Gdk::Color("red"));
495 canvas_item_visible (ArdourCanvas::Item* item)
497 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
501 set_color (Gdk::Color& c, int rgb)
503 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
508 gboolean gdk_quartz_possibly_forward (GdkEvent*);
513 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
515 if (!key_press_focus_accelerator_handler (*win, ev)) {
516 return PublicEditor::instance().on_key_press_event(ev);
523 forward_key_press (GdkEventKey* ev)
525 return PublicEditor::instance().on_key_press_event(ev);
529 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
531 GtkWindow* win = window.gobj();
532 GtkWidget* focus = gtk_window_get_focus (win);
533 bool special_handling_of_unmodified_accelerators = false;
534 bool allow_activating = true;
536 #undef DEBUG_ACCELERATOR_HANDLING
537 #ifdef DEBUG_ACCELERATOR_HANDLING
538 //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
542 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
543 special_handling_of_unmodified_accelerators = true;
548 /* should this be universally true? */
549 if (Keyboard::some_magic_widget_has_focus ()) {
550 allow_activating = false;
554 #ifdef DEBUG_ACCELERATOR_HANDLING
556 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? "
557 << special_handling_of_unmodified_accelerators
558 << " magic widget focus ? "
559 << Keyboard::some_magic_widget_has_focus()
560 << " allow_activation ? "
566 /* This exists to allow us to override the way GTK handles
567 key events. The normal sequence is:
569 a) event is delivered to a GtkWindow
570 b) accelerators/mnemonics are activated
571 c) if (b) didn't handle the event, propagate to
572 the focus widget and/or focus chain
574 The problem with this is that if the accelerators include
575 keys without modifiers, such as the space bar or the
576 letter "e", then pressing the key while typing into
577 a text entry widget results in the accelerator being
578 activated, instead of the desired letter appearing
581 There is no good way of fixing this, but this
582 represents a compromise. The idea is that
583 key events involving modifiers (not Shift)
584 get routed into the activation pathway first, then
585 get propagated to the focus widget if necessary.
587 If the key event doesn't involve modifiers,
588 we deliver to the focus widget first, thus allowing
589 it to get "normal text" without interference
592 Of course, this can also be problematic: if there
593 is a widget with focus, then it will swallow
594 all "normal text" accelerators.
598 if (!special_handling_of_unmodified_accelerators) {
600 /* pretend that certain key events that GTK does not allow
601 to be used as accelerators are actually something that
605 uint32_t fakekey = ev->keyval;
607 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
608 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
613 if (allow_activating) {
614 int oldval = ev->keyval;
615 ev->keyval = fakekey;
616 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
625 /* consider all relevant modifiers but not LOCK or SHIFT */
627 guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
629 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
631 /* no special handling or there are modifiers in effect: accelerate first */
633 #ifdef DEBUG_ACCELERATOR_HANDLING
635 cerr << "\tactivate, then propagate\n";
639 if (allow_activating) {
641 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
645 if (gtk_window_activate_key (win, ev)) {
650 #ifdef DEBUG_ACCELERATOR_HANDLING
652 cerr << "\tnot accelerated, now propagate\n";
655 return gtk_window_propagate_key_event (win, ev);
658 /* no modifiers, propagate first */
660 #ifdef DEBUG_ACCELERATOR_HANDLING
662 cerr << "\tpropagate, then activate\n";
665 if (!gtk_window_propagate_key_event (win, ev)) {
666 #ifdef DEBUG_ACCELERATOR_HANDLING
668 cerr << "\tpropagation didn't handle, so activate\n";
672 if (allow_activating) {
675 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
679 return gtk_window_activate_key (win, ev);
683 #ifdef DEBUG_ACCELERATOR_HANDLING
685 cerr << "\thandled by propagate\n";
691 #ifdef DEBUG_ACCELERATOR_HANDLING
693 cerr << "\tnot handled\n";
699 Glib::RefPtr<Gdk::Pixbuf>
700 get_xpm (std::string name)
702 if (!xpm_map[name]) {
704 SearchPath spath(ARDOUR::ardour_search_path());
705 spath += ARDOUR::system_data_search_path();
707 spath.add_subdirectory_to_paths("pixmaps");
709 sys::path data_file_path;
711 if(!find_file_in_search_path (spath, name, data_file_path)) {
712 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
716 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
717 } catch(const Glib::Error& e) {
718 warning << "Caught Glib::Error: " << e.what() << endmsg;
722 return xpm_map[name];
726 get_icon_path (const char* cname)
731 SearchPath spath(ARDOUR::ardour_search_path());
732 spath += ARDOUR::system_data_search_path();
734 spath.add_subdirectory_to_paths("icons");
736 sys::path data_file_path;
738 if (!find_file_in_search_path (spath, name, data_file_path)) {
739 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
742 return data_file_path.to_string();
745 Glib::RefPtr<Gdk::Pixbuf>
746 get_icon (const char* cname)
748 Glib::RefPtr<Gdk::Pixbuf> img;
750 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
751 } catch (const Gdk::PixbufError &e) {
752 cerr << "Caught PixbufError: " << e.what() << endl;
754 g_message("Caught ... ");
761 longest (vector<string>& strings)
763 if (strings.empty()) {
767 vector<string>::iterator longest = strings.begin();
768 string::size_type longest_length = (*longest).length();
770 vector<string>::iterator i = longest;
773 while (i != strings.end()) {
775 string::size_type len = (*i).length();
777 if (len > longest_length) {
779 longest_length = len;
789 key_is_legal_for_numeric_entry (guint keyval)
807 case GDK_KP_Subtract:
836 set_pango_fontsize ()
838 long val = ARDOUR::Config->get_font_scale();
840 /* FT2 rendering - used by GnomeCanvas, sigh */
842 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
844 /* Cairo rendering, in case there is any */
846 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
852 long val = ARDOUR::Config->get_font_scale();
853 set_pango_fontsize ();
856 gtk_settings_set_long_property (gtk_settings_get_default(),
857 "gtk-xft-dpi", val, "ardour");
858 DPIReset();//Emit Signal
862 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
864 int fakekey = GDK_VoidSymbol;
868 case GDK_ISO_Left_Tab:
873 fakekey = GDK_uparrow;
877 fakekey = GDK_downarrow;
881 fakekey = GDK_rightarrow;
885 fakekey = GDK_leftarrow;
889 fakekey = GDK_3270_Enter;
900 if (fakekey != GDK_VoidSymbol) {
909 possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
946 convert_color_channel (guint8 src,
949 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
953 convert_bgra_to_rgba (guint8 const* src,
958 guint8 const* src_pixel = src;
959 guint8* dst_pixel = dst;
961 for (int y = 0; y < height; y++)
962 for (int x = 0; x < width; x++)
964 dst_pixel[0] = convert_color_channel (src_pixel[2],
966 dst_pixel[1] = convert_color_channel (src_pixel[1],
968 dst_pixel[2] = convert_color_channel (src_pixel[0],
970 dst_pixel[3] = src_pixel[3];
978 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
980 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
981 Gdk::Rectangle monitor_rect;
982 screen->get_monitor_geometry (0, monitor_rect);
984 int const w = std::min (monitor_rect.get_width(), max_width) * 0.8;
985 int const h = std::min (monitor_rect.get_height(), max_height) * 0.8;
987 window->resize (w, h);
990 Glib::RefPtr<Gdk::Pixbuf>
991 pixbuf_from_ustring(const ustring& name, Pango::FontDescription* font, int clip_width, int clip_height, Gdk::Color fg)
993 static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
996 if (empty_pixbuf == 0) {
997 empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
998 *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
1000 return *empty_pixbuf;
1003 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
1005 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
1006 cairo_t* cr = cairo_create (surface);
1007 cairo_text_extents_t te;
1009 cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
1010 cairo_select_font_face (cr, font->get_family().c_str(),
1011 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
1012 cairo_set_font_size (cr, font->get_size() / Pango::SCALE);
1013 cairo_text_extents (cr, name.c_str(), &te);
1015 cairo_move_to (cr, 0.5, 0.5 - te.height / 2 - te.y_bearing + clip_height / 2);
1016 cairo_show_text (cr, name.c_str());
1018 convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
1021 cairo_surface_destroy(surface);
1026 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
1028 escape_underscores (string const & s)
1031 string::size_type const N = s.length ();
1033 for (string::size_type i = 0; i < N; ++i) {