/* cqcam - Color Quickcam image capture programs
 * Copyright (C) 1996-1998 by Patrick Reynolds
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* widget for allowing a user to select a rectangular subset of a
 * rectangular area.  In gtkcam, it is used to set the top, left, width,
 * and height properties of the camera.
 */

#include <stdlib.h>
#ifndef __PRETTY_FUNCTION__        /* GTK+ workaround for gcc1 */
#define __PRETTY_FUNCTION__ ""
#endif
#include <gtk/gtksignal.h>

#include "gtkimagesize.h"

#define MIN_WIDTH 32
#define MIN_HEIGHT 24
#define HANDLE_SIZE 8

#define HANDLE_TL 1
#define HANDLE_TR 2
#define HANDLE_BL 3
#define HANDLE_BR 4
#define HANDLE_BOX 5

enum {
  VALUE_CHANGED,
  LAST_SIGNAL
};

static guint image_size_signals[LAST_SIGNAL] = { 0 };

static void gtk_image_size_class_init     (GtkImageSizeClass *klass);
static void gtk_image_size_init           (GtkImageSize      *isz);
static void gtk_image_size_realize        (GtkWidget         *widget);
static void gtk_image_size_size_allocate  (GtkWidget         *widget,
                                           GtkAllocation     *allocation);
static gint gtk_image_size_expose         (GtkWidget         *widget,
                                           GdkEventExpose    *event);
static gint gtk_image_size_button_press   (GtkWidget         *widget,
                                           GdkEventButton    *event);
static gint gtk_image_size_button_release (GtkWidget         *widget,
                                           GdkEventButton    *event);
static gint gtk_image_size_motion_notify  (GtkWidget         *widget,
                                           GdkEventMotion    *event);

guint gtk_image_size_get_type() {
  static guint image_size_type = 0;

  if (!image_size_type) {
    GtkTypeInfo image_size_info = {
      "GtkImageSize",
      sizeof(GtkImageSize),
      sizeof(GtkImageSizeClass),
      (GtkClassInitFunc)gtk_image_size_class_init,
      (GtkObjectInitFunc)gtk_image_size_init,
      (GtkArgSetFunc)NULL,
      (GtkArgGetFunc)NULL
    };
    
    image_size_type = gtk_type_unique(gtk_widget_get_type(), &image_size_info);
  }
  
  return image_size_type;
}

static void gtk_image_size_class_init(GtkImageSizeClass *class) {
  GtkWidgetClass *widget_class = (GtkWidgetClass*)class;

  image_size_signals[VALUE_CHANGED] =
    gtk_signal_new("value_changed",
      GTK_RUN_FIRST | GTK_RUN_NO_RECURSE,
      GTK_OBJECT_CLASS(class)->type,
      GTK_SIGNAL_OFFSET(GtkImageSizeClass, value_changed),
      gtk_signal_default_marshaller,
      GTK_TYPE_NONE, 0);
  gtk_object_class_add_signals(GTK_OBJECT_CLASS(class), image_size_signals,
    LAST_SIGNAL);

  widget_class->realize = gtk_image_size_realize;
  widget_class->size_allocate = gtk_image_size_size_allocate;
  widget_class->expose_event = gtk_image_size_expose;
  widget_class->button_press_event = gtk_image_size_button_press;
  widget_class->button_release_event = gtk_image_size_button_release;
  widget_class->motion_notify_event = gtk_image_size_motion_notify;
  
  class->value_changed = NULL;
}

static void gtk_image_size_init(GtkImageSize *isz) {
#ifdef HAVE_GTK_1_0
  GTK_WIDGET_SET_FLAGS(isz, GTK_BASIC);
#endif
  
  isz->left = isz->top = isz->width = isz->height = isz->handle = 0;
  isz->max_width = MIN_WIDTH;
  isz->max_height = MIN_HEIGHT;
  
  gtk_widget_set_events(GTK_WIDGET(isz),
    GDK_EXPOSURE_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK);
}

GtkWidget *gtk_image_size_new(int max_width, int max_height) {
  GtkWidget *isz;

  g_return_val_if_fail(max_width >= MIN_WIDTH && max_width <= 640, NULL);
  g_return_val_if_fail(max_height >= MIN_HEIGHT && max_height <= 480, NULL);
  
  isz = gtk_type_new(gtk_image_size_get_type());
  GTK_WIDGET(isz)->requisition.width = GTK_IMAGE_SIZE(isz)->max_width = max_width;
  GTK_WIDGET(isz)->requisition.height = GTK_IMAGE_SIZE(isz)->max_height = max_height;
  
  return isz;
}

void gtk_image_size_set_size(GtkImageSize *isz, int max_width, int max_height) {
  g_return_if_fail(isz != NULL);
  g_return_if_fail(GTK_IS_IMAGE_SIZE(isz));
  g_return_if_fail(max_width >= MIN_WIDTH && max_width <= 640);
  g_return_if_fail(max_height >= MIN_HEIGHT && max_height <= 480);

  GTK_WIDGET(isz)->requisition.width = isz->max_width = max_width;
  GTK_WIDGET(isz)->requisition.height = isz->max_height = max_height;
  if (isz->left+isz->width-1 >= isz->max_width) {
    isz->width = isz->max_width - (isz->left+isz->width);
    if (isz->width < MIN_WIDTH) {
      isz->width = MIN_WIDTH;
      isz->left = isz->max_width - isz->width;
    }
    isz->height = isz->max_height - (isz->top+isz->height);
    if (isz->height < MIN_HEIGHT) {
      isz->height = MIN_HEIGHT;
      isz->top = isz->max_height - isz->height;
    }
  }
  gtk_widget_set_usize(GTK_WIDGET(isz), max_width, max_height);
}

void gtk_image_size_set(GtkImageSize *isz, int left, int top, int width, int height) {
  g_return_if_fail(isz != NULL);
  g_return_if_fail(GTK_IS_IMAGE_SIZE(isz));

  isz->left = left;
  isz->top = top;
  isz->width = width;
  isz->height = height;
  gtk_widget_queue_draw(GTK_WIDGET(isz));
}

void gtk_image_size_get(GtkImageSize *isz, int *left, int *top, int *width, int *height) {
  g_return_if_fail(isz != NULL);
  g_return_if_fail(GTK_IS_IMAGE_SIZE(isz));

  if (left) *left = isz->left;
  if (top) *top = isz->top;
  if (width) *width = isz->width;
  if (height) *height = isz->height;
}

static void gtk_image_size_realize(GtkWidget *widget) {
  GtkImageSize *isz;
  GdkWindowAttr attributes;
  gint attributes_mask;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_IMAGE_SIZE(widget));
  
  isz = GTK_IMAGE_SIZE(widget);
  GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
  
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual(widget);
  attributes.colormap = gtk_widget_get_colormap(widget);
  attributes.event_mask = gtk_widget_get_events(widget);
  attributes.event_mask |= GDK_EXPOSURE_MASK;
  
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  
  widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask);
  gdk_window_set_user_data(widget->window, isz);
  
  widget->style = gtk_style_attach(widget->style, widget->window);
  gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE);
}

static void gtk_image_size_size_allocate(GtkWidget *widget, GtkAllocation *allocation) {
  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_IMAGE_SIZE(widget));
  g_return_if_fail(allocation != NULL);

  widget->allocation = *allocation;
  
  if (GTK_WIDGET_REALIZED(widget))
    gdk_window_move_resize(widget->window,
      allocation->x, allocation->y, allocation->width, allocation->height);
}

static gint gtk_image_size_expose(GtkWidget *widget, GdkEventExpose *event) {
  GtkImageSize *isz;

  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_IMAGE_SIZE(widget), FALSE);
  g_return_val_if_fail(event != NULL, FALSE);
  
  if (GTK_WIDGET_DRAWABLE(widget)) {
    GdkGC *gc;
    GdkColor white, black;
    GdkRectangle clip;

    isz = GTK_IMAGE_SIZE(widget);
    
    clip.x = clip.y = 0;
    clip.width = isz->max_width;
    clip.height = isz->max_height;

    gc = gdk_gc_new(widget->window);
    gdk_color_white(gtk_widget_get_colormap(widget), &white);
    gdk_color_black(gtk_widget_get_colormap(widget), &black);
    gdk_gc_set_foreground(gc, &white);
    gdk_draw_rectangle(widget->window, gc, TRUE, 0, 0,
      isz->max_width, isz->max_height);
    gdk_gc_set_foreground(gc, &black);
    gdk_gc_set_clip_origin(gc, 0, 0);
    gdk_gc_set_clip_rectangle(gc, &clip);
    
    gdk_draw_rectangle(widget->window, gc, FALSE,
      isz->left - HANDLE_SIZE/2, isz->top - HANDLE_SIZE/2,
      HANDLE_SIZE, HANDLE_SIZE);
    gdk_draw_rectangle(widget->window, gc, FALSE,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top - HANDLE_SIZE/2,
      HANDLE_SIZE, HANDLE_SIZE);
    gdk_draw_rectangle(widget->window, gc, FALSE,
      isz->left - HANDLE_SIZE/2, isz->top+isz->height-1 - HANDLE_SIZE/2,
      HANDLE_SIZE, HANDLE_SIZE);
    gdk_draw_rectangle(widget->window, gc, FALSE,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top+isz->height-1 - HANDLE_SIZE/2,
      HANDLE_SIZE, HANDLE_SIZE);

    gdk_draw_line(widget->window, gc,
      isz->left + HANDLE_SIZE/2, isz->top,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top);
    gdk_draw_line(widget->window, gc,
      isz->left + HANDLE_SIZE/2, isz->top+isz->height-1,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top+isz->height-1);
    gdk_draw_line(widget->window, gc,
      isz->left, isz->top + HANDLE_SIZE/2,
      isz->left, isz->top+isz->height-1 - HANDLE_SIZE/2);
    gdk_draw_line(widget->window, gc,
      isz->left+isz->width-1, isz->top + HANDLE_SIZE/2,
      isz->left+isz->width-1, isz->top+isz->height-1 - HANDLE_SIZE/2);
      
    gdk_draw_line(widget->window, gc,
      isz->left + HANDLE_SIZE/2, isz->top + HANDLE_SIZE/2,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top+isz->height-1 - HANDLE_SIZE/2);
    gdk_draw_line(widget->window, gc,
      isz->left+isz->width-1 - HANDLE_SIZE/2, isz->top + HANDLE_SIZE/2,
      isz->left + HANDLE_SIZE/2, isz->top+isz->height-1 - HANDLE_SIZE/2);

    gdk_gc_destroy(gc);
  }
  
  return FALSE;
}

static gint gtk_image_size_button_press(GtkWidget *widget, GdkEventButton *event) {
  GtkImageSize *isz;
  
  if (event->button != 1)
    return FALSE;

  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_IMAGE_SIZE(widget), FALSE);
  g_return_val_if_fail(event != NULL, FALSE);

  isz = GTK_IMAGE_SIZE(widget);
  if (abs(event->x - isz->left) <= HANDLE_SIZE/2
    && abs(event->y - isz->top) <= HANDLE_SIZE/2)
    isz->handle = HANDLE_TL;
  else if (abs(event->x - (isz->left+isz->width-1)) <= HANDLE_SIZE/2
    && abs(event->y - isz->top) <= HANDLE_SIZE/2)
    isz->handle = HANDLE_TR;
  else if (abs(event->x - isz->left) <= HANDLE_SIZE/2
    && abs(event->y - (isz->top+isz->height-1)) <= HANDLE_SIZE/2)
    isz->handle = HANDLE_BL;
  else if (abs(event->x - (isz->left+isz->width-1)) <= HANDLE_SIZE/2
    && abs(event->y - (isz->top+isz->height-1)) <= HANDLE_SIZE/2)
    isz->handle = HANDLE_BR;
  else if (event->x >= isz->left && event->y >= isz->top &&
    event->x <= isz->left+isz->width-1 &&
    event->y <= isz->top+isz->height-1)
    isz->handle = HANDLE_BOX;
  else
    isz->handle = 0;

  isz->ox = event->x;
  isz->oy = event->y;
  
  return FALSE;
}

static gint gtk_image_size_button_release(GtkWidget *widget, GdkEventButton *event) {
  GtkImageSize *isz;

  if (event->button != 1)
    return FALSE;

  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_IMAGE_SIZE(widget), FALSE);
  g_return_val_if_fail(event != NULL, FALSE);

  isz = GTK_IMAGE_SIZE(widget);
  isz->handle = 0;  

  return FALSE;
}

static gint gtk_image_size_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
  GtkImageSize *isz;
  int dx, dy;

  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_IMAGE_SIZE(widget), FALSE);
  g_return_val_if_fail(event != NULL, FALSE);

  isz = GTK_IMAGE_SIZE(widget);
  if (event->x < 0) event->x = 0;
  if (event->y < 0) event->y = 0;
  if (event->x > isz->max_width) event->x = isz->max_width;
  if (event->y > isz->max_height) event->y = isz->max_height;
  dx = event->x - isz->ox;
  dy = event->y - isz->oy;
  isz->ox = event->x;
  isz->oy = event->y;
  if (isz->handle) {
    switch (isz->handle) {
      case HANDLE_TL:
        if (isz->left + dx < 0) dx = -isz->left;
        if (isz->top + dy < 0) dy = -isz->top;
        if (isz->width - dx < MIN_WIDTH) dx = isz->width - MIN_WIDTH;
        if (isz->height - dy < MIN_HEIGHT) dy = isz->height - MIN_HEIGHT;
        isz->width -= dx;
        isz->height -= dy;
        isz->left += dx;
        isz->top += dy; 
        break;
      case HANDLE_TR:
        if (isz->left+isz->width-1 + dx >= isz->max_width)
          dx = isz->max_width - (isz->left+isz->width);
        if (isz->top + dy < 0) dy = -isz->top;
        if (isz->width + dx < MIN_WIDTH) dx = isz->width - MIN_WIDTH;
        if (isz->height - dy < MIN_HEIGHT) dy = isz->height - MIN_HEIGHT;
        isz->width += dx;
        isz->height -= dy;
        isz->top += dy; 
        break;
      case HANDLE_BL:
        if (isz->left + dx < 0) dx = -isz->left;
        if (isz->top+isz->height-1 + dy >= isz->max_height)
          dy = isz->max_height - (isz->top+isz->height);
        if (isz->width - dx < MIN_WIDTH) dx = isz->width - MIN_WIDTH;
        if (isz->height + dy < MIN_HEIGHT) dy = isz->height - MIN_HEIGHT;
        isz->width -= dx;
        isz->height += dy;
        isz->left += dx;
        break;
      case HANDLE_BR:
        if (isz->left+isz->width-1 + dx >= isz->max_width)
          dx = isz->max_width - (isz->left+isz->width);
        if (isz->top+isz->height-1 + dy >= isz->max_height)
          dy = isz->max_height - (isz->top+isz->height);
        if (isz->width + dx < MIN_WIDTH) dx = isz->width - MIN_WIDTH;
        if (isz->height + dy < MIN_HEIGHT) dy = isz->height - MIN_HEIGHT;
        isz->width += dx;
        isz->height += dy;
        break;
      case HANDLE_BOX:
        if (isz->left + dx < 0) dx = -isz->left;
        else if (isz->left+isz->width-1 + dx >= isz->max_width)
          dx = isz->max_width - (isz->left+isz->width);
        if (isz->top + dy < 0) dy = -isz->top;
        else if (isz->top+isz->height-1 + dy >= isz->max_height)
          dy = isz->max_height - (isz->top+isz->height);
        isz->left += dx;
        isz->top += dy;
        break;
    }
    if (isz->width < 0) {
      isz->width = -isz->width;
      isz->left -= isz->width;
    }
    if (isz->height < 0) {
      isz->height = -isz->height;
      isz->top -= isz->height;
    }
    gtk_signal_emit_by_name(GTK_OBJECT(isz), "value_changed");
    gtk_widget_queue_draw(GTK_WIDGET(isz));
  }

  return FALSE;
}
