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"
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 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
525 GtkWindow* win = window.gobj();
526 GtkWidget* focus = gtk_window_get_focus (win);
527 bool special_handling_of_unmodified_accelerators = false;
528 bool allow_activating = true;
530 #undef DEBUG_ACCELERATOR_HANDLING
531 #ifdef DEBUG_ACCELERATOR_HANDLING
532 //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
536 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
537 special_handling_of_unmodified_accelerators = true;
542 /* should this be universally true? */
543 if (Keyboard::some_magic_widget_has_focus ()) {
544 allow_activating = false;
548 #ifdef DEBUG_ACCELERATOR_HANDLING
550 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? "
551 << special_handling_of_unmodified_accelerators
552 << " magic widget focus ? "
553 << Keyboard::some_magic_widget_has_focus()
554 << " allow_activation ? "
560 /* This exists to allow us to override the way GTK handles
561 key events. The normal sequence is:
563 a) event is delivered to a GtkWindow
564 b) accelerators/mnemonics are activated
565 c) if (b) didn't handle the event, propagate to
566 the focus widget and/or focus chain
568 The problem with this is that if the accelerators include
569 keys without modifiers, such as the space bar or the
570 letter "e", then pressing the key while typing into
571 a text entry widget results in the accelerator being
572 activated, instead of the desired letter appearing
575 There is no good way of fixing this, but this
576 represents a compromise. The idea is that
577 key events involving modifiers (not Shift)
578 get routed into the activation pathway first, then
579 get propagated to the focus widget if necessary.
581 If the key event doesn't involve modifiers,
582 we deliver to the focus widget first, thus allowing
583 it to get "normal text" without interference
586 Of course, this can also be problematic: if there
587 is a widget with focus, then it will swallow
588 all "normal text" accelerators.
592 if (!special_handling_of_unmodified_accelerators) {
594 /* pretend that certain key events that GTK does not allow
595 to be used as accelerators are actually something that
599 uint32_t fakekey = ev->keyval;
601 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
602 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
607 if (allow_activating) {
608 int oldval = ev->keyval;
609 ev->keyval = fakekey;
610 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
619 /* consider all relevant modifiers but not LOCK or SHIFT */
621 guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
623 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
625 /* no special handling or there are modifiers in effect: accelerate first */
627 #ifdef DEBUG_ACCELERATOR_HANDLING
629 cerr << "\tactivate, then propagate\n";
633 if (allow_activating) {
635 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
639 if (gtk_window_activate_key (win, ev)) {
644 #ifdef DEBUG_ACCELERATOR_HANDLING
646 cerr << "\tnot accelerated, now propagate\n";
649 return gtk_window_propagate_key_event (win, ev);
652 /* no modifiers, propagate first */
654 #ifdef DEBUG_ACCELERATOR_HANDLING
656 cerr << "\tpropagate, then activate\n";
659 if (!gtk_window_propagate_key_event (win, ev)) {
660 #ifdef DEBUG_ACCELERATOR_HANDLING
662 cerr << "\tpropagation didn't handle, so activate\n";
666 if (allow_activating) {
669 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
673 return gtk_window_activate_key (win, ev);
677 #ifdef DEBUG_ACCELERATOR_HANDLING
679 cerr << "\thandled by propagate\n";
685 #ifdef DEBUG_ACCELERATOR_HANDLING
687 cerr << "\tnot handled\n";
693 Glib::RefPtr<Gdk::Pixbuf>
694 get_xpm (std::string name)
696 if (!xpm_map[name]) {
698 SearchPath spath(ARDOUR::ardour_search_path());
699 spath += ARDOUR::system_data_search_path();
701 spath.add_subdirectory_to_paths("pixmaps");
703 sys::path data_file_path;
705 if(!find_file_in_search_path (spath, name, data_file_path)) {
706 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
710 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
711 } catch(const Glib::Error& e) {
712 warning << "Caught Glib::Error: " << e.what() << endmsg;
716 return xpm_map[name];
720 get_icon_path (const char* cname)
725 SearchPath spath(ARDOUR::ardour_search_path());
726 spath += ARDOUR::system_data_search_path();
728 spath.add_subdirectory_to_paths("icons");
730 sys::path data_file_path;
732 if (!find_file_in_search_path (spath, name, data_file_path)) {
733 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
736 return data_file_path.to_string();
739 Glib::RefPtr<Gdk::Pixbuf>
740 get_icon (const char* cname)
742 Glib::RefPtr<Gdk::Pixbuf> img;
744 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
745 } catch (const Gdk::PixbufError &e) {
746 cerr << "Caught PixbufError: " << e.what() << endl;
748 g_message("Caught ... ");
755 longest (vector<string>& strings)
757 if (strings.empty()) {
761 vector<string>::iterator longest = strings.begin();
762 string::size_type longest_length = (*longest).length();
764 vector<string>::iterator i = longest;
767 while (i != strings.end()) {
769 string::size_type len = (*i).length();
771 if (len > longest_length) {
773 longest_length = len;
783 key_is_legal_for_numeric_entry (guint keyval)
801 case GDK_KP_Subtract:
830 set_pango_fontsize ()
832 long val = ARDOUR::Config->get_font_scale();
834 /* FT2 rendering - used by GnomeCanvas, sigh */
836 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
838 /* Cairo rendering, in case there is any */
840 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
846 long val = ARDOUR::Config->get_font_scale();
847 set_pango_fontsize ();
850 gtk_settings_set_long_property (gtk_settings_get_default(),
851 "gtk-xft-dpi", val, "ardour");
852 DPIReset();//Emit Signal
856 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
858 int fakekey = GDK_VoidSymbol;
862 case GDK_ISO_Left_Tab:
867 fakekey = GDK_uparrow;
871 fakekey = GDK_downarrow;
875 fakekey = GDK_rightarrow;
879 fakekey = GDK_leftarrow;
883 fakekey = GDK_3270_Enter;
894 if (fakekey != GDK_VoidSymbol) {
903 possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
940 convert_color_channel (guint8 src,
943 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
947 convert_bgra_to_rgba (guint8 const* src,
952 guint8 const* src_pixel = src;
953 guint8* dst_pixel = dst;
955 for (int y = 0; y < height; y++)
956 for (int x = 0; x < width; x++)
958 dst_pixel[0] = convert_color_channel (src_pixel[2],
960 dst_pixel[1] = convert_color_channel (src_pixel[1],
962 dst_pixel[2] = convert_color_channel (src_pixel[0],
964 dst_pixel[3] = src_pixel[3];
972 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
974 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
975 Gdk::Rectangle monitor_rect;
976 screen->get_monitor_geometry (0, monitor_rect);
978 int const w = std::min (monitor_rect.get_width(), max_width) * 0.8;
979 int const h = std::min (monitor_rect.get_height(), max_height) * 0.8;
981 window->resize (w, h);
984 Glib::RefPtr<Gdk::Pixbuf>
985 pixbuf_from_ustring(const ustring& name, Pango::FontDescription* font, int clip_width, int clip_height, Gdk::Color fg)
987 static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
990 if (empty_pixbuf == 0) {
991 empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
992 *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
994 return *empty_pixbuf;
997 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
999 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
1000 cairo_t* cr = cairo_create (surface);
1001 cairo_text_extents_t te;
1003 cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
1004 cairo_select_font_face (cr, font->get_family().c_str(),
1005 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
1006 cairo_set_font_size (cr, font->get_size() / Pango::SCALE);
1007 cairo_text_extents (cr, name.c_str(), &te);
1009 cairo_move_to (cr, 0.5, 0.5 - te.height / 2 - te.y_bearing + clip_height / 2);
1010 cairo_show_text (cr, name.c_str());
1012 convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
1015 cairo_surface_destroy(surface);