1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright 2012 Red Hat, Inc,
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marek Kasik <mkasik@redhat.com>
19 */
20
21 #include "config.h"
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <ctype.h>
26 #include <glib/gi18n-lib.h>
27
28 #include "pp-ipp-option-widget.h"
29 #include "pp-utils.h"
30
31 static void pp_ipp_option_widget_finalize (GObject *object);
32
33 static gboolean construct_widget (PpIPPOptionWidget *self);
34 static void update_widget (PpIPPOptionWidget *self);
35 static void update_widget_real (PpIPPOptionWidget *self);
36
37 struct _PpIPPOptionWidget
38 {
39 GtkBox parent_instance;
40
41 GtkWidget *switch_button;
42 GtkWidget *spin_button;
43 GtkWidget *combo;
44
45 IPPAttribute *option_supported;
46 IPPAttribute *option_default;
47
48 gchar *printer_name;
49 gchar *option_name;
50
51 GHashTable *ipp_attribute;
52
53 GCancellable *cancellable;
54 };
55
56 G_DEFINE_TYPE (PpIPPOptionWidget, pp_ipp_option_widget, GTK_TYPE_BOX)
57
58 static const struct {
59 const char *keyword;
60 const char *choice;
61 const char *translation;
62 } ipp_choice_translations[] = {
63 /* Translators: this is an option of "Two Sided" */
64 { "sides", "one-sided", N_("One Sided") },
65 /* Translators: this is an option of "Two Sided" */
66 { "sides", "two-sided-long-edge", N_("Long Edge (Standard)") },
67 /* Translators: this is an option of "Two Sided" */
68 { "sides", "two-sided-short-edge", N_("Short Edge (Flip)") },
69 /* Translators: this is an option of "Orientation" */
70 { "orientation-requested", "3", N_("Portrait") },
71 /* Translators: this is an option of "Orientation" */
72 { "orientation-requested", "4", N_("Landscape") },
73 /* Translators: this is an option of "Orientation" */
74 { "orientation-requested", "5", N_("Reverse landscape") },
75 /* Translators: this is an option of "Orientation" */
76 { "orientation-requested", "6", N_("Reverse portrait") },
77 };
78
79 static const gchar *
ipp_choice_translate(const gchar * option,const gchar * choice)80 ipp_choice_translate (const gchar *option,
81 const gchar *choice)
82 {
83 gint i;
84
85 for (i = 0; i < G_N_ELEMENTS (ipp_choice_translations); i++)
86 {
87 if (g_strcmp0 (ipp_choice_translations[i].keyword, option) == 0 &&
88 g_strcmp0 (ipp_choice_translations[i].choice, choice) == 0)
89 return _(ipp_choice_translations[i].translation);
90 }
91
92 return choice;
93 }
94
95 static void
pp_ipp_option_widget_class_init(PpIPPOptionWidgetClass * class)96 pp_ipp_option_widget_class_init (PpIPPOptionWidgetClass *class)
97 {
98 GObjectClass *object_class;
99
100 object_class = G_OBJECT_CLASS (class);
101
102 object_class->finalize = pp_ipp_option_widget_finalize;
103 }
104
105 static void
pp_ipp_option_widget_init(PpIPPOptionWidget * self)106 pp_ipp_option_widget_init (PpIPPOptionWidget *self)
107 {
108 gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
109 GTK_ORIENTATION_HORIZONTAL);
110 }
111
112 static void
pp_ipp_option_widget_finalize(GObject * object)113 pp_ipp_option_widget_finalize (GObject *object)
114 {
115 PpIPPOptionWidget *self = PP_IPP_OPTION_WIDGET (object);
116
117 g_cancellable_cancel (self->cancellable);
118
119 g_clear_pointer (&self->option_name, g_free);
120 g_clear_pointer (&self->printer_name, g_free);
121 g_clear_pointer (&self->option_supported, ipp_attribute_free);
122 g_clear_pointer (&self->option_default, ipp_attribute_free);
123 g_clear_pointer (&self->ipp_attribute, g_hash_table_unref);
124 g_clear_object (&self->cancellable);
125
126 G_OBJECT_CLASS (pp_ipp_option_widget_parent_class)->finalize (object);
127 }
128
129 GtkWidget *
pp_ipp_option_widget_new(IPPAttribute * attr_supported,IPPAttribute * attr_default,const gchar * option_name,const gchar * printer)130 pp_ipp_option_widget_new (IPPAttribute *attr_supported,
131 IPPAttribute *attr_default,
132 const gchar *option_name,
133 const gchar *printer)
134 {
135 PpIPPOptionWidget *self = NULL;
136
137 if (attr_supported && option_name && printer)
138 {
139 self = g_object_new (PP_TYPE_IPP_OPTION_WIDGET, NULL);
140
141 self->printer_name = g_strdup (printer);
142 self->option_name = g_strdup (option_name);
143 self->option_supported = ipp_attribute_copy (attr_supported);
144 self->option_default = ipp_attribute_copy (attr_default);
145
146 if (construct_widget (self))
147 {
148 update_widget_real (self);
149 }
150 else
151 {
152 g_object_ref_sink (self);
153 g_object_unref (self);
154 self = NULL;
155 }
156 }
157
158 return (GtkWidget *) self;
159 }
160
161 enum {
162 NAME_COLUMN,
163 VALUE_COLUMN,
164 N_COLUMNS
165 };
166
167 static GtkWidget *
combo_box_new(void)168 combo_box_new (void)
169 {
170 GtkCellRenderer *cell;
171 g_autoptr(GtkListStore) store = NULL;
172 GtkWidget *combo_box;
173
174 combo_box = gtk_combo_box_new ();
175
176 store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
177 gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
178
179 cell = gtk_cell_renderer_text_new ();
180 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
181 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
182 "text", NAME_COLUMN,
183 NULL);
184
185 return combo_box;
186 }
187
188 static void
combo_box_append(GtkWidget * combo,const gchar * display_text,const gchar * value)189 combo_box_append (GtkWidget *combo,
190 const gchar *display_text,
191 const gchar *value)
192 {
193 GtkTreeModel *model;
194 GtkListStore *store;
195 GtkTreeIter iter;
196
197 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
198 store = GTK_LIST_STORE (model);
199
200 gtk_list_store_append (store, &iter);
201 gtk_list_store_set (store, &iter,
202 NAME_COLUMN, display_text,
203 VALUE_COLUMN, value,
204 -1);
205 }
206
207 struct ComboSet {
208 GtkComboBox *combo;
209 const gchar *value;
210 };
211
212 static gboolean
set_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)213 set_cb (GtkTreeModel *model,
214 GtkTreePath *path,
215 GtkTreeIter *iter,
216 gpointer data)
217 {
218 struct ComboSet *set_data = data;
219 g_autofree gchar *value = NULL;
220
221 gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
222 if (strcmp (value, set_data->value) == 0)
223 {
224 gtk_combo_box_set_active_iter (set_data->combo, iter);
225 return TRUE;
226 }
227
228 return FALSE;
229 }
230
231 static void
combo_box_set(GtkWidget * combo,const gchar * value)232 combo_box_set (GtkWidget *combo,
233 const gchar *value)
234 {
235 struct ComboSet set_data;
236 GtkTreeModel *model;
237
238 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
239
240 set_data.combo = GTK_COMBO_BOX (combo);
241 set_data.value = value;
242 gtk_tree_model_foreach (model, set_cb, &set_data);
243 }
244
245 static char *
combo_box_get(GtkWidget * combo)246 combo_box_get (GtkWidget *combo)
247 {
248 GtkTreeModel *model;
249 GtkTreeIter iter;
250 gchar *value = NULL;
251
252 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
253
254 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
255 gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
256
257 return value;
258 }
259
260 static void
printer_add_option_async_cb(gboolean success,gpointer user_data)261 printer_add_option_async_cb (gboolean success,
262 gpointer user_data)
263 {
264 PpIPPOptionWidget *self = user_data;
265
266 update_widget (user_data);
267 g_clear_object (&self->cancellable);
268 }
269
270 static void
switch_changed_cb(PpIPPOptionWidget * self)271 switch_changed_cb (PpIPPOptionWidget *self)
272 {
273 gchar **values;
274
275 values = g_new0 (gchar *, 2);
276
277 if (gtk_switch_get_active (GTK_SWITCH (self->switch_button)))
278 values[0] = g_strdup ("True");
279 else
280 values[0] = g_strdup ("False");
281
282 g_cancellable_cancel (self->cancellable);
283 g_clear_object (&self->cancellable);
284
285 self->cancellable = g_cancellable_new ();
286 printer_add_option_async (self->printer_name,
287 self->option_name,
288 values,
289 TRUE,
290 self->cancellable,
291 printer_add_option_async_cb,
292 self);
293
294 g_strfreev (values);
295 }
296
297 static void
combo_changed_cb(PpIPPOptionWidget * self)298 combo_changed_cb (PpIPPOptionWidget *self)
299 {
300 gchar **values;
301
302 values = g_new0 (gchar *, 2);
303 values[0] = combo_box_get (self->combo);
304
305 g_cancellable_cancel (self->cancellable);
306 g_clear_object (&self->cancellable);
307
308 self->cancellable = g_cancellable_new ();
309 printer_add_option_async (self->printer_name,
310 self->option_name,
311 values,
312 TRUE,
313 self->cancellable,
314 printer_add_option_async_cb,
315 self);
316
317 g_strfreev (values);
318 }
319
320 static void
spin_button_changed_cb(PpIPPOptionWidget * self)321 spin_button_changed_cb (PpIPPOptionWidget *self)
322 {
323 gchar **values;
324
325 values = g_new0 (gchar *, 2);
326 values[0] = g_strdup_printf ("%d", gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->spin_button)));
327
328 g_cancellable_cancel (self->cancellable);
329 g_clear_object (&self->cancellable);
330
331 self->cancellable = g_cancellable_new ();
332 printer_add_option_async (self->printer_name,
333 self->option_name,
334 values,
335 TRUE,
336 self->cancellable,
337 printer_add_option_async_cb,
338 self);
339
340 g_strfreev (values);
341 }
342
343 static gboolean
construct_widget(PpIPPOptionWidget * self)344 construct_widget (PpIPPOptionWidget *self)
345 {
346 gboolean trivial_option = FALSE;
347 gboolean result = FALSE;
348 gint i;
349
350 if (self->option_supported)
351 {
352 switch (self->option_supported->attribute_type)
353 {
354 case IPP_ATTRIBUTE_TYPE_INTEGER:
355 if (self->option_supported->num_of_values <= 1)
356 trivial_option = TRUE;
357 break;
358
359 case IPP_ATTRIBUTE_TYPE_STRING:
360 if (self->option_supported->num_of_values <= 1)
361 trivial_option = TRUE;
362 break;
363
364 case IPP_ATTRIBUTE_TYPE_RANGE:
365 if (self->option_supported->attribute_values[0].lower_range ==
366 self->option_supported->attribute_values[0].upper_range)
367 trivial_option = TRUE;
368 break;
369 }
370
371 if (!trivial_option)
372 {
373 switch (self->option_supported->attribute_type)
374 {
375 case IPP_ATTRIBUTE_TYPE_BOOLEAN:
376 self->switch_button = gtk_switch_new ();
377 gtk_widget_show (self->switch_button);
378
379 gtk_box_pack_start (GTK_BOX (self), self->switch_button, FALSE, FALSE, 0);
380 g_signal_connect_object (self->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), self, G_CONNECT_SWAPPED);
381 break;
382
383 case IPP_ATTRIBUTE_TYPE_INTEGER:
384 self->combo = combo_box_new ();
385 gtk_widget_show (self->combo);
386
387 for (i = 0; i < self->option_supported->num_of_values; i++)
388 {
389 g_autofree gchar *value = NULL;
390
391 value = g_strdup_printf ("%d", self->option_supported->attribute_values[i].integer_value);
392 combo_box_append (self->combo,
393 ipp_choice_translate (self->option_name,
394 value),
395 value);
396 }
397
398 gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
399 g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
400 break;
401
402 case IPP_ATTRIBUTE_TYPE_STRING:
403 self->combo = combo_box_new ();
404 gtk_widget_show (self->combo);
405
406 for (i = 0; i < self->option_supported->num_of_values; i++)
407 combo_box_append (self->combo,
408 ipp_choice_translate (self->option_name,
409 self->option_supported->attribute_values[i].string_value),
410 self->option_supported->attribute_values[i].string_value);
411
412 gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
413 g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
414 break;
415
416 case IPP_ATTRIBUTE_TYPE_RANGE:
417 self->spin_button = gtk_spin_button_new_with_range (
418 self->option_supported->attribute_values[0].lower_range,
419 self->option_supported->attribute_values[0].upper_range,
420 1);
421 gtk_widget_show (self->spin_button);
422
423 gtk_box_pack_start (GTK_BOX (self), self->spin_button, FALSE, FALSE, 0);
424 g_signal_connect_object (self->spin_button, "value-changed", G_CALLBACK (spin_button_changed_cb), self, G_CONNECT_SWAPPED);
425 break;
426
427 default:
428 break;
429 }
430
431 result = TRUE;
432 }
433 }
434
435 return result;
436 }
437
438 static void
update_widget_real(PpIPPOptionWidget * self)439 update_widget_real (PpIPPOptionWidget *self)
440 {
441 IPPAttribute *attr = NULL;
442
443 if (self->option_default)
444 {
445 attr = ipp_attribute_copy (self->option_default);
446
447 ipp_attribute_free (self->option_default);
448 self->option_default = NULL;
449 }
450 else if (self->ipp_attribute)
451 {
452 g_autofree gchar *attr_name = g_strdup_printf ("%s-default", self->option_name);
453 attr = ipp_attribute_copy (g_hash_table_lookup (self->ipp_attribute, attr_name));
454
455 g_hash_table_unref (self->ipp_attribute);
456 self->ipp_attribute = NULL;
457 }
458
459 switch (self->option_supported->attribute_type)
460 {
461 case IPP_ATTRIBUTE_TYPE_BOOLEAN:
462 g_signal_handlers_block_by_func (self->switch_button, switch_changed_cb, self);
463
464 if (attr && attr->num_of_values > 0 &&
465 attr->attribute_type == IPP_ATTRIBUTE_TYPE_BOOLEAN)
466 {
467 gtk_switch_set_active (GTK_SWITCH (self->switch_button),
468 attr->attribute_values[0].boolean_value);
469 }
470
471 g_signal_handlers_unblock_by_func (self->switch_button, switch_changed_cb, self);
472 break;
473
474 case IPP_ATTRIBUTE_TYPE_INTEGER:
475 g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
476
477 if (attr && attr->num_of_values > 0 &&
478 attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
479 {
480 g_autofree gchar *value = g_strdup_printf ("%d", attr->attribute_values[0].integer_value);
481 combo_box_set (self->combo, value);
482 }
483 else
484 {
485 g_autofree gchar *value = g_strdup_printf ("%d", self->option_supported->attribute_values[0].integer_value);
486 combo_box_set (self->combo, value);
487 }
488
489 g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
490 break;
491
492 case IPP_ATTRIBUTE_TYPE_STRING:
493 g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
494
495 if (attr && attr->num_of_values > 0 &&
496 attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING)
497 {
498 combo_box_set (self->combo, attr->attribute_values[0].string_value);
499 }
500 else
501 {
502 combo_box_set (self->combo, self->option_supported->attribute_values[0].string_value);
503 }
504
505 g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
506 break;
507
508 case IPP_ATTRIBUTE_TYPE_RANGE:
509 g_signal_handlers_block_by_func (self->spin_button, spin_button_changed_cb, self);
510
511 if (attr && attr->num_of_values > 0 &&
512 attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
513 {
514 gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->spin_button),
515 attr->attribute_values[0].integer_value);
516 }
517 else
518 {
519 gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->spin_button),
520 self->option_supported->attribute_values[0].lower_range);
521 }
522
523 g_signal_handlers_unblock_by_func (self->spin_button, spin_button_changed_cb, self);
524 break;
525
526 default:
527 break;
528 }
529
530 ipp_attribute_free (attr);
531 }
532
533 static void
get_ipp_attributes_cb(GHashTable * table,gpointer user_data)534 get_ipp_attributes_cb (GHashTable *table,
535 gpointer user_data)
536 {
537 PpIPPOptionWidget *self = user_data;
538
539 if (self->ipp_attribute)
540 g_hash_table_unref (self->ipp_attribute);
541
542 self->ipp_attribute = g_hash_table_ref (table);
543
544 update_widget_real (self);
545 }
546
547 static void
update_widget(PpIPPOptionWidget * self)548 update_widget (PpIPPOptionWidget *self)
549 {
550 gchar **attributes_names;
551
552 attributes_names = g_new0 (gchar *, 2);
553 attributes_names[0] = g_strdup_printf ("%s-default", self->option_name);
554
555 get_ipp_attributes_async (self->printer_name,
556 attributes_names,
557 get_ipp_attributes_cb,
558 self);
559
560 g_strfreev (attributes_names);
561 }
562