1 | /* |
2 | * Copyright (C) 2011 Igalia S.L. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "BrowserCellRendererVariant.h" |
27 | #include "BrowserMarshal.h" |
28 | #include <errno.h> |
29 | |
30 | enum { |
31 | PROP_0, |
32 | |
33 | PROP_VALUE, |
34 | PROP_ADJUSTMENT |
35 | }; |
36 | |
37 | enum { |
38 | CHANGED, |
39 | |
40 | LAST_SIGNAL |
41 | }; |
42 | |
43 | struct _BrowserCellRendererVariant { |
44 | GtkCellRenderer parent; |
45 | |
46 | GValue *value; |
47 | |
48 | GtkCellRenderer *textRenderer; |
49 | GtkCellRenderer *toggleRenderer; |
50 | GtkCellRenderer *spinRenderer; |
51 | }; |
52 | |
53 | struct _BrowserCellRendererVariantClass { |
54 | GtkCellRendererClass parent; |
55 | }; |
56 | |
57 | static guint signals[LAST_SIGNAL] = { 0 }; |
58 | |
59 | G_DEFINE_TYPE(BrowserCellRendererVariant, browser_cell_renderer_variant, GTK_TYPE_CELL_RENDERER) |
60 | |
61 | static void browserCellRendererVariantFinalize(GObject *object) |
62 | { |
63 | BrowserCellRendererVariant *renderer = BROWSER_CELL_RENDERER_VARIANT(object); |
64 | |
65 | g_object_unref(renderer->toggleRenderer); |
66 | g_object_unref(renderer->spinRenderer); |
67 | g_object_unref(renderer->textRenderer); |
68 | if (renderer->value) |
69 | g_boxed_free(G_TYPE_VALUE, renderer->value); |
70 | |
71 | G_OBJECT_CLASS(browser_cell_renderer_variant_parent_class)->finalize(object); |
72 | } |
73 | |
74 | static void browserCellRendererVariantGetProperty(GObject *object, guint propId, GValue *value, GParamSpec *pspec) |
75 | { |
76 | BrowserCellRendererVariant *renderer = BROWSER_CELL_RENDERER_VARIANT(object); |
77 | |
78 | switch (propId) { |
79 | case PROP_VALUE: |
80 | g_value_set_boxed(value, renderer->value); |
81 | break; |
82 | case PROP_ADJUSTMENT: { |
83 | GtkAdjustment *adjustment = NULL; |
84 | g_object_get(G_OBJECT(renderer->spinRenderer), "adjustment" , &adjustment, NULL); |
85 | if (adjustment) { |
86 | g_value_set_object(value, adjustment); |
87 | g_object_unref(adjustment); |
88 | } |
89 | break; |
90 | } |
91 | default: |
92 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); |
93 | } |
94 | } |
95 | |
96 | static void browserCellRendererVariantSetModeForValue(BrowserCellRendererVariant *renderer) |
97 | { |
98 | if (!renderer->value) |
99 | return; |
100 | |
101 | GtkCellRendererMode mode; |
102 | if (G_VALUE_HOLDS_BOOLEAN(renderer->value)) |
103 | mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE; |
104 | else if (G_VALUE_HOLDS_STRING(renderer->value) || G_VALUE_HOLDS_UINT(renderer->value)) |
105 | mode = GTK_CELL_RENDERER_MODE_EDITABLE; |
106 | else |
107 | return; |
108 | |
109 | g_object_set(G_OBJECT(renderer), "mode" , mode, NULL); |
110 | } |
111 | |
112 | static void browserCellRendererVariantSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec) |
113 | { |
114 | BrowserCellRendererVariant *renderer = BROWSER_CELL_RENDERER_VARIANT(object); |
115 | |
116 | switch (propId) { |
117 | case PROP_VALUE: |
118 | if (renderer->value) |
119 | g_boxed_free(G_TYPE_VALUE, renderer->value); |
120 | renderer->value = g_value_dup_boxed(value); |
121 | browserCellRendererVariantSetModeForValue(renderer); |
122 | break; |
123 | case PROP_ADJUSTMENT: |
124 | g_object_set(G_OBJECT(renderer->spinRenderer), "adjustment" , g_value_get_object(value), NULL); |
125 | break; |
126 | default: |
127 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); |
128 | } |
129 | } |
130 | |
131 | static GtkCellRenderer *browserCellRendererVariantGetRendererForValue(BrowserCellRendererVariant *renderer) |
132 | { |
133 | if (!renderer->value) |
134 | return NULL; |
135 | |
136 | if (G_VALUE_HOLDS_BOOLEAN(renderer->value)) { |
137 | g_object_set(G_OBJECT(renderer->toggleRenderer), |
138 | "active" , g_value_get_boolean(renderer->value), |
139 | NULL); |
140 | return renderer->toggleRenderer; |
141 | } |
142 | |
143 | if (G_VALUE_HOLDS_STRING(renderer->value)) { |
144 | g_object_set(G_OBJECT(renderer->textRenderer), |
145 | "text" , g_value_get_string(renderer->value), |
146 | NULL); |
147 | return renderer->textRenderer; |
148 | } |
149 | |
150 | if (G_VALUE_HOLDS_UINT(renderer->value)) { |
151 | gchar *text = g_strdup_printf("%u" , g_value_get_uint(renderer->value)); |
152 | g_object_set(G_OBJECT(renderer->spinRenderer), "text" , text, NULL); |
153 | g_free(text); |
154 | return renderer->spinRenderer; |
155 | } |
156 | |
157 | return NULL; |
158 | } |
159 | |
160 | static void browserCellRendererVariantCellRendererTextEdited(BrowserCellRendererVariant *renderer, const gchar *path, const gchar *newText) |
161 | { |
162 | if (!renderer->value) |
163 | return; |
164 | |
165 | if (!G_VALUE_HOLDS_STRING(renderer->value)) |
166 | return; |
167 | |
168 | g_value_set_string(renderer->value, newText); |
169 | g_signal_emit(renderer, signals[CHANGED], 0, path, renderer->value); |
170 | } |
171 | |
172 | static void browserCellRendererVariantCellRendererSpinEdited(BrowserCellRendererVariant *renderer, const gchar *path, const gchar *newText) |
173 | { |
174 | if (!renderer->value) |
175 | return; |
176 | |
177 | if (!G_VALUE_HOLDS_UINT(renderer->value)) |
178 | return; |
179 | |
180 | GtkAdjustment *adjustment; |
181 | g_object_get(G_OBJECT(renderer->spinRenderer), "adjustment" , &adjustment, NULL); |
182 | if (!adjustment) |
183 | return; |
184 | |
185 | errno = 0; |
186 | gchar *endPtr; |
187 | gdouble value = g_strtod(newText, &endPtr); |
188 | if (errno || value > gtk_adjustment_get_upper(adjustment) || value < gtk_adjustment_get_lower(adjustment) || endPtr == newText) { |
189 | g_warning("Invalid input for cell: %s\n" , newText); |
190 | return; |
191 | } |
192 | |
193 | g_value_set_uint(renderer->value, (guint)value); |
194 | g_signal_emit(renderer, signals[CHANGED], 0, path, renderer->value); |
195 | } |
196 | |
197 | static gboolean browserCellRendererVariantCellRendererActivate(GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, const GdkRectangle *bgArea, const GdkRectangle *cellArea, GtkCellRendererState flags) |
198 | { |
199 | BrowserCellRendererVariant *renderer = BROWSER_CELL_RENDERER_VARIANT(cell); |
200 | |
201 | if (!renderer->value) |
202 | return TRUE; |
203 | |
204 | if (!G_VALUE_HOLDS_BOOLEAN(renderer->value)) |
205 | return TRUE; |
206 | |
207 | g_value_set_boolean(renderer->value, !g_value_get_boolean(renderer->value)); |
208 | g_signal_emit(renderer, signals[CHANGED], 0, path, renderer->value); |
209 | |
210 | return TRUE; |
211 | } |
212 | |
213 | static void browserCellRendererVariantCellRendererRender(GtkCellRenderer *cell, cairo_t *cr, GtkWidget *widget, const GdkRectangle *bgArea, const GdkRectangle *cellArea, GtkCellRendererState flags) |
214 | { |
215 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
216 | if (!renderer) |
217 | return; |
218 | |
219 | GTK_CELL_RENDERER_GET_CLASS(renderer)->render(renderer, cr, widget, bgArea, cellArea, flags); |
220 | } |
221 | |
222 | static GtkCellEditable *browserCellRendererVariantCellRendererStartEditing(GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, const GdkRectangle *bgArea, const GdkRectangle *cellArea, GtkCellRendererState flags) |
223 | { |
224 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
225 | if (!renderer) |
226 | return NULL; |
227 | |
228 | if (!GTK_CELL_RENDERER_GET_CLASS(renderer)->start_editing) |
229 | return NULL; |
230 | |
231 | return GTK_CELL_RENDERER_GET_CLASS(renderer)->start_editing(renderer, event, widget, path, bgArea, cellArea, flags); |
232 | } |
233 | |
234 | static void browserCellRendererVariantCellRendererGetPreferredWidth(GtkCellRenderer *cell, GtkWidget *widget, gint *minimumWidth, gint *naturalWidth) |
235 | { |
236 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
237 | if (!renderer) |
238 | return; |
239 | |
240 | GTK_CELL_RENDERER_GET_CLASS(renderer)->get_preferred_width(renderer, widget, minimumWidth, naturalWidth); |
241 | } |
242 | |
243 | static void browserCellRendererVariantCellRendererGetPreferredHeight(GtkCellRenderer *cell, GtkWidget *widget, gint *minimumHeight, gint *naturalHeight) |
244 | { |
245 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
246 | if (!renderer) |
247 | return; |
248 | |
249 | GTK_CELL_RENDERER_GET_CLASS(renderer)->get_preferred_height(renderer, widget, minimumHeight, naturalHeight); |
250 | } |
251 | |
252 | static void browserCellRendererVariantCellRendererGetPreferredWidthForHeight(GtkCellRenderer *cell, GtkWidget *widget, gint height, gint *minimumWidth, gint *naturalWidth) |
253 | { |
254 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
255 | if (!renderer) |
256 | return; |
257 | |
258 | GTK_CELL_RENDERER_GET_CLASS(renderer)->get_preferred_width_for_height(renderer, widget, height, minimumWidth, naturalWidth); |
259 | } |
260 | |
261 | static void browserCellRendererVariantCellRendererGetPreferredHeightForWidth(GtkCellRenderer *cell, GtkWidget *widget, gint width, gint *minimumHeight, gint *naturalHeight) |
262 | { |
263 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
264 | if (!renderer) |
265 | return; |
266 | |
267 | GTK_CELL_RENDERER_GET_CLASS(renderer)->get_preferred_height_for_width(renderer, widget, width, minimumHeight, naturalHeight); |
268 | } |
269 | |
270 | static void browserCellRendererVariantCellRendererGetAlignedArea(GtkCellRenderer *cell, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cellArea, GdkRectangle *alignedArea) |
271 | { |
272 | GtkCellRenderer *renderer = browserCellRendererVariantGetRendererForValue(BROWSER_CELL_RENDERER_VARIANT(cell)); |
273 | if (!renderer) |
274 | return; |
275 | |
276 | GTK_CELL_RENDERER_GET_CLASS(renderer)->get_aligned_area(renderer, widget, flags, cellArea, alignedArea); |
277 | } |
278 | |
279 | static void browser_cell_renderer_variant_init(BrowserCellRendererVariant *renderer) |
280 | { |
281 | g_object_set(renderer, "mode" , GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); |
282 | |
283 | renderer->toggleRenderer = gtk_cell_renderer_toggle_new(); |
284 | g_object_set(G_OBJECT(renderer->toggleRenderer), "xalign" , 0.0, NULL); |
285 | g_object_ref_sink(renderer->toggleRenderer); |
286 | |
287 | renderer->textRenderer = gtk_cell_renderer_text_new(); |
288 | g_signal_connect_swapped(renderer->textRenderer, "edited" , |
289 | G_CALLBACK(browserCellRendererVariantCellRendererTextEdited), renderer); |
290 | g_object_set(G_OBJECT(renderer->textRenderer), "editable" , TRUE, NULL); |
291 | g_object_ref_sink(renderer->textRenderer); |
292 | |
293 | renderer->spinRenderer = gtk_cell_renderer_spin_new(); |
294 | g_signal_connect_swapped(renderer->spinRenderer, "edited" , |
295 | G_CALLBACK(browserCellRendererVariantCellRendererSpinEdited), renderer); |
296 | g_object_set(G_OBJECT(renderer->spinRenderer), "editable" , TRUE, NULL); |
297 | } |
298 | |
299 | static void browser_cell_renderer_variant_class_init(BrowserCellRendererVariantClass *klass) |
300 | { |
301 | GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); |
302 | GtkCellRendererClass *cellRendererClass = GTK_CELL_RENDERER_CLASS(klass); |
303 | |
304 | gobjectClass->get_property = browserCellRendererVariantGetProperty; |
305 | gobjectClass->set_property = browserCellRendererVariantSetProperty; |
306 | gobjectClass->finalize = browserCellRendererVariantFinalize; |
307 | |
308 | cellRendererClass->activate = browserCellRendererVariantCellRendererActivate; |
309 | cellRendererClass->render = browserCellRendererVariantCellRendererRender; |
310 | cellRendererClass->start_editing = browserCellRendererVariantCellRendererStartEditing; |
311 | cellRendererClass->get_preferred_width = browserCellRendererVariantCellRendererGetPreferredWidth; |
312 | cellRendererClass->get_preferred_height = browserCellRendererVariantCellRendererGetPreferredHeight; |
313 | cellRendererClass->get_preferred_width_for_height = browserCellRendererVariantCellRendererGetPreferredWidthForHeight; |
314 | cellRendererClass->get_preferred_height_for_width = browserCellRendererVariantCellRendererGetPreferredHeightForWidth; |
315 | cellRendererClass->get_aligned_area = browserCellRendererVariantCellRendererGetAlignedArea; |
316 | |
317 | g_object_class_install_property(gobjectClass, |
318 | PROP_VALUE, |
319 | g_param_spec_boxed("value" , |
320 | "Value" , |
321 | "The cell renderer value" , |
322 | G_TYPE_VALUE, |
323 | G_PARAM_READWRITE)); |
324 | g_object_class_install_property(gobjectClass, |
325 | PROP_ADJUSTMENT, |
326 | g_param_spec_object("adjustment" , |
327 | "Adjustment" , |
328 | "The adjustment that holds the value of the spin button" , |
329 | GTK_TYPE_ADJUSTMENT, |
330 | G_PARAM_READWRITE)); |
331 | |
332 | signals[CHANGED] = |
333 | g_signal_new("changed" , |
334 | G_TYPE_FROM_CLASS(gobjectClass), |
335 | G_SIGNAL_RUN_LAST, |
336 | 0, NULL, NULL, |
337 | browser_marshal_VOID__STRING_BOXED, |
338 | G_TYPE_NONE, 2, |
339 | G_TYPE_STRING, G_TYPE_VALUE); |
340 | } |
341 | |
342 | GtkCellRenderer *browser_cell_renderer_variant_new(void) |
343 | { |
344 | return GTK_CELL_RENDERER(g_object_new(BROWSER_TYPE_CELL_RENDERER_VARIANT, NULL)); |
345 | } |
346 | |
347 | |