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