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