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"
50 #include "gui_thread.h"
56 using Gtkmm2ext::Keyboard;
58 sigc::signal<void> DPIReset;
61 pixel_width (const ustring& str, Pango::FontDescription& font)
64 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
66 layout->set_font_description (font);
67 layout->set_text (str);
70 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
75 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
78 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
79 ustring::size_type shorter_by = 0;
82 layout->set_font_description (font);
87 ustring::iterator last = ustr.end();
88 --last; /* now points at final entry */
92 while (!ustr.empty()) {
94 layout->set_text (txt);
97 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
99 if (width < pixel_width) {
100 actual_width = width;
107 if (with_ellipses && shorter_by > 3) {
118 /** Try to fit a string into a given horizontal space by ellipsizing it.
119 * @param cr Cairo context in which the text will be plotted.
121 * @param avail Available horizontal space.
122 * @return (Text, possibly ellipsized) and (horizontal size of text)
125 std::pair<std::string, double>
126 fit_to_pixels (cairo_t* cr, std::string name, double avail)
128 /* XXX hopefully there exists a more efficient way of doing this */
130 bool abbreviated = false;
134 cairo_text_extents_t ext;
135 cairo_text_extents (cr, name.c_str(), &ext);
137 if (ext.width < avail || name.length() <= 4) {
143 name = name.substr (0, name.length() - 4) + "...";
145 name = name.substr (0, name.length() - 3) + "...";
150 return std::make_pair (name, width);
154 /** Add an element to a menu, settings its sensitivity.
155 * @param m Menu to add to.
156 * @param e Element to add.
157 * @param s true to make sensitive, false to make insensitive
160 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
164 m.back().set_sensitive (false);
170 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
176 /* xpm2rgb copied from nixieclock, which bore the legend:
178 nixieclock - a nixie desktop timepiece
179 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
181 and was released under the GPL.
185 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
187 static long vals[256], val;
188 uint32_t t, x, y, colors, cpp;
190 unsigned char *savergb, *rgb;
194 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
195 error << string_compose (_("bad XPM header %1"), xpm[0])
200 savergb = rgb = (unsigned char*) malloc (h * w * 3);
202 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
203 for (t = 0; t < colors; ++t) {
204 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
208 // COLORMAP -> RGB CONVERSION
209 // Get low 3 bytes from vals[]
213 for (y = h-1; y > 0; --y) {
215 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
216 val = vals[(int)*p++];
217 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
218 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
219 *(rgb+0) = val & 0xff; // 0:R
227 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
229 static long vals[256], val;
230 uint32_t t, x, y, colors, cpp;
232 unsigned char *savergb, *rgb;
237 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
238 error << string_compose (_("bad XPM header %1"), xpm[0])
243 savergb = rgb = (unsigned char*) malloc (h * w * 4);
245 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
247 if (strstr (xpm[1], "None")) {
248 sscanf (xpm[1], "%c", &transparent);
255 for (; t < colors; ++t) {
256 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
260 // COLORMAP -> RGB CONVERSION
261 // Get low 3 bytes from vals[]
265 for (y = h-1; y > 0; --y) {
269 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
271 if (transparent && (*p++ == transparent)) {
279 *(rgb+3) = alpha; // 3: alpha
280 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
281 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
282 *(rgb+0) = val & 0xff; // 0:R
289 ArdourCanvas::Points*
290 get_canvas_points (string /*who*/, uint32_t npoints)
292 // cerr << who << ": wants " << npoints << " canvas points" << endl;
293 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
294 if (npoints > (uint32_t) gdk_screen_width() + 4) {
298 return new ArdourCanvas::Points (npoints);
301 Pango::FontDescription*
302 get_font_for_style (string widgetname)
304 Gtk::Window window (WINDOW_TOPLEVEL);
306 Glib::RefPtr<Gtk::Style> style;
309 foobar.set_name (widgetname);
310 foobar.ensure_style();
312 style = foobar.get_style ();
314 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
316 PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
320 /* layout inherited its font description from a PangoContext */
322 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
323 pfd = pango_context_get_font_description (ctxt);
324 return new Pango::FontDescription (pfd, true); /* make a copy */
327 return new Pango::FontDescription (pfd, true); /* make a copy */
331 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
333 /* In GTK+2, styles aren't set up correctly if the widget is not
334 attached to a toplevel window that has a screen pointer.
337 static Gtk::Window* window = 0;
340 window = new Window (WINDOW_TOPLEVEL);
347 foo.set_name (style);
350 GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
354 r = waverc->fg[state].red / 257;
355 g = waverc->fg[state].green / 257;
356 b = waverc->fg[state].blue / 257;
358 /* what a hack ... "a" is for "active" */
359 if (state == Gtk::STATE_NORMAL && rgba) {
360 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
362 } else if (attr == "bg") {
364 r = waverc->bg[state].red / 257;
365 g = waverc->bg[state].green / 257;
366 b = waverc->bg[state].blue / 257;
367 } else if (attr == "base") {
368 r = waverc->base[state].red / 257;
369 g = waverc->base[state].green / 257;
370 b = waverc->base[state].blue / 257;
371 } else if (attr == "text") {
372 r = waverc->text[state].red / 257;
373 g = waverc->text[state].green / 257;
374 b = waverc->text[state].blue / 257;
377 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
382 if (state == Gtk::STATE_NORMAL && rgba) {
383 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
385 return (uint32_t) RGB_TO_UINT(r,g,b);
391 color_from_style (string widget_style_name, int state, string attr)
395 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
396 widget_style_name.c_str(),
400 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
401 return Gdk::Color ("red");
405 return Gdk::Color (&style->fg[state]);
409 return Gdk::Color (&style->bg[state]);
412 if (attr == "light") {
413 return Gdk::Color (&style->light[state]);
416 if (attr == "dark") {
417 return Gdk::Color (&style->dark[state]);
421 return Gdk::Color (&style->mid[state]);
424 if (attr == "text") {
425 return Gdk::Color (&style->text[state]);
428 if (attr == "base") {
429 return Gdk::Color (&style->base[state]);
432 if (attr == "text_aa") {
433 return Gdk::Color (&style->text_aa[state]);
436 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
437 return Gdk::Color ("red");
440 Glib::RefPtr<Gdk::GC>
441 gc_from_style (string widget_style_name, int state, string attr)
445 style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
446 widget_style_name.c_str(),
450 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
451 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
452 ret->set_rgb_fg_color(Gdk::Color("red"));
457 return Glib::wrap(style->fg_gc[state]);
461 return Glib::wrap(style->bg_gc[state]);
464 if (attr == "light") {
465 return Glib::wrap(style->light_gc[state]);
468 if (attr == "dark") {
469 return Glib::wrap(style->dark_gc[state]);
473 return Glib::wrap(style->mid_gc[state]);
476 if (attr == "text") {
477 return Glib::wrap(style->text_gc[state]);
480 if (attr == "base") {
481 return Glib::wrap(style->base_gc[state]);
484 if (attr == "text_aa") {
485 return Glib::wrap(style->text_aa_gc[state]);
488 error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
489 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
490 ret->set_rgb_fg_color(Gdk::Color("red"));
496 canvas_item_visible (ArdourCanvas::Item* item)
498 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
502 set_color (Gdk::Color& c, int rgb)
504 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
509 gboolean gdk_quartz_possibly_forward (GdkEvent*);
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;
537 #undef DEBUG_ACCELERATOR_HANDLING
538 #ifdef DEBUG_ACCELERATOR_HANDLING
539 //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
543 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
544 special_handling_of_unmodified_accelerators = true;
549 /* should this be universally true? */
550 if (Keyboard::some_magic_widget_has_focus ()) {
551 allow_activating = false;
555 #ifdef DEBUG_ACCELERATOR_HANDLING
557 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? "
558 << special_handling_of_unmodified_accelerators
559 << " magic widget focus ? "
560 << Keyboard::some_magic_widget_has_focus()
561 << " allow_activation ? "
567 /* This exists to allow us to override the way GTK handles
568 key events. The normal sequence is:
570 a) event is delivered to a GtkWindow
571 b) accelerators/mnemonics are activated
572 c) if (b) didn't handle the event, propagate to
573 the focus widget and/or focus chain
575 The problem with this is that if the accelerators include
576 keys without modifiers, such as the space bar or the
577 letter "e", then pressing the key while typing into
578 a text entry widget results in the accelerator being
579 activated, instead of the desired letter appearing
582 There is no good way of fixing this, but this
583 represents a compromise. The idea is that
584 key events involving modifiers (not Shift)
585 get routed into the activation pathway first, then
586 get propagated to the focus widget if necessary.
588 If the key event doesn't involve modifiers,
589 we deliver to the focus widget first, thus allowing
590 it to get "normal text" without interference
593 Of course, this can also be problematic: if there
594 is a widget with focus, then it will swallow
595 all "normal text" accelerators.
599 if (!special_handling_of_unmodified_accelerators) {
601 /* pretend that certain key events that GTK does not allow
602 to be used as accelerators are actually something that
606 uint32_t fakekey = ev->keyval;
608 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
609 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
614 if (allow_activating) {
615 int oldval = ev->keyval;
616 ev->keyval = fakekey;
617 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
626 /* consider all relevant modifiers but not LOCK or SHIFT */
628 guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
630 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
632 /* no special handling or there are modifiers in effect: accelerate first */
634 #ifdef DEBUG_ACCELERATOR_HANDLING
636 cerr << "\tactivate, then propagate\n";
640 if (allow_activating) {
642 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
646 if (gtk_window_activate_key (win, ev)) {
651 #ifdef DEBUG_ACCELERATOR_HANDLING
653 cerr << "\tnot accelerated, now propagate\n";
656 return gtk_window_propagate_key_event (win, ev);
659 /* no modifiers, propagate first */
661 #ifdef DEBUG_ACCELERATOR_HANDLING
663 cerr << "\tpropagate, then activate\n";
666 if (!gtk_window_propagate_key_event (win, ev)) {
667 #ifdef DEBUG_ACCELERATOR_HANDLING
669 cerr << "\tpropagation didn't handle, so activate\n";
673 if (allow_activating) {
676 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
680 return gtk_window_activate_key (win, ev);
684 #ifdef DEBUG_ACCELERATOR_HANDLING
686 cerr << "\thandled by propagate\n";
692 #ifdef DEBUG_ACCELERATOR_HANDLING
694 cerr << "\tnot handled\n";
700 Glib::RefPtr<Gdk::Pixbuf>
701 get_xpm (std::string name)
703 if (!xpm_map[name]) {
705 SearchPath spath(ARDOUR::ardour_search_path());
706 spath += ARDOUR::system_data_search_path();
708 spath.add_subdirectory_to_paths("pixmaps");
710 sys::path data_file_path;
712 if(!find_file_in_search_path (spath, name, data_file_path)) {
713 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
717 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
718 } catch(const Glib::Error& e) {
719 warning << "Caught Glib::Error: " << e.what() << endmsg;
723 return xpm_map[name];
727 get_icon_path (const char* cname)
732 SearchPath spath(ARDOUR::ardour_search_path());
733 spath += ARDOUR::system_data_search_path();
735 spath.add_subdirectory_to_paths("icons");
737 sys::path data_file_path;
739 if (!find_file_in_search_path (spath, name, data_file_path)) {
740 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
743 return data_file_path.to_string();
746 Glib::RefPtr<Gdk::Pixbuf>
747 get_icon (const char* cname)
749 Glib::RefPtr<Gdk::Pixbuf> img;
751 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
752 } catch (const Gdk::PixbufError &e) {
753 cerr << "Caught PixbufError: " << e.what() << endl;
755 g_message("Caught ... ");
762 longest (vector<string>& strings)
764 if (strings.empty()) {
768 vector<string>::iterator longest = strings.begin();
769 string::size_type longest_length = (*longest).length();
771 vector<string>::iterator i = longest;
774 while (i != strings.end()) {
776 string::size_type len = (*i).length();
778 if (len > longest_length) {
780 longest_length = len;
790 key_is_legal_for_numeric_entry (guint keyval)
808 case GDK_KP_Subtract:
837 set_pango_fontsize ()
839 long val = ARDOUR::Config->get_font_scale();
841 /* FT2 rendering - used by GnomeCanvas, sigh */
843 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
845 /* Cairo rendering, in case there is any */
847 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
853 long val = ARDOUR::Config->get_font_scale();
854 set_pango_fontsize ();
857 gtk_settings_set_long_property (gtk_settings_get_default(),
858 "gtk-xft-dpi", val, "ardour");
859 DPIReset();//Emit Signal
863 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
865 int fakekey = GDK_VoidSymbol;
869 case GDK_ISO_Left_Tab:
874 fakekey = GDK_uparrow;
878 fakekey = GDK_downarrow;
882 fakekey = GDK_rightarrow;
886 fakekey = GDK_leftarrow;
890 fakekey = GDK_3270_Enter;
901 if (fakekey != GDK_VoidSymbol) {
910 possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
947 convert_color_channel (guint8 src,
950 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
954 convert_bgra_to_rgba (guint8 const* src,
959 guint8 const* src_pixel = src;
960 guint8* dst_pixel = dst;
962 for (int y = 0; y < height; y++)
963 for (int x = 0; x < width; x++)
965 dst_pixel[0] = convert_color_channel (src_pixel[2],
967 dst_pixel[1] = convert_color_channel (src_pixel[1],
969 dst_pixel[2] = convert_color_channel (src_pixel[0],
971 dst_pixel[3] = src_pixel[3];
979 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
981 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
982 Gdk::Rectangle monitor_rect;
983 screen->get_monitor_geometry (0, monitor_rect);
985 int const w = std::min (monitor_rect.get_width(), max_width) * 0.8;
986 int const h = std::min (monitor_rect.get_height(), max_height) * 0.8;
988 window->resize (w, h);
991 Glib::RefPtr<Gdk::Pixbuf>
992 pixbuf_from_ustring(const ustring& name, Pango::FontDescription* font, int clip_width, int clip_height, Gdk::Color fg)
994 static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
997 if (empty_pixbuf == 0) {
998 empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
999 *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
1001 return *empty_pixbuf;
1004 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
1006 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
1007 cairo_t* cr = cairo_create (surface);
1008 cairo_text_extents_t te;
1010 cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
1011 cairo_select_font_face (cr, font->get_family().c_str(),
1012 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
1013 cairo_set_font_size (cr, font->get_size() / Pango::SCALE);
1014 cairo_text_extents (cr, name.c_str(), &te);
1016 cairo_move_to (cr, 0.5, 0.5 - te.height / 2 - te.y_bearing + clip_height / 2);
1017 cairo_show_text (cr, name.c_str());
1019 convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
1022 cairo_surface_destroy(surface);
1027 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
1029 escape_underscores (string const & s)
1032 string::size_type const N = s.length ();
1034 for (string::size_type i = 0; i < N; ++i) {
1046 adjustment_to_controllable (Gtk::Adjustment* adj, boost::weak_ptr<Controllable> wcont)
1048 boost::shared_ptr<Controllable> cont = wcont.lock();
1051 double val = adj->get_value();
1052 if (val != cont->get_value()) {
1053 cont->set_value (val);
1059 controllable_to_adjustment (Gtk::Adjustment* adj, boost::weak_ptr<Controllable> wcont)
1061 boost::shared_ptr<Controllable> cont = wcont.lock();
1064 float val = cont->get_value();
1066 if (val != adj->get_value()) {
1067 adj->set_value (val);
1073 control_link (ScopedConnectionList& scl, boost::shared_ptr<Controllable> c, Gtk::Adjustment& a)
1075 boost::weak_ptr<Controllable> wc (c);
1077 a.signal_value_changed().connect (sigc::bind (sigc::ptr_fun (adjustment_to_controllable), &a, wc));
1078 c->Changed.connect (scl, MISSING_INVALIDATOR, boost::bind (controllable_to_adjustment, &a, wc),