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