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