summaryrefslogtreecommitdiff
path: root/libs/widgets
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2017-07-16 22:13:46 +0200
committerRobin Gareus <robin@gareus.org>2017-07-17 21:06:04 +0200
commitf6e182b937efda6ed0ba50dbc02af98524beb61c (patch)
treec5304cbfc96a82f00582ef038e2a9223881a7489 /libs/widgets
parentb6e4dfe37be32009ce7ffc58d4a6139923c12981 (diff)
Move Gtkmm2ext widgets into libwidget
Diffstat (limited to 'libs/widgets')
-rw-r--r--libs/widgets/ardour_fader.cc747
-rw-r--r--libs/widgets/auto_spin.cc298
-rw-r--r--libs/widgets/barcontroller.cc169
-rw-r--r--libs/widgets/click_box.cc188
-rw-r--r--libs/widgets/fastmeter.cc919
-rw-r--r--libs/widgets/focus_entry.cc49
-rw-r--r--libs/widgets/searchbar.cc92
-rw-r--r--libs/widgets/slider_controller.cc122
-rw-r--r--libs/widgets/widgets/ardour_fader.h158
-rw-r--r--libs/widgets/widgets/auto_spin.h76
-rw-r--r--libs/widgets/widgets/barcontroller.h83
-rw-r--r--libs/widgets/widgets/click_box.h81
-rw-r--r--libs/widgets/widgets/fastmeter.h177
-rw-r--r--libs/widgets/widgets/focus_entry.h43
-rw-r--r--libs/widgets/widgets/searchbar.h40
-rw-r--r--libs/widgets/widgets/slider_controller.h81
-rw-r--r--libs/widgets/wscript8
17 files changed, 3331 insertions, 0 deletions
diff --git a/libs/widgets/ardour_fader.cc b/libs/widgets/ardour_fader.cc
new file mode 100644
index 0000000000..e48c9f6ae5
--- /dev/null
+++ b/libs/widgets/ardour_fader.cc
@@ -0,0 +1,747 @@
+/*
+ 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.
+
+ $Id: fastmeter.h 570 2006-06-07 21:21:21Z sampo $
+*/
+
+
+#include <iostream>
+#include <assert.h>
+
+#include "pbd/stacktrace.h"
+
+#include "gtkmm2ext/cairo_widget.h"
+#include "gtkmm2ext/keyboard.h"
+#include "gtkmm2ext/utils.h"
+
+#include "widgets/ardour_fader.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
+
+#define CORNER_RADIUS 2.5
+#define CORNER_SIZE 2
+#define CORNER_OFFSET 1
+#define FADER_RESERVE 6
+
+std::list<ArdourFader::FaderImage*> ArdourFader::_patterns;
+
+ArdourFader::ArdourFader (Gtk::Adjustment& adj, int orientation, int fader_length, int fader_girth)
+ : _layout (0)
+ , _tweaks (Tweaks(0))
+ , _adjustment (adj)
+ , _text_width (0)
+ , _text_height (0)
+ , _span (fader_length)
+ , _girth (fader_girth)
+ , _min_span (fader_length)
+ , _min_girth (fader_girth)
+ , _orien (orientation)
+ , _pattern (0)
+ , _hovering (false)
+ , _dragging (false)
+ , _centered_text (true)
+ , _current_parent (0)
+{
+ _default_value = _adjustment.get_value();
+ update_unity_position ();
+
+ add_events (
+ Gdk::BUTTON_PRESS_MASK
+ | Gdk::BUTTON_RELEASE_MASK
+ | Gdk::POINTER_MOTION_MASK
+ | Gdk::SCROLL_MASK
+ | Gdk::ENTER_NOTIFY_MASK
+ | Gdk::LEAVE_NOTIFY_MASK
+ );
+
+ _adjustment.signal_value_changed().connect (mem_fun (*this, &ArdourFader::adjustment_changed));
+ _adjustment.signal_changed().connect (mem_fun (*this, &ArdourFader::adjustment_changed));
+ signal_grab_broken_event ().connect (mem_fun (*this, &ArdourFader::on_grab_broken_event));
+ if (_orien == VERT) {
+ CairoWidget::set_size_request(_girth, _span);
+ } else {
+ CairoWidget::set_size_request(_span, _girth);
+ }
+}
+
+ArdourFader::~ArdourFader ()
+{
+ if (_parent_style_change) _parent_style_change.disconnect();
+ if (_layout) _layout.clear (); // drop reference to existing layout
+}
+
+void
+ArdourFader::flush_pattern_cache () {
+ for (list<FaderImage*>::iterator f = _patterns.begin(); f != _patterns.end(); ++f) {
+ cairo_pattern_destroy ((*f)->pattern);
+ }
+ _patterns.clear();
+}
+
+
+cairo_pattern_t*
+ArdourFader::find_pattern (double afr, double afg, double afb,
+ double abr, double abg, double abb,
+ int w, int h)
+{
+ for (list<FaderImage*>::iterator f = _patterns.begin(); f != _patterns.end(); ++f) {
+ if ((*f)->matches (afr, afg, afb, abr, abg, abb, w, h)) {
+ return (*f)->pattern;
+ }
+ }
+ return 0;
+}
+
+void
+ArdourFader::create_patterns ()
+{
+ Gdk::Color c = get_style()->get_fg (get_state());
+ float fr, fg, fb;
+ float br, bg, bb;
+
+ fr = c.get_red_p ();
+ fg = c.get_green_p ();
+ fb = c.get_blue_p ();
+
+ c = get_style()->get_bg (get_state());
+
+ br = c.get_red_p ();
+ bg = c.get_green_p ();
+ bb = c.get_blue_p ();
+
+ cairo_surface_t* surface;
+ cairo_t* tc = 0;
+
+ if (get_width() <= 1 || get_height() <= 1) {
+ return;
+ }
+
+ if ((_pattern = find_pattern (fr, fg, fb, br, bg, bb, get_width(), get_height())) != 0) {
+ /* found it - use it */
+ return;
+ }
+
+ if (_orien == VERT) {
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width(), get_height() * 2.0);
+ tc = cairo_create (surface);
+
+ /* paint background + border */
+
+ cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, get_width(), 0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.4,bg*0.4,bb*0.4, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.25, br*0.6,bg*0.6,bb*0.6, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.8,bg*0.8,bb*0.8, 1.0);
+ cairo_set_source (tc, shade_pattern);
+ cairo_rectangle (tc, 0, 0, get_width(), get_height() * 2.0);
+ cairo_fill (tc);
+
+ cairo_pattern_destroy (shade_pattern);
+
+ /* paint lower shade */
+
+ shade_pattern = cairo_pattern_create_linear (0.0, 0.0, get_width() - 2 - CORNER_OFFSET , 0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
+ cairo_set_source (tc, shade_pattern);
+ Gtkmm2ext::rounded_top_half_rectangle (tc, CORNER_OFFSET, get_height() + CORNER_OFFSET,
+ get_width() - CORNER_SIZE, get_height(), CORNER_RADIUS);
+ cairo_fill (tc);
+
+ cairo_pattern_destroy (shade_pattern);
+
+ _pattern = cairo_pattern_create_for_surface (surface);
+
+ } else {
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width() * 2.0, get_height());
+ tc = cairo_create (surface);
+
+ /* paint right shade (background section)*/
+
+ cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.4,bg*0.4,bb*0.4, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.25, br*0.6,bg*0.6,bb*0.6, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.8,bg*0.8,bb*0.8, 1.0);
+ cairo_set_source (tc, shade_pattern);
+ cairo_rectangle (tc, 0, 0, get_width() * 2.0, get_height());
+ cairo_fill (tc);
+
+ /* paint left shade (active section/foreground) */
+
+ shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
+ cairo_set_source (tc, shade_pattern);
+ Gtkmm2ext::rounded_right_half_rectangle (tc, CORNER_OFFSET, CORNER_OFFSET,
+ get_width() - CORNER_OFFSET, get_height() - CORNER_SIZE, CORNER_RADIUS);
+ cairo_fill (tc);
+ cairo_pattern_destroy (shade_pattern);
+
+ _pattern = cairo_pattern_create_for_surface (surface);
+ }
+
+ /* cache it for others to use */
+
+ _patterns.push_back (new FaderImage (_pattern, fr, fg, fb, br, bg, bb, get_width(), get_height()));
+
+ cairo_destroy (tc);
+ cairo_surface_destroy (surface);
+}
+
+void
+ArdourFader::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t* area)
+{
+ cairo_t* cr = ctx->cobj();
+
+ if (!_pattern) {
+ create_patterns();
+ }
+
+ if (!_pattern) {
+ /* this isn't supposed to be happen, but some wackiness whereby
+ * the pixfader ends up with a 1xN or Nx1 size allocation
+ * leads to it. the basic wackiness needs fixing but we
+ * shouldn't crash. just fill in the expose area with
+ * our bg color.
+ */
+
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_bg (get_state()), 1);
+ cairo_rectangle (cr, area->x, area->y, area->width, area->height);
+ cairo_fill (cr);
+ return;
+ }
+
+ OnExpose();
+ int ds = display_span ();
+ const float w = get_width();
+ const float h = get_height();
+
+ CairoWidget::set_source_rgb_a (cr, get_parent_bg(), 1);
+ cairo_rectangle (cr, 0, 0, w, h);
+ cairo_fill(cr);
+
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
+
+ cairo_matrix_t matrix;
+ Gtkmm2ext::rounded_rectangle (cr, CORNER_OFFSET, CORNER_OFFSET, w-CORNER_SIZE, h-CORNER_SIZE, CORNER_RADIUS);
+ // we use a 'trick' here: The stoke is off by .5px but filling the interior area
+ // after a stroke of 2px width results in an outline of 1px
+ cairo_stroke_preserve(cr);
+
+ if (_orien == VERT) {
+
+ if (ds > h - FADER_RESERVE - CORNER_OFFSET) {
+ ds = h - FADER_RESERVE - CORNER_OFFSET;
+ }
+
+ if (!CairoWidget::flat_buttons() ) {
+ cairo_set_source (cr, _pattern);
+ cairo_matrix_init_translate (&matrix, 0, (h - ds));
+ cairo_pattern_set_matrix (_pattern, &matrix);
+ } else {
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_bg (get_state()), 1);
+ cairo_fill (cr);
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_fg (get_state()), 1);
+ Gtkmm2ext::rounded_rectangle (cr, CORNER_OFFSET, ds + CORNER_OFFSET,
+ w - CORNER_SIZE, h - ds - CORNER_SIZE, CORNER_RADIUS);
+ }
+ cairo_fill (cr);
+
+ } else {
+
+ if (ds < FADER_RESERVE) {
+ ds = FADER_RESERVE;
+ }
+ assert(ds <= w);
+
+ /*
+ * if ds == w, the pattern does not need to be translated
+ * if ds == 0 (or FADER_RESERVE), the pattern needs to be moved
+ * w to the left, which is -w in pattern space, and w in user space
+ * if ds == 10, then the pattern needs to be moved w - 10
+ * to the left, which is -(w-10) in pattern space, which
+ * is (w - 10) in user space
+ * thus: translation = (w - ds)
+ */
+
+ if (!CairoWidget::flat_buttons() ) {
+ cairo_set_source (cr, _pattern);
+ cairo_matrix_init_translate (&matrix, w - ds, 0);
+ cairo_pattern_set_matrix (_pattern, &matrix);
+ } else {
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_bg (get_state()), 1);
+ cairo_fill (cr);
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_fg (get_state()), 1);
+ Gtkmm2ext::rounded_rectangle (cr, CORNER_OFFSET, CORNER_OFFSET,
+ ds - CORNER_SIZE, h - CORNER_SIZE, CORNER_RADIUS);
+ }
+ cairo_fill (cr);
+ }
+
+ /* draw the unity-position line if it's not at either end*/
+ if (!(_tweaks & NoShowUnityLine) && _unity_loc > CORNER_RADIUS) {
+ cairo_set_line_width(cr, 1);
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ Gdk::Color c = get_style()->get_fg (Gtk::STATE_ACTIVE);
+ cairo_set_source_rgba (cr, c.get_red_p() * 1.5, c.get_green_p() * 1.5, c.get_blue_p() * 1.5, 0.85);
+ if (_orien == VERT) {
+ if (_unity_loc < h - CORNER_RADIUS) {
+ cairo_move_to (cr, 1.5, _unity_loc + CORNER_OFFSET + .5);
+ cairo_line_to (cr, _girth - 1.5, _unity_loc + CORNER_OFFSET + .5);
+ cairo_stroke (cr);
+ }
+ } else {
+ if (_unity_loc < w - CORNER_RADIUS) {
+ cairo_move_to (cr, _unity_loc - CORNER_OFFSET + .5, 1.5);
+ cairo_line_to (cr, _unity_loc - CORNER_OFFSET + .5, _girth - 1.5);
+ cairo_stroke (cr);
+ }
+ }
+ }
+
+ if (_layout && !_text.empty() && _orien == HORIZ) {
+ cairo_save (cr);
+ if (_centered_text) {
+ /* center text */
+ cairo_move_to (cr, (w - _text_width)/2.0, h/2.0 - _text_height/2.0);
+ } else if (ds > .5 * w) {
+ cairo_move_to (cr, CORNER_OFFSET + 3, h/2.0 - _text_height/2.0);
+ cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
+ } else {
+ cairo_move_to (cr, w - _text_width - CORNER_OFFSET - 3, h/2.0 - _text_height/2.0);
+ }
+ CairoWidget::set_source_rgb_a (cr, get_style()->get_text (get_state()), 1);
+ pango_cairo_show_layout (cr, _layout->gobj());
+ cairo_restore (cr);
+ }
+
+ if (!get_sensitive()) {
+ Gtkmm2ext::rounded_rectangle (cr, CORNER_OFFSET, CORNER_OFFSET, w-CORNER_SIZE, h-CORNER_SIZE, CORNER_RADIUS);
+ cairo_set_source_rgba (cr, 0.505, 0.517, 0.525, 0.4);
+ cairo_fill (cr);
+ } else if (_hovering && CairoWidget::widget_prelight()) {
+ Gtkmm2ext::rounded_rectangle (cr, CORNER_OFFSET, CORNER_OFFSET, w-CORNER_SIZE, h-CORNER_SIZE, CORNER_RADIUS);
+ cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
+ cairo_fill (cr);
+ }
+}
+
+void
+ArdourFader::on_size_request (GtkRequisition* req)
+{
+ if (_orien == VERT) {
+ req->width = (_min_girth ? _min_girth : -1);
+ req->height = (_min_span ? _min_span : -1);
+ } else {
+ req->height = (_min_girth ? _min_girth : -1);
+ req->width = (_min_span ? _min_span : -1);
+ }
+}
+
+void
+ArdourFader::on_size_allocate (Gtk::Allocation& alloc)
+{
+ int old_girth = _girth;
+ int old_span = _span;
+
+ CairoWidget::on_size_allocate(alloc);
+
+ if (_orien == VERT) {
+ _girth = alloc.get_width ();
+ _span = alloc.get_height ();
+ } else {
+ _girth = alloc.get_height ();
+ _span = alloc.get_width ();
+ }
+
+ if (is_realized() && ((old_girth != _girth) || (old_span != _span))) {
+ /* recreate patterns in case we've changed size */
+ create_patterns ();
+ }
+
+ update_unity_position ();
+}
+
+bool
+ArdourFader::on_grab_broken_event (GdkEventGrabBroken* ev)
+{
+ if (_dragging) {
+ remove_modal_grab();
+ _dragging = false;
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ StopGesture ();
+ }
+ return (_tweaks & NoButtonForward) ? true : false;
+}
+
+bool
+ArdourFader::on_button_press_event (GdkEventButton* ev)
+{
+ if (ev->type != GDK_BUTTON_PRESS) {
+ if (_dragging) {
+ remove_modal_grab();
+ _dragging = false;
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ StopGesture ();
+ }
+ return (_tweaks & NoButtonForward) ? true : false;
+ }
+
+ if (ev->button != 1 && ev->button != 2) {
+ return false;
+ }
+
+ add_modal_grab ();
+ StartGesture ();
+ _grab_loc = (_orien == VERT) ? ev->y : ev->x;
+ _grab_start = (_orien == VERT) ? ev->y : ev->x;
+ _grab_window = ev->window;
+ _dragging = true;
+ gdk_pointer_grab(ev->window,false,
+ GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
+ NULL,NULL,ev->time);
+
+ if (ev->button == 2) {
+ set_adjustment_from_event (ev);
+ }
+
+ return (_tweaks & NoButtonForward) ? true : false;
+}
+
+bool
+ArdourFader::on_button_release_event (GdkEventButton* ev)
+{
+ double ev_pos = (_orien == VERT) ? ev->y : ev->x;
+
+ switch (ev->button) {
+ case 1:
+ if (_dragging) {
+ remove_modal_grab();
+ _dragging = false;
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ StopGesture ();
+
+ if (!_hovering) {
+ if (!(_tweaks & NoVerticalScroll)) {
+ Keyboard::magic_widget_drop_focus();
+ }
+ queue_draw ();
+ }
+
+ if (ev_pos == _grab_start) {
+ /* no motion - just a click */
+ ev_pos = rint(ev_pos);
+
+ if (ev->state & Keyboard::TertiaryModifier) {
+ _adjustment.set_value (_default_value);
+ } else if (ev->state & Keyboard::GainFineScaleModifier) {
+ _adjustment.set_value (_adjustment.get_lower());
+#if 0 // ignore clicks
+ } else if (ev_pos == slider_pos) {
+ ; // click on current position, no move.
+ } else if ((_orien == VERT && ev_pos < slider_pos) || (_orien == HORIZ && ev_pos > slider_pos)) {
+ /* above the current display height, remember X Window coords */
+ _adjustment.set_value (_adjustment.get_value() + _adjustment.get_step_increment());
+ } else {
+ _adjustment.set_value (_adjustment.get_value() - _adjustment.get_step_increment());
+#endif
+ }
+ }
+ return true;
+ }
+ break;
+
+ case 2:
+ if (_dragging) {
+ remove_modal_grab();
+ _dragging = false;
+ StopGesture ();
+ set_adjustment_from_event (ev);
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+bool
+ArdourFader::on_scroll_event (GdkEventScroll* ev)
+{
+ double scale;
+ bool ret = false;
+
+ if (ev->state & Keyboard::GainFineScaleModifier) {
+ if (ev->state & Keyboard::GainExtraFineScaleModifier) {
+ scale = 0.005;
+ } else {
+ scale = 0.1;
+ }
+ } else {
+ scale = 1.0;
+ }
+
+ if (_orien == VERT) {
+ switch (ev->direction) {
+ case GDK_SCROLL_UP:
+ _adjustment.set_value (_adjustment.get_value() + (_adjustment.get_page_increment() * scale));
+ ret = true;
+ break;
+ case GDK_SCROLL_DOWN:
+ _adjustment.set_value (_adjustment.get_value() - (_adjustment.get_page_increment() * scale));
+ ret = true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ int dir = ev->direction;
+
+ if (ev->state & Keyboard::ScrollHorizontalModifier || !(_tweaks & NoVerticalScroll)) {
+ if (ev->direction == GDK_SCROLL_UP) dir = GDK_SCROLL_RIGHT;
+ if (ev->direction == GDK_SCROLL_DOWN) dir = GDK_SCROLL_LEFT;
+ }
+
+ switch (dir) {
+ case GDK_SCROLL_RIGHT:
+ _adjustment.set_value (_adjustment.get_value() + (_adjustment.get_page_increment() * scale));
+ ret = true;
+ break;
+ case GDK_SCROLL_LEFT:
+ _adjustment.set_value (_adjustment.get_value() - (_adjustment.get_page_increment() * scale));
+ ret = true;
+ break;
+ default:
+ break;
+ }
+ }
+ return ret;
+}
+
+bool
+ArdourFader::on_motion_notify_event (GdkEventMotion* ev)
+{
+ if (_dragging) {
+ double scale = 1.0;
+ double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
+
+ if (ev->window != _grab_window) {
+ _grab_loc = ev_pos;
+ _grab_window = ev->window;
+ return true;
+ }
+
+ if (ev->state & Keyboard::GainFineScaleModifier) {
+ if (ev->state & Keyboard::GainExtraFineScaleModifier) {
+ scale = 0.005;
+ } else {
+ scale = 0.1;
+ }
+ }
+
+ double const delta = ev_pos - _grab_loc;
+ _grab_loc = ev_pos;
+
+ const double off = FADER_RESERVE + ((_orien == VERT) ? CORNER_OFFSET : 0);
+ const double span = _span - off;
+ double fract = (delta / span);
+
+ fract = min (1.0, fract);
+ fract = max (-1.0, fract);
+
+ // X Window is top->bottom for 0..Y
+
+ if (_orien == VERT) {
+ fract = -fract;
+ }
+
+ _adjustment.set_value (_adjustment.get_value() + scale * fract * (_adjustment.get_upper() - _adjustment.get_lower()));
+ }
+
+ return true;
+}
+
+void
+ArdourFader::adjustment_changed ()
+{
+ queue_draw ();
+}
+
+/** @return pixel offset of the current value from the right or bottom of the fader */
+int
+ArdourFader::display_span ()
+{
+ float fract = (_adjustment.get_value () - _adjustment.get_lower()) / ((_adjustment.get_upper() - _adjustment.get_lower()));
+ int ds;
+ if (_orien == VERT) {
+ const double off = FADER_RESERVE + CORNER_OFFSET;
+ const double span = _span - off;
+ ds = (int)rint (span * (1.0 - fract));
+ } else {
+ const double off = FADER_RESERVE;
+ const double span = _span - off;
+ ds = (int)rint (span * fract + off);
+ }
+
+ return ds;
+}
+
+void
+ArdourFader::update_unity_position ()
+{
+ if (_orien == VERT) {
+ const double span = _span - FADER_RESERVE - CORNER_OFFSET;
+ _unity_loc = (int) rint (span * (1 - ((_default_value - _adjustment.get_lower()) / (_adjustment.get_upper() - _adjustment.get_lower())))) - 1;
+ } else {
+ const double span = _span - FADER_RESERVE;
+ _unity_loc = (int) rint (FADER_RESERVE + (_default_value - _adjustment.get_lower()) * span / (_adjustment.get_upper() - _adjustment.get_lower()));
+ }
+
+ queue_draw ();
+}
+
+bool
+ArdourFader::on_enter_notify_event (GdkEventCrossing*)
+{
+ _hovering = true;
+ if (!(_tweaks & NoVerticalScroll)) {
+ Keyboard::magic_widget_grab_focus ();
+ }
+ queue_draw ();
+ return false;
+}
+
+bool
+ArdourFader::on_leave_notify_event (GdkEventCrossing*)
+{
+ if (!_dragging) {
+ _hovering = false;
+ if (!(_tweaks & NoVerticalScroll)) {
+ Keyboard::magic_widget_drop_focus();
+ }
+ queue_draw ();
+ }
+ return false;
+}
+
+void
+ArdourFader::set_adjustment_from_event (GdkEventButton* ev)
+{
+ const double off = FADER_RESERVE + ((_orien == VERT) ? CORNER_OFFSET : 0);
+ const double span = _span - off;
+ double fract = (_orien == VERT) ? (1.0 - ((ev->y - off) / span)) : ((ev->x - off) / span);
+
+ fract = min (1.0, fract);
+ fract = max (0.0, fract);
+
+ _adjustment.set_value (fract * (_adjustment.get_upper () - _adjustment.get_lower ()));
+}
+
+void
+ArdourFader::set_default_value (float d)
+{
+ _default_value = d;
+ update_unity_position ();
+}
+
+void
+ArdourFader::set_tweaks (Tweaks t)
+{
+ bool need_redraw = false;
+ if ((_tweaks & NoShowUnityLine) ^ (t & NoShowUnityLine)) {
+ need_redraw = true;
+ }
+ _tweaks = t;
+ if (need_redraw) {
+ queue_draw();
+ }
+}
+
+void
+ArdourFader::set_text (const std::string& str, bool centered, bool expose)
+{
+ if (_layout && _text == str) {
+ return;
+ }
+ if (!_layout && !str.empty()) {
+ _layout = Pango::Layout::create (get_pango_context());
+ }
+
+ _text = str;
+ _centered_text = centered;
+ if (_layout) {
+ _layout->set_text (str);
+ _layout->get_pixel_size (_text_width, _text_height);
+ // queue_resize ();
+ if (expose) queue_draw ();
+ }
+}
+
+void
+ArdourFader::on_state_changed (Gtk::StateType old_state)
+{
+ Widget::on_state_changed (old_state);
+ create_patterns ();
+ queue_draw ();
+}
+
+void
+ArdourFader::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
+{
+ if (_layout) {
+ std::string txt = _layout->get_text();
+ _layout.clear (); // drop reference to existing layout
+ _text = "";
+ set_text (txt, _centered_text, false);
+ }
+ /* patterns are cached and re-created as needed
+ * during 'expose' in the GUI thread */
+ _pattern = 0;
+ queue_draw ();
+}
+
+Gdk::Color
+ArdourFader::get_parent_bg ()
+{
+ Widget* parent = get_parent ();
+
+ while (parent) {
+ if (parent->get_has_window()) {
+ break;
+ }
+ parent = parent->get_parent();
+ }
+
+ if (parent && parent->get_has_window()) {
+ if (_current_parent != parent) {
+ if (_parent_style_change) _parent_style_change.disconnect();
+ _current_parent = parent;
+ _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &ArdourFader::on_style_changed));
+ }
+ return parent->get_style ()->get_bg (parent->get_state());
+ }
+
+ return get_style ()->get_bg (get_state());
+}
diff --git a/libs/widgets/auto_spin.cc b/libs/widgets/auto_spin.cc
new file mode 100644
index 0000000000..9d86eb50ac
--- /dev/null
+++ b/libs/widgets/auto_spin.cc
@@ -0,0 +1,298 @@
+/*
+ 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 <cmath>
+#include "gtkmm2ext/keyboard.h"
+#include "widgets/auto_spin.h"
+
+using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
+using namespace std;
+
+#define upper adjustment.get_upper()
+#define lower adjustment.get_lower()
+#define step_increment adjustment.get_step_increment()
+#define page_increment adjustment.get_page_increment()
+
+const unsigned int AutoSpin::initial_timer_interval = 500; /* msecs */
+const unsigned int AutoSpin::timer_interval = 20; /* msecs */
+const unsigned int AutoSpin::climb_timer_calls = 5; /* between climbing */
+
+AutoSpin::AutoSpin (Gtk::Adjustment &adjr, gfloat cr, bool round_to_steps_yn)
+ : adjustment (adjr),
+ climb_rate (cr)
+
+{
+ initial = adjustment.get_value ();
+ left_is_decrement = true;
+ wrap = false;
+ have_timer = false;
+ need_timer = false;
+ timer_calls = 0;
+ round_to_steps = round_to_steps_yn;
+}
+
+void
+AutoSpin::stop_timer ()
+{
+ if (have_timer) {
+ g_source_remove (timeout_tag);
+ have_timer = false;
+ }
+}
+
+gint
+AutoSpin::stop_spinning (GdkEventButton */*ev*/)
+{
+ need_timer = false;
+ stop_timer ();
+ return FALSE;
+}
+
+gint
+AutoSpin::button_press (GdkEventButton *ev)
+{
+ bool shifted = false;
+ bool control = false;
+ bool with_decrement = false;
+
+ stop_spinning (0);
+
+ if (ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS ) {
+ return true;
+ }
+
+ if (ev->state & Keyboard::TertiaryModifier) {
+ /* use page shift */
+
+ shifted = true;
+ }
+
+ if (ev->state & Keyboard::PrimaryModifier) {
+ /* go to upper/lower bound on button1/button2 */
+
+ control = true;
+ }
+
+ /* XXX should figure out which button is left/right */
+
+ switch (ev->button) {
+ case 1:
+ if (control) {
+ set_value (left_is_decrement ? lower : upper);
+ return TRUE;
+ } else {
+ if (left_is_decrement) {
+ with_decrement = true;
+ } else {
+ with_decrement = false;
+ }
+ }
+ break;
+
+ case 2:
+ if (!control) {
+ set_value (initial);
+ }
+ return TRUE;
+ break;
+
+ case 3:
+ if (control) {
+ set_value (left_is_decrement ? upper : lower);
+ return TRUE;
+ }
+ break;
+
+ case 4:
+ if (!control) {
+ adjust_value (shifted ? page_increment : step_increment);
+ } else {
+ set_value (upper);
+ }
+ return TRUE;
+ break;
+
+ case 5:
+ if (!control) {
+ adjust_value (shifted ? -page_increment : -step_increment);
+ } else {
+ set_value (lower);
+ }
+ return TRUE;
+ break;
+ }
+
+ start_spinning (with_decrement, shifted);
+ return TRUE;
+}
+
+gint
+AutoSpin::scroll_event (GdkEventScroll *ev)
+{
+ stop_spinning (0);
+
+ gfloat increment = step_increment;
+
+ if (ev->state & Keyboard::TertiaryModifier) {
+ increment = page_increment;
+ }
+
+ switch (ev->direction) {
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_LEFT:
+ adjust_value (-increment);
+ break;
+ case GDK_SCROLL_RIGHT:
+ case GDK_SCROLL_UP:
+ adjust_value (increment);
+ break;
+ }
+ return TRUE;
+}
+
+void
+AutoSpin::start_spinning (bool decrement, bool page)
+{
+ timer_increment = page ? page_increment : step_increment;
+
+ if (decrement) {
+ timer_increment = -timer_increment;
+ }
+
+ adjust_value (timer_increment);
+
+ have_timer = true;
+ timer_calls = 0;
+ timeout_tag = g_timeout_add (initial_timer_interval,
+ AutoSpin::_timer,
+ this);
+}
+
+gint
+AutoSpin::_timer (void *arg)
+{
+ return ((AutoSpin *) arg)->timer ();
+}
+
+void
+AutoSpin::set_value (gfloat value)
+{
+ if (round_to_steps)
+ adjustment.set_value (floor((value / step_increment) + 0.5f) * step_increment);
+ else
+ adjustment.set_value (value);
+}
+
+bool
+AutoSpin::adjust_value (gfloat increment)
+{
+ gfloat val;
+ bool done = false;
+
+ val = adjustment.get_value ();
+
+ val += increment;
+
+ if (val > upper) {
+ if (wrap) {
+ val = lower;
+ } else {
+ val = upper;
+ done = true;
+ }
+ } else if (val < lower) {
+ if (wrap) {
+ val = upper;
+ } else {
+ val = lower;
+ done = true;
+ }
+ }
+
+ set_value (val);
+ return done;
+}
+
+gint
+AutoSpin::timer ()
+{
+ bool done;
+ int retval = FALSE;
+
+ done = adjust_value (timer_increment);
+
+ if (need_timer) {
+
+ /* we're in the initial call, which happened
+ after initial_timer_interval msecs. Now
+ request a much more frequent update.
+ */
+
+ timeout_tag = g_timeout_add (timer_interval,
+ _timer,
+ this);
+ have_timer = true;
+ need_timer = false;
+
+ /* cancel this initial timeout */
+
+ retval = FALSE;
+
+ } else {
+ /* this is the regular "fast" call after each
+ timer_interval msecs.
+ */
+
+ if (timer_calls < climb_timer_calls) {
+ timer_calls++;
+ } else {
+ if (climb_rate > 0.0) {
+ if (timer_increment > 0) {
+ timer_increment += climb_rate;
+ } else {
+ timer_increment -= climb_rate;
+ }
+ }
+ timer_calls = 0;
+ }
+
+ if (!done) {
+ retval = TRUE;
+ }
+ }
+
+ return retval;
+}
+
+void
+AutoSpin::set_bounds (gfloat init, gfloat up, gfloat down, bool with_reset)
+{
+ adjustment.set_upper (up);
+ adjustment.set_lower (down);
+
+ initial = init;
+
+ adjustment.changed ();
+
+ if (with_reset) {
+ adjustment.set_value (init);
+ }
+}
diff --git a/libs/widgets/barcontroller.cc b/libs/widgets/barcontroller.cc
new file mode 100644
index 0000000000..896652c600
--- /dev/null
+++ b/libs/widgets/barcontroller.cc
@@ -0,0 +1,169 @@
+/*
+ Copyright (C) 2004 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 <sstream>
+#include <climits>
+#include <cstdio>
+#include <cmath>
+#include <algorithm>
+
+#include <pbd/controllable.h>
+
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/keyboard.h"
+#include "gtkmm2ext/cairo_widget.h"
+
+#include "widgets/barcontroller.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
+
+BarController::BarController (Gtk::Adjustment& adj,
+ boost::shared_ptr<PBD::Controllable> mc)
+ : _slider (&adj, mc, 60, 16)
+ , _switching (false)
+ , _switch_on_release (false)
+{
+
+ add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
+ set (.5, .5, 1.0, 1.0);
+ set_border_width (0);
+ _slider.set_tweaks (ArdourFader::NoShowUnityLine);
+
+ _slider.StartGesture.connect (sigc::mem_fun(*this, &BarController::passtrhu_gesture_start));
+ _slider.StopGesture.connect (sigc::mem_fun(*this, &BarController::passtrhu_gesture_stop));
+ _slider.OnExpose.connect (sigc::mem_fun(*this, &BarController::before_expose));
+ _slider.set_name (get_name());
+
+ Gtk::SpinButton& spinner = _slider.get_spin_button();
+ spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
+ spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
+ spinner.set_digits (9);
+ spinner.set_numeric (true);
+ spinner.set_name ("BarControlSpinner");
+ add (_slider);
+ show_all ();
+}
+
+BarController::~BarController ()
+{
+}
+
+bool
+BarController::on_button_press_event (GdkEventButton* ev)
+{
+ if (get_child() != &_slider) {
+ return false;
+ }
+ if (ev->button == 1 && ev->type == GDK_2BUTTON_PRESS) {
+ _switch_on_release = true;
+ return true;
+ } else {
+ _switch_on_release = false;
+ }
+ return false;
+}
+
+bool
+BarController::on_button_release_event (GdkEventButton* ev)
+{
+ if (get_child() != &_slider) {
+ return false;
+ }
+ if (ev->button == 1 && _switch_on_release) {
+ Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
+ return true;
+ }
+ return false;
+}
+
+void
+BarController::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
+{
+ _slider.set_name (get_name());
+}
+
+gint
+BarController::switch_to_bar ()
+{
+ if (_switching || get_child() == &_slider) {
+ return FALSE;
+ }
+ _switching = true;
+ remove ();
+ add (_slider);
+ _slider.show ();
+ _slider.queue_draw ();
+ _switching = false;
+ SpinnerActive (false); /* EMIT SIGNAL */
+ return FALSE;
+}
+
+gint
+BarController::switch_to_spinner ()
+{
+ if (_switching || get_child() != &_slider) {
+ return FALSE;
+ }
+
+ _switching = true;
+ Gtk::SpinButton& spinner = _slider.get_spin_button();
+ if (spinner.get_parent()) {
+ spinner.get_parent()->remove(spinner);
+ }
+ remove ();
+ add (spinner);
+ spinner.show ();
+ spinner.select_region (0, spinner.get_text_length());
+ spinner.grab_focus ();
+ _switching = false;
+ SpinnerActive (true); /* EMIT SIGNAL */
+ return FALSE;
+}
+
+void
+BarController::entry_activated ()
+{
+ switch_to_bar ();
+}
+
+bool
+BarController::entry_focus_out (GdkEventFocus* /*ev*/)
+{
+ entry_activated ();
+ return true;
+}
+
+void
+BarController::before_expose ()
+{
+ double xpos = -1;
+ _slider.set_text (get_label (xpos), false, false);
+}
+
+void
+BarController::set_sensitive (bool yn)
+{
+ Alignment::set_sensitive (yn);
+ _slider.set_sensitive (yn);
+}
diff --git a/libs/widgets/click_box.cc b/libs/widgets/click_box.cc
new file mode 100644
index 0000000000..b3271a0cc4
--- /dev/null
+++ b/libs/widgets/click_box.cc
@@ -0,0 +1,188 @@
+/*
+ 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 <iostream>
+#include <cstdio> /* for sprintf, sigh ... */
+
+#include "pbd/controllable.h"
+#include "gtkmm2ext/utils.h"
+#include "widgets/click_box.h"
+
+using namespace std;
+using namespace Gtk;
+using namespace ArdourWidgets;
+using namespace sigc;
+
+ClickBox::ClickBox (Gtk::Adjustment *adjp, const string &name, bool round_to_steps)
+ : AutoSpin (*adjp,0,round_to_steps)
+{
+ layout = create_pango_layout ("");
+ twidth = 0;
+ theight = 0;
+
+
+ add_events (Gdk::BUTTON_RELEASE_MASK|
+ Gdk::BUTTON_PRESS_MASK|
+ Gdk::ENTER_NOTIFY_MASK|
+ Gdk::LEAVE_NOTIFY_MASK);
+
+ get_adjustment().signal_value_changed().connect (mem_fun (*this, &ClickBox::set_label));
+ signal_style_changed().connect (mem_fun (*this, &ClickBox::style_changed));
+ signal_button_press_event().connect (mem_fun (*this, &ClickBox::button_press_handler));
+ signal_button_release_event().connect (mem_fun (*this, &ClickBox::button_release_handler));
+ set_name (name);
+ set_label ();
+}
+
+ClickBox::~ClickBox ()
+{
+}
+
+bool
+ClickBox::button_press_handler (GdkEventButton* ev)
+{
+ if (_binding_proxy.button_press_handler (ev)) {
+ return true;
+ }
+ add_modal_grab();
+ AutoSpin::button_press (ev);
+ return true;
+}
+
+bool
+ClickBox::on_scroll_event (GdkEventScroll* ev)
+{
+ AutoSpin::scroll_event (ev);
+ return true;
+}
+
+bool
+ClickBox::button_release_handler (GdkEventButton* ev)
+{
+ switch (ev->button) {
+ case 1:
+ case 2:
+ case 3:
+ stop_spinning (0);
+ default:
+ remove_modal_grab();
+ break;
+ }
+ return true;
+}
+
+void
+ClickBox::set_label ()
+{
+ char buf[32];
+ int width, height;
+
+ bool const h = _printer (buf, get_adjustment());
+ if (!h) {
+ /* the printer didn't handle it, so use a default */
+ sprintf (buf, "%.2f", get_adjustment().get_value ());
+ }
+
+ layout->set_text (buf);
+ layout->get_pixel_size (width, height);
+
+ if (twidth < width && (width > 50)) {
+ /* override GenericPluginUI::build_control_ui()
+ * Gtkmm2ext::set_size_request_to_display_given_text ("g9999999")
+ * see http://tracker.ardour.org/view.php?id=6499
+ */
+ set_size_request (std::min (300, width + 6), height + 4);
+ }
+
+ twidth = width; theight = height;
+
+ queue_draw ();
+}
+
+void
+ClickBox::style_changed (const Glib::RefPtr<Gtk::Style>&)
+{
+ layout->context_changed ();
+ layout->get_pixel_size (twidth, theight);
+}
+
+bool
+ClickBox::on_expose_event (GdkEventExpose *ev)
+{
+ /* Why do we do things like this rather than use a Gtk::Label?
+ Because whenever Gtk::Label::set_label() is called, it
+ triggers a recomputation of its own size, along with that
+ of its container and on up the tree. That's intended
+ to be unnecessary here.
+ */
+
+ Gtk::DrawingArea::on_expose_event (ev);
+
+ Glib::RefPtr<Gtk::Style> style (get_style());
+ Glib::RefPtr<Gdk::GC> fg_gc (style->get_fg_gc (Gtk::STATE_NORMAL));
+ Glib::RefPtr<Gdk::GC> bg_gc (style->get_bg_gc (Gtk::STATE_NORMAL));
+ Glib::RefPtr<Gdk::Window> win (get_window());
+
+ GdkRectangle base_rect;
+ GdkRectangle draw_rect;
+ gint x, y, width, height, depth;
+
+ win->get_geometry (x, y, width, height, depth);
+
+ base_rect.width = width;
+ base_rect.height = height;
+ base_rect.x = 0;
+ base_rect.y = 0;
+
+ gdk_rectangle_intersect (&ev->area, &base_rect, &draw_rect);
+ win->draw_rectangle (bg_gc, true, draw_rect.x, draw_rect.y, draw_rect.width, draw_rect.height);
+
+ if (twidth && theight) {
+ win->draw_layout (fg_gc, (width - twidth) / 2, (height - theight) / 2, layout);
+ }
+
+ return true;
+}
+
+void
+ClickBox::set_printer (sigc::slot<bool, char *, Gtk::Adjustment &> p)
+{
+ _printer = p;
+ set_label ();
+}
+
+bool
+ClickBox::on_enter_notify_event (GdkEventCrossing* ev)
+{
+ boost::shared_ptr<PBD::Controllable> c (_binding_proxy.get_controllable ());
+ if (c) {
+ PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> (c));
+ }
+ return false;
+}
+
+bool
+ClickBox::on_leave_notify_event (GdkEventCrossing* ev)
+{
+ if (_binding_proxy.get_controllable()) {
+ PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> ());
+ }
+ return false;
+}
diff --git a/libs/widgets/fastmeter.cc b/libs/widgets/fastmeter.cc
new file mode 100644
index 0000000000..cf1d9dd070
--- /dev/null
+++ b/libs/widgets/fastmeter.cc
@@ -0,0 +1,919 @@
+/*
+ Copyright (C) 2003-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 <iostream>
+#include <cmath>
+#include <algorithm>
+#include <cstring>
+
+#include <stdlib.h>
+
+#include <glibmm.h>
+#include <gdkmm.h>
+
+#include "gtkmm2ext/utils.h"
+#include "widgets/fastmeter.h"
+
+#define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
+#define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
+
+using namespace Gtk;
+using namespace Glib;
+using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
+using namespace std;
+
+int FastMeter::min_pattern_metric_size = 16;
+int FastMeter::max_pattern_metric_size = 1024;
+bool FastMeter::no_rgba_overlay = false;
+
+FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
+FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
+
+FastMeter::Pattern10Map FastMeter::hm_pattern_cache;
+FastMeter::PatternBgMap FastMeter::hb_pattern_cache;
+
+FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
+ int clr0, int clr1, int clr2, int clr3,
+ int clr4, int clr5, int clr6, int clr7,
+ int clr8, int clr9,
+ int bgc0, int bgc1,
+ int bgh0, int bgh1,
+ float stp0, float stp1,
+ float stp2, float stp3,
+ int styleflags
+ )
+ : pixheight(0)
+ , pixwidth(0)
+ , _styleflags(styleflags)
+ , orientation(o)
+ , hold_cnt(hold)
+ , hold_state(0)
+ , bright_hold(false)
+ , current_level(0)
+ , current_peak(0)
+ , highlight(false)
+{
+ last_peak_rect.width = 0;
+ last_peak_rect.height = 0;
+ last_peak_rect.x = 0;
+ last_peak_rect.y = 0;
+
+ no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
+
+ _clr[0] = clr0;
+ _clr[1] = clr1;
+ _clr[2] = clr2;
+ _clr[3] = clr3;
+ _clr[4] = clr4;
+ _clr[5] = clr5;
+ _clr[6] = clr6;
+ _clr[7] = clr7;
+ _clr[8] = clr8;
+ _clr[9] = clr9;
+
+ _bgc[0] = bgc0;
+ _bgc[1] = bgc1;
+
+ _bgh[0] = bgh0;
+ _bgh[1] = bgh1;
+
+ _stp[0] = stp0;
+ _stp[1] = stp1;
+ _stp[2] = stp2;
+ _stp[3] = stp3;
+
+ set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
+
+ pixrect.x = 1;
+ pixrect.y = 1;
+
+ if (!len) {
+ len = 250;
+ }
+ if (orientation == Vertical) {
+ pixheight = len;
+ pixwidth = dimen;
+ fgpattern = request_vertical_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
+ bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, _bgc, false);
+
+ } else {
+ pixheight = dimen;
+ pixwidth = len;
+ fgpattern = request_horizontal_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
+ bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, _bgc, false);
+ }
+
+ pixrect.width = pixwidth;
+ pixrect.height = pixheight;
+
+ request_width = pixrect.width + 2;
+ request_height= pixrect.height + 2;
+
+ clear ();
+}
+
+FastMeter::~FastMeter ()
+{
+}
+
+void
+FastMeter::flush_pattern_cache () {
+ hb_pattern_cache.clear();
+ hm_pattern_cache.clear();
+ vb_pattern_cache.clear();
+ vm_pattern_cache.clear();
+}
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::generate_meter_pattern (
+ int width, int height, int *clr, float *stp, int styleflags, bool horiz)
+{
+ guint8 r,g,b,a;
+ double knee;
+ const double soft = 3.0 / (double) height;
+ const double offs = -1.0 / (double) height;
+
+ cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
+
+ /*
+ Cairo coordinate space goes downwards as y value goes up, so invert
+ knee-based positions by using (1.0 - y)
+ */
+
+ UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
+ cairo_pattern_add_color_stop_rgb (pat, 0.0,
+ r/255.0, g/255.0, b/255.0);
+
+ knee = offs + stp[3] / 115.0f; // -0dB
+
+ UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
+ r/255.0, g/255.0, b/255.0);
+
+ UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
+ r/255.0, g/255.0, b/255.0);
+
+ knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
+
+ UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
+ r/255.0, g/255.0, b/255.0);
+
+ UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
+ r/255.0, g/255.0, b/255.0);
+
+ knee = offs + stp[1] / 115.0f; // -9dB
+
+ UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
+ r/255.0, g/255.0, b/255.0);
+
+ UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
+ r/255.0, g/255.0, b/255.0);
+
+ knee = offs + stp[0] / 115.0f; // -18dB
+
+ UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
+ r/255.0, g/255.0, b/255.0);
+
+ UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
+ cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
+ r/255.0, g/255.0, b/255.0);
+
+ UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
+ cairo_pattern_add_color_stop_rgb (pat, 1.0,
+ r/255.0, g/255.0, b/255.0);
+
+ if ((styleflags & 1) && !no_rgba_overlay) {
+ cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
+
+ cairo_surface_t* surface;
+ cairo_t* tc = 0;
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ tc = cairo_create (surface);
+ cairo_set_source (tc, pat);
+ cairo_rectangle (tc, 0, 0, width, height);
+ cairo_fill (tc);
+ cairo_pattern_destroy (pat);
+
+ cairo_set_source (tc, shade_pattern);
+ cairo_rectangle (tc, 0, 0, width, height);
+ cairo_fill (tc);
+ cairo_pattern_destroy (shade_pattern);
+
+ if (styleflags & 2) { // LED stripes
+ cairo_save (tc);
+ cairo_set_line_width(tc, 1.0);
+ cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
+ //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
+ for (int i = 0; float y = 0.5 + i * 2.0; ++i) {
+ if (y >= height) {
+ break;
+ }
+ cairo_move_to(tc, 0, y);
+ cairo_line_to(tc, width, y);
+ cairo_stroke (tc);
+ }
+ cairo_restore (tc);
+ }
+
+ pat = cairo_pattern_create_for_surface (surface);
+ cairo_destroy (tc);
+ cairo_surface_destroy (surface);
+ }
+
+ if (horiz) {
+ cairo_surface_t* surface;
+ cairo_t* tc = 0;
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
+ tc = cairo_create (surface);
+
+ cairo_matrix_t m;
+ cairo_matrix_init_rotate (&m, -M_PI/2.0);
+ cairo_matrix_translate (&m, -height, 0);
+ cairo_pattern_set_matrix (pat, &m);
+ cairo_set_source (tc, pat);
+ cairo_rectangle (tc, 0, 0, height, width);
+ cairo_fill (tc);
+ cairo_pattern_destroy (pat);
+ pat = cairo_pattern_create_for_surface (surface);
+ cairo_destroy (tc);
+ cairo_surface_destroy (surface);
+ }
+ Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
+
+ return p;
+}
+
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::generate_meter_background (
+ int width, int height, int *clr, bool shade, bool horiz)
+{
+ guint8 r0,g0,b0,r1,g1,b1,a;
+
+ cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
+
+ UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
+ UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
+
+ cairo_pattern_add_color_stop_rgb (pat, 0.0,
+ r1/255.0, g1/255.0, b1/255.0);
+
+ cairo_pattern_add_color_stop_rgb (pat, 1.0,
+ r0/255.0, g0/255.0, b0/255.0);
+
+ if (shade && !no_rgba_overlay) {
+ cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
+
+ cairo_surface_t* surface;
+ cairo_t* tc = 0;
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ tc = cairo_create (surface);
+ cairo_set_source (tc, pat);
+ cairo_rectangle (tc, 0, 0, width, height);
+ cairo_fill (tc);
+ cairo_set_source (tc, shade_pattern);
+ cairo_rectangle (tc, 0, 0, width, height);
+ cairo_fill (tc);
+
+ cairo_pattern_destroy (pat);
+ cairo_pattern_destroy (shade_pattern);
+
+ pat = cairo_pattern_create_for_surface (surface);
+
+ cairo_destroy (tc);
+ cairo_surface_destroy (surface);
+ }
+
+ if (horiz) {
+ cairo_surface_t* surface;
+ cairo_t* tc = 0;
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
+ tc = cairo_create (surface);
+
+ cairo_matrix_t m;
+ cairo_matrix_init_rotate (&m, -M_PI/2.0);
+ cairo_matrix_translate (&m, -height, 0);
+ cairo_pattern_set_matrix (pat, &m);
+ cairo_set_source (tc, pat);
+ cairo_rectangle (tc, 0, 0, height, width);
+ cairo_fill (tc);
+ cairo_pattern_destroy (pat);
+ pat = cairo_pattern_create_for_surface (surface);
+ cairo_destroy (tc);
+ cairo_surface_destroy (surface);
+ }
+
+ Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
+
+ return p;
+}
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::request_vertical_meter(
+ int width, int height, int *clr, float *stp, int styleflags)
+{
+ height = max(height, min_pattern_metric_size);
+ height = min(height, max_pattern_metric_size);
+
+ const Pattern10MapKey key (width, height,
+ stp[0], stp[1], stp[2], stp[3],
+ clr[0], clr[1], clr[2], clr[3],
+ clr[4], clr[5], clr[6], clr[7],
+ clr[8], clr[9], styleflags);
+
+ Pattern10Map::iterator i;
+ if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
+ return i->second;
+ }
+ // TODO flush pattern cache if it gets too large
+
+ Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
+ width, height, clr, stp, styleflags, false);
+ vm_pattern_cache[key] = p;
+
+ return p;
+}
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::request_vertical_background(
+ int width, int height, int *bgc, bool shade)
+{
+ height = max(height, min_pattern_metric_size);
+ height = min(height, max_pattern_metric_size);
+ height += 2;
+
+ const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
+ PatternBgMap::iterator i;
+ if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
+ return i->second;
+ }
+ // TODO flush pattern cache if it gets too large
+
+ Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
+ width, height, bgc, shade, false);
+ vb_pattern_cache[key] = p;
+
+ return p;
+}
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::request_horizontal_meter(
+ int width, int height, int *clr, float *stp, int styleflags)
+{
+ width = max(width, min_pattern_metric_size);
+ width = min(width, max_pattern_metric_size);
+
+ const Pattern10MapKey key (width, height,
+ stp[0], stp[1], stp[2], stp[3],
+ clr[0], clr[1], clr[2], clr[3],
+ clr[4], clr[5], clr[6], clr[7],
+ clr[8], clr[9], styleflags);
+
+ Pattern10Map::iterator i;
+ if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
+ return i->second;
+ }
+ // TODO flush pattern cache if it gets too large
+
+ Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
+ height, width, clr, stp, styleflags, true);
+
+ hm_pattern_cache[key] = p;
+ return p;
+}
+
+Cairo::RefPtr<Cairo::Pattern>
+FastMeter::request_horizontal_background(
+ int width, int height, int *bgc, bool shade)
+{
+ width = max(width, min_pattern_metric_size);
+ width = min(width, max_pattern_metric_size);
+ width += 2;
+
+ const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
+ PatternBgMap::iterator i;
+ if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
+ return i->second;
+ }
+ // TODO flush pattern cache if it gets too large
+
+ Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
+ height, width, bgc, shade, true);
+
+ hb_pattern_cache[key] = p;
+
+ return p;
+}
+
+
+
+void
+FastMeter::set_hold_count (long val)
+{
+ if (val < 1) {
+ val = 1;
+ }
+
+ hold_cnt = val;
+ hold_state = 0;
+ current_peak = 0;
+
+ queue_draw ();
+}
+
+void
+FastMeter::on_size_request (GtkRequisition* req)
+{
+ if (orientation == Vertical) {
+ vertical_size_request (req);
+ } else {
+ horizontal_size_request (req);
+ }
+}
+
+void
+FastMeter::vertical_size_request (GtkRequisition* req)
+{
+ req->height = request_height;
+ req->height = max(req->height, min_pattern_metric_size);
+ req->height = min(req->height, max_pattern_metric_size);
+ req->height += 2;
+
+ req->width = request_width;
+}
+
+void
+FastMeter::horizontal_size_request (GtkRequisition* req)
+{
+ req->width = request_width;
+ req->width = max(req->width, min_pattern_metric_size);
+ req->width = min(req->width, max_pattern_metric_size);
+ req->width += 2;
+
+ req->height = request_height;
+}
+
+void
+FastMeter::on_size_allocate (Gtk::Allocation &alloc)
+{
+ if (orientation == Vertical) {
+ vertical_size_allocate (alloc);
+ } else {
+ horizontal_size_allocate (alloc);
+ }
+ queue_draw ();
+}
+
+void
+FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
+{
+ if (alloc.get_width() != request_width) {
+ alloc.set_width (request_width);
+ }
+
+ int h = alloc.get_height();
+ h = max (h, min_pattern_metric_size + 2);
+ h = min (h, max_pattern_metric_size + 2);
+
+ if (h != alloc.get_height()) {
+ alloc.set_height (h);
+ }
+
+ if (pixheight != h) {
+ fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
+ bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
+ pixheight = h - 2;
+ pixwidth = request_width - 2;
+ }
+
+ CairoWidget::on_size_allocate (alloc);
+}
+
+void
+FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
+{
+ if (alloc.get_height() != request_height) {
+ alloc.set_height (request_height);
+ }
+
+ int w = alloc.get_width();
+ w = max (w, min_pattern_metric_size + 2);
+ w = min (w, max_pattern_metric_size + 2);
+
+ if (w != alloc.get_width()) {
+ alloc.set_width (w);
+ }
+
+ if (pixwidth != w) {
+ fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
+ bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, highlight);
+ pixwidth = w - 2;
+ pixheight = request_height - 2;
+ }
+
+ CairoWidget::on_size_allocate (alloc);
+}
+
+void
+FastMeter::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t* area)
+{
+ if (orientation == Vertical) {
+ return vertical_expose (ctx->cobj(), area);
+ } else {
+ return horizontal_expose (ctx->cobj(), area);
+ }
+}
+
+void
+FastMeter::vertical_expose (cairo_t* cr, cairo_rectangle_t* area)
+{
+ gint top_of_meter;
+ GdkRectangle intersection;
+ GdkRectangle background;
+ GdkRectangle eventarea;
+
+ cairo_set_source_rgb (cr, 0, 0, 0); // black
+ rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
+ cairo_stroke (cr);
+
+ top_of_meter = (gint) floor (pixheight * current_level);
+
+ /* reset the height & origin of the rect that needs to show the pixbuf
+ */
+
+ pixrect.height = top_of_meter;
+ pixrect.y = 1 + pixheight - top_of_meter;
+
+ background.x = 1;
+ background.y = 1;
+ background.width = pixrect.width;
+ background.height = pixheight - top_of_meter;
+
+ eventarea.x = area->x;
+ eventarea.y = area->y;
+ eventarea.width = area->width;
+ eventarea.height = area->height;
+
+ if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
+ cairo_set_source (cr, bgpattern->cobj());
+ cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
+ cairo_fill (cr);
+ }
+
+ if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
+ // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
+ cairo_set_source (cr, fgpattern->cobj());
+ cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
+ cairo_fill (cr);
+ }
+
+ // draw peak bar
+
+ if (hold_state) {
+ last_peak_rect.x = 1;
+ last_peak_rect.width = pixwidth;
+ last_peak_rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
+ if (_styleflags & 2) { // LED stripes
+ last_peak_rect.y = max(0, (last_peak_rect.y & (~1)));
+ }
+ if (bright_hold || (_styleflags & 2)) {
+ last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y - 1 ));
+ } else {
+ last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y - 1 ));
+ }
+
+ cairo_set_source (cr, fgpattern->cobj());
+ cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
+
+ if (bright_hold && !no_rgba_overlay) {
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
+ }
+ cairo_fill (cr);
+
+ } else {
+ last_peak_rect.width = 0;
+ last_peak_rect.height = 0;
+ }
+}
+
+void
+FastMeter::horizontal_expose (cairo_t* cr, cairo_rectangle_t* area)
+{
+ gint right_of_meter;
+ GdkRectangle intersection;
+ GdkRectangle background;
+ GdkRectangle eventarea;
+
+ cairo_set_source_rgb (cr, 0, 0, 0); // black
+ rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
+ cairo_stroke (cr);
+
+ right_of_meter = (gint) floor (pixwidth * current_level);
+
+ /* reset the height & origin of the rect that needs to show the pixbuf
+ */
+
+ pixrect.width = right_of_meter;
+
+ background.x = 1 + right_of_meter;
+ background.y = 1;
+ background.width = pixwidth - right_of_meter;
+ background.height = pixheight;
+
+ eventarea.x = area->x;
+ eventarea.y = area->y;
+ eventarea.width = area->width;
+ eventarea.height = area->height;
+
+ if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
+ cairo_set_source (cr, bgpattern->cobj());
+ cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
+ cairo_fill (cr);
+ }
+
+ if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
+ cairo_set_source (cr, fgpattern->cobj());
+ cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
+ cairo_fill (cr);
+ }
+
+ // draw peak bar
+
+ if (hold_state) {
+ last_peak_rect.y = 1;
+ last_peak_rect.height = pixheight;
+ const int xpos = floor (pixwidth * current_peak);
+ if (bright_hold || (_styleflags & 2)) {
+ last_peak_rect.width = min(3, xpos );
+ } else {
+ last_peak_rect.width = min(2, xpos );
+ }
+ last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
+
+ cairo_set_source (cr, fgpattern->cobj());
+ cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
+
+ if (bright_hold && !no_rgba_overlay) {
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
+ }
+ cairo_fill (cr);
+
+ } else {
+ last_peak_rect.width = 0;
+ last_peak_rect.height = 0;
+ }
+}
+
+void
+FastMeter::set (float lvl, float peak)
+{
+ float old_level = current_level;
+ float old_peak = current_peak;
+
+ if (pixwidth <= 0 || pixheight <=0) return;
+
+ if (peak == -1) {
+ if (lvl >= current_peak) {
+ current_peak = lvl;
+ hold_state = hold_cnt;
+ }
+
+ if (hold_state > 0) {
+ if (--hold_state == 0) {
+ current_peak = lvl;
+ }
+ }
+ bright_hold = false;
+ } else {
+ current_peak = peak;
+ hold_state = 1;
+ bright_hold = true;
+ }
+
+ current_level = lvl;
+
+ const float pixscale = (orientation == Vertical) ? pixheight : pixwidth;
+#define PIX(X) floor(pixscale * (X))
+ if (PIX(current_level) == PIX(old_level) && PIX(current_peak) == PIX(old_peak) && (hold_state == 0 || peak != -1)) {
+ return;
+ }
+
+ Glib::RefPtr<Gdk::Window> win;
+
+ if (! (win = get_window())) {
+ queue_draw ();
+ return;
+ }
+
+ if (orientation == Vertical) {
+ queue_vertical_redraw (win, old_level);
+ } else {
+ queue_horizontal_redraw (win, old_level);
+ }
+}
+
+void
+FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
+{
+ GdkRectangle rect;
+
+ gint new_top = (gint) floor (pixheight * current_level);
+
+ rect.x = 1;
+ rect.width = pixwidth;
+ rect.height = new_top;
+ rect.y = 1 + pixheight - new_top;
+
+ if (current_level > old_level) {
+ /* colored/pixbuf got larger, just draw the new section */
+ /* rect.y stays where it is because of X coordinates */
+ /* height of invalidated area is between new.y (smaller) and old.y
+ (larger).
+ X coordinates just make my brain hurt.
+ */
+ rect.height = pixrect.y - rect.y;
+ } else {
+ /* it got smaller, compute the difference */
+ /* rect.y becomes old.y (the smaller value) */
+ rect.y = pixrect.y;
+ /* rect.height is the old.y (smaller) minus the new.y (larger)
+ */
+ rect.height = pixrect.height - rect.height;
+ }
+
+ GdkRegion* region = 0;
+ bool queue = false;
+
+ if (rect.height != 0) {
+
+ /* ok, first region to draw ... */
+
+ region = gdk_region_rectangle (&rect);
+ queue = true;
+ }
+
+ /* redraw the last place where the last peak hold bar was;
+ the next expose will draw the new one whether its part of
+ expose region or not.
+ */
+
+ if (last_peak_rect.width * last_peak_rect.height != 0) {
+ if (!queue) {
+ region = gdk_region_new ();
+ queue = true;
+ }
+ gdk_region_union_with_rect (region, &last_peak_rect);
+ }
+
+ if (hold_state && current_peak > 0) {
+ if (!queue) {
+ region = gdk_region_new ();
+ queue = true;
+ }
+ rect.x = 1;
+ rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
+ if (_styleflags & 2) { // LED stripes
+ rect.y = max(0, (rect.y & (~1)));
+ }
+ if (bright_hold || (_styleflags & 2)) {
+ rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
+ } else {
+ rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
+ }
+ rect.width = pixwidth;
+ gdk_region_union_with_rect (region, &rect);
+ }
+
+ if (queue) {
+ gdk_window_invalidate_region (win->gobj(), region, true);
+ }
+ if (region) {
+ gdk_region_destroy(region);
+ region = 0;
+ }
+}
+
+void
+FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
+{
+ GdkRectangle rect;
+
+ gint new_right = (gint) floor (pixwidth * current_level);
+
+ rect.height = pixheight;
+ rect.y = 1;
+
+ if (current_level > old_level) {
+ rect.x = 1 + pixrect.width;
+ /* colored/pixbuf got larger, just draw the new section */
+ rect.width = new_right - pixrect.width;
+ } else {
+ /* it got smaller, compute the difference */
+ rect.x = 1 + new_right;
+ /* rect.height is the old.x (smaller) minus the new.x (larger) */
+ rect.width = pixrect.width - new_right;
+ }
+
+ GdkRegion* region = 0;
+ bool queue = false;
+
+ if (rect.height != 0) {
+
+ /* ok, first region to draw ... */
+
+ region = gdk_region_rectangle (&rect);
+ queue = true;
+ }
+
+ /* redraw the last place where the last peak hold bar was;
+ the next expose will draw the new one whether its part of
+ expose region or not.
+ */
+
+ if (last_peak_rect.width * last_peak_rect.height != 0) {
+ if (!queue) {
+ region = gdk_region_new ();
+ queue = true;
+ }
+ gdk_region_union_with_rect (region, &last_peak_rect);
+ }
+
+ if (hold_state && current_peak > 0) {
+ if (!queue) {
+ region = gdk_region_new ();
+ queue = true;
+ }
+ rect.y = 1;
+ rect.height = pixheight;
+ const int xpos = floor (pixwidth * current_peak);
+ if (bright_hold || (_styleflags & 2)) {
+ rect.width = min(3, xpos);
+ } else {
+ rect.width = min(2, xpos);
+ }
+ rect.x = 1 + max(0, xpos - rect.width);
+ gdk_region_union_with_rect (region, &rect);
+ }
+
+ if (queue) {
+ gdk_window_invalidate_region (win->gobj(), region, true);
+ }
+ if (region) {
+ gdk_region_destroy(region);
+ region = 0;
+ }
+}
+
+void
+FastMeter::set_highlight (bool onoff)
+{
+ if (highlight == onoff) {
+ return;
+ }
+ highlight = onoff;
+ if (orientation == Vertical) {
+ bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
+ } else {
+ bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
+ }
+ queue_draw ();
+}
+
+void
+FastMeter::clear ()
+{
+ current_level = 0;
+ current_peak = 0;
+ hold_state = 0;
+ queue_draw ();
+}
diff --git a/libs/widgets/focus_entry.cc b/libs/widgets/focus_entry.cc
new file mode 100644
index 0000000000..b503bc1dbd
--- /dev/null
+++ b/libs/widgets/focus_entry.cc
@@ -0,0 +1,49 @@
+/*
+ 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 "widgets/focus_entry.h"
+
+using namespace ArdourWidgets;
+
+FocusEntry::FocusEntry ()
+ : next_release_selects (false)
+{
+}
+
+bool
+FocusEntry::on_button_press_event (GdkEventButton* ev)
+{
+ if (!has_focus()) {
+ next_release_selects = true;
+ }
+ return Entry::on_button_press_event (ev);
+}
+
+bool
+FocusEntry::on_button_release_event (GdkEventButton* ev)
+{
+ if (next_release_selects) {
+ bool ret = Entry::on_button_release_event (ev);
+ select_region (0, -1);
+ next_release_selects = false;
+ return ret;
+ }
+
+ return Entry::on_button_release_event (ev);
+}
diff --git a/libs/widgets/searchbar.cc b/libs/widgets/searchbar.cc
new file mode 100644
index 0000000000..69ceac8007
--- /dev/null
+++ b/libs/widgets/searchbar.cc
@@ -0,0 +1,92 @@
+#include <iostream>
+
+#include "gtkmm2ext/keyboard.h"
+#include "widgets/searchbar.h"
+
+using namespace ArdourWidgets;
+
+SearchBar::SearchBar (const std::string& label, bool icon_resets)
+ : placeholder_text (label)
+ , icon_click_resets (icon_resets)
+{
+ set_text (placeholder_text);
+ set_alignment (Gtk::ALIGN_CENTER);
+ signal_key_press_event().connect (sigc::mem_fun (*this, &SearchBar::key_press_event));
+ signal_focus_in_event().connect (sigc::mem_fun (*this, &SearchBar::focus_in_event));
+ signal_focus_out_event().connect (sigc::mem_fun (*this, &SearchBar::focus_out_event));
+ signal_changed().connect (sigc::mem_fun (*this, &SearchBar::search_string_changed));
+ signal_icon_release().connect (sigc::mem_fun (*this, &SearchBar::icon_clicked_event));
+}
+
+bool
+SearchBar::focus_in_event (GdkEventFocus*)
+{
+ if (get_text ().compare (placeholder_text) == 0) {
+ set_text ("");
+ }
+
+ icon = get_icon_pixbuf ();
+ if (icon) {
+ set_icon_from_pixbuf (Glib::RefPtr<Gdk::Pixbuf> ());
+ }
+ return true;
+}
+
+bool
+SearchBar::focus_out_event (GdkEventFocus*)
+{
+ if (get_text ().empty ()) {
+ set_text (placeholder_text);
+ }
+
+ if (icon) {
+ set_icon_from_pixbuf (icon);
+ icon.reset ();
+ }
+
+ search_string_changed ();
+ return false;
+}
+
+bool
+SearchBar::key_press_event (GdkEventKey* ev)
+{
+ switch (ev->keyval) {
+ case GDK_Escape:
+ set_text (placeholder_text);
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void
+SearchBar::icon_clicked_event (Gtk::EntryIconPosition, const GdkEventButton*)
+{
+ if (icon_click_resets) {
+ reset ();
+ }
+ else {
+ search_string_changed ();
+ }
+}
+
+void
+SearchBar::search_string_changed () const
+{
+ const std::string& text = get_text ();
+ if (text.empty() || text.compare (placeholder_text) == 0) {
+ sig_search_string_updated ("");
+ return;
+ }
+ sig_search_string_updated (text);
+}
+
+void
+SearchBar::reset ()
+{
+ set_text (placeholder_text);
+ search_string_changed ();
+}
diff --git a/libs/widgets/slider_controller.cc b/libs/widgets/slider_controller.cc
new file mode 100644
index 0000000000..4bf784a505
--- /dev/null
+++ b/libs/widgets/slider_controller.cc
@@ -0,0 +1,122 @@
+/*
+ Copyright (C) 1998-99 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.
+
+ $Id$
+*/
+
+#include <string>
+
+#include "gtkmm2ext/gtk_ui.h"
+#include "pbd/controllable.h"
+
+#include "widgets/ardour_fader.h"
+#include "widgets/slider_controller.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ArdourWidgets;
+
+SliderController::SliderController (Gtk::Adjustment *adj, boost::shared_ptr<PBD::Controllable> mc, int orientation, int fader_length, int fader_girth)
+ : ArdourFader (*adj, orientation, fader_length, fader_girth)
+ , _ctrl (mc)
+ , _ctrl_adj (adj)
+ , _spin_adj (0, 0, 1.0, .1, .01)
+ , _spin (_spin_adj, 0, 2)
+ , _ctrl_ignore (false)
+ , _spin_ignore (false)
+{
+ if (mc) {
+ _spin_adj.set_lower (mc->lower ());
+ _spin_adj.set_upper (mc->upper ());
+ _spin_adj.set_step_increment(_ctrl->interface_to_internal(adj->get_step_increment()) - mc->lower ());
+ _spin_adj.set_page_increment(_ctrl->interface_to_internal(adj->get_page_increment()) - mc->lower ());
+
+ adj->signal_value_changed().connect (sigc::mem_fun(*this, &SliderController::ctrl_adjusted));
+ _spin_adj.signal_value_changed().connect (sigc::mem_fun(*this, &SliderController::spin_adjusted));
+
+ _binding_proxy.set_controllable (mc);
+ }
+
+ _spin.set_name ("SliderControllerValue");
+ _spin.set_numeric (true);
+ _spin.set_snap_to_ticks (false);
+}
+
+bool
+SliderController::on_button_press_event (GdkEventButton *ev)
+{
+ if (_binding_proxy.button_press_handler (ev)) {
+ return true;
+ }
+
+ return ArdourFader::on_button_press_event (ev);
+}
+
+bool
+SliderController::on_enter_notify_event (GdkEventCrossing* ev)
+{
+ boost::shared_ptr<PBD::Controllable> c (_binding_proxy.get_controllable ());
+ if (c) {
+ PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> (c));
+ }
+ return ArdourFader::on_enter_notify_event (ev);
+}
+
+bool
+SliderController::on_leave_notify_event (GdkEventCrossing* ev)
+{
+ if (_binding_proxy.get_controllable()) {
+ PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> ());
+ }
+ return ArdourFader::on_leave_notify_event (ev);
+}
+
+void
+SliderController::ctrl_adjusted ()
+{
+ assert (_ctrl); // only used w/BarControlle
+ if (_spin_ignore) return;
+ _ctrl_ignore = true;
+ // TODO consider using internal_to_user, too (amp, dB)
+ // (also needs _spin_adj min/max range changed accordingly
+ // and dedicated support for log-scale, revert parts of ceff2e3a62f839)
+ _spin_adj.set_value (_ctrl->interface_to_internal (_ctrl_adj->get_value()));
+ _ctrl_ignore = false;
+}
+
+void
+SliderController::spin_adjusted ()
+{
+ assert (_ctrl); // only used w/BarController
+ if (_ctrl_ignore) return;
+ _spin_ignore = true;
+ // TODO consider using user_to_internal, as well
+ _ctrl_adj->set_value(_ctrl->internal_to_interface (_spin_adj.get_value()));
+ _spin_ignore = false;
+}
+
+
+
+VSliderController::VSliderController (Gtk::Adjustment *adj, boost::shared_ptr<PBD::Controllable> mc, int fader_length, int fader_girth)
+ : SliderController (adj, mc, VERT, fader_length, fader_girth)
+{
+}
+
+HSliderController::HSliderController (Gtk::Adjustment *adj, boost::shared_ptr<PBD::Controllable> mc, int fader_length, int fader_girth)
+ : SliderController (adj, mc, HORIZ, fader_length, fader_girth)
+{
+}
diff --git a/libs/widgets/widgets/ardour_fader.h b/libs/widgets/widgets/ardour_fader.h
new file mode 100644
index 0000000000..b9270cae51
--- /dev/null
+++ b/libs/widgets/widgets/ardour_fader.h
@@ -0,0 +1,158 @@
+/*
+ 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_ARDOUR_FADER_H_
+#define _WIDGETS_ARDOUR_FADER_H_
+
+#include <cmath>
+#include <stdint.h>
+
+#include <gdkmm.h>
+#include <gtkmm/adjustment.h>
+
+#include "gtkmm2ext/cairo_widget.h"
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API ArdourFader : public CairoWidget
+{
+public:
+ ArdourFader (Gtk::Adjustment& adjustment, int orientation, int span, int girth);
+ virtual ~ArdourFader ();
+ static void flush_pattern_cache();
+
+ sigc::signal<void> StartGesture;
+ sigc::signal<void> StopGesture;
+ sigc::signal<void> OnExpose;
+
+ void set_default_value (float);
+ void set_text (const std::string&, bool centered = true, bool expose = true);
+
+ enum Tweaks {
+ NoShowUnityLine = 0x1,
+ NoButtonForward = 0x2,
+ NoVerticalScroll = 0x4,
+ };
+
+ Tweaks tweaks() const { return _tweaks; }
+ void set_tweaks (Tweaks);
+
+protected:
+ void on_size_request (GtkRequisition*);
+ void on_size_allocate (Gtk::Allocation& alloc);
+
+ void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
+ bool on_grab_broken_event (GdkEventGrabBroken*);
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+ bool on_motion_notify_event (GdkEventMotion*);
+ bool on_scroll_event (GdkEventScroll* ev);
+ bool on_enter_notify_event (GdkEventCrossing* ev);
+ bool on_leave_notify_event (GdkEventCrossing* ev);
+
+ void on_state_changed (Gtk::StateType);
+ void on_style_changed (const Glib::RefPtr<Gtk::Style>&);
+
+ enum Orientation {
+ VERT,
+ HORIZ,
+ };
+
+private:
+ Glib::RefPtr<Pango::Layout> _layout;
+ std::string _text;
+ Tweaks _tweaks;
+ Gtk::Adjustment& _adjustment;
+ int _text_width;
+ int _text_height;
+
+ int _span, _girth;
+ int _min_span, _min_girth;
+ int _orien;
+ cairo_pattern_t* _pattern;
+ bool _hovering;
+ GdkWindow* _grab_window;
+ double _grab_loc;
+ double _grab_start;
+ bool _dragging;
+ float _default_value;
+ int _unity_loc;
+ bool _centered_text;
+
+ sigc::connection _parent_style_change;
+ Widget * _current_parent;
+ Gdk::Color get_parent_bg ();
+
+ void create_patterns();
+ void adjustment_changed ();
+ void set_adjustment_from_event (GdkEventButton *);
+ void update_unity_position ();
+ int display_span ();
+
+ struct FaderImage {
+ cairo_pattern_t* pattern;
+ double fr;
+ double fg;
+ double fb;
+ double br;
+ double bg;
+ double bb;
+ int width;
+ int height;
+
+ FaderImage (cairo_pattern_t* p,
+ double afr, double afg, double afb,
+ double abr, double abg, double abb,
+ int w, int h)
+ : pattern (p)
+ , fr (afr)
+ , fg (afg)
+ , fb (afb)
+ , br (abr)
+ , bg (abg)
+ , bb (abb)
+ , width (w)
+ , height (h)
+ {}
+
+ bool matches (double afr, double afg, double afb,
+ double abr, double abg, double abb,
+ int w, int h) {
+ return width == w &&
+ height == h &&
+ afr == fr &&
+ afg == fg &&
+ afb == fb &&
+ abr == br &&
+ abg == bg &&
+ abb == bb;
+ }
+ };
+
+ static std::list<FaderImage*> _patterns;
+ static cairo_pattern_t* find_pattern (double afr, double afg, double afb,
+ double abr, double abg, double abb,
+ int w, int h);
+
+};
+
+} /* namespace */
+
+#endif /* __gtkmm2ext_pixfader_h__ */
diff --git a/libs/widgets/widgets/auto_spin.h b/libs/widgets/widgets/auto_spin.h
new file mode 100644
index 0000000000..ecea31e7fe
--- /dev/null
+++ b/libs/widgets/widgets/auto_spin.h
@@ -0,0 +1,76 @@
+/*
+ Copyright (C) 2000 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_AUTO_SPIN_H_
+#define _WIDGETS_AUTO_SPIN_H_
+
+#ifdef interface
+#undef interface
+#endif
+
+#include <gtkmm.h>
+
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API AutoSpin
+{
+public:
+ AutoSpin (Gtk::Adjustment &adj, gfloat cr = 0, bool round_to_steps_yn = false);
+
+ Gtk::Adjustment &get_adjustment() { return adjustment; }
+
+ void use_left_as_decrement (bool yn) { left_is_decrement = yn; }
+ void set_wrap (bool yn) { wrap = yn; }
+ void set_climb_rate (gfloat cr) { climb_rate = cr; }
+ void set_bounds (gfloat initial, gfloat low, gfloat high, bool with_reset = true);
+
+ gint button_press (GdkEventButton *);
+ gint stop_spinning (GdkEventButton *ignored_but_here_for_clicked);
+ void start_spinning (bool decrementing, bool use_page);
+ gint scroll_event (GdkEventScroll *);
+
+private:
+ Gtk::Adjustment &adjustment;
+ gfloat climb_rate;
+ gfloat timer_increment;
+ gfloat initial;
+ unsigned int timer_calls;
+ bool have_timer;
+ bool need_timer;
+ bool wrap;
+ gint timeout_tag;
+ bool left_is_decrement;
+ bool round_to_steps;
+
+ static const unsigned int initial_timer_interval;
+ static const unsigned int timer_interval;
+ static const unsigned int climb_timer_calls;
+
+ void stop_timer ();
+ static gint _timer (void *arg);
+ gint timer ();
+ bool adjust_value (gfloat increment);
+ void set_value (gfloat value);
+};
+
+} /* namespace */
+
+#endif
diff --git a/libs/widgets/widgets/barcontroller.h b/libs/widgets/widgets/barcontroller.h
new file mode 100644
index 0000000000..fc2ac358cc
--- /dev/null
+++ b/libs/widgets/widgets/barcontroller.h
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2004 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_BAR_CONTROLLER_H_
+#define _WIDGETS_BAR_CONTROLLER_H_
+
+#include <gtkmm/alignment.h>
+#include <cairo.h>
+
+#include "gtkmm2ext/binding_proxy.h"
+#include "widgets/slider_controller.h"
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API BarController : public Gtk::Alignment
+{
+public:
+ BarController (Gtk::Adjustment& adj, boost::shared_ptr<PBD::Controllable>);
+
+ virtual ~BarController ();
+
+ void set_sensitive (bool yn);
+
+ ArdourFader::Tweaks tweaks() const { return _slider.tweaks (); }
+ void set_tweaks (ArdourFader::Tweaks t) { _slider.set_tweaks (t);}
+
+ sigc::signal<void> StartGesture;
+ sigc::signal<void> StopGesture;
+
+ /* export this to allow direct connection to button events */
+ Gtk::Widget& event_widget() { return _slider; }
+
+ /** Emitted when the adjustment spinner is activated or deactivated;
+ * the parameter is true on activation, false on deactivation.
+ */
+ sigc::signal<void, bool> SpinnerActive;
+
+protected:
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+ void on_style_changed (const Glib::RefPtr<Gtk::Style>&);
+
+ virtual std::string get_label (double& /*x*/) {
+ return "";
+ }
+
+private:
+ HSliderController _slider;
+ bool entry_focus_out (GdkEventFocus*);
+ void entry_activated ();
+ void before_expose ();
+
+ gint switch_to_bar ();
+ gint switch_to_spinner ();
+
+ bool _switching;
+ bool _switch_on_release;
+
+
+ void passtrhu_gesture_start() { StartGesture (); }
+ void passtrhu_gesture_stop() { StopGesture (); }
+};
+
+
+}; /* namespace */
+
+#endif // __gtkmm2ext_bar_controller_h__
diff --git a/libs/widgets/widgets/click_box.h b/libs/widgets/widgets/click_box.h
new file mode 100644
index 0000000000..fa4868467f
--- /dev/null
+++ b/libs/widgets/widgets/click_box.h
@@ -0,0 +1,81 @@
+/*
+ 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_CLICK_BOX_H_
+#define _WIDGETS_CLICK_BOX_H_
+
+#ifdef interface
+#undef interface
+#endif
+
+#include <string>
+#include <gtkmm.h>
+
+#include "gtkmm2ext/binding_proxy.h"
+
+#include "widgets/auto_spin.h"
+#include "widgets/visibility.h"
+
+namespace PBD {
+ class Controllable;
+}
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API ClickBox : public Gtk::DrawingArea, public AutoSpin
+{
+ public:
+ ClickBox (Gtk::Adjustment *adj, const std::string &name, bool round_to_steps = false);
+ ~ClickBox ();
+
+ /** Set a slot to `print' the value to put in the box.
+ * The slot should write the value of the Gtk::Adjustment
+ * into the char array, and should return true if it has done the printing,
+ * or false to use the ClickBox's default printing method.
+ */
+ void set_printer (sigc::slot<bool, char *, Gtk::Adjustment &>);
+
+ void set_controllable (boost::shared_ptr<PBD::Controllable> c) {
+ _binding_proxy.set_controllable (c);
+ }
+
+ protected:
+ bool on_expose_event (GdkEventExpose*);
+ bool on_enter_notify_event (GdkEventCrossing* ev);
+ bool on_leave_notify_event (GdkEventCrossing* ev);
+
+ BindingProxy _binding_proxy;
+
+ private:
+ Glib::RefPtr<Pango::Layout> layout;
+ int twidth;
+ int theight;
+
+ void set_label ();
+ void style_changed (const Glib::RefPtr<Gtk::Style> &);
+ bool button_press_handler (GdkEventButton *);
+ bool button_release_handler (GdkEventButton *);
+ bool on_scroll_event (GdkEventScroll*);
+
+ sigc::slot<bool, char *, Gtk::Adjustment &> _printer;
+};
+
+} /* namespace */
+
+#endif
diff --git a/libs/widgets/widgets/fastmeter.h b/libs/widgets/widgets/fastmeter.h
new file mode 100644
index 0000000000..b1d2f3f4f9
--- /dev/null
+++ b/libs/widgets/widgets/fastmeter.h
@@ -0,0 +1,177 @@
+/*
+ Copyright (C) 2003 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_FAST_METER_H_
+#define _WIDGETS_FAST_METER_H_
+
+#include <map>
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+#include <cairomm/pattern.h>
+#include "gtkmm2ext/cairo_widget.h"
+
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API FastMeter : public CairoWidget {
+public:
+ enum Orientation {
+ Horizontal,
+ Vertical
+ };
+
+ FastMeter (long hold_cnt, unsigned long width, Orientation, int len=0,
+ int clr0=0x008800ff, int clr1=0x008800ff,
+ int clr2=0x00ff00ff, int clr3=0x00ff00ff,
+ int clr4=0xffaa00ff, int clr5=0xffaa00ff,
+ int clr6=0xffff00ff, int clr7=0xffff00ff,
+ int clr8=0xff0000ff, int clr9=0xff0000ff,
+ int bgc0=0x333333ff, int bgc1=0x444444ff,
+ int bgh0=0x991122ff, int bgh1=0x551111ff,
+ float stp0 = 55.0, // log_meter(-18);
+ float stp1 = 77.5, // log_meter(-9);
+ float stp2 = 92.5, // log_meter(-3); // 95.0, // log_meter(-2);
+ float stp3 = 100.0,
+ int styleflags = 3
+ );
+ virtual ~FastMeter ();
+ static void flush_pattern_cache();
+
+ void set (float level, float peak = -1);
+ void clear ();
+
+ float get_level() { return current_level; }
+ float get_user_level() { return current_user_level; }
+ float get_peak() { return current_peak; }
+
+ long hold_count() { return hold_cnt; }
+ void set_hold_count (long);
+ void set_highlight (bool);
+ bool get_highlight () { return highlight; }
+ void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
+
+protected:
+ void on_size_request (GtkRequisition*);
+ void on_size_allocate (Gtk::Allocation&);
+
+private:
+ Cairo::RefPtr<Cairo::Pattern> fgpattern;
+ Cairo::RefPtr<Cairo::Pattern> bgpattern;
+ gint pixheight;
+ gint pixwidth;
+
+ float _stp[4];
+ int _clr[10];
+ int _bgc[2];
+ int _bgh[2];
+ int _styleflags;
+
+ Orientation orientation;
+ GdkRectangle pixrect;
+ GdkRectangle last_peak_rect;
+ gint request_width;
+ gint request_height;
+ unsigned long hold_cnt;
+ unsigned long hold_state;
+ bool bright_hold;
+ float current_level;
+ float current_peak;
+ float current_user_level;
+ bool highlight;
+
+ void vertical_expose (cairo_t*, cairo_rectangle_t*);
+ void vertical_size_request (GtkRequisition*);
+ void vertical_size_allocate (Gtk::Allocation&);
+ void queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>&, float);
+
+ void horizontal_expose (cairo_t*, cairo_rectangle_t*);
+ void horizontal_size_request (GtkRequisition*);
+ void horizontal_size_allocate (Gtk::Allocation&);
+ void queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>&, float);
+
+ static bool no_rgba_overlay;
+
+ static Cairo::RefPtr<Cairo::Pattern> generate_meter_pattern (
+ int, int, int *, float *, int, bool);
+ static Cairo::RefPtr<Cairo::Pattern> request_vertical_meter (
+ int, int, int *, float *, int);
+ static Cairo::RefPtr<Cairo::Pattern> request_horizontal_meter (
+ int, int, int *, float *, int);
+
+ static Cairo::RefPtr<Cairo::Pattern> generate_meter_background (
+ int, int, int *, bool, bool);
+ static Cairo::RefPtr<Cairo::Pattern> request_vertical_background (
+ int, int, int *, bool);
+ static Cairo::RefPtr<Cairo::Pattern> request_horizontal_background (
+ int, int, int *, bool);
+
+ struct Pattern10MapKey {
+ Pattern10MapKey (
+ int w, int h,
+ float stp0, float stp1, float stp2, float stp3,
+ int c0, int c1, int c2, int c3,
+ int c4, int c5, int c6, int c7,
+ int c8, int c9, int st
+ )
+ : dim(w, h)
+ , stp(stp0, stp1, stp2, stp3)
+ , cols(c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)
+ , style(st)
+ {}
+ inline bool operator<(const Pattern10MapKey& rhs) const {
+ return (dim < rhs.dim)
+ || (dim == rhs.dim && stp < rhs.stp)
+ || (dim == rhs.dim && stp == rhs.stp && cols < rhs.cols)
+ || (dim == rhs.dim && stp == rhs.stp && cols == rhs.cols && style < rhs.style);
+ }
+ boost::tuple<int, int> dim;
+ boost::tuple<float, float, float, float> stp;
+ boost::tuple<int, int, int, int, int, int, int, int, int, int> cols;
+ int style;
+ };
+ typedef std::map<Pattern10MapKey, Cairo::RefPtr<Cairo::Pattern> > Pattern10Map;
+
+ struct PatternBgMapKey {
+ PatternBgMapKey (int w, int h, int c0, int c1, bool shade)
+ : dim(w, h)
+ , cols(c0, c1)
+ , sh(shade)
+ {}
+ inline bool operator<(const PatternBgMapKey& rhs) const {
+ return (dim < rhs.dim) || (dim == rhs.dim && cols < rhs.cols) || (dim == rhs.dim && cols == rhs.cols && (sh && !rhs.sh));
+ }
+ boost::tuple<int, int> dim;
+ boost::tuple<int, int> cols;
+ bool sh;
+ };
+ typedef std::map<PatternBgMapKey, Cairo::RefPtr<Cairo::Pattern> > PatternBgMap;
+
+ static Pattern10Map vm_pattern_cache;
+ static PatternBgMap vb_pattern_cache;
+ static Pattern10Map hm_pattern_cache;
+ static PatternBgMap hb_pattern_cache;
+ static int min_pattern_metric_size; // min dimension for axis that displays the meter level
+ static int max_pattern_metric_size; // max dimension for axis that displays the meter level
+};
+
+
+} /* namespace */
+
+#endif
diff --git a/libs/widgets/widgets/focus_entry.h b/libs/widgets/widgets/focus_entry.h
new file mode 100644
index 0000000000..f0fa22232b
--- /dev/null
+++ b/libs/widgets/widgets/focus_entry.h
@@ -0,0 +1,43 @@
+/*
+ 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_FOCUS_ENTRY_H_
+#define _WIDGETS_FOCUS_ENTRY_H_
+
+#include <gtkmm/entry.h>
+
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API FocusEntry : public Gtk::Entry
+{
+public:
+ FocusEntry ();
+
+protected:
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+private:
+ bool next_release_selects;
+};
+
+} /* end namespace */
+
+#endif
diff --git a/libs/widgets/widgets/searchbar.h b/libs/widgets/widgets/searchbar.h
new file mode 100644
index 0000000000..2b3957f38f
--- /dev/null
+++ b/libs/widgets/widgets/searchbar.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <gtkmm/entry.h>
+#include <string>
+
+#include "widgets/visibility.h"
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API SearchBar : public Gtk::Entry
+{
+public:
+ SearchBar (
+ const std::string& placeholder_text = "Search...",
+ bool icon_click_resets = true);
+
+ /** resets the searchbar to the initial state */
+ void reset ();
+
+ /* emitted when the filter has been updated */
+ sigc::signal<void, const std::string&> signal_search_string_updated () { return sig_search_string_updated; }
+
+protected:
+ bool focus_in_event (GdkEventFocus*);
+ bool focus_out_event (GdkEventFocus*);
+
+ bool key_press_event (GdkEventKey*);
+ void icon_clicked_event (Gtk::EntryIconPosition, const GdkEventButton*);
+
+ const std::string placeholder_text;
+ sigc::signal<void, const std::string&> sig_search_string_updated;
+
+private:
+ void search_string_changed () const;
+
+ Glib::RefPtr<Gdk::Pixbuf> icon;
+ bool icon_click_resets;
+};
+
+} /* end namespace */
diff --git a/libs/widgets/widgets/slider_controller.h b/libs/widgets/widgets/slider_controller.h
new file mode 100644
index 0000000000..e80e76d7bc
--- /dev/null
+++ b/libs/widgets/widgets/slider_controller.h
@@ -0,0 +1,81 @@
+/*
+ Copyright (C) 1998-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_SLIDER_CONTROLLER_H_
+#define _WIDGETS_SLIDER_CONTROLLER_H_
+
+#ifdef interface
+#undef interface
+#endif
+
+#include <gtkmm.h>
+#include <boost/shared_ptr.hpp>
+
+#include "gtkmm2ext/popup.h"
+#include "gtkmm2ext/binding_proxy.h"
+
+#include "widgets/ardour_fader.h"
+#include "widgets/visibility.h"
+
+namespace PBD {
+ class Controllable;
+}
+
+namespace ArdourWidgets {
+
+class LIBWIDGETS_API SliderController : public ArdourWidgets::ArdourFader
+{
+public:
+ SliderController (Gtk::Adjustment* adj, boost::shared_ptr<PBD::Controllable> mc, int orientation, int, int);
+
+ virtual ~SliderController () {}
+
+ Gtk::SpinButton& get_spin_button () { assert(_ctrl); return _spin; }
+ void set_controllable (boost::shared_ptr<PBD::Controllable> c) { _binding_proxy.set_controllable (c); }
+
+protected:
+ bool on_button_press_event (GdkEventButton *ev);
+ bool on_enter_notify_event (GdkEventCrossing* ev);
+ bool on_leave_notify_event (GdkEventCrossing* ev);
+ void ctrl_adjusted();
+ void spin_adjusted();
+
+ BindingProxy _binding_proxy;
+ boost::shared_ptr<PBD::Controllable> _ctrl;
+ Gtk::Adjustment *_ctrl_adj;
+ Gtk::Adjustment _spin_adj;
+ Gtk::SpinButton _spin;
+ bool _ctrl_ignore;
+ bool _spin_ignore;
+};
+
+class LIBWIDGETS_API VSliderController : public SliderController
+{
+public:
+ VSliderController (Gtk::Adjustment *adj, boost::shared_ptr<PBD::Controllable> mc, int, int);
+};
+
+class LIBWIDGETS_API HSliderController : public SliderController
+{
+public:
+ HSliderController (Gtk::Adjustment *adj, boost::shared_ptr<PBD::Controllable> mc, int, int);
+};
+
+}; /* namespace */
+
+#endif
diff --git a/libs/widgets/wscript b/libs/widgets/wscript
index 6432589177..dd2ee9587b 100644
--- a/libs/widgets/wscript
+++ b/libs/widgets/wscript
@@ -29,9 +29,17 @@ widgets_sources = [
'ardour_button.cc',
'ardour_display.cc',
'ardour_dropdown.cc',
+ 'ardour_fader.cc',
'ardour_knob.cc',
'ardour_spacer.cc',
'ardour_spinner.cc',
+ 'auto_spin.cc',
+ 'barcontroller.cc',
+ 'click_box.cc',
+ 'fastmeter.cc',
+ 'focus_entry.cc',
+ 'searchbar.cc',
+ 'slider_controller.cc',
'tooltips.cc',
'ui_config.cc',
]