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