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