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 Pango::FontDescription (pfd
); /* make a copy */
332 return Pango::FontDescription (pfd
); /* 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;
602 /* consider all relevant modifiers but not LOCK or SHIFT */
603 const guint mask
= (Keyboard::RelevantModifierKeyMask
& ~(Gdk::SHIFT_MASK
|Gdk::LOCK_MASK
));
606 if (GTK_IS_ENTRY(focus
) || Keyboard::some_magic_widget_has_focus()) {
607 special_handling_of_unmodified_accelerators
= true;
612 /* should this be universally true? */
613 if (Keyboard::some_magic_widget_has_focus ()) {
614 allow_activating
= false;
619 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",
623 special_handling_of_unmodified_accelerators
,
624 Keyboard::some_magic_widget_has_focus(),
627 /* This exists to allow us to override the way GTK handles
628 key events. The normal sequence is:
630 a) event is delivered to a GtkWindow
631 b) accelerators/mnemonics are activated
632 c) if (b) didn't handle the event, propagate to
633 the focus widget and/or focus chain
635 The problem with this is that if the accelerators include
636 keys without modifiers, such as the space bar or the
637 letter "e", then pressing the key while typing into
638 a text entry widget results in the accelerator being
639 activated, instead of the desired letter appearing
642 There is no good way of fixing this, but this
643 represents a compromise. The idea is that
644 key events involving modifiers (not Shift)
645 get routed into the activation pathway first, then
646 get propagated to the focus widget if necessary.
648 If the key event doesn't involve modifiers,
649 we deliver to the focus widget first, thus allowing
650 it to get "normal text" without interference
653 Of course, this can also be problematic: if there
654 is a widget with focus, then it will swallow
655 all "normal text" accelerators.
659 if (!special_handling_of_unmodified_accelerators
&& (ev
->state
& Keyboard::SecondaryModifier
)) {
660 /* we're not in a text entry or "magic focus" widget so we don't want OS X "special-character"
661 text-style handling of alt-<key>. change the keyval back to what it would be without
662 the alt key. this way, we see <alt>-v rather than <alt>-radical and so on.
664 guint keyval_without_alt
= osx_keyval_without_alt (ev
->keyval
);
666 if (keyval_without_alt
!= GDK_VoidSymbol
) {
667 DEBUG_TRACE (DEBUG::Accelerators
, string_compose ("Remapped %1 to %2\n", gdk_keyval_name (ev
->keyval
), gdk_keyval_name (keyval_without_alt
)));
668 ev
->keyval
= keyval_without_alt
;
674 if (!special_handling_of_unmodified_accelerators
|| (ev
->state
& mask
)) {
676 /* pretend that certain key events that GTK does not allow
677 to be used as accelerators are actually something that
678 it does allow. but only where there are no modifiers.
681 uint32_t fakekey
= ev
->keyval
;
683 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey
)) {
684 if (allow_activating
&& gtk_accel_groups_activate(G_OBJECT(win
), fakekey
, GdkModifierType(ev
->state
))) {
685 DEBUG_TRACE (DEBUG::Accelerators
, "\taccel group activated by fakekey\n");
691 if (!special_handling_of_unmodified_accelerators
|| (ev
->state
& mask
)) {
693 /* no special handling or there are modifiers in effect: accelerate first */
695 DEBUG_TRACE (DEBUG::Accelerators
, "\tactivate, then propagate\n");
697 if (allow_activating
) {
698 if (gtk_window_activate_key (win
, ev
)) {
702 DEBUG_TRACE (DEBUG::Accelerators
, "\tactivation skipped\n");
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
);
719 DEBUG_TRACE (DEBUG::Accelerators
, "\tactivation skipped\n");
723 DEBUG_TRACE (DEBUG::Accelerators
, "\thandled by propagate\n");
727 DEBUG_TRACE (DEBUG::Accelerators
, "\tnot handled\n");
731 Glib::RefPtr
<Gdk::Pixbuf
>
732 get_xpm (std::string name
)
734 if (!xpm_map
[name
]) {
736 SearchPath
spath(ARDOUR::ardour_search_path());
737 spath
+= ARDOUR::system_data_search_path();
739 spath
.add_subdirectory_to_paths("pixmaps");
741 sys::path data_file_path
;
743 if(!find_file_in_search_path (spath
, name
, data_file_path
)) {
744 fatal
<< string_compose (_("cannot find XPM file for %1"), name
) << endmsg
;
748 xpm_map
[name
] = Gdk::Pixbuf::create_from_file (data_file_path
.to_string());
749 } catch(const Glib::Error
& e
) {
750 warning
<< "Caught Glib::Error: " << e
.what() << endmsg
;
754 return xpm_map
[name
];
758 get_icon_path (const char* cname
)
763 SearchPath
spath(ARDOUR::ardour_search_path());
764 spath
+= ARDOUR::system_data_search_path();
766 spath
.add_subdirectory_to_paths("icons");
768 sys::path data_file_path
;
770 if (!find_file_in_search_path (spath
, name
, data_file_path
)) {
771 fatal
<< string_compose (_("cannot find icon image for %1"), name
) << endmsg
;
774 return data_file_path
.to_string();
777 Glib::RefPtr
<Gdk::Pixbuf
>
778 get_icon (const char* cname
)
780 Glib::RefPtr
<Gdk::Pixbuf
> img
;
782 img
= Gdk::Pixbuf::create_from_file (get_icon_path (cname
));
783 } catch (const Gdk::PixbufError
&e
) {
784 cerr
<< "Caught PixbufError: " << e
.what() << endl
;
786 g_message("Caught ... ");
793 longest (vector
<string
>& strings
)
795 if (strings
.empty()) {
799 vector
<string
>::iterator longest
= strings
.begin();
800 string::size_type longest_length
= (*longest
).length();
802 vector
<string
>::iterator i
= longest
;
805 while (i
!= strings
.end()) {
807 string::size_type len
= (*i
).length();
809 if (len
> longest_length
) {
811 longest_length
= len
;
821 key_is_legal_for_numeric_entry (guint keyval
)
839 case GDK_KP_Subtract
:
868 set_pango_fontsize ()
870 long val
= ARDOUR::Config
->get_font_scale();
872 /* FT2 rendering - used by GnomeCanvas, sigh */
874 pango_ft2_font_map_set_resolution ((PangoFT2FontMap
*) pango_ft2_font_map_for_display(), val
/1024, val
/1024);
876 /* Cairo rendering, in case there is any */
878 pango_cairo_font_map_set_resolution ((PangoCairoFontMap
*) pango_cairo_font_map_get_default(), val
/1024);
884 long val
= ARDOUR::Config
->get_font_scale();
885 set_pango_fontsize ();
888 gtk_settings_set_long_property (gtk_settings_get_default(),
889 "gtk-xft-dpi", val
, "ardour");
890 DPIReset();//Emit Signal
894 resize_window_to_proportion_of_monitor (Gtk::Window
* window
, int max_width
, int max_height
)
896 Glib::RefPtr
<Gdk::Screen
> screen
= window
->get_screen ();
897 Gdk::Rectangle monitor_rect
;
898 screen
->get_monitor_geometry (0, monitor_rect
);
900 int const w
= std::min (int (monitor_rect
.get_width() * 0.8), max_width
);
901 int const h
= std::min (int (monitor_rect
.get_height() * 0.8), max_height
);
903 window
->resize (w
, h
);
907 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
909 escape_underscores (string
const & s
)
912 string::size_type
const N
= s
.length ();
914 for (string::size_type i
= 0; i
< N
; ++i
) {