GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-cell-renderer-popup.c
1 /*************************************************************************
2  * The following code has come from Planner. This code implements a
3  * GtkCalendar in a custom GtkCellEditable popup from GtkCellRenderer.
4  *
5  * These files have been renamed and changed to remove code not required
6  * and to remove a dependency on libplanner.
7  *
8  * Copyright (C) 2012 Robert Fewell
9  *
10  * Copyright (C) 2001-2002 CodeFactory AB
11  * Copyright (C) 2001-2002 Richard Hult <[email protected]>
12  * Copyright (C) 2001-2002 Mikael Hallendal <[email protected]>
13  *
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22  * General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public
25  * License along with this program; if not, write to the
26  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27  * Boston, MA 02111-1307, USA.
28  *************************************************************************/
29 #include "config.h"
30 
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
34 
35 #include "gnc-cell-renderer-popup.h"
36 #include "gnc-cell-renderer-popup-entry.h"
37 
38 enum {
39  SHOW_POPUP,
40  HIDE_POPUP,
41  LAST_SIGNAL
42 };
43 
44 static void gcrp_init (GncCellRendererPopup *popup);
45 static void gcrp_class_init (GncCellRendererPopupClass *klass);
46 
47 static GtkCellEditable *
48 gcrp_start_editing (GtkCellRenderer *cell,
49  GdkEvent *event,
50  GtkWidget *widget,
51  const gchar *path,
52  GdkRectangle *background_area,
53  GdkRectangle *cell_area,
54  GtkCellRendererState flags);
55 static void gcrp_show_popup (GncCellRendererPopup *cell,
56  const gchar *path,
57  gint x1,
58  gint y1,
59  gint x2,
60  gint y2);
61 static void gcrp_hide_popup (GncCellRendererPopup *cell);
62 static void gcrp_get_size (GtkCellRenderer *cell,
63  GtkWidget *widget,
64  GdkRectangle *cell_area,
65  gint *x_offset,
66  gint *y_offset,
67  gint *width,
68  gint *height);
69 static void gcrp_style_set (GtkWidget *widget,
70  GtkStyle *old_style,
71  GncCellRendererPopup *popup);
72 static gboolean gcrp_key_press_event (GtkWidget *popup_window,
73  GdkEventKey *event,
74  GncCellRendererPopup *cell);
75 static gboolean gcrp_button_press_event (GtkWidget *widget,
76  GdkEventButton *event,
77  GncCellRendererPopup *popup);
78 
79 
80 void gnc_marshal_VOID__STRING_INT_INT_INT_INT (GClosure *closure,
81  GValue *return_value,
82  guint n_param_values,
83  const GValue *param_values,
84  gpointer invocation_hint,
85  gpointer marshal_data);
86 
87 
88 static GtkCellRendererTextClass *parent_class;
89 static guint signals[LAST_SIGNAL];
90 
91 #define GNC_CELL_RENDERER_POPUP_PATH "gnc-cell-renderer-popup-path"
92 
93 GType
94 gnc_cell_renderer_popup_get_type (void)
95 {
96  static GType cell_text_type = 0;
97 
98  if (!cell_text_type) {
99  static const GTypeInfo cell_text_info = {
100  sizeof (GncCellRendererPopupClass),
101  NULL, /* base_init */
102  NULL, /* base_finalize */
103  (GClassInitFunc) gcrp_class_init,
104  NULL, /* class_finalize */
105  NULL, /* class_data */
106  sizeof (GncCellRendererPopup),
107  0, /* n_preallocs */
108  (GInstanceInitFunc) gcrp_init,
109  };
110 
111  cell_text_type = g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT,
112  "GncCellRendererPopup",
113  &cell_text_info,
114  0);
115  }
116 
117  return cell_text_type;
118 }
119 
120 static void
121 gcrp_init (GncCellRendererPopup *popup)
122 {
123  popup->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
124 
125  popup->button_width = -1;
126 
127  g_signal_connect (popup->popup_window,
128  "button-press-event",
129  G_CALLBACK (gcrp_button_press_event),
130  popup);
131 
132  g_signal_connect (popup->popup_window,
133  "key-press-event",
134  G_CALLBACK (gcrp_key_press_event),
135  popup);
136 
137  g_signal_connect (popup->popup_window,
138  "style-set",
139  G_CALLBACK (gcrp_style_set),
140  popup);
141 }
142 
143 static void
144 gcrp_class_init (GncCellRendererPopupClass *klass)
145 {
146  GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
147 
148  parent_class = GTK_CELL_RENDERER_TEXT_CLASS (g_type_class_peek_parent (klass));
149 
150  cell_class->start_editing = gcrp_start_editing;
151  cell_class->get_size = gcrp_get_size;
152 
153  klass->show_popup = gcrp_show_popup;
154  klass->hide_popup = gcrp_hide_popup;
155 
156  signals[SHOW_POPUP] = g_signal_new (
157  "show-popup",
158  G_TYPE_FROM_CLASS (klass),
159  G_SIGNAL_RUN_LAST,
160  G_STRUCT_OFFSET (GncCellRendererPopupClass, show_popup),
161  NULL, NULL,
162  gnc_marshal_VOID__STRING_INT_INT_INT_INT,
163  G_TYPE_NONE, 5,
164  G_TYPE_STRING,
165  G_TYPE_INT,
166  G_TYPE_INT,
167  G_TYPE_INT,
168  G_TYPE_INT);
169 
170  signals[HIDE_POPUP] = g_signal_new (
171  "hide-popup",
172  G_TYPE_FROM_CLASS (klass),
173  G_SIGNAL_RUN_LAST,
174  G_STRUCT_OFFSET (GncCellRendererPopupClass, hide_popup),
175  NULL, NULL,
176  g_cclosure_marshal_VOID__VOID,
177  G_TYPE_NONE, 0);
178 }
179 
180 static void
181 gcrp_editing_done (GtkCellEditable *editable,
182  GncCellRendererPopup *cell)
183 {
184  gchar *path;
185  const gchar *new_text;
186 
187  if (GNC_POPUP_ENTRY (editable)->editing_canceled ||
188  cell->editing_canceled) {
189  gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell), TRUE);
190  return;
191  }
192 
193  path = g_object_get_data (G_OBJECT (editable),
194  GNC_CELL_RENDERER_POPUP_PATH);
195  new_text = gnc_popup_entry_get_text (GNC_POPUP_ENTRY (editable));
196 
197  gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell), FALSE);
198 
199  g_signal_emit_by_name (cell,
200  "edited",
201  path,
202  new_text);
203 }
204 
205 static void
206 gcrp_style_set (GtkWidget *widget,
207  GtkStyle *old_style,
208  GncCellRendererPopup *popup)
209 {
210  /* Invalidate the cache. */
211  popup->button_width = -1;
212 }
213 
214 static gboolean
215 gcrp_grab_on_window (GdkWindow *window,
216  guint32 activate_time)
217 {
218  if ((gdk_pointer_grab (window, TRUE,
219  GDK_BUTTON_PRESS_MASK |
220  GDK_BUTTON_RELEASE_MASK |
221  GDK_POINTER_MOTION_MASK,
222  NULL, NULL, activate_time) == 0)) {
223  if (gdk_keyboard_grab (window, TRUE,
224  activate_time) == 0)
225  return TRUE;
226  else {
227  gdk_pointer_ungrab (activate_time);
228  return FALSE;
229  }
230  }
231 
232  return FALSE;
233 }
234 
235 static void
236 gcrp_show_popup (GncCellRendererPopup *cell,
237  const gchar *path,
238  gint x1,
239  gint y1,
240  gint x2,
241  gint y2)
242 {
243  GtkAllocation alloc;
244  gint x, y;
245  gint screen_height, screen_width;
246  gint button_height;
247 
248  cell->shown = TRUE;
249 
250  gtk_widget_realize (cell->popup_window);
251 
252  /* I'm not sure this is ok to do, but we need to show the window to be
253  * able to get the allocation right.
254  */
255  gtk_window_move (GTK_WINDOW (cell->popup_window), -500, -500);
256  gtk_widget_show (cell->popup_window);
257 
258  alloc = cell->popup_window->allocation;
259 
260  x = x2;
261  y = y2;
262 
263  button_height = y2 - y1;
264 
265  screen_height = gdk_screen_height () - y;
266  screen_width = gdk_screen_width ();
267 
268  /* Check if it fits in the available height. */
269  if (alloc.height > screen_height) {
270  /* It doesn't fit, so we see if we have the minimum space needed. */
271  if (alloc.height > screen_height && y - button_height > screen_height) {
272  /* We don't, so we show the popup above the cell
273  instead of below it. */
274  screen_height = y - button_height;
275  y -= (alloc.height + button_height);
276  if (y < 0) {
277  y = 0;
278  }
279  }
280  }
281 
282  /* We try to line it up with the right edge of the column, but we don't
283  * want it to go off the edges of the screen.
284  */
285  if (x > screen_width) {
286  x = screen_width;
287  }
288 
289  x -= alloc.width;
290  if (x < 0) {
291  x = 0;
292  }
293 
294  gtk_grab_add (cell->popup_window);
295 
296  gtk_window_move (GTK_WINDOW (cell->popup_window), x, y);
297  gtk_widget_show (cell->popup_window);
298 
299  gtk_widget_grab_focus (cell->focus_window);
300 
301  gcrp_grab_on_window (cell->popup_window->window,
302  gtk_get_current_event_time ());
303 }
304 
305 static void
306 gcrp_hide_popup (GncCellRendererPopup *cell)
307 {
308  gtk_grab_remove (cell->popup_window);
309  gtk_widget_hide (cell->popup_window);
310 
311  if (cell->editable) {
312  gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (cell->editable));
313  }
314 
315  /* This may look weird (the test), but the weak pointer will actually be
316  * nulled out for some cells, like the date cell.
317  */
318  if (cell->editable) {
319  gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (cell->editable));
320  }
321 
322  cell->shown = FALSE;
323  cell->editing_canceled = FALSE;
324 }
325 
326 static void
327 gcrp_arrow_clicked (GtkCellEditable *entry,
328  GncCellRendererPopup *cell)
329 {
330  GtkAllocation alloc;
331  gint x, y;
332  const gchar *path;
333 
334  if (cell->shown) {
335  cell->editing_canceled = TRUE;
336  gnc_cell_renderer_popup_hide (cell);
337  return;
338  }
339 
340  path = g_object_get_data (G_OBJECT (entry),
341  GNC_CELL_RENDERER_POPUP_PATH);
342 
343  /* Temporarily grab pointer and keyboard on a window we know exists; we
344  * do this so that the grab (with owner events == TRUE) affects
345  * events generated when the window is mapped, such as enter
346  * notify events on subwidgets. If the grab fails, bail out.
347  */
348  if (!gcrp_grab_on_window (GTK_WIDGET (entry)->window,
349  gtk_get_current_event_time ())) {
350  return;
351  }
352 
353  gtk_editable_select_region (GTK_EDITABLE (GNC_POPUP_ENTRY (entry)->entry), 0, 0);
354 
355  gdk_window_get_origin (GTK_WIDGET (entry)->window, &x, &y);
356 
357  alloc = GTK_WIDGET (entry)->allocation;
358 
359  g_signal_emit (cell, signals[SHOW_POPUP], 0,
360  path,
361  x,
362  y,
363  x + alloc.width,
364  y + alloc.height);
365 }
366 
367 static GtkCellEditable *
368 gcrp_start_editing (GtkCellRenderer *cell,
369  GdkEvent *event,
370  GtkWidget *widget,
371  const gchar *path,
372  GdkRectangle *background_area,
373  GdkRectangle *cell_area,
374  GtkCellRendererState flags)
375 {
376  GncCellRendererPopup *popup;
377  GtkWidget *editable;
378  gchar *text;
379 
380  popup = GNC_CELL_RENDERER_POPUP (cell);
381 
382  /* If the cell isn't editable we return NULL. */
383  if (GTK_CELL_RENDERER_TEXT (popup)->editable == FALSE) {
384  return NULL;
385  }
386 
387  editable = g_object_new (GNC_TYPE_POPUP_ENTRY, NULL);
388 
389  text = GTK_CELL_RENDERER_TEXT (cell)->text;
390  popup->cell_text = text;
391 
392  gnc_popup_entry_set_text (GNC_POPUP_ENTRY (editable), text ? text : "");
393 
394  g_object_set_data_full (G_OBJECT (editable),
395  GNC_CELL_RENDERER_POPUP_PATH,
396  g_strdup (path),
397  g_free);
398 
399  gtk_widget_show (editable);
400 
401  g_signal_connect (editable,
402  "editing-done",
403  G_CALLBACK (gcrp_editing_done),
404  popup);
405 
406  g_signal_connect (editable,
407  "arrow-clicked",
408  G_CALLBACK (gcrp_arrow_clicked),
409  popup);
410 
411  popup->editable = editable;
412 
413  g_object_add_weak_pointer (G_OBJECT (popup->editable),
414  (gpointer) &popup->editable);
415 
416  return GTK_CELL_EDITABLE (editable);
417 }
418 
419 GtkCellRenderer *
420 gnc_cell_renderer_popup_new (void)
421 {
422  return GTK_CELL_RENDERER (
423  g_object_new (gnc_cell_renderer_popup_get_type (), NULL));
424 }
425 
426 void
427 gnc_cell_renderer_popup_hide (GncCellRendererPopup *cell)
428 {
429  g_return_if_fail (GNC_IS_CELL_RENDERER_POPUP (cell));
430 
431  g_signal_emit (cell, signals[HIDE_POPUP], 0);
432 }
433 
434 static void
435 gcrp_get_size (GtkCellRenderer *cell,
436  GtkWidget *widget,
437  GdkRectangle *cell_area,
438  gint *x_offset,
439  gint *y_offset,
440  gint *width,
441  gint *height)
442 {
443  GncCellRendererPopup *popup;
444 
445  popup = GNC_CELL_RENDERER_POPUP (cell);
446 
447  if (GTK_CELL_RENDERER_CLASS (parent_class)->get_size) {
448  (* GTK_CELL_RENDERER_CLASS (parent_class)->get_size) (cell,
449  widget,
450  cell_area,
451  x_offset,
452  y_offset,
453  width,
454  height);
455  }
456 
457  /* We cache this because it takes really long to get the width. */
458  if (popup->button_width == -1) {
459  popup->button_width = gnc_popup_get_button_width ();
460  }
461 
462  *width += popup->button_width;
463 }
464 
465 static gboolean
466 gcrp_key_press_event (GtkWidget *popup_window,
467  GdkEventKey *event,
468  GncCellRendererPopup *cell)
469 {
470  if (event->keyval != GDK_Escape &&
471  event->keyval != GDK_Return &&
472  event->keyval != GDK_KP_Enter &&
473  event->keyval != GDK_ISO_Enter &&
474  event->keyval != GDK_3270_Enter) {
475  return FALSE;
476  }
477  if (event->keyval == GDK_Escape) {
478  cell->editing_canceled = TRUE;
479  } else {
480  cell->editing_canceled = FALSE;
481  }
482  gnc_cell_renderer_popup_hide (cell);
483 
484  return TRUE;
485 }
486 
487 static gboolean
488 gcrp_button_press_event (GtkWidget *widget,
489  GdkEventButton *event,
490  GncCellRendererPopup *popup)
491 {
492  GtkAllocation alloc;
493  gdouble x, y;
494  gint xoffset, yoffset;
495  gint x1, y1;
496  gint x2, y2;
497 
498  if (event->button != 1) {
499  return FALSE;
500  }
501 
502  /* If the event happened outside the popup, cancel editing.
503  */
504 
505  /*gdk_event_get_root_coords ((GdkEvent *) event, &x, &y);*/
506  x = event->x_root;
507  y = event->y_root;
508 
509  gdk_window_get_root_origin (widget->window,
510  &xoffset,
511  &yoffset);
512 
513  xoffset += widget->allocation.x;
514  yoffset += widget->allocation.y;
515 
516  alloc = popup->popup_window->allocation;
517  x1 = alloc.x + xoffset;
518  y1 = alloc.y + yoffset;
519  x2 = x1 + alloc.width;
520  y2 = y1 + alloc.height;
521 
522  if (x > x1 && x < x2 && y > y1 && y < y2) {
523  return FALSE;
524  }
525 
526  popup->editing_canceled = TRUE;
527  gnc_cell_renderer_popup_hide (popup);
528 
529  return FALSE;
530 }
531 
532 
533 #define g_marshal_value_peek_int(v) (v)->data[0].v_int
534 #define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
535 
536 void
537 gnc_marshal_VOID__STRING_INT_INT_INT_INT (GClosure *closure,
538  GValue *return_value G_GNUC_UNUSED,
539  guint n_param_values,
540  const GValue *param_values,
541  gpointer invocation_hint G_GNUC_UNUSED,
542  gpointer marshal_data)
543 {
544  typedef void (*GMarshalFunc_VOID__STRING_INT_INT_INT_INT) (gpointer data1,
545  gpointer arg_1,
546  gint arg_2,
547  gint arg_3,
548  gint arg_4,
549  gint arg_5,
550  gpointer data2);
551  register GMarshalFunc_VOID__STRING_INT_INT_INT_INT callback;
552  register GCClosure *cc = (GCClosure*) closure;
553  register gpointer data1, data2;
554 
555  g_return_if_fail (n_param_values == 6);
556 
557  if (G_CCLOSURE_SWAP_DATA (closure))
558  {
559  data1 = closure->data;
560  data2 = g_value_peek_pointer (param_values + 0);
561  }
562  else
563  {
564  data1 = g_value_peek_pointer (param_values + 0);
565  data2 = closure->data;
566  }
567  callback = (GMarshalFunc_VOID__STRING_INT_INT_INT_INT) (marshal_data ? marshal_data : cc->callback);
568 
569  callback (data1,
570  g_marshal_value_peek_string (param_values + 1),
571  g_marshal_value_peek_int (param_values + 2),
572  g_marshal_value_peek_int (param_values + 3),
573  g_marshal_value_peek_int (param_values + 4),
574  g_marshal_value_peek_int (param_values + 5),
575  data2);
576 }
577