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