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"
48 #include "public_editor.h"
52 #include "rgb_macros.h"
53 #include "canvas_impl.h"
54 #include "gui_thread.h"
60 using Gtkmm2ext::Keyboard
;
62 sigc::signal
<void> DPIReset
;
65 pixel_width (const string
& str
, Pango::FontDescription
& font
)
68 Glib::RefPtr
<Pango::Layout
> layout
= foo
.create_pango_layout ("");
70 layout
->set_font_description (font
);
71 layout
->set_text (str
);
74 Gtkmm2ext::get_ink_pixel_size (layout
, width
, height
);
79 fit_to_pixels (const string
& str
, int pixel_width
, Pango::FontDescription
& font
, int& actual_width
, bool with_ellipses
)
82 Glib::RefPtr
<Pango::Layout
> layout
= foo
.create_pango_layout ("");
83 string::size_type shorter_by
= 0;
86 layout
->set_font_description (font
);
91 string::iterator last
= ustr
.end();
92 --last
; /* now points at final entry */
96 while (!ustr
.empty()) {
98 layout
->set_text (txt
);
101 Gtkmm2ext::get_ink_pixel_size (layout
, width
, height
);
103 if (width
< pixel_width
) {
104 actual_width
= width
;
111 if (with_ellipses
&& shorter_by
> 3) {
122 /** Try to fit a string into a given horizontal space by ellipsizing it.
123 * @param cr Cairo context in which the text will be plotted.
125 * @param avail Available horizontal space.
126 * @return (Text, possibly ellipsized) and (horizontal size of text)
129 std::pair
<std::string
, double>
130 fit_to_pixels (cairo_t
* cr
, std::string name
, double avail
)
132 /* XXX hopefully there exists a more efficient way of doing this */
134 bool abbreviated
= false;
138 cairo_text_extents_t ext
;
139 cairo_text_extents (cr
, name
.c_str(), &ext
);
141 if (ext
.width
< avail
|| name
.length() <= 4) {
147 name
= name
.substr (0, name
.length() - 4) + "...";
149 name
= name
.substr (0, name
.length() - 3) + "...";
154 return std::make_pair (name
, width
);
158 /** Add an element to a menu, settings its sensitivity.
159 * @param m Menu to add to.
160 * @param e Element to add.
161 * @param s true to make sensitive, false to make insensitive
164 add_item_with_sensitivity (Menu_Helpers::MenuList
& m
, Menu_Helpers::MenuElem e
, bool s
)
168 m
.back().set_sensitive (false);
174 just_hide_it (GdkEventAny */
*ev*/
, Gtk::Window
*win
)
180 /* xpm2rgb copied from nixieclock, which bore the legend:
182 nixieclock - a nixie desktop timepiece
183 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
185 and was released under the GPL.
189 xpm2rgb (const char** xpm
, uint32_t& w
, uint32_t& h
)
191 static long vals
[256], val
;
192 uint32_t t
, x
, y
, colors
, cpp
;
194 unsigned char *savergb
, *rgb
;
198 if ( sscanf(xpm
[0], "%u%u%u%u", &w
, &h
, &colors
, &cpp
) != 4 ) {
199 error
<< string_compose (_("bad XPM header %1"), xpm
[0])
204 savergb
= rgb
= (unsigned char*) malloc (h
* w
* 3);
206 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
207 for (t
= 0; t
< colors
; ++t
) {
208 sscanf (xpm
[t
+1], "%c c #%lx", &c
, &val
);
212 // COLORMAP -> RGB CONVERSION
213 // Get low 3 bytes from vals[]
217 for (y
= h
-1; y
> 0; --y
) {
219 for (p
= xpm
[1+colors
+(h
-y
-1)], x
= 0; x
< w
; x
++, rgb
+= 3) {
220 val
= vals
[(int)*p
++];
221 *(rgb
+2) = val
& 0xff; val
>>= 8; // 2:B
222 *(rgb
+1) = val
& 0xff; val
>>= 8; // 1:G
223 *(rgb
+0) = val
& 0xff; // 0:R
231 xpm2rgba (const char** xpm
, uint32_t& w
, uint32_t& h
)
233 static long vals
[256], val
;
234 uint32_t t
, x
, y
, colors
, cpp
;
236 unsigned char *savergb
, *rgb
;
241 if ( sscanf(xpm
[0], "%u%u%u%u", &w
, &h
, &colors
, &cpp
) != 4 ) {
242 error
<< string_compose (_("bad XPM header %1"), xpm
[0])
247 savergb
= rgb
= (unsigned char*) malloc (h
* w
* 4);
249 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
251 if (strstr (xpm
[1], "None")) {
252 sscanf (xpm
[1], "%c", &transparent
);
259 for (; t
< colors
; ++t
) {
260 sscanf (xpm
[t
+1], "%c c #%lx", &c
, &val
);
264 // COLORMAP -> RGB CONVERSION
265 // Get low 3 bytes from vals[]
269 for (y
= h
-1; y
> 0; --y
) {
273 for (p
= xpm
[1+colors
+(h
-y
-1)], x
= 0; x
< w
; x
++, rgb
+= 4) {
275 if (transparent
&& (*p
++ == transparent
)) {
283 *(rgb
+3) = alpha
; // 3: alpha
284 *(rgb
+2) = val
& 0xff; val
>>= 8; // 2:B
285 *(rgb
+1) = val
& 0xff; val
>>= 8; // 1:G
286 *(rgb
+0) = val
& 0xff; // 0:R
293 ArdourCanvas::Points
*
294 get_canvas_points (string
/*who*/, uint32_t npoints
)
296 // cerr << who << ": wants " << npoints << " canvas points" << endl;
297 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
298 if (npoints
> (uint32_t) gdk_screen_width() + 4) {
302 return new ArdourCanvas::Points (npoints
);
305 Pango::FontDescription
*
306 get_font_for_style (string widgetname
)
308 Gtk::Window
window (WINDOW_TOPLEVEL
);
310 Glib::RefPtr
<Gtk::Style
> style
;
313 foobar
.set_name (widgetname
);
314 foobar
.ensure_style();
316 style
= foobar
.get_style ();
318 Glib::RefPtr
<const Pango::Layout
> layout
= foobar
.get_layout();
320 PangoFontDescription
*pfd
= (PangoFontDescription
*)pango_layout_get_font_description((PangoLayout
*)layout
->gobj());
324 /* layout inherited its font description from a PangoContext */
326 PangoContext
* ctxt
= (PangoContext
*) pango_layout_get_context ((PangoLayout
*) layout
->gobj());
327 pfd
= pango_context_get_font_description (ctxt
);
328 return new Pango::FontDescription (pfd
, true); /* make a copy */
331 return new Pango::FontDescription (pfd
, true); /* make a copy */
335 rgba_from_style (string style
, uint32_t r
, uint32_t g
, uint32_t b
, uint32_t a
, string attr
, int state
, bool rgba
)
337 /* In GTK+2, styles aren't set up correctly if the widget is not
338 attached to a toplevel window that has a screen pointer.
341 static Gtk::Window
* window
= 0;
344 window
= new Window (WINDOW_TOPLEVEL
);
351 foo
.set_name (style
);
354 GtkRcStyle
* rc
= foo
.get_style()->gobj()->rc_style
;
358 r
= rc
->fg
[state
].red
/ 257;
359 g
= rc
->fg
[state
].green
/ 257;
360 b
= rc
->fg
[state
].blue
/ 257;
362 /* what a hack ... "a" is for "active" */
363 if (state
== Gtk::STATE_NORMAL
&& rgba
) {
364 a
= rc
->fg
[GTK_STATE_ACTIVE
].red
/ 257;
366 } else if (attr
== "bg") {
368 r
= rc
->bg
[state
].red
/ 257;
369 g
= rc
->bg
[state
].green
/ 257;
370 b
= rc
->bg
[state
].blue
/ 257;
371 } else if (attr
== "base") {
372 r
= rc
->base
[state
].red
/ 257;
373 g
= rc
->base
[state
].green
/ 257;
374 b
= rc
->base
[state
].blue
/ 257;
375 } else if (attr
== "text") {
376 r
= rc
->text
[state
].red
/ 257;
377 g
= rc
->text
[state
].green
/ 257;
378 b
= rc
->text
[state
].blue
/ 257;
381 warning
<< string_compose (_("missing RGBA style for \"%1\""), style
) << endl
;
386 if (state
== Gtk::STATE_NORMAL
&& rgba
) {
387 return (uint32_t) RGBA_TO_UINT(r
,g
,b
,a
);
389 return (uint32_t) RGB_TO_UINT(r
,g
,b
);
395 color_from_style (string widget_style_name
, int state
, string attr
)
399 style
= gtk_rc_get_style_by_paths (gtk_settings_get_default(),
400 widget_style_name
.c_str(),
404 error
<< string_compose (_("no style found for %1, using red"), style
) << endmsg
;
405 return Gdk::Color ("red");
409 return Gdk::Color (&style
->fg
[state
]);
413 return Gdk::Color (&style
->bg
[state
]);
416 if (attr
== "light") {
417 return Gdk::Color (&style
->light
[state
]);
420 if (attr
== "dark") {
421 return Gdk::Color (&style
->dark
[state
]);
425 return Gdk::Color (&style
->mid
[state
]);
428 if (attr
== "text") {
429 return Gdk::Color (&style
->text
[state
]);
432 if (attr
== "base") {
433 return Gdk::Color (&style
->base
[state
]);
436 if (attr
== "text_aa") {
437 return Gdk::Color (&style
->text_aa
[state
]);
440 error
<< string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr
) << endmsg
;
441 return Gdk::Color ("red");
444 Glib::RefPtr
<Gdk::GC
>
445 gc_from_style (string widget_style_name
, int state
, string attr
)
449 style
= gtk_rc_get_style_by_paths (gtk_settings_get_default(),
450 widget_style_name
.c_str(),
454 error
<< string_compose (_("no style found for %1, using red"), style
) << endmsg
;
455 Glib::RefPtr
<Gdk::GC
> ret
= Gdk::GC::create();
456 ret
->set_rgb_fg_color(Gdk::Color("red"));
461 return Glib::wrap(style
->fg_gc
[state
]);
465 return Glib::wrap(style
->bg_gc
[state
]);
468 if (attr
== "light") {
469 return Glib::wrap(style
->light_gc
[state
]);
472 if (attr
== "dark") {
473 return Glib::wrap(style
->dark_gc
[state
]);
477 return Glib::wrap(style
->mid_gc
[state
]);
480 if (attr
== "text") {
481 return Glib::wrap(style
->text_gc
[state
]);
484 if (attr
== "base") {
485 return Glib::wrap(style
->base_gc
[state
]);
488 if (attr
== "text_aa") {
489 return Glib::wrap(style
->text_aa_gc
[state
]);
492 error
<< string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr
) << endmsg
;
493 Glib::RefPtr
<Gdk::GC
> ret
= Gdk::GC::create();
494 ret
->set_rgb_fg_color(Gdk::Color("red"));
500 canvas_item_visible (ArdourCanvas::Item
* item
)
502 return (item
->gobj()->object
.flags
& GNOME_CANVAS_ITEM_VISIBLE
) ? true : false;
506 set_color (Gdk::Color
& c
, int rgb
)
508 c
.set_rgb((rgb
>> 16)*256, ((rgb
& 0xff00) >> 8)*256, (rgb
& 0xff)*256);
512 relay_key_press (GdkEventKey
* ev
, Gtk::Window
* win
)
514 if (!key_press_focus_accelerator_handler (*win
, ev
)) {
515 return PublicEditor::instance().on_key_press_event(ev
);
522 forward_key_press (GdkEventKey
* ev
)
524 return PublicEditor::instance().on_key_press_event(ev
);
529 osx_keyval_without_alt (guint accent_keyval
)
531 switch (accent_keyval
) {
546 case GDK_leftdoublequotemark
:
547 return GDK_bracketleft
;
548 case GDK_leftsinglequotemark
:
549 return GDK_bracketright
;
550 case GDK_guillemotleft
:
551 return GDK_backslash
;
556 case GDK_partialderivative
:
567 return GDK_semicolon
;
569 return GDK_apostrophe
;
570 case GDK_Greek_OMEGA
:
580 case GDK_lessthanequal
:
582 case GDK_greaterthanequal
:
590 return GDK_VoidSymbol
;
595 key_press_focus_accelerator_handler (Gtk::Window
& window
, GdkEventKey
* ev
)
597 GtkWindow
* win
= window
.gobj();
598 GtkWidget
* focus
= gtk_window_get_focus (win
);
599 bool special_handling_of_unmodified_accelerators
= false;
600 bool allow_activating
= true;
602 #undef DEBUG_ACCELERATOR_HANDLING
603 #ifdef DEBUG_ACCELERATOR_HANDLING
604 //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
608 if (GTK_IS_ENTRY(focus
) || Keyboard::some_magic_widget_has_focus()) {
609 special_handling_of_unmodified_accelerators
= true;
614 /* should this be universally true? */
615 if (Keyboard::some_magic_widget_has_focus ()) {
616 allow_activating
= false;
620 #ifdef DEBUG_ACCELERATOR_HANDLING
622 cerr
<< "Win = " << win
<< " Key event: code = " << ev
->keyval
<< " state = " << hex
<< ev
->state
<< dec
<< " special handling ? "
623 << special_handling_of_unmodified_accelerators
624 << " magic widget focus ? "
625 << Keyboard::some_magic_widget_has_focus()
626 << " allow_activation ? "
632 /* This exists to allow us to override the way GTK handles
633 key events. The normal sequence is:
635 a) event is delivered to a GtkWindow
636 b) accelerators/mnemonics are activated
637 c) if (b) didn't handle the event, propagate to
638 the focus widget and/or focus chain
640 The problem with this is that if the accelerators include
641 keys without modifiers, such as the space bar or the
642 letter "e", then pressing the key while typing into
643 a text entry widget results in the accelerator being
644 activated, instead of the desired letter appearing
647 There is no good way of fixing this, but this
648 represents a compromise. The idea is that
649 key events involving modifiers (not Shift)
650 get routed into the activation pathway first, then
651 get propagated to the focus widget if necessary.
653 If the key event doesn't involve modifiers,
654 we deliver to the focus widget first, thus allowing
655 it to get "normal text" without interference
658 Of course, this can also be problematic: if there
659 is a widget with focus, then it will swallow
660 all "normal text" accelerators.
664 if (!special_handling_of_unmodified_accelerators
) {
665 if (ev
->state
& GDK_MOD1_MASK
) {
666 /* we're not in a text entry or "magic focus" widget so we don't want OS X "special-character"
667 text-style handling of alt-<key>. change the keyval back to what it would be without
668 the alt key. this way, we see <alt>-v rather than <alt>-radical and so on.
670 guint keyval_without_alt
= osx_keyval_without_alt (ev
->keyval
);
672 if (keyval_without_alt
!= GDK_VoidSymbol
) {
673 #ifdef DEBUG_ACCELERATOR_HANDLING
674 cerr
<< "Remapped " << gdk_keyval_name (ev
->keyval
) << " to " << gdk_keyval_name (keyval_without_alt
) << endl
;
676 #endif ev->keyval = keyval_without_alt;
682 if (!special_handling_of_unmodified_accelerators
) {
684 /* pretend that certain key events that GTK does not allow
685 to be used as accelerators are actually something that
689 uint32_t fakekey
= ev
->keyval
;
691 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey
)) {
692 if (allow_activating
&& gtk_accel_groups_activate(G_OBJECT(win
), fakekey
, GdkModifierType(ev
->state
))) {
698 /* consider all relevant modifiers but not LOCK or SHIFT */
700 guint mask
= (Keyboard::RelevantModifierKeyMask
& ~(Gdk::SHIFT_MASK
|Gdk::LOCK_MASK
));
702 if (!special_handling_of_unmodified_accelerators
|| (ev
->state
& mask
)) {
704 /* no special handling or there are modifiers in effect: accelerate first */
706 #ifdef DEBUG_ACCELERATOR_HANDLING
708 cerr
<< "\tactivate, then propagate\n";
712 if (allow_activating
) {
713 if (gtk_window_activate_key (win
, ev
)) {
718 #ifdef DEBUG_ACCELERATOR_HANDLING
720 cerr
<< "\tnot accelerated, now propagate\n";
723 return gtk_window_propagate_key_event (win
, ev
);
726 /* no modifiers, propagate first */
728 #ifdef DEBUG_ACCELERATOR_HANDLING
730 cerr
<< "\tpropagate, then activate\n";
733 if (!gtk_window_propagate_key_event (win
, ev
)) {
734 #ifdef DEBUG_ACCELERATOR_HANDLING
736 cerr
<< "\tpropagation didn't handle, so activate\n";
740 if (allow_activating
) {
741 return gtk_window_activate_key (win
, ev
);
745 #ifdef DEBUG_ACCELERATOR_HANDLING
747 cerr
<< "\thandled by propagate\n";
753 #ifdef DEBUG_ACCELERATOR_HANDLING
755 cerr
<< "\tnot handled\n";
761 Glib::RefPtr
<Gdk::Pixbuf
>
762 get_xpm (std::string name
)
764 if (!xpm_map
[name
]) {
766 SearchPath
spath(ARDOUR::ardour_search_path());
767 spath
+= ARDOUR::system_data_search_path();
769 spath
.add_subdirectory_to_paths("pixmaps");
771 sys::path data_file_path
;
773 if(!find_file_in_search_path (spath
, name
, data_file_path
)) {
774 fatal
<< string_compose (_("cannot find XPM file for %1"), name
) << endmsg
;
778 xpm_map
[name
] = Gdk::Pixbuf::create_from_file (data_file_path
.to_string());
779 } catch(const Glib::Error
& e
) {
780 warning
<< "Caught Glib::Error: " << e
.what() << endmsg
;
784 return xpm_map
[name
];
788 get_icon_path (const char* cname
)
793 SearchPath
spath(ARDOUR::ardour_search_path());
794 spath
+= ARDOUR::system_data_search_path();
796 spath
.add_subdirectory_to_paths("icons");
798 sys::path data_file_path
;
800 if (!find_file_in_search_path (spath
, name
, data_file_path
)) {
801 fatal
<< string_compose (_("cannot find icon image for %1"), name
) << endmsg
;
804 return data_file_path
.to_string();
807 Glib::RefPtr
<Gdk::Pixbuf
>
808 get_icon (const char* cname
)
810 Glib::RefPtr
<Gdk::Pixbuf
> img
;
812 img
= Gdk::Pixbuf::create_from_file (get_icon_path (cname
));
813 } catch (const Gdk::PixbufError
&e
) {
814 cerr
<< "Caught PixbufError: " << e
.what() << endl
;
816 g_message("Caught ... ");
823 longest (vector
<string
>& strings
)
825 if (strings
.empty()) {
829 vector
<string
>::iterator longest
= strings
.begin();
830 string::size_type longest_length
= (*longest
).length();
832 vector
<string
>::iterator i
= longest
;
835 while (i
!= strings
.end()) {
837 string::size_type len
= (*i
).length();
839 if (len
> longest_length
) {
841 longest_length
= len
;
851 key_is_legal_for_numeric_entry (guint keyval
)
869 case GDK_KP_Subtract
:
898 set_pango_fontsize ()
900 long val
= ARDOUR::Config
->get_font_scale();
902 /* FT2 rendering - used by GnomeCanvas, sigh */
904 pango_ft2_font_map_set_resolution ((PangoFT2FontMap
*) pango_ft2_font_map_for_display(), val
/1024, val
/1024);
906 /* Cairo rendering, in case there is any */
908 pango_cairo_font_map_set_resolution ((PangoCairoFontMap
*) pango_cairo_font_map_get_default(), val
/1024);
914 long val
= ARDOUR::Config
->get_font_scale();
915 set_pango_fontsize ();
918 gtk_settings_set_long_property (gtk_settings_get_default(),
919 "gtk-xft-dpi", val
, "ardour");
920 DPIReset();//Emit Signal
924 resize_window_to_proportion_of_monitor (Gtk::Window
* window
, int max_width
, int max_height
)
926 Glib::RefPtr
<Gdk::Screen
> screen
= window
->get_screen ();
927 Gdk::Rectangle monitor_rect
;
928 screen
->get_monitor_geometry (0, monitor_rect
);
930 int const w
= std::min (int (monitor_rect
.get_width() * 0.8), max_width
);
931 int const h
= std::min (int (monitor_rect
.get_height() * 0.8), max_height
);
933 window
->resize (w
, h
);
937 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
939 escape_underscores (string
const & s
)
942 string::size_type
const N
= s
.length ();
944 for (string::size_type i
= 0; i
< N
; ++i
) {