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