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