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