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