1 /*
2 A widget to display and manipulate tabular data
3 Copyright (C) 2016, 2017, 2020 John Darrington
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20 #include <math.h>
21 #include <string.h>
22 #include "ssw-sheet.h"
23 #include "ssw-sheet-body.h"
24 #include "ssw-constraint.h"
25 #include "ssw-marshaller.h"
26
27 #define P_(X) (X)
28
29 typedef GtkCellRenderer * (* fetch_renderer_func) (SswSheet *sheet, gint col, gint row, GType type, gpointer ud);
30
31 static const int linewidth = 1;
32
33 struct _SswSheetBodyPrivate
34 {
35 SswSheetAxis *vaxis;
36 SswSheetAxis *haxis;
37
38 /* The widget which is editing the active cell,
39 or null if no cell is being edited */
40 GtkWidget *editor;
41 GtkWidget *active_cell_holder;
42
43 /* A string of the form "%d:%d" describing the current cell */
44 gchar path[512];
45
46 gboolean show_gridlines;
47 gboolean editable;
48
49 GtkTreeModel *data_model;
50
51 /* Cursors used when resizing columns and rows respectively */
52 GdkCursor *vc;
53 GdkCursor *hc;
54
55 /* Cursor used when extending/defining a cell selection */
56 GdkCursor *xc;
57
58 /* Gestures used when selecting / resizing */
59 GtkGesture *selection_gesture;
60 GtkGesture *horizontal_resize_gesture;
61 GtkGesture *vertical_resize_gesture;
62
63 gboolean dispose_has_run;
64
65 fetch_renderer_func renderer_func;
66
67 GtkCellRenderer *default_renderer;
68
69 SswRange *selection;
70
71 /* The sheet to which this body belongs */
72 SswSheet *sheet;
73
74 ssw_sheet_forward_conversion_func cf;
75 ssw_sheet_reverse_conversion_func revf;
76 };
77
78 typedef struct _SswSheetBodyPrivate SswSheetBodyPrivate;
79
80 G_DEFINE_TYPE_WITH_CODE (SswSheetBody, ssw_sheet_body,
81 GTK_TYPE_LAYOUT,
82 G_ADD_PRIVATE (SswSheetBody));
83
84 #define PRIV_DECL(l) SswSheetBodyPrivate *priv = ((SswSheetBodyPrivate *)ssw_sheet_body_get_instance_private ((SswSheetBody *)l))
85 #define PRIV(l) ((SswSheetBodyPrivate *)ssw_sheet_body_get_instance_private ((SswSheetBody *)l))
86
87
88 static void start_editing (SswSheetBody *body, GdkEvent *e);
89
90 static void
limit_selection(SswSheetBody * body)91 limit_selection (SswSheetBody *body)
92 {
93 PRIV_DECL (body);
94
95 if (priv->selection->end_x < 0)
96 priv->selection->end_x = 0;
97
98 if (priv->selection->end_y < 0)
99 priv->selection->end_y = 0;
100
101 if (priv->selection->end_x >= ssw_sheet_axis_get_size (priv->haxis))
102 priv->selection->end_x = ssw_sheet_axis_get_size (priv->haxis) - 1;
103
104 if (priv->selection->end_y >= ssw_sheet_axis_get_size (priv->vaxis))
105 priv->selection->end_y = ssw_sheet_axis_get_size (priv->vaxis) - 1;
106 }
107
108 enum {SELECTION_CHANGED,
109 VALUE_CHANGED,
110 n_SIGNALS};
111
112 static guint signals [n_SIGNALS];
113
114 static inline gboolean
get_active_cell(SswSheetBody * body,gint * col,gint * row)115 get_active_cell (SswSheetBody *body, gint *col, gint *row)
116 {
117 PRIV_DECL (body);
118
119 SswSheetBody *active_body = NULL;
120
121 sscanf (priv->path, "r%dc%ds%p", row, col, &active_body);
122
123 if (active_body == NULL) /* There is no active cell! */
124 return FALSE;
125
126 if (active_body != body) /* This body is not active */
127 return FALSE;
128
129 if (*col < 0 || *row < 0)
130 return FALSE;
131
132 return TRUE;
133 }
134
135 static inline
set_active_cell(SswSheetBody * body,gint col,gint row)136 void set_active_cell (SswSheetBody *body, gint col, gint row)
137 {
138 PRIV_DECL (body);
139
140 gint old_start_x = priv->selection->start_x;
141 gint old_start_y = priv->selection->start_y;
142
143 priv->selection->start_x = col;
144 priv->selection->start_y = row;
145
146 if (old_start_y < row)
147 priv->selection->end_y = row;
148
149 if (old_start_x < col)
150 priv->selection->end_x = col;
151
152 snprintf (priv->path, 512, "r%dc%ds%p", row, col, body);
153 }
154
155 static void
scroll_vertically(SswSheetBody * body)156 scroll_vertically (SswSheetBody *body)
157 {
158 PRIV_DECL (body);
159
160 if (!gtk_widget_is_visible (GTK_WIDGET (body)))
161 return;
162
163 gint row = -1, col = -1;
164 get_active_cell (body, &col, &row);
165
166 if (row >= 0 && col >= 0)
167 {
168 gint vlocation, hlocation;
169 gint visible = ssw_sheet_axis_find_boundary (priv->vaxis, row, &vlocation, NULL);
170
171 gtk_container_child_get (GTK_CONTAINER (body),
172 priv->active_cell_holder, "x", &hlocation, NULL);
173
174 gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder,
175 hlocation, vlocation + 1);
176
177 gtk_widget_set_visible (priv->active_cell_holder, (visible == 0));
178 }
179
180 gtk_widget_queue_draw (GTK_WIDGET (body));
181 }
182
183 static void
scroll_horizontally(SswSheetBody * body)184 scroll_horizontally (SswSheetBody *body)
185 {
186 PRIV_DECL (body);
187
188 if (!gtk_widget_is_visible (GTK_WIDGET (body)))
189 return;
190
191 gint row = -1, col = -1;
192 get_active_cell (body, &col, &row);
193
194 if (row >= 0 && col >= 0)
195 {
196 gint vlocation, hlocation;
197 gint visible = ssw_sheet_axis_find_boundary (priv->haxis, col, &hlocation, NULL);
198
199 gtk_container_child_get (GTK_CONTAINER (body), priv->active_cell_holder, "y", &vlocation, NULL);
200
201 gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder, hlocation + 1, vlocation);
202 gtk_widget_set_visible (priv->active_cell_holder, visible == 0);
203 }
204
205 gtk_widget_queue_draw (GTK_WIDGET (body));
206 }
207
208
209
210
211 static void
normalise_selection(const SswRange * in,SswRange * out)212 normalise_selection (const SswRange *in, SswRange *out)
213 {
214 if (in->start_y < in->end_y)
215 {
216 out->end_y = in->end_y;
217 out->start_y = in->start_y;
218 }
219 else
220 {
221 out->start_y = in->end_y;
222 out->end_y = in->start_y;
223 }
224
225 if (in->start_x < in->end_x)
226 {
227 out->end_x = in->end_x;
228 out->start_x = in->start_x;
229 }
230 else
231 {
232 out->start_x = in->end_x;
233 out->end_x = in->start_x;
234 }
235 }
236
237 static void
draw_selection(SswSheetBody * body,cairo_t * cr)238 draw_selection (SswSheetBody *body, cairo_t *cr)
239 {
240 GtkWidget *w = GTK_WIDGET (body);
241 guint width = gtk_widget_get_allocated_width (w);
242 guint height = gtk_widget_get_allocated_height (w);
243 PRIV_DECL (body);
244
245 GdkRGBA *color;
246 GtkStyleContext *sc = gtk_widget_get_style_context (w);
247 gtk_style_context_get (sc,
248 GTK_STATE_FLAG_SELECTED,
249 "background-color",
250 &color, NULL);
251
252 GtkCssProvider *cp = gtk_css_provider_new ();
253 gchar *css = g_strdup_printf ("* {background-color: rgba(%d, %d, %d, 0.25);}",
254 (int) (100.0 * color->red),
255 (int) (100.0 * color->green),
256 (int) (100.0 * color->blue));
257
258 gtk_css_provider_load_from_data (cp, css, strlen (css), 0);
259
260 gtk_style_context_add_provider (sc, GTK_STYLE_PROVIDER (cp),
261 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
262
263 /* Draw the selection */
264 if ((priv->selection->start_x != priv->selection->end_x)
265 ||
266 (priv->selection->start_y != priv->selection->end_y))
267 {
268 SswRange mySelect;
269 normalise_selection (priv->selection, &mySelect);
270 gint xpos_start = 0;
271 gint ypos_start = 0;
272 if (0 < ssw_sheet_axis_find_boundary (priv->haxis, mySelect.start_x,
273 &xpos_start, NULL))
274 goto done;
275
276 if (0 < ssw_sheet_axis_find_boundary (priv->vaxis, mySelect.start_y,
277 &ypos_start, NULL))
278 goto done;
279
280 gint xpos_end, ypos_end, yextent, xextent;
281 gint xrel = ssw_sheet_axis_find_boundary (priv->haxis,
282 mySelect.end_x, &xpos_end,
283 &xextent);
284 if (xrel < 0)
285 goto done;
286
287 gint xsize = (xrel == 0) ? xpos_end - xpos_start + xextent : width;
288
289
290 gint yrel = ssw_sheet_axis_find_boundary (priv->vaxis,
291 mySelect.end_y, &ypos_end,
292 &yextent);
293 if (yrel < 0)
294 goto done;
295
296 gint ysize = (yrel == 0) ? ypos_end - ypos_start + yextent : height;
297
298 gtk_render_background (sc, cr,
299 xpos_start, ypos_start,
300 xsize, ysize);
301 }
302
303 done:
304 gtk_style_context_remove_provider (sc, GTK_STYLE_PROVIDER (cp));
305 g_free (css);
306 g_object_unref (cp);
307 gdk_rgba_free (color);
308 }
309
310 gboolean
ssw_sheet_default_reverse_conversion(GtkTreeModel * model,gint col,gint row,const gchar * in,GValue * out)311 ssw_sheet_default_reverse_conversion (GtkTreeModel *model, gint col, gint row,
312 const gchar *in, GValue *out)
313 {
314 GType src_type = G_TYPE_STRING;
315 GType target_type = gtk_tree_model_get_column_type (model, col);
316
317 GValue src_value = G_VALUE_INIT;
318
319 if (! g_value_type_transformable (src_type, target_type))
320 {
321 g_critical ("Value of type %s cannot be transformed to type %s",
322 g_type_name (src_type),
323 g_type_name (target_type));
324 return FALSE;
325 }
326
327 g_value_init (&src_value, src_type);
328 g_value_set_string (&src_value, in);
329
330 g_value_init (out, target_type);
331
332 g_value_transform (&src_value, out);
333 g_value_unset (&src_value);
334 return TRUE;
335 }
336
337
338 gchar *
ssw_sheet_default_forward_conversion(SswSheet * sheet,GtkTreeModel * m,gint col,gint row,const GValue * value)339 ssw_sheet_default_forward_conversion (SswSheet *sheet, GtkTreeModel *m,
340 gint col, gint row, const GValue *value)
341 {
342 GValue target_value = G_VALUE_INIT;
343
344 g_value_init (&target_value, G_TYPE_STRING);
345
346 if (!g_value_transform (value, &target_value))
347 {
348 if (G_VALUE_HOLDS_VARIANT (value))
349 {
350 GVariant *vt = g_value_get_variant (value);
351 gchar *s = g_variant_print (vt, FALSE);
352 g_value_unset (&target_value);
353 return s;
354 }
355 else
356 {
357 g_warning ("Failed to transform type \"%s\" to type \"%s\"\n",
358 G_VALUE_TYPE_NAME (&value),
359 g_type_name (G_TYPE_STRING));
360 }
361 }
362
363 gchar *cell_text = g_value_dup_string (&target_value);
364
365 g_value_unset (&target_value);
366
367 return cell_text;
368 }
369
370
371 static GtkCellRenderer * choose_renderer (SswSheetBody *body, gint col, gint row);
372
373 static const gchar *unfocused_border = "* {\n"
374 " border-width: 2px;\n"
375 " border-radius: 2px;\n"
376 " border-color: black;\n"
377 " border-style: dotted;\n"
378 "}";
379
380
381 static const gchar *focused_border = "* {\n"
382 " border-width: 2px;\n"
383 " border-radius: 2px;\n"
384 " border-color: black;\n"
385 " border-style: solid;\n"
386 "}";
387
388 static gboolean
__draw(GtkWidget * widget,cairo_t * cr)389 __draw (GtkWidget *widget, cairo_t *cr)
390 {
391 SswSheetBody *body = SSW_SHEET_BODY (widget);
392 PRIV_DECL (body);
393
394 if (!priv->haxis || !priv->vaxis)
395 return TRUE;
396
397 gint active_row = -1, active_col = -1;
398 get_active_cell (body, &active_col, &active_row);
399
400 guint width = gtk_widget_get_allocated_width (widget);
401 guint height = gtk_widget_get_allocated_height (widget);
402
403 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
404
405 GtkCssProvider *cp = gtk_css_provider_new ();
406
407 GtkBorder border;
408 if (priv->editable)
409 {
410 gint yy = ssw_sheet_axis_find_boundary (priv->vaxis, active_row, NULL, NULL);
411 gint xx = ssw_sheet_axis_find_boundary (priv->haxis, active_col, NULL, NULL);
412
413 if (yy == 0 && xx == 0 && priv->editor == NULL)
414 start_editing (body, NULL);
415
416 if ((gtk_widget_is_focus (widget) ||
417 (priv->editor && gtk_widget_is_focus (priv->editor))))
418 gtk_css_provider_load_from_data (cp, focused_border, strlen (focused_border), 0);
419 else
420 gtk_css_provider_load_from_data (cp, unfocused_border,
421 strlen (unfocused_border), 0);
422
423 gtk_style_context_add_provider (sc, GTK_STYLE_PROVIDER (cp),
424 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
425
426 gtk_style_context_get_border (sc,
427 gtk_widget_get_state_flags (widget),
428 &border);
429 }
430
431 int row = priv->vaxis->last_cell;
432 gint y;
433 for (y = priv->vaxis->cell_limits->len - 1;
434 y >= 0;
435 --y)
436 {
437 const SswGeometry *vgeom = g_ptr_array_index (priv->vaxis->cell_limits, y);
438
439
440 if (priv->show_gridlines)
441 {
442 gint ypos = vgeom->position;
443 ypos += vgeom->size;
444
445 /* Draw horizontal grid lines */
446 gtk_render_line (sc, cr, 0, ypos, width, ypos);
447 }
448
449 --row;
450 GtkTreeIter iter;
451 if (priv->data_model)
452 gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
453 int col = priv->haxis->last_cell;
454 gint x;
455 for (x = priv->haxis->cell_limits->len - 1;
456 x >= 0;
457 --x)
458 {
459 const SswGeometry *hgeom = g_ptr_array_index (priv->haxis->cell_limits, x);
460
461 GdkRectangle rect;
462 rect.x = hgeom->position;
463 rect.y = vgeom->position;
464 rect.width = hgeom->size;
465 rect.height = vgeom->size;
466
467 --col;
468
469 if (priv->editable && col == active_col && row == active_row)
470 {
471 /* Draw frame */
472 gtk_render_frame (sc, cr,
473 rect.x - border.left,
474 rect.y - border.top,
475 rect.width + border.left + border.right + 1,
476 rect.height + border.top + border.bottom + 1);
477 }
478 if (priv->data_model
479 && row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
480 && col < gtk_tree_model_get_n_columns (priv->data_model))
481 {
482 GtkCellRenderer *renderer = choose_renderer (body, col, row);
483
484 if (GTK_IS_CELL_RENDERER_TEXT (renderer))
485 {
486 char *cell_text = NULL;
487
488 if (col != active_col || row != active_row ||
489 ! priv->editable ||
490 (priv->sheet->selected_body != GTK_WIDGET (body)))
491 {
492 /* Don't render the active cell.
493 It is already rendered by the cell_editable widget
494 and rendering twice looks unaesthetic */
495 GValue value = G_VALUE_INIT;
496 gtk_tree_model_get_value (priv->data_model, &iter, col, &value);
497 cell_text = priv->cf (priv->sheet, priv->data_model, col, row, &value);
498 g_value_unset (&value);
499 }
500
501 g_object_set (renderer,
502 "text", cell_text,
503 NULL);
504
505 g_free (cell_text);
506 }
507
508
509 gtk_cell_renderer_render (renderer, cr, widget,
510 &rect, &rect, 0);
511 }
512
513 if (priv->show_gridlines)
514 {
515 /* Draw vertical grid lines */
516 gtk_render_line (sc, cr,
517 rect.x + rect.width, 0,
518 rect.x + rect.width, height);
519 }
520 }
521 }
522
523 if (gtk_gesture_is_active (priv->horizontal_resize_gesture))
524 {
525 gdouble start_x, start_y;
526 gdouble offsetx, offsety;
527 gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (priv->horizontal_resize_gesture), &start_x, &start_y);
528 gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->horizontal_resize_gesture), &offsetx, &offsety);
529
530 gtk_render_line (sc, cr, start_x + offsetx, 0, start_x + offsetx, height);
531 }
532
533 if (gtk_gesture_is_active (priv->vertical_resize_gesture))
534 {
535 gdouble start_x, start_y;
536 gdouble offsetx, offsety;
537 gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (priv->vertical_resize_gesture), &start_x, &start_y);
538 gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->vertical_resize_gesture), &offsetx, &offsety);
539
540 gtk_render_line (sc, cr, 0, start_y + offsety, width, start_y + offsety);
541 }
542
543 draw_selection (body, cr);
544
545 gtk_style_context_remove_provider (sc, GTK_STYLE_PROVIDER (cp));
546
547 g_object_unref (cp);
548
549 return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->draw (widget, cr);
550 }
551
552
553 static void
__realize(GtkWidget * w)554 __realize (GtkWidget *w)
555 {
556 GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->realize (w);
557
558 GdkWindow *win = gtk_widget_get_window (w);
559 gdk_window_set_events (win, GDK_KEY_PRESS_MASK |
560 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
561 }
562
563 static void
__dispose(GObject * object)564 __dispose (GObject *object)
565 {
566 SswSheetBody *body = SSW_SHEET_BODY (object);
567 PRIV_DECL (body);
568
569 if (priv->dispose_has_run)
570 return;
571
572 if (priv->data_model)
573 g_object_unref (priv->data_model);
574
575 priv->dispose_has_run = TRUE;
576
577 G_OBJECT_CLASS (ssw_sheet_body_parent_class)->dispose (object);
578 }
579
580
581 static void
__finalize(GObject * obj)582 __finalize (GObject *obj)
583 {
584 SswSheetBody *body = SSW_SHEET_BODY (obj);
585 PRIV_DECL (body);
586
587 g_object_unref (priv->vc);
588 g_object_unref (priv->hc);
589 g_object_unref (priv->xc);
590
591 g_object_unref (priv->horizontal_resize_gesture);
592 g_object_unref (priv->vertical_resize_gesture);
593 g_object_unref (priv->selection_gesture);
594 g_object_unref (priv->default_renderer);
595
596 G_OBJECT_CLASS (ssw_sheet_body_parent_class)->finalize (obj);
597 }
598
599
600
601 static void
announce_selection(SswSheetBody * body)602 announce_selection (SswSheetBody *body)
603 {
604 gtk_widget_queue_draw (GTK_WIDGET (body));
605
606 PRIV_DECL (body);
607
608 g_signal_emit (body, signals [SELECTION_CHANGED], 0, priv->selection);
609
610 GtkClipboard *primary =
611 gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (body)),
612 GDK_SELECTION_PRIMARY);
613
614 ssw_sheet_body_set_clip (body, primary);
615 }
616
617 static void
set_selection(SswSheetBody * body,gint start_x,gint start_y,gint end_x,gint end_y)618 set_selection (SswSheetBody *body, gint start_x, gint start_y, gint end_x, gint end_y)
619 {
620 PRIV_DECL (body);
621
622 priv->selection->start_x = start_x;
623 priv->selection->start_y = start_y;
624
625 priv->selection->end_x = end_x;
626 priv->selection->end_y = end_y;
627
628 announce_selection (body);
629 }
630
631 /* Forces COL and ROW to be within the range of the currently loaded
632 model. Returns TRUE if COL or ROW was changed.
633 */
634 static gboolean
trim_to_model_limits(SswSheetBody * body,gint old_col,gint old_row,gint * col,gint * row)635 trim_to_model_limits (SswSheetBody *body, gint old_col, gint old_row, gint *col, gint *row)
636 {
637 gboolean clipped = FALSE;
638 PRIV_DECL (body);
639
640 /* Allow editing of a new cell */
641 if (priv->editable)
642 {
643 if (old_row == ssw_sheet_axis_get_size (priv->vaxis) && old_col == 0)
644 return FALSE;
645
646 if (old_col == ssw_sheet_axis_get_size (priv->haxis) && old_row == 0)
647 return FALSE;
648
649 if (*row == ssw_sheet_axis_get_size (priv->vaxis) && *col == 0)
650 return FALSE;
651
652 if (*col == ssw_sheet_axis_get_size (priv->haxis) && *row == 0)
653 return FALSE;
654 }
655
656 if (*col >= ssw_sheet_axis_get_size (priv->haxis))
657 {
658 *col = ssw_sheet_axis_get_size (priv->haxis) - 1;
659 clipped = TRUE;
660 }
661
662 if (*row >= ssw_sheet_axis_get_size (priv->vaxis))
663 {
664 *row = ssw_sheet_axis_get_size (priv->vaxis) - 1;
665 clipped = TRUE;
666 }
667
668 if (*col < 0)
669 {
670 *col = 0;
671 clipped = TRUE;
672 }
673
674 if (*row < 0)
675 {
676 *row = 0;
677 clipped = TRUE;
678 }
679
680 return clipped;
681 }
682
683 static gboolean
__button_release_event(GtkWidget * w,GdkEventButton * e)684 __button_release_event (GtkWidget *w, GdkEventButton *e)
685 {
686 return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->button_release_event (w, e);
687 }
688
689
690
691 static void
destroy_editor_widget(SswSheetBody * body)692 destroy_editor_widget (SswSheetBody *body)
693 {
694 PRIV_DECL (body);
695 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (priv->editor));
696 }
697
698 static void
finish_editing(GtkSpinButton * s,gpointer user_data)699 finish_editing (GtkSpinButton *s, gpointer user_data)
700 {
701 GtkCellEditable *ce = GTK_CELL_EDITABLE (s);
702 g_object_set (ce, "editing-canceled", FALSE, NULL);
703 gtk_cell_editable_editing_done (ce);
704 }
705
706 static void
start_editing(SswSheetBody * body,GdkEvent * e)707 start_editing (SswSheetBody *body, GdkEvent *e)
708 {
709 PRIV_DECL (body);
710
711 gint row = -1, col = -1;
712 get_active_cell (body, &col, &row);
713
714 /* There seems to be a bug in GtkCellRendererSpinButton, where
715 its GtkAdjustment spins at wierd moments. Blocking the handler
716 here seems to work around the problem. */
717 if (priv->editor && GTK_IS_SPIN_BUTTON (priv->editor))
718 g_signal_handlers_block_by_func (priv->editor, finish_editing, NULL);
719
720 if (GTK_WIDGET (body) == priv->sheet->selected_body)
721 {
722 GtkCellRenderer *renderer = choose_renderer (body, col, row);
723 GtkCellEditable *ce =
724 gtk_cell_renderer_start_editing (renderer, e, GTK_WIDGET (body),
725 priv->path, NULL, NULL,
726 GTK_CELL_RENDERER_SELECTED);
727
728 /* We use this property with a slightly different nuance:
729 It is rather a "not-started" flag than a canceled flag. That is
730 to say, it is TRUE by default and becomes FALSE, once an
731 edit has commenced */
732 g_object_set (ce, "editing-canceled", TRUE, NULL);
733 }
734 }
735
736 static void on_editing_done (GtkCellEditable *e, SswSheetBody *body);
737
738
739 static gboolean
__button_press_event(GtkWidget * w,GdkEventButton * e)740 __button_press_event (GtkWidget *w, GdkEventButton *e)
741 {
742 SswSheetBody *body = SSW_SHEET_BODY (w);
743 PRIV_DECL (body);
744
745 if (e->type != GDK_BUTTON_PRESS)
746 return FALSE;
747
748 GdkEventButton *eb = (GdkEventButton *) e;
749
750 gint old_row = -1, old_col = -1;
751 get_active_cell (body, &old_col, &old_row);
752
753 gint col = ssw_sheet_axis_find_cell (priv->haxis, eb->x, NULL, NULL);
754 gint row = ssw_sheet_axis_find_cell (priv->vaxis, eb->y, NULL, NULL);
755
756 if (trim_to_model_limits (body, old_col, old_row, &col, &row))
757 return FALSE;
758
759 if (priv->editor)
760 on_editing_done (GTK_CELL_EDITABLE (priv->editor), body);
761
762 if (priv->sheet->selected_body != GTK_WIDGET (body))
763 {
764 destroy_editor_widget (SSW_SHEET_BODY (priv->sheet->selected_body));
765 }
766
767 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
768
769 if (gdk_window_get_cursor (win) != priv->hc
770 && priv->editable
771 && gdk_window_get_cursor (win) != priv->vc)
772 set_active_cell (body, col, row);
773
774 priv->sheet->selected_body = GTK_WIDGET (body);
775 gtk_widget_grab_focus (w);
776
777 if (priv->editable)
778 start_editing (body, (GdkEvent *) e);
779
780 return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->button_press_event (w, e);
781 }
782
783 static gboolean
is_printable_key(gint keyval)784 is_printable_key (gint keyval)
785 {
786 switch (keyval)
787 {
788 case GDK_KEY_Return:
789 case GDK_KEY_ISO_Left_Tab:
790 case GDK_KEY_Tab:
791 return FALSE;
792 break;
793 }
794
795 return (0 != gdk_keyval_to_unicode (keyval));
796 }
797
798 static gboolean
__key_press_event_shifted(GtkWidget * w,GdkEventKey * e)799 __key_press_event_shifted (GtkWidget *w, GdkEventKey *e)
800 {
801 SswSheetBody *body = SSW_SHEET_BODY (w);
802 PRIV_DECL (body);
803
804 switch (e->keyval)
805 {
806 case GDK_KEY_Left:
807 if (e->state & GDK_CONTROL_MASK)
808 priv->selection->end_x = 0;
809 else
810 priv->selection->end_x--;
811 break;
812 case GDK_KEY_Right:
813 if (e->state & GDK_CONTROL_MASK)
814 priv->selection->end_x = ssw_sheet_axis_get_size (priv->haxis) - 1;
815 else
816 priv->selection->end_x++;
817 break;
818 case GDK_KEY_Up:
819 if (e->state & GDK_CONTROL_MASK)
820 priv->selection->end_y = 0;
821 else
822 priv->selection->end_y--;
823 break;
824 case GDK_KEY_Down:
825 if (e->state & GDK_CONTROL_MASK)
826 priv->selection->end_y = ssw_sheet_axis_get_size (priv->vaxis) - 1;
827 else
828 priv->selection->end_y++;
829 break;
830 default:
831 return FALSE;
832 break;
833 }
834
835 limit_selection (body);
836
837 announce_selection (body);
838
839 return FALSE;
840 }
841
842 gboolean
ssw_sheet_body_get_active_cell(SswSheetBody * body,gint * col,gint * row)843 ssw_sheet_body_get_active_cell (SswSheetBody *body,
844 gint *col, gint *row)
845 {
846 return get_active_cell (body, col, row);
847 }
848
849 void
ssw_sheet_body_set_active_cell(SswSheetBody * body,gint col,gint row,GdkEvent * e)850 ssw_sheet_body_set_active_cell (SswSheetBody *body,
851 gint col, gint row, GdkEvent *e)
852 {
853 PRIV_DECL (body);
854
855 if (!priv->editable)
856 return;
857
858 if (priv->editor && (GTK_WIDGET (body) == priv->sheet->selected_body))
859 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (priv->editor));
860
861 gint oldrow = -1, oldcol = -1;
862 get_active_cell (body, &oldcol, &oldrow);
863
864 if (row == -1)
865 row = oldrow;
866
867 if (col == -1)
868 col = oldcol;
869
870 if (row == -1)
871 row = 0;
872
873 if (col == -1)
874 col = 0;
875
876 set_active_cell (body, col, row);
877
878 start_editing (body, e);
879
880 /* Do not announce the selection if it hasn't changed.
881 Otherwise infinite loops can occur :( */
882 if (oldrow != row || oldcol != col)
883 {
884 set_selection (body, col, row, col, row);
885 }
886 }
887
888 static gboolean
__key_press_event(GtkWidget * w,GdkEventKey * e)889 __key_press_event (GtkWidget *w, GdkEventKey *e)
890 {
891 SswSheetBody *body = SSW_SHEET_BODY (w);
892 PRIV_DECL (body);
893 /* If send_event is true, then this event has been generated by on_entry_activate*/
894 if ( !e->send_event &&
895 GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->key_press_event (w, e))
896 return TRUE;
897
898 if (is_printable_key (e->keyval))
899 {
900 if (priv->editor && priv->editable &&
901 (priv->sheet->selected_body == GTK_WIDGET (body)))
902 {
903 gtk_widget_grab_focus (priv->editor);
904 if (GTK_IS_ENTRY (priv->editor))
905 {
906 gchar out[7] = {0,0,0,0,0,0,0};
907 guint32 code = gdk_keyval_to_unicode (e->keyval);
908 g_unichar_to_utf8 (code, out);
909 gtk_entry_set_text (GTK_ENTRY(priv->editor), out);
910 gtk_editable_set_position (GTK_EDITABLE (priv->editor), -1);
911 }
912 }
913 return FALSE;
914 }
915
916 if (e->state & GDK_SHIFT_MASK &&
917 e->keyval != GDK_KEY_ISO_Left_Tab)
918 return __key_press_event_shifted (w, e);
919
920 const gint vlimit = ssw_sheet_axis_get_size (priv->vaxis) - 1;
921 gint row = -1, col = -1;
922 get_active_cell (body, &col, &row);
923
924 gint page_length = ssw_sheet_axis_get_visible_size (priv->vaxis) - 1;
925
926 gint hstep ;
927 gint h_end_limit ;
928 gint h_start_limit ;
929
930 if (ssw_sheet_axis_rtl (priv->haxis))
931 {
932 hstep = -1;
933 h_end_limit = 0;
934 h_start_limit = ssw_sheet_axis_get_size (priv->haxis) - 1;
935 }
936 else
937 {
938 hstep = +1;
939 h_end_limit = ssw_sheet_axis_get_size (priv->haxis) - 1;
940 h_start_limit = 0;
941 }
942
943 const gint old_row = row;
944 const gint old_col = col;
945
946 switch (e->keyval)
947 {
948 case GDK_KEY_Home:
949 col = 0;
950 row = 0;
951 break;
952 case GDK_KEY_Left:
953 if (e->state & GDK_CONTROL_MASK)
954 col = h_start_limit;
955 else
956 col -= hstep;
957 break;
958 case GDK_KEY_Right:
959 if (e->state & GDK_CONTROL_MASK)
960 col = h_end_limit;
961 else
962 col += hstep;
963 break;
964 case GDK_KEY_Page_Up:
965 row -= page_length;
966 break;
967 case GDK_KEY_Page_Down:
968 row += page_length;
969 break;
970 case GDK_KEY_Up:
971 if (e->state & GDK_CONTROL_MASK)
972 row = 0;
973 else
974 row--;
975 break;
976 case GDK_KEY_Down:
977 if (e->state & GDK_CONTROL_MASK)
978 row = G_MAXINT;
979 else
980 row++;
981 break;
982 case GDK_KEY_Return:
983 row++;
984 break;
985 case GDK_KEY_ISO_Left_Tab:
986 {
987 col --;
988 if (col < 0 && row > 0)
989 {
990 col = ssw_sheet_axis_get_size (priv->haxis) - 1;
991 row --;
992 }
993 }
994 break;
995 case GDK_KEY_Tab:
996 {
997 col ++;
998 if (col >= ssw_sheet_axis_get_size (priv->haxis) && row < vlimit)
999 {
1000 col = 0;
1001 row ++;
1002 }
1003 }
1004 break;
1005 default:
1006 return FALSE;
1007 break;
1008 }
1009
1010 trim_to_model_limits (body, old_col, old_row, &col, &row);
1011
1012 if (col > ssw_sheet_axis_get_last (priv->haxis))
1013 ssw_sheet_axis_jump_end (priv->haxis, col);
1014
1015 if (col < ssw_sheet_axis_get_first (priv->haxis))
1016 ssw_sheet_axis_jump_start (priv->haxis, col);
1017
1018 if (row > ssw_sheet_axis_get_last (priv->vaxis))
1019 ssw_sheet_axis_jump_end (priv->vaxis, row);
1020
1021 if (row < ssw_sheet_axis_get_first (priv->vaxis))
1022 ssw_sheet_axis_jump_start (priv->vaxis, row);
1023
1024 ssw_sheet_body_set_active_cell (body, col, row, (GdkEvent *) e);
1025
1026 if (old_row != row || old_col != col)
1027 set_selection (body, col, row, col, row);
1028
1029 /* Return true, to stop keys unfocusing the sheet */
1030 return TRUE;
1031 }
1032
1033
1034
1035 static gboolean
__motion_notify_event(GtkWidget * w,GdkEventMotion * e)1036 __motion_notify_event (GtkWidget *w, GdkEventMotion *e)
1037 {
1038 SswSheetBody *body = SSW_SHEET_BODY (w);
1039 PRIV_DECL (body);
1040
1041 if (GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->motion_notify_event (w, e))
1042 return TRUE;
1043
1044 if (gtk_gesture_is_active (priv->horizontal_resize_gesture) || gtk_gesture_is_active (priv->selection_gesture))
1045 return FALSE;
1046
1047 const int threshold = 5;
1048
1049 gint xpos;
1050 gint xsize;
1051 gint ypos;
1052 gint ysize;
1053
1054 GdkWindow *win = gtk_widget_get_window (w);
1055
1056 gdouble xm = e->x;
1057 gdouble ym = e->y;
1058 if (win != e->window)
1059 {
1060 gdk_window_coords_to_parent (e->window, e->x, e->y, &xm, &ym);
1061 }
1062
1063 gint x = ssw_sheet_axis_find_cell (priv->haxis, xm, &xpos, &xsize);
1064 gint y = ssw_sheet_axis_find_cell (priv->vaxis, ym, &ypos, &ysize);
1065
1066 if (x < 0 || y < 0)
1067 {
1068 gdk_window_set_cursor (win, NULL);
1069 return FALSE;
1070 }
1071
1072 gint nearest_x =
1073 (fabs (xpos - xm) < fabs (xpos + xsize - xm)) ? xpos : xpos + xsize;
1074 gint nearest_y =
1075 (fabs (ypos - ym) < fabs (ypos + ysize - ym)) ? ypos : ypos + ysize;
1076
1077 int xdiff = fabs (nearest_x - xm);
1078 int ydiff = fabs (nearest_y - ym);
1079
1080 x = (fabs (xpos - xm) < fabs (xpos + xsize - xm)) ? x : x + 1;
1081 y = (fabs (ypos - ym) < fabs (ypos + ysize - ym)) ? y : y + 1;
1082
1083 gint row = -1, col = -1;
1084 get_active_cell (body, &col, &row);
1085
1086 if ((xdiff < threshold) && (ydiff < threshold) &&
1087 (row == y - 1 && col == x - 1))
1088 {
1089 gdk_window_set_cursor (win, priv->xc);
1090 }
1091 else if (xdiff < threshold && x > 0)
1092 {
1093 gdk_window_set_cursor (win, priv->hc);
1094 }
1095 else if (ydiff < threshold && y > 0)
1096 {
1097 gdk_window_set_cursor (win, priv->vc);
1098 }
1099 else
1100 {
1101 gdk_window_set_cursor (win, NULL);
1102 }
1103
1104 return FALSE;
1105 }
1106
1107
1108 GtkWidget *
1109
ssw_sheet_body_new(SswSheet * sheet)1110 ssw_sheet_body_new (SswSheet *sheet)
1111 {
1112 return GTK_WIDGET (g_object_new (SSW_TYPE_SHEET_BODY,
1113 "sheet", sheet,
1114 NULL));
1115 }
1116
1117
1118 enum
1119 {
1120 PROP_0,
1121 PROP_VAXIS,
1122 PROP_HAXIS,
1123 PROP_DATA_MODEL,
1124 PROP_GRIDLINES,
1125 PROP_EDITABLE,
1126 PROP_SELECTION,
1127 PROP_RENDERER_FUNC,
1128 PROP_CONVERT_FWD_FUNC,
1129 PROP_CONVERT_REV_FUNC,
1130 PROP_SHEET
1131 };
1132
1133 static void
select_entire_row(SswSheetBody * body,gint i,guint state,gpointer ud)1134 select_entire_row (SswSheetBody *body, gint i, guint state, gpointer ud)
1135 {
1136 PRIV_DECL (body);
1137
1138 GdkDisplay *disp = gtk_widget_get_display (GTK_WIDGET (body));
1139 GdkKeymap *km = gdk_keymap_get_for_display (disp);
1140 GdkModifierType extend_mask =
1141 gdk_keymap_get_modifier_mask (km, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
1142
1143 gint start_y = (extend_mask & state) ? priv->selection->start_y : i;
1144
1145 set_selection (body,
1146 0, start_y,
1147 ssw_sheet_axis_get_size (priv->haxis) - 1, i);
1148 start_editing (body, NULL);
1149 }
1150
1151 static void
select_entire_column(SswSheetBody * body,gint i,guint state,gpointer ud)1152 select_entire_column (SswSheetBody *body, gint i, guint state, gpointer ud)
1153 {
1154 PRIV_DECL (body);
1155 GdkDisplay *disp = gtk_widget_get_display (GTK_WIDGET (body));
1156 GdkKeymap *km = gdk_keymap_get_for_display (disp);
1157 GdkModifierType extend_mask =
1158 gdk_keymap_get_modifier_mask (km, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
1159
1160 gint start_x = (extend_mask & state) ? priv->selection->start_x : i;
1161
1162 set_selection (body,
1163 start_x, 0,
1164 i, ssw_sheet_axis_get_size (priv->vaxis) - 1);
1165 start_editing (body, NULL);
1166 }
1167
1168
1169 static void
set_editor_widget_value(SswSheetBody * body,GValue * value,GtkEditable * editable)1170 set_editor_widget_value (SswSheetBody *body, GValue *value, GtkEditable *editable)
1171 {
1172 PRIV_DECL (body);
1173
1174 if (editable == NULL)
1175 return;
1176
1177 gint row = -1, col = -1;
1178 get_active_cell (body, &col, &row);
1179
1180 /* Allow the datum to be changed only if the "editable" property is set */
1181 gtk_editable_set_editable (editable, priv->editable);
1182
1183 if (GTK_IS_SPIN_BUTTON (editable))
1184 {
1185 if (G_IS_VALUE (value))
1186 {
1187 GValue dvalue = G_VALUE_INIT;
1188 g_value_init (&dvalue, G_TYPE_DOUBLE);
1189 if (g_value_transform (value, &dvalue))
1190 {
1191 gtk_spin_button_set_value (GTK_SPIN_BUTTON (editable), g_value_get_double (&dvalue));
1192 }
1193 g_value_unset (&dvalue);
1194 }
1195 }
1196 else if (GTK_IS_ENTRY (editable))
1197 {
1198 gchar *s = NULL;
1199 if (G_IS_VALUE (value))
1200 s = priv->cf (priv->sheet, priv->data_model, col, row, value);
1201 gtk_entry_set_text (GTK_ENTRY (editable), s ? s : "");
1202 g_free (s);
1203 }
1204 else if (GTK_IS_COMBO_BOX (editable))
1205 {
1206 gint n = -1;
1207 if (G_IS_VALUE (value))
1208 n = g_value_get_enum (value);
1209 gtk_combo_box_set_active (GTK_COMBO_BOX (editable), n);
1210 }
1211 else
1212 {
1213 gchar *x = NULL;
1214 if (G_IS_VALUE (value))
1215 x = g_strdup_value_contents (value);
1216 g_warning ("Unhandled edit widget %s, when dealing with %s\n",
1217 G_OBJECT_TYPE_NAME (editable), x);
1218 g_free (x);
1219 }
1220 }
1221
1222 static void
update_editable(SswSheetBody * body)1223 update_editable (SswSheetBody *body)
1224 {
1225 PRIV_DECL (body);
1226
1227 gint row = -1, col = -1;
1228 if (! get_active_cell (body, &col, &row))
1229 return;
1230
1231 GtkTreeIter iter;
1232 if (gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row))
1233 {
1234 GValue value = G_VALUE_INIT;
1235 gtk_tree_model_get_value (priv->data_model, &iter, col, &value);
1236
1237 set_editor_widget_value (body, &value, GTK_EDITABLE (priv->editor));
1238 g_value_unset (&value);
1239 }
1240 }
1241
1242 static void
on_data_change(GtkTreeModel * tm,guint posn,guint rm,guint add,gpointer p)1243 on_data_change (GtkTreeModel *tm, guint posn, guint rm, guint add, gpointer p)
1244 {
1245 /* Set the "editor widget" (typically a GtkEntry) to reflect the new value */
1246 SswSheetBody *body = SSW_SHEET_BODY (p);
1247 gtk_widget_queue_draw (GTK_WIDGET (body));
1248 update_editable (body);
1249 }
1250
1251 static void
__set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1252 __set_property (GObject *object,
1253 guint prop_id, const GValue *value, GParamSpec *pspec)
1254 {
1255 SswSheetBody *body = SSW_SHEET_BODY (object);
1256 PRIV_DECL (body);
1257
1258 switch (prop_id)
1259 {
1260 case PROP_SHEET:
1261 priv->sheet = g_value_get_object (value);
1262 break;
1263 case PROP_VAXIS:
1264 priv->vaxis = g_value_get_object (value);
1265 g_signal_connect_swapped (priv->vaxis, "changed",
1266 G_CALLBACK (scroll_vertically), object);
1267 g_signal_connect_swapped (priv->vaxis, "header-clicked",
1268 G_CALLBACK (select_entire_row), object);
1269 break;
1270 case PROP_HAXIS:
1271 priv->haxis = g_value_get_object (value);
1272 g_signal_connect_swapped (priv->haxis, "changed",
1273 G_CALLBACK (scroll_horizontally), object);
1274 g_signal_connect_swapped (priv->haxis, "header-clicked",
1275 G_CALLBACK (select_entire_column), object);
1276 break;
1277 case PROP_GRIDLINES:
1278 priv->show_gridlines = g_value_get_boolean (value);
1279 gtk_widget_queue_draw (GTK_WIDGET (object));
1280 break;
1281 case PROP_EDITABLE:
1282 priv->editable = g_value_get_boolean (value);
1283 break;
1284 case PROP_RENDERER_FUNC:
1285 priv->renderer_func = g_value_get_pointer (value);
1286 break;
1287 case PROP_CONVERT_FWD_FUNC:
1288 priv->cf = g_value_get_pointer (value);
1289 gtk_widget_queue_draw (GTK_WIDGET (body));
1290 update_editable (body);
1291 break;
1292 case PROP_CONVERT_REV_FUNC:
1293 priv->revf = g_value_get_pointer (value);
1294 break;
1295 case PROP_SELECTION:
1296 priv->selection = g_value_get_pointer (value);
1297 break;
1298 case PROP_DATA_MODEL:
1299 g_set_object (&priv->data_model, g_value_get_object (value));
1300 g_signal_connect_object (priv->data_model, "items-changed",
1301 G_CALLBACK (on_data_change), body, 0);
1302 break;
1303 default:
1304 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1305 break;
1306 }
1307 }
1308
1309 static void
__get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1310 __get_property (GObject * object,
1311 guint prop_id, GValue * value, GParamSpec * pspec)
1312 {
1313 SswSheetBody *body = SSW_SHEET_BODY (object);
1314 PRIV_DECL (body);
1315
1316 switch (prop_id)
1317 {
1318 case PROP_SHEET:
1319 g_value_set_object (value, priv->sheet);
1320 break;
1321 case PROP_VAXIS:
1322 g_value_set_object (value, priv->vaxis);
1323 break;
1324 case PROP_HAXIS:
1325 g_value_set_object (value, priv->haxis);
1326 break;
1327 case PROP_RENDERER_FUNC:
1328 g_value_set_pointer (value, priv->renderer_func);
1329 break;
1330 case PROP_CONVERT_FWD_FUNC:
1331 g_value_set_pointer (value, priv->cf);
1332 break;
1333 case PROP_CONVERT_REV_FUNC:
1334 g_value_set_pointer (value, priv->revf);
1335 break;
1336 default:
1337 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1338 break;
1339 }
1340 }
1341
1342 static void
ssw_sheet_body_class_init(SswSheetBodyClass * class)1343 ssw_sheet_body_class_init (SswSheetBodyClass * class)
1344 {
1345 GObjectClass *object_class = G_OBJECT_CLASS (class);
1346 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1347
1348 GParamSpec *sheet_spec =
1349 g_param_spec_object ("sheet",
1350 P_("Sheet"),
1351 P_("The SswSheet to which this body belongs"),
1352 SSW_TYPE_SHEET,
1353 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1354
1355 GParamSpec *haxis_spec =
1356 g_param_spec_object ("horizontal-axis",
1357 P_("Horizontal Axis"),
1358 P_("The Horizontal Axis"),
1359 SSW_TYPE_SHEET_AXIS,
1360 G_PARAM_READWRITE);
1361
1362 GParamSpec *vaxis_spec =
1363 g_param_spec_object ("vertical-axis",
1364 P_("Vertical Axis"),
1365 P_("The Vertical Axis"),
1366 SSW_TYPE_SHEET_AXIS,
1367 G_PARAM_READWRITE);
1368
1369 GParamSpec *data_model_spec =
1370 g_param_spec_object ("data-model",
1371 P_("Data Model"),
1372 P_("The model describing the contents of the data"),
1373 GTK_TYPE_TREE_MODEL,
1374 G_PARAM_READWRITE);
1375
1376 GParamSpec *gridlines_spec =
1377 g_param_spec_boolean ("gridlines",
1378 P_("Show Gridlines"),
1379 P_("True if gridlines should be shown"),
1380 TRUE,
1381 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1382
1383 GParamSpec *editable_spec =
1384 g_param_spec_boolean ("editable",
1385 P_("Editable"),
1386 P_("True if the data may be edited"),
1387 TRUE,
1388 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1389
1390 GParamSpec *renderer_func_spec =
1391 g_param_spec_pointer ("select-renderer-func",
1392 P_("Select Renderer Function"),
1393 P_("Function returning the renderer to use for a cell"),
1394 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1395
1396 GParamSpec *convert_fwd_spec =
1397 g_param_spec_pointer ("forward-conversion",
1398 P_("Forward conversion function"),
1399 P_("A function to convert a cell datum to a string"),
1400 G_PARAM_READWRITE);
1401
1402 GParamSpec *convert_rev_spec =
1403 g_param_spec_pointer ("reverse-conversion",
1404 P_("Reverse conversion function"),
1405 P_("A function to convert a string to a cell datum"),
1406 G_PARAM_READWRITE);
1407
1408 GParamSpec *selection_spec =
1409 g_param_spec_pointer ("selection",
1410 P_("The selection"),
1411 P_("A pointer to the current selection"),
1412 G_PARAM_READWRITE);
1413
1414 object_class->set_property = __set_property;
1415 object_class->get_property = __get_property;
1416
1417
1418 g_object_class_install_property (object_class,
1419 PROP_SHEET,
1420 sheet_spec);
1421
1422 g_object_class_install_property (object_class,
1423 PROP_VAXIS,
1424 vaxis_spec);
1425
1426 g_object_class_install_property (object_class,
1427 PROP_HAXIS,
1428 haxis_spec);
1429
1430 g_object_class_install_property (object_class,
1431 PROP_GRIDLINES,
1432 gridlines_spec);
1433
1434 g_object_class_install_property (object_class,
1435 PROP_EDITABLE,
1436 editable_spec);
1437
1438 g_object_class_install_property (object_class,
1439 PROP_DATA_MODEL,
1440 data_model_spec);
1441
1442 g_object_class_install_property (object_class,
1443 PROP_RENDERER_FUNC,
1444 renderer_func_spec);
1445
1446 g_object_class_install_property (object_class,
1447 PROP_CONVERT_FWD_FUNC,
1448 convert_fwd_spec);
1449
1450 g_object_class_install_property (object_class,
1451 PROP_CONVERT_REV_FUNC,
1452 convert_rev_spec);
1453
1454 g_object_class_install_property (object_class,
1455 PROP_SELECTION,
1456 selection_spec);
1457
1458 object_class->dispose = __dispose;
1459 object_class->finalize = __finalize;
1460
1461 widget_class->draw = __draw;
1462 widget_class->realize = __realize;
1463 widget_class->button_press_event = __button_press_event;
1464 widget_class->button_release_event = __button_release_event;
1465 widget_class->motion_notify_event = __motion_notify_event;
1466 widget_class->key_press_event = __key_press_event;
1467
1468 signals [SELECTION_CHANGED] =
1469 g_signal_new ("selection-changed",
1470 G_TYPE_FROM_CLASS (class),
1471 G_SIGNAL_RUN_FIRST,
1472 0,
1473 NULL, NULL,
1474 g_cclosure_marshal_VOID__POINTER,
1475 G_TYPE_NONE,
1476 1,
1477 G_TYPE_POINTER);
1478
1479
1480 signals [VALUE_CHANGED] =
1481 g_signal_new ("value-changed",
1482 G_TYPE_FROM_CLASS (class),
1483 G_SIGNAL_RUN_FIRST,
1484 0,
1485 NULL, NULL,
1486 ssw_cclosure_marshal_VOID__INT_INT_POINTER,
1487 G_TYPE_NONE,
1488 3,
1489 G_TYPE_INT,
1490 G_TYPE_INT,
1491 G_TYPE_POINTER);
1492 }
1493
1494
1495 static void
selection_drag_begin(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1496 selection_drag_begin (GtkGesture *gesture,
1497 gdouble start_x,
1498 gdouble start_y,
1499 gpointer user_data)
1500 {
1501 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1502 PRIV_DECL (body);
1503
1504 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1505
1506 if (gdk_window_get_cursor (win) == priv->hc || gdk_window_get_cursor (win) == priv->vc)
1507 {
1508 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1509 return;
1510 }
1511
1512 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1513
1514 ssw_sheet_body_unset_selection (body);
1515
1516 priv->selection->start_x = ssw_sheet_axis_find_cell (priv->haxis, start_x, NULL, NULL);
1517 priv->selection->start_y = ssw_sheet_axis_find_cell (priv->vaxis, start_y, NULL, NULL);
1518
1519 priv->selection->end_x = priv->selection->start_x;
1520 priv->selection->end_y = priv->selection->start_y;
1521 }
1522
1523
1524
1525 static void
selection_drag_update(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1526 selection_drag_update (GtkGestureDrag *gesture,
1527 gdouble offset_x,
1528 gdouble offset_y,
1529 gpointer user_data)
1530 {
1531 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1532 PRIV_DECL (body);
1533
1534 gdouble start_x, start_y;
1535 gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture), &start_x, &start_y);
1536
1537 gint xsize, ysize;
1538 gint xpos, ypos;
1539
1540 gint x = ssw_sheet_axis_find_cell (priv->haxis, start_x + offset_x, &xpos, &xsize);
1541 gint y = ssw_sheet_axis_find_cell (priv->vaxis, start_y + offset_y, &ypos, &ysize);
1542
1543 if (x < 0 || y < 0)
1544 return ;
1545
1546 priv->selection->end_x = x - 1;
1547 priv->selection->end_y = y - 1;
1548
1549 if (start_x + offset_x > (gdouble) xpos - (gdouble) xsize/2.0)
1550 priv->selection->end_x++;
1551
1552 if (start_y + offset_y > (gdouble) ypos - (gdouble) ysize/2.0)
1553 priv->selection->end_y++;
1554
1555 limit_selection (body);
1556
1557
1558 gtk_widget_queue_draw (GTK_WIDGET (body));
1559 }
1560
1561 static void
selection_drag_end(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1562 selection_drag_end (GtkGestureDrag *gesture,
1563 gdouble offset_x,
1564 gdouble offset_y,
1565 gpointer user_data)
1566 {
1567 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1568
1569 GdkEventSequence *seq =
1570 gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
1571
1572 GtkEventSequenceState state =
1573 gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
1574
1575 if (state == GTK_EVENT_SEQUENCE_DENIED)
1576 return;
1577
1578 announce_selection (body);
1579 }
1580
1581
1582
1583 /* The following targets are emitted by Libreoffice:
1584
1585 text/plain;charset=utf-8
1586 text/plain;charset=UTF-8
1587 UTF-8
1588 UTF8_STRING
1589 COMPOUND_TEXT
1590 STRING
1591 application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"
1592 application/x-openoffice-objectdescriptor-xml;windows_formatname="Star Object Descriptor (XML)";classname="47BBB4CB-CE4C-4E80-a591-42d9ae74950f";typename="calc8";viewaspect="1";width="4517";height="3162";posx="0";posy="0"
1593 application/x-openoffice-gdimetafile;windows_formatname="GDIMetaFile"
1594 application/x-openoffice-emf;windows_formatname="Image EMF"
1595 application/x-openoffice-wmf;windows_formatname="Image WMF"
1596 image/png
1597 application/x-openoffice-bitmap;windows_formatname="Bitmap"
1598 PIXMAP
1599 image/bmp
1600 text/html
1601 application/x-openoffice-sylk;windows_formatname="Sylk"
1602 application/x-openoffice-link;windows_formatname="Link"
1603 application/x-openoffice-dif;windows_formatname="DIF"
1604 text/richtext
1605 MULTIPLE
1606
1607 And Gnumeric generates the following targets:
1608
1609 TIMESTAMP
1610 TARGETS
1611 MULTIPLE
1612 SAVE_TARGETS
1613 application/x-gnumeric
1614 text/html
1615 UTF8_STRING
1616 COMPOUND_TEXT
1617 STRING
1618
1619 */
1620
1621
1622 enum target_info
1623 {
1624 TARGET_TEXT_TSV,
1625 TARGET_TEXT_CSV,
1626 TARGET_HTML,
1627 TARGET_TEXT_PLAIN,
1628 TARGET_UTF8_STRING,
1629 TARGET_STRING,
1630 N_TARGETS
1631 };
1632
1633 static const GtkTargetEntry targets[] =
1634 {
1635 {
1636 "text/tab-separated-values",
1637 GTK_TARGET_OTHER_APP,
1638 TARGET_TEXT_TSV
1639 },
1640 {
1641 "text/csv",
1642 GTK_TARGET_OTHER_APP,
1643 TARGET_TEXT_CSV
1644 },
1645 {
1646 "text/html",
1647 GTK_TARGET_OTHER_APP,
1648 TARGET_HTML
1649 },
1650 {
1651 "text/plain",
1652 GTK_TARGET_OTHER_APP,
1653 TARGET_TEXT_PLAIN
1654 },
1655 {
1656 "UTF8_STRING",
1657 GTK_TARGET_OTHER_APP,
1658 TARGET_UTF8_STRING
1659 },
1660 {
1661 "STRING",
1662 GTK_TARGET_OTHER_APP,
1663 TARGET_STRING
1664 },
1665 };
1666
1667
1668 /*
1669 Append a string representation of the value contained in MODEL,ITER,COL
1670 to the string OUTPUT.
1671 */
1672 static void
append_value_to_string(SswSheetBody * body,GtkTreeIter * iter,gint col,gint row,GString * output)1673 append_value_to_string (SswSheetBody *body,
1674 GtkTreeIter *iter, gint col, gint row,
1675 GString *output)
1676 {
1677 PRIV_DECL (body);
1678 GtkTreeModel *model = priv->data_model;
1679 GValue value = G_VALUE_INIT;
1680 GValue target_value = G_VALUE_INIT;
1681 g_value_init (&target_value, G_TYPE_STRING);
1682 gtk_tree_model_get_value (model, iter, col, &value);
1683
1684 if (priv->cf)
1685 {
1686 gchar *x = priv->cf (priv->sheet, model, col, row, &value);
1687 g_string_append (output, x);
1688 g_free (x);
1689 }
1690 else if (g_value_transform (&value, &target_value))
1691 {
1692 g_string_append (output, g_value_get_string (&target_value));
1693 }
1694 else
1695 {
1696 g_warning ("Pasting from SswSheet failed. "
1697 "You must register a transform function for source "
1698 "type \"%s\" to dest type \"%s\"\n",
1699 G_VALUE_TYPE_NAME (&value),
1700 g_type_name (G_TYPE_STRING));
1701 }
1702
1703 g_value_unset (&value);
1704 g_value_unset (&target_value);
1705 }
1706
1707
1708
1709 static void
clipit_html(SswSheetBody * body,GString * output,SswRange * source_range)1710 clipit_html (SswSheetBody *body, GString *output, SswRange *source_range)
1711 {
1712 PRIV_DECL (body);
1713 if (!priv->haxis || !priv->vaxis || !priv->data_model)
1714 return;
1715
1716 g_string_append (output, "<body>\n");
1717 g_string_append (output, "<table>\n");
1718
1719 gint row;
1720 gint col;
1721 for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1722 {
1723 GtkTreeIter iter;
1724 gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1725 g_string_append (output, "<tr>\n");
1726 for (col = source_range->start_x; col <= source_range->end_x; ++col)
1727 {
1728 if (row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1729 && col < gtk_tree_model_get_n_columns (priv->data_model))
1730 {
1731 g_string_append (output, "<td>");
1732 append_value_to_string (body, &iter, col, row, output);
1733 g_string_append (output, "</td>\n");
1734 }
1735 }
1736 g_string_append (output, "</tr>\n");
1737 }
1738
1739 g_string_append (output, "</table>\n");
1740 g_string_append (output, "</body>\n");
1741 }
1742
1743
1744 static void
clipit_ascii(SswSheetBody * body,GString * output,SswRange * source_range)1745 clipit_ascii (SswSheetBody *body, GString *output, SswRange *source_range)
1746 {
1747 PRIV_DECL (body);
1748 if (!priv->haxis || !priv->vaxis || !priv->data_model)
1749 return;
1750
1751 gint row;
1752 gint col;
1753 for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1754 {
1755 GtkTreeIter iter;
1756 gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1757
1758 for (col = source_range->start_x; col <= source_range->end_x; ++col)
1759 {
1760 if (priv->data_model
1761 && row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1762 && col < gtk_tree_model_get_n_columns (priv->data_model))
1763 {
1764 append_value_to_string (body, &iter, col, row, output);
1765
1766 if (col < source_range->end_x)
1767 g_string_append (output, "\t");
1768 }
1769 }
1770 if (row < source_range->end_y)
1771 g_string_append (output, "\n");
1772 }
1773 }
1774
1775 static void
clipit_utf8(SswSheetBody * body,GString * output,SswRange * source_range)1776 clipit_utf8 (SswSheetBody *body, GString *output, SswRange *source_range)
1777 {
1778 PRIV_DECL (body);
1779 if (!priv->haxis || !priv->vaxis || !priv->data_model)
1780 return;
1781
1782 gint row;
1783 gint col;
1784 for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1785 {
1786 GtkTreeIter iter;
1787 gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1788
1789 for (col = source_range->start_x; col <= source_range->end_x; ++col)
1790 {
1791 if (row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1792 && col < gtk_tree_model_get_n_columns (priv->data_model))
1793 {
1794 append_value_to_string (body, &iter, col, row, output);
1795
1796 if (col < source_range->end_x)
1797 g_string_append_c (output, '\t');
1798 }
1799 }
1800 if (row < source_range->end_y)
1801 g_string_append (output, "\n");
1802 }
1803 }
1804
1805
1806 static void
get_func(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer owner)1807 get_func (GtkClipboard *clipboard,
1808 GtkSelectionData *selection_data,
1809 guint info,
1810 gpointer owner)
1811 {
1812 SswSheetBody *body = SSW_SHEET_BODY (owner);
1813
1814 if (body == NULL)
1815 {
1816 gtk_clipboard_clear (clipboard);
1817 return;
1818 }
1819
1820 SswRange *source_range = g_object_get_data (G_OBJECT (clipboard), "source-range");
1821 g_return_if_fail (source_range);
1822
1823 GString *stuff = g_string_new ("");
1824 switch (info)
1825 {
1826 case TARGET_STRING:
1827 clipit_ascii (body, stuff, source_range);
1828 break;
1829 case TARGET_UTF8_STRING:
1830 clipit_utf8 (body, stuff, source_range);
1831 break;
1832 case TARGET_HTML:
1833 clipit_html (body, stuff, source_range);
1834 break;
1835 default:
1836 g_warning ("Request for unknown target %d\n", info);
1837 return ;
1838 break;
1839 }
1840
1841 gtk_selection_data_set (selection_data,
1842 gdk_atom_intern_static_string (targets[info].target),
1843 CHAR_BIT,
1844 (gpointer) stuff->str, stuff->len);
1845
1846 g_string_free (stuff, TRUE);
1847 }
1848
1849
1850 static void
clear_func(GtkClipboard * clipboard,gpointer user_data_or_owner)1851 clear_func (GtkClipboard *clipboard,
1852 gpointer user_data_or_owner)
1853 {
1854 }
1855
1856 void
ssw_sheet_body_set_clip(SswSheetBody * body,GtkClipboard * clip)1857 ssw_sheet_body_set_clip (SswSheetBody *body, GtkClipboard *clip)
1858 {
1859 PRIV_DECL (body);
1860 if (body == NULL)
1861 return;
1862
1863 if (priv->editor &&
1864 GTK_IS_EDITABLE (priv->editor) &&
1865 gtk_widget_is_focus (priv->editor))
1866 {
1867 gtk_editable_copy_clipboard (GTK_EDITABLE(priv->editor));
1868 return;
1869 }
1870
1871 SswRange *source_range = g_object_get_data (G_OBJECT (clip), "source-range");
1872 g_free (source_range);
1873 source_range = g_malloc (sizeof (*source_range));
1874 g_object_set_data (G_OBJECT (clip), "source-range", source_range);
1875 normalise_selection (priv->selection, source_range);
1876
1877 if (!gtk_clipboard_set_with_owner (clip, targets, N_TARGETS,
1878 get_func, clear_func, G_OBJECT (body)))
1879 {
1880 g_warning ("Clip failed\n");
1881 }
1882 }
1883
1884 static void
drag_begin_resize_horizontal(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1885 drag_begin_resize_horizontal (GtkGesture *gesture,
1886 gdouble start_x,
1887 gdouble start_y,
1888 gpointer user_data)
1889 {
1890 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1891 PRIV_DECL (body);
1892 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1893
1894 if (gdk_window_get_cursor (win) == priv->hc)
1895 {
1896 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1897 }
1898 else
1899 {
1900 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1901 }
1902 }
1903
1904 static void
drag_begin_resize_vertical(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1905 drag_begin_resize_vertical (GtkGesture *gesture,
1906 gdouble start_x,
1907 gdouble start_y,
1908 gpointer user_data)
1909 {
1910 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1911 PRIV_DECL (body);
1912 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1913
1914 if (gdk_window_get_cursor (win) == priv->vc)
1915 {
1916 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1917 }
1918 else
1919 {
1920 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1921 }
1922 }
1923
1924 static void
drag_update_resize(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1925 drag_update_resize (GtkGestureDrag *gesture,
1926 gdouble offset_x,
1927 gdouble offset_y,
1928 gpointer user_data)
1929 {
1930 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1931
1932 gtk_widget_queue_draw (GTK_WIDGET (body));
1933 }
1934
1935
1936
1937 static void
drag_end_resize_horizontal(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1938 drag_end_resize_horizontal (GtkGestureDrag *gesture,
1939 gdouble offset_x,
1940 gdouble offset_y,
1941 gpointer user_data)
1942 {
1943 gdouble start_x, start_y;
1944 SswSheetBody *body = SSW_SHEET_BODY (user_data);
1945 PRIV_DECL (body);
1946 gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
1947
1948 GdkEventSequence *seq =
1949 gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
1950
1951 GtkEventSequenceState state =
1952 gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
1953
1954 if (state == GTK_EVENT_SEQUENCE_DENIED)
1955 return;
1956
1957 gint nearest = -1;
1958 gint pos, size;
1959 gint item = ssw_sheet_axis_find_cell (priv->haxis, start_x, &pos, &size);
1960 if (ssw_sheet_axis_rtl (priv->haxis))
1961 {
1962 nearest =
1963 (start_x - pos) < (pos + size - start_x) ? item: item - 1 ;
1964 }
1965 else
1966 {
1967 nearest =
1968 (start_x - pos) < (pos + size - start_x) ? item - 1: item ;
1969 }
1970
1971 gint old_size;
1972 ssw_sheet_axis_find_boundary (priv->haxis, nearest, NULL, &old_size);
1973 const gint new_size = ssw_sheet_axis_rtl (priv->haxis) ?
1974 old_size - offset_x : old_size + offset_x;
1975
1976 gint row = -1, col = -1;
1977 get_active_cell (body, &col, &row);
1978
1979 if (nearest == col)
1980 {
1981 gint vsize;
1982
1983 ssw_sheet_axis_find_boundary (priv->vaxis, row, NULL, &vsize);
1984
1985
1986 gtk_widget_set_size_request (GTK_WIDGET (priv->editor),
1987 new_size - linewidth, vsize);
1988
1989 g_object_set (priv->active_cell_holder,
1990 "hconstraint", new_size - linewidth,
1991 NULL);
1992 }
1993
1994 ssw_sheet_axis_override_size (priv->haxis, nearest, new_size);
1995 }
1996
1997
1998 static void
drag_end_resize_vertical(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1999 drag_end_resize_vertical (GtkGestureDrag *gesture,
2000 gdouble offset_x,
2001 gdouble offset_y,
2002 gpointer user_data)
2003 {
2004 gdouble start_x, start_y;
2005 SswSheetBody *body = SSW_SHEET_BODY (user_data);
2006 PRIV_DECL (body);
2007 gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
2008
2009 GdkEventSequence *seq =
2010 gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
2011
2012 GtkEventSequenceState state =
2013 gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
2014
2015 if (state == GTK_EVENT_SEQUENCE_DENIED)
2016 return;
2017
2018 gint pos, size;
2019 gint item = ssw_sheet_axis_find_cell (priv->vaxis, start_y, &pos, &size);
2020 gint nearest = (start_y - pos) < (pos + size - start_y) ? item - 1: item ;
2021
2022
2023 gint old_size;
2024 ssw_sheet_axis_find_boundary (priv->vaxis, nearest, NULL, &old_size);
2025 const gint new_size = old_size + offset_y;
2026
2027 gint row = -1, col = -1;
2028 get_active_cell (body, &col, &row);
2029
2030 if (nearest == row)
2031 {
2032 gint hsize;
2033 ssw_sheet_axis_find_boundary (priv->haxis, nearest, &hsize, NULL);
2034
2035 gtk_widget_set_size_request (GTK_WIDGET (priv->editor),
2036 hsize,
2037 new_size - linewidth);
2038
2039 g_object_set (priv->active_cell_holder,
2040 "vconstraint", new_size - linewidth,
2041 NULL);
2042 }
2043
2044 ssw_sheet_axis_override_size (priv->vaxis, nearest, new_size);
2045 }
2046
2047 static void
ssw_sheet_body_init(SswSheetBody * body)2048 ssw_sheet_body_init (SswSheetBody *body)
2049 {
2050 PRIV_DECL (body);
2051
2052 /* This is derived from GtkLayout. So it has its own window. */
2053 gtk_widget_set_has_window (GTK_WIDGET (body), TRUE);
2054 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (body));
2055 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (body));
2056
2057 gtk_style_context_add_class (context, "cell");
2058
2059 priv->sheet = NULL;
2060 priv->path[0] = '\0';
2061 priv->dispose_has_run = FALSE;
2062
2063 priv->default_renderer = gtk_cell_renderer_text_new ();
2064
2065 priv->vc = gdk_cursor_new_for_display (display, GDK_SB_V_DOUBLE_ARROW);
2066 priv->hc = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW);
2067 priv->xc = gdk_cursor_new_for_display (display, GDK_SIZING);
2068
2069 priv->selection_gesture = gtk_gesture_drag_new (GTK_WIDGET (body));
2070
2071
2072 g_signal_connect (priv->selection_gesture, "drag-begin",
2073 G_CALLBACK (selection_drag_begin), body);
2074
2075 g_signal_connect (priv->selection_gesture, "drag-update",
2076 G_CALLBACK (selection_drag_update), body);
2077
2078 g_signal_connect (priv->selection_gesture, "drag-end",
2079 G_CALLBACK (selection_drag_end), body);
2080
2081 priv->horizontal_resize_gesture =
2082 gtk_gesture_drag_new (GTK_WIDGET (body));
2083
2084 g_signal_connect (priv->horizontal_resize_gesture, "drag-begin",
2085 G_CALLBACK (drag_begin_resize_horizontal), body);
2086
2087 g_signal_connect (priv->horizontal_resize_gesture, "drag-update",
2088 G_CALLBACK (drag_update_resize), body);
2089
2090 g_signal_connect (priv->horizontal_resize_gesture, "drag-end",
2091 G_CALLBACK (drag_end_resize_horizontal), body);
2092
2093 priv->vertical_resize_gesture =
2094 gtk_gesture_drag_new (GTK_WIDGET (body));
2095
2096 g_signal_connect (priv->vertical_resize_gesture, "drag-begin",
2097 G_CALLBACK (drag_begin_resize_vertical), body);
2098
2099 g_signal_connect (priv->vertical_resize_gesture, "drag-update",
2100 G_CALLBACK (drag_update_resize), body);
2101
2102 g_signal_connect (priv->vertical_resize_gesture, "drag-end",
2103 G_CALLBACK (drag_end_resize_vertical), body);
2104
2105
2106 priv->data_model = NULL;
2107 priv->editor = NULL;
2108 priv->cf = ssw_sheet_default_forward_conversion;
2109 priv->revf = ssw_sheet_default_reverse_conversion;
2110
2111 priv->active_cell_holder = ssw_constraint_new ();
2112 /* Keep the constraint widget well out of the visible range */
2113 gtk_layout_put (GTK_LAYOUT (body), priv->active_cell_holder, -99, -99);
2114 }
2115
2116 void
ssw_sheet_body_unset_selection(SswSheetBody * body)2117 ssw_sheet_body_unset_selection (SswSheetBody *body)
2118 {
2119 PRIV_DECL (body);
2120 priv->selection->start_x = priv->selection->start_y = -1;
2121 priv->selection->end_x = priv->selection->end_y = -1;
2122
2123 gtk_widget_queue_draw (GTK_WIDGET (body));
2124 }
2125
2126
2127 void
ssw_sheet_body_value_to_string(SswSheetBody * body,gint col,gint row,GString * output)2128 ssw_sheet_body_value_to_string (SswSheetBody *body, gint col, gint row,
2129 GString *output)
2130 {
2131 GtkTreeIter iter;
2132 PRIV_DECL (body);
2133
2134 gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
2135
2136 append_value_to_string (body, &iter, col, row, output);
2137 }
2138
2139
2140
2141
2142 static void text_editing_started (GtkCellRenderer *cell,
2143 GtkCellEditable *editable,
2144 const gchar *path,
2145 gpointer data);
2146
2147
2148 static GtkCellRenderer *
choose_renderer(SswSheetBody * body,gint col,gint row)2149 choose_renderer (SswSheetBody *body, gint col, gint row)
2150 {
2151 PRIV_DECL (body);
2152
2153 GtkCellRenderer *r = NULL;
2154
2155 if (priv->renderer_func)
2156 {
2157 GType t = gtk_tree_model_get_column_type (priv->data_model, col);
2158 r = priv->renderer_func (priv->sheet, col, row, t,
2159 priv->sheet->renderer_func_datum);
2160 }
2161
2162 if (r == NULL)
2163 r = priv->default_renderer;
2164
2165 g_object_set (r,
2166 "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
2167 "editable", TRUE,
2168 NULL);
2169
2170 if (!g_object_get_data (G_OBJECT (r), "ess"))
2171 {
2172 g_signal_connect (r,
2173 "editing-started",
2174 G_CALLBACK (text_editing_started),
2175 NULL);
2176
2177 g_object_set_data (G_OBJECT (r), "ess", GINT_TO_POINTER (TRUE));
2178 }
2179
2180 return r;
2181 }
2182
2183 static void
on_editing_done(GtkCellEditable * e,SswSheetBody * body)2184 on_editing_done (GtkCellEditable *e, SswSheetBody *body)
2185 {
2186 PRIV_DECL (body);
2187
2188 gint row = -1, col = -1;
2189 get_active_cell (body, &col, &row);
2190
2191 gboolean cell_value_unchanged = -1;
2192 g_object_get (e, "editing-canceled", &cell_value_unchanged, NULL);
2193
2194 /* Grab the focus back from the CellEditable */
2195 gtk_widget_grab_focus (GTK_WIDGET (body));
2196
2197 if (cell_value_unchanged)
2198 return;
2199
2200 GValue value = G_VALUE_INIT;
2201 if (GTK_IS_SPIN_BUTTON (e))
2202 {
2203 GType t = gtk_tree_model_get_column_type (priv->data_model, col);
2204 g_value_init (&value, t);
2205
2206 gdouble v = gtk_spin_button_get_value (GTK_SPIN_BUTTON (e));
2207 GValue dbl_value = G_VALUE_INIT;
2208 g_value_init (&dbl_value, G_TYPE_DOUBLE);
2209 g_value_set_double (&dbl_value, v);
2210 g_value_transform (&dbl_value, &value);
2211 g_value_unset (&dbl_value);
2212 }
2213 else if (GTK_IS_ENTRY (e))
2214 {
2215 const char *s = gtk_entry_get_text (GTK_ENTRY (e));
2216 priv->revf (priv->data_model, col, row, s, &value);
2217 }
2218 else if (GTK_IS_COMBO_BOX (e))
2219 {
2220 g_value_init (&value, G_TYPE_INT);
2221 gint n = gtk_combo_box_get_active (GTK_COMBO_BOX (e));
2222 g_value_set_int (&value, n);
2223 }
2224
2225 g_signal_emit (body, signals[VALUE_CHANGED], 0, col, row, &value);
2226
2227 g_value_unset (&value);
2228 }
2229
2230 static void
on_entry_activate(GtkEntry * e,gpointer ud)2231 on_entry_activate (GtkEntry *e, gpointer ud)
2232 {
2233 SswSheetBody *body = SSW_SHEET_BODY (ud);
2234 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
2235 g_return_if_fail (GDK_IS_WINDOW (win));
2236 GdkEvent *ev = gdk_event_new (GDK_KEY_PRESS);
2237 GdkEventKey *event = (GdkEventKey *) ev;
2238 event->window = g_object_ref(win);
2239 event->keyval = GDK_KEY_Return;
2240 event->send_event = TRUE;
2241 gboolean ret;
2242 g_signal_emit_by_name (body, "key-press-event", event, &ret);
2243 gdk_event_free (ev);
2244 }
2245
2246 static void
done_editing(GtkCellRendererCombo * combo,gchar * path_string,GtkTreeIter * new_iter,gpointer user_data)2247 done_editing (GtkCellRendererCombo *combo,
2248 gchar *path_string,
2249 GtkTreeIter *new_iter,
2250 gpointer user_data)
2251 {
2252 GtkCellEditable *ce = user_data;
2253 g_object_set (ce, "editing-canceled", FALSE, NULL);
2254 gtk_cell_editable_editing_done (ce);
2255 }
2256
2257
2258 static void
on_remove_widget(GtkCellEditable * e,gpointer ud)2259 on_remove_widget (GtkCellEditable *e, gpointer ud)
2260 {
2261 SswSheetBody *body = ud;
2262 PRIV_DECL (body);
2263
2264 gtk_widget_destroy (GTK_WIDGET (e));
2265 priv->editor = NULL;
2266 gtk_widget_hide (priv->active_cell_holder);
2267 }
2268
2269 static void
on_changed(GtkEditable * editable,gpointer user_data)2270 on_changed (GtkEditable *editable, gpointer user_data)
2271 {
2272 g_object_set (editable, "editing-canceled", FALSE, NULL);
2273 }
2274
2275 static gboolean
on_focus_out(GtkWidget * w,GdkEvent * e,gpointer ud)2276 on_focus_out (GtkWidget *w, GdkEvent *e, gpointer ud)
2277 {
2278 /* It seems that GtkEntry by default emits the "widget-remove" signal,
2279 in response to focus-out. We don't want that to happen, so disable
2280 it here. */
2281 return TRUE;
2282 }
2283
2284 static void
text_editing_started(GtkCellRenderer * cell,GtkCellEditable * editable,const gchar * path,gpointer data)2285 text_editing_started (GtkCellRenderer *cell,
2286 GtkCellEditable *editable,
2287 const gchar *path,
2288 gpointer data)
2289 {
2290 SswSheetBody *body = NULL;
2291 gint row = -1, col = -1;
2292 sscanf (path, "r%*dc%*ds%p", &body);
2293
2294 if (body == NULL) /* There is no active cell! */
2295 return;
2296
2297 get_active_cell (body, &col, &row);
2298 PRIV_DECL (body);
2299
2300 if (col < 0 || row < 0)
2301 {
2302 gtk_widget_destroy (GTK_WIDGET (editable));
2303 return;
2304 }
2305
2306 if (! gtk_widget_is_visible (GTK_WIDGET (body)))
2307 return;
2308
2309 if (priv->editor)
2310 {
2311 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell),
2312 TRUE);
2313
2314 g_signal_emit_by_name (priv->editor, "remove-widget");
2315 }
2316
2317 #if DEBUGGING
2318 GdkRGBA color = {1.0, 0, 0, 1};
2319 gtk_widget_override_background_color (GTK_WIDGET (editable),
2320 GTK_STATE_NORMAL,
2321 &color);
2322 #endif
2323
2324 priv->editor = NULL;
2325
2326 gint vlocation, vsize;
2327 gint hlocation, hsize;
2328 if (0 != ssw_sheet_axis_find_boundary (priv->vaxis, row, &vlocation, &vsize))
2329 return;
2330
2331 if (0 != ssw_sheet_axis_find_boundary (priv->haxis, col, &hlocation, &hsize))
2332 return;
2333
2334 priv->editor = GTK_WIDGET (editable);
2335
2336 #if GTK_CHECK_VERSION (3, 22, 20)
2337 /* Don't show the stupid "Insert Emoji" in the popup menu. */
2338 if (GTK_IS_ENTRY (priv->editor))
2339 g_object_set (priv->editor,
2340 "input-hints", GTK_INPUT_HINT_NO_EMOJI,
2341 NULL);
2342 #endif
2343
2344 g_signal_connect (editable, "remove-widget",
2345 G_CALLBACK (on_remove_widget), body);
2346
2347
2348 /* Avoid the gridlines */
2349 hsize -= linewidth;
2350 hlocation += linewidth;
2351 vsize -= linewidth;
2352 vlocation += linewidth;
2353
2354 gtk_widget_set_size_request (GTK_WIDGET (editable), hsize, vsize);
2355
2356 g_object_set (priv->active_cell_holder,
2357 "hconstraint", hsize,
2358 "vconstraint", vsize,
2359 NULL);
2360
2361 gtk_container_add (GTK_CONTAINER (priv->active_cell_holder),
2362 GTK_WIDGET (editable));
2363
2364 gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder,
2365 hlocation, vlocation);
2366
2367 GtkTreeIter iter;
2368 GValue value = G_VALUE_INIT;
2369 if (gtk_tree_model_iter_nth_child (priv->data_model,
2370 &iter, NULL, row))
2371 {
2372 gtk_tree_model_get_value (priv->data_model, &iter,
2373 col, &value);
2374 }
2375
2376 if (GTK_IS_ENTRY (editable))
2377 {
2378 g_signal_connect (editable, "focus-out-event", G_CALLBACK (on_focus_out), NULL);
2379 gtk_entry_set_text (GTK_ENTRY (editable), "");
2380 g_signal_connect (editable, "changed", G_CALLBACK (on_changed), NULL);
2381 }
2382 else if (GTK_IS_COMBO_BOX (editable))
2383 {
2384 gtk_combo_box_set_active (GTK_COMBO_BOX (editable), 0);
2385 }
2386
2387 g_signal_connect (editable, "editing-done",
2388 G_CALLBACK (on_editing_done), body);
2389
2390
2391 set_editor_widget_value (body, &value, GTK_EDITABLE (editable));
2392 g_value_unset (&value);
2393
2394 if (GTK_IS_SPIN_BUTTON (editable))
2395 {
2396 g_signal_connect (editable, "value-changed", G_CALLBACK (finish_editing), NULL);
2397 }
2398 else if (GTK_IS_ENTRY (editable))
2399 {
2400 /* "activate" means when the Enter key is pressed */
2401 g_signal_connect (editable, "activate", G_CALLBACK (on_entry_activate), body);
2402 }
2403 else if (GTK_IS_COMBO_BOX (editable))
2404 {
2405 g_signal_connect_object (cell, "changed", G_CALLBACK (done_editing),
2406 editable, 0);
2407 }
2408
2409 gtk_widget_show_all (priv->active_cell_holder);
2410 }
2411
2412 gboolean
ssw_sheet_body_paste_editable(SswSheetBody * body)2413 ssw_sheet_body_paste_editable (SswSheetBody *body)
2414 {
2415 PRIV_DECL (body);
2416 if (body &&
2417 priv->editor &&
2418 GTK_IS_EDITABLE (priv->editor) &&
2419 gtk_widget_is_focus (priv->editor))
2420 {
2421 gtk_editable_paste_clipboard (GTK_EDITABLE (priv->editor));
2422 return TRUE;
2423 }
2424 return FALSE;
2425 }
2426
2427 gboolean
ssw_sheet_body_cut_editable(SswSheetBody * body)2428 ssw_sheet_body_cut_editable (SswSheetBody *body)
2429 {
2430 PRIV_DECL (body);
2431 if (body &&
2432 priv->editor &&
2433 GTK_IS_ENTRY (priv->editor) &&
2434 gtk_widget_is_focus (priv->editor))
2435 {
2436 gtk_editable_cut_clipboard ( GTK_EDITABLE (priv->editor));
2437 return TRUE;
2438 }
2439 return FALSE;
2440 }
2441