7c6d57d9a94d0d7688e3095fb4b023a16816afe9
[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->selection_mode = GTK_SELECTION_NONE;
1133   sheet->select_status = PSPPIRE_SHEET_NORMAL;
1134
1135   GTK_WIDGET_UNSET_FLAGS (sheet, GTK_NO_WINDOW);
1136   GTK_WIDGET_SET_FLAGS (sheet, GTK_CAN_FOCUS);
1137
1138   sheet->column_title_window = NULL;
1139   sheet->column_title_area.x = 0;
1140   sheet->column_title_area.y = 0;
1141   sheet->column_title_area.width = 0;
1142   sheet->column_title_area.height = DEFAULT_ROW_HEIGHT;
1143
1144   sheet->row_title_window = NULL;
1145   sheet->row_title_area.x = 0;
1146   sheet->row_title_area.y = 0;
1147   sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
1148   sheet->row_title_area.height = 0;
1149
1150
1151   sheet->active_cell.row = 0;
1152   sheet->active_cell.col = 0;
1153
1154   sheet->range.row0 = 0;
1155   sheet->range.rowi = 0;
1156   sheet->range.col0 = 0;
1157   sheet->range.coli = 0;
1158
1159   sheet->sheet_window = NULL;
1160   sheet->entry_widget = NULL;
1161   sheet->button = NULL;
1162
1163   sheet->hadjustment = NULL;
1164   sheet->vadjustment = NULL;
1165
1166   sheet->cursor_drag = NULL;
1167
1168   sheet->xor_gc = NULL;
1169   sheet->fg_gc = NULL;
1170   sheet->bg_gc = NULL;
1171   sheet->x_drag = 0;
1172   sheet->y_drag = 0;
1173   sheet->show_grid = TRUE;
1174
1175   sheet->motion_timer = 0;
1176
1177   sheet->row_titles_visible = TRUE;
1178   sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
1179
1180   sheet->column_titles_visible = TRUE;
1181
1182
1183   /* create sheet entry */
1184   sheet->entry_type = GTK_TYPE_ENTRY;
1185   create_sheet_entry (sheet);
1186
1187   /* create global selection button */
1188   create_global_button (sheet);
1189 }
1190
1191
1192 /* Cause RANGE to be redrawn. If RANGE is null, then the
1193    entire visible range will be redrawn.
1194 */
1195 static void
1196 redraw_range (PsppireSheet *sheet, PsppireSheetRange *range)
1197 {
1198   GdkRectangle rect;
1199
1200   if ( ! GTK_WIDGET_REALIZED (sheet))
1201     return;
1202
1203   if ( NULL != range )
1204     rectangle_from_range (sheet, range, &rect);
1205   else
1206     {
1207       GdkRegion *r = gdk_drawable_get_visible_region (sheet->sheet_window);
1208       gdk_region_get_clipbox (r, &rect);
1209
1210       if ( sheet->column_titles_visible)
1211         {
1212           rect.y += sheet->column_title_area.height;
1213           rect.height -= sheet->column_title_area.height;
1214         }
1215
1216       if ( sheet->row_titles_visible)
1217         {
1218           rect.x += sheet->row_title_area.width;
1219           rect.width -= sheet->row_title_area.width;
1220         }
1221     }
1222
1223   gdk_window_invalidate_rect (sheet->sheet_window, &rect, FALSE);
1224 }
1225
1226
1227 /* Callback which occurs whenever columns are inserted / deleted in the model */
1228 static void
1229 columns_inserted_deleted_callback (PsppireSheetModel *model, gint first_column,
1230                                    gint n_columns,
1231                                    gpointer data)
1232 {
1233   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1234
1235   PsppireSheetRange range;
1236   gint model_columns = psppire_sheet_model_get_column_count (model);
1237
1238
1239   /* Need to update all the columns starting from the first column and onwards.
1240    * Previous column are unchanged, so don't need to be updated.
1241    */
1242   range.col0 = first_column;
1243   range.row0 = 0;
1244   range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
1245   range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
1246
1247   adjust_scrollbars (sheet);
1248
1249   if (sheet->active_cell.col >= model_columns)
1250     change_active_cell (sheet, sheet->active_cell.row, model_columns - 1);
1251
1252   draw_column_title_buttons_range (sheet,
1253                                    first_column, max_visible_column (sheet));
1254
1255
1256   redraw_range (sheet, &range);
1257 }
1258
1259
1260
1261
1262 /* Callback which occurs whenever rows are inserted / deleted in the model */
1263 static void
1264 rows_inserted_deleted_callback (PsppireSheetModel *model, gint first_row,
1265                                 gint n_rows, gpointer data)
1266 {
1267   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1268
1269   PsppireSheetRange range;
1270
1271   gint model_rows = psppire_sheet_model_get_row_count (model);
1272
1273   /* Need to update all the rows starting from the first row and onwards.
1274    * Previous rows are unchanged, so don't need to be updated.
1275    */
1276   range.row0 = first_row;
1277   range.col0 = 0;
1278   range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
1279   range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
1280
1281   adjust_scrollbars (sheet);
1282
1283   if (sheet->active_cell.row >= model_rows)
1284     change_active_cell (sheet, model_rows - 1, sheet->active_cell.col);
1285
1286   draw_row_title_buttons_range (sheet, first_row, max_visible_row (sheet));
1287
1288   redraw_range (sheet, &range);
1289 }
1290
1291 /*
1292   If row0 or rowi are negative, then all rows will be updated.
1293   If col0 or coli are negative, then all columns will be updated.
1294 */
1295 static void
1296 range_update_callback (PsppireSheetModel *m, gint row0, gint col0,
1297                        gint rowi, gint coli, gpointer data)
1298 {
1299   PsppireSheet *sheet = PSPPIRE_SHEET (data);
1300
1301   PsppireSheetRange range;
1302
1303   range.row0 = row0;
1304   range.col0 = col0;
1305   range.rowi = rowi;
1306   range.coli = coli;
1307
1308   if ( !GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1309     return;
1310
1311   if ( ( row0 < 0 && col0 < 0 ) || ( rowi < 0 && coli < 0 ) )
1312     {
1313       redraw_range (sheet, NULL);
1314       adjust_scrollbars (sheet);
1315
1316       draw_row_title_buttons_range (sheet, min_visible_row (sheet),
1317                                     max_visible_row (sheet));
1318
1319       draw_column_title_buttons_range (sheet, min_visible_column (sheet),
1320                                        max_visible_column (sheet));
1321
1322       return;
1323     }
1324   else if ( row0 < 0 || rowi < 0 )
1325     {
1326       range.row0 = min_visible_row (sheet);
1327       range.rowi = max_visible_row (sheet);
1328     }
1329   else if ( col0 < 0 || coli < 0 )
1330     {
1331       range.col0 = min_visible_column (sheet);
1332       range.coli = max_visible_column (sheet);
1333     }
1334
1335   redraw_range (sheet, &range);
1336 }
1337
1338
1339 /**
1340  * psppire_sheet_new:
1341  * @rows: initial number of rows
1342  * @columns: initial number of columns
1343  * @title: sheet title
1344  * @model: the model to use for the sheet data
1345  *
1346  * Creates a new sheet widget with the given number of rows and columns.
1347  *
1348  * Returns: the new sheet widget
1349  */
1350 GtkWidget *
1351 psppire_sheet_new (PsppireSheetModel *model)
1352 {
1353   GtkWidget *widget = g_object_new (PSPPIRE_TYPE_SHEET,
1354                                     "model", model,
1355                                     NULL);
1356   return widget;
1357 }
1358
1359
1360 /**
1361  * psppire_sheet_set_model
1362  * @sheet: the sheet to set the model for
1363  * @model: the model to use for the sheet data
1364  *
1365  * Sets the model for a PsppireSheet
1366  *
1367  */
1368 void
1369 psppire_sheet_set_model (PsppireSheet *sheet, PsppireSheetModel *model)
1370 {
1371   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1372
1373   if (sheet->model ) g_object_unref (sheet->model);
1374
1375   sheet->model = model;
1376
1377   if ( model)
1378     {
1379       g_object_ref (model);
1380
1381       sheet->update_handler_id = g_signal_connect (model, "range_changed",
1382                                                    G_CALLBACK (range_update_callback),
1383                                                    sheet);
1384
1385       g_signal_connect (model, "rows_inserted",
1386                         G_CALLBACK (rows_inserted_deleted_callback), sheet);
1387
1388       g_signal_connect (model, "rows_deleted",
1389                         G_CALLBACK (rows_inserted_deleted_callback), sheet);
1390
1391       g_signal_connect (model, "columns_inserted",
1392                         G_CALLBACK (columns_inserted_deleted_callback), sheet);
1393
1394       g_signal_connect (model, "columns_deleted",
1395                         G_CALLBACK (columns_inserted_deleted_callback), sheet);
1396     }
1397 }
1398
1399
1400 void
1401 psppire_sheet_change_entry (PsppireSheet *sheet, GtkType entry_type)
1402 {
1403   g_return_if_fail (sheet != NULL);
1404   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1405
1406   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
1407     psppire_sheet_hide_entry_widget (sheet);
1408
1409   sheet->entry_type = entry_type;
1410
1411   create_sheet_entry (sheet);
1412
1413   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
1414     psppire_sheet_show_entry_widget (sheet);
1415 }
1416
1417 void
1418 psppire_sheet_show_grid (PsppireSheet *sheet, gboolean show)
1419 {
1420   g_return_if_fail (sheet != NULL);
1421   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
1422
1423   if (show == sheet->show_grid) return;
1424
1425   sheet->show_grid = show;
1426
1427   redraw_range (sheet, NULL);
1428 }
1429
1430 gboolean
1431 psppire_sheet_grid_visible (PsppireSheet *sheet)
1432 {
1433   g_return_val_if_fail (sheet != NULL, 0);
1434   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
1435
1436   return sheet->show_grid;
1437 }
1438
1439 guint
1440 psppire_sheet_get_columns_count (PsppireSheet *sheet)
1441 {
1442   g_return_val_if_fail (sheet != NULL, 0);
1443   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
1444
1445   return psppire_axis_unit_count (sheet->haxis);
1446 }
1447
1448 static void set_column_width (PsppireSheet *sheet,
1449                               gint column,
1450                               gint width);
1451
1452
1453 void
1454 psppire_sheet_show_column_titles (PsppireSheet *sheet)
1455 {
1456   if (sheet->column_titles_visible) return;
1457
1458   sheet->column_titles_visible = TRUE;
1459
1460   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1461     return;
1462
1463   gdk_window_show (sheet->column_title_window);
1464   gdk_window_move_resize (sheet->column_title_window,
1465                           sheet->column_title_area.x,
1466                           sheet->column_title_area.y,
1467                           sheet->column_title_area.width,
1468                           sheet->column_title_area.height);
1469
1470   adjust_scrollbars (sheet);
1471
1472   if (sheet->vadjustment)
1473     g_signal_emit_by_name (sheet->vadjustment,
1474                            "value_changed");
1475
1476   size_allocate_global_button (sheet);
1477
1478   if ( sheet->row_titles_visible)
1479     gtk_widget_show (sheet->button);
1480 }
1481
1482
1483 void
1484 psppire_sheet_show_row_titles (PsppireSheet *sheet)
1485 {
1486   if (sheet->row_titles_visible) return;
1487
1488   sheet->row_titles_visible = TRUE;
1489
1490
1491   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1492     {
1493       gdk_window_show (sheet->row_title_window);
1494       gdk_window_move_resize (sheet->row_title_window,
1495                               sheet->row_title_area.x,
1496                               sheet->row_title_area.y,
1497                               sheet->row_title_area.width,
1498                               sheet->row_title_area.height);
1499
1500       adjust_scrollbars (sheet);
1501     }
1502
1503   if (sheet->hadjustment)
1504     g_signal_emit_by_name (sheet->hadjustment,
1505                            "value_changed");
1506
1507   size_allocate_global_button (sheet);
1508
1509   if ( sheet->column_titles_visible)
1510     gtk_widget_show (sheet->button);
1511 }
1512
1513 void
1514 psppire_sheet_hide_column_titles (PsppireSheet *sheet)
1515 {
1516   if (!sheet->column_titles_visible) return;
1517
1518   sheet->column_titles_visible = FALSE;
1519
1520   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1521     {
1522       if (sheet->column_title_window)
1523         gdk_window_hide (sheet->column_title_window);
1524
1525       gtk_widget_hide (sheet->button);
1526
1527       adjust_scrollbars (sheet);
1528     }
1529
1530   if (sheet->vadjustment)
1531     g_signal_emit_by_name (sheet->vadjustment,
1532                            "value_changed");
1533 }
1534
1535 void
1536 psppire_sheet_hide_row_titles (PsppireSheet *sheet)
1537 {
1538   if (!sheet->row_titles_visible) return;
1539
1540   sheet->row_titles_visible = FALSE;
1541
1542   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
1543     {
1544       if (sheet->row_title_window)
1545         gdk_window_hide (sheet->row_title_window);
1546
1547       gtk_widget_hide (sheet->button);
1548
1549       adjust_scrollbars (sheet);
1550     }
1551
1552   if (sheet->hadjustment)
1553     g_signal_emit_by_name (sheet->hadjustment,
1554                            "value_changed");
1555 }
1556
1557
1558 /* Scroll the sheet so that the cell ROW, COLUMN is visible.
1559    If {ROW,COL}_ALIGN is zero, then the cell will be placed
1560    at the {top,left} of the sheet.  If it's 1, then it'll
1561    be placed at the {bottom,right}.
1562    ROW or COL may be -1, in which case scrolling in that dimension
1563    does not occur.
1564 */
1565 void
1566 psppire_sheet_moveto (PsppireSheet *sheet,
1567                       gint row,
1568                       gint col,
1569                       gfloat row_align,
1570                       gfloat col_align)
1571 {
1572   gint width, height;
1573
1574   g_return_if_fail (row_align >= 0);
1575   g_return_if_fail (col_align >= 0);
1576
1577   g_return_if_fail (row_align <= 1);
1578   g_return_if_fail (col_align <= 1);
1579
1580   g_return_if_fail (col <
1581                     psppire_axis_unit_count (sheet->haxis));
1582   g_return_if_fail (row <
1583                     psppire_axis_unit_count (sheet->vaxis));
1584
1585   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
1586
1587
1588   if (row >= 0)
1589     {
1590       gint y =  psppire_axis_start_pixel (sheet->vaxis, row);
1591
1592       gtk_adjustment_set_value (sheet->vadjustment, y - height * row_align);
1593     }
1594
1595
1596   if (col >= 0)
1597     {
1598       gint x =  psppire_axis_start_pixel (sheet->haxis, col);
1599
1600       gtk_adjustment_set_value (sheet->hadjustment, x - width * col_align);
1601     }
1602 }
1603
1604
1605
1606
1607 static gboolean
1608 psppire_sheet_range_isvisible (const PsppireSheet *sheet,
1609                                const PsppireSheetRange *range)
1610 {
1611   g_return_val_if_fail (sheet != NULL, FALSE);
1612
1613   if (range->row0 < 0 || range->row0 >= psppire_axis_unit_count (sheet->vaxis))
1614     return FALSE;
1615
1616   if (range->rowi < 0 || range->rowi >= psppire_axis_unit_count (sheet->vaxis))
1617     return FALSE;
1618
1619   if (range->col0 < 0 || range->col0 >= psppire_axis_unit_count (sheet->haxis))
1620     return FALSE;
1621
1622   if (range->coli < 0 || range->coli >= psppire_axis_unit_count (sheet->haxis))
1623     return FALSE;
1624
1625   if (range->rowi < min_visible_row (sheet))
1626     return FALSE;
1627
1628   if (range->row0 > max_visible_row (sheet))
1629     return FALSE;
1630
1631   if (range->coli < min_visible_column (sheet))
1632     return FALSE;
1633
1634   if (range->col0 > max_visible_column (sheet))
1635     return FALSE;
1636
1637   return TRUE;
1638 }
1639
1640 static gboolean
1641 psppire_sheet_cell_isvisible (PsppireSheet *sheet,
1642                               gint row, gint column)
1643 {
1644   PsppireSheetRange range;
1645
1646   range.row0 = row;
1647   range.col0 = column;
1648   range.rowi = row;
1649   range.coli = column;
1650
1651   return psppire_sheet_range_isvisible (sheet, &range);
1652 }
1653
1654 void
1655 psppire_sheet_get_visible_range (PsppireSheet *sheet, PsppireSheetRange *range)
1656 {
1657   g_return_if_fail (sheet != NULL);
1658   g_return_if_fail (PSPPIRE_IS_SHEET (sheet)) ;
1659   g_return_if_fail (range != NULL);
1660
1661   range->row0 = min_visible_row (sheet);
1662   range->col0 = min_visible_column (sheet);
1663   range->rowi = max_visible_row (sheet);
1664   range->coli = max_visible_column (sheet);
1665 }
1666
1667
1668 static gboolean
1669 psppire_sheet_set_scroll_adjustments (PsppireSheet *sheet,
1670                                       GtkAdjustment *hadjustment,
1671                                       GtkAdjustment *vadjustment)
1672 {
1673   if ( sheet->vadjustment != vadjustment )
1674     {
1675       if (sheet->vadjustment)
1676         g_object_unref (sheet->vadjustment);
1677       sheet->vadjustment = vadjustment;
1678
1679       if ( vadjustment)
1680         {
1681           g_object_ref (vadjustment);
1682
1683           g_signal_connect (sheet->vadjustment, "value_changed",
1684                             G_CALLBACK (vadjustment_value_changed),
1685                             sheet);
1686         }
1687     }
1688
1689   if ( sheet->hadjustment != hadjustment )
1690     {
1691       if (sheet->hadjustment)
1692         g_object_unref (sheet->hadjustment);
1693
1694       sheet->hadjustment = hadjustment;
1695
1696       if ( hadjustment)
1697         {
1698           g_object_ref (hadjustment);
1699
1700           g_signal_connect (sheet->hadjustment, "value_changed",
1701                             G_CALLBACK (hadjustment_value_changed),
1702                             sheet);
1703         }
1704     }
1705   return TRUE;
1706 }
1707
1708 static void
1709 psppire_sheet_finalize (GObject *object)
1710 {
1711   PsppireSheet *sheet;
1712
1713   g_return_if_fail (object != NULL);
1714   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1715
1716   sheet = PSPPIRE_SHEET (object);
1717
1718   if (G_OBJECT_CLASS (parent_class)->finalize)
1719     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
1720 }
1721
1722 static void
1723 psppire_sheet_dispose  (GObject *object)
1724 {
1725   PsppireSheet *sheet = PSPPIRE_SHEET (object);
1726
1727   g_return_if_fail (object != NULL);
1728   g_return_if_fail (PSPPIRE_IS_SHEET (object));
1729
1730   if ( sheet->dispose_has_run )
1731     return ;
1732
1733   sheet->dispose_has_run = TRUE;
1734
1735   if ( sheet->cell_padding)
1736     g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
1737
1738   if (sheet->model) g_object_unref (sheet->model);
1739   if (sheet->vaxis) g_object_unref (sheet->vaxis);
1740   if (sheet->haxis) g_object_unref (sheet->haxis);
1741
1742   g_object_unref (sheet->button);
1743   sheet->button = NULL;
1744
1745   /* unref adjustments */
1746   if (sheet->hadjustment)
1747     {
1748       g_signal_handlers_disconnect_matched (sheet->hadjustment,
1749                                             G_SIGNAL_MATCH_DATA,
1750                                             0, 0, 0, 0,
1751                                             sheet);
1752
1753       g_object_unref (sheet->hadjustment);
1754       sheet->hadjustment = NULL;
1755     }
1756
1757   if (sheet->vadjustment)
1758     {
1759       g_signal_handlers_disconnect_matched (sheet->vadjustment,
1760                                             G_SIGNAL_MATCH_DATA,
1761                                             0, 0, 0, 0,
1762                                             sheet);
1763
1764       g_object_unref (sheet->vadjustment);
1765
1766       sheet->vadjustment = NULL;
1767     }
1768
1769   if (G_OBJECT_CLASS (parent_class)->dispose)
1770     (*G_OBJECT_CLASS (parent_class)->dispose) (object);
1771 }
1772
1773 static void
1774 psppire_sheet_style_set (GtkWidget *widget,
1775                          GtkStyle *previous_style)
1776 {
1777   PsppireSheet *sheet;
1778
1779   g_return_if_fail (widget != NULL);
1780   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1781
1782   if (GTK_WIDGET_CLASS (parent_class)->style_set)
1783     (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1784
1785   sheet = PSPPIRE_SHEET (widget);
1786
1787   if (GTK_WIDGET_REALIZED (widget))
1788     {
1789       gtk_style_set_background (widget->style, widget->window, widget->state);
1790     }
1791
1792   set_entry_widget_font (sheet);
1793 }
1794
1795
1796 static void
1797 psppire_sheet_realize (GtkWidget *widget)
1798 {
1799   PsppireSheet *sheet;
1800   GdkWindowAttr attributes;
1801   const gint attributes_mask =
1802     GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;
1803
1804   GdkGCValues values;
1805   GdkColormap *colormap;
1806   GdkDisplay *display;
1807
1808   g_return_if_fail (widget != NULL);
1809   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1810
1811   sheet = PSPPIRE_SHEET (widget);
1812
1813   colormap = gtk_widget_get_colormap (widget);
1814   display = gtk_widget_get_display (widget);
1815
1816   attributes.window_type = GDK_WINDOW_CHILD;
1817   attributes.x = widget->allocation.x;
1818   attributes.y = widget->allocation.y;
1819   attributes.width = widget->allocation.width;
1820   attributes.height = widget->allocation.height;
1821   attributes.wclass = GDK_INPUT_OUTPUT;
1822
1823   attributes.visual = gtk_widget_get_visual (widget);
1824   attributes.colormap = colormap;
1825
1826   attributes.event_mask = gtk_widget_get_events (widget);
1827   attributes.event_mask |= (GDK_EXPOSURE_MASK |
1828                             GDK_BUTTON_PRESS_MASK |
1829                             GDK_BUTTON_RELEASE_MASK |
1830                             GDK_KEY_PRESS_MASK |
1831                             GDK_ENTER_NOTIFY_MASK |
1832                             GDK_LEAVE_NOTIFY_MASK |
1833                             GDK_POINTER_MOTION_MASK |
1834                             GDK_POINTER_MOTION_HINT_MASK);
1835
1836   attributes.cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
1837
1838   /* main window */
1839   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1840
1841   gdk_window_set_user_data (widget->window, sheet);
1842
1843   widget->style = gtk_style_attach (widget->style, widget->window);
1844
1845   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1846
1847   gdk_color_parse ("white", &sheet->color[BG_COLOR]);
1848   gdk_colormap_alloc_color (colormap, &sheet->color[BG_COLOR], FALSE,
1849                             TRUE);
1850   gdk_color_parse ("gray", &sheet->color[GRID_COLOR]);
1851   gdk_colormap_alloc_color (colormap, &sheet->color[GRID_COLOR], FALSE,
1852                             TRUE);
1853
1854   attributes.x = 0;
1855   attributes.y = 0;
1856   attributes.width = sheet->column_title_area.width;
1857   attributes.height = sheet->column_title_area.height;
1858
1859
1860   /* column - title window */
1861   sheet->column_title_window =
1862     gdk_window_new (widget->window, &attributes, attributes_mask);
1863   gdk_window_set_user_data (sheet->column_title_window, sheet);
1864   gtk_style_set_background (widget->style, sheet->column_title_window,
1865                             GTK_STATE_NORMAL);
1866
1867
1868   attributes.x = 0;
1869   attributes.y = 0;
1870   attributes.width = sheet->row_title_area.width;
1871   attributes.height = sheet->row_title_area.height;
1872
1873   /* row - title window */
1874   sheet->row_title_window = gdk_window_new (widget->window,
1875                                             &attributes, attributes_mask);
1876   gdk_window_set_user_data (sheet->row_title_window, sheet);
1877   gtk_style_set_background (widget->style, sheet->row_title_window,
1878                             GTK_STATE_NORMAL);
1879
1880   /* sheet - window */
1881   attributes.cursor = gdk_cursor_new_for_display (display, GDK_PLUS);
1882
1883   attributes.x = 0;
1884   attributes.y = 0;
1885
1886   sheet->sheet_window = gdk_window_new (widget->window,
1887                                         &attributes, attributes_mask);
1888   gdk_window_set_user_data (sheet->sheet_window, sheet);
1889
1890   gdk_cursor_unref (attributes.cursor);
1891
1892   gdk_window_set_background (sheet->sheet_window, &widget->style->white);
1893   gdk_window_show (sheet->sheet_window);
1894
1895   /* GCs */
1896   sheet->fg_gc = gdk_gc_new (widget->window);
1897   sheet->bg_gc = gdk_gc_new (widget->window);
1898
1899   values.foreground = widget->style->white;
1900   values.function = GDK_INVERT;
1901   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
1902   values.line_width = MAX (sheet->cell_padding->left,
1903                            MAX (sheet->cell_padding->right,
1904                                 MAX (sheet->cell_padding->top,
1905                                      sheet->cell_padding->bottom)));
1906
1907   sheet->xor_gc = gdk_gc_new_with_values (widget->window,
1908                                           &values,
1909                                           GDK_GC_FOREGROUND |
1910                                           GDK_GC_FUNCTION |
1911                                           GDK_GC_SUBWINDOW |
1912                                           GDK_GC_LINE_WIDTH
1913                                           );
1914
1915   gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
1916   gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
1917
1918   gtk_widget_set_parent_window (sheet->button, sheet->sheet_window);
1919   gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet));
1920
1921   sheet->button->style = gtk_style_attach (sheet->button->style,
1922                                            sheet->sheet_window);
1923
1924
1925   sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
1926
1927   if (sheet->column_titles_visible)
1928     gdk_window_show (sheet->column_title_window);
1929   if (sheet->row_titles_visible)
1930     gdk_window_show (sheet->row_title_window);
1931
1932   sheet->hover_window = create_hover_window ();
1933
1934   draw_row_title_buttons (sheet);
1935   draw_column_title_buttons (sheet);
1936
1937   psppire_sheet_update_primary_selection (sheet);
1938
1939
1940   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1941 }
1942
1943 static void
1944 create_global_button (PsppireSheet *sheet)
1945 {
1946   sheet->button = gtk_button_new_with_label (" ");
1947
1948   GTK_WIDGET_UNSET_FLAGS(sheet->button, GTK_CAN_FOCUS);
1949
1950   g_object_ref_sink (sheet->button);
1951
1952   g_signal_connect (sheet->button,
1953                     "pressed",
1954                     G_CALLBACK (global_button_clicked),
1955                     sheet);
1956 }
1957
1958 static void
1959 size_allocate_global_button (PsppireSheet *sheet)
1960 {
1961   GtkAllocation allocation;
1962
1963   if (!sheet->column_titles_visible) return;
1964   if (!sheet->row_titles_visible) return;
1965
1966   gtk_widget_size_request (sheet->button, NULL);
1967
1968   allocation.x = 0;
1969   allocation.y = 0;
1970   allocation.width = sheet->row_title_area.width;
1971   allocation.height = sheet->column_title_area.height;
1972
1973   gtk_widget_size_allocate (sheet->button, &allocation);
1974 }
1975
1976 static void
1977 global_button_clicked (GtkWidget *widget, gpointer data)
1978 {
1979   psppire_sheet_click_cell (PSPPIRE_SHEET (data), -1, -1);
1980 }
1981
1982
1983 static void
1984 psppire_sheet_unrealize (GtkWidget *widget)
1985 {
1986   PsppireSheet *sheet;
1987
1988   g_return_if_fail (widget != NULL);
1989   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
1990
1991   sheet = PSPPIRE_SHEET (widget);
1992
1993   gdk_cursor_unref (sheet->cursor_drag);
1994   sheet->cursor_drag = NULL;
1995
1996   gdk_colormap_free_colors (gtk_widget_get_colormap (widget),
1997                             sheet->color, n_COLORS);
1998
1999   g_object_unref (sheet->xor_gc);
2000   g_object_unref (sheet->fg_gc);
2001   g_object_unref (sheet->bg_gc);
2002
2003   destroy_hover_window (sheet->hover_window);
2004
2005   gdk_window_destroy (sheet->sheet_window);
2006   gdk_window_destroy (sheet->column_title_window);
2007   gdk_window_destroy (sheet->row_title_window);
2008
2009   gtk_widget_unparent (sheet->entry_widget);
2010   if (sheet->button != NULL)
2011     gtk_widget_unparent (sheet->button);
2012
2013   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
2014     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
2015 }
2016
2017 static void
2018 psppire_sheet_map (GtkWidget *widget)
2019 {
2020   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2021
2022   g_return_if_fail (widget != NULL);
2023   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
2024
2025   if (!GTK_WIDGET_MAPPED (widget))
2026     {
2027       GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
2028
2029       gdk_window_show (widget->window);
2030       gdk_window_show (sheet->sheet_window);
2031
2032       if (sheet->column_titles_visible)
2033         {
2034           draw_column_title_buttons (sheet);
2035           gdk_window_show (sheet->column_title_window);
2036         }
2037       if (sheet->row_titles_visible)
2038         {
2039           draw_row_title_buttons (sheet);
2040           gdk_window_show (sheet->row_title_window);
2041         }
2042
2043       if (!GTK_WIDGET_MAPPED (sheet->entry_widget)
2044           && sheet->active_cell.row >= 0
2045           && sheet->active_cell.col >= 0 )
2046         {
2047           gtk_widget_show (sheet->entry_widget);
2048           gtk_widget_map (sheet->entry_widget);
2049         }
2050
2051       if (!GTK_WIDGET_MAPPED (sheet->button))
2052         {
2053           gtk_widget_show (sheet->button);
2054           gtk_widget_map (sheet->button);
2055         }
2056
2057       redraw_range (sheet, NULL);
2058       change_active_cell (sheet,
2059                           sheet->active_cell.row,
2060                           sheet->active_cell.col);
2061     }
2062 }
2063
2064 static void
2065 psppire_sheet_unmap (GtkWidget *widget)
2066 {
2067   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2068
2069   if (!GTK_WIDGET_MAPPED (widget))
2070     return;
2071
2072   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
2073
2074   gdk_window_hide (sheet->sheet_window);
2075   if (sheet->column_titles_visible)
2076     gdk_window_hide (sheet->column_title_window);
2077   if (sheet->row_titles_visible)
2078     gdk_window_hide (sheet->row_title_window);
2079   gdk_window_hide (widget->window);
2080
2081   gtk_widget_unmap (sheet->entry_widget);
2082   gtk_widget_unmap (sheet->button);
2083   gtk_widget_unmap (sheet->hover_window->window);
2084 }
2085
2086 /* get cell attributes of the given cell */
2087 /* TRUE means that the cell is currently allocated */
2088 static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
2089                                               gint row, gint col,
2090                                               PsppireSheetCellAttr *attributes);
2091
2092
2093
2094 static void
2095 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
2096 {
2097   PangoLayout *layout;
2098   PangoRectangle text;
2099   PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc;
2100   gint font_height;
2101
2102   gchar *label;
2103
2104   PsppireSheetCellAttr attributes;
2105   GdkRectangle area;
2106
2107   g_return_if_fail (sheet != NULL);
2108
2109   /* bail now if we aren't yet drawable */
2110   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
2111
2112   if (row < 0 ||
2113       row >= psppire_axis_unit_count (sheet->vaxis))
2114     return;
2115
2116   if (col < 0 ||
2117       col >= psppire_axis_unit_count (sheet->haxis))
2118     return;
2119
2120   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2121
2122   /* select GC for background rectangle */
2123   gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
2124   gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
2125
2126   rectangle_from_cell (sheet, row, col, &area);
2127
2128   gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
2129
2130   if (sheet->show_grid)
2131     {
2132       gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]);
2133
2134       gdk_draw_rectangle (sheet->sheet_window,
2135                           sheet->bg_gc,
2136                           FALSE,
2137                           area.x, area.y,
2138                           area.width, area.height);
2139     }
2140
2141
2142   label = psppire_sheet_cell_get_text (sheet, row, col);
2143   if (NULL == label)
2144     return;
2145
2146
2147   layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label);
2148   dispose_string (sheet, label);
2149
2150
2151   pango_layout_set_font_description (layout, font_desc);
2152
2153   pango_layout_get_pixel_extents (layout, NULL, &text);
2154
2155   gdk_gc_set_clip_rectangle (sheet->fg_gc, &area);
2156
2157   font_height = pango_font_description_get_size (font_desc);
2158   if ( !pango_font_description_get_size_is_absolute (font_desc))
2159     font_height /= PANGO_SCALE;
2160
2161
2162   if ( sheet->cell_padding )
2163     {
2164       area.x += sheet->cell_padding->left;
2165       area.width -= sheet->cell_padding->right
2166         + sheet->cell_padding->left;
2167
2168       area.y += sheet->cell_padding->top;
2169       area.height -= sheet->cell_padding->bottom
2170         +
2171         sheet->cell_padding->top;
2172     }
2173
2174   /* Centre the text vertically */
2175   area.y += (area.height - font_height) / 2.0;
2176
2177   switch (attributes.justification)
2178     {
2179     case GTK_JUSTIFY_RIGHT:
2180       area.x += area.width - text.width;
2181       break;
2182     case GTK_JUSTIFY_CENTER:
2183       area.x += (area.width - text.width) / 2.0;
2184       break;
2185     case GTK_JUSTIFY_LEFT:
2186       /* Do nothing */
2187       break;
2188     default:
2189       g_critical ("Unhandled justification %d in column %d\n",
2190                   attributes.justification, col);
2191       break;
2192     }
2193
2194   gdk_draw_layout (sheet->sheet_window, sheet->fg_gc,
2195                    area.x,
2196                    area.y,
2197                    layout);
2198
2199   gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL);
2200   g_object_unref (layout);
2201 }
2202
2203
2204 static void
2205 draw_sheet_region (PsppireSheet *sheet, GdkRegion *region)
2206 {
2207   PsppireSheetRange range;
2208   GdkRectangle area;
2209   gint y, x;
2210   gint i, j;
2211
2212   PsppireSheetRange drawing_range;
2213
2214   gdk_region_get_clipbox (region, &area);
2215
2216   y = area.y + sheet->vadjustment->value;
2217   x = area.x + sheet->hadjustment->value;
2218
2219   if ( sheet->column_titles_visible)
2220     y -= sheet->column_title_area.height;
2221
2222   if ( sheet->row_titles_visible)
2223     x -= sheet->row_title_area.width;
2224
2225   maximize_int (&x, 0);
2226   maximize_int (&y, 0);
2227
2228   range.row0 = row_from_ypixel (sheet, y);
2229   range.rowi = row_from_ypixel (sheet, y + area.height);
2230
2231   range.col0 = column_from_xpixel (sheet, x);
2232   range.coli = column_from_xpixel (sheet, x + area.width);
2233
2234   g_return_if_fail (sheet != NULL);
2235   g_return_if_fail (PSPPIRE_SHEET (sheet));
2236
2237   if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return;
2238   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2239   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
2240
2241
2242   drawing_range.row0 = MAX (range.row0, min_visible_row (sheet));
2243   drawing_range.col0 = MAX (range.col0, min_visible_column (sheet));
2244   drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet));
2245   drawing_range.coli = MIN (range.coli, max_visible_column (sheet));
2246
2247   g_return_if_fail (drawing_range.rowi >= drawing_range.row0);
2248   g_return_if_fail (drawing_range.coli >= drawing_range.col0);
2249
2250   for (i = drawing_range.row0; i <= drawing_range.rowi; i++)
2251     {
2252       for (j = drawing_range.col0; j <= drawing_range.coli; j++)
2253         psppire_sheet_cell_draw (sheet, i, j);
2254     }
2255
2256   if (sheet->select_status == PSPPIRE_SHEET_NORMAL &&
2257       sheet->active_cell.row >= drawing_range.row0 &&
2258       sheet->active_cell.row <= drawing_range.rowi &&
2259       sheet->active_cell.col >= drawing_range.col0 &&
2260       sheet->active_cell.col <= drawing_range.coli)
2261     psppire_sheet_show_entry_widget (sheet);
2262 }
2263
2264
2265
2266 static inline gint
2267 safe_strcmp (const gchar *s1, const gchar *s2)
2268 {
2269   if ( !s1 && !s2) return 0;
2270   if ( !s1) return -1;
2271   if ( !s2) return +1;
2272   return strcmp (s1, s2);
2273 }
2274
2275 static void
2276 psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col,
2277                         GtkJustification justification,
2278                         const gchar *text)
2279 {
2280   PsppireSheetModel *model ;
2281   gchar *old_text ;
2282
2283   g_return_if_fail (sheet != NULL);
2284   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2285
2286   if (col >= psppire_axis_unit_count (sheet->haxis)
2287       || row >= psppire_axis_unit_count (sheet->vaxis))
2288     return;
2289
2290   if (col < 0 || row < 0) return;
2291
2292   model = psppire_sheet_get_model (sheet);
2293
2294   old_text = psppire_sheet_model_get_string (model, row, col);
2295
2296   if (0 != safe_strcmp (old_text, text))
2297     {
2298       g_signal_handler_block    (sheet->model, sheet->update_handler_id);
2299       psppire_sheet_model_set_string (model, text, row, col);
2300       g_signal_handler_unblock  (sheet->model, sheet->update_handler_id);
2301     }
2302
2303   if ( psppire_sheet_model_free_strings (model))
2304     g_free (old_text);
2305 }
2306
2307
2308 void
2309 psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column)
2310 {
2311   PsppireSheetRange range;
2312
2313   g_return_if_fail (sheet != NULL);
2314   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2315   if (column >= psppire_axis_unit_count (sheet->haxis) ||
2316       row >= psppire_axis_unit_count (sheet->vaxis)) return;
2317
2318   if (column < 0 || row < 0) return;
2319
2320   range.row0 = row;
2321   range.rowi = row;
2322   range.col0 = min_visible_column (sheet);
2323   range.coli = max_visible_column (sheet);
2324
2325   psppire_sheet_real_cell_clear (sheet, row, column);
2326
2327   redraw_range (sheet, &range);
2328 }
2329
2330 static void
2331 psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column)
2332 {
2333   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
2334
2335   gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column);
2336
2337   if (old_text && strlen (old_text) > 0 )
2338     {
2339       psppire_sheet_model_datum_clear (model, row, column);
2340     }
2341
2342   dispose_string (sheet, old_text);
2343 }
2344
2345 gchar *
2346 psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col)
2347 {
2348   PsppireSheetModel *model;
2349   g_return_val_if_fail (sheet != NULL, NULL);
2350   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
2351
2352   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis))
2353     return NULL;
2354   if (col < 0 || row < 0) return NULL;
2355
2356   model = psppire_sheet_get_model (sheet);
2357
2358   if ( !model )
2359     return NULL;
2360
2361   return psppire_sheet_model_get_string (model, row, col);
2362 }
2363
2364
2365 /* Convert X, Y (in pixels) to *ROW, *COLUMN
2366    If the function returns FALSE, then the results will be unreliable.
2367 */
2368 static gboolean
2369 psppire_sheet_get_pixel_info (PsppireSheet *sheet,
2370                               gint x,
2371                               gint y,
2372                               gint *row,
2373                               gint *column)
2374 {
2375   gint trow, tcol;
2376   *row = -G_MAXINT;
2377   *column = -G_MAXINT;
2378
2379   g_return_val_if_fail (sheet != NULL, 0);
2380   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2381
2382   /* bounds checking, return false if the user clicked
2383      on a blank area */
2384   if (y < 0)
2385     return FALSE;
2386
2387   if (x < 0)
2388     return FALSE;
2389
2390   if ( sheet->column_titles_visible)
2391     y -= sheet->column_title_area.height;
2392
2393   y += sheet->vadjustment->value;
2394
2395   if ( y < 0 && sheet->column_titles_visible)
2396     {
2397       trow = -1;
2398     }
2399   else
2400     {
2401       trow = row_from_ypixel (sheet, y);
2402       if (trow > psppire_axis_unit_count (sheet->vaxis))
2403         return FALSE;
2404     }
2405
2406   *row = trow;
2407
2408   if ( sheet->row_titles_visible)
2409     x -= sheet->row_title_area.width;
2410
2411   x += sheet->hadjustment->value;
2412
2413   if ( x < 0 && sheet->row_titles_visible)
2414     {
2415       tcol = -1;
2416     }
2417   else
2418     {
2419       tcol = column_from_xpixel (sheet, x);
2420       if (tcol > psppire_axis_unit_count (sheet->haxis))
2421         return FALSE;
2422     }
2423
2424   *column = tcol;
2425
2426   return TRUE;
2427 }
2428
2429 gboolean
2430 psppire_sheet_get_cell_area (PsppireSheet *sheet,
2431                              gint row,
2432                              gint column,
2433                              GdkRectangle *area)
2434 {
2435   g_return_val_if_fail (sheet != NULL, 0);
2436   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2437
2438   if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis))
2439     return FALSE;
2440
2441   area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column);
2442   area->y = (row == -1)    ? 0 : psppire_axis_start_pixel (sheet->vaxis, row);
2443
2444   area->width= (column == -1) ? sheet->row_title_area.width
2445     : psppire_axis_unit_size (sheet->haxis, column);
2446
2447   area->height= (row == -1) ? sheet->column_title_area.height
2448     : psppire_axis_unit_size (sheet->vaxis, row);
2449
2450   return TRUE;
2451 }
2452
2453 void
2454 psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col)
2455 {
2456   g_return_if_fail (sheet != NULL);
2457   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2458
2459   if (row < -1 || col < -1)
2460     return;
2461
2462   if (row >= psppire_axis_unit_count (sheet->vaxis)
2463       ||
2464       col >= psppire_axis_unit_count (sheet->haxis))
2465     return;
2466
2467   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2468     return;
2469
2470   if ( row == -1 || col == -1)
2471     {
2472       psppire_sheet_hide_entry_widget (sheet);
2473       return;
2474     }
2475
2476   change_active_cell (sheet, row, col);
2477 }
2478
2479 void
2480 psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column)
2481 {
2482   g_return_if_fail (sheet != NULL);
2483   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2484
2485   if ( row ) *row = sheet->active_cell.row;
2486   if (column) *column = sheet->active_cell.col;
2487 }
2488
2489 static void
2490 entry_load_text (PsppireSheet *sheet)
2491 {
2492   gint row, col;
2493   const char *text;
2494   GtkJustification justification;
2495   PsppireSheetCellAttr attributes;
2496
2497   if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return;
2498   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2499
2500   row = sheet->active_cell.row;
2501   col = sheet->active_cell.col;
2502
2503   if (row < 0 || col < 0) return;
2504
2505   text = gtk_entry_get_text (psppire_sheet_get_entry (sheet));
2506
2507   if (text && strlen (text) > 0)
2508     {
2509       psppire_sheet_get_attributes (sheet, row, col, &attributes);
2510       justification = attributes.justification;
2511       psppire_sheet_set_cell (sheet, row, col, justification, text);
2512     }
2513 }
2514
2515
2516 static void
2517 psppire_sheet_hide_entry_widget (PsppireSheet *sheet)
2518 {
2519   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2520     return;
2521
2522   if (sheet->active_cell.row < 0 ||
2523       sheet->active_cell.col < 0) return;
2524
2525   gtk_widget_hide (sheet->entry_widget);
2526   gtk_widget_unmap (sheet->entry_widget);
2527
2528   GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2529 }
2530
2531 static void
2532 change_active_cell (PsppireSheet *sheet, gint row, gint col)
2533 {
2534   gint old_row, old_col;
2535
2536   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2537
2538   if (row < 0 || col < 0)
2539     return;
2540
2541   if ( row > psppire_axis_unit_count (sheet->vaxis)
2542        || col > psppire_axis_unit_count (sheet->haxis))
2543     return;
2544
2545   old_row = sheet->active_cell.row;
2546   old_col = sheet->active_cell.col;
2547
2548   entry_load_text (sheet);
2549
2550   /* Erase the old cell border */
2551   psppire_sheet_draw_active_cell (sheet);
2552
2553   sheet->active_cell.row = row;
2554   sheet->active_cell.col = col;
2555
2556   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2557
2558   GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2559
2560   psppire_sheet_draw_active_cell (sheet);
2561   psppire_sheet_show_entry_widget (sheet);
2562
2563   GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2564
2565   g_signal_emit (sheet, sheet_signals [ACTIVATE], 0,
2566                  row, col, old_row, old_col);
2567
2568 }
2569
2570 static void
2571 psppire_sheet_show_entry_widget (PsppireSheet *sheet)
2572 {
2573   GtkEntry *sheet_entry;
2574   PsppireSheetCellAttr attributes;
2575
2576   gint row, col;
2577
2578   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2579
2580   row = sheet->active_cell.row;
2581   col = sheet->active_cell.col;
2582
2583   /* Don't show the active cell, if there is no active cell: */
2584   if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */
2585     return;
2586
2587   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2588   if (sheet->select_status != PSPPIRE_SHEET_NORMAL) return;
2589   if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return;
2590
2591   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2592
2593   sheet_entry = psppire_sheet_get_entry (sheet);
2594
2595   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2596
2597   if (GTK_IS_ENTRY (sheet_entry))
2598     {
2599       gchar *text = psppire_sheet_cell_get_text (sheet, row, col);
2600       const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry));
2601
2602       if ( ! text )
2603         text = g_strdup ("");
2604
2605       if (strcmp (old_text, text) != 0)
2606         gtk_entry_set_text (sheet_entry, text);
2607       
2608       dispose_string (sheet, text);
2609
2610       {
2611         switch (attributes.justification)
2612           {
2613           case GTK_JUSTIFY_RIGHT:
2614             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0);
2615             break;
2616           case GTK_JUSTIFY_CENTER:
2617             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5);
2618             break;
2619           case GTK_JUSTIFY_LEFT:
2620           default:
2621             gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0);
2622             break;
2623           }
2624       }
2625     }
2626
2627   psppire_sheet_size_allocate_entry (sheet);
2628
2629   gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry),
2630                             psppire_sheet_model_is_editable (sheet->model,
2631                                                              row, col));
2632   gtk_widget_map (sheet->entry_widget);
2633 }
2634
2635 static gboolean
2636 psppire_sheet_draw_active_cell (PsppireSheet *sheet)
2637 {
2638   gint row, col;
2639   PsppireSheetRange range;
2640
2641   row = sheet->active_cell.row;
2642   col = sheet->active_cell.col;
2643
2644   if (row < 0 || col < 0) return FALSE;
2645
2646   if (!psppire_sheet_cell_isvisible (sheet, row, col))
2647     return FALSE;
2648
2649   range.col0 = range.coli = col;
2650   range.row0 = range.rowi = row;
2651
2652   psppire_sheet_draw_border (sheet, range);
2653
2654   return FALSE;
2655 }
2656
2657
2658
2659 static void
2660 psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
2661 {
2662   GdkRectangle area;
2663
2664   rectangle_from_range (sheet, &new_range, &area);
2665
2666   area.width ++;
2667   area.height ++;
2668
2669   gdk_gc_set_clip_rectangle (sheet->xor_gc, &area);
2670
2671   area.x += sheet->cell_padding->left / 2;
2672   area.y += sheet->cell_padding->top / 2;
2673   area.width -= (sheet->cell_padding->left + sheet->cell_padding->right ) / 2;
2674   area.height -= (sheet->cell_padding->top + sheet->cell_padding->bottom ) / 2;
2675
2676   gdk_draw_rectangle (sheet->sheet_window,  sheet->xor_gc,
2677                       FALSE,
2678                       area.x,
2679                       area.y,
2680                       area.width,
2681                       area.height);
2682
2683   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
2684 }
2685
2686 \f
2687
2688 /* Selection related functions */
2689
2690 void
2691 psppire_sheet_select_row (PsppireSheet *sheet,  gint row)
2692 {
2693   GdkRectangle area;
2694   sheet->select_status = PSPPIRE_SHEET_ROW_SELECTED;
2695
2696   sheet->range.col0 = sheet->range.coli = -1;
2697   sheet->range.row0 = sheet->range.rowi = row;
2698
2699   rectangle_from_range (sheet, &sheet->range, &area);
2700   area.x++;
2701   area.y++;
2702
2703   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2704 }
2705
2706 void
2707 psppire_sheet_select_column (PsppireSheet *sheet,  gint column)
2708 {
2709   GdkRectangle area;
2710   sheet->select_status = PSPPIRE_SHEET_COLUMN_SELECTED;
2711
2712   sheet->range.col0 = sheet->range.coli = column;
2713   sheet->range.row0 = sheet->range.rowi = -1;
2714
2715   rectangle_from_range (sheet, &sheet->range, &area);
2716   area.x++;
2717   area.y++;
2718
2719   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2720 }
2721
2722
2723 void
2724 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
2725 {
2726   GdkRectangle area;
2727   sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
2728
2729   sheet->range = *range;
2730
2731   rectangle_from_range (sheet, range, &area);
2732   area.x++;
2733   area.y++;
2734   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);
2735 }
2736
2737
2738 void
2739 psppire_sheet_unselect_range (PsppireSheet *sheet)
2740 {
2741   GdkRectangle area;
2742   sheet->select_status = PSPPIRE_SHEET_NORMAL;
2743
2744   rectangle_from_range (sheet, &sheet->range, &area);
2745   area.x++;
2746   area.y++;
2747   gdk_window_invalidate_rect (sheet->sheet_window, &area, FALSE);         
2748 }
2749
2750 static void
2751 psppire_sheet_real_select_range (PsppireSheet *sheet,
2752                                  const PsppireSheetRange *range)
2753 {
2754   g_return_if_fail (sheet != NULL);
2755
2756   if (range == NULL) range = &sheet->range;
2757
2758   memcpy (&sheet->range, range, sizeof (*range));
2759
2760   if (range->row0 < 0 || range->rowi < 0) return;
2761   if (range->col0 < 0 || range->coli < 0) return;
2762
2763   psppire_sheet_update_primary_selection (sheet);
2764
2765   g_signal_emit (sheet, sheet_signals[SELECT_RANGE], 0, &sheet->range);
2766 }
2767
2768
2769 void
2770 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
2771 {
2772   g_return_if_fail (sheet != NULL);
2773   *range = sheet->range;
2774 }
2775 \f
2776
2777 static gint
2778 psppire_sheet_expose (GtkWidget *widget, GdkEventExpose *event)
2779 {
2780   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
2781
2782   g_return_val_if_fail (event != NULL, FALSE);
2783
2784   if (!GTK_WIDGET_DRAWABLE (widget))
2785     return FALSE;
2786
2787   /* exposure events on the sheet */
2788   if (event->window == sheet->row_title_window &&
2789       sheet->row_titles_visible)
2790     {
2791       draw_row_title_buttons_range (sheet,
2792                                     min_visible_row (sheet),
2793                                     max_visible_row (sheet));
2794     }
2795
2796   if (event->window == sheet->column_title_window &&
2797       sheet->column_titles_visible)
2798     {
2799       draw_column_title_buttons_range (sheet,
2800                                        min_visible_column (sheet),
2801                                        max_visible_column (sheet));
2802     }
2803
2804   if (event->window == sheet->sheet_window)
2805     {
2806       draw_sheet_region (sheet, event->region);
2807
2808       if (sheet->select_status != PSPPIRE_SHEET_NORMAL)
2809         {
2810 #if 0
2811           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
2812             psppire_sheet_range_draw (sheet, &sheet->range);
2813
2814           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2815             psppire_sheet_range_draw (sheet, &sheet->drag_range);
2816 #endif
2817
2818             {
2819               GdkRectangle area;
2820
2821               rectangle_from_range (sheet, &sheet->range, &area);
2822               
2823               gdk_draw_rectangle (sheet->sheet_window,
2824                                   sheet->xor_gc,
2825                                   TRUE,
2826                                   area.x + 1, area.y + 1,
2827                                   area.width, area.height);
2828             }
2829
2830 #if 0
2831           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
2832             draw_xor_rectangle (sheet, sheet->drag_range);
2833 #endif
2834         }
2835
2836
2837       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
2838         {
2839           GdkRectangle rect;
2840           PsppireSheetRange range;
2841           range.row0 = range.rowi =  sheet->active_cell.row;
2842           range.col0 = range.coli =  sheet->active_cell.col;
2843
2844           rectangle_from_range (sheet, &range, &rect);
2845
2846           if (GDK_OVERLAP_RECTANGLE_OUT !=
2847               gdk_region_rect_in (event->region, &rect))
2848             {
2849               psppire_sheet_draw_active_cell (sheet);
2850             }
2851         }
2852
2853     }
2854
2855   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
2856
2857   return FALSE;
2858 }
2859
2860
2861 static gboolean
2862 psppire_sheet_button_press (GtkWidget *widget, GdkEventButton *event)
2863 {
2864   PsppireSheet *sheet;
2865   GdkModifierType mods;
2866   gint x, y;
2867   gint  row, column;
2868
2869
2870   g_return_val_if_fail (widget != NULL, FALSE);
2871   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
2872   g_return_val_if_fail (event != NULL, FALSE);
2873
2874   sheet = PSPPIRE_SHEET (widget);
2875
2876   /* Cancel any pending tooltips */
2877   if (sheet->motion_timer)
2878     {
2879       g_source_remove (sheet->motion_timer);
2880       sheet->motion_timer = 0;
2881     }
2882
2883   gtk_widget_get_pointer (widget, &x, &y);
2884   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2885
2886
2887   if (event->window == sheet->column_title_window)
2888     {
2889       sheet->x_drag = event->x;
2890       g_signal_emit (sheet,
2891                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
2892                      column, event);
2893
2894       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2895         {
2896           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2897             g_signal_emit (sheet,
2898                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
2899         }
2900     }
2901   else if (event->window == sheet->row_title_window)
2902     {
2903       g_signal_emit (sheet,
2904                      sheet_signals[BUTTON_EVENT_ROW], 0,
2905                      row, event);
2906
2907       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
2908         {
2909           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
2910             g_signal_emit (sheet,
2911                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
2912         }
2913     }
2914
2915   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
2916
2917   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
2918
2919
2920   /* press on resize windows */
2921   if (event->window == sheet->column_title_window)
2922     {
2923       sheet->x_drag = event->x;
2924
2925       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
2926         {
2927           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
2928           gdk_pointer_grab (sheet->column_title_window, FALSE,
2929                             GDK_POINTER_MOTION_HINT_MASK |
2930                             GDK_BUTTON1_MOTION_MASK |
2931                             GDK_BUTTON_RELEASE_MASK,
2932                             NULL, NULL, event->time);
2933
2934           draw_xor_vline (sheet);
2935           return TRUE;
2936         }
2937     }
2938
2939   if (event->window == sheet->row_title_window)
2940     {
2941       sheet->y_drag = event->y;
2942
2943       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
2944         {
2945           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
2946           gdk_pointer_grab (sheet->row_title_window, FALSE,
2947                             GDK_POINTER_MOTION_HINT_MASK |
2948                             GDK_BUTTON1_MOTION_MASK |
2949                             GDK_BUTTON_RELEASE_MASK,
2950                             NULL, NULL, event->time);
2951
2952           draw_xor_hline (sheet);
2953           return TRUE;
2954         }
2955     }
2956
2957   /* the sheet itself does not handle other than single click events */
2958   if (event->type != GDK_BUTTON_PRESS) return FALSE;
2959
2960   /* selections on the sheet */
2961   if (event->window == sheet->sheet_window)
2962     {
2963       gtk_widget_get_pointer (widget, &x, &y);
2964       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
2965       gdk_pointer_grab (sheet->sheet_window, FALSE,
2966                         GDK_POINTER_MOTION_HINT_MASK |
2967                         GDK_BUTTON1_MOTION_MASK |
2968                         GDK_BUTTON_RELEASE_MASK,
2969                         NULL, NULL, event->time);
2970       gtk_grab_add (GTK_WIDGET (sheet));
2971
2972       if (psppire_sheet_click_cell (sheet, row, column))
2973         {
2974           if ( sheet->select_status == PSPPIRE_SHEET_NORMAL)
2975             {
2976               sheet->range.row0 = row;
2977               sheet->range.col0 = column;
2978             }
2979           else
2980             {
2981               psppire_sheet_unselect_range (sheet);
2982             }
2983         }
2984     }
2985
2986   if (event->window == sheet->column_title_window)
2987     {
2988       gtk_widget_get_pointer (widget, &x, &y);
2989       if ( sheet->row_titles_visible)
2990         x -= sheet->row_title_area.width;
2991
2992       x += sheet->hadjustment->value;
2993
2994       column = column_from_xpixel (sheet, x);
2995
2996       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
2997         {
2998           gtk_grab_add (GTK_WIDGET (sheet));
2999           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3000         }
3001     }
3002
3003   if (event->window == sheet->row_title_window)
3004     {
3005       gtk_widget_get_pointer (widget, &x, &y);
3006       if ( sheet->column_titles_visible)
3007         y -= sheet->column_title_area.height;
3008
3009       y += sheet->vadjustment->value;
3010
3011       row = row_from_ypixel (sheet, y);
3012       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3013         {
3014           gtk_grab_add (GTK_WIDGET (sheet));
3015           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3016         }
3017     }
3018
3019   return TRUE;
3020 }
3021
3022 static gboolean
3023 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
3024 {
3025   PsppireSheetCell cell;
3026   gboolean forbid_move;
3027
3028   cell.row = row;
3029   cell.col = column;
3030
3031   if (row >= psppire_axis_unit_count (sheet->vaxis)
3032       || column >= psppire_axis_unit_count (sheet->haxis))
3033     {
3034       return FALSE;
3035     }
3036
3037   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3038                  &sheet->active_cell,
3039                  &cell,
3040                  &forbid_move);
3041
3042   if (forbid_move)
3043     {
3044       if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3045         return FALSE;
3046
3047       row = sheet->active_cell.row;
3048       column = sheet->active_cell.col;
3049
3050       change_active_cell (sheet, row, column);
3051       return FALSE;
3052     }
3053
3054   if (row == -1 && column >= 0)
3055     {
3056       psppire_sheet_select_column (sheet, column);
3057       return TRUE;
3058     }
3059
3060   if (column == -1 && row >= 0)
3061     {
3062       psppire_sheet_select_row (sheet, row);
3063       return TRUE;
3064     }
3065
3066   if (row == -1 && column == -1)
3067     {
3068       sheet->range.row0 = 0;
3069       sheet->range.col0 = 0;
3070       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3071       sheet->range.coli =
3072         psppire_axis_unit_count (sheet->haxis) - 1;
3073       psppire_sheet_select_range (sheet, NULL);
3074       return TRUE;
3075     }
3076
3077   if (sheet->select_status == PSPPIRE_SHEET_NORMAL)
3078     change_active_cell (sheet, row, column);
3079
3080   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3081
3082   return TRUE;
3083 }
3084
3085 static gint
3086 psppire_sheet_button_release (GtkWidget *widget,
3087                               GdkEventButton *event)
3088 {
3089   GdkDisplay *display = gtk_widget_get_display (widget);
3090
3091   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3092
3093   /* release on resize windows */
3094   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3095     {
3096       gint width;
3097       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3098       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3099
3100       gdk_display_pointer_ungrab (display, event->time);
3101       draw_xor_vline (sheet);
3102
3103       width = event->x -
3104         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3105         + sheet->hadjustment->value;
3106
3107       set_column_width (sheet, sheet->drag_cell.col, width);
3108
3109       return TRUE;
3110     }
3111
3112   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3113     {
3114       gint height;
3115       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3116       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3117
3118       gdk_display_pointer_ungrab (display, event->time);
3119       draw_xor_hline (sheet);
3120
3121       height = event->y -
3122         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3123         sheet->vadjustment->value;
3124
3125       set_row_height (sheet, sheet->drag_cell.row, height);
3126
3127       return TRUE;
3128     }
3129
3130   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3131     {
3132       PsppireSheetRange old_range;
3133       draw_xor_rectangle (sheet, sheet->drag_range);
3134       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3135       gdk_display_pointer_ungrab (display, event->time);
3136
3137       psppire_sheet_unselect_range (sheet);
3138
3139       old_range = sheet->range;
3140       sheet->range = sheet->drag_range;
3141       sheet->drag_range = old_range;
3142       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3143                      &sheet->drag_range, &sheet->range);
3144       psppire_sheet_select_range (sheet, &sheet->range);
3145     }
3146
3147   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3148     {
3149       PsppireSheetRange old_range;
3150       draw_xor_rectangle (sheet, sheet->drag_range);
3151       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3152       gdk_display_pointer_ungrab (display, event->time);
3153
3154       psppire_sheet_unselect_range (sheet);
3155
3156       old_range = sheet->range;
3157       sheet->range = sheet->drag_range;
3158       sheet->drag_range = old_range;
3159
3160       if (sheet->select_status == PSPPIRE_SHEET_NORMAL) 
3161         sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3162
3163       g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0,
3164                      &sheet->drag_range, &sheet->range);
3165       psppire_sheet_select_range (sheet, &sheet->range);
3166     }
3167
3168   if (PSPPIRE_SHEET_IN_SELECTION (sheet))
3169     {
3170       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3171       sheet->select_status = PSPPIRE_SHEET_RANGE_SELECTED;
3172
3173       change_active_cell (sheet, sheet->active_cell.row,
3174                           sheet->active_cell.col);
3175     }
3176
3177   gdk_display_pointer_ungrab (display, event->time);
3178   gtk_grab_remove (GTK_WIDGET (sheet));
3179
3180   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3181
3182   return TRUE;
3183 }
3184
3185 \f
3186
3187
3188
3189 /* Shamelessly lifted from gtktooltips */
3190 static gboolean
3191 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3192 {
3193   GtkRequisition req;
3194
3195   gtk_widget_size_request (tip_window, &req);
3196   gtk_paint_flat_box (tip_window->style, tip_window->window,
3197                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3198                       NULL, GTK_WIDGET(tip_window), "tooltip",
3199                       0, 0, req.width, req.height);
3200
3201   return FALSE;
3202 }
3203
3204 static void
3205 destroy_hover_window (PsppireSheetHoverTitle *h)
3206 {
3207   gtk_widget_destroy (h->window);
3208   g_free (h);
3209 }
3210
3211 static PsppireSheetHoverTitle *
3212 create_hover_window (void)
3213 {
3214   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3215
3216   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3217
3218 #if GTK_CHECK_VERSION (2, 9, 0)
3219   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3220                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3221 #endif
3222
3223   gtk_widget_set_app_paintable (hw->window, TRUE);
3224   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3225   gtk_widget_set_name (hw->window, "gtk-tooltips");
3226   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3227
3228   g_signal_connect (hw->window,
3229                     "expose_event",
3230                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3231                     NULL);
3232
3233   hw->label = gtk_label_new (NULL);
3234
3235
3236   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3237   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3238
3239   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3240
3241   gtk_widget_show (hw->label);
3242
3243   g_signal_connect (hw->window,
3244                     "destroy",
3245                     G_CALLBACK (gtk_widget_destroyed),
3246                     &hw->window);
3247
3248   return hw;
3249 }
3250
3251 #define HOVER_WINDOW_Y_OFFSET 2
3252
3253 static void
3254 show_subtitle (PsppireSheet *sheet, gint row, gint column,
3255                const gchar *subtitle)
3256 {
3257   gint x, y;
3258   gint px, py;
3259   gint width;
3260
3261   if ( ! subtitle )
3262     return;
3263
3264   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3265                       subtitle);
3266
3267
3268   sheet->hover_window->row = row;
3269   sheet->hover_window->column = column;
3270
3271   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3272
3273   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3274
3275   gtk_widget_show (sheet->hover_window->window);
3276
3277   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3278
3279   if (row == -1 )
3280     {
3281       x += px;
3282       x -= width / 2;
3283       y += sheet->column_title_area.y;
3284       y += sheet->column_title_area.height;
3285       y += HOVER_WINDOW_Y_OFFSET;
3286     }
3287
3288   if ( column == -1 )
3289     {
3290       y += py;
3291       x += sheet->row_title_area.x;
3292       x += sheet->row_title_area.width * 2 / 3.0;
3293     }
3294
3295   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3296                    x, y);
3297 }
3298
3299 static gboolean
3300 motion_timeout_callback (gpointer data)
3301 {
3302   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3303   gint x, y;
3304   gint row, column;
3305
3306   gdk_threads_enter ();
3307   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3308
3309   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3310     {
3311       if (sheet->row_title_under && row >= 0)
3312         {
3313           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3314
3315           show_subtitle (sheet, row, -1, text);
3316           g_free (text);
3317         }
3318
3319       if (sheet->column_title_under && column >= 0)
3320         {
3321           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3322                                                                  column);
3323
3324           show_subtitle (sheet, -1, column, text);
3325
3326           g_free (text);
3327         }
3328     }
3329
3330   gdk_threads_leave ();
3331   return FALSE;
3332 }
3333
3334 static gboolean
3335 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3336 {
3337   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3338   GdkModifierType mods;
3339   GdkCursorType new_cursor;
3340   gint x, y;
3341   gint row, column;
3342   GdkDisplay *display;
3343
3344   g_return_val_if_fail (event != NULL, FALSE);
3345
3346   display = gtk_widget_get_display (widget);
3347
3348   /* selections on the sheet */
3349   x = event->x;
3350   y = event->y;
3351
3352   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3353     {
3354       if ( sheet->motion_timer > 0 )
3355         g_source_remove (sheet->motion_timer);
3356       sheet->motion_timer =
3357         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3358     }
3359   else
3360     {
3361       gint row, column;
3362       gint wx, wy;
3363       gtk_widget_get_pointer (widget, &wx, &wy);
3364
3365       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3366         {
3367           if ( row != sheet->hover_window->row ||
3368                column != sheet->hover_window->column)
3369             {
3370               gtk_widget_hide (sheet->hover_window->window);
3371             }
3372         }
3373     }
3374
3375   if (event->window == sheet->column_title_window)
3376     {
3377       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3378           on_column_boundary (sheet, x, &column))
3379         {
3380           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3381           if (new_cursor != sheet->cursor_drag->type)
3382             {
3383               gdk_cursor_unref (sheet->cursor_drag);
3384               sheet->cursor_drag =
3385                 gdk_cursor_new_for_display (display, new_cursor);
3386
3387               gdk_window_set_cursor (sheet->column_title_window,
3388                                      sheet->cursor_drag);
3389             }
3390         }
3391       else
3392         {
3393           new_cursor = GDK_TOP_LEFT_ARROW;
3394           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3395               new_cursor != sheet->cursor_drag->type)
3396             {
3397               gdk_cursor_unref (sheet->cursor_drag);
3398               sheet->cursor_drag =
3399                 gdk_cursor_new_for_display (display, new_cursor);
3400               gdk_window_set_cursor (sheet->column_title_window,
3401                                      sheet->cursor_drag);
3402             }
3403         }
3404     }
3405   else if (event->window == sheet->row_title_window)
3406     {
3407       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3408           on_row_boundary (sheet, y, &row))
3409         {
3410           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3411           if (new_cursor != sheet->cursor_drag->type)
3412             {
3413               gdk_cursor_unref (sheet->cursor_drag);
3414               sheet->cursor_drag =
3415                 gdk_cursor_new_for_display (display, new_cursor);
3416               gdk_window_set_cursor (sheet->row_title_window,
3417                                      sheet->cursor_drag);
3418             }
3419         }
3420       else
3421         {
3422           new_cursor = GDK_TOP_LEFT_ARROW;
3423           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3424               new_cursor != sheet->cursor_drag->type)
3425             {
3426               gdk_cursor_unref (sheet->cursor_drag);
3427               sheet->cursor_drag =
3428                 gdk_cursor_new_for_display (display, new_cursor);
3429               gdk_window_set_cursor (sheet->row_title_window,
3430                                      sheet->cursor_drag);
3431             }
3432         }
3433     }
3434
3435   new_cursor = GDK_PLUS;
3436   if ( event->window == sheet->sheet_window &&
3437        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3438        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3439        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3440        !PSPPIRE_SHEET_IN_RESIZE (sheet) &&
3441        new_cursor != sheet->cursor_drag->type)
3442     {
3443       gdk_cursor_unref (sheet->cursor_drag);
3444       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3445       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3446     }
3447
3448   new_cursor = GDK_TOP_LEFT_ARROW;
3449   if ( event->window == sheet->sheet_window &&
3450        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3451           PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3452        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3453         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3454        new_cursor != sheet->cursor_drag->type)
3455     {
3456       gdk_cursor_unref (sheet->cursor_drag);
3457       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3458       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3459     }
3460
3461   new_cursor = GDK_SIZING;
3462   if ( event->window == sheet->sheet_window &&
3463        sheet->selection_mode != GTK_SELECTION_NONE &&
3464        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3465        (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3466         PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3467        new_cursor != sheet->cursor_drag->type)
3468     {
3469       gdk_cursor_unref (sheet->cursor_drag);
3470       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_SIZING);
3471       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3472     }
3473
3474
3475   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3476   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3477
3478   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3479     {
3480       if (event->x != sheet->x_drag)
3481         {
3482           draw_xor_vline (sheet);
3483           sheet->x_drag = event->x;
3484           draw_xor_vline (sheet);
3485         }
3486
3487       return TRUE;
3488     }
3489
3490   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3491     {
3492       if (event->y != sheet->y_drag)
3493         {
3494           draw_xor_hline (sheet);
3495           sheet->y_drag = event->y;
3496           draw_xor_hline (sheet);
3497         }
3498
3499       return TRUE;
3500     }
3501
3502   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3503     {
3504       PsppireSheetRange aux;
3505       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
3506       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
3507       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3508       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3509       sheet->x_drag = x;
3510       sheet->y_drag = y;
3511       aux = sheet->range;
3512       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3513           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3514         {
3515           aux = sheet->drag_range;
3516           sheet->drag_range.row0 = sheet->range.row0 + row;
3517           sheet->drag_range.col0 = sheet->range.col0 + column;
3518           sheet->drag_range.rowi = sheet->range.rowi + row;
3519           sheet->drag_range.coli = sheet->range.coli + column;
3520           if (aux.row0 != sheet->drag_range.row0 ||
3521               aux.col0 != sheet->drag_range.col0)
3522             {
3523               draw_xor_rectangle (sheet, aux);
3524               draw_xor_rectangle (sheet, sheet->drag_range);
3525             }
3526         }
3527       return TRUE;
3528     }
3529
3530   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3531     {
3532       PsppireSheetRange aux;
3533       gint v_h, current_col, current_row, col_threshold, row_threshold;
3534       v_h = 1;
3535       if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) >
3536           abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2;
3537
3538       current_col = column_from_xpixel (sheet, x);
3539       current_row = row_from_ypixel (sheet, y);
3540       column = current_col - sheet->drag_cell.col;
3541       row = current_row - sheet->drag_cell.row;
3542
3543       /*use half of column width resp. row height as threshold to
3544         expand selection*/
3545       col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) +
3546         psppire_axis_unit_size (sheet->haxis, current_col) / 2;
3547       if (column > 0)
3548         {
3549           if (x < col_threshold)
3550             column -= 1;
3551         }
3552       else if (column < 0)
3553         {
3554           if (x > col_threshold)
3555             column +=1;
3556         }
3557       row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) +
3558         psppire_axis_unit_size (sheet->vaxis, current_row)/2;
3559       if (row > 0)
3560         {
3561           if (y < row_threshold)
3562             row -= 1;
3563         }
3564       else if (row < 0)
3565         {
3566           if (y > row_threshold)
3567             row +=1;
3568         }
3569
3570       if (sheet->select_status == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
3571       if (sheet->select_status == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
3572       sheet->x_drag = x;
3573       sheet->y_drag = y;
3574       aux = sheet->range;
3575
3576       if (v_h == 1)
3577         column = 0;
3578       else
3579         row = 0;
3580
3581       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
3582           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
3583         {
3584           aux = sheet->drag_range;
3585           sheet->drag_range = sheet->range;
3586
3587           if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row;
3588           if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row;
3589           if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column;
3590           if (column > 0) sheet->drag_range.coli = sheet->range.coli + column;
3591
3592           if (aux.row0 != sheet->drag_range.row0 ||
3593               aux.rowi != sheet->drag_range.rowi ||
3594               aux.col0 != sheet->drag_range.col0 ||
3595               aux.coli != sheet->drag_range.coli)
3596             {
3597               draw_xor_rectangle (sheet, aux);
3598               draw_xor_rectangle (sheet, sheet->drag_range);
3599             }
3600         }
3601       return TRUE;
3602     }
3603
3604   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3605
3606   if (sheet->select_status == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
3607       column == sheet->active_cell.col) return TRUE;
3608
3609   if ( mods & GDK_BUTTON1_MASK)
3610     {
3611       if (PSPPIRE_SHEET_IN_SELECTION (sheet) )
3612         {
3613           /* Redraw the old range */
3614           psppire_sheet_unselect_range (sheet);
3615
3616           sheet->range.rowi = row;
3617           sheet->range.coli = column;
3618
3619           /* Redraw the new range */
3620           psppire_sheet_select_range (sheet, &sheet->range);
3621         }
3622       else
3623         {
3624           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3625         }
3626     }
3627
3628   return TRUE;
3629 }
3630
3631 static gboolean
3632 psppire_sheet_crossing_notify (GtkWidget *widget,
3633                                GdkEventCrossing *event)
3634 {
3635   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3636
3637   if (event->window == sheet->column_title_window)
3638     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
3639   else if (event->window == sheet->row_title_window)
3640     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
3641
3642   if (event->type == GDK_LEAVE_NOTIFY)
3643     gtk_widget_hide (sheet->hover_window->window);
3644
3645   return TRUE;
3646 }
3647
3648
3649 static gboolean
3650 psppire_sheet_focus_in (GtkWidget     *w,
3651                         GdkEventFocus *event)
3652 {
3653   PsppireSheet *sheet = PSPPIRE_SHEET (w);
3654
3655   gtk_widget_grab_focus (sheet->entry_widget);
3656
3657   return TRUE;
3658 }
3659
3660
3661
3662 static gint
3663 psppire_sheet_entry_key_press (GtkWidget *widget,
3664                                GdkEventKey *key)
3665 {
3666   gboolean focus;
3667   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
3668   return focus;
3669 }
3670
3671
3672 /* Number of rows in a step-increment */
3673 #define ROWS_PER_STEP 1
3674
3675
3676 static void
3677 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
3678 {
3679   gint old_row = sheet->active_cell.row ;
3680   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
3681
3682   gint new_row;
3683
3684   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
3685                                       min_visible_row (sheet));
3686
3687   switch ( dir)
3688     {
3689     case GTK_SCROLL_PAGE_DOWN:
3690       gtk_adjustment_set_value (sheet->vadjustment,
3691                                 sheet->vadjustment->value +
3692                                 sheet->vadjustment->page_increment);
3693       break;
3694     case GTK_SCROLL_PAGE_UP:
3695       gtk_adjustment_set_value (sheet->vadjustment,
3696                                 sheet->vadjustment->value -
3697                                 sheet->vadjustment->page_increment);
3698
3699       break;
3700     default:
3701       g_assert_not_reached ();
3702       break;
3703     }
3704
3705
3706   vpixel += psppire_axis_start_pixel (sheet->vaxis,
3707                                       min_visible_row (sheet));
3708
3709   new_row =  row_from_ypixel (sheet, vpixel);
3710
3711   change_active_cell (sheet, new_row,
3712                       sheet->active_cell.col);
3713 }
3714
3715
3716 static void
3717 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
3718 {
3719   gint current_row = sheet->active_cell.row;
3720   gint current_col = sheet->active_cell.col;
3721   PsppireSheetCell new_cell ;
3722   gboolean forbidden = FALSE;
3723
3724   new_cell.row = current_row;
3725   new_cell.col = current_col;
3726
3727   switch ( dir)
3728     {
3729     case GTK_SCROLL_STEP_DOWN:
3730       new_cell.row++;
3731       break;
3732     case GTK_SCROLL_STEP_UP:
3733       new_cell.row--;
3734       break;
3735     case GTK_SCROLL_STEP_RIGHT:
3736       new_cell.col++;
3737       break;
3738     case GTK_SCROLL_STEP_LEFT:
3739       new_cell.col--;
3740       break;
3741     case GTK_SCROLL_STEP_FORWARD:
3742       new_cell.col++;
3743       if (new_cell.col >=
3744           psppire_sheet_model_get_column_count (sheet->model))
3745         {
3746           new_cell.col = 0;
3747           new_cell.row++;
3748         }
3749       break;
3750     case GTK_SCROLL_STEP_BACKWARD:
3751       new_cell.col--;
3752       if (new_cell.col < 0)
3753         {
3754           new_cell.col =
3755             psppire_sheet_model_get_column_count (sheet->model) - 1;
3756           new_cell.row--;
3757         }
3758       break;
3759     default:
3760       g_assert_not_reached ();
3761       break;
3762     }
3763
3764   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3765                  &sheet->active_cell,
3766                  &new_cell,
3767                  &forbidden);
3768
3769   if (forbidden)
3770     return;
3771
3772
3773   maximize_int (&new_cell.row, 0);
3774   maximize_int (&new_cell.col, 0);
3775
3776   minimize_int (&new_cell.row,
3777                 psppire_axis_unit_count (sheet->vaxis) - 1);
3778
3779   minimize_int (&new_cell.col,
3780                 psppire_axis_unit_count (sheet->haxis) - 1);
3781
3782   change_active_cell (sheet, new_cell.row, new_cell.col);
3783
3784
3785   if ( new_cell.col > max_fully_visible_column (sheet))
3786     {
3787       glong hpos  =
3788         psppire_axis_start_pixel (sheet->haxis,
3789                                   new_cell.col + 1);
3790       hpos -= sheet->hadjustment->page_size;
3791
3792       gtk_adjustment_set_value (sheet->hadjustment,
3793                                 hpos);
3794     }
3795   else if ( new_cell.col < min_fully_visible_column (sheet))
3796     {
3797       glong hpos  =
3798         psppire_axis_start_pixel (sheet->haxis,
3799                                   new_cell.col);
3800
3801       gtk_adjustment_set_value (sheet->hadjustment,
3802                                 hpos);
3803     }
3804
3805
3806   if ( new_cell.row > max_fully_visible_row (sheet))
3807     {
3808       glong vpos  =
3809         psppire_axis_start_pixel (sheet->vaxis,
3810                                   new_cell.row + 1);
3811       vpos -= sheet->vadjustment->page_size;
3812
3813       gtk_adjustment_set_value (sheet->vadjustment,
3814                                 vpos);
3815     }
3816   else if ( new_cell.row < min_fully_visible_row (sheet))
3817     {
3818       glong vpos  =
3819         psppire_axis_start_pixel (sheet->vaxis,
3820                                   new_cell.row);
3821
3822       gtk_adjustment_set_value (sheet->vadjustment,
3823                                 vpos);
3824     }
3825
3826   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3827 }
3828
3829
3830 static gboolean
3831 psppire_sheet_key_press (GtkWidget *widget,
3832                          GdkEventKey *key)
3833 {
3834   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3835
3836   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3837
3838   switch (key->keyval)
3839     {
3840     case GDK_Tab:
3841       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
3842       break;
3843     case GDK_Right:
3844       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
3845       break;
3846     case GDK_ISO_Left_Tab:
3847       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
3848       break;
3849     case GDK_Left:
3850       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
3851       break;
3852     case GDK_Return:
3853     case GDK_Down:
3854       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
3855       break;
3856     case GDK_Up:
3857       step_sheet (sheet, GTK_SCROLL_STEP_UP);
3858       break;
3859
3860     case GDK_Page_Down:
3861       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
3862       break;
3863     case GDK_Page_Up:
3864       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
3865       break;
3866
3867     case GDK_Home:
3868       gtk_adjustment_set_value (sheet->vadjustment,
3869                                 sheet->vadjustment->lower);
3870
3871       change_active_cell (sheet,  0,
3872                           sheet->active_cell.col);
3873
3874       break;
3875
3876     case GDK_End:
3877       gtk_adjustment_set_value (sheet->vadjustment,
3878                                 sheet->vadjustment->upper -
3879                                 sheet->vadjustment->page_size -
3880                                 sheet->vadjustment->page_increment);
3881
3882       /*
3883         change_active_cellx (sheet,
3884         psppire_axis_unit_count (sheet->vaxis) - 1,
3885         sheet->active_cell.col);
3886       */
3887       break;
3888     case GDK_Delete:
3889       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
3890       break;
3891     default:
3892       return FALSE;
3893       break;
3894     }
3895
3896   return TRUE;
3897 }
3898
3899 static void
3900 psppire_sheet_size_request (GtkWidget *widget,
3901                             GtkRequisition *requisition)
3902 {
3903   PsppireSheet *sheet;
3904
3905   g_return_if_fail (widget != NULL);
3906   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3907   g_return_if_fail (requisition != NULL);
3908
3909   sheet = PSPPIRE_SHEET (widget);
3910
3911   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
3912   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
3913
3914   /* compute the size of the column title area */
3915   if (sheet->column_titles_visible)
3916     requisition->height += sheet->column_title_area.height;
3917
3918   /* compute the size of the row title area */
3919   if (sheet->row_titles_visible)
3920     requisition->width += sheet->row_title_area.width;
3921 }
3922
3923
3924 static void
3925 psppire_sheet_size_allocate (GtkWidget *widget,
3926                              GtkAllocation *allocation)
3927 {
3928   PsppireSheet *sheet;
3929   GtkAllocation sheet_allocation;
3930   gint border_width;
3931
3932   g_return_if_fail (widget != NULL);
3933   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
3934   g_return_if_fail (allocation != NULL);
3935
3936   sheet = PSPPIRE_SHEET (widget);
3937   widget->allocation = *allocation;
3938   border_width = GTK_CONTAINER (widget)->border_width;
3939
3940   if (GTK_WIDGET_REALIZED (widget))
3941     gdk_window_move_resize (widget->window,
3942                             allocation->x + border_width,
3943                             allocation->y + border_width,
3944                             allocation->width - 2 * border_width,
3945                             allocation->height - 2 * border_width);
3946
3947   sheet_allocation.x = 0;
3948   sheet_allocation.y = 0;
3949   sheet_allocation.width = allocation->width - 2 * border_width;
3950   sheet_allocation.height = allocation->height - 2 * border_width;
3951
3952   if (GTK_WIDGET_REALIZED (widget))
3953     gdk_window_move_resize (sheet->sheet_window,
3954                             sheet_allocation.x,
3955                             sheet_allocation.y,
3956                             sheet_allocation.width,
3957                             sheet_allocation.height);
3958
3959   /* position the window which holds the column title buttons */
3960   sheet->column_title_area.x = 0;
3961   sheet->column_title_area.y = 0;
3962   sheet->column_title_area.width = sheet_allocation.width ;
3963
3964
3965   /* position the window which holds the row title buttons */
3966   sheet->row_title_area.x = 0;
3967   sheet->row_title_area.y = 0;
3968   sheet->row_title_area.height = sheet_allocation.height;
3969
3970   if (sheet->row_titles_visible)
3971     sheet->column_title_area.x += sheet->row_title_area.width;
3972
3973   if (sheet->column_titles_visible)
3974     sheet->row_title_area.y += sheet->column_title_area.height;
3975
3976   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
3977     gdk_window_move_resize (sheet->column_title_window,
3978                             sheet->column_title_area.x,
3979                             sheet->column_title_area.y,
3980                             sheet->column_title_area.width,
3981                             sheet->column_title_area.height);
3982
3983
3984   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
3985     gdk_window_move_resize (sheet->row_title_window,
3986                             sheet->row_title_area.x,
3987                             sheet->row_title_area.y,
3988                             sheet->row_title_area.width,
3989                             sheet->row_title_area.height);
3990
3991   size_allocate_global_button (sheet);
3992
3993   if (sheet->haxis)
3994     {
3995       gint width = sheet->column_title_area.width;
3996
3997       if ( sheet->row_titles_visible)
3998         width -= sheet->row_title_area.width;
3999
4000       g_object_set (sheet->haxis,
4001                     "minimum-extent", width,
4002                     NULL);
4003     }
4004
4005
4006   if (sheet->vaxis)
4007     {
4008       gint height = sheet->row_title_area.height;
4009
4010       if ( sheet->column_titles_visible)
4011         height -= sheet->column_title_area.height;
4012
4013       g_object_set (sheet->vaxis,
4014                     "minimum-extent", height,
4015                     NULL);
4016     }
4017
4018
4019   /* set the scrollbars adjustments */
4020   adjust_scrollbars (sheet);
4021 }
4022
4023 static void
4024 draw_column_title_buttons (PsppireSheet *sheet)
4025 {
4026   gint x, width;
4027
4028   if (!sheet->column_titles_visible) return;
4029   if (!GTK_WIDGET_REALIZED (sheet))
4030     return;
4031
4032   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
4033   x = 0;
4034
4035   if (sheet->row_titles_visible)
4036     {
4037       x = sheet->row_title_area.width;
4038     }
4039
4040   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
4041     {
4042       sheet->column_title_area.width = width;
4043       sheet->column_title_area.x = x;
4044       gdk_window_move_resize (sheet->column_title_window,
4045                               sheet->column_title_area.x,
4046                               sheet->column_title_area.y,
4047                               sheet->column_title_area.width,
4048                               sheet->column_title_area.height);
4049     }
4050
4051   if (max_visible_column (sheet) ==
4052       psppire_axis_unit_count (sheet->haxis) - 1)
4053     gdk_window_clear_area (sheet->column_title_window,
4054                            0, 0,
4055                            sheet->column_title_area.width,
4056                            sheet->column_title_area.height);
4057
4058   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4059
4060   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
4061                                    max_visible_column (sheet));
4062 }
4063
4064 static void
4065 draw_row_title_buttons (PsppireSheet *sheet)
4066 {
4067   gint y = 0;
4068   gint height;
4069
4070   if (!sheet->row_titles_visible) return;
4071   if (!GTK_WIDGET_REALIZED (sheet))
4072     return;
4073
4074   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
4075
4076   if (sheet->column_titles_visible)
4077     {
4078       y = sheet->column_title_area.height;
4079     }
4080
4081   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
4082     {
4083       sheet->row_title_area.y = y;
4084       sheet->row_title_area.height = height;
4085       gdk_window_move_resize (sheet->row_title_window,
4086                               sheet->row_title_area.x,
4087                               sheet->row_title_area.y,
4088                               sheet->row_title_area.width,
4089                               sheet->row_title_area.height);
4090     }
4091
4092   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
4093     gdk_window_clear_area (sheet->row_title_window,
4094                            0, 0,
4095                            sheet->row_title_area.width,
4096                            sheet->row_title_area.height);
4097
4098   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4099
4100   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
4101                                 max_visible_row (sheet));
4102 }
4103
4104
4105 static void
4106 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
4107 {
4108   GtkAllocation entry_alloc;
4109   PsppireSheetCellAttr attributes = { 0 };
4110   GtkEntry *sheet_entry;
4111
4112   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4113   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
4114
4115   sheet_entry = psppire_sheet_get_entry (sheet);
4116
4117   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
4118                                        sheet->active_cell.col,
4119                                        &attributes) )
4120     return ;
4121
4122   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
4123     {
4124       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
4125
4126       style->bg[GTK_STATE_NORMAL] = attributes.background;
4127       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
4128       style->text[GTK_STATE_NORMAL] = attributes.foreground;
4129       style->bg[GTK_STATE_ACTIVE] = attributes.background;
4130       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
4131       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
4132     }
4133
4134   rectangle_from_cell (sheet, sheet->active_cell.row,
4135                        sheet->active_cell.col, &entry_alloc);
4136
4137   entry_alloc.x += sheet->cell_padding->left;
4138   entry_alloc.y += sheet->cell_padding->right;
4139   entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
4140   entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
4141
4142
4143   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
4144                                entry_alloc.height);
4145   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
4146 }
4147
4148
4149 /* Copy the sheet's font to the entry widget */
4150 static void
4151 set_entry_widget_font (PsppireSheet *sheet)
4152 {
4153   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
4154
4155   pango_font_description_free (style->font_desc);
4156   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4157
4158   gtk_widget_modify_style (sheet->entry_widget, style);
4159 }
4160
4161 static void
4162 create_sheet_entry (PsppireSheet *sheet)
4163 {
4164   if (sheet->entry_widget)
4165     {
4166       gtk_widget_unparent (sheet->entry_widget);
4167     }
4168
4169   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4170   g_object_ref_sink (sheet->entry_widget);
4171
4172   gtk_widget_size_request (sheet->entry_widget, NULL);
4173
4174   if ( GTK_IS_ENTRY (sheet->entry_widget))
4175     {
4176       g_object_set (sheet->entry_widget,
4177                     "has-frame", FALSE,
4178                     NULL);
4179     }
4180
4181   if (GTK_WIDGET_REALIZED (sheet))
4182     {
4183       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4184       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4185       gtk_widget_realize (sheet->entry_widget);
4186     }
4187
4188   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4189                             G_CALLBACK (psppire_sheet_entry_key_press),
4190                             sheet);
4191
4192   set_entry_widget_font (sheet);
4193
4194   gtk_widget_show (sheet->entry_widget);
4195 }
4196
4197
4198 /* Finds the last child widget that happens to be of type GtkEntry */
4199 static void
4200 find_entry (GtkWidget *w, gpointer user_data)
4201 {
4202   GtkWidget **entry = user_data;
4203   if ( GTK_IS_ENTRY (w))
4204     {
4205       *entry = w;
4206     }
4207 }
4208
4209
4210 GtkEntry *
4211 psppire_sheet_get_entry (PsppireSheet *sheet)
4212 {
4213   GtkWidget *w = sheet->entry_widget;
4214
4215   g_return_val_if_fail (sheet != NULL, NULL);
4216   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4217   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4218
4219   while (! GTK_IS_ENTRY (w))
4220     {
4221       GtkWidget *entry = NULL;
4222
4223       if (GTK_IS_CONTAINER (w))
4224         {
4225           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4226
4227           if (NULL == entry)
4228             break;
4229
4230           w = entry;
4231         }
4232     }
4233
4234   return GTK_ENTRY (w);
4235 }
4236
4237
4238 static void
4239 draw_button (PsppireSheet *sheet, GdkWindow *window,
4240              PsppireSheetButton *button, gboolean is_sensitive,
4241              GdkRectangle allocation)
4242 {
4243   GtkShadowType shadow_type;
4244   gint text_width = 0, text_height = 0;
4245   PangoAlignment align = PANGO_ALIGN_LEFT;
4246
4247   gboolean rtl ;
4248
4249   gint state = 0;
4250
4251   g_return_if_fail (sheet != NULL);
4252   g_return_if_fail (button != NULL);
4253
4254
4255   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4256
4257   gdk_window_clear_area (window,
4258                          allocation.x, allocation.y,
4259                          allocation.width, allocation.height);
4260
4261   gtk_widget_ensure_style (sheet->button);
4262
4263   gtk_paint_box (sheet->button->style, window,
4264                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4265                  &allocation,
4266                  GTK_WIDGET (sheet->button),
4267                  NULL,
4268                  allocation.x, allocation.y,
4269                  allocation.width, allocation.height);
4270
4271   state = button->state;
4272   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4273
4274   if (state == GTK_STATE_ACTIVE)
4275     shadow_type = GTK_SHADOW_IN;
4276   else
4277     shadow_type = GTK_SHADOW_OUT;
4278
4279   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4280     gtk_paint_box (sheet->button->style, window,
4281                    button->state, shadow_type,
4282                    &allocation, GTK_WIDGET (sheet->button),
4283                    NULL,
4284                    allocation.x, allocation.y,
4285                    allocation.width, allocation.height);
4286
4287   if ( button->overstruck)
4288     {
4289       GdkPoint points[2] = {
4290         {allocation.x,  allocation.y},
4291         {allocation.x + allocation.width,
4292          allocation.y + allocation.height}
4293       };
4294
4295       gtk_paint_polygon (sheet->button->style,
4296                          window,
4297                          button->state,
4298                          shadow_type,
4299                          NULL,
4300                          GTK_WIDGET (sheet),
4301                          NULL,
4302                          points,
4303                          2,
4304                          TRUE);
4305     }
4306
4307   if (button->label_visible)
4308     {
4309       text_height = DEFAULT_ROW_HEIGHT -
4310         2 * COLUMN_TITLES_HEIGHT;
4311
4312       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4313                                  &allocation);
4314       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4315                                  &allocation);
4316
4317       allocation.y += 2 * sheet->button->style->ythickness;
4318
4319       if (button->label && strlen (button->label) > 0)
4320         {
4321           PangoRectangle rect;
4322           gchar *line = button->label;
4323
4324           PangoLayout *layout = NULL;
4325           gint real_x = allocation.x;
4326           gint real_y = allocation.y;
4327
4328           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4329           pango_layout_get_extents (layout, NULL, &rect);
4330
4331           text_width = PANGO_PIXELS (rect.width);
4332           switch (button->justification)
4333             {
4334             case GTK_JUSTIFY_LEFT:
4335               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4336               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4337               break;
4338             case GTK_JUSTIFY_RIGHT:
4339               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4340               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4341               break;
4342             case GTK_JUSTIFY_CENTER:
4343             default:
4344               real_x = allocation.x + (allocation.width - text_width)/2;
4345               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4346               pango_layout_set_justify (layout, TRUE);
4347             }
4348           pango_layout_set_alignment (layout, align);
4349           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4350                             window,
4351                             state,
4352                             FALSE,
4353                             &allocation,
4354                             GTK_WIDGET (sheet),
4355                             "label",
4356                             real_x, real_y,
4357                             layout);
4358           g_object_unref (layout);
4359         }
4360
4361       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4362                                  NULL);
4363       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4364
4365     }
4366
4367   psppire_sheet_button_free (button);
4368 }
4369
4370
4371 /* Draw the column title buttons FIRST through to LAST */
4372 static void
4373 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4374 {
4375   GdkRectangle rect;
4376   gint col;
4377   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4378
4379   if (!sheet->column_titles_visible) return;
4380
4381   g_return_if_fail (first >= min_visible_column (sheet));
4382   g_return_if_fail (last <= max_visible_column (sheet));
4383
4384   rect.y = 0;
4385   rect.height = sheet->column_title_area.height;
4386   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4387   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4388     + psppire_axis_unit_size (sheet->haxis, last);
4389
4390   rect.x -= sheet->hadjustment->value;
4391
4392   minimize_int (&rect.width, sheet->column_title_area.width);
4393   maximize_int (&rect.x, 0);
4394
4395   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4396
4397   for (col = first ; col <= last ; ++col)
4398     {
4399       GdkRectangle allocation;
4400       gboolean is_sensitive = FALSE;
4401
4402       PsppireSheetButton *
4403         button = psppire_sheet_model_get_column_button (sheet->model, col);
4404       allocation.y = 0;
4405       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4406         + CELL_SPACING;
4407       allocation.x -= sheet->hadjustment->value;
4408
4409       allocation.height = sheet->column_title_area.height;
4410       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4411       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4412
4413       draw_button (sheet, sheet->column_title_window,
4414                    button, is_sensitive, allocation);
4415     }
4416
4417   gdk_window_end_paint (sheet->column_title_window);
4418 }
4419
4420
4421 static void
4422 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4423 {
4424   GdkRectangle rect;
4425   gint row;
4426   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4427
4428   if (!sheet->row_titles_visible) return;
4429
4430   g_return_if_fail (first >= min_visible_row (sheet));
4431   g_return_if_fail (last <= max_visible_row (sheet));
4432
4433   rect.x = 0;
4434   rect.width = sheet->row_title_area.width;
4435   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4436   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4437     + psppire_axis_unit_size (sheet->vaxis, last);
4438
4439   rect.y -= sheet->vadjustment->value;
4440
4441   minimize_int (&rect.height, sheet->row_title_area.height);
4442   maximize_int (&rect.y, 0);
4443
4444   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4445   for (row = first; row <= last; ++row)
4446     {
4447       GdkRectangle allocation;
4448
4449       gboolean is_sensitive = FALSE;
4450
4451       PsppireSheetButton *button =
4452         psppire_sheet_model_get_row_button (sheet->model, row);
4453       allocation.x = 0;
4454       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4455         + CELL_SPACING;
4456       allocation.y -= sheet->vadjustment->value;
4457
4458       allocation.width = sheet->row_title_area.width;
4459       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4460       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4461
4462       draw_button (sheet, sheet->row_title_window,
4463                    button, is_sensitive, allocation);
4464     }
4465
4466   gdk_window_end_paint (sheet->row_title_window);
4467 }
4468
4469 /* SCROLLBARS
4470  *
4471  * functions:
4472  * adjust_scrollbars
4473  * vadjustment_value_changed
4474  * hadjustment_value_changed */
4475
4476
4477 static void
4478 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
4479 {
4480   double position =
4481     (adj->value + adj->page_size)
4482     /
4483     (adj->upper - adj->lower);
4484
4485   const glong last_item = psppire_axis_unit_count (axis) - 1;
4486
4487   if (isnan (position) || position < 0)
4488     position = 0;
4489
4490   adj->upper =
4491     psppire_axis_start_pixel (axis, last_item)
4492     +
4493     psppire_axis_unit_size (axis, last_item)
4494     ;
4495
4496   adj->lower = 0;
4497   adj->page_size = page_size;
4498
4499 #if 0
4500   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
4501
4502   if ( adj->value < adj->lower)
4503     adj->value = adj->lower;
4504 #endif
4505
4506   gtk_adjustment_changed (adj);
4507 }
4508
4509
4510 static void
4511 adjust_scrollbars (PsppireSheet *sheet)
4512 {
4513   gint width, height;
4514
4515   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4516     return;
4517
4518   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
4519
4520   if ( sheet->row_titles_visible)
4521     width -= sheet->row_title_area.width;
4522
4523   if (sheet->column_titles_visible)
4524     height -= sheet->column_title_area.height;
4525
4526   if (sheet->vadjustment)
4527     {
4528       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
4529
4530       sheet->vadjustment->step_increment =
4531         ROWS_PER_STEP *
4532         psppire_axis_unit_size (sheet->vaxis, last_row);
4533
4534       sheet->vadjustment->page_increment =
4535         height -
4536         sheet->column_title_area.height -
4537         psppire_axis_unit_size (sheet->vaxis, last_row);
4538
4539       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
4540     }
4541
4542   if (sheet->hadjustment)
4543     {
4544       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
4545       sheet->hadjustment->step_increment = 1;
4546
4547       sheet->hadjustment->page_increment = width;
4548
4549       sheet->hadjustment->upper =
4550         psppire_axis_start_pixel (sheet->haxis, last_col)
4551         +
4552         psppire_axis_unit_size (sheet->haxis, last_col)
4553         ;
4554
4555       update_adjustment (sheet->hadjustment, sheet->haxis, width);
4556     }
4557 }
4558
4559 /* Subtracts the region of WIDGET from REGION */
4560 static void
4561 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
4562 {
4563   GdkRectangle rect;
4564   GdkRectangle intersect;
4565   GdkRegion *region2;
4566
4567   gdk_region_get_clipbox (region, &rect);
4568   gtk_widget_intersect (widget,
4569                         &rect,
4570                         &intersect);
4571
4572   region2 = gdk_region_rectangle (&intersect);
4573   gdk_region_subtract (region, region2);
4574   gdk_region_destroy (region2);
4575 }
4576
4577 static void
4578 vadjustment_value_changed (GtkAdjustment *adjustment,
4579                            gpointer data)
4580 {
4581   GdkRegion *region;
4582   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4583
4584   g_return_if_fail (adjustment != NULL);
4585
4586   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4587
4588   gtk_widget_hide (sheet->entry_widget);
4589
4590   region =
4591     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4592
4593   subtract_widget_region (region, sheet->button);
4594   gdk_window_begin_paint_region (sheet->sheet_window, region);
4595
4596   draw_sheet_region (sheet, region);
4597
4598   draw_row_title_buttons (sheet);
4599   psppire_sheet_draw_active_cell (sheet);
4600
4601   gdk_window_end_paint (sheet->sheet_window);
4602   gdk_region_destroy (region);
4603 }
4604
4605
4606 static void
4607 hadjustment_value_changed (GtkAdjustment *adjustment,
4608                            gpointer data)
4609 {
4610   GdkRegion *region;
4611   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4612
4613   g_return_if_fail (adjustment != NULL);
4614
4615   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
4616
4617   gtk_widget_hide (sheet->entry_widget);
4618
4619
4620   region =
4621     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
4622
4623   subtract_widget_region (region, sheet->button);
4624   gdk_window_begin_paint_region (sheet->sheet_window, region);
4625
4626   draw_sheet_region (sheet, region);
4627
4628   draw_column_title_buttons (sheet);
4629
4630   psppire_sheet_draw_active_cell (sheet);
4631
4632   gdk_window_end_paint (sheet->sheet_window);
4633
4634   gdk_region_destroy (region);
4635 }
4636
4637
4638 /* COLUMN RESIZING */
4639 static void
4640 draw_xor_vline (PsppireSheet *sheet)
4641 {
4642   gint height;
4643   gint xpos = sheet->x_drag;
4644   gdk_drawable_get_size (sheet->sheet_window,
4645                          NULL, &height);
4646
4647   if (sheet->row_titles_visible)
4648     xpos += sheet->row_title_area.width;
4649
4650   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4651                  xpos,
4652                  sheet->column_title_area.height,
4653                  xpos,
4654                  height + CELL_SPACING);
4655 }
4656
4657 /* ROW RESIZING */
4658 static void
4659 draw_xor_hline (PsppireSheet *sheet)
4660
4661 {
4662   gint width;
4663   gint ypos = sheet->y_drag;
4664
4665   gdk_drawable_get_size (sheet->sheet_window,
4666                          &width, NULL);
4667
4668
4669   if (sheet->column_titles_visible)
4670     ypos += sheet->column_title_area.height;
4671
4672   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
4673                  sheet->row_title_area.width,
4674                  ypos,
4675                  width + CELL_SPACING,
4676                  ypos);
4677 }
4678
4679 /* SELECTED RANGE */
4680 static void
4681 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
4682 {
4683   gint i = 0;
4684   GdkRectangle clip_area, area;
4685   GdkGCValues values;
4686
4687   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
4688   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
4689   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
4690     psppire_axis_unit_size (sheet->haxis, range.coli);
4691   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
4692     psppire_axis_unit_size (sheet->vaxis, range.rowi);
4693
4694   clip_area.x = sheet->row_title_area.width;
4695   clip_area.y = sheet->column_title_area.height;
4696
4697   gdk_drawable_get_size (sheet->sheet_window,
4698                          &clip_area.width, &clip_area.height);
4699
4700   if (!sheet->row_titles_visible) clip_area.x = 0;
4701   if (!sheet->column_titles_visible) clip_area.y = 0;
4702
4703   if (area.x < 0)
4704     {
4705       area.width = area.width + area.x;
4706       area.x = 0;
4707     }
4708   if (area.width > clip_area.width) area.width = clip_area.width + 10;
4709   if (area.y < 0)
4710     {
4711       area.height = area.height + area.y;
4712       area.y = 0;
4713     }
4714   if (area.height > clip_area.height) area.height = clip_area.height + 10;
4715
4716   clip_area.x--;
4717   clip_area.y--;
4718   clip_area.width += 3;
4719   clip_area.height += 3;
4720
4721   gdk_gc_get_values (sheet->xor_gc, &values);
4722
4723   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
4724
4725   gdk_draw_rectangle (sheet->sheet_window,
4726                       sheet->xor_gc,
4727                       FALSE,
4728                       area.x + i, area.y + i,
4729                       area.width - 2 * i, area.height - 2 * i);
4730
4731
4732   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
4733
4734   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
4735 }
4736
4737
4738 static void
4739 set_column_width (PsppireSheet *sheet,
4740                   gint column,
4741                   gint width)
4742 {
4743   g_return_if_fail (sheet != NULL);
4744   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4745
4746   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
4747     return;
4748
4749   if ( width <= 0)
4750     return;
4751
4752   psppire_axis_resize (sheet->haxis, column,
4753                        width - sheet->cell_padding->left -
4754                        sheet->cell_padding->right);
4755
4756   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
4757     {
4758       draw_column_title_buttons (sheet);
4759       adjust_scrollbars (sheet);
4760       psppire_sheet_size_allocate_entry (sheet);
4761       redraw_range (sheet, NULL);
4762     }
4763 }
4764
4765 static void
4766 set_row_height (PsppireSheet *sheet,
4767                 gint row,
4768                 gint height)
4769 {
4770   g_return_if_fail (sheet != NULL);
4771   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
4772
4773   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
4774     return;
4775
4776   if (height <= 0)
4777     return;
4778
4779   psppire_axis_resize (sheet->vaxis, row,
4780                        height - sheet->cell_padding->top -
4781                        sheet->cell_padding->bottom);
4782
4783   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
4784     {
4785       draw_row_title_buttons (sheet);
4786       adjust_scrollbars (sheet);
4787       psppire_sheet_size_allocate_entry (sheet);
4788       redraw_range (sheet, NULL);
4789     }
4790 }
4791
4792 static gboolean
4793 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
4794                               PsppireSheetCellAttr *attr)
4795 {
4796   GdkColor *fg, *bg;
4797   const GtkJustification *j ;
4798   GdkColormap *colormap;
4799
4800   g_return_val_if_fail (sheet != NULL, FALSE);
4801   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
4802
4803   if (row < 0 || col < 0) return FALSE;
4804
4805   attr->foreground = GTK_WIDGET (sheet)->style->black;
4806   attr->background = sheet->color[BG_COLOR];
4807
4808   attr->border.width = 0;
4809   attr->border.line_style = GDK_LINE_SOLID;
4810   attr->border.cap_style = GDK_CAP_NOT_LAST;
4811   attr->border.join_style = GDK_JOIN_MITER;
4812   attr->border.mask = 0;
4813   attr->border.color = GTK_WIDGET (sheet)->style->black;
4814
4815   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
4816   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
4817   if ( fg )
4818     {
4819       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
4820       attr->foreground = *fg;
4821     }
4822
4823   bg = psppire_sheet_model_get_background (sheet->model, row, col);
4824   if ( bg )
4825     {
4826       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
4827       attr->background = *bg;
4828     }
4829
4830   attr->justification =
4831     psppire_sheet_model_get_column_justification (sheet->model, col);
4832
4833   j = psppire_sheet_model_get_justification (sheet->model, row, col);
4834   if (j)
4835     attr->justification = *j;
4836
4837   return TRUE;
4838 }
4839
4840 static void
4841 psppire_sheet_button_size_request        (PsppireSheet *sheet,
4842                                           const PsppireSheetButton *button,
4843                                           GtkRequisition *button_requisition)
4844 {
4845   GtkRequisition requisition;
4846   GtkRequisition label_requisition;
4847
4848   label_requisition.height = DEFAULT_ROW_HEIGHT;
4849   label_requisition.width = COLUMN_MIN_WIDTH;
4850
4851   requisition.height = DEFAULT_ROW_HEIGHT;
4852   requisition.width = COLUMN_MIN_WIDTH;
4853
4854
4855   *button_requisition = requisition;
4856   button_requisition->width = MAX (requisition.width, label_requisition.width);
4857   button_requisition->height = MAX (requisition.height, label_requisition.height);
4858
4859 }
4860
4861 static void
4862 psppire_sheet_forall (GtkContainer *container,
4863                       gboolean include_internals,
4864                       GtkCallback callback,
4865                       gpointer callback_data)
4866 {
4867   PsppireSheet *sheet = PSPPIRE_SHEET (container);
4868
4869   g_return_if_fail (callback != NULL);
4870
4871   if (sheet->button && sheet->button->parent)
4872     (* callback) (sheet->button, callback_data);
4873
4874   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
4875     (* callback) (sheet->entry_widget, callback_data);
4876 }
4877
4878
4879 PsppireSheetModel *
4880 psppire_sheet_get_model (const PsppireSheet *sheet)
4881 {
4882   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4883
4884   return sheet->model;
4885 }
4886
4887
4888 PsppireSheetButton *
4889 psppire_sheet_button_new (void)
4890 {
4891   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
4892
4893   button->state = GTK_STATE_NORMAL;
4894   button->label = NULL;
4895   button->label_visible = TRUE;
4896   button->justification = GTK_JUSTIFY_FILL;
4897   button->overstruck = FALSE;
4898
4899   return button;
4900 }
4901
4902
4903 void
4904 psppire_sheet_button_free (PsppireSheetButton *button)
4905 {
4906   if (!button) return ;
4907
4908   g_free (button->label);
4909   g_free (button);
4910 }
4911
4912 static void
4913 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
4914 {
4915   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
4916
4917   if ( NULL == celltext)
4918     return;
4919
4920   g_string_append (string, celltext);
4921   g_free (celltext);
4922 }
4923
4924
4925 static GString *
4926 range_to_text (const PsppireSheet *sheet)
4927 {
4928   gint r, c;
4929   GString *string;
4930
4931   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4932     return NULL;
4933
4934   string = g_string_sized_new (80);
4935
4936   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4937     {
4938       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
4939         {
4940           append_cell_text (string, sheet, r, c);
4941           g_string_append (string, "\t");
4942         }
4943       append_cell_text (string, sheet, r, c);
4944       if ( r < sheet->range.rowi)
4945         g_string_append (string, "\n");
4946     }
4947
4948   return string;
4949 }
4950
4951 static GString *
4952 range_to_html (const PsppireSheet *sheet)
4953 {
4954   gint r, c;
4955   GString *string;
4956
4957   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
4958     return NULL;
4959
4960   string = g_string_sized_new (480);
4961
4962   g_string_append (string, "<html>\n");
4963   g_string_append (string, "<body>\n");
4964   g_string_append (string, "<table>\n");
4965   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
4966     {
4967       g_string_append (string, "<tr>\n");
4968       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
4969         {
4970           g_string_append (string, "<td>");
4971           append_cell_text (string, sheet, r, c);
4972           g_string_append (string, "</td>\n");
4973         }
4974       g_string_append (string, "</tr>\n");
4975     }
4976   g_string_append (string, "</table>\n");
4977   g_string_append (string, "</body>\n");
4978   g_string_append (string, "</html>\n");
4979
4980   return string;
4981 }
4982
4983 enum {
4984   SELECT_FMT_NULL,
4985   SELECT_FMT_TEXT,
4986   SELECT_FMT_HTML
4987 };
4988
4989 static void
4990 primary_get_cb (GtkClipboard     *clipboard,
4991                 GtkSelectionData *selection_data,
4992                 guint             info,
4993                 gpointer          data)
4994 {
4995   PsppireSheet *sheet = PSPPIRE_SHEET (data);
4996   GString *string = NULL;
4997
4998   switch (info)
4999     {
5000     case SELECT_FMT_TEXT:
5001       string = range_to_text (sheet);
5002       break;
5003     case SELECT_FMT_HTML:
5004       string = range_to_html (sheet);
5005       break;
5006     default:
5007       g_assert_not_reached ();
5008     }
5009
5010   gtk_selection_data_set (selection_data, selection_data->target,
5011                           8,
5012                           (const guchar *) string->str, string->len);
5013   g_string_free (string, TRUE);
5014 }
5015
5016 static void
5017 primary_clear_cb (GtkClipboard *clipboard,
5018                   gpointer      data)
5019 {
5020   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5021   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5022     return;
5023
5024   psppire_sheet_unselect_range (sheet);
5025 }
5026
5027 static void
5028 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
5029 {
5030   static const GtkTargetEntry targets[] = {
5031     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
5032     { "STRING",        0, SELECT_FMT_TEXT },
5033     { "TEXT",          0, SELECT_FMT_TEXT },
5034     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
5035     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
5036     { "text/plain",    0, SELECT_FMT_TEXT },
5037     { "text/html",     0, SELECT_FMT_HTML }
5038   };
5039
5040   GtkClipboard *clipboard;
5041
5042   if (!GTK_WIDGET_REALIZED (sheet))
5043     return;
5044
5045   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
5046                                         GDK_SELECTION_PRIMARY);
5047
5048   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
5049     {
5050       if (!gtk_clipboard_set_with_owner (clipboard, targets,
5051                                          G_N_ELEMENTS (targets),
5052                                          primary_get_cb, primary_clear_cb,
5053                                          G_OBJECT (sheet)))
5054         primary_clear_cb (clipboard, sheet);
5055     }
5056   else
5057     {
5058       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
5059         gtk_clipboard_clear (clipboard);
5060     }
5061 }