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