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