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