1 /********************************************************************\
2  * gnucash-item-edit.c -- cell editor cut-n-paste from gnumeric     *
3  *                                                                  *
4  * This program is free software; you can redistribute it and/or    *
5  * modify it under the terms of the GNU General Public License as   *
6  * published by the Free Software Foundation; either version 2 of   *
7  * the License, or (at your option) any later version.              *
8  *                                                                  *
9  * This program is distributed in the hope that it will be useful,  *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
12  * GNU General Public License for more details.                     *
13  *                                                                  *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact:                        *
16  *                                                                  *
17  * Free Software Foundation           Voice:  +1-617-542-5942       *
18  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
19  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
20  *                                                                  *
21 \********************************************************************/
22 
23 /*
24  *  An editor for the gnucash sheet.
25  *  Cut and pasted from the gnumeric item-edit.c file.
26  *
27  *  And then substantially rewritten by Dave Peticolas <dave@krondo.com>.
28  */
29 
30 
31 #include <config.h>
32 
33 #include <string.h>
34 #include <qof.h>
35 
36 #include "gnucash-color.h"
37 #include "gnucash-cursor.h"
38 #include "gnucash-item-edit.h"
39 #include "gnucash-sheet.h"
40 #include "gnucash-sheetP.h"
41 #include "gnucash-style.h"
42 
43 #include "gnc-ui-util.h"
44 
45 /* The arguments we take */
46 enum
47 {
48     PROP_0,
49     PROP_SHEET,     /* The sheet property      */
50 };
51 
52 /* values for selection info */
53 enum
54 {
55     TARGET_UTF8_STRING,
56     TARGET_STRING,
57     TARGET_TEXT,
58     TARGET_COMPOUND_TEXT
59 };
60 
61 #define MIN_BUTT_WIDTH 20 // minimum size for a button excluding border
62 
63 static QofLogModule log_module = G_LOG_DOMAIN;
64 static GtkBoxClass *gnc_item_edit_parent_class;
65 
66 static GtkToggleButtonClass *gnc_item_edit_tb_parent_class;
67 static void gnc_item_edit_destroying (GtkWidget *this, gpointer data);
68 static void
gnc_item_edit_tb_init(GncItemEditTb * item_edit_tb)69 gnc_item_edit_tb_init (GncItemEditTb *item_edit_tb)
70 {
71     item_edit_tb->sheet = NULL;
72 }
73 
74 static void
gnc_item_edit_tb_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)75 gnc_item_edit_tb_get_property (GObject *object,
76                                guint param_id,
77                                GValue *value,
78                                GParamSpec *pspec)
79 {
80     GncItemEditTb *item_edit_tb = GNC_ITEM_EDIT_TB(object);
81 
82     switch (param_id)
83     {
84     case PROP_SHEET:
85         g_value_take_object (value, item_edit_tb->sheet);
86         break;
87     default:
88         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
89         break;
90     }
91 }
92 
93 static void
gnc_item_edit_tb_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)94 gnc_item_edit_tb_set_property (GObject *object,
95                                guint param_id,
96                                const GValue *value,
97                                GParamSpec *pspec)
98 {
99     GncItemEditTb *item_edit_tb = GNC_ITEM_EDIT_TB(object);
100 
101     switch (param_id)
102     {
103     case PROP_SHEET:
104         item_edit_tb->sheet = GNUCASH_SHEET(g_value_get_object (value));
105         break;
106     default:
107         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
108         break;
109     }
110 }
111 
112 static void
gnc_item_edit_tb_get_preferred_width(GtkWidget * widget,gint * minimal_width,gint * natural_width)113 gnc_item_edit_tb_get_preferred_width (GtkWidget *widget,
114                                       gint *minimal_width,
115                                       gint *natural_width)
116 {
117     GncItemEditTb *tb = GNC_ITEM_EDIT_TB(widget);
118     GncItemEdit *item_edit = GNC_ITEM_EDIT(tb->sheet->item_editor);
119     GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(tb));
120     GtkBorder border;
121     gint x, y, w, h = 2, width = 0;
122     gnc_item_edit_get_pixel_coords (GNC_ITEM_EDIT(item_edit), &x, &y, &w, &h);
123     width = ((h - 2)*2)/3;
124 
125     gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
126 
127     if (width < MIN_BUTT_WIDTH + border.left + border.right)
128         width = MIN_BUTT_WIDTH + border.left + border.right;
129 
130     *minimal_width = *natural_width = width;
131     item_edit->button_width = width;
132 }
133 
134 static void
gnc_item_edit_tb_get_preferred_height(GtkWidget * widget,gint * minimal_width,gint * natural_width)135 gnc_item_edit_tb_get_preferred_height (GtkWidget *widget,
136                                        gint *minimal_width,
137                                        gint *natural_width)
138 {
139     GncItemEditTb *tb = GNC_ITEM_EDIT_TB(widget);
140     GncItemEdit *item_edit = GNC_ITEM_EDIT(tb->sheet->item_editor);
141     gint x, y, w, h = 2;
142     gnc_item_edit_get_pixel_coords (GNC_ITEM_EDIT(item_edit), &x, &y, &w, &h);
143     *minimal_width = *natural_width = (h - 2);
144 }
145 
146 static void
gnc_item_edit_tb_class_init(GncItemEditTbClass * gnc_item_edit_tb_class)147 gnc_item_edit_tb_class_init (GncItemEditTbClass *gnc_item_edit_tb_class)
148 {
149     GObjectClass  *object_class;
150     GtkWidgetClass *widget_class;
151 
152     gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(gnc_item_edit_tb_class), "button");
153 
154     gnc_item_edit_tb_parent_class = g_type_class_peek_parent (gnc_item_edit_tb_class);
155 
156     object_class = G_OBJECT_CLASS(gnc_item_edit_tb_class);
157     widget_class = GTK_WIDGET_CLASS(gnc_item_edit_tb_class);
158 
159     object_class->get_property = gnc_item_edit_tb_get_property;
160     object_class->set_property = gnc_item_edit_tb_set_property;
161 
162     g_object_class_install_property (object_class,
163                                      PROP_SHEET,
164                                      g_param_spec_object ("sheet",
165                                              "Sheet Value",
166                                              "Sheet Value",
167                                              GNUCASH_TYPE_SHEET,
168                                              G_PARAM_READWRITE));
169 
170     /* GtkWidget method overrides */
171     widget_class->get_preferred_width = gnc_item_edit_tb_get_preferred_width;
172     widget_class->get_preferred_height = gnc_item_edit_tb_get_preferred_height;
173 }
174 
175 GType
gnc_item_edit_tb_get_type(void)176 gnc_item_edit_tb_get_type (void)
177 {
178     static GType gnc_item_edit_tb_type = 0;
179 
180     if (!gnc_item_edit_tb_type)
181     {
182         static const GTypeInfo gnc_item_edit_tb_info =
183         {
184             sizeof (GncItemEditTbClass),
185             NULL,
186             NULL,
187             (GClassInitFunc)gnc_item_edit_tb_class_init,
188             NULL,
189             NULL,
190             sizeof (GncItemEditTb),
191             0, /* n_preallocs */
192             (GInstanceInitFunc)gnc_item_edit_tb_init,
193             NULL,
194         };
195         gnc_item_edit_tb_type =
196             g_type_register_static (GTK_TYPE_TOGGLE_BUTTON,
197                                     "GncItemEditTb",
198                                     &gnc_item_edit_tb_info, 0);
199     }
200     return gnc_item_edit_tb_type;
201 }
202 
203 GtkWidget *
gnc_item_edit_tb_new(GnucashSheet * sheet)204 gnc_item_edit_tb_new (GnucashSheet *sheet)
205 {
206     GtkStyleContext *context;
207     GncItemEditTb *item_edit_tb = g_object_new (GNC_TYPE_ITEM_EDIT_TB,
208                                                 "sheet", sheet,
209                                                 NULL);
210 
211     context = gtk_widget_get_style_context (GTK_WIDGET(item_edit_tb));
212     gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
213 
214     return GTK_WIDGET(item_edit_tb);
215 }
216 
217 static gboolean
tb_button_press_cb(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer * user_data)218 tb_button_press_cb (G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event,
219                     G_GNUC_UNUSED gpointer *user_data)
220 {
221     /* Ignore double-clicks and triple-clicks */
222     if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
223     {
224         // block a right click
225         return TRUE;
226     }
227     return FALSE;
228 }
229 
230 /*
231  * Returns the coordinates for the editor bounding box
232  */
233 void
gnc_item_edit_get_pixel_coords(GncItemEdit * item_edit,int * x,int * y,int * w,int * h)234 gnc_item_edit_get_pixel_coords (GncItemEdit *item_edit,
235                                 int *x, int *y,
236                                 int *w, int *h)
237 {
238     GnucashSheet *sheet = item_edit->sheet;
239     SheetBlock *block;
240     int xd, yd;
241 
242     if (sheet == NULL)
243         return;
244 
245     block = gnucash_sheet_get_block (sheet, item_edit->virt_loc.vcell_loc);
246     if (block == NULL)
247         return;
248 
249     xd = block->origin_x;
250     yd = block->origin_y;
251 
252     gnucash_sheet_style_get_cell_pixel_rel_coords (item_edit->style,
253                                                    item_edit->virt_loc.phys_row_offset,
254                                                    item_edit->virt_loc.phys_col_offset,
255                                                    x, y, w, h);
256 
257     // alter cell size of first column
258     if (item_edit->virt_loc.phys_col_offset == 0)
259     {
260         *x = *x + 1;
261         *w = *w - 1;
262     }
263     *x += xd;
264     *y += yd;
265 }
266 
267 static gboolean
gnc_item_edit_update(GncItemEdit * item_edit)268 gnc_item_edit_update (GncItemEdit *item_edit)
269 {
270     gint x = 0, y = 0, w, h;
271 
272     if (item_edit == NULL || item_edit->sheet == NULL)
273         return FALSE;
274     gnc_item_edit_get_pixel_coords (item_edit, &x, &y, &w, &h);
275     gtk_layout_move (GTK_LAYOUT(item_edit->sheet),
276                      GTK_WIDGET(item_edit), x, y);
277 
278     if (item_edit->is_popup)
279     {
280         gtk_widget_show (item_edit->popup_toggle.ebox);
281         if (item_edit->show_popup)
282             gnc_item_edit_show_popup (item_edit);
283     }
284     return FALSE;
285 }
286 
287 void
gnc_item_edit_focus_in(GncItemEdit * item_edit)288 gnc_item_edit_focus_in (GncItemEdit *item_edit)
289 {
290     GdkEventFocus ev;
291 
292     g_return_if_fail (item_edit != NULL);
293     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
294 
295     ev.type = GDK_FOCUS_CHANGE;
296     ev.window = gtk_widget_get_window (GTK_WIDGET(item_edit->sheet));
297     ev.in = TRUE;
298     gtk_widget_event (item_edit->editor, (GdkEvent*) &ev);
299 }
300 
301 void
gnc_item_edit_focus_out(GncItemEdit * item_edit)302 gnc_item_edit_focus_out (GncItemEdit *item_edit)
303 {
304     GdkEventFocus ev;
305 
306     g_return_if_fail (item_edit != NULL);
307     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
308 
309     ev.type = GDK_FOCUS_CHANGE;
310     ev.window = gtk_widget_get_window (GTK_WIDGET(item_edit->sheet));
311     ev.in = FALSE;
312     gtk_widget_event (item_edit->editor, (GdkEvent*) &ev);
313 }
314 
315 /*
316  * Instance initialization
317  */
318 static void
gnc_item_edit_init(GncItemEdit * item_edit)319 gnc_item_edit_init (GncItemEdit *item_edit)
320 {
321     /* Set invalid values so that we know when we have been fully
322            initialized */
323     gtk_orientable_set_orientation (GTK_ORIENTABLE(item_edit),
324                                     GTK_ORIENTATION_HORIZONTAL);
325 
326     item_edit->sheet = NULL;
327     item_edit->editor = NULL;
328     item_edit->preedit_length = 0;
329 
330     item_edit->is_popup = FALSE;
331     item_edit->show_popup = FALSE;
332 
333     item_edit->popup_toggle.ebox = NULL;
334     item_edit->popup_toggle.tbutton = NULL;
335     item_edit->popup_toggle.arrow_down = TRUE;
336     item_edit->popup_toggle.signals_connected = FALSE;
337 
338     item_edit->popup_item = NULL;
339     item_edit->popup_get_height = NULL;
340     item_edit->popup_autosize = NULL;
341     item_edit->popup_set_focus = NULL;
342     item_edit->popup_post_show = NULL;
343     item_edit->popup_user_data = NULL;
344     item_edit->popup_returned_height = 0;
345     item_edit->popup_height_signal_id = 0;
346 
347     item_edit->style = NULL;
348     item_edit->button_width = MIN_BUTT_WIDTH;
349 
350     gnc_virtual_location_init (&item_edit->virt_loc);
351 }
352 
353 void
gnc_item_edit_configure(GncItemEdit * item_edit)354 gnc_item_edit_configure (GncItemEdit *item_edit)
355 {
356     GnucashSheet *sheet = item_edit->sheet;
357     GnucashCursor *cursor;
358     gfloat xalign;
359 
360     cursor = GNUCASH_CURSOR(sheet->cursor);
361 
362     item_edit->virt_loc.vcell_loc.virt_row = cursor->row;
363     item_edit->virt_loc.vcell_loc.virt_col = cursor->col;
364 
365     item_edit->style = gnucash_sheet_get_style (sheet,
366                            item_edit->virt_loc.vcell_loc);
367 
368     item_edit->virt_loc.phys_row_offset = cursor->cell.row;
369     item_edit->virt_loc.phys_col_offset = cursor->cell.col;
370 
371     switch (gnc_table_get_align (sheet->table, item_edit->virt_loc))
372     {
373         default:
374         case CELL_ALIGN_LEFT:
375             xalign = 0;
376             break;
377 
378         case CELL_ALIGN_RIGHT:
379             xalign = 1;
380             break;
381 
382         case CELL_ALIGN_CENTER:
383             xalign = 0.5;
384             break;
385     }
386     gtk_entry_set_alignment (GTK_ENTRY(item_edit->editor), xalign);
387 
388     if (!gnc_table_is_popup (sheet->table, item_edit->virt_loc))
389         gnc_item_edit_set_popup (item_edit, NULL, NULL, NULL,
390                                  NULL, NULL, NULL, NULL);
391 
392     g_idle_add_full (G_PRIORITY_HIGH_IDLE,
393                     (GSourceFunc)gnc_item_edit_update, item_edit, NULL);
394 }
395 
396 
397 void
gnc_item_edit_cut_clipboard(GncItemEdit * item_edit)398 gnc_item_edit_cut_clipboard (GncItemEdit *item_edit)
399 {
400     gtk_editable_cut_clipboard (GTK_EDITABLE(item_edit->editor));
401 }
402 
403 void
gnc_item_edit_copy_clipboard(GncItemEdit * item_edit)404 gnc_item_edit_copy_clipboard (GncItemEdit *item_edit)
405 {
406     gtk_editable_copy_clipboard (GTK_EDITABLE(item_edit->editor));
407 }
408 
409 void
gnc_item_edit_paste_clipboard(GncItemEdit * item_edit)410 gnc_item_edit_paste_clipboard (GncItemEdit *item_edit)
411 {
412     GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(item_edit->editor),
413                                                         GDK_SELECTION_CLIPBOARD);
414     gchar *text = gtk_clipboard_wait_for_text (clipboard);
415     gchar *filtered_text;
416     gint start_pos, end_pos;
417     gint position;
418 
419     if (!text)
420         return;
421 
422     filtered_text = gnc_filter_text_for_control_chars (text);
423 
424     if (!filtered_text)
425     {
426         g_free (text);
427         return;
428     }
429 
430     position = gtk_editable_get_position (GTK_EDITABLE(item_edit->editor));
431 
432     if (gtk_editable_get_selection_bounds (GTK_EDITABLE(item_edit->editor),
433                                            &start_pos, &end_pos))
434     {
435         position = start_pos;
436 
437         gtk_editable_delete_selection (GTK_EDITABLE(item_edit->editor));
438         gtk_editable_insert_text (GTK_EDITABLE(item_edit->editor),
439                                   filtered_text, -1, &position);
440     }
441     else
442         gtk_editable_insert_text (GTK_EDITABLE(item_edit->editor),
443                                   filtered_text, -1, &position);
444 
445     gtk_editable_set_position (GTK_EDITABLE(item_edit->editor), position);
446 
447     g_free (text);
448     g_free (filtered_text);
449 }
450 
451 
452 static gboolean
key_press_popup_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)453 key_press_popup_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
454 {
455     GncItemEdit *item_edit = GNC_ITEM_EDIT(data);
456 
457     g_signal_stop_emission_by_name (widget, "key_press_event");
458 
459     gtk_widget_event (GTK_WIDGET(item_edit->sheet), (GdkEvent *) event);
460 
461     return TRUE;
462 }
463 
464 
465 static void
gnc_item_edit_popup_toggled(GtkToggleButton * button,gpointer data)466 gnc_item_edit_popup_toggled (GtkToggleButton *button, gpointer data)
467 {
468     GncItemEdit *item_edit = GNC_ITEM_EDIT(data);
469     gboolean show_popup;
470 
471     show_popup = gtk_toggle_button_get_active (button);
472     if (show_popup)
473     {
474         Table *table;
475         VirtualLocation virt_loc;
476 
477         table = item_edit->sheet->table;
478         virt_loc = table->current_cursor_loc;
479 
480         if (!gnc_table_confirm_change (table, virt_loc))
481         {
482             g_signal_handlers_block_matched
483                  (button, G_SIGNAL_MATCH_DATA,
484                  0, 0, NULL, NULL, data);
485 
486             gtk_toggle_button_set_active (button, FALSE);
487 
488             g_signal_handlers_unblock_matched
489                 (button, G_SIGNAL_MATCH_DATA,
490                  0, 0, NULL, NULL, data);
491 
492             return;
493         }
494     }
495 
496     item_edit->show_popup = show_popup;
497 
498     if (!item_edit->show_popup)
499         gnc_item_edit_hide_popup (item_edit);
500 
501     gnc_item_edit_configure (item_edit);
502 }
503 
504 
505 static void
block_toggle_signals(GncItemEdit * item_edit)506 block_toggle_signals (GncItemEdit *item_edit)
507 {
508     GObject *obj;
509 
510     if (!item_edit->popup_toggle.signals_connected)
511         return;
512 
513     obj = G_OBJECT(item_edit->popup_toggle.tbutton);
514 
515     g_signal_handlers_block_matched (obj, G_SIGNAL_MATCH_DATA,
516                                      0, 0, NULL, NULL, item_edit);
517 }
518 
519 
520 static void
unblock_toggle_signals(GncItemEdit * item_edit)521 unblock_toggle_signals (GncItemEdit *item_edit)
522 {
523     GObject *obj;
524 
525     if (!item_edit->popup_toggle.signals_connected)
526         return;
527 
528     obj = G_OBJECT(item_edit->popup_toggle.tbutton);
529 
530     g_signal_handlers_unblock_matched (obj, G_SIGNAL_MATCH_DATA,
531                                        0, 0, NULL, NULL, item_edit);
532 }
533 
534 
535 static gboolean
draw_background_cb(GtkWidget * widget,cairo_t * cr,gpointer user_data)536 draw_background_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data)
537 {
538     GtkStyleContext *stylectxt = gtk_widget_get_style_context (widget);
539     GncItemEdit *item_edit = GNC_ITEM_EDIT(user_data);
540     gint width = gtk_widget_get_allocated_width (widget);
541     gint height = gtk_widget_get_allocated_height (widget);
542     guint32 color_type;
543 
544     gtk_style_context_save (stylectxt);
545 
546     // Get the color type and apply the css class
547     color_type = gnc_table_get_color (item_edit->sheet->table, item_edit->virt_loc, NULL);
548     gnucash_get_style_classes (item_edit->sheet, stylectxt, color_type, FALSE);
549 
550     gtk_render_background (stylectxt, cr, 0, 1, width, height - 2);
551 
552     gtk_style_context_restore (stylectxt);
553     return FALSE;
554 }
555 
556 /* The signal is emitted at the beginning of gtk_entry_preedit_changed_cb which
557  * proceeds to set its private members preedit_length = strlen(preedit) and
558  * preeditc_cursor = g_utf8_strlen(preedit, -1), then calls gtk_entry_recompute
559  * which in turn queues a redraw.
560  */
561 static void
preedit_changed_cb(GtkEntry * entry,gchar * preedit,GncItemEdit * item_edit)562 preedit_changed_cb (GtkEntry* entry, gchar *preedit, GncItemEdit* item_edit)
563 {
564     int pos, bound;
565     item_edit->preedit_length = g_utf8_strlen (preedit, -1); // Note codepoints not bytes
566     DEBUG("%s %lu", preedit, item_edit->preedit_length);
567 }
568 
569 
570 static gboolean
draw_text_cursor_cb(GtkWidget * widget,cairo_t * cr,gpointer user_data)571 draw_text_cursor_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data)
572 {
573     GncItemEdit *item_edit = GNC_ITEM_EDIT(user_data);
574     GtkEditable *editable = GTK_EDITABLE(widget);
575     GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(widget));
576     GtkStateFlags flags = gtk_widget_get_state_flags (GTK_WIDGET(widget));
577     gint height = gtk_widget_get_allocated_height (widget);
578     PangoLayout *layout = gtk_entry_get_layout (GTK_ENTRY(widget));
579     const char *pango_text = pango_layout_get_text (layout);
580     GdkRGBA *fg_color;
581     GdkRGBA color;
582     gint x_offset;
583     gint cursor_x = 0;
584 
585     // Get the layout x offset
586     gtk_entry_get_layout_offsets (GTK_ENTRY(widget), &x_offset, NULL);
587 
588     // Get the foreground color
589     gdk_rgba_parse (&color, "black");
590     gtk_style_context_get_color (stylectxt, flags, &color);
591     fg_color = &color;
592 
593 
594     if (pango_text && *pango_text)
595     {
596         PangoRectangle strong_pos;
597         glong text_len = g_utf8_strlen (pango_text, -1);
598         gint cursor_pos =
599             gtk_editable_get_position (editable) + item_edit->preedit_length;
600         gint cursor_byte_pos = cursor_pos < text_len ?
601             g_utf8_offset_to_pointer (pango_text, cursor_pos) - pango_text :
602             strlen (pango_text);
603         DEBUG("Cursor: %d, byte offset %d, text byte len %zu", cursor_pos,
604                cursor_byte_pos, strlen (pango_text));
605         pango_layout_get_cursor_pos (layout, cursor_byte_pos,
606                                      &strong_pos, NULL);
607         cursor_x = x_offset + PANGO_PIXELS (strong_pos.x);
608     }
609     else
610     {
611         DEBUG("No text, cursor at %d.", x_offset);
612         cursor_x = x_offset;
613     }
614     // Now draw a vertical line
615     cairo_set_source_rgb (cr, fg_color->red, fg_color->green, fg_color->blue);
616     cairo_set_line_width (cr, 1.0);
617 
618     cairo_move_to (cr, cursor_x + 0.5,
619                    gnc_item_edit_get_margin (item_edit, top) +
620                    gnc_item_edit_get_padding_border (item_edit, top));
621     cairo_rel_line_to (cr, 0,
622                        height - gnc_item_edit_get_margin (item_edit, top_bottom)
623                        - gnc_item_edit_get_padding_border (item_edit,
624                                                            top_bottom));
625 
626     cairo_stroke (cr);
627 
628     return FALSE;
629 }
630 
631 
632 static gboolean
draw_arrow_cb(GtkWidget * widget,cairo_t * cr,gpointer data)633 draw_arrow_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
634 {
635     GncItemEdit *item_edit = GNC_ITEM_EDIT(data);
636     GtkStyleContext *context = gtk_widget_get_style_context (widget);
637     gint width = gtk_widget_get_allocated_width (widget);
638     gint height = gtk_widget_get_allocated_height (widget);
639     gint size;
640 
641     // allow room for a border
642     gtk_render_background (context, cr, 2, 2, width - 4, height - 4);
643 
644     gtk_style_context_add_class (context, GTK_STYLE_CLASS_ARROW);
645 
646     size = MIN(width / 2, height / 2);
647 
648     if (item_edit->popup_toggle.arrow_down == 0)
649         gtk_render_arrow (context, cr, 0,
650                          (width - size)/2, (height - size)/2, size);
651     else
652         gtk_render_arrow (context, cr, G_PI,
653                          (width - size)/2, (height - size)/2, size);
654 
655     return FALSE;
656 }
657 
658 
659 static void
connect_popup_toggle_signals(GncItemEdit * item_edit)660 connect_popup_toggle_signals (GncItemEdit *item_edit)
661 {
662     GObject *object;
663 
664     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
665 
666     if (item_edit->popup_toggle.signals_connected)
667         return;
668 
669     object = G_OBJECT(item_edit->popup_toggle.tbutton);
670 
671     g_signal_connect (object, "toggled",
672                       G_CALLBACK(gnc_item_edit_popup_toggled),
673                       item_edit);
674 
675     g_signal_connect (object, "key_press_event",
676                       G_CALLBACK(key_press_popup_cb),
677                       item_edit);
678 
679     g_signal_connect_after (object, "draw",
680                             G_CALLBACK(draw_arrow_cb),
681                             item_edit);
682 
683     item_edit->popup_toggle.signals_connected = TRUE;
684 }
685 
686 
687 static void
disconnect_popup_toggle_signals(GncItemEdit * item_edit)688 disconnect_popup_toggle_signals (GncItemEdit *item_edit)
689 {
690     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
691 
692     if (!item_edit->popup_toggle.signals_connected)
693         return;
694 
695     g_signal_handlers_disconnect_matched (item_edit->popup_toggle.tbutton,
696         G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, item_edit);
697 
698     item_edit->popup_toggle.signals_connected = FALSE;
699 }
700 
701 /* Note that g_value_set_object() refs the object, as does
702  * g_object_get(). But g_object_get() only unrefs once when it disgorges
703  * the object, leaving an unbalanced ref, which leaks. So instead of
704  * using g_value_set_object(), use g_value_take_object() which doesn't
705  * ref the object when used in get_property().
706  */
707 static void
gnc_item_edit_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)708 gnc_item_edit_get_property (GObject *object,
709                             guint param_id,
710                             GValue *value,
711                             GParamSpec *pspec)
712 {
713     GncItemEdit *item_edit = GNC_ITEM_EDIT(object);
714 
715     switch (param_id)
716     {
717     case PROP_SHEET:
718         g_value_take_object (value, item_edit->sheet);
719         break;
720     default:
721         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
722         break;
723     }
724 }
725 
726 static void
gnc_item_edit_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)727 gnc_item_edit_set_property (GObject *object,
728                             guint param_id,
729                             const GValue *value,
730                             GParamSpec *pspec)
731 {
732     GncItemEdit *item_edit = GNC_ITEM_EDIT(object);
733     switch (param_id)
734     {
735     case PROP_SHEET:
736         item_edit->sheet = GNUCASH_SHEET(g_value_get_object (value));
737         break;
738     default:
739         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
740         break;
741     }
742 }
743 
744 static void
gnc_item_edit_get_preferred_width(GtkWidget * widget,gint * minimal_width,gint * natural_width)745 gnc_item_edit_get_preferred_width (GtkWidget *widget,
746                                    gint *minimal_width,
747                                    gint *natural_width)
748 {
749     gint x, y, w = 1, h;
750     gnc_item_edit_get_pixel_coords (GNC_ITEM_EDIT(widget), &x, &y, &w, &h);
751     *minimal_width = *natural_width = w - 1;
752 }
753 
754 
755 static void
gnc_item_edit_get_preferred_height(GtkWidget * widget,gint * minimal_width,gint * natural_width)756 gnc_item_edit_get_preferred_height (GtkWidget *widget,
757                                     gint *minimal_width,
758                                     gint *natural_width)
759 {
760     gint x, y, w, h = 1;
761     gnc_item_edit_get_pixel_coords (GNC_ITEM_EDIT(widget), &x, &y, &w, &h);
762     *minimal_width = *natural_width = h - 1;
763 }
764 
765 /*
766  * GncItemEdit class initialization
767  */
768 static void
gnc_item_edit_class_init(GncItemEditClass * gnc_item_edit_class)769 gnc_item_edit_class_init (GncItemEditClass *gnc_item_edit_class)
770 {
771     GObjectClass  *object_class;
772     GtkWidgetClass *widget_class;
773 
774     gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(gnc_item_edit_class), "gnc-id-cursor");
775 
776     gnc_item_edit_parent_class = g_type_class_peek_parent (gnc_item_edit_class);
777 
778     object_class = G_OBJECT_CLASS(gnc_item_edit_class);
779     widget_class = GTK_WIDGET_CLASS(gnc_item_edit_class);
780 
781     object_class->get_property = gnc_item_edit_get_property;
782     object_class->set_property = gnc_item_edit_set_property;
783 
784     g_object_class_install_property (object_class,
785                                      PROP_SHEET,
786                                      g_param_spec_object ("sheet",
787                                              "Sheet Value",
788                                              "Sheet Value",
789                                              GNUCASH_TYPE_SHEET,
790                                              G_PARAM_READWRITE));
791 
792     /* GtkWidget method overrides */
793     widget_class->get_preferred_width = gnc_item_edit_get_preferred_width;
794     widget_class->get_preferred_height = gnc_item_edit_get_preferred_height;
795 }
796 
797 /* FIXME: This way of initializing GObjects is obsolete. We should be
798  * using G_DECLARE_FINAL_TYPE instead of rolling _get_type by hand.
799  */
800 GType
gnc_item_edit_get_type(void)801 gnc_item_edit_get_type (void)
802 {
803     static GType gnc_item_edit_type = 0;
804 
805     if (!gnc_item_edit_type)
806     {
807         static const GTypeInfo gnc_item_edit_info =
808         {
809             sizeof (GncItemEditClass),
810             NULL,
811             NULL,
812             (GClassInitFunc) gnc_item_edit_class_init,
813             NULL,
814             NULL,
815             sizeof (GncItemEdit),
816             0, /* n_preallocs */
817             (GInstanceInitFunc) gnc_item_edit_init,
818             NULL,
819         };
820 
821         gnc_item_edit_type =
822             g_type_register_static (GTK_TYPE_BOX,
823                                     "GncItemEdit",
824                                     &gnc_item_edit_info, 0);
825     }
826 
827     return gnc_item_edit_type;
828 }
829 
830 gint
gnc_item_edit_get_margin(GncItemEdit * item_edit,Sides side)831 gnc_item_edit_get_margin (GncItemEdit *item_edit, Sides side)
832 {
833     switch (side)
834     {
835     case left:
836         return item_edit->margin.left;
837     case right:
838         return item_edit->margin.right;
839     case top:
840         return item_edit->margin.top;
841     case bottom:
842         return item_edit->margin.bottom;
843     case left_right:
844         return item_edit->margin.left + item_edit->margin.right;
845     case top_bottom:
846         return item_edit->margin.top + item_edit->margin.bottom;
847     default:
848         return 2;
849     }
850 }
851 
852 gint
gnc_item_edit_get_padding_border(GncItemEdit * item_edit,Sides side)853 gnc_item_edit_get_padding_border (GncItemEdit *item_edit, Sides side)
854 {
855     switch (side)
856     {
857     case left:
858         return item_edit->padding.left + item_edit->border.left;
859     case right:
860         return item_edit->padding.right + item_edit->border.right;
861     case top:
862         return item_edit->padding.top + item_edit->border.top;
863     case bottom:
864         return item_edit->padding.bottom + item_edit->border.bottom;
865     case left_right:
866         return item_edit->padding.left + item_edit->border.left +
867                item_edit->padding.right + item_edit->border.right;
868     case top_bottom:
869         return item_edit->padding.top + item_edit->border.top +
870                item_edit->padding.bottom + item_edit->border.bottom;
871     default:
872         return 2;
873     }
874 }
875 
876 gint
gnc_item_edit_get_button_width(GncItemEdit * item_edit)877 gnc_item_edit_get_button_width (GncItemEdit *item_edit)
878 {
879     if (item_edit)
880     {
881         if (gtk_widget_is_visible (GTK_WIDGET(item_edit->popup_toggle.tbutton)))
882             return item_edit->button_width;
883         else
884         {
885             GtkStyleContext *context = gtk_widget_get_style_context (
886                                            GTK_WIDGET(item_edit->popup_toggle.tbutton));
887             GtkBorder border;
888 
889             gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
890             return MIN_BUTT_WIDTH + border.left + border.right;
891         }
892     }
893     return MIN_BUTT_WIDTH + 2; // add the default border
894 }
895 
896 static gboolean
button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer * pointer)897 button_press_cb (GtkWidget *widget, GdkEventButton *event, gpointer *pointer)
898 {
899     GncItemEdit *item_edit = GNC_ITEM_EDIT(pointer);
900     GnucashSheet *sheet = item_edit->sheet;
901 
902     /* Ignore double-clicks and triple-clicks */
903     if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
904     {
905         if (!item_edit->show_popup)
906         {
907             // This is a right click event so over ride entry menu and
908             // display main register popup menu if no item_edit popup
909             // is showing.
910             g_signal_emit_by_name (sheet->reg, "show_popup_menu");
911         }
912         return TRUE;
913     }
914     return FALSE;
915 }
916 
917 GtkWidget *
gnc_item_edit_new(GnucashSheet * sheet)918 gnc_item_edit_new (GnucashSheet *sheet)
919 {
920     GtkStyleContext *stylectxt;
921     GtkBorder padding;
922     GtkBorder margin;
923     GtkBorder border;
924     GncItemEdit *item_edit = g_object_new (GNC_TYPE_ITEM_EDIT,
925                                            "sheet", sheet,
926                                            "spacing",     0,
927                                            "homogeneous", FALSE,
928                                             NULL);
929     gtk_layout_put (GTK_LAYOUT(sheet), GTK_WIDGET(item_edit), 0, 0);
930 
931     /* Create the text entry */
932     item_edit->editor = gtk_entry_new ();
933     sheet->entry = item_edit->editor;
934     gtk_entry_set_width_chars (GTK_ENTRY(item_edit->editor), 1);
935     gtk_box_pack_start (GTK_BOX(item_edit), item_edit->editor, TRUE, TRUE, 0);
936 
937     // Get the CSS space settings for the entry
938     stylectxt = gtk_widget_get_style_context (GTK_WIDGET(item_edit->editor));
939     gtk_style_context_add_class (stylectxt, "gnc-class-register-foreground");
940     gtk_style_context_get_padding (stylectxt, GTK_STATE_FLAG_NORMAL, &padding);
941     gtk_style_context_get_margin (stylectxt, GTK_STATE_FLAG_NORMAL, &margin);
942     gtk_style_context_get_border (stylectxt, GTK_STATE_FLAG_NORMAL, &border);
943 
944     item_edit->padding = padding;
945     item_edit->margin = margin;
946     item_edit->border = border;
947 
948     // Make sure the Entry can not have focus and no frame
949     gtk_widget_set_can_focus (GTK_WIDGET(item_edit->editor), FALSE);
950     gtk_entry_set_has_frame (GTK_ENTRY(item_edit->editor), FALSE);
951 
952     // Connect to the draw signal so we can draw a cursor
953     g_signal_connect_after (item_edit->editor, "draw",
954                             G_CALLBACK(draw_text_cursor_cb), item_edit);
955 
956     g_signal_connect (item_edit->editor, "preedit-changed",
957                       G_CALLBACK(preedit_changed_cb), item_edit);
958 
959     // Fill in the background so the underlying sheet cell can not be seen
960     g_signal_connect (item_edit, "draw",
961                       G_CALLBACK(draw_background_cb), item_edit);
962 
963     // This call back intercepts the mouse button event so the main
964     // register popup menu can be displayed instead of the entry one.
965     g_signal_connect (item_edit->editor, "button-press-event",
966                       G_CALLBACK(button_press_cb), item_edit);
967 
968     /* Create the popup button
969        It will only be displayed when the cell being edited provides
970        a popup item (like a calendar or account list) */
971     item_edit->popup_toggle.tbutton = gnc_item_edit_tb_new (sheet);
972     gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON(item_edit->popup_toggle.tbutton), FALSE);
973 
974     /* Wrap the popup button in an event box to give it its own gdkwindow.
975      * Without one the button would disappear behind the grid object. */
976     item_edit->popup_toggle.ebox = gtk_event_box_new ();
977     g_object_ref (item_edit->popup_toggle.ebox);
978     gtk_container_add (GTK_CONTAINER(item_edit->popup_toggle.ebox),
979                        item_edit->popup_toggle.tbutton);
980 
981     // This call back intercepts the right mouse button event to stop the
982     // gnucash_sheet_button_press_event from running.
983     g_signal_connect (item_edit->popup_toggle.ebox, "button-press-event",
984                       G_CALLBACK(tb_button_press_cb), NULL);
985 
986     gtk_box_pack_start (GTK_BOX(item_edit), item_edit->popup_toggle.ebox, FALSE, FALSE, 0);
987     gtk_widget_show_all (GTK_WIDGET(item_edit));
988     g_signal_connect (G_OBJECT(item_edit), "destroy",
989                       G_CALLBACK(gnc_item_edit_destroying), NULL);
990     return GTK_WIDGET(item_edit);
991 }
992 
993 static void
gnc_item_edit_destroying(GtkWidget * item_edit,gpointer data)994 gnc_item_edit_destroying (GtkWidget *item_edit, gpointer data)
995 {
996     if (GNC_ITEM_EDIT(item_edit)->popup_height_signal_id > 0)
997         g_signal_handler_disconnect (GNC_ITEM_EDIT(item_edit)->popup_item,
998                                      GNC_ITEM_EDIT(item_edit)->popup_height_signal_id);
999 
1000     while (g_idle_remove_by_data ((gpointer)item_edit))
1001         continue;
1002 }
1003 
1004 static void
check_popup_height_is_true(GtkWidget * widget,GdkRectangle * allocation,gpointer user_data)1005 check_popup_height_is_true (GtkWidget    *widget,
1006                             GdkRectangle *allocation,
1007                             gpointer user_data)
1008 {
1009     GncItemEdit *item_edit = GNC_ITEM_EDIT(user_data);
1010 
1011     // if a larger font is specified in css for the sheet, the popup returned height value
1012     // on first pop does not reflect the true height but the minimum height so just to be
1013     // sure check this value against the allocated one.
1014     if (allocation->height != item_edit->popup_returned_height)
1015     {
1016         gtk_container_remove (GTK_CONTAINER(item_edit->sheet), item_edit->popup_item);
1017 
1018         g_idle_add_full (G_PRIORITY_HIGH_IDLE,
1019                         (GSourceFunc)gnc_item_edit_update, item_edit, NULL);
1020     }
1021 }
1022 
1023 void
gnc_item_edit_show_popup(GncItemEdit * item_edit)1024 gnc_item_edit_show_popup (GncItemEdit *item_edit)
1025 {
1026     GtkToggleButton *toggle;
1027     GtkAdjustment *vadj, *hadj;
1028     GtkAllocation alloc;
1029     GnucashSheet *sheet;
1030     gint x = 0, y = 0, w = 0, h = 0;
1031     gint y_offset, x_offset;
1032     gint popup_x, popup_y;
1033     gint popup_w = -1, popup_h = -1;
1034     gint popup_max_width, popup_max_height;
1035     gint view_width, view_height;
1036     gint down_height, up_height;
1037     gint sheet_width;
1038 
1039     g_return_if_fail (item_edit != NULL);
1040     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
1041 
1042     if (!item_edit->is_popup)
1043         return;
1044 
1045     sheet = item_edit->sheet;
1046 
1047     sheet_width = sheet->width;
1048 
1049     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
1050     view_height = alloc.height;
1051 
1052     vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
1053     hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE(sheet));
1054 
1055     y_offset = gtk_adjustment_get_value (vadj);
1056     x_offset = gtk_adjustment_get_value (hadj);
1057     gnc_item_edit_get_pixel_coords (item_edit, &x, &y, &w, &h);
1058 
1059     popup_x = x;
1060 
1061     up_height = y - y_offset;
1062     down_height = view_height - (up_height + h);
1063 
1064     popup_max_height = MAX(up_height, down_height);
1065     popup_max_width = sheet_width - popup_x + x_offset; // always pops to the right
1066 
1067     if (item_edit->popup_get_height)
1068         popup_h = item_edit->popup_get_height
1069                        (item_edit->popup_item, popup_max_height, h,
1070                         item_edit->popup_user_data);
1071 
1072     if (item_edit->popup_autosize)
1073         popup_w =
1074             item_edit->popup_autosize (item_edit->popup_item,
1075                                        popup_max_width,
1076                                        item_edit->popup_user_data);
1077     else
1078         popup_w = 0;
1079 
1080     // Adjust the popup_y point based on popping above or below
1081     if (up_height > down_height)
1082         popup_y = y - popup_h - 1;
1083     else
1084         popup_y = y + h;
1085 
1086     if (!gtk_widget_get_parent (item_edit->popup_item))
1087         gtk_layout_put (GTK_LAYOUT(sheet), item_edit->popup_item, popup_x, popup_y);
1088 
1089     // Lets check popup height is the true height
1090     item_edit->popup_returned_height = popup_h;
1091 
1092     if (popup_h == popup_max_height)
1093         gtk_widget_set_size_request (item_edit->popup_item, popup_w - 1, popup_h);
1094     else
1095         gtk_widget_set_size_request (item_edit->popup_item, popup_w - 1, -1);
1096 
1097     toggle = GTK_TOGGLE_BUTTON(item_edit->popup_toggle.tbutton);
1098 
1099     if (!gtk_toggle_button_get_active (toggle))
1100     {
1101         block_toggle_signals (item_edit);
1102         gtk_toggle_button_set_active (toggle, TRUE);
1103         unblock_toggle_signals (item_edit);
1104     }
1105 
1106     // set the popup arrow direction up
1107     item_edit->popup_toggle.arrow_down = FALSE;
1108     item_edit->show_popup = TRUE;
1109 
1110     if (item_edit->popup_set_focus)
1111         item_edit->popup_set_focus (item_edit->popup_item,
1112                                     item_edit->popup_user_data);
1113 
1114     if (item_edit->popup_post_show)
1115         item_edit->popup_post_show (item_edit->popup_item,
1116                                     item_edit->popup_user_data);
1117 
1118     if (item_edit->popup_get_width)
1119     {
1120         int popup_width;
1121 
1122         popup_width = item_edit->popup_get_width
1123                       (item_edit->popup_item,
1124                        item_edit->popup_user_data);
1125 
1126         if (popup_width > popup_w)
1127             popup_width = popup_w;
1128 
1129         if (popup_width > popup_max_width)
1130         {
1131             popup_x -= popup_width - popup_max_width;
1132             popup_x = MAX(0, popup_x);
1133         }
1134         else
1135             popup_x = x;
1136 
1137         gtk_layout_move (GTK_LAYOUT(sheet), item_edit->popup_item, popup_x, popup_y);
1138     }
1139 }
1140 
1141 
1142 void
gnc_item_edit_hide_popup(GncItemEdit * item_edit)1143 gnc_item_edit_hide_popup (GncItemEdit *item_edit)
1144 {
1145     g_return_if_fail (item_edit != NULL);
1146     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
1147 
1148     if (!item_edit->is_popup)
1149         return;
1150 
1151     if (gtk_widget_get_parent (GTK_WIDGET(item_edit->popup_item)) != GTK_WIDGET(item_edit->sheet))
1152         return;
1153 
1154     gtk_container_remove (GTK_CONTAINER(item_edit->sheet), item_edit->popup_item);
1155 
1156     // set the popup arrow direction down
1157     item_edit->popup_toggle.arrow_down = TRUE;
1158 
1159     gtk_toggle_button_set_active
1160         (GTK_TOGGLE_BUTTON(item_edit->popup_toggle.tbutton), FALSE);
1161 
1162     gtk_widget_grab_focus (GTK_WIDGET(item_edit->sheet));
1163 }
1164 
1165 
1166 void
gnc_item_edit_set_popup(GncItemEdit * item_edit,GtkWidget * popup_item,PopupGetHeight popup_get_height,PopupAutosize popup_autosize,PopupSetFocus popup_set_focus,PopupPostShow popup_post_show,PopupGetWidth popup_get_width,gpointer popup_user_data)1167 gnc_item_edit_set_popup (GncItemEdit    *item_edit,
1168                          GtkWidget      *popup_item,
1169                          PopupGetHeight  popup_get_height,
1170                          PopupAutosize   popup_autosize,
1171                          PopupSetFocus   popup_set_focus,
1172                          PopupPostShow   popup_post_show,
1173                          PopupGetWidth   popup_get_width,
1174                          gpointer        popup_user_data)
1175 {
1176     g_return_if_fail (GNC_IS_ITEM_EDIT(item_edit));
1177 
1178     if (item_edit->is_popup)
1179         gnc_item_edit_hide_popup (item_edit);
1180 
1181     /* setup size-allocate callback for popup_item height, done here as
1182        item_edit is constant and popup_item changes per cell */
1183     if (popup_item)
1184     {
1185         item_edit->popup_height_signal_id = g_signal_connect_after (
1186                                             popup_item, "size-allocate",
1187                                             G_CALLBACK(check_popup_height_is_true),
1188                                             item_edit);
1189     }
1190     else
1191     {
1192         if (GNC_ITEM_EDIT(item_edit)->popup_height_signal_id > 0)
1193         {
1194             g_signal_handler_disconnect (item_edit->popup_item, item_edit->popup_height_signal_id);
1195             item_edit->popup_height_signal_id = 0;
1196         }
1197     }
1198 
1199     item_edit->is_popup = popup_item != NULL;
1200 
1201     item_edit->popup_item       = popup_item;
1202     item_edit->popup_get_height = popup_get_height;
1203     item_edit->popup_autosize   = popup_autosize;
1204     item_edit->popup_set_focus  = popup_set_focus;
1205     item_edit->popup_post_show  = popup_post_show;
1206     item_edit->popup_get_width  = popup_get_width;
1207     item_edit->popup_user_data  = popup_user_data;
1208 
1209     if (item_edit->is_popup)
1210         connect_popup_toggle_signals (item_edit);
1211     else
1212     {
1213         disconnect_popup_toggle_signals (item_edit);
1214 
1215         gnc_item_edit_hide_popup (item_edit);
1216         gtk_widget_hide (item_edit->popup_toggle.ebox);
1217     }
1218 }
1219 
1220 gboolean
gnc_item_edit_get_has_selection(GncItemEdit * item_edit)1221 gnc_item_edit_get_has_selection (GncItemEdit *item_edit)
1222 {
1223     GtkEditable *editable;
1224 
1225     g_return_val_if_fail ((item_edit != NULL), FALSE);
1226     g_return_val_if_fail (GNC_IS_ITEM_EDIT(item_edit), FALSE);
1227 
1228     editable = GTK_EDITABLE(item_edit->editor);
1229     return gtk_editable_get_selection_bounds (editable, NULL, NULL);
1230 }
1231 
1232