Improve the style handling of the column/row title buttons.
[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   if (GTK_WIDGET_MAPPED (sheet->entry_widget))
2145     gtk_widget_unmap (sheet->entry_widget);
2146
2147   if (GTK_WIDGET_MAPPED (sheet->button))
2148     gtk_widget_unmap (sheet->button);
2149 }
2150
2151 /* get cell attributes of the given cell */
2152 /* TRUE means that the cell is currently allocated */
2153 static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
2154                                               gint row, gint col,
2155                                               PsppireSheetCellAttr *attributes);
2156
2157
2158
2159 static void
2160 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
2161 {
2162   PangoLayout *layout;
2163   PangoRectangle text;
2164   PangoFontDescription *font_desc = GTK_WIDGET (sheet)->style->font_desc;
2165   gint font_height;
2166
2167   gchar *label;
2168
2169   PsppireSheetCellAttr attributes;
2170   GdkRectangle area;
2171
2172   g_return_if_fail (sheet != NULL);
2173
2174   /* bail now if we aren't yet drawable */
2175   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
2176
2177   if (row < 0 ||
2178       row >= psppire_axis_unit_count (sheet->vaxis))
2179     return;
2180
2181   if (col < 0 ||
2182       col >= psppire_axis_unit_count (sheet->haxis))
2183     return;
2184
2185   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2186
2187   /* select GC for background rectangle */
2188   gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
2189   gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
2190
2191   rectangle_from_cell (sheet, row, col, &area);
2192
2193   gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
2194
2195   if (sheet->show_grid)
2196     {
2197       gdk_gc_set_foreground (sheet->bg_gc, &sheet->color[GRID_COLOR]);
2198
2199       gdk_draw_rectangle (sheet->sheet_window,
2200                           sheet->bg_gc,
2201                           FALSE,
2202                           area.x, area.y,
2203                           area.width, area.height);
2204     }
2205
2206
2207   label = psppire_sheet_cell_get_text (sheet, row, col);
2208   if (NULL == label)
2209     return;
2210
2211
2212   layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), label);
2213   dispose_string (sheet, label);
2214
2215
2216   pango_layout_set_font_description (layout, font_desc);
2217
2218   pango_layout_get_pixel_extents (layout, NULL, &text);
2219
2220   gdk_gc_set_clip_rectangle (sheet->fg_gc, &area);
2221
2222   font_height = pango_font_description_get_size (font_desc);
2223   if ( !pango_font_description_get_size_is_absolute (font_desc))
2224     font_height /= PANGO_SCALE;
2225
2226
2227   if ( sheet->cell_padding )
2228     {
2229       area.x += sheet->cell_padding->left;
2230       area.width -= sheet->cell_padding->right
2231         + sheet->cell_padding->left;
2232
2233       area.y += sheet->cell_padding->top;
2234       area.height -= sheet->cell_padding->bottom
2235         +
2236         sheet->cell_padding->top;
2237     }
2238
2239   /* Centre the text vertically */
2240   area.y += (area.height - font_height) / 2.0;
2241
2242   switch (attributes.justification)
2243     {
2244     case GTK_JUSTIFY_RIGHT:
2245       area.x += area.width - text.width;
2246       break;
2247     case GTK_JUSTIFY_CENTER:
2248       area.x += (area.width - text.width) / 2.0;
2249       break;
2250     case GTK_JUSTIFY_LEFT:
2251       /* Do nothing */
2252       break;
2253     default:
2254       g_critical ("Unhandled justification %d in column %d\n",
2255                  attributes.justification, col);
2256       break;
2257     }
2258
2259   gdk_draw_layout (sheet->sheet_window, sheet->fg_gc,
2260                    area.x,
2261                    area.y,
2262                    layout);
2263
2264   gdk_gc_set_clip_rectangle (sheet->fg_gc, NULL);
2265   g_object_unref (layout);
2266 }
2267
2268
2269 static void
2270 draw_sheet_region (PsppireSheet *sheet, GdkRegion *region)
2271 {
2272   PsppireSheetRange range;
2273   GdkRectangle area;
2274   gint y, x;
2275   gint i, j;
2276
2277   PsppireSheetRange drawing_range;
2278
2279   gdk_region_get_clipbox (region, &area);
2280
2281   y = area.y + sheet->vadjustment->value;
2282   x = area.x + sheet->hadjustment->value;
2283
2284   if ( sheet->column_titles_visible)
2285     y -= sheet->column_title_area.height;
2286
2287   if ( sheet->row_titles_visible)
2288     x -= sheet->row_title_area.width;
2289
2290   maximize_int (&x, 0);
2291   maximize_int (&y, 0);
2292
2293   range.row0 = row_from_ypixel (sheet, y);
2294   range.rowi = row_from_ypixel (sheet, y + area.height);
2295
2296   range.col0 = column_from_xpixel (sheet, x);
2297   range.coli = column_from_xpixel (sheet, x + area.width);
2298
2299   g_return_if_fail (sheet != NULL);
2300   g_return_if_fail (PSPPIRE_SHEET (sheet));
2301
2302   if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (sheet))) return;
2303   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2304   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
2305
2306
2307   drawing_range.row0 = MAX (range.row0, min_visible_row (sheet));
2308   drawing_range.col0 = MAX (range.col0, min_visible_column (sheet));
2309   drawing_range.rowi = MIN (range.rowi, max_visible_row (sheet));
2310   drawing_range.coli = MIN (range.coli, max_visible_column (sheet));
2311
2312   g_return_if_fail (drawing_range.rowi >= drawing_range.row0);
2313   g_return_if_fail (drawing_range.coli >= drawing_range.col0);
2314
2315   for (i = drawing_range.row0; i <= drawing_range.rowi; i++)
2316     {
2317       for (j = drawing_range.col0; j <= drawing_range.coli; j++)
2318         psppire_sheet_cell_draw (sheet, i, j);
2319     }
2320
2321   if (sheet->state != PSPPIRE_SHEET_NORMAL &&
2322       psppire_sheet_range_isvisible (sheet, &sheet->range))
2323     psppire_sheet_range_draw_selection (sheet, drawing_range);
2324
2325
2326   if (sheet->state == GTK_STATE_NORMAL &&
2327       sheet->active_cell.row >= drawing_range.row0 &&
2328       sheet->active_cell.row <= drawing_range.rowi &&
2329       sheet->active_cell.col >= drawing_range.col0 &&
2330       sheet->active_cell.col <= drawing_range.coli)
2331     psppire_sheet_show_entry_widget (sheet);
2332 }
2333
2334
2335 static void
2336 psppire_sheet_range_draw_selection (PsppireSheet *sheet, PsppireSheetRange range)
2337 {
2338   GdkRectangle area;
2339   gint i, j;
2340   PsppireSheetRange aux;
2341
2342   if (range.col0 > sheet->range.coli || range.coli < sheet->range.col0 ||
2343       range.row0 > sheet->range.rowi || range.rowi < sheet->range.row0)
2344     return;
2345
2346   if (!psppire_sheet_range_isvisible (sheet, &range)) return;
2347   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2348
2349   aux = range;
2350
2351   range.col0 = MAX (sheet->range.col0, range.col0);
2352   range.coli = MIN (sheet->range.coli, range.coli);
2353   range.row0 = MAX (sheet->range.row0, range.row0);
2354   range.rowi = MIN (sheet->range.rowi, range.rowi);
2355
2356   range.col0 = MAX (range.col0, min_visible_column (sheet));
2357   range.coli = MIN (range.coli, max_visible_column (sheet));
2358   range.row0 = MAX (range.row0, min_visible_row (sheet));
2359   range.rowi = MIN (range.rowi, max_visible_row (sheet));
2360
2361   for (i = range.row0; i <= range.rowi; i++)
2362     {
2363       for (j = range.col0; j <= range.coli; j++)
2364         {
2365           if (psppire_sheet_cell_get_state (sheet, i, j) == GTK_STATE_SELECTED)
2366             {
2367               rectangle_from_cell (sheet, i, j, &area);
2368
2369               if (i == sheet->range.row0)
2370                 {
2371                   area.y = area.y + 2;
2372                   area.height = area.height - 2;
2373                 }
2374               if (i == sheet->range.rowi) area.height = area.height - 3;
2375               if (j == sheet->range.col0)
2376                 {
2377                   area.x = area.x + 2;
2378                   area.width = area.width - 2;
2379                 }
2380               if (j == sheet->range.coli) area.width = area.width - 3;
2381
2382               if (i != sheet->active_cell.row || j != sheet->active_cell.col)
2383                 {
2384                   gdk_draw_rectangle (sheet->sheet_window,
2385                                       sheet->xor_gc,
2386                                       TRUE,
2387                                       area.x + 1, area.y + 1,
2388                                       area.width, area.height);
2389                 }
2390             }
2391
2392         }
2393     }
2394
2395   psppire_sheet_draw_border (sheet, sheet->range);
2396 }
2397
2398 static inline gint
2399 safe_strcmp (const gchar *s1, const gchar *s2)
2400 {
2401   if ( !s1 && !s2) return 0;
2402   if ( !s1) return -1;
2403   if ( !s2) return +1;
2404   return strcmp (s1, s2);
2405 }
2406
2407 static void
2408 psppire_sheet_set_cell (PsppireSheet *sheet, gint row, gint col,
2409                     GtkJustification justification,
2410                     const gchar *text)
2411 {
2412   PsppireSheetModel *model ;
2413   gchar *old_text ;
2414
2415   g_return_if_fail (sheet != NULL);
2416   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2417
2418   if (col >= psppire_axis_unit_count (sheet->haxis)
2419       || row >= psppire_axis_unit_count (sheet->vaxis))
2420     return;
2421
2422   if (col < 0 || row < 0) return;
2423
2424   model = psppire_sheet_get_model (sheet);
2425
2426   old_text = psppire_sheet_model_get_string (model, row, col);
2427
2428   if (0 != safe_strcmp (old_text, text))
2429     {
2430       g_signal_handler_block    (sheet->model, sheet->update_handler_id);
2431       psppire_sheet_model_set_string (model, text, row, col);
2432       g_signal_handler_unblock  (sheet->model, sheet->update_handler_id);
2433     }
2434
2435   if ( psppire_sheet_model_free_strings (model))
2436     g_free (old_text);
2437 }
2438
2439
2440 void
2441 psppire_sheet_cell_clear (PsppireSheet *sheet, gint row, gint column)
2442 {
2443   PsppireSheetRange range;
2444
2445   g_return_if_fail (sheet != NULL);
2446   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2447   if (column >= psppire_axis_unit_count (sheet->haxis) ||
2448       row >= psppire_axis_unit_count (sheet->vaxis)) return;
2449
2450   if (column < 0 || row < 0) return;
2451
2452   range.row0 = row;
2453   range.rowi = row;
2454   range.col0 = min_visible_column (sheet);
2455   range.coli = max_visible_column (sheet);
2456
2457   psppire_sheet_real_cell_clear (sheet, row, column);
2458
2459   redraw_range (sheet, &range);
2460 }
2461
2462 static void
2463 psppire_sheet_real_cell_clear (PsppireSheet *sheet, gint row, gint column)
2464 {
2465   PsppireSheetModel *model = psppire_sheet_get_model (sheet);
2466
2467   gchar *old_text = psppire_sheet_cell_get_text (sheet, row, column);
2468
2469   if (old_text && strlen (old_text) > 0 )
2470     {
2471       psppire_sheet_model_datum_clear (model, row, column);
2472     }
2473
2474   dispose_string (sheet, old_text);
2475 }
2476
2477 gchar *
2478 psppire_sheet_cell_get_text (const PsppireSheet *sheet, gint row, gint col)
2479 {
2480   PsppireSheetModel *model;
2481   g_return_val_if_fail (sheet != NULL, NULL);
2482   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
2483
2484   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis))
2485     return NULL;
2486   if (col < 0 || row < 0) return NULL;
2487
2488   model = psppire_sheet_get_model (sheet);
2489
2490   if ( !model )
2491     return NULL;
2492
2493   return psppire_sheet_model_get_string (model, row, col);
2494 }
2495
2496
2497 static GtkStateType
2498 psppire_sheet_cell_get_state (PsppireSheet *sheet, gint row, gint col)
2499 {
2500   gint state;
2501   PsppireSheetRange *range;
2502
2503   g_return_val_if_fail (sheet != NULL, 0);
2504   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2505   if (col >= psppire_axis_unit_count (sheet->haxis) || row >= psppire_axis_unit_count (sheet->vaxis)) return 0;
2506   if (col < 0 || row < 0) return 0;
2507
2508   state = sheet->state;
2509   range = &sheet->range;
2510
2511   switch (state)
2512     {
2513     case PSPPIRE_SHEET_NORMAL:
2514       return GTK_STATE_NORMAL;
2515       break;
2516     case PSPPIRE_SHEET_ROW_SELECTED:
2517       if (row >= range->row0 && row <= range->rowi)
2518         return GTK_STATE_SELECTED;
2519       break;
2520     case PSPPIRE_SHEET_COLUMN_SELECTED:
2521       if (col >= range->col0 && col <= range->coli)
2522         return GTK_STATE_SELECTED;
2523       break;
2524     case PSPPIRE_SHEET_RANGE_SELECTED:
2525       if (row >= range->row0 && row <= range->rowi && \
2526           col >= range->col0 && col <= range->coli)
2527         return GTK_STATE_SELECTED;
2528       break;
2529     }
2530   return GTK_STATE_NORMAL;
2531 }
2532
2533 /* Convert X, Y (in pixels) to *ROW, *COLUMN
2534    If the function returns FALSE, then the results will be unreliable.
2535 */
2536 static gboolean
2537 psppire_sheet_get_pixel_info (PsppireSheet *sheet,
2538                           gint x,
2539                           gint y,
2540                           gint *row,
2541                           gint *column)
2542 {
2543   gint trow, tcol;
2544   *row = -G_MAXINT;
2545   *column = -G_MAXINT;
2546
2547   g_return_val_if_fail (sheet != NULL, 0);
2548   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2549
2550   /* bounds checking, return false if the user clicked
2551      on a blank area */
2552   if (y < 0)
2553     return FALSE;
2554
2555   if (x < 0)
2556     return FALSE;
2557
2558   if ( sheet->column_titles_visible)
2559     y -= sheet->column_title_area.height;
2560
2561   y += sheet->vadjustment->value;
2562
2563   if ( y < 0 && sheet->column_titles_visible)
2564     {
2565       trow = -1;
2566     }
2567   else
2568     {
2569       trow = row_from_ypixel (sheet, y);
2570       if (trow > psppire_axis_unit_count (sheet->vaxis))
2571         return FALSE;
2572     }
2573
2574   *row = trow;
2575
2576   if ( sheet->row_titles_visible)
2577     x -= sheet->row_title_area.width;
2578
2579   x += sheet->hadjustment->value;
2580
2581   if ( x < 0 && sheet->row_titles_visible)
2582     {
2583       tcol = -1;
2584     }
2585   else
2586     {
2587       tcol = column_from_xpixel (sheet, x);
2588       if (tcol > psppire_axis_unit_count (sheet->haxis))
2589         return FALSE;
2590     }
2591
2592   *column = tcol;
2593
2594   return TRUE;
2595 }
2596
2597 gboolean
2598 psppire_sheet_get_cell_area (PsppireSheet *sheet,
2599                          gint row,
2600                          gint column,
2601                          GdkRectangle *area)
2602 {
2603   g_return_val_if_fail (sheet != NULL, 0);
2604   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), 0);
2605
2606   if (row >= psppire_axis_unit_count (sheet->vaxis) || column >= psppire_axis_unit_count (sheet->haxis))
2607     return FALSE;
2608
2609   area->x = (column == -1) ? 0 : psppire_axis_start_pixel (sheet->haxis, column);
2610   area->y = (row == -1)    ? 0 : psppire_axis_start_pixel (sheet->vaxis, row);
2611
2612   area->width= (column == -1) ? sheet->row_title_area.width
2613     : psppire_axis_unit_size (sheet->haxis, column);
2614
2615   area->height= (row == -1) ? sheet->column_title_area.height
2616     : psppire_axis_unit_size (sheet->vaxis, row);
2617
2618   return TRUE;
2619 }
2620
2621 void
2622 psppire_sheet_set_active_cell (PsppireSheet *sheet, gint row, gint col)
2623 {
2624   g_return_if_fail (sheet != NULL);
2625   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2626
2627   if (row < -1 || col < -1)
2628     return;
2629
2630   if (row >= psppire_axis_unit_count (sheet->vaxis)
2631       ||
2632       col >= psppire_axis_unit_count (sheet->haxis))
2633     return;
2634
2635   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2636     return;
2637
2638   if ( row == -1 || col == -1)
2639     {
2640       psppire_sheet_hide_entry_widget (sheet);
2641       return;
2642     }
2643
2644   change_active_cell (sheet, row, col);
2645 }
2646
2647 void
2648 psppire_sheet_get_active_cell (PsppireSheet *sheet, gint *row, gint *column)
2649 {
2650   g_return_if_fail (sheet != NULL);
2651   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2652
2653   if ( row ) *row = sheet->active_cell.row;
2654   if (column) *column = sheet->active_cell.col;
2655 }
2656
2657 static void
2658 entry_load_text (PsppireSheet *sheet)
2659 {
2660   gint row, col;
2661   const char *text;
2662   GtkJustification justification;
2663   PsppireSheetCellAttr attributes;
2664
2665   if (!GTK_WIDGET_VISIBLE (sheet->entry_widget)) return;
2666   if (sheet->state != GTK_STATE_NORMAL) return;
2667
2668   row = sheet->active_cell.row;
2669   col = sheet->active_cell.col;
2670
2671   if (row < 0 || col < 0) return;
2672
2673   text = gtk_entry_get_text (psppire_sheet_get_entry (sheet));
2674
2675   if (text && strlen (text) > 0)
2676     {
2677       psppire_sheet_get_attributes (sheet, row, col, &attributes);
2678       justification = attributes.justification;
2679       psppire_sheet_set_cell (sheet, row, col, justification, text);
2680     }
2681 }
2682
2683
2684 static void
2685 psppire_sheet_hide_entry_widget (PsppireSheet *sheet)
2686 {
2687   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
2688     return;
2689
2690   if (sheet->active_cell.row < 0 ||
2691       sheet->active_cell.col < 0) return;
2692
2693   gtk_widget_hide (sheet->entry_widget);
2694   gtk_widget_unmap (sheet->entry_widget);
2695
2696   GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2697 }
2698
2699 static void
2700 change_active_cell (PsppireSheet *sheet, gint row, gint col)
2701 {
2702   gint old_row, old_col;
2703
2704   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2705
2706   if (row < 0 || col < 0)
2707     return;
2708
2709   if ( row > psppire_axis_unit_count (sheet->vaxis)
2710        || col > psppire_axis_unit_count (sheet->haxis))
2711     return;
2712
2713   if (sheet->state != PSPPIRE_SHEET_NORMAL)
2714     {
2715       sheet->state = PSPPIRE_SHEET_NORMAL;
2716       psppire_sheet_real_unselect_range (sheet, NULL);
2717     }
2718
2719   old_row = sheet->active_cell.row;
2720   old_col = sheet->active_cell.col;
2721
2722   /* Erase the old cell */
2723   psppire_sheet_draw_active_cell (sheet);
2724
2725   entry_load_text (sheet);
2726
2727   sheet->range.row0 = row;
2728   sheet->range.col0 = col;
2729   sheet->range.rowi = row;
2730   sheet->range.coli = col;
2731   sheet->active_cell.row = row;
2732   sheet->active_cell.col = col;
2733   sheet->selection_cell.row = row;
2734   sheet->selection_cell.col = col;
2735
2736   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
2737
2738   GTK_WIDGET_UNSET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2739
2740   psppire_sheet_draw_active_cell (sheet);
2741   psppire_sheet_show_entry_widget (sheet);
2742
2743   GTK_WIDGET_SET_FLAGS (sheet->entry_widget, GTK_HAS_FOCUS);
2744
2745   g_signal_emit (sheet, sheet_signals [ACTIVATE], 0,
2746                  row, col, old_row, old_col);
2747
2748 }
2749
2750 static void
2751 psppire_sheet_show_entry_widget (PsppireSheet *sheet)
2752 {
2753   GtkEntry *sheet_entry;
2754   PsppireSheetCellAttr attributes;
2755
2756   gint row, col;
2757
2758   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
2759
2760   row = sheet->active_cell.row;
2761   col = sheet->active_cell.col;
2762
2763   /* Don't show the active cell, if there is no active cell: */
2764   if (! (row >= 0 && col >= 0)) /* e.g row or coll == -1. */
2765     return;
2766
2767   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
2768   if (sheet->state != PSPPIRE_SHEET_NORMAL) return;
2769   if (PSPPIRE_SHEET_IN_SELECTION (sheet)) return;
2770
2771   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (sheet->entry_widget), GTK_VISIBLE);
2772
2773   sheet_entry = psppire_sheet_get_entry (sheet);
2774
2775   psppire_sheet_get_attributes (sheet, row, col, &attributes);
2776
2777   if (GTK_IS_ENTRY (sheet_entry))
2778     {
2779       gchar *text = psppire_sheet_cell_get_text (sheet, row, col);
2780       const gchar *old_text = gtk_entry_get_text (GTK_ENTRY (sheet_entry));
2781
2782       if ( ! text )
2783         text = g_strdup ("");
2784
2785       if (strcmp (old_text, text) != 0)
2786         gtk_entry_set_text (sheet_entry, text);
2787       
2788       dispose_string (sheet, text);
2789
2790         {
2791           switch (attributes.justification)
2792             {
2793             case GTK_JUSTIFY_RIGHT:
2794               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 1.0);
2795               break;
2796             case GTK_JUSTIFY_CENTER:
2797               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.5);
2798               break;
2799             case GTK_JUSTIFY_LEFT:
2800             default:
2801               gtk_entry_set_alignment (GTK_ENTRY (sheet_entry), 0.0);
2802               break;
2803             }
2804         }
2805     }
2806
2807   psppire_sheet_size_allocate_entry (sheet);
2808
2809   gtk_widget_set_sensitive (GTK_WIDGET (sheet_entry),
2810                             psppire_sheet_model_is_editable (sheet->model,
2811                                                        row, col));
2812   gtk_widget_map (sheet->entry_widget);
2813 }
2814
2815 static gboolean
2816 psppire_sheet_draw_active_cell (PsppireSheet *sheet)
2817 {
2818   gint row, col;
2819   PsppireSheetRange range;
2820
2821   row = sheet->active_cell.row;
2822   col = sheet->active_cell.col;
2823
2824   if (row < 0 || col < 0) return FALSE;
2825
2826   if (!psppire_sheet_cell_isvisible (sheet, row, col))
2827     return FALSE;
2828
2829   range.col0 = range.coli = col;
2830   range.row0 = range.rowi = row;
2831
2832   psppire_sheet_draw_border (sheet, range);
2833
2834   return FALSE;
2835 }
2836
2837
2838
2839 static void
2840 psppire_sheet_new_selection (PsppireSheet *sheet, PsppireSheetRange *range)
2841 {
2842   gint i, j, mask1, mask2;
2843   gint state, selected;
2844   gint x, y, width, height;
2845   PsppireSheetRange new_range, aux_range;
2846
2847   g_return_if_fail (sheet != NULL);
2848
2849   if (range == NULL) range=&sheet->range;
2850
2851   new_range=*range;
2852
2853   range->row0 = MIN (range->row0, sheet->range.row0);
2854   range->rowi = MAX (range->rowi, sheet->range.rowi);
2855   range->col0 = MIN (range->col0, sheet->range.col0);
2856   range->coli = MAX (range->coli, sheet->range.coli);
2857
2858   range->row0 = MAX (range->row0, min_visible_row (sheet));
2859   range->rowi = MIN (range->rowi, max_visible_row (sheet));
2860   range->col0 = MAX (range->col0, min_visible_column (sheet));
2861   range->coli = MIN (range->coli, max_visible_column (sheet));
2862
2863   aux_range.row0 = MAX (new_range.row0, min_visible_row (sheet));
2864   aux_range.rowi = MIN (new_range.rowi, max_visible_row (sheet));
2865   aux_range.col0 = MAX (new_range.col0, min_visible_column (sheet));
2866   aux_range.coli = MIN (new_range.coli, max_visible_column (sheet));
2867
2868   for (i = range->row0; i <= range->rowi; i++)
2869     {
2870       for (j = range->col0; j <= range->coli; j++)
2871         {
2872
2873           state = psppire_sheet_cell_get_state (sheet, i, j);
2874           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2875                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2876
2877           if (state == GTK_STATE_SELECTED && selected &&
2878               (i == sheet->range.row0 || i == sheet->range.rowi ||
2879                j == sheet->range.col0 || j == sheet->range.coli ||
2880                i == new_range.row0 || i == new_range.rowi ||
2881                j == new_range.col0 || j == new_range.coli))
2882             {
2883
2884               mask1 = i == sheet->range.row0 ? 1 : 0;
2885               mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1;
2886               mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1;
2887               mask1 = j == sheet->range.coli ? mask1 + 8 : mask1;
2888
2889               mask2 = i == new_range.row0 ? 1 : 0;
2890               mask2 = i == new_range.rowi ? mask2 + 2 : mask2;
2891               mask2 = j == new_range.col0 ? mask2 + 4 : mask2;
2892               mask2 = j == new_range.coli ? mask2 + 8 : mask2;
2893
2894               if (mask1 != mask2)
2895                 {
2896                   x = psppire_axis_start_pixel (sheet->haxis, j);
2897                   y = psppire_axis_start_pixel (sheet->vaxis, i);
2898                   width = psppire_axis_start_pixel (sheet->haxis, j)- x+
2899                     psppire_axis_unit_size (sheet->haxis, j);
2900                   height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2901
2902                   if (i == sheet->range.row0)
2903                     {
2904                       y = y - 3;
2905                       height = height + 3;
2906                     }
2907                   if (i == sheet->range.rowi) height = height + 3;
2908                   if (j == sheet->range.col0)
2909                     {
2910                       x = x - 3;
2911                       width = width + 3;
2912                     }
2913                   if (j == sheet->range.coli) width = width + 3;
2914
2915                   if (i != sheet->active_cell.row || j != sheet->active_cell.col)
2916                     {
2917                       x = psppire_axis_start_pixel (sheet->haxis, j);
2918                       y = psppire_axis_start_pixel (sheet->vaxis, i);
2919                       width = psppire_axis_start_pixel (sheet->haxis, j)- x+
2920                         psppire_axis_unit_size (sheet->haxis, j);
2921
2922                       height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2923
2924                       if (i == new_range.row0)
2925                         {
2926                           y = y+2;
2927                           height = height - 2;
2928                         }
2929                       if (i == new_range.rowi) height = height - 3;
2930                       if (j == new_range.col0)
2931                         {
2932                           x = x+2;
2933                           width = width - 2;
2934                         }
2935                       if (j == new_range.coli) width = width - 3;
2936
2937                       gdk_draw_rectangle (sheet->sheet_window,
2938                                           sheet->xor_gc,
2939                                           TRUE,
2940                                           x + 1, y + 1,
2941                                           width, height);
2942                     }
2943                 }
2944             }
2945         }
2946     }
2947
2948   for (i = range->row0; i <= range->rowi; i++)
2949     {
2950       for (j = range->col0; j <= range->coli; j++)
2951         {
2952
2953           state = psppire_sheet_cell_get_state (sheet, i, j);
2954           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2955                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2956
2957           if (state == GTK_STATE_SELECTED && !selected)
2958             {
2959
2960               x = psppire_axis_start_pixel (sheet->haxis, j);
2961               y = psppire_axis_start_pixel (sheet->vaxis, i);
2962               width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j);
2963               height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2964
2965               if (i == sheet->range.row0)
2966                 {
2967                   y = y - 3;
2968                   height = height + 3;
2969                 }
2970               if (i == sheet->range.rowi) height = height + 3;
2971               if (j == sheet->range.col0)
2972                 {
2973                   x = x - 3;
2974                   width = width + 3;
2975                 }
2976               if (j == sheet->range.coli) width = width + 3;
2977
2978             }
2979         }
2980     }
2981
2982   for (i = range->row0; i <= range->rowi; i++)
2983     {
2984       for (j = range->col0; j <= range->coli; j++)
2985         {
2986
2987           state = psppire_sheet_cell_get_state (sheet, i, j);
2988           selected= (i <= new_range.rowi && i >= new_range.row0 &&
2989                      j <= new_range.coli && j >= new_range.col0) ? TRUE : FALSE;
2990
2991           if (state != GTK_STATE_SELECTED && selected &&
2992               (i != sheet->active_cell.row || j != sheet->active_cell.col))
2993             {
2994
2995               x = psppire_axis_start_pixel (sheet->haxis, j);
2996               y = psppire_axis_start_pixel (sheet->vaxis, i);
2997               width = psppire_axis_start_pixel (sheet->haxis, j) - x + psppire_axis_unit_size (sheet->haxis, j);
2998               height = psppire_axis_start_pixel (sheet->vaxis, i) - y + psppire_axis_unit_size (sheet->vaxis, i);
2999
3000               if (i == new_range.row0)
3001                 {
3002                   y = y+2;
3003                   height = height - 2;
3004                 }
3005               if (i == new_range.rowi) height = height - 3;
3006               if (j == new_range.col0)
3007                 {
3008                   x = x+2;
3009                   width = width - 2;
3010                 }
3011               if (j == new_range.coli) width = width - 3;
3012
3013               gdk_draw_rectangle (sheet->sheet_window,
3014                                   sheet->xor_gc,
3015                                   TRUE,
3016                                   x + 1, y + 1,
3017                                   width, height);
3018
3019             }
3020
3021         }
3022     }
3023
3024   for (i = aux_range.row0; i <= aux_range.rowi; i++)
3025     {
3026       for (j = aux_range.col0; j <= aux_range.coli; j++)
3027         {
3028           state = psppire_sheet_cell_get_state (sheet, i, j);
3029
3030           mask1 = i == sheet->range.row0 ? 1 : 0;
3031           mask1 = i == sheet->range.rowi ? mask1 + 2 : mask1;
3032           mask1 = j == sheet->range.col0 ? mask1 + 4 : mask1;
3033           mask1 = j == sheet->range.coli ? mask1 + 8 : mask1;
3034
3035           mask2 = i == new_range.row0 ? 1 : 0;
3036           mask2 = i == new_range.rowi ? mask2 + 2 : mask2;
3037           mask2 = j == new_range.col0 ? mask2 + 4 : mask2;
3038           mask2 = j == new_range.coli ? mask2 + 8 : mask2;
3039           if (mask2 != mask1 || (mask2 == mask1 && state != GTK_STATE_SELECTED))
3040             {
3041               x = psppire_axis_start_pixel (sheet->haxis, j);
3042               y = psppire_axis_start_pixel (sheet->vaxis, i);
3043               width = psppire_axis_unit_size (sheet->haxis, j);
3044               height = psppire_axis_unit_size (sheet->vaxis, i);
3045               if (mask2 & 1)
3046                 gdk_draw_rectangle (sheet->sheet_window,
3047                                     sheet->xor_gc,
3048                                     TRUE,
3049                                     x + 1, y - 1,
3050                                     width, 3);
3051
3052
3053               if (mask2 & 2)
3054                 gdk_draw_rectangle (sheet->sheet_window,
3055                                     sheet->xor_gc,
3056                                     TRUE,
3057                                     x + 1, y + height - 1,
3058                                     width, 3);
3059
3060               if (mask2 & 4)
3061                 gdk_draw_rectangle (sheet->sheet_window,
3062                                     sheet->xor_gc,
3063                                     TRUE,
3064                                     x - 1, y + 1,
3065                                     3, height);
3066
3067
3068               if (mask2 & 8)
3069                 gdk_draw_rectangle (sheet->sheet_window,
3070                                     sheet->xor_gc,
3071                                     TRUE,
3072                                     x + width - 1, y + 1,
3073                                     3, height);
3074             }
3075         }
3076     }
3077
3078   *range = new_range;
3079 }
3080
3081
3082
3083 static void
3084 psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
3085 {
3086   GdkRectangle area;
3087
3088   rectangle_from_range (sheet, &new_range, &area);
3089
3090   gdk_draw_rectangle (sheet->sheet_window,
3091                       sheet->xor_gc,
3092                       FALSE,
3093                       area.x, area.y,
3094                       area.width, area.height);
3095 }
3096
3097
3098 static void
3099 psppire_sheet_real_select_range (PsppireSheet *sheet,
3100                              const PsppireSheetRange *range)
3101 {
3102   gint state;
3103
3104   g_return_if_fail (sheet != NULL);
3105
3106   if (range == NULL) range = &sheet->range;
3107
3108   memcpy (&sheet->range, range, sizeof (*range));
3109
3110   if (range->row0 < 0 || range->rowi < 0) return;
3111   if (range->col0 < 0 || range->coli < 0) return;
3112
3113   state = sheet->state;
3114
3115 #if 0
3116   if (range->coli != sheet->range.coli || range->col0 != sheet->range.col0 ||
3117       range->rowi != sheet->range.rowi || range->row0 != sheet->range.row0)
3118     {
3119       psppire_sheet_new_selection (sheet, &sheet->range);
3120     }
3121   else
3122     {
3123       psppire_sheet_range_draw_selection (sheet, sheet->range);
3124     }
3125 #endif
3126
3127   psppire_sheet_update_primary_selection (sheet);
3128
3129   g_signal_emit (sheet, sheet_signals[SELECT_RANGE], 0, &sheet->range);
3130 }
3131
3132
3133 void
3134 psppire_sheet_get_selected_range (PsppireSheet *sheet, PsppireSheetRange *range)
3135 {
3136   g_return_if_fail (sheet != NULL);
3137   *range = sheet->range;
3138 }
3139
3140
3141 void
3142 psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
3143 {
3144   g_return_if_fail (sheet != NULL);
3145
3146   if (range == NULL) range=&sheet->range;
3147
3148   if (range->row0 < 0 || range->rowi < 0) return;
3149   if (range->col0 < 0 || range->coli < 0) return;
3150
3151
3152   if (sheet->state != PSPPIRE_SHEET_NORMAL)
3153     psppire_sheet_real_unselect_range (sheet, NULL);
3154
3155   sheet->range.row0 = range->row0;
3156   sheet->range.rowi = range->rowi;
3157   sheet->range.col0 = range->col0;
3158   sheet->range.coli = range->coli;
3159   sheet->active_cell.row = range->row0;
3160   sheet->active_cell.col = range->col0;
3161   sheet->selection_cell.row = range->rowi;
3162   sheet->selection_cell.col = range->coli;
3163
3164   sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3165   psppire_sheet_real_select_range (sheet, NULL);
3166 }
3167
3168 void
3169 psppire_sheet_unselect_range (PsppireSheet *sheet)
3170 {
3171   if (! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
3172     return;
3173
3174   psppire_sheet_real_unselect_range (sheet, NULL);
3175   sheet->state = GTK_STATE_NORMAL;
3176
3177   change_active_cell (sheet,
3178                  sheet->active_cell.row, sheet->active_cell.col);
3179 }
3180
3181
3182 static void
3183 psppire_sheet_real_unselect_range (PsppireSheet *sheet,
3184                                const PsppireSheetRange *range)
3185 {
3186   g_return_if_fail (sheet != NULL);
3187   g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)));
3188
3189   if ( range == NULL)
3190     range = &sheet->range;
3191
3192   if (range->row0 < 0 || range->rowi < 0) return;
3193   if (range->col0 < 0 || range->coli < 0) return;
3194
3195   g_signal_emit (sheet, sheet_signals[SELECT_COLUMN], 0, -1);
3196   g_signal_emit (sheet, sheet_signals[SELECT_ROW], 0, -1);
3197
3198   sheet->range.row0 = -1;
3199   sheet->range.rowi = -1;
3200   sheet->range.col0 = -1;
3201   sheet->range.coli = -1;
3202 }
3203
3204
3205 static gint
3206 psppire_sheet_expose (GtkWidget *widget,
3207                   GdkEventExpose *event)
3208 {
3209   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3210
3211   g_return_val_if_fail (event != NULL, FALSE);
3212
3213   if (!GTK_WIDGET_DRAWABLE (widget))
3214     return FALSE;
3215
3216   /* exposure events on the sheet */
3217   if (event->window == sheet->row_title_window &&
3218       sheet->row_titles_visible)
3219     {
3220       draw_row_title_buttons_range (sheet,
3221                                     min_visible_row (sheet),
3222                                     max_visible_row (sheet));
3223     }
3224
3225   if (event->window == sheet->column_title_window &&
3226       sheet->column_titles_visible)
3227     {
3228       draw_column_title_buttons_range (sheet,
3229                                        min_visible_column (sheet),
3230                                        max_visible_column (sheet));
3231     }
3232
3233   if (event->window == sheet->sheet_window)
3234     {
3235       draw_sheet_region (sheet, event->region);
3236
3237 #if 0
3238       if (sheet->state != PSPPIRE_SHEET_NORMAL)
3239         {
3240           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
3241             psppire_sheet_range_draw (sheet, &sheet->range);
3242
3243           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
3244             psppire_sheet_range_draw (sheet, &sheet->drag_range);
3245
3246           if (psppire_sheet_range_isvisible (sheet, &sheet->range))
3247             psppire_sheet_range_draw_selection (sheet, sheet->range);
3248           if (PSPPIRE_SHEET_IN_RESIZE (sheet) || PSPPIRE_SHEET_IN_DRAG (sheet))
3249             draw_xor_rectangle (sheet, sheet->drag_range);
3250         }
3251 #endif
3252
3253       if ((!PSPPIRE_SHEET_IN_XDRAG (sheet)) && (!PSPPIRE_SHEET_IN_YDRAG (sheet)))
3254         {
3255           GdkRectangle rect;
3256           PsppireSheetRange range;
3257           range.row0 = range.rowi =  sheet->active_cell.row;
3258           range.col0 = range.coli =  sheet->active_cell.col;
3259
3260           rectangle_from_range (sheet, &range, &rect);
3261
3262           if (GDK_OVERLAP_RECTANGLE_OUT !=
3263               gdk_region_rect_in (event->region, &rect))
3264             {
3265               psppire_sheet_draw_active_cell (sheet);
3266             }
3267         }
3268
3269     }
3270
3271   (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
3272
3273   return FALSE;
3274 }
3275
3276
3277 static gboolean
3278 psppire_sheet_button_press (GtkWidget *widget,
3279                         GdkEventButton *event)
3280 {
3281   PsppireSheet *sheet;
3282   GdkModifierType mods;
3283   gint x, y;
3284   gint  row, column;
3285   gboolean veto;
3286
3287   g_return_val_if_fail (widget != NULL, FALSE);
3288   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
3289   g_return_val_if_fail (event != NULL, FALSE);
3290
3291   sheet = PSPPIRE_SHEET (widget);
3292
3293   /* Cancel any pending tooltips */
3294   if (sheet->motion_timer)
3295     {
3296       g_source_remove (sheet->motion_timer);
3297       sheet->motion_timer = 0;
3298     }
3299
3300   gtk_widget_get_pointer (widget, &x, &y);
3301   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3302
3303
3304   if (event->window == sheet->column_title_window)
3305     {
3306       sheet->x_drag = event->x;
3307       g_signal_emit (sheet,
3308                      sheet_signals[BUTTON_EVENT_COLUMN], 0,
3309                      column, event);
3310
3311       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
3312         {
3313           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
3314             g_signal_emit (sheet,
3315                            sheet_signals[DOUBLE_CLICK_COLUMN], 0, column);
3316         }
3317     }
3318   else if (event->window == sheet->row_title_window)
3319     {
3320       g_signal_emit (sheet,
3321                      sheet_signals[BUTTON_EVENT_ROW], 0,
3322                      row, event);
3323
3324       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3325         {
3326           if ( event->type == GDK_2BUTTON_PRESS && event->button == 1)
3327             g_signal_emit (sheet,
3328                            sheet_signals[DOUBLE_CLICK_ROW], 0, row);
3329         }
3330     }
3331
3332   gdk_window_get_pointer (widget->window, NULL, NULL, &mods);
3333
3334   if (! (mods & GDK_BUTTON1_MASK)) return TRUE;
3335
3336
3337   /* press on resize windows */
3338   if (event->window == sheet->column_title_window)
3339     {
3340       sheet->x_drag = event->x;
3341
3342       if (on_column_boundary (sheet, sheet->x_drag, &sheet->drag_cell.col))
3343         {
3344           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3345           gdk_pointer_grab (sheet->column_title_window, FALSE,
3346                             GDK_POINTER_MOTION_HINT_MASK |
3347                             GDK_BUTTON1_MOTION_MASK |
3348                             GDK_BUTTON_RELEASE_MASK,
3349                             NULL, NULL, event->time);
3350
3351           draw_xor_vline (sheet);
3352           return TRUE;
3353         }
3354     }
3355
3356   if (event->window == sheet->row_title_window)
3357     {
3358       sheet->y_drag = event->y;
3359
3360       if (on_row_boundary (sheet, sheet->y_drag, &sheet->drag_cell.row))
3361         {
3362           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3363           gdk_pointer_grab (sheet->row_title_window, FALSE,
3364                             GDK_POINTER_MOTION_HINT_MASK |
3365                             GDK_BUTTON1_MOTION_MASK |
3366                             GDK_BUTTON_RELEASE_MASK,
3367                             NULL, NULL, event->time);
3368
3369           draw_xor_hline (sheet);
3370           return TRUE;
3371         }
3372     }
3373
3374   /* the sheet itself does not handle other than single click events */
3375   if (event->type != GDK_BUTTON_PRESS) return FALSE;
3376
3377   /* selections on the sheet */
3378   if (event->window == sheet->sheet_window)
3379     {
3380       gtk_widget_get_pointer (widget, &x, &y);
3381       psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
3382       gdk_pointer_grab (sheet->sheet_window, FALSE,
3383                         GDK_POINTER_MOTION_HINT_MASK |
3384                         GDK_BUTTON1_MOTION_MASK |
3385                         GDK_BUTTON_RELEASE_MASK,
3386                         NULL, NULL, event->time);
3387       gtk_grab_add (GTK_WIDGET (sheet));
3388
3389       if (sheet->selection_mode != GTK_SELECTION_SINGLE &&
3390           sheet->selection_mode != GTK_SELECTION_NONE &&
3391           sheet->cursor_drag->type == GDK_SIZING &&
3392           !PSPPIRE_SHEET_IN_SELECTION (sheet) && !PSPPIRE_SHEET_IN_RESIZE (sheet))
3393         {
3394           if (sheet->state == GTK_STATE_NORMAL)
3395             {
3396               row = sheet->active_cell.row;
3397               column = sheet->active_cell.col;
3398               sheet->active_cell.row = row;
3399               sheet->active_cell.col = column;
3400               sheet->drag_range = sheet->range;
3401               sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3402               psppire_sheet_select_range (sheet, &sheet->drag_range);
3403             }
3404           sheet->x_drag = x;
3405           sheet->y_drag = y;
3406           if (row > sheet->range.rowi) row--;
3407           if (column > sheet->range.coli) column--;
3408           sheet->drag_cell.row = row;
3409           sheet->drag_cell.col = column;
3410           sheet->drag_range = sheet->range;
3411           draw_xor_rectangle (sheet, sheet->drag_range);
3412           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3413         }
3414       else if (sheet->cursor_drag->type == GDK_TOP_LEFT_ARROW &&
3415                !PSPPIRE_SHEET_IN_SELECTION (sheet)
3416                && ! PSPPIRE_SHEET_IN_DRAG (sheet)
3417                && sheet->active_cell.row >= 0
3418                && sheet->active_cell.col >= 0
3419                )
3420         {
3421           if (sheet->state == GTK_STATE_NORMAL)
3422             {
3423               row = sheet->active_cell.row;
3424               column = sheet->active_cell.col;
3425               sheet->active_cell.row = row;
3426               sheet->active_cell.col = column;
3427               sheet->drag_range = sheet->range;
3428               sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3429               psppire_sheet_select_range (sheet, &sheet->drag_range);
3430             }
3431           sheet->x_drag = x;
3432           sheet->y_drag = y;
3433           if (row < sheet->range.row0) row++;
3434           if (row > sheet->range.rowi) row--;
3435           if (column < sheet->range.col0) column++;
3436           if (column > sheet->range.coli) column--;
3437           sheet->drag_cell.row = row;
3438           sheet->drag_cell.col = column;
3439           sheet->drag_range = sheet->range;
3440           draw_xor_rectangle (sheet, sheet->drag_range);
3441           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3442         }
3443       else
3444         {
3445           veto = psppire_sheet_click_cell (sheet, row, column);
3446           if (veto) PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3447         }
3448     }
3449
3450   if (event->window == sheet->column_title_window)
3451     {
3452       gtk_widget_get_pointer (widget, &x, &y);
3453       if ( sheet->row_titles_visible)
3454         x -= sheet->row_title_area.width;
3455
3456       x += sheet->hadjustment->value;
3457
3458       column = column_from_xpixel (sheet, x);
3459
3460       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
3461         {
3462           veto = psppire_sheet_click_cell (sheet, -1, column);
3463           gtk_grab_add (GTK_WIDGET (sheet));
3464           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3465         }
3466     }
3467
3468   if (event->window == sheet->row_title_window)
3469     {
3470       gtk_widget_get_pointer (widget, &x, &y);
3471       if ( sheet->column_titles_visible)
3472         y -= sheet->column_title_area.height;
3473
3474       y += sheet->vadjustment->value;
3475
3476       row = row_from_ypixel (sheet, y);
3477       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
3478         {
3479           veto = psppire_sheet_click_cell (sheet, row, -1);
3480           gtk_grab_add (GTK_WIDGET (sheet));
3481           PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3482         }
3483     }
3484
3485   return TRUE;
3486 }
3487
3488 static gboolean
3489 psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
3490 {
3491   PsppireSheetCell cell;
3492   gboolean forbid_move;
3493
3494   cell.row = row;
3495   cell.col = column;
3496
3497   if (row >= psppire_axis_unit_count (sheet->vaxis)
3498       || column >= psppire_axis_unit_count (sheet->haxis))
3499     {
3500       return FALSE;
3501     }
3502
3503   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
3504                  &sheet->active_cell,
3505                  &cell,
3506                  &forbid_move);
3507
3508   if (forbid_move)
3509     {
3510       if (sheet->state == GTK_STATE_NORMAL)
3511         return FALSE;
3512
3513       row = sheet->active_cell.row;
3514       column = sheet->active_cell.col;
3515
3516       change_active_cell (sheet, row, column);
3517       return FALSE;
3518     }
3519
3520   if (row == -1 && column >= 0)
3521     {
3522       psppire_sheet_select_column (sheet, column);
3523       return TRUE;
3524     }
3525
3526   if (column == -1 && row >= 0)
3527     {
3528       psppire_sheet_select_row (sheet, row);
3529       return TRUE;
3530     }
3531
3532   if (row == -1 && column == -1)
3533     {
3534       sheet->range.row0 = 0;
3535       sheet->range.col0 = 0;
3536       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
3537       sheet->range.coli =
3538         psppire_axis_unit_count (sheet->haxis) - 1;
3539       sheet->active_cell.row = 0;
3540       sheet->active_cell.col = 0;
3541       psppire_sheet_select_range (sheet, NULL);
3542       return TRUE;
3543     }
3544
3545   if (sheet->state != PSPPIRE_SHEET_NORMAL)
3546     {
3547       sheet->state = PSPPIRE_SHEET_NORMAL;
3548       psppire_sheet_real_unselect_range (sheet, NULL);
3549     }
3550   else
3551     {
3552       change_active_cell (sheet, row, column);
3553     }
3554
3555   sheet->active_cell.row = row;
3556   sheet->active_cell.col = column;
3557   sheet->selection_cell.row = row;
3558   sheet->selection_cell.col = column;
3559   sheet->range.row0 = row;
3560   sheet->range.col0 = column;
3561   sheet->range.rowi = row;
3562   sheet->range.coli = column;
3563   sheet->state = PSPPIRE_SHEET_NORMAL;
3564   PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3565
3566   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
3567
3568   return TRUE;
3569 }
3570
3571 static gint
3572 psppire_sheet_button_release (GtkWidget *widget,
3573                           GdkEventButton *event)
3574 {
3575   GdkDisplay *display = gtk_widget_get_display (widget);
3576
3577   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3578
3579   /* release on resize windows */
3580   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3581     {
3582       gint width;
3583       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_XDRAG);
3584       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3585
3586       gdk_display_pointer_ungrab (display, event->time);
3587       draw_xor_vline (sheet);
3588
3589       width = event->x -
3590         psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)
3591         + sheet->hadjustment->value;
3592
3593       set_column_width (sheet, sheet->drag_cell.col, width);
3594
3595       return TRUE;
3596     }
3597
3598   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3599     {
3600       gint height;
3601       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_YDRAG);
3602       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3603
3604       gdk_display_pointer_ungrab (display, event->time);
3605       draw_xor_hline (sheet);
3606
3607       height = event->y -
3608         psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row) +
3609         sheet->vadjustment->value;
3610
3611       set_row_height (sheet, sheet->drag_cell.row, height);
3612
3613       return TRUE;
3614     }
3615
3616   if (PSPPIRE_SHEET_IN_DRAG (sheet))
3617     {
3618       PsppireSheetRange old_range;
3619       draw_xor_rectangle (sheet, sheet->drag_range);
3620       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
3621       gdk_display_pointer_ungrab (display, event->time);
3622
3623       psppire_sheet_real_unselect_range (sheet, NULL);
3624
3625       sheet->active_cell.row = sheet->active_cell.row +
3626         (sheet->drag_range.row0 - sheet->range.row0);
3627       sheet->active_cell.col = sheet->active_cell.col +
3628         (sheet->drag_range.col0 - sheet->range.col0);
3629       sheet->selection_cell.row = sheet->selection_cell.row +
3630         (sheet->drag_range.row0 - sheet->range.row0);
3631       sheet->selection_cell.col = sheet->selection_cell.col +
3632         (sheet->drag_range.col0 - sheet->range.col0);
3633       old_range = sheet->range;
3634       sheet->range = sheet->drag_range;
3635       sheet->drag_range = old_range;
3636       g_signal_emit (sheet, sheet_signals[MOVE_RANGE], 0,
3637                      &sheet->drag_range, &sheet->range);
3638       psppire_sheet_select_range (sheet, &sheet->range);
3639     }
3640
3641   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
3642     {
3643       PsppireSheetRange old_range;
3644       draw_xor_rectangle (sheet, sheet->drag_range);
3645       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
3646       gdk_display_pointer_ungrab (display, event->time);
3647
3648       psppire_sheet_real_unselect_range (sheet, NULL);
3649
3650       sheet->active_cell.row = sheet->active_cell.row +
3651         (sheet->drag_range.row0 - sheet->range.row0);
3652       sheet->active_cell.col = sheet->active_cell.col +
3653         (sheet->drag_range.col0 - sheet->range.col0);
3654       if (sheet->drag_range.row0 < sheet->range.row0)
3655         sheet->selection_cell.row = sheet->drag_range.row0;
3656       if (sheet->drag_range.rowi >= sheet->range.rowi)
3657         sheet->selection_cell.row = sheet->drag_range.rowi;
3658       if (sheet->drag_range.col0 < sheet->range.col0)
3659         sheet->selection_cell.col = sheet->drag_range.col0;
3660       if (sheet->drag_range.coli >= sheet->range.coli)
3661         sheet->selection_cell.col = sheet->drag_range.coli;
3662       old_range = sheet->range;
3663       sheet->range = sheet->drag_range;
3664       sheet->drag_range = old_range;
3665
3666       if (sheet->state == GTK_STATE_NORMAL) sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
3667       g_signal_emit (sheet, sheet_signals[RESIZE_RANGE], 0,
3668                      &sheet->drag_range, &sheet->range);
3669       psppire_sheet_select_range (sheet, &sheet->range);
3670     }
3671
3672   if (sheet->state == PSPPIRE_SHEET_NORMAL && PSPPIRE_SHEET_IN_SELECTION (sheet))
3673     {
3674       PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3675       gdk_display_pointer_ungrab (display, event->time);
3676       change_active_cell (sheet, sheet->active_cell.row,
3677                                sheet->active_cell.col);
3678     }
3679
3680   if (PSPPIRE_SHEET_IN_SELECTION)
3681     gdk_display_pointer_ungrab (display, event->time);
3682   gtk_grab_remove (GTK_WIDGET (sheet));
3683
3684   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
3685
3686   return TRUE;
3687 }
3688
3689 \f
3690
3691
3692
3693 /* Shamelessly lifted from gtktooltips */
3694 static gboolean
3695 psppire_sheet_subtitle_paint_window (GtkWidget *tip_window)
3696 {
3697   GtkRequisition req;
3698
3699   gtk_widget_size_request (tip_window, &req);
3700   gtk_paint_flat_box (tip_window->style, tip_window->window,
3701                       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
3702                       NULL, GTK_WIDGET(tip_window), "tooltip",
3703                       0, 0, req.width, req.height);
3704
3705   return FALSE;
3706 }
3707
3708 static void
3709 destroy_hover_window (PsppireSheetHoverTitle *h)
3710 {
3711   gtk_widget_destroy (h->window);
3712   g_free (h);
3713 }
3714
3715 static PsppireSheetHoverTitle *
3716 create_hover_window (void)
3717 {
3718   PsppireSheetHoverTitle *hw = g_malloc (sizeof (*hw));
3719
3720   hw->window = gtk_window_new (GTK_WINDOW_POPUP);
3721
3722 #if GTK_CHECK_VERSION (2, 9, 0)
3723   gtk_window_set_type_hint (GTK_WINDOW (hw->window),
3724                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
3725 #endif
3726
3727   gtk_widget_set_app_paintable (hw->window, TRUE);
3728   gtk_window_set_resizable (GTK_WINDOW (hw->window), FALSE);
3729   gtk_widget_set_name (hw->window, "gtk-tooltips");
3730   gtk_container_set_border_width (GTK_CONTAINER (hw->window), 4);
3731
3732   g_signal_connect (hw->window,
3733                     "expose_event",
3734                     G_CALLBACK (psppire_sheet_subtitle_paint_window),
3735                     NULL);
3736
3737   hw->label = gtk_label_new (NULL);
3738
3739
3740   gtk_label_set_line_wrap (GTK_LABEL (hw->label), TRUE);
3741   gtk_misc_set_alignment (GTK_MISC (hw->label), 0.5, 0.5);
3742
3743   gtk_container_add (GTK_CONTAINER (hw->window), hw->label);
3744
3745   gtk_widget_show (hw->label);
3746
3747   g_signal_connect (hw->window,
3748                     "destroy",
3749                     G_CALLBACK (gtk_widget_destroyed),
3750                     &hw->window);
3751
3752   return hw;
3753 }
3754
3755 #define HOVER_WINDOW_Y_OFFSET 2
3756
3757 static void
3758 show_subtitle (PsppireSheet *sheet, gint row, gint column, const gchar *subtitle)
3759 {
3760   gint x, y;
3761   gint px, py;
3762   gint width;
3763
3764   if ( ! subtitle )
3765     return;
3766
3767   gtk_label_set_text (GTK_LABEL (sheet->hover_window->label),
3768                       subtitle);
3769
3770
3771   sheet->hover_window->row = row;
3772   sheet->hover_window->column = column;
3773
3774   gdk_window_get_origin (GTK_WIDGET (sheet)->window, &x, &y);
3775
3776   gtk_widget_get_pointer (GTK_WIDGET (sheet), &px, &py);
3777
3778   gtk_widget_show (sheet->hover_window->window);
3779
3780   width = GTK_WIDGET (sheet->hover_window->label)->allocation.width;
3781
3782   if (row == -1 )
3783     {
3784       x += px;
3785       x -= width / 2;
3786       y += sheet->column_title_area.y;
3787       y += sheet->column_title_area.height;
3788       y += HOVER_WINDOW_Y_OFFSET;
3789     }
3790
3791   if ( column == -1 )
3792     {
3793       y += py;
3794       x += sheet->row_title_area.x;
3795       x += sheet->row_title_area.width * 2 / 3.0;
3796     }
3797
3798   gtk_window_move (GTK_WINDOW (sheet->hover_window->window),
3799                    x, y);
3800 }
3801
3802 static gboolean
3803 motion_timeout_callback (gpointer data)
3804 {
3805   PsppireSheet *sheet = PSPPIRE_SHEET (data);
3806   gint x, y;
3807   gint row, column;
3808
3809   gdk_threads_enter ();
3810   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
3811
3812   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
3813     {
3814       if (sheet->row_title_under && row >= 0)
3815         {
3816           gchar *text = psppire_sheet_model_get_row_subtitle (sheet->model, row);
3817
3818           show_subtitle (sheet, row, -1, text);
3819           g_free (text);
3820         }
3821
3822       if (sheet->column_title_under && column >= 0)
3823         {
3824           gchar *text = psppire_sheet_model_get_column_subtitle (sheet->model,
3825                                                            column);
3826
3827           show_subtitle (sheet, -1, column, text);
3828
3829           g_free (text);
3830         }
3831     }
3832
3833   gdk_threads_leave ();
3834   return FALSE;
3835 }
3836
3837 static gboolean
3838 psppire_sheet_motion (GtkWidget *widget,  GdkEventMotion *event)
3839 {
3840   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
3841   GdkModifierType mods;
3842   GdkCursorType new_cursor;
3843   gint x, y;
3844   gint row, column;
3845   GdkDisplay *display;
3846
3847   g_return_val_if_fail (event != NULL, FALSE);
3848
3849   display = gtk_widget_get_display (widget);
3850
3851   /* selections on the sheet */
3852   x = event->x;
3853   y = event->y;
3854
3855   if (!GTK_WIDGET_VISIBLE (sheet->hover_window->window))
3856     {
3857       if ( sheet->motion_timer > 0 )
3858         g_source_remove (sheet->motion_timer);
3859       sheet->motion_timer =
3860         g_timeout_add (TIMEOUT_HOVER, motion_timeout_callback, sheet);
3861     }
3862   else
3863     {
3864       gint row, column;
3865       gint wx, wy;
3866       gtk_widget_get_pointer (widget, &wx, &wy);
3867
3868       if ( psppire_sheet_get_pixel_info (sheet, wx, wy, &row, &column) )
3869         {
3870           if ( row != sheet->hover_window->row ||
3871                column != sheet->hover_window->column)
3872             {
3873               gtk_widget_hide (sheet->hover_window->window);
3874             }
3875         }
3876     }
3877
3878   if (event->window == sheet->column_title_window)
3879     {
3880       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3881           on_column_boundary (sheet, x, &column))
3882         {
3883           new_cursor = GDK_SB_H_DOUBLE_ARROW;
3884           if (new_cursor != sheet->cursor_drag->type)
3885             {
3886               gdk_cursor_unref (sheet->cursor_drag);
3887               sheet->cursor_drag =
3888                 gdk_cursor_new_for_display (display, new_cursor);
3889
3890               gdk_window_set_cursor (sheet->column_title_window,
3891                                      sheet->cursor_drag);
3892             }
3893         }
3894       else
3895         {
3896           new_cursor = GDK_TOP_LEFT_ARROW;
3897           if (!PSPPIRE_SHEET_IN_XDRAG (sheet) &&
3898               new_cursor != sheet->cursor_drag->type)
3899             {
3900               gdk_cursor_unref (sheet->cursor_drag);
3901               sheet->cursor_drag =
3902                 gdk_cursor_new_for_display (display, new_cursor);
3903               gdk_window_set_cursor (sheet->column_title_window,
3904                                      sheet->cursor_drag);
3905             }
3906         }
3907     }
3908   else if (event->window == sheet->row_title_window)
3909     {
3910       if (!PSPPIRE_SHEET_IN_SELECTION (sheet) &&
3911           on_row_boundary (sheet, y, &row))
3912         {
3913           new_cursor = GDK_SB_V_DOUBLE_ARROW;
3914           if (new_cursor != sheet->cursor_drag->type)
3915             {
3916               gdk_cursor_unref (sheet->cursor_drag);
3917               sheet->cursor_drag =
3918                 gdk_cursor_new_for_display (display, new_cursor);
3919               gdk_window_set_cursor (sheet->row_title_window,
3920                                      sheet->cursor_drag);
3921             }
3922         }
3923       else
3924         {
3925           new_cursor = GDK_TOP_LEFT_ARROW;
3926           if (!PSPPIRE_SHEET_IN_YDRAG (sheet) &&
3927               new_cursor != sheet->cursor_drag->type)
3928             {
3929               gdk_cursor_unref (sheet->cursor_drag);
3930               sheet->cursor_drag =
3931                 gdk_cursor_new_for_display (display, new_cursor);
3932               gdk_window_set_cursor (sheet->row_title_window,
3933                                      sheet->cursor_drag);
3934             }
3935         }
3936     }
3937
3938   new_cursor = GDK_PLUS;
3939   if ( event->window == sheet->sheet_window &&
3940        !POSSIBLE_DRAG (sheet, x, y, &row, &column) &&
3941        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3942        !POSSIBLE_RESIZE (sheet, x, y, &row, &column) &&
3943        !PSPPIRE_SHEET_IN_RESIZE (sheet) &&
3944        new_cursor != sheet->cursor_drag->type)
3945     {
3946       gdk_cursor_unref (sheet->cursor_drag);
3947       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
3948       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3949     }
3950
3951   new_cursor = GDK_TOP_LEFT_ARROW;
3952   if ( event->window == sheet->sheet_window &&
3953        ! (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3954           PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3955        (POSSIBLE_DRAG (sheet, x, y, &row, &column) ||
3956         PSPPIRE_SHEET_IN_DRAG (sheet)) &&
3957        new_cursor != sheet->cursor_drag->type)
3958     {
3959       gdk_cursor_unref (sheet->cursor_drag);
3960       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_ARROW);
3961       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3962     }
3963
3964   new_cursor = GDK_SIZING;
3965   if ( event->window == sheet->sheet_window &&
3966        sheet->selection_mode != GTK_SELECTION_NONE &&
3967        !PSPPIRE_SHEET_IN_DRAG (sheet) &&
3968        (POSSIBLE_RESIZE (sheet, x, y, &row, &column) ||
3969         PSPPIRE_SHEET_IN_RESIZE (sheet)) &&
3970        new_cursor != sheet->cursor_drag->type)
3971     {
3972       gdk_cursor_unref (sheet->cursor_drag);
3973       sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_SIZING);
3974       gdk_window_set_cursor (sheet->sheet_window, sheet->cursor_drag);
3975     }
3976
3977
3978   gdk_window_get_pointer (widget->window, &x, &y, &mods);
3979   if (! (mods & GDK_BUTTON1_MASK)) return FALSE;
3980
3981   if (PSPPIRE_SHEET_IN_XDRAG (sheet))
3982     {
3983       if (event->x != sheet->x_drag)
3984         {
3985           draw_xor_vline (sheet);
3986           sheet->x_drag = event->x;
3987           draw_xor_vline (sheet);
3988         }
3989
3990       return TRUE;
3991     }
3992
3993   if (PSPPIRE_SHEET_IN_YDRAG (sheet))
3994     {
3995       if (event->y != sheet->y_drag)
3996         {
3997           draw_xor_hline (sheet);
3998           sheet->y_drag = event->y;
3999           draw_xor_hline (sheet);
4000         }
4001
4002       return TRUE;
4003     }
4004
4005   if (PSPPIRE_SHEET_IN_DRAG (sheet))
4006     {
4007       PsppireSheetRange aux;
4008       column = column_from_xpixel (sheet, x)- sheet->drag_cell.col;
4009       row = row_from_ypixel (sheet, y) - sheet->drag_cell.row;
4010       if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
4011       if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
4012       sheet->x_drag = x;
4013       sheet->y_drag = y;
4014       aux = sheet->range;
4015       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
4016           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
4017         {
4018           aux = sheet->drag_range;
4019           sheet->drag_range.row0 = sheet->range.row0 + row;
4020           sheet->drag_range.col0 = sheet->range.col0 + column;
4021           sheet->drag_range.rowi = sheet->range.rowi + row;
4022           sheet->drag_range.coli = sheet->range.coli + column;
4023           if (aux.row0 != sheet->drag_range.row0 ||
4024               aux.col0 != sheet->drag_range.col0)
4025             {
4026               draw_xor_rectangle (sheet, aux);
4027               draw_xor_rectangle (sheet, sheet->drag_range);
4028             }
4029         }
4030       return TRUE;
4031     }
4032
4033   if (PSPPIRE_SHEET_IN_RESIZE (sheet))
4034     {
4035       PsppireSheetRange aux;
4036       gint v_h, current_col, current_row, col_threshold, row_threshold;
4037       v_h = 1;
4038       if (abs (x - psppire_axis_start_pixel (sheet->haxis, sheet->drag_cell.col)) >
4039           abs (y - psppire_axis_start_pixel (sheet->vaxis, sheet->drag_cell.row))) v_h = 2;
4040
4041       current_col = column_from_xpixel (sheet, x);
4042       current_row = row_from_ypixel (sheet, y);
4043       column = current_col - sheet->drag_cell.col;
4044       row = current_row - sheet->drag_cell.row;
4045
4046       /*use half of column width resp. row height as threshold to
4047         expand selection*/
4048       col_threshold = psppire_axis_start_pixel (sheet->haxis, current_col) +
4049         psppire_axis_unit_size (sheet->haxis, current_col) / 2;
4050       if (column > 0)
4051         {
4052           if (x < col_threshold)
4053             column -= 1;
4054         }
4055       else if (column < 0)
4056         {
4057           if (x > col_threshold)
4058             column +=1;
4059         }
4060       row_threshold = psppire_axis_start_pixel (sheet->vaxis, current_row) +
4061         psppire_axis_unit_size (sheet->vaxis, current_row)/2;
4062       if (row > 0)
4063         {
4064           if (y < row_threshold)
4065             row -= 1;
4066         }
4067       else if (row < 0)
4068         {
4069           if (y > row_threshold)
4070             row +=1;
4071         }
4072
4073       if (sheet->state == PSPPIRE_SHEET_COLUMN_SELECTED) row = 0;
4074       if (sheet->state == PSPPIRE_SHEET_ROW_SELECTED) column = 0;
4075       sheet->x_drag = x;
4076       sheet->y_drag = y;
4077       aux = sheet->range;
4078
4079       if (v_h == 1)
4080         column = 0;
4081       else
4082         row = 0;
4083
4084       if (aux.row0 + row >= 0 && aux.rowi + row < psppire_axis_unit_count (sheet->vaxis) &&
4085           aux.col0 + column >= 0 && aux.coli + column < psppire_axis_unit_count (sheet->haxis))
4086         {
4087           aux = sheet->drag_range;
4088           sheet->drag_range = sheet->range;
4089
4090           if (row < 0) sheet->drag_range.row0 = sheet->range.row0 + row;
4091           if (row > 0) sheet->drag_range.rowi = sheet->range.rowi + row;
4092           if (column < 0) sheet->drag_range.col0 = sheet->range.col0 + column;
4093           if (column > 0) sheet->drag_range.coli = sheet->range.coli + column;
4094
4095           if (aux.row0 != sheet->drag_range.row0 ||
4096               aux.rowi != sheet->drag_range.rowi ||
4097               aux.col0 != sheet->drag_range.col0 ||
4098               aux.coli != sheet->drag_range.coli)
4099             {
4100               draw_xor_rectangle (sheet, aux);
4101               draw_xor_rectangle (sheet, sheet->drag_range);
4102             }
4103         }
4104       return TRUE;
4105     }
4106
4107   psppire_sheet_get_pixel_info (sheet, x, y, &row, &column);
4108
4109   if (sheet->state == PSPPIRE_SHEET_NORMAL && row == sheet->active_cell.row &&
4110       column == sheet->active_cell.col) return TRUE;
4111
4112   if (PSPPIRE_SHEET_IN_SELECTION (sheet) && mods&GDK_BUTTON1_MASK)
4113     psppire_sheet_extend_selection (sheet, row, column);
4114
4115   return TRUE;
4116 }
4117
4118 static gboolean
4119 psppire_sheet_crossing_notify (GtkWidget *widget,
4120                            GdkEventCrossing *event)
4121 {
4122   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
4123
4124   if (event->window == sheet->column_title_window)
4125     sheet->column_title_under = event->type == GDK_ENTER_NOTIFY;
4126   else if (event->window == sheet->row_title_window)
4127     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
4128
4129   return TRUE;
4130 }
4131
4132
4133 static gboolean
4134 psppire_sheet_focus_in (GtkWidget     *w,
4135                         GdkEventFocus *event)
4136 {
4137   PsppireSheet *sheet = PSPPIRE_SHEET (w);
4138
4139   gtk_widget_grab_focus (sheet->entry_widget);
4140
4141   return TRUE;
4142 }
4143
4144
4145 static void
4146 psppire_sheet_extend_selection (PsppireSheet *sheet, gint row, gint column)
4147 {
4148   PsppireSheetRange range;
4149   gint state;
4150   gint r, c;
4151
4152   if (row == sheet->selection_cell.row && column == sheet->selection_cell.col)
4153     return;
4154
4155   if (sheet->selection_mode == GTK_SELECTION_SINGLE) return;
4156
4157   gtk_widget_grab_focus (GTK_WIDGET (sheet));
4158
4159   if (PSPPIRE_SHEET_IN_DRAG (sheet)) return;
4160
4161   state = sheet->state;
4162
4163   switch (sheet->state)
4164     {
4165     case PSPPIRE_SHEET_ROW_SELECTED:
4166       column = psppire_axis_unit_count (sheet->haxis) - 1;
4167       break;
4168     case PSPPIRE_SHEET_COLUMN_SELECTED:
4169       row = psppire_axis_unit_count (sheet->vaxis) - 1;
4170       break;
4171     case PSPPIRE_SHEET_NORMAL:
4172       sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
4173       r = sheet->active_cell.row;
4174       c = sheet->active_cell.col;
4175       sheet->range.col0 = c;
4176       sheet->range.row0 = r;
4177       sheet->range.coli = c;
4178       sheet->range.rowi = r;
4179       psppire_sheet_range_draw_selection (sheet, sheet->range);
4180     case PSPPIRE_SHEET_RANGE_SELECTED:
4181       sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
4182     }
4183
4184   sheet->selection_cell.row = row;
4185   sheet->selection_cell.col = column;
4186
4187   range.col0 = MIN (column, sheet->active_cell.col);
4188   range.coli = MAX (column, sheet->active_cell.col);
4189   range.row0 = MIN (row, sheet->active_cell.row);
4190   range.rowi = MAX (row, sheet->active_cell.row);
4191
4192   if (range.row0 != sheet->range.row0 || range.rowi != sheet->range.rowi ||
4193       range.col0 != sheet->range.col0 || range.coli != sheet->range.coli ||
4194       state == PSPPIRE_SHEET_NORMAL)
4195     psppire_sheet_real_select_range (sheet, &range);
4196
4197 }
4198
4199 static gint
4200 psppire_sheet_entry_key_press (GtkWidget *widget,
4201                            GdkEventKey *key)
4202 {
4203   gboolean focus;
4204   g_signal_emit_by_name (widget, "key_press_event", key, &focus);
4205   return focus;
4206 }
4207
4208
4209 /* Number of rows in a step-increment */
4210 #define ROWS_PER_STEP 1
4211
4212
4213 static void
4214 page_vertical (PsppireSheet *sheet, GtkScrollType dir)
4215 {
4216   gint old_row = sheet->active_cell.row ;
4217   glong vpixel = psppire_axis_start_pixel (sheet->vaxis, old_row);
4218
4219   gint new_row;
4220
4221   vpixel -= psppire_axis_start_pixel (sheet->vaxis,
4222                                      min_visible_row (sheet));
4223
4224   switch ( dir)
4225     {
4226     case GTK_SCROLL_PAGE_DOWN:
4227       gtk_adjustment_set_value (sheet->vadjustment,
4228                                 sheet->vadjustment->value +
4229                                 sheet->vadjustment->page_increment);
4230       break;
4231     case GTK_SCROLL_PAGE_UP:
4232       gtk_adjustment_set_value (sheet->vadjustment,
4233                                 sheet->vadjustment->value -
4234                                 sheet->vadjustment->page_increment);
4235
4236       break;
4237     default:
4238       g_assert_not_reached ();
4239       break;
4240     }
4241
4242
4243   vpixel += psppire_axis_start_pixel (sheet->vaxis,
4244                                      min_visible_row (sheet));
4245
4246   new_row =  row_from_ypixel (sheet, vpixel);
4247
4248   change_active_cell (sheet, new_row,
4249                            sheet->active_cell.col);
4250 }
4251
4252
4253 static void
4254 step_sheet (PsppireSheet *sheet, GtkScrollType dir)
4255 {
4256   gint current_row = sheet->active_cell.row;
4257   gint current_col = sheet->active_cell.col;
4258   PsppireSheetCell new_cell ;
4259   gboolean forbidden = FALSE;
4260
4261   new_cell.row = current_row;
4262   new_cell.col = current_col;
4263
4264   switch ( dir)
4265     {
4266     case GTK_SCROLL_STEP_DOWN:
4267       new_cell.row++;
4268       break;
4269     case GTK_SCROLL_STEP_UP:
4270       new_cell.row--;
4271       break;
4272     case GTK_SCROLL_STEP_RIGHT:
4273       new_cell.col++;
4274       break;
4275     case GTK_SCROLL_STEP_LEFT:
4276       new_cell.col--;
4277       break;
4278     case GTK_SCROLL_STEP_FORWARD:
4279       new_cell.col++;
4280       if (new_cell.col >=
4281           psppire_sheet_model_get_column_count (sheet->model))
4282         {
4283           new_cell.col = 0;
4284           new_cell.row++;
4285         }
4286       break;
4287     case GTK_SCROLL_STEP_BACKWARD:
4288       new_cell.col--;
4289       if (new_cell.col < 0)
4290         {
4291           new_cell.col =
4292             psppire_sheet_model_get_column_count (sheet->model) - 1;
4293           new_cell.row--;
4294         }
4295       break;
4296     default:
4297       g_assert_not_reached ();
4298       break;
4299     }
4300
4301   g_signal_emit (sheet, sheet_signals[TRAVERSE], 0,
4302                  &sheet->active_cell,
4303                  &new_cell,
4304                  &forbidden);
4305
4306   if (forbidden)
4307     return;
4308
4309
4310   maximize_int (&new_cell.row, 0);
4311   maximize_int (&new_cell.col, 0);
4312
4313   minimize_int (&new_cell.row,
4314                 psppire_axis_unit_count (sheet->vaxis) - 1);
4315
4316   minimize_int (&new_cell.col,
4317                 psppire_axis_unit_count (sheet->haxis) - 1);
4318
4319   change_active_cell (sheet, new_cell.row, new_cell.col);
4320
4321
4322   if ( new_cell.col > max_fully_visible_column (sheet))
4323     {
4324       glong hpos  =
4325         psppire_axis_start_pixel (sheet->haxis,
4326                                     new_cell.col + 1);
4327       hpos -= sheet->hadjustment->page_size;
4328
4329       gtk_adjustment_set_value (sheet->hadjustment,
4330                                 hpos);
4331     }
4332   else if ( new_cell.col < min_fully_visible_column (sheet))
4333     {
4334       glong hpos  =
4335         psppire_axis_start_pixel (sheet->haxis,
4336                                     new_cell.col);
4337
4338       gtk_adjustment_set_value (sheet->hadjustment,
4339                                 hpos);
4340     }
4341
4342
4343   if ( new_cell.row > max_fully_visible_row (sheet))
4344     {
4345       glong vpos  =
4346         psppire_axis_start_pixel (sheet->vaxis,
4347                                     new_cell.row + 1);
4348       vpos -= sheet->vadjustment->page_size;
4349
4350       gtk_adjustment_set_value (sheet->vadjustment,
4351                                 vpos);
4352     }
4353   else if ( new_cell.row < min_fully_visible_row (sheet))
4354     {
4355       glong vpos  =
4356         psppire_axis_start_pixel (sheet->vaxis,
4357                                     new_cell.row);
4358
4359       gtk_adjustment_set_value (sheet->vadjustment,
4360                                 vpos);
4361     }
4362
4363   gtk_widget_grab_focus (GTK_WIDGET (sheet->entry_widget));
4364 }
4365
4366
4367 static gboolean
4368 psppire_sheet_key_press (GtkWidget *widget,
4369                      GdkEventKey *key)
4370 {
4371   PsppireSheet *sheet = PSPPIRE_SHEET (widget);
4372
4373   PSPPIRE_SHEET_UNSET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
4374
4375   switch (key->keyval)
4376     {
4377     case GDK_Tab:
4378       step_sheet (sheet, GTK_SCROLL_STEP_FORWARD);
4379       break;
4380     case GDK_Right:
4381       step_sheet (sheet, GTK_SCROLL_STEP_RIGHT);
4382       break;
4383     case GDK_ISO_Left_Tab:
4384       step_sheet (sheet, GTK_SCROLL_STEP_BACKWARD);
4385       break;
4386     case GDK_Left:
4387       step_sheet (sheet, GTK_SCROLL_STEP_LEFT);
4388       break;
4389     case GDK_Return:
4390     case GDK_Down:
4391       step_sheet (sheet, GTK_SCROLL_STEP_DOWN);
4392       break;
4393     case GDK_Up:
4394       step_sheet (sheet, GTK_SCROLL_STEP_UP);
4395       break;
4396
4397     case GDK_Page_Down:
4398       page_vertical (sheet, GTK_SCROLL_PAGE_DOWN);
4399       break;
4400     case GDK_Page_Up:
4401       page_vertical (sheet, GTK_SCROLL_PAGE_UP);
4402       break;
4403
4404     case GDK_Home:
4405       gtk_adjustment_set_value (sheet->vadjustment,
4406                                 sheet->vadjustment->lower);
4407
4408       change_active_cell (sheet,  0,
4409                                sheet->active_cell.col);
4410
4411       break;
4412
4413     case GDK_End:
4414       gtk_adjustment_set_value (sheet->vadjustment,
4415                                 sheet->vadjustment->upper -
4416                                 sheet->vadjustment->page_size -
4417                                 sheet->vadjustment->page_increment);
4418
4419       /*
4420         change_active_cellx (sheet,
4421         psppire_axis_unit_count (sheet->vaxis) - 1,
4422         sheet->active_cell.col);
4423       */
4424       break;
4425     case GDK_Delete:
4426       psppire_sheet_real_cell_clear (sheet, sheet->active_cell.row, sheet->active_cell.col);
4427       break;
4428     default:
4429       return FALSE;
4430       break;
4431     }
4432
4433   return TRUE;
4434 }
4435
4436 static void
4437 psppire_sheet_size_request (GtkWidget *widget,
4438                         GtkRequisition *requisition)
4439 {
4440   PsppireSheet *sheet;
4441
4442   g_return_if_fail (widget != NULL);
4443   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
4444   g_return_if_fail (requisition != NULL);
4445
4446   sheet = PSPPIRE_SHEET (widget);
4447
4448   requisition->width = 3 * DEFAULT_COLUMN_WIDTH;
4449   requisition->height = 3 * DEFAULT_ROW_HEIGHT;
4450
4451   /* compute the size of the column title area */
4452   if (sheet->column_titles_visible)
4453     requisition->height += sheet->column_title_area.height;
4454
4455   /* compute the size of the row title area */
4456   if (sheet->row_titles_visible)
4457     requisition->width += sheet->row_title_area.width;
4458 }
4459
4460
4461 static void
4462 psppire_sheet_size_allocate (GtkWidget *widget,
4463                          GtkAllocation *allocation)
4464 {
4465   PsppireSheet *sheet;
4466   GtkAllocation sheet_allocation;
4467   gint border_width;
4468
4469   g_return_if_fail (widget != NULL);
4470   g_return_if_fail (PSPPIRE_IS_SHEET (widget));
4471   g_return_if_fail (allocation != NULL);
4472
4473   sheet = PSPPIRE_SHEET (widget);
4474   widget->allocation = *allocation;
4475   border_width = GTK_CONTAINER (widget)->border_width;
4476
4477   if (GTK_WIDGET_REALIZED (widget))
4478     gdk_window_move_resize (widget->window,
4479                             allocation->x + border_width,
4480                             allocation->y + border_width,
4481                             allocation->width - 2 * border_width,
4482                             allocation->height - 2 * border_width);
4483
4484   sheet_allocation.x = 0;
4485   sheet_allocation.y = 0;
4486   sheet_allocation.width = allocation->width - 2 * border_width;
4487   sheet_allocation.height = allocation->height - 2 * border_width;
4488
4489   if (GTK_WIDGET_REALIZED (widget))
4490     gdk_window_move_resize (sheet->sheet_window,
4491                             sheet_allocation.x,
4492                             sheet_allocation.y,
4493                             sheet_allocation.width,
4494                             sheet_allocation.height);
4495
4496   /* position the window which holds the column title buttons */
4497   sheet->column_title_area.x = 0;
4498   sheet->column_title_area.y = 0;
4499   sheet->column_title_area.width = sheet_allocation.width ;
4500
4501
4502   /* position the window which holds the row title buttons */
4503   sheet->row_title_area.x = 0;
4504   sheet->row_title_area.y = 0;
4505   sheet->row_title_area.height = sheet_allocation.height;
4506
4507   if (sheet->row_titles_visible)
4508     sheet->column_title_area.x += sheet->row_title_area.width;
4509
4510   if (sheet->column_titles_visible)
4511     sheet->row_title_area.y += sheet->column_title_area.height;
4512
4513   if (GTK_WIDGET_REALIZED (widget) && sheet->column_titles_visible)
4514     gdk_window_move_resize (sheet->column_title_window,
4515                             sheet->column_title_area.x,
4516                             sheet->column_title_area.y,
4517                             sheet->column_title_area.width,
4518                             sheet->column_title_area.height);
4519
4520
4521   if (GTK_WIDGET_REALIZED (widget) && sheet->row_titles_visible)
4522     gdk_window_move_resize (sheet->row_title_window,
4523                             sheet->row_title_area.x,
4524                             sheet->row_title_area.y,
4525                             sheet->row_title_area.width,
4526                             sheet->row_title_area.height);
4527
4528   size_allocate_global_button (sheet);
4529
4530   if (sheet->haxis)
4531     {
4532       gint width = sheet->column_title_area.width;
4533
4534       if ( sheet->row_titles_visible)
4535         width -= sheet->row_title_area.width;
4536
4537       g_object_set (sheet->haxis,
4538                     "minimum-extent", width,
4539                     NULL);
4540     }
4541
4542
4543   if (sheet->vaxis)
4544     {
4545       gint height = sheet->row_title_area.height;
4546
4547       if ( sheet->column_titles_visible)
4548         height -= sheet->column_title_area.height;
4549
4550       g_object_set (sheet->vaxis,
4551                     "minimum-extent", height,
4552                     NULL);
4553     }
4554
4555
4556   /* set the scrollbars adjustments */
4557   adjust_scrollbars (sheet);
4558 }
4559
4560 static void
4561 draw_column_title_buttons (PsppireSheet *sheet)
4562 {
4563   gint x, width;
4564
4565   if (!sheet->column_titles_visible) return;
4566   if (!GTK_WIDGET_REALIZED (sheet))
4567     return;
4568
4569   gdk_drawable_get_size (sheet->sheet_window, &width, NULL);
4570   x = 0;
4571
4572   if (sheet->row_titles_visible)
4573     {
4574       x = sheet->row_title_area.width;
4575     }
4576
4577   if (sheet->column_title_area.width != width || sheet->column_title_area.x != x)
4578     {
4579       sheet->column_title_area.width = width;
4580       sheet->column_title_area.x = x;
4581       gdk_window_move_resize (sheet->column_title_window,
4582                               sheet->column_title_area.x,
4583                               sheet->column_title_area.y,
4584                               sheet->column_title_area.width,
4585                               sheet->column_title_area.height);
4586     }
4587
4588   if (max_visible_column (sheet) ==
4589       psppire_axis_unit_count (sheet->haxis) - 1)
4590     gdk_window_clear_area (sheet->column_title_window,
4591                            0, 0,
4592                            sheet->column_title_area.width,
4593                            sheet->column_title_area.height);
4594
4595   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4596
4597   draw_column_title_buttons_range (sheet, min_visible_column (sheet), 
4598                                    max_visible_column (sheet));
4599 }
4600
4601 static void
4602 draw_row_title_buttons (PsppireSheet *sheet)
4603 {
4604   gint y = 0;
4605   gint height;
4606
4607   if (!sheet->row_titles_visible) return;
4608   if (!GTK_WIDGET_REALIZED (sheet))
4609     return;
4610
4611   gdk_drawable_get_size (sheet->sheet_window, NULL, &height);
4612
4613   if (sheet->column_titles_visible)
4614     {
4615       y = sheet->column_title_area.height;
4616     }
4617
4618   if (sheet->row_title_area.height != height || sheet->row_title_area.y != y)
4619     {
4620       sheet->row_title_area.y = y;
4621       sheet->row_title_area.height = height;
4622       gdk_window_move_resize (sheet->row_title_window,
4623                               sheet->row_title_area.x,
4624                               sheet->row_title_area.y,
4625                               sheet->row_title_area.width,
4626                               sheet->row_title_area.height);
4627     }
4628
4629   if (max_visible_row (sheet) == psppire_axis_unit_count (sheet->vaxis) - 1)
4630     gdk_window_clear_area (sheet->row_title_window,
4631                            0, 0,
4632                            sheet->row_title_area.width,
4633                            sheet->row_title_area.height);
4634
4635   if (!GTK_WIDGET_DRAWABLE (sheet)) return;
4636
4637   draw_row_title_buttons_range (sheet, min_visible_row (sheet),
4638                                 max_visible_row (sheet));
4639 }
4640
4641
4642 static void
4643 psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
4644 {
4645   GtkAllocation entry_alloc;
4646   PsppireSheetCellAttr attributes = { 0 };
4647   GtkEntry *sheet_entry;
4648
4649   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4650   if (!GTK_WIDGET_MAPPED (GTK_WIDGET (sheet))) return;
4651
4652   sheet_entry = psppire_sheet_get_entry (sheet);
4653
4654   if ( ! psppire_sheet_get_attributes (sheet, sheet->active_cell.row,
4655                                    sheet->active_cell.col,
4656                                    &attributes) )
4657     return ;
4658
4659   if ( GTK_WIDGET_REALIZED (sheet->entry_widget) )
4660     {
4661       GtkStyle *style = GTK_WIDGET (sheet_entry)->style;
4662
4663       style->bg[GTK_STATE_NORMAL] = attributes.background;
4664       style->fg[GTK_STATE_NORMAL] = attributes.foreground;
4665       style->text[GTK_STATE_NORMAL] = attributes.foreground;
4666       style->bg[GTK_STATE_ACTIVE] = attributes.background;
4667       style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
4668       style->text[GTK_STATE_ACTIVE] = attributes.foreground;
4669     }
4670
4671   rectangle_from_cell (sheet, sheet->active_cell.row,
4672                        sheet->active_cell.col, &entry_alloc);
4673
4674   entry_alloc.width -= BORDER_WIDTH ;
4675   entry_alloc.height -= BORDER_WIDTH ;
4676   entry_alloc.x += DIV_RND_UP (BORDER_WIDTH, 2);
4677   entry_alloc.y += DIV_RND_UP (BORDER_WIDTH, 2);
4678
4679
4680   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
4681                                entry_alloc.height);
4682   gtk_widget_size_allocate (sheet->entry_widget, &entry_alloc);
4683 }
4684
4685
4686 /* Copy the sheet's font to the entry widget */
4687 static void
4688 set_entry_widget_font (PsppireSheet *sheet)
4689 {
4690   GtkRcStyle *style = gtk_widget_get_modifier_style (sheet->entry_widget);
4691
4692   pango_font_description_free (style->font_desc);
4693   style->font_desc = pango_font_description_copy (GTK_WIDGET (sheet)->style->font_desc);
4694
4695   gtk_widget_modify_style (sheet->entry_widget, style);
4696 }
4697
4698 static void
4699 create_sheet_entry (PsppireSheet *sheet)
4700 {
4701   if (sheet->entry_widget)
4702     {
4703       gtk_widget_unparent (sheet->entry_widget);
4704     }
4705
4706   sheet->entry_widget = g_object_new (sheet->entry_type, NULL);
4707   g_object_ref_sink (sheet->entry_widget);
4708
4709   gtk_widget_size_request (sheet->entry_widget, NULL);
4710
4711   if ( GTK_IS_ENTRY (sheet->entry_widget))
4712     {
4713       g_object_set (sheet->entry_widget,
4714                     "has-frame", FALSE,
4715                     NULL);
4716     }
4717
4718   if (GTK_WIDGET_REALIZED (sheet))
4719     {
4720       gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
4721       gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
4722       gtk_widget_realize (sheet->entry_widget);
4723     }
4724
4725   g_signal_connect_swapped (sheet->entry_widget, "key_press_event",
4726                             G_CALLBACK (psppire_sheet_entry_key_press),
4727                             sheet);
4728
4729   set_entry_widget_font (sheet);
4730
4731   gtk_widget_show (sheet->entry_widget);
4732 }
4733
4734
4735 /* Finds the last child widget that happens to be of type GtkEntry */
4736 static void
4737 find_entry (GtkWidget *w, gpointer user_data)
4738 {
4739   GtkWidget **entry = user_data;
4740   if ( GTK_IS_ENTRY (w))
4741     {
4742       *entry = w;
4743     }
4744 }
4745
4746
4747 GtkEntry *
4748 psppire_sheet_get_entry (PsppireSheet *sheet)
4749 {
4750   GtkWidget *w = sheet->entry_widget;
4751
4752   g_return_val_if_fail (sheet != NULL, NULL);
4753   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
4754   g_return_val_if_fail (sheet->entry_widget != NULL, NULL);
4755
4756   while (! GTK_IS_ENTRY (w))
4757     {
4758       GtkWidget *entry = NULL;
4759
4760       if (GTK_IS_CONTAINER (w))
4761         {
4762           gtk_container_forall (GTK_CONTAINER (w), find_entry, &entry);
4763
4764           if (NULL == entry)
4765             break;
4766
4767           w = entry;
4768         }
4769     }
4770
4771   return GTK_ENTRY (w);
4772 }
4773
4774
4775 static void
4776 draw_button (PsppireSheet *sheet, GdkWindow *window,
4777                        PsppireSheetButton *button, gboolean is_sensitive,
4778                        GdkRectangle allocation)
4779 {
4780   GtkShadowType shadow_type;
4781   gint text_width = 0, text_height = 0;
4782   PangoAlignment align = PANGO_ALIGN_LEFT;
4783
4784   gboolean rtl ;
4785
4786   gint state = 0;
4787
4788   g_return_if_fail (sheet != NULL);
4789   g_return_if_fail (button != NULL);
4790
4791
4792   rtl = gtk_widget_get_direction (GTK_WIDGET (sheet)) == GTK_TEXT_DIR_RTL;
4793
4794   gdk_window_clear_area (window,
4795                          allocation.x, allocation.y,
4796                          allocation.width, allocation.height);
4797
4798   gtk_widget_ensure_style (sheet->button);
4799
4800   gtk_paint_box (sheet->button->style, window,
4801                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
4802                  &allocation,
4803                  GTK_WIDGET (sheet->button),
4804                  NULL,
4805                  allocation.x, allocation.y,
4806                  allocation.width, allocation.height);
4807
4808   state = button->state;
4809   if (!is_sensitive) state = GTK_STATE_INSENSITIVE;
4810
4811   if (state == GTK_STATE_ACTIVE)
4812     shadow_type = GTK_SHADOW_IN;
4813   else
4814     shadow_type = GTK_SHADOW_OUT;
4815
4816   if (state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
4817     gtk_paint_box (sheet->button->style, window,
4818                    button->state, shadow_type,
4819                    &allocation, GTK_WIDGET (sheet->button),
4820                    NULL,
4821                    allocation.x, allocation.y,
4822                    allocation.width, allocation.height);
4823
4824   if ( button->overstruck)
4825     {
4826       GdkPoint points[2] = {
4827         {allocation.x,  allocation.y},
4828         {allocation.x + allocation.width,
4829          allocation.y + allocation.height}
4830       };
4831
4832       gtk_paint_polygon (sheet->button->style,
4833                          window,
4834                          button->state,
4835                          shadow_type,
4836                          NULL,
4837                          GTK_WIDGET (sheet),
4838                          NULL,
4839                          points,
4840                          2,
4841                          TRUE);
4842     }
4843
4844   if (button->label_visible)
4845     {
4846       text_height = DEFAULT_ROW_HEIGHT -
4847         2 * COLUMN_TITLES_HEIGHT;
4848
4849       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4850                                  &allocation);
4851       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc,
4852                                  &allocation);
4853
4854       allocation.y += 2 * sheet->button->style->ythickness;
4855
4856       if (button->label && strlen (button->label) > 0)
4857         {
4858           PangoRectangle rect;
4859           gchar *line = button->label;
4860
4861           PangoLayout *layout = NULL;
4862           gint real_x = allocation.x;
4863           gint real_y = allocation.y;
4864
4865           layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), line);
4866           pango_layout_get_extents (layout, NULL, &rect);
4867
4868           text_width = PANGO_PIXELS (rect.width);
4869           switch (button->justification)
4870             {
4871             case GTK_JUSTIFY_LEFT:
4872               real_x = allocation.x + COLUMN_TITLES_HEIGHT;
4873               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4874               break;
4875             case GTK_JUSTIFY_RIGHT:
4876               real_x = allocation.x + allocation.width - text_width - COLUMN_TITLES_HEIGHT;
4877               align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
4878               break;
4879             case GTK_JUSTIFY_CENTER:
4880             default:
4881               real_x = allocation.x + (allocation.width - text_width)/2;
4882               align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
4883               pango_layout_set_justify (layout, TRUE);
4884             }
4885           pango_layout_set_alignment (layout, align);
4886           gtk_paint_layout (GTK_WIDGET (sheet)->style,
4887                             window,
4888                             state,
4889                             FALSE,
4890                             &allocation,
4891                             GTK_WIDGET (sheet),
4892                             "label",
4893                             real_x, real_y,
4894                             layout);
4895           g_object_unref (layout);
4896         }
4897
4898       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->fg_gc[button->state],
4899                                  NULL);
4900       gdk_gc_set_clip_rectangle (GTK_WIDGET (sheet)->style->white_gc, NULL);
4901
4902     }
4903
4904   psppire_sheet_button_free (button);
4905 }
4906
4907
4908 /* Draw the column title buttons FIRST through to LAST */
4909 static void
4910 draw_column_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4911 {
4912   GdkRectangle rect;
4913   gint col;
4914   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4915
4916   if (!sheet->column_titles_visible) return;
4917
4918   g_return_if_fail (first >= min_visible_column (sheet));
4919   g_return_if_fail (last <= max_visible_column (sheet));
4920
4921   rect.y = 0;
4922   rect.height = sheet->column_title_area.height;
4923   rect.x = psppire_axis_start_pixel (sheet->haxis, first) + CELL_SPACING;
4924   rect.width = psppire_axis_start_pixel (sheet->haxis, last) + CELL_SPACING
4925     + psppire_axis_unit_size (sheet->haxis, last);
4926
4927   rect.x -= sheet->hadjustment->value;
4928
4929   minimize_int (&rect.width, sheet->column_title_area.width);
4930   maximize_int (&rect.x, 0);
4931
4932   gdk_window_begin_paint_rect (sheet->column_title_window, &rect);
4933
4934   for (col = first ; col <= last ; ++col)
4935     {
4936       GdkRectangle allocation;
4937       gboolean is_sensitive = FALSE;
4938
4939       PsppireSheetButton *
4940         button = psppire_sheet_model_get_column_button (sheet->model, col);
4941       allocation.y = 0;
4942       allocation.x = psppire_axis_start_pixel (sheet->haxis, col)
4943         + CELL_SPACING;
4944       allocation.x -= sheet->hadjustment->value;
4945
4946       allocation.height = sheet->column_title_area.height;
4947       allocation.width = psppire_axis_unit_size (sheet->haxis, col);
4948       is_sensitive = psppire_sheet_model_get_column_sensitivity (sheet->model, col);
4949
4950       draw_button (sheet, sheet->column_title_window,
4951                    button, is_sensitive, allocation);
4952     }
4953
4954   gdk_window_end_paint (sheet->column_title_window);
4955 }
4956
4957
4958 static void
4959 draw_row_title_buttons_range (PsppireSheet *sheet, gint first, gint last)
4960 {
4961   GdkRectangle rect;
4962   gint row;
4963   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet))) return;
4964
4965   if (!sheet->row_titles_visible) return;
4966
4967   g_return_if_fail (first >= min_visible_row (sheet));
4968   g_return_if_fail (last <= max_visible_row (sheet));
4969
4970   rect.x = 0;
4971   rect.width = sheet->row_title_area.width;
4972   rect.y = psppire_axis_start_pixel (sheet->vaxis, first) + CELL_SPACING;
4973   rect.height = psppire_axis_start_pixel (sheet->vaxis, last) + CELL_SPACING
4974     + psppire_axis_unit_size (sheet->vaxis, last);
4975
4976   rect.y -= sheet->vadjustment->value;
4977
4978   minimize_int (&rect.height, sheet->row_title_area.height);
4979   maximize_int (&rect.y, 0);
4980
4981   gdk_window_begin_paint_rect (sheet->row_title_window, &rect);
4982   for (row = first; row <= last; ++row)
4983     {
4984       GdkRectangle allocation;
4985
4986       gboolean is_sensitive = FALSE;
4987
4988       PsppireSheetButton *button =
4989         psppire_sheet_model_get_row_button (sheet->model, row);
4990       allocation.x = 0;
4991       allocation.y = psppire_axis_start_pixel (sheet->vaxis, row)
4992         + CELL_SPACING;
4993       allocation.y -= sheet->vadjustment->value;
4994
4995       allocation.width = sheet->row_title_area.width;
4996       allocation.height = psppire_axis_unit_size (sheet->vaxis, row);
4997       is_sensitive = psppire_sheet_model_get_row_sensitivity (sheet->model, row);
4998
4999       draw_button (sheet, sheet->row_title_window,
5000                    button, is_sensitive, allocation);
5001     }
5002
5003   gdk_window_end_paint (sheet->row_title_window);
5004 }
5005
5006 /* SCROLLBARS
5007  *
5008  * functions:
5009  * adjust_scrollbars
5010  * vadjustment_value_changed
5011  * hadjustment_value_changed */
5012
5013
5014 static void
5015 update_adjustment (GtkAdjustment *adj, PsppireAxis *axis, gint page_size)
5016 {
5017   double position =
5018     (adj->value + adj->page_size)
5019     /
5020     (adj->upper - adj->lower);
5021
5022   const glong last_item = psppire_axis_unit_count (axis) - 1;
5023
5024   if (isnan (position) || position < 0)
5025     position = 0;
5026
5027   adj->upper =
5028     psppire_axis_start_pixel (axis, last_item)
5029     +
5030     psppire_axis_unit_size (axis, last_item)
5031     ;
5032
5033   adj->lower = 0;
5034   adj->page_size = page_size;
5035
5036 #if 0
5037   adj->value = position * (adj->upper - adj->lower) - adj->page_size;
5038
5039   if ( adj->value < adj->lower)
5040     adj->value = adj->lower;
5041 #endif
5042
5043   gtk_adjustment_changed (adj);
5044 }
5045
5046
5047 static void
5048 adjust_scrollbars (PsppireSheet *sheet)
5049 {
5050   gint width, height;
5051
5052   if (!GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5053     return;
5054
5055   gdk_drawable_get_size (sheet->sheet_window, &width, &height);
5056
5057   if ( sheet->row_titles_visible)
5058     width -= sheet->row_title_area.width;
5059
5060   if (sheet->column_titles_visible)
5061     height -= sheet->column_title_area.height;
5062
5063   if (sheet->vadjustment)
5064     {
5065       glong last_row = psppire_axis_unit_count (sheet->vaxis) - 1;
5066
5067       sheet->vadjustment->step_increment =
5068         ROWS_PER_STEP *
5069         psppire_axis_unit_size (sheet->vaxis, last_row);
5070
5071       sheet->vadjustment->page_increment =
5072         height -
5073         sheet->column_title_area.height -
5074         psppire_axis_unit_size (sheet->vaxis, last_row);
5075
5076       update_adjustment (sheet->vadjustment, sheet->vaxis, height);
5077     }
5078
5079   if (sheet->hadjustment)
5080     {
5081       gint last_col = psppire_axis_unit_count (sheet->haxis) - 1;
5082       sheet->hadjustment->step_increment = 1;
5083
5084       sheet->hadjustment->page_increment = width;
5085
5086       sheet->hadjustment->upper =
5087         psppire_axis_start_pixel (sheet->haxis, last_col)
5088         +
5089         psppire_axis_unit_size (sheet->haxis, last_col)
5090         ;
5091
5092       update_adjustment (sheet->hadjustment, sheet->haxis, width);
5093     }
5094 }
5095
5096 /* Subtracts the region of WIDGET from REGION */
5097 static void
5098 subtract_widget_region (GdkRegion *region, GtkWidget *widget)
5099 {
5100   GdkRectangle rect;
5101   GdkRectangle intersect;
5102   GdkRegion *region2;
5103
5104   gdk_region_get_clipbox (region, &rect);
5105   gtk_widget_intersect (widget,
5106                         &rect,
5107                         &intersect);
5108
5109   region2 = gdk_region_rectangle (&intersect);
5110   gdk_region_subtract (region, region2);
5111   gdk_region_destroy (region2);
5112 }
5113
5114 static void
5115 vadjustment_value_changed (GtkAdjustment *adjustment,
5116                            gpointer data)
5117 {
5118   GdkRegion *region;
5119   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5120
5121   g_return_if_fail (adjustment != NULL);
5122
5123   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
5124
5125   gtk_widget_hide (sheet->entry_widget);
5126
5127   region =
5128     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
5129
5130   subtract_widget_region (region, sheet->button);
5131   gdk_window_begin_paint_region (sheet->sheet_window, region);
5132
5133   draw_sheet_region (sheet, region);
5134
5135   draw_row_title_buttons (sheet);
5136   psppire_sheet_draw_active_cell (sheet);
5137
5138   gdk_window_end_paint (sheet->sheet_window);
5139   gdk_region_destroy (region);
5140 }
5141
5142
5143 static void
5144 hadjustment_value_changed (GtkAdjustment *adjustment,
5145                            gpointer data)
5146 {
5147   GdkRegion *region;
5148   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5149
5150   g_return_if_fail (adjustment != NULL);
5151
5152   if ( ! GTK_WIDGET_REALIZED (sheet)) return;
5153
5154   gtk_widget_hide (sheet->entry_widget);
5155
5156
5157   region =
5158     gdk_drawable_get_visible_region (GDK_DRAWABLE (sheet->sheet_window));
5159
5160   subtract_widget_region (region, sheet->button);
5161   gdk_window_begin_paint_region (sheet->sheet_window, region);
5162
5163   draw_sheet_region (sheet, region);
5164
5165   draw_column_title_buttons (sheet);
5166
5167   psppire_sheet_draw_active_cell (sheet);
5168
5169   gdk_window_end_paint (sheet->sheet_window);
5170
5171   gdk_region_destroy (region);
5172 }
5173
5174
5175 /* COLUMN RESIZING */
5176 static void
5177 draw_xor_vline (PsppireSheet *sheet)
5178 {
5179   gint height;
5180   gint xpos = sheet->x_drag;
5181   gdk_drawable_get_size (sheet->sheet_window,
5182                          NULL, &height);
5183
5184   if (sheet->row_titles_visible)
5185     xpos += sheet->row_title_area.width;
5186
5187   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
5188                  xpos,
5189                  sheet->column_title_area.height,
5190                  xpos,
5191                  height + CELL_SPACING);
5192 }
5193
5194 /* ROW RESIZING */
5195 static void
5196 draw_xor_hline (PsppireSheet *sheet)
5197
5198 {
5199   gint width;
5200   gint ypos = sheet->y_drag;
5201
5202   gdk_drawable_get_size (sheet->sheet_window,
5203                          &width, NULL);
5204
5205
5206   if (sheet->column_titles_visible)
5207     ypos += sheet->column_title_area.height;
5208
5209   gdk_draw_line (GTK_WIDGET (sheet)->window, sheet->xor_gc,
5210                  sheet->row_title_area.width,
5211                  ypos,
5212                  width + CELL_SPACING,
5213                  ypos);
5214 }
5215
5216 /* SELECTED RANGE */
5217 static void
5218 draw_xor_rectangle (PsppireSheet *sheet, PsppireSheetRange range)
5219 {
5220   gint i = 0;
5221   GdkRectangle clip_area, area;
5222   GdkGCValues values;
5223
5224   area.x = psppire_axis_start_pixel (sheet->haxis, range.col0);
5225   area.y = psppire_axis_start_pixel (sheet->vaxis, range.row0);
5226   area.width = psppire_axis_start_pixel (sheet->haxis, range.coli)- area.x+
5227     psppire_axis_unit_size (sheet->haxis, range.coli);
5228   area.height = psppire_axis_start_pixel (sheet->vaxis, range.rowi)- area.y +
5229     psppire_axis_unit_size (sheet->vaxis, range.rowi);
5230
5231   clip_area.x = sheet->row_title_area.width;
5232   clip_area.y = sheet->column_title_area.height;
5233
5234   gdk_drawable_get_size (sheet->sheet_window,
5235                          &clip_area.width, &clip_area.height);
5236
5237   if (!sheet->row_titles_visible) clip_area.x = 0;
5238   if (!sheet->column_titles_visible) clip_area.y = 0;
5239
5240   if (area.x < 0)
5241     {
5242       area.width = area.width + area.x;
5243       area.x = 0;
5244     }
5245   if (area.width > clip_area.width) area.width = clip_area.width + 10;
5246   if (area.y < 0)
5247     {
5248       area.height = area.height + area.y;
5249       area.y = 0;
5250     }
5251   if (area.height > clip_area.height) area.height = clip_area.height + 10;
5252
5253   clip_area.x--;
5254   clip_area.y--;
5255   clip_area.width += 3;
5256   clip_area.height += 3;
5257
5258   gdk_gc_get_values (sheet->xor_gc, &values);
5259
5260   gdk_gc_set_clip_rectangle (sheet->xor_gc, &clip_area);
5261
5262   gdk_draw_rectangle (sheet->sheet_window,
5263                       sheet->xor_gc,
5264                       FALSE,
5265                       area.x + i, area.y + i,
5266                       area.width - 2 * i, area.height - 2 * i);
5267
5268
5269   gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
5270
5271   gdk_gc_set_foreground (sheet->xor_gc, &values.foreground);
5272 }
5273
5274
5275 static void
5276 set_column_width (PsppireSheet *sheet,
5277                   gint column,
5278                   gint width)
5279 {
5280   g_return_if_fail (sheet != NULL);
5281   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
5282
5283   if (column < 0 || column >= psppire_axis_unit_count (sheet->haxis))
5284     return;
5285
5286   if ( width <= 0)
5287     return;
5288
5289   psppire_axis_resize (sheet->haxis, column,
5290                        width - sheet->cell_padding->left -
5291                        sheet->cell_padding->right);
5292
5293   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5294     {
5295       draw_column_title_buttons (sheet);
5296       adjust_scrollbars (sheet);
5297       psppire_sheet_size_allocate_entry (sheet);
5298       redraw_range (sheet, NULL);
5299     }
5300 }
5301
5302 static void
5303 set_row_height (PsppireSheet *sheet,
5304                 gint row,
5305                 gint height)
5306 {
5307   g_return_if_fail (sheet != NULL);
5308   g_return_if_fail (PSPPIRE_IS_SHEET (sheet));
5309
5310   if (row < 0 || row >= psppire_axis_unit_count (sheet->vaxis))
5311     return;
5312
5313   if (height <= 0)
5314     return;
5315
5316   psppire_axis_resize (sheet->vaxis, row,
5317                        height - sheet->cell_padding->top -
5318                        sheet->cell_padding->bottom);
5319
5320   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
5321     {
5322       draw_row_title_buttons (sheet);
5323       adjust_scrollbars (sheet);
5324       psppire_sheet_size_allocate_entry (sheet);
5325       redraw_range (sheet, NULL);
5326     }
5327 }
5328
5329 static gboolean
5330 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
5331                           PsppireSheetCellAttr *attr)
5332 {
5333   GdkColor *fg, *bg;
5334   const GtkJustification *j ;
5335   GdkColormap *colormap;
5336
5337   g_return_val_if_fail (sheet != NULL, FALSE);
5338   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), FALSE);
5339
5340   if (row < 0 || col < 0) return FALSE;
5341
5342   attr->foreground = GTK_WIDGET (sheet)->style->black;
5343   attr->background = sheet->color[BG_COLOR];
5344
5345   attr->border.width = 0;
5346   attr->border.line_style = GDK_LINE_SOLID;
5347   attr->border.cap_style = GDK_CAP_NOT_LAST;
5348   attr->border.join_style = GDK_JOIN_MITER;
5349   attr->border.mask = 0;
5350   attr->border.color = GTK_WIDGET (sheet)->style->black;
5351
5352   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
5353   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
5354   if ( fg )
5355     {
5356       gdk_colormap_alloc_color (colormap, fg, TRUE, TRUE);
5357       attr->foreground = *fg;
5358     }
5359
5360   bg = psppire_sheet_model_get_background (sheet->model, row, col);
5361   if ( bg )
5362     {
5363       gdk_colormap_alloc_color (colormap, bg, TRUE, TRUE);
5364       attr->background = *bg;
5365     }
5366
5367   attr->justification =
5368     psppire_sheet_model_get_column_justification (sheet->model, col);
5369
5370   j = psppire_sheet_model_get_justification (sheet->model, row, col);
5371   if (j)
5372     attr->justification = *j;
5373
5374   return TRUE;
5375 }
5376
5377 static void
5378 psppire_sheet_button_size_request        (PsppireSheet *sheet,
5379                                   const PsppireSheetButton *button,
5380                                   GtkRequisition *button_requisition)
5381 {
5382   GtkRequisition requisition;
5383   GtkRequisition label_requisition;
5384
5385   label_requisition.height = DEFAULT_ROW_HEIGHT;
5386   label_requisition.width = COLUMN_MIN_WIDTH;
5387
5388   requisition.height = DEFAULT_ROW_HEIGHT;
5389   requisition.width = COLUMN_MIN_WIDTH;
5390
5391
5392   *button_requisition = requisition;
5393   button_requisition->width = MAX (requisition.width, label_requisition.width);
5394   button_requisition->height = MAX (requisition.height, label_requisition.height);
5395
5396 }
5397
5398 static void
5399 psppire_sheet_forall (GtkContainer *container,
5400                   gboolean include_internals,
5401                   GtkCallback callback,
5402                   gpointer callback_data)
5403 {
5404   PsppireSheet *sheet = PSPPIRE_SHEET (container);
5405
5406   g_return_if_fail (callback != NULL);
5407
5408   if (sheet->button && sheet->button->parent)
5409     (* callback) (sheet->button, callback_data);
5410
5411   if (sheet->entry_widget && GTK_IS_CONTAINER (sheet->entry_widget))
5412     (* callback) (sheet->entry_widget, callback_data);
5413 }
5414
5415
5416 PsppireSheetModel *
5417 psppire_sheet_get_model (const PsppireSheet *sheet)
5418 {
5419   g_return_val_if_fail (PSPPIRE_IS_SHEET (sheet), NULL);
5420
5421   return sheet->model;
5422 }
5423
5424
5425 PsppireSheetButton *
5426 psppire_sheet_button_new (void)
5427 {
5428   PsppireSheetButton *button = g_malloc (sizeof (PsppireSheetButton));
5429
5430   button->state = GTK_STATE_NORMAL;
5431   button->label = NULL;
5432   button->label_visible = TRUE;
5433   button->justification = GTK_JUSTIFY_FILL;
5434   button->overstruck = FALSE;
5435
5436   return button;
5437 }
5438
5439
5440 void
5441 psppire_sheet_button_free (PsppireSheetButton *button)
5442 {
5443   if (!button) return ;
5444
5445   g_free (button->label);
5446   g_free (button);
5447 }
5448
5449 static void
5450 append_cell_text (GString *string, const PsppireSheet *sheet, gint r, gint c)
5451 {
5452   gchar *celltext = psppire_sheet_cell_get_text (sheet, r, c);
5453
5454   if ( NULL == celltext)
5455     return;
5456
5457   g_string_append (string, celltext);
5458   g_free (celltext);
5459 }
5460
5461
5462 static GString *
5463 range_to_text (const PsppireSheet *sheet)
5464 {
5465   gint r, c;
5466   GString *string;
5467
5468   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
5469     return NULL;
5470
5471   string = g_string_sized_new (80);
5472
5473   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
5474     {
5475       for (c = sheet->range.col0; c < sheet->range.coli; ++c)
5476         {
5477           append_cell_text (string, sheet, r, c);
5478           g_string_append (string, "\t");
5479         }
5480       append_cell_text (string, sheet, r, c);
5481       if ( r < sheet->range.rowi)
5482         g_string_append (string, "\n");
5483     }
5484
5485   return string;
5486 }
5487
5488 static GString *
5489 range_to_html (const PsppireSheet *sheet)
5490 {
5491   gint r, c;
5492   GString *string;
5493
5494   if ( !psppire_sheet_range_isvisible (sheet, &sheet->range))
5495     return NULL;
5496
5497   string = g_string_sized_new (480);
5498
5499   g_string_append (string, "<html>\n");
5500   g_string_append (string, "<body>\n");
5501   g_string_append (string, "<table>\n");
5502   for (r = sheet->range.row0; r <= sheet->range.rowi; ++r)
5503     {
5504       g_string_append (string, "<tr>\n");
5505       for (c = sheet->range.col0; c <= sheet->range.coli; ++c)
5506         {
5507           g_string_append (string, "<td>");
5508           append_cell_text (string, sheet, r, c);
5509           g_string_append (string, "</td>\n");
5510         }
5511       g_string_append (string, "</tr>\n");
5512     }
5513   g_string_append (string, "</table>\n");
5514   g_string_append (string, "</body>\n");
5515   g_string_append (string, "</html>\n");
5516
5517   return string;
5518 }
5519
5520 enum {
5521   SELECT_FMT_NULL,
5522   SELECT_FMT_TEXT,
5523   SELECT_FMT_HTML
5524 };
5525
5526 static void
5527 primary_get_cb (GtkClipboard     *clipboard,
5528                 GtkSelectionData *selection_data,
5529                 guint             info,
5530                 gpointer          data)
5531 {
5532   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5533   GString *string = NULL;
5534
5535   switch (info)
5536     {
5537     case SELECT_FMT_TEXT:
5538       string = range_to_text (sheet);
5539       break;
5540     case SELECT_FMT_HTML:
5541       string = range_to_html (sheet);
5542       break;
5543     default:
5544       g_assert_not_reached ();
5545     }
5546
5547   gtk_selection_data_set (selection_data, selection_data->target,
5548                           8,
5549                           (const guchar *) string->str, string->len);
5550   g_string_free (string, TRUE);
5551 }
5552
5553 static void
5554 primary_clear_cb (GtkClipboard *clipboard,
5555                   gpointer      data)
5556 {
5557   PsppireSheet *sheet = PSPPIRE_SHEET (data);
5558   if ( ! GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
5559     return;
5560
5561   psppire_sheet_real_unselect_range (sheet, NULL);
5562 }
5563
5564 static void
5565 psppire_sheet_update_primary_selection (PsppireSheet *sheet)
5566 {
5567   static const GtkTargetEntry targets[] = {
5568     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
5569     { "STRING",        0, SELECT_FMT_TEXT },
5570     { "TEXT",          0, SELECT_FMT_TEXT },
5571     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
5572     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
5573     { "text/plain",    0, SELECT_FMT_TEXT },
5574     { "text/html",     0, SELECT_FMT_HTML }
5575   };
5576
5577   GtkClipboard *clipboard;
5578
5579   if (!GTK_WIDGET_REALIZED (sheet))
5580     return;
5581
5582   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (sheet),
5583                                         GDK_SELECTION_PRIMARY);
5584
5585   if (psppire_sheet_range_isvisible (sheet, &sheet->range))
5586     {
5587       if (!gtk_clipboard_set_with_owner (clipboard, targets,
5588                                          G_N_ELEMENTS (targets),
5589                                          primary_get_cb, primary_clear_cb,
5590                                          G_OBJECT (sheet)))
5591         primary_clear_cb (clipboard, sheet);
5592     }
5593   else
5594     {
5595       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (sheet))
5596         gtk_clipboard_clear (clipboard);
5597     }
5598 }