1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include "GxWheel.h"
20 
21 #define P_(s) (s)   // FIXME -> gettext
22 
23 struct _GxWheelPrivate
24 {
25 	int last_x;
26 };
27 
28 static gboolean gx_wheel_draw (GtkWidget *widget, cairo_t *cr);
29 static void gx_wheel_get_preferred_width (GtkWidget *widget, gint *min_width, gint *natural_width);
30 static void gx_wheel_get_preferred_height (GtkWidget *widget, gint *min_height, gint *natural_height);
31 static void gx_wheel_size_request (GtkWidget *widget, gint *width, gint *height);
32 static gboolean gx_wheel_button_press (GtkWidget *widget, GdkEventButton *event);
33 static gboolean gx_wheel_pointer_motion (GtkWidget *widget, GdkEventMotion *event);
34 
G_DEFINE_TYPE_WITH_PRIVATE(GxWheel,gx_wheel,GX_TYPE_REGLER)35 G_DEFINE_TYPE_WITH_PRIVATE(GxWheel, gx_wheel, GX_TYPE_REGLER)
36 
37 static void gx_wheel_class_init(GxWheelClass *klass)
38 {
39 	GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
40 
41 	widget_class->draw = gx_wheel_draw;
42 	widget_class->get_preferred_width = gx_wheel_get_preferred_width;
43 	widget_class->get_preferred_height = gx_wheel_get_preferred_height;
44 	widget_class->button_press_event = gx_wheel_button_press;
45 	widget_class->motion_notify_event = gx_wheel_pointer_motion;
46 	widget_class->enter_notify_event = NULL;
47 	widget_class->leave_notify_event = NULL;
48 
49 	gtk_widget_class_set_css_name(widget_class, "gx-wheel");
50 
51 	gtk_widget_class_install_style_property(
52 		widget_class,
53 		g_param_spec_int("framecount",
54 		                P_("framecount"),
55 		                P_("Number of frames in the animation specified by the gtkrc"),
56 		                -1, 250, -1,
57 		                GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
58 }
59 
get_image_dimensions(GtkWidget * widget,GdkPixbuf * pb,GdkRectangle * rect,gint * frame_count)60 static void get_image_dimensions (GtkWidget *widget, GdkPixbuf *pb,
61 										GdkRectangle *rect, gint *frame_count)
62 {
63 	gtk_widget_style_get (widget, "framecount",
64 							frame_count, NULL);
65 
66 	rect->width  = gdk_pixbuf_get_width(pb);
67 	rect->height = gdk_pixbuf_get_height(pb);
68 
69 	if (*frame_count >1)
70 		rect->width = (rect->width / *frame_count);
71 	if (*frame_count == 0) {// rc directs to assume square frames
72 		*frame_count = rect->width / rect->height;
73 		rect->width = rect->height;
74 
75 	}
76 }
77 
gx_wheel_draw(GtkWidget * widget,cairo_t * cr)78 static gboolean gx_wheel_draw (GtkWidget *widget, cairo_t *cr)
79 {
80 	g_assert(GX_IS_WHEEL(widget));
81 	GxRegler *regler = GX_REGLER(widget);
82 	GdkRectangle image_rect, value_rect;
83 	gint fcount, findex;
84 	gdouble wheelstate;
85 	gtk_widget_style_get (widget, "framecount", &fcount, NULL);
86 
87 	GdkPixbuf *wb = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
88 											 "wheel_back", -1,
89 											 GTK_ICON_LOOKUP_GENERIC_FALLBACK, nullptr);
90 	if (fcount > -1) {
91 
92 		wheelstate = _gx_regler_get_step_pos(regler, 1);
93 		get_image_dimensions (widget, wb, &image_rect, &fcount);
94 		_gx_regler_get_positions(regler, &image_rect, &value_rect, false);
95 
96 		fcount--; // zero based index
97 		findex = (int)(fcount * wheelstate);
98 		gdk_cairo_set_source_pixbuf (cr, wb, image_rect.x-(image_rect.width * findex), image_rect.y);
99 		cairo_rectangle(cr, image_rect.x, image_rect.y,image_rect.width, image_rect.height);
100 		cairo_fill(cr);
101 		_gx_regler_display_value(regler, cr, &value_rect);
102 	} else {
103 
104 		GdkPixbuf *ws = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
105 											 "wheel_fringe", -1,
106 											 GTK_ICON_LOOKUP_GENERIC_FALLBACK, nullptr);
107 		GdkPixbuf *wp = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
108 											 "wheel_pointer", -1,
109 											 GTK_ICON_LOOKUP_GENERIC_FALLBACK, nullptr);
110 
111 		image_rect.width = gdk_pixbuf_get_width(wb);
112 		image_rect.height = gdk_pixbuf_get_height(wb);
113 
114 		gint step = gdk_pixbuf_get_width(ws) / 2;
115 		wheelstate = _gx_regler_get_step_pos(regler, step);
116 		_gx_regler_get_positions(regler, &image_rect, &value_rect, false);
117 		GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
118 		int smoth_pointer = 0;
119 		if (wheelstate > (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj))) {
120 			smoth_pointer = -4;
121 		}
122 		gdk_cairo_set_source_pixbuf(cr, wb, image_rect.x, image_rect.y);
123 		cairo_paint(cr);
124 		//- ((int)(wheelstate) + image_rect.width)
125 		gdk_cairo_set_source_pixbuf(cr, ws, image_rect.x+wheelstate*0.6-4*image_rect.width, image_rect.y);
126 		cairo_rectangle(cr, image_rect.x, image_rect.y, image_rect.width, image_rect.height);
127 		cairo_fill(cr);
128 		gdk_cairo_set_source_pixbuf(cr, wp, image_rect.x+smoth_pointer+wheelstate*0.4, image_rect.y);
129 		cairo_rectangle(cr, image_rect.x+smoth_pointer+wheelstate*0.4, image_rect.y,
130 				gdk_pixbuf_get_width(wp), image_rect.height);
131 		cairo_fill(cr);
132 		_gx_regler_display_value(regler, cr, &value_rect);
133 
134 		g_clear_object(&ws);
135 		g_clear_object(&wp);
136 	}
137 
138 	g_clear_object(&wb);
139 	return TRUE;
140 }
141 
gx_wheel_get_preferred_width(GtkWidget * widget,gint * min_width,gint * natural_width)142 static void gx_wheel_get_preferred_width (GtkWidget *widget, gint *min_width, gint *natural_width)
143 {
144 	gint width, height;
145 	gx_wheel_size_request(widget, &width, &height);
146 
147 	if (min_width) {
148 		*min_width = width;
149 	}
150 	if (natural_width) {
151 		*natural_width = width;
152 	}
153 }
154 
gx_wheel_get_preferred_height(GtkWidget * widget,gint * min_height,gint * natural_height)155 static void gx_wheel_get_preferred_height (GtkWidget *widget, gint *min_height, gint *natural_height)
156 {
157 	gint width, height;
158 	gx_wheel_size_request(widget, &width, &height);
159 
160 	if (min_height) {
161 		*min_height = height;
162 	}
163 	if (natural_height) {
164 		*natural_height = height;
165 	}
166 }
167 
gx_wheel_size_request(GtkWidget * widget,gint * width,gint * height)168 static void gx_wheel_size_request (GtkWidget *widget, gint *width, gint *height)
169 {
170 	g_assert(GX_IS_WHEEL(widget));
171 	gint fcount;
172 	GdkRectangle rect;
173 
174     GdkPixbuf *wb = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
175 											 "wheel_back", -1,
176 											 GTK_ICON_LOOKUP_GENERIC_FALLBACK, nullptr);
177 	get_image_dimensions (widget, wb, &rect, &fcount);
178 	*width = rect.width;
179 	*height = rect.height;
180 	_gx_regler_calc_size_request(GX_REGLER(widget), width, height, TRUE);
181 	g_clear_object(&wb);
182 }
183 
wheel_set_from_pointer(GtkWidget * widget,gdouble x,gdouble y,gboolean drag,int state,int button,GdkEventButton * event)184 static gboolean wheel_set_from_pointer(GtkWidget *widget, gdouble x, gdouble y, gboolean drag, int state, int button, GdkEventButton *event)
185 {
186 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
187 	GdkPixbuf *wb = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
188 											 "wheel_back", -1,
189 											 GTK_ICON_LOOKUP_GENERIC_FALLBACK, nullptr);
190 	GdkRectangle image_rect, value_rect;
191 	GxWheel *wheel = GX_WHEEL(widget);
192 	GxWheelPrivate *priv = wheel->priv;
193 	gint fcount;
194 	get_image_dimensions (widget, wb, &image_rect, &fcount);
195 	GtkAllocation allocation;
196 	gtk_widget_get_allocation(widget, &allocation);
197 	x += allocation.x;
198 	y += allocation.y;
199 
200 	_gx_regler_get_positions(GX_REGLER(widget), &image_rect, &value_rect, false);
201 	if (!drag) {
202 		GdkRectangle *rect = NULL;
203 		if (_approx_in_rectangle(x, y, &image_rect)) {
204 			if (button == 3) {
205 				rect = &image_rect;
206 			}
207 		} else if (_approx_in_rectangle(x, y, &value_rect)) {
208 			if (button == 1 || button == 3) {
209 				rect = &value_rect;
210 			} else {
211 				return FALSE;
212 			}
213 		} else {
214 			return FALSE;
215 		}
216 		if (rect) {
217 			gboolean ret;
218 			g_signal_emit_by_name(GX_REGLER(widget), "value-entry", rect, event, &ret);
219 			return FALSE;
220 		}
221 	}
222 
223 	double value;
224         gdouble lower = gtk_adjustment_get_lower(adj);
225         gdouble upper = gtk_adjustment_get_upper(adj);
226         gdouble adj_value = gtk_adjustment_get_value(adj);
227 	if (!drag) {
228 		priv->last_x = x;
229 		if (event && event->type == GDK_2BUTTON_PRESS) {
230 		    const int frame = 5;
231 		    value = lower + ((x - (image_rect.x+frame)) * (upper - lower)) / (image_rect.width-2*frame);
232 		    gtk_range_set_value(GTK_RANGE(widget), value);
233 		}
234 		return TRUE;
235 	}
236 	int mode = ((state & GDK_CONTROL_MASK) == 0);
237 	const double scaling = 0.01;
238 	double scal = (mode ? scaling : scaling*0.1);
239 	value = adj_value + (((x - priv->last_x) * scal) * (upper - lower));
240 	priv->last_x = x;
241 	if (adj_value != value)
242 		gtk_range_set_value(GTK_RANGE(widget), value);
243 	g_clear_object(&wb);
244 	return TRUE;
245 }
246 
gx_wheel_button_press(GtkWidget * widget,GdkEventButton * event)247 static gboolean gx_wheel_button_press (GtkWidget *widget, GdkEventButton *event)
248 {
249 	g_assert(GX_IS_WHEEL(widget));
250 	if (event->button != 1 && event->button != 3) {
251 		return FALSE;
252 	}
253 	gtk_widget_grab_focus(widget);
254 	if (wheel_set_from_pointer(widget, event->x, event->y, FALSE, event->state, event->button, event)) {
255 		gtk_grab_add(widget);
256 	}
257 	return FALSE;
258 }
259 
260 /****************************************************************
261  ** set the value from mouse movement
262  */
263 
gx_wheel_pointer_motion(GtkWidget * widget,GdkEventMotion * event)264 static gboolean gx_wheel_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
265 {
266 	g_assert(GX_IS_WHEEL(widget));
267 	gdk_event_request_motions (event);
268 	if (!gtk_widget_has_grab(widget)) {
269 		return FALSE;
270 	}
271 	wheel_set_from_pointer(widget, event->x, event->y, TRUE, event->state, 0, NULL);
272 	return FALSE;
273 }
274 
gx_wheel_init(GxWheel * wheel)275 static void gx_wheel_init(GxWheel *wheel)
276 {
277 	wheel->priv = (GxWheelPrivate*)gx_wheel_get_instance_private(wheel);
278 }
279