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