Removed unused function
[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
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
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
2705 void
2706 psppire_sheet_select_column (PsppireSheet *sheet,  gint column)
2707 {
2708   GdkRectangle area;
2709   sheet->select_status = PSPPIRE_SHEET_COLUMN_SELECTED;
2710
2711   sheet->range.col0 = sheet->range.coli = column;
2712   sheet->range.row0 = sheet->range.rowi = -1;
2713
2714   rectangle_from_range (sheet, &sheet->range, &area);
2715   area.x++;
2716   area.y++;
2717
2718   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2719 }
2720
2721
2722 void
2723 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
2724 {
2725   GdkRectangle area;
2726   sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
2727
2728   sheet->range = *range;
2729
2730   rectangle_from_range (sheet, range, &area);
2731   area.x++;
2732   area.y++;
2733   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2734 }
2735
2736
2737 void
2738 psppire_sheet_unselect_range (PsppireSheet *sheet)
2739 {
2740   GdkRectangle area;
2741   sheet->select_status = PSPPIRE_SHEET_NORMAL;
2742
2743   rectangle_from_range (sheet, &sheet->range, &area);
2744   area.x++;
2745   area.y++;
2746   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);         
2747 }
2748
2749 void
2750 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
2751 {
2752   g_return_if_fail (sheet != NULL);
2753   *range = sheet->range;
2754 }
2755 \f
2756
2757 static gint
2758 psppire_sheet_expose (GtkWidget *widget, GdkEventExpose *event)
2759 {
2760   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2761
2762   g_return_val_if_fail (event != NULL, FALSE);
2763
2764   if (!GTK_WIDGET_DRAWABLE (widget))
2765     return FALSE;
2766
2767   /* exposure events on the sheet */
2768   if (event->window == sheet->row_title_window &&
2769       sheet->row_titles_visible)
2770     {
2771       draw_row_title_buttons_range (sheet,
2772                                     min_visible_row (sheet),
2773                                     max_visible_row (sheet));
2774     }
2775
2776   if (event->window == sheet->column_title_window &&
2777       sheet->column_titles_visible)
2778     {
2779       draw_column_title_buttons_range (sheet,
2780                                        min_visible_column (sheet),
2781                                        max_visible_column (sheet));
2782     }
2783
2784   if (event->window == sheet->sheet_window)
2785     {
2786       draw_sheet_region (sheet, event->region);
2787
2788       if (sheet->select_status != PSPPIRE_SHEET_NORMAL)
2789         {
2790 #if 0
2791           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
2792             psppire_sheet_range_draw (sheet, &sheet->range);
2793
2794           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2795             psppire_sheet_range_draw (sheet, &sheet->drag_range);
2796 #endif
2797
2798             {
2799               GdkRectangle area;
2800
2801               rectangle_from_range (sheet, &sheet->range, &area);
2802               
2803               gdk_draw_rectangle (sheet->sheet_window,
2804                                   sheet->xor_gc,
2805                                   TRUE,
2806                                   area.x + 1, area.y + 1,
2807                                   area.width, area.height);
2808             }
2809
2810 #if 0
2811           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2812             draw_xor_rectangle (sheet, sheet->drag_range);
2813 #endif
2814         }
2815
2816
2817       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
2818         {
2819           GdkRectangle rect;
2820           PsppireSheetRange range;
2821           range.row0 = range.rowi =  sheet->active_cell.row;
2822           range.col0 = range.coli =  sheet->active_cell.col;
2823
2824           rectangle_from_range (sheet, &range, &rect);
2825
2826           if (GDK_OVERLAP_RECTANGLE_OUT !=
2827               gdk_region_rect_in (event->region, &rect))
2828             {
2829               psppire_sheet_draw_active_cell (sheet);
2830             }
2831         }
2832
2833     }
2834
2835   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
2836
2837   return FALSE;
2838 }
2839
2840
2841 static gboolean
2842 psppire_sheet_button_press (GtkWidget *widget, GdkEventButton *event)
2843 {
2844   PsppireSheet *sheet;
2845   GdkModifierType mods;
2846   gint x, y;
2847   gint  row, column;
2848
2849
2850   g_return_val_if_fail (widget != NULL, FALSE);
2851   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
2852   g_return_val_if_fail (event != NULL, FALSE);
2853
2854   sheet = PSPPIRE_SHEET (widget);
2855
2856   /* Cancel any pending tooltips */
2857   if (sheet->motion_timer)
2858     {
2859       g_source_remove (sheet->motion_timer);
2860       sheet->motion_timer = 0;
2861     }
2862
2863   gtk_widget_get_pointer (widget, &x, &y);
2864   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2865
2866
2867   if (event->window == sheet->column_title_window)
2868     {
2869       sheet->x_drag = event->x;
2870       g_signal_emit (sheet,
2871                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
2872                      column, event);
2873
2874       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2875         {
2876           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2877             g_signal_emit (sheet,
2878                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
2879         }
2880     }
2881   
2882   if (event->window == sheet->row_title_window)
2883     {
2884       g_signal_emit (sheet,
2885                      sheet_signals[BUTTON_EVENT_ROW], 0,
2886                      row, event);
2887
2888       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2889         {
2890           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2891             g_signal_emit (sheet,
2892                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
2893         }
2894     }
2895
2896   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
2897
2898   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
2899
2900
2901   /* press on resize windows */
2902   if (event->window == sheet->column_title_window)
2903     {
2904       sheet->x_drag = event->x;
2905
2906       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
2907         {
2908           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
2909           gdk_pointer_grab (sheet->column_title_window, FALSE,
2910                             GDK_POINTER_MOTION_HINT_MASK |
2911                             GDK_BUTTON1_MOTION_MASK |
2912                             GDK_BUTTON_RELEASE_MASK,
2913                             NULL, NULL, event->time);
2914
2915           draw_xor_vline (sheet);
2916           return TRUE;
2917         }
2918     }
2919
2920   if (event->window == sheet->row_title_window)
2921     {
2922       sheet->y_drag = event->y;
2923
2924       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
2925         {
2926           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
2927           gdk_pointer_grab (sheet->row_title_window, FALSE,
2928                             GDK_POINTER_MOTION_HINT_MASK |
2929                             GDK_BUTTON1_MOTION_MASK |
2930                             GDK_BUTTON_RELEASE_MASK,
2931                             NULL, NULL, event->time);
2932
2933           draw_xor_hline (sheet);
2934           return TRUE;
2935         }
2936     }
2937
2938   /* the sheet itself does not handle other than single click events */
2939   if (event->type != GDK_BUTTON_PRESS) return FALSE;
2940
2941   /* selections on the sheet */
2942   if (event->window == sheet->sheet_window)
2943     {
2944       gtk_widget_get_pointer (widget, &x, &y);
2945       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2946       gdk_pointer_grab (sheet->sheet_window, FALSE,
2947                         GDK_POINTER_MOTION_HINT_MASK |
2948                         GDK_BUTTON1_MOTION_MASK |
2949                         GDK_BUTTON_RELEASE_MASK,
2950                         NULL, NULL, event->time);
2951       gtk_grab_add (GTK_WIDGET (sheet));
2952
2953       if ( sheet->select_status == PSPPIRE_SHEET_NORMAL)
2954         {
2955           sheet->range.row0 = row;
2956           sheet->range.col0 = column;
2957         }
2958       else
2959         {
2960           psppire_sheet_unselect_range (sheet);
2961         }
2962       psppire_sheet_click_cell (sheet, row, column);
2963     }
2964
2965   if (event->window == sheet->column_title_window)
2966     {
2967       gtk_widget_get_pointer (widget, &x, &y);
2968       if ( sheet->row_titles_visible)
2969         x -= sheet->row_title_area.width;
2970
2971       x += sheet->hadjustment->value;
2972
2973       column = column_from_xpixel (sheet, x);
2974
2975       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2976         {
2977           gtk_grab_add (GTK_WIDGET (sheet));
2978           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2979         }
2980     }
2981
2982   if (event->window == sheet->row_title_window)
2983     {
2984       gtk_widget_get_pointer (widget, &x, &y);
2985       if ( sheet->column_titles_visible)
2986         y -= sheet->column_title_area.height;
2987
2988       y += sheet->vadjustment->value;
2989
2990       row = row_from_ypixel (sheet, y);
2991       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2992         {
2993           gtk_grab_add (GTK_WIDGET (sheet));
2994           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2995         }
2996     }
2997
2998   return TRUE;
2999 }
3000
3001 static gboolean
3002 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
3003 {
3004   PsppireSheetCell cell;
3005   gboolean forbid_move;
3006
3007   cell.row = row;
3008   cell.col = column;
3009
3010   if (row >= psppire_axis_unit_count (sheet->vaxis)
3011       || column >= psppire_axis_unit_count (sheet->haxis))
3012     {
3013       return FALSE;
3014     }
3015
3016   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3017                  &sheet->active_cell,
3018                  &cell,
3019                  &forbid_move);
3020
3021   if (forbid_move)
3022     {
3023       if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3024         return FALSE;
3025
3026       row = sheet->active_cell.row;
3027       column = sheet->active_cell.col;
3028
3029       change_active_cell (sheet, row, column);
3030       return FALSE;
3031     }
3032
3033   if (row == -1 && column >= 0)
3034     {
3035       psppire_sheet_select_column (sheet, column);
3036       return TRUE;
3037     }
3038
3039   if (column == -1 && row >= 0)
3040     {
3041       psppire_sheet_select_row (sheet, row);
3042       return TRUE;
3043     }
3044
3045   if (row == -1 && column == -1)
3046     {
3047       sheet->range.row0 = 0;
3048       sheet->range.col0 = 0;
3049       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3050       sheet->range.coli =
3051         psppire_axis_unit_count (sheet->haxis) - 1;
3052       psppire_sheet_select_range (sheet, NULL);
3053       return TRUE;
3054     }
3055
3056   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3057     change_active_cell (sheet, row, column);
3058
3059   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3060
3061   return TRUE;
3062 }
3063
3064 static gint
3065 psppire_sheet_button_release (GtkWidget *widget,
3066                               GdkEventButton *event)
3067 {
3068   GdkDisplay *display = gtk_widget_get_display (widget);
3069
3070   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3071
3072   /* release on resize windows */
3073   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3074     {
3075       gint width;
3076       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3077       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3078
3079       gdk_display_pointer_ungrab (display, event->time);
3080       draw_xor_vline (sheet);
3081
3082       width = event->x -
3083         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3084         + sheet->hadjustment->value;
3085
3086       set_column_width (sheet, sheet->drag_cell.col, width);
3087
3088       return TRUE;
3089     }
3090
3091   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3092     {
3093       gint height;
3094       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3095       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3096
3097       gdk_display_pointer_ungrab (display, event->time);
3098       draw_xor_hline (sheet);
3099
3100       height = event->y -
3101         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3102         sheet->vadjustment->value;
3103
3104       set_row_height (sheet, sheet->drag_cell.row, height);
3105
3106       return TRUE;
3107     }
3108
3109   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3110     {
3111       PsppireSheetRange old_range;
3112       draw_xor_rectangle (sheet, sheet->drag_range);
3113       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3114       gdk_display_pointer_ungrab (display, event->time);
3115
3116       psppire_sheet_unselect_range (sheet);
3117
3118       old_range = sheet->range;
3119       sheet->range = sheet->drag_range;
3120       sheet->drag_range = old_range;
3121       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3122                      &sheet->drag_range, &sheet->range);
3123       psppire_sheet_select_range (sheet, &sheet->range);
3124     }
3125
3126   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3127     {
3128       PsppireSheetRange old_range;
3129       draw_xor_rectangle (sheet, sheet->drag_range);
3130       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3131       gdk_display_pointer_ungrab (display, event->time);
3132
3133       psppire_sheet_unselect_range (sheet);
3134
3135       old_range = sheet->range;
3136       sheet->range = sheet->drag_range;
3137       sheet->drag_range = old_range;
3138
3139       if (sheet->select_status == PSPPIRE_SHEET_NORMAL) 
3140         sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3141
3142       g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0,
3143                      &sheet->drag_range, &sheet->range);
3144       psppire_sheet_select_range (sheet, &sheet->range);
3145     }
3146
3147   if (PSPPIRE_SHEET_IN_SELECTION (sheet))
3148     {
3149       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3150       sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3151
3152       change_active_cell (sheet, sheet->active_cell.row,
3153                           sheet->active_cell.col);
3154     }
3155
3156   gdk_display_pointer_ungrab (display, event->time);
3157   gtk_grab_remove (GTK_WIDGET (sheet));
3158
3159   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3160
3161   return TRUE;
3162 }
3163
3164 \f
3165
3166
3167
3168 /* Shamelessly lifted from gtktooltips */
3169 static gboolean
3170 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3171 {
3172   GtkRequisition req;
3173
3174   gtk_widget_size_request (tip_window, &req);
3175   gtk_paint_flat_box (tip_window->style, tip_window->window,
3176                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3177                       NULL, GTK_WIDGET(tip_window), "tooltip",
3178                       0, 0, req.width, req.height);
3179
3180   return FALSE;
3181 }
3182
3183 static void
3184 destroy_hover_window (PsppireSheetHoverTitle *h)
3185 {
3186   gtk_widget_destroy (h->window);
3187   g_free (h);
3188 }
3189
3190 static PsppireSheetHoverTitle *
3191 create_hover_window (void)
3192 {
3193   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3194
3195   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3196
3197 #if GTK_CHECK_VERSION (2, 9, 0)
3198   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3199                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3200 #endif
3201
3202   gtk_widget_set_app_paintable (hw->window, TRUE);
3203   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3204   gtk_widget_set_name (hw->window, "gtk-tooltips");
3205   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3206
3207   g_signal_connect (hw->window,
3208                     "expose_event",
3209                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3210                     NULL);
3211
3212   hw->label = gtk_label_new (NULL);
3213
3214
3215   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3216   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3217
3218   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3219
3220   gtk_widget_show (hw->label);
3221
3222   g_signal_connect (hw->window,
3223                     "destroy",
3224                     G_CALLBACK (gtk_widget_destroyed),
3225                     &hw->window);
3226
3227   return hw;
3228 }
3229
3230 #define HOVER_WINDOW_Y_OFFSET 2
3231
3232 static void
3233 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3234                const gchar *subtitle)
3235 {
3236   gint x, y;
3237   gint px, py;
3238   gint width;
3239
3240   if ( ! subtitle )
3241     return;
3242
3243   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3244                       subtitle);
3245
3246
3247   sheet->hover_window->row = row;
3248   sheet->hover_window->column = column;
3249
3250   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3251
3252   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3253
3254   gtk_widget_show (sheet->hover_window->window);
3255
3256   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3257
3258   if (row == -1 )
3259     {
3260       x += px;
3261       x -= width / 2;
3262       y += sheet->column_title_area.y;
3263       y += sheet->column_title_area.height;
3264       y += HOVER_WINDOW_Y_OFFSET;
3265     }
3266
3267   if ( column == -1 )
3268     {
3269       y += py;
3270       x += sheet->row_title_area.x;
3271       x += sheet->row_title_area.width * 2 / 3.0;
3272     }
3273
3274   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3275                    x, y);
3276 }
3277
3278 static gboolean
3279 motion_timeout_callback (gpointer data)
3280 {
3281   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3282   gint x, y;
3283   gint row, column;
3284
3285   gdk_threads_enter ();
3286   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3287
3288   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3289     {
3290       if (sheet->row_title_under && row >= 0)
3291         {
3292           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3293
3294           show_subtitle (sheet, row, -1, text);
3295           g_free (text);
3296         }
3297
3298       if (sheet->column_title_under && column >= 0)
3299         {
3300           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3301                                                                  column);
3302
3303           show_subtitle (sheet, -1, column, text);
3304
3305           g_free (text);
3306         }
3307     }
3308
3309   gdk_threads_leave ();
3310   return FALSE;
3311 }
3312
3313 static gboolean
3314 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3315 {
3316   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3317   GdkModifierType mods;
3318   GdkCursorType new_cursor;
3319   gint x, y;
3320   gint row, column;
3321   GdkDisplay *display;
3322
3323   g_return_val_if_fail (event != NULL, FALSE);
3324
3325   display = gtk_widget_get_display (widget);
3326
3327   /* selections on the sheet */
3328   x = event->x;
3329   y = event->y;
3330
3331   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3332     {
3333       if ( sheet->motion_timer > 0 )
3334         g_source_remove (sheet->motion_timer);
3335       sheet->motion_timer =
3336         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3337     }
3338   else
3339     {
3340       gint row, column;
3341       gint wx, wy;
3342       gtk_widget_get_pointer (widget, &wx, &wy);
3343
3344       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3345         {
3346           if ( row != sheet->hover_window->row ||
3347                column != sheet->hover_window->column)
3348             {
3349               gtk_widget_hide (sheet->hover_window->window);
3350             }
3351         }
3352     }
3353
3354   if (event->window == sheet->column_title_window)
3355     {
3356       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3357           on_column_boundary (sheet, x, &column))
3358         {
3359           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3360           if (new_cursor != sheet->cursor_drag->type)
3361             {
3362               gdk_cursor_unref (sheet->cursor_drag);
3363               sheet->cursor_drag =
3364                 gdk_cursor_new_for_display (display, new_cursor);
3365
3366               gdk_window_set_cursor (sheet->column_title_window,
3367                                      sheet->cursor_drag);
3368             }
3369         }
3370       else
3371         {
3372           new_cursor = GDK_TOP_LEFT_ARROW;
3373           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3374               new_cursor != sheet->cursor_drag->type)
3375             {
3376               gdk_cursor_unref (sheet->cursor_drag);
3377               sheet->cursor_drag =
3378                 gdk_cursor_new_for_display (display, new_cursor);
3379               gdk_window_set_cursor (sheet->column_title_window,
3380                                      sheet->cursor_drag);
3381             }
3382         }
3383     }
3384   else if (event->window == sheet->row_title_window)
3385     {
3386       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3387           on_row_boundary (sheet, y, &row))
3388         {
3389           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3390           if (new_cursor != sheet->cursor_drag->type)
3391             {
3392               gdk_cursor_unref (sheet->cursor_drag);
3393               sheet->cursor_drag =
3394                 gdk_cursor_new_for_display (display, new_cursor);
3395               gdk_window_set_cursor (sheet->row_title_window,
3396                                      sheet->cursor_drag);
3397             }
3398         }
3399       else
3400         {
3401           new_cursor = GDK_TOP_LEFT_ARROW;
3402           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3403               new_cursor != sheet->cursor_drag->type)
3404             {
3405               gdk_cursor_unref (sheet->cursor_drag);
3406               sheet->cursor_drag =
3407                 gdk_cursor_new_for_display (display, new_cursor);
3408               gdk_window_set_cursor (sheet->row_title_window,
3409                                      sheet->cursor_drag);
3410             }
3411         }
3412     }
3413
3414   new_cursor = GDK_PLUS;
3415   if ( event->window == sheet->sheet_window &&
3416        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3417        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3418        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3419        !PSPPIRE_SHEET_IN_RESIZE (sheet) &&
3420        new_cursor != sheet->cursor_drag->type)
3421     {
3422       gdk_cursor_unref (sheet->cursor_drag);
3423       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3424       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3425     }
3426
3427   new_cursor = GDK_TOP_LEFT_ARROW;
3428   if ( event->window == sheet->sheet_window &&
3429        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3430           PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3431        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3432         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3433        new_cursor != sheet->cursor_drag->type)
3434     {
3435       gdk_cursor_unref (sheet->cursor_drag);
3436       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3437       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3438     }
3439
3440   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3441   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3442
3443   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3444     {
3445       if (event->x != sheet->x_drag)
3446         {
3447           draw_xor_vline (sheet);
3448           sheet->x_drag = event->x;
3449           draw_xor_vline (sheet);
3450         }
3451
3452       return TRUE;
3453     }
3454
3455   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3456     {
3457       if (event->y != sheet->y_drag)
3458         {
3459           draw_xor_hline (sheet);
3460           sheet->y_drag = event->y;
3461           draw_xor_hline (sheet);
3462         }
3463
3464       return TRUE;
3465     }
3466
3467   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3468     {
3469       PsppireSheetRange aux;
3470       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
3471       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
3472       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3473       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3474       sheet->x_drag = x;
3475       sheet->y_drag = y;
3476       aux = sheet->range;
3477       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3478           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3479         {
3480           aux = sheet->drag_range;
3481           sheet->drag_range.row0 = sheet->range.row0 + row;
3482           sheet->drag_range.col0 = sheet->range.col0 + column;
3483           sheet->drag_range.rowi = sheet->range.rowi + row;
3484           sheet->drag_range.coli = sheet->range.coli + column;
3485           if (aux.row0 != sheet->drag_range.row0 ||
3486               aux.col0 != sheet->drag_range.col0)
3487             {
3488               draw_xor_rectangle (sheet, aux);
3489               draw_xor_rectangle (sheet, sheet->drag_range);
3490             }
3491         }
3492       return TRUE;
3493     }
3494
3495   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3496     {
3497       PsppireSheetRange aux;
3498       gint v_h, current_col, current_row, col_threshold, row_threshold;
3499       v_h = 1;
3500       if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) >
3501           abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2;
3502
3503       current_col = column_from_xpixel (sheet, x);
3504       current_row = row_from_ypixel (sheet, y);
3505       column = current_col - sheet->drag_cell.col;
3506       row = current_row - sheet->drag_cell.row;
3507
3508       /*use half of column width resp. row height as threshold to
3509         expand selection*/
3510       col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) +
3511         psppire_axis_unit_size (sheet->haxis, current_col) / 2;
3512       if (column > 0)
3513         {
3514           if (x < col_threshold)
3515             column -= 1;
3516         }
3517       else if (column < 0)
3518         {
3519           if (x > col_threshold)
3520             column +=1;
3521         }
3522       row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) +
3523         psppire_axis_unit_size (sheet->vaxis, current_row)/2;
3524       if (row > 0)
3525         {
3526           if (y < row_threshold)
3527             row -= 1;
3528         }
3529       else if (row < 0)
3530         {
3531           if (y > row_threshold)
3532             row +=1;
3533         }
3534
3535       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3536       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3537       sheet->x_drag = x;
3538       sheet->y_drag = y;
3539       aux = sheet->range;
3540
3541       if (v_h == 1)
3542         column = 0;
3543       else
3544         row = 0;
3545
3546       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3547           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3548         {
3549           aux = sheet->drag_range;
3550           sheet->drag_range = sheet->range;
3551
3552           if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row;
3553           if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row;
3554           if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column;
3555           if (column > 0) sheet->drag_range.coli = sheet->range.coli + column;
3556
3557           if (aux.row0 != sheet->drag_range.row0 ||
3558               aux.rowi != sheet->drag_range.rowi ||
3559               aux.col0 != sheet->drag_range.col0 ||
3560               aux.coli != sheet->drag_range.coli)
3561             {
3562               draw_xor_rectangle (sheet, aux);
3563               draw_xor_rectangle (sheet, sheet->drag_range);
3564             }
3565         }
3566       return TRUE;
3567     }
3568
3569   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3570
3571   if (sheet->select_status == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
3572       column == sheet->active_cell.col) return TRUE;
3573
3574   if ( mods & GDK_BUTTON1_MASK)
3575     {
3576       if (PSPPIRE_SHEET_IN_SELECTION (sheet) )
3577         {
3578           /* Redraw the old range */
3579           psppire_sheet_unselect_range (sheet);
3580
3581           sheet->range.rowi = row;
3582           sheet->range.coli = column;
3583
3584           /* Redraw the new range */
3585           psppire_sheet_select_range (sheet, &sheet->range);
3586         }
3587       else
3588         {
3589           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3590         }
3591     }
3592
3593   return TRUE;
3594 }
3595
3596 static gboolean
3597 psppire_sheet_crossing_notify (GtkWidget *widget,
3598                                GdkEventCrossing *event)
3599 {
3600   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3601
3602   if (event->window == sheet->column_title_window)
3603     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
3604   else if (event->window == sheet->row_title_window)
3605     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
3606
3607   if (event->type == GDK_LEAVE_NOTIFY)
3608     gtk_widget_hide (sheet->hover_window->window);
3609
3610   return TRUE;
3611 }
3612
3613
3614 static gboolean
3615 psppire_sheet_focus_in (GtkWidget     *w,
3616                         GdkEventFocus *event)
3617 {
3618   PsppireSheet *sheet = PSPPIRE_SHEET (w);
3619
3620   gtk_widget_grab_focus (sheet->entry_widget);
3621
3622   return TRUE;
3623 }
3624
3625
3626
3627 static gint
3628 psppire_sheet_entry_key_press (GtkWidget *widget,
3629                                GdkEventKey *key)
3630 {
3631   gboolean focus;
3632   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
3633   return focus;
3634 }
3635
3636
3637 /* Number of rows in a step-increment */
3638 #define ROWS_PER_STEP 1
3639
3640
3641 static void
3642 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
3643 {
3644   gint old_row = sheet->active_cell.row ;
3645   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
3646
3647   gint new_row;
3648
3649   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
3650                                       min_visible_row (sheet));
3651
3652   switch ( dir)
3653     {
3654     case GTK_SCROLL_PAGE_DOWN:
3655       gtk_adjustment_set_value (sheet->vadjustment,
3656                                 sheet->vadjustment->value +
3657                                 sheet->vadjustment->page_increment);
3658       break;
3659     case GTK_SCROLL_PAGE_UP:
3660       gtk_adjustment_set_value (sheet->vadjustment,
3661                                 sheet->vadjustment->value -
3662                                 sheet->vadjustment->page_increment);
3663
3664       break;
3665     default:
3666       g_assert_not_reached ();
3667       break;
3668     }
3669
3670
3671   vpixel += psppire_axis_start_pixel (sheet->vaxis,
3672                                       min_visible_row (sheet));
3673
3674   new_row =  row_from_ypixel (sheet, vpixel);
3675
3676   change_active_cell (sheet, new_row,
3677                       sheet->active_cell.col);
3678 }
3679
3680
3681 static void
3682 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
3683 {
3684   gint current_row = sheet->active_cell.row;
3685   gint current_col = sheet->active_cell.col;
3686   PsppireSheetCell new_cell ;
3687   gboolean forbidden = FALSE;
3688
3689   new_cell.row = current_row;
3690   new_cell.col = current_col;
3691
3692   switch ( dir)
3693     {
3694     case GTK_SCROLL_STEP_DOWN:
3695       new_cell.row++;
3696       break;
3697     case GTK_SCROLL_STEP_UP:
3698       new_cell.row--;
3699       break;
3700     case GTK_SCROLL_STEP_RIGHT:
3701       new_cell.col++;
3702       break;
3703     case GTK_SCROLL_STEP_LEFT:
3704       new_cell.col--;
3705       break;
3706     case GTK_SCROLL_STEP_FORWARD:
3707       new_cell.col++;
3708       if (new_cell.col >=
3709           psppire_sheet_model_get_column_count (sheet->model))
3710         {
3711           new_cell.col = 0;
3712           new_cell.row++;
3713         }
3714       break;
3715     case GTK_SCROLL_STEP_BACKWARD:
3716       new_cell.col--;
3717       if (new_cell.col < 0)
3718         {
3719           new_cell.col =
3720             psppire_sheet_model_get_column_count (sheet->model) - 1;
3721           new_cell.row--;
3722         }
3723       break;
3724     default:
3725       g_assert_not_reached ();
3726       break;
3727     }
3728
3729   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3730                  &sheet->active_cell,
3731                  &new_cell,
3732                  &forbidden);
3733
3734   if (forbidden)
3735     return;
3736
3737
3738   maximize_int (&new_cell.row, 0);
3739   maximize_int (&new_cell.col, 0);
3740
3741   minimize_int (&new_cell.row,
3742                 psppire_axis_unit_count (sheet->vaxis) - 1);
3743
3744   minimize_int (&new_cell.col,
3745                 psppire_axis_unit_count (sheet->haxis) - 1);
3746
3747   change_active_cell (sheet, new_cell.row, new_cell.col);
3748
3749
3750   if ( new_cell.col > max_fully_visible_column (sheet))
3751     {
3752       glong hpos  =
3753         psppire_axis_start_pixel (sheet->haxis,
3754                                   new_cell.col + 1);
3755       hpos -= sheet->hadjustment->page_size;
3756
3757       gtk_adjustment_set_value (sheet->hadjustment,
3758                                 hpos);
3759     }
3760   else if ( new_cell.col < min_fully_visible_column (sheet))
3761     {
3762       glong hpos  =
3763         psppire_axis_start_pixel (sheet->haxis,
3764                                   new_cell.col);
3765
3766       gtk_adjustment_set_value (sheet->hadjustment,
3767                                 hpos);
3768     }
3769
3770
3771   if ( new_cell.row > max_fully_visible_row (sheet))
3772     {
3773       glong vpos  =
3774         psppire_axis_start_pixel (sheet->vaxis,
3775                                   new_cell.row + 1);
3776       vpos -= sheet->vadjustment->page_size;
3777
3778       gtk_adjustment_set_value (sheet->vadjustment,
3779                                 vpos);
3780     }
3781   else if ( new_cell.row < min_fully_visible_row (sheet))
3782     {
3783       glong vpos  =
3784         psppire_axis_start_pixel (sheet->vaxis,
3785                                   new_cell.row);
3786
3787       gtk_adjustment_set_value (sheet->vadjustment,
3788                                 vpos);
3789     }
3790
3791   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3792 }
3793
3794
3795 static gboolean
3796 psppire_sheet_key_press (GtkWidget *widget,
3797                          GdkEventKey *key)
3798 {
3799   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3800
3801   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3802
3803   switch (key->keyval)
3804     {
3805     case GDK_Tab:
3806       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
3807       break;
3808     case GDK_Right:
3809       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
3810       break;
3811     case GDK_ISO_Left_Tab:
3812       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
3813       break;
3814     case GDK_Left:
3815       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
3816       break;
3817     case GDK_Return:
3818     case GDK_Down:
3819       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
3820       break;
3821     case GDK_Up:
3822       step_sheet (sheet, GTK_SCROLL_STEP_UP);
3823       break;
3824
3825     case GDK_Page_Down:
3826       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
3827       break;
3828     case GDK_Page_Up:
3829       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
3830       break;
3831
3832     case GDK_Home:
3833       gtk_adjustment_set_value (sheet->vadjustment,
3834                                 sheet->vadjustment->lower);
3835
3836       change_active_cell (sheet,  0,
3837                           sheet->active_cell.col);
3838
3839       break;
3840
3841     case GDK_End:
3842       gtk_adjustment_set_value (sheet->vadjustment,
3843                                 sheet->vadjustment->upper -
3844                                 sheet->vadjustment->page_size -
3845                                 sheet->vadjustment->page_increment);
3846
3847       /*
3848         change_active_cellx (sheet,
3849         psppire_axis_unit_count (sheet->vaxis) - 1,
3850         sheet->active_cell.col);
3851       */
3852       break;
3853     case GDK_Delete:
3854       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
3855       break;
3856     default:
3857       return FALSE;
3858       break;
3859     }
3860
3861   return TRUE;
3862 }
3863
3864 static void
3865 psppire_sheet_size_request (GtkWidget *widget,
3866                             GtkRequisition *requisition)
3867 {
3868   PsppireSheet *sheet;
3869
3870   g_return_if_fail (widget != NULL);
3871   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3872   g_return_if_fail (requisition != NULL);
3873
3874   sheet = PSPPIRE_SHEET (widget);
3875
3876   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
3877   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
3878
3879   /* compute the size of the column title area */
3880   if (sheet->column_titles_visible)
3881     requisition->height += sheet->column_title_area.height;
3882
3883   /* compute the size of the row title area */
3884   if (sheet->row_titles_visible)
3885     requisition->width += sheet->row_title_area.width;
3886 }
3887
3888
3889 static void
3890 psppire_sheet_size_allocate (GtkWidget *widget,
3891                              GtkAllocation *allocation)
3892 {
3893   PsppireSheet *sheet;
3894   GtkAllocation sheet_allocation;
3895   gint border_width;
3896
3897   g_return_if_fail (widget != NULL);
3898   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3899   g_return_if_fail (allocation != NULL);
3900
3901   sheet = PSPPIRE_SHEET (widget);
3902   widget->allocation = *allocation;
3903   border_width = GTK_CONTAINER (widget)->border_width;
3904
3905   if (GTK_WIDGET_REALIZED (widget))
3906     gdk_window_move_resize (widget->window,
3907                             allocation->x + border_width,
3908                             allocation->y + border_width,
3909                             allocation->width - 2 * border_width,
3910                             allocation->height - 2 * border_width);
3911
3912   sheet_allocation.x = 0;
3913   sheet_allocation.y = 0;
3914   sheet_allocation.width = allocation->width - 2 * border_width;
3915   sheet_allocation.height = allocation->height - 2 * border_width;
3916
3917   if (GTK_WIDGET_REALIZED (widget))
3918     gdk_window_move_resize (sheet->sheet_window,
3919                             sheet_allocation.x,
3920                             sheet_allocation.y,
3921                             sheet_allocation.width,
3922                             sheet_allocation.height);
3923
3924   /* position the window which holds the column title buttons */
3925   sheet->column_title_area.x = 0;
3926   sheet->column_title_area.y = 0;
3927   sheet->column_title_area.width = sheet_allocation.width ;
3928
3929
3930   /* position the window which holds the row title buttons */
3931   sheet->row_title_area.x = 0;
3932   sheet->row_title_area.y = 0;
3933   sheet->row_title_area.height = sheet_allocation.height;
3934
3935   if (sheet->row_titles_visible)
3936     sheet->column_title_area.x += sheet->row_title_area.width;
3937
3938   if (sheet->column_titles_visible)
3939     sheet->row_title_area.y += sheet->column_title_area.height;
3940
3941   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
3942     gdk_window_move_resize (sheet->column_title_window,
3943                             sheet->column_title_area.x,
3944                             sheet->column_title_area.y,
3945                             sheet->column_title_area.width,
3946                             sheet->column_title_area.height);
3947
3948
3949   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
3950     gdk_window_move_resize (sheet->row_title_window,
3951                             sheet->row_title_area.x,
3952                             sheet->row_title_area.y,
3953                             sheet->row_title_area.width,
3954                             sheet->row_title_area.height);
3955
3956   size_allocate_global_button (sheet);
3957
3958   if (sheet->haxis)
3959     {
3960       gint width = sheet->column_title_area.width;
3961
3962       if ( sheet->row_titles_visible)
3963         width -= sheet->row_title_area.width;
3964
3965       g_object_set (sheet->haxis,
3966                     "minimum-extent", width,
3967                     NULL);
3968     }
3969
3970
3971   if (sheet->vaxis)
3972     {
3973       gint height = sheet->row_title_area.height;
3974
3975       if ( sheet->column_titles_visible)
3976         height -= sheet->column_title_area.height;
3977
3978       g_object_set (sheet->vaxis,
3979                     "minimum-extent", height,
3980                     NULL);
3981     }
3982
3983
3984   /* set the scrollbars adjustments */
3985   adjust_scrollbars (sheet);
3986 }
3987
3988 static void
3989 draw_column_title_buttons (PsppireSheet *sheet)
3990 {
3991   gint x, width;
3992
3993   if (!sheet->column_titles_visible) return;
3994   if (!GTK_WIDGET_REALIZED (sheet))
3995     return;
3996
3997   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
3998   x = 0;
3999
4000   if (sheet->row_titles_visible)
4001     {
4002       x = sheet->row_title_area.width;
4003     }
4004
4005   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
4006     {
4007       sheet->column_title_area.width = width;
4008       sheet->column_title_area.x = x;
4009       gdk_window_move_resize (sheet->column_title_window,
4010                               sheet->column_title_area.x,
4011                               sheet->column_title_area.y,
4012                               sheet->column_title_area.width,
4013                               sheet->column_title_area.height);
4014     }
4015
4016   if (max_visible_column (sheet) ==
4017       psppire_axis_unit_count (sheet->haxis) - 1)
4018     gdk_window_clear_area (sheet->column_title_window,
4019                            0, 0,
4020                            sheet->column_title_area.width,
4021                            sheet->column_title_area.height);
4022
4023   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4024
4025   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
4026                                    max_visible_column (sheet));
4027 }
4028
4029 static void
4030 draw_row_title_buttons (PsppireSheet *sheet)
4031 {
4032   gint y = 0;
4033   gint height;
4034
4035   if (!sheet->row_titles_visible) return;
4036   if (!GTK_WIDGET_REALIZED (sheet))
4037     return;
4038
4039   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
4040
4041   if (sheet->column_titles_visible)
4042     {
4043       y = sheet->column_title_area.height;
4044     }
4045
4046   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
4047     {
4048       sheet->row_title_area.y = y;
4049       sheet->row_title_area.height = height;
4050       gdk_window_move_resize (sheet->row_title_window,
4051                               sheet->row_title_area.x,
4052                               sheet->row_title_area.y,
4053                               sheet->row_title_area.width,
4054                               sheet->row_title_area.height);
4055     }
4056
4057   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
4058     gdk_window_clear_area (sheet->row_title_window,
4059                            0, 0,
4060                            sheet->row_title_area.width,
4061                            sheet->row_title_area.height);
4062
4063   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4064
4065   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
4066                                 max_visible_row (sheet));
4067 }
4068
4069
4070 static void
4071 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
4072 {
4073   GtkAllocation entry_alloc;
4074   PsppireSheetCellAttr attributes = { 0 };
4075   GtkEntry *sheet_entry;
4076
4077   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4078   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
4079
4080   sheet_entry = psppire_sheet_get_entry (sheet);
4081
4082   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
4083                                        sheet->active_cell.col,
4084                                        &attributes) )
4085     return ;
4086
4087   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
4088     {
4089       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
4090
4091       style->bg[GTK_STATE_NORMAL] = attributes.background;
4092       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
4093       style->text[GTK_STATE_NORMAL] = attributes.foreground;
4094       style->bg[GTK_STATE_ACTIVE] = attributes.background;
4095       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
4096       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
4097     }
4098
4099   rectangle_from_cell (sheet, sheet->active_cell.row,
4100                        sheet->active_cell.col, &entry_alloc);
4101
4102   entry_alloc.x += sheet->cell_padding->left;
4103   entry_alloc.y += sheet->cell_padding->right;
4104   entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
4105   entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
4106
4107
4108   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
4109                                entry_alloc.height);
4110   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
4111 }
4112
4113
4114 /* Copy the sheet's font to the entry widget */
4115 static void
4116 set_entry_widget_font (PsppireSheet *sheet)
4117 {
4118   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
4119
4120   pango_font_description_free (style->font_desc);
4121   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4122
4123   gtk_widget_modify_style (sheet->entry_widget, style);
4124 }
4125
4126 static void
4127 create_sheet_entry (PsppireSheet *sheet)
4128 {
4129   if (sheet->entry_widget)
4130     {
4131       gtk_widget_unparent (sheet->entry_widget);
4132     }
4133
4134   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4135   g_object_ref_sink (sheet->entry_widget);
4136
4137   gtk_widget_size_request (sheet->entry_widget, NULL);
4138
4139   if ( GTK_IS_ENTRY (sheet->entry_widget))
4140     {
4141       g_object_set (sheet->entry_widget,
4142                     "has-frame", FALSE,
4143                     NULL);
4144     }
4145
4146   if (GTK_WIDGET_REALIZED (sheet))
4147     {
4148       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4149       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4150       gtk_widget_realize (sheet->entry_widget);
4151     }
4152
4153   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4154                             G_CALLBACK (psppire_sheet_entry_key_press),
4155                             sheet);
4156
4157   set_entry_widget_font (sheet);
4158
4159   gtk_widget_show (sheet->entry_widget);
4160 }
4161
4162
4163 /* Finds the last child widget that happens to be of type GtkEntry */
4164 static void
4165 find_entry (GtkWidget *w, gpointer user_data)
4166 {
4167   GtkWidget **entry = user_data;
4168   if ( GTK_IS_ENTRY (w))
4169     {
4170       *entry = w;
4171     }
4172 }
4173
4174
4175 GtkEntry *
4176 psppire_sheet_get_entry (PsppireSheet *sheet)
4177 {
4178   GtkWidget *w = sheet->entry_widget;
4179
4180   g_return_val_if_fail (sheet != NULL, NULL);
4181   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4182   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4183
4184   while (! GTK_IS_ENTRY (w))
4185     {
4186       GtkWidget *entry = NULL;
4187
4188       if (GTK_IS_CONTAINER (w))
4189         {
4190           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4191
4192           if (NULL == entry)
4193             break;
4194
4195           w = entry;
4196         }
4197     }
4198
4199   return GTK_ENTRY (w);
4200 }
4201
4202
4203 static void
4204 draw_button (PsppireSheet *sheet, GdkWindow *window,
4205              PsppireSheetButton *button, gboolean is_sensitive,
4206              GdkRectangle allocation)
4207 {
4208   GtkShadowType shadow_type;
4209   gint text_width = 0, text_height = 0;
4210   PangoAlignment align = PANGO_ALIGN_LEFT;
4211
4212   gboolean rtl ;
4213
4214   gint state = 0;
4215
4216   g_return_if_fail (sheet != NULL);
4217   g_return_if_fail (button != NULL);
4218
4219
4220   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4221
4222   gdk_window_clear_area (window,
4223                          allocation.x, allocation.y,
4224                          allocation.width, allocation.height);
4225
4226   gtk_widget_ensure_style (sheet->button);
4227
4228   gtk_paint_box (sheet->button->style, window,
4229                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4230                  &allocation,
4231                  GTK_WIDGET (sheet->button),
4232                  NULL,
4233                  allocation.x, allocation.y,
4234                  allocation.width, allocation.height);
4235
4236   state = button->state;
4237   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4238
4239   if (state == GTK_STATE_ACTIVE)
4240     shadow_type = GTK_SHADOW_IN;
4241   else
4242     shadow_type = GTK_SHADOW_OUT;
4243
4244   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4245     gtk_paint_box (sheet->button->style, window,
4246                    button->state, shadow_type,
4247                    &allocation, GTK_WIDGET (sheet->button),
4248                    NULL,
4249                    allocation.x, allocation.y,
4250                    allocation.width, allocation.height);
4251
4252   if ( button->overstruck)
4253     {
4254       GdkPoint points[2] = {
4255         {allocation.x,  allocation.y},
4256         {allocation.x + allocation.width,
4257          allocation.y + allocation.height}
4258       };
4259
4260       gtk_paint_polygon (sheet->button->style,
4261                          window,
4262                          button->state,
4263                          shadow_type,
4264                          NULL,
4265                          GTK_WIDGET (sheet),
4266                          NULL,
4267                          points,
4268                          2,
4269                          TRUE);
4270     }
4271
4272   if (button->label_visible)
4273     {
4274       text_height = DEFAULT_ROW_HEIGHT -
4275         2 * COLUMN_TITLES_HEIGHT;
4276
4277       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4278                                  &allocation);
4279       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4280                                  &allocation);
4281
4282       allocation.y += 2 * sheet->button->style->ythickness;
4283
4284       if (button->label && strlen (button->label) > 0)
4285         {
4286           PangoRectangle rect;
4287           gchar *line = button->label;
4288
4289           PangoLayout *layout = NULL;
4290           gint real_x = allocation.x;
4291           gint real_y = allocation.y;
4292
4293           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4294           pango_layout_get_extents (layout, NULL, &rect);
4295
4296           text_width = PANGO_PIXELS (rect.width);
4297           switch (button->justification)
4298             {
4299             case GTK_JUSTIFY_LEFT:
4300               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4301               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4302               break;
4303             case GTK_JUSTIFY_RIGHT:
4304               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4305               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4306               break;
4307             case GTK_JUSTIFY_CENTER:
4308             default:
4309               real_x = allocation.x + (allocation.width - text_width)/2;
4310               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4311               pango_layout_set_justify (layout, TRUE);
4312             }
4313           pango_layout_set_alignment (layout, align);
4314           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4315                             window,
4316                             state,
4317                             FALSE,
4318                             &allocation,
4319                             GTK_WIDGET (sheet),
4320                             "label",
4321                             real_x, real_y,
4322                             layout);
4323           g_object_unref (layout);
4324         }
4325
4326       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4327                                  NULL);
4328       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4329
4330     }
4331
4332   psppire_sheet_button_free (button);
4333 }
4334
4335
4336 /* Draw the column title buttons FIRST through to LAST */
4337 static void
4338 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4339 {
4340   GdkRectangle rect;
4341   gint col;
4342   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4343
4344   if (!sheet->column_titles_visible) return;
4345
4346   g_return_if_fail (first >= min_visible_column (sheet));
4347   g_return_if_fail (last <= max_visible_column (sheet));
4348
4349   rect.y = 0;
4350   rect.height = sheet->column_title_area.height;
4351   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4352   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4353     + psppire_axis_unit_size (sheet->haxis, last);
4354
4355   rect.x -= sheet->hadjustment->value;
4356
4357   minimize_int (&rect.width, sheet->column_title_area.width);
4358   maximize_int (&rect.x, 0);
4359
4360   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4361
4362   for (col = first ; col <= last ; ++col)
4363     {
4364       GdkRectangle allocation;
4365       gboolean is_sensitive = FALSE;
4366
4367       PsppireSheetButton *
4368         button = psppire_sheet_model_get_column_button (sheet->model, col);
4369       allocation.y = 0;
4370       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4371         + CELL_SPACING;
4372       allocation.x -= sheet->hadjustment->value;
4373
4374       allocation.height = sheet->column_title_area.height;
4375       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4376       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4377
4378       draw_button (sheet, sheet->column_title_window,
4379                    button, is_sensitive, allocation);
4380     }
4381
4382   gdk_window_end_paint (sheet->column_title_window);
4383 }
4384
4385
4386 static void
4387 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4388 {
4389   GdkRectangle rect;
4390   gint row;
4391   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4392
4393   if (!sheet->row_titles_visible) return;
4394
4395   g_return_if_fail (first >= min_visible_row (sheet));
4396   g_return_if_fail (last <= max_visible_row (sheet));
4397
4398   rect.x = 0;
4399   rect.width = sheet->row_title_area.width;
4400   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4401   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4402     + psppire_axis_unit_size (sheet->vaxis, last);
4403
4404   rect.y -= sheet->vadjustment->value;
4405
4406   minimize_int (&rect.height, sheet->row_title_area.height);
4407   maximize_int (&rect.y, 0);
4408
4409   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4410   for (row = first; row <= last; ++row)
4411     {
4412       GdkRectangle allocation;
4413
4414       gboolean is_sensitive = FALSE;
4415
4416       PsppireSheetButton *button =
4417         psppire_sheet_model_get_row_button (sheet->model, row);
4418       allocation.x = 0;
4419       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4420         + CELL_SPACING;
4421       allocation.y -= sheet->vadjustment->value;
4422
4423       allocation.width = sheet->row_title_area.width;
4424       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4425       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4426
4427       draw_button (sheet, sheet->row_title_window,
4428                    button, is_sensitive, allocation);
4429     }
4430
4431   gdk_window_end_paint (sheet->row_title_window);
4432 }
4433
4434 /* SCROLLBARS
4435  *
4436  * functions:
4437  * adjust_scrollbars
4438  * vadjustment_value_changed
4439  * hadjustment_value_changed */
4440
4441
4442 static void
4443 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
4444 {
4445   double position =
4446     (adj->value + adj->page_size)
4447     /
4448     (adj->upper - adj->lower);
4449
4450   const glong last_item = psppire_axis_unit_count (axis) - 1;
4451
4452   if (isnan (position) || position < 0)
4453     position = 0;
4454
4455   adj->upper =
4456     psppire_axis_start_pixel (axis, last_item)
4457     +
4458     psppire_axis_unit_size (axis, last_item)
4459     ;
4460
4461   adj->lower = 0;
4462   adj->page_size = page_size;
4463
4464 #if 0
4465   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
4466
4467   if ( adj->value < adj->lower)
4468     adj->value = adj->lower;
4469 #endif
4470
4471   gtk_adjustment_changed (adj);
4472 }
4473
4474
4475 static void
4476 adjust_scrollbars (PsppireSheet *sheet)
4477 {
4478   gint width, height;
4479
4480   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4481     return;
4482
4483   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
4484
4485   if ( sheet->row_titles_visible)
4486     width -= sheet->row_title_area.width;
4487
4488   if (sheet->column_titles_visible)
4489     height -= sheet->column_title_area.height;
4490
4491   if (sheet->vadjustment)
4492     {
4493       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
4494
4495       sheet->vadjustment->step_increment =
4496         ROWS_PER_STEP *
4497         psppire_axis_unit_size (sheet->vaxis, last_row);
4498
4499       sheet->vadjustment->page_increment =
4500         height -
4501         sheet->column_title_area.height -
4502         psppire_axis_unit_size (sheet->vaxis, last_row);
4503
4504       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
4505     }
4506
4507   if (sheet->hadjustment)
4508     {
4509       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
4510       sheet->hadjustment->step_increment = 1;
4511
4512       sheet->hadjustment->page_increment = width;
4513
4514       sheet->hadjustment->upper =
4515         psppire_axis_start_pixel (sheet->haxis, last_col)
4516         +
4517         psppire_axis_unit_size (sheet->haxis, last_col)
4518         ;
4519
4520       update_adjustment (sheet->hadjustment, sheet->haxis, width);
4521     }
4522 }
4523
4524 /* Subtracts the region of WIDGET from REGION */
4525 static void
4526 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
4527 {
4528   GdkRectangle rect;
4529   GdkRectangle intersect;
4530   GdkRegion *region2;
4531
4532   gdk_region_get_clipbox (region, &rect);
4533   gtk_widget_intersect (widget,
4534                         &rect,
4535                         &intersect);
4536
4537   region2 = gdk_region_rectangle (&intersect);
4538   gdk_region_subtract (region, region2);
4539   gdk_region_destroy (region2);
4540 }
4541
4542 static void
4543 vadjustment_value_changed (GtkAdjustment *adjustment,
4544                            gpointer data)
4545 {
4546   GdkRegion *region;
4547   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4548
4549   g_return_if_fail (adjustment != NULL);
4550
4551   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4552
4553   gtk_widget_hide (sheet->entry_widget);
4554
4555   region =
4556     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4557
4558   subtract_widget_region (region, sheet->button);
4559   gdk_window_begin_paint_region (sheet->sheet_window, region);
4560
4561   draw_sheet_region (sheet, region);
4562
4563   draw_row_title_buttons (sheet);
4564   psppire_sheet_draw_active_cell (sheet);
4565
4566   gdk_window_end_paint (sheet->sheet_window);
4567   gdk_region_destroy (region);
4568 }
4569
4570
4571 static void
4572 hadjustment_value_changed (GtkAdjustment *adjustment,
4573                            gpointer data)
4574 {
4575   GdkRegion *region;
4576   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4577
4578   g_return_if_fail (adjustment != NULL);
4579
4580   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4581
4582   gtk_widget_hide (sheet->entry_widget);
4583
4584
4585   region =
4586     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4587
4588   subtract_widget_region (region, sheet->button);
4589   gdk_window_begin_paint_region (sheet->sheet_window, region);
4590
4591   draw_sheet_region (sheet, region);
4592
4593   draw_column_title_buttons (sheet);
4594
4595   psppire_sheet_draw_active_cell (sheet);
4596
4597   gdk_window_end_paint (sheet->sheet_window);
4598
4599   gdk_region_destroy (region);
4600 }
4601
4602
4603 /* COLUMN RESIZING */
4604 static void
4605 draw_xor_vline (PsppireSheet *sheet)
4606 {
4607   gint height;
4608   gint xpos = sheet->x_drag;
4609   gdk_drawable_get_size (sheet->sheet_window,
4610                          NULL, &height);
4611
4612   if (sheet->row_titles_visible)
4613     xpos += sheet->row_title_area.width;
4614
4615   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4616                  xpos,
4617                  sheet->column_title_area.height,
4618                  xpos,
4619                  height + CELL_SPACING);
4620 }
4621
4622 /* ROW RESIZING */
4623 static void
4624 draw_xor_hline (PsppireSheet *sheet)
4625
4626 {
4627   gint width;
4628   gint ypos = sheet->y_drag;
4629
4630   gdk_drawable_get_size (sheet->sheet_window,
4631                          &width, NULL);
4632
4633
4634   if (sheet->column_titles_visible)
4635     ypos += sheet->column_title_area.height;
4636
4637   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4638                  sheet->row_title_area.width,
4639                  ypos,
4640                  width + CELL_SPACING,
4641                  ypos);
4642 }
4643
4644 /* SELECTED RANGE */
4645 static void
4646 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
4647 {
4648   gint i = 0;
4649   GdkRectangle clip_area, area;
4650   GdkGCValues values;
4651
4652   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
4653   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
4654   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
4655     psppire_axis_unit_size (sheet->haxis, range.coli);
4656   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
4657     psppire_axis_unit_size (sheet->vaxis, range.rowi);
4658
4659   clip_area.x = sheet->row_title_area.width;
4660   clip_area.y = sheet->column_title_area.height;
4661
4662   gdk_drawable_get_size (sheet->sheet_window,
4663                          &clip_area.width, &clip_area.height);
4664
4665   if (!sheet->row_titles_visible) clip_area.x = 0;
4666   if (!sheet->column_titles_visible) clip_area.y = 0;
4667
4668   if (area.x < 0)
4669     {
4670       area.width = area.width + area.x;
4671       area.x = 0;
4672     }
4673   if (area.width > clip_area.width) area.width = clip_area.width + 10;
4674   if (area.y < 0)
4675     {
4676       area.height = area.height + area.y;
4677       area.y = 0;
4678     }
4679   if (area.height > clip_area.height) area.height = clip_area.height + 10;
4680
4681   clip_area.x--;
4682   clip_area.y--;
4683   clip_area.width += 3;
4684   clip_area.height += 3;
4685
4686   gdk_gc_get_values (sheet->xor_gc, &values);
4687
4688   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
4689
4690   gdk_draw_rectangle (sheet->sheet_window,
4691                       sheet->xor_gc,
4692                       FALSE,
4693                       area.x + i, area.y + i,
4694                       area.width - 2 * i, area.height - 2 * i);
4695
4696
4697   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
4698
4699   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
4700 }
4701
4702
4703 static void
4704 set_column_width (PsppireSheet *sheet,
4705                   gint column,
4706                   gint width)
4707 {
4708   g_return_if_fail (sheet != NULL);
4709   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4710
4711   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
4712     return;
4713
4714   if ( width <= 0)
4715     return;
4716
4717   psppire_axis_resize (sheet->haxis, column,
4718                        width - sheet->cell_padding->left -
4719                        sheet->cell_padding->right);
4720
4721   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4722     {
4723       draw_column_title_buttons (sheet);
4724       adjust_scrollbars (sheet);
4725       psppire_sheet_size_allocate_entry (sheet);
4726       redraw_range (sheet, NULL);
4727     }
4728 }
4729
4730 static void
4731 set_row_height (PsppireSheet *sheet,
4732                 gint row,
4733                 gint height)
4734 {
4735   g_return_if_fail (sheet != NULL);
4736   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4737
4738   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
4739     return;
4740
4741   if (height <= 0)
4742     return;
4743
4744   psppire_axis_resize (sheet->vaxis, row,
4745                        height - sheet->cell_padding->top -
4746                        sheet->cell_padding->bottom);
4747
4748   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
4749     {
4750       draw_row_title_buttons (sheet);
4751       adjust_scrollbars (sheet);
4752       psppire_sheet_size_allocate_entry (sheet);
4753       redraw_range (sheet, NULL);
4754     }
4755 }
4756
4757 static gboolean
4758 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
4759                               PsppireSheetCellAttr *attr)
4760 {
4761   GdkColor *fg, *bg;
4762   const GtkJustification *j ;
4763   GdkColormap *colormap;
4764
4765   g_return_val_if_fail (sheet != NULL, FALSE);
4766   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
4767
4768   if (row < 0 || col < 0) return FALSE;
4769
4770   attr->foreground = GTK_WIDGET (sheet)->style->black;
4771   attr->background = sheet->color[BG_COLOR];
4772
4773   attr->border.width = 0;
4774   attr->border.line_style = GDK_LINE_SOLID;
4775   attr->border.cap_style = GDK_CAP_NOT_LAST;
4776   attr->border.join_style = GDK_JOIN_MITER;
4777   attr->border.mask = 0;
4778   attr->border.color = GTK_WIDGET (sheet)->style->black;
4779
4780   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
4781   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
4782   if ( fg )
4783     {
4784       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
4785       attr->foreground = *fg;
4786     }
4787
4788   bg = psppire_sheet_model_get_background (sheet->model, row, col);
4789   if ( bg )
4790     {
4791       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
4792       attr->background = *bg;
4793     }
4794
4795   attr->justification =
4796     psppire_sheet_model_get_column_justification (sheet->model, col);
4797
4798   j = psppire_sheet_model_get_justification (sheet->model, row, col);
4799   if (j)
4800     attr->justification = *j;
4801
4802   return TRUE;
4803 }
4804
4805 static void
4806 psppire_sheet_button_size_request        (PsppireSheet *sheet,
4807                                           const PsppireSheetButton *button,
4808                                           GtkRequisition *button_requisition)
4809 {
4810   GtkRequisition requisition;
4811   GtkRequisition label_requisition;
4812
4813   label_requisition.height = DEFAULT_ROW_HEIGHT;
4814   label_requisition.width = COLUMN_MIN_WIDTH;
4815
4816   requisition.height = DEFAULT_ROW_HEIGHT;
4817   requisition.width = COLUMN_MIN_WIDTH;
4818
4819
4820   *button_requisition = requisition;
4821   button_requisition->width = MAX (requisition.width, label_requisition.width);
4822   button_requisition->height = MAX (requisition.height, label_requisition.height);
4823
4824 }
4825
4826 static void
4827 psppire_sheet_forall (GtkContainer *container,
4828                       gboolean include_internals,
4829                       GtkCallback callback,
4830                       gpointer callback_data)
4831 {
4832   PsppireSheet *sheet = PSPPIRE_SHEET (container);
4833
4834   g_return_if_fail (callback != NULL);
4835
4836   if (sheet->button && sheet->button->parent)
4837     (* callback) (sheet->button, callback_data);
4838
4839   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
4840     (* callback) (sheet->entry_widget, callback_data);
4841 }
4842
4843
4844 PsppireSheetModel *
4845 psppire_sheet_get_model (const PsppireSheet *sheet)
4846 {
4847   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4848
4849   return sheet->model;
4850 }
4851
4852
4853 PsppireSheetButton *
4854 psppire_sheet_button_new (void)
4855 {
4856   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
4857
4858   button->state = GTK_STATE_NORMAL;
4859   button->label = NULL;
4860   button->label_visible = TRUE;
4861   button->justification = GTK_JUSTIFY_FILL;
4862   button->overstruck = FALSE;
4863
4864   return button;
4865 }
4866
4867
4868 void
4869 psppire_sheet_button_free (PsppireSheetButton *button)
4870 {
4871   if (!button) return ;
4872
4873   g_free (button->label);
4874   g_free (button);
4875 }
4876
4877 static void
4878 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
4879 {
4880   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
4881
4882   if ( NULL == celltext)
4883     return;
4884
4885   g_string_append (string, celltext);
4886   g_free (celltext);
4887 }
4888
4889
4890 static GString *
4891 range_to_text (const PsppireSheet *sheet)
4892 {
4893   gint r, c;
4894   GString *string;
4895
4896   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4897     return NULL;
4898
4899   string = g_string_sized_new (80);
4900
4901   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4902     {
4903       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
4904         {
4905           append_cell_text (string, sheet, r, c);
4906           g_string_append (string, "\t");
4907         }
4908       append_cell_text (string, sheet, r, c);
4909       if ( r < sheet->range.rowi)
4910         g_string_append (string, "\n");
4911     }
4912
4913   return string;
4914 }
4915
4916 static GString *
4917 range_to_html (const PsppireSheet *sheet)
4918 {
4919   gint r, c;
4920   GString *string;
4921
4922   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4923     return NULL;
4924
4925   string = g_string_sized_new (480);
4926
4927   g_string_append (string, "<html>\n");
4928   g_string_append (string, "<body>\n");
4929   g_string_append (string, "<table>\n");
4930   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4931     {
4932       g_string_append (string, "<tr>\n");
4933       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
4934         {
4935           g_string_append (string, "<td>");
4936           append_cell_text (string, sheet, r, c);
4937           g_string_append (string, "</td>\n");
4938         }
4939       g_string_append (string, "</tr>\n");
4940     }
4941   g_string_append (string, "</table>\n");
4942   g_string_append (string, "</body>\n");
4943   g_string_append (string, "</html>\n");
4944
4945   return string;
4946 }
4947
4948 enum {
4949   SELECT_FMT_NULL,
4950   SELECT_FMT_TEXT,
4951   SELECT_FMT_HTML
4952 };
4953
4954 static void
4955 primary_get_cb (GtkClipboard     *clipboard,
4956                 GtkSelectionData *selection_data,
4957                 guint             info,
4958                 gpointer          data)
4959 {
4960   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4961   GString *string = NULL;
4962
4963   switch (info)
4964     {
4965     case SELECT_FMT_TEXT:
4966       string = range_to_text (sheet);
4967       break;
4968     case SELECT_FMT_HTML:
4969       string = range_to_html (sheet);
4970       break;
4971     default:
4972       g_assert_not_reached ();
4973     }
4974
4975   gtk_selection_data_set (selection_data, selection_data->target,
4976                           8,
4977                           (const guchar *) string->str, string->len);
4978   g_string_free (string, TRUE);
4979 }
4980
4981 static void
4982 primary_clear_cb (GtkClipboard *clipboard,
4983                   gpointer      data)
4984 {
4985   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4986   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4987     return;
4988
4989   psppire_sheet_unselect_range (sheet);
4990 }
4991
4992 static void
4993 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
4994 {
4995   static const GtkTargetEntry targets[] = {
4996     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
4997     { "STRING",        0, SELECT_FMT_TEXT },
4998     { "TEXT",          0, SELECT_FMT_TEXT },
4999     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
5000     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
5001     { "text/plain",    0, SELECT_FMT_TEXT },
5002     { "text/html",     0, SELECT_FMT_HTML }
5003   };
5004
5005   GtkClipboard *clipboard;
5006
5007   if (!GTK_WIDGET_REALIZED (sheet))
5008     return;
5009
5010   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
5011                                         GDK_SELECTION_PRIMARY);
5012
5013   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
5014     {
5015       if (!gtk_clipboard_set_with_owner (clipboard, targets,
5016                                          G_N_ELEMENTS (targets),
5017                                          primary_get_cb, primary_clear_cb,
5018                                          G_OBJECT (sheet)))
5019         primary_clear_cb (clipboard, sheet);
5020     }
5021   else
5022     {
5023       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
5024         gtk_clipboard_clear (clipboard);
5025     }
5026 }