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 compilable as both .cpp and .c */
44 #include <string.h>
46 #include <gdk/gdkkeysyms.h>
47 #include <gtk/gtkversion.h>
48 #include <gtk/gtktoolitem.h>
49 #include <gtk/gtkspinbutton.h>
50 #include <gtk/gtkhscale.h>
51 #if GTK_CHECK_VERSION(2,12,0)
52 #include <gtk/gtkscalebutton.h>
53 #include <gtk/gtkstock.h>
54 #endif /* GTK_CHECK_VERSION(2,12,0) */
55 #include <gtk/gtkhbox.h>
56 #include <gtk/gtklabel.h>
57 #include <gtk/gtkmisc.h>
58 #include <gtk/gtktoolbar.h>
59 #include <gtk/gtktooltips.h>
60 #include <gtk/gtkradiomenuitem.h>
62 #include "ege-adjustment-action.h"
65 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass );
66 static void ege_adjustment_action_init( EgeAdjustmentAction* action );
67 static void ege_adjustment_action_finalize( GObject* object );
68 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec );
69 static void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec );
71 static GtkWidget* create_menu_item( GtkAction* action );
72 static GtkWidget* create_tool_item( GtkAction* action );
73 static void connect_proxy( GtkAction *action, GtkWidget *proxy );
74 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
76 static gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
77 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
78 static gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
80 static void ege_adjustment_action_defocus( EgeAdjustmentAction* action );
82 static void egeAct_free_description( gpointer data, gpointer user_data );
83 static void egeAct_free_all_descriptions( EgeAdjustmentAction* action );
86 static GtkActionClass* gParentClass = 0;
87 static GQuark gDataName = 0;
89 enum {
90 APPEARANCE_UNKNOWN = -1,
91 APPEARANCE_NONE = 0,
92 APPEARANCE_FULL, // label, then all choices represented by separate buttons
93 APPEARANCE_COMPACT, // label, then choices in a drop-down menu
94 APPEARANCE_MINIMAL, // no label, just choices in a drop-down menu
95 };
97 #if GTK_CHECK_VERSION(2,12,0)
98 /* TODO need to have appropriate icons setup for these: */
99 static const gchar *floogles[] = {
100 GTK_STOCK_REMOVE,
101 GTK_STOCK_ADD,
102 GTK_STOCK_GO_DOWN,
103 GTK_STOCK_ABOUT,
104 GTK_STOCK_GO_UP,
105 0};
106 #endif /* GTK_CHECK_VERSION(2,12,0) */
108 typedef struct _EgeAdjustmentDescr EgeAdjustmentDescr;
110 struct _EgeAdjustmentDescr
111 {
112 gchar* descr;
113 gdouble value;
114 };
116 struct _EgeAdjustmentActionPrivate
117 {
118 GtkAdjustment* adj;
119 GtkTooltips* toolTips;
120 GtkWidget* focusWidget;
121 gdouble climbRate;
122 guint digits;
123 gdouble epsilon;
124 gchar* format;
125 gchar* selfId;
126 EgeWidgetFixup toolPost;
127 gdouble lastVal;
128 gdouble step;
129 gdouble page;
130 gint appearanceMode;
131 gboolean transferFocus;
132 GList* descriptions;
133 gchar* appearance;
134 };
136 #define EGE_ADJUSTMENT_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_ADJUSTMENT_ACTION_TYPE, EgeAdjustmentActionPrivate ) )
138 enum {
139 PROP_ADJUSTMENT = 1,
140 PROP_FOCUS_WIDGET,
141 PROP_CLIMB_RATE,
142 PROP_DIGITS,
143 PROP_SELFID,
144 PROP_TOOL_POST,
145 PROP_APPEARANCE
146 };
148 enum {
149 BUMP_TOP = 0,
150 BUMP_PAGE_UP,
151 BUMP_UP,
152 BUMP_NONE,
153 BUMP_DOWN,
154 BUMP_PAGE_DOWN,
155 BUMP_BOTTOM,
156 BUMP_CUSTOM = 100
157 };
159 GType ege_adjustment_action_get_type( void )
160 {
161 static GType myType = 0;
162 if ( !myType ) {
163 static const GTypeInfo myInfo = {
164 sizeof( EgeAdjustmentActionClass ),
165 NULL, /* base_init */
166 NULL, /* base_finalize */
167 (GClassInitFunc)ege_adjustment_action_class_init,
168 NULL, /* class_finalize */
169 NULL, /* class_data */
170 sizeof( EgeAdjustmentAction ),
171 0, /* n_preallocs */
172 (GInstanceInitFunc)ege_adjustment_action_init,
173 NULL
174 };
176 myType = g_type_register_static( GTK_TYPE_ACTION, "EgeAdjustmentAction", &myInfo, (GTypeFlags)0 );
177 }
179 return myType;
180 }
183 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass )
184 {
185 if ( klass ) {
186 gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
187 GObjectClass * objClass = G_OBJECT_CLASS( klass );
189 gDataName = g_quark_from_string("ege-adj-action");
191 objClass->finalize = ege_adjustment_action_finalize;
193 objClass->get_property = ege_adjustment_action_get_property;
194 objClass->set_property = ege_adjustment_action_set_property;
196 klass->parent_class.create_menu_item = create_menu_item;
197 klass->parent_class.create_tool_item = create_tool_item;
198 klass->parent_class.connect_proxy = connect_proxy;
199 klass->parent_class.disconnect_proxy = disconnect_proxy;
201 g_object_class_install_property( objClass,
202 PROP_ADJUSTMENT,
203 g_param_spec_object( "adjustment",
204 "Adjustment",
205 "The adjustment to change",
206 GTK_TYPE_ADJUSTMENT,
207 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
209 g_object_class_install_property( objClass,
210 PROP_FOCUS_WIDGET,
211 g_param_spec_pointer( "focus-widget",
212 "Focus Widget",
213 "The widget to return focus to",
214 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
216 g_object_class_install_property( objClass,
217 PROP_CLIMB_RATE,
218 g_param_spec_double( "climb-rate",
219 "Climb Rate",
220 "The acelleraton rate",
221 0.0, G_MAXDOUBLE, 0.0,
222 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
224 g_object_class_install_property( objClass,
225 PROP_DIGITS,
226 g_param_spec_uint( "digits",
227 "Digits",
228 "The number of digits to show",
229 0, 20, 0,
230 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
232 g_object_class_install_property( objClass,
233 PROP_SELFID,
234 g_param_spec_string( "self-id",
235 "Self ID",
236 "Marker for self pointer",
237 0,
238 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
240 g_object_class_install_property( objClass,
241 PROP_TOOL_POST,
242 g_param_spec_pointer( "tool-post",
243 "Tool Widget post process",
244 "Function for final adjustments",
245 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
247 g_object_class_install_property( objClass,
248 PROP_APPEARANCE,
249 g_param_spec_string( "appearance",
250 "Appearance hint",
251 "A hint for how to display",
252 "",
253 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
255 g_type_class_add_private( klass, sizeof(EgeAdjustmentActionClass) );
256 }
257 }
259 static void ege_adjustment_action_init( EgeAdjustmentAction* action )
260 {
261 action->private_data = EGE_ADJUSTMENT_ACTION_GET_PRIVATE( action );
262 action->private_data->adj = 0;
263 action->private_data->toolTips = 0;
264 action->private_data->focusWidget = 0;
265 action->private_data->climbRate = 0.0;
266 action->private_data->digits = 2;
267 action->private_data->epsilon = 0.009;
268 action->private_data->format = g_strdup_printf("%%0.%df%%s%%s", action->private_data->digits);
269 action->private_data->selfId = 0;
270 action->private_data->toolPost = 0;
271 action->private_data->lastVal = 0.0;
272 action->private_data->step = 0.0;
273 action->private_data->page = 0.0;
274 action->private_data->appearanceMode = APPEARANCE_NONE;
275 action->private_data->transferFocus = FALSE;
276 action->private_data->descriptions = 0;
277 action->private_data->appearance = 0;
278 }
280 static void ege_adjustment_action_finalize( GObject* object )
281 {
282 EgeAdjustmentAction* action = 0;
283 g_return_if_fail( object != NULL );
284 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(object) );
286 action = EGE_ADJUSTMENT_ACTION( object );
288 if ( action->private_data->format ) {
289 g_free( action->private_data->format );
290 action->private_data->format = 0;
291 }
293 egeAct_free_all_descriptions( action );
295 if ( G_OBJECT_CLASS(gParentClass)->finalize ) {
296 (*G_OBJECT_CLASS(gParentClass)->finalize)(object);
297 }
298 }
300 EgeAdjustmentAction* ege_adjustment_action_new( GtkAdjustment* adjustment,
301 const gchar *name,
302 const gchar *label,
303 const gchar *tooltip,
304 const gchar *stock_id,
305 gdouble climb_rate,
306 guint digits )
307 {
308 GObject* obj = (GObject*)g_object_new( EGE_ADJUSTMENT_ACTION_TYPE,
309 "name", name,
310 "label", label,
311 "tooltip", tooltip,
312 "stock_id", stock_id,
313 "adjustment", adjustment,
314 "climb-rate", climb_rate,
315 "digits", digits,
316 NULL );
318 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
320 return action;
321 }
323 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
324 {
325 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
326 switch ( propId ) {
327 case PROP_ADJUSTMENT:
328 g_value_set_object( value, action->private_data->adj );
329 break;
331 case PROP_FOCUS_WIDGET:
332 g_value_set_pointer( value, action->private_data->focusWidget );
333 break;
335 case PROP_CLIMB_RATE:
336 g_value_set_double( value, action->private_data->climbRate );
337 break;
339 case PROP_DIGITS:
340 g_value_set_uint( value, action->private_data->digits );
341 break;
343 case PROP_SELFID:
344 g_value_set_string( value, action->private_data->selfId );
345 break;
347 case PROP_TOOL_POST:
348 g_value_set_pointer( value, (void*)action->private_data->toolPost );
349 break;
351 case PROP_APPEARANCE:
352 g_value_set_string( value, action->private_data->appearance );
353 break;
355 default:
356 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
357 }
358 }
360 void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
361 {
362 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
363 switch ( propId ) {
364 case PROP_ADJUSTMENT:
365 {
366 action->private_data->adj = GTK_ADJUSTMENT( g_value_get_object( value ) );
367 g_object_get( G_OBJECT(action->private_data->adj),
368 "step-increment", &action->private_data->step,
369 "page-increment", &action->private_data->page,
370 NULL );
371 }
372 break;
374 case PROP_FOCUS_WIDGET:
375 {
376 /* TODO unhook prior */
377 action->private_data->focusWidget = (GtkWidget*)g_value_get_pointer( value );
378 }
379 break;
381 case PROP_CLIMB_RATE:
382 {
383 /* TODO pass on */
384 action->private_data->climbRate = g_value_get_double( value );
385 }
386 break;
388 case PROP_DIGITS:
389 {
390 /* TODO pass on */
391 action->private_data->digits = g_value_get_uint( value );
392 switch ( action->private_data->digits ) {
393 case 0: action->private_data->epsilon = 0.9; break;
394 case 1: action->private_data->epsilon = 0.09; break;
395 case 2: action->private_data->epsilon = 0.009; break;
396 case 3: action->private_data->epsilon = 0.0009; break;
397 case 4: action->private_data->epsilon = 0.00009; break;
398 }
399 if ( action->private_data->format ) {
400 g_free( action->private_data->format );
401 }
402 action->private_data->format = g_strdup_printf("%%0.%df%%s%%s", action->private_data->digits);
403 }
404 break;
406 case PROP_SELFID:
407 {
408 /* TODO pass on */
409 gchar* prior = action->private_data->selfId;
410 action->private_data->selfId = g_value_dup_string( value );
411 g_free( prior );
412 }
413 break;
415 case PROP_TOOL_POST:
416 {
417 action->private_data->toolPost = (EgeWidgetFixup)g_value_get_pointer( value );
418 }
419 break;
421 case PROP_APPEARANCE:
422 {
423 gchar* tmp = action->private_data->appearance;
424 gchar* newVal = g_value_dup_string( value );
425 action->private_data->appearance = newVal;
426 g_free( tmp );
428 if ( !action->private_data->appearance || (strcmp("", newVal) == 0) ) {
429 action->private_data->appearanceMode = APPEARANCE_NONE;
430 } else if ( strcmp("full", newVal) == 0 ) {
431 action->private_data->appearanceMode = APPEARANCE_FULL;
432 } else if ( strcmp("compact", newVal) == 0 ) {
433 action->private_data->appearanceMode = APPEARANCE_COMPACT;
434 } else if ( strcmp("minimal", newVal) == 0 ) {
435 action->private_data->appearanceMode = APPEARANCE_MINIMAL;
436 } else {
437 action->private_data->appearanceMode = APPEARANCE_UNKNOWN;
438 }
439 }
440 break;
442 default:
443 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
444 }
445 }
447 GtkAdjustment* ege_adjustment_action_get_adjustment( EgeAdjustmentAction* action )
448 {
449 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
451 return action->private_data->adj;
452 }
454 void ege_adjustment_action_set_focuswidget( EgeAdjustmentAction* action, GtkWidget* widget )
455 {
456 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(action) );
458 /* TODO unhook prior */
460 action->private_data->focusWidget = widget;
461 }
463 GtkWidget* ege_adjustment_action_get_focuswidget( EgeAdjustmentAction* action )
464 {
465 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
467 return action->private_data->focusWidget;
468 }
470 static void egeAct_free_description( gpointer data, gpointer user_data ) {
471 (void)user_data;
472 if ( data ) {
473 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)data;
474 if ( descr->descr ) {
475 g_free( descr->descr );
476 descr->descr = 0;
477 }
478 g_free( descr );
479 }
480 }
482 static void egeAct_free_all_descriptions( EgeAdjustmentAction* action )
483 {
484 if ( action->private_data->descriptions ) {
485 g_list_foreach( action->private_data->descriptions, egeAct_free_description, 0 );
486 g_list_free( action->private_data->descriptions );
487 action->private_data->descriptions = 0;
488 }
489 }
491 static gint egeAct_compare_descriptions( gconstpointer a, gconstpointer b )
492 {
493 gint val = 0;
495 EgeAdjustmentDescr const * aa = (EgeAdjustmentDescr const *)a;
496 EgeAdjustmentDescr const * bb = (EgeAdjustmentDescr const *)b;
498 if ( aa && bb ) {
499 if ( aa->value < bb->value ) {
500 val = -1;
501 } else if ( aa->value > bb->value ) {
502 val = 1;
503 }
504 }
506 return val;
507 }
509 void ege_adjustment_action_set_descriptions( EgeAdjustmentAction* action, gchar const** descriptions, gdouble const* values, guint count )
510 {
511 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(action) );
513 egeAct_free_all_descriptions( action );
515 if ( count && descriptions && values ) {
516 guint i = 0;
517 for ( i = 0; i < count; i++ ) {
518 EgeAdjustmentDescr* descr = g_new0( EgeAdjustmentDescr, 1 );
519 descr->descr = descriptions[i] ? g_strdup( descriptions[i] ) : 0;
520 descr->value = values[i];
521 action->private_data->descriptions = g_list_insert_sorted( action->private_data->descriptions, (gpointer)descr, egeAct_compare_descriptions );
522 }
523 }
524 }
526 void ege_adjustment_action_set_appearance( EgeAdjustmentAction* action, gchar const* val )
527 {
528 g_object_set( G_OBJECT(action), "appearance", val, NULL );
529 }
531 static void process_menu_action( GtkWidget* obj, gpointer data )
532 {
533 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
534 if ( item->active ) {
535 EgeAdjustmentAction* act = (EgeAdjustmentAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
536 gint what = GPOINTER_TO_INT(data);
539 gdouble base = gtk_adjustment_get_value( act->private_data->adj );
540 gdouble lower = 0.0;
541 gdouble upper = 0.0;
542 gdouble step = 0.0;
543 gdouble page = 0.0;
544 g_object_get( G_OBJECT(act->private_data->adj),
545 "lower", &lower,
546 "upper", &upper,
547 "step-increment", &step,
548 "page-increment", &page,
549 NULL );
551 switch ( what ) {
552 case BUMP_TOP:
553 gtk_adjustment_set_value( act->private_data->adj, upper );
554 break;
556 case BUMP_PAGE_UP:
557 gtk_adjustment_set_value( act->private_data->adj, base + page );
558 break;
560 case BUMP_UP:
561 gtk_adjustment_set_value( act->private_data->adj, base + step );
562 break;
564 case BUMP_DOWN:
565 gtk_adjustment_set_value( act->private_data->adj, base - step );
566 break;
568 case BUMP_PAGE_DOWN:
569 gtk_adjustment_set_value( act->private_data->adj, base - page );
570 break;
572 case BUMP_BOTTOM:
573 gtk_adjustment_set_value( act->private_data->adj, lower );
574 break;
576 default:
577 if ( what >= BUMP_CUSTOM ) {
578 guint index = what - BUMP_CUSTOM;
579 if ( index < g_list_length( act->private_data->descriptions ) ) {
580 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)g_list_nth_data( act->private_data->descriptions, index );
581 if ( descr ) {
582 gtk_adjustment_set_value( act->private_data->adj, descr->value );
583 }
584 }
585 }
586 }
587 }
588 }
590 static void create_single_menu_item( GCallback toggleCb, int val, GtkWidget* menu, EgeAdjustmentAction* act, GtkWidget** dst, GSList** group, gdouble num, gboolean active )
591 {
592 char* str = 0;
593 EgeAdjustmentDescr* marker = 0;
594 GList* cur = act->private_data->descriptions;
596 while ( cur ) {
597 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)cur->data;
598 gdouble delta = num - descr->value;
599 if ( delta < 0.0 ) {
600 delta = -delta;
601 }
602 if ( delta < act->private_data->epsilon ) {
603 marker = descr;
604 break;
605 }
606 cur = g_list_next( cur );
607 }
609 str = g_strdup_printf( act->private_data->format, num,
610 ((marker && marker->descr) ? ": " : ""),
611 ((marker && marker->descr) ? marker->descr : ""));
613 *dst = gtk_radio_menu_item_new_with_label( *group, str );
614 if ( !*group) {
615 *group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(*dst) );
616 }
617 if ( active ) {
618 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(*dst), TRUE );
619 }
620 gtk_menu_shell_append( GTK_MENU_SHELL(menu), *dst );
621 g_object_set_qdata( G_OBJECT(*dst), gDataName, act );
623 g_signal_connect( G_OBJECT(*dst), "toggled", toggleCb, GINT_TO_POINTER(val) );
625 g_free(str);
626 }
628 static GList* flush_explicit_items( GList* descriptions,
629 GCallback toggleCb,
630 int val,
631 GtkWidget* menu,
632 EgeAdjustmentAction* act,
633 GtkWidget** dst,
634 GSList** group,
635 gdouble num )
636 {
637 GList* cur = descriptions;
639 if ( cur ) {
640 gdouble valUpper = num + act->private_data->epsilon;
641 gdouble valLower = num - act->private_data->epsilon;
643 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)cur->data;
645 while ( cur && descr && (descr->value >= valLower) ) {
646 if ( descr->value > valUpper ) {
647 create_single_menu_item( toggleCb, val + g_list_position(act->private_data->descriptions, cur), menu, act, dst, group, descr->value, FALSE );
648 }
650 cur = g_list_previous( cur );
651 descr = cur ? (EgeAdjustmentDescr*)cur->data : 0;
652 }
653 }
655 return cur;
656 }
658 static GtkWidget* create_popup_number_menu( EgeAdjustmentAction* act )
659 {
660 GtkWidget* menu = gtk_menu_new();
662 GSList* group = 0;
663 GtkWidget* single = 0;
665 GList* addOns = g_list_last( act->private_data->descriptions );
667 gdouble base = gtk_adjustment_get_value( act->private_data->adj );
668 gdouble lower = 0.0;
669 gdouble upper = 0.0;
670 gdouble step = 0.0;
671 gdouble page = 0.0;
672 g_object_get( G_OBJECT(act->private_data->adj),
673 "lower", &lower,
674 "upper", &upper,
675 "step-increment", &step,
676 "page-increment", &page,
677 NULL );
680 if ( base < upper ) {
681 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, upper );
682 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_TOP, menu, act, &single, &group, upper, FALSE );
683 if ( (base + page) < upper ) {
684 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base + page );
685 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_PAGE_UP, menu, act, &single, &group, base + page, FALSE );
686 }
687 if ( (base + step) < upper ) {
688 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base + step );
689 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_UP, menu, act, &single, &group, base + step, FALSE );
690 }
691 }
693 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base );
694 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_NONE, menu, act, &single, &group, base, TRUE );
696 if ( base > lower ) {
697 if ( (base - step) > lower ) {
698 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base - step );
699 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_DOWN, menu, act, &single, &group, base - step, FALSE );
700 }
701 if ( (base - page) > lower ) {
702 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base - page );
703 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_PAGE_DOWN, menu, act, &single, &group, base - page, FALSE );
704 }
705 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, lower );
706 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_BOTTOM, menu, act, &single, &group, lower, FALSE );
707 }
709 if ( act->private_data->descriptions ) {
710 gdouble value = ((EgeAdjustmentDescr*)act->private_data->descriptions->data)->value;
711 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, value );
712 }
714 return menu;
715 }
717 static GtkWidget* create_menu_item( GtkAction* action )
718 {
719 GtkWidget* item = 0;
721 if ( IS_EGE_ADJUSTMENT_ACTION(action) ) {
722 EgeAdjustmentAction* act = EGE_ADJUSTMENT_ACTION( action );
723 GValue value;
724 const gchar* sss = 0;
725 GtkWidget* subby = 0;
727 memset( &value, 0, sizeof(value) );
728 g_value_init( &value, G_TYPE_STRING );
729 g_object_get_property( G_OBJECT(action), "label", &value );
731 sss = g_value_get_string( &value );
733 item = gtk_menu_item_new_with_label( sss );
735 subby = create_popup_number_menu( act );
736 gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
737 gtk_widget_show_all( subby );
738 } else {
739 item = gParentClass->create_menu_item( action );
740 }
742 return item;
743 }
745 void value_changed_cb( GtkSpinButton* spin, EgeAdjustmentAction* act )
746 {
747 if ( GTK_WIDGET_HAS_FOCUS( GTK_WIDGET(spin) ) ) {
748 ege_adjustment_action_defocus( act );
749 }
750 }
752 static gboolean event_cb( EgeAdjustmentAction* act, GdkEvent* evt )
753 {
754 gboolean handled = FALSE;
755 if ( evt->type == GDK_BUTTON_PRESS ) {
756 if ( evt->button.button == 3 ) {
757 if ( IS_EGE_ADJUSTMENT_ACTION(act) ) {
758 GdkEventButton* btnevt = (GdkEventButton*)evt;
759 GtkWidget* menu = create_popup_number_menu(act);
760 gtk_widget_show_all( menu );
761 gtk_menu_popup( GTK_MENU(menu), NULL, NULL, NULL, NULL, btnevt->button, btnevt->time );
762 }
763 handled = TRUE;
764 }
765 }
767 return handled;
768 }
770 static GtkWidget* create_tool_item( GtkAction* action )
771 {
772 GtkWidget* item = 0;
774 if ( IS_EGE_ADJUSTMENT_ACTION(action) ) {
775 EgeAdjustmentAction* act = EGE_ADJUSTMENT_ACTION( action );
776 GtkWidget* spinbutton = 0;
777 GtkWidget* hb = gtk_hbox_new( FALSE, 5 );
778 GValue value;
780 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
781 spinbutton = gtk_hscale_new( act->private_data->adj);
782 gtk_widget_set_size_request(spinbutton, 100, -1);
783 gtk_scale_set_digits (GTK_SCALE(spinbutton), 0);
784 #if GTK_CHECK_VERSION(2,12,0)
785 } else if ( act->private_data->appearanceMode == APPEARANCE_MINIMAL ) {
786 spinbutton = gtk_scale_button_new( GTK_ICON_SIZE_MENU, 0, 100, 2, 0 );
787 gtk_scale_button_set_adjustment( GTK_SCALE_BUTTON(spinbutton), act->private_data->adj );
788 gtk_scale_button_set_icons( GTK_SCALE_BUTTON(spinbutton), floogles );
789 #endif /* GTK_CHECK_VERSION(2,12,0) */
790 } else {
791 spinbutton = gtk_spin_button_new( act->private_data->adj, act->private_data->climbRate, act->private_data->digits );
792 }
794 item = GTK_WIDGET( gtk_tool_item_new() );
796 memset( &value, 0, sizeof(value) );
797 g_value_init( &value, G_TYPE_STRING );
798 g_object_get_property( G_OBJECT(action), "short_label", &value );
799 const gchar* sss = g_value_get_string( &value );
801 GtkWidget* lbl = gtk_label_new( sss ? sss : "wwww" );
802 GtkWidget* filler1 = gtk_label_new(" ");
804 {
805 GValue tooltip;
806 memset( &tooltip, 0, sizeof(tooltip) );
807 g_value_init( &tooltip, G_TYPE_STRING );
808 g_object_get_property( G_OBJECT(action), "tooltip", &tooltip );
809 const gchar* tipstr = g_value_get_string( &tooltip );
810 if ( tipstr && *tipstr ) {
811 if ( !act->private_data->toolTips ) {
812 act->private_data->toolTips = gtk_tooltips_new();
813 }
814 gtk_tooltips_set_tip( act->private_data->toolTips, spinbutton, tipstr, 0 );
815 }
816 }
818 gtk_misc_set_alignment( GTK_MISC(lbl), 1.0, 0.5 );
820 gtk_box_pack_start( GTK_BOX(hb), filler1, FALSE, FALSE, 0 );
821 gtk_box_pack_start( GTK_BOX(hb), lbl, FALSE, FALSE, 0 );
822 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
823 gtk_box_pack_start( GTK_BOX(hb), spinbutton, TRUE, TRUE, 0 );
824 } else {
825 gtk_box_pack_start( GTK_BOX(hb), spinbutton, FALSE, FALSE, 0 );
826 }
828 gtk_container_add( GTK_CONTAINER(item), hb );
830 if ( act->private_data->selfId ) {
831 g_object_set_data( G_OBJECT(spinbutton), act->private_data->selfId, spinbutton );
832 }
834 g_signal_connect( G_OBJECT(spinbutton), "focus-in-event", G_CALLBACK(focus_in_cb), action );
835 g_signal_connect( G_OBJECT(spinbutton), "focus-out-event", G_CALLBACK(focus_out_cb), action );
836 g_signal_connect( G_OBJECT(spinbutton), "key-press-event", G_CALLBACK(keypress_cb), action );
838 g_signal_connect( G_OBJECT(spinbutton), "value-changed", G_CALLBACK(value_changed_cb), action );
840 g_signal_connect_swapped( G_OBJECT(spinbutton), "event", G_CALLBACK(event_cb), action );
841 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
842 /* */
843 #if GTK_CHECK_VERSION(2,12,0)
844 } else if ( act->private_data->appearanceMode == APPEARANCE_MINIMAL ) {
845 /* */
846 #endif /* GTK_CHECK_VERSION(2,12,0) */
847 } else {
848 gtk_entry_set_width_chars( GTK_ENTRY(spinbutton), act->private_data->digits + 3 );
849 }
851 gtk_widget_show_all( item );
853 /* Shrink or whatnot after shown */
854 if ( act->private_data->toolPost ) {
855 act->private_data->toolPost( item );
856 }
857 } else {
858 item = gParentClass->create_tool_item( action );
859 }
861 return item;
862 }
864 static void connect_proxy( GtkAction *action, GtkWidget *proxy )
865 {
866 gParentClass->connect_proxy( action, proxy );
867 }
869 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
870 {
871 gParentClass->disconnect_proxy( action, proxy );
872 }
874 void ege_adjustment_action_defocus( EgeAdjustmentAction* action )
875 {
876 if ( action->private_data->transferFocus ) {
877 if ( action->private_data->focusWidget ) {
878 gtk_widget_grab_focus( action->private_data->focusWidget );
879 }
880 }
881 }
883 gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
884 {
885 (void)event;
886 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
887 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
888 if ( GTK_IS_SPIN_BUTTON(widget) ) {
889 action->private_data->lastVal = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
890 #if GTK_CHECK_VERSION(2,12,0)
891 } else if ( GTK_IS_SCALE_BUTTON(widget) ) {
892 action->private_data->lastVal = gtk_scale_button_get_value( GTK_SCALE_BUTTON(widget) );
893 #endif /* GTK_CHECK_VERSION(2,12,0) */
894 } else if (GTK_IS_RANGE(widget) ) {
895 action->private_data->lastVal = gtk_range_get_value( GTK_RANGE(widget) );
896 }
897 action->private_data->transferFocus = TRUE;
898 }
900 return FALSE; /* report event not consumed */
901 }
903 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
904 {
905 (void)widget;
906 (void)event;
907 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
908 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
909 action->private_data->transferFocus = FALSE;
910 }
912 return FALSE; /* report event not consumed */
913 }
916 static gboolean process_tab( GtkWidget* widget, int direction )
917 {
918 gboolean handled = FALSE;
919 GtkWidget* parent = gtk_widget_get_parent(widget);
920 GtkWidget* gp = parent ? gtk_widget_get_parent(parent) : 0;
921 GtkWidget* ggp = gp ? gtk_widget_get_parent(gp) : 0;
923 if ( ggp && GTK_IS_TOOLBAR(ggp) ) {
924 GList* kids = gtk_container_get_children( GTK_CONTAINER(ggp) );
925 if ( kids ) {
926 GtkWidget* curr = widget;
927 while ( curr && (gtk_widget_get_parent(curr) != ggp) ) {
928 curr = gtk_widget_get_parent( curr );
929 }
930 if ( curr ) {
931 GList* mid = g_list_find( kids, curr );
932 while ( mid ) {
933 mid = ( direction < 0 ) ? g_list_previous(mid) : g_list_next(mid);
934 if ( mid && GTK_IS_TOOL_ITEM(mid->data) ) {
935 /* potential target */
936 GtkWidget* child = gtk_bin_get_child( GTK_BIN(mid->data) );
937 if ( child && GTK_IS_HBOX(child) ) { /* could be ours */
938 GList* subChildren = gtk_container_get_children( GTK_CONTAINER(child) );
939 if ( subChildren ) {
940 GList* last = g_list_last(subChildren);
941 if ( last && GTK_IS_SPIN_BUTTON(last->data) && GTK_WIDGET_IS_SENSITIVE( GTK_WIDGET(last->data) ) ) {
942 gtk_widget_grab_focus( GTK_WIDGET(last->data) );
943 handled = TRUE;
944 mid = 0; /* to stop loop */
945 }
947 g_list_free(subChildren);
948 }
949 }
950 }
951 }
952 }
953 g_list_free( kids );
954 }
955 }
957 return handled;
958 }
960 gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
961 {
962 gboolean wasConsumed = FALSE; /* default to report event not consumed */
963 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION(data);
964 guint key = 0;
965 gdk_keymap_translate_keyboard_state( gdk_keymap_get_for_display( gdk_display_get_default() ),
966 event->hardware_keycode, (GdkModifierType)event->state,
967 0, &key, 0, 0, 0 );
969 switch ( key ) {
970 case GDK_Escape:
971 {
972 action->private_data->transferFocus = TRUE;
973 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
974 ege_adjustment_action_defocus( action );
975 wasConsumed = TRUE;
976 }
977 break;
979 case GDK_Return:
980 case GDK_KP_Enter:
981 {
982 action->private_data->transferFocus = TRUE;
983 ege_adjustment_action_defocus( action );
984 wasConsumed = TRUE;
985 }
986 break;
988 case GDK_Tab:
989 {
990 action->private_data->transferFocus = FALSE;
991 wasConsumed = process_tab( widget, 1 );
992 }
993 break;
995 case GDK_ISO_Left_Tab:
996 {
997 action->private_data->transferFocus = FALSE;
998 wasConsumed = process_tab( widget, -1 );
999 }
1000 break;
1002 case GDK_Up:
1003 case GDK_KP_Up:
1004 {
1005 action->private_data->transferFocus = FALSE;
1006 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1007 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->step );
1008 wasConsumed = TRUE;
1009 }
1010 break;
1012 case GDK_Down:
1013 case GDK_KP_Down:
1014 {
1015 action->private_data->transferFocus = FALSE;
1016 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1017 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->step );
1018 wasConsumed = TRUE;
1019 }
1020 break;
1022 case GDK_Page_Up:
1023 case GDK_KP_Page_Up:
1024 {
1025 action->private_data->transferFocus = FALSE;
1026 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1027 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->page );
1028 wasConsumed = TRUE;
1029 }
1030 break;
1032 case GDK_Page_Down:
1033 case GDK_KP_Page_Down:
1034 {
1035 action->private_data->transferFocus = FALSE;
1036 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1037 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->page );
1038 wasConsumed = TRUE;
1039 }
1040 break;
1042 case GDK_z:
1043 case GDK_Z:
1044 {
1045 action->private_data->transferFocus = FALSE;
1046 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
1047 wasConsumed = TRUE;
1048 }
1049 break;
1051 }
1053 return wasConsumed;
1054 }