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
30enum {
31 PROP_0,
32
33 PROP_VALUE,
34 PROP_ADJUSTMENT
35};
36
37enum {
38 CHANGED,
39
40 LAST_SIGNAL
41};
42
43struct _BrowserCellRendererVariant {
44 GtkCellRenderer parent;
45
46 GValue *value;
47
48 GtkCellRenderer *textRenderer;
49 GtkCellRenderer *toggleRenderer;
50 GtkCellRenderer *spinRenderer;
51};
52
53struct _BrowserCellRendererVariantClass {
54 GtkCellRendererClass parent;
55};
56
57static guint signals[LAST_SIGNAL] = { 0 };
58
59G_DEFINE_TYPE(BrowserCellRendererVariant, browser_cell_renderer_variant, GTK_TYPE_CELL_RENDERER)
60
61static 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
74static 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
96static 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
112static 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
131static 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
160static 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
172static 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
197static 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
213static 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
222static 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
234static 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
243static 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
252static 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
261static 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
270static 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
279static 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
299static 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
342GtkCellRenderer *browser_cell_renderer_variant_new(void)
343{
344 return GTK_CELL_RENDERER(g_object_new(BROWSER_TYPE_CELL_RENDERER_VARIANT, NULL));
345}
346
347