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