summaryrefslogtreecommitdiff
path: root/libs/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'libs/widgets')
-rw-r--r--libs/widgets/ardour_button.cc9
-rw-r--r--libs/widgets/ardour_icon.cc1121
-rw-r--r--libs/widgets/binding_proxy.cc112
-rw-r--r--libs/widgets/choice.cc71
-rw-r--r--libs/widgets/eventboxext.cc25
-rw-r--r--libs/widgets/pane.cc671
-rw-r--r--libs/widgets/paths_dialog.cc164
-rw-r--r--libs/widgets/popup.cc149
-rw-r--r--libs/widgets/prompter.cc138
-rw-r--r--libs/widgets/scroomer.cc408
-rw-r--r--libs/widgets/stateful_button.cc273
-rw-r--r--libs/widgets/tabbable.cc389
-rw-r--r--libs/widgets/tearoff.cc341
-rw-r--r--libs/widgets/widgets/ardour_button.h10
-rw-r--r--libs/widgets/widgets/ardour_icon.h50
-rw-r--r--libs/widgets/widgets/ardour_knob.h2
-rw-r--r--libs/widgets/widgets/barcontroller.h2
-rw-r--r--libs/widgets/widgets/binding_proxy.h68
-rw-r--r--libs/widgets/widgets/choice.h47
-rw-r--r--libs/widgets/widgets/click_box.h3
-rw-r--r--libs/widgets/widgets/eventboxext.h59
-rw-r--r--libs/widgets/widgets/pane.h133
-rw-r--r--libs/widgets/widgets/paths_dialog.h57
-rw-r--r--libs/widgets/widgets/popup.h64
-rw-r--r--libs/widgets/widgets/prompter.h77
-rw-r--r--libs/widgets/widgets/scroomer.h93
-rw-r--r--libs/widgets/widgets/slider_controller.h4
-rw-r--r--libs/widgets/widgets/stateful_button.h95
-rw-r--r--libs/widgets/widgets/tabbable.h102
-rw-r--r--libs/widgets/widgets/tearoff.h92
-rw-r--r--libs/widgets/wscript12
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 = &notebook;
+
+ 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',
]