Hide tooltip when leaving sheet
[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, 2, 2 };
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 #define BORDER_WIDTH 3
1860
1861 static void
1862 psppire_sheet_realize (GtkWidget *widget)
1863 {
1864   PsppireSheet *sheet;
1865   GdkWindowAttr attributes;
1866   const gint attributes_mask =
1867     GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;
1868
1869   GdkGCValues values;
1870   GdkColormap *colormap;
1871   GdkDisplay *display;
1872
1873   g_return_if_fail (widget != NULL);
1874   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1875
1876   sheet = PSPPIRE_SHEET (widget);
1877
1878   colormap = gtk_widget_get_colormap (widget);
1879   display = gtk_widget_get_display (widget);
1880
1881   attributes.window_type = GDK_WINDOW_CHILD;
1882   attributes.x = widget->allocation.x;
1883   attributes.y = widget->allocation.y;
1884   attributes.width = widget->allocation.width;
1885   attributes.height = widget->allocation.height;
1886   attributes.wclass = GDK_INPUT_OUTPUT;
1887
1888   attributes.visual = gtk_widget_get_visual (widget);
1889   attributes.colormap = colormap;
1890
1891   attributes.event_mask = gtk_widget_get_events (widget);
1892   attributes.event_mask |= (GDK_EXPOSURE_MASK |
1893                             GDK_BUTTON_PRESS_MASK |
1894                             GDK_BUTTON_RELEASE_MASK |
1895                             GDK_KEY_PRESS_MASK |
1896                             GDK_ENTER_NOTIFY_MASK |
1897                             GDK_LEAVE_NOTIFY_MASK |
1898                             GDK_POINTER_MOTION_MASK |
1899                             GDK_POINTER_MOTION_HINT_MASK);
1900
1901   attributes.cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
1902
1903   /* main window */
1904   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1905
1906   gdk_window_set_user_data (widget->window, sheet);
1907
1908   widget->style = gtk_style_attach (widget->style, widget->window);
1909
1910   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1911
1912   gdk_color_parse ("white", &sheet->color[BG_COLOR]);
1913   gdk_colormap_alloc_color (colormap, &sheet->color[BG_COLOR], FALSE,
1914                             TRUE);
1915   gdk_color_parse ("gray", &sheet->color[GRID_COLOR]);
1916   gdk_colormap_alloc_color (colormap, &sheet->color[GRID_COLOR], FALSE,
1917                             TRUE);
1918
1919   attributes.x = 0;
1920   attributes.y = 0;
1921   attributes.width = sheet->column_title_area.width;
1922   attributes.height = sheet->column_title_area.height;
1923
1924
1925   /* column - title window */
1926   sheet->column_title_window =
1927     gdk_window_new (widget->window, &attributes, attributes_mask);
1928   gdk_window_set_user_data (sheet->column_title_window, sheet);
1929   gtk_style_set_background (widget->style, sheet->column_title_window,
1930                             GTK_STATE_NORMAL);
1931
1932
1933   attributes.x = 0;
1934   attributes.y = 0;
1935   attributes.width = sheet->row_title_area.width;
1936   attributes.height = sheet->row_title_area.height;
1937
1938   /* row - title window */
1939   sheet->row_title_window = gdk_window_new (widget->window,
1940                                             &attributes, attributes_mask);
1941   gdk_window_set_user_data (sheet->row_title_window, sheet);
1942   gtk_style_set_background (widget->style, sheet->row_title_window,
1943                             GTK_STATE_NORMAL);
1944
1945   /* sheet - window */
1946   attributes.cursor = gdk_cursor_new_for_display (display, GDK_PLUS);
1947
1948   attributes.x = 0;
1949   attributes.y = 0;
1950
1951   sheet->sheet_window = gdk_window_new (widget->window,
1952                                         &attributes, attributes_mask);
1953   gdk_window_set_user_data (sheet->sheet_window, sheet);
1954
1955   gdk_cursor_unref (attributes.cursor);
1956
1957   gdk_window_set_background (sheet->sheet_window, &widget->style->white);
1958   gdk_window_show (sheet->sheet_window);
1959
1960   /* GCs */
1961   sheet->fg_gc = gdk_gc_new (widget->window);
1962   sheet->bg_gc = gdk_gc_new (widget->window);
1963
1964   values.foreground = widget->style->white;
1965   values.function = GDK_INVERT;
1966   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
1967   values.line_width = BORDER_WIDTH;
1968
1969   sheet->xor_gc = gdk_gc_new_with_values (widget->window,
1970                                           &values,
1971                                           GDK_GC_FOREGROUND |
1972                                           GDK_GC_FUNCTION |
1973                                           GDK_GC_SUBWINDOW |
1974                                           GDK_GC_LINE_WIDTH
1975                                           );
1976
1977
1978   gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
1979   gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
1980
1981   gtk_widget_set_parent_window (sheet->button, sheet->sheet_window);
1982   gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet));
1983
1984   sheet->button->style = gtk_style_attach (sheet->button->style,
1985                                           sheet->sheet_window);
1986
1987
1988   sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
1989
1990   if (sheet->column_titles_visible)
1991     gdk_window_show (sheet->column_title_window);
1992   if (sheet->row_titles_visible)
1993     gdk_window_show (sheet->row_title_window);
1994
1995   sheet->hover_window = create_hover_window ();
1996
1997   draw_row_title_buttons (sheet);
1998   draw_column_title_buttons (sheet);
1999
2000   psppire_sheet_update_primary_selection (sheet);
2001
2002
2003   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
2004 }
2005
2006 static void
2007 create_global_button (PsppireSheet *sheet)
2008 {
2009   sheet->button = gtk_button_new_with_label (" ");
2010
2011   GTK_WIDGET_UNSET_FLAGS(sheet->button, GTK_CAN_FOCUS);
2012
2013   g_object_ref_sink (sheet->button);
2014
2015   g_signal_connect (sheet->button,
2016                     "pressed",
2017                     G_CALLBACK (global_button_clicked),
2018                     sheet);
2019 }
2020
2021 static void
2022 size_allocate_global_button (PsppireSheet *sheet)
2023 {
2024   GtkAllocation allocation;
2025
2026   if (!sheet->column_titles_visible) return;
2027   if (!sheet->row_titles_visible) return;
2028
2029   gtk_widget_size_request (sheet->button, NULL);
2030
2031   allocation.x = 0;
2032   allocation.y = 0;
2033   allocation.width = sheet->row_title_area.width;
2034   allocation.height = sheet->column_title_area.height;
2035
2036   gtk_widget_size_allocate (sheet->button, &allocation);
2037 }
2038
2039 static void
2040 global_button_clicked (GtkWidget *widget, gpointer data)
2041 {
2042   psppire_sheet_click_cell (PSPPIRE_SHEET (data), -1, -1);
2043 }
2044
2045
2046 static void
2047 psppire_sheet_unrealize (GtkWidget *widget)
2048 {
2049   PsppireSheet *sheet;
2050
2051   g_return_if_fail (widget != NULL);
2052   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
2053
2054   sheet = PSPPIRE_SHEET (widget);
2055
2056   gdk_cursor_unref (sheet->cursor_drag);
2057   sheet->cursor_drag = NULL;
2058
2059   gdk_colormap_free_colors (gtk_widget_get_colormap (widget),
2060                             sheet->color, n_COLORS);
2061
2062   g_object_unref (sheet->xor_gc);
2063   g_object_unref (sheet->fg_gc);
2064   g_object_unref (sheet->bg_gc);
2065
2066   destroy_hover_window (sheet->hover_window);
2067
2068   gdk_window_destroy (sheet->sheet_window);
2069   gdk_window_destroy (sheet->column_title_window);
2070   gdk_window_destroy (sheet->row_title_window);
2071
2072   gtk_widget_unparent (sheet->entry_widget);
2073   if (sheet->button != NULL)
2074     gtk_widget_unparent (sheet->button);
2075
2076   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
2077     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
2078 }
2079
2080 static void
2081 psppire_sheet_map (GtkWidget *widget)
2082 {
2083   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2084
2085   g_return_if_fail (widget != NULL);
2086   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
2087
2088   if (!GTK_WIDGET_MAPPED (widget))
2089     {
2090       GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
2091
2092       gdk_window_show (widget->window);
2093       gdk_window_show (sheet->sheet_window);
2094
2095       if (sheet->column_titles_visible)
2096         {
2097           draw_column_title_buttons (sheet);
2098           gdk_window_show (sheet->column_title_window);
2099         }
2100       if (sheet->row_titles_visible)
2101         {
2102           draw_row_title_buttons (sheet);
2103           gdk_window_show (sheet->row_title_window);
2104         }
2105
2106       if (!GTK_WIDGET_MAPPED (sheet->entry_widget)
2107           && sheet->active_cell.row >= 0
2108           && sheet->active_cell.col >= 0 )
2109         {
2110           gtk_widget_show (sheet->entry_widget);
2111           gtk_widget_map (sheet->entry_widget);
2112         }
2113
2114       if (!GTK_WIDGET_MAPPED (sheet->button))
2115         {
2116           gtk_widget_show (sheet->button);
2117           gtk_widget_map (sheet->button);
2118         }
2119
2120       redraw_range (sheet, NULL);
2121       change_active_cell (sheet,
2122                      sheet->active_cell.row,
2123                      sheet->active_cell.col);
2124     }
2125 }
2126
2127 static void
2128 psppire_sheet_unmap (GtkWidget *widget)
2129 {
2130   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2131
2132   if (!GTK_WIDGET_MAPPED (widget))
2133     return;
2134
2135   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
2136
2137   gdk_window_hide (sheet->sheet_window);
2138   if (sheet->column_titles_visible)
2139     gdk_window_hide (sheet->column_title_window);
2140   if (sheet->row_titles_visible)
2141     gdk_window_hide (sheet->row_title_window);
2142   gdk_window_hide (widget->window);
2143
2144   gtk_widget_unmap (sheet->entry_widget);
2145   gtk_widget_unmap (sheet->button);
2146   gtk_widget_unmap (sheet->hover_window->window);
2147 }
2148
2149 /* get cell attributes of the given cell */
2150 /* TRUE means that the cell is currently allocated */
2151 static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
2152                                               gint row, gint col,
2153                                               PsppireSheetCellAttr *attributes);
2154
2155
2156
2157 static void
2158 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
2159 {
2160   PangoLayout *layout;
2161   PangoRectangle text;
2162   PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc;
2163   gint font_height;
2164
2165   gchar *label;
2166
2167   PsppireSheetCellAttr attributes;
2168   GdkRectangle area;
2169
2170   g_return_if_fail (sheet != NULL);
2171
2172   /* bail now if we aren't yet drawable */
2173   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
2174
2175   if (row < 0 ||
2176       row >= psppire_axis_unit_count (sheet->vaxis))
2177     return;
2178
2179   if (col < 0 ||
2180       col >= psppire_axis_unit_count (sheet->haxis))
2181     return;
2182
2183   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2184
2185   /* select GC for background rectangle */
2186   gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
2187   gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
2188
2189   rectangle_from_cell (sheet, row, col, &area);
2190
2191   gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
2192
2193   if (sheet->show_grid)
2194     {
2195       gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]);
2196
2197       gdk_draw_rectangle (sheet->sheet_window,
2198                           sheet->bg_gc,
2199                           FALSE,
2200                           area.x, area.y,
2201                           area.width, area.height);
2202     }
2203
2204
2205   label = psppire_sheet_cell_get_text (sheet, row, col);
2206   if (NULL == label)
2207     return;
2208
2209
2210   layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label);
2211   dispose_string (sheet, label);
2212
2213
2214   pango_layout_set_font_description (layout, font_desc);
2215
2216   pango_layout_get_pixel_extents (layout, NULL, &text);
2217
2218   gdk_gc_set_clip_rectangle (sheet->fg_gc, &area);
2219
2220   font_height = pango_font_description_get_size (font_desc);
2221   if ( !pango_font_description_get_size_is_absolute (font_desc))
2222     font_height /= PANGO_SCALE;
2223
2224
2225   if ( sheet->cell_padding )
2226     {
2227       area.x += sheet->cell_padding->left;
2228       area.width -= sheet->cell_padding->right
2229         + sheet->cell_padding->left;
2230
2231       area.y += sheet->cell_padding->top;
2232       area.height -= sheet->cell_padding->bottom
2233         +
2234         sheet->cell_padding->top;
2235     }
2236
2237   /* Centre the text vertically */
2238   area.y += (area.height - font_height) / 2.0;
2239
2240   switch (attributes.justification)
2241     {
2242     case GTK_JUSTIFY_RIGHT:
2243       area.x += area.width - text.width;
2244       break;
2245     case GTK_JUSTIFY_CENTER:
2246       area.x += (area.width - text.width) / 2.0;
2247       break;
2248     case GTK_JUSTIFY_LEFT:
2249       /* Do nothing */
2250       break;
2251     default:
2252       g_critical ("Unhandled justification %d in column %d\n",
2253                  attributes.justification, col);
2254       break;
2255     }
2256
2257   gdk_draw_layout (sheet->sheet_window, sheet->fg_gc,
2258                    area.x,
2259                    area.y,
2260                    layout);
2261
2262   gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL);
2263   g_object_unref (layout);
2264 }
2265
2266
2267 static void
2268 draw_sheet_region (PsppireSheet *sheet, GdkRegion *region)
2269 {
2270   PsppireSheetRange range;
2271   GdkRectangle area;
2272   gint y, x;
2273   gint i, j;
2274
2275   PsppireSheetRange drawing_range;
2276
2277   gdk_region_get_clipbox (region, &area);
2278
2279   y = area.y + sheet->vadjustment->value;
2280   x = area.x + sheet->hadjustment->value;
2281
2282   if ( sheet->column_titles_visible)
2283     y -= sheet->column_title_area.height;
2284
2285   if ( sheet->row_titles_visible)
2286     x -= sheet->row_title_area.width;
2287
2288   maximize_int (&x, 0);
2289   maximize_int (&y, 0);
2290
2291   range.row0 = row_from_ypixel (sheet, y);
2292   range.rowi = row_from_ypixel (sheet, y + area.height);
2293
2294   range.col0 = column_from_xpixel (sheet, x);
2295   range.coli = column_from_xpixel (sheet, x + area.width);
2296
2297   g_return_if_fail (sheet != NULL);
2298   g_return_if_fail (PSPPIRE_SHEET (sheet));
2299
2300   if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return;
2301   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2302   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
2303
2304
2305   drawing_range.row0 = MAX (range.row0, min_visible_row (sheet));
2306   drawing_range.col0 = MAX (range.col0, min_visible_column (sheet));
2307   drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet));
2308   drawing_range.coli = MIN (range.coli, max_visible_column (sheet));
2309
2310   g_return_if_fail (drawing_range.rowi >= drawing_range.row0);
2311   g_return_if_fail (drawing_range.coli >= drawing_range.col0);
2312
2313   for (i = drawing_range.row0; i <= drawing_range.rowi; i++)
2314     {
2315       for (j = drawing_range.col0; j <= drawing_range.coli; j++)
2316         psppire_sheet_cell_draw (sheet, i, j);
2317     }
2318
2319   if (sheet->state != PSPPIRE_SHEET_NORMAL &&
2320       psppire_sheet_range_isvisible (sheet, &sheet->range))
2321     psppire_sheet_range_draw_selection (sheet, drawing_range);
2322
2323
2324   if (sheet->state == GTK_STATE_NORMAL &&
2325       sheet->active_cell.row >= drawing_range.row0 &&
2326       sheet->active_cell.row <= drawing_range.rowi &&
2327       sheet->active_cell.col >= drawing_range.col0 &&
2328       sheet->active_cell.col <= drawing_range.coli)
2329     psppire_sheet_show_entry_widget (sheet);
2330 }
2331
2332
2333 static void
2334 psppire_sheet_range_draw_selection (PsppireSheet *sheet, PsppireSheetRange range)
2335 {
2336   GdkRectangle area;
2337   gint i, j;
2338   PsppireSheetRange aux;
2339
2340   if (range.col0 > sheet->range.coli || range.coli < sheet->range.col0 ||
2341       range.row0 > sheet->range.rowi || range.rowi < sheet->range.row0)
2342     return;
2343
2344   if (!psppire_sheet_range_isvisible (sheet, &range)) return;
2345   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2346
2347   aux = range;
2348
2349   range.col0 = MAX (sheet->range.col0, range.col0);
2350   range.coli = MIN (sheet->range.coli, range.coli);
2351   range.row0 = MAX (sheet->range.row0, range.row0);
2352   range.rowi = MIN (sheet->range.rowi, range.rowi);
2353
2354   range.col0 = MAX (range.col0, min_visible_column (sheet));
2355   range.coli = MIN (range.coli, max_visible_column (sheet));
2356   range.row0 = MAX (range.row0, min_visible_row (sheet));
2357   range.rowi = MIN (range.rowi, max_visible_row (sheet));
2358
2359   for (i = range.row0; i <= range.rowi; i++)
2360     {
2361       for (j = range.col0; j <= range.coli; j++)
2362         {
2363           if (psppire_sheet_cell_get_state (sheet, i, j) == GTK_STATE_SELECTED)
2364             {
2365               rectangle_from_cell (sheet, i, j, &area);
2366
2367               if (i == sheet->range.row0)
2368                 {
2369                   area.y = area.y + 2;
2370                   area.height = area.height - 2;
2371                 }
2372               if (i == sheet->range.rowi) area.height = area.height - 3;
2373               if (j == sheet->range.col0)
2374                 {
2375                   area.x = area.x + 2;
2376                   area.width = area.width - 2;
2377                 }
2378               if (j == sheet->range.coli) area.width = area.width - 3;
2379
2380               if (i != sheet->active_cell.row || j != sheet->active_cell.col)
2381                 {
2382                   gdk_draw_rectangle (sheet->sheet_window,
2383                                       sheet->xor_gc,
2384                                       TRUE,
2385                                       area.x + 1, area.y + 1,
2386                                       area.width, area.height);
2387                 }
2388             }
2389
2390         }
2391     }
2392
2393   psppire_sheet_draw_border (sheet, sheet->range);
2394 }
2395
2396 static inline gint
2397 safe_strcmp (const gchar *s1, const gchar *s2)
2398 {
2399   if ( !s1 && !s2) return 0;
2400   if ( !s1) return -1;
2401   if ( !s2) return +1;
2402   return strcmp (s1, s2);
2403 }
2404
2405 static void
2406 psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col,
2407                     GtkJustification justification,
2408                     const gchar *text)
2409 {
2410   PsppireSheetModel *model ;
2411   gchar *old_text ;
2412
2413   g_return_if_fail (sheet != NULL);
2414   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2415
2416   if (col >= psppire_axis_unit_count (sheet->haxis)
2417       || row >= psppire_axis_unit_count (sheet->vaxis))
2418     return;
2419
2420   if (col < 0 || row < 0) return;
2421
2422   model = psppire_sheet_get_model (sheet);
2423
2424   old_text = psppire_sheet_model_get_string (model, row, col);
2425
2426   if (0 != safe_strcmp (old_text, text))
2427     {
2428       g_signal_handler_block    (sheet->model, sheet->update_handler_id);
2429       psppire_sheet_model_set_string (model, text, row, col);
2430       g_signal_handler_unblock  (sheet->model, sheet->update_handler_id);
2431     }
2432
2433   if ( psppire_sheet_model_free_strings (model))
2434     g_free (old_text);
2435 }
2436
2437
2438 void
2439 psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column)
2440 {
2441   PsppireSheetRange range;
2442
2443   g_return_if_fail (sheet != NULL);
2444   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2445   if (column >= psppire_axis_unit_count (sheet->haxis) ||
2446       row >= psppire_axis_unit_count (sheet->vaxis)) return;
2447
2448   if (column < 0 || row < 0) return;
2449
2450   range.row0 = row;
2451   range.rowi = row;
2452   range.col0 = min_visible_column (sheet);
2453   range.coli = max_visible_column (sheet);
2454
2455   psppire_sheet_real_cell_clear (sheet, row, column);
2456
2457   redraw_range (sheet, &range);
2458 }
2459
2460 static void
2461 psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column)
2462 {
2463   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
2464
2465   gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column);
2466
2467   if (old_text && strlen (old_text) > 0 )
2468     {
2469       psppire_sheet_model_datum_clear (model, row, column);
2470     }
2471
2472   dispose_string (sheet, old_text);
2473 }
2474
2475 gchar *
2476 psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col)
2477 {
2478   PsppireSheetModel *model;
2479   g_return_val_if_fail (sheet != NULL, NULL);
2480   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
2481
2482   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis))
2483     return NULL;
2484   if (col < 0 || row < 0) return NULL;
2485
2486   model = psppire_sheet_get_model (sheet);
2487
2488   if ( !model )
2489     return NULL;
2490
2491   return psppire_sheet_model_get_string (model, row, col);
2492 }
2493
2494
2495 static GtkStateType
2496 psppire_sheet_cell_get_state (PsppireSheet *sheet, gint row, gint col)
2497 {
2498   gint state;
2499   PsppireSheetRange *range;
2500
2501   g_return_val_if_fail (sheet != NULL, 0);
2502   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2503   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis)) return 0;
2504   if (col < 0 || row < 0) return 0;
2505
2506   state = sheet->state;
2507   range = &sheet->range;
2508
2509   switch (state)
2510     {
2511     case PSPPIRE_SHEET_NORMAL:
2512       return GTK_STATE_NORMAL;
2513       break;
2514     case PSPPIRE_SHEET_ROW_SELECTED:
2515       if (row >= range->row0 && row <= range->rowi)
2516         return GTK_STATE_SELECTED;
2517       break;
2518     case PSPPIRE_SHEET_COLUMN_SELECTED:
2519       if (col >= range->col0 && col <= range->coli)
2520         return GTK_STATE_SELECTED;
2521       break;
2522     case PSPPIRE_SHEET_RANGE_SELECTED:
2523       if (row >= range->row0 && row <= range->rowi && \
2524           col >= range->col0 && col <= range->coli)
2525         return GTK_STATE_SELECTED;
2526       break;
2527     }
2528   return GTK_STATE_NORMAL;
2529 }
2530
2531 /* Convert X, Y (in pixels) to *ROW, *COLUMN
2532    If the function returns FALSE, then the results will be unreliable.
2533 */
2534 static gboolean
2535 psppire_sheet_get_pixel_info (PsppireSheet *sheet,
2536                           gint x,
2537                           gint y,
2538                           gint *row,
2539                           gint *column)
2540 {
2541   gint trow, tcol;
2542   *row = -G_MAXINT;
2543   *column = -G_MAXINT;
2544
2545   g_return_val_if_fail (sheet != NULL, 0);
2546   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2547
2548   /* bounds checking, return false if the user clicked
2549      on a blank area */
2550   if (y < 0)
2551     return FALSE;
2552
2553   if (x < 0)
2554     return FALSE;
2555
2556   if ( sheet->column_titles_visible)
2557     y -= sheet->column_title_area.height;
2558
2559   y += sheet->vadjustment->value;
2560
2561   if ( y < 0 && sheet->column_titles_visible)
2562     {
2563       trow = -1;
2564     }
2565   else
2566     {
2567       trow = row_from_ypixel (sheet, y);
2568       if (trow > psppire_axis_unit_count (sheet->vaxis))
2569         return FALSE;
2570     }
2571
2572   *row = trow;
2573
2574   if ( sheet->row_titles_visible)
2575     x -= sheet->row_title_area.width;
2576
2577   x += sheet->hadjustment->value;
2578
2579   if ( x < 0 && sheet->row_titles_visible)
2580     {
2581       tcol = -1;
2582     }
2583   else
2584     {
2585       tcol = column_from_xpixel (sheet, x);
2586       if (tcol > psppire_axis_unit_count (sheet->haxis))
2587         return FALSE;
2588     }
2589
2590   *column = tcol;
2591
2592   return TRUE;
2593 }
2594
2595 gboolean
2596 psppire_sheet_get_cell_area (PsppireSheet *sheet,
2597                          gint row,
2598                          gint column,
2599                          GdkRectangle *area)
2600 {
2601   g_return_val_if_fail (sheet != NULL, 0);
2602   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2603
2604   if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis))
2605     return FALSE;
2606
2607   area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column);
2608   area->y = (row == -1)    ? 0 : psppire_axis_start_pixel (sheet->vaxis, row);
2609
2610   area->width= (column == -1) ? sheet->row_title_area.width
2611     : psppire_axis_unit_size (sheet->haxis, column);
2612
2613   area->height= (row == -1) ? sheet->column_title_area.height
2614     : psppire_axis_unit_size (sheet->vaxis, row);
2615
2616   return TRUE;
2617 }
2618
2619 void
2620 psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col)
2621 {
2622   g_return_if_fail (sheet != NULL);
2623   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2624
2625   if (row < -1 || col < -1)
2626     return;
2627
2628   if (row >= psppire_axis_unit_count (sheet->vaxis)
2629       ||
2630       col >= psppire_axis_unit_count (sheet->haxis))
2631     return;
2632
2633   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2634     return;
2635
2636   if ( row == -1 || col == -1)
2637     {
2638       psppire_sheet_hide_entry_widget (sheet);
2639       return;
2640     }
2641
2642   change_active_cell (sheet, row, col);
2643 }
2644
2645 void
2646 psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column)
2647 {
2648   g_return_if_fail (sheet != NULL);
2649   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2650
2651   if ( row ) *row = sheet->active_cell.row;
2652   if (column) *column = sheet->active_cell.col;
2653 }
2654
2655 static void
2656 entry_load_text (PsppireSheet *sheet)
2657 {
2658   gint row, col;
2659   const char *text;
2660   GtkJustification justification;
2661   PsppireSheetCellAttr attributes;
2662
2663   if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return;
2664   if (sheet->state != GTK_STATE_NORMAL) return;
2665
2666   row = sheet->active_cell.row;
2667   col = sheet->active_cell.col;
2668
2669   if (row < 0 || col < 0) return;
2670
2671   text = gtk_entry_get_text (psppire_sheet_get_entry (sheet));
2672
2673   if (text && strlen (text) > 0)
2674     {
2675       psppire_sheet_get_attributes (sheet, row, col, &attributes);
2676       justification = attributes.justification;
2677       psppire_sheet_set_cell (sheet, row, col, justification, text);
2678     }
2679 }
2680
2681
2682 static void
2683 psppire_sheet_hide_entry_widget (PsppireSheet *sheet)
2684 {
2685   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2686     return;
2687
2688   if (sheet->active_cell.row < 0 ||
2689       sheet->active_cell.col < 0) return;
2690
2691   gtk_widget_hide (sheet->entry_widget);
2692   gtk_widget_unmap (sheet->entry_widget);
2693
2694   GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2695 }
2696
2697 static void
2698 change_active_cell (PsppireSheet *sheet, gint row, gint col)
2699 {
2700   gint old_row, old_col;
2701
2702   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2703
2704   if (row < 0 || col < 0)
2705     return;
2706
2707   if ( row > psppire_axis_unit_count (sheet->vaxis)
2708        || col > psppire_axis_unit_count (sheet->haxis))
2709     return;
2710
2711   if (sheet->state != PSPPIRE_SHEET_NORMAL)
2712     {
2713       sheet->state = PSPPIRE_SHEET_NORMAL;
2714       psppire_sheet_real_unselect_range (sheet, NULL);
2715     }
2716
2717   old_row = sheet->active_cell.row;
2718   old_col = sheet->active_cell.col;
2719
2720   /* Erase the old cell */
2721   psppire_sheet_draw_active_cell (sheet);
2722
2723   entry_load_text (sheet);
2724
2725   sheet->range.row0 = row;
2726   sheet->range.col0 = col;
2727   sheet->range.rowi = row;
2728   sheet->range.coli = col;
2729   sheet->active_cell.row = row;
2730   sheet->active_cell.col = col;
2731   sheet->selection_cell.row = row;
2732   sheet->selection_cell.col = col;
2733
2734   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2735
2736   GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2737
2738   psppire_sheet_draw_active_cell (sheet);
2739   psppire_sheet_show_entry_widget (sheet);
2740
2741   GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2742
2743   g_signal_emit (sheet, sheet_signals [ACTIVATE], 0,
2744                  row, col, old_row, old_col);
2745
2746 }
2747
2748 static void
2749 psppire_sheet_show_entry_widget (PsppireSheet *sheet)
2750 {
2751   GtkEntry *sheet_entry;
2752   PsppireSheetCellAttr attributes;
2753
2754   gint row, col;
2755
2756   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2757
2758   row = sheet->active_cell.row;
2759   col = sheet->active_cell.col;
2760
2761   /* Don't show the active cell, if there is no active cell: */
2762   if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */
2763     return;
2764
2765   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2766   if (sheet->state != PSPPIRE_SHEET_NORMAL) return;
2767   if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return;
2768
2769   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2770
2771   sheet_entry = psppire_sheet_get_entry (sheet);
2772
2773   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2774
2775   if (GTK_IS_ENTRY (sheet_entry))
2776     {
2777       gchar *text = psppire_sheet_cell_get_text (sheet, row, col);
2778       const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry));
2779
2780       if ( ! text )
2781         text = g_strdup ("");
2782
2783       if (strcmp (old_text, text) != 0)
2784         gtk_entry_set_text (sheet_entry, text);
2785       
2786       dispose_string (sheet, text);
2787
2788         {
2789           switch (attributes.justification)
2790             {
2791             case GTK_JUSTIFY_RIGHT:
2792               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0);
2793               break;
2794             case GTK_JUSTIFY_CENTER:
2795               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5);
2796               break;
2797             case GTK_JUSTIFY_LEFT:
2798             default:
2799               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0);
2800               break;
2801             }
2802         }
2803     }
2804
2805   psppire_sheet_size_allocate_entry (sheet);
2806
2807   gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry),
2808                             psppire_sheet_model_is_editable (sheet->model,
2809                                                        row, col));
2810   gtk_widget_map (sheet->entry_widget);
2811 }
2812
2813 static gboolean
2814 psppire_sheet_draw_active_cell (PsppireSheet *sheet)
2815 {
2816   gint row, col;
2817   PsppireSheetRange range;
2818
2819   row = sheet->active_cell.row;
2820   col = sheet->active_cell.col;
2821
2822   if (row < 0 || col < 0) return FALSE;
2823
2824   if (!psppire_sheet_cell_isvisible (sheet, row, col))
2825     return FALSE;
2826
2827   range.col0 = range.coli = col;
2828   range.row0 = range.rowi = row;
2829
2830   psppire_sheet_draw_border (sheet, range);
2831
2832   return FALSE;
2833 }
2834
2835
2836
2837 static void
2838 psppire_sheet_new_selection (PsppireSheet *sheet, PsppireSheetRange *range)
2839 {
2840   gint i, j, mask1, mask2;
2841   gint state, selected;
2842   gint x, y, width, height;
2843   PsppireSheetRange new_range, aux_range;
2844
2845   g_return_if_fail (sheet != NULL);
2846
2847   if (range == NULL) range=&sheet->range;
2848
2849   new_range=*range;
2850
2851   range->row0 = MIN (range->row0, sheet->range.row0);
2852   range->rowi = MAX (range->rowi, sheet->range.rowi);
2853   range->col0 = MIN (range->col0, sheet->range.col0);
2854   range->coli = MAX (range->coli, sheet->range.coli);
2855
2856   range->row0 = MAX (range->row0, min_visible_row (sheet));
2857   range->rowi = MIN (range->rowi, max_visible_row (sheet));
2858   range->col0 = MAX (range->col0, min_visible_column (sheet));
2859   range->coli = MIN (range->coli, max_visible_column (sheet));
2860
2861   aux_range.row0 = MAX (new_range.row0, min_visible_row (sheet));
2862   aux_range.rowi = MIN (new_range.rowi, max_visible_row (sheet));
2863   aux_range.col0 = MAX (new_range.col0, min_visible_column (sheet));
2864   aux_range.coli = MIN (new_range.coli, max_visible_column (sheet));
2865
2866   for (i = range->row0; i <= range->rowi; i++)
2867     {
2868       for (j = range->col0; j <= range->coli; j++)
2869         {
2870
2871           state = psppire_sheet_cell_get_state (sheet, i, j);
2872           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2873                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2874
2875           if (state == GTK_STATE_SELECTED && selected &&
2876               (i == sheet->range.row0 || i == sheet->range.rowi ||
2877                j == sheet->range.col0 || j == sheet->range.coli ||
2878                i == new_range.row0 || i == new_range.rowi ||
2879                j == new_range.col0 || j == new_range.coli))
2880             {
2881
2882               mask1 = i == sheet->range.row0 ? 1 : 0;
2883               mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1;
2884               mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1;
2885               mask1 = j == sheet->range.coli ? mask1 + 8 : mask1;
2886
2887               mask2 = i == new_range.row0 ? 1 : 0;
2888               mask2 = i == new_range.rowi ? mask2 + 2 : mask2;
2889               mask2 = j == new_range.col0 ? mask2 + 4 : mask2;
2890               mask2 = j == new_range.coli ? mask2 + 8 : mask2;
2891
2892               if (mask1 != mask2)
2893                 {
2894                   x = psppire_axis_start_pixel (sheet->haxis, j);
2895                   y = psppire_axis_start_pixel (sheet->vaxis, i);
2896                   width = psppire_axis_start_pixel (sheet->haxis, j)- x+
2897                     psppire_axis_unit_size (sheet->haxis, j);
2898                   height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2899
2900                   if (i == sheet->range.row0)
2901                     {
2902                       y = y - 3;
2903                       height = height + 3;
2904                     }
2905                   if (i == sheet->range.rowi) height = height + 3;
2906                   if (j == sheet->range.col0)
2907                     {
2908                       x = x - 3;
2909                       width = width + 3;
2910                     }
2911                   if (j == sheet->range.coli) width = width + 3;
2912
2913                   if (i != sheet->active_cell.row || j != sheet->active_cell.col)
2914                     {
2915                       x = psppire_axis_start_pixel (sheet->haxis, j);
2916                       y = psppire_axis_start_pixel (sheet->vaxis, i);
2917                       width = psppire_axis_start_pixel (sheet->haxis, j)- x+
2918                         psppire_axis_unit_size (sheet->haxis, j);
2919
2920                       height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2921
2922                       if (i == new_range.row0)
2923                         {
2924                           y = y+2;
2925                           height = height - 2;
2926                         }
2927                       if (i == new_range.rowi) height = height - 3;
2928                       if (j == new_range.col0)
2929                         {
2930                           x = x+2;
2931                           width = width - 2;
2932                         }
2933                       if (j == new_range.coli) width = width - 3;
2934
2935                       gdk_draw_rectangle (sheet->sheet_window,
2936                                           sheet->xor_gc,
2937                                           TRUE,
2938                                           x + 1, y + 1,
2939                                           width, height);
2940                     }
2941                 }
2942             }
2943         }
2944     }
2945
2946   for (i = range->row0; i <= range->rowi; i++)
2947     {
2948       for (j = range->col0; j <= range->coli; j++)
2949         {
2950
2951           state = psppire_sheet_cell_get_state (sheet, i, j);
2952           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2953                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2954
2955           if (state == GTK_STATE_SELECTED && !selected)
2956             {
2957
2958               x = psppire_axis_start_pixel (sheet->haxis, j);
2959               y = psppire_axis_start_pixel (sheet->vaxis, i);
2960               width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j);
2961               height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2962
2963               if (i == sheet->range.row0)
2964                 {
2965                   y = y - 3;
2966                   height = height + 3;
2967                 }
2968               if (i == sheet->range.rowi) height = height + 3;
2969               if (j == sheet->range.col0)
2970                 {
2971                   x = x - 3;
2972                   width = width + 3;
2973                 }
2974               if (j == sheet->range.coli) width = width + 3;
2975
2976             }
2977         }
2978     }
2979
2980   for (i = range->row0; i <= range->rowi; i++)
2981     {
2982       for (j = range->col0; j <= range->coli; j++)
2983         {
2984
2985           state = psppire_sheet_cell_get_state (sheet, i, j);
2986           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2987                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2988
2989           if (state != GTK_STATE_SELECTED && selected &&
2990               (i != sheet->active_cell.row || j != sheet->active_cell.col))
2991             {
2992
2993               x = psppire_axis_start_pixel (sheet->haxis, j);
2994               y = psppire_axis_start_pixel (sheet->vaxis, i);
2995               width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j);
2996               height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2997
2998               if (i == new_range.row0)
2999                 {
3000                   y = y+2;
3001                   height = height - 2;
3002                 }
3003               if (i == new_range.rowi) height = height - 3;
3004               if (j == new_range.col0)
3005                 {
3006                   x = x+2;
3007                   width = width - 2;
3008                 }
3009               if (j == new_range.coli) width = width - 3;
3010
3011               gdk_draw_rectangle (sheet->sheet_window,
3012                                   sheet->xor_gc,
3013                                   TRUE,
3014                                   x + 1, y + 1,
3015                                   width, height);
3016
3017             }
3018
3019         }
3020     }
3021
3022   for (i = aux_range.row0; i <= aux_range.rowi; i++)
3023     {
3024       for (j = aux_range.col0; j <= aux_range.coli; j++)
3025         {
3026           state = psppire_sheet_cell_get_state (sheet, i, j);
3027
3028           mask1 = i == sheet->range.row0 ? 1 : 0;
3029           mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1;
3030           mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1;
3031           mask1 = j == sheet->range.coli ? mask1 + 8 : mask1;
3032
3033           mask2 = i == new_range.row0 ? 1 : 0;
3034           mask2 = i == new_range.rowi ? mask2 + 2 : mask2;
3035           mask2 = j == new_range.col0 ? mask2 + 4 : mask2;
3036           mask2 = j == new_range.coli ? mask2 + 8 : mask2;
3037           if (mask2 != mask1 || (mask2 == mask1 && state != GTK_STATE_SELECTED))
3038             {
3039               x = psppire_axis_start_pixel (sheet->haxis, j);
3040               y = psppire_axis_start_pixel (sheet->vaxis, i);
3041               width = psppire_axis_unit_size (sheet->haxis, j);
3042               height = psppire_axis_unit_size (sheet->vaxis, i);
3043               if (mask2 & 1)
3044                 gdk_draw_rectangle (sheet->sheet_window,
3045                                     sheet->xor_gc,
3046                                     TRUE,
3047                                     x + 1, y - 1,
3048                                     width, 3);
3049
3050
3051               if (mask2 & 2)
3052                 gdk_draw_rectangle (sheet->sheet_window,
3053                                     sheet->xor_gc,
3054                                     TRUE,
3055                                     x + 1, y + height - 1,
3056                                     width, 3);
3057
3058               if (mask2 & 4)
3059                 gdk_draw_rectangle (sheet->sheet_window,
3060                                     sheet->xor_gc,
3061                                     TRUE,
3062                                     x - 1, y + 1,
3063                                     3, height);
3064
3065
3066               if (mask2 & 8)
3067                 gdk_draw_rectangle (sheet->sheet_window,
3068                                     sheet->xor_gc,
3069                                     TRUE,
3070                                     x + width - 1, y + 1,
3071                                     3, height);
3072             }
3073         }
3074     }
3075
3076   *range = new_range;
3077 }
3078
3079
3080
3081 static void
3082 psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
3083 {
3084   GdkRectangle area;
3085
3086   rectangle_from_range (sheet, &new_range, &area);
3087
3088   gdk_draw_rectangle (sheet->sheet_window,
3089                       sheet->xor_gc,
3090                       FALSE,
3091                       area.x, area.y,
3092                       area.width, area.height);
3093 }
3094
3095
3096 static void
3097 psppire_sheet_real_select_range (PsppireSheet *sheet,
3098                              const PsppireSheetRange *range)
3099 {
3100   gint state;
3101
3102   g_return_if_fail (sheet != NULL);
3103
3104   if (range == NULL) range = &sheet->range;
3105
3106   memcpy (&sheet->range, range, sizeof (*range));
3107
3108   if (range->row0 < 0 || range->rowi < 0) return;
3109   if (range->col0 < 0 || range->coli < 0) return;
3110
3111   state = sheet->state;
3112
3113 #if 0
3114   if (range->coli != sheet->range.coli || range->col0 != sheet->range.col0 ||
3115       range->rowi != sheet->range.rowi || range->row0 != sheet->range.row0)
3116     {
3117       psppire_sheet_new_selection (sheet, &sheet->range);
3118     }
3119   else
3120     {
3121       psppire_sheet_range_draw_selection (sheet, sheet->range);
3122     }
3123 #endif
3124
3125   psppire_sheet_update_primary_selection (sheet);
3126
3127   g_signal_emit (sheet, sheet_signals[SELECT_RANGE], 0, &sheet->range);
3128 }
3129
3130
3131 void
3132 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
3133 {
3134   g_return_if_fail (sheet != NULL);
3135   *range = sheet->range;
3136 }
3137
3138
3139 void
3140 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
3141 {
3142   g_return_if_fail (sheet != NULL);
3143
3144   if (range == NULL) range=&sheet->range;
3145
3146   if (range->row0 < 0 || range->rowi < 0) return;
3147   if (range->col0 < 0 || range->coli < 0) return;
3148
3149
3150   if (sheet->state != PSPPIRE_SHEET_NORMAL)
3151     psppire_sheet_real_unselect_range (sheet, NULL);
3152
3153   sheet->range.row0 = range->row0;
3154   sheet->range.rowi = range->rowi;
3155   sheet->range.col0 = range->col0;
3156   sheet->range.coli = range->coli;
3157   sheet->active_cell.row = range->row0;
3158   sheet->active_cell.col = range->col0;
3159   sheet->selection_cell.row = range->rowi;
3160   sheet->selection_cell.col = range->coli;
3161
3162   sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3163   psppire_sheet_real_select_range (sheet, NULL);
3164 }
3165
3166 void
3167 psppire_sheet_unselect_range (PsppireSheet *sheet)
3168 {
3169   if (! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
3170     return;
3171
3172   psppire_sheet_real_unselect_range (sheet, NULL);
3173   sheet->state = GTK_STATE_NORMAL;
3174
3175   change_active_cell (sheet,
3176                  sheet->active_cell.row, sheet->active_cell.col);
3177 }
3178
3179
3180 static void
3181 psppire_sheet_real_unselect_range (PsppireSheet *sheet,
3182                                const PsppireSheetRange *range)
3183 {
3184   g_return_if_fail (sheet != NULL);
3185   g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)));
3186
3187   if ( range == NULL)
3188     range = &sheet->range;
3189
3190   if (range->row0 < 0 || range->rowi < 0) return;
3191   if (range->col0 < 0 || range->coli < 0) return;
3192
3193   g_signal_emit (sheet, sheet_signals[SELECT_COLUMN], 0, -1);
3194   g_signal_emit (sheet, sheet_signals[SELECT_ROW], 0, -1);
3195
3196   sheet->range.row0 = -1;
3197   sheet->range.rowi = -1;
3198   sheet->range.col0 = -1;
3199   sheet->range.coli = -1;
3200 }
3201
3202
3203 static gint
3204 psppire_sheet_expose (GtkWidget *widget,
3205                   GdkEventExpose *event)
3206 {
3207   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3208
3209   g_return_val_if_fail (event != NULL, FALSE);
3210
3211   if (!GTK_WIDGET_DRAWABLE (widget))
3212     return FALSE;
3213
3214   /* exposure events on the sheet */
3215   if (event->window == sheet->row_title_window &&
3216       sheet->row_titles_visible)
3217     {
3218       draw_row_title_buttons_range (sheet,
3219                                     min_visible_row (sheet),
3220                                     max_visible_row (sheet));
3221     }
3222
3223   if (event->window == sheet->column_title_window &&
3224       sheet->column_titles_visible)
3225     {
3226       draw_column_title_buttons_range (sheet,
3227                                        min_visible_column (sheet),
3228                                        max_visible_column (sheet));
3229     }
3230
3231   if (event->window == sheet->sheet_window)
3232     {
3233       draw_sheet_region (sheet, event->region);
3234
3235 #if 0
3236       if (sheet->state != PSPPIRE_SHEET_NORMAL)
3237         {
3238           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
3239             psppire_sheet_range_draw (sheet, &sheet->range);
3240
3241           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
3242             psppire_sheet_range_draw (sheet, &sheet->drag_range);
3243
3244           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
3245             psppire_sheet_range_draw_selection (sheet, sheet->range);
3246           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
3247             draw_xor_rectangle (sheet, sheet->drag_range);
3248         }
3249 #endif
3250
3251       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
3252         {
3253           GdkRectangle rect;
3254           PsppireSheetRange range;
3255           range.row0 = range.rowi =  sheet->active_cell.row;
3256           range.col0 = range.coli =  sheet->active_cell.col;
3257
3258           rectangle_from_range (sheet, &range, &rect);
3259
3260           if (GDK_OVERLAP_RECTANGLE_OUT !=
3261               gdk_region_rect_in (event->region, &rect))
3262             {
3263               psppire_sheet_draw_active_cell (sheet);
3264             }
3265         }
3266
3267     }
3268
3269   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
3270
3271   return FALSE;
3272 }
3273
3274
3275 static gboolean
3276 psppire_sheet_button_press (GtkWidget *widget,
3277                         GdkEventButton *event)
3278 {
3279   PsppireSheet *sheet;
3280   GdkModifierType mods;
3281   gint x, y;
3282   gint  row, column;
3283   gboolean veto;
3284
3285   g_return_val_if_fail (widget != NULL, FALSE);
3286   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
3287   g_return_val_if_fail (event != NULL, FALSE);
3288
3289   sheet = PSPPIRE_SHEET (widget);
3290
3291   /* Cancel any pending tooltips */
3292   if (sheet->motion_timer)
3293     {
3294       g_source_remove (sheet->motion_timer);
3295       sheet->motion_timer = 0;
3296     }
3297
3298   gtk_widget_get_pointer (widget, &x, &y);
3299   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3300
3301
3302   if (event->window == sheet->column_title_window)
3303     {
3304       sheet->x_drag = event->x;
3305       g_signal_emit (sheet,
3306                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
3307                      column, event);
3308
3309       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
3310         {
3311           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
3312             g_signal_emit (sheet,
3313                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
3314         }
3315     }
3316   else if (event->window == sheet->row_title_window)
3317     {
3318       g_signal_emit (sheet,
3319                      sheet_signals[BUTTON_EVENT_ROW], 0,
3320                      row, event);
3321
3322       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3323         {
3324           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
3325             g_signal_emit (sheet,
3326                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
3327         }
3328     }
3329
3330   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
3331
3332   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
3333
3334
3335   /* press on resize windows */
3336   if (event->window == sheet->column_title_window)
3337     {
3338       sheet->x_drag = event->x;
3339
3340       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
3341         {
3342           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3343           gdk_pointer_grab (sheet->column_title_window, FALSE,
3344                             GDK_POINTER_MOTION_HINT_MASK |
3345                             GDK_BUTTON1_MOTION_MASK |
3346                             GDK_BUTTON_RELEASE_MASK,
3347                             NULL, NULL, event->time);
3348
3349           draw_xor_vline (sheet);
3350           return TRUE;
3351         }
3352     }
3353
3354   if (event->window == sheet->row_title_window)
3355     {
3356       sheet->y_drag = event->y;
3357
3358       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
3359         {
3360           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3361           gdk_pointer_grab (sheet->row_title_window, FALSE,
3362                             GDK_POINTER_MOTION_HINT_MASK |
3363                             GDK_BUTTON1_MOTION_MASK |
3364                             GDK_BUTTON_RELEASE_MASK,
3365                             NULL, NULL, event->time);
3366
3367           draw_xor_hline (sheet);
3368           return TRUE;
3369         }
3370     }
3371
3372   /* the sheet itself does not handle other than single click events */
3373   if (event->type != GDK_BUTTON_PRESS) return FALSE;
3374
3375   /* selections on the sheet */
3376   if (event->window == sheet->sheet_window)
3377     {
3378       gtk_widget_get_pointer (widget, &x, &y);
3379       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3380       gdk_pointer_grab (sheet->sheet_window, FALSE,
3381                         GDK_POINTER_MOTION_HINT_MASK |
3382                         GDK_BUTTON1_MOTION_MASK |
3383                         GDK_BUTTON_RELEASE_MASK,
3384                         NULL, NULL, event->time);
3385       gtk_grab_add (GTK_WIDGET (sheet));
3386
3387       if (sheet->selection_mode != GTK_SELECTION_SINGLE &&
3388           sheet->selection_mode != GTK_SELECTION_NONE &&
3389           sheet->cursor_drag->type == GDK_SIZING &&
3390           !PSPPIRE_SHEET_IN_SELECTION (sheet) && !PSPPIRE_SHEET_IN_RESIZE (sheet))
3391         {
3392           if (sheet->state == GTK_STATE_NORMAL)
3393             {
3394               row = sheet->active_cell.row;
3395               column = sheet->active_cell.col;
3396               sheet->active_cell.row = row;
3397               sheet->active_cell.col = column;
3398               sheet->drag_range = sheet->range;
3399               sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3400               psppire_sheet_select_range (sheet, &sheet->drag_range);
3401             }
3402           sheet->x_drag = x;
3403           sheet->y_drag = y;
3404           if (row > sheet->range.rowi) row--;
3405           if (column > sheet->range.coli) column--;
3406           sheet->drag_cell.row = row;
3407           sheet->drag_cell.col = column;
3408           sheet->drag_range = sheet->range;
3409           draw_xor_rectangle (sheet, sheet->drag_range);
3410           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3411         }
3412       else if (sheet->cursor_drag->type == GDK_TOP_LEFT_ARROW &&
3413                !PSPPIRE_SHEET_IN_SELECTION (sheet)
3414                && ! PSPPIRE_SHEET_IN_DRAG (sheet)
3415                && sheet->active_cell.row >= 0
3416                && sheet->active_cell.col >= 0
3417                )
3418         {
3419           if (sheet->state == GTK_STATE_NORMAL)
3420             {
3421               row = sheet->active_cell.row;
3422               column = sheet->active_cell.col;
3423               sheet->active_cell.row = row;
3424               sheet->active_cell.col = column;
3425               sheet->drag_range = sheet->range;
3426               sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3427               psppire_sheet_select_range (sheet, &sheet->drag_range);
3428             }
3429           sheet->x_drag = x;
3430           sheet->y_drag = y;
3431           if (row < sheet->range.row0) row++;
3432           if (row > sheet->range.rowi) row--;
3433           if (column < sheet->range.col0) column++;
3434           if (column > sheet->range.coli) column--;
3435           sheet->drag_cell.row = row;
3436           sheet->drag_cell.col = column;
3437           sheet->drag_range = sheet->range;
3438           draw_xor_rectangle (sheet, sheet->drag_range);
3439           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3440         }
3441       else
3442         {
3443           veto = psppire_sheet_click_cell (sheet, row, column);
3444           if (veto) PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3445         }
3446     }
3447
3448   if (event->window == sheet->column_title_window)
3449     {
3450       gtk_widget_get_pointer (widget, &x, &y);
3451       if ( sheet->row_titles_visible)
3452         x -= sheet->row_title_area.width;
3453
3454       x += sheet->hadjustment->value;
3455
3456       column = column_from_xpixel (sheet, x);
3457
3458       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
3459         {
3460           veto = psppire_sheet_click_cell (sheet, -1, column);
3461           gtk_grab_add (GTK_WIDGET (sheet));
3462           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3463         }
3464     }
3465
3466   if (event->window == sheet->row_title_window)
3467     {
3468       gtk_widget_get_pointer (widget, &x, &y);
3469       if ( sheet->column_titles_visible)
3470         y -= sheet->column_title_area.height;
3471
3472       y += sheet->vadjustment->value;
3473
3474       row = row_from_ypixel (sheet, y);
3475       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3476         {
3477           veto = psppire_sheet_click_cell (sheet, row, -1);
3478           gtk_grab_add (GTK_WIDGET (sheet));
3479           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3480         }
3481     }
3482
3483   return TRUE;
3484 }
3485
3486 static gboolean
3487 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
3488 {
3489   PsppireSheetCell cell;
3490   gboolean forbid_move;
3491
3492   cell.row = row;
3493   cell.col = column;
3494
3495   if (row >= psppire_axis_unit_count (sheet->vaxis)
3496       || column >= psppire_axis_unit_count (sheet->haxis))
3497     {
3498       return FALSE;
3499     }
3500
3501   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3502                  &sheet->active_cell,
3503                  &cell,
3504                  &forbid_move);
3505
3506   if (forbid_move)
3507     {
3508       if (sheet->state == GTK_STATE_NORMAL)
3509         return FALSE;
3510
3511       row = sheet->active_cell.row;
3512       column = sheet->active_cell.col;
3513
3514       change_active_cell (sheet, row, column);
3515       return FALSE;
3516     }
3517
3518   if (row == -1 && column >= 0)
3519     {
3520       psppire_sheet_select_column (sheet, column);
3521       return TRUE;
3522     }
3523
3524   if (column == -1 && row >= 0)
3525     {
3526       psppire_sheet_select_row (sheet, row);
3527       return TRUE;
3528     }
3529
3530   if (row == -1 && column == -1)
3531     {
3532       sheet->range.row0 = 0;
3533       sheet->range.col0 = 0;
3534       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3535       sheet->range.coli =
3536         psppire_axis_unit_count (sheet->haxis) - 1;
3537       sheet->active_cell.row = 0;
3538       sheet->active_cell.col = 0;
3539       psppire_sheet_select_range (sheet, NULL);
3540       return TRUE;
3541     }
3542
3543   if (sheet->state != PSPPIRE_SHEET_NORMAL)
3544     {
3545       sheet->state = PSPPIRE_SHEET_NORMAL;
3546       psppire_sheet_real_unselect_range (sheet, NULL);
3547     }
3548   else
3549     {
3550       change_active_cell (sheet, row, column);
3551     }
3552
3553   sheet->active_cell.row = row;
3554   sheet->active_cell.col = column;
3555   sheet->selection_cell.row = row;
3556   sheet->selection_cell.col = column;
3557   sheet->range.row0 = row;
3558   sheet->range.col0 = column;
3559   sheet->range.rowi = row;
3560   sheet->range.coli = column;
3561   sheet->state = PSPPIRE_SHEET_NORMAL;
3562   PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3563
3564   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3565
3566   return TRUE;
3567 }
3568
3569 static gint
3570 psppire_sheet_button_release (GtkWidget *widget,
3571                           GdkEventButton *event)
3572 {
3573   GdkDisplay *display = gtk_widget_get_display (widget);
3574
3575   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3576
3577   /* release on resize windows */
3578   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3579     {
3580       gint width;
3581       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3582       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3583
3584       gdk_display_pointer_ungrab (display, event->time);
3585       draw_xor_vline (sheet);
3586
3587       width = event->x -
3588         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3589         + sheet->hadjustment->value;
3590
3591       set_column_width (sheet, sheet->drag_cell.col, width);
3592
3593       return TRUE;
3594     }
3595
3596   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3597     {
3598       gint height;
3599       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3600       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3601
3602       gdk_display_pointer_ungrab (display, event->time);
3603       draw_xor_hline (sheet);
3604
3605       height = event->y -
3606         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3607         sheet->vadjustment->value;
3608
3609       set_row_height (sheet, sheet->drag_cell.row, height);
3610
3611       return TRUE;
3612     }
3613
3614   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3615     {
3616       PsppireSheetRange old_range;
3617       draw_xor_rectangle (sheet, sheet->drag_range);
3618       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3619       gdk_display_pointer_ungrab (display, event->time);
3620
3621       psppire_sheet_real_unselect_range (sheet, NULL);
3622
3623       sheet->active_cell.row = sheet->active_cell.row +
3624         (sheet->drag_range.row0 - sheet->range.row0);
3625       sheet->active_cell.col = sheet->active_cell.col +
3626         (sheet->drag_range.col0 - sheet->range.col0);
3627       sheet->selection_cell.row = sheet->selection_cell.row +
3628         (sheet->drag_range.row0 - sheet->range.row0);
3629       sheet->selection_cell.col = sheet->selection_cell.col +
3630         (sheet->drag_range.col0 - sheet->range.col0);
3631       old_range = sheet->range;
3632       sheet->range = sheet->drag_range;
3633       sheet->drag_range = old_range;
3634       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3635                      &sheet->drag_range, &sheet->range);
3636       psppire_sheet_select_range (sheet, &sheet->range);
3637     }
3638
3639   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3640     {
3641       PsppireSheetRange old_range;
3642       draw_xor_rectangle (sheet, sheet->drag_range);
3643       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3644       gdk_display_pointer_ungrab (display, event->time);
3645
3646       psppire_sheet_real_unselect_range (sheet, NULL);
3647
3648       sheet->active_cell.row = sheet->active_cell.row +
3649         (sheet->drag_range.row0 - sheet->range.row0);
3650       sheet->active_cell.col = sheet->active_cell.col +
3651         (sheet->drag_range.col0 - sheet->range.col0);
3652       if (sheet->drag_range.row0 < sheet->range.row0)
3653         sheet->selection_cell.row = sheet->drag_range.row0;
3654       if (sheet->drag_range.rowi >= sheet->range.rowi)
3655         sheet->selection_cell.row = sheet->drag_range.rowi;
3656       if (sheet->drag_range.col0 < sheet->range.col0)
3657         sheet->selection_cell.col = sheet->drag_range.col0;
3658       if (sheet->drag_range.coli >= sheet->range.coli)
3659         sheet->selection_cell.col = sheet->drag_range.coli;
3660       old_range = sheet->range;
3661       sheet->range = sheet->drag_range;
3662       sheet->drag_range = old_range;
3663
3664       if (sheet->state == GTK_STATE_NORMAL) sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3665       g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0,
3666                      &sheet->drag_range, &sheet->range);
3667       psppire_sheet_select_range (sheet, &sheet->range);
3668     }
3669
3670   if (sheet->state == PSPPIRE_SHEET_NORMAL && PSPPIRE_SHEET_IN_SELECTION (sheet))
3671     {
3672       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3673       gdk_display_pointer_ungrab (display, event->time);
3674       change_active_cell (sheet, sheet->active_cell.row,
3675                                sheet->active_cell.col);
3676     }
3677
3678   if (PSPPIRE_SHEET_IN_SELECTION)
3679     gdk_display_pointer_ungrab (display, event->time);
3680   gtk_grab_remove (GTK_WIDGET (sheet));
3681
3682   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3683
3684   return TRUE;
3685 }
3686
3687 \f
3688
3689
3690
3691 /* Shamelessly lifted from gtktooltips */
3692 static gboolean
3693 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3694 {
3695   GtkRequisition req;
3696
3697   gtk_widget_size_request (tip_window, &req);
3698   gtk_paint_flat_box (tip_window->style, tip_window->window,
3699                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3700                       NULL, GTK_WIDGET(tip_window), "tooltip",
3701                       0, 0, req.width, req.height);
3702
3703   return FALSE;
3704 }
3705
3706 static void
3707 destroy_hover_window (PsppireSheetHoverTitle *h)
3708 {
3709   gtk_widget_destroy (h->window);
3710   g_free (h);
3711 }
3712
3713 static PsppireSheetHoverTitle *
3714 create_hover_window (void)
3715 {
3716   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3717
3718   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3719
3720 #if GTK_CHECK_VERSION (2, 9, 0)
3721   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3722                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3723 #endif
3724
3725   gtk_widget_set_app_paintable (hw->window, TRUE);
3726   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3727   gtk_widget_set_name (hw->window, "gtk-tooltips");
3728   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3729
3730   g_signal_connect (hw->window,
3731                     "expose_event",
3732                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3733                     NULL);
3734
3735   hw->label = gtk_label_new (NULL);
3736
3737
3738   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3739   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3740
3741   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3742
3743   gtk_widget_show (hw->label);
3744
3745   g_signal_connect (hw->window,
3746                     "destroy",
3747                     G_CALLBACK (gtk_widget_destroyed),
3748                     &hw->window);
3749
3750   return hw;
3751 }
3752
3753 #define HOVER_WINDOW_Y_OFFSET 2
3754
3755 static void
3756 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3757                const gchar *subtitle)
3758 {
3759   gint x, y;
3760   gint px, py;
3761   gint width;
3762
3763   if ( ! subtitle )
3764     return;
3765
3766   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3767                       subtitle);
3768
3769
3770   sheet->hover_window->row = row;
3771   sheet->hover_window->column = column;
3772
3773   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3774
3775   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3776
3777   gtk_widget_show (sheet->hover_window->window);
3778
3779   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3780
3781   if (row == -1 )
3782     {
3783       x += px;
3784       x -= width / 2;
3785       y += sheet->column_title_area.y;
3786       y += sheet->column_title_area.height;
3787       y += HOVER_WINDOW_Y_OFFSET;
3788     }
3789
3790   if ( column == -1 )
3791     {
3792       y += py;
3793       x += sheet->row_title_area.x;
3794       x += sheet->row_title_area.width * 2 / 3.0;
3795     }
3796
3797   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3798                    x, y);
3799 }
3800
3801 static gboolean
3802 motion_timeout_callback (gpointer data)
3803 {
3804   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3805   gint x, y;
3806   gint row, column;
3807
3808   gdk_threads_enter ();
3809   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3810
3811   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3812     {
3813       if (sheet->row_title_under && row >= 0)
3814         {
3815           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3816
3817           show_subtitle (sheet, row, -1, text);
3818           g_free (text);
3819         }
3820
3821       if (sheet->column_title_under && column >= 0)
3822         {
3823           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3824                                                            column);
3825
3826           show_subtitle (sheet, -1, column, text);
3827
3828           g_free (text);
3829         }
3830     }
3831
3832   gdk_threads_leave ();
3833   return FALSE;
3834 }
3835
3836 static gboolean
3837 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3838 {
3839   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3840   GdkModifierType mods;
3841   GdkCursorType new_cursor;
3842   gint x, y;
3843   gint row, column;
3844   GdkDisplay *display;
3845
3846   g_return_val_if_fail (event != NULL, FALSE);
3847
3848   display = gtk_widget_get_display (widget);
3849
3850   /* selections on the sheet */
3851   x = event->x;
3852   y = event->y;
3853
3854   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3855     {
3856       if ( sheet->motion_timer > 0 )
3857         g_source_remove (sheet->motion_timer);
3858       sheet->motion_timer =
3859         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3860     }
3861   else
3862     {
3863       gint row, column;
3864       gint wx, wy;
3865       gtk_widget_get_pointer (widget, &wx, &wy);
3866
3867       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3868         {
3869           if ( row != sheet->hover_window->row ||
3870                column != sheet->hover_window->column)
3871             {
3872               gtk_widget_hide (sheet->hover_window->window);
3873             }
3874         }
3875     }
3876
3877   if (event->window == sheet->column_title_window)
3878     {
3879       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3880           on_column_boundary (sheet, x, &column))
3881         {
3882           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3883           if (new_cursor != sheet->cursor_drag->type)
3884             {
3885               gdk_cursor_unref (sheet->cursor_drag);
3886               sheet->cursor_drag =
3887                 gdk_cursor_new_for_display (display, new_cursor);
3888
3889               gdk_window_set_cursor (sheet->column_title_window,
3890                                      sheet->cursor_drag);
3891             }
3892         }
3893       else
3894         {
3895           new_cursor = GDK_TOP_LEFT_ARROW;
3896           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3897               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               gdk_window_set_cursor (sheet->column_title_window,
3903                                      sheet->cursor_drag);
3904             }
3905         }
3906     }
3907   else if (event->window == sheet->row_title_window)
3908     {
3909       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3910           on_row_boundary (sheet, y, &row))
3911         {
3912           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3913           if (new_cursor != sheet->cursor_drag->type)
3914             {
3915               gdk_cursor_unref (sheet->cursor_drag);
3916               sheet->cursor_drag =
3917                 gdk_cursor_new_for_display (display, new_cursor);
3918               gdk_window_set_cursor (sheet->row_title_window,
3919                                      sheet->cursor_drag);
3920             }
3921         }
3922       else
3923         {
3924           new_cursor = GDK_TOP_LEFT_ARROW;
3925           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3926               new_cursor != sheet->cursor_drag->type)
3927             {
3928               gdk_cursor_unref (sheet->cursor_drag);
3929               sheet->cursor_drag =
3930                 gdk_cursor_new_for_display (display, new_cursor);
3931               gdk_window_set_cursor (sheet->row_title_window,
3932                                      sheet->cursor_drag);
3933             }
3934         }
3935     }
3936
3937   new_cursor = GDK_PLUS;
3938   if ( event->window == sheet->sheet_window &&
3939        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3940        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3941        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3942        !PSPPIRE_SHEET_IN_RESIZE (sheet) &&
3943        new_cursor != sheet->cursor_drag->type)
3944     {
3945       gdk_cursor_unref (sheet->cursor_drag);
3946       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3947       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3948     }
3949
3950   new_cursor = GDK_TOP_LEFT_ARROW;
3951   if ( event->window == sheet->sheet_window &&
3952        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3953           PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3954        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3955         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3956        new_cursor != sheet->cursor_drag->type)
3957     {
3958       gdk_cursor_unref (sheet->cursor_drag);
3959       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3960       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3961     }
3962
3963   new_cursor = GDK_SIZING;
3964   if ( event->window == sheet->sheet_window &&
3965        sheet->selection_mode != GTK_SELECTION_NONE &&
3966        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3967        (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3968         PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3969        new_cursor != sheet->cursor_drag->type)
3970     {
3971       gdk_cursor_unref (sheet->cursor_drag);
3972       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_SIZING);
3973       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3974     }
3975
3976
3977   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3978   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3979
3980   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3981     {
3982       if (event->x != sheet->x_drag)
3983         {
3984           draw_xor_vline (sheet);
3985           sheet->x_drag = event->x;
3986           draw_xor_vline (sheet);
3987         }
3988
3989       return TRUE;
3990     }
3991
3992   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3993     {
3994       if (event->y != sheet->y_drag)
3995         {
3996           draw_xor_hline (sheet);
3997           sheet->y_drag = event->y;
3998           draw_xor_hline (sheet);
3999         }
4000
4001       return TRUE;
4002     }
4003
4004   if (PSPPIRE_SHEET_IN_DRAG (sheet))
4005     {
4006       PsppireSheetRange aux;
4007       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
4008       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
4009       if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
4010       if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
4011       sheet->x_drag = x;
4012       sheet->y_drag = y;
4013       aux = sheet->range;
4014       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
4015           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
4016         {
4017           aux = sheet->drag_range;
4018           sheet->drag_range.row0 = sheet->range.row0 + row;
4019           sheet->drag_range.col0 = sheet->range.col0 + column;
4020           sheet->drag_range.rowi = sheet->range.rowi + row;
4021           sheet->drag_range.coli = sheet->range.coli + column;
4022           if (aux.row0 != sheet->drag_range.row0 ||
4023               aux.col0 != sheet->drag_range.col0)
4024             {
4025               draw_xor_rectangle (sheet, aux);
4026               draw_xor_rectangle (sheet, sheet->drag_range);
4027             }
4028         }
4029       return TRUE;
4030     }
4031
4032   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
4033     {
4034       PsppireSheetRange aux;
4035       gint v_h, current_col, current_row, col_threshold, row_threshold;
4036       v_h = 1;
4037       if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) >
4038           abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2;
4039
4040       current_col = column_from_xpixel (sheet, x);
4041       current_row = row_from_ypixel (sheet, y);
4042       column = current_col - sheet->drag_cell.col;
4043       row = current_row - sheet->drag_cell.row;
4044
4045       /*use half of column width resp. row height as threshold to
4046         expand selection*/
4047       col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) +
4048         psppire_axis_unit_size (sheet->haxis, current_col) / 2;
4049       if (column > 0)
4050         {
4051           if (x < col_threshold)
4052             column -= 1;
4053         }
4054       else if (column < 0)
4055         {
4056           if (x > col_threshold)
4057             column +=1;
4058         }
4059       row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) +
4060         psppire_axis_unit_size (sheet->vaxis, current_row)/2;
4061       if (row > 0)
4062         {
4063           if (y < row_threshold)
4064             row -= 1;
4065         }
4066       else if (row < 0)
4067         {
4068           if (y > row_threshold)
4069             row +=1;
4070         }
4071
4072       if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
4073       if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
4074       sheet->x_drag = x;
4075       sheet->y_drag = y;
4076       aux = sheet->range;
4077
4078       if (v_h == 1)
4079         column = 0;
4080       else
4081         row = 0;
4082
4083       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
4084           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
4085         {
4086           aux = sheet->drag_range;
4087           sheet->drag_range = sheet->range;
4088
4089           if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row;
4090           if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row;
4091           if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column;
4092           if (column > 0) sheet->drag_range.coli = sheet->range.coli + column;
4093
4094           if (aux.row0 != sheet->drag_range.row0 ||
4095               aux.rowi != sheet->drag_range.rowi ||
4096               aux.col0 != sheet->drag_range.col0 ||
4097               aux.coli != sheet->drag_range.coli)
4098             {
4099               draw_xor_rectangle (sheet, aux);
4100               draw_xor_rectangle (sheet, sheet->drag_range);
4101             }
4102         }
4103       return TRUE;
4104     }
4105
4106   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
4107
4108   if (sheet->state == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
4109       column == sheet->active_cell.col) return TRUE;
4110
4111   if (PSPPIRE_SHEET_IN_SELECTION (sheet) && mods&GDK_BUTTON1_MASK)
4112     psppire_sheet_extend_selection (sheet, row, column);
4113
4114   return TRUE;
4115 }
4116
4117 static gboolean
4118 psppire_sheet_crossing_notify (GtkWidget *widget,
4119                            GdkEventCrossing *event)
4120 {
4121   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
4122
4123   if (event->window == sheet->column_title_window)
4124     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
4125   else if (event->window == sheet->row_title_window)
4126     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
4127
4128   if (event->type == GDK_LEAVE_NOTIFY)
4129     gtk_widget_hide (sheet->hover_window->window);
4130
4131   return TRUE;
4132 }
4133
4134
4135 static gboolean
4136 psppire_sheet_focus_in (GtkWidget     *w,
4137                         GdkEventFocus *event)
4138 {
4139   PsppireSheet *sheet = PSPPIRE_SHEET (w);
4140
4141   gtk_widget_grab_focus (sheet->entry_widget);
4142
4143   return TRUE;
4144 }
4145
4146
4147 static void
4148 psppire_sheet_extend_selection (PsppireSheet *sheet, gint row, gint column)
4149 {
4150   PsppireSheetRange range;
4151   gint state;
4152   gint r, c;
4153
4154   if (row == sheet->selection_cell.row && column == sheet->selection_cell.col)
4155     return;
4156
4157   if (sheet->selection_mode == GTK_SELECTION_SINGLE) return;
4158
4159   gtk_widget_grab_focus (GTK_WIDGET (sheet));
4160
4161   if (PSPPIRE_SHEET_IN_DRAG (sheet)) return;
4162
4163   state = sheet->state;
4164
4165   switch (sheet->state)
4166     {
4167     case PSPPIRE_SHEET_ROW_SELECTED:
4168       column = psppire_axis_unit_count (sheet->haxis) - 1;
4169       break;
4170     case PSPPIRE_SHEET_COLUMN_SELECTED:
4171       row = psppire_axis_unit_count (sheet->vaxis) - 1;
4172       break;
4173     case PSPPIRE_SHEET_NORMAL:
4174       sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
4175       r = sheet->active_cell.row;
4176       c = sheet->active_cell.col;
4177       sheet->range.col0 = c;
4178       sheet->range.row0 = r;
4179       sheet->range.coli = c;
4180       sheet->range.rowi = r;
4181       psppire_sheet_range_draw_selection (sheet, sheet->range);
4182     case PSPPIRE_SHEET_RANGE_SELECTED:
4183       sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
4184     }
4185
4186   sheet->selection_cell.row = row;
4187   sheet->selection_cell.col = column;
4188
4189   range.col0 = MIN (column, sheet->active_cell.col);
4190   range.coli = MAX (column, sheet->active_cell.col);
4191   range.row0 = MIN (row, sheet->active_cell.row);
4192   range.rowi = MAX (row, sheet->active_cell.row);
4193
4194   if (range.row0 != sheet->range.row0 || range.rowi != sheet->range.rowi ||
4195       range.col0 != sheet->range.col0 || range.coli != sheet->range.coli ||
4196       state == PSPPIRE_SHEET_NORMAL)
4197     psppire_sheet_real_select_range (sheet, &range);
4198
4199 }
4200
4201 static gint
4202 psppire_sheet_entry_key_press (GtkWidget *widget,
4203                            GdkEventKey *key)
4204 {
4205   gboolean focus;
4206   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
4207   return focus;
4208 }
4209
4210
4211 /* Number of rows in a step-increment */
4212 #define ROWS_PER_STEP 1
4213
4214
4215 static void
4216 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
4217 {
4218   gint old_row = sheet->active_cell.row ;
4219   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
4220
4221   gint new_row;
4222
4223   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
4224                                      min_visible_row (sheet));
4225
4226   switch ( dir)
4227     {
4228     case GTK_SCROLL_PAGE_DOWN:
4229       gtk_adjustment_set_value (sheet->vadjustment,
4230                                 sheet->vadjustment->value +
4231                                 sheet->vadjustment->page_increment);
4232       break;
4233     case GTK_SCROLL_PAGE_UP:
4234       gtk_adjustment_set_value (sheet->vadjustment,
4235                                 sheet->vadjustment->value -
4236                                 sheet->vadjustment->page_increment);
4237
4238       break;
4239     default:
4240       g_assert_not_reached ();
4241       break;
4242     }
4243
4244
4245   vpixel += psppire_axis_start_pixel (sheet->vaxis,
4246                                      min_visible_row (sheet));
4247
4248   new_row =  row_from_ypixel (sheet, vpixel);
4249
4250   change_active_cell (sheet, new_row,
4251                            sheet->active_cell.col);
4252 }
4253
4254
4255 static void
4256 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
4257 {
4258   gint current_row = sheet->active_cell.row;
4259   gint current_col = sheet->active_cell.col;
4260   PsppireSheetCell new_cell ;
4261   gboolean forbidden = FALSE;
4262
4263   new_cell.row = current_row;
4264   new_cell.col = current_col;
4265
4266   switch ( dir)
4267     {
4268     case GTK_SCROLL_STEP_DOWN:
4269       new_cell.row++;
4270       break;
4271     case GTK_SCROLL_STEP_UP:
4272       new_cell.row--;
4273       break;
4274     case GTK_SCROLL_STEP_RIGHT:
4275       new_cell.col++;
4276       break;
4277     case GTK_SCROLL_STEP_LEFT:
4278       new_cell.col--;
4279       break;
4280     case GTK_SCROLL_STEP_FORWARD:
4281       new_cell.col++;
4282       if (new_cell.col >=
4283           psppire_sheet_model_get_column_count (sheet->model))
4284         {
4285           new_cell.col = 0;
4286           new_cell.row++;
4287         }
4288       break;
4289     case GTK_SCROLL_STEP_BACKWARD:
4290       new_cell.col--;
4291       if (new_cell.col < 0)
4292         {
4293           new_cell.col =
4294             psppire_sheet_model_get_column_count (sheet->model) - 1;
4295           new_cell.row--;
4296         }
4297       break;
4298     default:
4299       g_assert_not_reached ();
4300       break;
4301     }
4302
4303   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
4304                  &sheet->active_cell,
4305                  &new_cell,
4306                  &forbidden);
4307
4308   if (forbidden)
4309     return;
4310
4311
4312   maximize_int (&new_cell.row, 0);
4313   maximize_int (&new_cell.col, 0);
4314
4315   minimize_int (&new_cell.row,
4316                 psppire_axis_unit_count (sheet->vaxis) - 1);
4317
4318   minimize_int (&new_cell.col,
4319                 psppire_axis_unit_count (sheet->haxis) - 1);
4320
4321   change_active_cell (sheet, new_cell.row, new_cell.col);
4322
4323
4324   if ( new_cell.col > max_fully_visible_column (sheet))
4325     {
4326       glong hpos  =
4327         psppire_axis_start_pixel (sheet->haxis,
4328                                     new_cell.col + 1);
4329       hpos -= sheet->hadjustment->page_size;
4330
4331       gtk_adjustment_set_value (sheet->hadjustment,
4332                                 hpos);
4333     }
4334   else if ( new_cell.col < min_fully_visible_column (sheet))
4335     {
4336       glong hpos  =
4337         psppire_axis_start_pixel (sheet->haxis,
4338                                     new_cell.col);
4339
4340       gtk_adjustment_set_value (sheet->hadjustment,
4341                                 hpos);
4342     }
4343
4344
4345   if ( new_cell.row > max_fully_visible_row (sheet))
4346     {
4347       glong vpos  =
4348         psppire_axis_start_pixel (sheet->vaxis,
4349                                     new_cell.row + 1);
4350       vpos -= sheet->vadjustment->page_size;
4351
4352       gtk_adjustment_set_value (sheet->vadjustment,
4353                                 vpos);
4354     }
4355   else if ( new_cell.row < min_fully_visible_row (sheet))
4356     {
4357       glong vpos  =
4358         psppire_axis_start_pixel (sheet->vaxis,
4359                                     new_cell.row);
4360
4361       gtk_adjustment_set_value (sheet->vadjustment,
4362                                 vpos);
4363     }
4364
4365   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
4366 }
4367
4368
4369 static gboolean
4370 psppire_sheet_key_press (GtkWidget *widget,
4371                      GdkEventKey *key)
4372 {
4373   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
4374
4375   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
4376
4377   switch (key->keyval)
4378     {
4379     case GDK_Tab:
4380       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
4381       break;
4382     case GDK_Right:
4383       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
4384       break;
4385     case GDK_ISO_Left_Tab:
4386       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
4387       break;
4388     case GDK_Left:
4389       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
4390       break;
4391     case GDK_Return:
4392     case GDK_Down:
4393       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
4394       break;
4395     case GDK_Up:
4396       step_sheet (sheet, GTK_SCROLL_STEP_UP);
4397       break;
4398
4399     case GDK_Page_Down:
4400       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
4401       break;
4402     case GDK_Page_Up:
4403       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
4404       break;
4405
4406     case GDK_Home:
4407       gtk_adjustment_set_value (sheet->vadjustment,
4408                                 sheet->vadjustment->lower);
4409
4410       change_active_cell (sheet,  0,
4411                                sheet->active_cell.col);
4412
4413       break;
4414
4415     case GDK_End:
4416       gtk_adjustment_set_value (sheet->vadjustment,
4417                                 sheet->vadjustment->upper -
4418                                 sheet->vadjustment->page_size -
4419                                 sheet->vadjustment->page_increment);
4420
4421       /*
4422         change_active_cellx (sheet,
4423         psppire_axis_unit_count (sheet->vaxis) - 1,
4424         sheet->active_cell.col);
4425       */
4426       break;
4427     case GDK_Delete:
4428       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
4429       break;
4430     default:
4431       return FALSE;
4432       break;
4433     }
4434
4435   return TRUE;
4436 }
4437
4438 static void
4439 psppire_sheet_size_request (GtkWidget *widget,
4440                         GtkRequisition *requisition)
4441 {
4442   PsppireSheet *sheet;
4443
4444   g_return_if_fail (widget != NULL);
4445   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
4446   g_return_if_fail (requisition != NULL);
4447
4448   sheet = PSPPIRE_SHEET (widget);
4449
4450   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
4451   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
4452
4453   /* compute the size of the column title area */
4454   if (sheet->column_titles_visible)
4455     requisition->height += sheet->column_title_area.height;
4456
4457   /* compute the size of the row title area */
4458   if (sheet->row_titles_visible)
4459     requisition->width += sheet->row_title_area.width;
4460 }
4461
4462
4463 static void
4464 psppire_sheet_size_allocate (GtkWidget *widget,
4465                          GtkAllocation *allocation)
4466 {
4467   PsppireSheet *sheet;
4468   GtkAllocation sheet_allocation;
4469   gint border_width;
4470
4471   g_return_if_fail (widget != NULL);
4472   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
4473   g_return_if_fail (allocation != NULL);
4474
4475   sheet = PSPPIRE_SHEET (widget);
4476   widget->allocation = *allocation;
4477   border_width = GTK_CONTAINER (widget)->border_width;
4478
4479   if (GTK_WIDGET_REALIZED (widget))
4480     gdk_window_move_resize (widget->window,
4481                             allocation->x + border_width,
4482                             allocation->y + border_width,
4483                             allocation->width - 2 * border_width,
4484                             allocation->height - 2 * border_width);
4485
4486   sheet_allocation.x = 0;
4487   sheet_allocation.y = 0;
4488   sheet_allocation.width = allocation->width - 2 * border_width;
4489   sheet_allocation.height = allocation->height - 2 * border_width;
4490
4491   if (GTK_WIDGET_REALIZED (widget))
4492     gdk_window_move_resize (sheet->sheet_window,
4493                             sheet_allocation.x,
4494                             sheet_allocation.y,
4495                             sheet_allocation.width,
4496                             sheet_allocation.height);
4497
4498   /* position the window which holds the column title buttons */
4499   sheet->column_title_area.x = 0;
4500   sheet->column_title_area.y = 0;
4501   sheet->column_title_area.width = sheet_allocation.width ;
4502
4503
4504   /* position the window which holds the row title buttons */
4505   sheet->row_title_area.x = 0;
4506   sheet->row_title_area.y = 0;
4507   sheet->row_title_area.height = sheet_allocation.height;
4508
4509   if (sheet->row_titles_visible)
4510     sheet->column_title_area.x += sheet->row_title_area.width;
4511
4512   if (sheet->column_titles_visible)
4513     sheet->row_title_area.y += sheet->column_title_area.height;
4514
4515   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
4516     gdk_window_move_resize (sheet->column_title_window,
4517                             sheet->column_title_area.x,
4518                             sheet->column_title_area.y,
4519                             sheet->column_title_area.width,
4520                             sheet->column_title_area.height);
4521
4522
4523   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
4524     gdk_window_move_resize (sheet->row_title_window,
4525                             sheet->row_title_area.x,
4526                             sheet->row_title_area.y,
4527                             sheet->row_title_area.width,
4528                             sheet->row_title_area.height);
4529
4530   size_allocate_global_button (sheet);
4531
4532   if (sheet->haxis)
4533     {
4534       gint width = sheet->column_title_area.width;
4535
4536       if ( sheet->row_titles_visible)
4537         width -= sheet->row_title_area.width;
4538
4539       g_object_set (sheet->haxis,
4540                     "minimum-extent", width,
4541                     NULL);
4542     }
4543
4544
4545   if (sheet->vaxis)
4546     {
4547       gint height = sheet->row_title_area.height;
4548
4549       if ( sheet->column_titles_visible)
4550         height -= sheet->column_title_area.height;
4551
4552       g_object_set (sheet->vaxis,
4553                     "minimum-extent", height,
4554                     NULL);
4555     }
4556
4557
4558   /* set the scrollbars adjustments */
4559   adjust_scrollbars (sheet);
4560 }
4561
4562 static void
4563 draw_column_title_buttons (PsppireSheet *sheet)
4564 {
4565   gint x, width;
4566
4567   if (!sheet->column_titles_visible) return;
4568   if (!GTK_WIDGET_REALIZED (sheet))
4569     return;
4570
4571   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
4572   x = 0;
4573
4574   if (sheet->row_titles_visible)
4575     {
4576       x = sheet->row_title_area.width;
4577     }
4578
4579   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
4580     {
4581       sheet->column_title_area.width = width;
4582       sheet->column_title_area.x = x;
4583       gdk_window_move_resize (sheet->column_title_window,
4584                               sheet->column_title_area.x,
4585                               sheet->column_title_area.y,
4586                               sheet->column_title_area.width,
4587                               sheet->column_title_area.height);
4588     }
4589
4590   if (max_visible_column (sheet) ==
4591       psppire_axis_unit_count (sheet->haxis) - 1)
4592     gdk_window_clear_area (sheet->column_title_window,
4593                            0, 0,
4594                            sheet->column_title_area.width,
4595                            sheet->column_title_area.height);
4596
4597   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4598
4599   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
4600                                    max_visible_column (sheet));
4601 }
4602
4603 static void
4604 draw_row_title_buttons (PsppireSheet *sheet)
4605 {
4606   gint y = 0;
4607   gint height;
4608
4609   if (!sheet->row_titles_visible) return;
4610   if (!GTK_WIDGET_REALIZED (sheet))
4611     return;
4612
4613   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
4614
4615   if (sheet->column_titles_visible)
4616     {
4617       y = sheet->column_title_area.height;
4618     }
4619
4620   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
4621     {
4622       sheet->row_title_area.y = y;
4623       sheet->row_title_area.height = height;
4624       gdk_window_move_resize (sheet->row_title_window,
4625                               sheet->row_title_area.x,
4626                               sheet->row_title_area.y,
4627                               sheet->row_title_area.width,
4628                               sheet->row_title_area.height);
4629     }
4630
4631   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
4632     gdk_window_clear_area (sheet->row_title_window,
4633                            0, 0,
4634                            sheet->row_title_area.width,
4635                            sheet->row_title_area.height);
4636
4637   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4638
4639   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
4640                                 max_visible_row (sheet));
4641 }
4642
4643
4644 static void
4645 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
4646 {
4647   GtkAllocation entry_alloc;
4648   PsppireSheetCellAttr attributes = { 0 };
4649   GtkEntry *sheet_entry;
4650
4651   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4652   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
4653
4654   sheet_entry = psppire_sheet_get_entry (sheet);
4655
4656   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
4657                                    sheet->active_cell.col,
4658                                    &attributes) )
4659     return ;
4660
4661   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
4662     {
4663       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
4664
4665       style->bg[GTK_STATE_NORMAL] = attributes.background;
4666       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
4667       style->text[GTK_STATE_NORMAL] = attributes.foreground;
4668       style->bg[GTK_STATE_ACTIVE] = attributes.background;
4669       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
4670       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
4671     }
4672
4673   rectangle_from_cell (sheet, sheet->active_cell.row,
4674                        sheet->active_cell.col, &entry_alloc);
4675
4676   entry_alloc.width -= BORDER_WIDTH ;
4677   entry_alloc.height -= BORDER_WIDTH ;
4678   entry_alloc.x += DIV_RND_UP (BORDER_WIDTH, 2);
4679   entry_alloc.y += DIV_RND_UP (BORDER_WIDTH, 2);
4680
4681
4682   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
4683                                entry_alloc.height);
4684   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
4685 }
4686
4687
4688 /* Copy the sheet's font to the entry widget */
4689 static void
4690 set_entry_widget_font (PsppireSheet *sheet)
4691 {
4692   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
4693
4694   pango_font_description_free (style->font_desc);
4695   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4696
4697   gtk_widget_modify_style (sheet->entry_widget, style);
4698 }
4699
4700 static void
4701 create_sheet_entry (PsppireSheet *sheet)
4702 {
4703   if (sheet->entry_widget)
4704     {
4705       gtk_widget_unparent (sheet->entry_widget);
4706     }
4707
4708   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4709   g_object_ref_sink (sheet->entry_widget);
4710
4711   gtk_widget_size_request (sheet->entry_widget, NULL);
4712
4713   if ( GTK_IS_ENTRY (sheet->entry_widget))
4714     {
4715       g_object_set (sheet->entry_widget,
4716                     "has-frame", FALSE,
4717                     NULL);
4718     }
4719
4720   if (GTK_WIDGET_REALIZED (sheet))
4721     {
4722       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4723       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4724       gtk_widget_realize (sheet->entry_widget);
4725     }
4726
4727   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4728                             G_CALLBACK (psppire_sheet_entry_key_press),
4729                             sheet);
4730
4731   set_entry_widget_font (sheet);
4732
4733   gtk_widget_show (sheet->entry_widget);
4734 }
4735
4736
4737 /* Finds the last child widget that happens to be of type GtkEntry */
4738 static void
4739 find_entry (GtkWidget *w, gpointer user_data)
4740 {
4741   GtkWidget **entry = user_data;
4742   if ( GTK_IS_ENTRY (w))
4743     {
4744       *entry = w;
4745     }
4746 }
4747
4748
4749 GtkEntry *
4750 psppire_sheet_get_entry (PsppireSheet *sheet)
4751 {
4752   GtkWidget *w = sheet->entry_widget;
4753
4754   g_return_val_if_fail (sheet != NULL, NULL);
4755   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4756   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4757
4758   while (! GTK_IS_ENTRY (w))
4759     {
4760       GtkWidget *entry = NULL;
4761
4762       if (GTK_IS_CONTAINER (w))
4763         {
4764           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4765
4766           if (NULL == entry)
4767             break;
4768
4769           w = entry;
4770         }
4771     }
4772
4773   return GTK_ENTRY (w);
4774 }
4775
4776
4777 static void
4778 draw_button (PsppireSheet *sheet, GdkWindow *window,
4779                        PsppireSheetButton *button, gboolean is_sensitive,
4780                        GdkRectangle allocation)
4781 {
4782   GtkShadowType shadow_type;
4783   gint text_width = 0, text_height = 0;
4784   PangoAlignment align = PANGO_ALIGN_LEFT;
4785
4786   gboolean rtl ;
4787
4788   gint state = 0;
4789
4790   g_return_if_fail (sheet != NULL);
4791   g_return_if_fail (button != NULL);
4792
4793
4794   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4795
4796   gdk_window_clear_area (window,
4797                          allocation.x, allocation.y,
4798                          allocation.width, allocation.height);
4799
4800   gtk_widget_ensure_style (sheet->button);
4801
4802   gtk_paint_box (sheet->button->style, window,
4803                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4804                  &allocation,
4805                  GTK_WIDGET (sheet->button),
4806                  NULL,
4807                  allocation.x, allocation.y,
4808                  allocation.width, allocation.height);
4809
4810   state = button->state;
4811   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4812
4813   if (state == GTK_STATE_ACTIVE)
4814     shadow_type = GTK_SHADOW_IN;
4815   else
4816     shadow_type = GTK_SHADOW_OUT;
4817
4818   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4819     gtk_paint_box (sheet->button->style, window,
4820                    button->state, shadow_type,
4821                    &allocation, GTK_WIDGET (sheet->button),
4822                    NULL,
4823                    allocation.x, allocation.y,
4824                    allocation.width, allocation.height);
4825
4826   if ( button->overstruck)
4827     {
4828       GdkPoint points[2] = {
4829         {allocation.x,  allocation.y},
4830         {allocation.x + allocation.width,
4831          allocation.y + allocation.height}
4832       };
4833
4834       gtk_paint_polygon (sheet->button->style,
4835                          window,
4836                          button->state,
4837                          shadow_type,
4838                          NULL,
4839                          GTK_WIDGET (sheet),
4840                          NULL,
4841                          points,
4842                          2,
4843                          TRUE);
4844     }
4845
4846   if (button->label_visible)
4847     {
4848       text_height = DEFAULT_ROW_HEIGHT -
4849         2 * COLUMN_TITLES_HEIGHT;
4850
4851       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4852                                  &allocation);
4853       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4854                                  &allocation);
4855
4856       allocation.y += 2 * sheet->button->style->ythickness;
4857
4858       if (button->label && strlen (button->label) > 0)
4859         {
4860           PangoRectangle rect;
4861           gchar *line = button->label;
4862
4863           PangoLayout *layout = NULL;
4864           gint real_x = allocation.x;
4865           gint real_y = allocation.y;
4866
4867           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4868           pango_layout_get_extents (layout, NULL, &rect);
4869
4870           text_width = PANGO_PIXELS (rect.width);
4871           switch (button->justification)
4872             {
4873             case GTK_JUSTIFY_LEFT:
4874               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4875               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4876               break;
4877             case GTK_JUSTIFY_RIGHT:
4878               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4879               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4880               break;
4881             case GTK_JUSTIFY_CENTER:
4882             default:
4883               real_x = allocation.x + (allocation.width - text_width)/2;
4884               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4885               pango_layout_set_justify (layout, TRUE);
4886             }
4887           pango_layout_set_alignment (layout, align);
4888           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4889                             window,
4890                             state,
4891                             FALSE,
4892                             &allocation,
4893                             GTK_WIDGET (sheet),
4894                             "label",
4895                             real_x, real_y,
4896                             layout);
4897           g_object_unref (layout);
4898         }
4899
4900       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4901                                  NULL);
4902       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4903
4904     }
4905
4906   psppire_sheet_button_free (button);
4907 }
4908
4909
4910 /* Draw the column title buttons FIRST through to LAST */
4911 static void
4912 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4913 {
4914   GdkRectangle rect;
4915   gint col;
4916   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4917
4918   if (!sheet->column_titles_visible) return;
4919
4920   g_return_if_fail (first >= min_visible_column (sheet));
4921   g_return_if_fail (last <= max_visible_column (sheet));
4922
4923   rect.y = 0;
4924   rect.height = sheet->column_title_area.height;
4925   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4926   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4927     + psppire_axis_unit_size (sheet->haxis, last);
4928
4929   rect.x -= sheet->hadjustment->value;
4930
4931   minimize_int (&rect.width, sheet->column_title_area.width);
4932   maximize_int (&rect.x, 0);
4933
4934   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4935
4936   for (col = first ; col <= last ; ++col)
4937     {
4938       GdkRectangle allocation;
4939       gboolean is_sensitive = FALSE;
4940
4941       PsppireSheetButton *
4942         button = psppire_sheet_model_get_column_button (sheet->model, col);
4943       allocation.y = 0;
4944       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4945         + CELL_SPACING;
4946       allocation.x -= sheet->hadjustment->value;
4947
4948       allocation.height = sheet->column_title_area.height;
4949       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4950       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4951
4952       draw_button (sheet, sheet->column_title_window,
4953                    button, is_sensitive, allocation);
4954     }
4955
4956   gdk_window_end_paint (sheet->column_title_window);
4957 }
4958
4959
4960 static void
4961 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4962 {
4963   GdkRectangle rect;
4964   gint row;
4965   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4966
4967   if (!sheet->row_titles_visible) return;
4968
4969   g_return_if_fail (first >= min_visible_row (sheet));
4970   g_return_if_fail (last <= max_visible_row (sheet));
4971
4972   rect.x = 0;
4973   rect.width = sheet->row_title_area.width;
4974   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4975   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4976     + psppire_axis_unit_size (sheet->vaxis, last);
4977
4978   rect.y -= sheet->vadjustment->value;
4979
4980   minimize_int (&rect.height, sheet->row_title_area.height);
4981   maximize_int (&rect.y, 0);
4982
4983   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4984   for (row = first; row <= last; ++row)
4985     {
4986       GdkRectangle allocation;
4987
4988       gboolean is_sensitive = FALSE;
4989
4990       PsppireSheetButton *button =
4991         psppire_sheet_model_get_row_button (sheet->model, row);
4992       allocation.x = 0;
4993       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4994         + CELL_SPACING;
4995       allocation.y -= sheet->vadjustment->value;
4996
4997       allocation.width = sheet->row_title_area.width;
4998       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4999       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
5000
5001       draw_button (sheet, sheet->row_title_window,
5002                    button, is_sensitive, allocation);
5003     }
5004
5005   gdk_window_end_paint (sheet->row_title_window);
5006 }
5007
5008 /* SCROLLBARS
5009  *
5010  * functions:
5011  * adjust_scrollbars
5012  * vadjustment_value_changed
5013  * hadjustment_value_changed */
5014
5015
5016 static void
5017 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
5018 {
5019   double position =
5020     (adj->value + adj->page_size)
5021     /
5022     (adj->upper - adj->lower);
5023
5024   const glong last_item = psppire_axis_unit_count (axis) - 1;
5025
5026   if (isnan (position) || position < 0)
5027     position = 0;
5028
5029   adj->upper =
5030     psppire_axis_start_pixel (axis, last_item)
5031     +
5032     psppire_axis_unit_size (axis, last_item)
5033     ;
5034
5035   adj->lower = 0;
5036   adj->page_size = page_size;
5037
5038 #if 0
5039   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
5040
5041   if ( adj->value < adj->lower)
5042     adj->value = adj->lower;
5043 #endif
5044
5045   gtk_adjustment_changed (adj);
5046 }
5047
5048
5049 static void
5050 adjust_scrollbars (PsppireSheet *sheet)
5051 {
5052   gint width, height;
5053
5054   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5055     return;
5056
5057   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
5058
5059   if ( sheet->row_titles_visible)
5060     width -= sheet->row_title_area.width;
5061
5062   if (sheet->column_titles_visible)
5063     height -= sheet->column_title_area.height;
5064
5065   if (sheet->vadjustment)
5066     {
5067       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
5068
5069       sheet->vadjustment->step_increment =
5070         ROWS_PER_STEP *
5071         psppire_axis_unit_size (sheet->vaxis, last_row);
5072
5073       sheet->vadjustment->page_increment =
5074         height -
5075         sheet->column_title_area.height -
5076         psppire_axis_unit_size (sheet->vaxis, last_row);
5077
5078       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
5079     }
5080
5081   if (sheet->hadjustment)
5082     {
5083       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
5084       sheet->hadjustment->step_increment = 1;
5085
5086       sheet->hadjustment->page_increment = width;
5087
5088       sheet->hadjustment->upper =
5089         psppire_axis_start_pixel (sheet->haxis, last_col)
5090         +
5091         psppire_axis_unit_size (sheet->haxis, last_col)
5092         ;
5093
5094       update_adjustment (sheet->hadjustment, sheet->haxis, width);
5095     }
5096 }
5097
5098 /* Subtracts the region of WIDGET from REGION */
5099 static void
5100 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
5101 {
5102   GdkRectangle rect;
5103   GdkRectangle intersect;
5104   GdkRegion *region2;
5105
5106   gdk_region_get_clipbox (region, &rect);
5107   gtk_widget_intersect (widget,
5108                         &rect,
5109                         &intersect);
5110
5111   region2 = gdk_region_rectangle (&intersect);
5112   gdk_region_subtract (region, region2);
5113   gdk_region_destroy (region2);
5114 }
5115
5116 static void
5117 vadjustment_value_changed (GtkAdjustment *adjustment,
5118                            gpointer data)
5119 {
5120   GdkRegion *region;
5121   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5122
5123   g_return_if_fail (adjustment != NULL);
5124
5125   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
5126
5127   gtk_widget_hide (sheet->entry_widget);
5128
5129   region =
5130     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
5131
5132   subtract_widget_region (region, sheet->button);
5133   gdk_window_begin_paint_region (sheet->sheet_window, region);
5134
5135   draw_sheet_region (sheet, region);
5136
5137   draw_row_title_buttons (sheet);
5138   psppire_sheet_draw_active_cell (sheet);
5139
5140   gdk_window_end_paint (sheet->sheet_window);
5141   gdk_region_destroy (region);
5142 }
5143
5144
5145 static void
5146 hadjustment_value_changed (GtkAdjustment *adjustment,
5147                            gpointer data)
5148 {
5149   GdkRegion *region;
5150   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5151
5152   g_return_if_fail (adjustment != NULL);
5153
5154   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
5155
5156   gtk_widget_hide (sheet->entry_widget);
5157
5158
5159   region =
5160     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
5161
5162   subtract_widget_region (region, sheet->button);
5163   gdk_window_begin_paint_region (sheet->sheet_window, region);
5164
5165   draw_sheet_region (sheet, region);
5166
5167   draw_column_title_buttons (sheet);
5168
5169   psppire_sheet_draw_active_cell (sheet);
5170
5171   gdk_window_end_paint (sheet->sheet_window);
5172
5173   gdk_region_destroy (region);
5174 }
5175
5176
5177 /* COLUMN RESIZING */
5178 static void
5179 draw_xor_vline (PsppireSheet *sheet)
5180 {
5181   gint height;
5182   gint xpos = sheet->x_drag;
5183   gdk_drawable_get_size (sheet->sheet_window,
5184                          NULL, &height);
5185
5186   if (sheet->row_titles_visible)
5187     xpos += sheet->row_title_area.width;
5188
5189   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
5190                  xpos,
5191                  sheet->column_title_area.height,
5192                  xpos,
5193                  height + CELL_SPACING);
5194 }
5195
5196 /* ROW RESIZING */
5197 static void
5198 draw_xor_hline (PsppireSheet *sheet)
5199
5200 {
5201   gint width;
5202   gint ypos = sheet->y_drag;
5203
5204   gdk_drawable_get_size (sheet->sheet_window,
5205                          &width, NULL);
5206
5207
5208   if (sheet->column_titles_visible)
5209     ypos += sheet->column_title_area.height;
5210
5211   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
5212                  sheet->row_title_area.width,
5213                  ypos,
5214                  width + CELL_SPACING,
5215                  ypos);
5216 }
5217
5218 /* SELECTED RANGE */
5219 static void
5220 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
5221 {
5222   gint i = 0;
5223   GdkRectangle clip_area, area;
5224   GdkGCValues values;
5225
5226   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
5227   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
5228   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
5229     psppire_axis_unit_size (sheet->haxis, range.coli);
5230   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
5231     psppire_axis_unit_size (sheet->vaxis, range.rowi);
5232
5233   clip_area.x = sheet->row_title_area.width;
5234   clip_area.y = sheet->column_title_area.height;
5235
5236   gdk_drawable_get_size (sheet->sheet_window,
5237                          &clip_area.width, &clip_area.height);
5238
5239   if (!sheet->row_titles_visible) clip_area.x = 0;
5240   if (!sheet->column_titles_visible) clip_area.y = 0;
5241
5242   if (area.x < 0)
5243     {
5244       area.width = area.width + area.x;
5245       area.x = 0;
5246     }
5247   if (area.width > clip_area.width) area.width = clip_area.width + 10;
5248   if (area.y < 0)
5249     {
5250       area.height = area.height + area.y;
5251       area.y = 0;
5252     }
5253   if (area.height > clip_area.height) area.height = clip_area.height + 10;
5254
5255   clip_area.x--;
5256   clip_area.y--;
5257   clip_area.width += 3;
5258   clip_area.height += 3;
5259
5260   gdk_gc_get_values (sheet->xor_gc, &values);
5261
5262   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
5263
5264   gdk_draw_rectangle (sheet->sheet_window,
5265                       sheet->xor_gc,
5266                       FALSE,
5267                       area.x + i, area.y + i,
5268                       area.width - 2 * i, area.height - 2 * i);
5269
5270
5271   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
5272
5273   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
5274 }
5275
5276
5277 static void
5278 set_column_width (PsppireSheet *sheet,
5279                   gint column,
5280                   gint width)
5281 {
5282   g_return_if_fail (sheet != NULL);
5283   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
5284
5285   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
5286     return;
5287
5288   if ( width <= 0)
5289     return;
5290
5291   psppire_axis_resize (sheet->haxis, column,
5292                        width - sheet->cell_padding->left -
5293                        sheet->cell_padding->right);
5294
5295   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5296     {
5297       draw_column_title_buttons (sheet);
5298       adjust_scrollbars (sheet);
5299       psppire_sheet_size_allocate_entry (sheet);
5300       redraw_range (sheet, NULL);
5301     }
5302 }
5303
5304 static void
5305 set_row_height (PsppireSheet *sheet,
5306                 gint row,
5307                 gint height)
5308 {
5309   g_return_if_fail (sheet != NULL);
5310   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
5311
5312   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
5313     return;
5314
5315   if (height <= 0)
5316     return;
5317
5318   psppire_axis_resize (sheet->vaxis, row,
5319                        height - sheet->cell_padding->top -
5320                        sheet->cell_padding->bottom);
5321
5322   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
5323     {
5324       draw_row_title_buttons (sheet);
5325       adjust_scrollbars (sheet);
5326       psppire_sheet_size_allocate_entry (sheet);
5327       redraw_range (sheet, NULL);
5328     }
5329 }
5330
5331 static gboolean
5332 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
5333                           PsppireSheetCellAttr *attr)
5334 {
5335   GdkColor *fg, *bg;
5336   const GtkJustification *j ;
5337   GdkColormap *colormap;
5338
5339   g_return_val_if_fail (sheet != NULL, FALSE);
5340   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
5341
5342   if (row < 0 || col < 0) return FALSE;
5343
5344   attr->foreground = GTK_WIDGET (sheet)->style->black;
5345   attr->background = sheet->color[BG_COLOR];
5346
5347   attr->border.width = 0;
5348   attr->border.line_style = GDK_LINE_SOLID;
5349   attr->border.cap_style = GDK_CAP_NOT_LAST;
5350   attr->border.join_style = GDK_JOIN_MITER;
5351   attr->border.mask = 0;
5352   attr->border.color = GTK_WIDGET (sheet)->style->black;
5353
5354   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
5355   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
5356   if ( fg )
5357     {
5358       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
5359       attr->foreground = *fg;
5360     }
5361
5362   bg = psppire_sheet_model_get_background (sheet->model, row, col);
5363   if ( bg )
5364     {
5365       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
5366       attr->background = *bg;
5367     }
5368
5369   attr->justification =
5370     psppire_sheet_model_get_column_justification (sheet->model, col);
5371
5372   j = psppire_sheet_model_get_justification (sheet->model, row, col);
5373   if (j)
5374     attr->justification = *j;
5375
5376   return TRUE;
5377 }
5378
5379 static void
5380 psppire_sheet_button_size_request        (PsppireSheet *sheet,
5381                                   const PsppireSheetButton *button,
5382                                   GtkRequisition *button_requisition)
5383 {
5384   GtkRequisition requisition;
5385   GtkRequisition label_requisition;
5386
5387   label_requisition.height = DEFAULT_ROW_HEIGHT;
5388   label_requisition.width = COLUMN_MIN_WIDTH;
5389
5390   requisition.height = DEFAULT_ROW_HEIGHT;
5391   requisition.width = COLUMN_MIN_WIDTH;
5392
5393
5394   *button_requisition = requisition;
5395   button_requisition->width = MAX (requisition.width, label_requisition.width);
5396   button_requisition->height = MAX (requisition.height, label_requisition.height);
5397
5398 }
5399
5400 static void
5401 psppire_sheet_forall (GtkContainer *container,
5402                   gboolean include_internals,
5403                   GtkCallback callback,
5404                   gpointer callback_data)
5405 {
5406   PsppireSheet *sheet = PSPPIRE_SHEET (container);
5407
5408   g_return_if_fail (callback != NULL);
5409
5410   if (sheet->button && sheet->button->parent)
5411     (* callback) (sheet->button, callback_data);
5412
5413   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
5414     (* callback) (sheet->entry_widget, callback_data);
5415 }
5416
5417
5418 PsppireSheetModel *
5419 psppire_sheet_get_model (const PsppireSheet *sheet)
5420 {
5421   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
5422
5423   return sheet->model;
5424 }
5425
5426
5427 PsppireSheetButton *
5428 psppire_sheet_button_new (void)
5429 {
5430   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
5431
5432   button->state = GTK_STATE_NORMAL;
5433   button->label = NULL;
5434   button->label_visible = TRUE;
5435   button->justification = GTK_JUSTIFY_FILL;
5436   button->overstruck = FALSE;
5437
5438   return button;
5439 }
5440
5441
5442 void
5443 psppire_sheet_button_free (PsppireSheetButton *button)
5444 {
5445   if (!button) return ;
5446
5447   g_free (button->label);
5448   g_free (button);
5449 }
5450
5451 static void
5452 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
5453 {
5454   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
5455
5456   if ( NULL == celltext)
5457     return;
5458
5459   g_string_append (string, celltext);
5460   g_free (celltext);
5461 }
5462
5463
5464 static GString *
5465 range_to_text (const PsppireSheet *sheet)
5466 {
5467   gint r, c;
5468   GString *string;
5469
5470   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
5471     return NULL;
5472
5473   string = g_string_sized_new (80);
5474
5475   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
5476     {
5477       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
5478         {
5479           append_cell_text (string, sheet, r, c);
5480           g_string_append (string, "\t");
5481         }
5482       append_cell_text (string, sheet, r, c);
5483       if ( r < sheet->range.rowi)
5484         g_string_append (string, "\n");
5485     }
5486
5487   return string;
5488 }
5489
5490 static GString *
5491 range_to_html (const PsppireSheet *sheet)
5492 {
5493   gint r, c;
5494   GString *string;
5495
5496   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
5497     return NULL;
5498
5499   string = g_string_sized_new (480);
5500
5501   g_string_append (string, "<html>\n");
5502   g_string_append (string, "<body>\n");
5503   g_string_append (string, "<table>\n");
5504   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
5505     {
5506       g_string_append (string, "<tr>\n");
5507       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
5508         {
5509           g_string_append (string, "<td>");
5510           append_cell_text (string, sheet, r, c);
5511           g_string_append (string, "</td>\n");
5512         }
5513       g_string_append (string, "</tr>\n");
5514     }
5515   g_string_append (string, "</table>\n");
5516   g_string_append (string, "</body>\n");
5517   g_string_append (string, "</html>\n");
5518
5519   return string;
5520 }
5521
5522 enum {
5523   SELECT_FMT_NULL,
5524   SELECT_FMT_TEXT,
5525   SELECT_FMT_HTML
5526 };
5527
5528 static void
5529 primary_get_cb (GtkClipboard     *clipboard,
5530                 GtkSelectionData *selection_data,
5531                 guint             info,
5532                 gpointer          data)
5533 {
5534   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5535   GString *string = NULL;
5536
5537   switch (info)
5538     {
5539     case SELECT_FMT_TEXT:
5540       string = range_to_text (sheet);
5541       break;
5542     case SELECT_FMT_HTML:
5543       string = range_to_html (sheet);
5544       break;
5545     default:
5546       g_assert_not_reached ();
5547     }
5548
5549   gtk_selection_data_set (selection_data, selection_data->target,
5550                           8,
5551                           (const guchar *) string->str, string->len);
5552   g_string_free (string, TRUE);
5553 }
5554
5555 static void
5556 primary_clear_cb (GtkClipboard *clipboard,
5557                   gpointer      data)
5558 {
5559   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5560   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5561     return;
5562
5563   psppire_sheet_real_unselect_range (sheet, NULL);
5564 }
5565
5566 static void
5567 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
5568 {
5569   static const GtkTargetEntry targets[] = {
5570     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
5571     { "STRING",        0, SELECT_FMT_TEXT },
5572     { "TEXT",          0, SELECT_FMT_TEXT },
5573     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
5574     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
5575     { "text/plain",    0, SELECT_FMT_TEXT },
5576     { "text/html",     0, SELECT_FMT_HTML }
5577   };
5578
5579   GtkClipboard *clipboard;
5580
5581   if (!GTK_WIDGET_REALIZED (sheet))
5582     return;
5583
5584   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
5585                                         GDK_SELECTION_PRIMARY);
5586
5587   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
5588     {
5589       if (!gtk_clipboard_set_with_owner (clipboard, targets,
5590                                          G_N_ELEMENTS (targets),
5591                                          primary_get_cb, primary_clear_cb,
5592                                          G_OBJECT (sheet)))
5593         primary_clear_cb (clipboard, sheet);
5594     }
5595   else
5596     {
5597       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
5598         gtk_clipboard_clear (clipboard);
5599     }
5600 }