From 811559d0ab8c5e8281aaa93b2b99166261014b8e Mon Sep 17 00:00:00 2001 From: John Darrington Date: Sun, 14 Dec 2008 15:58:03 +0900 Subject: [PATCH] Readd lost files --- lib/gtksheet/psppire-sheet.c | 5465 +++++++++++++++++++++++++++++ lib/gtksheet/psppire-sheet.h | 320 ++ lib/gtksheet/psppire-sheetmodel.c | 541 +++ lib/gtksheet/psppire-sheetmodel.h | 221 ++ 4 files changed, 6547 insertions(+) create mode 100644 lib/gtksheet/psppire-sheet.c create mode 100644 lib/gtksheet/psppire-sheet.h create mode 100644 lib/gtksheet/psppire-sheetmodel.c create mode 100644 lib/gtksheet/psppire-sheetmodel.h diff --git a/lib/gtksheet/psppire-sheet.c b/lib/gtksheet/psppire-sheet.c new file mode 100644 index 00000000..2464b970 --- /dev/null +++ b/lib/gtksheet/psppire-sheet.c @@ -0,0 +1,5465 @@ +/* + Copyright (C) 2006, 2008 Free Software Foundation + + This program 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 3 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 program. If not, see . + + + This file is derived from the gtksheet.c and extensively modified for the + requirements of PSPPIRE. The changes are copyright by the + Free Software Foundation. The copyright notice for the original work is + below. +*/ + +/* GtkSheet widget for Gtk+. + * Copyright (C) 1999-2001 Adrian E. Feiguin + * + * Based on GtkClist widget by Jay Painter, but major changes. + * Memory allocation routines inspired on SC (Spreadsheet Calculator) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:psppiresheet + * @short_description: spreadsheet widget for gtk2 + * + * PsppireSheet is a matrix widget for GTK+. It consists of an scrollable grid of + * cells where you can allocate text. Cell contents can be edited interactively + * through a specially designed entry, GtkItemEntry. + * + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "psppire-sheet.h" +#include +#include "psppire-sheetmodel.h" +#include +#include + +/* sheet flags */ +enum + { + PSPPIRE_SHEET_IN_XDRAG = 1 << 1, + PSPPIRE_SHEET_IN_YDRAG = 1 << 2, + PSPPIRE_SHEET_IN_DRAG = 1 << 3, + PSPPIRE_SHEET_IN_SELECTION = 1 << 4, + PSPPIRE_SHEET_IN_RESIZE = 1 << 5 + }; + +#define PSPPIRE_SHEET_FLAGS(sheet) (PSPPIRE_SHEET (sheet)->flags) +#define PSPPIRE_SHEET_SET_FLAGS(sheet,flag) (PSPPIRE_SHEET_FLAGS (sheet) |= (flag)) +#define PSPPIRE_SHEET_UNSET_FLAGS(sheet,flag) (PSPPIRE_SHEET_FLAGS (sheet) &= ~ (flag)) + +#define PSPPIRE_SHEET_IN_XDRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_XDRAG) +#define PSPPIRE_SHEET_IN_YDRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_YDRAG) +#define PSPPIRE_SHEET_IN_DRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_DRAG) +#define PSPPIRE_SHEET_IN_SELECTION(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_SELECTION) +#define PSPPIRE_SHEET_IN_RESIZE(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_RESIZE) + +#define CELL_SPACING 1 + +#define TIMEOUT_HOVER 300 +#define COLUMN_MIN_WIDTH 10 +#define COLUMN_TITLES_HEIGHT 4 +#define DEFAULT_COLUMN_WIDTH 80 +#define DEFAULT_ROW_HEIGHT 25 + +static void set_entry_widget_font (PsppireSheet *sheet); + +static void psppire_sheet_update_primary_selection (PsppireSheet *sheet); +static void draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint n); +static void draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint n); +static void redraw_range (PsppireSheet *sheet, PsppireSheetRange *range); + + +static void set_row_height (PsppireSheet *sheet, + gint row, + gint height); + +static void destroy_hover_window (PsppireSheetHoverTitle *); +static PsppireSheetHoverTitle *create_hover_window (void); + +static GtkStateType psppire_sheet_cell_get_state (PsppireSheet *sheet, gint row, gint col); + + +static inline void +dispose_string (const PsppireSheet *sheet, gchar *text) +{ + PsppireSheetModel *model = psppire_sheet_get_model (sheet); + + if ( ! model ) + return; + + if (psppire_sheet_model_free_strings (model)) + g_free (text); +} + + +/* FIXME: Why bother with these two ? */ + +/* returns the column index from a pixel location */ +static inline gint +column_from_xpixel (const PsppireSheet *sheet, gint pixel) +{ + return psppire_axis_unit_at_pixel (sheet->haxis, pixel); +} + +static inline gint +row_from_ypixel (const PsppireSheet *sheet, gint pixel) +{ + return psppire_axis_unit_at_pixel (sheet->vaxis, pixel); +} + + +/* Return the lowest row number which is wholly or partially on + the visible range of the sheet */ +static inline glong +min_visible_row (const PsppireSheet *sheet) +{ + return row_from_ypixel (sheet, sheet->vadjustment->value); +} + +static inline glong +min_fully_visible_row (const PsppireSheet *sheet) +{ + glong row = min_visible_row (sheet); + + if ( psppire_axis_start_pixel (sheet->vaxis, row) < sheet->vadjustment->value) + row++; + + return row; +} + +static inline glong +max_visible_row (const PsppireSheet *sheet) +{ + return row_from_ypixel (sheet, sheet->vadjustment->value + sheet->vadjustment->page_size); +} + + +static inline glong +max_fully_visible_row (const PsppireSheet *sheet) +{ + glong row = max_visible_row (sheet); + + if ( psppire_axis_start_pixel (sheet->vaxis, row) + + + psppire_axis_unit_size (sheet->vaxis, row) + > sheet->vadjustment->value) + row--; + + return row; +} + + +/* Returns the lowest column number which is wholly or partially + on the sheet */ +static inline glong +min_visible_column (const PsppireSheet *sheet) +{ + return column_from_xpixel (sheet, sheet->hadjustment->value); +} + +static inline glong +min_fully_visible_column (const PsppireSheet *sheet) +{ + glong col = min_visible_column (sheet); + + if ( psppire_axis_start_pixel (sheet->haxis, col) < sheet->hadjustment->value) + col++; + + return col; +} + + +/* Returns the highest column number which is wholly or partially + on the sheet */ +static inline glong +max_visible_column (const PsppireSheet *sheet) +{ + return column_from_xpixel (sheet, sheet->hadjustment->value + sheet->hadjustment->page_size); +} + +static inline glong +max_fully_visible_column (const PsppireSheet *sheet) +{ + glong col = max_visible_column (sheet); + + if ( psppire_axis_start_pixel (sheet->haxis, col) + + + psppire_axis_unit_size (sheet->haxis, col) + > sheet->hadjustment->value) + col--; + + return col; +} + + + +/* The size of the region (in pixels) around the row/column boundaries + where the height/width may be grabbed to change size */ +#define DRAG_WIDTH 6 + +static gboolean +on_column_boundary (const PsppireSheet *sheet, gint x, gint *column) +{ + gint col; + gint pixel; + + x += sheet->hadjustment->value; + + if ( x < 0) + return FALSE; + + col = column_from_xpixel (sheet, x); + + pixel = x - DRAG_WIDTH / 2; + if (pixel < 0) + pixel = 0; + + if ( column_from_xpixel (sheet, pixel) < col ) + { + *column = col - 1; + return TRUE; + } + + if ( column_from_xpixel (sheet, x + DRAG_WIDTH / 2) > col ) + { + *column = col; + return TRUE; + } + + return FALSE; +} + +static gboolean +on_row_boundary (const PsppireSheet *sheet, gint y, gint *row) +{ + gint r; + gint pixel; + + y += sheet->vadjustment->value; + + if ( y < 0) + return FALSE; + + r = row_from_ypixel (sheet, y); + + pixel = y - DRAG_WIDTH / 2; + if (pixel < 0) + pixel = 0; + + if ( row_from_ypixel (sheet, pixel) < r ) + { + *row = r - 1; + return TRUE; + } + + if ( row_from_ypixel (sheet, y + DRAG_WIDTH / 2) > r ) + { + *row = r; + return TRUE; + } + + return FALSE; +} + + +static inline gboolean +POSSIBLE_DRAG (const PsppireSheet *sheet, gint x, gint y, + gint *drag_row, gint *drag_column) +{ + gint ydrag, xdrag; + + /* Can't drag if nothing is selected */ + if ( sheet->range.row0 < 0 || sheet->range.rowi < 0 || + sheet->range.col0 < 0 || sheet->range.coli < 0 ) + return FALSE; + + *drag_column = column_from_xpixel (sheet, x); + *drag_row = row_from_ypixel (sheet, y); + + if (x >= psppire_axis_start_pixel (sheet->haxis, sheet->range.col0) - DRAG_WIDTH / 2 && + x <= psppire_axis_start_pixel (sheet->haxis, sheet->range.coli) + + psppire_axis_unit_size (sheet->haxis, sheet->range.coli) + DRAG_WIDTH / 2) + { + ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.row0); + if (y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2) + { + *drag_row = sheet->range.row0; + return TRUE; + } + ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) + + psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi); + if (y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2) + { + *drag_row = sheet->range.rowi; + return TRUE; + } + } + + if (y >= psppire_axis_start_pixel (sheet->vaxis, sheet->range.row0) - DRAG_WIDTH / 2 && + y <= psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) + + psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi) + DRAG_WIDTH / 2) + { + xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.col0); + if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2) + { + *drag_column = sheet->range.col0; + return TRUE; + } + xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.coli) + + psppire_axis_unit_size (sheet->haxis, sheet->range.coli); + if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2) + { + *drag_column = sheet->range.coli; + return TRUE; + } + } + + return FALSE; +} + +static inline gboolean +POSSIBLE_RESIZE (const PsppireSheet *sheet, gint x, gint y, + gint *drag_row, gint *drag_column) +{ + gint xdrag, ydrag; + + /* Can't drag if nothing is selected */ + if ( sheet->range.row0 < 0 || sheet->range.rowi < 0 || + sheet->range.col0 < 0 || sheet->range.coli < 0 ) + return FALSE; + + xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.coli)+ + psppire_axis_unit_size (sheet->haxis, sheet->range.coli); + + ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) + + psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi); + + if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) + ydrag = psppire_axis_start_pixel (sheet->vaxis, min_visible_row (sheet)); + + if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) + xdrag = psppire_axis_start_pixel (sheet->haxis, min_visible_column (sheet)); + + *drag_column = column_from_xpixel (sheet, x); + *drag_row = row_from_ypixel (sheet, y); + + if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2 && + y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2) return TRUE; + + return FALSE; +} + + +static gboolean +rectangle_from_range (PsppireSheet *sheet, const PsppireSheetRange *range, + GdkRectangle *r) +{ + g_return_val_if_fail (range, FALSE); + + r->x = psppire_axis_start_pixel (sheet->haxis, range->col0); + r->x -= round (sheet->hadjustment->value); + + r->y = psppire_axis_start_pixel (sheet->vaxis, range->row0); + r->y -= round (sheet->vadjustment->value); + + r->width = psppire_axis_start_pixel (sheet->haxis, range->coli) - + psppire_axis_start_pixel (sheet->haxis, range->col0) + + psppire_axis_unit_size (sheet->haxis, range->coli); + + r->height = psppire_axis_start_pixel (sheet->vaxis, range->rowi) - + psppire_axis_start_pixel (sheet->vaxis, range->row0) + + psppire_axis_unit_size (sheet->vaxis, range->rowi); + + if ( sheet->column_titles_visible) + { + r->y += sheet->column_title_area.height; + } + + if ( sheet->row_titles_visible) + { + r->x += sheet->row_title_area.width; + } + + return TRUE; +} + +static gboolean +rectangle_from_cell (PsppireSheet *sheet, gint row, gint col, + GdkRectangle *r) +{ + PsppireSheetRange range; + g_return_val_if_fail (row >= 0, FALSE); + g_return_val_if_fail (col >= 0, FALSE); + + range.row0 = range.rowi = row; + range.col0 = range.coli = col; + + return rectangle_from_range (sheet, &range, r); +} + + +static void psppire_sheet_class_init (PsppireSheetClass *klass); +static void psppire_sheet_init (PsppireSheet *sheet); +static void psppire_sheet_dispose (GObject *object); +static void psppire_sheet_finalize (GObject *object); +static void psppire_sheet_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static void psppire_sheet_realize (GtkWidget *widget); +static void psppire_sheet_unrealize (GtkWidget *widget); +static void psppire_sheet_map (GtkWidget *widget); +static void psppire_sheet_unmap (GtkWidget *widget); +static gint psppire_sheet_expose (GtkWidget *widget, + GdkEventExpose *event); + +static void psppire_sheet_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); + +static gboolean psppire_sheet_set_scroll_adjustments (PsppireSheet *sheet, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); + +static gint psppire_sheet_button_press (GtkWidget *widget, + GdkEventButton *event); +static gint psppire_sheet_button_release (GtkWidget *widget, + GdkEventButton *event); +static gint psppire_sheet_motion (GtkWidget *widget, + GdkEventMotion *event); +static gboolean psppire_sheet_crossing_notify (GtkWidget *widget, + GdkEventCrossing *event); +static gint psppire_sheet_entry_key_press (GtkWidget *widget, + GdkEventKey *key); +static gboolean psppire_sheet_key_press (GtkWidget *widget, + GdkEventKey *key); +static void psppire_sheet_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void psppire_sheet_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +/* Sheet queries */ + +static gboolean psppire_sheet_range_isvisible (const PsppireSheet *sheet, + const PsppireSheetRange *range); +static gboolean psppire_sheet_cell_isvisible (PsppireSheet *sheet, + gint row, gint column); +/* Drawing Routines */ + +/* draw cell */ +static void psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint column); + + +/* draw visible part of range. */ +static void draw_sheet_region (PsppireSheet *sheet, GdkRegion *region); + + +/* highlight the visible part of the selected range */ +static void psppire_sheet_range_draw_selection (PsppireSheet *sheet, + PsppireSheetRange range); + +/* Selection */ + +static void psppire_sheet_real_select_range (PsppireSheet *sheet, + const PsppireSheetRange *range); +static void psppire_sheet_real_unselect_range (PsppireSheet *sheet, + const PsppireSheetRange *range); +static void psppire_sheet_extend_selection (PsppireSheet *sheet, + gint row, gint column); +static void psppire_sheet_new_selection (PsppireSheet *sheet, + PsppireSheetRange *range); +static void psppire_sheet_draw_border (PsppireSheet *sheet, + PsppireSheetRange range); + +/* Active Cell handling */ + +static void psppire_sheet_hide_entry_widget (PsppireSheet *sheet); +static void change_active_cell (PsppireSheet *sheet, gint row, gint col); +static gboolean psppire_sheet_draw_active_cell (PsppireSheet *sheet); +static void psppire_sheet_show_entry_widget (PsppireSheet *sheet); +static gboolean psppire_sheet_click_cell (PsppireSheet *sheet, + gint row, + gint column); + + +/* Scrollbars */ + +static void adjust_scrollbars (PsppireSheet *sheet); +static void vadjustment_value_changed (GtkAdjustment *adjustment, + gpointer data); +static void hadjustment_value_changed (GtkAdjustment *adjustment, + gpointer data); + + +static void draw_xor_vline (PsppireSheet *sheet); +static void draw_xor_hline (PsppireSheet *sheet); +static void draw_xor_rectangle (PsppireSheet *sheet, + PsppireSheetRange range); + +/* Sheet Button */ + +static void create_global_button (PsppireSheet *sheet); +static void global_button_clicked (GtkWidget *widget, + gpointer data); +/* Sheet Entry */ + +static void create_sheet_entry (PsppireSheet *sheet); +static void psppire_sheet_size_allocate_entry (PsppireSheet *sheet); + +/* Sheet button gadgets */ + +static void draw_column_title_buttons (PsppireSheet *sheet); +static void draw_row_title_buttons (PsppireSheet *sheet); + + +static void size_allocate_global_button (PsppireSheet *sheet); +static void psppire_sheet_button_size_request (PsppireSheet *sheet, + const PsppireSheetButton *button, + GtkRequisition *requisition); + +static void psppire_sheet_real_cell_clear (PsppireSheet *sheet, + gint row, + gint column); + + +/* Signals */ +enum + { + SELECT_ROW, + SELECT_COLUMN, + DOUBLE_CLICK_ROW, + DOUBLE_CLICK_COLUMN, + BUTTON_EVENT_ROW, + BUTTON_EVENT_COLUMN, + SELECT_RANGE, + RESIZE_RANGE, + MOVE_RANGE, + TRAVERSE, + ACTIVATE, + LAST_SIGNAL + }; + +static GtkContainerClass *parent_class = NULL; +static guint sheet_signals[LAST_SIGNAL] = { 0 }; + + +GType +psppire_sheet_get_type () +{ + static GType sheet_type = 0; + + if (!sheet_type) + { + static const GTypeInfo sheet_info = + { + sizeof (PsppireSheetClass), + NULL, + NULL, + (GClassInitFunc) psppire_sheet_class_init, + NULL, + NULL, + sizeof (PsppireSheet), + 0, + (GInstanceInitFunc) psppire_sheet_init, + NULL, + }; + + sheet_type = + g_type_register_static (GTK_TYPE_BIN, "PsppireSheet", + &sheet_info, 0); + } + return sheet_type; +} + + + +static PsppireSheetRange* +psppire_sheet_range_copy (const PsppireSheetRange *range) +{ + PsppireSheetRange *new_range; + + g_return_val_if_fail (range != NULL, NULL); + + new_range = g_new (PsppireSheetRange, 1); + + *new_range = *range; + + return new_range; +} + +static void +psppire_sheet_range_free (PsppireSheetRange *range) +{ + g_return_if_fail (range != NULL); + + g_free (range); +} + +GType +psppire_sheet_range_get_type (void) +{ + static GType sheet_range_type = 0; + + if (!sheet_range_type) + { + sheet_range_type = + g_boxed_type_register_static ("PsppireSheetRange", + (GBoxedCopyFunc) psppire_sheet_range_copy, + (GBoxedFreeFunc) psppire_sheet_range_free); + } + + return sheet_range_type; +} + +static PsppireSheetCell* +psppire_sheet_cell_copy (const PsppireSheetCell *cell) +{ + PsppireSheetCell *new_cell; + + g_return_val_if_fail (cell != NULL, NULL); + + new_cell = g_new (PsppireSheetCell, 1); + + *new_cell = *cell; + + return new_cell; +} + +static void +psppire_sheet_cell_free (PsppireSheetCell *cell) +{ + g_return_if_fail (cell != NULL); + + g_free (cell); +} + +GType +psppire_sheet_cell_get_type (void) +{ + static GType sheet_cell_type = 0; + + if (!sheet_cell_type) + { + sheet_cell_type = + g_boxed_type_register_static ("PsppireSheetCell", + (GBoxedCopyFunc) psppire_sheet_cell_copy, + (GBoxedFreeFunc) psppire_sheet_cell_free); + } + + return sheet_cell_type; +} + + +/* Properties */ +enum + { + PROP_0, + PROP_VAXIS, + PROP_HAXIS, + PROP_MODEL + }; + +static void +resize_column (PsppireSheet *sheet, gint unit, glong size) +{ + PsppireSheetRange range; + range.col0 = unit; + range.coli = max_visible_column (sheet); + range.row0 = min_visible_row (sheet); + range.rowi = max_visible_row (sheet); + + redraw_range (sheet, &range); + + draw_column_title_buttons_range (sheet, range.col0, range.coli); +} + + +static void +psppire_sheet_set_horizontal_axis (PsppireSheet *sheet, PsppireAxis *a) +{ + if ( sheet->haxis ) + g_object_unref (sheet->haxis); + + sheet->haxis = a; + g_signal_connect_swapped (a, "resize-unit", G_CALLBACK (resize_column), sheet); + + if ( sheet->haxis ) + g_object_ref (sheet->haxis); +} + +static void +resize_row (PsppireSheet *sheet, gint unit, glong size) +{ + PsppireSheetRange range; + range.col0 = min_visible_column (sheet); + range.coli = max_visible_column (sheet); + range.row0 = unit; + range.rowi = max_visible_row (sheet); + + redraw_range (sheet, &range); + + draw_row_title_buttons_range (sheet, range.row0, range.rowi); +} + +static void +psppire_sheet_set_vertical_axis (PsppireSheet *sheet, PsppireAxis *a) +{ + if ( sheet->vaxis ) + g_object_unref (sheet->vaxis); + + sheet->vaxis = a; + + g_signal_connect_swapped (a, "resize-unit", G_CALLBACK (resize_row), sheet); + + if ( sheet->vaxis ) + g_object_ref (sheet->vaxis); +} + + +static void +psppire_sheet_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + PsppireSheet *sheet = PSPPIRE_SHEET (object); + + switch (prop_id) + { + case PROP_VAXIS: + psppire_sheet_set_vertical_axis (sheet, g_value_get_pointer (value)); + break; + case PROP_HAXIS: + psppire_sheet_set_horizontal_axis (sheet, g_value_get_pointer (value)); + break; + case PROP_MODEL: + psppire_sheet_set_model (sheet, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static void +psppire_sheet_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (object); + + switch (prop_id) + { + case PROP_VAXIS: + g_value_set_pointer (value, sheet->vaxis); + break; + case PROP_HAXIS: + g_value_set_pointer (value, sheet->haxis); + break; + case PROP_MODEL: + g_value_set_pointer (value, sheet->model); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + + +static void +psppire_sheet_class_init (PsppireSheetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + GParamSpec *haxis_spec ; + GParamSpec *vaxis_spec ; + GParamSpec *model_spec ; + + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + /** + * PsppireSheet::select-row + * @sheet: the sheet widget that emitted the signal + * @row: the newly selected row index + * + * A row has been selected. + */ + sheet_signals[SELECT_ROW] = + g_signal_new ("select-row", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, select_row), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + + /** + * PsppireSheet::select - column + * @sheet: the sheet widget that emitted the signal + * @column: the newly selected column index + * + * A column has been selected. + */ + sheet_signals[SELECT_COLUMN] = + g_signal_new ("select-column", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, select_column), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + + /** + * PsppireSheet::double-click-row + * @sheet: the sheet widget that emitted the signal + * @row: the row that was double clicked. + * + * A row's title button has been double clicked + */ + sheet_signals[DOUBLE_CLICK_ROW] = + g_signal_new ("double-click-row", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + + /** + * PsppireSheet::double-click-column + * @sheet: the sheet widget that emitted the signal + * @column: the column that was double clicked. + * + * A column's title button has been double clicked + */ + sheet_signals[DOUBLE_CLICK_COLUMN] = + g_signal_new ("double-click-column", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + + /** + * PsppireSheet::button-event-column + * @sheet: the sheet widget that emitted the signal + * @column: the column on which the event occured. + * + * A button event occured on a column title button + */ + sheet_signals[BUTTON_EVENT_COLUMN] = + g_signal_new ("button-event-column", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + psppire_marshal_VOID__INT_POINTER, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_POINTER + ); + + + /** + * PsppireSheet::button-event-row + * @sheet: the sheet widget that emitted the signal + * @column: the column on which the event occured. + * + * A button event occured on a row title button + */ + sheet_signals[BUTTON_EVENT_ROW] = + g_signal_new ("button-event-row", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + psppire_marshal_VOID__INT_POINTER, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_POINTER + ); + + + sheet_signals[SELECT_RANGE] = + g_signal_new ("select-range", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, select_range), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + PSPPIRE_TYPE_SHEET_RANGE); + + + sheet_signals[RESIZE_RANGE] = + g_signal_new ("resize-range", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, resize_range), + NULL, NULL, + psppire_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, + 2, + PSPPIRE_TYPE_SHEET_RANGE, PSPPIRE_TYPE_SHEET_RANGE + ); + + sheet_signals[MOVE_RANGE] = + g_signal_new ("move-range", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, move_range), + NULL, NULL, + psppire_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, + 2, + PSPPIRE_TYPE_SHEET_RANGE, PSPPIRE_TYPE_SHEET_RANGE + ); + + sheet_signals[TRAVERSE] = + g_signal_new ("traverse", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, traverse), + NULL, NULL, + psppire_marshal_BOOLEAN__BOXED_POINTER, + G_TYPE_BOOLEAN, 2, + PSPPIRE_TYPE_SHEET_CELL, + G_TYPE_POINTER); + + + sheet_signals[ACTIVATE] = + g_signal_new ("activate", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, activate), + NULL, NULL, + psppire_marshal_VOID__INT_INT_INT_INT, + G_TYPE_NONE, 4, + G_TYPE_INT, G_TYPE_INT, + G_TYPE_INT, G_TYPE_INT); + + widget_class->set_scroll_adjustments_signal = + g_signal_new ("set-scroll-adjustments", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + offsetof (PsppireSheetClass, set_scroll_adjustments), + NULL, NULL, + psppire_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); + + + container_class->add = NULL; + container_class->remove = NULL; + container_class->forall = psppire_sheet_forall; + + object_class->dispose = psppire_sheet_dispose; + object_class->finalize = psppire_sheet_finalize; + + + vaxis_spec = + g_param_spec_pointer ("vertical-axis", + "Vertical Axis", + "A pointer to the PsppireAxis object for the rows", + G_PARAM_READABLE | G_PARAM_WRITABLE ); + + haxis_spec = + g_param_spec_pointer ("horizontal-axis", + "Horizontal Axis", + "A pointer to the PsppireAxis object for the columns", + G_PARAM_READABLE | G_PARAM_WRITABLE ); + + model_spec = + g_param_spec_pointer ("model", + "Model", + "A pointer to the data model", + G_PARAM_READABLE | G_PARAM_WRITABLE ); + + + object_class->set_property = psppire_sheet_set_property; + object_class->get_property = psppire_sheet_get_property; + + g_object_class_install_property (object_class, + PROP_VAXIS, + vaxis_spec); + + g_object_class_install_property (object_class, + PROP_HAXIS, + haxis_spec); + + g_object_class_install_property (object_class, + PROP_MODEL, + model_spec); + + + widget_class->realize = psppire_sheet_realize; + widget_class->unrealize = psppire_sheet_unrealize; + widget_class->map = psppire_sheet_map; + widget_class->unmap = psppire_sheet_unmap; + widget_class->style_set = psppire_sheet_style_set; + widget_class->button_press_event = psppire_sheet_button_press; + widget_class->button_release_event = psppire_sheet_button_release; + widget_class->motion_notify_event = psppire_sheet_motion; + widget_class->enter_notify_event = psppire_sheet_crossing_notify; + widget_class->leave_notify_event = psppire_sheet_crossing_notify; + widget_class->key_press_event = psppire_sheet_key_press; + widget_class->expose_event = psppire_sheet_expose; + widget_class->size_request = psppire_sheet_size_request; + widget_class->size_allocate = psppire_sheet_size_allocate; + widget_class->focus_in_event = NULL; + widget_class->focus_out_event = NULL; + + klass->set_scroll_adjustments = psppire_sheet_set_scroll_adjustments; + klass->select_row = NULL; + klass->select_column = NULL; + klass->select_range = NULL; + klass->resize_range = NULL; + klass->move_range = NULL; + klass->traverse = NULL; + klass->activate = NULL; + klass->changed = NULL; +} + +static void +psppire_sheet_init (PsppireSheet *sheet) +{ + sheet->model = NULL; + sheet->haxis = NULL; + sheet->vaxis = NULL; + + sheet->flags = 0; + sheet->selection_mode = GTK_SELECTION_NONE; + sheet->state = PSPPIRE_SHEET_NORMAL; + + GTK_WIDGET_UNSET_FLAGS (sheet, GTK_NO_WINDOW); + GTK_WIDGET_SET_FLAGS (sheet, GTK_CAN_FOCUS); + + sheet->column_title_window = NULL; + sheet->column_title_area.x = 0; + sheet->column_title_area.y = 0; + sheet->column_title_area.width = 0; + sheet->column_title_area.height = DEFAULT_ROW_HEIGHT; + + sheet->row_title_window = NULL; + sheet->row_title_area.x = 0; + sheet->row_title_area.y = 0; + sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH; + sheet->row_title_area.height = 0; + + + sheet->active_cell.row = 0; + sheet->active_cell.col = 0; + sheet->selection_cell.row = 0; + sheet->selection_cell.col = 0; + + sheet->range.row0 = 0; + sheet->range.rowi = 0; + sheet->range.col0 = 0; + sheet->range.coli = 0; + + sheet->state = PSPPIRE_SHEET_NORMAL; + + sheet->sheet_window = NULL; + sheet->entry_widget = NULL; + sheet->button = NULL; + + sheet->hadjustment = NULL; + sheet->vadjustment = NULL; + + sheet->cursor_drag = NULL; + + sheet->xor_gc = NULL; + sheet->fg_gc = NULL; + sheet->bg_gc = NULL; + sheet->x_drag = 0; + sheet->y_drag = 0; + sheet->show_grid = TRUE; + + sheet->motion_timer = 0; + + sheet->row_titles_visible = TRUE; + sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH; + + sheet->column_titles_visible = TRUE; + + + /* create sheet entry */ + sheet->entry_type = GTK_TYPE_ENTRY; + create_sheet_entry (sheet); + + /* create global selection button */ + create_global_button (sheet); +} + + +/* Cause RANGE to be redrawn. If RANGE is null, then the + entire visible range will be redrawn. + */ +static void +redraw_range (PsppireSheet *sheet, PsppireSheetRange *range) +{ + GdkRectangle rect; + + if ( ! GTK_WIDGET_REALIZED (sheet)) + return; + + if ( NULL != range ) + rectangle_from_range (sheet, range, &rect); + else + { + GdkRegion *r = gdk_drawable_get_visible_region (sheet->sheet_window); + gdk_region_get_clipbox (r, &rect); + + if ( sheet->column_titles_visible) + { + rect.y += sheet->column_title_area.height; + rect.height -= sheet->column_title_area.height; + } + + if ( sheet->row_titles_visible) + { + rect.x += sheet->row_title_area.width; + rect.width -= sheet->row_title_area.width; + } + } + + gdk_window_invalidate_rect (sheet->sheet_window, &rect, FALSE); +} + + +/* Callback which occurs whenever columns are inserted / deleted in the model */ +static void +columns_inserted_deleted_callback (PsppireSheetModel *model, gint first_column, + gint n_columns, + gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + + PsppireSheetRange range; + gint model_columns = psppire_sheet_model_get_column_count (model); + + + /* Need to update all the columns starting from the first column and onwards. + * Previous column are unchanged, so don't need to be updated. + */ + range.col0 = first_column; + range.row0 = 0; + range.coli = psppire_axis_unit_count (sheet->haxis) - 1; + range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1; + + adjust_scrollbars (sheet); + + if (sheet->active_cell.col >= model_columns) + change_active_cell (sheet, sheet->active_cell.row, model_columns - 1); + + draw_column_title_buttons_range (sheet, + first_column, max_visible_column (sheet)); + + + redraw_range (sheet, &range); +} + + + + +/* Callback which occurs whenever rows are inserted / deleted in the model */ +static void +rows_inserted_deleted_callback (PsppireSheetModel *model, gint first_row, + gint n_rows, gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + + PsppireSheetRange range; + + gint model_rows = psppire_sheet_model_get_row_count (model); + + /* Need to update all the rows starting from the first row and onwards. + * Previous rows are unchanged, so don't need to be updated. + */ + range.row0 = first_row; + range.col0 = 0; + range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1; + range.coli = psppire_axis_unit_count (sheet->haxis) - 1; + + adjust_scrollbars (sheet); + + if (sheet->active_cell.row >= model_rows) + change_active_cell (sheet, model_rows - 1, sheet->active_cell.col); + + draw_row_title_buttons_range (sheet, first_row, max_visible_row (sheet)); + + redraw_range (sheet, &range); +} + +/* + If row0 or rowi are negative, then all rows will be updated. + If col0 or coli are negative, then all columns will be updated. +*/ +static void +range_update_callback (PsppireSheetModel *m, gint row0, gint col0, + gint rowi, gint coli, gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + + PsppireSheetRange range; + + range.row0 = row0; + range.col0 = col0; + range.rowi = rowi; + range.coli = coli; + + if ( !GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + if ( ( row0 < 0 && col0 < 0 ) || ( rowi < 0 && coli < 0 ) ) + { + redraw_range (sheet, NULL); + adjust_scrollbars (sheet); + + draw_row_title_buttons_range (sheet, min_visible_row (sheet), + max_visible_row (sheet)); + + draw_column_title_buttons_range (sheet, min_visible_column (sheet), + max_visible_column (sheet)); + + return; + } + else if ( row0 < 0 || rowi < 0 ) + { + range.row0 = min_visible_row (sheet); + range.rowi = max_visible_row (sheet); + } + else if ( col0 < 0 || coli < 0 ) + { + range.col0 = min_visible_column (sheet); + range.coli = max_visible_column (sheet); + } + + redraw_range (sheet, &range); +} + + +/** + * psppire_sheet_new: + * @rows: initial number of rows + * @columns: initial number of columns + * @title: sheet title + * @model: the model to use for the sheet data + * + * Creates a new sheet widget with the given number of rows and columns. + * + * Returns: the new sheet widget + */ +GtkWidget * +psppire_sheet_new (PsppireSheetModel *model) +{ + GtkWidget *widget = g_object_new (PSPPIRE_TYPE_SHEET, + "model", model, + NULL); + return widget; +} + + +/** + * psppire_sheet_set_model + * @sheet: the sheet to set the model for + * @model: the model to use for the sheet data + * + * Sets the model for a PsppireSheet + * + */ +void +psppire_sheet_set_model (PsppireSheet *sheet, PsppireSheetModel *model) +{ + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (sheet->model ) g_object_unref (sheet->model); + + sheet->model = model; + + if ( model) + { + g_object_ref (model); + + sheet->update_handler_id = g_signal_connect (model, "range_changed", + G_CALLBACK (range_update_callback), + sheet); + + g_signal_connect (model, "rows_inserted", + G_CALLBACK (rows_inserted_deleted_callback), sheet); + + g_signal_connect (model, "rows_deleted", + G_CALLBACK (rows_inserted_deleted_callback), sheet); + + g_signal_connect (model, "columns_inserted", + G_CALLBACK (columns_inserted_deleted_callback), sheet); + + g_signal_connect (model, "columns_deleted", + G_CALLBACK (columns_inserted_deleted_callback), sheet); + } +} + + +void +psppire_sheet_change_entry (PsppireSheet *sheet, GtkType entry_type) +{ + gint state; + + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + state = sheet->state; + + if (sheet->state == PSPPIRE_SHEET_NORMAL) + psppire_sheet_hide_entry_widget (sheet); + + sheet->entry_type = entry_type; + + create_sheet_entry (sheet); + + if (state == PSPPIRE_SHEET_NORMAL) + { + psppire_sheet_show_entry_widget (sheet); + } + +} + +void +psppire_sheet_show_grid (PsppireSheet *sheet, gboolean show) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (show == sheet->show_grid) return; + + sheet->show_grid = show; + + redraw_range (sheet, NULL); +} + +gboolean +psppire_sheet_grid_visible (PsppireSheet *sheet) +{ + g_return_val_if_fail (sheet != NULL, 0); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0); + + return sheet->show_grid; +} + +guint +psppire_sheet_get_columns_count (PsppireSheet *sheet) +{ + g_return_val_if_fail (sheet != NULL, 0); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0); + + return psppire_axis_unit_count (sheet->haxis); +} + +static void set_column_width (PsppireSheet *sheet, + gint column, + gint width); + + +void +psppire_sheet_show_column_titles (PsppireSheet *sheet) +{ + if (sheet->column_titles_visible) return; + + sheet->column_titles_visible = TRUE; + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + gdk_window_show (sheet->column_title_window); + gdk_window_move_resize (sheet->column_title_window, + sheet->column_title_area.x, + sheet->column_title_area.y, + sheet->column_title_area.width, + sheet->column_title_area.height); + + adjust_scrollbars (sheet); + + if (sheet->vadjustment) + g_signal_emit_by_name (sheet->vadjustment, + "value_changed"); + + size_allocate_global_button (sheet); + + if ( sheet->row_titles_visible) + gtk_widget_show (sheet->button); +} + + +void +psppire_sheet_show_row_titles (PsppireSheet *sheet) +{ + if (sheet->row_titles_visible) return; + + sheet->row_titles_visible = TRUE; + + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + { + gdk_window_show (sheet->row_title_window); + gdk_window_move_resize (sheet->row_title_window, + sheet->row_title_area.x, + sheet->row_title_area.y, + sheet->row_title_area.width, + sheet->row_title_area.height); + + adjust_scrollbars (sheet); + } + + if (sheet->hadjustment) + g_signal_emit_by_name (sheet->hadjustment, + "value_changed"); + + size_allocate_global_button (sheet); + + if ( sheet->column_titles_visible) + gtk_widget_show (sheet->button); +} + +void +psppire_sheet_hide_column_titles (PsppireSheet *sheet) +{ + if (!sheet->column_titles_visible) return; + + sheet->column_titles_visible = FALSE; + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + { + if (sheet->column_title_window) + gdk_window_hide (sheet->column_title_window); + + gtk_widget_hide (sheet->button); + + adjust_scrollbars (sheet); + } + + if (sheet->vadjustment) + g_signal_emit_by_name (sheet->vadjustment, + "value_changed"); +} + +void +psppire_sheet_hide_row_titles (PsppireSheet *sheet) +{ + if (!sheet->row_titles_visible) return; + + sheet->row_titles_visible = FALSE; + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + { + if (sheet->row_title_window) + gdk_window_hide (sheet->row_title_window); + + gtk_widget_hide (sheet->button); + + adjust_scrollbars (sheet); + } + + if (sheet->hadjustment) + g_signal_emit_by_name (sheet->hadjustment, + "value_changed"); +} + + +/* Scroll the sheet so that the cell ROW, COLUMN is visible. + If {ROW,COL}_ALIGN is zero, then the cell will be placed + at the {top,left} of the sheet. If it's 1, then it'll + be placed at the {bottom,right}. + ROW or COL may be -1, in which case scrolling in that dimension + does not occur. + */ +void +psppire_sheet_moveto (PsppireSheet *sheet, + gint row, + gint col, + gfloat row_align, + gfloat col_align) +{ + gint width, height; + + g_return_if_fail (row_align >= 0); + g_return_if_fail (col_align >= 0); + + g_return_if_fail (row_align <= 1); + g_return_if_fail (col_align <= 1); + + g_return_if_fail (col < + psppire_axis_unit_count (sheet->haxis)); + g_return_if_fail (row < + psppire_axis_unit_count (sheet->vaxis)); + + gdk_drawable_get_size (sheet->sheet_window, &width, &height); + + + if (row >= 0) + { + gint y = psppire_axis_start_pixel (sheet->vaxis, row); + + gtk_adjustment_set_value (sheet->vadjustment, y - height * row_align); + } + + + if (col >= 0) + { + gint x = psppire_axis_start_pixel (sheet->haxis, col); + + gtk_adjustment_set_value (sheet->hadjustment, x - width * col_align); + } +} + + +void +psppire_sheet_select_row (PsppireSheet *sheet, gint row) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis)) + return; + + if (sheet->state != PSPPIRE_SHEET_NORMAL) + psppire_sheet_real_unselect_range (sheet, NULL); + + sheet->state = PSPPIRE_SHEET_ROW_SELECTED; + sheet->range.row0 = row; + sheet->range.col0 = 0; + sheet->range.rowi = row; + sheet->range.coli = psppire_axis_unit_count (sheet->haxis) - 1; + sheet->active_cell.row = row; + + g_signal_emit (sheet, sheet_signals[SELECT_ROW], 0, row); + psppire_sheet_real_select_range (sheet, NULL); +} + + +void +psppire_sheet_select_column (PsppireSheet *sheet, gint column) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis)) + return; + + if (sheet->state != PSPPIRE_SHEET_NORMAL) + psppire_sheet_real_unselect_range (sheet, NULL); + + sheet->state = PSPPIRE_SHEET_COLUMN_SELECTED; + sheet->range.row0 = 0; + sheet->range.col0 = column; + sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1; + sheet->range.coli = column; + sheet->active_cell.col = column; + + g_signal_emit (sheet, sheet_signals[SELECT_COLUMN], 0, column); + psppire_sheet_real_select_range (sheet, NULL); +} + + + + +static gboolean +psppire_sheet_range_isvisible (const PsppireSheet *sheet, + const PsppireSheetRange *range) +{ + g_return_val_if_fail (sheet != NULL, FALSE); + + if (range->row0 < 0 || range->row0 >= psppire_axis_unit_count (sheet->vaxis)) + return FALSE; + + if (range->rowi < 0 || range->rowi >= psppire_axis_unit_count (sheet->vaxis)) + return FALSE; + + if (range->col0 < 0 || range->col0 >= psppire_axis_unit_count (sheet->haxis)) + return FALSE; + + if (range->coli < 0 || range->coli >= psppire_axis_unit_count (sheet->haxis)) + return FALSE; + + if (range->rowi < min_visible_row (sheet)) + return FALSE; + + if (range->row0 > max_visible_row (sheet)) + return FALSE; + + if (range->coli < min_visible_column (sheet)) + return FALSE; + + if (range->col0 > max_visible_column (sheet)) + return FALSE; + + return TRUE; +} + +static gboolean +psppire_sheet_cell_isvisible (PsppireSheet *sheet, + gint row, gint column) +{ + PsppireSheetRange range; + + range.row0 = row; + range.col0 = column; + range.rowi = row; + range.coli = column; + + return psppire_sheet_range_isvisible (sheet, &range); +} + +void +psppire_sheet_get_visible_range (PsppireSheet *sheet, PsppireSheetRange *range) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)) ; + g_return_if_fail (range != NULL); + + range->row0 = min_visible_row (sheet); + range->col0 = min_visible_column (sheet); + range->rowi = max_visible_row (sheet); + range->coli = max_visible_column (sheet); +} + + +static gboolean +psppire_sheet_set_scroll_adjustments (PsppireSheet *sheet, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment) +{ + if ( sheet->vadjustment != vadjustment ) + { + if (sheet->vadjustment) + g_object_unref (sheet->vadjustment); + sheet->vadjustment = vadjustment; + + if ( vadjustment) + { + g_object_ref (vadjustment); + + g_signal_connect (sheet->vadjustment, "value_changed", + G_CALLBACK (vadjustment_value_changed), + sheet); + } + } + + if ( sheet->hadjustment != hadjustment ) + { + if (sheet->hadjustment) + g_object_unref (sheet->hadjustment); + + sheet->hadjustment = hadjustment; + + if ( hadjustment) + { + g_object_ref (hadjustment); + + g_signal_connect (sheet->hadjustment, "value_changed", + G_CALLBACK (hadjustment_value_changed), + sheet); + } + } + return TRUE; +} + +static void +psppire_sheet_finalize (GObject *object) +{ + PsppireSheet *sheet; + + g_return_if_fail (object != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (object)); + + sheet = PSPPIRE_SHEET (object); + + if (G_OBJECT_CLASS (parent_class)->finalize) + (*G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +static void +psppire_sheet_dispose (GObject *object) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (object); + + g_return_if_fail (object != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (object)); + + if ( sheet->dispose_has_run ) + return ; + + sheet->dispose_has_run = TRUE; + + if (sheet->model) g_object_unref (sheet->model); + if (sheet->vaxis) g_object_unref (sheet->vaxis); + if (sheet->haxis) g_object_unref (sheet->haxis); + + g_object_unref (sheet->button); + sheet->button = NULL; + + /* unref adjustments */ + if (sheet->hadjustment) + { + g_signal_handlers_disconnect_matched (sheet->hadjustment, + G_SIGNAL_MATCH_DATA, + 0, 0, 0, 0, + sheet); + + g_object_unref (sheet->hadjustment); + sheet->hadjustment = NULL; + } + + if (sheet->vadjustment) + { + g_signal_handlers_disconnect_matched (sheet->vadjustment, + G_SIGNAL_MATCH_DATA, + 0, 0, 0, 0, + sheet); + + g_object_unref (sheet->vadjustment); + + sheet->vadjustment = NULL; + } + + if (G_OBJECT_CLASS (parent_class)->dispose) + (*G_OBJECT_CLASS (parent_class)->dispose) (object); +} + +static void +psppire_sheet_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + PsppireSheet *sheet; + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + + if (GTK_WIDGET_CLASS (parent_class)->style_set) + (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style); + + sheet = PSPPIRE_SHEET (widget); + + if (GTK_WIDGET_REALIZED (widget)) + { + gtk_style_set_background (widget->style, widget->window, widget->state); + } + + set_entry_widget_font (sheet); +} + +#define BORDER_WIDTH 3 + +static void +psppire_sheet_realize (GtkWidget *widget) +{ + PsppireSheet *sheet; + GdkWindowAttr attributes; + const gint attributes_mask = + GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR; + + GdkGCValues values; + GdkColormap *colormap; + GdkDisplay *display; + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + + sheet = PSPPIRE_SHEET (widget); + + colormap = gtk_widget_get_colormap (widget); + display = gtk_widget_get_display (widget); + + 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 = colormap; + + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_EXPOSURE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK); + + attributes.cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW); + + /* main window */ + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); + + gdk_window_set_user_data (widget->window, sheet); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + + gdk_color_parse ("white", &sheet->color[BG_COLOR]); + gdk_colormap_alloc_color (colormap, &sheet->color[BG_COLOR], FALSE, + TRUE); + gdk_color_parse ("gray", &sheet->color[GRID_COLOR]); + gdk_colormap_alloc_color (colormap, &sheet->color[GRID_COLOR], FALSE, + TRUE); + + attributes.x = 0; + attributes.y = 0; + attributes.width = sheet->column_title_area.width; + attributes.height = sheet->column_title_area.height; + + + /* column - title window */ + sheet->column_title_window = + gdk_window_new (widget->window, &attributes, attributes_mask); + gdk_window_set_user_data (sheet->column_title_window, sheet); + gtk_style_set_background (widget->style, sheet->column_title_window, + GTK_STATE_NORMAL); + + + attributes.x = 0; + attributes.y = 0; + attributes.width = sheet->row_title_area.width; + attributes.height = sheet->row_title_area.height; + + /* row - title window */ + sheet->row_title_window = gdk_window_new (widget->window, + &attributes, attributes_mask); + gdk_window_set_user_data (sheet->row_title_window, sheet); + gtk_style_set_background (widget->style, sheet->row_title_window, + GTK_STATE_NORMAL); + + /* sheet - window */ + attributes.cursor = gdk_cursor_new_for_display (display, GDK_PLUS); + + attributes.x = 0; + attributes.y = 0; + + sheet->sheet_window = gdk_window_new (widget->window, + &attributes, attributes_mask); + gdk_window_set_user_data (sheet->sheet_window, sheet); + + gdk_cursor_unref (attributes.cursor); + + gdk_window_set_background (sheet->sheet_window, &widget->style->white); + gdk_window_show (sheet->sheet_window); + + /* GCs */ + sheet->fg_gc = gdk_gc_new (widget->window); + sheet->bg_gc = gdk_gc_new (widget->window); + + values.foreground = widget->style->white; + values.function = GDK_INVERT; + values.subwindow_mode = GDK_INCLUDE_INFERIORS; + values.line_width = BORDER_WIDTH; + + sheet->xor_gc = gdk_gc_new_with_values (widget->window, + &values, + GDK_GC_FOREGROUND | + GDK_GC_FUNCTION | + GDK_GC_SUBWINDOW | + GDK_GC_LINE_WIDTH + ); + + + gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window); + gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet)); + + gtk_widget_set_parent_window (sheet->button, sheet->sheet_window); + gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet)); + + + sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS); + + if (sheet->column_titles_visible) + gdk_window_show (sheet->column_title_window); + if (sheet->row_titles_visible) + gdk_window_show (sheet->row_title_window); + + sheet->hover_window = create_hover_window (); + + draw_row_title_buttons (sheet); + draw_column_title_buttons (sheet); + + psppire_sheet_update_primary_selection (sheet); + + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); +} + +static void +create_global_button (PsppireSheet *sheet) +{ + sheet->button = gtk_button_new_with_label (" "); + + GTK_WIDGET_UNSET_FLAGS(sheet->button, GTK_CAN_FOCUS); + + g_object_ref_sink (sheet->button); + + g_signal_connect (sheet->button, + "pressed", + G_CALLBACK (global_button_clicked), + sheet); +} + +static void +size_allocate_global_button (PsppireSheet *sheet) +{ + GtkAllocation allocation; + + if (!sheet->column_titles_visible) return; + if (!sheet->row_titles_visible) return; + + gtk_widget_size_request (sheet->button, NULL); + + allocation.x = 0; + allocation.y = 0; + allocation.width = sheet->row_title_area.width; + allocation.height = sheet->column_title_area.height; + + gtk_widget_size_allocate (sheet->button, &allocation); +} + +static void +global_button_clicked (GtkWidget *widget, gpointer data) +{ + psppire_sheet_click_cell (PSPPIRE_SHEET (data), -1, -1); +} + + +static void +psppire_sheet_unrealize (GtkWidget *widget) +{ + PsppireSheet *sheet; + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + + sheet = PSPPIRE_SHEET (widget); + + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = NULL; + + gdk_colormap_free_colors (gtk_widget_get_colormap (widget), + sheet->color, n_COLORS); + + g_object_unref (sheet->xor_gc); + g_object_unref (sheet->fg_gc); + g_object_unref (sheet->bg_gc); + + destroy_hover_window (sheet->hover_window); + + gdk_window_destroy (sheet->sheet_window); + gdk_window_destroy (sheet->column_title_window); + gdk_window_destroy (sheet->row_title_window); + + gtk_widget_unparent (sheet->entry_widget); + if (sheet->button != NULL) + gtk_widget_unparent (sheet->button); + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +psppire_sheet_map (GtkWidget *widget) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + + if (!GTK_WIDGET_MAPPED (widget)) + { + GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED); + + gdk_window_show (widget->window); + gdk_window_show (sheet->sheet_window); + + if (sheet->column_titles_visible) + { + draw_column_title_buttons (sheet); + gdk_window_show (sheet->column_title_window); + } + if (sheet->row_titles_visible) + { + draw_row_title_buttons (sheet); + gdk_window_show (sheet->row_title_window); + } + + if (!GTK_WIDGET_MAPPED (sheet->entry_widget) + && sheet->active_cell.row >= 0 + && sheet->active_cell.col >= 0 ) + { + gtk_widget_show (sheet->entry_widget); + gtk_widget_map (sheet->entry_widget); + } + + if (!GTK_WIDGET_MAPPED (sheet->button)) + { + gtk_widget_show (sheet->button); + gtk_widget_map (sheet->button); + } + + redraw_range (sheet, NULL); + change_active_cell (sheet, + sheet->active_cell.row, + sheet->active_cell.col); + } +} + +static void +psppire_sheet_unmap (GtkWidget *widget) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + if (!GTK_WIDGET_MAPPED (widget)) + return; + + GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED); + + gdk_window_hide (sheet->sheet_window); + if (sheet->column_titles_visible) + gdk_window_hide (sheet->column_title_window); + if (sheet->row_titles_visible) + gdk_window_hide (sheet->row_title_window); + gdk_window_hide (widget->window); + + if (GTK_WIDGET_MAPPED (sheet->entry_widget)) + gtk_widget_unmap (sheet->entry_widget); + + if (GTK_WIDGET_MAPPED (sheet->button)) + gtk_widget_unmap (sheet->button); +} + + +static void +psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col) +{ + PangoLayout *layout; + PangoRectangle text; + PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc; + gint font_height; + + gchar *label; + + PsppireSheetCellAttr attributes; + GdkRectangle area; + + g_return_if_fail (sheet != NULL); + + /* bail now if we aren't yet drawable */ + if (!GTK_WIDGET_DRAWABLE (sheet)) return; + + if (row < 0 || + row >= psppire_axis_unit_count (sheet->vaxis)) + return; + + if (col < 0 || + col >= psppire_axis_unit_count (sheet->haxis)) + return; + + psppire_sheet_get_attributes (sheet, row, col, &attributes); + + /* select GC for background rectangle */ + gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground); + gdk_gc_set_foreground (sheet->bg_gc, &attributes.background); + + rectangle_from_cell (sheet, row, col, &area); + + gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0); + + if (sheet->show_grid) + { + gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]); + + gdk_draw_rectangle (sheet->sheet_window, + sheet->bg_gc, + FALSE, + area.x, area.y, + area.width, area.height); + } + + + label = psppire_sheet_cell_get_text (sheet, row, col); + if (NULL == label) + return; + + + layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label); + dispose_string (sheet, label); + + + pango_layout_set_font_description (layout, font_desc); + + pango_layout_get_pixel_extents (layout, NULL, &text); + + gdk_gc_set_clip_rectangle (sheet->fg_gc, &area); + + font_height = pango_font_description_get_size (font_desc); + if ( !pango_font_description_get_size_is_absolute (font_desc)) + font_height /= PANGO_SCALE; + + /* Centre the text vertically */ + area.y += (area.height - font_height) / 2.0; + + switch (attributes.justification) + { + case GTK_JUSTIFY_RIGHT: + area.x += area.width - text.width; + break; + case GTK_JUSTIFY_CENTER: + area.x += (area.width - text.width) / 2.0; + break; + case GTK_JUSTIFY_LEFT: + /* Do nothing */ + break; + default: + g_critical ("Unhandled justification %d in column %d\n", + attributes.justification, col); + break; + } + + gdk_draw_layout (sheet->sheet_window, sheet->fg_gc, + area.x, + area.y, + layout); + + gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL); + g_object_unref (layout); +} + + +static void +draw_sheet_region (PsppireSheet *sheet, GdkRegion *region) +{ + PsppireSheetRange range; + GdkRectangle area; + gint y, x; + gint i, j; + + PsppireSheetRange drawing_range; + + gdk_region_get_clipbox (region, &area); + + y = area.y + sheet->vadjustment->value; + x = area.x + sheet->hadjustment->value; + + if ( sheet->column_titles_visible) + y -= sheet->column_title_area.height; + + if ( sheet->row_titles_visible) + x -= sheet->row_title_area.width; + + maximize_int (&x, 0); + maximize_int (&y, 0); + + range.row0 = row_from_ypixel (sheet, y); + range.rowi = row_from_ypixel (sheet, y + area.height); + + range.col0 = column_from_xpixel (sheet, x); + range.coli = column_from_xpixel (sheet, x + area.width); + + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_SHEET (sheet)); + + if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return; + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return; + + + drawing_range.row0 = MAX (range.row0, min_visible_row (sheet)); + drawing_range.col0 = MAX (range.col0, min_visible_column (sheet)); + drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet)); + drawing_range.coli = MIN (range.coli, max_visible_column (sheet)); + + g_return_if_fail (drawing_range.rowi >= drawing_range.row0); + g_return_if_fail (drawing_range.coli >= drawing_range.col0); + + for (i = drawing_range.row0; i <= drawing_range.rowi; i++) + { + for (j = drawing_range.col0; j <= drawing_range.coli; j++) + psppire_sheet_cell_draw (sheet, i, j); + } + + if (sheet->state != PSPPIRE_SHEET_NORMAL && + psppire_sheet_range_isvisible (sheet, &sheet->range)) + psppire_sheet_range_draw_selection (sheet, drawing_range); + + + if (sheet->state == GTK_STATE_NORMAL && + sheet->active_cell.row >= drawing_range.row0 && + sheet->active_cell.row <= drawing_range.rowi && + sheet->active_cell.col >= drawing_range.col0 && + sheet->active_cell.col <= drawing_range.coli) + psppire_sheet_show_entry_widget (sheet); +} + + +static void +psppire_sheet_range_draw_selection (PsppireSheet *sheet, PsppireSheetRange range) +{ + GdkRectangle area; + gint i, j; + PsppireSheetRange aux; + + if (range.col0 > sheet->range.coli || range.coli < sheet->range.col0 || + range.row0 > sheet->range.rowi || range.rowi < sheet->range.row0) + return; + + if (!psppire_sheet_range_isvisible (sheet, &range)) return; + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + + aux = range; + + range.col0 = MAX (sheet->range.col0, range.col0); + range.coli = MIN (sheet->range.coli, range.coli); + range.row0 = MAX (sheet->range.row0, range.row0); + range.rowi = MIN (sheet->range.rowi, range.rowi); + + range.col0 = MAX (range.col0, min_visible_column (sheet)); + range.coli = MIN (range.coli, max_visible_column (sheet)); + range.row0 = MAX (range.row0, min_visible_row (sheet)); + range.rowi = MIN (range.rowi, max_visible_row (sheet)); + + for (i = range.row0; i <= range.rowi; i++) + { + for (j = range.col0; j <= range.coli; j++) + { + if (psppire_sheet_cell_get_state (sheet, i, j) == GTK_STATE_SELECTED) + { + rectangle_from_cell (sheet, i, j, &area); + + if (i == sheet->range.row0) + { + area.y = area.y + 2; + area.height = area.height - 2; + } + if (i == sheet->range.rowi) area.height = area.height - 3; + if (j == sheet->range.col0) + { + area.x = area.x + 2; + area.width = area.width - 2; + } + if (j == sheet->range.coli) area.width = area.width - 3; + + if (i != sheet->active_cell.row || j != sheet->active_cell.col) + { + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + area.x + 1, area.y + 1, + area.width, area.height); + } + } + + } + } + + psppire_sheet_draw_border (sheet, sheet->range); +} + +static inline gint +safe_strcmp (const gchar *s1, const gchar *s2) +{ + if ( !s1 && !s2) return 0; + if ( !s1) return -1; + if ( !s2) return +1; + return strcmp (s1, s2); +} + +static void +psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col, + GtkJustification justification, + const gchar *text) +{ + PsppireSheetModel *model ; + gchar *old_text ; + + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (col >= psppire_axis_unit_count (sheet->haxis) + || row >= psppire_axis_unit_count (sheet->vaxis)) + return; + + if (col < 0 || row < 0) return; + + model = psppire_sheet_get_model (sheet); + + old_text = psppire_sheet_model_get_string (model, row, col); + + if (0 != safe_strcmp (old_text, text)) + { + g_signal_handler_block (sheet->model, sheet->update_handler_id); + psppire_sheet_model_set_string (model, text, row, col); + g_signal_handler_unblock (sheet->model, sheet->update_handler_id); + } + + if ( psppire_sheet_model_free_strings (model)) + g_free (old_text); +} + + +void +psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column) +{ + PsppireSheetRange range; + + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + if (column >= psppire_axis_unit_count (sheet->haxis) || + row >= psppire_axis_unit_count (sheet->vaxis)) return; + + if (column < 0 || row < 0) return; + + range.row0 = row; + range.rowi = row; + range.col0 = min_visible_column (sheet); + range.coli = max_visible_column (sheet); + + psppire_sheet_real_cell_clear (sheet, row, column); + + redraw_range (sheet, &range); +} + +static void +psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column) +{ + PsppireSheetModel *model = psppire_sheet_get_model (sheet); + + gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column); + + if (old_text && strlen (old_text) > 0 ) + { + psppire_sheet_model_datum_clear (model, row, column); + } + + dispose_string (sheet, old_text); +} + +gchar * +psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col) +{ + PsppireSheetModel *model; + g_return_val_if_fail (sheet != NULL, NULL); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL); + + if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis)) + return NULL; + if (col < 0 || row < 0) return NULL; + + model = psppire_sheet_get_model (sheet); + + if ( !model ) + return NULL; + + return psppire_sheet_model_get_string (model, row, col); +} + + +static GtkStateType +psppire_sheet_cell_get_state (PsppireSheet *sheet, gint row, gint col) +{ + gint state; + PsppireSheetRange *range; + + g_return_val_if_fail (sheet != NULL, 0); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0); + if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis)) return 0; + if (col < 0 || row < 0) return 0; + + state = sheet->state; + range = &sheet->range; + + switch (state) + { + case PSPPIRE_SHEET_NORMAL: + return GTK_STATE_NORMAL; + break; + case PSPPIRE_SHEET_ROW_SELECTED: + if (row >= range->row0 && row <= range->rowi) + return GTK_STATE_SELECTED; + break; + case PSPPIRE_SHEET_COLUMN_SELECTED: + if (col >= range->col0 && col <= range->coli) + return GTK_STATE_SELECTED; + break; + case PSPPIRE_SHEET_RANGE_SELECTED: + if (row >= range->row0 && row <= range->rowi && \ + col >= range->col0 && col <= range->coli) + return GTK_STATE_SELECTED; + break; + } + return GTK_STATE_NORMAL; +} + +/* Convert X, Y (in pixels) to *ROW, *COLUMN + If the function returns FALSE, then the results will be unreliable. +*/ +static gboolean +psppire_sheet_get_pixel_info (PsppireSheet *sheet, + gint x, + gint y, + gint *row, + gint *column) +{ + gint trow, tcol; + *row = -G_MAXINT; + *column = -G_MAXINT; + + g_return_val_if_fail (sheet != NULL, 0); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0); + + /* bounds checking, return false if the user clicked + on a blank area */ + if (y < 0) + return FALSE; + + if (x < 0) + return FALSE; + + if ( sheet->column_titles_visible) + y -= sheet->column_title_area.height; + + y += sheet->vadjustment->value; + + if ( y < 0 && sheet->column_titles_visible) + { + trow = -1; + } + else + { + trow = row_from_ypixel (sheet, y); + if (trow > psppire_axis_unit_count (sheet->vaxis)) + return FALSE; + } + + *row = trow; + + if ( sheet->row_titles_visible) + x -= sheet->row_title_area.width; + + x += sheet->hadjustment->value; + + if ( x < 0 && sheet->row_titles_visible) + { + tcol = -1; + } + else + { + tcol = column_from_xpixel (sheet, x); + if (tcol > psppire_axis_unit_count (sheet->haxis)) + return FALSE; + } + + *column = tcol; + + return TRUE; +} + +gboolean +psppire_sheet_get_cell_area (PsppireSheet *sheet, + gint row, + gint column, + GdkRectangle *area) +{ + g_return_val_if_fail (sheet != NULL, 0); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0); + + if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis)) + return FALSE; + + area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column); + area->y = (row == -1) ? 0 : psppire_axis_start_pixel (sheet->vaxis, row); + + area->width= (column == -1) ? sheet->row_title_area.width + : psppire_axis_unit_size (sheet->haxis, column); + + area->height= (row == -1) ? sheet->column_title_area.height + : psppire_axis_unit_size (sheet->vaxis, row); + + return TRUE; +} + +void +psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (row < -1 || col < -1) + return; + + if (row >= psppire_axis_unit_count (sheet->vaxis) + || + col >= psppire_axis_unit_count (sheet->haxis)) + return; + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + if ( row == -1 || col == -1) + { + psppire_sheet_hide_entry_widget (sheet); + return; + } + + change_active_cell (sheet, row, col); +} + +void +psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if ( row ) *row = sheet->active_cell.row; + if (column) *column = sheet->active_cell.col; +} + +static void +entry_load_text (PsppireSheet *sheet) +{ + gint row, col; + const char *text; + GtkJustification justification; + PsppireSheetCellAttr attributes; + + if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return; + if (sheet->state != GTK_STATE_NORMAL) return; + + row = sheet->active_cell.row; + col = sheet->active_cell.col; + + if (row < 0 || col < 0) return; + + text = gtk_entry_get_text (psppire_sheet_get_entry (sheet)); + + if (text && strlen (text) > 0) + { + psppire_sheet_get_attributes (sheet, row, col, &attributes); + justification = attributes.justification; + psppire_sheet_set_cell (sheet, row, col, justification, text); + } +} + + +static void +psppire_sheet_hide_entry_widget (PsppireSheet *sheet) +{ + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + if (sheet->active_cell.row < 0 || + sheet->active_cell.col < 0) return; + + gtk_widget_hide (sheet->entry_widget); + gtk_widget_unmap (sheet->entry_widget); + + GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE); +} + +static void +change_active_cell (PsppireSheet *sheet, gint row, gint col) +{ + gint old_row, old_col; + + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (row < 0 || col < 0) + return; + + if ( row > psppire_axis_unit_count (sheet->vaxis) + || col > psppire_axis_unit_count (sheet->haxis)) + return; + + if (sheet->state != PSPPIRE_SHEET_NORMAL) + { + sheet->state = PSPPIRE_SHEET_NORMAL; + psppire_sheet_real_unselect_range (sheet, NULL); + } + + old_row = sheet->active_cell.row; + old_col = sheet->active_cell.col; + + /* Erase the old cell */ + psppire_sheet_draw_active_cell (sheet); + + entry_load_text (sheet); + + sheet->range.row0 = row; + sheet->range.col0 = col; + sheet->range.rowi = row; + sheet->range.coli = col; + sheet->active_cell.row = row; + sheet->active_cell.col = col; + sheet->selection_cell.row = row; + sheet->selection_cell.col = col; + + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS); + + psppire_sheet_draw_active_cell (sheet); + psppire_sheet_show_entry_widget (sheet); + + GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS); + + g_signal_emit (sheet, sheet_signals [ACTIVATE], 0, + row, col, old_row, old_col); + +} + +static void +psppire_sheet_show_entry_widget (PsppireSheet *sheet) +{ + GtkEntry *sheet_entry; + PsppireSheetCellAttr attributes; + + gint row, col; + + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + row = sheet->active_cell.row; + col = sheet->active_cell.col; + + /* Don't show the active cell, if there is no active cell: */ + if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */ + return; + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + if (sheet->state != PSPPIRE_SHEET_NORMAL) return; + if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return; + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE); + + sheet_entry = psppire_sheet_get_entry (sheet); + + psppire_sheet_get_attributes (sheet, row, col, &attributes); + + if (GTK_IS_ENTRY (sheet_entry)) + { + gchar *text = psppire_sheet_cell_get_text (sheet, row, col); + const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry)); + + if ( ! text ) + text = g_strdup (""); + + if (strcmp (old_text, text) != 0) + gtk_entry_set_text (sheet_entry, text); + + dispose_string (sheet, text); + + { + switch (attributes.justification) + { + case GTK_JUSTIFY_RIGHT: + gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0); + break; + case GTK_JUSTIFY_CENTER: + gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5); + break; + case GTK_JUSTIFY_LEFT: + default: + gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0); + break; + } + } + } + + psppire_sheet_size_allocate_entry (sheet); + + gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry), + psppire_sheet_model_is_editable (sheet->model, + row, col)); + gtk_widget_map (sheet->entry_widget); +} + +static gboolean +psppire_sheet_draw_active_cell (PsppireSheet *sheet) +{ + gint row, col; + PsppireSheetRange range; + + row = sheet->active_cell.row; + col = sheet->active_cell.col; + + if (row < 0 || col < 0) return FALSE; + + if (!psppire_sheet_cell_isvisible (sheet, row, col)) + return FALSE; + + range.col0 = range.coli = col; + range.row0 = range.rowi = row; + + psppire_sheet_draw_border (sheet, range); + + return FALSE; +} + + + +static void +psppire_sheet_new_selection (PsppireSheet *sheet, PsppireSheetRange *range) +{ + gint i, j, mask1, mask2; + gint state, selected; + gint x, y, width, height; + PsppireSheetRange new_range, aux_range; + + g_return_if_fail (sheet != NULL); + + if (range == NULL) range=&sheet->range; + + new_range=*range; + + range->row0 = MIN (range->row0, sheet->range.row0); + range->rowi = MAX (range->rowi, sheet->range.rowi); + range->col0 = MIN (range->col0, sheet->range.col0); + range->coli = MAX (range->coli, sheet->range.coli); + + range->row0 = MAX (range->row0, min_visible_row (sheet)); + range->rowi = MIN (range->rowi, max_visible_row (sheet)); + range->col0 = MAX (range->col0, min_visible_column (sheet)); + range->coli = MIN (range->coli, max_visible_column (sheet)); + + aux_range.row0 = MAX (new_range.row0, min_visible_row (sheet)); + aux_range.rowi = MIN (new_range.rowi, max_visible_row (sheet)); + aux_range.col0 = MAX (new_range.col0, min_visible_column (sheet)); + aux_range.coli = MIN (new_range.coli, max_visible_column (sheet)); + + for (i = range->row0; i <= range->rowi; i++) + { + for (j = range->col0; j <= range->coli; j++) + { + + state = psppire_sheet_cell_get_state (sheet, i, j); + selected= (i <= new_range.rowi && i >= new_range.row0 && + j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE; + + if (state == GTK_STATE_SELECTED && selected && + (i == sheet->range.row0 || i == sheet->range.rowi || + j == sheet->range.col0 || j == sheet->range.coli || + i == new_range.row0 || i == new_range.rowi || + j == new_range.col0 || j == new_range.coli)) + { + + mask1 = i == sheet->range.row0 ? 1 : 0; + mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1; + mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1; + mask1 = j == sheet->range.coli ? mask1 + 8 : mask1; + + mask2 = i == new_range.row0 ? 1 : 0; + mask2 = i == new_range.rowi ? mask2 + 2 : mask2; + mask2 = j == new_range.col0 ? mask2 + 4 : mask2; + mask2 = j == new_range.coli ? mask2 + 8 : mask2; + + if (mask1 != mask2) + { + x = psppire_axis_start_pixel (sheet->haxis, j); + y = psppire_axis_start_pixel (sheet->vaxis, i); + width = psppire_axis_start_pixel (sheet->haxis, j)- x+ + psppire_axis_unit_size (sheet->haxis, j); + height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i); + + if (i == sheet->range.row0) + { + y = y - 3; + height = height + 3; + } + if (i == sheet->range.rowi) height = height + 3; + if (j == sheet->range.col0) + { + x = x - 3; + width = width + 3; + } + if (j == sheet->range.coli) width = width + 3; + + if (i != sheet->active_cell.row || j != sheet->active_cell.col) + { + x = psppire_axis_start_pixel (sheet->haxis, j); + y = psppire_axis_start_pixel (sheet->vaxis, i); + width = psppire_axis_start_pixel (sheet->haxis, j)- x+ + psppire_axis_unit_size (sheet->haxis, j); + + height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i); + + if (i == new_range.row0) + { + y = y+2; + height = height - 2; + } + if (i == new_range.rowi) height = height - 3; + if (j == new_range.col0) + { + x = x+2; + width = width - 2; + } + if (j == new_range.coli) width = width - 3; + + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x + 1, y + 1, + width, height); + } + } + } + } + } + + for (i = range->row0; i <= range->rowi; i++) + { + for (j = range->col0; j <= range->coli; j++) + { + + state = psppire_sheet_cell_get_state (sheet, i, j); + selected= (i <= new_range.rowi && i >= new_range.row0 && + j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE; + + if (state == GTK_STATE_SELECTED && !selected) + { + + x = psppire_axis_start_pixel (sheet->haxis, j); + y = psppire_axis_start_pixel (sheet->vaxis, i); + width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j); + height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i); + + if (i == sheet->range.row0) + { + y = y - 3; + height = height + 3; + } + if (i == sheet->range.rowi) height = height + 3; + if (j == sheet->range.col0) + { + x = x - 3; + width = width + 3; + } + if (j == sheet->range.coli) width = width + 3; + + } + } + } + + for (i = range->row0; i <= range->rowi; i++) + { + for (j = range->col0; j <= range->coli; j++) + { + + state = psppire_sheet_cell_get_state (sheet, i, j); + selected= (i <= new_range.rowi && i >= new_range.row0 && + j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE; + + if (state != GTK_STATE_SELECTED && selected && + (i != sheet->active_cell.row || j != sheet->active_cell.col)) + { + + x = psppire_axis_start_pixel (sheet->haxis, j); + y = psppire_axis_start_pixel (sheet->vaxis, i); + width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j); + height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i); + + if (i == new_range.row0) + { + y = y+2; + height = height - 2; + } + if (i == new_range.rowi) height = height - 3; + if (j == new_range.col0) + { + x = x+2; + width = width - 2; + } + if (j == new_range.coli) width = width - 3; + + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x + 1, y + 1, + width, height); + + } + + } + } + + for (i = aux_range.row0; i <= aux_range.rowi; i++) + { + for (j = aux_range.col0; j <= aux_range.coli; j++) + { + state = psppire_sheet_cell_get_state (sheet, i, j); + + mask1 = i == sheet->range.row0 ? 1 : 0; + mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1; + mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1; + mask1 = j == sheet->range.coli ? mask1 + 8 : mask1; + + mask2 = i == new_range.row0 ? 1 : 0; + mask2 = i == new_range.rowi ? mask2 + 2 : mask2; + mask2 = j == new_range.col0 ? mask2 + 4 : mask2; + mask2 = j == new_range.coli ? mask2 + 8 : mask2; + if (mask2 != mask1 || (mask2 == mask1 && state != GTK_STATE_SELECTED)) + { + x = psppire_axis_start_pixel (sheet->haxis, j); + y = psppire_axis_start_pixel (sheet->vaxis, i); + width = psppire_axis_unit_size (sheet->haxis, j); + height = psppire_axis_unit_size (sheet->vaxis, i); + if (mask2 & 1) + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x + 1, y - 1, + width, 3); + + + if (mask2 & 2) + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x + 1, y + height - 1, + width, 3); + + if (mask2 & 4) + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x - 1, y + 1, + 3, height); + + + if (mask2 & 8) + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + TRUE, + x + width - 1, y + 1, + 3, height); + } + } + } + + *range = new_range; +} + + + +static void +psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range) +{ + GdkRectangle area; + + rectangle_from_range (sheet, &new_range, &area); + + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + FALSE, + area.x, area.y, + area.width, area.height); +} + + +static void +psppire_sheet_real_select_range (PsppireSheet *sheet, + const PsppireSheetRange *range) +{ + gint state; + + g_return_if_fail (sheet != NULL); + + if (range == NULL) range = &sheet->range; + + memcpy (&sheet->range, range, sizeof (*range)); + + if (range->row0 < 0 || range->rowi < 0) return; + if (range->col0 < 0 || range->coli < 0) return; + + state = sheet->state; + +#if 0 + if (range->coli != sheet->range.coli || range->col0 != sheet->range.col0 || + range->rowi != sheet->range.rowi || range->row0 != sheet->range.row0) + { + psppire_sheet_new_selection (sheet, &sheet->range); + } + else + { + psppire_sheet_range_draw_selection (sheet, sheet->range); + } +#endif + + psppire_sheet_update_primary_selection (sheet); + + g_signal_emit (sheet, sheet_signals[SELECT_RANGE], 0, &sheet->range); +} + + +void +psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range) +{ + g_return_if_fail (sheet != NULL); + *range = sheet->range; +} + + +void +psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range) +{ + g_return_if_fail (sheet != NULL); + + if (range == NULL) range=&sheet->range; + + if (range->row0 < 0 || range->rowi < 0) return; + if (range->col0 < 0 || range->coli < 0) return; + + + if (sheet->state != PSPPIRE_SHEET_NORMAL) + psppire_sheet_real_unselect_range (sheet, NULL); + + sheet->range.row0 = range->row0; + sheet->range.rowi = range->rowi; + sheet->range.col0 = range->col0; + sheet->range.coli = range->coli; + sheet->active_cell.row = range->row0; + sheet->active_cell.col = range->col0; + sheet->selection_cell.row = range->rowi; + sheet->selection_cell.col = range->coli; + + sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + psppire_sheet_real_select_range (sheet, NULL); +} + +void +psppire_sheet_unselect_range (PsppireSheet *sheet) +{ + if (! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + psppire_sheet_real_unselect_range (sheet, NULL); + sheet->state = GTK_STATE_NORMAL; + + change_active_cell (sheet, + sheet->active_cell.row, sheet->active_cell.col); +} + + +static void +psppire_sheet_real_unselect_range (PsppireSheet *sheet, + const PsppireSheetRange *range) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))); + + if ( range == NULL) + range = &sheet->range; + + if (range->row0 < 0 || range->rowi < 0) return; + if (range->col0 < 0 || range->coli < 0) return; + + g_signal_emit (sheet, sheet_signals[SELECT_COLUMN], 0, -1); + g_signal_emit (sheet, sheet_signals[SELECT_ROW], 0, -1); + + sheet->range.row0 = -1; + sheet->range.rowi = -1; + sheet->range.col0 = -1; + sheet->range.coli = -1; +} + + +static gint +psppire_sheet_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + g_return_val_if_fail (event != NULL, FALSE); + + if (!GTK_WIDGET_DRAWABLE (widget)) + return FALSE; + + /* exposure events on the sheet */ + if (event->window == sheet->row_title_window && + sheet->row_titles_visible) + { + draw_row_title_buttons_range (sheet, + min_visible_row (sheet), + max_visible_row (sheet)); + } + + if (event->window == sheet->column_title_window && + sheet->column_titles_visible) + { + draw_column_title_buttons_range (sheet, + min_visible_column (sheet), + max_visible_column (sheet)); + } + + if (event->window == sheet->sheet_window) + { + draw_sheet_region (sheet, event->region); + +#if 0 + if (sheet->state != PSPPIRE_SHEET_NORMAL) + { + if (psppire_sheet_range_isvisible (sheet, &sheet->range)) + psppire_sheet_range_draw (sheet, &sheet->range); + + if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet)) + psppire_sheet_range_draw (sheet, &sheet->drag_range); + + if (psppire_sheet_range_isvisible (sheet, &sheet->range)) + psppire_sheet_range_draw_selection (sheet, sheet->range); + if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet)) + draw_xor_rectangle (sheet, sheet->drag_range); + } +#endif + + if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet))) + { + GdkRectangle rect; + PsppireSheetRange range; + range.row0 = range.rowi = sheet->active_cell.row; + range.col0 = range.coli = sheet->active_cell.col; + + rectangle_from_range (sheet, &range, &rect); + + if (GDK_OVERLAP_RECTANGLE_OUT != + gdk_region_rect_in (event->region, &rect)) + { + psppire_sheet_draw_active_cell (sheet); + } + } + + } + + (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event); + + return FALSE; +} + + +static gboolean +psppire_sheet_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + PsppireSheet *sheet; + GdkModifierType mods; + gint x, y; + gint row, column; + gboolean veto; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sheet = PSPPIRE_SHEET (widget); + + /* Cancel any pending tooltips */ + if (sheet->motion_timer) + { + g_source_remove (sheet->motion_timer); + sheet->motion_timer = 0; + } + + gtk_widget_get_pointer (widget, &x, &y); + psppire_sheet_get_pixel_info (sheet, x, y, &row, &column); + + + if (event->window == sheet->column_title_window) + { + sheet->x_drag = event->x; + g_signal_emit (sheet, + sheet_signals[BUTTON_EVENT_COLUMN], 0, + column, event); + + if (psppire_sheet_model_get_column_sensitivity (sheet->model, column)) + { + if ( event->type == GDK_2BUTTON_PRESS && event->button == 1) + g_signal_emit (sheet, + sheet_signals[DOUBLE_CLICK_COLUMN], 0, column); + } + } + else if (event->window == sheet->row_title_window) + { + g_signal_emit (sheet, + sheet_signals[BUTTON_EVENT_ROW], 0, + row, event); + + if (psppire_sheet_model_get_row_sensitivity (sheet->model, row)) + { + if ( event->type == GDK_2BUTTON_PRESS && event->button == 1) + g_signal_emit (sheet, + sheet_signals[DOUBLE_CLICK_ROW], 0, row); + } + } + + gdk_window_get_pointer (widget->window, NULL, NULL, &mods); + + if (! (mods & GDK_BUTTON1_MASK)) return TRUE; + + + /* press on resize windows */ + if (event->window == sheet->column_title_window) + { + sheet->x_drag = event->x; + + if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col)) + { + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG); + gdk_pointer_grab (sheet->column_title_window, FALSE, + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, NULL, event->time); + + draw_xor_vline (sheet); + return TRUE; + } + } + + if (event->window == sheet->row_title_window) + { + sheet->y_drag = event->y; + + if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row)) + { + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG); + gdk_pointer_grab (sheet->row_title_window, FALSE, + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, NULL, event->time); + + draw_xor_hline (sheet); + return TRUE; + } + } + + /* the sheet itself does not handle other than single click events */ + if (event->type != GDK_BUTTON_PRESS) return FALSE; + + /* selections on the sheet */ + if (event->window == sheet->sheet_window) + { + gtk_widget_get_pointer (widget, &x, &y); + psppire_sheet_get_pixel_info (sheet, x, y, &row, &column); + gdk_pointer_grab (sheet->sheet_window, FALSE, + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, NULL, event->time); + gtk_grab_add (GTK_WIDGET (sheet)); + + if (sheet->selection_mode != GTK_SELECTION_SINGLE && + sheet->selection_mode != GTK_SELECTION_NONE && + sheet->cursor_drag->type == GDK_SIZING && + !PSPPIRE_SHEET_IN_SELECTION (sheet) && !PSPPIRE_SHEET_IN_RESIZE (sheet)) + { + if (sheet->state == GTK_STATE_NORMAL) + { + row = sheet->active_cell.row; + column = sheet->active_cell.col; + sheet->active_cell.row = row; + sheet->active_cell.col = column; + sheet->drag_range = sheet->range; + sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + psppire_sheet_select_range (sheet, &sheet->drag_range); + } + sheet->x_drag = x; + sheet->y_drag = y; + if (row > sheet->range.rowi) row--; + if (column > sheet->range.coli) column--; + sheet->drag_cell.row = row; + sheet->drag_cell.col = column; + sheet->drag_range = sheet->range; + draw_xor_rectangle (sheet, sheet->drag_range); + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE); + } + else if (sheet->cursor_drag->type == GDK_TOP_LEFT_ARROW && + !PSPPIRE_SHEET_IN_SELECTION (sheet) + && ! PSPPIRE_SHEET_IN_DRAG (sheet) + && sheet->active_cell.row >= 0 + && sheet->active_cell.col >= 0 + ) + { + if (sheet->state == GTK_STATE_NORMAL) + { + row = sheet->active_cell.row; + column = sheet->active_cell.col; + sheet->active_cell.row = row; + sheet->active_cell.col = column; + sheet->drag_range = sheet->range; + sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + psppire_sheet_select_range (sheet, &sheet->drag_range); + } + sheet->x_drag = x; + sheet->y_drag = y; + if (row < sheet->range.row0) row++; + if (row > sheet->range.rowi) row--; + if (column < sheet->range.col0) column++; + if (column > sheet->range.coli) column--; + sheet->drag_cell.row = row; + sheet->drag_cell.col = column; + sheet->drag_range = sheet->range; + draw_xor_rectangle (sheet, sheet->drag_range); + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG); + } + else + { + veto = psppire_sheet_click_cell (sheet, row, column); + if (veto) PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + } + } + + if (event->window == sheet->column_title_window) + { + gtk_widget_get_pointer (widget, &x, &y); + if ( sheet->row_titles_visible) + x -= sheet->row_title_area.width; + + x += sheet->hadjustment->value; + + column = column_from_xpixel (sheet, x); + + if (psppire_sheet_model_get_column_sensitivity (sheet->model, column)) + { + veto = psppire_sheet_click_cell (sheet, -1, column); + gtk_grab_add (GTK_WIDGET (sheet)); + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + } + } + + if (event->window == sheet->row_title_window) + { + gtk_widget_get_pointer (widget, &x, &y); + if ( sheet->column_titles_visible) + y -= sheet->column_title_area.height; + + y += sheet->vadjustment->value; + + row = row_from_ypixel (sheet, y); + if (psppire_sheet_model_get_row_sensitivity (sheet->model, row)) + { + veto = psppire_sheet_click_cell (sheet, row, -1); + gtk_grab_add (GTK_WIDGET (sheet)); + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + } + } + + return TRUE; +} + +static gboolean +psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column) +{ + PsppireSheetCell cell; + gboolean forbid_move; + + cell.row = row; + cell.col = column; + + if (row >= psppire_axis_unit_count (sheet->vaxis) + || column >= psppire_axis_unit_count (sheet->haxis)) + { + return FALSE; + } + + g_signal_emit (sheet, sheet_signals[TRAVERSE], 0, + &sheet->active_cell, + &cell, + &forbid_move); + + if (forbid_move) + { + if (sheet->state == GTK_STATE_NORMAL) + return FALSE; + + row = sheet->active_cell.row; + column = sheet->active_cell.col; + + change_active_cell (sheet, row, column); + return FALSE; + } + + if (row == -1 && column >= 0) + { + psppire_sheet_select_column (sheet, column); + return TRUE; + } + + if (column == -1 && row >= 0) + { + psppire_sheet_select_row (sheet, row); + return TRUE; + } + + if (row == -1 && column == -1) + { + sheet->range.row0 = 0; + sheet->range.col0 = 0; + sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1; + sheet->range.coli = + psppire_axis_unit_count (sheet->haxis) - 1; + sheet->active_cell.row = 0; + sheet->active_cell.col = 0; + psppire_sheet_select_range (sheet, NULL); + return TRUE; + } + + if (sheet->state != PSPPIRE_SHEET_NORMAL) + { + sheet->state = PSPPIRE_SHEET_NORMAL; + psppire_sheet_real_unselect_range (sheet, NULL); + } + else + { + change_active_cell (sheet, row, column); + } + + sheet->active_cell.row = row; + sheet->active_cell.col = column; + sheet->selection_cell.row = row; + sheet->selection_cell.col = column; + sheet->range.row0 = row; + sheet->range.col0 = column; + sheet->range.rowi = row; + sheet->range.coli = column; + sheet->state = PSPPIRE_SHEET_NORMAL; + PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget)); + + return TRUE; +} + +static gint +psppire_sheet_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GdkDisplay *display = gtk_widget_get_display (widget); + + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + /* release on resize windows */ + if (PSPPIRE_SHEET_IN_XDRAG (sheet)) + { + gint width; + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG); + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + gdk_display_pointer_ungrab (display, event->time); + draw_xor_vline (sheet); + + width = event->x - + psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col) + + sheet->hadjustment->value; + + set_column_width (sheet, sheet->drag_cell.col, width); + + return TRUE; + } + + if (PSPPIRE_SHEET_IN_YDRAG (sheet)) + { + gint height; + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG); + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + gdk_display_pointer_ungrab (display, event->time); + draw_xor_hline (sheet); + + height = event->y - + psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) + + sheet->vadjustment->value; + + set_row_height (sheet, sheet->drag_cell.row, height); + + return TRUE; + } + + if (PSPPIRE_SHEET_IN_DRAG (sheet)) + { + PsppireSheetRange old_range; + draw_xor_rectangle (sheet, sheet->drag_range); + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG); + gdk_display_pointer_ungrab (display, event->time); + + psppire_sheet_real_unselect_range (sheet, NULL); + + sheet->active_cell.row = sheet->active_cell.row + + (sheet->drag_range.row0 - sheet->range.row0); + sheet->active_cell.col = sheet->active_cell.col + + (sheet->drag_range.col0 - sheet->range.col0); + sheet->selection_cell.row = sheet->selection_cell.row + + (sheet->drag_range.row0 - sheet->range.row0); + sheet->selection_cell.col = sheet->selection_cell.col + + (sheet->drag_range.col0 - sheet->range.col0); + old_range = sheet->range; + sheet->range = sheet->drag_range; + sheet->drag_range = old_range; + g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0, + &sheet->drag_range, &sheet->range); + psppire_sheet_select_range (sheet, &sheet->range); + } + + if (PSPPIRE_SHEET_IN_RESIZE (sheet)) + { + PsppireSheetRange old_range; + draw_xor_rectangle (sheet, sheet->drag_range); + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE); + gdk_display_pointer_ungrab (display, event->time); + + psppire_sheet_real_unselect_range (sheet, NULL); + + sheet->active_cell.row = sheet->active_cell.row + + (sheet->drag_range.row0 - sheet->range.row0); + sheet->active_cell.col = sheet->active_cell.col + + (sheet->drag_range.col0 - sheet->range.col0); + if (sheet->drag_range.row0 < sheet->range.row0) + sheet->selection_cell.row = sheet->drag_range.row0; + if (sheet->drag_range.rowi >= sheet->range.rowi) + sheet->selection_cell.row = sheet->drag_range.rowi; + if (sheet->drag_range.col0 < sheet->range.col0) + sheet->selection_cell.col = sheet->drag_range.col0; + if (sheet->drag_range.coli >= sheet->range.coli) + sheet->selection_cell.col = sheet->drag_range.coli; + old_range = sheet->range; + sheet->range = sheet->drag_range; + sheet->drag_range = old_range; + + if (sheet->state == GTK_STATE_NORMAL) sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0, + &sheet->drag_range, &sheet->range); + psppire_sheet_select_range (sheet, &sheet->range); + } + + if (sheet->state == PSPPIRE_SHEET_NORMAL && PSPPIRE_SHEET_IN_SELECTION (sheet)) + { + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + gdk_display_pointer_ungrab (display, event->time); + change_active_cell (sheet, sheet->active_cell.row, + sheet->active_cell.col); + } + + if (PSPPIRE_SHEET_IN_SELECTION) + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (GTK_WIDGET (sheet)); + + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + return TRUE; +} + + + + + +/* Shamelessly lifted from gtktooltips */ +static gboolean +psppire_sheet_subtitle_paint_window (GtkWidget *tip_window) +{ + GtkRequisition req; + + gtk_widget_size_request (tip_window, &req); + gtk_paint_flat_box (tip_window->style, tip_window->window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, GTK_WIDGET(tip_window), "tooltip", + 0, 0, req.width, req.height); + + return FALSE; +} + +static void +destroy_hover_window (PsppireSheetHoverTitle *h) +{ + gtk_widget_destroy (h->window); + g_free (h); +} + +static PsppireSheetHoverTitle * +create_hover_window (void) +{ + PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw)); + + hw->window = gtk_window_new (GTK_WINDOW_POPUP); + +#if GTK_CHECK_VERSION (2, 9, 0) + gtk_window_set_type_hint (GTK_WINDOW (hw->window), + GDK_WINDOW_TYPE_HINT_TOOLTIP); +#endif + + gtk_widget_set_app_paintable (hw->window, TRUE); + gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE); + gtk_widget_set_name (hw->window, "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4); + + g_signal_connect (hw->window, + "expose_event", + G_CALLBACK (psppire_sheet_subtitle_paint_window), + NULL); + + hw->label = gtk_label_new (NULL); + + + gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE); + gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5); + + gtk_container_add (GTK_CONTAINER (hw->window), hw->label); + + gtk_widget_show (hw->label); + + g_signal_connect (hw->window, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &hw->window); + + return hw; +} + +#define HOVER_WINDOW_Y_OFFSET 2 + +static void +show_subtitle (PsppireSheet *sheet, gint row, gint column, const gchar *subtitle) +{ + gint x, y; + gint px, py; + gint width; + + if ( ! subtitle ) + return; + + gtk_label_set_text (GTK_LABEL (sheet->hover_window->label), + subtitle); + + + sheet->hover_window->row = row; + sheet->hover_window->column = column; + + gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y); + + gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py); + + gtk_widget_show (sheet->hover_window->window); + + width = GTK_WIDGET (sheet->hover_window->label)->allocation.width; + + if (row == -1 ) + { + x += px; + x -= width / 2; + y += sheet->column_title_area.y; + y += sheet->column_title_area.height; + y += HOVER_WINDOW_Y_OFFSET; + } + + if ( column == -1 ) + { + y += py; + x += sheet->row_title_area.x; + x += sheet->row_title_area.width * 2 / 3.0; + } + + gtk_window_move (GTK_WINDOW (sheet->hover_window->window), + x, y); +} + +static gboolean +motion_timeout_callback (gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + gint x, y; + gint row, column; + gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y); + + if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) ) + { + if (sheet->row_title_under && row >= 0) + { + gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row); + + show_subtitle (sheet, row, -1, text); + g_free (text); + } + + if (sheet->column_title_under && column >= 0) + { + gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model, + column); + + show_subtitle (sheet, -1, column, text); + + g_free (text); + } + } + + return FALSE; +} + +static gboolean +psppire_sheet_motion (GtkWidget *widget, GdkEventMotion *event) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + GdkModifierType mods; + GdkCursorType new_cursor; + gint x, y; + gint row, column; + GdkDisplay *display; + + g_return_val_if_fail (event != NULL, FALSE); + + display = gtk_widget_get_display (widget); + + /* selections on the sheet */ + x = event->x; + y = event->y; + + if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window)) + { + if ( sheet->motion_timer > 0 ) + g_source_remove (sheet->motion_timer); + sheet->motion_timer = + g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet); + } + else + { + gint row, column; + gint wx, wy; + gtk_widget_get_pointer (widget, &wx, &wy); + + if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) ) + { + if ( row != sheet->hover_window->row || + column != sheet->hover_window->column) + { + gtk_widget_hide (sheet->hover_window->window); + } + } + } + + if (event->window == sheet->column_title_window) + { + if (!PSPPIRE_SHEET_IN_SELECTION (sheet) && + on_column_boundary (sheet, x, &column)) + { + new_cursor = GDK_SB_H_DOUBLE_ARROW; + if (new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = + gdk_cursor_new_for_display (display, new_cursor); + + gdk_window_set_cursor (sheet->column_title_window, + sheet->cursor_drag); + } + } + else + { + new_cursor = GDK_TOP_LEFT_ARROW; + if (!PSPPIRE_SHEET_IN_XDRAG (sheet) && + new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = + gdk_cursor_new_for_display (display, new_cursor); + gdk_window_set_cursor (sheet->column_title_window, + sheet->cursor_drag); + } + } + } + else if (event->window == sheet->row_title_window) + { + if (!PSPPIRE_SHEET_IN_SELECTION (sheet) && + on_row_boundary (sheet, y, &row)) + { + new_cursor = GDK_SB_V_DOUBLE_ARROW; + if (new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = + gdk_cursor_new_for_display (display, new_cursor); + gdk_window_set_cursor (sheet->row_title_window, + sheet->cursor_drag); + } + } + else + { + new_cursor = GDK_TOP_LEFT_ARROW; + if (!PSPPIRE_SHEET_IN_YDRAG (sheet) && + new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = + gdk_cursor_new_for_display (display, new_cursor); + gdk_window_set_cursor (sheet->row_title_window, + sheet->cursor_drag); + } + } + } + + new_cursor = GDK_PLUS; + if ( event->window == sheet->sheet_window && + !POSSIBLE_DRAG (sheet, x, y, &row, &column) && + !PSPPIRE_SHEET_IN_DRAG (sheet) && + !POSSIBLE_RESIZE (sheet, x, y, &row, &column) && + !PSPPIRE_SHEET_IN_RESIZE (sheet) && + new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS); + gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag); + } + + new_cursor = GDK_TOP_LEFT_ARROW; + if ( event->window == sheet->sheet_window && + ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) || + PSPPIRE_SHEET_IN_RESIZE (sheet)) && + (POSSIBLE_DRAG (sheet, x, y, &row, &column) || + PSPPIRE_SHEET_IN_DRAG (sheet)) && + new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW); + gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag); + } + + new_cursor = GDK_SIZING; + if ( event->window == sheet->sheet_window && + sheet->selection_mode != GTK_SELECTION_NONE && + !PSPPIRE_SHEET_IN_DRAG (sheet) && + (POSSIBLE_RESIZE (sheet, x, y, &row, &column) || + PSPPIRE_SHEET_IN_RESIZE (sheet)) && + new_cursor != sheet->cursor_drag->type) + { + gdk_cursor_unref (sheet->cursor_drag); + sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_SIZING); + gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag); + } + + + gdk_window_get_pointer (widget->window, &x, &y, &mods); + if (! (mods & GDK_BUTTON1_MASK)) return FALSE; + + if (PSPPIRE_SHEET_IN_XDRAG (sheet)) + { + if (event->x != sheet->x_drag) + { + draw_xor_vline (sheet); + sheet->x_drag = event->x; + draw_xor_vline (sheet); + } + + return TRUE; + } + + if (PSPPIRE_SHEET_IN_YDRAG (sheet)) + { + if (event->y != sheet->y_drag) + { + draw_xor_hline (sheet); + sheet->y_drag = event->y; + draw_xor_hline (sheet); + } + + return TRUE; + } + + if (PSPPIRE_SHEET_IN_DRAG (sheet)) + { + PsppireSheetRange aux; + column = column_from_xpixel (sheet, x)- sheet->drag_cell.col; + row = row_from_ypixel (sheet, y) - sheet->drag_cell.row; + if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0; + if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0; + sheet->x_drag = x; + sheet->y_drag = y; + aux = sheet->range; + if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) && + aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis)) + { + aux = sheet->drag_range; + sheet->drag_range.row0 = sheet->range.row0 + row; + sheet->drag_range.col0 = sheet->range.col0 + column; + sheet->drag_range.rowi = sheet->range.rowi + row; + sheet->drag_range.coli = sheet->range.coli + column; + if (aux.row0 != sheet->drag_range.row0 || + aux.col0 != sheet->drag_range.col0) + { + draw_xor_rectangle (sheet, aux); + draw_xor_rectangle (sheet, sheet->drag_range); + } + } + return TRUE; + } + + if (PSPPIRE_SHEET_IN_RESIZE (sheet)) + { + PsppireSheetRange aux; + gint v_h, current_col, current_row, col_threshold, row_threshold; + v_h = 1; + if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) > + abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2; + + current_col = column_from_xpixel (sheet, x); + current_row = row_from_ypixel (sheet, y); + column = current_col - sheet->drag_cell.col; + row = current_row - sheet->drag_cell.row; + + /*use half of column width resp. row height as threshold to + expand selection*/ + col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) + + psppire_axis_unit_size (sheet->haxis, current_col) / 2; + if (column > 0) + { + if (x < col_threshold) + column -= 1; + } + else if (column < 0) + { + if (x > col_threshold) + column +=1; + } + row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) + + psppire_axis_unit_size (sheet->vaxis, current_row)/2; + if (row > 0) + { + if (y < row_threshold) + row -= 1; + } + else if (row < 0) + { + if (y > row_threshold) + row +=1; + } + + if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0; + if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0; + sheet->x_drag = x; + sheet->y_drag = y; + aux = sheet->range; + + if (v_h == 1) + column = 0; + else + row = 0; + + if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) && + aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis)) + { + aux = sheet->drag_range; + sheet->drag_range = sheet->range; + + if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row; + if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row; + if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column; + if (column > 0) sheet->drag_range.coli = sheet->range.coli + column; + + if (aux.row0 != sheet->drag_range.row0 || + aux.rowi != sheet->drag_range.rowi || + aux.col0 != sheet->drag_range.col0 || + aux.coli != sheet->drag_range.coli) + { + draw_xor_rectangle (sheet, aux); + draw_xor_rectangle (sheet, sheet->drag_range); + } + } + return TRUE; + } + + psppire_sheet_get_pixel_info (sheet, x, y, &row, &column); + + if (sheet->state == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row && + column == sheet->active_cell.col) return TRUE; + + if (PSPPIRE_SHEET_IN_SELECTION (sheet) && mods&GDK_BUTTON1_MASK) + psppire_sheet_extend_selection (sheet, row, column); + + return TRUE; +} + +static gboolean +psppire_sheet_crossing_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + if (event->window == sheet->column_title_window) + sheet->column_title_under = event->type == GDK_ENTER_NOTIFY; + else if (event->window == sheet->row_title_window) + sheet->row_title_under = event->type == GDK_ENTER_NOTIFY; + + return TRUE; +} + +static void +psppire_sheet_extend_selection (PsppireSheet *sheet, gint row, gint column) +{ + PsppireSheetRange range; + gint state; + gint r, c; + + if (row == sheet->selection_cell.row && column == sheet->selection_cell.col) + return; + + if (sheet->selection_mode == GTK_SELECTION_SINGLE) return; + + gtk_widget_grab_focus (GTK_WIDGET (sheet)); + + if (PSPPIRE_SHEET_IN_DRAG (sheet)) return; + + state = sheet->state; + + switch (sheet->state) + { + case PSPPIRE_SHEET_ROW_SELECTED: + column = psppire_axis_unit_count (sheet->haxis) - 1; + break; + case PSPPIRE_SHEET_COLUMN_SELECTED: + row = psppire_axis_unit_count (sheet->vaxis) - 1; + break; + case PSPPIRE_SHEET_NORMAL: + sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + r = sheet->active_cell.row; + c = sheet->active_cell.col; + sheet->range.col0 = c; + sheet->range.row0 = r; + sheet->range.coli = c; + sheet->range.rowi = r; + psppire_sheet_range_draw_selection (sheet, sheet->range); + case PSPPIRE_SHEET_RANGE_SELECTED: + sheet->state = PSPPIRE_SHEET_RANGE_SELECTED; + } + + sheet->selection_cell.row = row; + sheet->selection_cell.col = column; + + range.col0 = MIN (column, sheet->active_cell.col); + range.coli = MAX (column, sheet->active_cell.col); + range.row0 = MIN (row, sheet->active_cell.row); + range.rowi = MAX (row, sheet->active_cell.row); + + if (range.row0 != sheet->range.row0 || range.rowi != sheet->range.rowi || + range.col0 != sheet->range.col0 || range.coli != sheet->range.coli || + state == PSPPIRE_SHEET_NORMAL) + psppire_sheet_real_select_range (sheet, &range); + +} + +static gint +psppire_sheet_entry_key_press (GtkWidget *widget, + GdkEventKey *key) +{ + gboolean focus; + g_signal_emit_by_name (widget, "key_press_event", key, &focus); + return focus; +} + + +/* Number of rows in a step-increment */ +#define ROWS_PER_STEP 1 + + +static void +page_vertical (PsppireSheet *sheet, GtkScrollType dir) +{ + gint old_row = sheet->active_cell.row ; + glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row); + + gint new_row; + + vpixel -= psppire_axis_start_pixel (sheet->vaxis, + min_visible_row (sheet)); + + switch ( dir) + { + case GTK_SCROLL_PAGE_DOWN: + gtk_adjustment_set_value (sheet->vadjustment, + sheet->vadjustment->value + + sheet->vadjustment->page_increment); + break; + case GTK_SCROLL_PAGE_UP: + gtk_adjustment_set_value (sheet->vadjustment, + sheet->vadjustment->value - + sheet->vadjustment->page_increment); + + break; + default: + g_assert_not_reached (); + break; + } + + + vpixel += psppire_axis_start_pixel (sheet->vaxis, + min_visible_row (sheet)); + + new_row = row_from_ypixel (sheet, vpixel); + + change_active_cell (sheet, new_row, + sheet->active_cell.col); +} + + +static void +step_sheet (PsppireSheet *sheet, GtkScrollType dir) +{ + gint current_row = sheet->active_cell.row; + gint current_col = sheet->active_cell.col; + PsppireSheetCell new_cell ; + gboolean forbidden = FALSE; + + new_cell.row = current_row; + new_cell.col = current_col; + + switch ( dir) + { + case GTK_SCROLL_STEP_DOWN: + new_cell.row++; + break; + case GTK_SCROLL_STEP_UP: + new_cell.row--; + break; + case GTK_SCROLL_STEP_RIGHT: + new_cell.col++; + break; + case GTK_SCROLL_STEP_LEFT: + new_cell.col--; + break; + default: + g_assert_not_reached (); + break; + } + + + g_signal_emit (sheet, sheet_signals[TRAVERSE], 0, + &sheet->active_cell, + &new_cell, + &forbidden); + + if (forbidden) + return; + + + maximize_int (&new_cell.row, 0); + maximize_int (&new_cell.col, 0); + + minimize_int (&new_cell.row, + psppire_axis_unit_count (sheet->vaxis) - 1); + + minimize_int (&new_cell.col, + psppire_axis_unit_count (sheet->haxis) - 1); + + change_active_cell (sheet, new_cell.row, new_cell.col); + + + if ( new_cell.col > max_fully_visible_column (sheet)) + { + glong hpos = + psppire_axis_start_pixel (sheet->haxis, + new_cell.col + 1); + hpos -= sheet->hadjustment->page_size; + + gtk_adjustment_set_value (sheet->hadjustment, + hpos); + } + else if ( new_cell.col < min_fully_visible_column (sheet)) + { + glong hpos = + psppire_axis_start_pixel (sheet->haxis, + new_cell.col); + + gtk_adjustment_set_value (sheet->hadjustment, + hpos); + } + + + if ( new_cell.row > max_fully_visible_row (sheet)) + { + glong vpos = + psppire_axis_start_pixel (sheet->vaxis, + new_cell.row + 1); + vpos -= sheet->vadjustment->page_size; + + gtk_adjustment_set_value (sheet->vadjustment, + vpos); + } + else if ( new_cell.row < min_fully_visible_row (sheet)) + { + glong vpos = + psppire_axis_start_pixel (sheet->vaxis, + new_cell.row); + + gtk_adjustment_set_value (sheet->vadjustment, + vpos); + } + + gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget)); +} + + +static gboolean +psppire_sheet_key_press (GtkWidget *widget, + GdkEventKey *key) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (widget); + + PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION); + + switch (key->keyval) + { + case GDK_Tab: + case GDK_Right: + step_sheet (sheet, GTK_SCROLL_STEP_RIGHT); + break; + case GDK_ISO_Left_Tab: + case GDK_Left: + step_sheet (sheet, GTK_SCROLL_STEP_LEFT); + break; + case GDK_Return: + case GDK_Down: + step_sheet (sheet, GTK_SCROLL_STEP_DOWN); + break; + case GDK_Up: + step_sheet (sheet, GTK_SCROLL_STEP_UP); + break; + + case GDK_Page_Down: + page_vertical (sheet, GTK_SCROLL_PAGE_DOWN); + break; + case GDK_Page_Up: + page_vertical (sheet, GTK_SCROLL_PAGE_UP); + break; + + case GDK_Home: + gtk_adjustment_set_value (sheet->vadjustment, + sheet->vadjustment->lower); + + change_active_cell (sheet, 0, + sheet->active_cell.col); + + break; + + case GDK_End: + gtk_adjustment_set_value (sheet->vadjustment, + sheet->vadjustment->upper - + sheet->vadjustment->page_size - + sheet->vadjustment->page_increment); + + /* + change_active_cellx (sheet, + psppire_axis_unit_count (sheet->vaxis) - 1, + sheet->active_cell.col); + */ + break; + case GDK_Delete: + psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col); + break; + default: + return FALSE; + break; + } + + return TRUE; +} + +static void +psppire_sheet_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + PsppireSheet *sheet; + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + g_return_if_fail (requisition != NULL); + + sheet = PSPPIRE_SHEET (widget); + + requisition->width = 3 * DEFAULT_COLUMN_WIDTH; + requisition->height = 3 * DEFAULT_ROW_HEIGHT; + + /* compute the size of the column title area */ + if (sheet->column_titles_visible) + requisition->height += sheet->column_title_area.height; + + /* compute the size of the row title area */ + if (sheet->row_titles_visible) + requisition->width += sheet->row_title_area.width; +} + + +static void +psppire_sheet_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + PsppireSheet *sheet; + GtkAllocation sheet_allocation; + gint border_width; + + g_return_if_fail (widget != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (widget)); + g_return_if_fail (allocation != NULL); + + sheet = PSPPIRE_SHEET (widget); + widget->allocation = *allocation; + border_width = GTK_CONTAINER (widget)->border_width; + + if (GTK_WIDGET_REALIZED (widget)) + gdk_window_move_resize (widget->window, + allocation->x + border_width, + allocation->y + border_width, + allocation->width - 2 * border_width, + allocation->height - 2 * border_width); + + sheet_allocation.x = 0; + sheet_allocation.y = 0; + sheet_allocation.width = allocation->width - 2 * border_width; + sheet_allocation.height = allocation->height - 2 * border_width; + + if (GTK_WIDGET_REALIZED (widget)) + gdk_window_move_resize (sheet->sheet_window, + sheet_allocation.x, + sheet_allocation.y, + sheet_allocation.width, + sheet_allocation.height); + + /* position the window which holds the column title buttons */ + sheet->column_title_area.x = 0; + sheet->column_title_area.y = 0; + sheet->column_title_area.width = sheet_allocation.width ; + + + /* position the window which holds the row title buttons */ + sheet->row_title_area.x = 0; + sheet->row_title_area.y = 0; + sheet->row_title_area.height = sheet_allocation.height; + + if (sheet->row_titles_visible) + sheet->column_title_area.x += sheet->row_title_area.width; + + if (sheet->column_titles_visible) + sheet->row_title_area.y += sheet->column_title_area.height; + + if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible) + gdk_window_move_resize (sheet->column_title_window, + sheet->column_title_area.x, + sheet->column_title_area.y, + sheet->column_title_area.width, + sheet->column_title_area.height); + + + if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible) + gdk_window_move_resize (sheet->row_title_window, + sheet->row_title_area.x, + sheet->row_title_area.y, + sheet->row_title_area.width, + sheet->row_title_area.height); + + size_allocate_global_button (sheet); + + if (sheet->haxis) + { + gint width = sheet->column_title_area.width; + + if ( sheet->row_titles_visible) + width -= sheet->row_title_area.width; + + g_object_set (sheet->haxis, + "minimum-extent", width, + NULL); + } + + + if (sheet->vaxis) + { + gint height = sheet->row_title_area.height; + + if ( sheet->column_titles_visible) + height -= sheet->column_title_area.height; + + g_object_set (sheet->vaxis, + "minimum-extent", height, + NULL); + } + + + /* set the scrollbars adjustments */ + adjust_scrollbars (sheet); +} + +static void +draw_column_title_buttons (PsppireSheet *sheet) +{ + gint x, width; + + if (!sheet->column_titles_visible) return; + if (!GTK_WIDGET_REALIZED (sheet)) + return; + + gdk_drawable_get_size (sheet->sheet_window, &width, NULL); + x = 0; + + if (sheet->row_titles_visible) + { + x = sheet->row_title_area.width; + } + + if (sheet->column_title_area.width != width || sheet->column_title_area.x != x) + { + sheet->column_title_area.width = width; + sheet->column_title_area.x = x; + gdk_window_move_resize (sheet->column_title_window, + sheet->column_title_area.x, + sheet->column_title_area.y, + sheet->column_title_area.width, + sheet->column_title_area.height); + } + + if (max_visible_column (sheet) == + psppire_axis_unit_count (sheet->haxis) - 1) + gdk_window_clear_area (sheet->column_title_window, + 0, 0, + sheet->column_title_area.width, + sheet->column_title_area.height); + + if (!GTK_WIDGET_DRAWABLE (sheet)) return; + + draw_column_title_buttons_range (sheet, min_visible_column (sheet), + max_visible_column (sheet)); +} + +static void +draw_row_title_buttons (PsppireSheet *sheet) +{ + gint y = 0; + gint height; + + if (!sheet->row_titles_visible) return; + if (!GTK_WIDGET_REALIZED (sheet)) + return; + + gdk_drawable_get_size (sheet->sheet_window, NULL, &height); + + if (sheet->column_titles_visible) + { + y = sheet->column_title_area.height; + } + + if (sheet->row_title_area.height != height || sheet->row_title_area.y != y) + { + sheet->row_title_area.y = y; + sheet->row_title_area.height = height; + gdk_window_move_resize (sheet->row_title_window, + sheet->row_title_area.x, + sheet->row_title_area.y, + sheet->row_title_area.width, + sheet->row_title_area.height); + } + + if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1) + gdk_window_clear_area (sheet->row_title_window, + 0, 0, + sheet->row_title_area.width, + sheet->row_title_area.height); + + if (!GTK_WIDGET_DRAWABLE (sheet)) return; + + draw_row_title_buttons_range (sheet, min_visible_row (sheet), + max_visible_row (sheet)); +} + + +static void +psppire_sheet_size_allocate_entry (PsppireSheet *sheet) +{ + GtkAllocation entry_alloc; + PsppireSheetCellAttr attributes = { 0 }; + GtkEntry *sheet_entry; + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return; + + sheet_entry = psppire_sheet_get_entry (sheet); + + if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row, + sheet->active_cell.col, + &attributes) ) + return ; + + if ( GTK_WIDGET_REALIZED (sheet->entry_widget) ) + { + GtkStyle *style = GTK_WIDGET (sheet_entry)->style; + + style->bg[GTK_STATE_NORMAL] = attributes.background; + style->fg[GTK_STATE_NORMAL] = attributes.foreground; + style->text[GTK_STATE_NORMAL] = attributes.foreground; + style->bg[GTK_STATE_ACTIVE] = attributes.background; + style->fg[GTK_STATE_ACTIVE] = attributes.foreground; + style->text[GTK_STATE_ACTIVE] = attributes.foreground; + } + + rectangle_from_cell (sheet, sheet->active_cell.row, + sheet->active_cell.col, &entry_alloc); + + entry_alloc.width -= BORDER_WIDTH ; + entry_alloc.height -= BORDER_WIDTH ; + entry_alloc.x += DIV_RND_UP (BORDER_WIDTH, 2); + entry_alloc.y += DIV_RND_UP (BORDER_WIDTH, 2); + + + gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width, + entry_alloc.height); + gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc); +} + + +/* Copy the sheet's font to the entry widget */ +static void +set_entry_widget_font (PsppireSheet *sheet) +{ + GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget); + + pango_font_description_free (style->font_desc); + style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc); + + gtk_widget_modify_style (sheet->entry_widget, style); +} + + + +static void +create_sheet_entry (PsppireSheet *sheet) +{ + if (sheet->entry_widget) + { + gtk_widget_unparent (sheet->entry_widget); + } + + sheet->entry_widget = g_object_new (sheet->entry_type, NULL); + g_object_ref_sink (sheet->entry_widget); + + gtk_widget_size_request (sheet->entry_widget, NULL); + + if ( GTK_IS_ENTRY (sheet->entry_widget)) + { + g_object_set (sheet->entry_widget, + "has-frame", FALSE, + NULL); + } + + if (GTK_WIDGET_REALIZED (sheet)) + { + gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window); + gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet)); + gtk_widget_realize (sheet->entry_widget); + } + + g_signal_connect_swapped (sheet->entry_widget, "key_press_event", + G_CALLBACK (psppire_sheet_entry_key_press), + sheet); + + set_entry_widget_font (sheet); + + gtk_widget_show (sheet->entry_widget); +} + + +/* Finds the last child widget that happens to be of type GtkEntry */ +static void +find_entry (GtkWidget *w, gpointer user_data) +{ + GtkWidget **entry = user_data; + if ( GTK_IS_ENTRY (w)) + { + *entry = w; + } +} + + +GtkEntry * +psppire_sheet_get_entry (PsppireSheet *sheet) +{ + GtkWidget *w = sheet->entry_widget; + + g_return_val_if_fail (sheet != NULL, NULL); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL); + g_return_val_if_fail (sheet->entry_widget != NULL, NULL); + + while (! GTK_IS_ENTRY (w)) + { + GtkWidget *entry = NULL; + + if (GTK_IS_CONTAINER (w)) + { + gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry); + + if (NULL == entry) + break; + + w = entry; + } + } + + return GTK_ENTRY (w); +} + + +static void +draw_button (PsppireSheet *sheet, GdkWindow *window, + PsppireSheetButton *button, gboolean is_sensitive, + GdkRectangle allocation) +{ + GtkShadowType shadow_type; + gint text_width = 0, text_height = 0; + PangoAlignment align = PANGO_ALIGN_LEFT; + + gboolean rtl ; + + gint state = 0; + + g_return_if_fail (sheet != NULL); + g_return_if_fail (button != NULL); + + + rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL; + + gdk_window_clear_area (window, + allocation.x, allocation.y, + allocation.width, allocation.height); + + gtk_widget_ensure_style (sheet->button); + + gtk_paint_box (sheet->button->style, window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + &allocation, GTK_WIDGET (sheet->button), + "buttondefault", + allocation.x, allocation.y, + allocation.width, allocation.height); + + state = button->state; + if (!is_sensitive) state = GTK_STATE_INSENSITIVE; + + if (state == GTK_STATE_ACTIVE) + shadow_type = GTK_SHADOW_IN; + else + shadow_type = GTK_SHADOW_OUT; + + if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE) + gtk_paint_box (sheet->button->style, window, + button->state, shadow_type, + &allocation, GTK_WIDGET (sheet->button), + "button", + allocation.x, allocation.y, + allocation.width, allocation.height); + + if (button->label_visible) + { + text_height = DEFAULT_ROW_HEIGHT - + 2 * COLUMN_TITLES_HEIGHT; + + gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state], + &allocation); + gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, + &allocation); + + allocation.y += 2 * sheet->button->style->ythickness; + + if (button->label && strlen (button->label) > 0) + { + PangoRectangle rect; + gchar *line = button->label; + + PangoLayout *layout = NULL; + gint real_x = allocation.x; + gint real_y = allocation.y; + + layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line); + pango_layout_get_extents (layout, NULL, &rect); + + text_width = PANGO_PIXELS (rect.width); + switch (button->justification) + { + case GTK_JUSTIFY_LEFT: + real_x = allocation.x + COLUMN_TITLES_HEIGHT; + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + break; + case GTK_JUSTIFY_RIGHT: + real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT; + align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT; + break; + case GTK_JUSTIFY_CENTER: + default: + real_x = allocation.x + (allocation.width - text_width)/2; + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + pango_layout_set_justify (layout, TRUE); + } + pango_layout_set_alignment (layout, align); + gtk_paint_layout (GTK_WIDGET (sheet)->style, + window, + state, + FALSE, + &allocation, + GTK_WIDGET (sheet), + "label", + real_x, real_y, + layout); + g_object_unref (layout); + } + + gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state], + NULL); + gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL); + + } + + psppire_sheet_button_free (button); +} + + +/* Draw the column title buttons FIRST through to LAST */ +static void +draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last) +{ + GdkRectangle rect; + gint col; + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + + if (!sheet->column_titles_visible) return; + + g_return_if_fail (first >= min_visible_column (sheet)); + g_return_if_fail (last <= max_visible_column (sheet)); + + rect.y = 0; + rect.height = sheet->column_title_area.height; + rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING; + rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING + + psppire_axis_unit_size (sheet->haxis, last); + + rect.x -= sheet->hadjustment->value; + + minimize_int (&rect.width, sheet->column_title_area.width); + maximize_int (&rect.x, 0); + + gdk_window_begin_paint_rect (sheet->column_title_window, &rect); + + for (col = first ; col <= last ; ++col) + { + GdkRectangle allocation; + gboolean is_sensitive = FALSE; + + PsppireSheetButton * + button = psppire_sheet_model_get_column_button (sheet->model, col); + allocation.y = 0; + allocation.x = psppire_axis_start_pixel (sheet->haxis, col) + + CELL_SPACING; + allocation.x -= sheet->hadjustment->value; + + allocation.height = sheet->column_title_area.height; + allocation.width = psppire_axis_unit_size (sheet->haxis, col); + is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col); + + draw_button (sheet, sheet->column_title_window, + button, is_sensitive, allocation); + } + + gdk_window_end_paint (sheet->column_title_window); +} + + +static void +draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last) +{ + GdkRectangle rect; + gint row; + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return; + + if (!sheet->row_titles_visible) return; + + g_return_if_fail (first >= min_visible_row (sheet)); + g_return_if_fail (last <= max_visible_row (sheet)); + + rect.x = 0; + rect.width = sheet->row_title_area.width; + rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING; + rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING + + psppire_axis_unit_size (sheet->vaxis, last); + + rect.y -= sheet->vadjustment->value; + + minimize_int (&rect.height, sheet->row_title_area.height); + maximize_int (&rect.y, 0); + + gdk_window_begin_paint_rect (sheet->row_title_window, &rect); + for (row = first; row <= last; ++row) + { + GdkRectangle allocation; + + gboolean is_sensitive = FALSE; + + PsppireSheetButton *button = + psppire_sheet_model_get_row_button (sheet->model, row); + allocation.x = 0; + allocation.y = psppire_axis_start_pixel (sheet->vaxis, row) + + CELL_SPACING; + allocation.y -= sheet->vadjustment->value; + + allocation.width = sheet->row_title_area.width; + allocation.height = psppire_axis_unit_size (sheet->vaxis, row); + is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row); + + draw_button (sheet, sheet->row_title_window, + button, is_sensitive, allocation); + } + + gdk_window_end_paint (sheet->row_title_window); +} + +/* SCROLLBARS + * + * functions: + * adjust_scrollbars + * vadjustment_value_changed + * hadjustment_value_changed */ + + +static void +update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size) +{ + double position = + (adj->value + adj->page_size) + / + (adj->upper - adj->lower); + + const glong last_item = psppire_axis_unit_count (axis) - 1; + + if (isnan (position) || position < 0) + position = 0; + + adj->upper = + psppire_axis_start_pixel (axis, last_item) + + + psppire_axis_unit_size (axis, last_item) + ; + + adj->lower = 0; + adj->page_size = page_size; + +#if 0 + adj->value = position * (adj->upper - adj->lower) - adj->page_size; + + if ( adj->value < adj->lower) + adj->value = adj->lower; +#endif + + gtk_adjustment_changed (adj); +} + + +static void +adjust_scrollbars (PsppireSheet *sheet) +{ + gint width, height; + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + gdk_drawable_get_size (sheet->sheet_window, &width, &height); + + if ( sheet->row_titles_visible) + width -= sheet->row_title_area.width; + + if (sheet->column_titles_visible) + height -= sheet->column_title_area.height; + + if (sheet->vadjustment) + { + glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1; + + sheet->vadjustment->step_increment = + ROWS_PER_STEP * + psppire_axis_unit_size (sheet->vaxis, last_row); + + sheet->vadjustment->page_increment = + height - + sheet->column_title_area.height - + psppire_axis_unit_size (sheet->vaxis, last_row); + + update_adjustment (sheet->vadjustment, sheet->vaxis, height); + } + + if (sheet->hadjustment) + { + gint last_col = psppire_axis_unit_count (sheet->haxis) - 1; + sheet->hadjustment->step_increment = 1; + + sheet->hadjustment->page_increment = width; + + sheet->hadjustment->upper = + psppire_axis_start_pixel (sheet->haxis, last_col) + + + psppire_axis_unit_size (sheet->haxis, last_col) + ; + + update_adjustment (sheet->hadjustment, sheet->haxis, width); + } +} + +/* Subtracts the region of WIDGET from REGION */ +static void +subtract_widget_region (GdkRegion *region, GtkWidget *widget) +{ + GdkRectangle rect; + GdkRectangle intersect; + GdkRegion *region2; + + gdk_region_get_clipbox (region, &rect); + gtk_widget_intersect (widget, + &rect, + &intersect); + + region2 = gdk_region_rectangle (&intersect); + gdk_region_subtract (region, region2); + gdk_region_destroy (region2); +} + +static void +vadjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GdkRegion *region; + PsppireSheet *sheet = PSPPIRE_SHEET (data); + + g_return_if_fail (adjustment != NULL); + + if ( ! GTK_WIDGET_REALIZED (sheet)) return; + + gtk_widget_hide (sheet->entry_widget); + + region = + gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window)); + + subtract_widget_region (region, sheet->button); + gdk_window_begin_paint_region (sheet->sheet_window, region); + + draw_sheet_region (sheet, region); + + draw_row_title_buttons (sheet); + psppire_sheet_draw_active_cell (sheet); + + gdk_window_end_paint (sheet->sheet_window); + gdk_region_destroy (region); +} + + +static void +hadjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GdkRegion *region; + PsppireSheet *sheet = PSPPIRE_SHEET (data); + + g_return_if_fail (adjustment != NULL); + + if ( ! GTK_WIDGET_REALIZED (sheet)) return; + + gtk_widget_hide (sheet->entry_widget); + + + region = + gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window)); + + subtract_widget_region (region, sheet->button); + gdk_window_begin_paint_region (sheet->sheet_window, region); + + draw_sheet_region (sheet, region); + + draw_column_title_buttons (sheet); + + psppire_sheet_draw_active_cell (sheet); + + gdk_window_end_paint (sheet->sheet_window); + + gdk_region_destroy (region); +} + + +/* COLUMN RESIZING */ +static void +draw_xor_vline (PsppireSheet *sheet) +{ + gint height; + gint xpos = sheet->x_drag; + gdk_drawable_get_size (sheet->sheet_window, + NULL, &height); + + if (sheet->row_titles_visible) + xpos += sheet->row_title_area.width; + + gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc, + xpos, + sheet->column_title_area.height, + xpos, + height + CELL_SPACING); +} + +/* ROW RESIZING */ +static void +draw_xor_hline (PsppireSheet *sheet) + +{ + gint width; + gint ypos = sheet->y_drag; + + gdk_drawable_get_size (sheet->sheet_window, + &width, NULL); + + + if (sheet->column_titles_visible) + ypos += sheet->column_title_area.height; + + gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc, + sheet->row_title_area.width, + ypos, + width + CELL_SPACING, + ypos); +} + +/* SELECTED RANGE */ +static void +draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range) +{ + gint i = 0; + GdkRectangle clip_area, area; + GdkGCValues values; + + area.x = psppire_axis_start_pixel (sheet->haxis, range.col0); + area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0); + area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+ + psppire_axis_unit_size (sheet->haxis, range.coli); + area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y + + psppire_axis_unit_size (sheet->vaxis, range.rowi); + + clip_area.x = sheet->row_title_area.width; + clip_area.y = sheet->column_title_area.height; + + gdk_drawable_get_size (sheet->sheet_window, + &clip_area.width, &clip_area.height); + + if (!sheet->row_titles_visible) clip_area.x = 0; + if (!sheet->column_titles_visible) clip_area.y = 0; + + if (area.x < 0) + { + area.width = area.width + area.x; + area.x = 0; + } + if (area.width > clip_area.width) area.width = clip_area.width + 10; + if (area.y < 0) + { + area.height = area.height + area.y; + area.y = 0; + } + if (area.height > clip_area.height) area.height = clip_area.height + 10; + + clip_area.x--; + clip_area.y--; + clip_area.width += 3; + clip_area.height += 3; + + gdk_gc_get_values (sheet->xor_gc, &values); + + gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area); + + gdk_draw_rectangle (sheet->sheet_window, + sheet->xor_gc, + FALSE, + area.x + i, area.y + i, + area.width - 2 * i, area.height - 2 * i); + + + gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL); + + gdk_gc_set_foreground (sheet->xor_gc, &values.foreground); +} + + +static void +set_column_width (PsppireSheet *sheet, + gint column, + gint width) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis)) + return; + + if ( width <= 0) + return; + + psppire_axis_resize (sheet->haxis, column, width); + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + { + draw_column_title_buttons (sheet); + adjust_scrollbars (sheet); + psppire_sheet_size_allocate_entry (sheet); + redraw_range (sheet, NULL); + } +} + +static void +set_row_height (PsppireSheet *sheet, + gint row, + gint height) +{ + g_return_if_fail (sheet != NULL); + g_return_if_fail (PSPPIRE_IS_SHEET (sheet)); + + if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis)) + return; + + if (height <= 0) + return; + + psppire_axis_resize (sheet->vaxis, row, height); + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) ) + { + draw_row_title_buttons (sheet); + adjust_scrollbars (sheet); + psppire_sheet_size_allocate_entry (sheet); + redraw_range (sheet, NULL); + } +} + +gboolean +psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col, + PsppireSheetCellAttr *attr) +{ + GdkColor *fg, *bg; + const GtkJustification *j ; + GdkColormap *colormap; + + g_return_val_if_fail (sheet != NULL, FALSE); + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE); + + if (row < 0 || col < 0) return FALSE; + + attr->foreground = GTK_WIDGET (sheet)->style->black; + attr->background = sheet->color[BG_COLOR]; + + attr->border.width = 0; + attr->border.line_style = GDK_LINE_SOLID; + attr->border.cap_style = GDK_CAP_NOT_LAST; + attr->border.join_style = GDK_JOIN_MITER; + attr->border.mask = 0; + attr->border.color = GTK_WIDGET (sheet)->style->black; + + attr->is_editable = psppire_sheet_model_is_editable (sheet->model, row, col); + + colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet)); + fg = psppire_sheet_model_get_foreground (sheet->model, row, col); + if ( fg ) + { + gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE); + attr->foreground = *fg; + } + + bg = psppire_sheet_model_get_background (sheet->model, row, col); + if ( bg ) + { + gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE); + attr->background = *bg; + } + + attr->justification = + psppire_sheet_model_get_column_justification (sheet->model, col); + + j = psppire_sheet_model_get_justification (sheet->model, row, col); + if (j) + attr->justification = *j; + + return TRUE; +} + +static void +psppire_sheet_button_size_request (PsppireSheet *sheet, + const PsppireSheetButton *button, + GtkRequisition *button_requisition) +{ + GtkRequisition requisition; + GtkRequisition label_requisition; + + label_requisition.height = DEFAULT_ROW_HEIGHT; + label_requisition.width = COLUMN_MIN_WIDTH; + + requisition.height = DEFAULT_ROW_HEIGHT; + requisition.width = COLUMN_MIN_WIDTH; + + + *button_requisition = requisition; + button_requisition->width = MAX (requisition.width, label_requisition.width); + button_requisition->height = MAX (requisition.height, label_requisition.height); + +} + +static void +psppire_sheet_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (container); + + g_return_if_fail (callback != NULL); + + if (sheet->button && sheet->button->parent) + (* callback) (sheet->button, callback_data); + + if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget)) + (* callback) (sheet->entry_widget, callback_data); +} + + +PsppireSheetModel * +psppire_sheet_get_model (const PsppireSheet *sheet) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL); + + return sheet->model; +} + + +PsppireSheetButton * +psppire_sheet_button_new (void) +{ + PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton)); + + button->state = GTK_STATE_NORMAL; + button->label = NULL; + button->label_visible = TRUE; + button->justification = GTK_JUSTIFY_FILL; + + return button; +} + + +void +psppire_sheet_button_free (PsppireSheetButton *button) +{ + if (!button) return ; + + g_free (button->label); + g_free (button); +} + +static void +append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c) +{ + gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c); + + if ( NULL == celltext) + return; + + g_string_append (string, celltext); + g_free (celltext); +} + + +static GString * +range_to_text (const PsppireSheet *sheet) +{ + gint r, c; + GString *string; + + if ( !psppire_sheet_range_isvisible (sheet, &sheet->range)) + return NULL; + + string = g_string_sized_new (80); + + for (r = sheet->range.row0; r <= sheet->range.rowi; ++r) + { + for (c = sheet->range.col0; c < sheet->range.coli; ++c) + { + append_cell_text (string, sheet, r, c); + g_string_append (string, "\t"); + } + append_cell_text (string, sheet, r, c); + if ( r < sheet->range.rowi) + g_string_append (string, "\n"); + } + + return string; +} + +static GString * +range_to_html (const PsppireSheet *sheet) +{ + gint r, c; + GString *string; + + if ( !psppire_sheet_range_isvisible (sheet, &sheet->range)) + return NULL; + + string = g_string_sized_new (480); + + g_string_append (string, "\n"); + g_string_append (string, "\n"); + g_string_append (string, "\n"); + for (r = sheet->range.row0; r <= sheet->range.rowi; ++r) + { + g_string_append (string, "\n"); + for (c = sheet->range.col0; c <= sheet->range.coli; ++c) + { + g_string_append (string, "\n"); + } + g_string_append (string, "\n"); + } + g_string_append (string, "
"); + append_cell_text (string, sheet, r, c); + g_string_append (string, "
\n"); + g_string_append (string, "\n"); + g_string_append (string, "\n"); + + return string; +} + +enum { + SELECT_FMT_NULL, + SELECT_FMT_TEXT, + SELECT_FMT_HTML +}; + +static void +primary_get_cb (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + GString *string = NULL; + + switch (info) + { + case SELECT_FMT_TEXT: + string = range_to_text (sheet); + break; + case SELECT_FMT_HTML: + string = range_to_html (sheet); + break; + default: + g_assert_not_reached (); + } + + gtk_selection_data_set (selection_data, selection_data->target, + 8, + (const guchar *) string->str, string->len); + g_string_free (string, TRUE); +} + +static void +primary_clear_cb (GtkClipboard *clipboard, + gpointer data) +{ + PsppireSheet *sheet = PSPPIRE_SHEET (data); + if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) + return; + + psppire_sheet_real_unselect_range (sheet, NULL); +} + +static void +psppire_sheet_update_primary_selection (PsppireSheet *sheet) +{ + static const GtkTargetEntry targets[] = { + { "UTF8_STRING", 0, SELECT_FMT_TEXT }, + { "STRING", 0, SELECT_FMT_TEXT }, + { "TEXT", 0, SELECT_FMT_TEXT }, + { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT }, + { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT }, + { "text/plain", 0, SELECT_FMT_TEXT }, + { "text/html", 0, SELECT_FMT_HTML } + }; + + GtkClipboard *clipboard; + + if (!GTK_WIDGET_REALIZED (sheet)) + return; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet), + GDK_SELECTION_PRIMARY); + + if (psppire_sheet_range_isvisible (sheet, &sheet->range)) + { + if (!gtk_clipboard_set_with_owner (clipboard, targets, + G_N_ELEMENTS (targets), + primary_get_cb, primary_clear_cb, + G_OBJECT (sheet))) + primary_clear_cb (clipboard, sheet); + } + else + { + if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet)) + gtk_clipboard_clear (clipboard); + } +} diff --git a/lib/gtksheet/psppire-sheet.h b/lib/gtksheet/psppire-sheet.h new file mode 100644 index 00000000..8a5e0790 --- /dev/null +++ b/lib/gtksheet/psppire-sheet.h @@ -0,0 +1,320 @@ +/* + Copyright (C) 2006, 2008 Free Software Foundation + + This program 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 3 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 program. If not, see . + + + This file is derived from the gtksheet.c and extensively modified for the + requirements of PSPPIRE. The changes are copyright by the + Free Software Foundation. The copyright notice for the original work is + below. + + + GtkSheet widget for Gtk+. + * Copyright (C) 1999-2001 Adrian E. Feiguin + * + * Based on GtkClist widget by Jay Painter, but major changes. + * Memory allocation routines inspired on SC (Spreadsheet Calculator) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PSPPIRE_SHEET_H__ +#define __PSPPIRE_SHEET_H__ + +#include + +#include "gtkextra-sheet.h" +#include "psppire-sheetmodel.h" +#include "psppire-axis.h" + +G_BEGIN_DECLS + +/* sheet->state */ +enum +{ + PSPPIRE_SHEET_NORMAL, + PSPPIRE_SHEET_ROW_SELECTED, + PSPPIRE_SHEET_COLUMN_SELECTED, + PSPPIRE_SHEET_RANGE_SELECTED +}; + + +#define PSPPIRE_TYPE_SHEET_RANGE (psppire_sheet_range_get_type ()) +#define PSPPIRE_TYPE_SHEET_CELL (psppire_sheet_cell_get_type ()) +#define PSPPIRE_TYPE_SHEET (psppire_sheet_get_type ()) + +#define PSPPIRE_SHEET(obj) GTK_CHECK_CAST (obj, psppire_sheet_get_type (), PsppireSheet) +#define PSPPIRE_SHEET_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, psppire_sheet_get_type (), PsppireSheetClass) +#define PSPPIRE_IS_SHEET(obj) GTK_CHECK_TYPE (obj, psppire_sheet_get_type ()) + + +typedef struct _PsppireSheetClass PsppireSheetClass; +typedef struct _PsppireSheetCellAttr PsppireSheetCellAttr; + +typedef struct _PsppireSheetHoverTitle PsppireSheetHoverTitle; + + +struct _PsppireSheetCellAttr +{ + GtkJustification justification; + GdkColor foreground; + GdkColor background; + PsppireSheetCellBorder border; + gboolean is_editable; +}; + +struct _PsppireSheetHoverTitle +{ + GtkWidget *window; + GtkWidget *label; + gint row, column; +}; + +enum + { + BG_COLOR, + GRID_COLOR, + n_COLORS + }; + +struct _PsppireSheet +{ + GtkBin parent; + + gboolean dispose_has_run; + PsppireAxis *haxis; + PsppireAxis *vaxis; + + guint16 flags; + + PsppireSheetModel *model; + + GtkSelectionMode selection_mode; + + /* Component colors */ + GdkColor color[n_COLORS]; + gboolean show_grid; + + /* active cell */ + PsppireSheetCell active_cell; + + /* The GtkEntry used for editing the cells */ + GtkWidget *entry_widget; + + /* The type of entry_widget */ + GtkType entry_type; + + /* expanding selection */ + PsppireSheetCell selection_cell; + + /* global selection button */ + GtkWidget *button; + + /* sheet state */ + gint state; + + /* selected range */ + PsppireSheetRange range; + + /* the scrolling window and its height and width to + * make things a little speedier */ + GdkWindow *sheet_window; + + /* border shadow style */ + GtkShadowType shadow_type; + + /* Column Titles */ + GdkRectangle column_title_area; + GdkWindow *column_title_window; + gboolean column_titles_visible; + /* TRUE if the cursor is over the column title window */ + gboolean column_title_under; + + /* Row Titles */ + GdkRectangle row_title_area; + GdkWindow *row_title_window; + gboolean row_titles_visible; + /* TRUE if the cursor is over the row title window */ + gboolean row_title_under; + + /*scrollbars*/ + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + + /* xor GC for the verticle drag line */ + GdkGC *xor_gc; + + /* gc for drawing unselected cells */ + GdkGC *fg_gc; + GdkGC *bg_gc; + + /* cursor used to indicate dragging */ + GdkCursor *cursor_drag; + + /* the current x-pixel location of the xor-drag vline */ + gint x_drag; + + /* the current y-pixel location of the xor-drag hline */ + gint y_drag; + + /* current cell being dragged */ + PsppireSheetCell drag_cell; + /* current range being dragged */ + PsppireSheetRange drag_range; + + /* Used for the subtitle (popups) */ + gint motion_timer; + PsppireSheetHoverTitle *hover_window; + + gulong update_handler_id; +}; + +struct _PsppireSheetClass +{ + GtkBinClass parent_class; + + gboolean (*set_scroll_adjustments) (PsppireSheet *sheet, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); + + void (*select_row) (PsppireSheet *sheet, gint row); + + void (*select_column) (PsppireSheet *sheet, gint column); + + void (*select_range) (PsppireSheet *sheet, PsppireSheetRange *range); + + void (*resize_range) (PsppireSheet *sheet, + PsppireSheetRange *old_range, + PsppireSheetRange *new_range); + + void (*move_range) (PsppireSheet *sheet, + PsppireSheetRange *old_range, + PsppireSheetRange *new_range); + + gboolean (*traverse) (PsppireSheet *sheet, + gint row, gint column, + gint *new_row, gint *new_column); + + gboolean (*activate) (PsppireSheet *sheet, + gint row, gint column); + + void (*changed) (PsppireSheet *sheet, + gint row, gint column); +}; + +GType psppire_sheet_get_type (void); +GtkType psppire_sheet_range_get_type (void); + + +/* create a new sheet */ +GtkWidget * psppire_sheet_new (PsppireSheetModel *model); + +/* create a new sheet with custom entry */ +GtkWidget * +psppire_sheet_new_with_custom_entry (GtkType entry_type); + +/* Change entry */ +void psppire_sheet_change_entry (PsppireSheet *sheet, GtkType entry_type); + +GtkEntry *psppire_sheet_get_entry (PsppireSheet *sheet); + + +void psppire_sheet_get_selected_range (PsppireSheet *sheet, + PsppireSheetRange *range); + +void psppire_sheet_show_grid (PsppireSheet *sheet, + gboolean show); + +gboolean psppire_sheet_grid_visible (PsppireSheet *sheet); + + +/* scroll the viewing area of the sheet to the given column + * and row; row_align and col_align are between 0-1 representing the + * location the row should appear on the screen, 0.0 being top or left, + * 1.0 being bottom or right; if row or column is negative then there + * is no change */ +void psppire_sheet_moveto (PsppireSheet *sheet, + gint row, + gint column, + gfloat row_align, + gfloat col_align); + + +void psppire_sheet_show_row_titles (PsppireSheet *sheet); +void psppire_sheet_hide_row_titles (PsppireSheet *sheet); +void psppire_sheet_show_column_titles (PsppireSheet *sheet); +void psppire_sheet_hide_column_titles (PsppireSheet *sheet); + +/* select the row. The range is then highlighted, and the bounds are stored + * in sheet->range */ +void psppire_sheet_select_row (PsppireSheet * sheet, gint row); + +/* select the column. The range is then highlighted, and the bounds are stored + * in sheet->range */ +void psppire_sheet_select_column (PsppireSheet * sheet, gint column); + +/* highlight the selected range and store bounds in sheet->range */ +void psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range); + +void psppire_sheet_get_visible_range (PsppireSheet *sheet, PsppireSheetRange *range); + + +/* obvious */ +void psppire_sheet_unselect_range (PsppireSheet *sheet); + +/* set active cell where the entry will be displayed */ +void psppire_sheet_set_active_cell (PsppireSheet *sheet, + gint row, gint column); + +/* Sets *ROW and *COLUMN to be the coordinates of the active cell. + ROW and/or COLUMN may be null if the caller is not interested in their + values */ +void psppire_sheet_get_active_cell (PsppireSheet *sheet, + gint *row, gint *column); + +/* get cell contents */ +gchar *psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col); + + +/* get cell attributes of the given cell */ +/* TRUE means that the cell is currently allocated */ +gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet, + gint row, gint col, + PsppireSheetCellAttr *attributes); + +void psppire_sheet_set_model (PsppireSheet *sheet, + PsppireSheetModel *model); + +PsppireSheetModel * psppire_sheet_get_model (const PsppireSheet *sheet); + + +G_END_DECLS + + +#endif /* __PSPPIRE_SHEET_H__ */ + + diff --git a/lib/gtksheet/psppire-sheetmodel.c b/lib/gtksheet/psppire-sheetmodel.c new file mode 100644 index 00000000..61b667d4 --- /dev/null +++ b/lib/gtksheet/psppire-sheetmodel.c @@ -0,0 +1,541 @@ +/* PsppireSheetModel --- an abstract model for the PsppireSheet widget. + * Copyright (C) 2006, 2008 Free Software Foundation + + This program 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 3 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 program. If not, see . */ + +#include + +#include +#include "psppire-sheetmodel.h" +#include + +enum { + RANGE_CHANGED, + ROWS_INSERTED, + ROWS_DELETED, + COLUMNS_INSERTED, + COLUMNS_DELETED, + LAST_SIGNAL +}; + +static guint sheet_model_signals[LAST_SIGNAL] = { 0 }; + + +static void psppire_sheet_model_base_init (gpointer g_class); + + +GType +psppire_sheet_model_get_type (void) +{ + static GType sheet_model_type = 0; + + if (! sheet_model_type) + { + static const GTypeInfo sheet_model_info = + { + sizeof (PsppireSheetModelIface), /* class_size */ + psppire_sheet_model_base_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + sheet_model_type = + g_type_register_static (G_TYPE_INTERFACE, "PsppireSheetModel", + &sheet_model_info, 0); + + g_type_interface_add_prerequisite (sheet_model_type, G_TYPE_OBJECT); + } + + return sheet_model_type; +} + +static void +psppire_sheet_model_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (! initialized) + { + sheet_model_signals[RANGE_CHANGED] = + g_signal_new ("range_changed", + PSPPIRE_TYPE_SHEET_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PsppireSheetModelIface, range_changed), + NULL, NULL, + psppire_marshal_VOID__INT_INT_INT_INT, + G_TYPE_NONE, 4, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT); + + + + sheet_model_signals[ROWS_INSERTED] = + g_signal_new ("rows_inserted", + PSPPIRE_TYPE_SHEET_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PsppireSheetModelIface, rows_inserted), + NULL, NULL, + psppire_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + + sheet_model_signals[ROWS_DELETED] = + g_signal_new ("rows_deleted", + PSPPIRE_TYPE_SHEET_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PsppireSheetModelIface, rows_deleted), + NULL, NULL, + psppire_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + sheet_model_signals[COLUMNS_INSERTED] = + g_signal_new ("columns_inserted", + PSPPIRE_TYPE_SHEET_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PsppireSheetModelIface, columns_inserted), + NULL, NULL, + psppire_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + + sheet_model_signals[COLUMNS_DELETED] = + g_signal_new ("columns_deleted", + PSPPIRE_TYPE_SHEET_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PsppireSheetModelIface, columns_deleted), + NULL, NULL, + psppire_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + + initialized = TRUE; + } +} + + +/** + * psppire_sheet_model_free_strings + * @sheet_model: A #PsppireSheetModel + * + * Returns: True if strings obtained with get_string should be freed by the + * sheet when no longer required. + **/ +gboolean +psppire_sheet_model_free_strings (const PsppireSheetModel *sheet_model) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model), FALSE); + + return PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->free_strings; +} + + +/** + * psppire_sheet_model_get_string: + * @sheet_model: A #PsppireSheetModel + * @row: The row of the cell to be retrieved. + * @column: The column of the cell to be retrieved. + * + * Retrieves the datum at location ROW, COLUMN in the form of a string. + * Returns: The string representation of the datum, or NULL on error. + **/ +gchar * +psppire_sheet_model_get_string (const PsppireSheetModel *sheet_model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model), 0); + + g_assert (PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->get_string); + + return (PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->get_string) (sheet_model, row, column); +} + +/** + * psppire_sheet_model_set_string + * @sheet_model: A #PsppireSheetModel + * @text: The text describing the datum to be set. + * @row: The row of the cell to be cleared. + * @column: The column of the cell to be cleared. + * + * Sets the datum at a location from a string. + * Returns: TRUE if the datum was changed, FALSE otherwise. + **/ +gboolean +psppire_sheet_model_set_string (PsppireSheetModel *sheet_model, + const gchar *text, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model), FALSE); + + g_assert (PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->set_string); + + return PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->set_string (sheet_model, + text, row, column); +} + + + +/** + * psppire_sheet_model_datum_clear: + * @sheet_model: A #PsppireSheetModel + * @row: The row of the cell to be cleared. + * @column: The column of the cell to be cleared. + * + * Called when the datum at a location is to be cleared. + * Returns: TRUE if the datum was cleared, FALSE otherwise. + **/ +gboolean +psppire_sheet_model_datum_clear (PsppireSheetModel *sheet_model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model), FALSE); + + g_assert (PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->clear_datum); + + return PSPPIRE_SHEET_MODEL_GET_IFACE (sheet_model)->clear_datum (sheet_model, + row, column); +} + + +/** + * psppire_sheet_model_range_changed: + * @sheet_model: A #PsppireSheetModel + * @range: The #PsppireSheetRange range of cells which have changed. + * + * Emits the "range_changed" signal on @sheet_model. + **/ +void +psppire_sheet_model_range_changed (PsppireSheetModel *sheet_model, + glong row0, glong col0, + glong rowi, glong coli) +{ + g_return_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model)); + + g_signal_emit (sheet_model, sheet_model_signals[RANGE_CHANGED], 0, + row0, col0, rowi, coli); +} + + + + +/** + * psppire_sheet_model_rows_inserted: + * @sheet_model: A #PsppireSheetModel + * @row: The row before which the new rows should be inserted. + * @n_rows: The number of rows to insert. + * + * Emits the "rows_inserted" signal on @sheet_model. + **/ +void +psppire_sheet_model_rows_inserted (PsppireSheetModel *sheet_model, + glong row, glong n_rows) +{ + g_return_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model)); + + g_signal_emit (sheet_model, sheet_model_signals[ROWS_INSERTED], 0, + row, n_rows); +} + + +/** + * psppire_sheet_model_columns_inserted: + * @sheet_model: A #PsppireSheetModel + * @column: The column before which the new columns should be inserted. + * @n_columns: The number of columns to insert. + * + * Emits the "columns_inserted" signal on @sheet_model. + **/ +void +psppire_sheet_model_columns_inserted (PsppireSheetModel *sheet_model, + glong column, glong n_columns) +{ + g_return_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model)); + + g_signal_emit (sheet_model, sheet_model_signals[COLUMNS_INSERTED], 0, + column, n_columns); +} + + + + +/** + * psppire_sheet_model_rows_deleted: + * @sheet_model: A #PsppireSheetModel + * @row: The first row to be deleted. + * @n_rows: The number of rows to delete. + * + * Emits the "rows_deleted" signal on @sheet_model. + **/ +void +psppire_sheet_model_rows_deleted (PsppireSheetModel *sheet_model, + glong row, glong n_rows) +{ + g_return_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model)); + + g_signal_emit (sheet_model, sheet_model_signals[ROWS_DELETED], 0, + row, n_rows); +} + + + +/** + * psppire_sheet_model_columns_deleted: + * @sheet_model: A #PsppireSheetModel + * @column: The first column to be deleted. + * @n_columns: The number of columns to delete. + * + * Emits the "columns_deleted" signal on @sheet_model. + **/ +void +psppire_sheet_model_columns_deleted (PsppireSheetModel *sheet_model, + glong column, glong n_columns) +{ + g_return_if_fail (PSPPIRE_IS_SHEET_MODEL (sheet_model)); + + g_signal_emit (sheet_model, sheet_model_signals[COLUMNS_DELETED], 0, + column, n_columns); +} + + + + + +/** + * psppire_sheet_model_is_editable: + * @sheet_model: A #PsppireSheetModel + * @row: The row + * @column: The column + * + * Returns: TRUE if the cell is editable, FALSE otherwise + **/ +gboolean +psppire_sheet_model_is_editable (const PsppireSheetModel *model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), TRUE); + + if ( ! PSPPIRE_SHEET_MODEL_GET_IFACE (model)->is_editable ) + return TRUE; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->is_editable (model, + row, column); +} + + +/** + * psppire_sheet_model_get_foreground: + * @sheet_model: A #PsppireSheetModel + * @row: The row + * @column: The column + * + * Returns the foreground colour of the cell at @row, @column + * The color is unallocated. It will be allocated by the viewing object. + **/ +GdkColor * +psppire_sheet_model_get_foreground (const PsppireSheetModel *model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( ! PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_foreground ) + return NULL; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_foreground (model, + row, column); +} + +/** + * psppire_sheet_model_get_background: + * @sheet_model: A #PsppireSheetModel + * @row: The row + * @column: The column + * + * Returns the background colour of the cell at @row, @column + * The color is unallocated. It will be allocated by the viewing object. + **/ +GdkColor * +psppire_sheet_model_get_background (const PsppireSheetModel *model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( ! PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_background ) + return NULL; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_background (model, + row, column); +} + +/** + * psppire_sheet_model_get_justification: + * @sheet_model: A #PsppireSheetModel + * @row: The row + * @column: The column + * + * Returns the justification of the cell at @row, @column + * Returns: the justification, or NULL on error. + **/ +const GtkJustification * +psppire_sheet_model_get_justification (const PsppireSheetModel *model, + glong row, glong column) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( ! PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_justification) + return NULL; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_justification (model, + row, column); +} + + +/** + * psppire_sheet_model_get_column_count: + * @model: A #PsppireSheetModel + * + * Returns the total number of columns represented by the model + **/ +glong +psppire_sheet_model_get_column_count (const PsppireSheetModel *model) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), -1); + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_count (model); +} + +/** + * psppire_sheet_model_get_row_count: + * @model: A #PsppireSheetModel + * + * Returns the total number of rows represented by the model + **/ +gint +psppire_sheet_model_get_row_count(const PsppireSheetModel *model) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), -1); + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_count (model); +} + + + +/* Column related functions */ +gboolean +psppire_sheet_model_get_column_sensitivity (const PsppireSheetModel *model, gint col) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), FALSE); + + if ( NULL == PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_sensitivity) + return TRUE; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_sensitivity (model, col); +} + + +gchar * +psppire_sheet_model_get_column_subtitle (const PsppireSheetModel *model, + gint col) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + g_return_val_if_fail (col >= 0, NULL); + + if ( NULL == PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_subtitle) + return NULL; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_subtitle (model, col); +} + + +PsppireSheetButton * +psppire_sheet_model_get_column_button (const PsppireSheetModel *model, + gint col) +{ + PsppireSheetButton *button = psppire_sheet_button_new (); + + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_title) + button->label = PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_title (model, col); + + return button; +} + +GtkJustification +psppire_sheet_model_get_column_justification (const PsppireSheetModel *model, + gint col) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), GTK_JUSTIFY_LEFT); + + if ( PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_justification) + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_column_justification (model, col); + + return GTK_JUSTIFY_LEFT; +} + + + +gboolean +psppire_sheet_model_get_row_sensitivity (const PsppireSheetModel *model, gint row) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), FALSE); + + if ( NULL == PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_sensitivity) + return TRUE; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_sensitivity (model, row); +} + + + +gchar * +psppire_sheet_model_get_row_subtitle (const PsppireSheetModel *model, + gint row) +{ + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( NULL == PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_subtitle) + return NULL; + + return PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_subtitle (model, row); +} + + +PsppireSheetButton * +psppire_sheet_model_get_row_button (const PsppireSheetModel *model, + gint row) +{ + PsppireSheetButton *button = psppire_sheet_button_new (); + + g_return_val_if_fail (PSPPIRE_IS_SHEET_MODEL (model), NULL); + + if ( PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_title) + button->label = PSPPIRE_SHEET_MODEL_GET_IFACE (model)->get_row_title (model, row); + + return button; +} + diff --git a/lib/gtksheet/psppire-sheetmodel.h b/lib/gtksheet/psppire-sheetmodel.h new file mode 100644 index 00000000..236cacb7 --- /dev/null +++ b/lib/gtksheet/psppire-sheetmodel.h @@ -0,0 +1,221 @@ +/* PsppireSheetModel --- an abstract model for the PsppireSheet widget. + * Copyright (C) 2006, 2008 Free Software Foundation + + This program 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 3 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 program. If not, see . */ + +#ifndef __PSPPIRE_SHEET_MODEL_H__ +#define __PSPPIRE_SHEET_MODEL_H__ + + +/* This file provides an abstract interface or the data displayed by the + PsppireSheet widget */ + +#include +#include +#include +#include "gtkextra-sheet.h" + +G_BEGIN_DECLS + +#define PSPPIRE_TYPE_SHEET_MODEL (psppire_sheet_model_get_type ()) +#define PSPPIRE_SHEET_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PSPPIRE_TYPE_SHEET_MODEL, PsppireSheetModel)) +#define PSPPIRE_IS_SHEET_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_TYPE_SHEET_MODEL)) +#define PSPPIRE_SHEET_MODEL_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), PSPPIRE_TYPE_SHEET_MODEL, PsppireSheetModelIface)) + +typedef enum +{ + PSPPIRE_SHEET_LEFT_BORDER = 1 << 0, + PSPPIRE_SHEET_RIGHT_BORDER = 1 << 1, + PSPPIRE_SHEET_TOP_BORDER = 1 << 2, + PSPPIRE_SHEET_BOTTOM_BORDER = 1 << 3 +} PsppireSheetBorderType ; + + +typedef struct _PsppireSheetModel PsppireSheetModel; /* Dummy typedef */ +typedef struct _PsppireSheetModelIface PsppireSheetModelIface; +typedef struct _PsppireSheetRange PsppireSheetRange; +typedef struct _PsppireSheetCellBorder PsppireSheetCellBorder; + +struct _PsppireSheetRange +{ + gint row0, col0; /* upper-left cell */ + gint rowi, coli; /* lower-right cell */ +}; + +struct _PsppireSheetCellBorder +{ + PsppireSheetBorderType mask; + guint width; + GdkLineStyle line_style; + GdkCapStyle cap_style; + GdkJoinStyle join_style; + GdkColor color; +}; + + + +struct _PsppireSheetModelIface +{ + GTypeInterface g_iface; + + gboolean free_strings; + + /* Signals */ + void (* range_changed) (PsppireSheetModel *sheet_model, + glong row0, glong col0, + glong rowi, glong coli); + + void (* rows_inserted) (PsppireSheetModel *sheet_model, + glong row, glong n_rows); + + void (* rows_deleted) (PsppireSheetModel *sheet_model, + glong row, glong n_rows); + + void (* columns_inserted) (PsppireSheetModel *sheet_model, + glong column, glong n_columns); + + void (* columns_deleted) (PsppireSheetModel *sheet_model, + glong column, glong n_columns); + + + + /* Virtual Table */ + + gchar * (* get_string) (const PsppireSheetModel *sheet_model, + glong row, glong column); + + gboolean (* set_string) (PsppireSheetModel *sheet_model, + const gchar *s, glong row, glong column); + + gboolean (* clear_datum) (PsppireSheetModel *sheet_model, + glong row, glong column); + + gboolean (* is_editable) (const PsppireSheetModel *sheet_model, glong row, glong column); + + GdkColor * (* get_foreground) (const PsppireSheetModel *sheet_model, + glong row, glong column); + + GdkColor * (* get_background) (const PsppireSheetModel *sheet_model, + glong row, glong column); + + const GtkJustification * (* get_justification) (const PsppireSheetModel *sheet_model, + glong row, glong column); + + /* column related metadata */ + + gchar * (*get_column_title) (const PsppireSheetModel *, gint col); + gchar * (*get_column_subtitle) (const PsppireSheetModel *, gint col); + gboolean (*get_column_sensitivity) (const PsppireSheetModel *, gint col); + GtkJustification (*get_column_justification) (const PsppireSheetModel *mode, gint col); + const PsppireSheetButton * (* get_button) (const PsppireSheetModel *model, gint col); + + glong (*get_column_count) (const PsppireSheetModel *model); + + + /* row related metadata */ + gchar * (*get_row_title) (const PsppireSheetModel *, gint row); + gchar * (*get_row_subtitle) (const PsppireSheetModel *, gint row); + glong (*get_row_count) (const PsppireSheetModel *model); + gboolean (*get_row_sensitivity) (const PsppireSheetModel *, gint row); +}; + + + +GType psppire_sheet_model_get_type (void) G_GNUC_CONST; + + +gchar * psppire_sheet_model_get_string (const PsppireSheetModel *sheet_model, + glong row, glong column); + +gboolean psppire_sheet_model_set_string (PsppireSheetModel *sheet_model, + const gchar *s, + glong row, glong column); + +gboolean psppire_sheet_model_datum_clear (PsppireSheetModel *sheet_model, + glong row, glong column); + + +void psppire_sheet_model_range_changed (PsppireSheetModel *sheet_model, + glong row0, glong col0, + glong rowi, glong coli); + +void psppire_sheet_model_rows_deleted (PsppireSheetModel *sheet_model, + glong row, glong n_rows); + +void psppire_sheet_model_rows_inserted (PsppireSheetModel *sheet_model, + glong row, glong n_rows); + +void psppire_sheet_model_columns_inserted (PsppireSheetModel *sheet_model, + glong column, glong n_columns); + +void psppire_sheet_model_columns_deleted (PsppireSheetModel *sheet_model, + glong column, glong n_columns); + + +gboolean psppire_sheet_model_is_editable (const PsppireSheetModel *model, + glong row, glong column); + +gboolean psppire_sheet_model_is_visible + (const PsppireSheetModel *model, glong row, glong column); + + +GdkColor *psppire_sheet_model_get_foreground + (const PsppireSheetModel *model, glong row, glong column); + +GdkColor *psppire_sheet_model_get_background + (const PsppireSheetModel *model, glong row, glong column); + +const GtkJustification *psppire_sheet_model_get_justification + (const PsppireSheetModel *model, glong row, glong column); + +const PsppireSheetCellBorder * psppire_sheet_model_get_cell_border + (const PsppireSheetModel *model, glong row, glong column); + +gboolean psppire_sheet_model_free_strings (const PsppireSheetModel *sheet_model); + +glong psppire_sheet_model_get_column_count (const PsppireSheetModel *sheet_model); + +gint psppire_sheet_model_get_row_count (const PsppireSheetModel *sheet_model); + + + +gboolean psppire_sheet_model_get_column_sensitivity (const PsppireSheetModel *model, + gint col); + +gchar * psppire_sheet_model_get_column_subtitle (const PsppireSheetModel *model, + gint col); + +PsppireSheetButton * psppire_sheet_model_get_column_button (const PsppireSheetModel *, gint); + +GtkJustification psppire_sheet_model_get_column_justification (const PsppireSheetModel *, + gint); + + + +gboolean psppire_sheet_model_get_row_sensitivity (const PsppireSheetModel *model, + gint row); + + +gchar * psppire_sheet_model_get_row_subtitle (const PsppireSheetModel *model, + gint row); + + +PsppireSheetButton * psppire_sheet_model_get_row_button (const PsppireSheetModel *, gint); + + + + +G_END_DECLS + +#endif /* __PSPPIRE_SHEET_MODEL_H__ */ -- 2.30.2