Remove commented code
[pspp-builds.git] / lib / gtk-contrib / psppire-sheet.c
1 /*
2   Copyright (C) 2006, 2008, 2009 Free Software Foundation
3
4   This program is free software: you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation, either version 3 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17
18   This file is derived from the gtksheet.c and extensively modified for the
19   requirements of PSPPIRE.  The changes are copyright by the
20   Free Software Foundation.  The copyright notice for the original work is
21   below.
22 */
23
24 /* GtkSheet widget for Gtk+.
25  * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
26  *
27  * Based on GtkClist widget by Jay Painter, but major changes.
28  * Memory allocation routines inspired on SC (Spreadsheet Calculator)
29  *
30  * This library is free software; you can redistribute it and/or
31  * modify it under the terms of the GNU Lesser General Public
32  * License as published by the Free Software Foundation; either
33  * version 2.1 of the License, or (at your option) any later version.
34  *
35  * This library is distributed in the hope that it will be useful,
36  * but WITHOUT ANY WARRANTY; without even the implied warranty of
37  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
38  * Lesser General Public License for more details.
39  *
40  * You should have received a copy of the GNU Lesser General Public
41  * License along with this library; if not, write to the Free Software
42  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
43  */
44
45 /**
46  * SECTION:psppiresheet
47  * @short_description: spreadsheet widget for gtk2
48  *
49  * PsppireSheet is a matrix widget for GTK+. It consists of an scrollable grid of
50  * cells where you can allocate text. Cell contents can be edited interactively
51  * through a specially designed entry, GtkItemEntry.
52  *
53  */
54 #include <config.h>
55
56 #include <string.h>
57 #include <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, GtkType 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 =
3026         psppire_axis_unit_count (sheet->haxis) - 1;
3027       psppire_sheet_select_range (sheet, NULL);
3028       return TRUE;
3029     }
3030
3031   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3032     change_active_cell (sheet, row, column);
3033
3034   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3035
3036   return TRUE;
3037 }
3038
3039 static gint
3040 psppire_sheet_button_release (GtkWidget *widget,
3041                               GdkEventButton *event)
3042 {
3043   GdkDisplay *display = gtk_widget_get_display (widget);
3044
3045   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3046
3047   /* release on resize windows */
3048   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3049     {
3050       gint width;
3051       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3052       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3053
3054       gdk_display_pointer_ungrab (display, event->time);
3055       draw_xor_vline (sheet);
3056
3057       width = event->x -
3058         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3059         + sheet->hadjustment->value;
3060
3061       set_column_width (sheet, sheet->drag_cell.col, width);
3062
3063       return TRUE;
3064     }
3065
3066   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3067     {
3068       gint height;
3069       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3070       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3071
3072       gdk_display_pointer_ungrab (display, event->time);
3073       draw_xor_hline (sheet);
3074
3075       height = event->y -
3076         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3077         sheet->vadjustment->value;
3078
3079       set_row_height (sheet, sheet->drag_cell.row, height);
3080
3081       return TRUE;
3082     }
3083
3084   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3085     {
3086       PsppireSheetRange old_range;
3087       draw_xor_rectangle (sheet, sheet->drag_range);
3088       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3089       gdk_display_pointer_ungrab (display, event->time);
3090
3091       psppire_sheet_unselect_range (sheet);
3092
3093       old_range = sheet->range;
3094       sheet->range = sheet->drag_range;
3095       sheet->drag_range = old_range;
3096       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3097                      &sheet->drag_range, &sheet->range);
3098       psppire_sheet_select_range (sheet, &sheet->range);
3099     }
3100
3101   if (PSPPIRE_SHEET_IN_SELECTION (sheet))
3102     {
3103       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3104       sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3105
3106       change_active_cell (sheet, sheet->active_cell.row,
3107                           sheet->active_cell.col);
3108     }
3109
3110   gdk_display_pointer_ungrab (display, event->time);
3111   gtk_grab_remove (GTK_WIDGET (sheet));
3112
3113   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3114
3115   return TRUE;
3116 }
3117
3118 \f
3119
3120
3121
3122 /* Shamelessly lifted from gtktooltips */
3123 static gboolean
3124 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3125 {
3126   GtkRequisition req;
3127
3128   gtk_widget_size_request (tip_window, &req);
3129   gtk_paint_flat_box (tip_window->style, tip_window->window,
3130                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3131                       NULL, GTK_WIDGET(tip_window), "tooltip",
3132                       0, 0, req.width, req.height);
3133
3134   return FALSE;
3135 }
3136
3137 static void
3138 destroy_hover_window (PsppireSheetHoverTitle *h)
3139 {
3140   gtk_widget_destroy (h->window);
3141   g_free (h);
3142 }
3143
3144 static PsppireSheetHoverTitle *
3145 create_hover_window (void)
3146 {
3147   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3148
3149   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3150
3151 #if GTK_CHECK_VERSION (2, 9, 0)
3152   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3153                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3154 #endif
3155
3156   gtk_widget_set_app_paintable (hw->window, TRUE);
3157   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3158   gtk_widget_set_name (hw->window, "gtk-tooltips");
3159   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3160
3161   g_signal_connect (hw->window,
3162                     "expose_event",
3163                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3164                     NULL);
3165
3166   hw->label = gtk_label_new (NULL);
3167
3168
3169   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3170   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3171
3172   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3173
3174   gtk_widget_show (hw->label);
3175
3176   g_signal_connect (hw->window,
3177                     "destroy",
3178                     G_CALLBACK (gtk_widget_destroyed),
3179                     &hw->window);
3180
3181   return hw;
3182 }
3183
3184 #define HOVER_WINDOW_Y_OFFSET 2
3185
3186 static void
3187 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3188                const gchar *subtitle)
3189 {
3190   gint x, y;
3191   gint px, py;
3192   gint width;
3193
3194   if ( ! subtitle )
3195     return;
3196
3197   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3198                       subtitle);
3199
3200
3201   sheet->hover_window->row = row;
3202   sheet->hover_window->column = column;
3203
3204   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3205
3206   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3207
3208   gtk_widget_show (sheet->hover_window->window);
3209
3210   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3211
3212   if (row == -1 )
3213     {
3214       x += px;
3215       x -= width / 2;
3216       y += sheet->column_title_area.y;
3217       y += sheet->column_title_area.height;
3218       y += HOVER_WINDOW_Y_OFFSET;
3219     }
3220
3221   if ( column == -1 )
3222     {
3223       y += py;
3224       x += sheet->row_title_area.x;
3225       x += sheet->row_title_area.width * 2 / 3.0;
3226     }
3227
3228   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3229                    x, y);
3230 }
3231
3232 static gboolean
3233 motion_timeout_callback (gpointer data)
3234 {
3235   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3236   gint x, y;
3237   gint row, column;
3238
3239   gdk_threads_enter ();
3240   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3241
3242   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3243     {
3244       if (sheet->row_title_under && row >= 0)
3245         {
3246           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3247
3248           show_subtitle (sheet, row, -1, text);
3249           g_free (text);
3250         }
3251
3252       if (sheet->column_title_under && column >= 0)
3253         {
3254           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3255                                                                  column);
3256
3257           show_subtitle (sheet, -1, column, text);
3258
3259           g_free (text);
3260         }
3261     }
3262
3263   gdk_threads_leave ();
3264   return FALSE;
3265 }
3266
3267 static gboolean
3268 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3269 {
3270   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3271   GdkModifierType mods;
3272   GdkCursorType new_cursor;
3273   gint x, y;
3274   gint row, column;
3275   GdkDisplay *display;
3276
3277   g_return_val_if_fail (event != NULL, FALSE);
3278
3279   display = gtk_widget_get_display (widget);
3280
3281   /* selections on the sheet */
3282   x = event->x;
3283   y = event->y;
3284
3285   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3286     {
3287       if ( sheet->motion_timer > 0 )
3288         g_source_remove (sheet->motion_timer);
3289       sheet->motion_timer =
3290         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3291     }
3292   else
3293     {
3294       gint row, column;
3295       gint wx, wy;
3296       gtk_widget_get_pointer (widget, &wx, &wy);
3297
3298       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3299         {
3300           if ( row != sheet->hover_window->row ||
3301                column != sheet->hover_window->column)
3302             {
3303               gtk_widget_hide (sheet->hover_window->window);
3304             }
3305         }
3306     }
3307
3308   if (event->window == sheet->column_title_window)
3309     {
3310       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3311           on_column_boundary (sheet, x, &column))
3312         {
3313           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3314           if (new_cursor != sheet->cursor_drag->type)
3315             {
3316               gdk_cursor_unref (sheet->cursor_drag);
3317               sheet->cursor_drag =
3318                 gdk_cursor_new_for_display (display, new_cursor);
3319
3320               gdk_window_set_cursor (sheet->column_title_window,
3321                                      sheet->cursor_drag);
3322             }
3323         }
3324       else
3325         {
3326           new_cursor = GDK_TOP_LEFT_ARROW;
3327           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3328               new_cursor != sheet->cursor_drag->type)
3329             {
3330               gdk_cursor_unref (sheet->cursor_drag);
3331               sheet->cursor_drag =
3332                 gdk_cursor_new_for_display (display, new_cursor);
3333               gdk_window_set_cursor (sheet->column_title_window,
3334                                      sheet->cursor_drag);
3335             }
3336         }
3337     }
3338   else if (event->window == sheet->row_title_window)
3339     {
3340       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3341           on_row_boundary (sheet, y, &row))
3342         {
3343           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3344           if (new_cursor != sheet->cursor_drag->type)
3345             {
3346               gdk_cursor_unref (sheet->cursor_drag);
3347               sheet->cursor_drag =
3348                 gdk_cursor_new_for_display (display, new_cursor);
3349               gdk_window_set_cursor (sheet->row_title_window,
3350                                      sheet->cursor_drag);
3351             }
3352         }
3353       else
3354         {
3355           new_cursor = GDK_TOP_LEFT_ARROW;
3356           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3357               new_cursor != sheet->cursor_drag->type)
3358             {
3359               gdk_cursor_unref (sheet->cursor_drag);
3360               sheet->cursor_drag =
3361                 gdk_cursor_new_for_display (display, new_cursor);
3362               gdk_window_set_cursor (sheet->row_title_window,
3363                                      sheet->cursor_drag);
3364             }
3365         }
3366     }
3367
3368   new_cursor = GDK_PLUS;
3369   if ( event->window == sheet->sheet_window &&
3370        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3371        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3372        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3373        new_cursor != sheet->cursor_drag->type)
3374     {
3375       gdk_cursor_unref (sheet->cursor_drag);
3376       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3377       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3378     }
3379
3380   new_cursor = GDK_TOP_LEFT_ARROW;
3381   if ( event->window == sheet->sheet_window &&
3382        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ) &&
3383        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3384         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3385        new_cursor != sheet->cursor_drag->type)
3386     {
3387       gdk_cursor_unref (sheet->cursor_drag);
3388       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3389       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3390     }
3391
3392   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3393   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3394
3395   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3396     {
3397       if (event->x != sheet->x_drag)
3398         {
3399           draw_xor_vline (sheet);
3400           sheet->x_drag = event->x;
3401           draw_xor_vline (sheet);
3402         }
3403
3404       return TRUE;
3405     }
3406
3407   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3408     {
3409       if (event->y != sheet->y_drag)
3410         {
3411           draw_xor_hline (sheet);
3412           sheet->y_drag = event->y;
3413           draw_xor_hline (sheet);
3414         }
3415
3416       return TRUE;
3417     }
3418
3419   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3420     {
3421       PsppireSheetRange aux;
3422       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
3423       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
3424       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3425       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3426       sheet->x_drag = x;
3427       sheet->y_drag = y;
3428       aux = sheet->range;
3429       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3430           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3431         {
3432           aux = sheet->drag_range;
3433           sheet->drag_range.row0 = sheet->range.row0 + row;
3434           sheet->drag_range.col0 = sheet->range.col0 + column;
3435           sheet->drag_range.rowi = sheet->range.rowi + row;
3436           sheet->drag_range.coli = sheet->range.coli + column;
3437           if (aux.row0 != sheet->drag_range.row0 ||
3438               aux.col0 != sheet->drag_range.col0)
3439             {
3440               draw_xor_rectangle (sheet, aux);
3441               draw_xor_rectangle (sheet, sheet->drag_range);
3442             }
3443         }
3444       return TRUE;
3445     }
3446
3447   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3448
3449   if (sheet->select_status == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
3450       column == sheet->active_cell.col) return TRUE;
3451
3452   if ( mods & GDK_BUTTON1_MASK)
3453     {
3454       if (PSPPIRE_SHEET_IN_SELECTION (sheet) )
3455         {
3456           /* Redraw the old range */
3457           psppire_sheet_unselect_range (sheet);
3458
3459           sheet->range.rowi = row;
3460           sheet->range.coli = column;
3461
3462           /* Redraw the new range */
3463           psppire_sheet_select_range (sheet, &sheet->range);
3464         }
3465       else
3466         {
3467           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3468         }
3469     }
3470
3471   return TRUE;
3472 }
3473
3474 static gboolean
3475 psppire_sheet_crossing_notify (GtkWidget *widget,
3476                                GdkEventCrossing *event)
3477 {
3478   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3479
3480   if (event->window == sheet->column_title_window)
3481     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
3482   else if (event->window == sheet->row_title_window)
3483     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
3484
3485   if (event->type == GDK_LEAVE_NOTIFY)
3486     gtk_widget_hide (sheet->hover_window->window);
3487
3488   return TRUE;
3489 }
3490
3491
3492 static gboolean
3493 psppire_sheet_focus_in (GtkWidget     *w,
3494                         GdkEventFocus *event)
3495 {
3496   PsppireSheet *sheet = PSPPIRE_SHEET (w);
3497
3498   gtk_widget_grab_focus (sheet->entry_widget);
3499
3500   return TRUE;
3501 }
3502
3503
3504
3505 static gint
3506 psppire_sheet_entry_key_press (GtkWidget *widget,
3507                                GdkEventKey *key)
3508 {
3509   gboolean focus;
3510   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
3511   return focus;
3512 }
3513
3514
3515 /* Number of rows in a step-increment */
3516 #define ROWS_PER_STEP 1
3517
3518
3519 static void
3520 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
3521 {
3522   gint old_row = sheet->active_cell.row ;
3523   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
3524
3525   gint new_row;
3526
3527   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
3528                                       min_visible_row (sheet));
3529
3530   switch ( dir)
3531     {
3532     case GTK_SCROLL_PAGE_DOWN:
3533       gtk_adjustment_set_value (sheet->vadjustment,
3534                                 sheet->vadjustment->value +
3535                                 sheet->vadjustment->page_increment);
3536       break;
3537     case GTK_SCROLL_PAGE_UP:
3538       gtk_adjustment_set_value (sheet->vadjustment,
3539                                 sheet->vadjustment->value -
3540                                 sheet->vadjustment->page_increment);
3541
3542       break;
3543     default:
3544       g_assert_not_reached ();
3545       break;
3546     }
3547
3548
3549   vpixel += psppire_axis_start_pixel (sheet->vaxis,
3550                                       min_visible_row (sheet));
3551
3552   new_row =  row_from_ypixel (sheet, vpixel);
3553
3554   change_active_cell (sheet, new_row,
3555                       sheet->active_cell.col);
3556 }
3557
3558
3559 static void
3560 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
3561 {
3562   gint current_row = sheet->active_cell.row;
3563   gint current_col = sheet->active_cell.col;
3564   PsppireSheetCell new_cell ;
3565   gboolean forbidden = FALSE;
3566
3567   new_cell.row = current_row;
3568   new_cell.col = current_col;
3569
3570   switch ( dir)
3571     {
3572     case GTK_SCROLL_STEP_DOWN:
3573       new_cell.row++;
3574       break;
3575     case GTK_SCROLL_STEP_UP:
3576       new_cell.row--;
3577       break;
3578     case GTK_SCROLL_STEP_RIGHT:
3579       new_cell.col++;
3580       break;
3581     case GTK_SCROLL_STEP_LEFT:
3582       new_cell.col--;
3583       break;
3584     case GTK_SCROLL_STEP_FORWARD:
3585       new_cell.col++;
3586       if (new_cell.col >=
3587           psppire_sheet_model_get_column_count (sheet->model))
3588         {
3589           new_cell.col = 0;
3590           new_cell.row++;
3591         }
3592       break;
3593     case GTK_SCROLL_STEP_BACKWARD:
3594       new_cell.col--;
3595       if (new_cell.col < 0)
3596         {
3597           new_cell.col =
3598             psppire_sheet_model_get_column_count (sheet->model) - 1;
3599           new_cell.row--;
3600         }
3601       break;
3602     default:
3603       g_assert_not_reached ();
3604       break;
3605     }
3606
3607   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3608                  &sheet->active_cell,
3609                  &new_cell,
3610                  &forbidden);
3611
3612   if (forbidden)
3613     return;
3614
3615
3616   maximize_int (&new_cell.row, 0);
3617   maximize_int (&new_cell.col, 0);
3618
3619   minimize_int (&new_cell.row,
3620                 psppire_axis_unit_count (sheet->vaxis) - 1);
3621
3622   minimize_int (&new_cell.col,
3623                 psppire_axis_unit_count (sheet->haxis) - 1);
3624
3625   change_active_cell (sheet, new_cell.row, new_cell.col);
3626
3627
3628   if ( new_cell.col > max_fully_visible_column (sheet))
3629     {
3630       glong hpos  =
3631         psppire_axis_start_pixel (sheet->haxis,
3632                                   new_cell.col + 1);
3633       hpos -= sheet->hadjustment->page_size;
3634
3635       gtk_adjustment_set_value (sheet->hadjustment,
3636                                 hpos);
3637     }
3638   else if ( new_cell.col < min_fully_visible_column (sheet))
3639     {
3640       glong hpos  =
3641         psppire_axis_start_pixel (sheet->haxis,
3642                                   new_cell.col);
3643
3644       gtk_adjustment_set_value (sheet->hadjustment,
3645                                 hpos);
3646     }
3647
3648
3649   if ( new_cell.row > max_fully_visible_row (sheet))
3650     {
3651       glong vpos  =
3652         psppire_axis_start_pixel (sheet->vaxis,
3653                                   new_cell.row + 1);
3654       vpos -= sheet->vadjustment->page_size;
3655
3656       gtk_adjustment_set_value (sheet->vadjustment,
3657                                 vpos);
3658     }
3659   else if ( new_cell.row < min_fully_visible_row (sheet))
3660     {
3661       glong vpos  =
3662         psppire_axis_start_pixel (sheet->vaxis,
3663                                   new_cell.row);
3664
3665       gtk_adjustment_set_value (sheet->vadjustment,
3666                                 vpos);
3667     }
3668
3669   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3670 }
3671
3672
3673 static gboolean
3674 psppire_sheet_key_press (GtkWidget *widget,
3675                          GdkEventKey *key)
3676 {
3677   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3678
3679   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3680
3681   switch (key->keyval)
3682     {
3683     case GDK_Tab:
3684       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
3685       break;
3686     case GDK_Right:
3687       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
3688       break;
3689     case GDK_ISO_Left_Tab:
3690       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
3691       break;
3692     case GDK_Left:
3693       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
3694       break;
3695     case GDK_Return:
3696     case GDK_Down:
3697       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
3698       break;
3699     case GDK_Up:
3700       step_sheet (sheet, GTK_SCROLL_STEP_UP);
3701       break;
3702
3703     case GDK_Page_Down:
3704       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
3705       break;
3706     case GDK_Page_Up:
3707       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
3708       break;
3709
3710     case GDK_Home:
3711       gtk_adjustment_set_value (sheet->vadjustment,
3712                                 sheet->vadjustment->lower);
3713
3714       change_active_cell (sheet,  0,
3715                           sheet->active_cell.col);
3716
3717       break;
3718
3719     case GDK_End:
3720       gtk_adjustment_set_value (sheet->vadjustment,
3721                                 sheet->vadjustment->upper -
3722                                 sheet->vadjustment->page_size -
3723                                 sheet->vadjustment->page_increment);
3724
3725       /*
3726         change_active_cellx (sheet,
3727         psppire_axis_unit_count (sheet->vaxis) - 1,
3728         sheet->active_cell.col);
3729       */
3730       break;
3731     case GDK_Delete:
3732       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
3733       break;
3734     default:
3735       return FALSE;
3736       break;
3737     }
3738
3739   return TRUE;
3740 }
3741
3742 static void
3743 psppire_sheet_size_request (GtkWidget *widget,
3744                             GtkRequisition *requisition)
3745 {
3746   PsppireSheet *sheet;
3747
3748   g_return_if_fail (widget != NULL);
3749   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3750   g_return_if_fail (requisition != NULL);
3751
3752   sheet = PSPPIRE_SHEET (widget);
3753
3754   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
3755   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
3756
3757   /* compute the size of the column title area */
3758   if (sheet->column_titles_visible)
3759     requisition->height += sheet->column_title_area.height;
3760
3761   /* compute the size of the row title area */
3762   if (sheet->row_titles_visible)
3763     requisition->width += sheet->row_title_area.width;
3764 }
3765
3766
3767 static void
3768 psppire_sheet_size_allocate (GtkWidget *widget,
3769                              GtkAllocation *allocation)
3770 {
3771   PsppireSheet *sheet;
3772   GtkAllocation sheet_allocation;
3773   gint border_width;
3774
3775   g_return_if_fail (widget != NULL);
3776   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3777   g_return_if_fail (allocation != NULL);
3778
3779   sheet = PSPPIRE_SHEET (widget);
3780   widget->allocation = *allocation;
3781   border_width = GTK_CONTAINER (widget)->border_width;
3782
3783   if (GTK_WIDGET_REALIZED (widget))
3784     gdk_window_move_resize (widget->window,
3785                             allocation->x + border_width,
3786                             allocation->y + border_width,
3787                             allocation->width - 2 * border_width,
3788                             allocation->height - 2 * border_width);
3789
3790   sheet_allocation.x = 0;
3791   sheet_allocation.y = 0;
3792   sheet_allocation.width = allocation->width - 2 * border_width;
3793   sheet_allocation.height = allocation->height - 2 * border_width;
3794
3795   if (GTK_WIDGET_REALIZED (widget))
3796     gdk_window_move_resize (sheet->sheet_window,
3797                             sheet_allocation.x,
3798                             sheet_allocation.y,
3799                             sheet_allocation.width,
3800                             sheet_allocation.height);
3801
3802   /* position the window which holds the column title buttons */
3803   sheet->column_title_area.x = 0;
3804   sheet->column_title_area.y = 0;
3805   sheet->column_title_area.width = sheet_allocation.width ;
3806
3807
3808   /* position the window which holds the row title buttons */
3809   sheet->row_title_area.x = 0;
3810   sheet->row_title_area.y = 0;
3811   sheet->row_title_area.height = sheet_allocation.height;
3812
3813   if (sheet->row_titles_visible)
3814     sheet->column_title_area.x += sheet->row_title_area.width;
3815
3816   if (sheet->column_titles_visible)
3817     sheet->row_title_area.y += sheet->column_title_area.height;
3818
3819   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
3820     gdk_window_move_resize (sheet->column_title_window,
3821                             sheet->column_title_area.x,
3822                             sheet->column_title_area.y,
3823                             sheet->column_title_area.width,
3824                             sheet->column_title_area.height);
3825
3826
3827   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
3828     gdk_window_move_resize (sheet->row_title_window,
3829                             sheet->row_title_area.x,
3830                             sheet->row_title_area.y,
3831                             sheet->row_title_area.width,
3832                             sheet->row_title_area.height);
3833
3834   size_allocate_global_button (sheet);
3835
3836   if (sheet->haxis)
3837     {
3838       gint width = sheet->column_title_area.width;
3839
3840       if ( sheet->row_titles_visible)
3841         width -= sheet->row_title_area.width;
3842
3843       g_object_set (sheet->haxis,
3844                     "minimum-extent", width,
3845                     NULL);
3846     }
3847
3848
3849   if (sheet->vaxis)
3850     {
3851       gint height = sheet->row_title_area.height;
3852
3853       if ( sheet->column_titles_visible)
3854         height -= sheet->column_title_area.height;
3855
3856       g_object_set (sheet->vaxis,
3857                     "minimum-extent", height,
3858                     NULL);
3859     }
3860
3861
3862   /* set the scrollbars adjustments */
3863   adjust_scrollbars (sheet);
3864 }
3865
3866 static void
3867 draw_column_title_buttons (PsppireSheet *sheet)
3868 {
3869   gint x, width;
3870
3871   if (!sheet->column_titles_visible) return;
3872   if (!GTK_WIDGET_REALIZED (sheet))
3873     return;
3874
3875   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
3876   x = 0;
3877
3878   if (sheet->row_titles_visible)
3879     {
3880       x = sheet->row_title_area.width;
3881     }
3882
3883   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
3884     {
3885       sheet->column_title_area.width = width;
3886       sheet->column_title_area.x = x;
3887       gdk_window_move_resize (sheet->column_title_window,
3888                               sheet->column_title_area.x,
3889                               sheet->column_title_area.y,
3890                               sheet->column_title_area.width,
3891                               sheet->column_title_area.height);
3892     }
3893
3894   if (max_visible_column (sheet) ==
3895       psppire_axis_unit_count (sheet->haxis) - 1)
3896     gdk_window_clear_area (sheet->column_title_window,
3897                            0, 0,
3898                            sheet->column_title_area.width,
3899                            sheet->column_title_area.height);
3900
3901   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
3902
3903   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
3904                                    max_visible_column (sheet));
3905 }
3906
3907 static void
3908 draw_row_title_buttons (PsppireSheet *sheet)
3909 {
3910   gint y = 0;
3911   gint height;
3912
3913   if (!sheet->row_titles_visible) return;
3914   if (!GTK_WIDGET_REALIZED (sheet))
3915     return;
3916
3917   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
3918
3919   if (sheet->column_titles_visible)
3920     {
3921       y = sheet->column_title_area.height;
3922     }
3923
3924   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
3925     {
3926       sheet->row_title_area.y = y;
3927       sheet->row_title_area.height = height;
3928       gdk_window_move_resize (sheet->row_title_window,
3929                               sheet->row_title_area.x,
3930                               sheet->row_title_area.y,
3931                               sheet->row_title_area.width,
3932                               sheet->row_title_area.height);
3933     }
3934
3935   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
3936     gdk_window_clear_area (sheet->row_title_window,
3937                            0, 0,
3938                            sheet->row_title_area.width,
3939                            sheet->row_title_area.height);
3940
3941   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
3942
3943   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
3944                                 max_visible_row (sheet));
3945 }
3946
3947
3948 static void
3949 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
3950 {
3951   GtkAllocation entry_alloc;
3952   PsppireSheetCellAttr attributes = { 0 };
3953   GtkEntry *sheet_entry;
3954
3955   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
3956   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
3957
3958   sheet_entry = psppire_sheet_get_entry (sheet);
3959
3960   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
3961                                        sheet->active_cell.col,
3962                                        &attributes) )
3963     return ;
3964
3965   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
3966     {
3967       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
3968
3969       style->bg[GTK_STATE_NORMAL] = attributes.background;
3970       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
3971       style->text[GTK_STATE_NORMAL] = attributes.foreground;
3972       style->bg[GTK_STATE_ACTIVE] = attributes.background;
3973       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
3974       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
3975     }
3976
3977   rectangle_from_cell (sheet, sheet->active_cell.row,
3978                        sheet->active_cell.col, &entry_alloc);
3979
3980   entry_alloc.x += sheet->cell_padding->left;
3981   entry_alloc.y += sheet->cell_padding->right;
3982   entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
3983   entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
3984
3985
3986   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
3987                                entry_alloc.height);
3988   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
3989 }
3990
3991
3992 /* Copy the sheet's font to the entry widget */
3993 static void
3994 set_entry_widget_font (PsppireSheet *sheet)
3995 {
3996   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
3997
3998   pango_font_description_free (style->font_desc);
3999   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4000
4001   gtk_widget_modify_style (sheet->entry_widget, style);
4002 }
4003
4004 static void
4005 create_sheet_entry (PsppireSheet *sheet)
4006 {
4007   if (sheet->entry_widget)
4008     {
4009       gtk_widget_unparent (sheet->entry_widget);
4010     }
4011
4012   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4013   g_object_ref_sink (sheet->entry_widget);
4014
4015   gtk_widget_size_request (sheet->entry_widget, NULL);
4016
4017   if ( GTK_IS_ENTRY (sheet->entry_widget))
4018     {
4019       g_object_set (sheet->entry_widget,
4020                     "has-frame", FALSE,
4021                     NULL);
4022     }
4023
4024   if (GTK_WIDGET_REALIZED (sheet))
4025     {
4026       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4027       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4028       gtk_widget_realize (sheet->entry_widget);
4029     }
4030
4031   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4032                             G_CALLBACK (psppire_sheet_entry_key_press),
4033                             sheet);
4034
4035   set_entry_widget_font (sheet);
4036
4037   gtk_widget_show (sheet->entry_widget);
4038 }
4039
4040
4041 /* Finds the last child widget that happens to be of type GtkEntry */
4042 static void
4043 find_entry (GtkWidget *w, gpointer user_data)
4044 {
4045   GtkWidget **entry = user_data;
4046   if ( GTK_IS_ENTRY (w))
4047     {
4048       *entry = w;
4049     }
4050 }
4051
4052
4053 GtkEntry *
4054 psppire_sheet_get_entry (PsppireSheet *sheet)
4055 {
4056   GtkWidget *w = sheet->entry_widget;
4057
4058   g_return_val_if_fail (sheet != NULL, NULL);
4059   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4060   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4061
4062   while (! GTK_IS_ENTRY (w))
4063     {
4064       GtkWidget *entry = NULL;
4065
4066       if (GTK_IS_CONTAINER (w))
4067         {
4068           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4069
4070           if (NULL == entry)
4071             break;
4072
4073           w = entry;
4074         }
4075     }
4076
4077   return GTK_ENTRY (w);
4078 }
4079
4080
4081 static void
4082 draw_button (PsppireSheet *sheet, GdkWindow *window,
4083              PsppireSheetButton *button, gboolean is_sensitive,
4084              GdkRectangle allocation)
4085 {
4086   GtkShadowType shadow_type;
4087   gint text_width = 0, text_height = 0;
4088   PangoAlignment align = PANGO_ALIGN_LEFT;
4089
4090   gboolean rtl ;
4091
4092   gint state = 0;
4093
4094   g_return_if_fail (sheet != NULL);
4095   g_return_if_fail (button != NULL);
4096
4097
4098   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4099
4100   gdk_window_clear_area (window,
4101                          allocation.x, allocation.y,
4102                          allocation.width, allocation.height);
4103
4104   gtk_widget_ensure_style (sheet->button);
4105
4106   gtk_paint_box (sheet->button->style, window,
4107                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4108                  &allocation,
4109                  GTK_WIDGET (sheet->button),
4110                  NULL,
4111                  allocation.x, allocation.y,
4112                  allocation.width, allocation.height);
4113
4114   state = button->state;
4115   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4116
4117   if (state == GTK_STATE_ACTIVE)
4118     shadow_type = GTK_SHADOW_IN;
4119   else
4120     shadow_type = GTK_SHADOW_OUT;
4121
4122   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4123     gtk_paint_box (sheet->button->style, window,
4124                    button->state, shadow_type,
4125                    &allocation, GTK_WIDGET (sheet->button),
4126                    NULL,
4127                    allocation.x, allocation.y,
4128                    allocation.width, allocation.height);
4129
4130   if ( button->overstruck)
4131     {
4132       GdkPoint points[2] = {
4133         {allocation.x,  allocation.y},
4134         {allocation.x + allocation.width,
4135          allocation.y + allocation.height}
4136       };
4137
4138       gtk_paint_polygon (sheet->button->style,
4139                          window,
4140                          button->state,
4141                          shadow_type,
4142                          NULL,
4143                          GTK_WIDGET (sheet),
4144                          NULL,
4145                          points,
4146                          2,
4147                          TRUE);
4148     }
4149
4150   if (button->label_visible)
4151     {
4152       text_height = DEFAULT_ROW_HEIGHT -
4153         2 * COLUMN_TITLES_HEIGHT;
4154
4155       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4156                                  &allocation);
4157       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4158                                  &allocation);
4159
4160       allocation.y += 2 * sheet->button->style->ythickness;
4161
4162       if (button->label && strlen (button->label) > 0)
4163         {
4164           PangoRectangle rect;
4165           gchar *line = button->label;
4166
4167           PangoLayout *layout = NULL;
4168           gint real_x = allocation.x;
4169           gint real_y = allocation.y;
4170
4171           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4172           pango_layout_get_extents (layout, NULL, &rect);
4173
4174           text_width = PANGO_PIXELS (rect.width);
4175           switch (button->justification)
4176             {
4177             case GTK_JUSTIFY_LEFT:
4178               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4179               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4180               break;
4181             case GTK_JUSTIFY_RIGHT:
4182               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4183               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4184               break;
4185             case GTK_JUSTIFY_CENTER:
4186             default:
4187               real_x = allocation.x + (allocation.width - text_width)/2;
4188               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4189               pango_layout_set_justify (layout, TRUE);
4190             }
4191           pango_layout_set_alignment (layout, align);
4192           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4193                             window,
4194                             state,
4195                             FALSE,
4196                             &allocation,
4197                             GTK_WIDGET (sheet),
4198                             "label",
4199                             real_x, real_y,
4200                             layout);
4201           g_object_unref (layout);
4202         }
4203
4204       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4205                                  NULL);
4206       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4207
4208     }
4209
4210   psppire_sheet_button_free (button);
4211 }
4212
4213
4214 /* Draw the column title buttons FIRST through to LAST */
4215 static void
4216 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4217 {
4218   GdkRectangle rect;
4219   gint col;
4220   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4221
4222   if (!sheet->column_titles_visible) return;
4223
4224   g_return_if_fail (first >= min_visible_column (sheet));
4225   g_return_if_fail (last <= max_visible_column (sheet));
4226
4227   rect.y = 0;
4228   rect.height = sheet->column_title_area.height;
4229   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4230   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4231     + psppire_axis_unit_size (sheet->haxis, last);
4232
4233   rect.x -= sheet->hadjustment->value;
4234
4235   minimize_int (&rect.width, sheet->column_title_area.width);
4236   maximize_int (&rect.x, 0);
4237
4238   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4239
4240   for (col = first ; col <= last ; ++col)
4241     {
4242       GdkRectangle allocation;
4243       gboolean is_sensitive = FALSE;
4244
4245       PsppireSheetButton *
4246         button = psppire_sheet_model_get_column_button (sheet->model, col);
4247       allocation.y = 0;
4248       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4249         + CELL_SPACING;
4250       allocation.x -= sheet->hadjustment->value;
4251
4252       allocation.height = sheet->column_title_area.height;
4253       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4254       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4255
4256       draw_button (sheet, sheet->column_title_window,
4257                    button, is_sensitive, allocation);
4258     }
4259
4260   gdk_window_end_paint (sheet->column_title_window);
4261 }
4262
4263
4264 static void
4265 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4266 {
4267   GdkRectangle rect;
4268   gint row;
4269   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4270
4271   if (!sheet->row_titles_visible) return;
4272
4273   g_return_if_fail (first >= min_visible_row (sheet));
4274   g_return_if_fail (last <= max_visible_row (sheet));
4275
4276   rect.x = 0;
4277   rect.width = sheet->row_title_area.width;
4278   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4279   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4280     + psppire_axis_unit_size (sheet->vaxis, last);
4281
4282   rect.y -= sheet->vadjustment->value;
4283
4284   minimize_int (&rect.height, sheet->row_title_area.height);
4285   maximize_int (&rect.y, 0);
4286
4287   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4288   for (row = first; row <= last; ++row)
4289     {
4290       GdkRectangle allocation;
4291
4292       gboolean is_sensitive = FALSE;
4293
4294       PsppireSheetButton *button =
4295         psppire_sheet_model_get_row_button (sheet->model, row);
4296       allocation.x = 0;
4297       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4298         + CELL_SPACING;
4299       allocation.y -= sheet->vadjustment->value;
4300
4301       allocation.width = sheet->row_title_area.width;
4302       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4303       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4304
4305       draw_button (sheet, sheet->row_title_window,
4306                    button, is_sensitive, allocation);
4307     }
4308
4309   gdk_window_end_paint (sheet->row_title_window);
4310 }
4311
4312 /* SCROLLBARS
4313  *
4314  * functions:
4315  * adjust_scrollbars
4316  * vadjustment_value_changed
4317  * hadjustment_value_changed */
4318
4319
4320 static void
4321 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
4322 {
4323   double position =
4324     (adj->value + adj->page_size)
4325     /
4326     (adj->upper - adj->lower);
4327
4328   const glong last_item = psppire_axis_unit_count (axis) - 1;
4329
4330   if (isnan (position) || position < 0)
4331     position = 0;
4332
4333   adj->upper =
4334     psppire_axis_start_pixel (axis, last_item)
4335     +
4336     psppire_axis_unit_size (axis, last_item)
4337     ;
4338
4339   adj->lower = 0;
4340   adj->page_size = page_size;
4341
4342 #if 0
4343   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
4344
4345   if ( adj->value < adj->lower)
4346     adj->value = adj->lower;
4347 #endif
4348
4349   gtk_adjustment_changed (adj);
4350 }
4351
4352
4353 static void
4354 adjust_scrollbars (PsppireSheet *sheet)
4355 {
4356   gint width, height;
4357
4358   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4359     return;
4360
4361   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
4362
4363   if ( sheet->row_titles_visible)
4364     width -= sheet->row_title_area.width;
4365
4366   if (sheet->column_titles_visible)
4367     height -= sheet->column_title_area.height;
4368
4369   if (sheet->vadjustment)
4370     {
4371       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
4372
4373       sheet->vadjustment->step_increment =
4374         ROWS_PER_STEP *
4375         psppire_axis_unit_size (sheet->vaxis, last_row);
4376
4377       sheet->vadjustment->page_increment =
4378         height -
4379         sheet->column_title_area.height -
4380         psppire_axis_unit_size (sheet->vaxis, last_row);
4381
4382       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
4383     }
4384
4385   if (sheet->hadjustment)
4386     {
4387       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
4388       sheet->hadjustment->step_increment = 1;
4389
4390       sheet->hadjustment->page_increment = width;
4391
4392       sheet->hadjustment->upper =
4393         psppire_axis_start_pixel (sheet->haxis, last_col)
4394         +
4395         psppire_axis_unit_size (sheet->haxis, last_col)
4396         ;
4397
4398       update_adjustment (sheet->hadjustment, sheet->haxis, width);
4399     }
4400 }
4401
4402 /* Subtracts the region of WIDGET from REGION */
4403 static void
4404 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
4405 {
4406   GdkRectangle rect;
4407   GdkRectangle intersect;
4408   GdkRegion *region2;
4409
4410   gdk_region_get_clipbox (region, &rect);
4411   gtk_widget_intersect (widget,
4412                         &rect,
4413                         &intersect);
4414
4415   region2 = gdk_region_rectangle (&intersect);
4416   gdk_region_subtract (region, region2);
4417   gdk_region_destroy (region2);
4418 }
4419
4420 static void
4421 vadjustment_value_changed (GtkAdjustment *adjustment,
4422                            gpointer data)
4423 {
4424   GdkRegion *region;
4425   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4426
4427   g_return_if_fail (adjustment != NULL);
4428
4429   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4430
4431   gtk_widget_hide (sheet->entry_widget);
4432
4433   region =
4434     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4435
4436   subtract_widget_region (region, sheet->button);
4437   gdk_window_begin_paint_region (sheet->sheet_window, region);
4438
4439   draw_sheet_region (sheet, region);
4440
4441   draw_row_title_buttons (sheet);
4442   psppire_sheet_draw_active_cell (sheet);
4443
4444   gdk_window_end_paint (sheet->sheet_window);
4445   gdk_region_destroy (region);
4446 }
4447
4448
4449 static void
4450 hadjustment_value_changed (GtkAdjustment *adjustment,
4451                            gpointer data)
4452 {
4453   GdkRegion *region;
4454   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4455
4456   g_return_if_fail (adjustment != NULL);
4457
4458   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4459
4460   gtk_widget_hide (sheet->entry_widget);
4461
4462
4463   region =
4464     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4465
4466   subtract_widget_region (region, sheet->button);
4467   gdk_window_begin_paint_region (sheet->sheet_window, region);
4468
4469   draw_sheet_region (sheet, region);
4470
4471   draw_column_title_buttons (sheet);
4472
4473   psppire_sheet_draw_active_cell (sheet);
4474
4475   gdk_window_end_paint (sheet->sheet_window);
4476
4477   gdk_region_destroy (region);
4478 }
4479
4480
4481 /* COLUMN RESIZING */
4482 static void
4483 draw_xor_vline (PsppireSheet *sheet)
4484 {
4485   gint height;
4486   gint xpos = sheet->x_drag;
4487   gdk_drawable_get_size (sheet->sheet_window,
4488                          NULL, &height);
4489
4490   if (sheet->row_titles_visible)
4491     xpos += sheet->row_title_area.width;
4492
4493   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4494                  xpos,
4495                  sheet->column_title_area.height,
4496                  xpos,
4497                  height + CELL_SPACING);
4498 }
4499
4500 /* ROW RESIZING */
4501 static void
4502 draw_xor_hline (PsppireSheet *sheet)
4503
4504 {
4505   gint width;
4506   gint ypos = sheet->y_drag;
4507
4508   gdk_drawable_get_size (sheet->sheet_window,
4509                          &width, NULL);
4510
4511
4512   if (sheet->column_titles_visible)
4513     ypos += sheet->column_title_area.height;
4514
4515   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4516                  sheet->row_title_area.width,
4517                  ypos,
4518                  width + CELL_SPACING,
4519                  ypos);
4520 }
4521
4522 /* SELECTED RANGE */
4523 static void
4524 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
4525 {
4526   gint i = 0;
4527   GdkRectangle clip_area, area;
4528   GdkGCValues values;
4529
4530   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
4531   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
4532   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
4533     psppire_axis_unit_size (sheet->haxis, range.coli);
4534   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
4535     psppire_axis_unit_size (sheet->vaxis, range.rowi);
4536
4537   clip_area.x = sheet->row_title_area.width;
4538   clip_area.y = sheet->column_title_area.height;
4539
4540   gdk_drawable_get_size (sheet->sheet_window,
4541                          &clip_area.width, &clip_area.height);
4542
4543   if (!sheet->row_titles_visible) clip_area.x = 0;
4544   if (!sheet->column_titles_visible) clip_area.y = 0;
4545
4546   if (area.x < 0)
4547     {
4548       area.width = area.width + area.x;
4549       area.x = 0;
4550     }
4551   if (area.width > clip_area.width) area.width = clip_area.width + 10;
4552   if (area.y < 0)
4553     {
4554       area.height = area.height + area.y;
4555       area.y = 0;
4556     }
4557   if (area.height > clip_area.height) area.height = clip_area.height + 10;
4558
4559   clip_area.x--;
4560   clip_area.y--;
4561   clip_area.width += 3;
4562   clip_area.height += 3;
4563
4564   gdk_gc_get_values (sheet->xor_gc, &values);
4565
4566   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
4567
4568   gdk_draw_rectangle (sheet->sheet_window,
4569                       sheet->xor_gc,
4570                       FALSE,
4571                       area.x + i, area.y + i,
4572                       area.width - 2 * i, area.height - 2 * i);
4573
4574
4575   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
4576
4577   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
4578 }
4579
4580
4581 static void
4582 set_column_width (PsppireSheet *sheet,
4583                   gint column,
4584                   gint width)
4585 {
4586   g_return_if_fail (sheet != NULL);
4587   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4588
4589   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
4590     return;
4591
4592   if ( width <= 0)
4593     return;
4594
4595   psppire_axis_resize (sheet->haxis, column,
4596                        width - sheet->cell_padding->left -
4597                        sheet->cell_padding->right);
4598
4599   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4600     {
4601       draw_column_title_buttons (sheet);
4602       adjust_scrollbars (sheet);
4603       psppire_sheet_size_allocate_entry (sheet);
4604       redraw_range (sheet, NULL);
4605     }
4606 }
4607
4608 static void
4609 set_row_height (PsppireSheet *sheet,
4610                 gint row,
4611                 gint height)
4612 {
4613   g_return_if_fail (sheet != NULL);
4614   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4615
4616   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
4617     return;
4618
4619   if (height <= 0)
4620     return;
4621
4622   psppire_axis_resize (sheet->vaxis, row,
4623                        height - sheet->cell_padding->top -
4624                        sheet->cell_padding->bottom);
4625
4626   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
4627     {
4628       draw_row_title_buttons (sheet);
4629       adjust_scrollbars (sheet);
4630       psppire_sheet_size_allocate_entry (sheet);
4631       redraw_range (sheet, NULL);
4632     }
4633 }
4634
4635 static gboolean
4636 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
4637                               PsppireSheetCellAttr *attr)
4638 {
4639   GdkColor *fg, *bg;
4640   const GtkJustification *j ;
4641   GdkColormap *colormap;
4642
4643   g_return_val_if_fail (sheet != NULL, FALSE);
4644   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
4645
4646   if (row < 0 || col < 0) return FALSE;
4647
4648   attr->foreground = GTK_WIDGET (sheet)->style->black;
4649   attr->background = sheet->color[BG_COLOR];
4650
4651   attr->border.width = 0;
4652   attr->border.line_style = GDK_LINE_SOLID;
4653   attr->border.cap_style = GDK_CAP_NOT_LAST;
4654   attr->border.join_style = GDK_JOIN_MITER;
4655   attr->border.mask = 0;
4656   attr->border.color = GTK_WIDGET (sheet)->style->black;
4657
4658   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
4659   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
4660   if ( fg )
4661     {
4662       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
4663       attr->foreground = *fg;
4664     }
4665
4666   bg = psppire_sheet_model_get_background (sheet->model, row, col);
4667   if ( bg )
4668     {
4669       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
4670       attr->background = *bg;
4671     }
4672
4673   attr->justification =
4674     psppire_sheet_model_get_column_justification (sheet->model, col);
4675
4676   j = psppire_sheet_model_get_justification (sheet->model, row, col);
4677   if (j)
4678     attr->justification = *j;
4679
4680   return TRUE;
4681 }
4682
4683 static void
4684 psppire_sheet_button_size_request        (PsppireSheet *sheet,
4685                                           const PsppireSheetButton *button,
4686                                           GtkRequisition *button_requisition)
4687 {
4688   GtkRequisition requisition;
4689   GtkRequisition label_requisition;
4690
4691   label_requisition.height = DEFAULT_ROW_HEIGHT;
4692   label_requisition.width = COLUMN_MIN_WIDTH;
4693
4694   requisition.height = DEFAULT_ROW_HEIGHT;
4695   requisition.width = COLUMN_MIN_WIDTH;
4696
4697
4698   *button_requisition = requisition;
4699   button_requisition->width = MAX (requisition.width, label_requisition.width);
4700   button_requisition->height = MAX (requisition.height, label_requisition.height);
4701
4702 }
4703
4704 static void
4705 psppire_sheet_forall (GtkContainer *container,
4706                       gboolean include_internals,
4707                       GtkCallback callback,
4708                       gpointer callback_data)
4709 {
4710   PsppireSheet *sheet = PSPPIRE_SHEET (container);
4711
4712   g_return_if_fail (callback != NULL);
4713
4714   if (sheet->button && sheet->button->parent)
4715     (* callback) (sheet->button, callback_data);
4716
4717   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
4718     (* callback) (sheet->entry_widget, callback_data);
4719 }
4720
4721
4722 PsppireSheetModel *
4723 psppire_sheet_get_model (const PsppireSheet *sheet)
4724 {
4725   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4726
4727   return sheet->model;
4728 }
4729
4730
4731 PsppireSheetButton *
4732 psppire_sheet_button_new (void)
4733 {
4734   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
4735
4736   button->state = GTK_STATE_NORMAL;
4737   button->label = NULL;
4738   button->label_visible = TRUE;
4739   button->justification = GTK_JUSTIFY_FILL;
4740   button->overstruck = FALSE;
4741
4742   return button;
4743 }
4744
4745
4746 void
4747 psppire_sheet_button_free (PsppireSheetButton *button)
4748 {
4749   if (!button) return ;
4750
4751   g_free (button->label);
4752   g_free (button);
4753 }
4754
4755 static void
4756 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
4757 {
4758   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
4759
4760   if ( NULL == celltext)
4761     return;
4762
4763   g_string_append (string, celltext);
4764   g_free (celltext);
4765 }
4766
4767
4768 static GString *
4769 range_to_text (const PsppireSheet *sheet)
4770 {
4771   gint r, c;
4772   GString *string;
4773
4774   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4775     return NULL;
4776
4777   string = g_string_sized_new (80);
4778
4779   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4780     {
4781       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
4782         {
4783           append_cell_text (string, sheet, r, c);
4784           g_string_append (string, "\t");
4785         }
4786       append_cell_text (string, sheet, r, c);
4787       if ( r < sheet->range.rowi)
4788         g_string_append (string, "\n");
4789     }
4790
4791   return string;
4792 }
4793
4794 static GString *
4795 range_to_html (const PsppireSheet *sheet)
4796 {
4797   gint r, c;
4798   GString *string;
4799
4800   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4801     return NULL;
4802
4803   string = g_string_sized_new (480);
4804
4805   g_string_append (string, "<html>\n");
4806   g_string_append (string, "<body>\n");
4807   g_string_append (string, "<table>\n");
4808   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4809     {
4810       g_string_append (string, "<tr>\n");
4811       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
4812         {
4813           g_string_append (string, "<td>");
4814           append_cell_text (string, sheet, r, c);
4815           g_string_append (string, "</td>\n");
4816         }
4817       g_string_append (string, "</tr>\n");
4818     }
4819   g_string_append (string, "</table>\n");
4820   g_string_append (string, "</body>\n");
4821   g_string_append (string, "</html>\n");
4822
4823   return string;
4824 }
4825
4826 enum {
4827   SELECT_FMT_NULL,
4828   SELECT_FMT_TEXT,
4829   SELECT_FMT_HTML
4830 };
4831
4832 static void
4833 primary_get_cb (GtkClipboard     *clipboard,
4834                 GtkSelectionData *selection_data,
4835                 guint             info,
4836                 gpointer          data)
4837 {
4838   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4839   GString *string = NULL;
4840
4841   switch (info)
4842     {
4843     case SELECT_FMT_TEXT:
4844       string = range_to_text (sheet);
4845       break;
4846     case SELECT_FMT_HTML:
4847       string = range_to_html (sheet);
4848       break;
4849     default:
4850       g_assert_not_reached ();
4851     }
4852
4853   gtk_selection_data_set (selection_data, selection_data->target,
4854                           8,
4855                           (const guchar *) string->str, string->len);
4856   g_string_free (string, TRUE);
4857 }
4858
4859 static void
4860 primary_clear_cb (GtkClipboard *clipboard,
4861                   gpointer      data)
4862 {
4863   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4864   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4865     return;
4866
4867   psppire_sheet_unselect_range (sheet);
4868 }
4869
4870 static void
4871 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
4872 {
4873   static const GtkTargetEntry targets[] = {
4874     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
4875     { "STRING",        0, SELECT_FMT_TEXT },
4876     { "TEXT",          0, SELECT_FMT_TEXT },
4877     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
4878     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
4879     { "text/plain",    0, SELECT_FMT_TEXT },
4880     { "text/html",     0, SELECT_FMT_HTML }
4881   };
4882
4883   GtkClipboard *clipboard;
4884
4885   if (!GTK_WIDGET_REALIZED (sheet))
4886     return;
4887
4888   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
4889                                         GDK_SELECTION_PRIMARY);
4890
4891   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
4892     {
4893       if (!gtk_clipboard_set_with_owner (clipboard, targets,
4894                                          G_N_ELEMENTS (targets),
4895                                          primary_get_cb, primary_clear_cb,
4896                                          G_OBJECT (sheet)))
4897         primary_clear_cb (clipboard, sheet);
4898     }
4899   else
4900     {
4901       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
4902         gtk_clipboard_clear (clipboard);
4903     }
4904 }