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