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 <cmath>
45 #include <string.h>
47 #include <gdk/gdkkeysyms.h>
48 #include <gtk/gtkversion.h>
49 #include <gtk/gtktoolitem.h>
50 #include <gtk/gtkspinbutton.h>
51 #include <gtk/gtkhscale.h>
52 #if GTK_CHECK_VERSION(2,12,0)
53 #include <gtk/gtkscalebutton.h>
54 #include <gtk/gtkstock.h>
55 #endif /* GTK_CHECK_VERSION(2,12,0) */
56 #include <gtk/gtkhbox.h>
57 #include <gtk/gtklabel.h>
58 #include <gtk/gtkmisc.h>
59 #include <gtk/gtktoolbar.h>
60 #include <gtk/gtktooltips.h>
61 #include <gtk/gtkradiomenuitem.h>
63 #include "ege-adjustment-action.h"
66 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass );
67 static void ege_adjustment_action_init( EgeAdjustmentAction* action );
68 static void ege_adjustment_action_finalize( GObject* object );
69 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec );
70 static void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec );
72 static GtkWidget* create_menu_item( GtkAction* action );
73 static GtkWidget* create_tool_item( GtkAction* action );
74 static void connect_proxy( GtkAction *action, GtkWidget *proxy );
75 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
77 static gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
78 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
79 static gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
81 static void ege_adjustment_action_defocus( EgeAdjustmentAction* action );
83 static void egeAct_free_description( gpointer data, gpointer user_data );
84 static void egeAct_free_all_descriptions( EgeAdjustmentAction* action );
87 static GtkActionClass* gParentClass = 0;
88 static GQuark gDataName = 0;
90 enum {
91 APPEARANCE_UNKNOWN = -1,
92 APPEARANCE_NONE = 0,
93 APPEARANCE_FULL, // label, then all choices represented by separate buttons
94 APPEARANCE_COMPACT, // label, then choices in a drop-down menu
95 APPEARANCE_MINIMAL, // no label, just choices in a drop-down menu
96 };
98 #if GTK_CHECK_VERSION(2,12,0)
99 /* TODO need to have appropriate icons setup for these: */
100 static const gchar *floogles[] = {
101 GTK_STOCK_REMOVE,
102 GTK_STOCK_ADD,
103 GTK_STOCK_GO_DOWN,
104 GTK_STOCK_ABOUT,
105 GTK_STOCK_GO_UP,
106 0};
107 #endif /* GTK_CHECK_VERSION(2,12,0) */
109 typedef struct _EgeAdjustmentDescr EgeAdjustmentDescr;
111 struct _EgeAdjustmentDescr
112 {
113 gchar* descr;
114 gdouble value;
115 };
117 struct _EgeAdjustmentActionPrivate
118 {
119 GtkAdjustment* adj;
120 GtkTooltips* toolTips;
121 GtkWidget* focusWidget;
122 gdouble climbRate;
123 guint digits;
124 gdouble epsilon;
125 gchar* format;
126 gchar* selfId;
127 EgeWidgetFixup toolPost;
128 gdouble lastVal;
129 gdouble step;
130 gdouble page;
131 gint appearanceMode;
132 gboolean transferFocus;
133 GList* descriptions;
134 gchar* appearance;
135 };
137 #define EGE_ADJUSTMENT_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_ADJUSTMENT_ACTION_TYPE, EgeAdjustmentActionPrivate ) )
139 enum {
140 PROP_ADJUSTMENT = 1,
141 PROP_FOCUS_WIDGET,
142 PROP_CLIMB_RATE,
143 PROP_DIGITS,
144 PROP_SELFID,
145 PROP_TOOL_POST,
146 PROP_APPEARANCE
147 };
149 enum {
150 BUMP_TOP = 0,
151 BUMP_PAGE_UP,
152 BUMP_UP,
153 BUMP_NONE,
154 BUMP_DOWN,
155 BUMP_PAGE_DOWN,
156 BUMP_BOTTOM,
157 BUMP_CUSTOM = 100
158 };
160 GType ege_adjustment_action_get_type( void )
161 {
162 static GType myType = 0;
163 if ( !myType ) {
164 static const GTypeInfo myInfo = {
165 sizeof( EgeAdjustmentActionClass ),
166 NULL, /* base_init */
167 NULL, /* base_finalize */
168 (GClassInitFunc)ege_adjustment_action_class_init,
169 NULL, /* class_finalize */
170 NULL, /* class_data */
171 sizeof( EgeAdjustmentAction ),
172 0, /* n_preallocs */
173 (GInstanceInitFunc)ege_adjustment_action_init,
174 NULL
175 };
177 myType = g_type_register_static( GTK_TYPE_ACTION, "EgeAdjustmentAction", &myInfo, (GTypeFlags)0 );
178 }
180 return myType;
181 }
184 static void ege_adjustment_action_class_init( EgeAdjustmentActionClass* klass )
185 {
186 if ( klass ) {
187 gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
188 GObjectClass * objClass = G_OBJECT_CLASS( klass );
190 gDataName = g_quark_from_string("ege-adj-action");
192 objClass->finalize = ege_adjustment_action_finalize;
194 objClass->get_property = ege_adjustment_action_get_property;
195 objClass->set_property = ege_adjustment_action_set_property;
197 klass->parent_class.create_menu_item = create_menu_item;
198 klass->parent_class.create_tool_item = create_tool_item;
199 klass->parent_class.connect_proxy = connect_proxy;
200 klass->parent_class.disconnect_proxy = disconnect_proxy;
202 g_object_class_install_property( objClass,
203 PROP_ADJUSTMENT,
204 g_param_spec_object( "adjustment",
205 "Adjustment",
206 "The adjustment to change",
207 GTK_TYPE_ADJUSTMENT,
208 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
210 g_object_class_install_property( objClass,
211 PROP_FOCUS_WIDGET,
212 g_param_spec_pointer( "focus-widget",
213 "Focus Widget",
214 "The widget to return focus to",
215 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
217 g_object_class_install_property( objClass,
218 PROP_CLIMB_RATE,
219 g_param_spec_double( "climb-rate",
220 "Climb Rate",
221 "The acelleraton rate",
222 0.0, G_MAXDOUBLE, 0.0,
223 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
225 g_object_class_install_property( objClass,
226 PROP_DIGITS,
227 g_param_spec_uint( "digits",
228 "Digits",
229 "The number of digits to show",
230 0, 20, 0,
231 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
233 g_object_class_install_property( objClass,
234 PROP_SELFID,
235 g_param_spec_string( "self-id",
236 "Self ID",
237 "Marker for self pointer",
238 0,
239 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
241 g_object_class_install_property( objClass,
242 PROP_TOOL_POST,
243 g_param_spec_pointer( "tool-post",
244 "Tool Widget post process",
245 "Function for final adjustments",
246 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
248 g_object_class_install_property( objClass,
249 PROP_APPEARANCE,
250 g_param_spec_string( "appearance",
251 "Appearance hint",
252 "A hint for how to display",
253 "",
254 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
256 g_type_class_add_private( klass, sizeof(EgeAdjustmentActionClass) );
257 }
258 }
260 static void ege_adjustment_action_init( EgeAdjustmentAction* action )
261 {
262 action->private_data = EGE_ADJUSTMENT_ACTION_GET_PRIVATE( action );
263 action->private_data->adj = 0;
264 action->private_data->toolTips = 0;
265 action->private_data->focusWidget = 0;
266 action->private_data->climbRate = 0.0;
267 action->private_data->digits = 2;
268 action->private_data->epsilon = 0.009;
269 action->private_data->format = g_strdup_printf("%%0.%df%%s%%s", action->private_data->digits);
270 action->private_data->selfId = 0;
271 action->private_data->toolPost = 0;
272 action->private_data->lastVal = 0.0;
273 action->private_data->step = 0.0;
274 action->private_data->page = 0.0;
275 action->private_data->appearanceMode = APPEARANCE_NONE;
276 action->private_data->transferFocus = FALSE;
277 action->private_data->descriptions = 0;
278 action->private_data->appearance = 0;
279 }
281 static void ege_adjustment_action_finalize( GObject* object )
282 {
283 EgeAdjustmentAction* action = 0;
284 g_return_if_fail( object != NULL );
285 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(object) );
287 action = EGE_ADJUSTMENT_ACTION( object );
289 if ( action->private_data->format ) {
290 g_free( action->private_data->format );
291 action->private_data->format = 0;
292 }
294 egeAct_free_all_descriptions( action );
296 if ( G_OBJECT_CLASS(gParentClass)->finalize ) {
297 (*G_OBJECT_CLASS(gParentClass)->finalize)(object);
298 }
299 }
301 EgeAdjustmentAction* ege_adjustment_action_new( GtkAdjustment* adjustment,
302 const gchar *name,
303 const gchar *label,
304 const gchar *tooltip,
305 const gchar *stock_id,
306 gdouble climb_rate,
307 guint digits )
308 {
309 GObject* obj = (GObject*)g_object_new( EGE_ADJUSTMENT_ACTION_TYPE,
310 "name", name,
311 "label", label,
312 "tooltip", tooltip,
313 "stock_id", stock_id,
314 "adjustment", adjustment,
315 "climb-rate", climb_rate,
316 "digits", digits,
317 NULL );
319 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
321 return action;
322 }
324 static void ege_adjustment_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
325 {
326 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
327 switch ( propId ) {
328 case PROP_ADJUSTMENT:
329 g_value_set_object( value, action->private_data->adj );
330 break;
332 case PROP_FOCUS_WIDGET:
333 g_value_set_pointer( value, action->private_data->focusWidget );
334 break;
336 case PROP_CLIMB_RATE:
337 g_value_set_double( value, action->private_data->climbRate );
338 break;
340 case PROP_DIGITS:
341 g_value_set_uint( value, action->private_data->digits );
342 break;
344 case PROP_SELFID:
345 g_value_set_string( value, action->private_data->selfId );
346 break;
348 case PROP_TOOL_POST:
349 g_value_set_pointer( value, (void*)action->private_data->toolPost );
350 break;
352 case PROP_APPEARANCE:
353 g_value_set_string( value, action->private_data->appearance );
354 break;
356 default:
357 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
358 }
359 }
361 void ege_adjustment_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
362 {
363 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( obj );
364 switch ( propId ) {
365 case PROP_ADJUSTMENT:
366 {
367 action->private_data->adj = GTK_ADJUSTMENT( g_value_get_object( value ) );
368 g_object_get( G_OBJECT(action->private_data->adj),
369 "step-increment", &action->private_data->step,
370 "page-increment", &action->private_data->page,
371 NULL );
372 }
373 break;
375 case PROP_FOCUS_WIDGET:
376 {
377 /* TODO unhook prior */
378 action->private_data->focusWidget = (GtkWidget*)g_value_get_pointer( value );
379 }
380 break;
382 case PROP_CLIMB_RATE:
383 {
384 /* TODO pass on */
385 action->private_data->climbRate = g_value_get_double( value );
386 }
387 break;
389 case PROP_DIGITS:
390 {
391 /* TODO pass on */
392 action->private_data->digits = g_value_get_uint( value );
393 switch ( action->private_data->digits ) {
394 case 0: action->private_data->epsilon = 0.9; break;
395 case 1: action->private_data->epsilon = 0.09; break;
396 case 2: action->private_data->epsilon = 0.009; break;
397 case 3: action->private_data->epsilon = 0.0009; break;
398 case 4: action->private_data->epsilon = 0.00009; break;
399 }
400 if ( action->private_data->format ) {
401 g_free( action->private_data->format );
402 }
403 action->private_data->format = g_strdup_printf("%%0.%df%%s%%s", action->private_data->digits);
404 }
405 break;
407 case PROP_SELFID:
408 {
409 /* TODO pass on */
410 gchar* prior = action->private_data->selfId;
411 action->private_data->selfId = g_value_dup_string( value );
412 g_free( prior );
413 }
414 break;
416 case PROP_TOOL_POST:
417 {
418 action->private_data->toolPost = (EgeWidgetFixup)g_value_get_pointer( value );
419 }
420 break;
422 case PROP_APPEARANCE:
423 {
424 gchar* tmp = action->private_data->appearance;
425 gchar* newVal = g_value_dup_string( value );
426 action->private_data->appearance = newVal;
427 g_free( tmp );
429 if ( !action->private_data->appearance || (strcmp("", newVal) == 0) ) {
430 action->private_data->appearanceMode = APPEARANCE_NONE;
431 } else if ( strcmp("full", newVal) == 0 ) {
432 action->private_data->appearanceMode = APPEARANCE_FULL;
433 } else if ( strcmp("compact", newVal) == 0 ) {
434 action->private_data->appearanceMode = APPEARANCE_COMPACT;
435 } else if ( strcmp("minimal", newVal) == 0 ) {
436 action->private_data->appearanceMode = APPEARANCE_MINIMAL;
437 } else {
438 action->private_data->appearanceMode = APPEARANCE_UNKNOWN;
439 }
440 }
441 break;
443 default:
444 G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
445 }
446 }
448 GtkAdjustment* ege_adjustment_action_get_adjustment( EgeAdjustmentAction* action )
449 {
450 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
452 return action->private_data->adj;
453 }
455 void ege_adjustment_action_set_focuswidget( EgeAdjustmentAction* action, GtkWidget* widget )
456 {
457 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(action) );
459 /* TODO unhook prior */
461 action->private_data->focusWidget = widget;
462 }
464 GtkWidget* ege_adjustment_action_get_focuswidget( EgeAdjustmentAction* action )
465 {
466 g_return_val_if_fail( IS_EGE_ADJUSTMENT_ACTION(action), NULL );
468 return action->private_data->focusWidget;
469 }
471 static void egeAct_free_description( gpointer data, gpointer user_data ) {
472 (void)user_data;
473 if ( data ) {
474 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)data;
475 if ( descr->descr ) {
476 g_free( descr->descr );
477 descr->descr = 0;
478 }
479 g_free( descr );
480 }
481 }
483 static void egeAct_free_all_descriptions( EgeAdjustmentAction* action )
484 {
485 if ( action->private_data->descriptions ) {
486 g_list_foreach( action->private_data->descriptions, egeAct_free_description, 0 );
487 g_list_free( action->private_data->descriptions );
488 action->private_data->descriptions = 0;
489 }
490 }
492 static gint egeAct_compare_descriptions( gconstpointer a, gconstpointer b )
493 {
494 gint val = 0;
496 EgeAdjustmentDescr const * aa = (EgeAdjustmentDescr const *)a;
497 EgeAdjustmentDescr const * bb = (EgeAdjustmentDescr const *)b;
499 if ( aa && bb ) {
500 if ( aa->value < bb->value ) {
501 val = -1;
502 } else if ( aa->value > bb->value ) {
503 val = 1;
504 }
505 }
507 return val;
508 }
510 void ege_adjustment_action_set_descriptions( EgeAdjustmentAction* action, gchar const** descriptions, gdouble const* values, guint count )
511 {
512 g_return_if_fail( IS_EGE_ADJUSTMENT_ACTION(action) );
514 egeAct_free_all_descriptions( action );
516 if ( count && descriptions && values ) {
517 guint i = 0;
518 for ( i = 0; i < count; i++ ) {
519 EgeAdjustmentDescr* descr = g_new0( EgeAdjustmentDescr, 1 );
520 descr->descr = descriptions[i] ? g_strdup( descriptions[i] ) : 0;
521 descr->value = values[i];
522 action->private_data->descriptions = g_list_insert_sorted( action->private_data->descriptions, (gpointer)descr, egeAct_compare_descriptions );
523 }
524 }
525 }
527 void ege_adjustment_action_set_appearance( EgeAdjustmentAction* action, gchar const* val )
528 {
529 g_object_set( G_OBJECT(action), "appearance", val, NULL );
530 }
532 static void process_menu_action( GtkWidget* obj, gpointer data )
533 {
534 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
535 if ( item->active ) {
536 EgeAdjustmentAction* act = (EgeAdjustmentAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
537 gint what = GPOINTER_TO_INT(data);
540 gdouble base = gtk_adjustment_get_value( act->private_data->adj );
541 gdouble lower = 0.0;
542 gdouble upper = 0.0;
543 gdouble step = 0.0;
544 gdouble page = 0.0;
545 g_object_get( G_OBJECT(act->private_data->adj),
546 "lower", &lower,
547 "upper", &upper,
548 "step-increment", &step,
549 "page-increment", &page,
550 NULL );
552 switch ( what ) {
553 case BUMP_TOP:
554 gtk_adjustment_set_value( act->private_data->adj, upper );
555 break;
557 case BUMP_PAGE_UP:
558 gtk_adjustment_set_value( act->private_data->adj, base + page );
559 break;
561 case BUMP_UP:
562 gtk_adjustment_set_value( act->private_data->adj, base + step );
563 break;
565 case BUMP_DOWN:
566 gtk_adjustment_set_value( act->private_data->adj, base - step );
567 break;
569 case BUMP_PAGE_DOWN:
570 gtk_adjustment_set_value( act->private_data->adj, base - page );
571 break;
573 case BUMP_BOTTOM:
574 gtk_adjustment_set_value( act->private_data->adj, lower );
575 break;
577 default:
578 if ( what >= BUMP_CUSTOM ) {
579 guint index = what - BUMP_CUSTOM;
580 if ( index < g_list_length( act->private_data->descriptions ) ) {
581 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)g_list_nth_data( act->private_data->descriptions, index );
582 if ( descr ) {
583 gtk_adjustment_set_value( act->private_data->adj, descr->value );
584 }
585 }
586 }
587 }
588 }
589 }
591 static void create_single_menu_item( GCallback toggleCb, int val, GtkWidget* menu, EgeAdjustmentAction* act, GtkWidget** dst, GSList** group, gdouble num, gboolean active )
592 {
593 char* str = 0;
594 EgeAdjustmentDescr* marker = 0;
595 GList* cur = act->private_data->descriptions;
597 while ( cur ) {
598 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)cur->data;
599 gdouble delta = num - descr->value;
600 if ( delta < 0.0 ) {
601 delta = -delta;
602 }
603 if ( delta < act->private_data->epsilon ) {
604 marker = descr;
605 break;
606 }
607 cur = g_list_next( cur );
608 }
610 str = g_strdup_printf( act->private_data->format, num,
611 ((marker && marker->descr) ? ": " : ""),
612 ((marker && marker->descr) ? marker->descr : ""));
614 *dst = gtk_radio_menu_item_new_with_label( *group, str );
615 if ( !*group) {
616 *group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(*dst) );
617 }
618 if ( active ) {
619 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(*dst), TRUE );
620 }
621 gtk_menu_shell_append( GTK_MENU_SHELL(menu), *dst );
622 g_object_set_qdata( G_OBJECT(*dst), gDataName, act );
624 g_signal_connect( G_OBJECT(*dst), "toggled", toggleCb, GINT_TO_POINTER(val) );
626 g_free(str);
627 }
629 static GList* flush_explicit_items( GList* descriptions,
630 GCallback toggleCb,
631 int val,
632 GtkWidget* menu,
633 EgeAdjustmentAction* act,
634 GtkWidget** dst,
635 GSList** group,
636 gdouble num )
637 {
638 GList* cur = descriptions;
640 if ( cur ) {
641 gdouble valUpper = num + act->private_data->epsilon;
642 gdouble valLower = num - act->private_data->epsilon;
644 EgeAdjustmentDescr* descr = (EgeAdjustmentDescr*)cur->data;
646 while ( cur && descr && (descr->value >= valLower) ) {
647 if ( descr->value > valUpper ) {
648 create_single_menu_item( toggleCb, val + g_list_position(act->private_data->descriptions, cur), menu, act, dst, group, descr->value, FALSE );
649 }
651 cur = g_list_previous( cur );
652 descr = cur ? (EgeAdjustmentDescr*)cur->data : 0;
653 }
654 }
656 return cur;
657 }
659 static GtkWidget* create_popup_number_menu( EgeAdjustmentAction* act )
660 {
661 GtkWidget* menu = gtk_menu_new();
663 GSList* group = 0;
664 GtkWidget* single = 0;
666 GList* addOns = g_list_last( act->private_data->descriptions );
668 gdouble base = gtk_adjustment_get_value( act->private_data->adj );
669 gdouble lower = 0.0;
670 gdouble upper = 0.0;
671 gdouble step = 0.0;
672 gdouble page = 0.0;
673 g_object_get( G_OBJECT(act->private_data->adj),
674 "lower", &lower,
675 "upper", &upper,
676 "step-increment", &step,
677 "page-increment", &page,
678 NULL );
681 if ( base < upper ) {
682 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, upper );
683 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_TOP, menu, act, &single, &group, upper, FALSE );
684 if ( (base + page) < upper ) {
685 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base + page );
686 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_PAGE_UP, menu, act, &single, &group, base + page, FALSE );
687 }
688 if ( (base + step) < upper ) {
689 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base + step );
690 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_UP, menu, act, &single, &group, base + step, FALSE );
691 }
692 }
694 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base );
695 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_NONE, menu, act, &single, &group, base, TRUE );
697 if ( base > lower ) {
698 if ( (base - step) > lower ) {
699 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base - step );
700 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_DOWN, menu, act, &single, &group, base - step, FALSE );
701 }
702 if ( (base - page) > lower ) {
703 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, base - page );
704 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_PAGE_DOWN, menu, act, &single, &group, base - page, FALSE );
705 }
706 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, lower );
707 create_single_menu_item( G_CALLBACK(process_menu_action), BUMP_BOTTOM, menu, act, &single, &group, lower, FALSE );
708 }
710 if ( act->private_data->descriptions ) {
711 gdouble value = ((EgeAdjustmentDescr*)act->private_data->descriptions->data)->value;
712 addOns = flush_explicit_items( addOns, G_CALLBACK(process_menu_action), BUMP_CUSTOM, menu, act, &single, &group, value );
713 }
715 return menu;
716 }
718 static GtkWidget* create_menu_item( GtkAction* action )
719 {
720 GtkWidget* item = 0;
722 if ( IS_EGE_ADJUSTMENT_ACTION(action) ) {
723 EgeAdjustmentAction* act = EGE_ADJUSTMENT_ACTION( action );
724 GValue value;
725 const gchar* sss = 0;
726 GtkWidget* subby = 0;
728 memset( &value, 0, sizeof(value) );
729 g_value_init( &value, G_TYPE_STRING );
730 g_object_get_property( G_OBJECT(action), "label", &value );
732 sss = g_value_get_string( &value );
734 item = gtk_menu_item_new_with_label( sss );
736 subby = create_popup_number_menu( act );
737 gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
738 gtk_widget_show_all( subby );
739 } else {
740 item = gParentClass->create_menu_item( action );
741 }
743 return item;
744 }
746 void value_changed_cb( GtkSpinButton* spin, EgeAdjustmentAction* act )
747 {
748 if ( GTK_WIDGET_HAS_FOCUS( GTK_WIDGET(spin) ) ) {
749 ege_adjustment_action_defocus( act );
750 }
751 }
753 static gboolean event_cb( EgeAdjustmentAction* act, GdkEvent* evt )
754 {
755 gboolean handled = FALSE;
756 if ( evt->type == GDK_BUTTON_PRESS ) {
757 if ( evt->button.button == 3 ) {
758 if ( IS_EGE_ADJUSTMENT_ACTION(act) ) {
759 GdkEventButton* btnevt = (GdkEventButton*)evt;
760 GtkWidget* menu = create_popup_number_menu(act);
761 gtk_widget_show_all( menu );
762 gtk_menu_popup( GTK_MENU(menu), NULL, NULL, NULL, NULL, btnevt->button, btnevt->time );
763 }
764 handled = TRUE;
765 }
766 }
768 return handled;
769 }
771 static gchar*
772 slider_format_falue (GtkScale* scale, gdouble value, gchar *label)
773 {
774 (void)scale;
775 return g_strdup_printf("%s %d", label, (int) round(value));
776 }
778 static GtkWidget* create_tool_item( GtkAction* action )
779 {
780 GtkWidget* item = 0;
782 if ( IS_EGE_ADJUSTMENT_ACTION(action) ) {
783 EgeAdjustmentAction* act = EGE_ADJUSTMENT_ACTION( action );
784 GtkWidget* spinbutton = 0;
785 GtkWidget* hb = gtk_hbox_new( FALSE, 5 );
787 GValue value;
788 memset( &value, 0, sizeof(value) );
789 g_value_init( &value, G_TYPE_STRING );
790 g_object_get_property( G_OBJECT(action), "short_label", &value );
791 const gchar* sss = g_value_get_string( &value );
793 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
794 spinbutton = gtk_hscale_new( act->private_data->adj);
795 gtk_widget_set_size_request(spinbutton, 100, -1);
796 gtk_scale_set_digits (GTK_SCALE(spinbutton), 0);
797 gtk_signal_connect(GTK_OBJECT(spinbutton), "format-value", GTK_SIGNAL_FUNC(slider_format_falue), (void *) sss);
799 #if GTK_CHECK_VERSION(2,12,0)
800 } else if ( act->private_data->appearanceMode == APPEARANCE_MINIMAL ) {
801 spinbutton = gtk_scale_button_new( GTK_ICON_SIZE_MENU, 0, 100, 2, 0 );
802 gtk_scale_button_set_adjustment( GTK_SCALE_BUTTON(spinbutton), act->private_data->adj );
803 gtk_scale_button_set_icons( GTK_SCALE_BUTTON(spinbutton), floogles );
804 #endif /* GTK_CHECK_VERSION(2,12,0) */
805 } else {
806 spinbutton = gtk_spin_button_new( act->private_data->adj, act->private_data->climbRate, act->private_data->digits );
807 }
809 item = GTK_WIDGET( gtk_tool_item_new() );
811 {
812 GValue tooltip;
813 memset( &tooltip, 0, sizeof(tooltip) );
814 g_value_init( &tooltip, G_TYPE_STRING );
815 g_object_get_property( G_OBJECT(action), "tooltip", &tooltip );
816 const gchar* tipstr = g_value_get_string( &tooltip );
817 if ( tipstr && *tipstr ) {
818 if ( !act->private_data->toolTips ) {
819 act->private_data->toolTips = gtk_tooltips_new();
820 }
821 gtk_tooltips_set_tip( act->private_data->toolTips, spinbutton, tipstr, 0 );
822 }
823 }
825 if ( act->private_data->appearanceMode != APPEARANCE_FULL ) {
826 GtkWidget* lbl = gtk_label_new( sss ? sss : "wwww" );
827 GtkWidget* filler1 = gtk_label_new(" ");
828 gtk_misc_set_alignment( GTK_MISC(lbl), 1.0, 0.5 );
829 gtk_box_pack_start( GTK_BOX(hb), filler1, FALSE, FALSE, 0 );
830 gtk_box_pack_start( GTK_BOX(hb), lbl, FALSE, FALSE, 0 );
831 }
833 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
834 gtk_box_pack_start( GTK_BOX(hb), spinbutton, TRUE, TRUE, 0 );
835 } else {
836 gtk_box_pack_start( GTK_BOX(hb), spinbutton, FALSE, FALSE, 0 );
837 }
839 gtk_container_add( GTK_CONTAINER(item), hb );
841 if ( act->private_data->selfId ) {
842 g_object_set_data( G_OBJECT(spinbutton), act->private_data->selfId, spinbutton );
843 }
845 g_signal_connect( G_OBJECT(spinbutton), "focus-in-event", G_CALLBACK(focus_in_cb), action );
846 g_signal_connect( G_OBJECT(spinbutton), "focus-out-event", G_CALLBACK(focus_out_cb), action );
847 g_signal_connect( G_OBJECT(spinbutton), "key-press-event", G_CALLBACK(keypress_cb), action );
849 g_signal_connect( G_OBJECT(spinbutton), "value-changed", G_CALLBACK(value_changed_cb), action );
851 g_signal_connect_swapped( G_OBJECT(spinbutton), "event", G_CALLBACK(event_cb), action );
852 if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
853 /* */
854 #if GTK_CHECK_VERSION(2,12,0)
855 } else if ( act->private_data->appearanceMode == APPEARANCE_MINIMAL ) {
856 /* */
857 #endif /* GTK_CHECK_VERSION(2,12,0) */
858 } else {
859 gtk_entry_set_width_chars( GTK_ENTRY(spinbutton), act->private_data->digits + 3 );
860 }
862 gtk_widget_show_all( item );
864 /* Shrink or whatnot after shown */
865 if ( act->private_data->toolPost ) {
866 act->private_data->toolPost( item );
867 }
868 } else {
869 item = gParentClass->create_tool_item( action );
870 }
872 return item;
873 }
875 static void connect_proxy( GtkAction *action, GtkWidget *proxy )
876 {
877 gParentClass->connect_proxy( action, proxy );
878 }
880 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
881 {
882 gParentClass->disconnect_proxy( action, proxy );
883 }
885 void ege_adjustment_action_defocus( EgeAdjustmentAction* action )
886 {
887 if ( action->private_data->transferFocus ) {
888 if ( action->private_data->focusWidget ) {
889 gtk_widget_grab_focus( action->private_data->focusWidget );
890 }
891 }
892 }
894 gboolean focus_in_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
895 {
896 (void)event;
897 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
898 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
899 if ( GTK_IS_SPIN_BUTTON(widget) ) {
900 action->private_data->lastVal = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
901 #if GTK_CHECK_VERSION(2,12,0)
902 } else if ( GTK_IS_SCALE_BUTTON(widget) ) {
903 action->private_data->lastVal = gtk_scale_button_get_value( GTK_SCALE_BUTTON(widget) );
904 #endif /* GTK_CHECK_VERSION(2,12,0) */
905 } else if (GTK_IS_RANGE(widget) ) {
906 action->private_data->lastVal = gtk_range_get_value( GTK_RANGE(widget) );
907 }
908 action->private_data->transferFocus = TRUE;
909 }
911 return FALSE; /* report event not consumed */
912 }
914 static gboolean focus_out_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
915 {
916 (void)widget;
917 (void)event;
918 if ( IS_EGE_ADJUSTMENT_ACTION(data) ) {
919 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION( data );
920 action->private_data->transferFocus = FALSE;
921 }
923 return FALSE; /* report event not consumed */
924 }
927 static gboolean process_tab( GtkWidget* widget, int direction )
928 {
929 gboolean handled = FALSE;
930 GtkWidget* parent = gtk_widget_get_parent(widget);
931 GtkWidget* gp = parent ? gtk_widget_get_parent(parent) : 0;
932 GtkWidget* ggp = gp ? gtk_widget_get_parent(gp) : 0;
934 if ( ggp && GTK_IS_TOOLBAR(ggp) ) {
935 GList* kids = gtk_container_get_children( GTK_CONTAINER(ggp) );
936 if ( kids ) {
937 GtkWidget* curr = widget;
938 while ( curr && (gtk_widget_get_parent(curr) != ggp) ) {
939 curr = gtk_widget_get_parent( curr );
940 }
941 if ( curr ) {
942 GList* mid = g_list_find( kids, curr );
943 while ( mid ) {
944 mid = ( direction < 0 ) ? g_list_previous(mid) : g_list_next(mid);
945 if ( mid && GTK_IS_TOOL_ITEM(mid->data) ) {
946 /* potential target */
947 GtkWidget* child = gtk_bin_get_child( GTK_BIN(mid->data) );
948 if ( child && GTK_IS_HBOX(child) ) { /* could be ours */
949 GList* subChildren = gtk_container_get_children( GTK_CONTAINER(child) );
950 if ( subChildren ) {
951 GList* last = g_list_last(subChildren);
952 if ( last && GTK_IS_SPIN_BUTTON(last->data) && GTK_WIDGET_IS_SENSITIVE( GTK_WIDGET(last->data) ) ) {
953 gtk_widget_grab_focus( GTK_WIDGET(last->data) );
954 handled = TRUE;
955 mid = 0; /* to stop loop */
956 }
958 g_list_free(subChildren);
959 }
960 }
961 }
962 }
963 }
964 g_list_free( kids );
965 }
966 }
968 return handled;
969 }
971 gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data )
972 {
973 gboolean wasConsumed = FALSE; /* default to report event not consumed */
974 EgeAdjustmentAction* action = EGE_ADJUSTMENT_ACTION(data);
975 guint key = 0;
976 gdk_keymap_translate_keyboard_state( gdk_keymap_get_for_display( gdk_display_get_default() ),
977 event->hardware_keycode, (GdkModifierType)event->state,
978 0, &key, 0, 0, 0 );
980 switch ( key ) {
981 case GDK_Escape:
982 {
983 action->private_data->transferFocus = TRUE;
984 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
985 ege_adjustment_action_defocus( action );
986 wasConsumed = TRUE;
987 }
988 break;
990 case GDK_Return:
991 case GDK_KP_Enter:
992 {
993 action->private_data->transferFocus = TRUE;
994 ege_adjustment_action_defocus( action );
995 wasConsumed = TRUE;
996 }
997 break;
999 case GDK_Tab:
1000 {
1001 action->private_data->transferFocus = FALSE;
1002 wasConsumed = process_tab( widget, 1 );
1003 }
1004 break;
1006 case GDK_ISO_Left_Tab:
1007 {
1008 action->private_data->transferFocus = FALSE;
1009 wasConsumed = process_tab( widget, -1 );
1010 }
1011 break;
1013 case GDK_Up:
1014 case GDK_KP_Up:
1015 {
1016 action->private_data->transferFocus = FALSE;
1017 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1018 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->step );
1019 wasConsumed = TRUE;
1020 }
1021 break;
1023 case GDK_Down:
1024 case GDK_KP_Down:
1025 {
1026 action->private_data->transferFocus = FALSE;
1027 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1028 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->step );
1029 wasConsumed = TRUE;
1030 }
1031 break;
1033 case GDK_Page_Up:
1034 case GDK_KP_Page_Up:
1035 {
1036 action->private_data->transferFocus = FALSE;
1037 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1038 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val + action->private_data->page );
1039 wasConsumed = TRUE;
1040 }
1041 break;
1043 case GDK_Page_Down:
1044 case GDK_KP_Page_Down:
1045 {
1046 action->private_data->transferFocus = FALSE;
1047 gdouble val = gtk_spin_button_get_value( GTK_SPIN_BUTTON(widget) );
1048 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), val - action->private_data->page );
1049 wasConsumed = TRUE;
1050 }
1051 break;
1053 case GDK_z:
1054 case GDK_Z:
1055 {
1056 action->private_data->transferFocus = FALSE;
1057 gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
1058 wasConsumed = TRUE;
1059 }
1060 break;
1062 }
1064 return wasConsumed;
1065 }