GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-cell-view.c
1/*************************************************************************
2 * The following code implements a text view in a custom GtkCellRenderer.
3 *
4 * Copyright (C) 2020 Robert Fewell
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 *************************************************************************/
21#include <config.h>
22
23#include <gtk/gtk.h>
24#include <glib/gi18n.h>
25#include <gdk/gdkkeysyms.h>
26#include <string.h>
27
28#include "gnc-cell-view.h"
29
30static void gnc_cell_view_editable_init (GtkCellEditableIface *iface);
31
32static void gnc_cell_view_set_property (GObject *object,
33 guint param_id,
34 const GValue *value,
35 GParamSpec *pspec);
36
37static void gnc_cell_view_get_property (GObject *object,
38 guint param_id,
39 GValue *value,
40 GParamSpec *pspec);
41
42enum {
43 PROP_0,
44 PROP_EDITING_CANCELED,
45};
46
47G_DEFINE_TYPE_WITH_CODE (GncCellView, gnc_cell_view, GTK_TYPE_EVENT_BOX,
48 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,
49 gnc_cell_view_editable_init))
50
51static void
52gnc_cell_view_dispose (GObject *gobject)
53{
54 if (GNC_CELL_VIEW(gobject)->tooltip_id > 0)
55 {
56 g_source_remove (GNC_CELL_VIEW(gobject)->tooltip_id);
57 GNC_CELL_VIEW(gobject)->tooltip_id = 0;
58 }
59 G_OBJECT_CLASS (gnc_cell_view_parent_class)->dispose (gobject);
60}
61
62static void
63gnc_cell_view_init (GncCellView *cv)
64{
65 cv->editing_canceled = FALSE;
66
67 cv->text_view = g_object_new (GTK_TYPE_TEXT_VIEW, "accepts-tab", FALSE, NULL);
68 cv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(cv->text_view));
69
70 gtk_text_view_set_left_margin (GTK_TEXT_VIEW(cv->text_view), 2);
71 gtk_text_view_set_right_margin (GTK_TEXT_VIEW(cv->text_view), 2);
72
73 gtk_widget_set_tooltip_text (GTK_WIDGET(cv->text_view),
74 _("Use Shift combined with Return or Keypad Enter to finish editing"));
75
76 gtk_container_add (GTK_CONTAINER(cv), GTK_WIDGET(cv->text_view));
77 gtk_widget_show (cv->text_view);
78
79 cv->focus_out_id = 0;
80 cv->populate_popup_id = 0;
81 cv->tooltip_id = 0;
82
83 gtk_widget_set_can_focus (GTK_WIDGET(cv->text_view), TRUE);
84 gtk_widget_add_events (GTK_WIDGET(cv), GDK_KEY_PRESS_MASK);
85 gtk_widget_add_events (GTK_WIDGET(cv), GDK_KEY_RELEASE_MASK);
86}
87
88static void
89gnc_cell_view_class_init (GncCellViewClass *klass)
90{
91 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
92
93 gobject_class->dispose = gnc_cell_view_dispose;
94
95 gobject_class->set_property = gnc_cell_view_set_property;
96 gobject_class->get_property = gnc_cell_view_get_property;
97
98 g_object_class_override_property (gobject_class,
99 PROP_EDITING_CANCELED,
100 "editing-canceled");
101}
102
103static void
104gnc_cell_view_set_property (GObject *object,
105 guint param_id,
106 const GValue *value,
107 GParamSpec *pspec)
108{
109 GncCellView *cv = GNC_CELL_VIEW(object);
110
111 switch (param_id)
112 {
113 case PROP_EDITING_CANCELED:
114 cv->editing_canceled = g_value_get_boolean (value);
115 break;
116
117 default:
118 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
119 break;
120 }
121}
122
123static void
124gnc_cell_view_get_property (GObject *object,
125 guint param_id,
126 GValue *value,
127 GParamSpec *pspec)
128{
129 GncCellView *cv = GNC_CELL_VIEW(object);
130
131 switch (param_id)
132 {
133 case PROP_EDITING_CANCELED:
134 g_value_set_boolean (value, cv->editing_canceled);
135 break;
136
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
139 break;
140 }
141}
142
143static gboolean
144gtk_cell_editable_key_press_event (GtkTextView *text_view,
145 GdkEventKey *key_event,
146 GncCellView *cv)
147{
148 if (key_event->keyval == GDK_KEY_Escape)
149 {
150 cv->editing_canceled = TRUE;
151
152 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE(cv));
153 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE(cv));
154 return TRUE;
155 }
156
157 if ((key_event->keyval == GDK_KEY_Return || key_event->keyval == GDK_KEY_KP_Enter)
158 && (key_event->state & GDK_SHIFT_MASK))
159 {
160 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE(cv));
161 return TRUE;
162 }
163 return FALSE;
164}
165
166static void
167gcv_popup_unmap (GtkMenu *menu, GncCellView *cv)
168{
169 cv->in_popup_menu = FALSE;
170}
171
172static void
173gcv_populate_popup (GtkTextView *text_view,
174 GtkWidget *popup,
175 GncCellView *cv)
176{
177 cv->in_popup_menu = TRUE;
178 g_signal_connect (popup, "unmap",
179 G_CALLBACK (gcv_popup_unmap), cv);
180}
181
182static gboolean
183gcv_focus_out_event (GtkWidget *widget, GdkEvent *event, GncCellView *cv)
184{
185 if (cv->in_popup_menu)
186 return FALSE;
187
188 cv->editing_canceled = TRUE;
189
190 if (cv->focus_out_id > 0)
191 {
192 g_signal_handler_disconnect (cv->text_view, cv->focus_out_id);
193 cv->focus_out_id = 0;
194 }
195 if (cv->populate_popup_id > 0)
196 {
197 g_signal_handler_disconnect (cv->text_view, cv->populate_popup_id);
198 cv->populate_popup_id = 0;
199 }
200 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE(cv));
201 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE(cv));
202
203 return FALSE;
204}
205
206static gboolean
207gcv_remove_tooltip (GncCellView *cv)
208{
209 if (cv->tooltip_id > 0)
210 {
211 gtk_widget_set_tooltip_text (GTK_WIDGET(cv->text_view), NULL);
212 cv->tooltip_id = 0;
213 }
214 return FALSE;
215}
216
217static void
218gcv_start_editing (GtkCellEditable *cell_editable,
219 GdkEvent *event)
220{
221 GncCellView *cv = GNC_CELL_VIEW(cell_editable);
222 GtkTextIter siter, eiter;
223
224 // Remove the text_view tooltip after 5secs to stop it recurring
225 cv->tooltip_id = g_timeout_add (5000, (GSourceFunc) gcv_remove_tooltip, cv);
226
227 gtk_text_buffer_get_bounds (cv->buffer, &siter, &eiter);
228 gtk_text_buffer_select_range (cv->buffer, &eiter, &siter);
229
230 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW(cv->text_view), TRUE);
231
232 gtk_widget_grab_focus (GTK_WIDGET(cv->text_view));
233
234 g_signal_connect (G_OBJECT(cv->text_view), "key_press_event",
235 G_CALLBACK(gtk_cell_editable_key_press_event), cv);
236
237 cv->focus_out_id = g_signal_connect (G_OBJECT(cv->text_view),
238 "focus-out-event",
239 G_CALLBACK(gcv_focus_out_event), cv);
240
241 cv->populate_popup_id = g_signal_connect (G_OBJECT(cv->text_view),
242 "populate-popup",
243 G_CALLBACK(gcv_populate_popup),
244 cv);
245}
246
247static void
248gnc_cell_view_editable_init (GtkCellEditableIface *iface)
249{
250 iface->start_editing = gcv_start_editing;
251}
252
253void
254gnc_cell_view_set_text (GncCellView *cv, const gchar *text)
255{
256 g_return_if_fail (GNC_IS_CELL_VIEW(cv));
257
258 gtk_text_buffer_set_text (cv->buffer,
259 text ? text : "",
260 -1);
261}
262
263gchar *
264gnc_cell_view_get_text (GncCellView *cv)
265{
266 GtkTextIter siter, eiter;
267
268 g_return_val_if_fail (GNC_IS_CELL_VIEW(cv), NULL);
269
270 gtk_text_buffer_get_bounds (cv->buffer, &siter, &eiter);
271
272 return gtk_text_buffer_get_text (cv->buffer, &siter, &eiter, TRUE);
273}