gui: Factor out duplicated code for executing and pasting syntax.
[pspp] / src / ui / gui / customentry.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2007  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (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, see <http://www.gnu.org/licenses/>. */
16
17 /*
18    This widget is a subclass of GtkEntry.  It's an entry widget with a
19    button on the right hand side.
20
21    This code is heavily based upon the GtkSpinButton widget.  Therefore
22    the copyright notice of that code is pasted below.
23
24    Please note however,  this code is covered by the GPL, not the LGPL.
25 */
26
27 /* GTK - The GIMP Toolkit
28  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
29  *
30  * GtkSpinButton widget for GTK+
31  * Copyright (C) 1998 Lars Hamann and Stefan Jeske
32  *
33  * This library is free software; you can redistribute it and/or
34  * modify it under the terms of the GNU Lesser General Public
35  * License as published by the Free Software Foundation; either
36  * version 2 of the License, or (at your option) any later version.
37  *
38  * This library is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
41  * Lesser General Public License for more details.
42  *
43  * You should have received a copy of the GNU Lesser General Public
44  * License along with this library.  If not, see
45  * <http://www.gnu.org/licenses/>.
46  */
47
48
49 /*
50  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
51  * file for a list of people on the GTK+ Team.  See the ChangeLog
52  * files for a list of changes.  These files are distributed with
53  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
54  */
55
56 #include <config.h>
57
58 #include <gtk/gtksignal.h>
59 #include <gtk/gtkentry.h>
60 #include "customentry.h"
61
62
63 static void psppire_custom_entry_class_init          (PsppireCustomEntryClass *klass);
64 static void psppire_custom_entry_init                (PsppireCustomEntry      *ce);
65
66 static GtkEntryClass *parent_class = NULL;
67
68 /* Signals */
69 enum
70 {
71   CLICKED,
72   n_SIGNALS
73 };
74
75
76 static guint custom_entry_signals[n_SIGNALS] = {0};
77
78
79 GType
80 psppire_custom_entry_get_type (void)
81 {
82   static GType ce_type = 0;
83
84   if (!ce_type)
85     {
86       static const GTypeInfo ce_info =
87         {
88           sizeof (PsppireCustomEntryClass),
89           NULL, /* base_init */
90           NULL, /* base_finalize */
91           (GClassInitFunc) psppire_custom_entry_class_init,
92           NULL, /* class_finalize */
93           NULL, /* class_data */
94           sizeof (PsppireCustomEntry),
95           0,
96           (GInstanceInitFunc) psppire_custom_entry_init,
97         };
98
99       ce_type = g_type_register_static (GTK_TYPE_ENTRY, "PsppireCustomEntry",
100                                         &ce_info, 0);
101     }
102
103   return ce_type;
104 }
105
106
107 static void
108 psppire_custom_entry_map (GtkWidget *widget)
109 {
110   if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_MAPPED (widget))
111     {
112       GTK_WIDGET_CLASS (parent_class)->map (widget);
113       gdk_window_show (PSPPIRE_CUSTOM_ENTRY (widget)->panel);
114     }
115 }
116
117 static void
118 psppire_custom_entry_unmap (GtkWidget *widget)
119 {
120   if (GTK_WIDGET_MAPPED (widget))
121     {
122       gdk_window_hide (PSPPIRE_CUSTOM_ENTRY (widget)->panel);
123       GTK_WIDGET_CLASS (parent_class)->unmap (widget);
124     }
125 }
126
127 static gint psppire_custom_entry_get_button_width (PsppireCustomEntry *custom_entry);
128
129 static void
130 psppire_custom_entry_realize (GtkWidget *widget)
131 {
132   PsppireCustomEntry *custom_entry;
133   GdkWindowAttr attributes;
134   gint attributes_mask;
135   guint real_width;
136   gint button_size ;
137
138   custom_entry = PSPPIRE_CUSTOM_ENTRY (widget);
139
140   button_size = psppire_custom_entry_get_button_width (custom_entry);
141
142   real_width = widget->allocation.width;
143   widget->allocation.width -= button_size + 2 * widget->style->xthickness;
144   gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
145                          GDK_KEY_RELEASE_MASK);
146   GTK_WIDGET_CLASS (parent_class)->realize (widget);
147
148   widget->allocation.width = real_width;
149
150   attributes.window_type = GDK_WINDOW_CHILD;
151   attributes.wclass = GDK_INPUT_OUTPUT;
152   attributes.visual = gtk_widget_get_visual (widget);
153   attributes.colormap = gtk_widget_get_colormap (widget);
154   attributes.event_mask = gtk_widget_get_events (widget);
155   attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
156     | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
157     | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
158
159   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
160
161   attributes.x = (widget->allocation.x +
162                   widget->allocation.width - button_size -
163                   2 * widget->style->xthickness);
164   attributes.y = widget->allocation.y + (widget->allocation.height -
165                                          widget->requisition.height) / 2;
166   attributes.width = button_size + 2 * widget->style->xthickness;
167   attributes.height = widget->requisition.height;
168
169   custom_entry->panel = gdk_window_new (gtk_widget_get_parent_window (widget),
170                                         &attributes, attributes_mask);
171   gdk_window_set_user_data (custom_entry->panel, widget);
172
173   gtk_style_set_background (widget->style, custom_entry->panel, GTK_STATE_NORMAL);
174
175
176   gtk_widget_queue_resize (GTK_WIDGET (custom_entry));
177 }
178
179
180 #define MIN_BUTTON_WIDTH  6
181
182 static gint
183 psppire_custom_entry_get_button_width (PsppireCustomEntry *custom_entry)
184 {
185   const gint size = pango_font_description_get_size
186     (GTK_WIDGET (custom_entry)->style->font_desc);
187
188   gint button_width = MAX (PANGO_PIXELS (size), MIN_BUTTON_WIDTH);
189
190   return button_width - button_width % 2; /* force even */
191 }
192
193 /**
194  * custom_entry_get_shadow_type:
195  * @custom_entry: a #PsppireCustomEntry
196  *
197  * Convenience function to Get the shadow type from the underlying widget's
198  * style.
199  *
200  * Return value: the #GtkShadowType
201  **/
202 static gint
203 psppire_custom_entry_get_shadow_type (PsppireCustomEntry *custom_entry)
204 {
205   GtkShadowType rc_shadow_type;
206
207   gtk_widget_style_get (GTK_WIDGET (custom_entry), "shadow_type", &rc_shadow_type, NULL);
208
209   return rc_shadow_type;
210 }
211
212
213 static void
214 psppire_custom_entry_unrealize (GtkWidget *widget)
215 {
216   PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY (widget);
217
218   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
219
220   if (ce->panel)
221     {
222       gdk_window_set_user_data (ce->panel, NULL);
223       gdk_window_destroy (ce->panel);
224       ce->panel = NULL;
225     }
226 }
227
228
229 static void
230 psppire_custom_entry_redraw (PsppireCustomEntry *custom_entry)
231 {
232   GtkWidget *widget;
233
234   widget = GTK_WIDGET (custom_entry);
235
236   if (GTK_WIDGET_DRAWABLE (widget))
237     {
238       gtk_widget_queue_draw (widget);
239
240       /* We must invalidate the panel window ourselves, because it
241        * is not a child of widget->window
242        */
243       gdk_window_invalidate_rect (custom_entry->panel, NULL, TRUE);
244     }
245 }
246
247
248 static gint
249 psppire_custom_entry_expose (GtkWidget      *widget,
250                      GdkEventExpose *event)
251 {
252   PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY (widget);
253
254   g_return_val_if_fail (PSPPIRE_IS_CUSTOM_ENTRY (widget), FALSE);
255   g_return_val_if_fail (event != NULL, FALSE);
256
257   if (GTK_WIDGET_DRAWABLE (widget))
258     {
259       gboolean is_editable;
260       GtkShadowType shadow_type;
261       GdkRectangle rect;
262
263       rect.x = 0;
264       rect.y = 0;
265
266       if (event->window != ce->panel)
267         GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
268
269       gdk_drawable_get_size (ce->panel, &rect.width, &rect.height);
270
271       gdk_window_begin_paint_rect (ce->panel, &rect);
272
273
274       shadow_type = psppire_custom_entry_get_shadow_type (ce);
275
276       g_object_get (widget, "editable", &is_editable, NULL);
277
278       gtk_paint_box (widget->style, ce->panel,
279                      is_editable ? GTK_STATE_NORMAL: GTK_STATE_INSENSITIVE,
280                      shadow_type,
281                      NULL, widget, "customentry",
282                      rect.x, rect.y, rect.width, rect.height);
283
284
285       gdk_window_end_paint (ce->panel);
286     }
287
288   return FALSE;
289 }
290
291
292 static gint
293 psppire_custom_entry_button_press (GtkWidget      *widget,
294                            GdkEventButton *event);
295
296 static void
297 psppire_custom_entry_size_allocate (GtkWidget     *widget,
298                             GtkAllocation *allocation);
299
300
301
302 static void
303 psppire_custom_entry_class_init (PsppireCustomEntryClass *klass)
304 {
305   GObjectClass     *gobject_class = G_OBJECT_CLASS (klass);
306
307   GtkWidgetClass   *widget_class;
308   GtkEntryClass   *entry_class;
309
310   parent_class = g_type_class_peek_parent (klass);
311
312   widget_class   = (GtkWidgetClass*)   klass;
313   entry_class   = (GtkEntryClass*)   klass;
314
315   widget_class->map = psppire_custom_entry_map;
316   widget_class->unmap = psppire_custom_entry_unmap;
317
318   widget_class->realize = psppire_custom_entry_realize;
319   widget_class->unrealize = psppire_custom_entry_unrealize;
320
321   widget_class->expose_event = psppire_custom_entry_expose;
322   widget_class->button_press_event = psppire_custom_entry_button_press;
323
324   widget_class->size_allocate = psppire_custom_entry_size_allocate;
325
326
327   gtk_widget_class_install_style_property_parser
328     (widget_class,
329      g_param_spec_enum ("shadow_type",
330                         "Shadow Type",
331                         "Style of bevel around the custom entry button",
332                         GTK_TYPE_SHADOW_TYPE,
333                         GTK_SHADOW_ETCHED_IN,
334                         G_PARAM_READABLE),
335      gtk_rc_property_parse_enum);
336
337   custom_entry_signals[CLICKED] =
338     g_signal_new ("clicked",
339                   G_TYPE_FROM_CLASS (gobject_class),
340                   G_SIGNAL_RUN_LAST,
341                   0,
342                   NULL, NULL,
343                   g_cclosure_marshal_VOID__VOID,
344                   G_TYPE_NONE,
345                   0);
346
347
348 }
349
350 static void
351 psppire_custom_entry_init (PsppireCustomEntry *ce)
352 {
353 }
354
355 GtkWidget*
356 psppire_custom_entry_new ()
357 {
358   return GTK_WIDGET (g_object_new (psppire_custom_entry_get_type (), NULL));
359 }
360
361
362
363 static gint
364 psppire_custom_entry_button_press (GtkWidget *widget,
365                                    GdkEventButton *event)
366 {
367   PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY (widget);
368
369   if (event->window == ce->panel)
370     {
371       gboolean is_editable ;
372       if (!GTK_WIDGET_HAS_FOCUS (widget))
373         gtk_widget_grab_focus (widget);
374
375       g_object_get (ce, "editable", &is_editable, NULL);
376
377       if ( event->button == 1 && is_editable )
378         g_signal_emit (widget, custom_entry_signals[CLICKED], 0);
379
380     }
381   else
382     return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
383
384   return FALSE;
385 }
386
387
388
389 static void
390 psppire_custom_entry_size_allocate (GtkWidget     *widget,
391                             GtkAllocation *allocation)
392 {
393   PsppireCustomEntry *ce;
394   GtkAllocation entry_allocation;
395   GtkAllocation panel_allocation;
396   gint button_width;
397   gint panel_width;
398
399   g_return_if_fail (PSPPIRE_IS_CUSTOM_ENTRY (widget));
400   g_return_if_fail (allocation != NULL);
401
402   ce = PSPPIRE_CUSTOM_ENTRY (widget);
403   button_width = psppire_custom_entry_get_button_width (ce);
404   panel_width = button_width + 2 * widget->style->xthickness;
405
406   widget->allocation = *allocation;
407
408   entry_allocation = *allocation;
409   entry_allocation.width -= panel_width;
410
411   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
412     {
413       entry_allocation.x += panel_width;
414       panel_allocation.x = allocation->x;
415     }
416   else
417     {
418       panel_allocation.x = allocation->x + allocation->width - panel_width;
419     }
420
421   panel_allocation.width = panel_width;
422   panel_allocation.height = MIN (widget->requisition.height, allocation->height);
423
424   panel_allocation.y = allocation->y + (allocation->height -
425                                         panel_allocation.height) / 2;
426
427   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, &entry_allocation);
428
429   if (GTK_WIDGET_REALIZED (widget))
430     {
431       gdk_window_move_resize (PSPPIRE_CUSTOM_ENTRY (widget)->panel,
432                               panel_allocation.x,
433                               panel_allocation.y,
434                               panel_allocation.width,
435                               panel_allocation.height);
436     }
437
438   psppire_custom_entry_redraw (ce);
439 }
440
441
442
443
444