RGBHistogramManipulator.vala 10.5 KB
Newer Older
1
/* Copyright 2016 Software Freedom Conservancy Inc.
2 3
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
4
 * See the COPYING file in this distribution.
5 6 7 8 9 10 11
 */

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;
12
    private const int NUB_V_NUDGE = 4;
13
    private const int TROUGH_WIDTH = 256 + (2 * NUB_HALF_WIDTH);
14
    private const int TROUGH_HEIGHT = 4;
15 16 17 18
    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
19
        - (NUB_SIZE - TROUGH_HEIGHT) / 2 - NUB_V_NUDGE - 2;
20 21 22
    private int left_nub_max = 255 - NUB_SIZE - 1;
    private int right_nub_min = NUB_SIZE + 1;

23 24 25
    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;
26 27 28 29 30 31 32 33

    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;
Jens Georg's avatar
Jens Georg committed
34
    private int offset = 0;
35 36 37

    public RGBHistogramManipulator( ) {
        set_size_request(CONTROL_WIDTH, CONTROL_HEIGHT);
38
        can_focus = true;
39 40 41 42 43 44 45 46 47 48 49 50
        
        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;
        }
            
51 52 53
        add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
        add_events(Gdk.EventMask.BUTTON_RELEASE_MASK);
        add_events(Gdk.EventMask.BUTTON_MOTION_MASK);
54 55
        add_events(Gdk.EventMask.FOCUS_CHANGE_MASK);
        add_events(Gdk.EventMask.KEY_PRESS_MASK);
56

57 58 59
        button_press_event.connect(on_button_press);
        button_release_event.connect(on_button_release);
        motion_notify_event.connect(on_button_motion);
Jens Georg's avatar
Jens Georg committed
60 61

        this.size_allocate.connect(on_size_allocate);
62
    }
Jens Georg's avatar
Jens Georg committed
63 64 65 66 67

    private void on_size_allocate(Gtk.Allocation region) {
        this.offset = (region.width - RGBHistogram.GRAPHIC_WIDTH - NUB_SIZE) / 2;
    }

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    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) {
Jens Georg's avatar
Jens Georg committed
89 90 91
        // 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;
92
        LocationCode loc = hit_test_point((int) event_record.x, (int) event_record.y);
Jens Georg's avatar
Jens Georg committed
93
        bool retval = true;
94 95 96 97 98 99

        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;
Jens Georg's avatar
Jens Georg committed
100
                break;
101 102 103 104 105

            case LocationCode.RIGHT_NUB:
                track_start_x = ((int) event_record.x);
                track_nub_start_position = right_nub_position;
                is_right_nub_tracking = true;
Jens Georg's avatar
Jens Georg committed
106
                break;
107 108 109 110 111 112 113

            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();
Jens Georg's avatar
Jens Georg committed
114
                break;
115 116 117 118 119 120 121

            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();
Jens Georg's avatar
Jens Georg committed
122
                break;
123 124

            default:
Jens Georg's avatar
Jens Georg committed
125 126
                retval = false;
                break;
127
        }
Jens Georg's avatar
Jens Georg committed
128 129 130 131 132

        // Remove adjustment position to drawing offset
        event_record.x += this.offset;

        return retval;
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    }
    
    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;
    
Jens Georg's avatar
Jens Georg committed
151
        event_record.x -= this.offset;
152 153 154 155 156 157 158 159 160 161 162
        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();
Jens Georg's avatar
Jens Georg committed
163 164
        event_record.x += this.offset;

165 166
        return true;
    }
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

    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;
    }
216
    
Jim Nelson's avatar
Jim Nelson committed
217
    public override bool draw(Cairo.Context ctx) {
218
        Gtk.Border padding = get_style_context().get_padding(Gtk.StateFlags.NORMAL);
219

Jim Nelson's avatar
Jim Nelson committed
220
        Gdk.Rectangle area = Gdk.Rectangle();
Jens Georg's avatar
Jens Georg committed
221
        area.x = padding.left + this.offset;
Jim Nelson's avatar
Jim Nelson committed
222
        area.y = padding.top;
223 224 225
        area.width = RGBHistogram.GRAPHIC_WIDTH + padding.right;
        area.height = RGBHistogram.GRAPHIC_HEIGHT + padding.bottom;

226 227 228 229 230 231
        if (has_focus) {
            get_style_context().render_focus(ctx, area.x, area.y,
                                             area.width + NUB_SIZE,
                                             area.height + NUB_SIZE + NUB_HALF_WIDTH);
        }

232
        draw_histogram(ctx, area);
Jim Nelson's avatar
Jim Nelson committed
233 234
        draw_nub(ctx, area, left_nub_position);
        draw_nub(ctx, area, right_nub_position);
235

236 237 238
        return true;
    }
    
Jim Nelson's avatar
Jim Nelson committed
239
    private void draw_histogram(Cairo.Context ctx, Gdk.Rectangle area) {
240 241 242
        if (histogram == null)
            return;

243
        var histogram_graphic = histogram.get_graphic();
244

245 246
        Gdk.cairo_set_source_pixbuf(ctx, histogram_graphic, area.x + NUB_HALF_WIDTH, area.y + 2);
        ctx.paint();
247

248 249 250 251 252 253 254
        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();
        }
255 256

        if (right_nub_position < 255) {
257 258 259 260 261 262
            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();
263 264
        }
    }
265

Jim Nelson's avatar
Jim Nelson committed
266
    private void draw_nub(Cairo.Context ctx, Gdk.Rectangle area, int position) {
267 268 269 270 271 272
        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();
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
    }
    
    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();
    }
}