Avoid the need for an extra click when de-selecting a region
[pspp-builds.git] / lib / gtk-contrib / psppire-sheet.c
1 /*
2   Copyright (C) 2006, 2008, 2009 Free Software Foundation
3
4   This program is free software: you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation, either version 3 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17
18   This file is derived from the gtksheet.c and extensively modified for the
19   requirements of PSPPIRE.  The changes are copyright by the
20   Free Software Foundation.  The copyright notice for the original work is
21   below.
22 */
23
24 /* GtkSheet widget for Gtk+.
25  * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
26  *
27  * Based on GtkClist widget by Jay Painter, but major changes.
28  * Memory allocation routines inspired on SC (Spreadsheet Calculator)
29  *
30  * This library is free software; you can redistribute it and/or
31  * modify it under the terms of the GNU Lesser General Public
32  * License as published by the Free Software Foundation; either
33  * version 2.1 of the License, or (at your option) any later version.
34  *
35  * This library is distributed in the hope that it will be useful,
36  * but WITHOUT ANY WARRANTY; without even the implied warranty of
37  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
38  * Lesser General Public License for more details.
39  *
40  * You should have received a copy of the GNU Lesser General Public
41  * License along with this library; if not, write to the Free Software
42  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
43  */
44
45 /**
46  * SECTION:psppiresheet
47  * @short_description: spreadsheet widget for gtk2
48  *
49  * PsppireSheet is a matrix widget for GTK+. It consists of an scrollable grid of
50  * cells where you can allocate text. Cell contents can be edited interactively
51  * through a specially designed entry, GtkItemEntry.
52  *
53  */
54 #include <config.h>
55
56 #include <string.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <glib.h>
60 #include <gdk/gdk.h>
61 #include <gdk/gdkkeysyms.h>
62 #include <gtk/gtksignal.h>
63 #include <gtk/gtkbutton.h>
64 #include <gtk/gtkadjustment.h>
65 #include <gtk/gtktypeutils.h>
66 #include <gtk/gtkentry.h>
67 #include <gtk/gtkcontainer.h>
68 #include <pango/pango.h>
69 #include "psppire-sheet.h"
70 #include <ui/gui/psppire-marshal.h>
71 #include <ui/gui/sheet/psppire-sheetmodel.h>
72 #include <ui/gui/sheet/psppire-axis.h>
73 #include <libpspp/misc.h>
74
75 #include <math.h>
76
77 /* sheet flags */
78 enum
79   {
80     PSPPIRE_SHEET_IN_XDRAG = 1 << 1,
81     PSPPIRE_SHEET_IN_YDRAG = 1 << 2,
82     PSPPIRE_SHEET_IN_DRAG = 1 << 3,
83
84     /* This flag is set when the user is actually in the process
85        of making a selection - ie while the mouse button is
86        depressed.
87     */
88     PSPPIRE_SHEET_IN_SELECTION = 1 << 4,
89
90     PSPPIRE_SHEET_IN_RESIZE = 1 << 5
91   };
92
93 #define PSPPIRE_SHEET_FLAGS(sheet) (PSPPIRE_SHEET (sheet)->flags)
94 #define PSPPIRE_SHEET_SET_FLAGS(sheet,flag) (PSPPIRE_SHEET_FLAGS (sheet) |= (flag))
95 #define PSPPIRE_SHEET_UNSET_FLAGS(sheet,flag) (PSPPIRE_SHEET_FLAGS (sheet) &= ~ (flag))
96
97 #define PSPPIRE_SHEET_IN_XDRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_XDRAG)
98 #define PSPPIRE_SHEET_IN_YDRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_YDRAG)
99 #define PSPPIRE_SHEET_IN_DRAG(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_DRAG)
100 #define PSPPIRE_SHEET_IN_SELECTION(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_SELECTION)
101 #define PSPPIRE_SHEET_IN_RESIZE(sheet) (PSPPIRE_SHEET_FLAGS (sheet) & PSPPIRE_SHEET_IN_RESIZE)
102
103 #define CELL_SPACING 1
104
105 #define TIMEOUT_HOVER 300
106 #define COLUMN_MIN_WIDTH 10
107 #define COLUMN_TITLES_HEIGHT 4
108 #define DEFAULT_COLUMN_WIDTH 80
109 #define DEFAULT_ROW_HEIGHT 25
110
111 static void set_entry_widget_font (PsppireSheet *sheet);
112
113 static void psppire_sheet_update_primary_selection (PsppireSheet *sheet);
114 static void draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint n);
115 static void draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint n);
116 static void redraw_range (PsppireSheet *sheet, PsppireSheetRange *range);
117
118
119 static void set_row_height (PsppireSheet *sheet,
120                             gint row,
121                             gint height);
122
123 static void destroy_hover_window (PsppireSheetHoverTitle *);
124 static PsppireSheetHoverTitle *create_hover_window (void);
125
126 static inline  void
127 dispose_string (const PsppireSheet *sheet, gchar *text)
128 {
129   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
130
131   if ( ! model )
132     return;
133
134   if (psppire_sheet_model_free_strings (model))
135     g_free (text);
136 }
137
138
139 /* FIXME: Why bother with these two ? */
140
141 /* returns the column index from a pixel location */
142 static inline gint
143 column_from_xpixel (const PsppireSheet *sheet, gint pixel)
144 {
145   return psppire_axis_unit_at_pixel (sheet->haxis, pixel);
146 }
147
148 static inline gint
149 row_from_ypixel (const PsppireSheet *sheet, gint pixel)
150 {
151   return psppire_axis_unit_at_pixel (sheet->vaxis, pixel);
152 }
153
154
155 /* Return the lowest row number which is wholly or partially on
156    the visible range of the sheet */
157 static inline glong
158 min_visible_row (const PsppireSheet *sheet)
159 {
160   return row_from_ypixel (sheet, sheet->vadjustment->value);
161 }
162
163 static inline glong
164 min_fully_visible_row (const PsppireSheet *sheet)
165 {
166   glong row = min_visible_row (sheet);
167
168   if ( psppire_axis_start_pixel (sheet->vaxis, row) < sheet->vadjustment->value)
169     row++;
170
171   return row;
172 }
173
174 static inline glong
175 max_visible_row (const PsppireSheet *sheet)
176 {
177   return row_from_ypixel (sheet, sheet->vadjustment->value + sheet->vadjustment->page_size);
178 }
179
180
181 static inline glong
182 max_fully_visible_row (const PsppireSheet *sheet)
183 {
184   glong row = max_visible_row (sheet);
185
186   if ( psppire_axis_start_pixel (sheet->vaxis, row)
187        +
188        psppire_axis_unit_size (sheet->vaxis, row)
189        > sheet->vadjustment->value)
190     row--;
191
192   return row;
193 }
194
195
196 /* Returns the lowest column number which is wholly or partially
197    on the sheet */
198 static inline glong
199 min_visible_column (const PsppireSheet *sheet)
200 {
201   return column_from_xpixel (sheet, sheet->hadjustment->value);
202 }
203
204 static inline glong
205 min_fully_visible_column (const PsppireSheet *sheet)
206 {
207   glong col = min_visible_column (sheet);
208
209   if ( psppire_axis_start_pixel (sheet->haxis, col) < sheet->hadjustment->value)
210     col++;
211
212   return col;
213 }
214
215
216 /* Returns the highest column number which is wholly or partially
217    on the sheet */
218 static inline glong
219 max_visible_column (const PsppireSheet *sheet)
220 {
221   return column_from_xpixel (sheet, sheet->hadjustment->value + sheet->hadjustment->page_size);
222 }
223
224 static inline glong
225 max_fully_visible_column (const PsppireSheet *sheet)
226 {
227   glong col = max_visible_column (sheet);
228
229   if ( psppire_axis_start_pixel (sheet->haxis, col)
230        +
231        psppire_axis_unit_size (sheet->haxis, col)
232        > sheet->hadjustment->value)
233     col--;
234
235   return col;
236 }
237
238
239
240 /* The size of the region (in pixels) around the row/column boundaries
241    where the height/width may be grabbed to change size */
242 #define DRAG_WIDTH 6
243
244 static gboolean
245 on_column_boundary (const PsppireSheet *sheet, gint x, gint *column)
246 {
247   gint col;
248   gint pixel;
249
250   x += sheet->hadjustment->value;
251
252   if ( x < 0)
253     return FALSE;
254
255   col = column_from_xpixel (sheet, x);
256
257   pixel = x - DRAG_WIDTH / 2;
258   if (pixel < 0)
259     pixel = 0;
260
261   if ( column_from_xpixel (sheet, pixel) < col )
262     {
263       *column = col - 1;
264       return TRUE;
265     }
266
267   if  ( column_from_xpixel (sheet, x + DRAG_WIDTH / 2) > col )
268     {
269       *column = col;
270       return TRUE;
271     }
272
273   return FALSE;
274 }
275
276 static gboolean
277 on_row_boundary (const PsppireSheet *sheet, gint y, gint *row)
278 {
279   gint r;
280   gint pixel;
281
282   y += sheet->vadjustment->value;
283
284   if ( y < 0)
285     return FALSE;
286
287   r = row_from_ypixel (sheet, y);
288
289   pixel = y - DRAG_WIDTH / 2;
290   if (pixel < 0)
291     pixel = 0;
292
293   if ( row_from_ypixel (sheet, pixel) < r )
294     {
295       *row = r - 1;
296       return TRUE;
297     }
298
299   if  ( row_from_ypixel (sheet, y + DRAG_WIDTH / 2) > r )
300     {
301       *row = r;
302       return TRUE;
303     }
304
305   return FALSE;
306 }
307
308
309 static inline gboolean
310 POSSIBLE_DRAG (const PsppireSheet *sheet, gint x, gint y,
311                gint *drag_row, gint *drag_column)
312 {
313   gint ydrag, xdrag;
314
315   /* Can't drag if nothing is selected */
316   if ( sheet->range.row0 < 0 || sheet->range.rowi < 0 ||
317        sheet->range.col0 < 0 || sheet->range.coli < 0 )
318     return FALSE;
319
320   *drag_column = column_from_xpixel (sheet, x);
321   *drag_row = row_from_ypixel (sheet, y);
322
323   if (x >= psppire_axis_start_pixel (sheet->haxis, sheet->range.col0) - DRAG_WIDTH / 2 &&
324       x <= psppire_axis_start_pixel (sheet->haxis, sheet->range.coli) +
325       psppire_axis_unit_size (sheet->haxis, sheet->range.coli) + DRAG_WIDTH / 2)
326     {
327       ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.row0);
328       if (y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2)
329         {
330           *drag_row = sheet->range.row0;
331           return TRUE;
332         }
333       ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) +
334         psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi);
335       if (y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2)
336         {
337           *drag_row = sheet->range.rowi;
338           return TRUE;
339         }
340     }
341
342   if (y >= psppire_axis_start_pixel (sheet->vaxis, sheet->range.row0) - DRAG_WIDTH / 2 &&
343       y <= psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) +
344       psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi) + DRAG_WIDTH / 2)
345     {
346       xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.col0);
347       if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2)
348         {
349           *drag_column = sheet->range.col0;
350           return TRUE;
351         }
352       xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.coli) +
353         psppire_axis_unit_size (sheet->haxis, sheet->range.coli);
354       if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2)
355         {
356           *drag_column = sheet->range.coli;
357           return TRUE;
358         }
359     }
360
361   return FALSE;
362 }
363
364 static inline gboolean
365 POSSIBLE_RESIZE (const PsppireSheet *sheet, gint x, gint y,
366                  gint *drag_row, gint *drag_column)
367 {
368   gint xdrag, ydrag;
369
370   /* Can't drag if nothing is selected */
371   if ( sheet->range.row0 < 0 || sheet->range.rowi < 0 ||
372        sheet->range.col0 < 0 || sheet->range.coli < 0 )
373     return FALSE;
374
375   xdrag = psppire_axis_start_pixel (sheet->haxis, sheet->range.coli)+
376     psppire_axis_unit_size (sheet->haxis, sheet->range.coli);
377
378   ydrag = psppire_axis_start_pixel (sheet->vaxis, sheet->range.rowi) +
379     psppire_axis_unit_size (sheet->vaxis, sheet->range.rowi);
380
381   if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED)
382     ydrag = psppire_axis_start_pixel (sheet->vaxis, min_visible_row (sheet));
383
384   if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED)
385     xdrag = psppire_axis_start_pixel (sheet->haxis, min_visible_column (sheet));
386
387   *drag_column = column_from_xpixel (sheet, x);
388   *drag_row = row_from_ypixel (sheet, y);
389
390   if (x >= xdrag - DRAG_WIDTH / 2 && x <= xdrag + DRAG_WIDTH / 2 &&
391       y >= ydrag - DRAG_WIDTH / 2 && y <= ydrag + DRAG_WIDTH / 2) return TRUE;
392
393   return FALSE;
394 }
395
396
397 static gboolean
398 rectangle_from_range (PsppireSheet *sheet, const PsppireSheetRange *input,
399                       GdkRectangle *r)
400 {
401   PsppireSheetRange range = *input;
402
403   if ( range.row0 == -1 ) range.row0 = min_visible_row (sheet);
404   if ( range.rowi == -1 ) range.rowi = max_visible_row (sheet);
405   if ( range.col0 == -1 ) range.col0 = min_visible_column (sheet);
406   if ( range.coli == -1 ) range.coli = max_visible_column (sheet);
407
408   r->x = psppire_axis_start_pixel (sheet->haxis, range.col0);
409   r->x -= round (sheet->hadjustment->value);
410
411   r->y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
412   r->y -= round (sheet->vadjustment->value);
413
414   r->width = psppire_axis_start_pixel (sheet->haxis, range.coli) -
415     psppire_axis_start_pixel (sheet->haxis, range.col0) +
416     psppire_axis_unit_size (sheet->haxis, range.coli);
417
418   r->height = psppire_axis_start_pixel (sheet->vaxis, range.rowi) -
419     psppire_axis_start_pixel (sheet->vaxis, range.row0) +
420     psppire_axis_unit_size (sheet->vaxis, range.rowi);
421
422   if ( sheet->column_titles_visible)
423     {
424       r->y += sheet->column_title_area.height;
425     }
426
427   if ( sheet->row_titles_visible)
428     {
429       r->x += sheet->row_title_area.width;
430     }
431
432   return TRUE;
433 }
434
435 static gboolean
436 rectangle_from_cell (PsppireSheet *sheet, gint row, gint col,
437                      GdkRectangle *r)
438 {
439   PsppireSheetRange range;
440   g_return_val_if_fail (row >= 0, FALSE);
441   g_return_val_if_fail (col >= 0, FALSE);
442
443   range.row0 = range.rowi = row;
444   range.col0 = range.coli = col;
445
446   return rectangle_from_range (sheet, &range, r);
447 }
448
449
450 static void psppire_sheet_class_init             (PsppireSheetClass *klass);
451 static void psppire_sheet_init                   (PsppireSheet *sheet);
452 static void psppire_sheet_dispose                        (GObject *object);
453 static void psppire_sheet_finalize                       (GObject *object);
454 static void psppire_sheet_style_set              (GtkWidget *widget,
455                                                   GtkStyle *previous_style);
456 static void psppire_sheet_realize                        (GtkWidget *widget);
457 static void psppire_sheet_unrealize              (GtkWidget *widget);
458 static void psppire_sheet_map                    (GtkWidget *widget);
459 static void psppire_sheet_unmap                          (GtkWidget *widget);
460 static gint psppire_sheet_expose                         (GtkWidget *widget,
461                                                           GdkEventExpose *event);
462
463 static void psppire_sheet_forall                         (GtkContainer *container,
464                                                           gboolean include_internals,
465                                                           GtkCallback callback,
466                                                           gpointer callback_data);
467
468 static gboolean psppire_sheet_set_scroll_adjustments  (PsppireSheet *sheet,
469                                                        GtkAdjustment *hadjustment,
470                                                        GtkAdjustment *vadjustment);
471
472 static gint psppire_sheet_button_press           (GtkWidget *widget,
473                                                   GdkEventButton *event);
474 static gint psppire_sheet_button_release                 (GtkWidget *widget,
475                                                           GdkEventButton *event);
476 static gint psppire_sheet_motion                         (GtkWidget *widget,
477                                                           GdkEventMotion *event);
478 static gboolean psppire_sheet_crossing_notify           (GtkWidget *widget,
479                                                          GdkEventCrossing *event);
480 static gint psppire_sheet_entry_key_press                (GtkWidget *widget,
481                                                           GdkEventKey *key);
482 static gboolean psppire_sheet_key_press          (GtkWidget *widget,
483                                                   GdkEventKey *key);
484 static void psppire_sheet_size_request           (GtkWidget *widget,
485                                                   GtkRequisition *requisition);
486 static void psppire_sheet_size_allocate                  (GtkWidget *widget,
487                                                           GtkAllocation *allocation);
488
489 static gboolean psppire_sheet_focus_in               (GtkWidget     *widget,
490                                                       GdkEventFocus *event);
491
492 /* Sheet queries */
493
494 static gboolean psppire_sheet_range_isvisible (const PsppireSheet *sheet,
495                                                const PsppireSheetRange *range);
496 static gboolean psppire_sheet_cell_isvisible  (PsppireSheet *sheet,
497                                                gint row, gint column);
498 /* Drawing Routines */
499
500 /* draw cell */
501 static void psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint column);
502
503
504 /* draw visible part of range. */
505 static void draw_sheet_region (PsppireSheet *sheet, GdkRegion *region);
506
507
508 /* Selection */
509 static void psppire_sheet_draw_border            (PsppireSheet *sheet,
510                                                   PsppireSheetRange range);
511
512 /* Active Cell handling */
513
514 static void psppire_sheet_hide_entry_widget              (PsppireSheet *sheet);
515 static void change_active_cell  (PsppireSheet *sheet, gint row, gint col);
516 static gboolean psppire_sheet_draw_active_cell   (PsppireSheet *sheet);
517 static void psppire_sheet_show_entry_widget              (PsppireSheet *sheet);
518 static gboolean psppire_sheet_click_cell                 (PsppireSheet *sheet,
519                                                           gint row,
520                                                           gint column);
521
522
523 /* Scrollbars */
524
525 static void adjust_scrollbars                    (PsppireSheet *sheet);
526 static void vadjustment_value_changed            (GtkAdjustment *adjustment,
527                                                   gpointer data);
528 static void hadjustment_value_changed            (GtkAdjustment *adjustment,
529                                                   gpointer data);
530
531
532 static void draw_xor_vline                       (PsppireSheet *sheet);
533 static void draw_xor_hline                       (PsppireSheet *sheet);
534 static void draw_xor_rectangle                   (PsppireSheet *sheet,
535                                                   PsppireSheetRange range);
536
537 /* Sheet Button */
538
539 static void create_global_button                 (PsppireSheet *sheet);
540 static void global_button_clicked                (GtkWidget *widget,
541                                                   gpointer data);
542 /* Sheet Entry */
543
544 static void create_sheet_entry                   (PsppireSheet *sheet);
545 static void psppire_sheet_size_allocate_entry    (PsppireSheet *sheet);
546
547 /* Sheet button gadgets */
548
549 static void draw_column_title_buttons    (PsppireSheet *sheet);
550 static void draw_row_title_buttons       (PsppireSheet *sheet);
551
552
553 static void size_allocate_global_button          (PsppireSheet *sheet);
554 static void psppire_sheet_button_size_request    (PsppireSheet *sheet,
555                                                   const PsppireSheetButton *button,
556                                                   GtkRequisition *requisition);
557
558 static void psppire_sheet_real_cell_clear                (PsppireSheet *sheet,
559                                                           gint row,
560                                                           gint column);
561
562
563 /* Signals */
564 enum
565   {
566     SELECT_ROW,
567     SELECT_COLUMN,
568     DOUBLE_CLICK_ROW,
569     DOUBLE_CLICK_COLUMN,
570     BUTTON_EVENT_ROW,
571     BUTTON_EVENT_COLUMN,
572     SELECT_RANGE,
573     RESIZE_RANGE,
574     MOVE_RANGE,
575     TRAVERSE,
576     ACTIVATE,
577     LAST_SIGNAL
578   };
579
580 static GtkContainerClass *parent_class = NULL;
581 static guint sheet_signals[LAST_SIGNAL] = { 0 };
582
583
584 GType
585 psppire_sheet_get_type ()
586 {
587   static GType sheet_type = 0;
588
589   if (!sheet_type)
590     {
591       static const GTypeInfo sheet_info =
592         {
593           sizeof (PsppireSheetClass),
594           NULL,
595           NULL,
596           (GClassInitFunc) psppire_sheet_class_init,
597           NULL,
598           NULL,
599           sizeof (PsppireSheet),
600           0,
601           (GInstanceInitFunc) psppire_sheet_init,
602           NULL,
603         };
604
605       sheet_type =
606         g_type_register_static (GTK_TYPE_BIN, "PsppireSheet",
607                                 &sheet_info, 0);
608     }
609   return sheet_type;
610 }
611
612 \f
613
614 static PsppireSheetRange*
615 psppire_sheet_range_copy (const PsppireSheetRange *range)
616 {
617   PsppireSheetRange *new_range;
618
619   g_return_val_if_fail (range != NULL, NULL);
620
621   new_range = g_new (PsppireSheetRange, 1);
622
623   *new_range = *range;
624
625   return new_range;
626 }
627
628 static void
629 psppire_sheet_range_free (PsppireSheetRange *range)
630 {
631   g_return_if_fail (range != NULL);
632
633   g_free (range);
634 }
635
636 GType
637 psppire_sheet_range_get_type (void)
638 {
639   static GType sheet_range_type = 0;
640
641   if (!sheet_range_type)
642     {
643       sheet_range_type =
644         g_boxed_type_register_static ("PsppireSheetRange",
645                                       (GBoxedCopyFunc) psppire_sheet_range_copy,
646                                       (GBoxedFreeFunc) psppire_sheet_range_free);
647     }
648
649   return sheet_range_type;
650 }
651
652 static PsppireSheetCell*
653 psppire_sheet_cell_copy (const PsppireSheetCell *cell)
654 {
655   PsppireSheetCell *new_cell;
656
657   g_return_val_if_fail (cell != NULL, NULL);
658
659   new_cell = g_new (PsppireSheetCell, 1);
660
661   *new_cell = *cell;
662
663   return new_cell;
664 }
665
666 static void
667 psppire_sheet_cell_free (PsppireSheetCell *cell)
668 {
669   g_return_if_fail (cell != NULL);
670
671   g_free (cell);
672 }
673
674 GType
675 psppire_sheet_cell_get_type (void)
676 {
677   static GType sheet_cell_type = 0;
678
679   if (!sheet_cell_type)
680     {
681       sheet_cell_type =
682         g_boxed_type_register_static ("PsppireSheetCell",
683                                       (GBoxedCopyFunc) psppire_sheet_cell_copy,
684                                       (GBoxedFreeFunc) psppire_sheet_cell_free);
685     }
686
687   return sheet_cell_type;
688 }
689 \f
690
691 /* Properties */
692 enum
693   {
694     PROP_0,
695     PROP_VAXIS,
696     PROP_HAXIS,
697     PROP_CELL_PADDING,
698     PROP_MODEL
699   };
700
701 static void
702 resize_column (PsppireSheet *sheet, gint unit, glong size)
703 {
704   PsppireSheetRange range;
705   range.col0 = unit;
706   range.coli = max_visible_column (sheet);
707   range.row0 = min_visible_row (sheet);
708   range.rowi = max_visible_row (sheet);
709
710   redraw_range (sheet, &range);
711
712   draw_column_title_buttons_range (sheet, range.col0, range.coli);
713 }
714
715
716 static void
717 psppire_sheet_set_horizontal_axis (PsppireSheet *sheet, PsppireAxis *a)
718 {
719   if ( sheet->haxis )
720     g_object_unref (sheet->haxis);
721
722   sheet->haxis = a;
723   g_signal_connect_swapped (a, "resize-unit", G_CALLBACK (resize_column), sheet);
724
725   if ( sheet->haxis )
726     g_object_ref (sheet->haxis);
727 }
728
729 static void
730 resize_row (PsppireSheet *sheet, gint unit, glong size)
731 {
732   PsppireSheetRange range;
733   range.col0 = min_visible_column (sheet);
734   range.coli = max_visible_column (sheet);
735   range.row0 = unit;
736   range.rowi = max_visible_row (sheet);
737
738   redraw_range (sheet, &range);
739
740   draw_row_title_buttons_range (sheet, range.row0, range.rowi);
741 }
742
743 static void
744 psppire_sheet_set_vertical_axis (PsppireSheet *sheet, PsppireAxis *a)
745 {
746   if ( sheet->vaxis )
747     g_object_unref (sheet->vaxis);
748
749   sheet->vaxis = a;
750
751   g_signal_connect_swapped (a, "resize-unit", G_CALLBACK (resize_row), sheet);
752
753   if ( sheet->vaxis )
754     g_object_ref (sheet->vaxis);
755 }
756
757 static const GtkBorder default_cell_padding = {3, 3, 3, 3};
758
759 static void
760 psppire_sheet_set_property (GObject         *object,
761                             guint            prop_id,
762                             const GValue    *value,
763                             GParamSpec      *pspec)
764
765 {
766   PsppireSheet *sheet = PSPPIRE_SHEET (object);
767
768   switch (prop_id)
769     {
770     case PROP_CELL_PADDING:
771       if ( sheet->cell_padding)
772         g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
773
774       sheet->cell_padding = g_value_dup_boxed (value);
775
776       if (NULL == sheet->cell_padding)
777         sheet->cell_padding = g_boxed_copy (GTK_TYPE_BORDER,
778                                             &default_cell_padding);
779
780       if (sheet->vaxis)
781         g_object_set (sheet->vaxis, "padding",
782                       sheet->cell_padding->top + sheet->cell_padding->bottom,
783                       NULL);
784
785       if (sheet->haxis)
786         g_object_set (sheet->haxis, "padding",
787                       sheet->cell_padding->left + sheet->cell_padding->right,
788                       NULL);
789       break;
790     case PROP_VAXIS:
791       psppire_sheet_set_vertical_axis (sheet, g_value_get_pointer (value));
792       g_object_set (sheet->vaxis, "padding",
793                     sheet->cell_padding->top + sheet->cell_padding->bottom,
794                     NULL);
795       break;
796     case PROP_HAXIS:
797       psppire_sheet_set_horizontal_axis (sheet, g_value_get_pointer (value));
798       g_object_set (sheet->haxis, "padding",
799                     sheet->cell_padding->left + sheet->cell_padding->right,
800                     NULL);
801       break;
802     case PROP_MODEL:
803       psppire_sheet_set_model (sheet, g_value_get_pointer (value));
804       break;
805     default:
806       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
807       break;
808     };
809 }
810
811 static void
812 psppire_sheet_get_property (GObject         *object,
813                             guint            prop_id,
814                             GValue          *value,
815                             GParamSpec      *pspec)
816 {
817   PsppireSheet *sheet = PSPPIRE_SHEET (object);
818
819   switch (prop_id)
820     {
821     case PROP_CELL_PADDING:
822       g_value_set_boxed (value, sheet->cell_padding);
823       break;
824     case PROP_VAXIS:
825       g_value_set_pointer (value, sheet->vaxis);
826       break;
827     case PROP_HAXIS:
828       g_value_set_pointer (value, sheet->haxis);
829       break;
830     case PROP_MODEL:
831       g_value_set_pointer (value, sheet->model);
832       break;
833     default:
834       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
835       break;
836     };
837 }
838
839
840 static void
841 psppire_sheet_class_init (PsppireSheetClass *klass)
842 {
843   GObjectClass *object_class = G_OBJECT_CLASS (klass);
844
845   GParamSpec *haxis_spec ;
846   GParamSpec *vaxis_spec ;
847   GParamSpec *model_spec ;
848   GParamSpec *cell_padding_spec ;
849
850   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
851   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
852
853   parent_class = g_type_class_peek_parent (klass);
854
855   /**
856    * PsppireSheet::select-row
857    * @sheet: the sheet widget that emitted the signal
858    * @row: the newly selected row index
859    *
860    * A row has been selected.
861    */
862   sheet_signals[SELECT_ROW] =
863     g_signal_new ("select-row",
864                   G_TYPE_FROM_CLASS (object_class),
865                   G_SIGNAL_RUN_LAST,
866                   offsetof (PsppireSheetClass, select_row),
867                   NULL, NULL,
868                   g_cclosure_marshal_VOID__INT,
869                   G_TYPE_NONE,
870                   1,
871                   G_TYPE_INT);
872
873
874   /**
875    * PsppireSheet::select - column
876    * @sheet: the sheet widget that emitted the signal
877    * @column: the newly selected column index
878    *
879    * A column has been selected.
880    */
881   sheet_signals[SELECT_COLUMN] =
882     g_signal_new ("select-column",
883                   G_TYPE_FROM_CLASS (object_class),
884                   G_SIGNAL_RUN_LAST,
885                   offsetof (PsppireSheetClass, select_column),
886                   NULL, NULL,
887                   g_cclosure_marshal_VOID__INT,
888                   G_TYPE_NONE,
889                   1,
890                   G_TYPE_INT);
891
892
893   /**
894    * PsppireSheet::double-click-row
895    * @sheet: the sheet widget that emitted the signal
896    * @row: the row that was double clicked.
897    *
898    * A row's title button has been double clicked
899    */
900   sheet_signals[DOUBLE_CLICK_ROW] =
901     g_signal_new ("double-click-row",
902                   G_TYPE_FROM_CLASS (object_class),
903                   G_SIGNAL_RUN_LAST,
904                   0,
905                   NULL, NULL,
906                   g_cclosure_marshal_VOID__INT,
907                   G_TYPE_NONE,
908                   1,
909                   G_TYPE_INT);
910
911
912   /**
913    * PsppireSheet::double-click-column
914    * @sheet: the sheet widget that emitted the signal
915    * @column: the column that was double clicked.
916    *
917    * A column's title button has been double clicked
918    */
919   sheet_signals[DOUBLE_CLICK_COLUMN] =
920     g_signal_new ("double-click-column",
921                   G_TYPE_FROM_CLASS (object_class),
922                   G_SIGNAL_RUN_LAST,
923                   0,
924                   NULL, NULL,
925                   g_cclosure_marshal_VOID__INT,
926                   G_TYPE_NONE,
927                   1,
928                   G_TYPE_INT);
929
930
931   /**
932    * PsppireSheet::button-event-column
933    * @sheet: the sheet widget that emitted the signal
934    * @column: the column on which the event occured.
935    *
936    * A button event occured on a column title button
937    */
938   sheet_signals[BUTTON_EVENT_COLUMN] =
939     g_signal_new ("button-event-column",
940                   G_TYPE_FROM_CLASS (object_class),
941                   G_SIGNAL_RUN_LAST,
942                   0,
943                   NULL, NULL,
944                   psppire_marshal_VOID__INT_POINTER,
945                   G_TYPE_NONE,
946                   2,
947                   G_TYPE_INT,
948                   G_TYPE_POINTER
949                   );
950
951
952   /**
953    * PsppireSheet::button-event-row
954    * @sheet: the sheet widget that emitted the signal
955    * @column: the column on which the event occured.
956    *
957    * A button event occured on a row title button
958    */
959   sheet_signals[BUTTON_EVENT_ROW] =
960     g_signal_new ("button-event-row",
961                   G_TYPE_FROM_CLASS (object_class),
962                   G_SIGNAL_RUN_LAST,
963                   0,
964                   NULL, NULL,
965                   psppire_marshal_VOID__INT_POINTER,
966                   G_TYPE_NONE,
967                   2,
968                   G_TYPE_INT,
969                   G_TYPE_POINTER
970                   );
971
972
973   sheet_signals[SELECT_RANGE] =
974     g_signal_new ("select-range",
975                   G_TYPE_FROM_CLASS (object_class),
976                   G_SIGNAL_RUN_LAST,
977                   offsetof (PsppireSheetClass, select_range),
978                   NULL, NULL,
979                   g_cclosure_marshal_VOID__BOXED,
980                   G_TYPE_NONE,
981                   1,
982                   PSPPIRE_TYPE_SHEET_RANGE);
983
984
985   sheet_signals[RESIZE_RANGE] =
986     g_signal_new ("resize-range",
987                   G_TYPE_FROM_CLASS (object_class),
988                   G_SIGNAL_RUN_LAST,
989                   offsetof (PsppireSheetClass, resize_range),
990                   NULL, NULL,
991                   psppire_marshal_VOID__BOXED_BOXED,
992                   G_TYPE_NONE,
993                   2,
994                   PSPPIRE_TYPE_SHEET_RANGE, PSPPIRE_TYPE_SHEET_RANGE
995                   );
996
997   sheet_signals[MOVE_RANGE] =
998     g_signal_new ("move-range",
999                   G_TYPE_FROM_CLASS (object_class),
1000                   G_SIGNAL_RUN_LAST,
1001                   offsetof (PsppireSheetClass, move_range),
1002                   NULL, NULL,
1003                   psppire_marshal_VOID__BOXED_BOXED,
1004                   G_TYPE_NONE,
1005                   2,
1006                   PSPPIRE_TYPE_SHEET_RANGE, PSPPIRE_TYPE_SHEET_RANGE
1007                   );
1008
1009   sheet_signals[TRAVERSE] =
1010     g_signal_new ("traverse",
1011                   G_TYPE_FROM_CLASS (object_class),
1012                   G_SIGNAL_RUN_LAST,
1013                   offsetof (PsppireSheetClass, traverse),
1014                   NULL, NULL,
1015                   psppire_marshal_BOOLEAN__BOXED_POINTER,
1016                   G_TYPE_BOOLEAN, 2,
1017                   PSPPIRE_TYPE_SHEET_CELL,
1018                   G_TYPE_POINTER);
1019
1020
1021   sheet_signals[ACTIVATE] =
1022     g_signal_new ("activate",
1023                   G_TYPE_FROM_CLASS (object_class),
1024                   G_SIGNAL_RUN_LAST,
1025                   offsetof (PsppireSheetClass, activate),
1026                   NULL, NULL,
1027                   psppire_marshal_VOID__INT_INT_INT_INT,
1028                   G_TYPE_NONE, 4,
1029                   G_TYPE_INT, G_TYPE_INT,
1030                   G_TYPE_INT, G_TYPE_INT);
1031
1032   widget_class->set_scroll_adjustments_signal =
1033     g_signal_new ("set-scroll-adjustments",
1034                   G_TYPE_FROM_CLASS (object_class),
1035                   G_SIGNAL_RUN_LAST,
1036                   offsetof (PsppireSheetClass, set_scroll_adjustments),
1037                   NULL, NULL,
1038                   psppire_marshal_VOID__OBJECT_OBJECT,
1039                   G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
1040
1041
1042   container_class->add = NULL;
1043   container_class->remove = NULL;
1044   container_class->forall = psppire_sheet_forall;
1045   container_class->set_focus_child = NULL;
1046
1047   object_class->dispose = psppire_sheet_dispose;
1048   object_class->finalize = psppire_sheet_finalize;
1049
1050   cell_padding_spec =
1051     g_param_spec_boxed ("cell-padding",
1052                         "Cell Padding",
1053                         "The space between a cell's contents and its border",
1054                         GTK_TYPE_BORDER,
1055                         G_PARAM_CONSTRUCT | G_PARAM_READABLE | G_PARAM_WRITABLE);
1056
1057   vaxis_spec =
1058     g_param_spec_pointer ("vertical-axis",
1059                           "Vertical Axis",
1060                           "A pointer to the PsppireAxis object for the rows",
1061                           G_PARAM_READABLE | G_PARAM_WRITABLE );
1062
1063   haxis_spec =
1064     g_param_spec_pointer ("horizontal-axis",
1065                           "Horizontal Axis",
1066                           "A pointer to the PsppireAxis object for the columns",
1067                           G_PARAM_READABLE | G_PARAM_WRITABLE );
1068
1069   model_spec =
1070     g_param_spec_pointer ("model",
1071                           "Model",
1072                           "A pointer to the data model",
1073                           G_PARAM_READABLE | G_PARAM_WRITABLE );
1074
1075
1076   object_class->set_property = psppire_sheet_set_property;
1077   object_class->get_property = psppire_sheet_get_property;
1078
1079   g_object_class_install_property (object_class,
1080                                    PROP_VAXIS,
1081                                    vaxis_spec);
1082
1083   g_object_class_install_property (object_class,
1084                                    PROP_HAXIS,
1085                                    haxis_spec);
1086
1087   g_object_class_install_property (object_class,
1088                                    PROP_CELL_PADDING,
1089                                    cell_padding_spec);
1090
1091   g_object_class_install_property (object_class,
1092                                    PROP_MODEL,
1093                                    model_spec);
1094
1095
1096   widget_class->realize = psppire_sheet_realize;
1097   widget_class->unrealize = psppire_sheet_unrealize;
1098   widget_class->map = psppire_sheet_map;
1099   widget_class->unmap = psppire_sheet_unmap;
1100   widget_class->style_set = psppire_sheet_style_set;
1101   widget_class->button_press_event = psppire_sheet_button_press;
1102   widget_class->button_release_event = psppire_sheet_button_release;
1103   widget_class->motion_notify_event = psppire_sheet_motion;
1104   widget_class->enter_notify_event = psppire_sheet_crossing_notify;
1105   widget_class->leave_notify_event = psppire_sheet_crossing_notify;
1106   widget_class->key_press_event = psppire_sheet_key_press;
1107   widget_class->expose_event = psppire_sheet_expose;
1108   widget_class->size_request = psppire_sheet_size_request;
1109   widget_class->size_allocate = psppire_sheet_size_allocate;
1110   widget_class->focus_in_event = psppire_sheet_focus_in;
1111   widget_class->focus_out_event = NULL;
1112
1113   klass->set_scroll_adjustments = psppire_sheet_set_scroll_adjustments;
1114   klass->select_row = NULL;
1115   klass->select_column = NULL;
1116   klass->select_range = NULL;
1117   klass->resize_range = NULL;
1118   klass->move_range = NULL;
1119   klass->traverse = NULL;
1120   klass->activate = NULL;
1121   klass->changed = NULL;
1122 }
1123
1124 static void
1125 psppire_sheet_init (PsppireSheet *sheet)
1126 {
1127   sheet->model = NULL;
1128   sheet->haxis = NULL;
1129   sheet->vaxis = NULL;
1130
1131   sheet->flags = 0;
1132   sheet->selection_mode = GTK_SELECTION_NONE;
1133   sheet->select_status = PSPPIRE_SHEET_NORMAL;
1134
1135   GTK_WIDGET_UNSET_FLAGS (sheet, GTK_NO_WINDOW);
1136   GTK_WIDGET_SET_FLAGS (sheet, GTK_CAN_FOCUS);
1137
1138   sheet->column_title_window = NULL;
1139   sheet->column_title_area.x = 0;
1140   sheet->column_title_area.y = 0;
1141   sheet->column_title_area.width = 0;
1142   sheet->column_title_area.height = DEFAULT_ROW_HEIGHT;
1143
1144   sheet->row_title_window = NULL;
1145   sheet->row_title_area.x = 0;
1146   sheet->row_title_area.y = 0;
1147   sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
1148   sheet->row_title_area.height = 0;
1149
1150
1151   sheet->active_cell.row = 0;
1152   sheet->active_cell.col = 0;
1153
1154   sheet->range.row0 = 0;
1155   sheet->range.rowi = 0;
1156   sheet->range.col0 = 0;
1157   sheet->range.coli = 0;
1158
1159   sheet->sheet_window = NULL;
1160   sheet->entry_widget = NULL;
1161   sheet->button = NULL;
1162
1163   sheet->hadjustment = NULL;
1164   sheet->vadjustment = NULL;
1165
1166   sheet->cursor_drag = NULL;
1167
1168   sheet->xor_gc = NULL;
1169   sheet->fg_gc = NULL;
1170   sheet->bg_gc = NULL;
1171   sheet->x_drag = 0;
1172   sheet->y_drag = 0;
1173   sheet->show_grid = TRUE;
1174
1175   sheet->motion_timer = 0;
1176
1177   sheet->row_titles_visible = TRUE;
1178   sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
1179
1180   sheet->column_titles_visible = TRUE;
1181
1182
1183   /* create sheet entry */
1184   sheet->entry_type = GTK_TYPE_ENTRY;
1185   create_sheet_entry (sheet);
1186
1187   /* create global selection button */
1188   create_global_button (sheet);
1189 }
1190
1191
1192 /* Cause RANGE to be redrawn. If RANGE is null, then the
1193    entire visible range will be redrawn.
1194 */
1195 static void
1196 redraw_range (PsppireSheet *sheet, PsppireSheetRange *range)
1197 {
1198   GdkRectangle rect;
1199
1200   if ( ! GTK_WIDGET_REALIZED (sheet))
1201     return;
1202
1203   if ( NULL != range )
1204     rectangle_from_range (sheet, range, &rect);
1205   else
1206     {
1207       GdkRegion *r = gdk_drawable_get_visible_region (sheet->sheet_window);
1208       gdk_region_get_clipbox (r, &rect);
1209
1210       if ( sheet->column_titles_visible)
1211         {
1212           rect.y += sheet->column_title_area.height;
1213           rect.height -= sheet->column_title_area.height;
1214         }
1215
1216       if ( sheet->row_titles_visible)
1217         {
1218           rect.x += sheet->row_title_area.width;
1219           rect.width -= sheet->row_title_area.width;
1220         }
1221     }
1222
1223   gdk_window_invalidate_rect (sheet->sheet_window, &rect, FALSE);
1224 }
1225
1226
1227 /* Callback which occurs whenever columns are inserted / deleted in the model */
1228 static void
1229 columns_inserted_deleted_callback (PsppireSheetModel *model, gint first_column,
1230                                    gint n_columns,
1231                                    gpointer data)
1232 {
1233   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1234
1235   PsppireSheetRange range;
1236   gint model_columns = psppire_sheet_model_get_column_count (model);
1237
1238
1239   /* Need to update all the columns starting from the first column and onwards.
1240    * Previous column are unchanged, so don't need to be updated.
1241    */
1242   range.col0 = first_column;
1243   range.row0 = 0;
1244   range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
1245   range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
1246
1247   adjust_scrollbars (sheet);
1248
1249   if (sheet->active_cell.col >= model_columns)
1250     change_active_cell (sheet, sheet->active_cell.row, model_columns - 1);
1251
1252   draw_column_title_buttons_range (sheet,
1253                                    first_column, max_visible_column (sheet));
1254
1255
1256   redraw_range (sheet, &range);
1257 }
1258
1259
1260
1261
1262 /* Callback which occurs whenever rows are inserted / deleted in the model */
1263 static void
1264 rows_inserted_deleted_callback (PsppireSheetModel *model, gint first_row,
1265                                 gint n_rows, gpointer data)
1266 {
1267   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1268
1269   PsppireSheetRange range;
1270
1271   gint model_rows = psppire_sheet_model_get_row_count (model);
1272
1273   /* Need to update all the rows starting from the first row and onwards.
1274    * Previous rows are unchanged, so don't need to be updated.
1275    */
1276   range.row0 = first_row;
1277   range.col0 = 0;
1278   range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
1279   range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
1280
1281   adjust_scrollbars (sheet);
1282
1283   if (sheet->active_cell.row >= model_rows)
1284     change_active_cell (sheet, model_rows - 1, sheet->active_cell.col);
1285
1286   draw_row_title_buttons_range (sheet, first_row, max_visible_row (sheet));
1287
1288   redraw_range (sheet, &range);
1289 }
1290
1291 /*
1292   If row0 or rowi are negative, then all rows will be updated.
1293   If col0 or coli are negative, then all columns will be updated.
1294 */
1295 static void
1296 range_update_callback (PsppireSheetModel *m, gint row0, gint col0,
1297                        gint rowi, gint coli, gpointer data)
1298 {
1299   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1300
1301   PsppireSheetRange range;
1302
1303   range.row0 = row0;
1304   range.col0 = col0;
1305   range.rowi = rowi;
1306   range.coli = coli;
1307
1308   if ( !GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1309     return;
1310
1311   if ( ( row0 < 0 && col0 < 0 ) || ( rowi < 0 && coli < 0 ) )
1312     {
1313       redraw_range (sheet, NULL);
1314       adjust_scrollbars (sheet);
1315
1316       draw_row_title_buttons_range (sheet, min_visible_row (sheet),
1317                                     max_visible_row (sheet));
1318
1319       draw_column_title_buttons_range (sheet, min_visible_column (sheet),
1320                                        max_visible_column (sheet));
1321
1322       return;
1323     }
1324   else if ( row0 < 0 || rowi < 0 )
1325     {
1326       range.row0 = min_visible_row (sheet);
1327       range.rowi = max_visible_row (sheet);
1328     }
1329   else if ( col0 < 0 || coli < 0 )
1330     {
1331       range.col0 = min_visible_column (sheet);
1332       range.coli = max_visible_column (sheet);
1333     }
1334
1335   redraw_range (sheet, &range);
1336 }
1337
1338
1339 /**
1340  * psppire_sheet_new:
1341  * @rows: initial number of rows
1342  * @columns: initial number of columns
1343  * @title: sheet title
1344  * @model: the model to use for the sheet data
1345  *
1346  * Creates a new sheet widget with the given number of rows and columns.
1347  *
1348  * Returns: the new sheet widget
1349  */
1350 GtkWidget *
1351 psppire_sheet_new (PsppireSheetModel *model)
1352 {
1353   GtkWidget *widget = g_object_new (PSPPIRE_TYPE_SHEET,
1354                                     "model", model,
1355                                     NULL);
1356   return widget;
1357 }
1358
1359
1360 /**
1361  * psppire_sheet_set_model
1362  * @sheet: the sheet to set the model for
1363  * @model: the model to use for the sheet data
1364  *
1365  * Sets the model for a PsppireSheet
1366  *
1367  */
1368 void
1369 psppire_sheet_set_model (PsppireSheet *sheet, PsppireSheetModel *model)
1370 {
1371   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1372
1373   if (sheet->model ) g_object_unref (sheet->model);
1374
1375   sheet->model = model;
1376
1377   if ( model)
1378     {
1379       g_object_ref (model);
1380
1381       sheet->update_handler_id = g_signal_connect (model, "range_changed",
1382                                                    G_CALLBACK (range_update_callback),
1383                                                    sheet);
1384
1385       g_signal_connect (model, "rows_inserted",
1386                         G_CALLBACK (rows_inserted_deleted_callback), sheet);
1387
1388       g_signal_connect (model, "rows_deleted",
1389                         G_CALLBACK (rows_inserted_deleted_callback), sheet);
1390
1391       g_signal_connect (model, "columns_inserted",
1392                         G_CALLBACK (columns_inserted_deleted_callback), sheet);
1393
1394       g_signal_connect (model, "columns_deleted",
1395                         G_CALLBACK (columns_inserted_deleted_callback), sheet);
1396     }
1397 }
1398
1399
1400 void
1401 psppire_sheet_change_entry (PsppireSheet *sheet, GtkType entry_type)
1402 {
1403   g_return_if_fail (sheet != NULL);
1404   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1405
1406   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
1407     psppire_sheet_hide_entry_widget (sheet);
1408
1409   sheet->entry_type = entry_type;
1410
1411   create_sheet_entry (sheet);
1412
1413   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
1414     psppire_sheet_show_entry_widget (sheet);
1415 }
1416
1417 void
1418 psppire_sheet_show_grid (PsppireSheet *sheet, gboolean show)
1419 {
1420   g_return_if_fail (sheet != NULL);
1421   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1422
1423   if (show == sheet->show_grid) return;
1424
1425   sheet->show_grid = show;
1426
1427   redraw_range (sheet, NULL);
1428 }
1429
1430 gboolean
1431 psppire_sheet_grid_visible (PsppireSheet *sheet)
1432 {
1433   g_return_val_if_fail (sheet != NULL, 0);
1434   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
1435
1436   return sheet->show_grid;
1437 }
1438
1439 guint
1440 psppire_sheet_get_columns_count (PsppireSheet *sheet)
1441 {
1442   g_return_val_if_fail (sheet != NULL, 0);
1443   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
1444
1445   return psppire_axis_unit_count (sheet->haxis);
1446 }
1447
1448 static void set_column_width (PsppireSheet *sheet,
1449                               gint column,
1450                               gint width);
1451
1452
1453 void
1454 psppire_sheet_show_column_titles (PsppireSheet *sheet)
1455 {
1456   if (sheet->column_titles_visible) return;
1457
1458   sheet->column_titles_visible = TRUE;
1459
1460   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1461     return;
1462
1463   gdk_window_show (sheet->column_title_window);
1464   gdk_window_move_resize (sheet->column_title_window,
1465                           sheet->column_title_area.x,
1466                           sheet->column_title_area.y,
1467                           sheet->column_title_area.width,
1468                           sheet->column_title_area.height);
1469
1470   adjust_scrollbars (sheet);
1471
1472   if (sheet->vadjustment)
1473     g_signal_emit_by_name (sheet->vadjustment,
1474                            "value_changed");
1475
1476   size_allocate_global_button (sheet);
1477
1478   if ( sheet->row_titles_visible)
1479     gtk_widget_show (sheet->button);
1480 }
1481
1482
1483 void
1484 psppire_sheet_show_row_titles (PsppireSheet *sheet)
1485 {
1486   if (sheet->row_titles_visible) return;
1487
1488   sheet->row_titles_visible = TRUE;
1489
1490
1491   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1492     {
1493       gdk_window_show (sheet->row_title_window);
1494       gdk_window_move_resize (sheet->row_title_window,
1495                               sheet->row_title_area.x,
1496                               sheet->row_title_area.y,
1497                               sheet->row_title_area.width,
1498                               sheet->row_title_area.height);
1499
1500       adjust_scrollbars (sheet);
1501     }
1502
1503   if (sheet->hadjustment)
1504     g_signal_emit_by_name (sheet->hadjustment,
1505                            "value_changed");
1506
1507   size_allocate_global_button (sheet);
1508
1509   if ( sheet->column_titles_visible)
1510     gtk_widget_show (sheet->button);
1511 }
1512
1513 void
1514 psppire_sheet_hide_column_titles (PsppireSheet *sheet)
1515 {
1516   if (!sheet->column_titles_visible) return;
1517
1518   sheet->column_titles_visible = FALSE;
1519
1520   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1521     {
1522       if (sheet->column_title_window)
1523         gdk_window_hide (sheet->column_title_window);
1524
1525       gtk_widget_hide (sheet->button);
1526
1527       adjust_scrollbars (sheet);
1528     }
1529
1530   if (sheet->vadjustment)
1531     g_signal_emit_by_name (sheet->vadjustment,
1532                            "value_changed");
1533 }
1534
1535 void
1536 psppire_sheet_hide_row_titles (PsppireSheet *sheet)
1537 {
1538   if (!sheet->row_titles_visible) return;
1539
1540   sheet->row_titles_visible = FALSE;
1541
1542   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1543     {
1544       if (sheet->row_title_window)
1545         gdk_window_hide (sheet->row_title_window);
1546
1547       gtk_widget_hide (sheet->button);
1548
1549       adjust_scrollbars (sheet);
1550     }
1551
1552   if (sheet->hadjustment)
1553     g_signal_emit_by_name (sheet->hadjustment,
1554                            "value_changed");
1555 }
1556
1557
1558 /* Scroll the sheet so that the cell ROW, COLUMN is visible.
1559    If {ROW,COL}_ALIGN is zero, then the cell will be placed
1560    at the {top,left} of the sheet.  If it's 1, then it'll
1561    be placed at the {bottom,right}.
1562    ROW or COL may be -1, in which case scrolling in that dimension
1563    does not occur.
1564 */
1565 void
1566 psppire_sheet_moveto (PsppireSheet *sheet,
1567                       gint row,
1568                       gint col,
1569                       gfloat row_align,
1570                       gfloat col_align)
1571 {
1572   gint width, height;
1573
1574   g_return_if_fail (row_align >= 0);
1575   g_return_if_fail (col_align >= 0);
1576
1577   g_return_if_fail (row_align <= 1);
1578   g_return_if_fail (col_align <= 1);
1579
1580   g_return_if_fail (col <
1581                     psppire_axis_unit_count (sheet->haxis));
1582   g_return_if_fail (row <
1583                     psppire_axis_unit_count (sheet->vaxis));
1584
1585   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
1586
1587
1588   if (row >= 0)
1589     {
1590       gint y =  psppire_axis_start_pixel (sheet->vaxis, row);
1591
1592       gtk_adjustment_set_value (sheet->vadjustment, y - height * row_align);
1593     }
1594
1595
1596   if (col >= 0)
1597     {
1598       gint x =  psppire_axis_start_pixel (sheet->haxis, col);
1599
1600       gtk_adjustment_set_value (sheet->hadjustment, x - width * col_align);
1601     }
1602 }
1603
1604
1605
1606
1607 static gboolean
1608 psppire_sheet_range_isvisible (const PsppireSheet *sheet,
1609                                const PsppireSheetRange *range)
1610 {
1611   g_return_val_if_fail (sheet != NULL, FALSE);
1612
1613   if (range->row0 < 0 || range->row0 >= psppire_axis_unit_count (sheet->vaxis))
1614     return FALSE;
1615
1616   if (range->rowi < 0 || range->rowi >= psppire_axis_unit_count (sheet->vaxis))
1617     return FALSE;
1618
1619   if (range->col0 < 0 || range->col0 >= psppire_axis_unit_count (sheet->haxis))
1620     return FALSE;
1621
1622   if (range->coli < 0 || range->coli >= psppire_axis_unit_count (sheet->haxis))
1623     return FALSE;
1624
1625   if (range->rowi < min_visible_row (sheet))
1626     return FALSE;
1627
1628   if (range->row0 > max_visible_row (sheet))
1629     return FALSE;
1630
1631   if (range->coli < min_visible_column (sheet))
1632     return FALSE;
1633
1634   if (range->col0 > max_visible_column (sheet))
1635     return FALSE;
1636
1637   return TRUE;
1638 }
1639
1640 static gboolean
1641 psppire_sheet_cell_isvisible (PsppireSheet *sheet,
1642                               gint row, gint column)
1643 {
1644   PsppireSheetRange range;
1645
1646   range.row0 = row;
1647   range.col0 = column;
1648   range.rowi = row;
1649   range.coli = column;
1650
1651   return psppire_sheet_range_isvisible (sheet, &range);
1652 }
1653
1654 void
1655 psppire_sheet_get_visible_range (PsppireSheet *sheet, PsppireSheetRange *range)
1656 {
1657   g_return_if_fail (sheet != NULL);
1658   g_return_if_fail (PSPPIRE_IS_SHEET (sheet)) ;
1659   g_return_if_fail (range != NULL);
1660
1661   range->row0 = min_visible_row (sheet);
1662   range->col0 = min_visible_column (sheet);
1663   range->rowi = max_visible_row (sheet);
1664   range->coli = max_visible_column (sheet);
1665 }
1666
1667
1668 static gboolean
1669 psppire_sheet_set_scroll_adjustments (PsppireSheet *sheet,
1670                                       GtkAdjustment *hadjustment,
1671                                       GtkAdjustment *vadjustment)
1672 {
1673   if ( sheet->vadjustment != vadjustment )
1674     {
1675       if (sheet->vadjustment)
1676         g_object_unref (sheet->vadjustment);
1677       sheet->vadjustment = vadjustment;
1678
1679       if ( vadjustment)
1680         {
1681           g_object_ref (vadjustment);
1682
1683           g_signal_connect (sheet->vadjustment, "value_changed",
1684                             G_CALLBACK (vadjustment_value_changed),
1685                             sheet);
1686         }
1687     }
1688
1689   if ( sheet->hadjustment != hadjustment )
1690     {
1691       if (sheet->hadjustment)
1692         g_object_unref (sheet->hadjustment);
1693
1694       sheet->hadjustment = hadjustment;
1695
1696       if ( hadjustment)
1697         {
1698           g_object_ref (hadjustment);
1699
1700           g_signal_connect (sheet->hadjustment, "value_changed",
1701                             G_CALLBACK (hadjustment_value_changed),
1702                             sheet);
1703         }
1704     }
1705   return TRUE;
1706 }
1707
1708 static void
1709 psppire_sheet_finalize (GObject *object)
1710 {
1711   PsppireSheet *sheet;
1712
1713   g_return_if_fail (object != NULL);
1714   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1715
1716   sheet = PSPPIRE_SHEET (object);
1717
1718   if (G_OBJECT_CLASS (parent_class)->finalize)
1719     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
1720 }
1721
1722 static void
1723 psppire_sheet_dispose  (GObject *object)
1724 {
1725   PsppireSheet *sheet = PSPPIRE_SHEET (object);
1726
1727   g_return_if_fail (object != NULL);
1728   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1729
1730   if ( sheet->dispose_has_run )
1731     return ;
1732
1733   sheet->dispose_has_run = TRUE;
1734
1735   if ( sheet->cell_padding)
1736     g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
1737
1738   if (sheet->model) g_object_unref (sheet->model);
1739   if (sheet->vaxis) g_object_unref (sheet->vaxis);
1740   if (sheet->haxis) g_object_unref (sheet->haxis);
1741
1742   g_object_unref (sheet->button);
1743   sheet->button = NULL;
1744
1745   /* unref adjustments */
1746   if (sheet->hadjustment)
1747     {
1748       g_signal_handlers_disconnect_matched (sheet->hadjustment,
1749                                             G_SIGNAL_MATCH_DATA,
1750                                             0, 0, 0, 0,
1751                                             sheet);
1752
1753       g_object_unref (sheet->hadjustment);
1754       sheet->hadjustment = NULL;
1755     }
1756
1757   if (sheet->vadjustment)
1758     {
1759       g_signal_handlers_disconnect_matched (sheet->vadjustment,
1760                                             G_SIGNAL_MATCH_DATA,
1761                                             0, 0, 0, 0,
1762                                             sheet);
1763
1764       g_object_unref (sheet->vadjustment);
1765
1766       sheet->vadjustment = NULL;
1767     }
1768
1769   if (G_OBJECT_CLASS (parent_class)->dispose)
1770     (*G_OBJECT_CLASS (parent_class)->dispose) (object);
1771 }
1772
1773 static void
1774 psppire_sheet_style_set (GtkWidget *widget,
1775                          GtkStyle *previous_style)
1776 {
1777   PsppireSheet *sheet;
1778
1779   g_return_if_fail (widget != NULL);
1780   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1781
1782   if (GTK_WIDGET_CLASS (parent_class)->style_set)
1783     (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1784
1785   sheet = PSPPIRE_SHEET (widget);
1786
1787   if (GTK_WIDGET_REALIZED (widget))
1788     {
1789       gtk_style_set_background (widget->style, widget->window, widget->state);
1790     }
1791
1792   set_entry_widget_font (sheet);
1793 }
1794
1795
1796 static void
1797 psppire_sheet_realize (GtkWidget *widget)
1798 {
1799   PsppireSheet *sheet;
1800   GdkWindowAttr attributes;
1801   const gint attributes_mask =
1802     GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;
1803
1804   GdkGCValues values;
1805   GdkColormap *colormap;
1806   GdkDisplay *display;
1807
1808   g_return_if_fail (widget != NULL);
1809   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1810
1811   sheet = PSPPIRE_SHEET (widget);
1812
1813   colormap = gtk_widget_get_colormap (widget);
1814   display = gtk_widget_get_display (widget);
1815
1816   attributes.window_type = GDK_WINDOW_CHILD;
1817   attributes.x = widget->allocation.x;
1818   attributes.y = widget->allocation.y;
1819   attributes.width = widget->allocation.width;
1820   attributes.height = widget->allocation.height;
1821   attributes.wclass = GDK_INPUT_OUTPUT;
1822
1823   attributes.visual = gtk_widget_get_visual (widget);
1824   attributes.colormap = colormap;
1825
1826   attributes.event_mask = gtk_widget_get_events (widget);
1827   attributes.event_mask |= (GDK_EXPOSURE_MASK |
1828                             GDK_BUTTON_PRESS_MASK |
1829                             GDK_BUTTON_RELEASE_MASK |
1830                             GDK_KEY_PRESS_MASK |
1831                             GDK_ENTER_NOTIFY_MASK |
1832                             GDK_LEAVE_NOTIFY_MASK |
1833                             GDK_POINTER_MOTION_MASK |
1834                             GDK_POINTER_MOTION_HINT_MASK);
1835
1836   attributes.cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
1837
1838   /* main window */
1839   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1840
1841   gdk_window_set_user_data (widget->window, sheet);
1842
1843   widget->style = gtk_style_attach (widget->style, widget->window);
1844
1845   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1846
1847   gdk_color_parse ("white", &sheet->color[BG_COLOR]);
1848   gdk_colormap_alloc_color (colormap, &sheet->color[BG_COLOR], FALSE,
1849                             TRUE);
1850   gdk_color_parse ("gray", &sheet->color[GRID_COLOR]);
1851   gdk_colormap_alloc_color (colormap, &sheet->color[GRID_COLOR], FALSE,
1852                             TRUE);
1853
1854   attributes.x = 0;
1855   attributes.y = 0;
1856   attributes.width = sheet->column_title_area.width;
1857   attributes.height = sheet->column_title_area.height;
1858
1859
1860   /* column - title window */
1861   sheet->column_title_window =
1862     gdk_window_new (widget->window, &attributes, attributes_mask);
1863   gdk_window_set_user_data (sheet->column_title_window, sheet);
1864   gtk_style_set_background (widget->style, sheet->column_title_window,
1865                             GTK_STATE_NORMAL);
1866
1867
1868   attributes.x = 0;
1869   attributes.y = 0;
1870   attributes.width = sheet->row_title_area.width;
1871   attributes.height = sheet->row_title_area.height;
1872
1873   /* row - title window */
1874   sheet->row_title_window = gdk_window_new (widget->window,
1875                                             &attributes, attributes_mask);
1876   gdk_window_set_user_data (sheet->row_title_window, sheet);
1877   gtk_style_set_background (widget->style, sheet->row_title_window,
1878                             GTK_STATE_NORMAL);
1879
1880   /* sheet - window */
1881   attributes.cursor = gdk_cursor_new_for_display (display, GDK_PLUS);
1882
1883   attributes.x = 0;
1884   attributes.y = 0;
1885
1886   sheet->sheet_window = gdk_window_new (widget->window,
1887                                         &attributes, attributes_mask);
1888   gdk_window_set_user_data (sheet->sheet_window, sheet);
1889
1890   gdk_cursor_unref (attributes.cursor);
1891
1892   gdk_window_set_background (sheet->sheet_window, &widget->style->white);
1893   gdk_window_show (sheet->sheet_window);
1894
1895   /* GCs */
1896   sheet->fg_gc = gdk_gc_new (widget->window);
1897   sheet->bg_gc = gdk_gc_new (widget->window);
1898
1899   values.foreground = widget->style->white;
1900   values.function = GDK_INVERT;
1901   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
1902   values.line_width = MAX (sheet->cell_padding->left,
1903                            MAX (sheet->cell_padding->right,
1904                                 MAX (sheet->cell_padding->top,
1905                                      sheet->cell_padding->bottom)));
1906
1907   sheet->xor_gc = gdk_gc_new_with_values (widget->window,
1908                                           &values,
1909                                           GDK_GC_FOREGROUND |
1910                                           GDK_GC_FUNCTION |
1911                                           GDK_GC_SUBWINDOW |
1912                                           GDK_GC_LINE_WIDTH
1913                                           );
1914
1915   gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
1916   gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
1917
1918   gtk_widget_set_parent_window (sheet->button, sheet->sheet_window);
1919   gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet));
1920
1921   sheet->button->style = gtk_style_attach (sheet->button->style,
1922                                            sheet->sheet_window);
1923
1924
1925   sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
1926
1927   if (sheet->column_titles_visible)
1928     gdk_window_show (sheet->column_title_window);
1929   if (sheet->row_titles_visible)
1930     gdk_window_show (sheet->row_title_window);
1931
1932   sheet->hover_window = create_hover_window ();
1933
1934   draw_row_title_buttons (sheet);
1935   draw_column_title_buttons (sheet);
1936
1937   psppire_sheet_update_primary_selection (sheet);
1938
1939
1940   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1941 }
1942
1943 static void
1944 create_global_button (PsppireSheet *sheet)
1945 {
1946   sheet->button = gtk_button_new_with_label (" ");
1947
1948   GTK_WIDGET_UNSET_FLAGS(sheet->button, GTK_CAN_FOCUS);
1949
1950   g_object_ref_sink (sheet->button);
1951
1952   g_signal_connect (sheet->button,
1953                     "pressed",
1954                     G_CALLBACK (global_button_clicked),
1955                     sheet);
1956 }
1957
1958 static void
1959 size_allocate_global_button (PsppireSheet *sheet)
1960 {
1961   GtkAllocation allocation;
1962
1963   if (!sheet->column_titles_visible) return;
1964   if (!sheet->row_titles_visible) return;
1965
1966   gtk_widget_size_request (sheet->button, NULL);
1967
1968   allocation.x = 0;
1969   allocation.y = 0;
1970   allocation.width = sheet->row_title_area.width;
1971   allocation.height = sheet->column_title_area.height;
1972
1973   gtk_widget_size_allocate (sheet->button, &allocation);
1974 }
1975
1976 static void
1977 global_button_clicked (GtkWidget *widget, gpointer data)
1978 {
1979   psppire_sheet_click_cell (PSPPIRE_SHEET (data), -1, -1);
1980 }
1981
1982
1983 static void
1984 psppire_sheet_unrealize (GtkWidget *widget)
1985 {
1986   PsppireSheet *sheet;
1987
1988   g_return_if_fail (widget != NULL);
1989   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1990
1991   sheet = PSPPIRE_SHEET (widget);
1992
1993   gdk_cursor_unref (sheet->cursor_drag);
1994   sheet->cursor_drag = NULL;
1995
1996   gdk_colormap_free_colors (gtk_widget_get_colormap (widget),
1997                             sheet->color, n_COLORS);
1998
1999   g_object_unref (sheet->xor_gc);
2000   g_object_unref (sheet->fg_gc);
2001   g_object_unref (sheet->bg_gc);
2002
2003   destroy_hover_window (sheet->hover_window);
2004
2005   gdk_window_destroy (sheet->sheet_window);
2006   gdk_window_destroy (sheet->column_title_window);
2007   gdk_window_destroy (sheet->row_title_window);
2008
2009   gtk_widget_unparent (sheet->entry_widget);
2010   if (sheet->button != NULL)
2011     gtk_widget_unparent (sheet->button);
2012
2013   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
2014     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
2015 }
2016
2017 static void
2018 psppire_sheet_map (GtkWidget *widget)
2019 {
2020   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2021
2022   g_return_if_fail (widget != NULL);
2023   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
2024
2025   if (!GTK_WIDGET_MAPPED (widget))
2026     {
2027       GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
2028
2029       gdk_window_show (widget->window);
2030       gdk_window_show (sheet->sheet_window);
2031
2032       if (sheet->column_titles_visible)
2033         {
2034           draw_column_title_buttons (sheet);
2035           gdk_window_show (sheet->column_title_window);
2036         }
2037       if (sheet->row_titles_visible)
2038         {
2039           draw_row_title_buttons (sheet);
2040           gdk_window_show (sheet->row_title_window);
2041         }
2042
2043       if (!GTK_WIDGET_MAPPED (sheet->entry_widget)
2044           && sheet->active_cell.row >= 0
2045           && sheet->active_cell.col >= 0 )
2046         {
2047           gtk_widget_show (sheet->entry_widget);
2048           gtk_widget_map (sheet->entry_widget);
2049         }
2050
2051       if (!GTK_WIDGET_MAPPED (sheet->button))
2052         {
2053           gtk_widget_show (sheet->button);
2054           gtk_widget_map (sheet->button);
2055         }
2056
2057       redraw_range (sheet, NULL);
2058       change_active_cell (sheet,
2059                           sheet->active_cell.row,
2060                           sheet->active_cell.col);
2061     }
2062 }
2063
2064 static void
2065 psppire_sheet_unmap (GtkWidget *widget)
2066 {
2067   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2068
2069   if (!GTK_WIDGET_MAPPED (widget))
2070     return;
2071
2072   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
2073
2074   gdk_window_hide (sheet->sheet_window);
2075   if (sheet->column_titles_visible)
2076     gdk_window_hide (sheet->column_title_window);
2077   if (sheet->row_titles_visible)
2078     gdk_window_hide (sheet->row_title_window);
2079   gdk_window_hide (widget->window);
2080
2081   gtk_widget_unmap (sheet->entry_widget);
2082   gtk_widget_unmap (sheet->button);
2083   gtk_widget_unmap (sheet->hover_window->window);
2084 }
2085
2086 /* get cell attributes of the given cell */
2087 /* TRUE means that the cell is currently allocated */
2088 static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
2089                                               gint row, gint col,
2090                                               PsppireSheetCellAttr *attributes);
2091
2092
2093
2094 static void
2095 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
2096 {
2097   PangoLayout *layout;
2098   PangoRectangle text;
2099   PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc;
2100   gint font_height;
2101
2102   gchar *label;
2103
2104   PsppireSheetCellAttr attributes;
2105   GdkRectangle area;
2106
2107   g_return_if_fail (sheet != NULL);
2108
2109   /* bail now if we aren't yet drawable */
2110   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
2111
2112   if (row < 0 ||
2113       row >= psppire_axis_unit_count (sheet->vaxis))
2114     return;
2115
2116   if (col < 0 ||
2117       col >= psppire_axis_unit_count (sheet->haxis))
2118     return;
2119
2120   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2121
2122   /* select GC for background rectangle */
2123   gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
2124   gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
2125
2126   rectangle_from_cell (sheet, row, col, &area);
2127
2128   gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
2129
2130   if (sheet->show_grid)
2131     {
2132       gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]);
2133
2134       gdk_draw_rectangle (sheet->sheet_window,
2135                           sheet->bg_gc,
2136                           FALSE,
2137                           area.x, area.y,
2138                           area.width, area.height);
2139     }
2140
2141
2142   label = psppire_sheet_cell_get_text (sheet, row, col);
2143   if (NULL == label)
2144     return;
2145
2146
2147   layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label);
2148   dispose_string (sheet, label);
2149
2150
2151   pango_layout_set_font_description (layout, font_desc);
2152
2153   pango_layout_get_pixel_extents (layout, NULL, &text);
2154
2155   gdk_gc_set_clip_rectangle (sheet->fg_gc, &area);
2156
2157   font_height = pango_font_description_get_size (font_desc);
2158   if ( !pango_font_description_get_size_is_absolute (font_desc))
2159     font_height /= PANGO_SCALE;
2160
2161
2162   if ( sheet->cell_padding )
2163     {
2164       area.x += sheet->cell_padding->left;
2165       area.width -= sheet->cell_padding->right
2166         + sheet->cell_padding->left;
2167
2168       area.y += sheet->cell_padding->top;
2169       area.height -= sheet->cell_padding->bottom
2170         +
2171         sheet->cell_padding->top;
2172     }
2173
2174   /* Centre the text vertically */
2175   area.y += (area.height - font_height) / 2.0;
2176
2177   switch (attributes.justification)
2178     {
2179     case GTK_JUSTIFY_RIGHT:
2180       area.x += area.width - text.width;
2181       break;
2182     case GTK_JUSTIFY_CENTER:
2183       area.x += (area.width - text.width) / 2.0;
2184       break;
2185     case GTK_JUSTIFY_LEFT:
2186       /* Do nothing */
2187       break;
2188     default:
2189       g_critical ("Unhandled justification %d in column %d\n",
2190                   attributes.justification, col);
2191       break;
2192     }
2193
2194   gdk_draw_layout (sheet->sheet_window, sheet->fg_gc,
2195                    area.x,
2196                    area.y,
2197                    layout);
2198
2199   gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL);
2200   g_object_unref (layout);
2201 }
2202
2203
2204 static void
2205 draw_sheet_region (PsppireSheet *sheet, GdkRegion *region)
2206 {
2207   PsppireSheetRange range;
2208   GdkRectangle area;
2209   gint y, x;
2210   gint i, j;
2211
2212   PsppireSheetRange drawing_range;
2213
2214   gdk_region_get_clipbox (region, &area);
2215
2216   y = area.y + sheet->vadjustment->value;
2217   x = area.x + sheet->hadjustment->value;
2218
2219   if ( sheet->column_titles_visible)
2220     y -= sheet->column_title_area.height;
2221
2222   if ( sheet->row_titles_visible)
2223     x -= sheet->row_title_area.width;
2224
2225   maximize_int (&x, 0);
2226   maximize_int (&y, 0);
2227
2228   range.row0 = row_from_ypixel (sheet, y);
2229   range.rowi = row_from_ypixel (sheet, y + area.height);
2230
2231   range.col0 = column_from_xpixel (sheet, x);
2232   range.coli = column_from_xpixel (sheet, x + area.width);
2233
2234   g_return_if_fail (sheet != NULL);
2235   g_return_if_fail (PSPPIRE_SHEET (sheet));
2236
2237   if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return;
2238   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2239   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
2240
2241
2242   drawing_range.row0 = MAX (range.row0, min_visible_row (sheet));
2243   drawing_range.col0 = MAX (range.col0, min_visible_column (sheet));
2244   drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet));
2245   drawing_range.coli = MIN (range.coli, max_visible_column (sheet));
2246
2247   g_return_if_fail (drawing_range.rowi >= drawing_range.row0);
2248   g_return_if_fail (drawing_range.coli >= drawing_range.col0);
2249
2250   for (i = drawing_range.row0; i <= drawing_range.rowi; i++)
2251     {
2252       for (j = drawing_range.col0; j <= drawing_range.coli; j++)
2253         psppire_sheet_cell_draw (sheet, i, j);
2254     }
2255
2256   if (sheet->select_status == PSPPIRE_SHEET_NORMAL &&
2257       sheet->active_cell.row >= drawing_range.row0 &&
2258       sheet->active_cell.row <= drawing_range.rowi &&
2259       sheet->active_cell.col >= drawing_range.col0 &&
2260       sheet->active_cell.col <= drawing_range.coli)
2261     psppire_sheet_show_entry_widget (sheet);
2262 }
2263
2264
2265
2266 static inline gint
2267 safe_strcmp (const gchar *s1, const gchar *s2)
2268 {
2269   if ( !s1 && !s2) return 0;
2270   if ( !s1) return -1;
2271   if ( !s2) return +1;
2272   return strcmp (s1, s2);
2273 }
2274
2275 static void
2276 psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col,
2277                         GtkJustification justification,
2278                         const gchar *text)
2279 {
2280   PsppireSheetModel *model ;
2281   gchar *old_text ;
2282
2283   g_return_if_fail (sheet != NULL);
2284   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2285
2286   if (col >= psppire_axis_unit_count (sheet->haxis)
2287       || row >= psppire_axis_unit_count (sheet->vaxis))
2288     return;
2289
2290   if (col < 0 || row < 0) return;
2291
2292   model = psppire_sheet_get_model (sheet);
2293
2294   old_text = psppire_sheet_model_get_string (model, row, col);
2295
2296   if (0 != safe_strcmp (old_text, text))
2297     {
2298       g_signal_handler_block    (sheet->model, sheet->update_handler_id);
2299       psppire_sheet_model_set_string (model, text, row, col);
2300       g_signal_handler_unblock  (sheet->model, sheet->update_handler_id);
2301     }
2302
2303   if ( psppire_sheet_model_free_strings (model))
2304     g_free (old_text);
2305 }
2306
2307
2308 void
2309 psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column)
2310 {
2311   PsppireSheetRange range;
2312
2313   g_return_if_fail (sheet != NULL);
2314   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2315   if (column >= psppire_axis_unit_count (sheet->haxis) ||
2316       row >= psppire_axis_unit_count (sheet->vaxis)) return;
2317
2318   if (column < 0 || row < 0) return;
2319
2320   range.row0 = row;
2321   range.rowi = row;
2322   range.col0 = min_visible_column (sheet);
2323   range.coli = max_visible_column (sheet);
2324
2325   psppire_sheet_real_cell_clear (sheet, row, column);
2326
2327   redraw_range (sheet, &range);
2328 }
2329
2330 static void
2331 psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column)
2332 {
2333   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
2334
2335   gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column);
2336
2337   if (old_text && strlen (old_text) > 0 )
2338     {
2339       psppire_sheet_model_datum_clear (model, row, column);
2340     }
2341
2342   dispose_string (sheet, old_text);
2343 }
2344
2345 gchar *
2346 psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col)
2347 {
2348   PsppireSheetModel *model;
2349   g_return_val_if_fail (sheet != NULL, NULL);
2350   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
2351
2352   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis))
2353     return NULL;
2354   if (col < 0 || row < 0) return NULL;
2355
2356   model = psppire_sheet_get_model (sheet);
2357
2358   if ( !model )
2359     return NULL;
2360
2361   return psppire_sheet_model_get_string (model, row, col);
2362 }
2363
2364
2365 /* Convert X, Y (in pixels) to *ROW, *COLUMN
2366    If the function returns FALSE, then the results will be unreliable.
2367 */
2368 static gboolean
2369 psppire_sheet_get_pixel_info (PsppireSheet *sheet,
2370                               gint x,
2371                               gint y,
2372                               gint *row,
2373                               gint *column)
2374 {
2375   gint trow, tcol;
2376   *row = -G_MAXINT;
2377   *column = -G_MAXINT;
2378
2379   g_return_val_if_fail (sheet != NULL, 0);
2380   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2381
2382   /* bounds checking, return false if the user clicked
2383      on a blank area */
2384   if (y < 0)
2385     return FALSE;
2386
2387   if (x < 0)
2388     return FALSE;
2389
2390   if ( sheet->column_titles_visible)
2391     y -= sheet->column_title_area.height;
2392
2393   y += sheet->vadjustment->value;
2394
2395   if ( y < 0 && sheet->column_titles_visible)
2396     {
2397       trow = -1;
2398     }
2399   else
2400     {
2401       trow = row_from_ypixel (sheet, y);
2402       if (trow > psppire_axis_unit_count (sheet->vaxis))
2403         return FALSE;
2404     }
2405
2406   *row = trow;
2407
2408   if ( sheet->row_titles_visible)
2409     x -= sheet->row_title_area.width;
2410
2411   x += sheet->hadjustment->value;
2412
2413   if ( x < 0 && sheet->row_titles_visible)
2414     {
2415       tcol = -1;
2416     }
2417   else
2418     {
2419       tcol = column_from_xpixel (sheet, x);
2420       if (tcol > psppire_axis_unit_count (sheet->haxis))
2421         return FALSE;
2422     }
2423
2424   *column = tcol;
2425
2426   return TRUE;
2427 }
2428
2429 gboolean
2430 psppire_sheet_get_cell_area (PsppireSheet *sheet,
2431                              gint row,
2432                              gint column,
2433                              GdkRectangle *area)
2434 {
2435   g_return_val_if_fail (sheet != NULL, 0);
2436   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2437
2438   if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis))
2439     return FALSE;
2440
2441   area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column);
2442   area->y = (row == -1)    ? 0 : psppire_axis_start_pixel (sheet->vaxis, row);
2443
2444   area->width= (column == -1) ? sheet->row_title_area.width
2445     : psppire_axis_unit_size (sheet->haxis, column);
2446
2447   area->height= (row == -1) ? sheet->column_title_area.height
2448     : psppire_axis_unit_size (sheet->vaxis, row);
2449
2450   return TRUE;
2451 }
2452
2453 void
2454 psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col)
2455 {
2456   g_return_if_fail (sheet != NULL);
2457   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2458
2459   if (row < -1 || col < -1)
2460     return;
2461
2462   if (row >= psppire_axis_unit_count (sheet->vaxis)
2463       ||
2464       col >= psppire_axis_unit_count (sheet->haxis))
2465     return;
2466
2467   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2468     return;
2469
2470   if ( row == -1 || col == -1)
2471     {
2472       psppire_sheet_hide_entry_widget (sheet);
2473       return;
2474     }
2475
2476   change_active_cell (sheet, row, col);
2477 }
2478
2479 void
2480 psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column)
2481 {
2482   g_return_if_fail (sheet != NULL);
2483   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2484
2485   if ( row ) *row = sheet->active_cell.row;
2486   if (column) *column = sheet->active_cell.col;
2487 }
2488
2489 static void
2490 entry_load_text (PsppireSheet *sheet)
2491 {
2492   gint row, col;
2493   const char *text;
2494   GtkJustification justification;
2495   PsppireSheetCellAttr attributes;
2496
2497   if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return;
2498   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2499
2500   row = sheet->active_cell.row;
2501   col = sheet->active_cell.col;
2502
2503   if (row < 0 || col < 0) return;
2504
2505   text = gtk_entry_get_text (psppire_sheet_get_entry (sheet));
2506
2507   if (text && strlen (text) > 0)
2508     {
2509       psppire_sheet_get_attributes (sheet, row, col, &attributes);
2510       justification = attributes.justification;
2511       psppire_sheet_set_cell (sheet, row, col, justification, text);
2512     }
2513 }
2514
2515
2516 static void
2517 psppire_sheet_hide_entry_widget (PsppireSheet *sheet)
2518 {
2519   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2520     return;
2521
2522   if (sheet->active_cell.row < 0 ||
2523       sheet->active_cell.col < 0) return;
2524
2525   gtk_widget_hide (sheet->entry_widget);
2526   gtk_widget_unmap (sheet->entry_widget);
2527
2528   GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2529 }
2530
2531 static void
2532 change_active_cell (PsppireSheet *sheet, gint row, gint col)
2533 {
2534   gint old_row, old_col;
2535
2536   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2537
2538   if (row < 0 || col < 0)
2539     return;
2540
2541   if ( row > psppire_axis_unit_count (sheet->vaxis)
2542        || col > psppire_axis_unit_count (sheet->haxis))
2543     return;
2544
2545   old_row = sheet->active_cell.row;
2546   old_col = sheet->active_cell.col;
2547
2548   entry_load_text (sheet);
2549
2550   /* Erase the old cell border */
2551   psppire_sheet_draw_active_cell (sheet);
2552
2553   sheet->active_cell.row = row;
2554   sheet->active_cell.col = col;
2555
2556   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2557
2558   GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2559
2560   psppire_sheet_draw_active_cell (sheet);
2561   psppire_sheet_show_entry_widget (sheet);
2562
2563   GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2564
2565   g_signal_emit (sheet, sheet_signals [ACTIVATE], 0,
2566                  row, col, old_row, old_col);
2567
2568 }
2569
2570 static void
2571 psppire_sheet_show_entry_widget (PsppireSheet *sheet)
2572 {
2573   GtkEntry *sheet_entry;
2574   PsppireSheetCellAttr attributes;
2575
2576   gint row, col;
2577
2578   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2579
2580   row = sheet->active_cell.row;
2581   col = sheet->active_cell.col;
2582
2583   /* Don't show the active cell, if there is no active cell: */
2584   if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */
2585     return;
2586
2587   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2588   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2589   if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return;
2590
2591   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2592
2593   sheet_entry = psppire_sheet_get_entry (sheet);
2594
2595   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2596
2597   if (GTK_IS_ENTRY (sheet_entry))
2598     {
2599       gchar *text = psppire_sheet_cell_get_text (sheet, row, col);
2600       const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry));
2601
2602       if ( ! text )
2603         text = g_strdup ("");
2604
2605       if (strcmp (old_text, text) != 0)
2606         gtk_entry_set_text (sheet_entry, text);
2607       
2608       dispose_string (sheet, text);
2609
2610       {
2611         switch (attributes.justification)
2612           {
2613           case GTK_JUSTIFY_RIGHT:
2614             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0);
2615             break;
2616           case GTK_JUSTIFY_CENTER:
2617             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5);
2618             break;
2619           case GTK_JUSTIFY_LEFT:
2620           default:
2621             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0);
2622             break;
2623           }
2624       }
2625     }
2626
2627   psppire_sheet_size_allocate_entry (sheet);
2628
2629   gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry),
2630                             psppire_sheet_model_is_editable (sheet->model,
2631                                                              row, col));
2632   gtk_widget_map (sheet->entry_widget);
2633 }
2634
2635 static gboolean
2636 psppire_sheet_draw_active_cell (PsppireSheet *sheet)
2637 {
2638   gint row, col;
2639   PsppireSheetRange range;
2640
2641   row = sheet->active_cell.row;
2642   col = sheet->active_cell.col;
2643
2644   if (row < 0 || col < 0) return FALSE;
2645
2646   if (!psppire_sheet_cell_isvisible (sheet, row, col))
2647     return FALSE;
2648
2649   range.col0 = range.coli = col;
2650   range.row0 = range.rowi = row;
2651
2652   psppire_sheet_draw_border (sheet, range);
2653
2654   return FALSE;
2655 }
2656
2657
2658
2659 static void
2660 psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
2661 {
2662   GdkRectangle area;
2663
2664   rectangle_from_range (sheet, &new_range, &area);
2665
2666   area.width ++;
2667   area.height ++;
2668
2669   gdk_gc_set_clip_rectangle (sheet->xor_gc, &area);
2670
2671   area.x += sheet->cell_padding->left / 2;
2672   area.y += sheet->cell_padding->top / 2;
2673   area.width -= (sheet->cell_padding->left + sheet->cell_padding->right ) / 2;
2674   area.height -= (sheet->cell_padding->top + sheet->cell_padding->bottom ) / 2;
2675
2676   gdk_draw_rectangle (sheet->sheet_window,  sheet->xor_gc,
2677                       FALSE,
2678                       area.x,
2679                       area.y,
2680                       area.width,
2681                       area.height);
2682
2683   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
2684 }
2685
2686 \f
2687
2688 /* Selection related functions */
2689
2690 void
2691 psppire_sheet_select_row (PsppireSheet *sheet,  gint row)
2692 {
2693   GdkRectangle area;
2694   sheet->select_status = PSPPIRE_SHEET_ROW_SELECTED;
2695
2696   sheet->range.col0 = sheet->range.coli = -1;
2697   sheet->range.row0 = sheet->range.rowi = row;
2698
2699   rectangle_from_range (sheet, &sheet->range, &area);
2700   area.x++;
2701   area.y++;
2702
2703   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2704 }
2705
2706 void
2707 psppire_sheet_select_column (PsppireSheet *sheet,  gint column)
2708 {
2709   GdkRectangle area;
2710   sheet->select_status = PSPPIRE_SHEET_COLUMN_SELECTED;
2711
2712   sheet->range.col0 = sheet->range.coli = column;
2713   sheet->range.row0 = sheet->range.rowi = -1;
2714
2715   rectangle_from_range (sheet, &sheet->range, &area);
2716   area.x++;
2717   area.y++;
2718
2719   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2720 }
2721
2722
2723 void
2724 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
2725 {
2726   GdkRectangle area;
2727   sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
2728
2729   sheet->range = *range;
2730
2731   rectangle_from_range (sheet, range, &area);
2732   area.x++;
2733   area.y++;
2734   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2735 }
2736
2737
2738 void
2739 psppire_sheet_unselect_range (PsppireSheet *sheet)
2740 {
2741   GdkRectangle area;
2742   sheet->select_status = PSPPIRE_SHEET_NORMAL;
2743
2744   rectangle_from_range (sheet, &sheet->range, &area);
2745   area.x++;
2746   area.y++;
2747   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);         
2748 }
2749
2750 static void
2751 psppire_sheet_real_select_range (PsppireSheet *sheet,
2752                                  const PsppireSheetRange *range)
2753 {
2754   g_return_if_fail (sheet != NULL);
2755
2756   if (range == NULL) range = &sheet->range;
2757
2758   memcpy (&sheet->range, range, sizeof (*range));
2759
2760   if (range->row0 < 0 || range->rowi < 0) return;
2761   if (range->col0 < 0 || range->coli < 0) return;
2762
2763   psppire_sheet_update_primary_selection (sheet);
2764
2765   g_signal_emit (sheet, sheet_signals[SELECT_RANGE], 0, &sheet->range);
2766 }
2767
2768
2769 void
2770 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
2771 {
2772   g_return_if_fail (sheet != NULL);
2773   *range = sheet->range;
2774 }
2775 \f
2776
2777 static gint
2778 psppire_sheet_expose (GtkWidget *widget, GdkEventExpose *event)
2779 {
2780   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2781
2782   g_return_val_if_fail (event != NULL, FALSE);
2783
2784   if (!GTK_WIDGET_DRAWABLE (widget))
2785     return FALSE;
2786
2787   /* exposure events on the sheet */
2788   if (event->window == sheet->row_title_window &&
2789       sheet->row_titles_visible)
2790     {
2791       draw_row_title_buttons_range (sheet,
2792                                     min_visible_row (sheet),
2793                                     max_visible_row (sheet));
2794     }
2795
2796   if (event->window == sheet->column_title_window &&
2797       sheet->column_titles_visible)
2798     {
2799       draw_column_title_buttons_range (sheet,
2800                                        min_visible_column (sheet),
2801                                        max_visible_column (sheet));
2802     }
2803
2804   if (event->window == sheet->sheet_window)
2805     {
2806       draw_sheet_region (sheet, event->region);
2807
2808       if (sheet->select_status != PSPPIRE_SHEET_NORMAL)
2809         {
2810 #if 0
2811           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
2812             psppire_sheet_range_draw (sheet, &sheet->range);
2813
2814           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2815             psppire_sheet_range_draw (sheet, &sheet->drag_range);
2816 #endif
2817
2818             {
2819               GdkRectangle area;
2820
2821               rectangle_from_range (sheet, &sheet->range, &area);
2822               
2823               gdk_draw_rectangle (sheet->sheet_window,
2824                                   sheet->xor_gc,
2825                                   TRUE,
2826                                   area.x + 1, area.y + 1,
2827                                   area.width, area.height);
2828             }
2829
2830 #if 0
2831           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2832             draw_xor_rectangle (sheet, sheet->drag_range);
2833 #endif
2834         }
2835
2836
2837       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
2838         {
2839           GdkRectangle rect;
2840           PsppireSheetRange range;
2841           range.row0 = range.rowi =  sheet->active_cell.row;
2842           range.col0 = range.coli =  sheet->active_cell.col;
2843
2844           rectangle_from_range (sheet, &range, &rect);
2845
2846           if (GDK_OVERLAP_RECTANGLE_OUT !=
2847               gdk_region_rect_in (event->region, &rect))
2848             {
2849               psppire_sheet_draw_active_cell (sheet);
2850             }
2851         }
2852
2853     }
2854
2855   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
2856
2857   return FALSE;
2858 }
2859
2860
2861 static gboolean
2862 psppire_sheet_button_press (GtkWidget *widget, GdkEventButton *event)
2863 {
2864   PsppireSheet *sheet;
2865   GdkModifierType mods;
2866   gint x, y;
2867   gint  row, column;
2868
2869
2870   g_return_val_if_fail (widget != NULL, FALSE);
2871   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
2872   g_return_val_if_fail (event != NULL, FALSE);
2873
2874   sheet = PSPPIRE_SHEET (widget);
2875
2876   /* Cancel any pending tooltips */
2877   if (sheet->motion_timer)
2878     {
2879       g_source_remove (sheet->motion_timer);
2880       sheet->motion_timer = 0;
2881     }
2882
2883   gtk_widget_get_pointer (widget, &x, &y);
2884   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2885
2886
2887   if (event->window == sheet->column_title_window)
2888     {
2889       sheet->x_drag = event->x;
2890       g_signal_emit (sheet,
2891                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
2892                      column, event);
2893
2894       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2895         {
2896           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2897             g_signal_emit (sheet,
2898                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
2899         }
2900     }
2901   
2902   if (event->window == sheet->row_title_window)
2903     {
2904       g_signal_emit (sheet,
2905                      sheet_signals[BUTTON_EVENT_ROW], 0,
2906                      row, event);
2907
2908       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2909         {
2910           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2911             g_signal_emit (sheet,
2912                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
2913         }
2914     }
2915
2916   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
2917
2918   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
2919
2920
2921   /* press on resize windows */
2922   if (event->window == sheet->column_title_window)
2923     {
2924       sheet->x_drag = event->x;
2925
2926       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
2927         {
2928           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
2929           gdk_pointer_grab (sheet->column_title_window, FALSE,
2930                             GDK_POINTER_MOTION_HINT_MASK |
2931                             GDK_BUTTON1_MOTION_MASK |
2932                             GDK_BUTTON_RELEASE_MASK,
2933                             NULL, NULL, event->time);
2934
2935           draw_xor_vline (sheet);
2936           return TRUE;
2937         }
2938     }
2939
2940   if (event->window == sheet->row_title_window)
2941     {
2942       sheet->y_drag = event->y;
2943
2944       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
2945         {
2946           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
2947           gdk_pointer_grab (sheet->row_title_window, FALSE,
2948                             GDK_POINTER_MOTION_HINT_MASK |
2949                             GDK_BUTTON1_MOTION_MASK |
2950                             GDK_BUTTON_RELEASE_MASK,
2951                             NULL, NULL, event->time);
2952
2953           draw_xor_hline (sheet);
2954           return TRUE;
2955         }
2956     }
2957
2958   /* the sheet itself does not handle other than single click events */
2959   if (event->type != GDK_BUTTON_PRESS) return FALSE;
2960
2961   /* selections on the sheet */
2962   if (event->window == sheet->sheet_window)
2963     {
2964       gtk_widget_get_pointer (widget, &x, &y);
2965       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2966       gdk_pointer_grab (sheet->sheet_window, FALSE,
2967                         GDK_POINTER_MOTION_HINT_MASK |
2968                         GDK_BUTTON1_MOTION_MASK |
2969                         GDK_BUTTON_RELEASE_MASK,
2970                         NULL, NULL, event->time);
2971       gtk_grab_add (GTK_WIDGET (sheet));
2972
2973       if ( sheet->select_status == PSPPIRE_SHEET_NORMAL)
2974         {
2975           sheet->range.row0 = row;
2976           sheet->range.col0 = column;
2977         }
2978       else
2979         {
2980           psppire_sheet_unselect_range (sheet);
2981         }
2982       psppire_sheet_click_cell (sheet, row, column);
2983     }
2984
2985   if (event->window == sheet->column_title_window)
2986     {
2987       gtk_widget_get_pointer (widget, &x, &y);
2988       if ( sheet->row_titles_visible)
2989         x -= sheet->row_title_area.width;
2990
2991       x += sheet->hadjustment->value;
2992
2993       column = column_from_xpixel (sheet, x);
2994
2995       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2996         {
2997           gtk_grab_add (GTK_WIDGET (sheet));
2998           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2999         }
3000     }
3001
3002   if (event->window == sheet->row_title_window)
3003     {
3004       gtk_widget_get_pointer (widget, &x, &y);
3005       if ( sheet->column_titles_visible)
3006         y -= sheet->column_title_area.height;
3007
3008       y += sheet->vadjustment->value;
3009
3010       row = row_from_ypixel (sheet, y);
3011       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3012         {
3013           gtk_grab_add (GTK_WIDGET (sheet));
3014           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3015         }
3016     }
3017
3018   return TRUE;
3019 }
3020
3021 static gboolean
3022 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
3023 {
3024   PsppireSheetCell cell;
3025   gboolean forbid_move;
3026
3027   cell.row = row;
3028   cell.col = column;
3029
3030   if (row >= psppire_axis_unit_count (sheet->vaxis)
3031       || column >= psppire_axis_unit_count (sheet->haxis))
3032     {
3033       return FALSE;
3034     }
3035
3036   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3037                  &sheet->active_cell,
3038                  &cell,
3039                  &forbid_move);
3040
3041   if (forbid_move)
3042     {
3043       if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3044         return FALSE;
3045
3046       row = sheet->active_cell.row;
3047       column = sheet->active_cell.col;
3048
3049       change_active_cell (sheet, row, column);
3050       return FALSE;
3051     }
3052
3053   if (row == -1 && column >= 0)
3054     {
3055       psppire_sheet_select_column (sheet, column);
3056       return TRUE;
3057     }
3058
3059   if (column == -1 && row >= 0)
3060     {
3061       psppire_sheet_select_row (sheet, row);
3062       return TRUE;
3063     }
3064
3065   if (row == -1 && column == -1)
3066     {
3067       sheet->range.row0 = 0;
3068       sheet->range.col0 = 0;
3069       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3070       sheet->range.coli =
3071         psppire_axis_unit_count (sheet->haxis) - 1;
3072       psppire_sheet_select_range (sheet, NULL);
3073       return TRUE;
3074     }
3075
3076   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3077     change_active_cell (sheet, row, column);
3078
3079   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3080
3081   return TRUE;
3082 }
3083
3084 static gint
3085 psppire_sheet_button_release (GtkWidget *widget,
3086                               GdkEventButton *event)
3087 {
3088   GdkDisplay *display = gtk_widget_get_display (widget);
3089
3090   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3091
3092   /* release on resize windows */
3093   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3094     {
3095       gint width;
3096       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3097       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3098
3099       gdk_display_pointer_ungrab (display, event->time);
3100       draw_xor_vline (sheet);
3101
3102       width = event->x -
3103         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3104         + sheet->hadjustment->value;
3105
3106       set_column_width (sheet, sheet->drag_cell.col, width);
3107
3108       return TRUE;
3109     }
3110
3111   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3112     {
3113       gint height;
3114       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3115       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3116
3117       gdk_display_pointer_ungrab (display, event->time);
3118       draw_xor_hline (sheet);
3119
3120       height = event->y -
3121         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3122         sheet->vadjustment->value;
3123
3124       set_row_height (sheet, sheet->drag_cell.row, height);
3125
3126       return TRUE;
3127     }
3128
3129   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3130     {
3131       PsppireSheetRange old_range;
3132       draw_xor_rectangle (sheet, sheet->drag_range);
3133       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3134       gdk_display_pointer_ungrab (display, event->time);
3135
3136       psppire_sheet_unselect_range (sheet);
3137
3138       old_range = sheet->range;
3139       sheet->range = sheet->drag_range;
3140       sheet->drag_range = old_range;
3141       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3142                      &sheet->drag_range, &sheet->range);
3143       psppire_sheet_select_range (sheet, &sheet->range);
3144     }
3145
3146   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3147     {
3148       PsppireSheetRange old_range;
3149       draw_xor_rectangle (sheet, sheet->drag_range);
3150       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3151       gdk_display_pointer_ungrab (display, event->time);
3152
3153       psppire_sheet_unselect_range (sheet);
3154
3155       old_range = sheet->range;
3156       sheet->range = sheet->drag_range;
3157       sheet->drag_range = old_range;
3158
3159       if (sheet->select_status == PSPPIRE_SHEET_NORMAL) 
3160         sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3161
3162       g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0,
3163                      &sheet->drag_range, &sheet->range);
3164       psppire_sheet_select_range (sheet, &sheet->range);
3165     }
3166
3167   if (PSPPIRE_SHEET_IN_SELECTION (sheet))
3168     {
3169       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3170       sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3171
3172       change_active_cell (sheet, sheet->active_cell.row,
3173                           sheet->active_cell.col);
3174     }
3175
3176   gdk_display_pointer_ungrab (display, event->time);
3177   gtk_grab_remove (GTK_WIDGET (sheet));
3178
3179   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3180
3181   return TRUE;
3182 }
3183
3184 \f
3185
3186
3187
3188 /* Shamelessly lifted from gtktooltips */
3189 static gboolean
3190 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3191 {
3192   GtkRequisition req;
3193
3194   gtk_widget_size_request (tip_window, &req);
3195   gtk_paint_flat_box (tip_window->style, tip_window->window,
3196                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3197                       NULL, GTK_WIDGET(tip_window), "tooltip",
3198                       0, 0, req.width, req.height);
3199
3200   return FALSE;
3201 }
3202
3203 static void
3204 destroy_hover_window (PsppireSheetHoverTitle *h)
3205 {
3206   gtk_widget_destroy (h->window);
3207   g_free (h);
3208 }
3209
3210 static PsppireSheetHoverTitle *
3211 create_hover_window (void)
3212 {
3213   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3214
3215   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3216
3217 #if GTK_CHECK_VERSION (2, 9, 0)
3218   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3219                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3220 #endif
3221
3222   gtk_widget_set_app_paintable (hw->window, TRUE);
3223   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3224   gtk_widget_set_name (hw->window, "gtk-tooltips");
3225   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3226
3227   g_signal_connect (hw->window,
3228                     "expose_event",
3229                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3230                     NULL);
3231
3232   hw->label = gtk_label_new (NULL);
3233
3234
3235   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3236   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3237
3238   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3239
3240   gtk_widget_show (hw->label);
3241
3242   g_signal_connect (hw->window,
3243                     "destroy",
3244                     G_CALLBACK (gtk_widget_destroyed),
3245                     &hw->window);
3246
3247   return hw;
3248 }
3249
3250 #define HOVER_WINDOW_Y_OFFSET 2
3251
3252 static void
3253 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3254                const gchar *subtitle)
3255 {
3256   gint x, y;
3257   gint px, py;
3258   gint width;
3259
3260   if ( ! subtitle )
3261     return;
3262
3263   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3264                       subtitle);
3265
3266
3267   sheet->hover_window->row = row;
3268   sheet->hover_window->column = column;
3269
3270   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3271
3272   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3273
3274   gtk_widget_show (sheet->hover_window->window);
3275
3276   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3277
3278   if (row == -1 )
3279     {
3280       x += px;
3281       x -= width / 2;
3282       y += sheet->column_title_area.y;
3283       y += sheet->column_title_area.height;
3284       y += HOVER_WINDOW_Y_OFFSET;
3285     }
3286
3287   if ( column == -1 )
3288     {
3289       y += py;
3290       x += sheet->row_title_area.x;
3291       x += sheet->row_title_area.width * 2 / 3.0;
3292     }
3293
3294   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3295                    x, y);
3296 }
3297
3298 static gboolean
3299 motion_timeout_callback (gpointer data)
3300 {
3301   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3302   gint x, y;
3303   gint row, column;
3304
3305   gdk_threads_enter ();
3306   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3307
3308   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3309     {
3310       if (sheet->row_title_under && row >= 0)
3311         {
3312           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3313
3314           show_subtitle (sheet, row, -1, text);
3315           g_free (text);
3316         }
3317
3318       if (sheet->column_title_under && column >= 0)
3319         {
3320           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3321                                                                  column);
3322
3323           show_subtitle (sheet, -1, column, text);
3324
3325           g_free (text);
3326         }
3327     }
3328
3329   gdk_threads_leave ();
3330   return FALSE;
3331 }
3332
3333 static gboolean
3334 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3335 {
3336   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3337   GdkModifierType mods;
3338   GdkCursorType new_cursor;
3339   gint x, y;
3340   gint row, column;
3341   GdkDisplay *display;
3342
3343   g_return_val_if_fail (event != NULL, FALSE);
3344
3345   display = gtk_widget_get_display (widget);
3346
3347   /* selections on the sheet */
3348   x = event->x;
3349   y = event->y;
3350
3351   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3352     {
3353       if ( sheet->motion_timer > 0 )
3354         g_source_remove (sheet->motion_timer);
3355       sheet->motion_timer =
3356         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3357     }
3358   else
3359     {
3360       gint row, column;
3361       gint wx, wy;
3362       gtk_widget_get_pointer (widget, &wx, &wy);
3363
3364       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3365         {
3366           if ( row != sheet->hover_window->row ||
3367                column != sheet->hover_window->column)
3368             {
3369               gtk_widget_hide (sheet->hover_window->window);
3370             }
3371         }
3372     }
3373
3374   if (event->window == sheet->column_title_window)
3375     {
3376       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3377           on_column_boundary (sheet, x, &column))
3378         {
3379           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3380           if (new_cursor != sheet->cursor_drag->type)
3381             {
3382               gdk_cursor_unref (sheet->cursor_drag);
3383               sheet->cursor_drag =
3384                 gdk_cursor_new_for_display (display, new_cursor);
3385
3386               gdk_window_set_cursor (sheet->column_title_window,
3387                                      sheet->cursor_drag);
3388             }
3389         }
3390       else
3391         {
3392           new_cursor = GDK_TOP_LEFT_ARROW;
3393           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3394               new_cursor != sheet->cursor_drag->type)
3395             {
3396               gdk_cursor_unref (sheet->cursor_drag);
3397               sheet->cursor_drag =
3398                 gdk_cursor_new_for_display (display, new_cursor);
3399               gdk_window_set_cursor (sheet->column_title_window,
3400                                      sheet->cursor_drag);
3401             }
3402         }
3403     }
3404   else if (event->window == sheet->row_title_window)
3405     {
3406       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3407           on_row_boundary (sheet, y, &row))
3408         {
3409           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3410           if (new_cursor != sheet->cursor_drag->type)
3411             {
3412               gdk_cursor_unref (sheet->cursor_drag);
3413               sheet->cursor_drag =
3414                 gdk_cursor_new_for_display (display, new_cursor);
3415               gdk_window_set_cursor (sheet->row_title_window,
3416                                      sheet->cursor_drag);
3417             }
3418         }
3419       else
3420         {
3421           new_cursor = GDK_TOP_LEFT_ARROW;
3422           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3423               new_cursor != sheet->cursor_drag->type)
3424             {
3425               gdk_cursor_unref (sheet->cursor_drag);
3426               sheet->cursor_drag =
3427                 gdk_cursor_new_for_display (display, new_cursor);
3428               gdk_window_set_cursor (sheet->row_title_window,
3429                                      sheet->cursor_drag);
3430             }
3431         }
3432     }
3433
3434   new_cursor = GDK_PLUS;
3435   if ( event->window == sheet->sheet_window &&
3436        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3437        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3438        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3439        !PSPPIRE_SHEET_IN_RESIZE (sheet) &&
3440        new_cursor != sheet->cursor_drag->type)
3441     {
3442       gdk_cursor_unref (sheet->cursor_drag);
3443       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3444       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3445     }
3446
3447   new_cursor = GDK_TOP_LEFT_ARROW;
3448   if ( event->window == sheet->sheet_window &&
3449        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3450           PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3451        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3452         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3453        new_cursor != sheet->cursor_drag->type)
3454     {
3455       gdk_cursor_unref (sheet->cursor_drag);
3456       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3457       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3458     }
3459
3460   new_cursor = GDK_SIZING;
3461   if ( event->window == sheet->sheet_window &&
3462        sheet->selection_mode != GTK_SELECTION_NONE &&
3463        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3464        (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3465         PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3466        new_cursor != sheet->cursor_drag->type)
3467     {
3468       gdk_cursor_unref (sheet->cursor_drag);
3469       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_SIZING);
3470       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3471     }
3472
3473
3474   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3475   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3476
3477   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3478     {
3479       if (event->x != sheet->x_drag)
3480         {
3481           draw_xor_vline (sheet);
3482           sheet->x_drag = event->x;
3483           draw_xor_vline (sheet);
3484         }
3485
3486       return TRUE;
3487     }
3488
3489   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3490     {
3491       if (event->y != sheet->y_drag)
3492         {
3493           draw_xor_hline (sheet);
3494           sheet->y_drag = event->y;
3495           draw_xor_hline (sheet);
3496         }
3497
3498       return TRUE;
3499     }
3500
3501   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3502     {
3503       PsppireSheetRange aux;
3504       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
3505       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
3506       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3507       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3508       sheet->x_drag = x;
3509       sheet->y_drag = y;
3510       aux = sheet->range;
3511       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3512           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3513         {
3514           aux = sheet->drag_range;
3515           sheet->drag_range.row0 = sheet->range.row0 + row;
3516           sheet->drag_range.col0 = sheet->range.col0 + column;
3517           sheet->drag_range.rowi = sheet->range.rowi + row;
3518           sheet->drag_range.coli = sheet->range.coli + column;
3519           if (aux.row0 != sheet->drag_range.row0 ||
3520               aux.col0 != sheet->drag_range.col0)
3521             {
3522               draw_xor_rectangle (sheet, aux);
3523               draw_xor_rectangle (sheet, sheet->drag_range);
3524             }
3525         }
3526       return TRUE;
3527     }
3528
3529   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3530     {
3531       PsppireSheetRange aux;
3532       gint v_h, current_col, current_row, col_threshold, row_threshold;
3533       v_h = 1;
3534       if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) >
3535           abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2;
3536
3537       current_col = column_from_xpixel (sheet, x);
3538       current_row = row_from_ypixel (sheet, y);
3539       column = current_col - sheet->drag_cell.col;
3540       row = current_row - sheet->drag_cell.row;
3541
3542       /*use half of column width resp. row height as threshold to
3543         expand selection*/
3544       col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) +
3545         psppire_axis_unit_size (sheet->haxis, current_col) / 2;
3546       if (column > 0)
3547         {
3548           if (x < col_threshold)
3549             column -= 1;
3550         }
3551       else if (column < 0)
3552         {
3553           if (x > col_threshold)
3554             column +=1;
3555         }
3556       row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) +
3557         psppire_axis_unit_size (sheet->vaxis, current_row)/2;
3558       if (row > 0)
3559         {
3560           if (y < row_threshold)
3561             row -= 1;
3562         }
3563       else if (row < 0)
3564         {
3565           if (y > row_threshold)
3566             row +=1;
3567         }
3568
3569       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3570       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3571       sheet->x_drag = x;
3572       sheet->y_drag = y;
3573       aux = sheet->range;
3574
3575       if (v_h == 1)
3576         column = 0;
3577       else
3578         row = 0;
3579
3580       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3581           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3582         {
3583           aux = sheet->drag_range;
3584           sheet->drag_range = sheet->range;
3585
3586           if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row;
3587           if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row;
3588           if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column;
3589           if (column > 0) sheet->drag_range.coli = sheet->range.coli + column;
3590
3591           if (aux.row0 != sheet->drag_range.row0 ||
3592               aux.rowi != sheet->drag_range.rowi ||
3593               aux.col0 != sheet->drag_range.col0 ||
3594               aux.coli != sheet->drag_range.coli)
3595             {
3596               draw_xor_rectangle (sheet, aux);
3597               draw_xor_rectangle (sheet, sheet->drag_range);
3598             }
3599         }
3600       return TRUE;
3601     }
3602
3603   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3604
3605   if (sheet->select_status == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
3606       column == sheet->active_cell.col) return TRUE;
3607
3608   if ( mods & GDK_BUTTON1_MASK)
3609     {
3610       if (PSPPIRE_SHEET_IN_SELECTION (sheet) )
3611         {
3612           /* Redraw the old range */
3613           psppire_sheet_unselect_range (sheet);
3614
3615           sheet->range.rowi = row;
3616           sheet->range.coli = column;
3617
3618           /* Redraw the new range */
3619           psppire_sheet_select_range (sheet, &sheet->range);
3620         }
3621       else
3622         {
3623           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3624         }
3625     }
3626
3627   return TRUE;
3628 }
3629
3630 static gboolean
3631 psppire_sheet_crossing_notify (GtkWidget *widget,
3632                                GdkEventCrossing *event)
3633 {
3634   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3635
3636   if (event->window == sheet->column_title_window)
3637     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
3638   else if (event->window == sheet->row_title_window)
3639     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
3640
3641   if (event->type == GDK_LEAVE_NOTIFY)
3642     gtk_widget_hide (sheet->hover_window->window);
3643
3644   return TRUE;
3645 }
3646
3647
3648 static gboolean
3649 psppire_sheet_focus_in (GtkWidget     *w,
3650                         GdkEventFocus *event)
3651 {
3652   PsppireSheet *sheet = PSPPIRE_SHEET (w);
3653
3654   gtk_widget_grab_focus (sheet->entry_widget);
3655
3656   return TRUE;
3657 }
3658
3659
3660
3661 static gint
3662 psppire_sheet_entry_key_press (GtkWidget *widget,
3663                                GdkEventKey *key)
3664 {
3665   gboolean focus;
3666   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
3667   return focus;
3668 }
3669
3670
3671 /* Number of rows in a step-increment */
3672 #define ROWS_PER_STEP 1
3673
3674
3675 static void
3676 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
3677 {
3678   gint old_row = sheet->active_cell.row ;
3679   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
3680
3681   gint new_row;
3682
3683   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
3684                                       min_visible_row (sheet));
3685
3686   switch ( dir)
3687     {
3688     case GTK_SCROLL_PAGE_DOWN:
3689       gtk_adjustment_set_value (sheet->vadjustment,
3690                                 sheet->vadjustment->value +
3691                                 sheet->vadjustment->page_increment);
3692       break;
3693     case GTK_SCROLL_PAGE_UP:
3694       gtk_adjustment_set_value (sheet->vadjustment,
3695                                 sheet->vadjustment->value -
3696                                 sheet->vadjustment->page_increment);
3697
3698       break;
3699     default:
3700       g_assert_not_reached ();
3701       break;
3702     }
3703
3704
3705   vpixel += psppire_axis_start_pixel (sheet->vaxis,
3706                                       min_visible_row (sheet));
3707
3708   new_row =  row_from_ypixel (sheet, vpixel);
3709
3710   change_active_cell (sheet, new_row,
3711                       sheet->active_cell.col);
3712 }
3713
3714
3715 static void
3716 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
3717 {
3718   gint current_row = sheet->active_cell.row;
3719   gint current_col = sheet->active_cell.col;
3720   PsppireSheetCell new_cell ;
3721   gboolean forbidden = FALSE;
3722
3723   new_cell.row = current_row;
3724   new_cell.col = current_col;
3725
3726   switch ( dir)
3727     {
3728     case GTK_SCROLL_STEP_DOWN:
3729       new_cell.row++;
3730       break;
3731     case GTK_SCROLL_STEP_UP:
3732       new_cell.row--;
3733       break;
3734     case GTK_SCROLL_STEP_RIGHT:
3735       new_cell.col++;
3736       break;
3737     case GTK_SCROLL_STEP_LEFT:
3738       new_cell.col--;
3739       break;
3740     case GTK_SCROLL_STEP_FORWARD:
3741       new_cell.col++;
3742       if (new_cell.col >=
3743           psppire_sheet_model_get_column_count (sheet->model))
3744         {
3745           new_cell.col = 0;
3746           new_cell.row++;
3747         }
3748       break;
3749     case GTK_SCROLL_STEP_BACKWARD:
3750       new_cell.col--;
3751       if (new_cell.col < 0)
3752         {
3753           new_cell.col =
3754             psppire_sheet_model_get_column_count (sheet->model) - 1;
3755           new_cell.row--;
3756         }
3757       break;
3758     default:
3759       g_assert_not_reached ();
3760       break;
3761     }
3762
3763   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3764                  &sheet->active_cell,
3765                  &new_cell,
3766                  &forbidden);
3767
3768   if (forbidden)
3769     return;
3770
3771
3772   maximize_int (&new_cell.row, 0);
3773   maximize_int (&new_cell.col, 0);
3774
3775   minimize_int (&new_cell.row,
3776                 psppire_axis_unit_count (sheet->vaxis) - 1);
3777
3778   minimize_int (&new_cell.col,
3779                 psppire_axis_unit_count (sheet->haxis) - 1);
3780
3781   change_active_cell (sheet, new_cell.row, new_cell.col);
3782
3783
3784   if ( new_cell.col > max_fully_visible_column (sheet))
3785     {
3786       glong hpos  =
3787         psppire_axis_start_pixel (sheet->haxis,
3788                                   new_cell.col + 1);
3789       hpos -= sheet->hadjustment->page_size;
3790
3791       gtk_adjustment_set_value (sheet->hadjustment,
3792                                 hpos);
3793     }
3794   else if ( new_cell.col < min_fully_visible_column (sheet))
3795     {
3796       glong hpos  =
3797         psppire_axis_start_pixel (sheet->haxis,
3798                                   new_cell.col);
3799
3800       gtk_adjustment_set_value (sheet->hadjustment,
3801                                 hpos);
3802     }
3803
3804
3805   if ( new_cell.row > max_fully_visible_row (sheet))
3806     {
3807       glong vpos  =
3808         psppire_axis_start_pixel (sheet->vaxis,
3809                                   new_cell.row + 1);
3810       vpos -= sheet->vadjustment->page_size;
3811
3812       gtk_adjustment_set_value (sheet->vadjustment,
3813                                 vpos);
3814     }
3815   else if ( new_cell.row < min_fully_visible_row (sheet))
3816     {
3817       glong vpos  =
3818         psppire_axis_start_pixel (sheet->vaxis,
3819                                   new_cell.row);
3820
3821       gtk_adjustment_set_value (sheet->vadjustment,
3822                                 vpos);
3823     }
3824
3825   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3826 }
3827
3828
3829 static gboolean
3830 psppire_sheet_key_press (GtkWidget *widget,
3831                          GdkEventKey *key)
3832 {
3833   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3834
3835   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3836
3837   switch (key->keyval)
3838     {
3839     case GDK_Tab:
3840       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
3841       break;
3842     case GDK_Right:
3843       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
3844       break;
3845     case GDK_ISO_Left_Tab:
3846       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
3847       break;
3848     case GDK_Left:
3849       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
3850       break;
3851     case GDK_Return:
3852     case GDK_Down:
3853       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
3854       break;
3855     case GDK_Up:
3856       step_sheet (sheet, GTK_SCROLL_STEP_UP);
3857       break;
3858
3859     case GDK_Page_Down:
3860       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
3861       break;
3862     case GDK_Page_Up:
3863       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
3864       break;
3865
3866     case GDK_Home:
3867       gtk_adjustment_set_value (sheet->vadjustment,
3868                                 sheet->vadjustment->lower);
3869
3870       change_active_cell (sheet,  0,
3871                           sheet->active_cell.col);
3872
3873       break;
3874
3875     case GDK_End:
3876       gtk_adjustment_set_value (sheet->vadjustment,
3877                                 sheet->vadjustment->upper -
3878                                 sheet->vadjustment->page_size -
3879                                 sheet->vadjustment->page_increment);
3880
3881       /*
3882         change_active_cellx (sheet,
3883         psppire_axis_unit_count (sheet->vaxis) - 1,
3884         sheet->active_cell.col);
3885       */
3886       break;
3887     case GDK_Delete:
3888       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
3889       break;
3890     default:
3891       return FALSE;
3892       break;
3893     }
3894
3895   return TRUE;
3896 }
3897
3898 static void
3899 psppire_sheet_size_request (GtkWidget *widget,
3900                             GtkRequisition *requisition)
3901 {
3902   PsppireSheet *sheet;
3903
3904   g_return_if_fail (widget != NULL);
3905   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3906   g_return_if_fail (requisition != NULL);
3907
3908   sheet = PSPPIRE_SHEET (widget);
3909
3910   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
3911   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
3912
3913   /* compute the size of the column title area */
3914   if (sheet->column_titles_visible)
3915     requisition->height += sheet->column_title_area.height;
3916
3917   /* compute the size of the row title area */
3918   if (sheet->row_titles_visible)
3919     requisition->width += sheet->row_title_area.width;
3920 }
3921
3922
3923 static void
3924 psppire_sheet_size_allocate (GtkWidget *widget,
3925                              GtkAllocation *allocation)
3926 {
3927   PsppireSheet *sheet;
3928   GtkAllocation sheet_allocation;
3929   gint border_width;
3930
3931   g_return_if_fail (widget != NULL);
3932   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3933   g_return_if_fail (allocation != NULL);
3934
3935   sheet = PSPPIRE_SHEET (widget);
3936   widget->allocation = *allocation;
3937   border_width = GTK_CONTAINER (widget)->border_width;
3938
3939   if (GTK_WIDGET_REALIZED (widget))
3940     gdk_window_move_resize (widget->window,
3941                             allocation->x + border_width,
3942                             allocation->y + border_width,
3943                             allocation->width - 2 * border_width,
3944                             allocation->height - 2 * border_width);
3945
3946   sheet_allocation.x = 0;
3947   sheet_allocation.y = 0;
3948   sheet_allocation.width = allocation->width - 2 * border_width;
3949   sheet_allocation.height = allocation->height - 2 * border_width;
3950
3951   if (GTK_WIDGET_REALIZED (widget))
3952     gdk_window_move_resize (sheet->sheet_window,
3953                             sheet_allocation.x,
3954                             sheet_allocation.y,
3955                             sheet_allocation.width,
3956                             sheet_allocation.height);
3957
3958   /* position the window which holds the column title buttons */
3959   sheet->column_title_area.x = 0;
3960   sheet->column_title_area.y = 0;
3961   sheet->column_title_area.width = sheet_allocation.width ;
3962
3963
3964   /* position the window which holds the row title buttons */
3965   sheet->row_title_area.x = 0;
3966   sheet->row_title_area.y = 0;
3967   sheet->row_title_area.height = sheet_allocation.height;
3968
3969   if (sheet->row_titles_visible)
3970     sheet->column_title_area.x += sheet->row_title_area.width;
3971
3972   if (sheet->column_titles_visible)
3973     sheet->row_title_area.y += sheet->column_title_area.height;
3974
3975   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
3976     gdk_window_move_resize (sheet->column_title_window,
3977                             sheet->column_title_area.x,
3978                             sheet->column_title_area.y,
3979                             sheet->column_title_area.width,
3980                             sheet->column_title_area.height);
3981
3982
3983   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
3984     gdk_window_move_resize (sheet->row_title_window,
3985                             sheet->row_title_area.x,
3986                             sheet->row_title_area.y,
3987                             sheet->row_title_area.width,
3988                             sheet->row_title_area.height);
3989
3990   size_allocate_global_button (sheet);
3991
3992   if (sheet->haxis)
3993     {
3994       gint width = sheet->column_title_area.width;
3995
3996       if ( sheet->row_titles_visible)
3997         width -= sheet->row_title_area.width;
3998
3999       g_object_set (sheet->haxis,
4000                     "minimum-extent", width,
4001                     NULL);
4002     }
4003
4004
4005   if (sheet->vaxis)
4006     {
4007       gint height = sheet->row_title_area.height;
4008
4009       if ( sheet->column_titles_visible)
4010         height -= sheet->column_title_area.height;
4011
4012       g_object_set (sheet->vaxis,
4013                     "minimum-extent", height,
4014                     NULL);
4015     }
4016
4017
4018   /* set the scrollbars adjustments */
4019   adjust_scrollbars (sheet);
4020 }
4021
4022 static void
4023 draw_column_title_buttons (PsppireSheet *sheet)
4024 {
4025   gint x, width;
4026
4027   if (!sheet->column_titles_visible) return;
4028   if (!GTK_WIDGET_REALIZED (sheet))
4029     return;
4030
4031   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
4032   x = 0;
4033
4034   if (sheet->row_titles_visible)
4035     {
4036       x = sheet->row_title_area.width;
4037     }
4038
4039   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
4040     {
4041       sheet->column_title_area.width = width;
4042       sheet->column_title_area.x = x;
4043       gdk_window_move_resize (sheet->column_title_window,
4044                               sheet->column_title_area.x,
4045                               sheet->column_title_area.y,
4046                               sheet->column_title_area.width,
4047                               sheet->column_title_area.height);
4048     }
4049
4050   if (max_visible_column (sheet) ==
4051       psppire_axis_unit_count (sheet->haxis) - 1)
4052     gdk_window_clear_area (sheet->column_title_window,
4053                            0, 0,
4054                            sheet->column_title_area.width,
4055                            sheet->column_title_area.height);
4056
4057   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4058
4059   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
4060                                    max_visible_column (sheet));
4061 }
4062
4063 static void
4064 draw_row_title_buttons (PsppireSheet *sheet)
4065 {
4066   gint y = 0;
4067   gint height;
4068
4069   if (!sheet->row_titles_visible) return;
4070   if (!GTK_WIDGET_REALIZED (sheet))
4071     return;
4072
4073   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
4074
4075   if (sheet->column_titles_visible)
4076     {
4077       y = sheet->column_title_area.height;
4078     }
4079
4080   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
4081     {
4082       sheet->row_title_area.y = y;
4083       sheet->row_title_area.height = height;
4084       gdk_window_move_resize (sheet->row_title_window,
4085                               sheet->row_title_area.x,
4086                               sheet->row_title_area.y,
4087                               sheet->row_title_area.width,
4088                               sheet->row_title_area.height);
4089     }
4090
4091   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
4092     gdk_window_clear_area (sheet->row_title_window,
4093                            0, 0,
4094                            sheet->row_title_area.width,
4095                            sheet->row_title_area.height);
4096
4097   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4098
4099   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
4100                                 max_visible_row (sheet));
4101 }
4102
4103
4104 static void
4105 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
4106 {
4107   GtkAllocation entry_alloc;
4108   PsppireSheetCellAttr attributes = { 0 };
4109   GtkEntry *sheet_entry;
4110
4111   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4112   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
4113
4114   sheet_entry = psppire_sheet_get_entry (sheet);
4115
4116   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
4117                                        sheet->active_cell.col,
4118                                        &attributes) )
4119     return ;
4120
4121   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
4122     {
4123       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
4124
4125       style->bg[GTK_STATE_NORMAL] = attributes.background;
4126       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
4127       style->text[GTK_STATE_NORMAL] = attributes.foreground;
4128       style->bg[GTK_STATE_ACTIVE] = attributes.background;
4129       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
4130       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
4131     }
4132
4133   rectangle_from_cell (sheet, sheet->active_cell.row,
4134                        sheet->active_cell.col, &entry_alloc);
4135
4136   entry_alloc.x += sheet->cell_padding->left;
4137   entry_alloc.y += sheet->cell_padding->right;
4138   entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
4139   entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
4140
4141
4142   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
4143                                entry_alloc.height);
4144   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
4145 }
4146
4147
4148 /* Copy the sheet's font to the entry widget */
4149 static void
4150 set_entry_widget_font (PsppireSheet *sheet)
4151 {
4152   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
4153
4154   pango_font_description_free (style->font_desc);
4155   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4156
4157   gtk_widget_modify_style (sheet->entry_widget, style);
4158 }
4159
4160 static void
4161 create_sheet_entry (PsppireSheet *sheet)
4162 {
4163   if (sheet->entry_widget)
4164     {
4165       gtk_widget_unparent (sheet->entry_widget);
4166     }
4167
4168   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4169   g_object_ref_sink (sheet->entry_widget);
4170
4171   gtk_widget_size_request (sheet->entry_widget, NULL);
4172
4173   if ( GTK_IS_ENTRY (sheet->entry_widget))
4174     {
4175       g_object_set (sheet->entry_widget,
4176                     "has-frame", FALSE,
4177                     NULL);
4178     }
4179
4180   if (GTK_WIDGET_REALIZED (sheet))
4181     {
4182       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4183       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4184       gtk_widget_realize (sheet->entry_widget);
4185     }
4186
4187   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4188                             G_CALLBACK (psppire_sheet_entry_key_press),
4189                             sheet);
4190
4191   set_entry_widget_font (sheet);
4192
4193   gtk_widget_show (sheet->entry_widget);
4194 }
4195
4196
4197 /* Finds the last child widget that happens to be of type GtkEntry */
4198 static void
4199 find_entry (GtkWidget *w, gpointer user_data)
4200 {
4201   GtkWidget **entry = user_data;
4202   if ( GTK_IS_ENTRY (w))
4203     {
4204       *entry = w;
4205     }
4206 }
4207
4208
4209 GtkEntry *
4210 psppire_sheet_get_entry (PsppireSheet *sheet)
4211 {
4212   GtkWidget *w = sheet->entry_widget;
4213
4214   g_return_val_if_fail (sheet != NULL, NULL);
4215   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4216   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4217
4218   while (! GTK_IS_ENTRY (w))
4219     {
4220       GtkWidget *entry = NULL;
4221
4222       if (GTK_IS_CONTAINER (w))
4223         {
4224           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4225
4226           if (NULL == entry)
4227             break;
4228
4229           w = entry;
4230         }
4231     }
4232
4233   return GTK_ENTRY (w);
4234 }
4235
4236
4237 static void
4238 draw_button (PsppireSheet *sheet, GdkWindow *window,
4239              PsppireSheetButton *button, gboolean is_sensitive,
4240              GdkRectangle allocation)
4241 {
4242   GtkShadowType shadow_type;
4243   gint text_width = 0, text_height = 0;
4244   PangoAlignment align = PANGO_ALIGN_LEFT;
4245
4246   gboolean rtl ;
4247
4248   gint state = 0;
4249
4250   g_return_if_fail (sheet != NULL);
4251   g_return_if_fail (button != NULL);
4252
4253
4254   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4255
4256   gdk_window_clear_area (window,
4257                          allocation.x, allocation.y,
4258                          allocation.width, allocation.height);
4259
4260   gtk_widget_ensure_style (sheet->button);
4261
4262   gtk_paint_box (sheet->button->style, window,
4263                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4264                  &allocation,
4265                  GTK_WIDGET (sheet->button),
4266                  NULL,
4267                  allocation.x, allocation.y,
4268                  allocation.width, allocation.height);
4269
4270   state = button->state;
4271   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4272
4273   if (state == GTK_STATE_ACTIVE)
4274     shadow_type = GTK_SHADOW_IN;
4275   else
4276     shadow_type = GTK_SHADOW_OUT;
4277
4278   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4279     gtk_paint_box (sheet->button->style, window,
4280                    button->state, shadow_type,
4281                    &allocation, GTK_WIDGET (sheet->button),
4282                    NULL,
4283                    allocation.x, allocation.y,
4284                    allocation.width, allocation.height);
4285
4286   if ( button->overstruck)
4287     {
4288       GdkPoint points[2] = {
4289         {allocation.x,  allocation.y},
4290         {allocation.x + allocation.width,
4291          allocation.y + allocation.height}
4292       };
4293
4294       gtk_paint_polygon (sheet->button->style,
4295                          window,
4296                          button->state,
4297                          shadow_type,
4298                          NULL,
4299                          GTK_WIDGET (sheet),
4300                          NULL,
4301                          points,
4302                          2,
4303                          TRUE);
4304     }
4305
4306   if (button->label_visible)
4307     {
4308       text_height = DEFAULT_ROW_HEIGHT -
4309         2 * COLUMN_TITLES_HEIGHT;
4310
4311       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4312                                  &allocation);
4313       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4314                                  &allocation);
4315
4316       allocation.y += 2 * sheet->button->style->ythickness;
4317
4318       if (button->label && strlen (button->label) > 0)
4319         {
4320           PangoRectangle rect;
4321           gchar *line = button->label;
4322
4323           PangoLayout *layout = NULL;
4324           gint real_x = allocation.x;
4325           gint real_y = allocation.y;
4326
4327           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4328           pango_layout_get_extents (layout, NULL, &rect);
4329
4330           text_width = PANGO_PIXELS (rect.width);
4331           switch (button->justification)
4332             {
4333             case GTK_JUSTIFY_LEFT:
4334               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4335               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4336               break;
4337             case GTK_JUSTIFY_RIGHT:
4338               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4339               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4340               break;
4341             case GTK_JUSTIFY_CENTER:
4342             default:
4343               real_x = allocation.x + (allocation.width - text_width)/2;
4344               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4345               pango_layout_set_justify (layout, TRUE);
4346             }
4347           pango_layout_set_alignment (layout, align);
4348           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4349                             window,
4350                             state,
4351                             FALSE,
4352                             &allocation,
4353                             GTK_WIDGET (sheet),
4354                             "label",
4355                             real_x, real_y,
4356                             layout);
4357           g_object_unref (layout);
4358         }
4359
4360       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4361                                  NULL);
4362       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4363
4364     }
4365
4366   psppire_sheet_button_free (button);
4367 }
4368
4369
4370 /* Draw the column title buttons FIRST through to LAST */
4371 static void
4372 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4373 {
4374   GdkRectangle rect;
4375   gint col;
4376   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4377
4378   if (!sheet->column_titles_visible) return;
4379
4380   g_return_if_fail (first >= min_visible_column (sheet));
4381   g_return_if_fail (last <= max_visible_column (sheet));
4382
4383   rect.y = 0;
4384   rect.height = sheet->column_title_area.height;
4385   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4386   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4387     + psppire_axis_unit_size (sheet->haxis, last);
4388
4389   rect.x -= sheet->hadjustment->value;
4390
4391   minimize_int (&rect.width, sheet->column_title_area.width);
4392   maximize_int (&rect.x, 0);
4393
4394   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4395
4396   for (col = first ; col <= last ; ++col)
4397     {
4398       GdkRectangle allocation;
4399       gboolean is_sensitive = FALSE;
4400
4401       PsppireSheetButton *
4402         button = psppire_sheet_model_get_column_button (sheet->model, col);
4403       allocation.y = 0;
4404       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4405         + CELL_SPACING;
4406       allocation.x -= sheet->hadjustment->value;
4407
4408       allocation.height = sheet->column_title_area.height;
4409       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4410       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4411
4412       draw_button (sheet, sheet->column_title_window,
4413                    button, is_sensitive, allocation);
4414     }
4415
4416   gdk_window_end_paint (sheet->column_title_window);
4417 }
4418
4419
4420 static void
4421 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4422 {
4423   GdkRectangle rect;
4424   gint row;
4425   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4426
4427   if (!sheet->row_titles_visible) return;
4428
4429   g_return_if_fail (first >= min_visible_row (sheet));
4430   g_return_if_fail (last <= max_visible_row (sheet));
4431
4432   rect.x = 0;
4433   rect.width = sheet->row_title_area.width;
4434   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4435   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4436     + psppire_axis_unit_size (sheet->vaxis, last);
4437
4438   rect.y -= sheet->vadjustment->value;
4439
4440   minimize_int (&rect.height, sheet->row_title_area.height);
4441   maximize_int (&rect.y, 0);
4442
4443   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4444   for (row = first; row <= last; ++row)
4445     {
4446       GdkRectangle allocation;
4447
4448       gboolean is_sensitive = FALSE;
4449
4450       PsppireSheetButton *button =
4451         psppire_sheet_model_get_row_button (sheet->model, row);
4452       allocation.x = 0;
4453       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4454         + CELL_SPACING;
4455       allocation.y -= sheet->vadjustment->value;
4456
4457       allocation.width = sheet->row_title_area.width;
4458       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4459       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4460
4461       draw_button (sheet, sheet->row_title_window,
4462                    button, is_sensitive, allocation);
4463     }
4464
4465   gdk_window_end_paint (sheet->row_title_window);
4466 }
4467
4468 /* SCROLLBARS
4469  *
4470  * functions:
4471  * adjust_scrollbars
4472  * vadjustment_value_changed
4473  * hadjustment_value_changed */
4474
4475
4476 static void
4477 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
4478 {
4479   double position =
4480     (adj->value + adj->page_size)
4481     /
4482     (adj->upper - adj->lower);
4483
4484   const glong last_item = psppire_axis_unit_count (axis) - 1;
4485
4486   if (isnan (position) || position < 0)
4487     position = 0;
4488
4489   adj->upper =
4490     psppire_axis_start_pixel (axis, last_item)
4491     +
4492     psppire_axis_unit_size (axis, last_item)
4493     ;
4494
4495   adj->lower = 0;
4496   adj->page_size = page_size;
4497
4498 #if 0
4499   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
4500
4501   if ( adj->value < adj->lower)
4502     adj->value = adj->lower;
4503 #endif
4504
4505   gtk_adjustment_changed (adj);
4506 }
4507
4508
4509 static void
4510 adjust_scrollbars (PsppireSheet *sheet)
4511 {
4512   gint width, height;
4513
4514   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4515     return;
4516
4517   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
4518
4519   if ( sheet->row_titles_visible)
4520     width -= sheet->row_title_area.width;
4521
4522   if (sheet->column_titles_visible)
4523     height -= sheet->column_title_area.height;
4524
4525   if (sheet->vadjustment)
4526     {
4527       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
4528
4529       sheet->vadjustment->step_increment =
4530         ROWS_PER_STEP *
4531         psppire_axis_unit_size (sheet->vaxis, last_row);
4532
4533       sheet->vadjustment->page_increment =
4534         height -
4535         sheet->column_title_area.height -
4536         psppire_axis_unit_size (sheet->vaxis, last_row);
4537
4538       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
4539     }
4540
4541   if (sheet->hadjustment)
4542     {
4543       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
4544       sheet->hadjustment->step_increment = 1;
4545
4546       sheet->hadjustment->page_increment = width;
4547
4548       sheet->hadjustment->upper =
4549         psppire_axis_start_pixel (sheet->haxis, last_col)
4550         +
4551         psppire_axis_unit_size (sheet->haxis, last_col)
4552         ;
4553
4554       update_adjustment (sheet->hadjustment, sheet->haxis, width);
4555     }
4556 }
4557
4558 /* Subtracts the region of WIDGET from REGION */
4559 static void
4560 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
4561 {
4562   GdkRectangle rect;
4563   GdkRectangle intersect;
4564   GdkRegion *region2;
4565
4566   gdk_region_get_clipbox (region, &rect);
4567   gtk_widget_intersect (widget,
4568                         &rect,
4569                         &intersect);
4570
4571   region2 = gdk_region_rectangle (&intersect);
4572   gdk_region_subtract (region, region2);
4573   gdk_region_destroy (region2);
4574 }
4575
4576 static void
4577 vadjustment_value_changed (GtkAdjustment *adjustment,
4578                            gpointer data)
4579 {
4580   GdkRegion *region;
4581   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4582
4583   g_return_if_fail (adjustment != NULL);
4584
4585   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4586
4587   gtk_widget_hide (sheet->entry_widget);
4588
4589   region =
4590     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4591
4592   subtract_widget_region (region, sheet->button);
4593   gdk_window_begin_paint_region (sheet->sheet_window, region);
4594
4595   draw_sheet_region (sheet, region);
4596
4597   draw_row_title_buttons (sheet);
4598   psppire_sheet_draw_active_cell (sheet);
4599
4600   gdk_window_end_paint (sheet->sheet_window);
4601   gdk_region_destroy (region);
4602 }
4603
4604
4605 static void
4606 hadjustment_value_changed (GtkAdjustment *adjustment,
4607                            gpointer data)
4608 {
4609   GdkRegion *region;
4610   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4611
4612   g_return_if_fail (adjustment != NULL);
4613
4614   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4615
4616   gtk_widget_hide (sheet->entry_widget);
4617
4618
4619   region =
4620     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4621
4622   subtract_widget_region (region, sheet->button);
4623   gdk_window_begin_paint_region (sheet->sheet_window, region);
4624
4625   draw_sheet_region (sheet, region);
4626
4627   draw_column_title_buttons (sheet);
4628
4629   psppire_sheet_draw_active_cell (sheet);
4630
4631   gdk_window_end_paint (sheet->sheet_window);
4632
4633   gdk_region_destroy (region);
4634 }
4635
4636
4637 /* COLUMN RESIZING */
4638 static void
4639 draw_xor_vline (PsppireSheet *sheet)
4640 {
4641   gint height;
4642   gint xpos = sheet->x_drag;
4643   gdk_drawable_get_size (sheet->sheet_window,
4644                          NULL, &height);
4645
4646   if (sheet->row_titles_visible)
4647     xpos += sheet->row_title_area.width;
4648
4649   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4650                  xpos,
4651                  sheet->column_title_area.height,
4652                  xpos,
4653                  height + CELL_SPACING);
4654 }
4655
4656 /* ROW RESIZING */
4657 static void
4658 draw_xor_hline (PsppireSheet *sheet)
4659
4660 {
4661   gint width;
4662   gint ypos = sheet->y_drag;
4663
4664   gdk_drawable_get_size (sheet->sheet_window,
4665                          &width, NULL);
4666
4667
4668   if (sheet->column_titles_visible)
4669     ypos += sheet->column_title_area.height;
4670
4671   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4672                  sheet->row_title_area.width,
4673                  ypos,
4674                  width + CELL_SPACING,
4675                  ypos);
4676 }
4677
4678 /* SELECTED RANGE */
4679 static void
4680 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
4681 {
4682   gint i = 0;
4683   GdkRectangle clip_area, area;
4684   GdkGCValues values;
4685
4686   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
4687   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
4688   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
4689     psppire_axis_unit_size (sheet->haxis, range.coli);
4690   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
4691     psppire_axis_unit_size (sheet->vaxis, range.rowi);
4692
4693   clip_area.x = sheet->row_title_area.width;
4694   clip_area.y = sheet->column_title_area.height;
4695
4696   gdk_drawable_get_size (sheet->sheet_window,
4697                          &clip_area.width, &clip_area.height);
4698
4699   if (!sheet->row_titles_visible) clip_area.x = 0;
4700   if (!sheet->column_titles_visible) clip_area.y = 0;
4701
4702   if (area.x < 0)
4703     {
4704       area.width = area.width + area.x;
4705       area.x = 0;
4706     }
4707   if (area.width > clip_area.width) area.width = clip_area.width + 10;
4708   if (area.y < 0)
4709     {
4710       area.height = area.height + area.y;
4711       area.y = 0;
4712     }
4713   if (area.height > clip_area.height) area.height = clip_area.height + 10;
4714
4715   clip_area.x--;
4716   clip_area.y--;
4717   clip_area.width += 3;
4718   clip_area.height += 3;
4719
4720   gdk_gc_get_values (sheet->xor_gc, &values);
4721
4722   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
4723
4724   gdk_draw_rectangle (sheet->sheet_window,
4725                       sheet->xor_gc,
4726                       FALSE,
4727                       area.x + i, area.y + i,
4728                       area.width - 2 * i, area.height - 2 * i);
4729
4730
4731   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
4732
4733   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
4734 }
4735
4736
4737 static void
4738 set_column_width (PsppireSheet *sheet,
4739                   gint column,
4740                   gint width)
4741 {
4742   g_return_if_fail (sheet != NULL);
4743   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4744
4745   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
4746     return;
4747
4748   if ( width <= 0)
4749     return;
4750
4751   psppire_axis_resize (sheet->haxis, column,
4752                        width - sheet->cell_padding->left -
4753                        sheet->cell_padding->right);
4754
4755   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4756     {
4757       draw_column_title_buttons (sheet);
4758       adjust_scrollbars (sheet);
4759       psppire_sheet_size_allocate_entry (sheet);
4760       redraw_range (sheet, NULL);
4761     }
4762 }
4763
4764 static void
4765 set_row_height (PsppireSheet *sheet,
4766                 gint row,
4767                 gint height)
4768 {
4769   g_return_if_fail (sheet != NULL);
4770   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4771
4772   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
4773     return;
4774
4775   if (height <= 0)
4776     return;
4777
4778   psppire_axis_resize (sheet->vaxis, row,
4779                        height - sheet->cell_padding->top -
4780                        sheet->cell_padding->bottom);
4781
4782   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
4783     {
4784       draw_row_title_buttons (sheet);
4785       adjust_scrollbars (sheet);
4786       psppire_sheet_size_allocate_entry (sheet);
4787       redraw_range (sheet, NULL);
4788     }
4789 }
4790
4791 static gboolean
4792 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
4793                               PsppireSheetCellAttr *attr)
4794 {
4795   GdkColor *fg, *bg;
4796   const GtkJustification *j ;
4797   GdkColormap *colormap;
4798
4799   g_return_val_if_fail (sheet != NULL, FALSE);
4800   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
4801
4802   if (row < 0 || col < 0) return FALSE;
4803
4804   attr->foreground = GTK_WIDGET (sheet)->style->black;
4805   attr->background = sheet->color[BG_COLOR];
4806
4807   attr->border.width = 0;
4808   attr->border.line_style = GDK_LINE_SOLID;
4809   attr->border.cap_style = GDK_CAP_NOT_LAST;
4810   attr->border.join_style = GDK_JOIN_MITER;
4811   attr->border.mask = 0;
4812   attr->border.color = GTK_WIDGET (sheet)->style->black;
4813
4814   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
4815   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
4816   if ( fg )
4817     {
4818       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
4819       attr->foreground = *fg;
4820     }
4821
4822   bg = psppire_sheet_model_get_background (sheet->model, row, col);
4823   if ( bg )
4824     {
4825       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
4826       attr->background = *bg;
4827     }
4828
4829   attr->justification =
4830     psppire_sheet_model_get_column_justification (sheet->model, col);
4831
4832   j = psppire_sheet_model_get_justification (sheet->model, row, col);
4833   if (j)
4834     attr->justification = *j;
4835
4836   return TRUE;
4837 }
4838
4839 static void
4840 psppire_sheet_button_size_request        (PsppireSheet *sheet,
4841                                           const PsppireSheetButton *button,
4842                                           GtkRequisition *button_requisition)
4843 {
4844   GtkRequisition requisition;
4845   GtkRequisition label_requisition;
4846
4847   label_requisition.height = DEFAULT_ROW_HEIGHT;
4848   label_requisition.width = COLUMN_MIN_WIDTH;
4849
4850   requisition.height = DEFAULT_ROW_HEIGHT;
4851   requisition.width = COLUMN_MIN_WIDTH;
4852
4853
4854   *button_requisition = requisition;
4855   button_requisition->width = MAX (requisition.width, label_requisition.width);
4856   button_requisition->height = MAX (requisition.height, label_requisition.height);
4857
4858 }
4859
4860 static void
4861 psppire_sheet_forall (GtkContainer *container,
4862                       gboolean include_internals,
4863                       GtkCallback callback,
4864                       gpointer callback_data)
4865 {
4866   PsppireSheet *sheet = PSPPIRE_SHEET (container);
4867
4868   g_return_if_fail (callback != NULL);
4869
4870   if (sheet->button && sheet->button->parent)
4871     (* callback) (sheet->button, callback_data);
4872
4873   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
4874     (* callback) (sheet->entry_widget, callback_data);
4875 }
4876
4877
4878 PsppireSheetModel *
4879 psppire_sheet_get_model (const PsppireSheet *sheet)
4880 {
4881   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4882
4883   return sheet->model;
4884 }
4885
4886
4887 PsppireSheetButton *
4888 psppire_sheet_button_new (void)
4889 {
4890   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
4891
4892   button->state = GTK_STATE_NORMAL;
4893   button->label = NULL;
4894   button->label_visible = TRUE;
4895   button->justification = GTK_JUSTIFY_FILL;
4896   button->overstruck = FALSE;
4897
4898   return button;
4899 }
4900
4901
4902 void
4903 psppire_sheet_button_free (PsppireSheetButton *button)
4904 {
4905   if (!button) return ;
4906
4907   g_free (button->label);
4908   g_free (button);
4909 }
4910
4911 static void
4912 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
4913 {
4914   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
4915
4916   if ( NULL == celltext)
4917     return;
4918
4919   g_string_append (string, celltext);
4920   g_free (celltext);
4921 }
4922
4923
4924 static GString *
4925 range_to_text (const PsppireSheet *sheet)
4926 {
4927   gint r, c;
4928   GString *string;
4929
4930   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4931     return NULL;
4932
4933   string = g_string_sized_new (80);
4934
4935   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4936     {
4937       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
4938         {
4939           append_cell_text (string, sheet, r, c);
4940           g_string_append (string, "\t");
4941         }
4942       append_cell_text (string, sheet, r, c);
4943       if ( r < sheet->range.rowi)
4944         g_string_append (string, "\n");
4945     }
4946
4947   return string;
4948 }
4949
4950 static GString *
4951 range_to_html (const PsppireSheet *sheet)
4952 {
4953   gint r, c;
4954   GString *string;
4955
4956   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4957     return NULL;
4958
4959   string = g_string_sized_new (480);
4960
4961   g_string_append (string, "<html>\n");
4962   g_string_append (string, "<body>\n");
4963   g_string_append (string, "<table>\n");
4964   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4965     {
4966       g_string_append (string, "<tr>\n");
4967       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
4968         {
4969           g_string_append (string, "<td>");
4970           append_cell_text (string, sheet, r, c);
4971           g_string_append (string, "</td>\n");
4972         }
4973       g_string_append (string, "</tr>\n");
4974     }
4975   g_string_append (string, "</table>\n");
4976   g_string_append (string, "</body>\n");
4977   g_string_append (string, "</html>\n");
4978
4979   return string;
4980 }
4981
4982 enum {
4983   SELECT_FMT_NULL,
4984   SELECT_FMT_TEXT,
4985   SELECT_FMT_HTML
4986 };
4987
4988 static void
4989 primary_get_cb (GtkClipboard     *clipboard,
4990                 GtkSelectionData *selection_data,
4991                 guint             info,
4992                 gpointer          data)
4993 {
4994   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4995   GString *string = NULL;
4996
4997   switch (info)
4998     {
4999     case SELECT_FMT_TEXT:
5000       string = range_to_text (sheet);
5001       break;
5002     case SELECT_FMT_HTML:
5003       string = range_to_html (sheet);
5004       break;
5005     default:
5006       g_assert_not_reached ();
5007     }
5008
5009   gtk_selection_data_set (selection_data, selection_data->target,
5010                           8,
5011                           (const guchar *) string->str, string->len);
5012   g_string_free (string, TRUE);
5013 }
5014
5015 static void
5016 primary_clear_cb (GtkClipboard *clipboard,
5017                   gpointer      data)
5018 {
5019   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5020   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5021     return;
5022
5023   psppire_sheet_unselect_range (sheet);
5024 }
5025
5026 static void
5027 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
5028 {
5029   static const GtkTargetEntry targets[] = {
5030     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
5031     { "STRING",        0, SELECT_FMT_TEXT },
5032     { "TEXT",          0, SELECT_FMT_TEXT },
5033     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
5034     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
5035     { "text/plain",    0, SELECT_FMT_TEXT },
5036     { "text/html",     0, SELECT_FMT_HTML }
5037   };
5038
5039   GtkClipboard *clipboard;
5040
5041   if (!GTK_WIDGET_REALIZED (sheet))
5042     return;
5043
5044   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
5045                                         GDK_SELECTION_PRIMARY);
5046
5047   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
5048     {
5049       if (!gtk_clipboard_set_with_owner (clipboard, targets,
5050                                          G_N_ELEMENTS (targets),
5051                                          primary_get_cb, primary_clear_cb,
5052                                          G_OBJECT (sheet)))
5053         primary_clear_cb (clipboard, sheet);
5054     }
5055   else
5056     {
5057       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
5058         gtk_clipboard_clear (clipboard);
5059     }
5060 }