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