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