GnuCash  5.6-150-g038405b370+
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 
30 static void gnc_cell_view_editable_init (GtkCellEditableIface *iface);
31 
32 static void gnc_cell_view_set_property (GObject *object,
33  guint param_id,
34  const GValue *value,
35  GParamSpec *pspec);
36 
37 static void gnc_cell_view_get_property (GObject *object,
38  guint param_id,
39  GValue *value,
40  GParamSpec *pspec);
41 
42 enum {
43  PROP_0,
44  PROP_EDITING_CANCELED,
45 };
46 
47 G_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 
51 static void
52 gnc_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 
62 static void
63 gnc_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 
88 static void
89 gnc_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 
103 static void
104 gnc_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 
123 static void
124 gnc_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 
143 static gboolean
144 gtk_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 
166 static void
167 gcv_popup_unmap (GtkMenu *menu, GncCellView *cv)
168 {
169  cv->in_popup_menu = FALSE;
170 }
171 
172 static void
173 gcv_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 
182 static gboolean
183 gcv_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 
206 static gboolean
207 gcv_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 
217 static void
218 gcv_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 
247 static void
248 gnc_cell_view_editable_init (GtkCellEditableIface *iface)
249 {
250  iface->start_editing = gcv_start_editing;
251 }
252 
253 void
254 gnc_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 
263 gchar *
264 gnc_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 }
G_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_APPLICATION_WINDOW, G_IMPLEMENT_INTERFACE(GNC_TYPE_WINDOW, gnc_window_main_window_init)) static guint main_window_signals[LAST_SIGNAL]
A holding place for all the signals generated by the main window code.