/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-reflow.c
* Copyright (C) 2000 Helix Code, Inc.
* Author: Chris Lahey <clahey@helixcode.com>
*
* This library 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 library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gnome.h>
#include <math.h>
#include "e-reflow.h"
#include "e-canvas-utils.h"
static void e_reflow_init (EReflow *card);
static void e_reflow_class_init (EReflowClass *klass);
static void e_reflow_set_arg (GtkObject *o, GtkArg *arg, guint arg_id);
static void e_reflow_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
static gboolean e_reflow_event (GnomeCanvasItem *item, GdkEvent *event);
static void e_reflow_realize (GnomeCanvasItem *item);
static void e_reflow_unrealize (GnomeCanvasItem *item);
static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
int x, int y, int width, int height);
static void e_reflow_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags);
static double e_reflow_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item);
static void _update_reflow ( EReflow *reflow );
static void _resize( GtkObject *object, gpointer data );
static void _queue_reflow(EReflow *e_reflow);
static GnomeCanvasGroupClass *parent_class = NULL;
enum {
E_REFLOW_RESIZE,
E_REFLOW_LAST_SIGNAL
};
static guint e_reflow_signals[E_REFLOW_LAST_SIGNAL] = { 0 };
/* The arguments we take */
enum {
ARG_0,
ARG_MINIMUM_WIDTH,
ARG_WIDTH,
ARG_HEIGHT
};
GtkType
e_reflow_get_type (void)
{
static GtkType reflow_type = 0;
if (!reflow_type)
{
static const GtkTypeInfo reflow_info =
{
"EReflow",
sizeof (EReflow),
sizeof (EReflowClass),
(GtkClassInitFunc) e_reflow_class_init,
(GtkObjectInitFunc) e_reflow_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
reflow_type = gtk_type_unique (gnome_canvas_group_get_type (), &reflow_info);
}
return reflow_type;
}
static void
e_reflow_class_init (EReflowClass *klass)
{
GtkObjectClass *object_class;
GnomeCanvasItemClass *item_class;
object_class = (GtkObjectClass*) klass;
item_class = (GnomeCanvasItemClass *) klass;
parent_class = gtk_type_class (gnome_canvas_group_get_type ());
e_reflow_signals[E_REFLOW_RESIZE] =
gtk_signal_new ("resize",
GTK_RUN_LAST,
object_class->type,
GTK_SIGNAL_OFFSET (EReflowClass, resize),
gtk_marshal_NONE__NONE,
GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, e_reflow_signals, E_REFLOW_LAST_SIGNAL);
gtk_object_add_arg_type ("EReflow::minimum_width", GTK_TYPE_DOUBLE,
GTK_ARG_READWRITE, ARG_MINIMUM_WIDTH);
gtk_object_add_arg_type ("EReflow::width", GTK_TYPE_DOUBLE,
GTK_ARG_READABLE, ARG_WIDTH);
gtk_object_add_arg_type ("EReflow::height", GTK_TYPE_DOUBLE,
GTK_ARG_READWRITE, ARG_HEIGHT);
object_class->set_arg = e_reflow_set_arg;
object_class->get_arg = e_reflow_get_arg;
/* object_class->destroy = e_reflow_destroy; */
/* GnomeCanvasItem method overrides */
item_class->event = e_reflow_event;
item_class->realize = e_reflow_realize;
item_class->unrealize = e_reflow_unrealize;
item_class->draw = e_reflow_draw;
item_class->update = e_reflow_update;
item_class->point = e_reflow_point;
}
static void
e_reflow_init (EReflow *reflow)
{
/* reflow->card = NULL;*/
reflow->items = NULL;
reflow->columns = NULL;
reflow->column_width = 150;
reflow->minimum_width = 10;
reflow->width = 10;
reflow->height = 10;
reflow->idle = 0;
reflow->column_drag = FALSE;
reflow->need_height_update = FALSE;
reflow->need_column_resize = FALSE;
reflow->default_cursor_shown = TRUE;
reflow->arrow_cursor = NULL;
reflow->default_cursor = NULL;
}
static void
e_reflow_set_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
GnomeCanvasItem *item;
EReflow *e_reflow;
item = GNOME_CANVAS_ITEM (o);
e_reflow = E_REFLOW (o);
switch (arg_id){
case ARG_HEIGHT:
e_reflow->height = GTK_VALUE_DOUBLE (*arg);
_queue_reflow(e_reflow);
break;
case ARG_MINIMUM_WIDTH:
e_reflow->minimum_width = GTK_VALUE_DOUBLE (*arg);
_queue_reflow(e_reflow);
break;
}
}
static void
e_reflow_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
EReflow *e_reflow;
e_reflow = E_REFLOW (object);
switch (arg_id) {
case ARG_MINIMUM_WIDTH:
GTK_VALUE_DOUBLE (*arg) = e_reflow->minimum_width;
break;
case ARG_WIDTH:
GTK_VALUE_DOUBLE (*arg) = e_reflow->width;
break;
case ARG_HEIGHT:
GTK_VALUE_DOUBLE (*arg) = e_reflow->height;
break;
default:
arg->type = GTK_TYPE_INVALID;
break;
}
}
static void
e_reflow_realize (GnomeCanvasItem *item)
{
EReflow *e_reflow;
GnomeCanvasGroup *group;
GList *list;
e_reflow = E_REFLOW (item);
group = GNOME_CANVAS_GROUP( item );
if (GNOME_CANVAS_ITEM_CLASS(parent_class)->realize)
(* GNOME_CANVAS_ITEM_CLASS(parent_class)->realize) (item);
e_reflow->arrow_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
e_reflow->default_cursor = gdk_cursor_new (GDK_LEFT_PTR);
for(list = e_reflow->items; list; list = g_list_next(list)) {
GnomeCanvasItem *item = GNOME_CANVAS_ITEM(list->data);
gtk_signal_connect(GTK_OBJECT(item),
"resize",
GTK_SIGNAL_FUNC(_resize),
(gpointer) e_reflow);
gnome_canvas_item_set(item,
"width", (double) e_reflow->column_width,
NULL);
}
_queue_reflow( e_reflow );
if (!item->canvas->aa) {
}
}
static void
e_reflow_unrealize (GnomeCanvasItem *item)
{
EReflow *e_reflow;
e_reflow = E_REFLOW (item);
if (!item->canvas->aa)
{
}
gdk_cursor_destroy (e_reflow->arrow_cursor);
gdk_cursor_destroy (e_reflow->default_cursor);
g_list_free (e_reflow->items);
g_list_free (e_reflow->columns);
if (GNOME_CANVAS_ITEM_CLASS(parent_class)->unrealize)
(* GNOME_CANVAS_ITEM_CLASS(parent_class)->unrealize) (item);
}
static gint
e_reflow_pick_line (EReflow *e_reflow, double x)
{
x += 9;
x /= e_reflow->column_width + 16;
return x;
}
static gboolean
e_reflow_event (GnomeCanvasItem *item, GdkEvent *event)
{
EReflow *e_reflow;
e_reflow = E_REFLOW (item);
switch( event->type )
{
case GDK_KEY_PRESS:
if (event->key.keyval == GDK_Tab ||
event->key.keyval == GDK_KP_Tab ||
event->key.keyval == GDK_ISO_Left_Tab) {
GList *list;
for (list = e_reflow->items; list; list = list->next) {
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (list->data);
gboolean has_focus;
gtk_object_get(GTK_OBJECT(item),
"has_focus", &has_focus,
NULL);
if (has_focus) {
if (event->key.state & GDK_SHIFT_MASK)
list = list->prev;
else
list = list->next;
if (list) {
item = GNOME_CANVAS_ITEM(list->data);
gnome_canvas_item_set(item,
"has_focus", TRUE,
NULL);
return 1;
} else {
return 0;
}
}
}
}
break;
case GDK_BUTTON_PRESS:
{
GdkEventButton *button = (GdkEventButton *) event;
double n_x;
n_x = button->x;
n_x += 9;
n_x = fmod(n_x,(e_reflow->column_width + 16));
if ( button->y >= 7 && button->y <= e_reflow->height - 7 && n_x < 16 ) {
e_reflow->which_column_dragged = e_reflow_pick_line(e_reflow, button->x);
e_reflow->start_x = e_reflow->which_column_dragged * (e_reflow->column_width + 16) - 1;
e_reflow->temp_column_width = e_reflow->column_width;
e_reflow->column_drag = TRUE;
gnome_canvas_item_grab (item,
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
e_reflow->arrow_cursor,
button->time);
e_reflow->previous_temp_column_width = -1;
e_reflow->need_column_resize = TRUE;
gnome_canvas_item_request_update(item);
return TRUE;
}
}
break;
case GDK_BUTTON_RELEASE:
if (e_reflow->column_drag) {
GdkEventButton *button = (GdkEventButton *) event;
GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas));
e_reflow->temp_column_width = e_reflow->column_width +
(button->x - e_reflow->start_x)/(e_reflow->which_column_dragged - e_reflow_pick_line(e_reflow, adjustment->value));
if ( e_reflow->temp_column_width < 50 )
e_reflow->temp_column_width = 50;
gtk_adjustment_set_value(adjustment, adjustment->value + e_reflow_pick_line(e_reflow, adjustment->value) * (e_reflow->temp_column_width - e_reflow->column_width));
e_reflow->column_width = e_reflow->temp_column_width;
e_reflow->column_drag = FALSE;
_queue_reflow(e_reflow);
gnome_canvas_item_ungrab (item, button->time);
return TRUE;
}
break;
case GDK_MOTION_NOTIFY:
if (e_reflow->column_drag) {
double old_width = e_reflow->temp_column_width;
GdkEventMotion *motion = (GdkEventMotion *) event;
GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas));
e_reflow->temp_column_width = e_reflow->column_width +
(motion->x - e_reflow->start_x)/(e_reflow->which_column_dragged - e_reflow_pick_line(e_reflow, adjustment->value));
if (e_reflow->temp_column_width < 50)
e_reflow->temp_column_width = 50;
if (old_width != e_reflow->temp_column_width) {
e_reflow->need_column_resize = TRUE;
gnome_canvas_item_request_update(item);
}
return TRUE;
} else {
GdkEventMotion *motion = (GdkEventMotion *) event;
double n_x;
n_x = motion->x;
n_x += 9;
n_x = fmod(n_x,(e_reflow->column_width + 16));
if ( motion->y >= 7 && motion->y <= e_reflow->height - 7 && n_x < 16 ) {
if ( e_reflow->default_cursor_shown ) {
gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, e_reflow->arrow_cursor);
e_reflow->default_cursor_shown = FALSE;
}
} else
if ( ! e_reflow->default_cursor_shown ) {
gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, e_reflow->default_cursor);
e_reflow->default_cursor_shown = TRUE;
}
}
break;
case GDK_ENTER_NOTIFY:
if (!e_reflow->column_drag) {
GdkEventCrossing *crossing = (GdkEventCrossing *) event;
double n_x;
n_x = crossing->x;
n_x += 9;
n_x = fmod(n_x,(e_reflow->column_width + 16));
if ( crossing->y >= 7 && crossing->y <= e_reflow->height - 7 && n_x < 16 ) {
if ( e_reflow->default_cursor_shown ) {
gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, e_reflow->arrow_cursor);
e_reflow->default_cursor_shown = FALSE;
}
}
}
break;
case GDK_LEAVE_NOTIFY:
if (!e_reflow->column_drag) {
GdkEventCrossing *crossing = (GdkEventCrossing *) event;
double n_x;
n_x = crossing->x;
n_x += 9;
n_x = fmod(n_x,(e_reflow->column_width + 16));
if ( !( crossing->y >= 7 && crossing->y <= e_reflow->height - 7 && n_x < 16 ) ) {
if ( ! e_reflow->default_cursor_shown ) {
gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, e_reflow->default_cursor);
e_reflow->default_cursor_shown = TRUE;
}
}
}
break;
default:
break;
}
if (GNOME_CANVAS_ITEM_CLASS( parent_class )->event)
return (* GNOME_CANVAS_ITEM_CLASS( parent_class )->event) (item, event);
else
return 0;
}
void
e_reflow_add_item(EReflow *e_reflow, GnomeCanvasItem *item)
{
e_reflow->items = g_list_append(e_reflow->items, item);
if ( GTK_OBJECT_FLAGS( e_reflow ) & GNOME_CANVAS_ITEM_REALIZED ) {
gtk_signal_connect(GTK_OBJECT(item),
"resize",
GTK_SIGNAL_FUNC(_resize),
(gpointer) e_reflow);
gnome_canvas_item_set(item,
"width", (double) e_reflow->column_width,
NULL);
_queue_reflow(e_reflow);
}
}
static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
int x, int y, int width, int height)
{
int x_rect, y_rect, width_rect, height_rect;
gdouble running_width;
EReflow *e_reflow = E_REFLOW(item);
int i;
double column_width;
if (GNOME_CANVAS_ITEM_CLASS(parent_class)->draw)
GNOME_CANVAS_ITEM_CLASS(parent_class)->draw (item, drawable, x, y, width, height);
column_width = e_reflow->column_width;
running_width = 7 + column_width + 7;
x_rect = running_width;
y_rect = 7;
width_rect = 2;
height_rect = e_reflow->height - 14;
/* Compute first column to draw. */
i = x;
i /= column_width + 16;
running_width += i * (column_width + 16);
for ( ; i < e_reflow->column_count; i++) {
if ( running_width > x + width )
break;
x_rect = running_width;
gtk_paint_flat_box(GTK_WIDGET(item->canvas)->style,
drawable,
GTK_STATE_ACTIVE,
GTK_SHADOW_NONE,
NULL,
GTK_WIDGET(item->canvas),
"reflow",
x_rect - x,
y_rect - y,
width_rect,
height_rect);
running_width += 2 + 7 + column_width + 7;
}
if (e_reflow->column_drag) {
int start_line = e_reflow_pick_line(e_reflow,
gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas))->value);
i = x - start_line * (column_width + 16);
running_width = start_line * (column_width + 16);
column_width = e_reflow->temp_column_width;
running_width -= start_line * (column_width + 16);
i += start_line * (column_width + 16);
running_width += 7 + column_width + 7;
x_rect = running_width;
y_rect = 7;
width_rect = 2;
height_rect = e_reflow->height - 14;
/* Compute first column to draw. */
i /= column_width + 16;
running_width += i * (column_width + 16);
for ( ; i < e_reflow->column_count; i++) {
if ( running_width > x + width )
break;
x_rect = running_width;
gdk_draw_rectangle(drawable,
GTK_WIDGET(item->canvas)->style->fg_gc[GTK_STATE_NORMAL],
TRUE,
x_rect - x,
y_rect - y,
width_rect - 1,
height_rect - 1);
running_width += 2 + 7 + column_width + 7;
}
}
}
static void
e_reflow_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags)
{
EReflow *e_reflow;
double x0, x1, y0, y1;
e_reflow = E_REFLOW (item);
if (GNOME_CANVAS_ITEM_CLASS(parent_class)->update)
GNOME_CANVAS_ITEM_CLASS(parent_class)->update (item, affine, clip_path, flags);
x0 = item->x1;
y0 = item->y1;
x1 = item->x2;
y1 = item->y2;
if ( x1 < x0 + e_reflow->width )
x1 = x0 + e_reflow->width;
if ( y1 < y0 + e_reflow->height )
y1 = y0 + e_reflow->height;
item->x2 = x1;
item->y2 = y1;
if (e_reflow->need_height_update) {
x0 = item->x1;
y0 = item->y1;
x1 = item->x2;
y1 = item->y2;
if ( x0 > 0 )
x0 = 0;
if ( y0 > 0 )
y0 = 0;
if ( x1 < E_REFLOW(item)->width )
x1 = E_REFLOW(item)->width;
if ( x1 < E_REFLOW(item)->height )
x1 = E_REFLOW(item)->height;
gnome_canvas_request_redraw(item->canvas, x0, y0, x1, y1);
e_reflow->need_height_update = FALSE;
} else if (e_reflow->need_column_resize) {
int x_rect, y_rect, width_rect, height_rect;
int start_line = e_reflow_pick_line(e_reflow,
gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas))->value);
gdouble running_width;
int i;
double column_width;
if ( e_reflow->previous_temp_column_width != -1 ) {
running_width = start_line * (e_reflow->column_width + 16);
column_width = e_reflow->previous_temp_column_width;
running_width -= start_line * (column_width + 16);
running_width += 7 + column_width + 7;
y_rect = 7;
width_rect = 2;
height_rect = e_reflow->height - 14;
for ( i = 0; i < e_reflow->column_count; i++) {
x_rect = running_width;
gnome_canvas_request_redraw(item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect);
running_width += 2 + 7 + column_width + 7;
}
}
if ( e_reflow->temp_column_width != -1 ) {
running_width = start_line * (e_reflow->column_width + 16);
column_width = e_reflow->temp_column_width;
running_width -= start_line * (column_width + 16);
running_width += 7 + column_width + 7;
y_rect = 7;
width_rect = 2;
height_rect = e_reflow->height - 14;
for ( i = 0; i < e_reflow->column_count; i++) {
x_rect = running_width;
gnome_canvas_request_redraw(item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect);
running_width += 2 + 7 + column_width + 7;
}
}
e_reflow->previous_temp_column_width = e_reflow->temp_column_width;
e_reflow->need_column_resize = FALSE;
}
}
static double
e_reflow_point (GnomeCanvasItem *item,
double x, double y, int cx, int cy,
GnomeCanvasItem **actual_item)
{
EReflow *e_reflow = E_REFLOW(item);
double distance = 1;
if (GNOME_CANVAS_ITEM_CLASS(parent_class)->point)
distance = GNOME_CANVAS_ITEM_CLASS(parent_class)->point (item, x, y, cx, cy, actual_item);
if (*actual_item)
return 0;
if (y >= 7 && y <= e_reflow->height - 7) {
float n_x;
n_x = x;
n_x += 9.0;
n_x = fmod(n_x, (e_reflow->column_width + 16));
if (n_x < 16.0) {
*actual_item = item;
return 0;
}
}
return distance;
}
static void
_reflow( EReflow *e_reflow )
{
gdouble running_height;
GList *list;
double item_height;
if (e_reflow->columns) {
g_list_free (e_reflow->columns);
e_reflow->columns = NULL;
}
e_reflow->column_count = 0;
if (e_reflow->items == NULL) {
e_reflow->columns = NULL;
e_reflow->column_count = 1;
return;
}
list = e_reflow->items;
gtk_object_get (GTK_OBJECT(list->data),
"height", &item_height,
NULL);
running_height = 7 + item_height + 7;
e_reflow->columns = g_list_append (e_reflow->columns, list);
e_reflow->column_count = 1;
list = g_list_next(list);
for ( ; list; list = g_list_next(list)) {
gtk_object_get (GTK_OBJECT(list->data),
"height", &item_height,
NULL);
if (running_height + item_height + 7 > e_reflow->height) {
running_height = 7 + item_height + 7;
e_reflow->columns = g_list_append (e_reflow->columns, list);
e_reflow->column_count ++;
} else {
running_height += item_height + 7;
}
}
}
static void
_update_reflow( EReflow *e_reflow )
{
if ( GTK_OBJECT_FLAGS( e_reflow ) & GNOME_CANVAS_ITEM_REALIZED ) {
gdouble old_width;
gdouble running_width;
_reflow (e_reflow);
old_width = e_reflow->width;
running_width = 7;
if (e_reflow->items == NULL) {
} else {
GList *list;
GList *next_column;
gdouble item_height;
gdouble running_height;
running_height = 7;
list = e_reflow->items;
gtk_object_set (GTK_OBJECT(list->data),
"width", e_reflow->column_width,
NULL);
gtk_object_get (GTK_OBJECT(list->data),
"height", &item_height,
NULL);
e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(list->data),
(double) running_width,
(double) running_height);
running_height += item_height + 7;
next_column = g_list_next(e_reflow->columns);
list = g_list_next(list);
for( ; list; list = g_list_next(list)) {
gtk_object_set (GTK_OBJECT(list->data),
"width", e_reflow->column_width,
NULL);
gtk_object_get (GTK_OBJECT(list->data),
"height", &item_height,
NULL);
if (next_column && (next_column->data == list)) {
next_column = g_list_next (next_column);
running_height = 7;
running_width += e_reflow->column_width + 7 + 2 + 7;
}
e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(list->data),
(double) running_width,
(double) running_height);
running_height += item_height + 7;
}
}
e_reflow->width = running_width + e_reflow->column_width + 7;
if ( e_reflow->width < e_reflow->minimum_width )
e_reflow->width = e_reflow->minimum_width;
if (old_width != e_reflow->width)
gtk_signal_emit_by_name (GTK_OBJECT (e_reflow), "resize");
}
}
static gboolean
_idle_reflow(gpointer data)
{
EReflow *e_reflow = E_REFLOW(data);
_update_reflow(e_reflow);
e_reflow->need_height_update = TRUE;
gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(e_reflow));
e_reflow->idle = 0;
return FALSE;
}
static void
_queue_reflow(EReflow *e_reflow)
{
if (e_reflow->idle == 0)
e_reflow->idle = g_idle_add(_idle_reflow, e_reflow);
}
static void
_resize( GtkObject *object, gpointer data )
{
_queue_reflow(E_REFLOW(data));
}