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