summaryrefslogtreecommitdiff
path: root/src/editing_tools/RGBHistogramManipulator.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/editing_tools/RGBHistogramManipulator.vala')
-rw-r--r--src/editing_tools/RGBHistogramManipulator.vala311
1 files changed, 311 insertions, 0 deletions
diff --git a/src/editing_tools/RGBHistogramManipulator.vala b/src/editing_tools/RGBHistogramManipulator.vala
new file mode 100644
index 0000000..4b0a8a2
--- /dev/null
+++ b/src/editing_tools/RGBHistogramManipulator.vala
@@ -0,0 +1,311 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+public class RGBHistogramManipulator : Gtk.DrawingArea {
+ private enum LocationCode { LEFT_NUB, RIGHT_NUB, LEFT_TROUGH, RIGHT_TROUGH,
+ INSENSITIVE_AREA }
+ private const int NUB_SIZE = 13;
+ private const int NUB_HALF_WIDTH = NUB_SIZE / 2;
+ private const int NUB_V_NUDGE = 4;
+ private const int TROUGH_WIDTH = 256 + (2 * NUB_HALF_WIDTH);
+ private const int TROUGH_HEIGHT = 4;
+ private const int TROUGH_BOTTOM_OFFSET = 1;
+ private const int CONTROL_WIDTH = TROUGH_WIDTH + 2;
+ private const int CONTROL_HEIGHT = 118;
+ private const int NUB_V_POSITION = CONTROL_HEIGHT - TROUGH_HEIGHT - TROUGH_BOTTOM_OFFSET
+ - (NUB_SIZE - TROUGH_HEIGHT) / 2 - NUB_V_NUDGE - 2;
+ private int left_nub_max = 255 - NUB_SIZE - 1;
+ private int right_nub_min = NUB_SIZE + 1;
+
+ private static Gtk.WidgetPath slider_draw_path = new Gtk.WidgetPath();
+ private static Gtk.WidgetPath frame_draw_path = new Gtk.WidgetPath();
+ private static bool paths_setup = false;
+
+ private RGBHistogram histogram = null;
+ private int left_nub_position = 0;
+ private int right_nub_position = 255;
+ private bool is_left_nub_tracking = false;
+ private bool is_right_nub_tracking = false;
+ private int track_start_x = 0;
+ private int track_nub_start_position = 0;
+ private int offset = 0;
+
+ public RGBHistogramManipulator( ) {
+ set_size_request(CONTROL_WIDTH, CONTROL_HEIGHT);
+ can_focus = true;
+
+ if (!paths_setup) {
+ slider_draw_path.append_type(typeof(Gtk.Scale));
+ slider_draw_path.iter_add_class(0, "scale");
+ slider_draw_path.iter_add_class(0, "range");
+
+ frame_draw_path.append_type(typeof(Gtk.Frame));
+ frame_draw_path.iter_add_class(0, "default");
+
+ paths_setup = true;
+ }
+
+ add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
+ add_events(Gdk.EventMask.BUTTON_RELEASE_MASK);
+ add_events(Gdk.EventMask.BUTTON_MOTION_MASK);
+ add_events(Gdk.EventMask.FOCUS_CHANGE_MASK);
+ add_events(Gdk.EventMask.KEY_PRESS_MASK);
+
+ button_press_event.connect(on_button_press);
+ button_release_event.connect(on_button_release);
+ motion_notify_event.connect(on_button_motion);
+
+ this.size_allocate.connect(on_size_allocate);
+ }
+
+ private void on_size_allocate(Gtk.Allocation region) {
+ this.offset = (region.width - RGBHistogram.GRAPHIC_WIDTH - NUB_SIZE) / 2;
+ }
+
+ private LocationCode hit_test_point(int x, int y) {
+ if (y < NUB_V_POSITION)
+ return LocationCode.INSENSITIVE_AREA;
+
+ if ((x > left_nub_position) && (x < left_nub_position + NUB_SIZE))
+ return LocationCode.LEFT_NUB;
+
+ if ((x > right_nub_position) && (x < right_nub_position + NUB_SIZE))
+ return LocationCode.RIGHT_NUB;
+
+ if (y < (NUB_V_POSITION + NUB_V_NUDGE + 1))
+ return LocationCode.INSENSITIVE_AREA;
+
+ if ((x - left_nub_position) * (x - left_nub_position) <
+ (x - right_nub_position) * (x - right_nub_position))
+ return LocationCode.LEFT_TROUGH;
+ else
+ return LocationCode.RIGHT_TROUGH;
+ }
+
+ private bool on_button_press(Gdk.EventButton event_record) {
+ // Adjust mouse position to drawing offset
+ // Easier to modify the event and shit the whole drawing then adjusting the nub drawing code
+ event_record.x -= this.offset;
+ LocationCode loc = hit_test_point((int) event_record.x, (int) event_record.y);
+ bool retval = true;
+
+ switch (loc) {
+ case LocationCode.LEFT_NUB:
+ track_start_x = ((int) event_record.x);
+ track_nub_start_position = left_nub_position;
+ is_left_nub_tracking = true;
+ break;
+
+ case LocationCode.RIGHT_NUB:
+ track_start_x = ((int) event_record.x);
+ track_nub_start_position = right_nub_position;
+ is_right_nub_tracking = true;
+ break;
+
+ case LocationCode.LEFT_TROUGH:
+ left_nub_position = ((int) event_record.x) - NUB_HALF_WIDTH;
+ left_nub_position = left_nub_position.clamp(0, left_nub_max);
+ force_update();
+ nub_position_changed();
+ update_nub_extrema();
+ break;
+
+ case LocationCode.RIGHT_TROUGH:
+ right_nub_position = ((int) event_record.x) - NUB_HALF_WIDTH;
+ right_nub_position = right_nub_position.clamp(right_nub_min, 255);
+ force_update();
+ nub_position_changed();
+ update_nub_extrema();
+ break;
+
+ default:
+ retval = false;
+ break;
+ }
+
+ // Remove adjustment position to drawing offset
+ event_record.x += this.offset;
+
+ return retval;
+ }
+
+ private bool on_button_release(Gdk.EventButton event_record) {
+ if (is_left_nub_tracking || is_right_nub_tracking) {
+ nub_position_changed();
+ update_nub_extrema();
+ }
+
+ is_left_nub_tracking = false;
+ is_right_nub_tracking = false;
+
+ return false;
+ }
+
+ private bool on_button_motion(Gdk.EventMotion event_record) {
+ if ((!is_left_nub_tracking) && (!is_right_nub_tracking))
+ return false;
+
+ event_record.x -= this.offset;
+ if (is_left_nub_tracking) {
+ int track_x_delta = ((int) event_record.x) - track_start_x;
+ left_nub_position = (track_nub_start_position + track_x_delta);
+ left_nub_position = left_nub_position.clamp(0, left_nub_max);
+ } else { /* right nub is tracking */
+ int track_x_delta = ((int) event_record.x) - track_start_x;
+ right_nub_position = (track_nub_start_position + track_x_delta);
+ right_nub_position = right_nub_position.clamp(right_nub_min, 255);
+ }
+
+ force_update();
+ event_record.x += this.offset;
+
+ return true;
+ }
+
+ public override bool focus_out_event(Gdk.EventFocus event) {
+ if (base.focus_out_event(event)) {
+ return true;
+ }
+
+ queue_draw();
+
+ return false;
+ }
+
+ public override bool key_press_event(Gdk.EventKey event) {
+ if (base.key_press_event(event)) {
+ return true;
+ }
+
+ int delta = 0;
+
+ if (event.keyval == Gdk.Key.Left || event.keyval == Gdk.Key.Up) {
+ delta = -1;
+ }
+
+ if (event.keyval == Gdk.Key.Right || event.keyval == Gdk.Key.Down) {
+ delta = 1;
+ }
+
+ if (!(Gdk.ModifierType.CONTROL_MASK in event.state)) {
+ delta *= 5;
+ }
+
+ if (delta == 0) {
+ return false;
+ }
+
+ if (Gdk.ModifierType.SHIFT_MASK in event.state) {
+ right_nub_position += delta;
+ right_nub_position = right_nub_position.clamp(right_nub_min, 255);
+ } else {
+ left_nub_position += delta;
+ left_nub_position = left_nub_position.clamp(0, left_nub_max);
+
+ }
+
+ nub_position_changed();
+ update_nub_extrema();
+ force_update();
+
+ return true;
+ }
+
+ public override bool draw(Cairo.Context ctx) {
+ Gtk.Border padding = get_style_context().get_padding(Gtk.StateFlags.NORMAL);
+
+ Gdk.Rectangle area = Gdk.Rectangle();
+ area.x = padding.left + this.offset;
+ area.y = padding.top;
+ area.width = RGBHistogram.GRAPHIC_WIDTH + padding.right;
+ area.height = RGBHistogram.GRAPHIC_HEIGHT + padding.bottom;
+
+ if (has_focus) {
+ get_style_context().render_focus(ctx, area.x, area.y,
+ area.width + NUB_SIZE,
+ area.height + NUB_SIZE + NUB_HALF_WIDTH);
+ }
+
+ draw_histogram(ctx, area);
+ draw_nub(ctx, area, left_nub_position);
+ draw_nub(ctx, area, right_nub_position);
+
+ return true;
+ }
+
+ private void draw_histogram(Cairo.Context ctx, Gdk.Rectangle area) {
+ if (histogram == null)
+ return;
+
+ var histogram_graphic = histogram.get_graphic();
+
+ Gdk.cairo_set_source_pixbuf(ctx, histogram_graphic, area.x + NUB_HALF_WIDTH, area.y + 2);
+ ctx.paint();
+
+ if (left_nub_position > 0) {
+ ctx.rectangle(area.x + NUB_HALF_WIDTH, area.y + 2,
+ left_nub_position,
+ histogram_graphic.height);
+ ctx.set_source_rgba(0.0, 0.0, 0.0, 0.45);
+ ctx.fill();
+ }
+
+ if (right_nub_position < 255) {
+ ctx.rectangle(area.x + right_nub_position + NUB_HALF_WIDTH,
+ area.y + 2,
+ histogram_graphic.width - right_nub_position,
+ histogram_graphic.height);
+ ctx.set_source_rgba(1.0, 1.0, 1.0, 0.45);
+ ctx.fill();
+ }
+ }
+
+ private void draw_nub(Cairo.Context ctx, Gdk.Rectangle area, int position) {
+ ctx.move_to(area.x + position, area.y + NUB_V_POSITION + NUB_SIZE);
+ ctx.line_to(area.x + position + NUB_HALF_WIDTH, area.y + NUB_V_POSITION);
+ ctx.line_to(area.x + position + NUB_SIZE, area.y + NUB_V_POSITION + NUB_SIZE);
+ ctx.close_path();
+ ctx.set_source_rgb(0.333, 0.333, 0.333);
+ ctx.fill();
+ }
+
+ private void force_update() {
+ get_window().invalidate_rect(null, true);
+ }
+
+ private void update_nub_extrema() {
+ right_nub_min = left_nub_position + NUB_SIZE + 1;
+ left_nub_max = right_nub_position - NUB_SIZE - 1;
+ }
+
+ public signal void nub_position_changed();
+
+ public void update_histogram(Gdk.Pixbuf source_pixbuf) {
+ histogram = new RGBHistogram(source_pixbuf);
+ force_update();
+ }
+
+ public int get_left_nub_position() {
+ return left_nub_position;
+ }
+
+ public int get_right_nub_position() {
+ return right_nub_position;
+ }
+
+ public void set_left_nub_position(int user_nub_pos) {
+ assert ((user_nub_pos >= 0) && (user_nub_pos <= 255));
+ left_nub_position = user_nub_pos.clamp(0, left_nub_max);
+ update_nub_extrema();
+ }
+
+ public void set_right_nub_position(int user_nub_pos) {
+ assert ((user_nub_pos >= 0) && (user_nub_pos <= 255));
+ right_nub_position = user_nub_pos.clamp(right_nub_min, 255);
+ update_nub_extrema();
+ }
+}
+