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