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