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