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