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
31 #include <libart_lgpl/art_misc.h>
33 #include <gtkmm/window.h>
34 #include <gtkmm/combo.h>
35 #include <gtkmm/label.h>
36 #include <gtkmm/paned.h>
37 #include <gtk/gtkpaned.h>
39 #include "pbd/file_utils.h"
41 #include <gtkmm2ext/utils.h>
42 #include "ardour/configuration.h"
43 #include "ardour/rc_configuration.h"
45 #include "ardour/filesystem_paths.h"
47 #include "ardour_ui.h"
49 #include "public_editor.h"
53 #include "rgb_macros.h"
54 #include "canvas_impl.h"
55 #include "gui_thread.h"
61 using Gtkmm2ext::Keyboard
;
63 sigc::signal
<void> DPIReset
;
66 pixel_width (const string
& str
, Pango::FontDescription
& font
)
69 Glib::RefPtr
<Pango::Layout
> layout
= foo
.create_pango_layout ("");
71 layout
->set_font_description (font
);
72 layout
->set_text (str
);
75 Gtkmm2ext::get_ink_pixel_size (layout
, width
, height
);
80 fit_to_pixels (const string
& str
, int pixel_width
, Pango::FontDescription
& font
, int& actual_width
, bool with_ellipses
)
83 Glib::RefPtr
<Pango::Layout
> layout
= foo
.create_pango_layout ("");
84 string::size_type shorter_by
= 0;
87 layout
->set_font_description (font
);
92 string::iterator last
= ustr
.end();
93 --last
; /* now points at final entry */
97 while (!ustr
.empty()) {
99 layout
->set_text (txt
);
102 Gtkmm2ext::get_ink_pixel_size (layout
, width
, height
);
104 if (width
< pixel_width
) {
105 actual_width
= width
;
112 if (with_ellipses
&& shorter_by
> 3) {
123 /** Try to fit a string into a given horizontal space by ellipsizing it.
124 * @param cr Cairo context in which the text will be plotted.
126 * @param avail Available horizontal space.
127 * @return (Text, possibly ellipsized) and (horizontal size of text)
130 std::pair
<std::string
, double>
131 fit_to_pixels (cairo_t
* cr
, std::string name
, double avail
)
133 /* XXX hopefully there exists a more efficient way of doing this */
135 bool abbreviated
= false;
139 cairo_text_extents_t ext
;
140 cairo_text_extents (cr
, name
.c_str(), &ext
);
142 if (ext
.width
< avail
|| name
.length() <= 4) {
148 name
= name
.substr (0, name
.length() - 4) + "...";
150 name
= name
.substr (0, name
.length() - 3) + "...";
155 return std::make_pair (name
, width
);
159 /** Add an element to a menu, settings its sensitivity.
160 * @param m Menu to add to.
161 * @param e Element to add.
162 * @param s true to make sensitive, false to make insensitive
165 add_item_with_sensitivity (Menu_Helpers::MenuList
& m
, Menu_Helpers::MenuElem e
, bool s
)
169 m
.back().set_sensitive (false);
175 just_hide_it (GdkEventAny */
*ev*/
, Gtk::Window
*win
)
181 /* xpm2rgb copied from nixieclock, which bore the legend:
183 nixieclock - a nixie desktop timepiece
184 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
186 and was released under the GPL.
190 xpm2rgb (const char** xpm
, uint32_t& w
, uint32_t& h
)
192 static long vals
[256], val
;
193 uint32_t t
, x
, y
, colors
, cpp
;
195 unsigned char *savergb
, *rgb
;
199 if ( sscanf(xpm
[0], "%u%u%u%u", &w
, &h
, &colors
, &cpp
) != 4 ) {
200 error
<< string_compose (_("bad XPM header %1"), xpm
[0])
205 savergb
= rgb
= (unsigned char*) malloc (h
* w
* 3);
207 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
208 for (t
= 0; t
< colors
; ++t
) {
209 sscanf (xpm
[t
+1], "%c c #%lx", &c
, &val
);
213 // COLORMAP -> RGB CONVERSION
214 // Get low 3 bytes from vals[]
218 for (y
= h
-1; y
> 0; --y
) {
220 for (p
= xpm
[1+colors
+(h
-y
-1)], x
= 0; x
< w
; x
++, rgb
+= 3) {
221 val
= vals
[(int)*p
++];
222 *(rgb
+2) = val
& 0xff; val
>>= 8; // 2:B
223 *(rgb
+1) = val
& 0xff; val
>>= 8; // 1:G
224 *(rgb
+0) = val
& 0xff; // 0:R
232 xpm2rgba (const char** xpm
, uint32_t& w
, uint32_t& h
)
234 static long vals
[256], val
;
235 uint32_t t
, x
, y
, colors
, cpp
;
237 unsigned char *savergb
, *rgb
;
242 if ( sscanf(xpm
[0], "%u%u%u%u", &w
, &h
, &colors
, &cpp
) != 4 ) {
243 error
<< string_compose (_("bad XPM header %1"), xpm
[0])
248 savergb
= rgb
= (unsigned char*) malloc (h
* w
* 4);
250 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
252 if (strstr (xpm
[1], "None")) {
253 sscanf (xpm
[1], "%c", &transparent
);
260 for (; t
< colors
; ++t
) {
261 sscanf (xpm
[t
+1], "%c c #%lx", &c
, &val
);
265 // COLORMAP -> RGB CONVERSION
266 // Get low 3 bytes from vals[]
270 for (y
= h
-1; y
> 0; --y
) {
274 for (p
= xpm
[1+colors
+(h
-y
-1)], x
= 0; x
< w
; x
++, rgb
+= 4) {
276 if (transparent
&& (*p
++ == transparent
)) {
284 *(rgb
+3) = alpha
; // 3: alpha
285 *(rgb
+2) = val
& 0xff; val
>>= 8; // 2:B
286 *(rgb
+1) = val
& 0xff; val
>>= 8; // 1:G
287 *(rgb
+0) = val
& 0xff; // 0:R
294 ArdourCanvas::Points
*
295 get_canvas_points (string
/*who*/, uint32_t npoints
)
297 // cerr << who << ": wants " << npoints << " canvas points" << endl;
298 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
299 if (npoints
> (uint32_t) gdk_screen_width() + 4) {
303 return new ArdourCanvas::Points (npoints
);
306 Pango::FontDescription
*
307 get_font_for_style (string widgetname
)
309 Gtk::Window
window (WINDOW_TOPLEVEL
);
311 Glib::RefPtr
<Gtk::Style
> style
;
314 foobar
.set_name (widgetname
);
315 foobar
.ensure_style();
317 style
= foobar
.get_style ();
319 Glib::RefPtr
<const Pango::Layout
> layout
= foobar
.get_layout();
321 PangoFontDescription
*pfd
= (PangoFontDescription
*)pango_layout_get_font_description((PangoLayout
*)layout
->gobj());
325 /* layout inherited its font description from a PangoContext */
327 PangoContext
* ctxt
= (PangoContext
*) pango_layout_get_context ((PangoLayout
*) layout
->gobj());
328 pfd
= pango_context_get_font_description (ctxt
);
329 return new Pango::FontDescription (pfd
, true); /* make a copy */
332 return new Pango::FontDescription (pfd
, true); /* make a copy */
336 rgba_from_style (string style
, uint32_t r
, uint32_t g
, uint32_t b
, uint32_t a
, string attr
, int state
, bool rgba
)
338 /* In GTK+2, styles aren't set up correctly if the widget is not
339 attached to a toplevel window that has a screen pointer.
342 static Gtk::Window
* window
= 0;
345 window
= new Window (WINDOW_TOPLEVEL
);
352 foo
.set_name (style
);
355 GtkRcStyle
* rc
= foo
.get_style()->gobj()->rc_style
;
359 r
= rc
->fg
[state
].red
/ 257;
360 g
= rc
->fg
[state
].green
/ 257;
361 b
= rc
->fg
[state
].blue
/ 257;
363 /* what a hack ... "a" is for "active" */
364 if (state
== Gtk::STATE_NORMAL
&& rgba
) {
365 a
= rc
->fg
[GTK_STATE_ACTIVE
].red
/ 257;
367 } else if (attr
== "bg") {
369 r
= rc
->bg
[state
].red
/ 257;
370 g
= rc
->bg
[state
].green
/ 257;
371 b
= rc
->bg
[state
].blue
/ 257;
372 } else if (attr
== "base") {
373 r
= rc
->base
[state
].red
/ 257;
374 g
= rc
->base
[state
].green
/ 257;
375 b
= rc
->base
[state
].blue
/ 257;
376 } else if (attr
== "text") {
377 r
= rc
->text
[state
].red
/ 257;
378 g
= rc
->text
[state
].green
/ 257;
379 b
= rc
->text
[state
].blue
/ 257;
382 warning
<< string_compose (_("missing RGBA style for \"%1\""), style
) << endl
;
387 if (state
== Gtk::STATE_NORMAL
&& rgba
) {
388 return (uint32_t) RGBA_TO_UINT(r
,g
,b
,a
);
390 return (uint32_t) RGB_TO_UINT(r
,g
,b
);
396 color_from_style (string widget_style_name
, int state
, string attr
)
400 style
= gtk_rc_get_style_by_paths (gtk_settings_get_default(),
401 widget_style_name
.c_str(),
405 error
<< string_compose (_("no style found for %1, using red"), style
) << endmsg
;
406 return Gdk::Color ("red");
410 return Gdk::Color (&style
->fg
[state
]);
414 return Gdk::Color (&style
->bg
[state
]);
417 if (attr
== "light") {
418 return Gdk::Color (&style
->light
[state
]);
421 if (attr
== "dark") {
422 return Gdk::Color (&style
->dark
[state
]);
426 return Gdk::Color (&style
->mid
[state
]);
429 if (attr
== "text") {
430 return Gdk::Color (&style
->text
[state
]);
433 if (attr
== "base") {
434 return Gdk::Color (&style
->base
[state
]);
437 if (attr
== "text_aa") {
438 return Gdk::Color (&style
->text_aa
[state
]);
441 error
<< string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr
) << endmsg
;
442 return Gdk::Color ("red");
445 Glib::RefPtr
<Gdk::GC
>
446 gc_from_style (string widget_style_name
, int state
, string attr
)
450 style
= gtk_rc_get_style_by_paths (gtk_settings_get_default(),
451 widget_style_name
.c_str(),
455 error
<< string_compose (_("no style found for %1, using red"), style
) << endmsg
;
456 Glib::RefPtr
<Gdk::GC
> ret
= Gdk::GC::create();
457 ret
->set_rgb_fg_color(Gdk::Color("red"));
462 return Glib::wrap(style
->fg_gc
[state
]);
466 return Glib::wrap(style
->bg_gc
[state
]);
469 if (attr
== "light") {
470 return Glib::wrap(style
->light_gc
[state
]);
473 if (attr
== "dark") {
474 return Glib::wrap(style
->dark_gc
[state
]);
478 return Glib::wrap(style
->mid_gc
[state
]);
481 if (attr
== "text") {
482 return Glib::wrap(style
->text_gc
[state
]);
485 if (attr
== "base") {
486 return Glib::wrap(style
->base_gc
[state
]);
489 if (attr
== "text_aa") {
490 return Glib::wrap(style
->text_aa_gc
[state
]);
493 error
<< string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr
) << endmsg
;
494 Glib::RefPtr
<Gdk::GC
> ret
= Gdk::GC::create();
495 ret
->set_rgb_fg_color(Gdk::Color("red"));
501 canvas_item_visible (ArdourCanvas::Item
* item
)
503 return (item
->gobj()->object
.flags
& GNOME_CANVAS_ITEM_VISIBLE
) ? true : false;
507 set_color (Gdk::Color
& c
, int rgb
)
509 c
.set_rgb((rgb
>> 16)*256, ((rgb
& 0xff00) >> 8)*256, (rgb
& 0xff)*256);
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
);
530 osx_keyval_without_alt (guint accent_keyval
)
532 switch (accent_keyval
) {
547 case GDK_leftdoublequotemark
:
548 return GDK_bracketleft
;
549 case GDK_leftsinglequotemark
:
550 return GDK_bracketright
;
551 case GDK_guillemotleft
:
552 return GDK_backslash
;
557 case GDK_partialderivative
:
568 return GDK_semicolon
;
570 return GDK_apostrophe
;
571 case GDK_Greek_OMEGA
:
581 case GDK_lessthanequal
:
583 case GDK_greaterthanequal
:
591 return GDK_VoidSymbol
;
596 key_press_focus_accelerator_handler (Gtk::Window
& window
, GdkEventKey
* ev
)
598 GtkWindow
* win
= window
.gobj();
599 GtkWidget
* focus
= gtk_window_get_focus (win
);
600 bool special_handling_of_unmodified_accelerators
= false;
601 bool allow_activating
= true;
604 if (GTK_IS_ENTRY(focus
) || Keyboard::some_magic_widget_has_focus()) {
605 special_handling_of_unmodified_accelerators
= true;
610 /* should this be universally true? */
611 if (Keyboard::some_magic_widget_has_focus ()) {
612 allow_activating
= false;
617 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",
621 special_handling_of_unmodified_accelerators
,
622 Keyboard::some_magic_widget_has_focus(),
625 /* This exists to allow us to override the way GTK handles
626 key events. The normal sequence is:
628 a) event is delivered to a GtkWindow
629 b) accelerators/mnemonics are activated
630 c) if (b) didn't handle the event, propagate to
631 the focus widget and/or focus chain
633 The problem with this is that if the accelerators include
634 keys without modifiers, such as the space bar or the
635 letter "e", then pressing the key while typing into
636 a text entry widget results in the accelerator being
637 activated, instead of the desired letter appearing
640 There is no good way of fixing this, but this
641 represents a compromise. The idea is that
642 key events involving modifiers (not Shift)
643 get routed into the activation pathway first, then
644 get propagated to the focus widget if necessary.
646 If the key event doesn't involve modifiers,
647 we deliver to the focus widget first, thus allowing
648 it to get "normal text" without interference
651 Of course, this can also be problematic: if there
652 is a widget with focus, then it will swallow
653 all "normal text" accelerators.
657 if (!special_handling_of_unmodified_accelerators
) {
658 if (ev
->state
& GDK_MOD1_MASK
) {
659 /* we're not in a text entry or "magic focus" widget so we don't want OS X "special-character"
660 text-style handling of alt-<key>. change the keyval back to what it would be without
661 the alt key. this way, we see <alt>-v rather than <alt>-radical and so on.
663 guint keyval_without_alt
= osx_keyval_without_alt (ev
->keyval
);
665 if (keyval_without_alt
!= GDK_VoidSymbol
) {
666 DEBUG_TRACE (DEBUG::Accelerators
, string_compose ("Remapped %1 to %2\n", gdk_keyval_name (ev
->keyval
), gdk_keyval_name (keyval_without_alt
)));
667 ev
->keyval
= keyval_without_alt
;
673 if (!special_handling_of_unmodified_accelerators
) {
675 /* pretend that certain key events that GTK does not allow
676 to be used as accelerators are actually something that
680 uint32_t fakekey
= ev
->keyval
;
682 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey
)) {
683 if (allow_activating
&& gtk_accel_groups_activate(G_OBJECT(win
), fakekey
, GdkModifierType(ev
->state
))) {
689 /* consider all relevant modifiers but not LOCK or SHIFT */
691 guint mask
= (Keyboard::RelevantModifierKeyMask
& ~(Gdk::SHIFT_MASK
|Gdk::LOCK_MASK
));
693 if (!special_handling_of_unmodified_accelerators
|| (ev
->state
& mask
)) {
695 /* no special handling or there are modifiers in effect: accelerate first */
697 DEBUG_TRACE (DEBUG::Accelerators
, "\tactivate, then propagate\n");
699 if (allow_activating
) {
700 if (gtk_window_activate_key (win
, ev
)) {
705 DEBUG_TRACE (DEBUG::Accelerators
, "\tnot accelerated, now propagate\n");
707 return gtk_window_propagate_key_event (win
, ev
);
710 /* no modifiers, propagate first */
712 DEBUG_TRACE (DEBUG::Accelerators
, "\tpropagate, then activate\n");
714 if (!gtk_window_propagate_key_event (win
, ev
)) {
715 DEBUG_TRACE (DEBUG::Accelerators
, "\tpropagation didn't handle, so activate\n");
716 if (allow_activating
) {
717 return gtk_window_activate_key (win
, ev
);
721 DEBUG_TRACE (DEBUG::Accelerators
, "\thandled by propagate\n");
725 DEBUG_TRACE (DEBUG::Accelerators
, "\tnot handled\n");
729 Glib::RefPtr
<Gdk::Pixbuf
>
730 get_xpm (std::string name
)
732 if (!xpm_map
[name
]) {
734 SearchPath
spath(ARDOUR::ardour_search_path());
735 spath
+= ARDOUR::system_data_search_path();
737 spath
.add_subdirectory_to_paths("pixmaps");
739 sys::path data_file_path
;
741 if(!find_file_in_search_path (spath
, name
, data_file_path
)) {
742 fatal
<< string_compose (_("cannot find XPM file for %1"), name
) << endmsg
;
746 xpm_map
[name
] = Gdk::Pixbuf::create_from_file (data_file_path
.to_string());
747 } catch(const Glib::Error
& e
) {
748 warning
<< "Caught Glib::Error: " << e
.what() << endmsg
;
752 return xpm_map
[name
];
756 get_icon_path (const char* cname
)
761 SearchPath
spath(ARDOUR::ardour_search_path());
762 spath
+= ARDOUR::system_data_search_path();
764 spath
.add_subdirectory_to_paths("icons");
766 sys::path data_file_path
;
768 if (!find_file_in_search_path (spath
, name
, data_file_path
)) {
769 fatal
<< string_compose (_("cannot find icon image for %1"), name
) << endmsg
;
772 return data_file_path
.to_string();
775 Glib::RefPtr
<Gdk::Pixbuf
>
776 get_icon (const char* cname
)
778 Glib::RefPtr
<Gdk::Pixbuf
> img
;
780 img
= Gdk::Pixbuf::create_from_file (get_icon_path (cname
));
781 } catch (const Gdk::PixbufError
&e
) {
782 cerr
<< "Caught PixbufError: " << e
.what() << endl
;
784 g_message("Caught ... ");
791 longest (vector
<string
>& strings
)
793 if (strings
.empty()) {
797 vector
<string
>::iterator longest
= strings
.begin();
798 string::size_type longest_length
= (*longest
).length();
800 vector
<string
>::iterator i
= longest
;
803 while (i
!= strings
.end()) {
805 string::size_type len
= (*i
).length();
807 if (len
> longest_length
) {
809 longest_length
= len
;
819 key_is_legal_for_numeric_entry (guint keyval
)
837 case GDK_KP_Subtract
:
866 set_pango_fontsize ()
868 long val
= ARDOUR::Config
->get_font_scale();
870 /* FT2 rendering - used by GnomeCanvas, sigh */
872 pango_ft2_font_map_set_resolution ((PangoFT2FontMap
*) pango_ft2_font_map_for_display(), val
/1024, val
/1024);
874 /* Cairo rendering, in case there is any */
876 pango_cairo_font_map_set_resolution ((PangoCairoFontMap
*) pango_cairo_font_map_get_default(), val
/1024);
882 long val
= ARDOUR::Config
->get_font_scale();
883 set_pango_fontsize ();
886 gtk_settings_set_long_property (gtk_settings_get_default(),
887 "gtk-xft-dpi", val
, "ardour");
888 DPIReset();//Emit Signal
892 resize_window_to_proportion_of_monitor (Gtk::Window
* window
, int max_width
, int max_height
)
894 Glib::RefPtr
<Gdk::Screen
> screen
= window
->get_screen ();
895 Gdk::Rectangle monitor_rect
;
896 screen
->get_monitor_geometry (0, monitor_rect
);
898 int const w
= std::min (int (monitor_rect
.get_width() * 0.8), max_width
);
899 int const h
= std::min (int (monitor_rect
.get_height() * 0.8), max_height
);
901 window
->resize (w
, h
);
905 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
907 escape_underscores (string
const & s
)
910 string::size_type
const N
= s
.length ();
912 for (string::size_type i
= 0; i
< N
; ++i
) {