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 #include <glib/gstdio.h>
28 
29 #include "pp-ppd-option-widget.h"
30 #include "pp-utils.h"
31 
32 static void pp_ppd_option_widget_finalize (GObject *object);
33 
34 static gboolean construct_widget   (PpPPDOptionWidget *self);
35 static void     update_widget      (PpPPDOptionWidget *self);
36 static void     update_widget_real (PpPPDOptionWidget *self);
37 
38 struct _PpPPDOptionWidget
39 {
40   GtkBox parent_instance;
41 
42   GtkWidget *switch_button;
43   GtkWidget *combo;
44   GtkWidget *image;
45   GtkWidget *box;
46 
47   ppd_option_t *option;
48 
49   gchar *printer_name;
50   gchar *option_name;
51 
52   cups_dest_t *destination;
53   gboolean     destination_set;
54 
55   gchar    *ppd_filename;
56   gboolean  ppd_filename_set;
57 
58   GCancellable *cancellable;
59 };
60 
61 G_DEFINE_TYPE (PpPPDOptionWidget, pp_ppd_option_widget, GTK_TYPE_BOX)
62 
63 /* This list comes from Gtk+ */
64 static const struct {
65   const char *keyword;
66   const char *choice;
67   const char *translation;
68 } ppd_choice_translations[] = {
69   { "Duplex", "None", N_("One Sided") },
70   /* Translators: this is an option of "Two Sided" */
71   { "Duplex", "DuplexNoTumble", N_("Long Edge (Standard)") },
72   /* Translators: this is an option of "Two Sided" */
73   { "Duplex", "DuplexTumble", N_("Short Edge (Flip)") },
74   /* Translators: this is an option of "Paper Source" */
75   { "InputSlot", "Auto", N_("Auto Select") },
76   /* Translators: this is an option of "Paper Source" */
77   { "InputSlot", "AutoSelect", N_("Auto Select") },
78   /* Translators: this is an option of "Paper Source" */
79   { "InputSlot", "Default", N_("Printer Default") },
80   /* Translators: this is an option of "Paper Source" */
81   { "InputSlot", "None", N_("Printer Default") },
82   /* Translators: this is an option of "Paper Source" */
83   { "InputSlot", "PrinterDefault", N_("Printer Default") },
84   /* Translators: this is an option of "Paper Source" */
85   { "InputSlot", "Unspecified", N_("Auto Select") },
86   /* Translators: this is an option of "Resolution" */
87   { "Resolution", "default", N_("Printer Default") },
88   /* Translators: this is an option of "GhostScript" */
89   { "PreFilter", "EmbedFonts", N_("Embed GhostScript fonts only") },
90   /* Translators: this is an option of "GhostScript" */
91   { "PreFilter", "Level1", N_("Convert to PS level 1") },
92   /* Translators: this is an option of "GhostScript" */
93   { "PreFilter", "Level2", N_("Convert to PS level 2") },
94   /* Translators: this is an option of "GhostScript" */
95   { "PreFilter", "No", N_("No pre-filtering") },
96 };
97 
98 static ppd_option_t *
cups_option_copy(ppd_option_t * option)99 cups_option_copy (ppd_option_t *option)
100 {
101   ppd_option_t *result;
102   gint          i;
103 
104   result = g_new0 (ppd_option_t, 1);
105 
106   *result = *option;
107 
108   result->choices = g_new (ppd_choice_t, result->num_choices);
109   for (i = 0; i < result->num_choices; i++)
110     {
111       result->choices[i] = option->choices[i];
112       result->choices[i].code = g_strdup (option->choices[i].code);
113       result->choices[i].option = result;
114     }
115 
116   return result;
117 }
118 
119 static void
cups_option_free(ppd_option_t * option)120 cups_option_free (ppd_option_t *option)
121 {
122   gint i;
123 
124   if (option)
125     {
126       for (i = 0; i < option->num_choices; i++)
127         g_free (option->choices[i].code);
128 
129       g_free (option->choices);
130       g_free (option);
131     }
132 }
133 
134 static void
pp_ppd_option_widget_class_init(PpPPDOptionWidgetClass * class)135 pp_ppd_option_widget_class_init (PpPPDOptionWidgetClass *class)
136 {
137   GObjectClass *object_class;
138 
139   object_class = G_OBJECT_CLASS (class);
140 
141   object_class->finalize = pp_ppd_option_widget_finalize;
142 }
143 
144 static void
pp_ppd_option_widget_init(PpPPDOptionWidget * self)145 pp_ppd_option_widget_init (PpPPDOptionWidget *self)
146 {
147   gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
148                                   GTK_ORIENTATION_HORIZONTAL);
149 }
150 
151 static void
pp_ppd_option_widget_finalize(GObject * object)152 pp_ppd_option_widget_finalize (GObject *object)
153 {
154   PpPPDOptionWidget *self = PP_PPD_OPTION_WIDGET (object);
155 
156   g_cancellable_cancel (self->cancellable);
157   if (self->ppd_filename)
158     g_unlink (self->ppd_filename);
159 
160   g_clear_pointer (&self->option, cups_option_free);
161   g_clear_pointer (&self->printer_name, g_free);
162   g_clear_pointer (&self->option_name, g_free);
163   if (self->destination)
164     {
165       cupsFreeDests (1, self->destination);
166       self->destination = NULL;
167     }
168   g_clear_pointer (&self->ppd_filename, g_free);
169   g_clear_object (&self->cancellable);
170 
171   G_OBJECT_CLASS (pp_ppd_option_widget_parent_class)->finalize (object);
172 }
173 
174 static const gchar *
ppd_choice_translate(ppd_choice_t * choice)175 ppd_choice_translate (ppd_choice_t *choice)
176 {
177   const gchar *keyword = choice->option->keyword;
178   gint         i;
179 
180   for (i = 0; i < G_N_ELEMENTS (ppd_choice_translations); i++)
181     {
182       if (g_strcmp0 (ppd_choice_translations[i].keyword, keyword) == 0 &&
183 	  g_strcmp0 (ppd_choice_translations[i].choice, choice->choice) == 0)
184 	return _(ppd_choice_translations[i].translation);
185     }
186 
187   return choice->text;
188 }
189 
190 GtkWidget *
pp_ppd_option_widget_new(ppd_option_t * option,const gchar * printer_name)191 pp_ppd_option_widget_new (ppd_option_t *option,
192                           const gchar  *printer_name)
193 {
194   PpPPDOptionWidget *self = NULL;
195 
196   if (option && printer_name)
197     {
198       self = g_object_new (PP_TYPE_PPD_OPTION_WIDGET, NULL);
199 
200       self->printer_name = g_strdup (printer_name);
201       self->option = cups_option_copy (option);
202       self->option_name = g_strdup (option->keyword);
203 
204       if (construct_widget (self))
205         {
206           update_widget_real (self);
207         }
208       else
209         {
210           g_object_ref_sink (self);
211           g_object_unref (self);
212           self = NULL;
213         }
214     }
215 
216   return (GtkWidget *) self;
217 }
218 
219 enum {
220   NAME_COLUMN,
221   VALUE_COLUMN,
222   N_COLUMNS
223 };
224 
225 static GtkWidget *
combo_box_new(void)226 combo_box_new (void)
227 {
228   GtkCellRenderer *cell;
229   g_autoptr(GtkListStore) store = NULL;
230   GtkWidget       *combo_box;
231 
232   combo_box = gtk_combo_box_new ();
233 
234   store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
235   gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
236 
237   cell = gtk_cell_renderer_text_new ();
238   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
239   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
240                                   "text", NAME_COLUMN,
241                                   NULL);
242 
243   return combo_box;
244 }
245 
246 static void
combo_box_append(GtkWidget * combo,const gchar * display_text,const gchar * value)247 combo_box_append (GtkWidget   *combo,
248                   const gchar *display_text,
249                   const gchar *value)
250 {
251   GtkTreeModel *model;
252   GtkListStore *store;
253   GtkTreeIter   iter;
254 
255   model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
256   store = GTK_LIST_STORE (model);
257 
258   gtk_list_store_append (store, &iter);
259   gtk_list_store_set (store, &iter,
260                       NAME_COLUMN, display_text,
261                       VALUE_COLUMN, value,
262                       -1);
263 }
264 
265 struct ComboSet {
266   GtkComboBox *combo;
267   const gchar *value;
268 };
269 
270 static gboolean
set_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)271 set_cb (GtkTreeModel *model,
272         GtkTreePath  *path,
273         GtkTreeIter  *iter,
274         gpointer      data)
275 {
276   struct ComboSet  *set_data = data;
277   g_autofree gchar *value = NULL;
278 
279   gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
280 
281   if (strcmp (value, set_data->value) == 0)
282     {
283       gtk_combo_box_set_active_iter (set_data->combo, iter);
284       return TRUE;
285     }
286 
287   return FALSE;
288 }
289 
290 static void
combo_box_set(GtkWidget * combo,const gchar * value)291 combo_box_set (GtkWidget   *combo,
292                const gchar *value)
293 {
294   struct ComboSet  set_data;
295   GtkTreeModel    *model;
296 
297   model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
298 
299   set_data.combo = GTK_COMBO_BOX (combo);
300   set_data.value = value;
301   gtk_tree_model_foreach (model, set_cb, &set_data);
302 }
303 
304 static char *
combo_box_get(GtkWidget * combo)305 combo_box_get (GtkWidget *combo)
306 {
307   GtkTreeModel *model;
308   GtkTreeIter   iter;
309   gchar        *value = NULL;
310 
311   model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
312 
313   if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
314      gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
315 
316   return value;
317 }
318 
319 static void
printer_add_option_async_cb(gboolean success,gpointer user_data)320 printer_add_option_async_cb (gboolean success,
321                              gpointer user_data)
322 {
323   PpPPDOptionWidget *self = user_data;
324 
325   update_widget (user_data);
326   g_clear_object (&self->cancellable);
327 }
328 
329 static void
switch_changed_cb(PpPPDOptionWidget * self)330 switch_changed_cb (PpPPDOptionWidget *self)
331 {
332   gchar                    **values;
333 
334   values = g_new0 (gchar *, 2);
335 
336   if (gtk_switch_get_active (GTK_SWITCH (self->switch_button)))
337     values[0] = g_strdup ("True");
338   else
339     values[0] = g_strdup ("False");
340 
341   g_cancellable_cancel (self->cancellable);
342   g_clear_object (&self->cancellable);
343 
344   self->cancellable = g_cancellable_new ();
345   printer_add_option_async (self->printer_name,
346                             self->option_name,
347                             values,
348                             FALSE,
349                             self->cancellable,
350                             printer_add_option_async_cb,
351                             self);
352 
353   g_strfreev (values);
354 }
355 
356 static void
combo_changed_cb(PpPPDOptionWidget * self)357 combo_changed_cb (PpPPDOptionWidget *self)
358 {
359   gchar                    **values;
360 
361   values = g_new0 (gchar *, 2);
362   values[0] = combo_box_get (self->combo);
363 
364   g_cancellable_cancel (self->cancellable);
365   g_clear_object (&self->cancellable);
366 
367   self->cancellable = g_cancellable_new ();
368   printer_add_option_async (self->printer_name,
369                             self->option_name,
370                             values,
371                             FALSE,
372                             self->cancellable,
373                             printer_add_option_async_cb,
374                             self);
375 
376   g_strfreev (values);
377 }
378 
379 static gboolean
construct_widget(PpPPDOptionWidget * self)380 construct_widget (PpPPDOptionWidget *self)
381 {
382   gint                      i;
383 
384   /* Don't show options which has only one choice */
385   if (self->option && self->option->num_choices > 1)
386     {
387       switch (self->option->ui)
388         {
389           case PPD_UI_BOOLEAN:
390               self->switch_button = gtk_switch_new ();
391               gtk_widget_show (self->switch_button);
392 
393               g_signal_connect_object (self->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), self, G_CONNECT_SWAPPED);
394               gtk_box_pack_start (GTK_BOX (self), self->switch_button, FALSE, FALSE, 0);
395               break;
396 
397           case PPD_UI_PICKONE:
398               self->combo = combo_box_new ();
399               gtk_widget_show (self->combo);
400 
401               for (i = 0; i < self->option->num_choices; i++)
402                 {
403                   combo_box_append (self->combo,
404                                     ppd_choice_translate (&self->option->choices[i]),
405                                     self->option->choices[i].choice);
406                 }
407 
408               gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
409               g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
410               break;
411 
412           case PPD_UI_PICKMANY:
413               self->combo = combo_box_new ();
414               gtk_widget_show (self->combo);
415 
416               for (i = 0; i < self->option->num_choices; i++)
417                 {
418                   combo_box_append (self->combo,
419                                     ppd_choice_translate (&self->option->choices[i]),
420                                     self->option->choices[i].choice);
421                 }
422 
423               gtk_box_pack_start (GTK_BOX (self), self->combo, TRUE, TRUE, 0);
424               g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
425               break;
426 
427           default:
428               break;
429         }
430 
431       self->image = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_MENU);
432       if (!self->image)
433         self->image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_MENU);
434       gtk_widget_show (self->image);
435       gtk_box_pack_start (GTK_BOX (self), self->image, FALSE, FALSE, 0);
436       gtk_widget_set_no_show_all (GTK_WIDGET (self->image), TRUE);
437 
438       return TRUE;
439     }
440   else
441     {
442       return FALSE;
443     }
444 }
445 
446 static void
update_widget_real(PpPPDOptionWidget * self)447 update_widget_real (PpPPDOptionWidget *self)
448 {
449   ppd_option_t             *option = NULL, *iter;
450   ppd_file_t               *ppd_file;
451   gint                      i;
452 
453   if (self->option)
454     {
455       option = cups_option_copy (self->option);
456       cups_option_free (self->option);
457       self->option = NULL;
458     }
459   else if (self->ppd_filename)
460     {
461       ppd_file = ppdOpenFile (self->ppd_filename);
462       ppdLocalize (ppd_file);
463 
464       if (ppd_file)
465         {
466           ppdMarkDefaults (ppd_file);
467 
468           for (iter = ppdFirstOption(ppd_file); iter; iter = ppdNextOption(ppd_file))
469             {
470               if (g_str_equal (iter->keyword, self->option_name))
471                 {
472                   option = cups_option_copy (iter);
473                   break;
474                 }
475             }
476 
477           ppdClose (ppd_file);
478         }
479 
480       g_unlink (self->ppd_filename);
481       g_free (self->ppd_filename);
482       self->ppd_filename = NULL;
483     }
484 
485   if (option)
486     {
487       g_autofree gchar *value = NULL;
488 
489       for (i = 0; i < option->num_choices; i++)
490         if (option->choices[i].marked)
491           value = g_strdup (option->choices[i].choice);
492 
493       if (value == NULL)
494         value = g_strdup (option->defchoice);
495 
496       if (value)
497         {
498           switch (option->ui)
499             {
500               case PPD_UI_BOOLEAN:
501                 g_signal_handlers_block_by_func (self->switch_button, switch_changed_cb, self);
502                 if (g_ascii_strcasecmp (value, "True") == 0)
503                   gtk_switch_set_active (GTK_SWITCH (self->switch_button), TRUE);
504                 else
505                   gtk_switch_set_active (GTK_SWITCH (self->switch_button), FALSE);
506                 g_signal_handlers_unblock_by_func (self->switch_button, switch_changed_cb, self);
507                 break;
508 
509               case PPD_UI_PICKONE:
510                 g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
511                 combo_box_set (self->combo, value);
512                 g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
513                 break;
514 
515               case PPD_UI_PICKMANY:
516                 g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
517                 combo_box_set (self->combo, value);
518                 g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
519                 break;
520 
521               default:
522                 break;
523             }
524         }
525 
526       if (option->conflicted)
527         gtk_widget_show (self->image);
528       else
529         gtk_widget_hide (self->image);
530     }
531 
532   cups_option_free (option);
533 }
534 
535 static void
get_named_dest_cb(cups_dest_t * dest,gpointer user_data)536 get_named_dest_cb (cups_dest_t *dest,
537                    gpointer     user_data)
538 {
539   PpPPDOptionWidget *self = user_data;
540 
541   if (self->destination)
542     cupsFreeDests (1, self->destination);
543 
544   self->destination = dest;
545   self->destination_set = TRUE;
546 
547   if (self->ppd_filename_set)
548     {
549       update_widget_real (self);
550     }
551 }
552 
553 static void
printer_get_ppd_cb(const gchar * ppd_filename,gpointer user_data)554 printer_get_ppd_cb (const gchar *ppd_filename,
555                     gpointer     user_data)
556 {
557   PpPPDOptionWidget *self = user_data;
558 
559   if (self->ppd_filename)
560     {
561       g_unlink (self->ppd_filename);
562       g_free (self->ppd_filename);
563     }
564 
565   self->ppd_filename = g_strdup (ppd_filename);
566   self->ppd_filename_set = TRUE;
567 
568   if (self->destination_set)
569     {
570       update_widget_real (self);
571     }
572 }
573 
574 static void
update_widget(PpPPDOptionWidget * self)575 update_widget (PpPPDOptionWidget *self)
576 {
577   self->ppd_filename_set = FALSE;
578   self->destination_set = FALSE;
579 
580   get_named_dest_async (self->printer_name,
581                         get_named_dest_cb,
582                         self);
583 
584   printer_get_ppd_async (self->printer_name,
585                          NULL,
586                          0,
587                          printer_get_ppd_cb,
588                          self);
589 }
590