3 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
4 *
5 */
6 /* ***** BEGIN LICENSE BLOCK *****
7 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
8 *
9 * The contents of this file are subject to the Mozilla Public License Version
10 * 1.1 (the "License"); you may not use this file except in compliance with
11 * the License. You may obtain a copy of the License at
12 * http://www.mozilla.org/MPL/
13 *
14 * Software distributed under the License is distributed on an "AS IS" basis,
15 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
16 * for the specific language governing rights and limitations under the
17 * License.
18 *
19 * The Original Code is EGE Adjustment Action.
20 *
21 * The Initial Developer of the Original Code is
22 * Jon A. Cruz.
23 * Portions created by the Initial Developer are Copyright (C) 2006
24 * the Initial Developer. All Rights Reserved.
25 *
26 * Contributor(s):
27 *
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
39 *
40 * ***** END LICENSE BLOCK ***** */
42 /* Note: this file should be kept compliable as both .cpp and .c */
44 #include <string.h>
46 #include <gdk/gdkkeysyms.h>
47 #include <gtk/gtktoolitem.h>
48 #include <gtk/gtkspinbutton.h>
49 #include <gtk/gtkhbox.h>
50 #include <gtk/gtklabel.h>
51 #include <gtk/gtkmisc.h>
52 #include <gtk/gtktoolbar.h>
54 #include "ege-adjustment-action.h"
57 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass );
58 static void ege_adjustment_action_init( EgeAdjustmentAction* action );
59 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec );
60 static void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec );
62 static GtkWidget* create_menu_item( GtkAction* action );
63 static GtkWidget* create_tool_item( GtkAction* action );
64 static void connect_proxy( GtkAction *action, GtkWidget *proxy );
65 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
67 static gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
68 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
69 static gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
71 static void ege_adjustment_action_defocus( EgeAdjustmentAction* action );
74 static GtkActionClass* gParentClass = 0;
77 struct _EgeAdjustmentActionPrivate
78 {
79 GtkAdjustment* adj;
80 GtkWidget* focusWidget;
81 gdouble climbRate;
82 guint digits;
83 gchar* selfId;
84 EgeWidgetFixup toolPost;
85 gdouble lastVal;
86 gdouble step;
87 gdouble page;
88 gboolean transferFocus;
89 };
91 #define EGE_ADJUSTMENT_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_ADJUSTMENT_ACTION_TYPE, EgeAdjustmentActionPrivate ) )
93 enum {
94 PROP_ADJUSTMENT = 1,
95 PROP_FOCUS_WIDGET,
96 PROP_CLIMB_RATE,
97 PROP_DIGITS,
98 PROP_SELFID,
99 PROP_TOOL_POST
100 };
102 GType ege_adjustment_action_get_type( void )
103 {
104 static GType myType = 0;
105 if ( !myType ) {
106 static const GTypeInfo myInfo = {
107 sizeof( EgeAdjustmentActionClass ),
108 NULL, /* base_init */
109 NULL, /* base_finalize */
110 (GClassInitFunc)ege_adjustment_action_class_init,
111 NULL, /* class_finalize */
112 NULL, /* class_data */
113 sizeof( EgeAdjustmentAction ),
114 0, /* n_preallocs */
115 (GInstanceInitFunc)ege_adjustment_action_init,
116 NULL
117 };
119 myType = g_type_register_static( GTK_TYPE_ACTION, "EgeAdjustmentAction", &myInfo, (GTypeFlags)0 );
120 }
122 return myType;
123 }
126 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass )
127 {
128 if ( klass ) {
129 gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
130 GObjectClass * objClass = G_OBJECT_CLASS( klass );
132 objClass->get_property = ege_adjustment_action_get_property;
133 objClass->set_property = ege_adjustment_action_set_property;
135 klass->parent_class.create_menu_item = create_menu_item;
136 klass->parent_class.create_tool_item = create_tool_item;
137 klass->parent_class.connect_proxy = connect_proxy;
138 klass->parent_class.disconnect_proxy = disconnect_proxy;
140 g_object_class_install_property( objClass,
141 PROP_ADJUSTMENT,
142 g_param_spec_object( "adjustment",
143 "Adjustment",
144 "The adjustment to change",
145 GTK_TYPE_ADJUSTMENT,
146 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
148 g_object_class_install_property( objClass,
149 PROP_FOCUS_WIDGET,
150 g_param_spec_pointer( "focus-widget",
151 "Focus Widget",
152 "The widget to return focus to",
153 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
155 g_object_class_install_property( objClass,
156 PROP_CLIMB_RATE,
157 g_param_spec_double( "climb-rate",
158 "Climb Rate",
159 "The acelleraton rate",
160 0.0, G_MAXDOUBLE, 0.0,
161 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
163 g_object_class_install_property( objClass,
164 PROP_DIGITS,
165 g_param_spec_uint( "digits",
166 "Digits",
167 "The number of digits to show",
168 0, 20, 0,
169 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
171 g_object_class_install_property( objClass,
172 PROP_SELFID,
173 g_param_spec_string( "self-id",
174 "Self ID",
175 "Marker for self pointer",
176 0,
177 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
179 g_object_class_install_property( objClass,
180 PROP_TOOL_POST,
181 g_param_spec_pointer( "tool-post",
182 "Tool Widget post process",
183 "Function for final adjustments",
184 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
186 g_type_class_add_private( klass, sizeof(EgeAdjustmentActionClass) );
187 }
188 }
190 static void ege_adjustment_action_init( EgeAdjustmentAction* action )
191 {
192 action->private_data = EGE_ADJUSTMENT_ACTION_GET_PRIVATE( action );
193 action->private_data->adj = 0;
194 action->private_data->focusWidget = 0;
195 action->private_data->climbRate = 0.0;
196 action->private_data->digits = 2;
197 action->private_data->selfId = 0;
198 action->private_data->toolPost = 0;
199 action->private_data->lastVal = 0.0;
200 action->private_data->step = 0.0;
201 action->private_data->page = 0.0;
202 action->private_data->transferFocus = FALSE;
203 }
205 EgeAdjustmentAction* ege_adjustment_action_new( GtkAdjustment* adjustment,
206 const gchar *name,
207 const gchar *label,
208 const gchar *tooltip,
209 const gchar *stock_id,
210 gdouble climb_rate,
211 guint digits )
212 {
213 GObject* obj = (GObject*)g_object_new( EGE_ADJUSTMENT_ACTION_TYPE,
214 "name", name,
215 "label", label,
216 "tooltip", tooltip,
217 "stock_id", stock_id,
218 "adjustment", adjustment,
219 "climb-rate", climb_rate,
220 "digits", digits,
221 NULL );
223 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
225 return action;
226 }
228 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
229 {
230 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
231 switch ( propId ) {
232 case PROP_ADJUSTMENT:
233 g_value_set_object( value, action->private_data->adj );
234 break;
236 case PROP_FOCUS_WIDGET:
237 g_value_set_pointer( value, action->private_data->focusWidget );
238 break;
240 case PROP_CLIMB_RATE:
241 g_value_set_double( value, action->private_data->climbRate );
242 break;
244 case PROP_DIGITS:
245 g_value_set_uint( value, action->private_data->digits );
246 break;
248 case PROP_SELFID:
249 g_value_set_string( value, action->private_data->selfId );
250 break;
252 case PROP_TOOL_POST:
253 g_value_set_pointer( value, (void*)action->private_data->toolPost );
254 break;
256 default:
257 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
258 }
259 }
261 void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
262 {
263 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
264 switch ( propId ) {
265 case PROP_ADJUSTMENT:
266 {
267 action->private_data->adj = GTK_ADJUSTMENT( g_value_get_object( value ) );
268 g_object_get( G_OBJECT(action->private_data->adj),
269 "step-increment", &action->private_data->step,
270 "page-increment", &action->private_data->page,
271 NULL );
272 }
273 break;
275 case PROP_FOCUS_WIDGET:
276 {
277 /* TODO unhook prior */
278 action->private_data->focusWidget = (GtkWidget*)g_value_get_pointer( value );
279 }
280 break;
282 case PROP_CLIMB_RATE:
283 {
284 /* TODO pass on */
285 action->private_data->climbRate = g_value_get_double( value );
286 }
287 break;
289 case PROP_DIGITS:
290 {
291 /* TODO pass on */
292 action->private_data->digits = g_value_get_uint( value );
293 }
294 break;
296 case PROP_SELFID:
297 {
298 /* TODO pass on */
299 gchar* prior = action->private_data->selfId;
300 action->private_data->selfId = g_value_dup_string( value );
301 g_free( prior );
302 }
303 break;
305 case PROP_TOOL_POST:
306 {
307 action->private_data->toolPost = (EgeWidgetFixup)g_value_get_pointer( value );
308 }
309 break;
311 default:
312 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
313 }
314 }
316 GtkAdjustment* ege_adjustment_action_get_adjustment( EgeAdjustmentAction* action )
317 {
318 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
320 return action->private_data->adj;
321 }
323 void ege_adjustment_action_set_focuswidget( EgeAdjustmentAction* action, GtkWidget* widget )
324 {
325 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(action) );
327 /* TODO unhook prior */
329 action->private_data->focusWidget = widget;
330 }
332 GtkWidget* ege_adjustment_action_get_focuswidget( EgeAdjustmentAction* action )
333 {
334 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
336 return action->private_data->focusWidget;
337 }
339 static GtkWidget* create_menu_item( GtkAction* action )
340 {
341 GtkWidget* item = 0;
343 item = gParentClass->create_menu_item( action );
345 return item;
346 }
348 void value_changed_cb( GtkSpinButton* spin, EgeAdjustmentAction* act )
349 {
350 if ( GTK_WIDGET_HAS_FOCUS( GTK_WIDGET(spin) ) ) {
351 ege_adjustment_action_defocus( act );
352 }
353 }
355 static GtkWidget* create_tool_item( GtkAction* action )
356 {
357 GtkWidget* item = 0;
359 if ( IS_EGE_ADJUSTMENT_ACTION(action) ) {
360 EgeAdjustmentAction* act = EGE_ADJUSTMENT_ACTION( action );
361 GtkWidget* spinbutton = gtk_spin_button_new( act->private_data->adj, act->private_data->climbRate, act->private_data->digits );
362 GtkWidget* hb = gtk_hbox_new( FALSE, 5 );
363 GValue value;
365 item = GTK_WIDGET( gtk_tool_item_new() );
367 memset( &value, 0, sizeof(value) );
368 g_value_init( &value, G_TYPE_STRING );
369 g_object_get_property( G_OBJECT(action), "label", &value );
370 const gchar* sss = g_value_get_string( &value );
371 GtkWidget* lbl = gtk_label_new( sss ? sss : "wwww" );
373 gtk_misc_set_alignment( GTK_MISC(lbl), 1.0, 0.5 );
375 gtk_box_pack_start( GTK_BOX(hb), lbl, FALSE, FALSE, 0 );
376 gtk_box_pack_end( GTK_BOX(hb), spinbutton, FALSE, FALSE, 0 );
377 gtk_container_add( GTK_CONTAINER(item), hb );
379 if ( act->private_data->selfId ) {
380 gtk_object_set_data( GTK_OBJECT(spinbutton), act->private_data->selfId, spinbutton );
381 }
383 g_signal_connect( G_OBJECT(spinbutton), "focus-in-event", G_CALLBACK(focus_in_cb), action );
384 g_signal_connect( G_OBJECT(spinbutton), "focus-out-event", G_CALLBACK(focus_out_cb), action );
385 g_signal_connect( G_OBJECT(spinbutton), "key-press-event", G_CALLBACK(keypress_cb), action );
387 g_signal_connect( G_OBJECT(spinbutton), "value-changed", G_CALLBACK(value_changed_cb), action );
388 /* g_signal_connect( G_OBJECT(EGE_ADJUSTMENT_ACTION(action)->private_data->adj), "value-changed", G_CALLBACK(flippy), action ); */
391 gtk_widget_show_all( item );
393 /* Shrink or whatnot after shown */
394 if ( act->private_data->toolPost ) {
395 act->private_data->toolPost( item );
396 }
397 } else {
398 item = gParentClass->create_tool_item( action );
399 }
401 return item;
402 }
404 static void connect_proxy( GtkAction *action, GtkWidget *proxy )
405 {
406 gParentClass->connect_proxy( action, proxy );
407 }
409 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
410 {
411 gParentClass->disconnect_proxy( action, proxy );
412 }
414 void ege_adjustment_action_defocus( EgeAdjustmentAction* action )
415 {
416 if ( action->private_data->transferFocus ) {
417 if ( action->private_data->focusWidget ) {
418 gtk_widget_grab_focus( action->private_data->focusWidget );
419 }
420 }
421 }
423 gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
424 {
425 (void)event;
426 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
427 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
428 action->private_data->lastVal = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
429 action->private_data->transferFocus = TRUE;
430 }
432 return FALSE; /* report event not consumed */
433 }
435 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
436 {
437 (void)widget;
438 (void)event;
439 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
440 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
441 action->private_data->transferFocus = FALSE;
442 }
444 return FALSE; /* report event not consumed */
445 }
448 static gboolean process_tab( GtkWidget* widget, int direction )
449 {
450 gboolean handled = FALSE;
451 GtkWidget* parent = gtk_widget_get_parent(widget);
452 GtkWidget* gp = parent ? gtk_widget_get_parent(parent) : 0;
453 GtkWidget* ggp = gp ? gtk_widget_get_parent(gp) : 0;
455 if ( ggp && GTK_IS_TOOLBAR(ggp) ) {
456 GList* kids = gtk_container_get_children( GTK_CONTAINER(ggp) );
457 if ( kids ) {
458 GtkWidget* curr = widget;
459 while ( curr && (gtk_widget_get_parent(curr) != ggp) ) {
460 curr = gtk_widget_get_parent( curr );
461 }
462 if ( curr ) {
463 GList* mid = g_list_find( kids, curr );
464 while ( mid ) {
465 mid = ( direction < 0 ) ? g_list_previous(mid) : g_list_next(mid);
466 if ( mid && GTK_IS_TOOL_ITEM(mid->data) ) {
467 /* potential target */
468 GtkWidget* child = gtk_bin_get_child( GTK_BIN(mid->data) );
469 if ( child && GTK_IS_HBOX(child) ) { /* could be ours */
470 GList* subChildren = gtk_container_get_children( GTK_CONTAINER(child) );
471 if ( subChildren ) {
472 GList* last = g_list_last(subChildren);
473 if ( last && GTK_IS_SPIN_BUTTON(last->data) && GTK_WIDGET_IS_SENSITIVE( GTK_WIDGET(last->data) ) ) {
474 gtk_widget_grab_focus( GTK_WIDGET(last->data) );
475 handled = TRUE;
476 mid = 0; /* to stop loop */
477 }
479 g_list_free(subChildren);
480 }
481 }
482 }
483 }
484 }
485 g_list_free( kids );
486 }
487 }
489 return handled;
490 }
492 gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
493 {
494 gboolean wasConsumed = FALSE; /* default to report event not consumed */
495 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION(data);
496 guint key = 0;
497 gdk_keymap_translate_keyboard_state( gdk_keymap_get_for_display( gdk_display_get_default() ),
498 event->hardware_keycode, (GdkModifierType)event->state,
499 0, &key, 0, 0, 0 );
501 switch ( key ) {
502 case GDK_Escape:
503 {
504 action->private_data->transferFocus = TRUE;
505 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
506 ege_adjustment_action_defocus( action );
507 wasConsumed = TRUE;
508 }
509 break;
511 case GDK_Return:
512 case GDK_KP_Enter:
513 {
514 action->private_data->transferFocus = TRUE;
515 ege_adjustment_action_defocus( action );
516 wasConsumed = TRUE;
517 }
518 break;
520 case GDK_Tab:
521 {
522 action->private_data->transferFocus = FALSE;
523 wasConsumed = process_tab( widget, 1 );
524 }
525 break;
527 case GDK_ISO_Left_Tab:
528 {
529 action->private_data->transferFocus = FALSE;
530 wasConsumed = process_tab( widget, -1 );
531 }
532 break;
534 case GDK_Up:
535 case GDK_KP_Up:
536 {
537 action->private_data->transferFocus = FALSE;
538 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
539 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->step );
540 wasConsumed = TRUE;
541 }
542 break;
544 case GDK_Down:
545 case GDK_KP_Down:
546 {
547 action->private_data->transferFocus = FALSE;
548 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
549 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->step );
550 wasConsumed = TRUE;
551 }
552 break;
554 case GDK_Page_Up:
555 case GDK_KP_Page_Up:
556 {
557 action->private_data->transferFocus = FALSE;
558 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
559 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->page );
560 wasConsumed = TRUE;
561 }
562 break;
564 case GDK_Page_Down:
565 case GDK_KP_Page_Down:
566 {
567 action->private_data->transferFocus = FALSE;
568 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
569 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->page );
570 wasConsumed = TRUE;
571 }
572 break;
574 case GDK_z:
575 case GDK_Z:
576 {
577 action->private_data->transferFocus = FALSE;
578 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
579 wasConsumed = TRUE;
580 }
581 break;
583 }
585 return wasConsumed;
586 }