#!/usr/bin/perl

use warnings;
use strict;
use List::Util qw/min max/;
use Cairo;
use Glib qw(TRUE FALSE);
use Gtk3 -init;

my $button_x = 0;
my $button_y = 0;
my $button_down = 0;
my $selection = {x => -10, y => -10, width => 0, height => 0};
my $image_rect = {width => 1000, height => 1000};
my ($sb_selector_x, $sb_selector_y, $sb_selector_w, $sb_selector_h);
my $show_selection = FALSE;

system("convert -geometry $image_rect->{width}x$image_rect->{height} rose: test.png");
my $image = Cairo::ImageSurface->create_from_png("test.png");
my $window = Gtk3::Window->new('toplevel');
$window->set_size_request (500, 500);

$window->signal_connect('delete-event' => sub{Gtk3->main_quit});
my $canvas = Gtk3::DrawingArea->new;
$window->add($canvas);
$canvas->signal_connect(draw => \&_draw);
$canvas->signal_connect('button-press-event' => \&_button_pressed);
$canvas->signal_connect('button-release-event' => \&_button_released);
$canvas->signal_connect('motion-notify-event' => \&_motion);
$canvas->set_app_paintable(TRUE);
$canvas->add_events(
    Glib::Object::Introspection->convert_sv_to_flags ( 'Gtk3::Gdk::EventMask', 'exposure-mask') |
        Glib::Object::Introspection->convert_sv_to_flags ( 'Gtk3::Gdk::EventMask', 'button-press-mask') |
            Glib::Object::Introspection->convert_sv_to_flags ( 'Gtk3::Gdk::EventMask', 'button-release-mask') |
                Glib::Object::Introspection->convert_sv_to_flags ( 'Gtk3::Gdk::EventMask', 'pointer-motion-mask') );

$window->show_all;
my $selector = selection($window);
Gtk3->main;
unlink 'test.png';

sub _button_pressed {
    my ($widget, $event) = @_;
    # left mouse button
    if ($event->button != 1) { return FALSE }

    ($button_x, $button_y) = ($event->x, $event->y);
    $button_down = TRUE;

    $widget->get_window()->invalidate_rect($selection, FALSE);
    return TRUE;
}

sub _button_released {
    my ($widget, $event) = @_;
    $button_down = FALSE;
    $show_selection = FALSE;
}

sub _motion {
    my ($widget, $event) = @_;
    if (not $button_down) { return FALSE }

    # calculate the rectangle, give it the right orientation
    my ($x1, $y1) = ($button_x, $button_y);
    my ($x2, $y2) = ($event->x, $event->y);
    my $x = int(min($x1, $x2));
    my $y = int(min($y1, $y2));
    my $w = int(abs($x1-$x2));
    my $h = int(abs($y1-$y2));
    update_selector($widget, {x => $x, y => $y, width => $w, height => $h});
}

sub _draw {
    my ($widget, $context) = @_;

    # Create pixbuf
    $context->set_source_surface($image, 0, 0);
    $context->paint;

    if ($show_selection) {
        # to get a non-antialiased rubber band rectangle,
        # stroke needs half-integer coordinates,
        # and fill/clip integer coordinates to be sharp.
        my ($x, $y, $w, $h) = ($selection->{x}, $selection->{y},
                               $selection->{width}, $selection->{height});
        if ($w <= 0 or $h <= 0) { return TRUE }

        $context->set_line_width(1);
        $context->set_source_rgb(0, 0, 0);

        $context->rectangle($x+1, $y+1, $w-2, $h-2);
        $context->set_source_rgba(0.2, 0.6, 0.8, 0.2);
        $context->fill();

        $context->rectangle($x+0.5, $y+0.5, $w-1, $h-1);
        $context->set_source_rgba(0.2, 0.6, 0.8, 0.35);
        $context->set_line_width(1);
        $context->stroke();
    }

    # suppress default expose handler
    return TRUE;
}

sub selection {
    my ($parent) = @_;

    my $window = Gtk3::Dialog->new;
    $window->set('transient-for', $parent);
    $window->set(title            => 'Selection');
    my $vbox = $window->get_content_area;

    # table for nice layout
    my $grid = Gtk3::Grid->new;
    $vbox->pack_start( $grid, TRUE, TRUE, 0 );

    # SpinButton for x
    my $hbox = Gtk3::HBox->new;
    my ( $row, $desc, $widget, $units ) = ( 0, 0, 1, 3 );
    $grid->attach( $hbox, $desc, $row, $desc + 1, $row + 1 );
    my $label = Gtk3::Label->new('x');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $widget, $row, $widget + 1, $row + 1 );
    $sb_selector_x =
      Gtk3::SpinButton->new_with_range( 0, $image_rect->{width}, 1 );
    $hbox->pack_end( $sb_selector_x, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $units, $row, $units + 1, $row + 1 );
    $label = Gtk3::Label->new('pixels');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );

    # SpinButton for y
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $desc, ++$row, $desc + 1, $row + 1 );
    $label = Gtk3::Label->new('y');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $widget, $row, $widget + 1, $row + 1 );
    $sb_selector_y =
      Gtk3::SpinButton->new_with_range( 0, $image_rect->{height}, 1 );
    $hbox->pack_end( $sb_selector_y, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $units, $row, $units + 1, $row + 1 );
    $label = Gtk3::Label->new('pixels');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );

    # SpinButton for w
    $row += 1; # don't know why this is necessary
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $desc, ++$row, $desc + 1, $row + 1 );
    $label = Gtk3::Label->new('Width');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $widget, $row, $widget + 1, $row + 1 );
    $sb_selector_w =
      Gtk3::SpinButton->new_with_range( 0, $image_rect->{width}, 1 );
    $hbox->pack_end( $sb_selector_w, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $units, $row, $units + 1, $row + 1 );
    $label = Gtk3::Label->new('pixels');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );

    # SpinButton for h
    $row += 3; # don't know why this is necessary
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $desc, ++$row, $desc + 1, $row + 1 );
    $label = Gtk3::Label->new('Height');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $widget, $row, $widget + 1, $row + 1 );
    $sb_selector_h =
      Gtk3::SpinButton->new_with_range( 0, $image_rect->{height}, 1 );
    $hbox->pack_end( $sb_selector_h, FALSE, TRUE, 0 );
    $hbox = Gtk3::HBox->new;
    $grid->attach( $hbox, $units, $row, $units + 1, $row + 1 );
    $label = Gtk3::Label->new('pixels');
    $hbox->pack_start( $label, FALSE, TRUE, 0 );

    # Callbacks if the spinbuttons change
    $sb_selector_x->{value_changed_signal} = $sb_selector_x->signal_connect(
        'value-changed' => sub {
            my %new_selection = %{$selection};
            $new_selection{x} = $sb_selector_x->get_value;
            $sb_selector_w->set_range( 0, $image_rect->{width} - $new_selection{x} );
            update_selector($canvas, \%new_selection);
        }
    );
    $sb_selector_y->{value_changed_signal} = $sb_selector_y->signal_connect(
        'value-changed' => sub {
            my %new_selection = %{$selection};
            $new_selection{y} = $sb_selector_y->get_value;
            $sb_selector_h->set_range( 0, $image_rect->{height} - $new_selection{y} );
            update_selector($canvas, \%new_selection);
        }
    );
    $sb_selector_w->{value_changed_signal} = $sb_selector_w->signal_connect(
        'value-changed' => sub {
            my %new_selection = %{$selection};
            $new_selection{width} = $sb_selector_w->get_value;
            $sb_selector_x->set_range( 0, $image_rect->{width} - $new_selection{width} );
            update_selector($canvas, \%new_selection);
        }
    );
    $sb_selector_h->{value_changed_signal} = $sb_selector_h->signal_connect(
        'value-changed' => sub {
            my %new_selection = %{$selection};
            $new_selection{height} = $sb_selector_h->get_value;
            $sb_selector_y->set_range( 0, $image_rect->{height} - $new_selection{height} );
            update_selector($canvas, \%new_selection);
        }
    );

    $window->show_all;
    return;
}

sub update_selector {
    my ($widget, $new_selection) = @_;

    $sb_selector_x->signal_handler_block(
        $sb_selector_x->{value_changed_signal});
    $sb_selector_y->signal_handler_block(
        $sb_selector_y->{value_changed_signal});
    $sb_selector_w->signal_handler_block(
        $sb_selector_w->{value_changed_signal});
    $sb_selector_h->signal_handler_block(
        $sb_selector_h->{value_changed_signal});

    $sb_selector_x->set_value( $new_selection->{x} );
    $sb_selector_y->set_value( $new_selection->{y} );
    $sb_selector_w->set_value( $new_selection->{width} );
    $sb_selector_h->set_value( $new_selection->{height} );

    $sb_selector_x->signal_handler_unblock(
        $sb_selector_x->{value_changed_signal});
    $sb_selector_y->signal_handler_unblock(
        $sb_selector_y->{value_changed_signal});
    $sb_selector_w->signal_handler_unblock(
        $sb_selector_w->{value_changed_signal});
    $sb_selector_h->signal_handler_unblock(
        $sb_selector_h->{value_changed_signal});

    my $old = $selection;
    $selection = $new_selection;
    my $win = $widget->get_window();
    $show_selection = TRUE;
    for my $rect (($selection, $old)) {
        $win->invalidate_rect($rect, FALSE)
    }
    return;
}

