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 "ssw-sheet.h"
21
22 #include "ssw-sheet-single.h"
23 #include "ssw-sheet-axis.h"
24 #include "ssw-axis-model.h"
25 #include "ssw-sheet-body.h"
26 #include "ssw-marshaller.h"
27 #include "ssw-xpaned.h"
28 #include "ssw-paste.h"
29
30 #define P_(X) (X)
31
32 G_DEFINE_TYPE (SswSheet, ssw_sheet, GTK_TYPE_BIN)
33
34 #define DIM 2
35
36 enum {ROW_HEADER_CLICKED,
37 ROW_HEADER_DOUBLE_CLICKED,
38 COLUMN_HEADER_CLICKED,
39 COLUMN_HEADER_DOUBLE_CLICKED,
40 ROW_HEADER_PRESSED,
41 ROW_HEADER_RELEASED,
42 COLUMN_HEADER_PRESSED,
43 COLUMN_HEADER_RELEASED,
44 SELECTION_CHANGED,
45 VALUE_CHANGED,
46 ROW_MOVED,
47 COLUMN_MOVED,
48 n_SIGNALS};
49
50 static guint signals [n_SIGNALS];
51
52 GtkWidget *
ssw_sheet_new(void)53 ssw_sheet_new (void)
54 {
55 GObject *object = g_object_new (SSW_TYPE_SHEET, NULL);
56
57 return GTK_WIDGET (object);
58 }
59
60 GtkWidget *
ssw_sheet_get_button(SswSheet * s)61 ssw_sheet_get_button (SswSheet *s)
62 {
63 return SSW_SHEET_SINGLE (s->sheet[0])->button;
64 }
65
66 static void
__realize(GtkWidget * w)67 __realize (GtkWidget *w)
68 {
69 SswSheet *sheet = SSW_SHEET (w);
70 GTK_WIDGET_CLASS (ssw_sheet_parent_class)->realize (w);
71 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (w));
72 GdkDisplay *display = gdk_window_get_display (win);
73 sheet->wait_cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
74 }
75
76 static void
__unrealize(GtkWidget * w)77 __unrealize (GtkWidget *w)
78 {
79 SswSheet *sheet = SSW_SHEET (w);
80 g_object_unref (sheet->wait_cursor);
81 GTK_WIDGET_CLASS (ssw_sheet_parent_class)->unrealize (w);
82 }
83
84 enum
85 {
86 PROP_0,
87 PROP_SELECTION,
88 PROP_SPLITTER,
89 PROP_VMODEL,
90 PROP_HMODEL,
91 PROP_DATA_MODEL,
92 PROP_SPLIT,
93 PROP_GRIDLINES,
94 PROP_EDITABLE,
95 PROP_HORIZONTAL_DRAGGABLE,
96 PROP_VERTICAL_DRAGGABLE,
97 PROP_RENDERER_FUNC,
98 PROP_RENDERER_FUNC_DATUM,
99 PROP_CONVERT_FWD_FUNC,
100 PROP_CONVERT_REV_FUNC
101 };
102
103 static void
on_header_button_pressed(SswSheetAxis * axis,gint id,guint button,guint state,gpointer user_data)104 on_header_button_pressed (SswSheetAxis *axis, gint id, guint button, guint state,
105 gpointer user_data)
106 {
107 SswSheet *sheet = SSW_SHEET (user_data);
108
109 GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
110
111 if (o == GTK_ORIENTATION_HORIZONTAL)
112 g_signal_emit (sheet, signals [COLUMN_HEADER_PRESSED], 0, id, button, state);
113 else
114 g_signal_emit (sheet, signals [ROW_HEADER_PRESSED], 0, id, button, state);
115 }
116
117
118 static void
on_header_button_released(SswSheetAxis * axis,gint id,guint button,guint state,gpointer user_data)119 on_header_button_released (SswSheetAxis *axis, gint id, guint button, guint state,
120 gpointer user_data)
121 {
122 SswSheet *sheet = SSW_SHEET (user_data);
123
124 GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
125
126 if (o == GTK_ORIENTATION_HORIZONTAL)
127 g_signal_emit (sheet, signals [COLUMN_HEADER_RELEASED], 0, id, button, state);
128 else
129 g_signal_emit (sheet, signals [ROW_HEADER_RELEASED], 0, id, button, state);
130 }
131
132
133 static void
on_header_clicked(SswSheetAxis * axis,gint which,guint state,gpointer user_data)134 on_header_clicked (SswSheetAxis *axis, gint which, guint state, gpointer user_data)
135 {
136 SswSheet *sheet = SSW_SHEET (user_data);
137
138 GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
139
140 if (o == GTK_ORIENTATION_HORIZONTAL)
141 g_signal_emit (sheet, signals [COLUMN_HEADER_CLICKED], 0, which);
142 else
143 g_signal_emit (sheet, signals [ROW_HEADER_CLICKED], 0, which);
144 }
145
146 static void
on_header_double_clicked(SswSheetAxis * axis,gint which,guint state,gpointer user_data)147 on_header_double_clicked (SswSheetAxis *axis, gint which, guint state, gpointer user_data)
148 {
149 SswSheet *sheet = SSW_SHEET (user_data);
150
151 GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
152
153 if (o == GTK_ORIENTATION_HORIZONTAL)
154 g_signal_emit (sheet, signals [COLUMN_HEADER_DOUBLE_CLICKED], 0, which);
155 else
156 g_signal_emit (sheet, signals [ROW_HEADER_DOUBLE_CLICKED], 0, which);
157 }
158
159
160 static void
arrange(SswSheet * sheet)161 arrange (SswSheet *sheet)
162 {
163 gint i;
164
165 for (i = 0; i < DIM; ++i)
166 {
167 if (sheet->vmodel)
168 ssw_sheet_axis_set_model (SSW_SHEET_AXIS (sheet->vertical_axis[i]),
169 sheet->vmodel);
170
171 if (sheet->hmodel)
172 ssw_sheet_axis_set_model (SSW_SHEET_AXIS (sheet->horizontal_axis[i]),
173 sheet->hmodel);
174 }
175 }
176
177 static void
__finalize(GObject * obj)178 __finalize (GObject *obj)
179 {
180 SswSheet *sheet = SSW_SHEET (obj);
181
182 g_free (sheet->selection);
183
184 G_OBJECT_CLASS (ssw_sheet_parent_class)->finalize (obj);
185 }
186
187 static void
__dispose(GObject * object)188 __dispose (GObject *object)
189 {
190 SswSheet *sheet = SSW_SHEET (object);
191
192 if (sheet->dispose_has_run)
193 return;
194
195 if (sheet->vmodel)
196 g_object_unref (sheet->vmodel);
197
198 if (sheet->hmodel)
199 g_object_unref (sheet->hmodel);
200
201 sheet->dispose_has_run = TRUE;
202
203 G_OBJECT_CLASS (ssw_sheet_parent_class)->dispose (object);
204 }
205
206
207 static void
resize_vmodel(GtkTreeModel * tm,guint posn,guint rm,guint add,GListModel * vmodel)208 resize_vmodel (GtkTreeModel *tm, guint posn, guint rm, guint add, GListModel *vmodel)
209 {
210 gint rows = gtk_tree_model_iter_n_children (tm, NULL);
211
212 g_object_set (vmodel, "size", rows, NULL);
213 }
214
215 static void
resize_hmodel(GtkTreeModel * tm,guint posn,guint rm,guint add,GListModel * hmodel)216 resize_hmodel (GtkTreeModel *tm, guint posn, guint rm, guint add, GListModel *hmodel)
217 {
218 gint cols = gtk_tree_model_get_n_columns (tm);
219
220 g_object_set (hmodel, "size", cols, NULL);
221 }
222
223
224
225 static void
__set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)226 __set_property (GObject *object,
227 guint prop_id, const GValue *value, GParamSpec *pspec)
228 {
229 gint i;
230 SswSheet *sheet = SSW_SHEET (object);
231
232 switch (prop_id)
233 {
234 case PROP_SPLITTER:
235 {
236 GType t = g_value_get_gtype (value);
237
238 if (t == GTK_TYPE_CONTAINER)
239 t = SSW_TYPE_XPANED;
240
241 GtkWidget *splitter = g_object_new (t, NULL);
242 gtk_container_add (GTK_CONTAINER (sheet), splitter);
243 gtk_widget_show (splitter);
244
245 for (i = 0; i < DIM * DIM ; ++i)
246 {
247 gtk_container_add_with_properties (GTK_CONTAINER (splitter),
248 sheet->sw[i],
249 "left-attach", i%DIM,
250 "top-attach", i/DIM,
251 NULL);
252 }
253 }
254 break;
255 case PROP_GRIDLINES:
256 {
257 gboolean lines = g_value_get_boolean (value);
258
259 for (i = 0; i < DIM * DIM ; ++i)
260 {
261 g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body, "gridlines", lines, NULL);
262 }
263 sheet->gridlines = lines;
264 }
265 break;
266 case PROP_EDITABLE:
267 {
268 gboolean editable = g_value_get_boolean (value);
269
270 for (i = 0; i < DIM * DIM ; ++i)
271 {
272 g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
273 "editable", editable,
274 NULL);
275 }
276 sheet->editable = editable;
277 }
278 break;
279
280 case PROP_HORIZONTAL_DRAGGABLE:
281 for (i = 0; i < DIM; ++i)
282 g_object_set (SSW_SHEET_AXIS (sheet->horizontal_axis[i]),
283 "draggable", g_value_get_boolean (value), NULL);
284 break;
285
286 case PROP_VERTICAL_DRAGGABLE:
287 for (i = 0; i < DIM; ++i)
288 g_object_set (SSW_SHEET_AXIS (sheet->vertical_axis[i]),
289 "draggable", g_value_get_boolean (value), NULL);
290 break;
291
292 case PROP_SELECTION:
293 {
294 gpointer p = g_value_get_pointer (value);
295 if (p)
296 {
297 SswRange *r = p;
298 *sheet->selection = *r;
299
300 for (i = 0; i < DIM * DIM ; ++i)
301 gtk_widget_queue_draw (SSW_SHEET_SINGLE (sheet->sheet[i])->body);
302
303 g_signal_emit (sheet, signals [SELECTION_CHANGED], 0, sheet->selection);
304 }
305 }
306 break;
307
308 case PROP_CONVERT_FWD_FUNC:
309 {
310 gpointer p = g_value_get_pointer (value);
311
312 if (p)
313 for (i = 0; i < DIM * DIM ; ++i)
314 {
315 g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
316 "forward-conversion", p,
317 NULL);
318 }
319 }
320 break;
321
322 case PROP_CONVERT_REV_FUNC:
323 {
324 gpointer p = g_value_get_pointer (value);
325
326 if (p)
327 for (i = 0; i < DIM * DIM ; ++i)
328 {
329 g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
330 "reverse-conversion", p,
331 NULL);
332 }
333 }
334 break;
335
336 case PROP_RENDERER_FUNC:
337 {
338 gpointer select_renderer_func = g_value_get_pointer (value);
339
340 for (i = 0; i < DIM * DIM ; ++i)
341 {
342 g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
343 "select-renderer-func", select_renderer_func,
344 NULL);
345 }
346 // sheet->select_renderer_func = select_renderer_func;
347 }
348 break;
349
350 case PROP_RENDERER_FUNC_DATUM:
351 sheet->renderer_func_datum = g_value_get_pointer (value);
352 break;
353
354 case PROP_SPLIT:
355 {
356 gboolean split = g_value_get_boolean (value);
357
358 for (i = 0; i < DIM * DIM ; ++i)
359 {
360 g_object_set (sheet->sw[i], "no-show-all", !split, NULL);
361 gtk_widget_set_visible (sheet->sw[i], split);
362 }
363 g_object_set (sheet->sw[0], "no-show-all", FALSE, NULL);
364 gtk_widget_show (sheet->sw[0]);
365
366 const int dimens = (split ? 2 : 1);
367 for (i = 0; i < dimens * dimens; ++i)
368 {
369 g_object_set (sheet->sw[i], "vscrollbar-policy",
370 ((i%dimens) == dimens - 1) ? GTK_POLICY_ALWAYS : GTK_POLICY_NEVER,
371 NULL);
372
373 g_object_set (sheet->sw[i], "hscrollbar-policy",
374 ((i/dimens) == dimens - 1) ? GTK_POLICY_ALWAYS : GTK_POLICY_NEVER,
375 NULL);
376 }
377
378 sheet->split = split;
379 }
380 break;
381 case PROP_VMODEL:
382 g_set_object (&sheet->vmodel, g_value_get_object (value));
383 arrange (sheet);
384 break;
385 case PROP_HMODEL:
386 g_set_object (&sheet->hmodel, g_value_get_object (value));
387 arrange (sheet);
388 break;
389 case PROP_DATA_MODEL:
390 sheet->data_model = g_value_get_object (value);
391
392 GtkTreeModelFlags flags =
393 gtk_tree_model_get_flags (GTK_TREE_MODEL (sheet->data_model));
394
395 if (!(flags & GTK_TREE_MODEL_LIST_ONLY))
396 g_warning ("SswSheet can interpret list models only. Child nodes will be ignored.");
397
398 if (sheet->vmodel == NULL)
399 sheet->vmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
400
401 if (sheet->hmodel == NULL)
402 sheet->hmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
403
404 if (SSW_IS_AXIS_MODEL (sheet->vmodel))
405 {
406 int n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (sheet->data_model), NULL);
407 g_object_set (sheet->vmodel, "size", n_rows, NULL);
408 g_signal_connect_object (sheet->data_model, "items-changed",
409 G_CALLBACK (resize_vmodel), sheet->vmodel, 0);
410 }
411
412 if (SSW_IS_AXIS_MODEL (sheet->hmodel))
413 {
414 int n_cols = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (sheet->data_model));
415 g_object_set (sheet->hmodel, "size", n_cols, NULL);
416 g_signal_connect_object (sheet->data_model, "items-changed",
417 G_CALLBACK (resize_hmodel), sheet->hmodel, 0);
418 }
419
420 arrange (sheet);
421
422 for (i = 0; i < DIM * DIM; ++i)
423 {
424 g_object_set (sheet->sheet[i], "data-model", sheet->data_model, NULL);
425 }
426 break;
427 default:
428 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429 break;
430 }
431 }
432
433 static void
__get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)434 __get_property (GObject *object,
435 guint prop_id, GValue *value, GParamSpec *pspec)
436 {
437 switch (prop_id)
438 {
439 case PROP_DATA_MODEL:
440 g_value_set_object (value, SSW_SHEET (object)->data_model);
441 break;
442 case PROP_VMODEL:
443 g_value_set_object (value, SSW_SHEET (object)->vmodel);
444 break;
445 case PROP_HMODEL:
446 g_value_set_object (value, SSW_SHEET (object)->hmodel);
447 break;
448 case PROP_SPLIT:
449 g_value_set_boolean (value, SSW_SHEET (object)->split);
450 break;
451 case PROP_GRIDLINES:
452 g_value_set_boolean (value, SSW_SHEET (object)->gridlines);
453 break;
454 case PROP_SELECTION:
455 g_value_set_pointer (value, SSW_SHEET (object)->selection);
456 break;
457 case PROP_EDITABLE:
458 g_value_set_boolean (value, SSW_SHEET (object)->editable);
459 break;
460 case PROP_RENDERER_FUNC_DATUM:
461 g_value_set_pointer (value, SSW_SHEET (object)->renderer_func_datum);
462 break;
463 default:
464 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
465 break;
466 }
467 }
468
469
470 static void
ssw_sheet_class_init(SswSheetClass * class)471 ssw_sheet_class_init (SswSheetClass *class)
472 {
473 GObjectClass *object_class = G_OBJECT_CLASS (class);
474 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
475
476 GParamSpec *convert_fwd_spec =
477 g_param_spec_pointer ("forward-conversion",
478 P_("Forward conversion function"),
479 P_("A function to convert a cell datum to a string"),
480 G_PARAM_WRITABLE);
481
482 GParamSpec *convert_rev_spec =
483 g_param_spec_pointer ("reverse-conversion",
484 P_("Reverse conversion function"),
485 P_("A function to convert a string to a cell datum"),
486 G_PARAM_WRITABLE);
487
488
489 GParamSpec *selection_spec =
490 g_param_spec_pointer ("selection",
491 P_("The selection"),
492 P_("A pointer to the current selection"),
493 G_PARAM_READWRITE);
494
495 GParamSpec *splitter_spec =
496 g_param_spec_gtype ("splitter",
497 P_("Splitter Container Type"),
498 P_("The type of container widget to handle splits"),
499 GTK_TYPE_CONTAINER,
500 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
501
502 GParamSpec *vmodel_spec =
503 g_param_spec_object ("vmodel",
504 P_("Vertical Model"),
505 P_("The model describing the rows"),
506 G_TYPE_LIST_MODEL,
507 G_PARAM_READWRITE);
508
509 GParamSpec *hmodel_spec =
510 g_param_spec_object ("hmodel",
511 P_("Horizontal Model"),
512 P_("The model describing the columns"),
513 G_TYPE_LIST_MODEL,
514 G_PARAM_READWRITE);
515
516 GParamSpec *data_model_spec =
517 g_param_spec_object ("data-model",
518 P_("Data Model"),
519 P_("The model describing the contents of the data"),
520 GTK_TYPE_TREE_MODEL,
521 G_PARAM_READWRITE);
522
523 GParamSpec *split_spec =
524 g_param_spec_boolean ("split",
525 P_("Split View"),
526 P_("If TRUE the sheet view is split four ways"),
527 FALSE,
528 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
529
530 GParamSpec *horizontal_draggable_spec =
531 g_param_spec_boolean ("horizontal-draggable",
532 P_("Horizontal_Draggable"),
533 P_("If TRUE, items in the horizontal axis can be dragged."),
534 FALSE,
535 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
536
537 GParamSpec *vertical_draggable_spec =
538 g_param_spec_boolean ("vertical-draggable",
539 P_("Vertical_Draggable"),
540 P_("If TRUE, items in the vertical axis can be dragged."),
541 FALSE,
542 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
543
544 GParamSpec *gridlines_spec =
545 g_param_spec_boolean ("gridlines",
546 P_("Show Gridlines"),
547 P_("True if gridlines should be shown"),
548 TRUE,
549 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
550
551 GParamSpec *editable_spec =
552 g_param_spec_boolean ("editable",
553 P_("Editable"),
554 P_("True if the sheet is editable"),
555 FALSE,
556 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
557
558 GParamSpec *renderer_func_spec =
559 g_param_spec_pointer ("select-renderer-func",
560 P_("Select Renderer Function"),
561 P_("Function returning the renderer to use for a cell"),
562 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
563
564 GParamSpec *renderer_func_datum_spec =
565 g_param_spec_pointer ("select-renderer-datum",
566 P_("Select Renderer Function Datum"),
567 P_("The Datum to be passed to the \"select-renderer-func\" property"),
568 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
569
570 object_class->set_property = __set_property;
571 object_class->get_property = __get_property;
572 object_class->dispose = __dispose;
573 object_class->finalize = __finalize;
574 widget_class->realize = __realize;
575 widget_class->unrealize = __unrealize;
576
577 g_object_class_install_property (object_class,
578 PROP_CONVERT_FWD_FUNC,
579 convert_fwd_spec);
580
581 g_object_class_install_property (object_class,
582 PROP_CONVERT_REV_FUNC,
583 convert_rev_spec);
584
585 g_object_class_install_property (object_class,
586 PROP_RENDERER_FUNC,
587 renderer_func_spec);
588
589 g_object_class_install_property (object_class,
590 PROP_RENDERER_FUNC_DATUM,
591 renderer_func_datum_spec);
592
593 g_object_class_install_property (object_class,
594 PROP_SPLITTER,
595 splitter_spec);
596
597 g_object_class_install_property (object_class,
598 PROP_SELECTION,
599 selection_spec);
600
601 g_object_class_install_property (object_class,
602 PROP_VMODEL,
603 vmodel_spec);
604
605 g_object_class_install_property (object_class,
606 PROP_HMODEL,
607 hmodel_spec);
608
609 g_object_class_install_property (object_class,
610 PROP_DATA_MODEL,
611 data_model_spec);
612
613 g_object_class_install_property (object_class,
614 PROP_SPLIT,
615 split_spec);
616
617 g_object_class_install_property (object_class,
618 PROP_GRIDLINES,
619 gridlines_spec);
620
621 g_object_class_install_property (object_class,
622 PROP_EDITABLE,
623 editable_spec);
624
625 g_object_class_install_property (object_class,
626 PROP_HORIZONTAL_DRAGGABLE,
627 horizontal_draggable_spec);
628
629 g_object_class_install_property (object_class,
630 PROP_VERTICAL_DRAGGABLE,
631 vertical_draggable_spec);
632
633 signals [ROW_HEADER_CLICKED] =
634 g_signal_new ("row-header-clicked",
635 G_TYPE_FROM_CLASS (class),
636 G_SIGNAL_RUN_FIRST,
637 0,
638 NULL, NULL,
639 g_cclosure_marshal_VOID__INT,
640 G_TYPE_NONE,
641 1,
642 G_TYPE_INT);
643
644 signals [ROW_HEADER_DOUBLE_CLICKED] =
645 g_signal_new ("row-header-double-clicked",
646 G_TYPE_FROM_CLASS (class),
647 G_SIGNAL_RUN_FIRST,
648 0,
649 NULL, NULL,
650 g_cclosure_marshal_VOID__INT,
651 G_TYPE_NONE,
652 1,
653 G_TYPE_INT);
654
655 signals [COLUMN_HEADER_CLICKED] =
656 g_signal_new ("column-header-clicked",
657 G_TYPE_FROM_CLASS (class),
658 G_SIGNAL_RUN_FIRST,
659 0,
660 NULL, NULL,
661 g_cclosure_marshal_VOID__INT,
662 G_TYPE_NONE,
663 1,
664 G_TYPE_INT);
665
666 signals [COLUMN_HEADER_DOUBLE_CLICKED] =
667 g_signal_new ("column-header-double-clicked",
668 G_TYPE_FROM_CLASS (class),
669 G_SIGNAL_RUN_FIRST,
670 0,
671 NULL, NULL,
672 g_cclosure_marshal_VOID__INT,
673 G_TYPE_NONE,
674 1,
675 G_TYPE_INT);
676
677
678 signals [ROW_HEADER_PRESSED] =
679 g_signal_new ("row-header-pressed",
680 G_TYPE_FROM_CLASS (class),
681 G_SIGNAL_RUN_FIRST,
682 0,
683 NULL, NULL,
684 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
685 G_TYPE_NONE,
686 3,
687 G_TYPE_INT,
688 G_TYPE_UINT,
689 G_TYPE_UINT);
690
691 signals [ROW_HEADER_RELEASED] =
692 g_signal_new ("row-header-released",
693 G_TYPE_FROM_CLASS (class),
694 G_SIGNAL_RUN_FIRST,
695 0,
696 NULL, NULL,
697 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
698 G_TYPE_NONE,
699 3,
700 G_TYPE_INT,
701 G_TYPE_UINT,
702 G_TYPE_UINT);
703
704 signals [COLUMN_HEADER_PRESSED] =
705 g_signal_new ("column-header-pressed",
706 G_TYPE_FROM_CLASS (class),
707 G_SIGNAL_RUN_FIRST,
708 0,
709 NULL, NULL,
710 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
711 G_TYPE_NONE,
712 3,
713 G_TYPE_INT,
714 G_TYPE_UINT,
715 G_TYPE_UINT);
716
717 signals [COLUMN_HEADER_RELEASED] =
718 g_signal_new ("column-header-released",
719 G_TYPE_FROM_CLASS (class),
720 G_SIGNAL_RUN_FIRST,
721 0,
722 NULL, NULL,
723 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
724 G_TYPE_NONE,
725 3,
726 G_TYPE_INT,
727 G_TYPE_UINT,
728 G_TYPE_UINT);
729
730
731 signals [SELECTION_CHANGED] =
732 g_signal_new ("selection-changed",
733 G_TYPE_FROM_CLASS (class),
734 G_SIGNAL_RUN_FIRST,
735 0,
736 NULL, NULL,
737 g_cclosure_marshal_VOID__POINTER,
738 G_TYPE_NONE,
739 1,
740 G_TYPE_POINTER);
741
742
743 signals [VALUE_CHANGED] =
744 g_signal_new ("value-changed",
745 G_TYPE_FROM_CLASS (class),
746 G_SIGNAL_RUN_FIRST,
747 0,
748 NULL, NULL,
749 ssw_cclosure_marshal_VOID__INT_INT_POINTER,
750 G_TYPE_NONE,
751 3,
752 G_TYPE_INT,
753 G_TYPE_INT,
754 G_TYPE_POINTER);
755
756 signals [ROW_MOVED] =
757 g_signal_new ("row-moved",
758 G_TYPE_FROM_CLASS (class),
759 G_SIGNAL_RUN_FIRST,
760 0,
761 NULL, NULL,
762 ssw_cclosure_marshal_VOID__INT_INT,
763 G_TYPE_NONE,
764 2,
765 G_TYPE_INT,
766 G_TYPE_INT);
767
768 signals [COLUMN_MOVED] =
769 g_signal_new ("column-moved",
770 G_TYPE_FROM_CLASS (class),
771 G_SIGNAL_RUN_FIRST,
772 0,
773 NULL, NULL,
774 ssw_cclosure_marshal_VOID__INT_INT,
775 G_TYPE_NONE,
776 2,
777 G_TYPE_INT,
778 G_TYPE_INT);
779 }
780
781 static void
forward_signal(SswSheet * sheet,...)782 forward_signal (SswSheet *sheet, ...)
783 {
784 va_list ap;
785 va_start (ap, sheet);
786 g_signal_emit_valist (sheet, signals [VALUE_CHANGED], 0, ap);
787 va_end (ap);
788 }
789
790 static void
forward_selection_signal(SswSheet * sheet,SswRange * sel,gpointer ud)791 forward_selection_signal (SswSheet *sheet, SswRange *sel, gpointer ud)
792 {
793 gint i;
794
795 for (i = 0; i < DIM * DIM ; ++i)
796 {
797 GtkWidget *body = SSW_SHEET_SINGLE (sheet->sheet[i])->body;
798 if (body == ud)
799 continue;
800
801 gtk_widget_queue_draw (body);
802 }
803 g_signal_emit (sheet, signals [SELECTION_CHANGED], 0, sel);
804 }
805
806 static void
on_drag_n_drop(SswSheet * sheet,gint from,gint to,GtkOrientable * axis)807 on_drag_n_drop (SswSheet *sheet, gint from, gint to, GtkOrientable *axis)
808 {
809 if (to - from == 1 || to == from) /* This is a null move */
810 return;
811
812 if (gtk_orientable_get_orientation (axis) == GTK_ORIENTATION_HORIZONTAL)
813 g_signal_emit (sheet, signals [COLUMN_MOVED], 0, from, to);
814 else
815 g_signal_emit (sheet, signals [ROW_MOVED], 0, from, to);
816 }
817
818 static void
ssw_sheet_init(SswSheet * sheet)819 ssw_sheet_init (SswSheet *sheet)
820 {
821 gtk_widget_set_has_window (GTK_WIDGET (sheet), FALSE);
822 sheet->vmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
823 sheet->hmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
824
825 gint i;
826 for (i = 0; i < DIM; ++i)
827 {
828 sheet->vadj[i] = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
829 sheet->hadj[i] = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
830
831 sheet->horizontal_axis[i] =
832 ssw_sheet_axis_new (GTK_ORIENTATION_HORIZONTAL);
833
834
835 g_signal_connect (sheet->horizontal_axis[i], "header-clicked",
836 G_CALLBACK (on_header_clicked), sheet);
837
838 g_signal_connect (sheet->horizontal_axis[i], "header-double-clicked",
839 G_CALLBACK (on_header_double_clicked), sheet);
840
841 g_signal_connect (sheet->horizontal_axis[i], "header-button-pressed",
842 G_CALLBACK (on_header_button_pressed), sheet);
843
844 g_signal_connect (sheet->horizontal_axis[i], "header-button-released",
845 G_CALLBACK (on_header_button_released), sheet);
846
847 sheet->vertical_axis[i] =
848 ssw_sheet_axis_new (GTK_ORIENTATION_VERTICAL);
849
850 g_signal_connect (sheet->vertical_axis[i], "header-clicked",
851 G_CALLBACK (on_header_clicked), sheet);
852
853 g_signal_connect (sheet->vertical_axis[i], "header-double-clicked",
854 G_CALLBACK (on_header_double_clicked), sheet);
855
856 g_signal_connect (sheet->vertical_axis[i], "header-button-pressed",
857 G_CALLBACK (on_header_button_pressed), sheet);
858
859 g_signal_connect (sheet->vertical_axis[i], "header-button-released",
860 G_CALLBACK (on_header_button_released), sheet);
861 }
862
863 sheet->selection = g_malloc (sizeof *sheet->selection);
864 sheet->selection->start_x = -1;
865 sheet->selection->start_y = -1;
866 sheet->selection->end_x = -1;
867 sheet->selection->end_y = -1;
868
869 for (i = 0; i < DIM ; ++i)
870 {
871 g_signal_connect_swapped (sheet->horizontal_axis[i], "drag-n-dropped",
872 G_CALLBACK (on_drag_n_drop), sheet);
873
874 g_signal_connect_swapped (sheet->vertical_axis[i], "drag-n-dropped",
875 G_CALLBACK (on_drag_n_drop), sheet);
876 }
877
878 for (i = 0; i < DIM * DIM ; ++i)
879 {
880 sheet->sw[i] = gtk_scrolled_window_new (sheet->hadj[i%DIM],
881 sheet->vadj[i/DIM]);
882
883 g_object_set (sheet->sw[i], "shadow-type", GTK_SHADOW_IN, NULL);
884
885 sheet->sheet[i] =
886 ssw_sheet_single_new (sheet,
887 SSW_SHEET_AXIS (sheet->horizontal_axis[i%DIM]),
888 SSW_SHEET_AXIS (sheet->vertical_axis[i/DIM]),
889 sheet->selection);
890
891 gtk_widget_show_all (sheet->sheet[i]);
892
893 gtk_container_add (GTK_CONTAINER (sheet->sw[i]), sheet->sheet[i]);
894
895 g_signal_connect_swapped (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
896 "selection-changed", G_CALLBACK (forward_selection_signal), sheet);
897
898 g_signal_connect_swapped (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
899 "value-changed", G_CALLBACK (forward_signal), sheet);
900 }
901
902 sheet->renderer_func_datum = NULL;
903 sheet->dispose_has_run = FALSE;
904 sheet->selected_body = SSW_SHEET_SINGLE (sheet->sheet[0])->body;
905
906 arrange (sheet);
907
908 sheet->cursor_stack = NULL;
909 }
910
911 void
ssw_sheet_set_clip(SswSheet * sheet,GtkClipboard * clip)912 ssw_sheet_set_clip (SswSheet *sheet, GtkClipboard *clip)
913 {
914 if (!sheet->data_model)
915 return;
916
917 ssw_sheet_body_set_clip (SSW_SHEET_BODY (sheet->selected_body), clip);
918 }
919
920
921 void
ssw_sheet_scroll_to(SswSheet * sheet,gint hpos,gint vpos)922 ssw_sheet_scroll_to (SswSheet *sheet, gint hpos, gint vpos)
923 {
924 if (hpos >= 0)
925 ssw_sheet_axis_jump_center (SSW_SHEET_AXIS (sheet->horizontal_axis[0]), hpos);
926
927 if (vpos >= 0)
928 ssw_sheet_axis_jump_center (SSW_SHEET_AXIS (sheet->vertical_axis[0]), vpos);
929 }
930
931
932 void
ssw_sheet_set_active_cell(SswSheet * sheet,gint col,gint row,GdkEvent * e)933 ssw_sheet_set_active_cell (SswSheet *sheet,
934 gint col, gint row, GdkEvent *e)
935 {
936 ssw_sheet_body_set_active_cell (SSW_SHEET_BODY (sheet->selected_body),
937 col, row, e);
938 }
939
940
941 gboolean
ssw_sheet_get_active_cell(SswSheet * sheet,gint * col,gint * row)942 ssw_sheet_get_active_cell (SswSheet *sheet,
943 gint *col, gint *row)
944 {
945 return ssw_sheet_body_get_active_cell (SSW_SHEET_BODY (sheet->selected_body),
946 col, row);
947 }
948
949
950
951 /* A callback function which populates the cell indicated by PS with the value
952 indicated by STR and the current reverse conversion function. */
953 void
ssw_sheet_paste_insert_datum(const gchar * str,size_t len,const struct paste_state * ps)954 ssw_sheet_paste_insert_datum (const gchar *str, size_t len, const struct paste_state *ps)
955 {
956 SswSheet *sheet = ps->sheet;
957
958 GValue value = G_VALUE_INIT;
959
960 gint col = ps->col + ps->col0;
961 gint row = ps->row + ps->row0;
962
963 ssw_sheet_reverse_conversion_func rcf;
964
965 g_object_get (SSW_SHEET_SINGLE (sheet->sheet[0])->body,
966 "reverse-conversion", &rcf,
967 NULL);
968
969 if (rcf (sheet->data_model, col, row, str, &value))
970 ps->set_cell (sheet->data_model, col, row, &value);
971 g_value_unset (&value);
972 }
973
974 static void
paste_datum(const gchar * x,size_t len,struct paste_state * t)975 paste_datum (const gchar *x, size_t len, struct paste_state *t)
976 {
977 ssw_sheet_paste_insert_datum (x, len, t);
978 t->col ++;
979 }
980
981 static void
end_of_row(struct paste_state * t)982 end_of_row (struct paste_state *t)
983 {
984 t->row++;
985 t->col = 0;
986 }
987
988 static void
end_of_paste(struct paste_state * t)989 end_of_paste (struct paste_state *t)
990 {
991 g_free (t);
992 }
993
994
995 static void
parse_delimited_data(const gchar * data,int len,const char * delim,void (* payload)(const gchar *,size_t,struct paste_state *),void (* endload)(struct paste_state *),struct paste_state * dw)996 parse_delimited_data (const gchar *data, int len, const char *delim,
997 void (*payload)(const gchar *, size_t, struct paste_state *),
998 void (*endload)(struct paste_state *),
999 struct paste_state * dw)
1000 {
1001 while (len > 0)
1002 {
1003 const gchar *x = g_strstr_len (data, len, delim);
1004 if (x == NULL)
1005 {
1006 char *f = g_strndup (data, len);
1007 payload (f, len, dw);
1008 g_free (f);
1009 break;
1010 }
1011 len -= x - data + 1;
1012
1013 gchar *line = g_strndup (data, x - data);
1014 payload (line, x - data, dw);
1015 data = x + 1;
1016 g_free (line);
1017 }
1018 if (endload)
1019 endload (dw);
1020 }
1021
1022 static void
parseit(const gchar * x,size_t len,struct paste_state * dw)1023 parseit (const gchar *x, size_t len, struct paste_state *dw)
1024 {
1025 parse_delimited_data (x, len, "\t", paste_datum, end_of_row, dw);
1026 }
1027
1028 static void
utf8_tab_delimited_parse(GtkClipboard * clip,GtkSelectionData * sd,gpointer user_data)1029 utf8_tab_delimited_parse (GtkClipboard *clip, GtkSelectionData *sd,
1030 gpointer user_data)
1031 {
1032 struct paste_state *ps = user_data;
1033 SswSheet *sheet = SSW_SHEET (ps->sheet);
1034 const gchar *data = (const gchar *) gtk_selection_data_get_data (sd);
1035 gint len = gtk_selection_data_get_length (sd);
1036
1037 if (len < 0)
1038 {
1039 g_free (ps);
1040 return;
1041 }
1042
1043 ps->row = 0;
1044 ps->col = 0;
1045
1046 parse_delimited_data (data, len, "\n", parseit, end_of_paste, ps);
1047 gtk_widget_queue_draw (GTK_WIDGET (sheet));
1048 }
1049
1050 static void
target_marshaller(GtkClipboard * clip,GdkAtom * atoms,gint n_atoms,gpointer user_data)1051 target_marshaller (GtkClipboard *clip, GdkAtom *atoms, gint n_atoms,
1052 gpointer user_data)
1053 {
1054 int i;
1055 struct paste_state *ps = user_data;
1056
1057 if (atoms == NULL)
1058 {
1059 g_free (ps);
1060 return;
1061 }
1062
1063 for (i = 0; i < n_atoms; ++i)
1064 {
1065 if (atoms[i] == gdk_atom_intern_static_string ("text/html"))
1066 {
1067 gtk_clipboard_request_contents (clip, atoms[i], ssw_html_parse, ps);
1068 break;
1069 }
1070 else if (atoms[i] == gdk_atom_intern_static_string ("UTF8_STRING"))
1071 {
1072 gtk_clipboard_request_contents (clip, atoms[i], utf8_tab_delimited_parse, ps);
1073 break;
1074 }
1075 }
1076
1077 if (i == n_atoms)
1078 g_free (ps);
1079 }
1080
1081 void
ssw_sheet_paste(SswSheet * sheet,GtkClipboard * clip,ssw_sheet_set_cell sc)1082 ssw_sheet_paste (SswSheet *sheet, GtkClipboard *clip, ssw_sheet_set_cell sc)
1083 {
1084 g_return_if_fail (sheet);
1085
1086 gint col, row;
1087
1088 if (ssw_sheet_body_paste_editable (SSW_SHEET_BODY (sheet->selected_body)))
1089 return;
1090
1091 if (ssw_sheet_get_active_cell (sheet, &col, &row))
1092 {
1093 struct paste_state *ps = g_malloc (sizeof *ps);
1094 ps->sheet = sheet;
1095 ps->set_cell = sc;
1096
1097 ps->col0 = col;
1098 ps->row0 = row;
1099
1100 gtk_clipboard_request_targets (clip, target_marshaller, ps);
1101 }
1102 }
1103
1104 gboolean
ssw_sheet_try_cut(SswSheet * sheet)1105 ssw_sheet_try_cut (SswSheet *sheet)
1106 {
1107 g_return_val_if_fail (sheet, FALSE);
1108
1109 return ssw_sheet_body_cut_editable (SSW_SHEET_BODY (sheet->selected_body));
1110 }
1111
1112
1113
1114 void
ssw_sheet_wait_push(SswSheet * sheet)1115 ssw_sheet_wait_push (SswSheet *sheet)
1116 {
1117 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (sheet));
1118 if (win == NULL)
1119 return;
1120
1121 GdkCursor *cursor = gdk_window_get_cursor (win);
1122 sheet->cursor_stack = g_slist_prepend (sheet->cursor_stack, cursor);
1123 gdk_window_set_cursor (win, sheet->wait_cursor);
1124 }
1125
1126
1127 void
ssw_sheet_wait_pop(SswSheet * sheet)1128 ssw_sheet_wait_pop (SswSheet *sheet)
1129 {
1130 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (sheet));
1131 if (win == NULL)
1132 return;
1133
1134 GSList *cursor = sheet->cursor_stack;
1135 gdk_window_set_cursor (win, cursor->data);
1136 sheet->cursor_stack = sheet->cursor_stack->next;
1137 }
1138