1 // -*- C++ -*-
2
3 /*
4 * Gnome Crystal
5 * grid.cc
6 *
7 * Copyright (C) 2010-2011 Jean Bréfort <jean.brefort@normalesup.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 * USA
23 */
24
25 #include "config.h"
26 #include "grid.h"
27 #include <gcugtk/marshalers.h>
28 #include <goffice/goffice.h>
29 #include <glib/gi18n-lib.h>
30 #include <list>
31 #include <set>
32 #include <string>
33 #include <vector>
34 #include <cstring>
35
36 #include <gsf/gsf-impl-utils.h>
37
38 #define CURSOR_ON_TIME 800
39 #define CURSOR_OFF_TIME 400
40
41 struct _GcrGrid
42 {
43 GtkLayout base;
44 unsigned cols, rows, allocated_rows;
45 int col, row, last_row;
46 int first_visible;
47 unsigned nb_visible;
48 int header_width, row_height, width, actual_width, *col_widths,
49 line_offset, scroll_width, *min_widths, cols_min_width;
50 int cursor_index, sel_start;
51 GtkAdjustment *vadj;
52 GtkWidget *scroll;
53 std::string *titles;
54 GType *types;
55 bool *editable;
56 std::vector < std::string * > row_data; // storing as string since this is what is displayed
57 bool cursor_visible;
58 unsigned long cursor_signal;
59 std::string *orig_string;
60 int can_edit;
61 bool allow_multiple;
62 bool dragging;
63 bool selection_locked;
64 std::set < int > *selected_rows;
65 };
66
67 static GtkWidgetClass *parent_class;
68 static GdkPixbuf *checked = NULL, *unchecked = NULL;
69
70 enum {
71 VALUE_CHANGED,
72 ROW_SELECTED,
73 ROW_DELETED,
74 LAST_SIGNAL
75 };
76
77 static gulong gcr_grid_signals [LAST_SIGNAL] = { 0, };
78
79 typedef struct
80 {
81 GtkLayoutClass parent_class;
82 void (*value_changed_event) (GcrGrid *grid, unsigned row, unsigned column, gpointer data);
83 void (*row_added_event) (GcrGrid *grid, unsigned row, gpointer data);
84 void (*row_selected_event) (GcrGrid *grid, unsigned row, gpointer data);
85 void (*row_deleted_event) (GcrGrid *grid, unsigned row, gpointer data);
86 } GcrGridClass;
87
88 // static functions
89
gcr_grid_validate_change(GcrGrid * grid)90 static bool gcr_grid_validate_change (GcrGrid *grid)
91 {
92 if (grid->row < 0 || grid->col < 0)
93 return true;
94 std::string new_string = grid->row_data[grid->row][grid->col];
95 if (new_string == *grid->orig_string)
96 return true;
97 switch (grid->types[grid->col]) {
98 case G_TYPE_INT: {
99 long orig, next;
100 bool neg = !grid->orig_string->compare (0, strlen ("−"), "−");
101 char const *str = grid->orig_string->c_str ();
102 char *buf;
103 if (neg)
104 str += strlen ("−");
105 orig = strtol (str, NULL, 10);
106 if (neg)
107 orig = -orig;
108 neg = !new_string.compare (0, strlen ("−"), "−");
109 str = new_string.c_str ();
110 if (neg)
111 str += strlen ("−");
112 next = strtol (str, &buf, 10);
113 if (neg)
114 next = -next;
115 if (buf && *buf)
116 goto error_handler;
117 buf = (next < 0)? g_strdup_printf ("−%ld", -next): g_strdup_printf ("%ld", next);
118 grid->row_data[grid->row][grid->col] = buf;
119 grid->sel_start = grid->cursor_index = strlen (buf);
120 g_free (buf);
121 if (orig != next)
122 g_signal_emit (grid, gcr_grid_signals[VALUE_CHANGED], 0, grid->row, grid->col);
123 return true;
124 }
125 case G_TYPE_UINT: {
126 unsigned long orig, next;
127 char *buf;
128 orig = strtoul (grid->orig_string->c_str (), NULL, 10);
129 next = strtoul (new_string.c_str (), &buf, 10);
130 if (buf && *buf)
131 goto error_handler;
132 buf = g_strdup_printf ("%lu", next);
133 grid->row_data[grid->row][grid->col] = buf;
134 grid->sel_start = grid->cursor_index = strlen (buf);
135 g_free (buf);
136 if (orig != next)
137 g_signal_emit (grid, gcr_grid_signals[VALUE_CHANGED], 0, grid->row, grid->col);
138 return true;
139 }
140 case G_TYPE_DOUBLE: {
141 double orig, next;
142 bool neg = !grid->orig_string->compare (0, strlen ("−"), "−");
143 char const *str = grid->orig_string->c_str ();
144 char *buf;
145 if (neg)
146 str += strlen ("−");
147 orig = strtod (str, NULL);
148 if (neg)
149 orig = -orig;
150 neg = !new_string.compare (0, strlen ("−"), "−");
151 str = new_string.c_str ();
152 if (neg)
153 str += strlen ("−");
154 next = strtod (str, &buf);
155 if (neg)
156 next = -next;
157 if (buf && *buf)
158 goto error_handler;
159 buf = (next < 0)? g_strdup_printf ("−%f", -next): g_strdup_printf ("%f", next);
160 grid->row_data[grid->row][grid->col] = buf;
161 grid->sel_start = grid->cursor_index = strlen (buf);
162 g_free (buf);
163 if (orig != next)
164 g_signal_emit (grid, gcr_grid_signals[VALUE_CHANGED], 0, grid->row, grid->col);
165 return true;
166 }
167 case G_TYPE_BOOLEAN:
168 break;
169 default:
170 break;
171 }
172 return false;
173 error_handler:
174 // directly using gtk to display an error message since we dont't know about Application there
175 GtkWidget *widget = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (grid))),
176 GTK_DIALOG_MODAL,
177 GTK_MESSAGE_ERROR,
178 GTK_BUTTONS_CLOSE,
179 _("Invalid data"));
180 gtk_dialog_run (GTK_DIALOG (widget));
181 grid->sel_start = 0;
182 grid->cursor_index = new_string.length ();
183 return false;
184 }
185
186 // overriden methods
187
gcr_grid_draw(GtkWidget * w,cairo_t * cr)188 static gboolean gcr_grid_draw (GtkWidget *w, cairo_t* cr)
189 {
190 GcrGrid *grid = reinterpret_cast < GcrGrid * > (w);
191 GtkStyleContext *ctxt = gtk_widget_get_style_context (w);
192 unsigned i, j;
193 GtkAllocation alloc;
194 gtk_widget_get_allocation (w, &alloc);
195 gtk_style_context_save (ctxt);
196 gtk_style_context_add_class (ctxt, GTK_STYLE_CLASS_BUTTON);
197 int pos = grid->header_width, y = grid->line_offset, width;
198 PangoLayout *l = gtk_widget_create_pango_layout (w, "");
199 cairo_save (cr);
200 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
201 cairo_rectangle (cr, 0, 0, grid->width, grid->row_height + 1);
202 cairo_fill (cr);
203 cairo_restore (cr);
204 gtk_render_background (ctxt, cr, 0, 0, grid->header_width + 1, grid->row_height + 1);
205 gtk_render_frame (ctxt, cr, 0, 0, grid->header_width + 1, grid->row_height + 1);
206 for (i = 0; i < grid->cols; i++) {
207 gtk_style_context_set_state (ctxt, (static_cast < int > (i) == grid->col)? GTK_STATE_FLAG_ACTIVE: GTK_STATE_FLAG_NORMAL);
208 gtk_render_background (ctxt, cr, pos, 0, grid->col_widths[i] + 1, grid->row_height + 1);
209 gtk_render_frame (ctxt, cr, pos, 0, grid->col_widths[i] + 1, grid->row_height + 1);
210 pango_layout_set_markup (l, grid->titles[i].c_str (), -1);
211 pango_layout_get_pixel_size (l, &width, NULL);
212 cairo_move_to (cr, pos + (grid->col_widths[i] - width) / 2, y);
213 pango_cairo_show_layout (cr, l);
214 pos += grid->col_widths[i];
215 }
216 gtk_style_context_set_state (ctxt, GTK_STATE_FLAG_NORMAL);
217 gtk_render_background (ctxt, cr, pos, 0, grid->scroll_width, grid->row_height + 1);
218 gtk_render_frame (ctxt, cr, pos, 0, grid->scroll_width, grid->row_height + 1);
219 y = grid->row_height;
220 cairo_set_line_width (cr, 1.);
221 // draw row headers
222 int row = grid->first_visible;
223 unsigned max = (grid->nb_visible >= grid->rows - grid->first_visible)? grid->rows - grid->first_visible: grid->nb_visible + 1;
224 for (j = 0; j < max; j++) {
225 cairo_save (cr);
226 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
227 cairo_rectangle (cr, 0, y, grid->header_width + 1, grid->row_height + 1);
228 cairo_fill (cr);
229 cairo_restore (cr);
230 gtk_style_context_set_state (ctxt, (row == grid->row ||
231 grid->selected_rows->find (row) != grid->selected_rows->end ())?
232 GTK_STATE_FLAG_ACTIVE: GTK_STATE_FLAG_NORMAL);
233 gtk_render_background (ctxt, cr, 0, y, grid->header_width + 1, grid->row_height + 1);
234 gtk_render_frame (ctxt, cr, 0, y, grid->header_width + 1, grid->row_height + 1);
235 char *buf = g_strdup_printf("%d", ++row);
236 pango_layout_set_text (l, buf, -1);
237 pango_layout_get_pixel_size (l, &width, NULL);
238 cairo_move_to (cr, (grid->header_width - width) / 2, y + grid->line_offset);
239 pango_cairo_show_layout (cr, l);
240 g_free (buf);
241 y += grid->row_height;
242 }
243 y = grid->row_height;
244 cairo_save (cr);
245 cairo_rectangle (cr, grid->header_width, y, alloc.width - grid->header_width, alloc.height - y);
246 cairo_clip (cr);
247 // show a rectangle around current selection
248 if (grid->row >= 0) {
249 pos = grid->header_width;
250 cairo_save (cr);
251 if (grid->col >= 0) {
252 for (i = 0; static_cast < int > (i) < grid->col; i++)
253 pos += grid->col_widths[i];
254 cairo_rectangle (cr, pos + .5,
255 y + (grid->row - grid->first_visible) * grid->row_height + .5,
256 grid->col_widths[grid->col], grid->row_height);
257 } else
258 cairo_rectangle (cr, pos + .5,
259 y + (grid->row - grid->first_visible) * grid->row_height + .5,
260 grid->actual_width, grid->row_height);
261 cairo_set_line_width (cr, 3.);
262 cairo_stroke_preserve (cr);
263 cairo_restore (cr);
264 }
265 cairo_set_line_width (cr, 1.);
266 // draw cells
267 row = grid->first_visible;
268 for (j = 0; j < max; j++) {
269 pos = grid->header_width;
270 for (i = 0; i < grid->cols; i++) {
271 cairo_save (cr);
272 cairo_rectangle (cr, pos + .5, y + .5, grid->col_widths[i], grid->row_height);
273 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
274 cairo_stroke (cr);
275 cairo_restore (cr);
276 if (grid->types[i] == G_TYPE_BOOLEAN) {
277 GdkPixbuf *pixbuf = (grid->row_data[row][i] == "t")? checked: unchecked;
278 cairo_save (cr);
279 cairo_translate (cr, pos + .5 * (grid->col_widths[i] - grid->row_height), y);
280 cairo_rectangle (cr, 2., 2.,
281 grid->row_height - 4, grid->row_height - 4);
282 gdk_cairo_set_source_pixbuf (cr, pixbuf, 0., 0.);
283 cairo_fill (cr);
284 cairo_restore (cr);
285 } else {
286 pango_layout_set_text (l, grid->row_data[row][i].c_str(), -1);
287 pango_layout_get_pixel_size (l, &width, NULL);
288 pango_layout_set_markup (l, grid->row_data[row][i].c_str(), -1);
289 if (static_cast < int > (row) == grid->row && static_cast < int > (i) == grid->col) {
290 if (grid->cursor_index != grid->sel_start) {
291 PangoAttrList *al = pango_attr_list_new ();
292 int start, end;
293 if (grid->cursor_index < grid->sel_start) {
294 start = grid->cursor_index;
295 end = grid->sel_start;
296 } else {
297 end = grid->cursor_index;
298 start = grid->sel_start;
299 }
300 PangoAttribute *attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
301 attr->start_index = start;
302 attr->end_index =end;
303 pango_attr_list_insert (al, attr);
304 attr = pango_attr_background_new (0, 0, 0);
305 attr->start_index = start;
306 attr->end_index =end;
307 pango_attr_list_insert (al, attr);
308 pango_layout_set_attributes (l, al);
309 pango_attr_list_unref (al);
310 }
311 if (grid->cursor_visible) {
312 PangoRectangle rect;
313 pango_layout_get_cursor_pos (l, grid->cursor_index, &rect, NULL);
314 cairo_move_to (cr, pos + (grid->col_widths[i] - width) / 2 + rect.x / PANGO_SCALE + .5, y + grid->line_offset + rect.y / PANGO_SCALE);
315 cairo_rel_line_to (cr, 0, rect.height / PANGO_SCALE);
316 cairo_stroke (cr);
317 }
318 }
319 cairo_move_to (cr, pos + (grid->col_widths[i] - width) / 2, y + grid->line_offset);
320 pango_cairo_show_layout (cr, l);
321 }
322 pos += grid->col_widths[i];
323 }
324
325 row++;
326 y += grid->row_height;
327 }
328 cairo_restore (cr);
329 gtk_style_context_restore (ctxt);
330 return parent_class->draw (w, cr);
331 }
332
gcr_grid_get_preferred_height(GtkWidget * w,int * min,int * preferred)333 static void gcr_grid_get_preferred_height (GtkWidget *w, int *min, int *preferred)
334 {
335 *min = *preferred = reinterpret_cast < GcrGrid * > (w)->row_height * 6 + 1;
336 }
337
gcr_grid_get_preferred_width(GtkWidget * w,int * min,int * preferred)338 static void gcr_grid_get_preferred_width (GtkWidget *w, int *min, int *preferred)
339 {
340 *min = *preferred = reinterpret_cast < GcrGrid * > (w)->width;
341 }
342
gcr_grid_unrealize(GtkWidget * w)343 static void gcr_grid_unrealize (GtkWidget *w)
344 {
345 GcrGrid *grid = GCR_GRID (w);
346 if (grid->cursor_signal > 0)
347 g_source_remove (grid->cursor_signal);
348 parent_class->unrealize (w);
349 }
350
gcr_grid_focus_out_event(GtkWidget * widget,G_GNUC_UNUSED GdkEventFocus * event)351 static gboolean gcr_grid_focus_out_event (GtkWidget *widget, G_GNUC_UNUSED GdkEventFocus *event)
352 {
353 GcrGrid *grid = GCR_GRID (widget);
354 if (!gcr_grid_validate_change (grid))
355 gtk_widget_grab_focus (widget);
356 else {
357 if (grid->cursor_signal)
358 g_source_remove (grid->cursor_signal);
359 grid->cursor_signal = 0;
360 grid->col = -1;
361 grid->dragging = false;
362 gtk_widget_queue_draw (widget);
363 }
364 return true;
365 }
366
gcr_grid_grab_notify(GtkWidget * widget,gboolean)367 static void gcr_grid_grab_notify (GtkWidget *widget, gboolean)
368 {
369 GCR_GRID (widget)->dragging = false;
370 }
371
gcr_grid_finalize(GObject * obj)372 static void gcr_grid_finalize (GObject *obj)
373 {
374 GcrGrid *grid = reinterpret_cast < GcrGrid * > (obj);
375 delete [] grid->col_widths;
376 delete [] grid->titles;
377 delete [] grid->types;
378 delete [] grid->editable;
379 for (unsigned i = 0 ; i < grid->rows; i++)
380 delete [] grid->row_data[i];
381 delete grid->selected_rows;
382 reinterpret_cast < GObjectClass * > (parent_class)->finalize (obj);
383 }
384
gcr_grid_size_allocate(GtkWidget * w,GtkAllocation * alloc)385 static void gcr_grid_size_allocate (GtkWidget *w, GtkAllocation *alloc)
386 {
387 GcrGrid *grid = GCR_GRID (w);
388 gtk_layout_move (GTK_LAYOUT (grid), grid->scroll, alloc->width - grid->scroll_width, grid->row_height + 1);
389 g_object_set (G_OBJECT (grid->scroll), "height-request", alloc->height - grid->row_height - 1, NULL); //default size
390 grid->nb_visible = alloc->height / grid->row_height - 1; // -1 to avoid counting the header
391 if (grid->rows) {
392 gtk_adjustment_set_page_size (grid->vadj, static_cast < double > (grid->nb_visible) / grid->rows);
393 gtk_adjustment_set_upper (grid->vadj, (grid->nb_visible < grid->rows)? grid->rows - grid->nb_visible: .1);
394 if (grid->rows < grid->nb_visible + grid->first_visible) {
395 grid->first_visible = (grid->nb_visible < grid->rows)? grid->rows - grid->nb_visible: 0;
396 gtk_adjustment_set_value (grid->vadj, grid->first_visible);
397 }
398 } else
399 gtk_adjustment_set_page_size (grid->vadj, 1.);
400 // adjust column widths
401 grid->actual_width = alloc->width - grid->header_width - grid->scroll_width;
402 double ratio = static_cast < double > (grid->actual_width) / grid->cols_min_width;
403 if (ratio < 0.)
404 ratio = 1.;
405 double last_pos = 0., pos = 0.;
406 for (unsigned i = 0; i < grid->cols; i++) {
407 pos += grid->min_widths[i];
408 grid->col_widths[i] = pos * ratio - last_pos;
409 last_pos += grid->col_widths[i];
410 }
411 parent_class->size_allocate (w, alloc);
412 }
413
on_blink(gpointer data)414 static gint on_blink (gpointer data)
415 {
416 GcrGrid *grid = GCR_GRID (data);
417 grid->cursor_signal = g_timeout_add (((grid->cursor_visible)? CURSOR_OFF_TIME: CURSOR_ON_TIME), on_blink, data);
418
419 grid->cursor_visible = !grid->cursor_visible;
420 gtk_widget_queue_draw (GTK_WIDGET (data));
421 /* Remove ourself */
422 return false;
423 }
424
gcr_grid_button_press_event(GtkWidget * widget,GdkEventButton * event)425 static gboolean gcr_grid_button_press_event (GtkWidget *widget, GdkEventButton *event)
426 {
427 if (event->button != 1)
428 return false; // FIXME: at least middle buttons should be accepted to paste data
429 GcrGrid *grid = GCR_GRID (widget);
430 if ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == 0)
431 grid->selected_rows->clear ();
432 int value_changed = -1;
433 int x = grid->first_visible + event->y / grid->row_height - 1, i, new_row, new_col;
434 new_row = (x < 0 || x >= static_cast < int > (grid->rows))? -1: x;
435 grid->dragging = true;
436 if (new_row < 0)
437 new_col = -1;
438 else {
439 new_col = -1;
440 x = grid->header_width;
441 if (event->x >= x)
442 for (i = 0; i < static_cast < int > (grid->cols); i++) {
443 x += grid->col_widths[i];
444 if (event->x < x) {
445 new_col = i;
446 break;
447 }
448 }
449 }
450 if (grid->col != new_col || grid->row != new_row) {
451 if (grid->row >= 0 && !gcr_grid_validate_change (grid))
452 return true;
453 if (grid->allow_multiple && event->state & GDK_SHIFT_MASK) {
454 new_col = -1;
455 if (new_row != grid->last_row) {
456 int incr = (grid->last_row > grid->row)? -1: 1;
457 for (x = grid->last_row; x != grid->row; x += incr)
458 grid->selected_rows->erase (x);
459 incr = (new_row > grid->row)? -1: 1;
460 for (x = new_row; x != grid->row; x += incr)
461 grid->selected_rows->insert (x);
462 grid->last_row = new_row;
463 }
464 } else if (grid->allow_multiple && event->state & GDK_CONTROL_MASK) {
465 new_col = -1;
466 if (grid->selected_rows->find (new_row) != grid->selected_rows->end ()) {
467 grid->selected_rows->erase (new_row);
468 new_row = grid->row;
469 } else
470 grid->selected_rows->insert (grid->row);
471 if (new_row != grid->row)
472 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, new_row);
473 grid->row = grid->last_row = new_row;
474 } else {
475 if (new_col >= 0 && !grid->editable[new_col])
476 new_col = -1;
477 grid->col = new_col;
478 if (new_row != grid->row) {
479 grid->row = grid->last_row = new_row;
480 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, new_row);
481 }
482 }
483 }
484 // evaluate the cursor position if any
485 if (grid->col >= 0) {
486 switch (grid->types[grid->col]) {
487 case G_TYPE_INT:
488 case G_TYPE_UINT:
489 case G_TYPE_DOUBLE:
490 if (event->type == GDK_BUTTON_PRESS) {
491 x -= grid->col_widths[grid->col];
492 PangoLayout *l = gtk_widget_create_pango_layout (widget, grid->row_data[grid->row][grid->col].c_str());
493 int xpos, startx;
494 pango_layout_get_pixel_size (l, &xpos, NULL);
495 startx = x + (grid->col_widths[grid->col] - xpos) / 2;
496 xpos = event->x - startx;
497 int index, trailing;
498 pango_layout_xy_to_index (l, xpos * PANGO_SCALE, 0, &index, &trailing);
499 index += trailing;
500 grid->cursor_index = index;
501 if ((event->state & GDK_SHIFT_MASK) == 0)
502 grid->sel_start = index;
503 } else if (event->type == GDK_2BUTTON_PRESS) {
504 grid->sel_start = 0;
505 grid->cursor_index = grid->row_data[grid->row][grid->col].length ();
506 }
507 break;
508 case G_TYPE_BOOLEAN:
509 grid->cursor_index = -1;
510 // nothing to do, just wait and see if the mouse button is released inside the cell
511 // for now toggle the button if the click occurs near enough
512 x = event->x - x + grid->col_widths[new_col] / 2.;
513 if (fabs (x) < grid->row_height / 2) {
514 grid->row_data[grid->row][grid->col] = (grid->row_data[grid->row][grid->col] == "t")? "f": "t";
515 value_changed = new_col;
516 }
517 break;
518 default:
519 grid->cursor_index = -1;
520 g_critical ("Unsupported type.");
521 break;
522 }
523 *grid->orig_string = grid->row_data[grid->row][grid->col];
524 } else
525 grid->cursor_index = -1;
526 if (grid->cursor_index >= 0 && grid->cursor_signal == 0) {
527 grid->cursor_signal = g_timeout_add (CURSOR_ON_TIME, on_blink, grid);
528 grid->cursor_visible = true;
529 } else if (grid->cursor_index < 0 && grid->cursor_signal != 0) {
530 g_source_remove (grid->cursor_signal);
531 grid->cursor_signal = 0;
532 }
533 if (value_changed >= 0) // a boolean changed
534 g_signal_emit (grid, gcr_grid_signals[VALUE_CHANGED], 0, new_row, new_col);
535 gtk_widget_grab_focus (widget);
536 gtk_widget_queue_draw (widget);
537 return true;
538 }
539
gcr_grid_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)540 static gboolean gcr_grid_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
541 {
542 GcrGrid *grid = GCR_GRID (widget);
543 if (!grid->dragging)
544 return true;
545 // evaluate the current row
546 int x = grid->first_visible + event->y / grid->row_height - 1, new_row, incr;
547 new_row = (x < 0 || x >= static_cast < int > (grid->rows))? -1: x;
548 // update selection
549 if (new_row != grid->last_row && grid->allow_multiple) {
550 incr = (grid->last_row > grid->row)? -1: 1;
551 for (x = grid->last_row; x != grid->row; x += incr)
552 grid->selected_rows->erase (x);
553 incr = (new_row > grid->row)? -1: 1;
554 for (x = new_row; x != grid->row; x += incr)
555 grid->selected_rows->insert (x);
556 grid->last_row = new_row;
557 grid->col = -1;
558 } else if (grid->col >= 0) {
559 int new_col = -1;
560 x = grid->header_width;
561 if (event->x >= x)
562 for (incr = 0; incr < static_cast < int > (grid->cols); incr++) {
563 x += grid->col_widths[incr];
564 if (event->x < x) {
565 new_col = incr;
566 break;
567 }
568 }
569 if (new_col == grid->col) {
570 // evaluate the cursor position if any
571 if (new_col >= 0) {
572 switch (grid->types[grid->col]) {
573 case G_TYPE_INT:
574 case G_TYPE_UINT:
575 case G_TYPE_DOUBLE: {
576 x -= grid->col_widths[grid->col];
577 PangoLayout *l = gtk_widget_create_pango_layout (widget, grid->row_data[grid->row][grid->col].c_str());
578 int xpos, startx;
579 pango_layout_get_pixel_size (l, &xpos, NULL);
580 startx = x + (grid->col_widths[grid->col] - xpos) / 2;
581 xpos = event->x - startx;
582 int index, trailing;
583 pango_layout_xy_to_index (l, xpos * PANGO_SCALE, 0, &index, &trailing);
584 grid->cursor_index = index + trailing;
585 break;
586 }
587 default: // nothing to do
588 break;
589 }
590 *grid->orig_string = grid->row_data[grid->row][grid->col];
591 }
592 } else
593 grid->col = -1;
594 }
595 gtk_widget_grab_focus (widget);
596 gtk_widget_queue_draw (widget);
597 return true;
598 }
599
gcr_grid_button_release_event(GtkWidget * widget,G_GNUC_UNUSED GdkEventButton * event)600 static gboolean gcr_grid_button_release_event (GtkWidget *widget, G_GNUC_UNUSED GdkEventButton *event)
601 {
602 GcrGrid *grid = GCR_GRID (widget);
603 grid->dragging = false;
604 return true;
605 }
606
gcr_grid_scroll_event(GtkWidget * widget,GdkEventScroll * event)607 static gboolean gcr_grid_scroll_event (GtkWidget *widget, GdkEventScroll *event)
608 {
609 return gtk_widget_event (GCR_GRID (widget)->scroll, reinterpret_cast < GdkEvent * > (event));
610 }
611
gcr_grid_key_press_event(GtkWidget * widget,GdkEventKey * event)612 static gboolean gcr_grid_key_press_event (GtkWidget *widget, GdkEventKey *event)
613 {
614 GcrGrid *grid = GCR_GRID (widget);
615 if (grid->row < 0)
616 return false;
617 int new_row = grid->last_row, new_col = grid->col, new_index = grid->cursor_index;
618 char new_char = 0;
619 switch (event->keyval) {
620 case GDK_KEY_Tab:
621 case GDK_KEY_ISO_Left_Tab:
622 if (event->state & GDK_CONTROL_MASK)
623 return false; // give up the grab
624 if (grid->col > 0 && !gcr_grid_validate_change (grid))
625 return true;
626 if (grid->can_edit == 0 && (new_row + 1 < static_cast < int > (grid->rows))) {
627 new_row++;
628 break;
629 }
630 if (event->state & GDK_SHIFT_MASK) {
631 do {
632 if (new_col > 0)
633 new_col--;
634 else if (new_row > 0) {
635 new_row--;
636 new_col = grid->cols - 1;
637 } else
638 return true;
639 } while (!grid->editable[new_col]);
640 } else {
641 do {
642 if (new_col < static_cast < int > (grid->cols) - 1)
643 new_col++;
644 else if (new_row < static_cast < int > (grid->rows) - 1) {
645 new_row++;
646 new_col = 0;
647 } else
648 return true;
649 } while (!grid->editable[new_col]);
650 }
651 new_index = grid->row_data[new_row][new_col].length ();
652 grid->sel_start = 0;
653 break;
654 case GDK_KEY_Left:
655 case GDK_KEY_KP_Left:
656 if (new_index > 0) {
657 new_index = g_utf8_prev_char (grid->row_data[new_row][new_col].c_str () + grid->cursor_index) - grid->row_data[new_row][new_col].c_str ();
658 if ((event->state & GDK_SHIFT_MASK) == 0)
659 grid->sel_start = new_index;
660 } else {
661 if (event->state & GDK_SHIFT_MASK) {
662 if (new_col < static_cast < int > (grid->cols) - 1)
663 new_col = -1; // select the whole line
664 grid->sel_start = new_index = -1;
665 } else {
666 do {
667 if (new_col > 0)
668 new_col--;
669 else if (new_row > 0) {
670 new_row--;
671 new_col = grid->cols - 1;
672 } else
673 return true;
674 } while (!grid->editable[new_col]);
675 new_index = grid->row_data[new_row][new_col].length ();
676 }
677 }
678 break;
679 case GDK_KEY_Right:
680 case GDK_KEY_KP_Right:
681 if (new_index >= 0 && grid->cursor_index < static_cast < int > (grid->row_data[new_row][new_col].length ())) {
682 new_index = g_utf8_next_char (grid->row_data[new_row][new_col].c_str () + grid->cursor_index) - grid->row_data[new_row][new_col].c_str ();
683 if ((event->state & GDK_SHIFT_MASK) == 0)
684 grid->sel_start = new_index;
685 } else {
686 if (event->state & GDK_SHIFT_MASK) {
687 if (new_col < static_cast < int > (grid->cols) - 1)
688 new_col = -1; // select the whole line
689 grid->sel_start = new_index = -1;
690 } else {
691 do {
692 if (new_col < static_cast < int > (grid->cols) - 1)
693 new_col++;
694 else if (grid->row < static_cast < int > (grid->rows) - 1) {
695 new_row++;
696 new_col = 0;
697 } else
698 return true;
699 } while (!grid->editable[new_col]);
700 grid->selected_rows->clear ();
701 grid->sel_start = new_index = 0;
702 }
703 }
704 break;
705 case GDK_KEY_Up:
706 case GDK_KEY_KP_Up:
707 if (event->state & GDK_SHIFT_MASK) {
708 new_col = -1; // select the whole line
709 grid->sel_start = new_index = -1;
710 if (new_row > 0) {
711 int row = new_row - 1;
712 if (row < grid->row)
713 gcr_grid_add_row_to_selection (grid, row);
714 else
715 gcr_grid_unselect_row (grid, grid->last_row);
716 grid->last_row = new_row = row;
717 }
718 } else if (grid->row > 0)
719 new_row--;
720 break;
721 case GDK_KEY_Down:
722 case GDK_KEY_KP_Down:
723 if (event->state & GDK_SHIFT_MASK) {
724 new_col = -1; // select the whole line
725 grid->sel_start = new_index = -1;
726 if (new_row < static_cast < int > (grid->rows) - 1) {
727 int row = new_row + 1;
728 if (row > grid->row)
729 gcr_grid_add_row_to_selection (grid, row);
730 else
731 gcr_grid_unselect_row (grid, grid->last_row);
732 grid->last_row = new_row = row;
733 }
734 } else if (grid->row < static_cast < int > (grid->rows) - 1)
735 new_row++;
736 break;
737 case GDK_KEY_Page_Up:
738 case GDK_KEY_KP_Page_Up:
739 if (event->state & GDK_SHIFT_MASK) {
740 new_col = -1; // select the whole line
741 grid->sel_start = new_index = -1;
742 int i, row = (grid->row >= static_cast < int > (grid->nb_visible))?
743 new_row - grid->nb_visible: 0;
744 for (i = row; i < new_row; i++) {
745 if (i < grid->row)
746 gcr_grid_add_row_to_selection (grid, i);
747 else
748 gcr_grid_unselect_row (grid, i);
749 }
750 grid->last_row = new_row = row;
751 } else if (grid->row >= static_cast < int > (grid->nb_visible))
752 new_row -= grid->nb_visible;
753 else
754 new_row = 0;
755 break;
756 case GDK_KEY_Page_Down:
757 case GDK_KEY_KP_Page_Down:
758 if (event->state & GDK_SHIFT_MASK) {
759 new_col = -1; // select the whole line
760 grid->sel_start = new_index = -1;
761 int i, row = (grid->rows > grid->nb_visible && grid->row < static_cast < int > (grid->rows - grid->nb_visible) - 1)?
762 new_row + grid->nb_visible: grid->rows - 1;
763 for (i = row; i > new_row; i--) {
764 if (i > grid->row)
765 gcr_grid_add_row_to_selection (grid, i);
766 else
767 gcr_grid_unselect_row (grid, i);
768 }
769 grid->last_row = new_row = row;
770 } if (grid->rows > grid->nb_visible && grid->row < static_cast < int > (grid->rows - grid->nb_visible) - 1)
771 new_row += grid->nb_visible;
772 else
773 new_row = grid->rows - 1;
774 break;
775 case GDK_KEY_KP_Subtract:
776 case GDK_KEY_minus:
777 if (grid->types[new_col] == G_TYPE_INT || grid->types[new_col] == G_TYPE_DOUBLE) {
778 if ((new_index > 0 && grid->sel_start > 0) || !grid->row_data[new_row][new_col].compare (0, strlen ("−"), "−"))
779 return true;
780 grid->row_data[new_row][new_col].insert (0, "−");
781 grid->sel_start = new_index = strlen ("−");
782 }
783 break;
784 case GDK_KEY_0:
785 case GDK_KEY_1:
786 case GDK_KEY_2:
787 case GDK_KEY_3:
788 case GDK_KEY_4:
789 case GDK_KEY_5:
790 case GDK_KEY_6:
791 case GDK_KEY_7:
792 case GDK_KEY_8:
793 case GDK_KEY_9:
794 if (new_index < 0)
795 return true;
796 new_char = '0' + event->keyval - GDK_KEY_0;
797 break;
798 case GDK_KEY_KP_0:
799 case GDK_KEY_KP_1:
800 case GDK_KEY_KP_2:
801 case GDK_KEY_KP_3:
802 case GDK_KEY_KP_4:
803 case GDK_KEY_KP_5:
804 case GDK_KEY_KP_6:
805 case GDK_KEY_KP_7:
806 case GDK_KEY_KP_8:
807 case GDK_KEY_KP_9:
808 if (new_index < 0)
809 return true;
810 new_char = '0' + event->keyval - GDK_KEY_KP_0;
811 break;
812 case GDK_KEY_Home:
813 case GDK_KEY_KP_Home:
814 case GDK_KEY_KP_Begin:
815 if (event->state & GDK_CONTROL_MASK) {
816 new_col = 0;
817 if (event->state & GDK_SHIFT_MASK)
818 new_row = 0;
819 grid->sel_start = 0;
820 new_index = grid->row_data[new_row][0].length ();
821 break;
822 }
823 if (new_index <= 0)
824 return true;
825 new_index = 0;
826 if ((event->state & GDK_SHIFT_MASK) == 0)
827 grid->sel_start = 0;
828 break;
829 case GDK_KEY_End:
830 case GDK_KEY_KP_End:
831 if (event->state & GDK_CONTROL_MASK) {
832 new_col = grid->cols - 1;
833 if (event->state & GDK_SHIFT_MASK)
834 new_row = grid->rows - 1;
835 grid->sel_start = 0;
836 new_index = grid->row_data[new_row][new_col].length ();
837 break;
838 }
839 if (new_index == static_cast < int > (grid->row_data[new_row][new_col].length ()))
840 return true;
841 new_index = grid->row_data[new_row][new_col].length ();
842 if ((event->state & GDK_SHIFT_MASK) == 0)
843 grid->sel_start = new_index;
844 break;
845 case GDK_KEY_Return:
846 case GDK_KEY_KP_Enter:
847 if (new_index <= 0)
848 return true;
849 gcr_grid_validate_change (grid);
850 return true;
851 case GDK_KEY_Delete:
852 case GDK_KEY_KP_Delete:
853 if (grid->sel_start == grid->cursor_index) {
854 if (grid->sel_start == static_cast < int > (grid->row_data[new_row][new_col].length ()))
855 return true;
856 char const *start = grid->row_data[new_row][new_col].c_str () + grid->sel_start, *end;
857 end = g_utf8_next_char (start);
858 grid->cursor_index = grid->sel_start + (end - start);
859 } else if (grid->sel_start > grid->cursor_index) {
860 int buf = grid->sel_start;
861 grid->sel_start = grid->cursor_index;
862 grid->cursor_index = buf;
863 }
864 grid->row_data[new_row][new_col].erase (grid->sel_start, grid->cursor_index - grid->sel_start);
865 new_index = grid->sel_start;
866 break;
867 case GDK_KEY_BackSpace:
868 if (grid->sel_start == grid->cursor_index) {
869 if (grid->sel_start == 0)
870 return true;
871 char const *start = grid->row_data[new_row][new_col].c_str () + grid->sel_start, *end;
872 end = g_utf8_prev_char (start);
873 grid->sel_start = grid->cursor_index - (start - end);
874 } else if (grid->sel_start > grid->cursor_index) {
875 int buf = grid->sel_start;
876 grid->sel_start = grid->cursor_index;
877 grid->cursor_index = buf;
878 }
879 grid->row_data[new_row][new_col].erase (grid->sel_start, grid->cursor_index - grid->sel_start);
880 new_index = grid->sel_start;
881 break;
882 case GDK_KEY_period: {
883 int pos, start, end;
884 char const *sep;
885 if (new_index < grid->sel_start) {
886 start = new_index;
887 end = grid->sel_start;
888 } else {
889 start = grid->sel_start;
890 end = new_index;
891 }
892 if (grid->types[new_col] != G_TYPE_DOUBLE ||
893 strcmp (go_locale_get_decimal ()->str, ".") ||
894 ((sep = strchr (grid->row_data[new_row][new_col].c_str (), '.')) != NULL &&
895 (pos = sep - grid->row_data[new_row][new_col].c_str (),
896 pos < start || pos > end)))
897 return true;
898 new_char = '.';
899 break;
900 }
901 case GDK_KEY_comma: {
902 int pos, start, end;
903 char const *sep;
904 if (new_index < grid->sel_start) {
905 start = new_index;
906 end = grid->sel_start;
907 } else {
908 start = grid->sel_start;
909 end = new_index;
910 }
911 if (grid->types[new_col] != G_TYPE_DOUBLE ||
912 strcmp (go_locale_get_decimal ()->str, ",") ||
913 ((sep = strchr (grid->row_data[new_row][new_col].c_str (), ',')) != NULL &&
914 (pos = sep - grid->row_data[new_row][new_col].c_str (),
915 pos < start || pos > end)))
916 return true;
917 new_char = ',';
918 break;
919 }
920 case GDK_KEY_KP_Decimal: {
921 int pos, start, end;
922 char const *sep;
923 if (new_index < grid->sel_start) {
924 start = new_index;
925 end = grid->sel_start;
926 } else {
927 start = grid->sel_start;
928 end = new_index;
929 }
930 if (grid->types[new_col] != G_TYPE_DOUBLE ||
931 ((sep = strstr (grid->row_data[new_row][new_col].c_str (), go_locale_get_decimal ()->str)) != NULL &&
932 (pos = sep - grid->row_data[new_row][new_col].c_str (),
933 pos < start || pos > end)))
934 return true;
935 // don't add anything before the minus sign
936 if (new_index == 0 && grid->sel_start == 0 && !grid->row_data[new_row][new_col].compare (0, strlen ("−"), "−"))
937 return true;
938 // first delete the selected chars if any
939 if (new_index != grid->sel_start) {
940 int length;
941 if (new_index < grid->sel_start)
942 length = grid->sel_start - new_index;
943 else {
944 length = new_index - grid->sel_start;
945 new_index = grid->sel_start;
946 }
947 grid->row_data[new_row][new_col].erase (new_index, length);
948 }
949 if (grid->cursor_index == 0 && !strncmp (grid->row_data[new_row][new_col].c_str (), "−", strlen ("−")))
950 return true; // do not insert a figure before the minus sign
951 // insert the new char(s)
952 grid->row_data[new_row][new_col].insert (new_index, go_locale_get_decimal ()->str);
953 new_index += go_locale_get_decimal ()->len;
954 grid->sel_start = new_index;
955 break;
956 }
957 case GDK_KEY_space:
958 if (grid->types[new_col] != G_TYPE_BOOLEAN)
959 return true;
960 grid->row_data[new_row][new_col] = (grid->row_data[new_row][new_col] == "t")? "f": "t";
961 g_signal_emit (grid, gcr_grid_signals[VALUE_CHANGED], 0, new_row, new_col);
962 break;
963 default:
964 return true;
965 }
966 if (new_char > 0) {
967 // don't add aanything before the minus sign
968 if (new_index == 0 && grid->sel_start == 0 && !grid->row_data[new_row][new_col].compare (0, strlen ("−"), "−"))
969 return true;
970 // first delete the selected chars if any
971 if (new_index != grid->sel_start) {
972 int length;
973 if (new_index < grid->sel_start)
974 length = grid->sel_start - new_index;
975 else {
976 length = new_index - grid->sel_start;
977 new_index = grid->sel_start;
978 }
979 grid->row_data[new_row][new_col].erase (new_index, length);
980 }
981 // insert the new char
982 if (grid->cursor_index == 0 && !strncmp (grid->row_data[new_row][new_col].c_str (), "−", strlen ("−")))
983 return true; // do not insert a figure before the minus sign
984 grid->row_data[new_row][new_col].insert (new_index, 1, new_char);
985 new_index++;
986 grid->sel_start = new_index;
987 }
988 if (new_row != grid->last_row || new_col != grid->col) {
989 if (grid->row >= 0 && grid->col >= 0 && !gcr_grid_validate_change (grid))
990 return true;
991 if (new_row != grid->row)
992 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, new_row);
993 if (new_col >= 0 && grid->editable[new_col]) {
994 grid->row = new_row;
995 grid->col = new_col;
996 *grid->orig_string = grid->row_data[new_row][new_col];
997 int l = grid->orig_string->length ();
998 if (new_index > l)
999 new_index = grid->sel_start = l;
1000 } else if (new_col >= 0) {
1001 grid->row = grid->col = new_index = -1;
1002 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, -1);
1003 } else
1004 grid->col = new_col;
1005 if ((event->state & GDK_SHIFT_MASK) == 0)
1006 grid->last_row = grid->row;
1007 }
1008 // ensure that the selection is visible
1009 if (grid->last_row < grid->first_visible)
1010 grid->first_visible = grid->last_row;
1011 else if (grid->last_row >= grid->first_visible + static_cast < int > (grid->nb_visible))
1012 grid->first_visible = grid->last_row + 1 - grid->nb_visible;
1013 grid->cursor_index = new_index;
1014 gtk_widget_queue_draw (widget);
1015 return true;
1016 }
1017
gcr_grid_class_init(GtkWidgetClass * klass)1018 static void gcr_grid_class_init (GtkWidgetClass *klass)
1019 {
1020 parent_class = reinterpret_cast < GtkWidgetClass * > (g_type_class_peek_parent (klass));
1021 klass->draw = gcr_grid_draw;
1022 klass->size_allocate = gcr_grid_size_allocate;
1023 klass->button_press_event = gcr_grid_button_press_event;
1024 klass->motion_notify_event = gcr_grid_motion_notify_event;
1025 klass->button_release_event = gcr_grid_button_release_event;
1026 klass->scroll_event = gcr_grid_scroll_event;
1027 klass->key_press_event = gcr_grid_key_press_event;
1028 klass->grab_notify = gcr_grid_grab_notify;
1029 klass->focus_out_event = gcr_grid_focus_out_event;
1030 reinterpret_cast < GObjectClass * > (klass)->finalize = gcr_grid_finalize;
1031 klass->get_preferred_height = gcr_grid_get_preferred_height;
1032 klass->get_preferred_width = gcr_grid_get_preferred_width;
1033 klass->unrealize = gcr_grid_unrealize;
1034 gcr_grid_signals[VALUE_CHANGED] = g_signal_new ("value-changed",
1035 G_TYPE_FROM_CLASS(klass),
1036 G_SIGNAL_RUN_LAST,
1037 G_STRUCT_OFFSET(GcrGridClass, value_changed_event),
1038 NULL, NULL,
1039 gcu__VOID__UINT_UINT,
1040 G_TYPE_NONE, 2,
1041 G_TYPE_UINT, G_TYPE_UINT
1042 );
1043 gcr_grid_signals[ROW_SELECTED] = g_signal_new ("row-selected",
1044 G_TYPE_FROM_CLASS(klass),
1045 G_SIGNAL_RUN_LAST,
1046 G_STRUCT_OFFSET(GcrGridClass, row_selected_event),
1047 NULL, NULL,
1048 g_cclosure_marshal_VOID__INT,
1049 G_TYPE_NONE, 1,
1050 G_TYPE_INT
1051 );
1052 gcr_grid_signals[ROW_DELETED] = g_signal_new ("row-deleted",
1053 G_TYPE_FROM_CLASS(klass),
1054 G_SIGNAL_RUN_LAST,
1055 G_STRUCT_OFFSET(GcrGridClass, row_deleted_event),
1056 NULL, NULL,
1057 g_cclosure_marshal_VOID__INT,
1058 G_TYPE_NONE, 1,
1059 G_TYPE_INT
1060 );
1061 }
1062
1063 static void
gcr_grid_init(G_GNUC_UNUSED GcrGrid * grid)1064 gcr_grid_init (G_GNUC_UNUSED GcrGrid *grid)
1065 {
1066 grid->col = grid->row = -1; // nothing selected
1067 }
1068
GSF_CLASS(GcrGrid,gcr_grid,gcr_grid_class_init,gcr_grid_init,GTK_TYPE_LAYOUT)1069 GSF_CLASS (GcrGrid, gcr_grid, gcr_grid_class_init, gcr_grid_init, GTK_TYPE_LAYOUT)
1070
1071 // signal callbacks
1072
1073 void gcr_grid_adjustment_changed (GtkAdjustment *adj, GcrGrid *grid)
1074 {
1075 grid->first_visible = ceil (gtk_adjustment_get_value (adj));
1076 gtk_widget_queue_draw (GTK_WIDGET (grid));
1077 }
1078
gcr_grid_new(G_GNUC_UNUSED char const * col_title,GType col_type,...)1079 GtkWidget *gcr_grid_new (G_GNUC_UNUSED char const *col_title, GType col_type, ...)
1080 {
1081 g_return_val_if_fail (col_title && g_utf8_validate (col_title, -1, NULL), NULL);
1082 GcrGrid *grid = GCR_GRID (g_object_new (GCR_TYPE_GRID, NULL));
1083 gtk_widget_add_events (GTK_WIDGET (grid),
1084 GDK_POINTER_MOTION_MASK |
1085 GDK_BUTTON_MOTION_MASK |
1086 GDK_BUTTON_PRESS_MASK |
1087 GDK_BUTTON_RELEASE_MASK |
1088 GDK_KEY_PRESS_MASK |
1089 GDK_LEAVE_NOTIFY_MASK
1090 );
1091 std::list < char const * > titles;
1092 std::list < GType > types;
1093 titles.push_front (col_title);
1094 types.push_front (col_type);
1095 va_list args;
1096 va_start (args, col_type);
1097 int int_size, double_size, col_size, title_size, height;
1098 PangoLayout *layout = gtk_widget_create_pango_layout (reinterpret_cast <GtkWidget *> (grid), "000000");
1099 pango_layout_get_pixel_size (layout, &int_size, &height);
1100 pango_layout_set_text (layout, "0.00000000", -1);
1101 pango_layout_get_pixel_size (layout, &double_size, NULL);
1102 grid->width = 0;
1103 GtkWidget *w = gtk_button_new_with_label ("00");
1104 gtk_widget_get_preferred_height (w, &grid->row_height, NULL);
1105 if (grid->row_height < height)
1106 grid->row_height = height + 2;
1107 grid->line_offset = (grid->row_height - height) / 2;
1108 gtk_widget_get_preferred_width (w, &grid->header_width, NULL);
1109 g_object_ref_sink (w);
1110 while (1) {
1111 col_title = va_arg (args, char const *);
1112 if (!col_title)
1113 break;
1114 col_type = va_arg (args, GType);
1115 if (g_utf8_validate (col_title, -1, NULL)) {
1116 titles.push_back (col_title);
1117 types.push_back (col_type);
1118 }
1119 }
1120 va_end (args);
1121 grid->cols = titles.size ();
1122 grid->col_widths = new int[grid->cols];
1123 grid->min_widths = new int[grid->cols];
1124 grid->titles = new std::string[grid->cols];
1125 grid->types = new GType[grid->cols];
1126 grid->editable = new bool[grid->cols];
1127 grid->selected_rows = new std::set < int > ();
1128 unsigned i;
1129 std::list <char const *>::iterator title = titles.begin ();
1130 std::list <GType>::iterator type = types.begin ();
1131 grid->width = grid->header_width;
1132 grid->cols_min_width = 0;
1133 for (i = 0; i < grid->cols; i++, title++, type++) {
1134 switch (*type) {
1135 case G_TYPE_INT:
1136 case G_TYPE_UINT:
1137 case G_TYPE_STRING:
1138 col_size = int_size;
1139 break;
1140 case G_TYPE_DOUBLE:
1141 col_size = double_size;
1142 break;
1143 case G_TYPE_BOOLEAN:
1144 if (!checked) {
1145 GtkWidget *ofw = gtk_offscreen_window_new ();
1146 GtkWidget *layout = gtk_layout_new (NULL, NULL);
1147 GtkWidget *widget = gtk_check_button_new ();
1148 GdkRGBA rgba = {1., 1., 1., 1.};
1149 gtk_widget_override_background_color (layout, GTK_STATE_FLAG_NORMAL, &rgba);
1150 gtk_widget_set_size_request (layout, grid->row_height - 1, grid->row_height - 1);
1151 gtk_container_add (GTK_CONTAINER (ofw), layout);
1152 gtk_layout_put (GTK_LAYOUT (layout), widget, 0, 0);
1153 gtk_widget_show_all (ofw);
1154 while (gtk_events_pending ())
1155 gtk_main_iteration ();
1156 unchecked = gtk_offscreen_window_get_pixbuf (GTK_OFFSCREEN_WINDOW (ofw));
1157 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), true);
1158 while (gtk_events_pending ())
1159 gtk_main_iteration ();
1160 checked = gtk_offscreen_window_get_pixbuf (GTK_OFFSCREEN_WINDOW (ofw));
1161 gtk_widget_destroy (ofw);
1162 }
1163 col_size = grid->row_height;
1164 break;
1165 default:
1166 col_size = 0;
1167 break;
1168 }
1169 // now evaluate the size of the title
1170 pango_layout_set_markup (layout, *title, -1);
1171 pango_layout_get_pixel_size (layout, &title_size, NULL);
1172 if (col_size < title_size)
1173 col_size = title_size;
1174 col_size += 6; // add some padding
1175 grid->min_widths[i] = col_size;
1176 grid->cols_min_width += col_size;
1177 grid->titles[i] = *title;
1178 grid->types[i] = *type;
1179 grid->editable[i] = true;
1180 }
1181 grid->can_edit = grid->cols;
1182 grid->width += grid->cols_min_width;
1183 g_object_unref (layout);
1184 GdkRGBA rgba = {1.0, 1.0, 1.0, 1.0};
1185 gtk_widget_override_background_color (reinterpret_cast <GtkWidget *> (grid), GTK_STATE_FLAG_NORMAL, &rgba);
1186 // add a vertical scrollbar
1187 grid->vadj = gtk_adjustment_new (0, 0, 1, 1, 10, 0);
1188 grid->scroll = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, grid->vadj);
1189 g_object_set (G_OBJECT (grid->scroll), "height-request", grid->row_height * 5, NULL); //default size
1190 gtk_layout_put (GTK_LAYOUT (grid), grid->scroll, grid->width + 1, grid->row_height + 1);
1191 gtk_widget_get_preferred_width (grid->scroll, &grid->scroll_width, NULL);
1192 grid->width += grid->scroll_width + 1;
1193 gtk_widget_set_can_focus(reinterpret_cast <GtkWidget *> (grid), true);
1194 // add signals
1195 g_signal_connect (grid->vadj, "value-changed", G_CALLBACK (gcr_grid_adjustment_changed), grid);
1196 grid->orig_string = new std::string ();
1197 return reinterpret_cast <GtkWidget *> (grid);
1198 }
1199
gcr_grid_get_int(GcrGrid * grid,unsigned row,unsigned column)1200 int gcr_grid_get_int (GcrGrid *grid, unsigned row, unsigned column)
1201 {
1202 g_return_val_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_INT, 0);
1203 return grid->row_data[row][column].compare (0, strlen ("−"), "−")?
1204 atoi (grid->row_data[row][column].c_str ()):
1205 -atoi (grid->row_data[row][column].c_str () + strlen ("−"));
1206 }
1207
gcr_grid_get_uint(GcrGrid * grid,unsigned row,unsigned column)1208 unsigned gcr_grid_get_uint (GcrGrid *grid, unsigned row, unsigned column)
1209 {
1210 g_return_val_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_UINT, 0);
1211 return strtoul (grid->row_data[row][column].c_str (), NULL, 10);
1212 }
1213
gcr_grid_get_double(GcrGrid * grid,unsigned row,unsigned column)1214 double gcr_grid_get_double (GcrGrid *grid, unsigned row, unsigned column)
1215 {
1216 g_return_val_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_DOUBLE, go_nan);
1217 return grid->row_data[row][column].compare (0, strlen ("−"), "−")?
1218 atof (grid->row_data[row][column].c_str ()):
1219 -atof (grid->row_data[row][column].c_str ());
1220 }
1221
gcr_grid_get_string(GcrGrid * grid,unsigned row,unsigned column)1222 char const *gcr_grid_get_string (GcrGrid *grid, unsigned row, unsigned column)
1223 {
1224 g_return_val_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_STRING, NULL);
1225 return grid->row_data[row][column].c_str ();
1226 }
1227
gcr_grid_get_boolean(GcrGrid * grid,unsigned row,unsigned column)1228 bool gcr_grid_get_boolean (GcrGrid *grid, unsigned row, unsigned column)
1229 {
1230 g_return_val_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_BOOLEAN, false);
1231 return grid->row_data[row][column] == "t";
1232 }
1233
gcr_grid_set_int(GcrGrid * grid,unsigned row,unsigned column,int value)1234 void gcr_grid_set_int (GcrGrid *grid, unsigned row, unsigned column, int value)
1235 {
1236 g_return_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_INT);
1237 char *buf;
1238 if (value >= 0)
1239 buf = g_strdup_printf ("%d", value);
1240 else
1241 buf = g_strdup_printf ("−%d", -value);
1242 grid->row_data[row][column] = buf;
1243 g_free (buf);
1244 gtk_widget_queue_draw (GTK_WIDGET (grid));
1245 }
1246
gcr_grid_set_uint(GcrGrid * grid,unsigned row,unsigned column,unsigned value)1247 void gcr_grid_set_uint (GcrGrid *grid, unsigned row, unsigned column, unsigned value)
1248 {
1249 g_return_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_UINT);
1250 char *buf = g_strdup_printf ("%u", value);
1251 grid->row_data[row][column] = buf;
1252 g_free (buf);
1253 gtk_widget_queue_draw (GTK_WIDGET (grid));
1254 }
1255
gcr_grid_set_double(GcrGrid * grid,unsigned row,unsigned column,double value)1256 void gcr_grid_set_double (GcrGrid *grid, unsigned row, unsigned column, double value)
1257 {
1258 g_return_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_DOUBLE);
1259 char *buf = g_strdup_printf ("%g", value);
1260 grid->row_data[row][column] = buf;
1261 g_free (buf);
1262 gtk_widget_queue_draw (GTK_WIDGET (grid));
1263 }
1264
gcr_grid_set_string(GcrGrid * grid,unsigned row,unsigned column,char const * value)1265 void gcr_grid_set_string (GcrGrid *grid, unsigned row, unsigned column, char const *value)
1266 {
1267 g_return_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_STRING);
1268 grid->row_data[row][column] = value;
1269 gtk_widget_queue_draw (GTK_WIDGET (grid));
1270 }
1271
gcr_grid_set_boolean(GcrGrid * grid,unsigned row,unsigned column,bool value)1272 void gcr_grid_set_boolean (GcrGrid *grid, unsigned row, unsigned column, bool value)
1273 {
1274 g_return_if_fail (GCR_IS_GRID (grid) && row < grid->rows && column < grid->cols && grid->types[column] == G_TYPE_BOOLEAN);
1275 grid->row_data[row][column] = value? "t": "f";
1276 gtk_widget_queue_draw (GTK_WIDGET (grid));
1277 }
1278
gcr_grid_append_row(GcrGrid * grid,...)1279 unsigned gcr_grid_append_row (GcrGrid *grid,...)
1280 {
1281 g_return_val_if_fail (GCR_IS_GRID (grid) && grid->cols > 0, 0);
1282 unsigned row = grid->rows++, col;
1283 char *buf;
1284 if (grid->row_data.capacity () < grid->rows)
1285 grid->row_data.resize (grid->row_data.capacity () + 5);
1286 grid->row_data[row] = new std::string[grid->cols];
1287 va_list args;
1288 va_start (args, grid);
1289 for (col = 0; col < grid->cols; col++) {
1290 switch (grid->types[col]) {
1291 case G_TYPE_INT: {
1292 int value = va_arg (args, int);
1293 if (value >= 0)
1294 buf = g_strdup_printf ("%d", value);
1295 else
1296 buf = g_strdup_printf ("−%d", -value);
1297 grid->row_data[row][col] = buf;
1298 g_free (buf);
1299 break;
1300 }
1301 case G_TYPE_UINT:
1302 buf = g_strdup_printf ("%u", va_arg (args, unsigned));
1303 grid->row_data[row][col] = buf;
1304 g_free (buf);
1305 break;
1306 case G_TYPE_DOUBLE:
1307 buf = g_strdup_printf ("%f", va_arg (args, double));
1308 grid->row_data[row][col] = buf;
1309 g_free (buf);
1310 break;
1311 case G_TYPE_STRING:
1312 grid->row_data[row][col] = va_arg (args, char const*);
1313 break;
1314 case G_TYPE_BOOLEAN:
1315 grid->row_data[row][col] = (va_arg (args, gboolean))? "t": "f";
1316 break;
1317 default:
1318 // do nothing, unsupported type
1319 break;
1320 }
1321 }
1322 va_end (args);
1323 gtk_adjustment_set_page_size (grid->vadj, static_cast < double > (grid->nb_visible) / grid->rows);
1324 gtk_adjustment_set_upper (grid->vadj, (grid->nb_visible < grid->rows)? grid->rows - grid->nb_visible: .1);
1325 if (grid->rows < grid->nb_visible + grid->first_visible) {
1326 grid->first_visible = (grid->nb_visible < grid->rows)? grid->rows - grid->nb_visible: 0;
1327 gtk_adjustment_set_value (grid->vadj, grid->first_visible);
1328 }
1329 gtk_widget_queue_draw (GTK_WIDGET (grid));
1330 return row;
1331 }
1332
gcr_grid_delete_row(GcrGrid * grid,unsigned row)1333 void gcr_grid_delete_row (GcrGrid *grid, unsigned row)
1334 {
1335 g_return_if_fail (GCR_IS_GRID (grid) && grid->rows > row);
1336 delete [] grid->row_data[row];
1337 g_signal_emit (grid, gcr_grid_signals[ROW_DELETED], 0, row);
1338 for (unsigned n = row + 1 ; n < grid->rows; n++)
1339 grid->row_data[n - 1] = grid->row_data[n];
1340 grid->rows--;
1341 std::set < int > decreased;
1342 std::set < int >::iterator i, end = grid->selected_rows->end ();
1343 for (i = grid->selected_rows->begin (); i != end; i++)
1344 if ((*i) > static_cast < int > (row))
1345 decreased.insert (*i);
1346 grid->selected_rows->erase (row);
1347 end = decreased.end ();
1348 for (i = decreased.begin (); i != end; i++)
1349 grid->selected_rows->erase (*i);
1350 for (i = decreased.begin (); i != end; i++)
1351 grid->selected_rows->insert ((*i) - 1);
1352 if (grid->row == static_cast < int > (grid->rows)) {
1353 grid->row = -1;
1354 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, -1);
1355 }
1356 if (!grid->selection_locked)
1357 grid->selected_rows->clear ();
1358 gtk_widget_queue_draw (GTK_WIDGET (grid));
1359 }
1360
gcr_grid_delete_selected_rows(GcrGrid * grid)1361 void gcr_grid_delete_selected_rows (GcrGrid *grid)
1362 {
1363 g_return_if_fail (GCR_IS_GRID (grid));
1364 if (grid->row == -1)
1365 return;
1366 // save row and set it to -1 to not fire ROW_SELECTED when not needed
1367 int row = grid->row;
1368 grid->row = -1;
1369 grid->selection_locked = true;
1370 gcr_grid_delete_row (grid, row);
1371 while (!grid->selected_rows->empty ())
1372 gcr_grid_delete_row (grid, *grid->selected_rows->begin ());
1373 grid->selected_rows->clear ();
1374 if (row >= static_cast < int > (grid->rows))
1375 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, -1);
1376 else
1377 grid->row = row;
1378 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, -1);
1379 gtk_widget_queue_draw (GTK_WIDGET (grid));
1380 grid->selection_locked = false;
1381 }
1382
gcr_grid_delete_all(GcrGrid * grid)1383 void gcr_grid_delete_all (GcrGrid *grid)
1384 {
1385 g_return_if_fail (GCR_IS_GRID (grid));
1386 for (unsigned i = 0; i < grid->rows; i++)
1387 delete [] grid->row_data[i];
1388 grid->rows = 0;
1389 if (grid->row >= 0) {
1390 grid->row = -1;
1391 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, -1);
1392 }
1393 gtk_widget_queue_draw (GTK_WIDGET (grid));
1394 }
1395
gcr_grid_customize_column(GcrGrid * grid,unsigned column,unsigned chars,bool editable)1396 void gcr_grid_customize_column (GcrGrid *grid, unsigned column, unsigned chars, bool editable)
1397 {
1398 g_return_if_fail (GCR_IS_GRID (grid) && column < grid->cols);
1399 if (grid->editable[column])
1400 grid->can_edit--;
1401 grid->editable[column] = editable;
1402 if (editable)
1403 grid->can_edit++;
1404 PangoLayout *l = gtk_widget_create_pango_layout (reinterpret_cast <GtkWidget *> (grid), grid->titles[column].c_str ());
1405 int width, title_width;
1406 pango_layout_get_pixel_size (l, &title_width, NULL);
1407 std::string s (chars, 'W');
1408 pango_layout_set_text (l, s.c_str (), -1);
1409 pango_layout_get_pixel_size (l, &width, NULL);
1410 if (width < title_width)
1411 width = title_width;
1412 if (width != grid->min_widths[column]) {
1413 grid->cols_min_width -= grid->min_widths[column];
1414 grid->min_widths[column] = width;
1415 grid->cols_min_width += width;
1416 grid->width = grid->header_width + grid->cols_min_width + grid->scroll_width;
1417 gtk_widget_queue_resize (GTK_WIDGET (grid));
1418 }
1419 }
1420
gcr_grid_set_allow_multiple_selection(GcrGrid * grid,bool allow)1421 void gcr_grid_set_allow_multiple_selection (GcrGrid *grid, bool allow)
1422 {
1423 grid->allow_multiple = allow;
1424 }
1425
gcr_grid_for_each_selected(GcrGrid * grid,GridCb cb,void * user_data)1426 void gcr_grid_for_each_selected (GcrGrid *grid, GridCb cb, void *user_data)
1427 {
1428 g_return_if_fail (GCR_IS_GRID (grid));
1429 if (grid->row < 0)
1430 return;
1431 cb (grid->row, user_data);
1432 std::set < int >::iterator i, end = grid->selected_rows->end ();
1433 for (i = grid->selected_rows->begin (); i != end; i++)
1434 cb (*i, user_data);
1435 }
1436
gcr_grid_select_all(GcrGrid * grid)1437 void gcr_grid_select_all (GcrGrid *grid)
1438 {
1439 g_return_if_fail (GCR_IS_GRID (grid) && grid->allow_multiple);
1440 if (grid->rows == 0)
1441 return;
1442 if (grid->row < 0) {
1443 grid->row = 0;
1444 g_signal_emit (grid, gcr_grid_signals[ROW_SELECTED], 0, 0);
1445 } else if (grid->col > 0 && !gcr_grid_validate_change (grid))
1446 return;
1447 for (unsigned i = 0; i < grid->rows; i++)
1448 if (i != static_cast < unsigned > (grid->row))
1449 grid->selected_rows->insert (i);
1450 gtk_widget_queue_draw (GTK_WIDGET (grid));
1451 }
1452
gcr_grid_add_row_to_selection(GcrGrid * grid,unsigned row)1453 void gcr_grid_add_row_to_selection (GcrGrid *grid, unsigned row)
1454 {
1455 if (grid->row < 0)
1456 grid->row = row;
1457 else if (row != static_cast < unsigned > (grid->row))
1458 grid->selected_rows->insert (row);
1459 gtk_widget_queue_draw (GTK_WIDGET (grid));
1460 }
1461
gcr_grid_unselect_row(GcrGrid * grid,unsigned row)1462 void gcr_grid_unselect_row (GcrGrid *grid, unsigned row)
1463 {
1464 grid->selected_rows->erase (row);
1465 gtk_widget_queue_draw (GTK_WIDGET (grid));
1466 }
1467