/* * Implements the resizable guides for columns and rows * in the Gnumeric Spreadsheet. * * Author: * Miguel de Icaza (miguel@kernel.org) */ #include #include #include "gnumeric.h" #include "gnumeric-sheet.h" #include "item-bar.h" #include "item-debug.h" /* Marshal forward declarations */ static void item_bar_marshal (GtkObject *, GtkSignalFunc, gpointer, GtkArg *); /* The signal signatures */ typedef void (*ItemBarSignal1) (GtkObject *, gint arg1, gpointer data); typedef void (*ItemBarSignal2) (GtkObject *, gint arg1, gint arg2, gpointer data); /* The signals we emit */ enum { SELECTION_CHANGED, SIZE_CHANGED, LAST_SIGNAL }; static guint item_bar_signals [LAST_SIGNAL] = { 0 }; static GnomeCanvasItem *item_bar_parent_class; /* The arguments we take */ enum { ARG_0, ARG_SHEET_VIEW, ARG_ORIENTATION, ARG_FIRST_ELEMENT }; static void item_bar_destroy (GtkObject *object) { ItemBar *bar; bar = ITEM_BAR (object); if (GTK_OBJECT_CLASS (item_bar_parent_class)->destroy) (*GTK_OBJECT_CLASS (item_bar_parent_class)->destroy)(object); } static void item_bar_realize (GnomeCanvasItem *item) { ItemBar *item_bar; GdkWindow *window; GdkGC *gc; GdkColor c; if (GNOME_CANVAS_ITEM_CLASS (item_bar_parent_class)->realize) (*GNOME_CANVAS_ITEM_CLASS (item_bar_parent_class)->realize)(item); item_bar = ITEM_BAR (item); window = GTK_WIDGET (item->canvas)->window; /* Configure our gc */ item_bar->gc = gc = gdk_gc_new (window); gnome_canvas_get_color (item->canvas, "black", &c); gdk_gc_set_foreground (item_bar->gc, &c); item_bar->normal_cursor = gdk_cursor_new (GDK_ARROW); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) item_bar->change_cursor = gdk_cursor_new (GDK_SB_V_DOUBLE_ARROW); else item_bar->change_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); /* Reference the bold font */ style_font_ref (gnumeric_default_bold_font); } static void item_bar_unrealize (GnomeCanvasItem *item) { ItemBar *item_bar = ITEM_BAR (item); style_font_unref (gnumeric_default_bold_font); gdk_gc_unref (item_bar->gc); gdk_cursor_destroy (item_bar->change_cursor); gdk_cursor_destroy (item_bar->normal_cursor); if (GNOME_CANVAS_ITEM_CLASS (item_bar_parent_class)->unrealize) (*GNOME_CANVAS_ITEM_CLASS (item_bar_parent_class)->unrealize)(item); } static void item_bar_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) { if (GNOME_CANVAS_ITEM_CLASS(item_bar_parent_class)->update) (*GNOME_CANVAS_ITEM_CLASS(item_bar_parent_class)->update)(item, affine, clip_path, flags); item->x1 = 0; item->y1 = 0; item->x2 = INT_MAX; item->y2 = INT_MAX; gnome_canvas_group_child_bounds (GNOME_CANVAS_GROUP (item->parent), item); } static char * get_row_name (int n) { static char x [4 * sizeof (int)]; g_assert (n < SHEET_MAX_ROWS); sprintf (x, "%d", n + 1); return x; } static char * get_col_name (int n) { static char x [3]; g_assert (n < SHEET_MAX_COLS); if (n <= 'z'-'a') { x [0] = n + 'A'; x [1] = 0; } else { x [0] = (n / ('z'-'a'+1) - 1) + 'A'; x [1] = (n % ('z'-'a'+1)) + 'A'; x [2] = 0; } return x; } static void bar_draw_cell (ItemBar *item_bar, GdkDrawable *drawable, ItemBarSelectionType type, char *str, int x1, int y1, int x2, int y2) { GtkWidget *canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (item_bar)->canvas); GdkFont *font = canvas->style->font; GdkGC *gc; int len, texth, shadow; len = gdk_string_width (font, str); texth = font->ascent + font->descent; switch (type){ default: case ITEM_BAR_NO_SELECTION: shadow = GTK_SHADOW_OUT; gc = canvas->style->bg_gc [GTK_STATE_ACTIVE]; break; case ITEM_BAR_PARTIAL_SELECTION: shadow = GTK_SHADOW_OUT; gc = canvas->style->bg_gc [GTK_STATE_ACTIVE]; font = gnumeric_default_bold_font->font; break; case ITEM_BAR_FULL_SELECTION: shadow = GTK_SHADOW_IN; gc = canvas->style->dark_gc [GTK_STATE_NORMAL]; font = gnumeric_default_bold_font->font; break; } gdk_draw_rectangle (drawable, gc, TRUE, x1 + 1, y1 + 1, x2-x1-2, y2-y1-2); gtk_draw_shadow (canvas->style, drawable, GTK_STATE_NORMAL, shadow, x1, y1, x2-x1, y2-y1); gdk_draw_string (drawable, font, item_bar->gc, x1 + ((x2 - x1) - len) / 2, y1 + ((y2 - y1) - texth) / 2 + font->ascent, str); } static void item_bar_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) { ItemBar *item_bar = ITEM_BAR (item); Sheet *sheet = item_bar->sheet_view->sheet; ColRowInfo *cri; int element, total, pixels, limit; char *str; element = item_bar->first_element; if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) limit = y + height; else limit = x + width; total = 0; do { if (item_bar->orientation == GTK_ORIENTATION_VERTICAL){ if (element >= SHEET_MAX_ROWS){ GtkWidget *canvas = GTK_WIDGET (item->canvas); gtk_draw_shadow (canvas->style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, x, y, width, height); return; } cri = sheet_row_get_info (sheet, element); if (item_bar->resize_pos == element) pixels = item_bar->resize_width; else pixels = cri->pixels; if (total + pixels >= y){ str = get_row_name (element); bar_draw_cell (item_bar, drawable, sheet_row_selection_type (sheet, element), str, -x, 1 + total - y, GTK_WIDGET (item->canvas)->allocation.width - x, 1 + total + pixels - y); } } else { if (element >= SHEET_MAX_COLS){ GtkWidget *canvas = GTK_WIDGET (item->canvas); gtk_draw_shadow (canvas->style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, x, y, width, height); return; } cri = sheet_col_get_info (sheet, element); if (item_bar->resize_pos == element) pixels = item_bar->resize_width; else pixels = cri->pixels; if (total + pixels >= x){ str = get_col_name (element); bar_draw_cell (item_bar, drawable, sheet_col_selection_type (sheet, element), str, 1 + total - x, -y, 1 + total + pixels - x, GTK_WIDGET (item->canvas)->allocation.height - y); } } total += pixels; element++; } while (total < limit); } static double item_bar_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) { *actual_item = item; return 0.0; } static void item_bar_translate (GnomeCanvasItem *item, double dx, double dy) { printf ("item_bar_translate %g, %g\n", dx, dy); } static ColRowInfo * is_pointer_on_division (ItemBar *item_bar, int pos, int *the_total, int *the_element) { ColRowInfo *cri; Sheet *sheet; int i, total; total = 0; sheet = item_bar->sheet_view->sheet; for (i = item_bar->first_element; total < pos; i++){ if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) cri = sheet_row_get_info (sheet, i); else cri = sheet_col_get_info (sheet, i); total += cri->pixels; if ((total - 4 < pos) && (pos < total + 4)){ if (the_total) *the_total = total; if (the_element) *the_element = i; return cri; } if (total > pos){ if (the_element) *the_element = i; return NULL; } } return NULL; } static void set_cursor (ItemBar *item_bar, int pos) { GtkWidget *canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (item_bar)->canvas); /* We might be invoked before we are realized */ if (!canvas->window) return; if (is_pointer_on_division (item_bar, pos, NULL, NULL)) gdk_window_set_cursor(canvas->window, item_bar->change_cursor); else gdk_window_set_cursor(canvas->window, item_bar->normal_cursor); } /* * Returns the GnomeCanvasPoints for a line at position in the * correct orientation. */ static GnomeCanvasPoints * item_bar_get_line_points (ItemBar *item_bar, gdouble position) { GnomeCanvasPoints *points; GtkWidget *canvas = GTK_WIDGET (item_bar->sheet_view->sheet_view); points = gnome_canvas_points_new (2); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL){ points->coords [0] = 0.0; points->coords [1] = position; points->coords [2] = canvas->allocation.width; points->coords [3] = position; } else { points->coords [0] = position; points->coords [1] = 0.0; points->coords [2] = position; points->coords [3] = canvas->allocation.height; } return points; } static void item_bar_start_resize (ItemBar *item_bar, int pos, int pixels) { GnomeCanvasPoints *points; GnomeCanvasGroup *group; GnomeCanvasItem *item; GnumericSheet *gsheet; Sheet *sheet; int division_pos; double division_pos_d; sheet = item_bar->sheet_view->sheet; gsheet = GNUMERIC_SHEET (item_bar->sheet_view->sheet_view); group = GNOME_CANVAS_GROUP (GNOME_CANVAS (gsheet)->root); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL){ division_pos = sheet_row_get_distance ( sheet, gsheet->top_row, pos+1); } else { division_pos = sheet_col_get_distance ( sheet, gsheet->top_col, pos+1); } /* division_pos is in pixels, convert to canvas units */ gnome_canvas_window_to_world (GNOME_CANVAS (gsheet), division_pos, 0, &division_pos_d, NULL); points = item_bar_get_line_points (item_bar, division_pos_d); item_bar->resize_guide_offset = division_pos_d - pixels; item = gnome_canvas_item_new ( group, gnome_canvas_line_get_type (), "points", points, "fill_color", "black", "width_pixels", 1, NULL); gnome_canvas_points_free (points); item_bar->resize_guide = GTK_OBJECT (item); } static int get_col_from_pos (ItemBar *item_bar, int pos) { ColRowInfo *cri; Sheet *sheet; int i, total; total = 0; sheet = item_bar->sheet_view->sheet; for (i = item_bar->first_element; total < pos; i++){ if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) cri = sheet_row_get_info (sheet, i); else cri = sheet_col_get_info (sheet, i); total += cri->pixels; if (total > pos) return i; } return i; } #define convert(c,sx,sy,x,y) gnome_canvas_w2c (c,sx,sy,x,y) static gint item_bar_event (GnomeCanvasItem *item, GdkEvent *e) { ColRowInfo *cri; GnomeCanvas *canvas = item->canvas; ItemBar *item_bar = ITEM_BAR (item); int pos, start, element, x, y; int resizing; resizing = ITEM_BAR_RESIZING (item_bar); switch (e->type){ case GDK_ENTER_NOTIFY: convert (canvas, e->crossing.x, e->crossing.y, &x, &y); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) pos = y; else pos = x; set_cursor (item_bar, pos); break; case GDK_MOTION_NOTIFY: convert (canvas, e->motion.x, e->motion.y, &x, &y); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) pos = y; else pos = x; /* Do column resizing or incremental marking */ if (resizing){ GnomeCanvasPoints *points; GnomeCanvasItem *resize_guide; int npos; npos = pos - item_bar->resize_start_pos; if (npos <= 0) break; item_bar->resize_width = npos; resize_guide = GNOME_CANVAS_ITEM (item_bar->resize_guide); points = item_bar_get_line_points (item_bar, pos); gnome_canvas_item_set (resize_guide, "points", points, NULL); gnome_canvas_points_free (points); /* Redraw the ItemBar to show nice incremental progress */ gnome_canvas_request_redraw ( canvas, 0, 0, INT_MAX, INT_MAX); } else if (ITEM_BAR_IS_SELECTING (item_bar)) { element = get_col_from_pos (item_bar, pos); gtk_signal_emit ( GTK_OBJECT (item), item_bar_signals [SELECTION_CHANGED], element, 0); set_cursor (item_bar, pos); } else set_cursor (item_bar, pos); break; case GDK_BUTTON_PRESS: convert (canvas, e->button.x, e->button.y, &x, &y); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL) pos = y; else pos = x; cri = is_pointer_on_division (item_bar, pos, &start, &element); if (item_bar->orientation == GTK_ORIENTATION_VERTICAL){ if (element > SHEET_MAX_ROWS-1) break; } else { if (element > SHEET_MAX_COLS-1) break; } if (cri){ /* Record the important bits */ item_bar->resize_pos = element; item_bar->resize_start_pos = start - cri->pixels; item_bar->resize_width = cri->pixels; item_bar_start_resize (item_bar, element, pos); gnome_canvas_item_grab (item, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, item_bar->change_cursor, e->button.time); } else { item_bar->start_selection = element; gnome_canvas_item_grab (item, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, item_bar->normal_cursor, e->button.time); gtk_signal_emit (GTK_OBJECT (item), item_bar_signals [SELECTION_CHANGED], element, e->button.state | GDK_BUTTON1_MASK); } break; case GDK_BUTTON_RELEASE: if (resizing){ gtk_signal_emit (GTK_OBJECT (item), item_bar_signals [SIZE_CHANGED], item_bar->resize_pos, item_bar->resize_width); item_bar->resize_pos = -1; gtk_object_destroy (item_bar->resize_guide); } gnome_canvas_item_ungrab (item, e->button.time); item_bar->start_selection = -1; break; default: return FALSE; } return TRUE; } /* * Instance initialization */ static void item_bar_init (ItemBar *item_bar) { GnomeCanvasItem *item = GNOME_CANVAS_ITEM (item_bar); item->x1 = 0; item->y1 = 0; item->x2 = 0; item->y2 = 0; item_bar->first_element = 0; item_bar->orientation = GTK_ORIENTATION_VERTICAL; item_bar->resize_pos = -1; item_bar->start_selection = -1; } static void item_bar_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) { GnomeCanvasItem *item; ItemBar *item_bar; int v; item = GNOME_CANVAS_ITEM (o); item_bar = ITEM_BAR (o); switch (arg_id){ case ARG_SHEET_VIEW: item_bar->sheet_view = GTK_VALUE_POINTER (*arg); break; case ARG_ORIENTATION: item_bar->orientation = GTK_VALUE_INT (*arg); break; case ARG_FIRST_ELEMENT: v = GTK_VALUE_INT (*arg); if (item_bar->first_element != v){ item_bar->first_element = v; g_warning ("ARG_FIRST_ELEMENT: do scroll\n"); } break; } item_bar_update (item, NULL, NULL, 0); } /* * ItemBar class initialization */ static void item_bar_class_init (ItemBarClass *item_bar_class) { GtkObjectClass *object_class; GnomeCanvasItemClass *item_class; item_bar_parent_class = gtk_type_class (gnome_canvas_item_get_type()); object_class = (GtkObjectClass *) item_bar_class; item_class = (GnomeCanvasItemClass *) item_bar_class; gtk_object_add_arg_type ("ItemBar::SheetView", GTK_TYPE_POINTER, GTK_ARG_WRITABLE, ARG_SHEET_VIEW); gtk_object_add_arg_type ("ItemBar::Orientation", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_ORIENTATION); gtk_object_add_arg_type ("ItemBar::First", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_FIRST_ELEMENT); item_bar_signals [SELECTION_CHANGED] = gtk_signal_new ("selection_changed", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (ItemBarClass, selection_changed), item_bar_marshal, GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT); item_bar_signals [SIZE_CHANGED] = gtk_signal_new ("size_changed", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (ItemBarClass, size_changed), item_bar_marshal, GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT); /* Register our signals */ gtk_object_class_add_signals (object_class, item_bar_signals, LAST_SIGNAL); /* Method overrides */ object_class->destroy = item_bar_destroy; object_class->set_arg = item_bar_set_arg; /* GnomeCanvasItem method overrides */ item_class->update = item_bar_update; item_class->realize = item_bar_realize; item_class->unrealize = item_bar_unrealize; item_class->draw = item_bar_draw; item_class->point = item_bar_point; item_class->translate = item_bar_translate; item_class->event = item_bar_event; } GtkType item_bar_get_type (void) { static GtkType item_bar_type = 0; if (!item_bar_type) { GtkTypeInfo item_bar_info = { "ItemBar", sizeof (ItemBar), sizeof (ItemBarClass), (GtkClassInitFunc) item_bar_class_init, (GtkObjectInitFunc) item_bar_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; item_bar_type = gtk_type_unique (gnome_canvas_item_get_type (), &item_bar_info); } return item_bar_type; } /* * Marshaling routines for our signals */ static void item_bar_marshal (GtkObject *object, GtkSignalFunc func, gpointer func_data, GtkArg *args) { ItemBarSignal2 rfunc; rfunc = (ItemBarSignal2) func; (*rfunc) (object, GTK_VALUE_INT (args [0]), GTK_VALUE_INT (args [1]), func_data); }