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