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