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