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