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