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