diff options
Diffstat (limited to 'libs/widgets')
31 files changed, 4825 insertions, 16 deletions
diff --git a/libs/widgets/ardour_button.cc b/libs/widgets/ardour_button.cc index 913e18bae8..8c16bcdca1 100644 --- a/libs/widgets/ardour_button.cc +++ b/libs/widgets/ardour_button.cc @@ -22,6 +22,7 @@ #include <algorithm> #include <pangomm/layout.h> +#include <gtkmm/toggleaction.h> #include "pbd/compose.h" #include "pbd/controllable.h" @@ -60,7 +61,7 @@ ArdourButton::ArdourButton (Element e) : _sizing_text("") , _markup (false) , _elements (e) - , _icon (Gtkmm2ext::ArdourIcon::NoIcon) + , _icon (ArdourIcon::NoIcon) , _icon_render_cb (0) , _icon_render_cb_data (0) , _tweaks (Tweaks (0)) @@ -108,7 +109,7 @@ ArdourButton::ArdourButton (const std::string& str, Element e) : _sizing_text("") , _markup (false) , _elements (e) - , _icon (Gtkmm2ext::ArdourIcon::NoIcon) + , _icon (ArdourIcon::NoIcon) , _tweaks (Tweaks (0)) , _char_pixel_width (0) , _char_pixel_height (0) @@ -379,7 +380,7 @@ ArdourButton::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_ vw -= _diameter + 4; } if (_elements & VectorIcon) { - Gtkmm2ext::ArdourIcon::render (cr, _icon, vw, vh, active_state(), text_color); + ArdourIcon::render (cr, _icon, vw, vh, active_state(), text_color); } else { cairo_save (cr); rounded_function (cr, 0, 0, get_width(), get_height(), corner_radius + 1.5); @@ -1303,7 +1304,7 @@ ArdourButton::add_elements (Element e) } void -ArdourButton::set_icon (Gtkmm2ext::ArdourIcon::Icon i) +ArdourButton::set_icon (ArdourIcon::Icon i) { _icon = i; _icon_render_cb = 0; diff --git a/libs/widgets/ardour_icon.cc b/libs/widgets/ardour_icon.cc new file mode 100644 index 0000000000..f111e8919c --- /dev/null +++ b/libs/widgets/ardour_icon.cc @@ -0,0 +1,1121 @@ +/* + Copyright (C) 2009 Paul Davis + Copyright (C) 2015 Robin Gareus <robin@gareus.org> + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +*/ + +#include <math.h> // M_PI +#include <assert.h> +#include <algorithm> // std:min + +#include "widgets/ardour_icon.h" + +using namespace ArdourWidgets::ArdourIcon; + +/* general style info: + * + * - geometry: icons should be centered, spanning + * wh = std::min (width * .5, height *.5) * .55; + * + * - all shapes should have a contrasting outline + * (usually white foreground, black outline) + */ + +#define OUTLINEWIDTH 1.5 // px + +#define VECTORICONSTROKEFILL(fillalpha) \ + cairo_set_line_width (cr, OUTLINEWIDTH); \ + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); \ + cairo_stroke_preserve (cr); \ + cairo_set_source_rgba (cr, 1, 1, 1, (fillalpha)); \ + cairo_fill (cr); + +#define VECTORICONSTROKEOUTLINE(LW, color) \ + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); \ + cairo_set_line_width (cr, (LW) + OUTLINEWIDTH); \ + ardour_icon_set_source_inv_rgba (cr, color); \ + cairo_stroke_preserve (cr); \ + ardour_icon_set_source_rgba (cr, color); \ + cairo_set_line_width (cr, (LW)); \ + cairo_stroke (cr); + + +/** convert 32bit 'RRGGBBAA' to cairo doubles + * from libs/canvas/utils.cc and canvas/types.h: typedef uint32_t Color; + */ +static void ardour_icon_set_source_rgba (cairo_t *cr, uint32_t color) +{ + cairo_set_source_rgba (cr, + ((color >> 24) & 0xff) / 255.0, + ((color >> 16) & 0xff) / 255.0, + ((color >> 8) & 0xff) / 255.0, + ((color >> 0) & 0xff) / 255.0 + ); +} + +/** inverse color */ +static void ardour_icon_set_source_inv_rgba (cairo_t *cr, uint32_t color) +{ + cairo_set_source_rgba (cr, + 1.0 - ((color >> 24) & 0xff) / 255.0, + 1.0 - ((color >> 16) & 0xff) / 255.0, + 1.0 - ((color >> 8) & 0xff) / 255.0, + ((color >> 0) & 0xff) / 255.0 + ); +} + +/***************************************************************************** + * Tool Icons. + * Foreground is always white, compatible with small un-blurred rendering. + */ + +/** internal edit icon */ +static void icon_tool_content (cairo_t *cr, const int width, const int height) { +#define EM_POINT(X,Y) round (x + (X) * em) + .5, round (y + (Y) * em) + .5 + + const double x = width * .5; + const double y = height * .5; + const double em = std::min (x, y) * .1; // 1px at 20x20 + + // draw dot outlines (control-points) + cairo_move_to (cr, EM_POINT(-6.0, 0.0)); + cairo_close_path (cr); + cairo_move_to (cr, EM_POINT(-2.5, 4.0)); + cairo_close_path (cr); + cairo_move_to (cr, EM_POINT( 5.0, -5.0)); + cairo_close_path (cr); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + ardour_icon_set_source_inv_rgba (cr, 0xffffffff); + cairo_set_line_width (cr, 3 * em + OUTLINEWIDTH); + cairo_stroke (cr); + + // "midi note" lines + cairo_move_to (cr, EM_POINT(-7.0, -5.0)); + cairo_line_to (cr, EM_POINT( 0.0, -5.0)); + + cairo_move_to (cr, EM_POINT( 2.0, 4.0)); + cairo_line_to (cr, EM_POINT( 6.0, 4.0)); + + // automation line (connect control-points) + cairo_move_to (cr, EM_POINT(-6.0, 0.0)); + cairo_line_to (cr, EM_POINT(-2.5, 4.0)); + cairo_line_to (cr, EM_POINT( 5.0, -5.0)); + + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + VECTORICONSTROKEOUTLINE(1 * em, 0xffffffff); + + // remove automation line outline at control-points + cairo_move_to (cr, EM_POINT(-6.0, 0.0)); + cairo_close_path (cr); + cairo_move_to (cr, EM_POINT(-2.5, 4.0)); + cairo_close_path (cr); + cairo_move_to (cr, EM_POINT( 5.0, -5.0)); + cairo_close_path (cr); + + ardour_icon_set_source_rgba (cr, 0xffffffff); + cairo_set_line_width (cr, 3 * em); + cairo_stroke (cr); +#undef EM_POINT +} + +/** range tool |<->| */ +static void icon_tool_range (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y) * .55; + const double lw = rint (wh / 6.0); // line width + const double ar = wh * .6; // arrow + + const double bw = ceil (wh) - .5; + const double y0 = ceil (y); + const double ym = rint (y0 - wh * .1) + .5; // arrow-horizontal; slightly to the top, on a px + const double x0 = rint (x) - bw; // left arrow tip + const double x1 = rint (x) + bw; // right arrow tip + + // left and right box + cairo_move_to (cr, x0, y0 - bw); + cairo_line_to (cr, x0, y0 + bw); + VECTORICONSTROKEOUTLINE(lw, 0xffffffff); + cairo_move_to (cr, x1, y0 - bw); + cairo_line_to (cr, x1, y0 + bw); + VECTORICONSTROKEOUTLINE(lw, 0xffffffff); + + // arrows + cairo_move_to (cr, x0 + ar, ym - ar); + cairo_line_to (cr, x0 + .5, ym); + cairo_line_to (cr, x0 + ar, ym + ar); + + cairo_move_to (cr, x1 - ar, ym - ar); + cairo_line_to (cr, x1 - .5, ym); + cairo_line_to (cr, x1 - ar, ym + ar); + + // line connecting the arrows + cairo_move_to (cr, x0, ym); + cairo_line_to (cr, x1, ym); + VECTORICONSTROKEOUTLINE(lw, 0xffffffff); + + cairo_set_source_rgba (cr, 1, 1, 1, 1.0); + cairo_set_line_width (cr, lw); + + cairo_move_to (cr, x0, y0 - bw); + cairo_line_to (cr, x0, y0 + bw); + cairo_stroke (cr); + + cairo_move_to (cr, x1, y0 - bw); + cairo_line_to (cr, x1, y0 + bw); + cairo_stroke (cr); + + +} + +/** Grab/Object tool - 6x8em "hand", with 'em' wide index finger. */ +static void icon_tool_grab (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double em = std::min (x, y) * .15; // 1.5px at 20x20 + +#define EM_POINT(X,Y) x + (X) * em, y + (Y) * em + + // wrist + cairo_move_to (cr, EM_POINT( 2.0, 4.0)); + cairo_line_to (cr, EM_POINT(-1.5, 4.0)); + cairo_line_to (cr, EM_POINT(-2.5, 2.0)); + // thumb + cairo_line_to (cr, EM_POINT(-3.0, 1.0)); + + // index finger + cairo_line_to (cr, EM_POINT(-2.0, 0.0)); + cairo_line_to (cr, EM_POINT(-2.1, -4.0)); + cairo_line_to (cr, EM_POINT(-1.5, -4.5)); + cairo_line_to (cr, EM_POINT(-1.1, -4.0)); + cairo_line_to (cr, EM_POINT(-1.0, 0.1)); + + // middle finger knuckle + cairo_line_to (cr, EM_POINT(-0.6, 0.3)); + cairo_line_to (cr, EM_POINT(-0.3, 0.0)); + cairo_line_to (cr, EM_POINT(-0.2, -0.2)); + cairo_line_to (cr, EM_POINT( 0.1, -0.3)); + cairo_line_to (cr, EM_POINT( 0.4, -0.2)); + cairo_line_to (cr, EM_POINT( 0.5, 0.1)); + + // ring finger knuckle + cairo_line_to (cr, EM_POINT( 0.8, 0.4)); + cairo_line_to (cr, EM_POINT( 1.1, 0.2)); + cairo_line_to (cr, EM_POINT( 1.2, 0.0)); + cairo_line_to (cr, EM_POINT( 1.5, -0.1)); + cairo_line_to (cr, EM_POINT( 1.8, 0.0)); + cairo_line_to (cr, EM_POINT( 1.9, 0.4)); + + // pinky + cairo_line_to (cr, EM_POINT( 2.0, 0.6)); + cairo_line_to (cr, EM_POINT( 2.4, 0.4)); + cairo_line_to (cr, EM_POINT( 2.8, 0.5)); + cairo_line_to (cr, EM_POINT( 3.0, 1.0)); + + // wrist + cairo_line_to (cr, EM_POINT( 3.0, 1.5)); + cairo_line_to (cr, EM_POINT( 2.0, 4.0)); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + VECTORICONSTROKEFILL(1.0); +#undef EM_POINT +} + +/** cut icon - scissors */ +static void icon_tool_cut (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double em = std::min (x, y) * .1; // 1px at 20x20 + +#define EM_POINT(X,Y) x + (X) * em, y + (Y) * em + + cairo_save (cr); + cairo_translate (cr, EM_POINT(4, -3)); + cairo_scale (cr, 1.6, 1.0); // ellipse + cairo_arc (cr, 0., 0., 1.5 * em, 0., 2 * M_PI); + cairo_restore (cr); + + cairo_move_to (cr, EM_POINT(-6.0, 2.5)); + cairo_line_to (cr, EM_POINT( 5.5, -2.0)); + + cairo_move_to (cr, EM_POINT(-6.0, -2.5)); + cairo_line_to (cr, EM_POINT( 5.5, 2.0)); + + cairo_save (cr); + cairo_translate (cr, EM_POINT(4, 3)); + cairo_scale (cr, 1.6, 1.0); // ellipse + cairo_arc (cr, 0., 0., 1.5 * em, 0., 2 * M_PI); + cairo_restore (cr); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + VECTORICONSTROKEOUTLINE (1.5 * em, 0xffffffff); +#undef EM_POINT +} + +/** time stretch icon */ +static void icon_tool_stretch (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y) * .55; + + const double y0 = ceil (y); + const double bw = rint (wh); + const double lw = rint (wh / 3.0) / 2.0; + const double x0 = rint (x + lw) + .5; + + // box indication region + cairo_rectangle (cr, x0 - lw - bw - .5, y0 - bw, lw + bw, 2 * bw); + VECTORICONSTROKEFILL (0.75); + + cairo_set_line_width (cr, 1.0); + + // inside/left arrow + cairo_move_to (cr, x0, y); + cairo_line_to (cr, x0 - lw * 2, y); + cairo_line_to (cr, x0 - lw * 2, y - lw * 3.5); + cairo_line_to (cr, x0 - lw * 6, y); + cairo_line_to (cr, x0 - lw * 2, y + lw * 3.5); + cairo_line_to (cr, x0 - lw * 2, y); + + cairo_set_source_rgba (cr, 0, 0, 0, .5); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); + + // outside/right arrow + cairo_move_to (cr, x0, y); + cairo_line_to (cr, x0 + lw * 2, y); + cairo_line_to (cr, x0 + lw * 2, y - lw * 4); + cairo_line_to (cr, x0 + lw * 6, y); + cairo_line_to (cr, x0 + lw * 2, y + lw * 4); + cairo_line_to (cr, x0 + lw * 2, y); + + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 1, 1, 1, 1.0); + cairo_fill (cr); +} + +/** audition - small speaker with sound-waves*/ +static void icon_tool_audition (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double em = std::min (x, y) * .1; // 1px at 20x20 + +#define EM_POINT(X,Y) x + (X) * em, y + (Y) * em + + cairo_move_to (cr, EM_POINT(-7.0, -2.0)); + cairo_line_to (cr, EM_POINT(-7.0, 2.0)); + cairo_line_to (cr, EM_POINT(-6.0, 3.0)); + cairo_line_to (cr, EM_POINT(-3.0, 3.0)); + cairo_line_to (cr, EM_POINT( 2.0, 6.0)); + cairo_line_to (cr, EM_POINT( 2.0, -6.0)); + cairo_line_to (cr, EM_POINT(-3.0, -3.0)); + cairo_line_to (cr, EM_POINT(-6.0, -3.0)); + cairo_close_path (cr); + + cairo_pattern_t *speaker; + speaker = cairo_pattern_create_linear (EM_POINT(0, -3.0), EM_POINT(0, 3.0)); + cairo_pattern_add_color_stop_rgba (speaker, 0.0, 0.8, 0.8, 0.8, 1.0); + cairo_pattern_add_color_stop_rgba (speaker, 0.25, 1.0, 1.0, 1.0, 1.0); + cairo_pattern_add_color_stop_rgba (speaker, 1.0, 0.6, 0.6, 0.6, 1.0); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_width (cr, 1.5); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_stroke_preserve (cr); + cairo_set_source (cr, speaker); + cairo_fill (cr); + cairo_pattern_destroy (speaker); + + // TODO use a slight curve + cairo_move_to (cr, EM_POINT(-3.0, -3.0)); + cairo_line_to (cr, EM_POINT(-3.5, 0.0)); + cairo_line_to (cr, EM_POINT(-3.0, 3.0)); + cairo_set_source_rgba (cr, 0, 0, 0, 0.7); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + + cairo_save (cr); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_source_rgba (cr, 1, 1, 1, 1); + + cairo_translate (cr, EM_POINT (4.0, 0)); + cairo_scale (cr, 0.8, 1.25); // ellipse + + cairo_arc (cr, 0, 0, 4 * em, -.5 * M_PI, .5 * M_PI); + cairo_set_line_width (cr, .8 * em); + cairo_stroke (cr); + + cairo_arc (cr, 0, 0, 2 * em, -.5 * M_PI, .5 * M_PI); + cairo_set_line_width (cr, .5 * em); + cairo_stroke (cr); + cairo_restore (cr); +#undef EM_POINT +} + +/** pen top-left to bottom right */ +static void icon_tool_draw (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double em = std::min (x, y) * .1; // 1px at 20x20 + +#define EM_POINT(X,Y) x + (X) * em, y + (Y) * em + + // pen [6,-5] to [-3, 3] + // y = -8 * x / 9 + 1/3 + + // top-right end + cairo_move_to (cr, EM_POINT( 5.0, -6.11)); + cairo_line_to (cr, EM_POINT( 6.4, -5.35)); // todo round properly. + cairo_line_to (cr, EM_POINT( 7.0, -3.88)); + + // bottom-left w/tip + cairo_line_to (cr, EM_POINT(-2.0, 4.11)); + cairo_line_to (cr, EM_POINT(-6.0, 5.66)); // pen tip + cairo_line_to (cr, EM_POINT(-4.0, 1.88)); + cairo_close_path (cr); + + cairo_pattern_t *pen; + pen = cairo_pattern_create_linear (EM_POINT(-3.0, -6.0), EM_POINT(6.0, 4.0)); + cairo_pattern_add_color_stop_rgba (pen, 0.4, 0.6, 0.6, 0.6, 1.0); + cairo_pattern_add_color_stop_rgba (pen, 0.5, 1.0, 1.0, 1.0, 1.0); + cairo_pattern_add_color_stop_rgba (pen, 0.6, 0.1, 0.1, 0.1, 1.0); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_width (cr, em + .5); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_stroke_preserve (cr); + cairo_set_source (cr, pen); + cairo_fill (cr); + + // separate the tip + cairo_move_to (cr, EM_POINT(-2.0, 4.11)); + cairo_line_to (cr, EM_POINT(-3.0, 2.8)); // slight curve [-3,3] + cairo_line_to (cr, EM_POINT(-4.0, 2.0)); + cairo_set_line_width (cr, OUTLINEWIDTH); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_stroke (cr); + + // pen tip + cairo_move_to (cr, EM_POINT(-5.0, 3.9)); + cairo_line_to (cr, EM_POINT(-6.0, 5.66)); + cairo_line_to (cr, EM_POINT(-4.1, 4.9)); + cairo_close_path (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 0.7); + cairo_set_line_width (cr, em); + cairo_stroke_preserve (cr); + cairo_fill (cr); + + cairo_pattern_destroy (pen); +#undef EM_POINT +} + +/** Toolbar icon - Time Axis View reduce height */ +static void icon_tav_shrink (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y) * .66; + const double ar = std::min (x, y) * .15; + const double tri = .7 * (wh - ar); + + cairo_rectangle (cr, x - wh, y - ar, 2 * wh, 2 * ar); + VECTORICONSTROKEFILL(.75); + + cairo_set_line_width (cr, 1.0); + + cairo_move_to (cr, x, y - ar - 0.5); + cairo_line_to (cr, x - tri, y - wh + 0.5); + cairo_line_to (cr, x + tri, y - wh + 0.5); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .75); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); + + cairo_move_to (cr, x, y + ar + 0.5); + cairo_line_to (cr, x - tri, y + wh - 0.5); + cairo_line_to (cr, x + tri, y + wh - 0.5); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .75); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); +} + +/** Toolbar icon - Time Axis View increase height */ +static void icon_tav_expand (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y) * .66; + const double ar = std::min (x, y) * .15; + const double tri = .7 * (wh - ar); + + cairo_rectangle (cr, x - wh, y - wh, 2 * wh, 2 * wh); + VECTORICONSTROKEFILL(.75); + + cairo_set_line_width (cr, 1.0); + + cairo_move_to (cr, x, y - wh + 0.5); + cairo_line_to (cr, x - tri, y - ar - 0.5); + cairo_line_to (cr, x + tri, y - ar - 0.5); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .5); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); + + cairo_move_to (cr, x , y + wh - 0.5); + cairo_line_to (cr, x - tri, y + ar + 0.5); + cairo_line_to (cr, x + tri, y + ar + 0.5); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .5); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); +} + + +/***************************************************************************** + * Record enable (transport & track header). + * + * hardcoded "red" #f46f6f + */ + +/** standard rec-enable circle */ +static void icon_rec_enable (cairo_t *cr, const int width, const int height, const Gtkmm2ext::ActiveState state) +{ + const double x = width * .5; + const double y = height * .5; + const double r = std::min (x, y) * .55; + cairo_arc (cr, x, y, r, 0, 2 * M_PI); + if (state == Gtkmm2ext::ExplicitActive) { + cairo_set_source_rgba (cr, 1.0, .1, .1, 1.0); + } + else if (state == Gtkmm2ext::ImplicitActive) { + cairo_set_source_rgba (cr, .9, .3, .3, 1.0); + } + else { + cairo_set_source_rgba (cr, .4, .3, .3, 1.0); + } + cairo_fill_preserve (cr); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); // outline + cairo_set_line_width (cr, 1); + cairo_stroke (cr); +} + +/** tape-mode, "reel" */ +static void icon_rec_tape (cairo_t *cr, const int width, const int height, const Gtkmm2ext::ActiveState state) +{ + const double x = width * .5; + const double y = height * .5; + const double r = std::min (x, y) * .6; + const double slit = .11 * M_PI; + cairo_translate (cr, x, y); + + cairo_arc (cr, 0, 0, r, 0, 2 * M_PI); + if (state == Gtkmm2ext::ExplicitActive) { + cairo_set_source_rgba (cr, 1.0, .1, .1, 1.0); + } + else if (state == Gtkmm2ext::ImplicitActive) { + cairo_set_source_rgba (cr, .9, .3, .3, 1.0); + } + else { + cairo_set_source_rgba (cr, .4, .3, .3, 1.0); + } + cairo_fill_preserve (cr); + cairo_set_source_rgba (cr, .0, .0, .0, .5); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + + cairo_save (cr); + cairo_set_source_rgba (cr, .15, .07, .07, 1.0); + + cairo_rotate (cr, -.5 * M_PI); + cairo_move_to (cr, 0, 0); + cairo_arc (cr, 0, 0, r *.85, -slit, slit); + cairo_line_to (cr, 0, 0); + cairo_close_path (cr); + + cairo_fill (cr); + cairo_rotate (cr, 2. * M_PI / 3.); + + cairo_move_to (cr, 0, 0); + cairo_arc (cr, 0, 0, r *.85, -slit, slit); + cairo_line_to (cr, 0, 0); + cairo_close_path (cr); + cairo_fill (cr); + + cairo_rotate (cr, 2. * M_PI / 3.); + cairo_move_to (cr, 0, 0); + cairo_arc (cr, 0, 0, r *.85, -slit, slit); + cairo_line_to (cr, 0, 0); + cairo_close_path (cr); + cairo_fill (cr); + + cairo_restore (cr); + + cairo_arc (cr, 0, 0, r * .3, 0, 2 * M_PI); + if (state == Gtkmm2ext::ExplicitActive) { + cairo_set_source_rgba (cr, 1.0, .1, .1, 1.0); + } + else if (state == Gtkmm2ext::ImplicitActive) { + cairo_set_source_rgba (cr, .9, .3, .3, 1.0); + } + else { + cairo_set_source_rgba (cr, .4, .3, .3, 1.0); + } + cairo_fill (cr); + cairo_set_source_rgba (cr, .0, .0, .0, 1.0); + cairo_arc (cr, 0, 0, r *.15, 0, 2 * M_PI); // hole in the middle + cairo_fill (cr); +} + + +/***************************************************************************** + * Transport buttons, foreground is always white + */ + +/** stop square box */ +static void icon_transport_stop (cairo_t *cr, const int width, const int height) +{ + const int wh = std::min (width, height); + cairo_rectangle (cr, + (width - wh) * .5 + wh * .25, + (height - wh) * .5 + wh * .25, + wh * .5, wh * .5); + VECTORICONSTROKEFILL(0.9); // small 'shine' +} + +/** play triangle */ +static void icon_transport_play (cairo_t *cr, const int width, const int height) +{ + const int wh = std::min (width, height) * .5; + const double y = height * .5; + const double x = width - wh; + + const double tri = ceil (.577 * wh); // 1/sqrt(3) + + cairo_move_to (cr, x + wh * .5, y); + cairo_line_to (cr, x - wh * .5, y - tri); + cairo_line_to (cr, x - wh * .5, y + tri); + cairo_close_path (cr); + + VECTORICONSTROKEFILL(0.9); +} + +/** Midi Panic "!" */ +static void icon_transport_panic (cairo_t *cr, const int width, const int height) +{ + const int wh = ceil (std::min (width, height) * .1) - .5; + const double xc = rint (width * .5); + const double yh = height; + cairo_rectangle (cr, + xc - wh, yh *.19, + wh * 2, yh *.41); + VECTORICONSTROKEFILL(0.9); + + cairo_arc (cr, xc, yh *.75, wh, 0, 2 * M_PI); + VECTORICONSTROKEFILL(0.9); +} + +/** various combinations of lines and triangles "|>|", ">|" "|>" */ +static void icon_transport_ck (cairo_t *cr, + const enum ArdourWidgets::ArdourIcon::Icon icon, + const int width, const int height) +{ + // small play triangle + int wh = std::min (width, height); + const double y = height * .5; + const double x = width - wh * .5; + wh *= .18; + const double tri = ceil (.577 * wh * 2); // 1/sqrt(3) + + const float ln = std::min (width, height) * .07; + + if (icon == TransportStart || icon == TransportRange) { + cairo_rectangle (cr, + x - wh - ln, y - tri * 1.7, + ln * 2, tri * 3.4); + + VECTORICONSTROKEFILL(1.0); + } + + if (icon == TransportEnd || icon == TransportRange) { + cairo_rectangle (cr, + x + wh - ln, y - tri * 1.7, + ln * 2, tri * 3.4); + + VECTORICONSTROKEFILL(1.0); + } + + if (icon == TransportStart) { + cairo_move_to (cr, x - wh, y); + cairo_line_to (cr, x + wh, y - tri); + cairo_line_to (cr, x + wh, y + tri); + } else { + cairo_move_to (cr, x + wh, y); + cairo_line_to (cr, x - wh, y - tri); + cairo_line_to (cr, x - wh, y + tri); + } + + cairo_close_path (cr); + VECTORICONSTROKEFILL(1.0); +} + +/** loop spiral */ +static void icon_transport_loop (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double r = std::min (x, y); + + cairo_arc (cr, x, y, r * .62, 0, 2 * M_PI); + cairo_arc_negative (cr, x, y, r * .35, 2 * M_PI, 0); + + VECTORICONSTROKEFILL(1.0); + +#define ARCARROW(rad, ang) \ + x + (rad) * sin ((ang) * 2.0 * M_PI), y + (rad) * cos ((ang) * 2.0 * M_PI) + + cairo_move_to (cr, ARCARROW(r * .35, .72)); + cairo_line_to (cr, ARCARROW(r * .15, .72)); + cairo_line_to (cr, ARCARROW(r * .56, .60)); + cairo_line_to (cr, ARCARROW(r * .75, .72)); + cairo_line_to (cr, ARCARROW(r * .62, .72)); + + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_stroke_preserve (cr); + cairo_close_path (cr); + cairo_set_source_rgba (cr, 1, 1, 1, 1.0); + cairo_fill (cr); +#undef ARCARROW +} + +/** de-construct thorwil's metronom */ +static void icon_transport_metronom (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = .95 * std::min (x, y); + const double h = wh * .80; + const double w = wh * .55; + const double lw = w * .34; + + cairo_rectangle (cr, + x - w * .7, y + h * .25, + w * 1.4, lw); + + VECTORICONSTROKEFILL(1.0); + + cairo_move_to (cr, x - w, y + h); + cairo_line_to (cr, x + w, y + h); + cairo_line_to (cr, x + w * .35, y - h); + cairo_line_to (cr, x - w * .35, y - h); + cairo_line_to (cr, x - w, y + h); + + cairo_move_to (cr, x - w + lw, y + h -lw); + cairo_line_to (cr, x - w * .35 + lw, y - h + lw); + cairo_line_to (cr, x + w * .35 - lw, y - h + lw); + cairo_line_to (cr, x + w - lw, y + h -lw); + cairo_line_to (cr, x - w + lw, y + h -lw); + + VECTORICONSTROKEFILL(1.0); + + // Pendulum + // ddx = .70 w = .75 * .5 wh = .375 wh + // ddy = .75 h - lw = .75 * .8 wh - wh .5 * .2 = .5 wh + // ang = (ddx/ddy): + // -> angle = atan (ang) = atan (375 / .5) ~= 36deg + const double dx = lw * .2; // 1 - cos(tan^-1(ang)) + const double dy = lw * .4; // 1 - sin(tan^-1(ang)) + cairo_move_to (cr, x - w * .3 , y + h * .25 + lw * .5); + cairo_line_to (cr, x - w + dx , y - h + lw + dy); + cairo_line_to (cr, x - w + lw , y - h + lw); + cairo_line_to (cr, x - w * .3 + lw, y + h * .25 + lw * .5); + cairo_close_path (cr); + + VECTORICONSTROKEFILL(1.0); + + cairo_rectangle (cr, + x - w * .7, y + h * .25, + w * 1.4, lw); + cairo_fill (cr); +} + + +/***************************************************************************** + * Zoom: In "+", Out "-" and Full "[]" + */ +static void icon_zoom (cairo_t *cr, const enum ArdourWidgets::ArdourIcon::Icon icon, const int width, const int height, const uint32_t fg_color) +{ + const double x = width * .5; + const double y = height * .5; + const double r = std::min (x, y) * .7; + const double wh = std::min (x, y) * .45; + + // draw handle first +#define LINE45DEG(rad) \ + x + r * (rad) * .707, y + r * (rad) * .707 // sin(45deg) = cos(45deg) = .707 + cairo_move_to (cr, LINE45DEG(.9)); + cairo_line_to (cr, LINE45DEG(1.3)); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_stroke (cr); +#undef LINE45DEG + + // lens + ardour_icon_set_source_rgba (cr, fg_color); + cairo_arc (cr, x, y, r, 0, 2 * M_PI); + cairo_fill_preserve (cr); + + // add a lens gradient + cairo_pattern_t *lens; + lens = cairo_pattern_create_radial (x - r, y - r, r * .5, x - r, y - r, r * 2); + cairo_pattern_add_color_stop_rgba (lens, 0, 1, 1, 1, .4); + cairo_pattern_add_color_stop_rgba (lens, 1, 0, 0, 0, .4); + cairo_set_source (cr, lens); + cairo_fill_preserve (cr); + cairo_pattern_destroy (lens); + + // outline + cairo_set_line_width (cr, 1.5); + //ardour_icon_set_source_inv_rgba (cr, fg_color); // alpha + cairo_set_source_rgba (cr, .0, .0, .0, .8); + cairo_stroke (cr); + + // add "+", "-" or "[]" + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width (cr, 1.5); + ardour_icon_set_source_inv_rgba (cr, fg_color); + + if (icon == ZoomIn || icon == ZoomOut) { + cairo_move_to (cr, x - wh, y); + cairo_line_to (cr, x + wh, y); + cairo_stroke (cr); + } + if (icon == ZoomIn) { + cairo_move_to (cr, x, y - wh); + cairo_line_to (cr, x, y + wh); + cairo_stroke (cr); + } + if (icon == ZoomFull) { + const double br0 = std::min (x, y) * .1; + const double br1 = std::min (x, y) * .3; + const double bry = std::min (x, y) * .3; + cairo_move_to (cr, x - br0, y - bry); + cairo_line_to (cr, x - br1, y - bry); + cairo_line_to (cr, x - br1, y + bry); + cairo_line_to (cr, x - br0, y + bry); + cairo_stroke (cr); + + cairo_move_to (cr, x + br0, y - bry); + cairo_line_to (cr, x + br1, y - bry); + cairo_line_to (cr, x + br1, y + bry); + cairo_line_to (cr, x + br0, y + bry); + cairo_stroke (cr); + } +} + +/** Toolbar icon - Mixbus Zoom Expand, rotated TimeAxisExpand */ +static void icon_zoom_expand (cairo_t *cr, const int width, const int height) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y) * .66; + const double ar = std::min (x, y) * .15; + const double tri = .7 * (wh - ar); + + cairo_rectangle (cr, x - wh, y - wh, 2 * wh, 2 * wh); + VECTORICONSTROKEFILL(.75); + + cairo_set_line_width (cr, 1.0); + + cairo_move_to (cr, x - wh + 0.5, y); + cairo_line_to (cr, x - ar - 0.5, y - tri); + cairo_line_to (cr, x - ar - 0.5, y + tri); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .5); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); + + cairo_move_to (cr, x + wh - 0.5, y); + cairo_line_to (cr, x + ar + 0.5, y - tri); + cairo_line_to (cr, x + ar + 0.5, y + tri); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 1, 1, 1, .5); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_fill (cr); +} + + + +/***************************************************************************** + * Misc buttons + */ + +/** "close" - "X" , no outline */ +static void icon_close_cross (cairo_t *cr, const int width, const int height, const uint32_t fg_color) +{ + const double x = width * .5; + const double y = height * .5; + const double o = .5 + std::min (x, y) * .4; + ardour_icon_set_source_rgba (cr, fg_color); + cairo_set_line_width (cr, 1.0); + cairo_move_to (cr, x-o, y-o); + cairo_line_to (cr, x+o, y+o); + cairo_move_to (cr, x+o, y-o); + cairo_line_to (cr, x-o, y+o); + cairo_stroke (cr); +} + +/** "<" */ +static void icon_nudge_left (cairo_t *cr, const int width, const int height, const uint32_t fg_color) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y); + + const double tri_x = .3 * wh; + const double tri_y = .6 * wh; + + cairo_move_to (cr, x + tri_x, y - tri_y); + cairo_line_to (cr, x - tri_x, y); + cairo_line_to (cr, x + tri_x, y + tri_y); + VECTORICONSTROKEOUTLINE(1.5, fg_color); +} + +/** ">" */ +static void icon_nudge_right (cairo_t *cr, const int width, const int height, const uint32_t fg_color) +{ + + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y); + + const double tri_x = .3 * wh; + const double tri_y = .6 * wh; + + cairo_move_to (cr, x - tri_x, y - tri_y); + cairo_line_to (cr, x + tri_x, y); + cairo_line_to (cr, x - tri_x, y + tri_y); + VECTORICONSTROKEOUTLINE(1.5, fg_color); + +} + +/** mixer strip narrow/wide */ +static void icon_strip_width (cairo_t *cr, const int width, const int height, const uint32_t fg_color) +{ + const double x0 = width * .2; + const double x1 = width * .8; + + const double y0 = height * .25; + const double y1 = height * .75; + + const double ym = height * .5; + + // arrow + const double xa0= width * .39; + const double xa1= width * .61; + const double ya0= height * .35; + const double ya1= height * .65; + + ardour_icon_set_source_rgba (cr, fg_color); + cairo_set_line_width (cr, 1); + + // left + right + cairo_move_to (cr, x0, y0); + cairo_line_to (cr, x0, y1); + cairo_move_to (cr, x1, y0); + cairo_line_to (cr, x1, y1); + + // horiz center line + cairo_move_to (cr, x0, ym); + cairo_line_to (cr, x1, ym); + + // arrow left + cairo_move_to (cr, x0, ym); + cairo_line_to (cr, xa0, ya0); + cairo_move_to (cr, x0, ym); + cairo_line_to (cr, xa0, ya1); + + // arrow right + cairo_move_to (cr, x1, ym); + cairo_line_to (cr, xa1, ya0); + cairo_move_to (cr, x1, ym); + cairo_line_to (cr, xa1, ya1); + cairo_stroke (cr); +} + +/** 5-pin DIN MIDI socket */ +static void icon_din_midi (cairo_t *cr, const int width, const int height, const uint32_t fg_color) +{ + const double x = width * .5; + const double y = height * .5; + const double r = std::min (x, y) * .75; + ardour_icon_set_source_rgba (cr, fg_color); + cairo_set_line_width (cr, 1); + cairo_arc (cr, x, y, r, .57 * M_PI, 2.43 * M_PI); + cairo_stroke (cr); + + // pins equally spaced 45deg + cairo_arc (cr, x, y * 0.5, r * .15, 0, 2 * M_PI); + cairo_fill (cr); + cairo_arc (cr, x * 0.5, y, r * .15, 0, 2 * M_PI); + cairo_fill (cr); + cairo_arc (cr, x * 1.5, y, r * .15, 0, 2 * M_PI); + cairo_fill (cr); + // .5 + .5 * .5 * sin(45deg), 1.5 - .5 * .5 * cos(45deg) + cairo_arc (cr, x * 0.677, y * .677, r * .15, 0, 2 * M_PI); + cairo_fill (cr); + cairo_arc (cr, x * 1.323, y * .677, r * .15, 0, 2 * M_PI); + cairo_fill (cr); + + // bottom notch + cairo_arc (cr, x, y+r, r * .26, 1.05 * M_PI, 1.95 * M_PI); + cairo_stroke (cr); +} + + +/*****************************************************************************/ + +bool +ArdourWidgets::ArdourIcon::render (cairo_t *cr, + const enum ArdourWidgets::ArdourIcon::Icon icon, + const int width, const int height, + const Gtkmm2ext::ActiveState state, + const uint32_t fg_color) +{ + bool rv = true; + cairo_save (cr); + + if (width < 6 || height < 6) { + return false; + } + + switch (icon) { + case TransportStop: + icon_transport_stop (cr, width, height); + break; + case TransportPlay: + icon_transport_play (cr, width, height); + break; + case TransportLoop: + icon_transport_loop (cr, width, height); + break; + case TransportMetronom: + icon_transport_metronom (cr, width, height); + break; + case TransportPanic: + icon_transport_panic (cr, width, height); + break; + case TransportStart: // no break + case TransportEnd: // no break + case TransportRange: + icon_transport_ck (cr, icon, width, height); + break; + case RecTapeMode: + icon_rec_tape (cr, width, height, state); + break; + case RecButton: + icon_rec_enable (cr, width, height, state); + break; + case CloseCross: + icon_close_cross (cr, width, height, fg_color); + break; + case StripWidth: + icon_strip_width (cr, width, height, fg_color); + break; + case DinMidi: + icon_din_midi (cr, width, height, fg_color); + break; + case NudgeLeft: + icon_nudge_left (cr, width, height, fg_color); + break; + case NudgeRight: + icon_nudge_right (cr, width, height, fg_color); + break; + case ZoomIn: // no break + case ZoomOut: // no break + case ZoomFull: + icon_zoom (cr, icon, width, height, fg_color); + break; + case ZoomExpand: + icon_zoom_expand (cr, width, height); + break; + case TimeAxisShrink: + icon_tav_shrink (cr, width, height); + break; + case TimeAxisExpand: + icon_tav_expand (cr, width, height); + break; + case ToolRange: + icon_tool_range (cr, width, height); + break; + case ToolGrab: + icon_tool_grab (cr, width, height); + break; + case ToolCut: + icon_tool_cut (cr, width, height); + break; + case ToolStretch: + icon_tool_stretch (cr, width, height); + break; + case ToolAudition: + icon_tool_audition (cr, width, height); + break; + case ToolDraw: + icon_tool_draw (cr, width, height); + break; + case ToolContent: + icon_tool_content (cr, width, height); + break; + default: + rv = false; + break; + } + cairo_restore (cr); + return rv; +} + +#undef VECTORICONSTROKEFILL +#undef VECTORICONSTROKEOUTLINE diff --git a/libs/widgets/binding_proxy.cc b/libs/widgets/binding_proxy.cc new file mode 100644 index 0000000000..666797a0eb --- /dev/null +++ b/libs/widgets/binding_proxy.cc @@ -0,0 +1,112 @@ +/* + Copyright (C) 2006 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <string> +#include <climits> +#include <iostream> + +#include "pbd/controllable.h" +#include "gtkmm2ext/keyboard.h" +#include "widgets/binding_proxy.h" +#include "widgets/popup.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace PBD; +using namespace Gtkmm2ext; +using namespace ArdourWidgets; + +guint BindingProxy::bind_button = 2; +guint BindingProxy::bind_statemask = Gdk::CONTROL_MASK; + +BindingProxy::BindingProxy (boost::shared_ptr<Controllable> c) + : prompter (0), + controllable (c) +{ +} + +BindingProxy::BindingProxy () + : prompter (0) +{ +} + +BindingProxy::~BindingProxy () +{ + if (prompter) { + delete prompter; + } +} + +void +BindingProxy::set_controllable (boost::shared_ptr<Controllable> c) +{ + learning_finished (); + controllable = c; +} + +void +BindingProxy::set_bind_button_state (guint button, guint statemask) +{ + bind_button = button; + bind_statemask = statemask; +} + +bool +BindingProxy::is_bind_action (GdkEventButton *ev) +{ + return (Keyboard::modifier_state_equals (ev->state, bind_statemask) && ev->button == bind_button ); +} + +bool +BindingProxy::button_press_handler (GdkEventButton *ev) +{ + if ( controllable && is_bind_action(ev) ) { + if (Controllable::StartLearning (controllable.get())) { + string prompt = _("operate controller now"); + if (prompter == 0) { + prompter = new PopUp (Gtk::WIN_POS_MOUSE, 30000, false); + prompter->signal_unmap_event().connect (mem_fun (*this, &BindingProxy::prompter_hiding)); + } + prompter->set_text (prompt); + prompter->touch (); // shows popup + controllable->LearningFinished.connect_same_thread (learning_connection, boost::bind (&BindingProxy::learning_finished, this)); + } + return true; + } + + return false; +} + +void +BindingProxy::learning_finished () +{ + learning_connection.disconnect (); + if (prompter) { + prompter->touch (); // hides popup + } +} + +bool +BindingProxy::prompter_hiding (GdkEventAny* /*ev*/) +{ + learning_connection.disconnect (); + if (controllable) { + Controllable::StopLearning (controllable.get()); + } + return false; +} diff --git a/libs/widgets/choice.cc b/libs/widgets/choice.cc new file mode 100644 index 0000000000..6bfd0690b2 --- /dev/null +++ b/libs/widgets/choice.cc @@ -0,0 +1,71 @@ +/* + Copyright (C) 1998-99 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <gtkmm/label.h> +#include <widgets/choice.h> + +using namespace std; +using namespace sigc; +using namespace Gtk; +using namespace ArdourWidgets; + +Choice::Choice (string title, string prompt, vector<string> choices, bool center) + : Dialog (title) +{ + int n; + vector<string>::iterator i; + + if (center) { + set_position (Gtk::WIN_POS_CENTER); + } else { + set_position (Gtk::WIN_POS_MOUSE); + } + + set_name ("ChoiceWindow"); + + HBox* dhbox = manage (new HBox()); + Image* dimage = manage (new Gtk::Image(Stock::DIALOG_QUESTION, Gtk::ICON_SIZE_DIALOG)); + Label* label = manage (new Label (prompt)); + + dhbox->pack_start (*dimage, true, false, 10); + dhbox->pack_start (*label, true, false, 10); + + get_vbox()->set_border_width (12); + get_vbox()->pack_start (*dhbox, true, false); + + set_has_separator (false); + set_resizable (false); + show_all_children (); + + for (n = 0, i = choices.begin(); i != choices.end(); ++i, ++n) { + add_button (*i, n); + } +} + +void +Choice::on_realize () +{ + Gtk::Window::on_realize(); + get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)); +} + +Choice::~Choice () +{ +} diff --git a/libs/widgets/eventboxext.cc b/libs/widgets/eventboxext.cc new file mode 100644 index 0000000000..6cbd0b7427 --- /dev/null +++ b/libs/widgets/eventboxext.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Robin Gareus <robin@gareus.org> + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "widgets/eventboxext.h" + +using namespace ArdourWidgets; + +EventBoxExt::EventBoxExt () +{ +} diff --git a/libs/widgets/pane.cc b/libs/widgets/pane.cc new file mode 100644 index 0000000000..2968f7bfaa --- /dev/null +++ b/libs/widgets/pane.cc @@ -0,0 +1,671 @@ +/* + Copyright (C) 2016 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <assert.h> +#include <gdkmm/cursor.h> + +#include "widgets/pane.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace Gtk; +using namespace PBD; +using namespace ArdourWidgets; + +Pane::Pane (bool h) + : horizontal (h) + , did_move (false) + , divider_width (2) + , check_fract (false) +{ + using namespace Gdk; + + set_name ("Pane"); + set_has_window (false); + + if (horizontal) { + drag_cursor = Cursor (SB_H_DOUBLE_ARROW); + } else { + drag_cursor = Cursor (SB_V_DOUBLE_ARROW); + } +} + +Pane::~Pane () +{ + for (Children::iterator c = children.begin(); c != children.end(); ++c) { + (*c)->show_con.disconnect (); + (*c)->hide_con.disconnect (); + if ((*c)->w) { + (*c)->w->remove_destroy_notify_callback ((*c).get()); + (*c)->w->unparent (); + } + } + children.clear (); +} + +void +Pane::set_child_minsize (Gtk::Widget const& w, int32_t minsize) +{ + for (Children::iterator c = children.begin(); c != children.end(); ++c) { + if ((*c)->w == &w) { + (*c)->minsize = minsize; + break; + } + } +} + +void +Pane::set_drag_cursor (Gdk::Cursor c) +{ + drag_cursor = c; +} + +void +Pane::on_size_request (GtkRequisition* req) +{ + GtkRequisition largest; + + /* iterate over all children, get their size requests */ + + /* horizontal pane is as high as its tallest child, including the dividers. + * Its width is the sum of the children plus the dividers. + * + * vertical pane is as wide as its widest child, including the dividers. + * Its height is the sum of the children plus the dividers. + */ + + if (horizontal) { + largest.width = (children.size() - 1) * divider_width; + largest.height = 0; + } else { + largest.height = (children.size() - 1) * divider_width; + largest.width = 0; + } + + for (Children::iterator c = children.begin(); c != children.end(); ++c) { + GtkRequisition r; + + if (!(*c)->w->is_visible ()) { + continue; + } + + (*c)->w->size_request (r); + + if (horizontal) { + largest.height = max (largest.height, r.height); + if ((*c)->minsize) { + largest.width += (*c)->minsize; + } else { + largest.width += r.width; + } + } else { + largest.width = max (largest.width, r.width); + if ((*c)->minsize) { + largest.height += (*c)->minsize; + } else { + largest.height += r.height; + } + } + } + + *req = largest; +} + +GType +Pane::child_type_vfunc() const +{ + /* We accept any number of any types of widgets */ + return Gtk::Widget::get_type(); +} + +void +Pane::add_divider () +{ + Divider* d = new Divider; + d->set_name (X_("Divider")); + d->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_press_event), d), false); + d->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_release_event), d), false); + d->signal_motion_notify_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_motion_event), d), false); + d->signal_enter_notify_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_enter_event), d), false); + d->signal_leave_notify_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_leave_event), d), false); + d->set_parent (*this); + d->show (); + d->fract = 0.5; + dividers.push_back (d); +} + +void +Pane::handle_child_visibility () +{ + reallocate (get_allocation()); +} + +void +Pane::on_add (Widget* w) +{ + children.push_back (boost::shared_ptr<Child> (new Child (this, w, 0))); + Child* kid = children.back ().get(); + + w->set_parent (*this); + /* Gtkmm 2.4 does not correctly arrange for ::on_remove() to be called + for custom containers that derive from Gtk::Container. So ... we need + to ensure that we hear about child destruction ourselves. + */ + w->add_destroy_notify_callback (kid, &Pane::notify_child_destroyed); + + kid->show_con = w->signal_show().connect (sigc::mem_fun (*this, &Pane::handle_child_visibility)); + kid->hide_con = w->signal_hide().connect (sigc::mem_fun (*this, &Pane::handle_child_visibility)); + + while (dividers.size() < (children.size() - 1)) { + add_divider (); + } +} + +void* +Pane::notify_child_destroyed (void* data) +{ + Child* child = reinterpret_cast<Child*> (data); + return child->pane->child_destroyed (child->w); +} + +void* +Pane::child_destroyed (Gtk::Widget* w) +{ + for (Children::iterator c = children.begin(); c != children.end(); ++c) { + if ((*c)->w == w) { + (*c)->show_con.disconnect (); + (*c)->hide_con.disconnect (); + (*c)->w = NULL; // mark invalid + children.erase (c); + break; + } + } + return 0; +} + +void +Pane::on_remove (Widget* w) +{ + for (Children::iterator c = children.begin(); c != children.end(); ++c) { + if ((*c)->w == w) { + (*c)->show_con.disconnect (); + (*c)->hide_con.disconnect (); + w->remove_destroy_notify_callback ((*c).get()); + w->unparent (); + (*c)->w = NULL; // mark invalid + children.erase (c); + break; + } + } +} + +void +Pane::on_size_allocate (Gtk::Allocation& alloc) +{ + reallocate (alloc); + Container::on_size_allocate (alloc); + + /* minumum pane size constraints */ + Dividers::size_type div = 0; + for (Dividers::const_iterator d = dividers.begin(); d != dividers.end(); ++d, ++div) { + // XXX skip dividers that were just hidden in reallocate() + Pane::set_divider (div, (*d)->fract); + } + // TODO this needs tweaking for panes with > 2 children + // if a child grows, re-check the ones before it. + assert (dividers.size () < 3); +} + +void +Pane::reallocate (Gtk::Allocation const & alloc) +{ + int remaining; + int xpos = alloc.get_x(); + int ypos = alloc.get_y(); + float fract; + + if (children.empty()) { + return; + } + + if (children.size() == 1) { + /* only child gets the full allocation */ + if (children.front()->w->is_visible ()) { + children.front()->w->size_allocate (alloc); + } + return; + } + + if (horizontal) { + remaining = alloc.get_width (); + } else { + remaining = alloc.get_height (); + } + + Children::iterator child; + Children::iterator next; + Dividers::iterator div; + + child = children.begin(); + + /* skip initial hidden children */ + + while (child != children.end()) { + if ((*child)->w->is_visible()) { + break; + } + ++child; + } + + for (div = dividers.begin(); child != children.end(); ) { + + Gtk::Allocation child_alloc; + + next = child; + + /* Move on to next *visible* child */ + + while (++next != children.end()) { + if ((*next)->w->is_visible()) { + break; + } + } + + child_alloc.set_x (xpos); + child_alloc.set_y (ypos); + + if (next == children.end()) { + /* last child gets all the remaining space */ + fract = 1.0; + } else { + /* child gets the fraction of the remaining space given by the divider that follows it */ + fract = (*div)->fract; + } + + Gtk::Requisition cr; + (*child)->w->size_request (cr); + + if (horizontal) { + child_alloc.set_width ((gint) floor (remaining * fract)); + child_alloc.set_height (alloc.get_height()); + remaining = max (0, (remaining - child_alloc.get_width())); + xpos += child_alloc.get_width(); + } else { + child_alloc.set_width (alloc.get_width()); + child_alloc.set_height ((gint) floor (remaining * fract)); + remaining = max (0, (remaining - child_alloc.get_height())); + ypos += child_alloc.get_height (); + } + + if ((*child)->minsize) { + if (horizontal) { + child_alloc.set_width (max (child_alloc.get_width(), (*child)->minsize)); + } else { + child_alloc.set_height (max (child_alloc.get_height(), (*child)->minsize)); + } + } + + if ((*child)->w->is_visible ()) { + (*child)->w->size_allocate (child_alloc); + } + + if (next == children.end()) { + /* done, no more children, no need for a divider */ + break; + } + + child = next; + + /* add a divider between children */ + + Gtk::Allocation divider_allocation; + + divider_allocation.set_x (xpos); + divider_allocation.set_y (ypos); + + if (horizontal) { + divider_allocation.set_width (divider_width); + divider_allocation.set_height (alloc.get_height()); + remaining = max (0, remaining - divider_width); + xpos += divider_width; + } else { + divider_allocation.set_width (alloc.get_width()); + divider_allocation.set_height (divider_width); + remaining = max (0, remaining - divider_width); + ypos += divider_width; + } + + (*div)->size_allocate (divider_allocation); + (*div)->show (); + ++div; + } + + /* hide all remaining dividers */ + + while (div != dividers.end()) { + (*div)->hide (); + ++div; + } +} + +bool +Pane::on_expose_event (GdkEventExpose* ev) +{ + Children::iterator child; + Dividers::iterator div; + + for (child = children.begin(), div = dividers.begin(); child != children.end(); ++child) { + + if ((*child)->w->is_visible()) { + propagate_expose (*((*child)->w), ev); + } + + if (div != dividers.end()) { + if ((*div)->is_visible()) { + propagate_expose (**div, ev); + } + ++div; + } + } + + return true; +} + +bool +Pane::handle_press_event (GdkEventButton* ev, Divider* d) +{ + d->dragging = true; + d->queue_draw (); + + return false; +} + +bool +Pane::handle_release_event (GdkEventButton* ev, Divider* d) +{ + d->dragging = false; + + if (did_move && !children.empty()) { + children.front()->w->queue_resize (); + did_move = false; + } + + return false; +} +void +Pane::set_check_divider_position (bool yn) +{ + check_fract = yn; +} + +float +Pane::constrain_fract (Dividers::size_type div, float fract) +{ + if (get_allocation().get_width() == 1 && get_allocation().get_height() == 1) { + /* space not * allocated - * divider being set from startup code. Let it pass, + * since our goal is mostly to catch drags to a position that will interfere with window + * resizing. + */ + return fract; + } + + if (children.size () <= div + 1) { return fract; } // XXX remove once hidden divs are skipped + assert(children.size () > div + 1); + + const float size = horizontal ? get_allocation().get_width() : get_allocation().get_height(); + + // TODO: optimize: cache in Pane::on_size_request + Gtk::Requisition prev_req(children.at (div)->w->size_request ()); + Gtk::Requisition next_req(children.at (div + 1)->w->size_request ()); + float prev = (horizontal ? prev_req.width : prev_req.height); + float next = (horizontal ? next_req.width : next_req.height); + + if (children.at (div)->minsize) { + prev = children.at (div)->minsize; + } + if (children.at (div + 1)->minsize) { + next = children.at (div + 1)->minsize; + } + + if (size * fract < prev) { + return prev / size; + } + if (size * (1.f - fract) < next) { + return 1.f - next / size; + } + + if (!check_fract) { + return fract; + } + +#ifdef __APPLE__ + + /* On Quartz, if the pane handle (divider) gets to + be adjacent to the window edge, you can no longer grab it: + any attempt to do so is interpreted by the Quartz window + manager ("Finder") as a resize drag on the window edge. + */ + + + if (horizontal) { + if (div == dividers.size() - 1) { + if (get_allocation().get_width() * (1.0 - fract) < (divider_width*2)) { + /* too close to right edge */ + return 1.f - (divider_width * 2.f) / (float) get_allocation().get_width(); + } + } + + if (div == 0) { + if (get_allocation().get_width() * fract < (divider_width*2)) { + /* too close to left edge */ + return (divider_width * 2.f) / (float)get_allocation().get_width(); + } + } + } else { + if (div == dividers.size() - 1) { + if (get_allocation().get_height() * (1.0 - fract) < (divider_width*2)) { + /* too close to bottom */ + return 1.f - (divider_width * 2.f) / (float) get_allocation().get_height(); + } + } + + if (div == 0) { + if (get_allocation().get_height() * fract < (divider_width*2)) { + /* too close to top */ + return (divider_width * 2.f) / (float) get_allocation().get_height(); + } + } + } +#endif + return fract; +} + +bool +Pane::handle_motion_event (GdkEventMotion* ev, Divider* d) +{ + did_move = true; + + if (!d->dragging) { + return true; + } + + /* determine new position for handle */ + + float new_fract; + int px, py; + + d->translate_coordinates (*this, ev->x, ev->y, px, py); + + Dividers::iterator prev = dividers.end(); + Dividers::size_type div = 0; + + for (Dividers::iterator di = dividers.begin(); di != dividers.end(); ++di, ++div) { + if (*di == d) { + break; + } + prev = di; + } + + int space_remaining; + int prev_edge; + + if (horizontal) { + if (prev != dividers.end()) { + prev_edge = (*prev)->get_allocation().get_x() + (*prev)->get_allocation().get_width(); + } else { + prev_edge = 0; + } + space_remaining = get_allocation().get_width() - prev_edge; + new_fract = (float) (px - prev_edge) / space_remaining; + } else { + if (prev != dividers.end()) { + prev_edge = (*prev)->get_allocation().get_y() + (*prev)->get_allocation().get_height(); + } else { + prev_edge = 0; + } + space_remaining = get_allocation().get_height() - prev_edge; + new_fract = (float) (py - prev_edge) / space_remaining; + } + + new_fract = min (1.0f, max (0.0f, new_fract)); + new_fract = constrain_fract (div, new_fract); + new_fract = min (1.0f, max (0.0f, new_fract)); + + if (new_fract != d->fract) { + d->fract = new_fract; + reallocate (get_allocation ()); + queue_draw (); + } + + return true; +} + +void +Pane::set_divider (Dividers::size_type div, float fract) +{ + Dividers::iterator d = dividers.begin(); + + for (d = dividers.begin(); d != dividers.end() && div != 0; ++d, --div) { + /* relax */ + } + + if (d == dividers.end()) { + /* caller is trying to set divider that does not exist + * yet. + */ + return; + } + + fract = max (0.0f, min (1.0f, fract)); + fract = constrain_fract (div, fract); + fract = max (0.0f, min (1.0f, fract)); + + if (fract != (*d)->fract) { + (*d)->fract = fract; + /* our size hasn't changed, but our internal allocations have */ + reallocate (get_allocation()); + queue_draw (); + } +} + +float +Pane::get_divider (Dividers::size_type div) +{ + Dividers::iterator d = dividers.begin(); + + for (d = dividers.begin(); d != dividers.end() && div != 0; ++d, --div) { + /* relax */ + } + + if (d == dividers.end()) { + /* caller is trying to set divider that does not exist + * yet. + */ + return -1.0f; + } + + return (*d)->fract; +} + +void +Pane::forall_vfunc (gboolean include_internals, GtkCallback callback, gpointer callback_data) +{ + /* since the callback could modify the child list(s), make sure we keep + * the iterators safe; + */ + Children kids (children); + for (Children::const_iterator c = kids.begin(); c != kids.end(); ++c) { + if ((*c)->w) { + callback ((*c)->w->gobj(), callback_data); + } + } + + if (include_internals) { + for (Dividers::iterator d = dividers.begin(); d != dividers.end(); ) { + Dividers::iterator next = d; + ++next; + callback (GTK_WIDGET((*d)->gobj()), callback_data); + d = next; + } + } +} + +Pane::Divider::Divider () + : fract (0.0) + , dragging (false) +{ + set_events (Gdk::EventMask (Gdk::BUTTON_PRESS| + Gdk::BUTTON_RELEASE| + Gdk::MOTION_NOTIFY| + Gdk::ENTER_NOTIFY| + Gdk::LEAVE_NOTIFY)); +} + +bool +Pane::Divider::on_expose_event (GdkEventExpose* ev) +{ + Gdk::Color c = (dragging ? get_style()->get_fg (Gtk::STATE_ACTIVE) : + get_style()->get_fg (get_state())); + + Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context (); + draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height); + draw_context->clip_preserve (); + draw_context->set_source_rgba (c.get_red_p(), c.get_green_p(), c.get_blue_p(), 1.0); + draw_context->fill (); + + return true; +} + +bool +Pane::handle_enter_event (GdkEventCrossing*, Divider* d) +{ + d->get_window()->set_cursor (drag_cursor); + d->set_state (Gtk::STATE_SELECTED); + return true; +} + +bool +Pane::handle_leave_event (GdkEventCrossing*, Divider* d) +{ + d->get_window()->set_cursor (); + d->set_state (Gtk::STATE_NORMAL); + d->queue_draw (); + return true; +} diff --git a/libs/widgets/paths_dialog.cc b/libs/widgets/paths_dialog.cc new file mode 100644 index 0000000000..cf366a7c7c --- /dev/null +++ b/libs/widgets/paths_dialog.cc @@ -0,0 +1,164 @@ +/* + Copyright (C) 2014 Robin Gareus <robin@gareus.org> + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +#include <cstdio> + +#include "pbd/i18n.h" +#include "pbd/pathexpand.h" +#include "widgets/paths_dialog.h" + +using namespace Gtk; +using namespace std; +using namespace ArdourWidgets; + +PathsDialog::PathsDialog (Gtk::Window& parent, std::string title, std::string current_paths, std::string default_paths) + : Dialog (title, parent, true) + , paths_list_view(1, false, Gtk::SELECTION_SINGLE) + , add_path_button(_("Add")) + , remove_path_button(_("Delete")) + , set_default_button(_("Reset to Default")) + , _default_paths(default_paths) +{ + set_name ("PathsDialog"); + set_skip_taskbar_hint (true); + set_resizable (true); + set_size_request (400, -1); + + paths_list_view.set_border_width (4); + + add_path_button.signal_clicked().connect (sigc::mem_fun (*this, &PathsDialog::add_path)); + remove_path_button.signal_clicked().connect (sigc::mem_fun (*this, &PathsDialog::remove_path)); + set_default_button.signal_clicked().connect (sigc::mem_fun (*this, &PathsDialog::set_default)); + remove_path_button.set_sensitive(false); + + paths_list_view.set_column_title(0,"Path"); + + std::vector <std::string> a = PBD::parse_path(current_paths); + for(vector<std::string>::const_iterator i = a.begin(); i != a.end(); ++i) { + paths_list_view.append_text(*i); + } + + paths_list_view.get_selection()->signal_changed().connect (mem_fun (*this, &PathsDialog::selection_changed)); + + VBox *vbox = manage (new VBox); + vbox->pack_start (add_path_button, false, false); + vbox->pack_start (remove_path_button, false, false); + vbox->pack_start (set_default_button, false, false); + + /* Overall layout */ + HBox *hbox = manage (new HBox); + hbox->pack_start (*vbox, false, false); + hbox->pack_start (paths_list_view, true, true); // TODO, wrap in scroll-area ?! + hbox->set_spacing (4); + + get_vbox()->set_spacing (4); + get_vbox()->pack_start (*hbox, true, true); + + add_button (Stock::CANCEL, RESPONSE_CANCEL); + add_button (Stock::OK, RESPONSE_ACCEPT); + + show_all_children (); +} + +PathsDialog::~PathsDialog () +{ +} + +void +PathsDialog::on_show() { + Dialog::on_show (); +} + +std::string +PathsDialog::get_serialized_paths() { + std::string path; + for (unsigned int i = 0; i < paths_list_view.size(); ++i) { + if (i > 0) path += G_SEARCHPATH_SEPARATOR; + path += paths_list_view.get_text(i, 0); + } + return path; +} + +void +PathsDialog::selection_changed () { + std::vector<int> selection = paths_list_view.get_selected(); + if (selection.size() > 0) { + remove_path_button.set_sensitive(true); + } else { + remove_path_button.set_sensitive(false); + } +} + +void +PathsDialog::add_path() { + Gtk::FileChooserDialog d (_("Add folder to search path"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); + + std::vector<int> selection = paths_list_view.get_selected(); + if (selection.size() == 1 ) { + d.set_current_folder(paths_list_view.get_text(selection.at(0), 0)); + } + + d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + d.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK); + ResponseType r = (ResponseType) d.run (); + if (r == Gtk::RESPONSE_OK) { + std::string dir = d.get_filename(); + if (Glib::file_test (dir, Glib::FILE_TEST_IS_DIR|Glib::FILE_TEST_EXISTS)) { + bool dup = false; + for (unsigned int i = 0; i < paths_list_view.size(); ++i) { + if (paths_list_view.get_text(i, 0) == dir) { + dup = true; + break; + } + } + if (!dup) { + paths_list_view.prepend_text(dir); + } + } + } +} + +void +PathsDialog::remove_path() { + std::vector<int> selection = paths_list_view.get_selected(); + if (selection.size() == 0 ) { return ; } + + /* Gtk::ListViewText internals to delete row(s) */ + Gtk::TreeModel::iterator row_it = paths_list_view.get_selection()->get_selected(); + Glib::RefPtr<Gtk::TreeModel> reftm = paths_list_view.get_model(); + Glib::RefPtr<Gtk::TreeStore> refStore = Glib::RefPtr<Gtk::TreeStore>::cast_dynamic(reftm); + if(refStore) { + refStore->erase(row_it); + return; + } + Glib::RefPtr<Gtk::ListStore> refLStore = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(reftm); + if(refLStore){ + refLStore->erase(row_it); + return; + } +} + +void +PathsDialog::set_default() { + + paths_list_view.clear_items(); + std::vector <std::string> a = PBD::parse_path(_default_paths); + for(vector<std::string>::const_iterator i = a.begin(); i != a.end(); ++i) { + paths_list_view.append_text(*i); + } +} diff --git a/libs/widgets/popup.cc b/libs/widgets/popup.cc new file mode 100644 index 0000000000..49d33dd8af --- /dev/null +++ b/libs/widgets/popup.cc @@ -0,0 +1,149 @@ +/* + Copyright (C) 1998-99 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <iostream> + +#include <gtkmm2ext/gtk_ui.h> +#include <gtkmm2ext/utils.h> + +#include <widgets/popup.h> + +using namespace std; +using namespace Gtk; +using namespace ArdourWidgets; + +PopUp::PopUp (Gtk::WindowPosition pos, unsigned int showfor_msecs, bool doh) + : Window (WINDOW_POPUP) +{ + add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK); + signal_button_press_event().connect(mem_fun(*this,&PopUp::button_click)); + set_border_width (12); + add (label); + set_position (pos); + + delete_on_hide = doh; + popdown_time = showfor_msecs; + timeout = -1; +} + + +PopUp::~PopUp () +{ +} + +void +PopUp::on_realize () +{ + Gtk::Window::on_realize(); + get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)); +} + +gint +PopUp::remove_prompt_timeout (void *arg) +{ + PopUp *pup = (PopUp *) arg; + + pup->remove (); + return FALSE; +} + +static gint idle_delete (void *arg) +{ + delete static_cast<PopUp*> (arg); + return FALSE; +} + +void +PopUp::remove () +{ + hide (); + + if (popdown_time != 0 && timeout != -1) { + g_source_remove (timeout); + } + + if (delete_on_hide) { + std::cerr << "deleting prompter\n"; + g_idle_add (idle_delete, this); + } +} +#define ENSURE_GUI_THREAD(slot) \ + if (!Gtkmm2ext::UI::instance()->caller_is_ui_thread()) {\ + Gtkmm2ext::UI::instance()->call_slot (MISSING_INVALIDATOR, (slot)); \ + return;\ + } + + +void +PopUp::touch () +{ + ENSURE_GUI_THREAD (mem_fun (*this, &PopUp::touch)); + + if (is_visible ()) { + remove (); + } else { + Gtkmm2ext::set_size_request_to_display_given_text (label, my_text.c_str(), 25, 10); + label.set_text (my_text); + show_all (); + + if (popdown_time != 0) { + timeout = g_timeout_add (popdown_time, + remove_prompt_timeout, + this); + } + } +} + +gint +PopUp::button_click (GdkEventButton* /*ev*/) +{ + remove (); + return TRUE; +} + +void +PopUp::set_text (string txt) +{ + my_text = txt; +} + +void +PopUp::set_name (string name) +{ + Window::set_name (name); + label.set_name (name); +} + +bool +PopUp::on_delete_event (GdkEventAny* /*ev*/) +{ + hide(); + + if (popdown_time != 0 && timeout != -1) { + g_source_remove (timeout); + } + + if (delete_on_hide) { + std::cerr << "deleting prompter\n" << endl; + g_idle_add (idle_delete, this); + } + + return true; +} diff --git a/libs/widgets/prompter.cc b/libs/widgets/prompter.cc new file mode 100644 index 0000000000..73b3bf9e49 --- /dev/null +++ b/libs/widgets/prompter.cc @@ -0,0 +1,138 @@ +/* + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <string> +#include <gtkmm/stock.h> + +#include "pbd/whitespace.h" +#include "widgets/prompter.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace ArdourWidgets; + +Prompter::Prompter (Gtk::Window& parent, bool modal) + : Gtk::Dialog ("", parent, modal) + , first_show (true) + , can_accept_from_entry (false) +{ + init (); +} + +Prompter::Prompter (bool modal) + : Gtk::Dialog ("", modal) + , first_show (true) + , can_accept_from_entry (false) +{ + init (); +} + +void +Prompter::init () +{ + set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG); + set_position (Gtk::WIN_POS_MOUSE); + set_name ("Prompter"); + + add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + + /* + Alas a generic 'affirmative' button seems a bit useless sometimes. + You will have to add your own. + After adding, use : + set_response_sensitive (Gtk::RESPONSE_ACCEPT, false) + to prevent the RESPONSE_ACCEPT button from permitting blank strings. + */ + + entryLabel.set_line_wrap (true); + entryLabel.set_name ("PrompterLabel"); + + entryBox.set_homogeneous (false); + entryBox.set_spacing (5); + entryBox.set_border_width (10); + entryBox.pack_start (entryLabel, false, false); + entryBox.pack_start (entry, true, true); + + get_vbox()->pack_start (entryBox); + show_all_children(); +} + +void +Prompter::on_show () +{ + /* don't connect to signals till shown, so that we don't change the + response sensitivity etc. when the setup of the dialog sets the text. + */ + + if (first_show) { + entry.signal_changed().connect (mem_fun (*this, &Prompter::on_entry_changed)); + entry.signal_activate().connect (mem_fun (*this, &Prompter::entry_activated)); + can_accept_from_entry = !entry.get_text().empty(); + first_show = false; + } + + Dialog::on_show (); +} + +void +Prompter::change_labels (string /*okstr*/, string /*cancelstr*/) +{ + // dynamic_cast<Gtk::Label*>(ok.get_child())->set_text (okstr); + // dynamic_cast<Gtk::Label*>(cancel.get_child())->set_text (cancelstr); +} + +void +Prompter::get_result (string &str, bool strip) +{ + str = entry.get_text (); + if (strip) { + PBD::strip_whitespace_edges (str); + } +} + +void +Prompter::entry_activated () +{ + if (can_accept_from_entry) { + response (Gtk::RESPONSE_ACCEPT); + } else { + response (Gtk::RESPONSE_CANCEL); + } +} + +void +Prompter::on_entry_changed () +{ + /* + This is set up so that entering text in the entry + field makes the RESPONSE_ACCEPT button active. + Of course if you haven't added a RESPONSE_ACCEPT + button, nothing will happen at all. + */ + + if (!entry.get_text().empty()) { + set_response_sensitive (Gtk::RESPONSE_ACCEPT, true); + set_default_response (Gtk::RESPONSE_ACCEPT); + can_accept_from_entry = true; + } else { + set_response_sensitive (Gtk::RESPONSE_ACCEPT, false); + } +} diff --git a/libs/widgets/scroomer.cc b/libs/widgets/scroomer.cc new file mode 100644 index 0000000000..98c456dc74 --- /dev/null +++ b/libs/widgets/scroomer.cc @@ -0,0 +1,408 @@ +/* + Copyright (C) 2008 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <iostream> + +#include "gtkmm2ext/keyboard.h" +#include "widgets/scroomer.h" + +using namespace std; +using namespace Gdk; +using namespace Gtk; +using namespace ArdourWidgets; + +Scroomer::Scroomer(Gtk::Adjustment& adjustment) + : adj(adjustment) + , handle_size(0) + , grab_comp(None) +{ + position[TopBase] = 0; + position[Handle1] = 0; + position[Slider] = 0; + position[Handle2] = 0; + position[BottomBase] = 0; + position[Total] = 0; + + add_events (Gdk::BUTTON_PRESS_MASK | + Gdk::BUTTON_RELEASE_MASK | + Gdk::POINTER_MOTION_MASK | + Gdk::SCROLL_MASK); + + adjustment.signal_value_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed)); + //adjustment.signal_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed)); +} + +Scroomer::~Scroomer() +{ +} + +bool +Scroomer::on_motion_notify_event (GdkEventMotion* ev) +{ + double range = adj.get_upper() - adj.get_lower(); + double pixel2val = range / get_height(); + double val_at_pointer = ((get_height() - ev->y) * pixel2val) + adj.get_lower(); + double delta_y = ev->y - grab_y; + double half_min_page = min_page_size / 2; + double fract = delta_y / position[Total]; + double scale, temp, zoom; + double val, page; + + if (grab_comp == None || grab_comp == Total) { + return true; + } + + if (ev->window != grab_window) { + grab_y = ev->y; + grab_window = ev->window; + return true; + } + + if (ev->y < 0 || ev->y > get_height ()) { + return true; + } + + grab_y = ev->y; + + if (ev->state & Gtkmm2ext::Keyboard::PrimaryModifier) { + if (ev->state & Gtkmm2ext::Keyboard::SecondaryModifier) { + scale = 0.05; + } else { + scale = 0.1; + } + } else { + scale = 1.0; + } + + fract = min (1.0, fract); + fract = max (-1.0, fract); + fract = -fract; + + switch (grab_comp) { + case TopBase: + case BottomBase: + unzoomed_val += scale * fract * range; + unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page); + unzoomed_val = max(unzoomed_val, adj.get_lower()); + break; + case Slider: + unzoomed_val += scale * fract * range; + unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page); + unzoomed_val = max(unzoomed_val, adj.get_lower()); + break; + case Handle1: + + unzoomed_page += scale * fract * range; + unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val); + unzoomed_page = max(unzoomed_page, min_page_size); + + if (pinch){ + temp = unzoomed_val + unzoomed_page; + unzoomed_val -= scale * fract * range * 0.5; + unzoomed_val = min(unzoomed_val, temp - min_page_size); + unzoomed_val = max(unzoomed_val, adj.get_lower()); + } + + break; + case Handle2: + temp = unzoomed_val + unzoomed_page; + unzoomed_val += scale * fract * range; + unzoomed_val = min(unzoomed_val, temp - min_page_size); + unzoomed_val = max(unzoomed_val, adj.get_lower()); + + unzoomed_page = temp - unzoomed_val; + + if (pinch){ + + unzoomed_page -= scale * fract * range; + } + + unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val); + unzoomed_page = max(unzoomed_page, min_page_size); + break; + default: + break; + } + + /* Then we handle zoom, which is dragging horizontally. We zoom around the area that is + * the current y pointer value, not from the area that was the start of the drag. + * We don't start doing zoom until we are at least one scroomer width outside the scroomer's + * area. + */ + + if (ev->x > (get_width() * 2)) { + zoom = ev->x - get_width(); + + double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer; + double lower = val_at_pointer - (unzoomed_val + half_min_page); + + higher *= zoom / 128; + lower *= zoom / 128; + + val = unzoomed_val + lower; + page = unzoomed_page - higher - lower; + + page = max(page, min_page_size); + + if (lower < 0) { + val = max(val, val_at_pointer - half_min_page); + } else if (lower > 0) { + val = min(val, val_at_pointer - half_min_page); + } + + val = min(val, adj.get_upper() - min_page_size); + page = min(page, adj.get_upper() - val); + } else if (ev->x < 0) { + /* on zoom out increase the page size as well as moving the range towards the mouse pos*/ + /*zoom = abs(ev->x); + + double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer; + double lower = val_at_pointer - (unzoomed_val + half_min_page); + + higher *= zoom / 128; + lower *= zoom / 128; + + val = unzoomed_val + lower; + page = unzoomed_page - higher - lower; + + page = max(page, min_page_size); + + if (lower < 0) { + val = max(val, val_at_pointer - half_min_page); + } + else if (lower > 0) { + val = min(val, val_at_pointer - half_min_page); + } + + val = min(val, adj.get_upper() - min_page_size); + page = min(page, adj.get_upper() - val);*/ + + val = unzoomed_val; + page = unzoomed_page; + } else { + val = unzoomed_val; + page = unzoomed_page; + } + + /* Round these values to stop the scroomer handlers quivering about during drags */ + adj.set_page_size (rint (page)); + adj.set_value (rint (val)); + adj.value_changed(); + + return true; +} + +bool +Scroomer::on_scroll_event (GdkEventScroll* ev) +{ + switch (ev->direction) { + case GDK_SCROLL_UP: + adj.set_value (min (adj.get_value() + adj.get_page_size() / 10.0, adj.get_upper() - adj.get_page_size())); + break; + case GDK_SCROLL_DOWN: + adj.set_value (adj.get_value() - adj.get_page_size() / 10.0); + break; + default: + return false; + } + + return true; +} + +bool +Scroomer::on_button_press_event (GdkEventButton* ev) +{ + if (ev->button == 1 || ev->button == 3) { + Component comp = point_in(ev->y); + + if (comp == Total || comp == None) { + return false; + } + + add_modal_grab(); + grab_comp = comp; + grab_y = ev->y; + unzoomed_val = adj.get_value(); + unzoomed_page = adj.get_page_size(); + grab_window = ev->window; + + if (ev->button == 3){ + pinch = true; + } else { + pinch = false; + } + + DragStarting (); /* EMIT SIGNAL */ + } + + if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) { + DoubleClicked(); + } + + return true; +} + +bool +Scroomer::on_button_release_event (GdkEventButton* ev) +{ + if (grab_comp == None || grab_comp == Total) { + return true; + } + + if (ev->window != grab_window) { + grab_y = ev->y; + grab_window = ev->window; + return true; + } + + if (ev->button != 1 && ev->button != 3) { + return true; + } + + switch (grab_comp) { + case TopBase: + break; + case Handle1: + break; + case Slider: + break; + case Handle2: + break; + case BottomBase: + break; + default: + break; + } + + grab_comp = None; + + remove_modal_grab(); + DragFinishing (); /* EMIT SIGNAL */ + return true; +} + +void +Scroomer::on_size_allocate (Allocation& a) +{ + Gtk::DrawingArea::on_size_allocate(a); + + position[Total] = a.get_height(); + set_min_page_size(min_page_size); + update(); +} + +/** Assumes that x and width are correct, and they will not be altered. + */ +void +Scroomer::set_comp_rect(GdkRectangle& r, Component c) const +{ + int index = (int) c; + + switch (c) { + case None: + return; + case Total: + r.y = 0; + r.height = position[Total]; + break; + default: + r.y = position[index]; + r.height = position[index+1] - position[index]; + break; + } +} + +Scroomer::Component +Scroomer::point_in(double point) const +{ + for (int i = 0; i < Total; ++i) { + if (position[i+1] >= point) { + return (Component) i; + } + } + + return None; +} + +void +Scroomer::set_min_page_size(double ps) +{ + double coeff = ((double)position[Total]) / (adj.get_upper() - adj.get_lower()); + + min_page_size = ps; + handle_size = (int) floor((ps * coeff) / 2); +} + +void +Scroomer::update() +{ + double range = adj.get_upper() - adj.get_lower(); + //double value = adj.get_value() - adj.get_lower(); + int height = position[Total]; + double coeff = ((double) height) / range; + + /* save the old positions to calculate update regions later*/ + for (int i = Handle1; i < Total; ++i) { + old_pos[i] = position[i]; + } + + position[BottomBase] = (int) floor(height - (adj.get_value() * coeff)); + position[Handle2] = position[BottomBase] - handle_size; + + position[Handle1] = (int) floor(height - ((adj.get_value() + adj.get_page_size()) * coeff)); + position[Slider] = position[Handle1] + handle_size; +} + +void +Scroomer::adjustment_changed() +{ + //cerr << floor(adj.get_value()) << " " << floor(adj.get_value() + adj.get_page_size()) << endl; + Gdk::Rectangle rect; + Glib::RefPtr<Gdk::Window> win = get_window(); + + update(); + + if (!win) { + return; + } + + rect.set_x(0); + rect.set_width(get_width()); + + if (position[Handle1] < old_pos[Handle1]) { + rect.set_y(position[Handle1]); + rect.set_height(old_pos[Slider] - position[Handle1]); + win->invalidate_rect(rect, false); + } else if (position[Handle1] > old_pos[Handle1]) { + rect.set_y(old_pos[Handle1]); + rect.set_height(position[Slider] - old_pos[Handle1]); + win->invalidate_rect(rect, false); + } + + if (position[Handle2] < old_pos[Handle2]) { + rect.set_y(position[Handle2]); + rect.set_height(old_pos[BottomBase] - position[Handle2]); + win->invalidate_rect(rect, false); + } else if (position[Handle2] > old_pos[Handle2]) { + rect.set_y(old_pos[Handle2]); + rect.set_height(position[BottomBase] - old_pos[Handle2]); + win->invalidate_rect(rect, false); + } +} + diff --git a/libs/widgets/stateful_button.cc b/libs/widgets/stateful_button.cc new file mode 100644 index 0000000000..f0ba91f38e --- /dev/null +++ b/libs/widgets/stateful_button.cc @@ -0,0 +1,273 @@ +/* + Copyright (C) 2000-2007 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <string> +#include <iostream> + + +#include <gtkmm/main.h> + +#include "widgets/stateful_button.h" + +using namespace Gtk; +using namespace Glib; +using namespace ArdourWidgets; +using namespace std; + +StateButton::StateButton () + : visual_state (0) + , _self_managed (false) + , _is_realized (false) + , style_changing (false) + , state_before_prelight (Gtk::STATE_NORMAL) + , is_toggle (false) +{ +} + +void +StateButton::set_visual_state (int n) +{ + if (!_is_realized) { + /* not yet realized */ + visual_state = n; + return; + } + + if (n == visual_state) { + return; + } + + string name = get_widget_name (); + name = name.substr (0, name.find_last_of ('-')); + + switch (n) { + case 0: + /* relax */ + break; + case 1: + name += "-active"; + break; + + case 2: + name += "-alternate"; + break; + + case 3: + name += "-alternate2"; + break; + } + + set_widget_name (name); + visual_state = n; +} + +void +StateButton::avoid_prelight_on_style_changed (const Glib::RefPtr<Gtk::Style>& /* old_style */, GtkWidget* widget) +{ + /* don't go into an endless recursive loop if we're changing + the style in response to an existing style change. + */ + + if (style_changing) { + return; + } + + if (gtk_widget_get_state (widget) == GTK_STATE_PRELIGHT) { + + /* avoid PRELIGHT: make sure that the prelight colors in this new style match + the colors of the new style in whatever state we were in + before we switched to prelight. + */ + + GtkRcStyle* rcstyle = gtk_widget_get_modifier_style (widget); + GtkStyle* style = gtk_widget_get_style (widget); + + rcstyle->fg[GTK_STATE_PRELIGHT] = style->fg[state_before_prelight]; + rcstyle->bg[GTK_STATE_PRELIGHT] = style->bg[state_before_prelight]; + rcstyle->color_flags[GTK_STATE_PRELIGHT] = (GtkRcFlags) (GTK_RC_FG|GTK_RC_BG); + + style_changing = true; + g_object_ref (rcstyle); + gtk_widget_modify_style (widget, rcstyle); + + Widget* child = get_child_widget(); + if (child) { + gtk_widget_modify_style (GTK_WIDGET(child->gobj()), rcstyle); + } + + + g_object_unref (rcstyle); + style_changing = false; + } +} + +void +StateButton::avoid_prelight_on_state_changed (Gtk::StateType old_state, GtkWidget* widget) +{ + GtkStateType state = gtk_widget_get_state (widget); + + if (state == GTK_STATE_PRELIGHT) { + + state_before_prelight = old_state; + + + /* avoid PRELIGHT when currently ACTIVE: + if we just went into PRELIGHT, make sure that the colors + match those of whatever state we were in before. + */ + + GtkRcStyle* rcstyle = gtk_widget_get_modifier_style (widget); + GtkStyle* style = gtk_widget_get_style (widget); + + rcstyle->fg[GTK_STATE_PRELIGHT] = style->fg[old_state]; + rcstyle->bg[GTK_STATE_PRELIGHT] = style->bg[old_state]; + rcstyle->color_flags[GTK_STATE_PRELIGHT] = (GtkRcFlags) (GTK_RC_FG|GTK_RC_BG); + + g_object_ref (rcstyle); + gtk_widget_modify_style (widget, rcstyle); + + Widget* child = get_child_widget (); + + if (child) { + gtk_widget_modify_style (GTK_WIDGET(child->gobj()), rcstyle); + } + + g_object_unref (rcstyle); + + } +} + +/* ----------------------------------------------------------------- */ + +StatefulToggleButton::StatefulToggleButton () +{ + is_toggle = true; +} + +StatefulToggleButton::StatefulToggleButton (const std::string& label) + : ToggleButton (label) +{ + is_toggle = true; +} + +void +StatefulToggleButton::on_realize () +{ + ToggleButton::on_realize (); + + _is_realized = true; + visual_state++; // to force transition + set_visual_state (visual_state - 1); +} + +void +StatefulButton::on_realize () +{ + Button::on_realize (); + + _is_realized = true; + visual_state++; // to force transition + set_visual_state (visual_state - 1); +} + +void +StatefulToggleButton::on_toggled () +{ + if (!_self_managed) { + if (get_active()) { + set_state (Gtk::STATE_ACTIVE); + } else { + set_state (Gtk::STATE_NORMAL); + } + } +} + + +void +StatefulToggleButton::on_style_changed (const Glib::RefPtr<Gtk::Style>& style) +{ + avoid_prelight_on_style_changed (style, GTK_WIDGET(gobj())); + Button::on_style_changed (style); +} + +void +StatefulToggleButton::on_state_changed (Gtk::StateType old_state) +{ + avoid_prelight_on_state_changed (old_state, GTK_WIDGET(gobj())); + Button::on_state_changed (old_state); +} + +Widget* +StatefulToggleButton::get_child_widget () +{ + return get_child(); +} + +void +StatefulToggleButton::set_widget_name (const std::string& name) +{ + set_name (name); + Widget* w = get_child(); + + if (w) { + w->set_name (name); + } +} + +/*--------------------------------------------- */ + +StatefulButton::StatefulButton () +{ +} + +StatefulButton::StatefulButton (const std::string& label) + : Button (label) +{ +} + +void +StatefulButton::on_style_changed (const Glib::RefPtr<Gtk::Style>& style) +{ + avoid_prelight_on_style_changed (style, GTK_WIDGET(gobj())); + Button::on_style_changed (style); +} + +void +StatefulButton::on_state_changed (Gtk::StateType old_state) +{ + avoid_prelight_on_state_changed (old_state, GTK_WIDGET(gobj())); + Button::on_state_changed (old_state); +} + +Widget* +StatefulButton::get_child_widget () +{ + return get_child(); +} + +void +StatefulButton::set_widget_name (const std::string& name) +{ + set_name (name); + Widget* w = get_child(); + + if (w) { + w->set_name (name); + } +} diff --git a/libs/widgets/tabbable.cc b/libs/widgets/tabbable.cc new file mode 100644 index 0000000000..ea31e6f713 --- /dev/null +++ b/libs/widgets/tabbable.cc @@ -0,0 +1,389 @@ +/* + Copyright (C) 2015 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <gtkmm/action.h> +#include <gtkmm/notebook.h> +#include <gtkmm/window.h> +#include <gtkmm/stock.h> + +#include "pbd/stacktrace.h" + +#include "gtkmm2ext/gtk_ui.h" +#include "gtkmm2ext/utils.h" +#include "gtkmm2ext/visibility_tracker.h" + +#include "widgets/tabbable.h" + +#include "pbd/i18n.h" + +using std::string; +using namespace Gtk; +using namespace Gtkmm2ext; +using namespace ArdourWidgets; + +Tabbable::Tabbable (Widget& w, const string& name, bool tabbed_by_default) + : WindowProxy (name) + , _contents (w) + , _parent_notebook (0) + , tab_requested_by_state (tabbed_by_default) +{ +} + +Tabbable::~Tabbable () +{ + if (_window) { + delete _window; + _window = 0; + } +} + +void +Tabbable::add_to_notebook (Notebook& notebook, const string& tab_title) +{ + _parent_notebook = ¬ebook; + + if (tab_requested_by_state) { + attach (); + } +} + +Window* +Tabbable::use_own_window (bool and_pack_it) +{ + Gtk::Window* win = get (true); + + if (and_pack_it) { + Gtk::Container* parent = _contents.get_parent(); + if (parent) { + _contents.hide (); + parent->remove (_contents); + } + _own_notebook.append_page (_contents); + _contents.show (); + } + + return win; + +} + +bool +Tabbable::window_visible () const +{ + if (!_window) { + return false; + } + + return _window->is_visible(); +} + +Window* +Tabbable::get (bool create) +{ + if (_window) { + return _window; + } + + if (!create) { + return 0; + } + + /* From here on, we're creating the window + */ + + if ((_window = new Window (WINDOW_TOPLEVEL)) == 0) { + return 0; + } + + _window->add (_own_notebook); + _own_notebook.show (); + _own_notebook.set_show_tabs (false); + + _window->signal_map().connect (sigc::mem_fun (*this, &Tabbable::window_mapped)); + _window->signal_unmap().connect (sigc::mem_fun (*this, &Tabbable::window_unmapped)); + + /* do other window-related setup */ + + setup (); + + /* window should be ready for derived classes to do something with it */ + + return _window; +} + +void +Tabbable::show_own_window (bool and_pack_it) +{ + Gtk::Widget* parent = _contents.get_parent(); + Gtk::Allocation alloc; + + if (parent) { + alloc = parent->get_allocation(); + } + + (void) use_own_window (and_pack_it); + + if (parent) { + _window->set_default_size (alloc.get_width(), alloc.get_height()); + } + + tab_requested_by_state = false; + + _window->present (); +} + +Gtk::Notebook* +Tabbable::tab_root_drop () +{ + /* This is called after a drop of a tab onto the root window. Its + * responsibility xois to return the notebook that this Tabbable's + * contents should be packed into before the drop handling is + * completed. It is not responsible for actually taking care of this + * packing. + */ + + show_own_window (false); + return &_own_notebook; +} + +void +Tabbable::show_window () +{ + make_visible (); + + if (_window && (current_toplevel() == _window)) { + if (!_visible) { /* was hidden, update status */ + set_pos_and_size (); + } + } +} + +/** If this Tabbable is currently parented by a tab, ensure that the tab is the + * current one. If it is parented by a window, then toggle the visibility of + * that window. + */ +void +Tabbable::change_visibility () +{ + if (tabbed()) { + _parent_notebook->set_current_page (_parent_notebook->page_num (_contents)); + return; + } + + if (tab_requested_by_state) { + /* should be tabbed, but currently isn't parented by a notebook */ + return; + } + + if (_window && (current_toplevel() == _window)) { + /* Use WindowProxy method which will rotate then hide */ + toggle(); + } +} + +void +Tabbable::make_visible () +{ + if (_window && (current_toplevel() == _window)) { + set_pos (); + _window->present (); + } else { + + if (!tab_requested_by_state) { + show_own_window (true); + } else { + show_tab (); + } + } +} + +void +Tabbable::make_invisible () +{ + if (_window && (current_toplevel() == _window)) { + _window->hide (); + } else { + hide_tab (); + } +} + +void +Tabbable::detach () +{ + show_own_window (true); +} + +void +Tabbable::attach () +{ + if (!_parent_notebook) { + return; + } + + if (tabbed()) { + /* already tabbed */ + return; + } + + + if (_window && current_toplevel() == _window) { + /* unpack Tabbable from parent, put it back in the main tabbed + * notebook + */ + + save_pos_and_size (); + + _contents.hide (); + _contents.get_parent()->remove (_contents); + + /* leave the window around */ + + _window->hide (); + } + + _parent_notebook->append_page (_contents); + _parent_notebook->set_tab_detachable (_contents); + _parent_notebook->set_tab_reorderable (_contents); + _parent_notebook->set_current_page (_parent_notebook->page_num (_contents)); + _contents.show (); + + /* have to force this on, which is semantically correct, since + * the user has effectively asked for it. + */ + + tab_requested_by_state = true; + StateChange (*this); +} + +bool +Tabbable::delete_event_handler (GdkEventAny *ev) +{ + _window->hide(); + + return true; +} + +bool +Tabbable::tabbed () const +{ + if (_window && (current_toplevel() == _window)) { + return false; + } + + if (_parent_notebook && (_parent_notebook->page_num (_contents) >= 0)) { + return true; + } + + return false; +} + +void +Tabbable::hide_tab () +{ + if (tabbed()) { + _contents.hide(); + _parent_notebook->remove_page (_contents); + StateChange (*this); + } +} + +void +Tabbable::show_tab () +{ + if (!window_visible() && _parent_notebook) { + if (_contents.get_parent() == 0) { + tab_requested_by_state = true; + add_to_notebook (*_parent_notebook, _tab_title); + } + _parent_notebook->set_current_page (_parent_notebook->page_num (_contents)); + _contents.show (); + current_toplevel()->present (); + } +} + +Gtk::Window* +Tabbable::current_toplevel () const +{ + return dynamic_cast<Gtk::Window*> (contents().get_toplevel()); +} + +string +Tabbable::xml_node_name() +{ + return WindowProxy::xml_node_name(); +} + +bool +Tabbable::tabbed_by_default() const +{ + return tab_requested_by_state; +} + +XMLNode& +Tabbable::get_state() +{ + XMLNode& node (WindowProxy::get_state()); + + node.set_property (X_("tabbed"), tabbed()); + + return node; +} + +int +Tabbable::set_state (const XMLNode& node, int version) +{ + int ret; + + if ((ret = WindowProxy::set_state (node, version)) != 0) { + return ret; + } + + if (_visible) { + show_own_window (true); + } + + XMLNodeList children = node.children (); + XMLNode* window_node = node.child ("Window"); + + if (window_node) { + window_node->get_property (X_("tabbed"), tab_requested_by_state); + } + + if (!_visible) { + if (tab_requested_by_state) { + attach (); + } else { + /* this does nothing if not tabbed */ + hide_tab (); + } + } + + return ret; +} + +void +Tabbable::window_mapped () +{ + StateChange (*this); +} + +void +Tabbable::window_unmapped () +{ + StateChange (*this); +} diff --git a/libs/widgets/tearoff.cc b/libs/widgets/tearoff.cc new file mode 100644 index 0000000000..5fae7bcf3b --- /dev/null +++ b/libs/widgets/tearoff.cc @@ -0,0 +1,341 @@ +/* + Copyright (C) 2003 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <cmath> +#include <iostream> + +#include "pbd/xml++.h" + +#include "gtkmm2ext/utils.h" + +#include "widgets/tearoff.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace Glib; +using namespace Gdk; +using namespace Gtk; +using namespace ArdourWidgets; + +TearOff::TearOff (Widget& c, bool allow_resize) + : contents (c) + , own_window (Gtk::WINDOW_TOPLEVEL) + , tearoff_arrow (ARROW_DOWN, SHADOW_OUT) + , close_arrow (ARROW_UP, SHADOW_OUT) + , dragging (false) + , _visible (true) + , _torn (false) + , _can_be_torn_off (true) + +{ + own_window_width = 0; + own_window_height = 0; + own_window_xpos = 0; + own_window_ypos = 0; + + tearoff_event_box.add (tearoff_arrow); + tearoff_event_box.set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK); + tearoff_event_box.signal_button_release_event().connect (mem_fun (*this, &TearOff::tearoff_click)); + + tearoff_event_box.set_tooltip_text (_("Click to tear this into its own window")); + + close_event_box.add (close_arrow); + close_event_box.set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK); + close_event_box.signal_button_release_event().connect (mem_fun (*this, &TearOff::close_click)); + + close_event_box.set_tooltip_text (_("Click to put this back in the main window")); + + VBox* box1; + box1 = manage (new VBox); + box1->pack_start (close_event_box, false, false, 2); + + window_box.pack_end (*box1, false, false, 2); + + own_window.add_events (KEY_PRESS_MASK|KEY_RELEASE_MASK|BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK|POINTER_MOTION_MASK|POINTER_MOTION_HINT_MASK); + own_window.set_resizable (allow_resize); + own_window.set_type_hint (WINDOW_TYPE_HINT_UTILITY); + + own_window.add (window_box); + + own_window.signal_button_press_event().connect (mem_fun (*this, &TearOff::window_button_press)); + own_window.signal_button_release_event().connect (mem_fun (*this, &TearOff::window_button_release)); + own_window.signal_motion_notify_event().connect (mem_fun (*this, &TearOff::window_motion)); + own_window.signal_delete_event().connect (mem_fun (*this, &TearOff::window_delete_event)); + own_window.signal_realize().connect (sigc::mem_fun (*this, &TearOff::own_window_realized)); + own_window.signal_configure_event().connect (sigc::mem_fun (*this, &TearOff::own_window_configured), false); + + tearoff_arrow.set_name ("TearOffArrow"); + close_arrow.set_name ("TearOffArrow"); + + VBox* box2; + box2 = manage (new VBox); + box2->pack_start (tearoff_event_box, false, false); + + pack_start (contents); + pack_start (*box2, false, false); +} + +TearOff::~TearOff () +{ +} + +void +TearOff::set_can_be_torn_off (bool yn) +{ + if (yn != _can_be_torn_off) { + if (yn) { + tearoff_arrow.set_no_show_all (false); + tearoff_arrow.show (); + } else { + tearoff_arrow.set_no_show_all (true); + tearoff_arrow.hide (); + } + _can_be_torn_off = yn; + } +} + +void +TearOff::set_visible (bool yn, bool force) +{ + /* don't change visibility if torn off */ + + if (_torn) { + return; + } + + if (_visible != yn || force) { + _visible = yn; + if (yn) { + show_all(); + Visible (); + } else { + hide (); + Hidden (); + } + } +} + +gint +TearOff::tearoff_click (GdkEventButton* /*ev*/) +{ + tear_it_off (); + return true; +} + +void +TearOff::tear_it_off () +{ + if (!_can_be_torn_off) { + return; + } + + if (torn_off()) { + return; + } + + remove (contents); + window_box.pack_start (contents); + own_window.set_name (get_name()); + close_event_box.set_name (get_name()); + if (own_window_width == 0) { + own_window.set_position (WIN_POS_MOUSE); + } + own_window.show_all (); + own_window.present (); + hide (); + + _torn = true; + + Detach (); +} + +gint +TearOff::close_click (GdkEventButton* /*ev*/) +{ + put_it_back (); + return true; +} + +void +TearOff::put_it_back () +{ + if (!torn_off()) { + return; + } + + window_box.remove (contents); + pack_start (contents); + reorder_child (contents, 0); + own_window.hide (); + show_all (); + + _torn = false; + + Attach (); +} + +gint +TearOff::window_button_press (GdkEventButton* ev) +{ + if (dragging || ev->button != 1) { + dragging = false; + own_window.remove_modal_grab(); + return true; + } + + dragging = true; + drag_x = ev->x_root; + drag_y = ev->y_root; + + own_window.add_modal_grab(); + + return true; +} + +gint +TearOff::window_button_release (GdkEventButton* /*ev*/) +{ + dragging = false; + own_window.remove_modal_grab(); + return true; +} + +gint +TearOff::window_delete_event (GdkEventAny* /*ev*/) +{ + return close_click(0); +} + +gint +TearOff::window_motion (GdkEventMotion* ev) +{ + gint x; + gint y; + gint mx, my; + double x_delta; + double y_delta; + RefPtr<Gdk::Window> win (own_window.get_window()); + + own_window.get_pointer (mx, my); + + if (!dragging) { + return true; + } + + if (!(ev->state & GDK_BUTTON1_MASK)) { + dragging = false; + own_window.remove_modal_grab(); + return true; + } + + x_delta = ev->x_root - drag_x; + y_delta = ev->y_root - drag_y; + + win->get_root_origin (x, y); + win->move ((gint) floor (x + x_delta), (gint) floor (y + y_delta)); + + drag_x = ev->x_root; + drag_y = ev->y_root; + + return true; +} + +bool +TearOff::torn_off() const +{ + return _torn; +} + +void +TearOff::add_state (XMLNode& node) const +{ + node.set_property ("tornoff", _torn); + + if (own_window_width > 0) { + node.set_property ("width", own_window_width); + node.set_property ("height", own_window_height); + node.set_property ("xpos", own_window_xpos); + node.set_property ("ypos", own_window_ypos); + } +} + +void +TearOff::set_state (const XMLNode& node) +{ + Glib::RefPtr<Gdk::Window> win; + + bool tornoff; + if (!node.get_property (X_("tornoff"), tornoff)) { + return; + } + + if (tornoff) { + tear_it_off (); + } else { + put_it_back (); + } + + node.get_property (X_("width"), own_window_width); + node.get_property (X_("height"), own_window_height); + node.get_property (X_("xpos"), own_window_xpos); + node.get_property (X_("ypos"), own_window_ypos); + + if (own_window.is_realized ()) { + own_window.set_default_size (own_window_width, own_window_height); + own_window.move (own_window_xpos, own_window_ypos); + } + /* otherwise do it once the window is realized, see below */ +} + +void +TearOff::own_window_realized () +{ + own_window.get_window()->set_decorations (WMDecoration (DECOR_BORDER|DECOR_RESIZEH)); + + if (own_window_width > 0) { + own_window.set_default_size (own_window_width, own_window_height); + own_window.move (own_window_xpos, own_window_ypos); + } +} + +bool +TearOff::own_window_configured (GdkEventConfigure*) +{ + Glib::RefPtr<const Gdk::Window> win; + + win = own_window.get_window (); + + if (win) { + win->get_size (own_window_width, own_window_height); + win->get_position (own_window_xpos, own_window_ypos); + } + + return false; +} + +void +TearOff::hide_visible () +{ + if (torn_off()) { + own_window.hide (); + } + + hide (); +} diff --git a/libs/widgets/widgets/ardour_button.h b/libs/widgets/widgets/ardour_button.h index a51575e616..6630c19450 100644 --- a/libs/widgets/widgets/ardour_button.h +++ b/libs/widgets/widgets/ardour_button.h @@ -25,11 +25,11 @@ #include <gtkmm/action.h> #include "pbd/signals.h" -#include "gtkmm2ext/ardour_icon.h" -#include "gtkmm2ext/binding_proxy.h" #include "gtkmm2ext/activatable.h" #include "gtkmm2ext/cairo_widget.h" +#include "widgets/ardour_icon.h" +#include "widgets/binding_proxy.h" #include "widgets/visibility.h" namespace ArdourWidgets { @@ -80,8 +80,8 @@ class LIBWIDGETS_API ArdourButton : public CairoWidget , public Gtkmm2ext::Activ void set_elements (Element); void add_elements (Element); - Gtkmm2ext::ArdourIcon::Icon icon() const { return _icon; } - void set_icon (Gtkmm2ext::ArdourIcon::Icon); + ArdourIcon::Icon icon() const { return _icon; } + void set_icon (ArdourIcon::Icon); void set_icon (rendercallback_t, void*); void set_corner_radius (float); @@ -155,7 +155,7 @@ class LIBWIDGETS_API ArdourButton : public CairoWidget , public Gtkmm2ext::Activ std::string _sizing_text; bool _markup; Element _elements; - Gtkmm2ext::ArdourIcon::Icon _icon; + ArdourIcon::Icon _icon; rendercallback_t _icon_render_cb; void* _icon_render_cb_data; Tweaks _tweaks; diff --git a/libs/widgets/widgets/ardour_icon.h b/libs/widgets/widgets/ardour_icon.h new file mode 100644 index 0000000000..c49c5b833a --- /dev/null +++ b/libs/widgets/widgets/ardour_icon.h @@ -0,0 +1,50 @@ +#ifndef _WIDGETS_ARDOUR_ICON_H_ +#define _WIDGETS_ARDOUR_ICON_H_ + +#include <stdint.h> +#include <cairo.h> + +#include "gtkmm2ext/widget_state.h" +#include "widgets/visibility.h" + +namespace ArdourWidgets { namespace ArdourIcon { + enum Icon { + NoIcon, + RecButton, + RecTapeMode, + CloseCross, + StripWidth, + DinMidi, + TransportStop, + TransportPlay, + TransportLoop, + TransportRange, + TransportStart, + TransportEnd, + TransportPanic, + TransportMetronom, + NudgeLeft, + NudgeRight, + ZoomIn, + ZoomOut, + ZoomFull, + ZoomExpand, + TimeAxisShrink, + TimeAxisExpand, + ToolGrab, + ToolRange, + ToolCut, + ToolStretch, + ToolAudition, + ToolDraw, + ToolContent, + }; + + LIBWIDGETS_API bool render (cairo_t *cr, + const enum Icon icon, + const int width, const int height, + const Gtkmm2ext::ActiveState state, + const uint32_t fg_color); +}; } /* end namespace */ + +#endif diff --git a/libs/widgets/widgets/ardour_knob.h b/libs/widgets/widgets/ardour_knob.h index 84bb974adf..c07b0447cb 100644 --- a/libs/widgets/widgets/ardour_knob.h +++ b/libs/widgets/widgets/ardour_knob.h @@ -26,11 +26,11 @@ #include "pbd/signals.h" -#include "gtkmm2ext/binding_proxy.h" #include "gtkmm2ext/activatable.h" #include "gtkmm2ext/cairo_widget.h" #include "gtkmm2ext/persistent_tooltip.h" +#include "widgets/binding_proxy.h" #include "widgets/visibility.h" namespace ArdourWidgets { diff --git a/libs/widgets/widgets/barcontroller.h b/libs/widgets/widgets/barcontroller.h index fc2ac358cc..5fdbae6f2f 100644 --- a/libs/widgets/widgets/barcontroller.h +++ b/libs/widgets/widgets/barcontroller.h @@ -22,7 +22,7 @@ #include <gtkmm/alignment.h> #include <cairo.h> -#include "gtkmm2ext/binding_proxy.h" +#include "widgets/binding_proxy.h" #include "widgets/slider_controller.h" #include "widgets/visibility.h" diff --git a/libs/widgets/widgets/binding_proxy.h b/libs/widgets/widgets/binding_proxy.h new file mode 100644 index 0000000000..8ac49edb9f --- /dev/null +++ b/libs/widgets/widgets/binding_proxy.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2006 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _WIDGETS_BINDING_PROXY_ +#define _WIDGETS_BINDING_PROXY_ + +#include <string> +#include <boost/shared_ptr.hpp> + +#include "pbd/signals.h" + +#include "widgets/visibility.h" + +namespace PBD { + class Controllable; +} + +namespace ArdourWidgets { + class PopUp; +} + +namespace ArdourWidgets { + +class LIBWIDGETS_API BindingProxy : public sigc::trackable +{ +public: + BindingProxy (boost::shared_ptr<PBD::Controllable>); + BindingProxy (); + virtual ~BindingProxy(); + + void set_bind_button_state (guint button, guint statemask); + + static bool is_bind_action (GdkEventButton *); + bool button_press_handler (GdkEventButton *); + + boost::shared_ptr<PBD::Controllable> get_controllable() const { return controllable; } + void set_controllable (boost::shared_ptr<PBD::Controllable>); + +protected: + ArdourWidgets::PopUp* prompter; + boost::shared_ptr<PBD::Controllable> controllable; + + static guint bind_button; + static guint bind_statemask; + + PBD::ScopedConnection learning_connection; + void learning_finished (); + bool prompter_hiding (GdkEventAny *); +}; + +} + +#endif diff --git a/libs/widgets/widgets/choice.h b/libs/widgets/widgets/choice.h new file mode 100644 index 0000000000..a33c93ae00 --- /dev/null +++ b/libs/widgets/widgets/choice.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2000-2007 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_CHOICE_H_ +#define _WIDGETS_CHOICE_H_ + +#include <string> +#include <vector> + +#include <gtkmm/dialog.h> +#include <gtkmm/image.h> +#include <gtkmm/stock.h> +#include <gtkmm/box.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API Choice : public Gtk::Dialog +{ +public: + Choice (std::string title, std::string prompt, std::vector<std::string> choices, bool center = true); + virtual ~Choice (); + +protected: + void on_realize (); +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/click_box.h b/libs/widgets/widgets/click_box.h index fa4868467f..c83db83858 100644 --- a/libs/widgets/widgets/click_box.h +++ b/libs/widgets/widgets/click_box.h @@ -27,9 +27,8 @@ #include <string> #include <gtkmm.h> -#include "gtkmm2ext/binding_proxy.h" - #include "widgets/auto_spin.h" +#include "widgets/binding_proxy.h" #include "widgets/visibility.h" namespace PBD { diff --git a/libs/widgets/widgets/eventboxext.h b/libs/widgets/widgets/eventboxext.h new file mode 100644 index 0000000000..3e7402f3d3 --- /dev/null +++ b/libs/widgets/widgets/eventboxext.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 Robin Gareus <robin@gareus.org> + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _WIDGETS_EVENTBOX_EXT_H_ +#define _WIDGETS_EVENTBOX_EXT_H_ + +#include <gtkmm/eventbox.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API EventBoxExt : public Gtk::EventBox +{ +public: + EventBoxExt (); + virtual ~EventBoxExt () {} + +protected: + /* gtk2's gtk/gtkcontainer.c does not + * unmap child widgets if the container has a window. + * + * (this is for historical reasons and optimization + * because back in the day each GdkWindow was backed by + * an actual windowing system surface). + * + * In Ardour's case an EventBox is used in the Editor's top-level + * and child-widgets (e.g. Canvas::GtkCanvas never receive an unmap. + * + * However, when switching Tabbable pages, we do need to hide overlays + * such as ArdourCanvasOpenGLView + * + */ + void on_unmap () { + Gtk::EventBox::on_unmap(); + if (get_child ()) { + get_child()->unmap(); + } + } +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/pane.h b/libs/widgets/widgets/pane.h new file mode 100644 index 0000000000..bc971e6802 --- /dev/null +++ b/libs/widgets/widgets/pane.h @@ -0,0 +1,133 @@ +/* + Copyright (C) 2016 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_PANE_H_ +#define _WIDGETS_PANE_H_ + +#include <vector> +#include <algorithm> +#include <boost/shared_ptr.hpp> + +#include <stdint.h> + +#include <gdkmm/cursor.h> +#include <gtkmm/container.h> +#include <gtkmm/eventbox.h> + +#include "widgets/visibility.h" + +namespace Gtk { + class Widget; +} + +namespace ArdourWidgets { + +class LIBWIDGETS_API Pane : public Gtk::Container +{ +private: + class Divider; + +public: + struct Child + { + Pane* pane; + Gtk::Widget* w; + int32_t minsize; + sigc::connection show_con; + sigc::connection hide_con; + + Child (Pane* p, Gtk::Widget* widget, uint32_t ms) : pane (p), w (widget), minsize (ms) {} + }; + + typedef std::vector<boost::shared_ptr<Child> > Children; + + Pane (bool horizontal); + ~Pane(); + + void set_divider (std::vector<float>::size_type divider, float fract); + float get_divider (std::vector<float>::size_type divider = 0); + void set_child_minsize (Gtk::Widget const &, int32_t); + + GType child_type_vfunc() const; + void set_drag_cursor (Gdk::Cursor); + + void set_check_divider_position (bool); + +protected: + bool horizontal; + + void on_add (Gtk::Widget*); + void on_remove (Gtk::Widget*); + void on_size_request (GtkRequisition*); + void on_size_allocate (Gtk::Allocation&); + bool on_expose_event (GdkEventExpose*); + + bool handle_press_event (GdkEventButton*, Divider*); + bool handle_release_event (GdkEventButton*, Divider*); + bool handle_motion_event (GdkEventMotion*, Divider*); + bool handle_enter_event (GdkEventCrossing*, Divider*); + bool handle_leave_event (GdkEventCrossing*, Divider*); + + void forall_vfunc (gboolean include_internals, GtkCallback callback, gpointer callback_data); + +private: + Gdk::Cursor drag_cursor; + bool did_move; + + void reallocate (Gtk::Allocation const &); + + Children children; + + struct Divider : public Gtk::EventBox { + Divider (); + + float fract; + bool dragging; + + bool on_expose_event (GdkEventExpose* ev); + }; + + typedef std::list<Divider*> Dividers; + Dividers dividers; + int divider_width; + bool check_fract; + + void add_divider (); + void handle_child_visibility (); + float constrain_fract (Dividers::size_type, float fract); + + static void* notify_child_destroyed (void*); + void* child_destroyed (Gtk::Widget*); +}; + +class LIBWIDGETS_API HPane : public Pane +{ + public: + HPane () : Pane (true) {} +}; + +class LIBWIDGETS_API VPane : public Pane +{ + public: + VPane () : Pane (false) {} +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/paths_dialog.h b/libs/widgets/widgets/paths_dialog.h new file mode 100644 index 0000000000..8757c8b032 --- /dev/null +++ b/libs/widgets/widgets/paths_dialog.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2014 Robin Gareus <robin@gareus.org> + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +#ifndef _WIDGETS_PATHS_DIALOG_H_ +#define _WIDGETS_PATHS_DIALOG_H_ + +#include <string> +#include <vector> +#include <gtkmm.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API PathsDialog : public Gtk::Dialog +{ +public: + PathsDialog (Gtk::Window& parent, std::string, std::string current_paths = "", std::string default_paths = ""); + ~PathsDialog (); + + std::string get_serialized_paths (); + +private: + void on_show (); + + Gtk::ListViewText paths_list_view; + + Gtk::Button add_path_button; + Gtk::Button remove_path_button; + Gtk::Button set_default_button; + + void selection_changed(); + void add_path(); + void remove_path(); + void set_default(); + + std::string _default_paths; +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/popup.h b/libs/widgets/widgets/popup.h new file mode 100644 index 0000000000..769a1a56fd --- /dev/null +++ b/libs/widgets/widgets/popup.h @@ -0,0 +1,64 @@ +/* + Copyright (C) 1998-99 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_POPUP_H_ +#define _WIDGETS_POPUP_H_ + +#ifdef interface +#undef interface +#endif + +#include <string> +#include <gtkmm.h> + +#include <pbd/touchable.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API PopUp : public Gtk::Window, public Touchable +{ +public: + PopUp (Gtk::WindowPosition pos, unsigned int show_for_msecs = 0, + bool delete_on_hide = false); + virtual ~PopUp (); + void touch (); + void remove (); + void set_text (std::string); + void set_name (std::string); + gint button_click (GdkEventButton *); + + bool on_delete_event (GdkEventAny* ); + +protected: + void on_realize (); + +private: + Gtk::Label label; + std::string my_text; + gint timeout; + static gint remove_prompt_timeout (void *); + bool delete_on_hide; + unsigned int popdown_time; +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/prompter.h b/libs/widgets/widgets/prompter.h new file mode 100644 index 0000000000..064a9e4b29 --- /dev/null +++ b/libs/widgets/widgets/prompter.h @@ -0,0 +1,77 @@ +/* + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_PROMPTER_H_ +#define _WIDGETS_PROMPTER_H_ + +#include <string> +#include <gtkmm/box.h> +#include <gtkmm/entry.h> +#include <gtkmm/label.h> +#include <gtkmm/dialog.h> +#include <sigc++/sigc++.h> + +#include "widgets/visibility.h" + +namespace Gtk { + class Window; +} + +namespace ArdourWidgets { + +class LIBWIDGETS_API Prompter : public Gtk::Dialog +{ +public: + Prompter (bool modal = false); + Prompter (Gtk::Window& parent, bool modal = false); + ~Prompter () {}; + + void set_prompt (std::string prompt) { + entryLabel.set_label (prompt); + } + + void set_initial_text (std::string txt) { + entry.set_text (txt); + entry.select_region (0, entry.get_text_length()); + } + + void change_labels (std::string ok, std::string cancel); + + void get_result (std::string &str, bool strip=true); + +protected: + Gtk::Entry& the_entry() { return entry; } + + void on_entry_changed (); + void on_show (); + +private: + Gtk::Entry entry; + Gtk::HBox entryBox; + Gtk::Label entryLabel; + bool first_show; + bool can_accept_from_entry; + + void init (); + void entry_activated (); +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/widgets/scroomer.h b/libs/widgets/widgets/scroomer.h new file mode 100644 index 0000000000..c4c3cd3ade --- /dev/null +++ b/libs/widgets/widgets/scroomer.h @@ -0,0 +1,93 @@ +/* + Copyright (C) 2008 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_SCROOMER_H_ +#define _WIDGETS_SCROOMER_H_ + +#include <gdkmm.h> +#include <gtkmm/drawingarea.h> +#include <gtkmm/adjustment.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API Scroomer : public Gtk::DrawingArea +{ +public: + enum Component { + TopBase = 0, + Handle1 = 1, + Slider = 2, + Handle2 = 3, + BottomBase = 4, + Total = 5, + None = 6 + }; + + Scroomer(Gtk::Adjustment& adjustment); + ~Scroomer(); + + bool on_motion_notify_event (GdkEventMotion*); + bool on_button_press_event (GdkEventButton*); + bool on_button_release_event (GdkEventButton*); + bool on_scroll_event (GdkEventScroll*); + virtual void on_size_allocate (Gtk::Allocation&); + + void set_comp_rect(GdkRectangle&, Component) const; + + Component point_in(double point) const; + + void set_min_page_size(double page_size); + int get_handle_size() { return handle_size; } + + inline int position_of(Component comp) { return position[comp]; } + + sigc::signal0<void> DragStarting; + sigc::signal0<void> DragFinishing; + + sigc::signal0<void> DoubleClicked; + +protected: + Gtk::Adjustment& adj; + +private: + struct UpdateRect { + GdkRectangle rect; + Component first_comp; + }; + + void update(); + void adjustment_changed (); + + int position[6]; + int old_pos[6]; + int handle_size; + double min_page_size; + GdkWindow* grab_window; + Component grab_comp; + double grab_y; + double unzoomed_val; + double unzoomed_page; + bool pinch; +}; + +} /* end namespace */ + +#endif diff --git a/libs/widgets/widgets/slider_controller.h b/libs/widgets/widgets/slider_controller.h index e80e76d7bc..f8414ce4b4 100644 --- a/libs/widgets/widgets/slider_controller.h +++ b/libs/widgets/widgets/slider_controller.h @@ -26,10 +26,8 @@ #include <gtkmm.h> #include <boost/shared_ptr.hpp> -#include "gtkmm2ext/popup.h" -#include "gtkmm2ext/binding_proxy.h" - #include "widgets/ardour_fader.h" +#include "widgets/binding_proxy.h" #include "widgets/visibility.h" namespace PBD { diff --git a/libs/widgets/widgets/stateful_button.h b/libs/widgets/widgets/stateful_button.h new file mode 100644 index 0000000000..6bbbf6f7f0 --- /dev/null +++ b/libs/widgets/widgets/stateful_button.h @@ -0,0 +1,95 @@ +/* + Copyright (C) 2005 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_STATEFUL_BUTTON_H_ +#define _WIDGETS_STATEFUL_BUTTON_H_ + +#include <vector> + +#include <gtkmm/togglebutton.h> + +#include "widgets/visibility.h" + +namespace ArdourWidgets { + +class LIBWIDGETS_API StateButton +{ +public: + StateButton(); + virtual ~StateButton() {} + + void set_visual_state (int); + int get_visual_state () { return visual_state; } + void set_self_managed (bool yn) { _self_managed = yn; } + virtual void set_widget_name (const std::string& name) = 0; + +protected: + int visual_state; + bool _self_managed; + bool _is_realized; + bool style_changing; + Gtk::StateType state_before_prelight; + bool is_toggle; + + virtual std::string get_widget_name() const = 0; + virtual Gtk::Widget* get_child_widget () = 0; + + void avoid_prelight_on_style_changed (const Glib::RefPtr<Gtk::Style>& style, GtkWidget* widget); + void avoid_prelight_on_state_changed (Gtk::StateType old_state, GtkWidget* widget); +}; + + +class LIBWIDGETS_API StatefulToggleButton : public StateButton, public Gtk::ToggleButton +{ +public: + StatefulToggleButton(); + explicit StatefulToggleButton(const std::string &label); + ~StatefulToggleButton() {} + void set_widget_name (const std::string& name); + +protected: + void on_realize (); + void on_toggled (); + void on_style_changed (const Glib::RefPtr<Gtk::Style>& style); + void on_state_changed (Gtk::StateType old_state); + + Gtk::Widget* get_child_widget (); + std::string get_widget_name() const { return get_name(); } +}; + +class LIBWIDGETS_API StatefulButton : public StateButton, public Gtk::Button +{ +public: + StatefulButton(); + explicit StatefulButton(const std::string &label); + virtual ~StatefulButton() {} + void set_widget_name (const std::string& name); + +protected: + void on_realize (); + void on_style_changed (const Glib::RefPtr<Gtk::Style>& style); + void on_state_changed (Gtk::StateType old_state); + + Gtk::Widget* get_child_widget (); + std::string get_widget_name() const { return get_name(); } +}; + +} /* end namespace */ + +#endif diff --git a/libs/widgets/widgets/tabbable.h b/libs/widgets/widgets/tabbable.h new file mode 100644 index 0000000000..cb4cb1eaa9 --- /dev/null +++ b/libs/widgets/widgets/tabbable.h @@ -0,0 +1,102 @@ +/* + Copyright (C) 2015 Paul 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_TABBABLE_H_ +#define _WIDGETS_TABBABLE_H_ + +#include <gtkmm/bin.h> +#include <gtkmm/box.h> +#include <gtkmm/button.h> +#include <gtkmm/image.h> +#include <gtkmm/label.h> +#include <gtkmm/notebook.h> + +#include "gtkmm2ext/window_proxy.h" +#include "widgets/visibility.h" + +namespace Gtk { + class Window; + class Notebook; +} + +namespace Gtkmm2ext { + class VisibilityTracker; +} + +namespace ArdourWidgets { + +class LIBWIDGETS_API Tabbable : public Gtkmm2ext::WindowProxy +{ +public: + Tabbable (Gtk::Widget&, const std::string&, bool tabbed_by_default = true); + ~Tabbable (); + + void add_to_notebook (Gtk::Notebook& notebook, const std::string& tab_title); + void make_visible (); + void make_invisible (); + void change_visibility (); + void attach (); + void detach (); + + Gtk::Widget& contents() const { return _contents; } + + Gtk::Window* get (bool create = false); + Gtk::Window* own_window () { return get (false); } + virtual Gtk::Window* use_own_window (bool and_pack_it); + + void set_default_tabbed (bool yn); + + virtual void show_window (); + + bool window_visible () const; + bool tabbed() const; + bool tabbed_by_default () const; + + Gtk::Window* current_toplevel () const; + + Gtk::Notebook* tab_root_drop (); + + int set_state (const XMLNode&, int version); + XMLNode& get_state (); + + static std::string xml_node_name(); + + sigc::signal1<void,Tabbable&> StateChange; + +protected: + bool delete_event_handler (GdkEventAny *ev); + +private: + Gtk::Widget& _contents; + Gtk::Notebook _own_notebook; + Gtk::Notebook* _parent_notebook; + std::string _tab_title; + bool tab_requested_by_state; + + void show_tab (); + void hide_tab (); + bool tab_close_clicked (GdkEventButton*); + void show_own_window (bool and_pack_it); + void window_mapped (); + void window_unmapped (); +}; + +} /* end namespace */ + +#endif diff --git a/libs/widgets/widgets/tearoff.h b/libs/widgets/widgets/tearoff.h new file mode 100644 index 0000000000..6b4dc77634 --- /dev/null +++ b/libs/widgets/widgets/tearoff.h @@ -0,0 +1,92 @@ +/* + Copyright (C) 2003 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _WIDGETS_TEAROFF_H_ +#define _WIDGETS_TEAROFF_H_ + +#include <gtkmm/arrow.h> +#include <gtkmm/box.h> +#include <gtkmm/eventbox.h> +#include <gtkmm/window.h> + +#include "widgets/visibility.h" + +class XMLNode; + +namespace ArdourWidgets { + +class LIBWIDGETS_API TearOff : public Gtk::HBox +{ +public: + TearOff (Gtk::Widget& contents, bool allow_resize = false); + virtual ~TearOff (); + + void set_visible (bool yn, bool force = false); + void set_can_be_torn_off (bool); + bool can_be_torn_off () const { return _can_be_torn_off; } + bool visible () const { return _visible; } + + sigc::signal<void> Detach; + sigc::signal<void> Attach; + sigc::signal<void> Visible; + sigc::signal<void> Hidden; + + Gtk::Window& tearoff_window() { return own_window; } + bool torn_off() const; + void tear_it_off (); + void put_it_back (); + void hide_visible (); + + void set_state (const XMLNode&); + void add_state (XMLNode&) const; + +private: + Gtk::Widget& contents; + Gtk::Window own_window; + Gtk::Arrow tearoff_arrow; + Gtk::Arrow close_arrow; + Gtk::HBox window_box; + Gtk::EventBox tearoff_event_box; + Gtk::EventBox close_event_box; + double drag_x; + double drag_y; + bool dragging; + bool _visible; + bool _torn; + bool _can_be_torn_off; + int own_window_width; + int own_window_height; + int own_window_xpos; + int own_window_ypos; + + gint tearoff_click (GdkEventButton*); + gint close_click (GdkEventButton*); + + gint window_motion (GdkEventMotion*); + gint window_button_press (GdkEventButton*); + gint window_button_release (GdkEventButton*); + gint window_delete_event (GdkEventAny*); + + void own_window_realized (); + bool own_window_configured (GdkEventConfigure*); +}; + +} /* namespace */ + +#endif diff --git a/libs/widgets/wscript b/libs/widgets/wscript index dd2ee9587b..cf274e418c 100644 --- a/libs/widgets/wscript +++ b/libs/widgets/wscript @@ -30,16 +30,28 @@ widgets_sources = [ 'ardour_display.cc', 'ardour_dropdown.cc', 'ardour_fader.cc', + 'ardour_icon.cc', 'ardour_knob.cc', 'ardour_spacer.cc', 'ardour_spinner.cc', 'auto_spin.cc', 'barcontroller.cc', + 'binding_proxy.cc', + 'eventboxext.cc', + 'choice.cc', 'click_box.cc', 'fastmeter.cc', 'focus_entry.cc', + 'pane.cc', + 'paths_dialog.cc', + 'popup.cc', + 'prompter.cc', + 'scroomer.cc', 'searchbar.cc', 'slider_controller.cc', + 'stateful_button.cc', + 'tabbable.cc', + 'tearoff.cc', 'tooltips.cc', 'ui_config.cc', ] |