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