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