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