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