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