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