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