/*
- Copyright (C) 1999 Paul Barton-Davis
+ Copyright (C) 1999 Paul Barton-Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <gtkmm/label.h>
#include <gtkmm/comboboxtext.h>
#include <gtkmm/tooltip.h>
+#include <gtkmm/menuitem.h>
#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/persistent_tooltip.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
using namespace std;
int& height)
{
Pango::Rectangle ink_rect = layout->get_ink_extents ();
-
+
width = PANGO_PIXELS(ink_rect.get_width());
height = PANGO_PIXELS(ink_rect.get_height());
}
{
int width, height;
w.ensure_style ();
-
+
get_pixel_size (w.create_pango_layout (text), width, height);
w.set_size_request(width + hpadding, height + vpadding);
}
{
int width, height;
w.ensure_style ();
-
+
get_pixel_size (w.create_pango_layout (text), width, height);
w.set_size_request(width + hpadding, height + vpadding);
}
void
-Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w,
+Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w,
const std::vector<std::string>& strings,
gint hpadding, gint vpadding)
{
break;
}
}
-
+
if (i == strings.end()) {
/* make a copy of the strings then add one that has a descender */
copy = strings;
} else {
to_use = &strings;
}
-
+
for (vector<string>::const_iterator i = to_use->begin(); i != to_use->end(); ++i) {
get_pixel_size (w.create_pango_layout (*i), width, height);
width_max = max(width_max,width);
{
guint8 const* src_pixel = src;
guint8* dst_pixel = dst;
-
+
/* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
with premultipled alpha values (see preceding function)
src_pixel[3]); // R [0] <= [ 2 ]
dst_pixel[1] = demultiply_alpha (src_pixel[1],
src_pixel[3]); // G [1] <= [ 1 ]
- dst_pixel[2] = demultiply_alpha (src_pixel[0],
+ dst_pixel[2] = demultiply_alpha (src_pixel[0],
src_pixel[3]); // B [2] <= [ 0 ]
dst_pixel[3] = src_pixel[3]; // alpha
-
+
#elif G_BYTE_ORDER == G_BIG_ENDIAN
/* Cairo [ B G R A ] is actually [ A R G B ] in memory SOURCE
0 1 2 3
dst_pixel[2] = demultiply_alpha (src_pixel[3],
src_pixel[0]); // B [2] <= [ 3 ]
dst_pixel[3] = src_pixel[0]; // alpha
-
+
#else
#error ardour does not currently support PDP-endianess
-#endif
-
+#endif
+
dst_pixel += 4;
src_pixel += 4;
}
return *empty_pixbuf;
}
+ if (clip_width <= 0 || clip_height <= 0) {
+ /* negative values mean padding around natural size */
+ int width, height;
+ pixel_size (name, font, width, height);
+ if (clip_width <= 0) {
+ clip_width = width - clip_width;
+ }
+ if (clip_height <= 0) {
+ clip_height = height - clip_height;
+ }
+ }
+
Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
cairo_t* cr = cairo_create (surface);
cairo_text_extents_t te;
-
+
cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
cairo_select_font_face (cr, font.get_family().c_str(),
CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size (cr, font.get_size() / Pango::SCALE);
cairo_text_extents (cr, name.c_str(), &te);
-
+
cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
cairo_show_text (cr, name.c_str());
-
+
convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
cairo_destroy(cr);
return buf;
}
+void
+_position_menu_anchored (int& x, int& y, bool& push_in,
+ const Gtk::Menu* const menu,
+ Gtk::Widget* const anchor,
+ const std::string& selected) {
+ using namespace Gtk;
+ using namespace Gtk::Menu_Helpers;
+
+ /* TODO: lacks support for rotated dropdown buttons */
+
+ if (!anchor->has_screen () || !anchor->get_has_window ()) {
+ return;
+ }
+
+ Gdk::Rectangle monitor;
+ {
+ const int monitor_num = anchor->get_screen ()->get_monitor_at_window (
+ anchor->get_window ());
+ anchor->get_screen ()->get_monitor_geometry (
+ (monitor_num < 0) ? 0 : monitor_num, monitor);
+ }
+
+ const Requisition menu_req = menu->size_request();
+ const Gdk::Rectangle allocation = anchor->get_allocation();
+
+ /* The x and y position are handled separately.
+ *
+ * For the x position if the direction is LTR (or RTL), then we try in order:
+ * a) align the left (right) of the menu with the left (right) of the button
+ * if there's enough room until the right (left) border of the screen;
+ * b) align the right (left) of the menu with the right (left) of the button
+ * if there's enough room until the left (right) border of the screen;
+ * c) align the right (left) border of the menu with the right (left) border
+ * of the screen if there's enough space;
+ * d) align the left (right) border of the menu with the left (right) border
+ * of the screen, with the rightmost (leftmost) part of the menu that
+ * overflows the screen.
+ * XXX We always align left regardless of the direction because if x is
+ * left of the current monitor, the menu popup code after us notices it
+ * and enforces that the menu stays in the monitor that's at the left...*/
+
+ anchor->get_window ()->get_origin (x, y);
+
+ if (anchor->get_direction() == TEXT_DIR_RTL) {
+ if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
+ /* a) align menu right and button right */
+ x += allocation.get_width() - menu_req.width;
+ } else if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
+ /* b) align menu left and button left: nothing to do*/
+ } else if (menu_req.width > monitor.get_width()) {
+ /* c) align menu left and screen left, guaranteed to fit */
+ x = monitor.get_x();
+ } else {
+ /* d) XXX align left or the menu might change monitors */
+ x = monitor.get_x();
+ }
+ } else { /* LTR */
+ if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
+ /* a) align menu left and button left: nothing to do*/
+ } else if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
+ /* b) align menu right and button right */
+ x += allocation.get_width() - menu_req.width;
+ } else if (menu_req.width > monitor.get_width()) {
+ /* c) align menu right and screen right, guaranteed to fit */
+ x = monitor.get_x() + monitor.get_width() - menu_req.width;
+ } else {
+ /* d) align left */
+ x = monitor.get_x();
+ }
+ }
+
+ /* For the y position, try in order:
+ * a) if there is a menu item with the same text as the button, align it
+ * with the button, unless that makes the menu overflow the monitor.
+ * b) align the top of the menu with the bottom of the button if there is
+ * enough room below the button;
+ * c) align the bottom of the menu with the top of the button if there is
+ * enough room above the button;
+ * d) align the bottom of the menu with the bottom of the monitor if there
+ * is enough room, but avoid moving the menu to another monitor */
+
+ const MenuList& items = menu->items ();
+ int offset = 0;
+
+ MenuList::const_iterator i = items.begin();
+ for ( ; i != items.end(); ++i) {
+ const Label* label_widget = dynamic_cast<const Label*>(i->get_child());
+ if (label_widget && selected == ((std::string) label_widget->get_label())) {
+ break;
+ }
+ offset += i->size_request().height;
+ }
+ if (i != items.end() &&
+ y - offset >= monitor.get_y() &&
+ y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) {
+ y -= offset;
+ } else if (y + allocation.get_height() + menu_req.height <= monitor.get_y() + monitor.get_height()) {
+ y += allocation.get_height(); /* a) */
+ } else if ((y - menu_req.height) >= monitor.get_y()) {
+ y -= menu_req.height; /* b) */
+ } else {
+ y = monitor.get_y() + max(0, monitor.get_height() - menu_req.height);
+ }
+
+ push_in = false;
+}
+
+void
+Gtkmm2ext::anchored_menu_popup (Gtk::Menu* const menu,
+ Gtk::Widget* const anchor,
+ const std::string& selected,
+ guint button, guint32 time) {
+ menu->popup(
+ sigc::bind (sigc::ptr_fun(&_position_menu_anchored),
+ menu, anchor, selected),
+ button,
+ time);
+}
+
void
Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
{
}
}
-bool
-Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator (GdkModifierType& mod)
-{
-#ifdef GTKOSX
- /* GTK on OS X is currently (February 2012) setting both
- the Meta and Mod2 bits in the event modifier state if
- the Command key is down.
-
- gtk_accel_groups_activate() does not invoke any of the logic
- that gtk_window_activate_key() will that sorts out that stupid
- state of affairs, and as a result it fails to find a match
- for the key event and the current set of accelerators.
-
- to fix this, if the meta bit is set, remove the mod2 bit
- from the modifier. this assumes that our bindings use Primary
- which will have set the meta bit in the accelerator entry.
- */
- if (mod & GDK_META_MASK) {
- mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
- }
-#endif
- return true;
-}
-
bool
Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
{
Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
{
GdkScreen* scr = gdk_screen_get_default();
-
+
if (win) {
GdkRectangle r;
gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
}
int
-Gtkmm2ext::pixel_width (const string& str, Pango::FontDescription& font)
+Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
{
Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
}
void
-Gtkmm2ext::pixel_size (const string& str, Pango::FontDescription& font, int& width, int& height)
+Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
{
Gtk::Label foo;
Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
layout->set_width (pixel_width * PANGO_SCALE);
if (with_ellipses) {
- layout->set_ellipsize (Pango::ELLIPSIZE_END);
+ layout->set_ellipsize (Pango::ELLIPSIZE_END);
} else {
- layout->set_wrap (Pango::WRAP_CHAR);
+ layout->set_wrap (Pango::WRAP_CHAR);
}
line = layout->get_line (0);
/* XXX: might need special care to get the ellipsis character, not sure
- how that works
- */
+ how that works
+ */
string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
-
+
cerr << "fit to pixels of " << str << " returns " << s << endl;
return s;
return l;
}
+Gtk::Label *
+Gtkmm2ext::right_aligned_label (string const & t)
+{
+ Gtk::Label* l = new Gtk::Label (t);
+ l->set_alignment (1, 0.5);
+ return l;
+}
+
static bool
make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
{
Gtkmm2ext::enable_tooltips ()
{
gtk_rc_parse_string ("gtk-enable-tooltips = 1");
+ PersistentTooltip::set_tooltips_enabled (true);
}
void
Gtkmm2ext::disable_tooltips ()
{
gtk_rc_parse_string ("gtk-enable-tooltips = 0");
+ PersistentTooltip::set_tooltips_enabled (false);
}
bool
if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
return false;
}
-
+
gint wx;
gint wy;
gint width, height, depth;
widget_window->get_geometry (x, y, width, height, depth);
widget_window->get_root_origin (wx, wy);
-
- if ((evx >= wx && evx < wx + width) &&
+
+ if ((evx >= wx && evx < wx + width) &&
(evy >= wy && evy < wy + height)) {
return true;
- }
+ }
return false;
}
return "unknown";
}
+
+std::string
+Gtkmm2ext::markup_escape_text (std::string const& s)
+{
+ return Glib::Markup::escape_text (s);
+}
+
+void
+Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
+{
+#ifdef __APPLE__
+ try {
+ /* This is a first order approach, listing all mounted volumes (incl network).
+ * One could use `diskutil` or `mount` to query local disks only, or
+ * something even fancier if deemed appropriate.
+ */
+ Glib::Dir dir("/Volumes");
+ for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
+ string fullpath = Glib::build_filename ("/Volumes", *di);
+ if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
+
+ try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
+ c.add_shortcut_folder (fullpath);
+ }
+ catch (Glib::Error& e) {
+ std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
+ }
+ }
+ }
+ catch (Glib::FileError& e) {
+ std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
+ }
+#endif
+}
+
+float
+Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
+{
+ const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
+ return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
+}
+
+void
+Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
+{
+ gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
+
+ if (v < 1) {
+ return;
+ }
+
+ paned.set_position ((guint) floor (fraction * v));
+}
+
+string
+Gtkmm2ext::show_gdk_event_state (int state)
+{
+ string s;
+ if (state & GDK_SHIFT_MASK) {
+ s += "+SHIFT";
+ }
+ if (state & GDK_LOCK_MASK) {
+ s += "+LOCK";
+ }
+ if (state & GDK_CONTROL_MASK) {
+ s += "+CONTROL";
+ }
+ if (state & GDK_MOD1_MASK) {
+ s += "+MOD1";
+ }
+ if (state & GDK_MOD2_MASK) {
+ s += "+MOD2";
+ }
+ if (state & GDK_MOD3_MASK) {
+ s += "+MOD3";
+ }
+ if (state & GDK_MOD4_MASK) {
+ s += "+MOD4";
+ }
+ if (state & GDK_MOD5_MASK) {
+ s += "+MOD5";
+ }
+ if (state & GDK_BUTTON1_MASK) {
+ s += "+BUTTON1";
+ }
+ if (state & GDK_BUTTON2_MASK) {
+ s += "+BUTTON2";
+ }
+ if (state & GDK_BUTTON3_MASK) {
+ s += "+BUTTON3";
+ }
+ if (state & GDK_BUTTON4_MASK) {
+ s += "+BUTTON4";
+ }
+ if (state & GDK_BUTTON5_MASK) {
+ s += "+BUTTON5";
+ }
+ if (state & GDK_SUPER_MASK) {
+ s += "+SUPER";
+ }
+ if (state & GDK_HYPER_MASK) {
+ s += "+HYPER";
+ }
+ if (state & GDK_META_MASK) {
+ s += "+META";
+ }
+ if (state & GDK_RELEASE_MASK) {
+ s += "+RELEASE";
+ }
+
+ return s;
+}