sys-file-writer: Omit empty multiple response sets records.
[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   PsppireSheet *sheet;
1702
1703   g_return_if_fail (object != NULL);
1704   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1705
1706   sheet = PSPPIRE_SHEET (object);
1707
1708   if (G_OBJECT_CLASS (parent_class)->finalize)
1709     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
1710 }
1711
1712 static void
1713 psppire_sheet_dispose  (GObject *object)
1714 {
1715   PsppireSheet *sheet = PSPPIRE_SHEET (object);
1716
1717   g_return_if_fail (object != NULL);
1718   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1719
1720   if ( sheet->dispose_has_run )
1721     return ;
1722
1723   sheet->dispose_has_run = TRUE;
1724
1725   if ( sheet->cell_padding)
1726     g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
1727
1728   if (sheet->model) g_object_unref (sheet->model);
1729   if (sheet->vaxis) g_object_unref (sheet->vaxis);
1730   if (sheet->haxis) g_object_unref (sheet->haxis);
1731
1732   g_object_unref (sheet->button);
1733   sheet->button = NULL;
1734
1735   /* unref adjustments */
1736   if (sheet->hadjustment)
1737     {
1738       g_signal_handlers_disconnect_matched (sheet->hadjustment,
1739                                             G_SIGNAL_MATCH_DATA,
1740                                             0, 0, 0, 0,
1741                                             sheet);
1742
1743       g_object_unref (sheet->hadjustment);
1744       sheet->hadjustment = NULL;
1745     }
1746
1747   if (sheet->vadjustment)
1748     {
1749       g_signal_handlers_disconnect_matched (sheet->vadjustment,
1750                                             G_SIGNAL_MATCH_DATA,
1751                                             0, 0, 0, 0,
1752                                             sheet);
1753
1754       g_object_unref (sheet->vadjustment);
1755
1756       sheet->vadjustment = NULL;
1757     }
1758
1759   if (G_OBJECT_CLASS (parent_class)->dispose)
1760     (*G_OBJECT_CLASS (parent_class)->dispose) (object);
1761 }
1762
1763 static void
1764 psppire_sheet_style_set (GtkWidget *widget,
1765                          GtkStyle *previous_style)
1766 {
1767   PsppireSheet *sheet;
1768
1769   g_return_if_fail (widget != NULL);
1770   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1771
1772   if (GTK_WIDGET_CLASS (parent_class)->style_set)
1773     (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1774
1775   sheet = PSPPIRE_SHEET (widget);
1776
1777   if (GTK_WIDGET_REALIZED (widget))
1778     {
1779       gtk_style_set_background (widget->style, widget->window, widget->state);
1780     }
1781
1782   set_entry_widget_font (sheet);
1783 }
1784
1785
1786 static void
1787 psppire_sheet_realize (GtkWidget *widget)
1788 {
1789   PsppireSheet *sheet;
1790   GdkWindowAttr attributes;
1791   const gint attributes_mask =
1792     GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;
1793
1794   GdkGCValues values;
1795   GdkColormap *colormap;
1796   GdkDisplay *display;
1797
1798   g_return_if_fail (widget != NULL);
1799   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1800
1801   sheet = PSPPIRE_SHEET (widget);
1802
1803   colormap = gtk_widget_get_colormap (widget);
1804   display = gtk_widget_get_display (widget);
1805
1806   attributes.window_type = GDK_WINDOW_CHILD;
1807   attributes.x = widget->allocation.x;
1808   attributes.y = widget->allocation.y;
1809   attributes.width = widget->allocation.width;
1810   attributes.height = widget->allocation.height;
1811   attributes.wclass = GDK_INPUT_OUTPUT;
1812
1813   attributes.visual = gtk_widget_get_visual (widget);
1814   attributes.colormap = colormap;
1815
1816   attributes.event_mask = gtk_widget_get_events (widget);
1817   attributes.event_mask |= (GDK_EXPOSURE_MASK |
1818                             GDK_BUTTON_PRESS_MASK |
1819                             GDK_BUTTON_RELEASE_MASK |
1820                             GDK_KEY_PRESS_MASK |
1821                             GDK_ENTER_NOTIFY_MASK |
1822                             GDK_LEAVE_NOTIFY_MASK |
1823                             GDK_POINTER_MOTION_MASK |
1824                             GDK_POINTER_MOTION_HINT_MASK);
1825
1826   attributes.cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
1827
1828   /* main window */
1829   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1830
1831   gdk_window_set_user_data (widget->window, sheet);
1832
1833   widget->style = gtk_style_attach (widget->style, widget->window);
1834
1835   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1836
1837   gdk_color_parse ("white", &sheet->color[BG_COLOR]);
1838   gdk_colormap_alloc_color (colormap, &sheet->color[BG_COLOR], FALSE,
1839                             TRUE);
1840   gdk_color_parse ("gray", &sheet->color[GRID_COLOR]);
1841   gdk_colormap_alloc_color (colormap, &sheet->color[GRID_COLOR], FALSE,
1842                             TRUE);
1843
1844   attributes.x = 0;
1845   attributes.y = 0;
1846   attributes.width = sheet->column_title_area.width;
1847   attributes.height = sheet->column_title_area.height;
1848
1849
1850   /* column - title window */
1851   sheet->column_title_window =
1852     gdk_window_new (widget->window, &attributes, attributes_mask);
1853   gdk_window_set_user_data (sheet->column_title_window, sheet);
1854   gtk_style_set_background (widget->style, sheet->column_title_window,
1855                             GTK_STATE_NORMAL);
1856
1857
1858   attributes.x = 0;
1859   attributes.y = 0;
1860   attributes.width = sheet->row_title_area.width;
1861   attributes.height = sheet->row_title_area.height;
1862
1863   /* row - title window */
1864   sheet->row_title_window = gdk_window_new (widget->window,
1865                                             &attributes, attributes_mask);
1866   gdk_window_set_user_data (sheet->row_title_window, sheet);
1867   gtk_style_set_background (widget->style, sheet->row_title_window,
1868                             GTK_STATE_NORMAL);
1869
1870   /* sheet - window */
1871   attributes.cursor = gdk_cursor_new_for_display (display, GDK_PLUS);
1872
1873   attributes.x = 0;
1874   attributes.y = 0;
1875
1876   sheet->sheet_window = gdk_window_new (widget->window,
1877                                         &attributes, attributes_mask);
1878   gdk_window_set_user_data (sheet->sheet_window, sheet);
1879
1880   gdk_cursor_unref (attributes.cursor);
1881
1882   gdk_window_set_background (sheet->sheet_window, &widget->style->white);
1883   gdk_window_show (sheet->sheet_window);
1884
1885   /* GCs */
1886   sheet->fg_gc = gdk_gc_new (widget->window);
1887   sheet->bg_gc = gdk_gc_new (widget->window);
1888
1889   values.foreground = widget->style->white;
1890   values.function = GDK_INVERT;
1891   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
1892   values.line_width = MAX (sheet->cell_padding->left,
1893                            MAX (sheet->cell_padding->right,
1894                                 MAX (sheet->cell_padding->top,
1895                                      sheet->cell_padding->bottom)));
1896
1897   sheet->xor_gc = gdk_gc_new_with_values (widget->window,
1898                                           &values,
1899                                           GDK_GC_FOREGROUND |
1900                                           GDK_GC_FUNCTION |
1901                                           GDK_GC_SUBWINDOW |
1902                                           GDK_GC_LINE_WIDTH
1903                                           );
1904
1905   gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
1906   gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
1907
1908   gtk_widget_set_parent_window (sheet->button, sheet->sheet_window);
1909   gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet));
1910
1911   sheet->button->style = gtk_style_attach (sheet->button->style,
1912                                            sheet->sheet_window);
1913
1914
1915   sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
1916
1917   if (sheet->column_titles_visible)
1918     gdk_window_show (sheet->column_title_window);
1919   if (sheet->row_titles_visible)
1920     gdk_window_show (sheet->row_title_window);
1921
1922   sheet->hover_window = create_hover_window ();
1923
1924   draw_row_title_buttons (sheet);
1925   draw_column_title_buttons (sheet);
1926
1927   psppire_sheet_update_primary_selection (sheet);
1928
1929
1930   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1931 }
1932
1933 static void
1934 create_global_button (PsppireSheet *sheet)
1935 {
1936   sheet->button = gtk_button_new_with_label (" ");
1937
1938   GTK_WIDGET_UNSET_FLAGS(sheet->button, GTK_CAN_FOCUS);
1939
1940   g_object_ref_sink (sheet->button);
1941
1942   g_signal_connect (sheet->button,
1943                     "pressed",
1944                     G_CALLBACK (global_button_clicked),
1945                     sheet);
1946 }
1947
1948 static void
1949 size_allocate_global_button (PsppireSheet *sheet)
1950 {
1951   GtkAllocation allocation;
1952
1953   if (!sheet->column_titles_visible) return;
1954   if (!sheet->row_titles_visible) return;
1955
1956   gtk_widget_size_request (sheet->button, NULL);
1957
1958   allocation.x = 0;
1959   allocation.y = 0;
1960   allocation.width = sheet->row_title_area.width;
1961   allocation.height = sheet->column_title_area.height;
1962
1963   gtk_widget_size_allocate (sheet->button, &allocation);
1964 }
1965
1966 static void
1967 global_button_clicked (GtkWidget *widget, gpointer data)
1968 {
1969   psppire_sheet_click_cell (PSPPIRE_SHEET (data), -1, -1);
1970 }
1971
1972
1973 static void
1974 psppire_sheet_unrealize (GtkWidget *widget)
1975 {
1976   PsppireSheet *sheet;
1977
1978   g_return_if_fail (widget != NULL);
1979   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1980
1981   sheet = PSPPIRE_SHEET (widget);
1982
1983   gdk_cursor_unref (sheet->cursor_drag);
1984   sheet->cursor_drag = NULL;
1985
1986   gdk_colormap_free_colors (gtk_widget_get_colormap (widget),
1987                             sheet->color, n_COLORS);
1988
1989   g_object_unref (sheet->xor_gc);
1990   g_object_unref (sheet->fg_gc);
1991   g_object_unref (sheet->bg_gc);
1992
1993   destroy_hover_window (sheet->hover_window);
1994
1995   gdk_window_destroy (sheet->sheet_window);
1996   gdk_window_destroy (sheet->column_title_window);
1997   gdk_window_destroy (sheet->row_title_window);
1998
1999   gtk_widget_unparent (sheet->entry_widget);
2000   if (sheet->button != NULL)
2001     gtk_widget_unparent (sheet->button);
2002
2003   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
2004     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
2005 }
2006
2007 static void
2008 psppire_sheet_map (GtkWidget *widget)
2009 {
2010   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2011
2012   g_return_if_fail (widget != NULL);
2013   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
2014
2015   if (!GTK_WIDGET_MAPPED (widget))
2016     {
2017       GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
2018
2019       gdk_window_show (widget->window);
2020       gdk_window_show (sheet->sheet_window);
2021
2022       if (sheet->column_titles_visible)
2023         {
2024           draw_column_title_buttons (sheet);
2025           gdk_window_show (sheet->column_title_window);
2026         }
2027       if (sheet->row_titles_visible)
2028         {
2029           draw_row_title_buttons (sheet);
2030           gdk_window_show (sheet->row_title_window);
2031         }
2032
2033       if (!GTK_WIDGET_MAPPED (sheet->entry_widget)
2034           && sheet->active_cell.row >= 0
2035           && sheet->active_cell.col >= 0 )
2036         {
2037           gtk_widget_show (sheet->entry_widget);
2038           gtk_widget_map (sheet->entry_widget);
2039         }
2040
2041       if (!GTK_WIDGET_MAPPED (sheet->button))
2042         {
2043           gtk_widget_show (sheet->button);
2044           gtk_widget_map (sheet->button);
2045         }
2046
2047       redraw_range (sheet, NULL);
2048       change_active_cell (sheet,
2049                           sheet->active_cell.row,
2050                           sheet->active_cell.col);
2051     }
2052 }
2053
2054 static void
2055 psppire_sheet_unmap (GtkWidget *widget)
2056 {
2057   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2058
2059   if (!GTK_WIDGET_MAPPED (widget))
2060     return;
2061
2062   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
2063
2064   gdk_window_hide (sheet->sheet_window);
2065   if (sheet->column_titles_visible)
2066     gdk_window_hide (sheet->column_title_window);
2067   if (sheet->row_titles_visible)
2068     gdk_window_hide (sheet->row_title_window);
2069   gdk_window_hide (widget->window);
2070
2071   gtk_widget_unmap (sheet->entry_widget);
2072   gtk_widget_unmap (sheet->button);
2073   gtk_widget_unmap (sheet->hover_window->window);
2074 }
2075
2076 /* get cell attributes of the given cell */
2077 /* TRUE means that the cell is currently allocated */
2078 static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
2079                                               gint row, gint col,
2080                                               PsppireSheetCellAttr *attributes);
2081
2082
2083
2084 static void
2085 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
2086 {
2087   PangoLayout *layout;
2088   PangoRectangle text;
2089   PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc;
2090   gint font_height;
2091
2092   gchar *label;
2093
2094   PsppireSheetCellAttr attributes;
2095   GdkRectangle area;
2096
2097   g_return_if_fail (sheet != NULL);
2098
2099   /* bail now if we aren't yet drawable */
2100   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
2101
2102   if (row < 0 ||
2103       row >= psppire_axis_unit_count (sheet->vaxis))
2104     return;
2105
2106   if (col < 0 ||
2107       col >= psppire_axis_unit_count (sheet->haxis))
2108     return;
2109
2110   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2111
2112   /* select GC for background rectangle */
2113   gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
2114   gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
2115
2116   rectangle_from_cell (sheet, row, col, &area);
2117
2118   gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
2119
2120   if (sheet->show_grid)
2121     {
2122       gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]);
2123
2124       gdk_draw_rectangle (sheet->sheet_window,
2125                           sheet->bg_gc,
2126                           FALSE,
2127                           area.x, area.y,
2128                           area.width, area.height);
2129     }
2130
2131
2132   label = psppire_sheet_cell_get_text (sheet, row, col);
2133   if (NULL == label)
2134     return;
2135
2136
2137   layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label);
2138   dispose_string (sheet, label);
2139
2140
2141   pango_layout_set_font_description (layout, font_desc);
2142
2143   pango_layout_get_pixel_extents (layout, NULL, &text);
2144
2145   gdk_gc_set_clip_rectangle (sheet->fg_gc, &area);
2146
2147   font_height = pango_font_description_get_size (font_desc);
2148   if ( !pango_font_description_get_size_is_absolute (font_desc))
2149     font_height /= PANGO_SCALE;
2150
2151
2152   if ( sheet->cell_padding )
2153     {
2154       area.x += sheet->cell_padding->left;
2155       area.width -= sheet->cell_padding->right
2156         + sheet->cell_padding->left;
2157
2158       area.y += sheet->cell_padding->top;
2159       area.height -= sheet->cell_padding->bottom
2160         +
2161         sheet->cell_padding->top;
2162     }
2163
2164   /* Centre the text vertically */
2165   area.y += (area.height - font_height) / 2.0;
2166
2167   switch (attributes.justification)
2168     {
2169     case GTK_JUSTIFY_RIGHT:
2170       area.x += area.width - text.width;
2171       break;
2172     case GTK_JUSTIFY_CENTER:
2173       area.x += (area.width - text.width) / 2.0;
2174       break;
2175     case GTK_JUSTIFY_LEFT:
2176       /* Do nothing */
2177       break;
2178     default:
2179       g_critical ("Unhandled justification %d in column %d\n",
2180                   attributes.justification, col);
2181       break;
2182     }
2183
2184   gdk_draw_layout (sheet->sheet_window, sheet->fg_gc,
2185                    area.x,
2186                    area.y,
2187                    layout);
2188
2189   gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL);
2190   g_object_unref (layout);
2191 }
2192
2193
2194 static void
2195 draw_sheet_region (PsppireSheet *sheet, GdkRegion *region)
2196 {
2197   PsppireSheetRange range;
2198   GdkRectangle area;
2199   gint y, x;
2200   gint i, j;
2201
2202   PsppireSheetRange drawing_range;
2203
2204   gdk_region_get_clipbox (region, &area);
2205
2206   y = area.y + sheet->vadjustment->value;
2207   x = area.x + sheet->hadjustment->value;
2208
2209   if ( sheet->column_titles_visible)
2210     y -= sheet->column_title_area.height;
2211
2212   if ( sheet->row_titles_visible)
2213     x -= sheet->row_title_area.width;
2214
2215   maximize_int (&x, 0);
2216   maximize_int (&y, 0);
2217
2218   range.row0 = row_from_ypixel (sheet, y);
2219   range.rowi = row_from_ypixel (sheet, y + area.height);
2220
2221   range.col0 = column_from_xpixel (sheet, x);
2222   range.coli = column_from_xpixel (sheet, x + area.width);
2223
2224   g_return_if_fail (sheet != NULL);
2225   g_return_if_fail (PSPPIRE_SHEET (sheet));
2226
2227   if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return;
2228   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2229   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
2230
2231
2232   drawing_range.row0 = MAX (range.row0, min_visible_row (sheet));
2233   drawing_range.col0 = MAX (range.col0, min_visible_column (sheet));
2234   drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet));
2235   drawing_range.coli = MIN (range.coli, max_visible_column (sheet));
2236
2237   g_return_if_fail (drawing_range.rowi >= drawing_range.row0);
2238   g_return_if_fail (drawing_range.coli >= drawing_range.col0);
2239
2240   for (i = drawing_range.row0; i <= drawing_range.rowi; i++)
2241     {
2242       for (j = drawing_range.col0; j <= drawing_range.coli; j++)
2243         psppire_sheet_cell_draw (sheet, i, j);
2244     }
2245
2246   if (sheet->select_status == PSPPIRE_SHEET_NORMAL &&
2247       sheet->active_cell.row >= drawing_range.row0 &&
2248       sheet->active_cell.row <= drawing_range.rowi &&
2249       sheet->active_cell.col >= drawing_range.col0 &&
2250       sheet->active_cell.col <= drawing_range.coli)
2251     psppire_sheet_show_entry_widget (sheet);
2252 }
2253
2254 static void
2255 psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col,
2256                         GtkJustification justification,
2257                         const gchar *text)
2258 {
2259   PsppireSheetModel *model ;
2260   gchar *old_text ;
2261
2262   g_return_if_fail (sheet != NULL);
2263   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2264
2265   if (col >= psppire_axis_unit_count (sheet->haxis)
2266       || row >= psppire_axis_unit_count (sheet->vaxis))
2267     return;
2268
2269   if (col < 0 || row < 0) return;
2270
2271   model = psppire_sheet_get_model (sheet);
2272
2273   old_text = psppire_sheet_model_get_string (model, row, col);
2274
2275   if (0 != g_strcmp0 (old_text, text))
2276     {
2277       g_signal_handler_block    (sheet->model, sheet->update_handler_id);
2278       psppire_sheet_model_set_string (model, text, row, col);
2279       g_signal_handler_unblock  (sheet->model, sheet->update_handler_id);
2280     }
2281
2282   if ( psppire_sheet_model_free_strings (model))
2283     g_free (old_text);
2284 }
2285
2286
2287 void
2288 psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column)
2289 {
2290   PsppireSheetRange range;
2291
2292   g_return_if_fail (sheet != NULL);
2293   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2294   if (column >= psppire_axis_unit_count (sheet->haxis) ||
2295       row >= psppire_axis_unit_count (sheet->vaxis)) return;
2296
2297   if (column < 0 || row < 0) return;
2298
2299   range.row0 = row;
2300   range.rowi = row;
2301   range.col0 = min_visible_column (sheet);
2302   range.coli = max_visible_column (sheet);
2303
2304   psppire_sheet_real_cell_clear (sheet, row, column);
2305
2306   redraw_range (sheet, &range);
2307 }
2308
2309 static void
2310 psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column)
2311 {
2312   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
2313
2314   gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column);
2315
2316   if (old_text && strlen (old_text) > 0 )
2317     {
2318       psppire_sheet_model_datum_clear (model, row, column);
2319     }
2320
2321   dispose_string (sheet, old_text);
2322 }
2323
2324 gchar *
2325 psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col)
2326 {
2327   PsppireSheetModel *model;
2328   g_return_val_if_fail (sheet != NULL, NULL);
2329   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
2330
2331   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis))
2332     return NULL;
2333   if (col < 0 || row < 0) return NULL;
2334
2335   model = psppire_sheet_get_model (sheet);
2336
2337   if ( !model )
2338     return NULL;
2339
2340   return psppire_sheet_model_get_string (model, row, col);
2341 }
2342
2343
2344 /* Convert X, Y (in pixels) to *ROW, *COLUMN
2345    If the function returns FALSE, then the results will be unreliable.
2346 */
2347 static gboolean
2348 psppire_sheet_get_pixel_info (PsppireSheet *sheet,
2349                               gint x,
2350                               gint y,
2351                               gint *row,
2352                               gint *column)
2353 {
2354   gint trow, tcol;
2355   *row = -G_MAXINT;
2356   *column = -G_MAXINT;
2357
2358   g_return_val_if_fail (sheet != NULL, 0);
2359   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2360
2361   /* bounds checking, return false if the user clicked
2362      on a blank area */
2363   if (y < 0)
2364     return FALSE;
2365
2366   if (x < 0)
2367     return FALSE;
2368
2369   if ( sheet->column_titles_visible)
2370     y -= sheet->column_title_area.height;
2371
2372   y += sheet->vadjustment->value;
2373
2374   if ( y < 0 && sheet->column_titles_visible)
2375     {
2376       trow = -1;
2377     }
2378   else
2379     {
2380       trow = row_from_ypixel (sheet, y);
2381       if (trow > psppire_axis_unit_count (sheet->vaxis))
2382         return FALSE;
2383     }
2384
2385   *row = trow;
2386
2387   if ( sheet->row_titles_visible)
2388     x -= sheet->row_title_area.width;
2389
2390   x += sheet->hadjustment->value;
2391
2392   if ( x < 0 && sheet->row_titles_visible)
2393     {
2394       tcol = -1;
2395     }
2396   else
2397     {
2398       tcol = column_from_xpixel (sheet, x);
2399       if (tcol > psppire_axis_unit_count (sheet->haxis))
2400         return FALSE;
2401     }
2402
2403   *column = tcol;
2404
2405   return TRUE;
2406 }
2407
2408 gboolean
2409 psppire_sheet_get_cell_area (PsppireSheet *sheet,
2410                              gint row,
2411                              gint column,
2412                              GdkRectangle *area)
2413 {
2414   g_return_val_if_fail (sheet != NULL, 0);
2415   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2416
2417   if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis))
2418     return FALSE;
2419
2420   area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column);
2421   area->y = (row == -1)    ? 0 : psppire_axis_start_pixel (sheet->vaxis, row);
2422
2423   area->width= (column == -1) ? sheet->row_title_area.width
2424     : psppire_axis_unit_size (sheet->haxis, column);
2425
2426   area->height= (row == -1) ? sheet->column_title_area.height
2427     : psppire_axis_unit_size (sheet->vaxis, row);
2428
2429   return TRUE;
2430 }
2431
2432 void
2433 psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col)
2434 {
2435   g_return_if_fail (sheet != NULL);
2436   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2437
2438   if (row < -1 || col < -1)
2439     return;
2440
2441   if (row >= psppire_axis_unit_count (sheet->vaxis)
2442       ||
2443       col >= psppire_axis_unit_count (sheet->haxis))
2444     return;
2445
2446   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2447     return;
2448
2449   if ( row == -1 || col == -1)
2450     {
2451       psppire_sheet_hide_entry_widget (sheet);
2452       return;
2453     }
2454
2455   change_active_cell (sheet, row, col);
2456 }
2457
2458 void
2459 psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column)
2460 {
2461   g_return_if_fail (sheet != NULL);
2462   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2463
2464   if ( row ) *row = sheet->active_cell.row;
2465   if (column) *column = sheet->active_cell.col;
2466 }
2467
2468 static void
2469 entry_load_text (PsppireSheet *sheet)
2470 {
2471   gint row, col;
2472   const char *text;
2473   GtkJustification justification;
2474   PsppireSheetCellAttr attributes;
2475
2476   if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return;
2477   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2478
2479   row = sheet->active_cell.row;
2480   col = sheet->active_cell.col;
2481
2482   if (row < 0 || col < 0) return;
2483
2484   text = gtk_entry_get_text (psppire_sheet_get_entry (sheet));
2485
2486   if (text && strlen (text) > 0)
2487     {
2488       psppire_sheet_get_attributes (sheet, row, col, &attributes);
2489       justification = attributes.justification;
2490       psppire_sheet_set_cell (sheet, row, col, justification, text);
2491     }
2492 }
2493
2494
2495 static void
2496 psppire_sheet_hide_entry_widget (PsppireSheet *sheet)
2497 {
2498   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2499     return;
2500
2501   if (sheet->active_cell.row < 0 ||
2502       sheet->active_cell.col < 0) return;
2503
2504   gtk_widget_hide (sheet->entry_widget);
2505   gtk_widget_unmap (sheet->entry_widget);
2506
2507   GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2508 }
2509
2510 static void
2511 change_active_cell (PsppireSheet *sheet, gint row, gint col)
2512 {
2513   gint old_row, old_col;
2514
2515   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2516
2517   if (row < 0 || col < 0)
2518     return;
2519
2520   if ( row > psppire_axis_unit_count (sheet->vaxis)
2521        || col > psppire_axis_unit_count (sheet->haxis))
2522     return;
2523
2524   old_row = sheet->active_cell.row;
2525   old_col = sheet->active_cell.col;
2526
2527   entry_load_text (sheet);
2528
2529   /* Erase the old cell border */
2530   psppire_sheet_draw_active_cell (sheet);
2531
2532   sheet->active_cell.row = row;
2533   sheet->active_cell.col = col;
2534
2535   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2536
2537   GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2538
2539   psppire_sheet_draw_active_cell (sheet);
2540   psppire_sheet_show_entry_widget (sheet);
2541
2542   GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2543
2544   g_signal_emit (sheet, sheet_signals [ACTIVATE], 0,
2545                  row, col, old_row, old_col);
2546
2547 }
2548
2549 static void
2550 psppire_sheet_show_entry_widget (PsppireSheet *sheet)
2551 {
2552   GtkEntry *sheet_entry;
2553   PsppireSheetCellAttr attributes;
2554
2555   gint row, col;
2556
2557   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2558
2559   row = sheet->active_cell.row;
2560   col = sheet->active_cell.col;
2561
2562   /* Don't show the active cell, if there is no active cell: */
2563   if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */
2564     return;
2565
2566   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2567   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2568   if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return;
2569
2570   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2571
2572   sheet_entry = psppire_sheet_get_entry (sheet);
2573
2574   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2575
2576   if (GTK_IS_ENTRY (sheet_entry))
2577     {
2578       gchar *text = psppire_sheet_cell_get_text (sheet, row, col);
2579       const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry));
2580
2581       if ( ! text )
2582         text = g_strdup ("");
2583
2584       if (strcmp (old_text, text) != 0)
2585         gtk_entry_set_text (sheet_entry, text);
2586       
2587       dispose_string (sheet, text);
2588
2589       {
2590         switch (attributes.justification)
2591           {
2592           case GTK_JUSTIFY_RIGHT:
2593             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0);
2594             break;
2595           case GTK_JUSTIFY_CENTER:
2596             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5);
2597             break;
2598           case GTK_JUSTIFY_LEFT:
2599           default:
2600             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0);
2601             break;
2602           }
2603       }
2604     }
2605
2606   psppire_sheet_size_allocate_entry (sheet);
2607
2608   gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry),
2609                             psppire_sheet_model_is_editable (sheet->model,
2610                                                              row, col));
2611   gtk_widget_map (sheet->entry_widget);
2612 }
2613
2614 static gboolean
2615 psppire_sheet_draw_active_cell (PsppireSheet *sheet)
2616 {
2617   gint row, col;
2618   PsppireSheetRange range;
2619
2620   row = sheet->active_cell.row;
2621   col = sheet->active_cell.col;
2622
2623   if (row < 0 || col < 0) return FALSE;
2624
2625   if (!psppire_sheet_cell_isvisible (sheet, row, col))
2626     return FALSE;
2627
2628   range.col0 = range.coli = col;
2629   range.row0 = range.rowi = row;
2630
2631   psppire_sheet_draw_border (sheet, range);
2632
2633   return FALSE;
2634 }
2635
2636
2637
2638 static void
2639 psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
2640 {
2641   GdkRectangle area;
2642
2643   rectangle_from_range (sheet, &new_range, &area);
2644
2645   area.width ++;
2646   area.height ++;
2647
2648   gdk_gc_set_clip_rectangle (sheet->xor_gc, &area);
2649
2650   area.x += sheet->cell_padding->left / 2;
2651   area.y += sheet->cell_padding->top / 2;
2652   area.width -= (sheet->cell_padding->left + sheet->cell_padding->right ) / 2;
2653   area.height -= (sheet->cell_padding->top + sheet->cell_padding->bottom ) / 2;
2654
2655   gdk_draw_rectangle (sheet->sheet_window,  sheet->xor_gc,
2656                       FALSE,
2657                       area.x,
2658                       area.y,
2659                       area.width,
2660                       area.height);
2661
2662   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
2663 }
2664
2665 \f
2666
2667 /* Selection related functions */
2668
2669 void
2670 psppire_sheet_select_row (PsppireSheet *sheet,  gint row)
2671 {
2672   GdkRectangle area;
2673   sheet->select_status = PSPPIRE_SHEET_ROW_SELECTED;
2674
2675   sheet->range.col0 = sheet->range.coli = -1;
2676   sheet->range.row0 = sheet->range.rowi = row;
2677
2678   rectangle_from_range (sheet, &sheet->range, &area);
2679   area.x++;
2680   area.y++;
2681
2682   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2683
2684   g_signal_emit (sheet, sheet_signals [SELECT_ROW], 0, row);
2685 }
2686
2687 void
2688 psppire_sheet_select_column (PsppireSheet *sheet,  gint column)
2689 {
2690   GdkRectangle area;
2691   sheet->select_status = PSPPIRE_SHEET_COLUMN_SELECTED;
2692
2693   sheet->range.col0 = sheet->range.coli = column;
2694   sheet->range.row0 = sheet->range.rowi = -1;
2695
2696   rectangle_from_range (sheet, &sheet->range, &area);
2697   area.x++;
2698   area.y++;
2699
2700   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2701
2702   g_signal_emit (sheet, sheet_signals [SELECT_COLUMN], 0, column);
2703 }
2704
2705
2706 void
2707 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
2708 {
2709   GdkRectangle area;
2710   sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
2711
2712   sheet->range = *range;
2713
2714   rectangle_from_range (sheet, range, &area);
2715   area.x++;
2716   area.y++;
2717   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2718 }
2719
2720
2721 void
2722 psppire_sheet_unselect_range (PsppireSheet *sheet)
2723 {
2724   sheet->select_status = PSPPIRE_SHEET_NORMAL;
2725
2726   if (sheet->sheet_window != NULL)
2727     {
2728       GdkRectangle area;
2729
2730       rectangle_from_range (sheet, &sheet->range, &area);
2731       area.x++;
2732       area.y++;
2733       gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2734     }
2735
2736   g_signal_emit (sheet, sheet_signals [SELECT_COLUMN], 0, -1);
2737   g_signal_emit (sheet, sheet_signals [SELECT_ROW], 0, -1);  
2738 }
2739
2740 void
2741 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
2742 {
2743   g_return_if_fail (sheet != NULL);
2744   *range = sheet->range;
2745 }
2746 \f
2747
2748 static gint
2749 psppire_sheet_expose (GtkWidget *widget, GdkEventExpose *event)
2750 {
2751   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2752
2753   g_return_val_if_fail (event != NULL, FALSE);
2754
2755   if (!GTK_WIDGET_DRAWABLE (widget))
2756     return FALSE;
2757
2758   /* exposure events on the sheet */
2759   if (event->window == sheet->row_title_window &&
2760       sheet->row_titles_visible)
2761     {
2762       draw_row_title_buttons_range (sheet,
2763                                     min_visible_row (sheet),
2764                                     max_visible_row (sheet));
2765     }
2766
2767   if (event->window == sheet->column_title_window &&
2768       sheet->column_titles_visible)
2769     {
2770       draw_column_title_buttons_range (sheet,
2771                                        min_visible_column (sheet),
2772                                        max_visible_column (sheet));
2773     }
2774
2775   if (event->window == sheet->sheet_window)
2776     {
2777       draw_sheet_region (sheet, event->region);
2778
2779       if (sheet->select_status != PSPPIRE_SHEET_NORMAL)
2780         {
2781           GdkRectangle area;
2782
2783           rectangle_from_range (sheet, &sheet->range, &area);
2784               
2785           gdk_draw_rectangle (sheet->sheet_window,
2786                               sheet->xor_gc,
2787                               TRUE,
2788                               area.x + 1, area.y + 1,
2789                               area.width, area.height);
2790         }
2791
2792
2793       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
2794         {
2795           GdkRectangle rect;
2796           PsppireSheetRange range;
2797           range.row0 = range.rowi =  sheet->active_cell.row;
2798           range.col0 = range.coli =  sheet->active_cell.col;
2799
2800           rectangle_from_range (sheet, &range, &rect);
2801
2802           if (GDK_OVERLAP_RECTANGLE_OUT !=
2803               gdk_region_rect_in (event->region, &rect))
2804             {
2805               psppire_sheet_draw_active_cell (sheet);
2806             }
2807         }
2808
2809     }
2810
2811   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
2812
2813   return FALSE;
2814 }
2815
2816
2817 static gboolean
2818 psppire_sheet_button_press (GtkWidget *widget, GdkEventButton *event)
2819 {
2820   PsppireSheet *sheet;
2821   GdkModifierType mods;
2822   gint x, y;
2823   gint  row, column;
2824
2825
2826   g_return_val_if_fail (widget != NULL, FALSE);
2827   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
2828   g_return_val_if_fail (event != NULL, FALSE);
2829
2830   sheet = PSPPIRE_SHEET (widget);
2831
2832   /* Cancel any pending tooltips */
2833   if (sheet->motion_timer)
2834     {
2835       g_source_remove (sheet->motion_timer);
2836       sheet->motion_timer = 0;
2837     }
2838
2839   gtk_widget_get_pointer (widget, &x, &y);
2840   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2841
2842
2843   if (event->window == sheet->column_title_window)
2844     {
2845       sheet->x_drag = event->x;
2846       g_signal_emit (sheet,
2847                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
2848                      column, event);
2849
2850       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2851         {
2852           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2853             g_signal_emit (sheet,
2854                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
2855         }
2856     }
2857   
2858   if (event->window == sheet->row_title_window)
2859     {
2860       g_signal_emit (sheet,
2861                      sheet_signals[BUTTON_EVENT_ROW], 0,
2862                      row, event);
2863
2864       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2865         {
2866           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2867             g_signal_emit (sheet,
2868                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
2869         }
2870     }
2871
2872   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
2873
2874   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
2875
2876
2877   /* press on resize windows */
2878   if (event->window == sheet->column_title_window)
2879     {
2880       sheet->x_drag = event->x;
2881
2882       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
2883         {
2884           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
2885           gdk_pointer_grab (sheet->column_title_window, FALSE,
2886                             GDK_POINTER_MOTION_HINT_MASK |
2887                             GDK_BUTTON1_MOTION_MASK |
2888                             GDK_BUTTON_RELEASE_MASK,
2889                             NULL, NULL, event->time);
2890
2891           draw_xor_vline (sheet);
2892           return TRUE;
2893         }
2894     }
2895
2896   if (event->window == sheet->row_title_window)
2897     {
2898       sheet->y_drag = event->y;
2899
2900       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
2901         {
2902           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
2903           gdk_pointer_grab (sheet->row_title_window, FALSE,
2904                             GDK_POINTER_MOTION_HINT_MASK |
2905                             GDK_BUTTON1_MOTION_MASK |
2906                             GDK_BUTTON_RELEASE_MASK,
2907                             NULL, NULL, event->time);
2908
2909           draw_xor_hline (sheet);
2910           return TRUE;
2911         }
2912     }
2913
2914   /* the sheet itself does not handle other than single click events */
2915   if (event->type != GDK_BUTTON_PRESS) return FALSE;
2916
2917   /* selections on the sheet */
2918   if (event->window == sheet->sheet_window)
2919     {
2920       gtk_widget_get_pointer (widget, &x, &y);
2921       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2922       gdk_pointer_grab (sheet->sheet_window, FALSE,
2923                         GDK_POINTER_MOTION_HINT_MASK |
2924                         GDK_BUTTON1_MOTION_MASK |
2925                         GDK_BUTTON_RELEASE_MASK,
2926                         NULL, NULL, event->time);
2927       gtk_grab_add (GTK_WIDGET (sheet));
2928
2929       if ( sheet->select_status == PSPPIRE_SHEET_NORMAL)
2930         {
2931           sheet->range.row0 = row;
2932           sheet->range.col0 = column;
2933         }
2934       else
2935         {
2936           psppire_sheet_unselect_range (sheet);
2937         }
2938       psppire_sheet_click_cell (sheet, row, column);
2939     }
2940
2941   if (event->window == sheet->column_title_window)
2942     {
2943       gtk_widget_get_pointer (widget, &x, &y);
2944       if ( sheet->row_titles_visible)
2945         x -= sheet->row_title_area.width;
2946
2947       x += sheet->hadjustment->value;
2948
2949       column = column_from_xpixel (sheet, x);
2950
2951       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2952         {
2953           gtk_grab_add (GTK_WIDGET (sheet));
2954           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2955         }
2956     }
2957
2958   if (event->window == sheet->row_title_window)
2959     {
2960       gtk_widget_get_pointer (widget, &x, &y);
2961       if ( sheet->column_titles_visible)
2962         y -= sheet->column_title_area.height;
2963
2964       y += sheet->vadjustment->value;
2965
2966       row = row_from_ypixel (sheet, y);
2967       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2968         {
2969           gtk_grab_add (GTK_WIDGET (sheet));
2970           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2971         }
2972     }
2973
2974   return TRUE;
2975 }
2976
2977 static gboolean
2978 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
2979 {
2980   PsppireSheetCell cell;
2981   gboolean forbid_move;
2982
2983   cell.row = row;
2984   cell.col = column;
2985
2986   if (row >= psppire_axis_unit_count (sheet->vaxis)
2987       || column >= psppire_axis_unit_count (sheet->haxis))
2988     {
2989       return FALSE;
2990     }
2991
2992   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
2993                  &sheet->active_cell,
2994                  &cell,
2995                  &forbid_move);
2996
2997   if (forbid_move)
2998     {
2999       if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3000         return FALSE;
3001
3002       row = sheet->active_cell.row;
3003       column = sheet->active_cell.col;
3004
3005       change_active_cell (sheet, row, column);
3006       return FALSE;
3007     }
3008
3009   if (row == -1 && column >= 0)
3010     {
3011       psppire_sheet_select_column (sheet, column);
3012       return TRUE;
3013     }
3014
3015   if (column == -1 && row >= 0)
3016     {
3017       psppire_sheet_select_row (sheet, row);
3018       return TRUE;
3019     }
3020
3021   if (row == -1 && column == -1)
3022     {
3023       sheet->range.row0 = 0;
3024       sheet->range.col0 = 0;
3025       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3026       sheet->range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
3027       return TRUE;
3028     }
3029
3030   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3031     change_active_cell (sheet, row, column);
3032
3033   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3034
3035   return TRUE;
3036 }
3037
3038 static gint
3039 psppire_sheet_button_release (GtkWidget *widget,
3040                               GdkEventButton *event)
3041 {
3042   GdkDisplay *display = gtk_widget_get_display (widget);
3043
3044   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3045
3046   /* release on resize windows */
3047   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3048     {
3049       gint width;
3050       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3051       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3052
3053       gdk_display_pointer_ungrab (display, event->time);
3054       draw_xor_vline (sheet);
3055
3056       width = event->x -
3057         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3058         + sheet->hadjustment->value;
3059
3060       set_column_width (sheet, sheet->drag_cell.col, width);
3061
3062       return TRUE;
3063     }
3064
3065   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3066     {
3067       gint height;
3068       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3069       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3070
3071       gdk_display_pointer_ungrab (display, event->time);
3072       draw_xor_hline (sheet);
3073
3074       height = event->y -
3075         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3076         sheet->vadjustment->value;
3077
3078       set_row_height (sheet, sheet->drag_cell.row, height);
3079
3080       return TRUE;
3081     }
3082
3083   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3084     {
3085       PsppireSheetRange old_range;
3086       draw_xor_rectangle (sheet, sheet->drag_range);
3087       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3088       gdk_display_pointer_ungrab (display, event->time);
3089
3090       psppire_sheet_unselect_range (sheet);
3091
3092       old_range = sheet->range;
3093       sheet->range = sheet->drag_range;
3094       sheet->drag_range = old_range;
3095       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3096                      &sheet->drag_range, &sheet->range);
3097       psppire_sheet_select_range (sheet, &sheet->range);
3098     }
3099
3100   if (PSPPIRE_SHEET_IN_SELECTION (sheet))
3101     {
3102       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3103       sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3104
3105       change_active_cell (sheet, sheet->active_cell.row,
3106                           sheet->active_cell.col);
3107     }
3108
3109   gdk_display_pointer_ungrab (display, event->time);
3110   gtk_grab_remove (GTK_WIDGET (sheet));
3111
3112   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3113
3114   return TRUE;
3115 }
3116
3117 \f
3118
3119
3120
3121 /* Shamelessly lifted from gtktooltips */
3122 static gboolean
3123 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3124 {
3125   GtkRequisition req;
3126
3127   gtk_widget_size_request (tip_window, &req);
3128   gtk_paint_flat_box (tip_window->style, tip_window->window,
3129                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3130                       NULL, GTK_WIDGET(tip_window), "tooltip",
3131                       0, 0, req.width, req.height);
3132
3133   return FALSE;
3134 }
3135
3136 static void
3137 destroy_hover_window (PsppireSheetHoverTitle *h)
3138 {
3139   gtk_widget_destroy (h->window);
3140   g_free (h);
3141 }
3142
3143 static PsppireSheetHoverTitle *
3144 create_hover_window (void)
3145 {
3146   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3147
3148   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3149
3150 #if GTK_CHECK_VERSION (2, 9, 0)
3151   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3152                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3153 #endif
3154
3155   gtk_widget_set_app_paintable (hw->window, TRUE);
3156   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3157   gtk_widget_set_name (hw->window, "gtk-tooltips");
3158   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3159
3160   g_signal_connect (hw->window,
3161                     "expose_event",
3162                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3163                     NULL);
3164
3165   hw->label = gtk_label_new (NULL);
3166
3167
3168   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3169   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3170
3171   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3172
3173   gtk_widget_show (hw->label);
3174
3175   g_signal_connect (hw->window,
3176                     "destroy",
3177                     G_CALLBACK (gtk_widget_destroyed),
3178                     &hw->window);
3179
3180   return hw;
3181 }
3182
3183 #define HOVER_WINDOW_Y_OFFSET 2
3184
3185 static void
3186 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3187                const gchar *subtitle)
3188 {
3189   gint x, y;
3190   gint px, py;
3191   gint width;
3192
3193   if ( ! subtitle )
3194     return;
3195
3196   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3197                       subtitle);
3198
3199
3200   sheet->hover_window->row = row;
3201   sheet->hover_window->column = column;
3202
3203   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3204
3205   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3206
3207   gtk_widget_show (sheet->hover_window->window);
3208
3209   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3210
3211   if (row == -1 )
3212     {
3213       x += px;
3214       x -= width / 2;
3215       y += sheet->column_title_area.y;
3216       y += sheet->column_title_area.height;
3217       y += HOVER_WINDOW_Y_OFFSET;
3218     }
3219
3220   if ( column == -1 )
3221     {
3222       y += py;
3223       x += sheet->row_title_area.x;
3224       x += sheet->row_title_area.width * 2 / 3.0;
3225     }
3226
3227   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3228                    x, y);
3229 }
3230
3231 static gboolean
3232 motion_timeout_callback (gpointer data)
3233 {
3234   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3235   gint x, y;
3236   gint row, column;
3237
3238   gdk_threads_enter ();
3239   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3240
3241   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3242     {
3243       if (sheet->row_title_under && row >= 0)
3244         {
3245           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3246
3247           show_subtitle (sheet, row, -1, text);
3248           g_free (text);
3249         }
3250
3251       if (sheet->column_title_under && column >= 0)
3252         {
3253           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3254                                                                  column);
3255
3256           show_subtitle (sheet, -1, column, text);
3257
3258           g_free (text);
3259         }
3260     }
3261
3262   gdk_threads_leave ();
3263   return FALSE;
3264 }
3265
3266 static gboolean
3267 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3268 {
3269   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3270   GdkModifierType mods;
3271   GdkCursorType new_cursor;
3272   gint x, y;
3273   gint row, column;
3274   GdkDisplay *display;
3275
3276   g_return_val_if_fail (event != NULL, FALSE);
3277
3278   display = gtk_widget_get_display (widget);
3279
3280   /* selections on the sheet */
3281   x = event->x;
3282   y = event->y;
3283
3284   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3285     {
3286       if ( sheet->motion_timer > 0 )
3287         g_source_remove (sheet->motion_timer);
3288       sheet->motion_timer =
3289         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3290     }
3291   else
3292     {
3293       gint row, column;
3294       gint wx, wy;
3295       gtk_widget_get_pointer (widget, &wx, &wy);
3296
3297       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3298         {
3299           if ( row != sheet->hover_window->row ||
3300                column != sheet->hover_window->column)
3301             {
3302               gtk_widget_hide (sheet->hover_window->window);
3303             }
3304         }
3305     }
3306
3307   if (event->window == sheet->column_title_window)
3308     {
3309       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3310           on_column_boundary (sheet, x, &column))
3311         {
3312           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3313           if (new_cursor != sheet->cursor_drag->type)
3314             {
3315               gdk_cursor_unref (sheet->cursor_drag);
3316               sheet->cursor_drag =
3317                 gdk_cursor_new_for_display (display, new_cursor);
3318
3319               gdk_window_set_cursor (sheet->column_title_window,
3320                                      sheet->cursor_drag);
3321             }
3322         }
3323       else
3324         {
3325           new_cursor = GDK_TOP_LEFT_ARROW;
3326           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3327               new_cursor != sheet->cursor_drag->type)
3328             {
3329               gdk_cursor_unref (sheet->cursor_drag);
3330               sheet->cursor_drag =
3331                 gdk_cursor_new_for_display (display, new_cursor);
3332               gdk_window_set_cursor (sheet->column_title_window,
3333                                      sheet->cursor_drag);
3334             }
3335         }
3336     }
3337   else if (event->window == sheet->row_title_window)
3338     {
3339       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3340           on_row_boundary (sheet, y, &row))
3341         {
3342           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3343           if (new_cursor != sheet->cursor_drag->type)
3344             {
3345               gdk_cursor_unref (sheet->cursor_drag);
3346               sheet->cursor_drag =
3347                 gdk_cursor_new_for_display (display, new_cursor);
3348               gdk_window_set_cursor (sheet->row_title_window,
3349                                      sheet->cursor_drag);
3350             }
3351         }
3352       else
3353         {
3354           new_cursor = GDK_TOP_LEFT_ARROW;
3355           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3356               new_cursor != sheet->cursor_drag->type)
3357             {
3358               gdk_cursor_unref (sheet->cursor_drag);
3359               sheet->cursor_drag =
3360                 gdk_cursor_new_for_display (display, new_cursor);
3361               gdk_window_set_cursor (sheet->row_title_window,
3362                                      sheet->cursor_drag);
3363             }
3364         }
3365     }
3366
3367   new_cursor = GDK_PLUS;
3368   if ( event->window == sheet->sheet_window &&
3369        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3370        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3371        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3372        new_cursor != sheet->cursor_drag->type)
3373     {
3374       gdk_cursor_unref (sheet->cursor_drag);
3375       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3376       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3377     }
3378
3379   new_cursor = GDK_TOP_LEFT_ARROW;
3380   if ( event->window == sheet->sheet_window &&
3381        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ) &&
3382        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3383         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3384        new_cursor != sheet->cursor_drag->type)
3385     {
3386       gdk_cursor_unref (sheet->cursor_drag);
3387       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3388       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3389     }
3390
3391   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3392   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3393
3394   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3395     {
3396       if (event->x != sheet->x_drag)
3397         {
3398           draw_xor_vline (sheet);
3399           sheet->x_drag = event->x;
3400           draw_xor_vline (sheet);
3401         }
3402
3403       return TRUE;
3404     }
3405
3406   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3407     {
3408       if (event->y != sheet->y_drag)
3409         {
3410           draw_xor_hline (sheet);
3411           sheet->y_drag = event->y;
3412           draw_xor_hline (sheet);
3413         }
3414
3415       return TRUE;
3416     }
3417
3418   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3419     {
3420       PsppireSheetRange aux;
3421       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
3422       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
3423       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3424       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3425       sheet->x_drag = x;
3426       sheet->y_drag = y;
3427       aux = sheet->range;
3428       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3429           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3430         {
3431           aux = sheet->drag_range;
3432           sheet->drag_range.row0 = sheet->range.row0 + row;
3433           sheet->drag_range.col0 = sheet->range.col0 + column;
3434           sheet->drag_range.rowi = sheet->range.rowi + row;
3435           sheet->drag_range.coli = sheet->range.coli + column;
3436           if (aux.row0 != sheet->drag_range.row0 ||
3437               aux.col0 != sheet->drag_range.col0)
3438             {
3439               draw_xor_rectangle (sheet, aux);
3440               draw_xor_rectangle (sheet, sheet->drag_range);
3441             }
3442         }
3443       return TRUE;
3444     }
3445
3446   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3447
3448   if (sheet->select_status == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
3449       column == sheet->active_cell.col) return TRUE;
3450
3451   if ( mods & GDK_BUTTON1_MASK)
3452     {
3453       if (PSPPIRE_SHEET_IN_SELECTION (sheet) )
3454         {
3455           /* Redraw the old range */
3456           psppire_sheet_unselect_range (sheet);
3457
3458           sheet->range.rowi = row;
3459           sheet->range.coli = column;
3460
3461           /* Redraw the new range */
3462           psppire_sheet_select_range (sheet, &sheet->range);
3463         }
3464       else
3465         {
3466           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3467         }
3468     }
3469
3470   return TRUE;
3471 }
3472
3473 static gboolean
3474 psppire_sheet_crossing_notify (GtkWidget *widget,
3475                                GdkEventCrossing *event)
3476 {
3477   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3478
3479   if (event->window == sheet->column_title_window)
3480     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
3481   else if (event->window == sheet->row_title_window)
3482     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
3483
3484   if (event->type == GDK_LEAVE_NOTIFY)
3485     gtk_widget_hide (sheet->hover_window->window);
3486
3487   return TRUE;
3488 }
3489
3490
3491 static gboolean
3492 psppire_sheet_focus_in (GtkWidget     *w,
3493                         GdkEventFocus *event)
3494 {
3495   PsppireSheet *sheet = PSPPIRE_SHEET (w);
3496
3497   gtk_widget_grab_focus (sheet->entry_widget);
3498
3499   return TRUE;
3500 }
3501
3502
3503
3504 static gint
3505 psppire_sheet_entry_key_press (GtkWidget *widget,
3506                                GdkEventKey *key)
3507 {
3508   gboolean focus;
3509   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
3510   return focus;
3511 }
3512
3513
3514 /* Number of rows in a step-increment */
3515 #define ROWS_PER_STEP 1
3516
3517
3518 static void
3519 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
3520 {
3521   gint old_row = sheet->active_cell.row ;
3522   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
3523
3524   gint new_row;
3525
3526   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
3527                                       min_visible_row (sheet));
3528
3529   switch ( dir)
3530     {
3531     case GTK_SCROLL_PAGE_DOWN:
3532       gtk_adjustment_set_value (sheet->vadjustment,
3533                                 sheet->vadjustment->value +
3534                                 sheet->vadjustment->page_increment);
3535       break;
3536     case GTK_SCROLL_PAGE_UP:
3537       gtk_adjustment_set_value (sheet->vadjustment,
3538                                 sheet->vadjustment->value -
3539                                 sheet->vadjustment->page_increment);
3540
3541       break;
3542     default:
3543       g_assert_not_reached ();
3544       break;
3545     }
3546
3547
3548   vpixel += psppire_axis_start_pixel (sheet->vaxis,
3549                                       min_visible_row (sheet));
3550
3551   new_row =  row_from_ypixel (sheet, vpixel);
3552
3553   change_active_cell (sheet, new_row,
3554                       sheet->active_cell.col);
3555 }
3556
3557
3558 static void
3559 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
3560 {
3561   gint current_row = sheet->active_cell.row;
3562   gint current_col = sheet->active_cell.col;
3563   PsppireSheetCell new_cell ;
3564   gboolean forbidden = FALSE;
3565
3566   new_cell.row = current_row;
3567   new_cell.col = current_col;
3568
3569   switch ( dir)
3570     {
3571     case GTK_SCROLL_STEP_DOWN:
3572       new_cell.row++;
3573       break;
3574     case GTK_SCROLL_STEP_UP:
3575       new_cell.row--;
3576       break;
3577     case GTK_SCROLL_STEP_RIGHT:
3578       new_cell.col++;
3579       break;
3580     case GTK_SCROLL_STEP_LEFT:
3581       new_cell.col--;
3582       break;
3583     case GTK_SCROLL_STEP_FORWARD:
3584       new_cell.col++;
3585       if (new_cell.col >=
3586           psppire_sheet_model_get_column_count (sheet->model))
3587         {
3588           new_cell.col = 0;
3589           new_cell.row++;
3590         }
3591       break;
3592     case GTK_SCROLL_STEP_BACKWARD:
3593       new_cell.col--;
3594       if (new_cell.col < 0)
3595         {
3596           new_cell.col =
3597             psppire_sheet_model_get_column_count (sheet->model) - 1;
3598           new_cell.row--;
3599         }
3600       break;
3601     default:
3602       g_assert_not_reached ();
3603       break;
3604     }
3605
3606   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3607                  &sheet->active_cell,
3608                  &new_cell,
3609                  &forbidden);
3610
3611   if (forbidden)
3612     return;
3613
3614
3615   maximize_int (&new_cell.row, 0);
3616   maximize_int (&new_cell.col, 0);
3617
3618   minimize_int (&new_cell.row,
3619                 psppire_axis_unit_count (sheet->vaxis) - 1);
3620
3621   minimize_int (&new_cell.col,
3622                 psppire_axis_unit_count (sheet->haxis) - 1);
3623
3624   change_active_cell (sheet, new_cell.row, new_cell.col);
3625
3626
3627   if ( new_cell.col > max_fully_visible_column (sheet))
3628     {
3629       glong hpos  =
3630         psppire_axis_start_pixel (sheet->haxis,
3631                                   new_cell.col + 1);
3632       hpos -= sheet->hadjustment->page_size;
3633
3634       gtk_adjustment_set_value (sheet->hadjustment,
3635                                 hpos);
3636     }
3637   else if ( new_cell.col < min_fully_visible_column (sheet))
3638     {
3639       glong hpos  =
3640         psppire_axis_start_pixel (sheet->haxis,
3641                                   new_cell.col);
3642
3643       gtk_adjustment_set_value (sheet->hadjustment,
3644                                 hpos);
3645     }
3646
3647
3648   if ( new_cell.row > max_fully_visible_row (sheet))
3649     {
3650       glong vpos  =
3651         psppire_axis_start_pixel (sheet->vaxis,
3652                                   new_cell.row + 1);
3653       vpos -= sheet->vadjustment->page_size;
3654
3655       gtk_adjustment_set_value (sheet->vadjustment,
3656                                 vpos);
3657     }
3658   else if ( new_cell.row < min_fully_visible_row (sheet))
3659     {
3660       glong vpos  =
3661         psppire_axis_start_pixel (sheet->vaxis,
3662                                   new_cell.row);
3663
3664       gtk_adjustment_set_value (sheet->vadjustment,
3665                                 vpos);
3666     }
3667
3668   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3669 }
3670
3671
3672 static gboolean
3673 psppire_sheet_key_press (GtkWidget *widget,
3674                          GdkEventKey *key)
3675 {
3676   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3677
3678   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3679
3680   switch (key->keyval)
3681     {
3682     case GDK_Tab:
3683       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
3684       break;
3685     case GDK_Right:
3686       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
3687       break;
3688     case GDK_ISO_Left_Tab:
3689       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
3690       break;
3691     case GDK_Left:
3692       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
3693       break;
3694     case GDK_Return:
3695     case GDK_Down:
3696       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
3697       break;
3698     case GDK_Up:
3699       step_sheet (sheet, GTK_SCROLL_STEP_UP);
3700       break;
3701
3702     case GDK_Page_Down:
3703       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
3704       break;
3705     case GDK_Page_Up:
3706       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
3707       break;
3708
3709     case GDK_Home:
3710       gtk_adjustment_set_value (sheet->vadjustment,
3711                                 sheet->vadjustment->lower);
3712
3713       change_active_cell (sheet,  0,
3714                           sheet->active_cell.col);
3715
3716       break;
3717
3718     case GDK_End:
3719       gtk_adjustment_set_value (sheet->vadjustment,
3720                                 sheet->vadjustment->upper -
3721                                 sheet->vadjustment->page_size -
3722                                 sheet->vadjustment->page_increment);
3723
3724       /*
3725         change_active_cellx (sheet,
3726         psppire_axis_unit_count (sheet->vaxis) - 1,
3727         sheet->active_cell.col);
3728       */
3729       break;
3730     case GDK_Delete:
3731       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
3732       break;
3733     default:
3734       return FALSE;
3735       break;
3736     }
3737
3738   return TRUE;
3739 }
3740
3741 static void
3742 psppire_sheet_size_request (GtkWidget *widget,
3743                             GtkRequisition *requisition)
3744 {
3745   PsppireSheet *sheet;
3746
3747   g_return_if_fail (widget != NULL);
3748   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3749   g_return_if_fail (requisition != NULL);
3750
3751   sheet = PSPPIRE_SHEET (widget);
3752
3753   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
3754   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
3755
3756   /* compute the size of the column title area */
3757   if (sheet->column_titles_visible)
3758     requisition->height += sheet->column_title_area.height;
3759
3760   /* compute the size of the row title area */
3761   if (sheet->row_titles_visible)
3762     requisition->width += sheet->row_title_area.width;
3763 }
3764
3765
3766 static void
3767 psppire_sheet_size_allocate (GtkWidget *widget,
3768                              GtkAllocation *allocation)
3769 {
3770   PsppireSheet *sheet;
3771   GtkAllocation sheet_allocation;
3772   gint border_width;
3773
3774   g_return_if_fail (widget != NULL);
3775   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3776   g_return_if_fail (allocation != NULL);
3777
3778   sheet = PSPPIRE_SHEET (widget);
3779   widget->allocation = *allocation;
3780   border_width = GTK_CONTAINER (widget)->border_width;
3781
3782   if (GTK_WIDGET_REALIZED (widget))
3783     gdk_window_move_resize (widget->window,
3784                             allocation->x + border_width,
3785                             allocation->y + border_width,
3786                             allocation->width - 2 * border_width,
3787                             allocation->height - 2 * border_width);
3788
3789   sheet_allocation.x = 0;
3790   sheet_allocation.y = 0;
3791   sheet_allocation.width = allocation->width - 2 * border_width;
3792   sheet_allocation.height = allocation->height - 2 * border_width;
3793
3794   if (GTK_WIDGET_REALIZED (widget))
3795     gdk_window_move_resize (sheet->sheet_window,
3796                             sheet_allocation.x,
3797                             sheet_allocation.y,
3798                             sheet_allocation.width,
3799                             sheet_allocation.height);
3800
3801   /* position the window which holds the column title buttons */
3802   sheet->column_title_area.x = 0;
3803   sheet->column_title_area.y = 0;
3804   sheet->column_title_area.width = sheet_allocation.width ;
3805
3806
3807   /* position the window which holds the row title buttons */
3808   sheet->row_title_area.x = 0;
3809   sheet->row_title_area.y = 0;
3810   sheet->row_title_area.height = sheet_allocation.height;
3811
3812   if (sheet->row_titles_visible)
3813     sheet->column_title_area.x += sheet->row_title_area.width;
3814
3815   if (sheet->column_titles_visible)
3816     sheet->row_title_area.y += sheet->column_title_area.height;
3817
3818   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
3819     gdk_window_move_resize (sheet->column_title_window,
3820                             sheet->column_title_area.x,
3821                             sheet->column_title_area.y,
3822                             sheet->column_title_area.width,
3823                             sheet->column_title_area.height);
3824
3825
3826   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
3827     gdk_window_move_resize (sheet->row_title_window,
3828                             sheet->row_title_area.x,
3829                             sheet->row_title_area.y,
3830                             sheet->row_title_area.width,
3831                             sheet->row_title_area.height);
3832
3833   size_allocate_global_button (sheet);
3834
3835   if (sheet->haxis)
3836     {
3837       gint width = sheet->column_title_area.width;
3838
3839       if ( sheet->row_titles_visible)
3840         width -= sheet->row_title_area.width;
3841
3842       g_object_set (sheet->haxis,
3843                     "minimum-extent", width,
3844                     NULL);
3845     }
3846
3847
3848   if (sheet->vaxis)
3849     {
3850       gint height = sheet->row_title_area.height;
3851
3852       if ( sheet->column_titles_visible)
3853         height -= sheet->column_title_area.height;
3854
3855       g_object_set (sheet->vaxis,
3856                     "minimum-extent", height,
3857                     NULL);
3858     }
3859
3860
3861   /* set the scrollbars adjustments */
3862   adjust_scrollbars (sheet);
3863 }
3864
3865 static void
3866 draw_column_title_buttons (PsppireSheet *sheet)
3867 {
3868   gint x, width;
3869
3870   if (!sheet->column_titles_visible) return;
3871   if (!GTK_WIDGET_REALIZED (sheet))
3872     return;
3873
3874   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
3875   x = 0;
3876
3877   if (sheet->row_titles_visible)
3878     {
3879       x = sheet->row_title_area.width;
3880     }
3881
3882   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
3883     {
3884       sheet->column_title_area.width = width;
3885       sheet->column_title_area.x = x;
3886       gdk_window_move_resize (sheet->column_title_window,
3887                               sheet->column_title_area.x,
3888                               sheet->column_title_area.y,
3889                               sheet->column_title_area.width,
3890                               sheet->column_title_area.height);
3891     }
3892
3893   if (max_visible_column (sheet) ==
3894       psppire_axis_unit_count (sheet->haxis) - 1)
3895     gdk_window_clear_area (sheet->column_title_window,
3896                            0, 0,
3897                            sheet->column_title_area.width,
3898                            sheet->column_title_area.height);
3899
3900   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
3901
3902   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
3903                                    max_visible_column (sheet));
3904 }
3905
3906 static void
3907 draw_row_title_buttons (PsppireSheet *sheet)
3908 {
3909   gint y = 0;
3910   gint height;
3911
3912   if (!sheet->row_titles_visible) return;
3913   if (!GTK_WIDGET_REALIZED (sheet))
3914     return;
3915
3916   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
3917
3918   if (sheet->column_titles_visible)
3919     {
3920       y = sheet->column_title_area.height;
3921     }
3922
3923   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
3924     {
3925       sheet->row_title_area.y = y;
3926       sheet->row_title_area.height = height;
3927       gdk_window_move_resize (sheet->row_title_window,
3928                               sheet->row_title_area.x,
3929                               sheet->row_title_area.y,
3930                               sheet->row_title_area.width,
3931                               sheet->row_title_area.height);
3932     }
3933
3934   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
3935     gdk_window_clear_area (sheet->row_title_window,
3936                            0, 0,
3937                            sheet->row_title_area.width,
3938                            sheet->row_title_area.height);
3939
3940   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
3941
3942   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
3943                                 max_visible_row (sheet));
3944 }
3945
3946
3947 static void
3948 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
3949 {
3950   GtkAllocation entry_alloc;
3951   PsppireSheetCellAttr attributes = { 0 };
3952   GtkEntry *sheet_entry;
3953
3954   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
3955   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
3956
3957   sheet_entry = psppire_sheet_get_entry (sheet);
3958
3959   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
3960                                        sheet->active_cell.col,
3961                                        &attributes) )
3962     return ;
3963
3964   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
3965     {
3966       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
3967
3968       style->bg[GTK_STATE_NORMAL] = attributes.background;
3969       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
3970       style->text[GTK_STATE_NORMAL] = attributes.foreground;
3971       style->bg[GTK_STATE_ACTIVE] = attributes.background;
3972       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
3973       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
3974     }
3975
3976   rectangle_from_cell (sheet, sheet->active_cell.row,
3977                        sheet->active_cell.col, &entry_alloc);
3978
3979   entry_alloc.x += sheet->cell_padding->left;
3980   entry_alloc.y += sheet->cell_padding->right;
3981   entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
3982   entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
3983
3984
3985   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
3986                                entry_alloc.height);
3987   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
3988 }
3989
3990
3991 /* Copy the sheet's font to the entry widget */
3992 static void
3993 set_entry_widget_font (PsppireSheet *sheet)
3994 {
3995   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
3996
3997   pango_font_description_free (style->font_desc);
3998   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
3999
4000   gtk_widget_modify_style (sheet->entry_widget, style);
4001 }
4002
4003 static void
4004 create_sheet_entry (PsppireSheet *sheet)
4005 {
4006   if (sheet->entry_widget)
4007     {
4008       gtk_widget_unparent (sheet->entry_widget);
4009     }
4010
4011   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4012   g_object_ref_sink (sheet->entry_widget);
4013
4014   gtk_widget_size_request (sheet->entry_widget, NULL);
4015
4016   if ( GTK_IS_ENTRY (sheet->entry_widget))
4017     {
4018       g_object_set (sheet->entry_widget,
4019                     "has-frame", FALSE,
4020                     NULL);
4021     }
4022
4023   if (GTK_WIDGET_REALIZED (sheet))
4024     {
4025       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4026       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4027       gtk_widget_realize (sheet->entry_widget);
4028     }
4029
4030   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4031                             G_CALLBACK (psppire_sheet_entry_key_press),
4032                             sheet);
4033
4034   set_entry_widget_font (sheet);
4035
4036   gtk_widget_show (sheet->entry_widget);
4037 }
4038
4039
4040 /* Finds the last child widget that happens to be of type GtkEntry */
4041 static void
4042 find_entry (GtkWidget *w, gpointer user_data)
4043 {
4044   GtkWidget **entry = user_data;
4045   if ( GTK_IS_ENTRY (w))
4046     {
4047       *entry = w;
4048     }
4049 }
4050
4051
4052 GtkEntry *
4053 psppire_sheet_get_entry (PsppireSheet *sheet)
4054 {
4055   GtkWidget *w = sheet->entry_widget;
4056
4057   g_return_val_if_fail (sheet != NULL, NULL);
4058   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4059   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4060
4061   while (! GTK_IS_ENTRY (w))
4062     {
4063       GtkWidget *entry = NULL;
4064
4065       if (GTK_IS_CONTAINER (w))
4066         {
4067           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4068
4069           if (NULL == entry)
4070             break;
4071
4072           w = entry;
4073         }
4074     }
4075
4076   return GTK_ENTRY (w);
4077 }
4078
4079
4080 static void
4081 draw_button (PsppireSheet *sheet, GdkWindow *window,
4082              PsppireSheetButton *button, gboolean is_sensitive,
4083              GdkRectangle allocation)
4084 {
4085   GtkShadowType shadow_type;
4086   gint text_width = 0, text_height = 0;
4087   PangoAlignment align = PANGO_ALIGN_LEFT;
4088
4089   gboolean rtl ;
4090
4091   gint state = 0;
4092
4093   g_return_if_fail (sheet != NULL);
4094   g_return_if_fail (button != NULL);
4095
4096
4097   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4098
4099   gdk_window_clear_area (window,
4100                          allocation.x, allocation.y,
4101                          allocation.width, allocation.height);
4102
4103   gtk_widget_ensure_style (sheet->button);
4104
4105   gtk_paint_box (sheet->button->style, window,
4106                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4107                  &allocation,
4108                  GTK_WIDGET (sheet->button),
4109                  NULL,
4110                  allocation.x, allocation.y,
4111                  allocation.width, allocation.height);
4112
4113   state = button->state;
4114   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4115
4116   if (state == GTK_STATE_ACTIVE)
4117     shadow_type = GTK_SHADOW_IN;
4118   else
4119     shadow_type = GTK_SHADOW_OUT;
4120
4121   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4122     gtk_paint_box (sheet->button->style, window,
4123                    button->state, shadow_type,
4124                    &allocation, GTK_WIDGET (sheet->button),
4125                    NULL,
4126                    allocation.x, allocation.y,
4127                    allocation.width, allocation.height);
4128
4129   if ( button->overstruck)
4130     {
4131       GdkPoint points[2] = {
4132         {allocation.x,  allocation.y},
4133         {allocation.x + allocation.width,
4134          allocation.y + allocation.height}
4135       };
4136
4137       gtk_paint_polygon (sheet->button->style,
4138                          window,
4139                          button->state,
4140                          shadow_type,
4141                          NULL,
4142                          GTK_WIDGET (sheet),
4143                          NULL,
4144                          points,
4145                          2,
4146                          TRUE);
4147     }
4148
4149   if (button->label_visible)
4150     {
4151       text_height = DEFAULT_ROW_HEIGHT -
4152         2 * COLUMN_TITLES_HEIGHT;
4153
4154       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4155                                  &allocation);
4156       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4157                                  &allocation);
4158
4159       allocation.y += 2 * sheet->button->style->ythickness;
4160
4161       if (button->label && strlen (button->label) > 0)
4162         {
4163           PangoRectangle rect;
4164           gchar *line = button->label;
4165
4166           PangoLayout *layout = NULL;
4167           gint real_x = allocation.x;
4168           gint real_y = allocation.y;
4169
4170           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4171           pango_layout_get_extents (layout, NULL, &rect);
4172
4173           text_width = PANGO_PIXELS (rect.width);
4174           switch (button->justification)
4175             {
4176             case GTK_JUSTIFY_LEFT:
4177               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4178               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4179               break;
4180             case GTK_JUSTIFY_RIGHT:
4181               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4182               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4183               break;
4184             case GTK_JUSTIFY_CENTER:
4185             default:
4186               real_x = allocation.x + (allocation.width - text_width)/2;
4187               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4188               pango_layout_set_justify (layout, TRUE);
4189             }
4190           pango_layout_set_alignment (layout, align);
4191           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4192                             window,
4193                             state,
4194                             FALSE,
4195                             &allocation,
4196                             GTK_WIDGET (sheet),
4197                             "label",
4198                             real_x, real_y,
4199                             layout);
4200           g_object_unref (layout);
4201         }
4202
4203       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4204                                  NULL);
4205       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4206
4207     }
4208
4209   psppire_sheet_button_free (button);
4210 }
4211
4212
4213 /* Draw the column title buttons FIRST through to LAST */
4214 static void
4215 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4216 {
4217   GdkRectangle rect;
4218   gint col;
4219   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4220
4221   if (!sheet->column_titles_visible) return;
4222
4223   g_return_if_fail (first >= min_visible_column (sheet));
4224   g_return_if_fail (last <= max_visible_column (sheet));
4225
4226   rect.y = 0;
4227   rect.height = sheet->column_title_area.height;
4228   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4229   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4230     + psppire_axis_unit_size (sheet->haxis, last);
4231
4232   rect.x -= sheet->hadjustment->value;
4233
4234   minimize_int (&rect.width, sheet->column_title_area.width);
4235   maximize_int (&rect.x, 0);
4236
4237   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4238
4239   for (col = first ; col <= last ; ++col)
4240     {
4241       GdkRectangle allocation;
4242       gboolean is_sensitive = FALSE;
4243
4244       PsppireSheetButton *
4245         button = psppire_sheet_model_get_column_button (sheet->model, col);
4246       allocation.y = 0;
4247       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4248         + CELL_SPACING;
4249       allocation.x -= sheet->hadjustment->value;
4250
4251       allocation.height = sheet->column_title_area.height;
4252       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4253       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4254
4255       draw_button (sheet, sheet->column_title_window,
4256                    button, is_sensitive, allocation);
4257     }
4258
4259   gdk_window_end_paint (sheet->column_title_window);
4260 }
4261
4262
4263 static void
4264 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4265 {
4266   GdkRectangle rect;
4267   gint row;
4268   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4269
4270   if (!sheet->row_titles_visible) return;
4271
4272   g_return_if_fail (first >= min_visible_row (sheet));
4273   g_return_if_fail (last <= max_visible_row (sheet));
4274
4275   rect.x = 0;
4276   rect.width = sheet->row_title_area.width;
4277   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4278   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4279     + psppire_axis_unit_size (sheet->vaxis, last);
4280
4281   rect.y -= sheet->vadjustment->value;
4282
4283   minimize_int (&rect.height, sheet->row_title_area.height);
4284   maximize_int (&rect.y, 0);
4285
4286   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4287   for (row = first; row <= last; ++row)
4288     {
4289       GdkRectangle allocation;
4290
4291       gboolean is_sensitive = FALSE;
4292
4293       PsppireSheetButton *button =
4294         psppire_sheet_model_get_row_button (sheet->model, row);
4295       allocation.x = 0;
4296       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4297         + CELL_SPACING;
4298       allocation.y -= sheet->vadjustment->value;
4299
4300       allocation.width = sheet->row_title_area.width;
4301       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4302       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4303
4304       draw_button (sheet, sheet->row_title_window,
4305                    button, is_sensitive, allocation);
4306     }
4307
4308   gdk_window_end_paint (sheet->row_title_window);
4309 }
4310
4311 /* SCROLLBARS
4312  *
4313  * functions:
4314  * adjust_scrollbars
4315  * vadjustment_value_changed
4316  * hadjustment_value_changed */
4317
4318
4319 static void
4320 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
4321 {
4322   double position =
4323     (adj->value + adj->page_size)
4324     /
4325     (adj->upper - adj->lower);
4326
4327   const glong last_item = psppire_axis_unit_count (axis) - 1;
4328
4329   if (isnan (position) || position < 0)
4330     position = 0;
4331
4332   adj->upper =
4333     psppire_axis_start_pixel (axis, last_item)
4334     +
4335     psppire_axis_unit_size (axis, last_item)
4336     ;
4337
4338   adj->lower = 0;
4339   adj->page_size = page_size;
4340
4341 #if 0
4342   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
4343
4344   if ( adj->value < adj->lower)
4345     adj->value = adj->lower;
4346 #endif
4347
4348   gtk_adjustment_changed (adj);
4349 }
4350
4351
4352 static void
4353 adjust_scrollbars (PsppireSheet *sheet)
4354 {
4355   gint width, height;
4356
4357   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4358     return;
4359
4360   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
4361
4362   if ( sheet->row_titles_visible)
4363     width -= sheet->row_title_area.width;
4364
4365   if (sheet->column_titles_visible)
4366     height -= sheet->column_title_area.height;
4367
4368   if (sheet->vadjustment)
4369     {
4370       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
4371
4372       sheet->vadjustment->step_increment =
4373         ROWS_PER_STEP *
4374         psppire_axis_unit_size (sheet->vaxis, last_row);
4375
4376       sheet->vadjustment->page_increment =
4377         height -
4378         sheet->column_title_area.height -
4379         psppire_axis_unit_size (sheet->vaxis, last_row);
4380
4381       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
4382     }
4383
4384   if (sheet->hadjustment)
4385     {
4386       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
4387       sheet->hadjustment->step_increment = 1;
4388
4389       sheet->hadjustment->page_increment = width;
4390
4391       sheet->hadjustment->upper =
4392         psppire_axis_start_pixel (sheet->haxis, last_col)
4393         +
4394         psppire_axis_unit_size (sheet->haxis, last_col)
4395         ;
4396
4397       update_adjustment (sheet->hadjustment, sheet->haxis, width);
4398     }
4399 }
4400
4401 /* Subtracts the region of WIDGET from REGION */
4402 static void
4403 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
4404 {
4405   GdkRectangle rect;
4406   GdkRectangle intersect;
4407   GdkRegion *region2;
4408
4409   gdk_region_get_clipbox (region, &rect);
4410   gtk_widget_intersect (widget,
4411                         &rect,
4412                         &intersect);
4413
4414   region2 = gdk_region_rectangle (&intersect);
4415   gdk_region_subtract (region, region2);
4416   gdk_region_destroy (region2);
4417 }
4418
4419 static void
4420 vadjustment_value_changed (GtkAdjustment *adjustment,
4421                            gpointer data)
4422 {
4423   GdkRegion *region;
4424   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4425
4426   g_return_if_fail (adjustment != NULL);
4427
4428   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4429
4430   gtk_widget_hide (sheet->entry_widget);
4431
4432   region =
4433     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4434
4435   subtract_widget_region (region, sheet->button);
4436   gdk_window_begin_paint_region (sheet->sheet_window, region);
4437
4438   draw_sheet_region (sheet, region);
4439
4440   draw_row_title_buttons (sheet);
4441   psppire_sheet_draw_active_cell (sheet);
4442
4443   gdk_window_end_paint (sheet->sheet_window);
4444   gdk_region_destroy (region);
4445 }
4446
4447
4448 static void
4449 hadjustment_value_changed (GtkAdjustment *adjustment,
4450                            gpointer data)
4451 {
4452   GdkRegion *region;
4453   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4454
4455   g_return_if_fail (adjustment != NULL);
4456
4457   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4458
4459   gtk_widget_hide (sheet->entry_widget);
4460
4461
4462   region =
4463     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4464
4465   subtract_widget_region (region, sheet->button);
4466   gdk_window_begin_paint_region (sheet->sheet_window, region);
4467
4468   draw_sheet_region (sheet, region);
4469
4470   draw_column_title_buttons (sheet);
4471
4472   psppire_sheet_draw_active_cell (sheet);
4473
4474   gdk_window_end_paint (sheet->sheet_window);
4475
4476   gdk_region_destroy (region);
4477 }
4478
4479
4480 /* COLUMN RESIZING */
4481 static void
4482 draw_xor_vline (PsppireSheet *sheet)
4483 {
4484   gint height;
4485   gint xpos = sheet->x_drag;
4486   gdk_drawable_get_size (sheet->sheet_window,
4487                          NULL, &height);
4488
4489   if (sheet->row_titles_visible)
4490     xpos += sheet->row_title_area.width;
4491
4492   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4493                  xpos,
4494                  sheet->column_title_area.height,
4495                  xpos,
4496                  height + CELL_SPACING);
4497 }
4498
4499 /* ROW RESIZING */
4500 static void
4501 draw_xor_hline (PsppireSheet *sheet)
4502
4503 {
4504   gint width;
4505   gint ypos = sheet->y_drag;
4506
4507   gdk_drawable_get_size (sheet->sheet_window,
4508                          &width, NULL);
4509
4510
4511   if (sheet->column_titles_visible)
4512     ypos += sheet->column_title_area.height;
4513
4514   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4515                  sheet->row_title_area.width,
4516                  ypos,
4517                  width + CELL_SPACING,
4518                  ypos);
4519 }
4520
4521 /* SELECTED RANGE */
4522 static void
4523 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
4524 {
4525   gint i = 0;
4526   GdkRectangle clip_area, area;
4527   GdkGCValues values;
4528
4529   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
4530   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
4531   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
4532     psppire_axis_unit_size (sheet->haxis, range.coli);
4533   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
4534     psppire_axis_unit_size (sheet->vaxis, range.rowi);
4535
4536   clip_area.x = sheet->row_title_area.width;
4537   clip_area.y = sheet->column_title_area.height;
4538
4539   gdk_drawable_get_size (sheet->sheet_window,
4540                          &clip_area.width, &clip_area.height);
4541
4542   if (!sheet->row_titles_visible) clip_area.x = 0;
4543   if (!sheet->column_titles_visible) clip_area.y = 0;
4544
4545   if (area.x < 0)
4546     {
4547       area.width = area.width + area.x;
4548       area.x = 0;
4549     }
4550   if (area.width > clip_area.width) area.width = clip_area.width + 10;
4551   if (area.y < 0)
4552     {
4553       area.height = area.height + area.y;
4554       area.y = 0;
4555     }
4556   if (area.height > clip_area.height) area.height = clip_area.height + 10;
4557
4558   clip_area.x--;
4559   clip_area.y--;
4560   clip_area.width += 3;
4561   clip_area.height += 3;
4562
4563   gdk_gc_get_values (sheet->xor_gc, &values);
4564
4565   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
4566
4567   gdk_draw_rectangle (sheet->sheet_window,
4568                       sheet->xor_gc,
4569                       FALSE,
4570                       area.x + i, area.y + i,
4571                       area.width - 2 * i, area.height - 2 * i);
4572
4573
4574   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
4575
4576   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
4577 }
4578
4579
4580 static void
4581 set_column_width (PsppireSheet *sheet,
4582                   gint column,
4583                   gint width)
4584 {
4585   g_return_if_fail (sheet != NULL);
4586   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4587
4588   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
4589     return;
4590
4591   if ( width <= 0)
4592     return;
4593
4594   psppire_axis_resize (sheet->haxis, column,
4595                        width - sheet->cell_padding->left -
4596                        sheet->cell_padding->right);
4597
4598   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4599     {
4600       draw_column_title_buttons (sheet);
4601       adjust_scrollbars (sheet);
4602       psppire_sheet_size_allocate_entry (sheet);
4603       redraw_range (sheet, NULL);
4604     }
4605 }
4606
4607 static void
4608 set_row_height (PsppireSheet *sheet,
4609                 gint row,
4610                 gint height)
4611 {
4612   g_return_if_fail (sheet != NULL);
4613   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4614
4615   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
4616     return;
4617
4618   if (height <= 0)
4619     return;
4620
4621   psppire_axis_resize (sheet->vaxis, row,
4622                        height - sheet->cell_padding->top -
4623                        sheet->cell_padding->bottom);
4624
4625   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
4626     {
4627       draw_row_title_buttons (sheet);
4628       adjust_scrollbars (sheet);
4629       psppire_sheet_size_allocate_entry (sheet);
4630       redraw_range (sheet, NULL);
4631     }
4632 }
4633
4634 static gboolean
4635 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
4636                               PsppireSheetCellAttr *attr)
4637 {
4638   GdkColor *fg, *bg;
4639   const GtkJustification *j ;
4640   GdkColormap *colormap;
4641
4642   g_return_val_if_fail (sheet != NULL, FALSE);
4643   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
4644
4645   if (row < 0 || col < 0) return FALSE;
4646
4647   attr->foreground = GTK_WIDGET (sheet)->style->black;
4648   attr->background = sheet->color[BG_COLOR];
4649
4650   attr->border.width = 0;
4651   attr->border.line_style = GDK_LINE_SOLID;
4652   attr->border.cap_style = GDK_CAP_NOT_LAST;
4653   attr->border.join_style = GDK_JOIN_MITER;
4654   attr->border.mask = 0;
4655   attr->border.color = GTK_WIDGET (sheet)->style->black;
4656
4657   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
4658   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
4659   if ( fg )
4660     {
4661       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
4662       attr->foreground = *fg;
4663     }
4664
4665   bg = psppire_sheet_model_get_background (sheet->model, row, col);
4666   if ( bg )
4667     {
4668       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
4669       attr->background = *bg;
4670     }
4671
4672   attr->justification =
4673     psppire_sheet_model_get_column_justification (sheet->model, col);
4674
4675   j = psppire_sheet_model_get_justification (sheet->model, row, col);
4676   if (j)
4677     attr->justification = *j;
4678
4679   return TRUE;
4680 }
4681
4682 static void
4683 psppire_sheet_forall (GtkContainer *container,
4684                       gboolean include_internals,
4685                       GtkCallback callback,
4686                       gpointer callback_data)
4687 {
4688   PsppireSheet *sheet = PSPPIRE_SHEET (container);
4689
4690   g_return_if_fail (callback != NULL);
4691
4692   if (sheet->button && sheet->button->parent)
4693     (* callback) (sheet->button, callback_data);
4694
4695   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
4696     (* callback) (sheet->entry_widget, callback_data);
4697 }
4698
4699
4700 PsppireSheetModel *
4701 psppire_sheet_get_model (const PsppireSheet *sheet)
4702 {
4703   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4704
4705   return sheet->model;
4706 }
4707
4708
4709 PsppireSheetButton *
4710 psppire_sheet_button_new (void)
4711 {
4712   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
4713
4714   button->state = GTK_STATE_NORMAL;
4715   button->label = NULL;
4716   button->label_visible = TRUE;
4717   button->justification = GTK_JUSTIFY_FILL;
4718   button->overstruck = FALSE;
4719
4720   return button;
4721 }
4722
4723
4724 void
4725 psppire_sheet_button_free (PsppireSheetButton *button)
4726 {
4727   if (!button) return ;
4728
4729   g_free (button->label);
4730   g_free (button);
4731 }
4732
4733 static void
4734 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
4735 {
4736   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
4737
4738   if ( NULL == celltext)
4739     return;
4740
4741   g_string_append (string, celltext);
4742   g_free (celltext);
4743 }
4744
4745
4746 static GString *
4747 range_to_text (const PsppireSheet *sheet)
4748 {
4749   gint r, c;
4750   GString *string;
4751
4752   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4753     return NULL;
4754
4755   string = g_string_sized_new (80);
4756
4757   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4758     {
4759       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
4760         {
4761           append_cell_text (string, sheet, r, c);
4762           g_string_append (string, "\t");
4763         }
4764       append_cell_text (string, sheet, r, c);
4765       if ( r < sheet->range.rowi)
4766         g_string_append (string, "\n");
4767     }
4768
4769   return string;
4770 }
4771
4772 static GString *
4773 range_to_html (const PsppireSheet *sheet)
4774 {
4775   gint r, c;
4776   GString *string;
4777
4778   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4779     return NULL;
4780
4781   string = g_string_sized_new (480);
4782
4783   g_string_append (string, "<html>\n");
4784   g_string_append (string, "<body>\n");
4785   g_string_append (string, "<table>\n");
4786   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4787     {
4788       g_string_append (string, "<tr>\n");
4789       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
4790         {
4791           g_string_append (string, "<td>");
4792           append_cell_text (string, sheet, r, c);
4793           g_string_append (string, "</td>\n");
4794         }
4795       g_string_append (string, "</tr>\n");
4796     }
4797   g_string_append (string, "</table>\n");
4798   g_string_append (string, "</body>\n");
4799   g_string_append (string, "</html>\n");
4800
4801   return string;
4802 }
4803
4804 enum {
4805   SELECT_FMT_NULL,
4806   SELECT_FMT_TEXT,
4807   SELECT_FMT_HTML
4808 };
4809
4810 static void
4811 primary_get_cb (GtkClipboard     *clipboard,
4812                 GtkSelectionData *selection_data,
4813                 guint             info,
4814                 gpointer          data)
4815 {
4816   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4817   GString *string = NULL;
4818
4819   switch (info)
4820     {
4821     case SELECT_FMT_TEXT:
4822       string = range_to_text (sheet);
4823       break;
4824     case SELECT_FMT_HTML:
4825       string = range_to_html (sheet);
4826       break;
4827     default:
4828       g_assert_not_reached ();
4829     }
4830
4831   gtk_selection_data_set (selection_data, selection_data->target,
4832                           8,
4833                           (const guchar *) string->str, string->len);
4834   g_string_free (string, TRUE);
4835 }
4836
4837 static void
4838 primary_clear_cb (GtkClipboard *clipboard,
4839                   gpointer      data)
4840 {
4841   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4842   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4843     return;
4844
4845   psppire_sheet_unselect_range (sheet);
4846 }
4847
4848 static void
4849 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
4850 {
4851   static const GtkTargetEntry targets[] = {
4852     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
4853     { "STRING",        0, SELECT_FMT_TEXT },
4854     { "TEXT",          0, SELECT_FMT_TEXT },
4855     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
4856     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
4857     { "text/plain",    0, SELECT_FMT_TEXT },
4858     { "text/html",     0, SELECT_FMT_HTML }
4859   };
4860
4861   GtkClipboard *clipboard;
4862
4863   if (!GTK_WIDGET_REALIZED (sheet))
4864     return;
4865
4866   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
4867                                         GDK_SELECTION_PRIMARY);
4868
4869   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
4870     {
4871       if (!gtk_clipboard_set_with_owner (clipboard, targets,
4872                                          G_N_ELEMENTS (targets),
4873                                          primary_get_cb, primary_clear_cb,
4874                                          G_OBJECT (sheet)))
4875         primary_clear_cb (clipboard, sheet);
4876     }
4877   else
4878     {
4879       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
4880         gtk_clipboard_clear (clipboard);
4881     }
4882 }