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