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