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 
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <glib/gstdio.h>
31 #include <gtk/gtk.h>
32 
33 #include <cups/cups.h>
34 #include <cups/ppd.h>
35 
36 #include "pp-options-dialog.h"
37 #include "pp-maintenance-command.h"
38 #include "pp-ppd-option-widget.h"
39 #include "pp-ipp-option-widget.h"
40 #include "pp-utils.h"
41 #include "pp-printer.h"
42 
43 struct _PpOptionsDialog {
44   GtkDialog    parent_instance;
45 
46   GtkTreeSelection *categories_selection;
47   GtkTreeView      *categories_treeview;
48   GtkBox           *main_box;
49   GtkNotebook      *notebook;
50   GtkSpinner       *spinner;
51   GtkStack         *stack;
52 
53   gchar       *printer_name;
54 
55   gchar       *ppd_filename;
56   gboolean     ppd_filename_set;
57 
58   cups_dest_t *destination;
59   gboolean     destination_set;
60 
61   GHashTable  *ipp_attributes;
62   gboolean     ipp_attributes_set;
63 
64   gboolean sensitive;
65 };
66 
67 G_DEFINE_TYPE (PpOptionsDialog, pp_options_dialog, GTK_TYPE_DIALOG)
68 
69 enum
70 {
71   CATEGORY_IDS_COLUMN = 0,
72   CATEGORY_NAMES_COLUMN
73 };
74 
75 /* These lists come from Gtk+ */
76 /* TODO: Only "Resolution" currently has a context to disambiguate it from
77  *       the display settings. All of these should have contexts, but it
78  *       was late in the release cycle and this partial solution was
79  *       preferable. See:
80  *       https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/414#note_446778
81  */
82 static const struct {
83   const char *keyword;
84   const char *translation_context;
85   const char *translation;
86 } ppd_option_translations[] = {
87   { "Duplex", NULL, N_("Two Sided") },
88   { "MediaType", NULL, N_("Paper Type") },
89   { "InputSlot", NULL, N_("Paper Source") },
90   { "OutputBin", NULL, N_("Output Tray") },
91   { "Resolution", "printing option", NC_("printing option", "Resolution") },
92   { "PreFilter", NULL, N_("GhostScript pre-filtering") },
93 };
94 
95 /* keep sorted when changing */
96 static const char *allowed_page_setup_options[] = {
97   "InputSlot",
98   "MediaType",
99   "OutputBin",
100   "PageSize",
101 };
102 
103 /* keep sorted when changing */
104 static const char *allowed_color_options[] = {
105   "BRColorEnhancement",
106   "BRColorMatching",
107   "BRColorMatching",
108   "BRColorMode",
109   "BRGammaValue",
110   "BRImprovedGray",
111   "BlackSubstitution",
112   "ColorModel",
113   "HPCMYKInks",
114   "HPCSGraphics",
115   "HPCSImages",
116   "HPCSText",
117   "HPColorSmart",
118   "RPSBlackMode",
119   "RPSBlackOverPrint",
120   "Rcmyksimulation",
121 };
122 
123 /* keep sorted when changing */
124 static const char *allowed_color_groups[] = {
125   "Color",
126   "Color1",
127   "Color2",
128   "ColorBalance",
129   "ColorPage",
130   "ColorSettings1",
131   "ColorSettings2",
132   "ColorSettings3",
133   "ColorSettings4",
134   "EPColorSettings",
135   "FPColorWise1",
136   "FPColorWise2",
137   "FPColorWise3",
138   "FPColorWise4",
139   "FPColorWise5",
140   "HPCMYKInksPanel",
141   "HPColorOptions",
142   "HPColorOptionsPanel",
143   "HPColorQualityOptionsPanel",
144   "ManualColor",
145 };
146 
147 /* keep sorted when changing */
148 static const char *allowed_image_quality_options[] = {
149   "BRDocument",
150   "BRHalfTonePattern",
151   "BRNormalPrt",
152   "BRPrintQuality",
153   "BitsPerPixel",
154   "Darkness",
155   "Dithering",
156   "EconoMode",
157   "Economode",
158   "HPEconoMode",
159   "HPEdgeControl",
160   "HPGraphicsHalftone",
161   "HPHalftone",
162   "HPImagingOptions",
163   "HPLJDensity",
164   "HPPhotoHalftone",
165   "HPPrintQualityOptions",
166   "HPResolutionOptions",
167   "OutputMode",
168   "REt",
169   "RPSBitsPerPixel",
170   "RPSDitherType",
171   "Resolution",
172   "ScreenLock",
173   "Smoothing",
174   "TonerSaveMode",
175   "UCRGCRForImage",
176 };
177 
178 /* keep sorted when changing */
179 static const char *allowed_image_quality_groups[] = {
180   "EPQualitySettings",
181   "FPImageQuality1",
182   "FPImageQuality2",
183   "FPImageQuality3",
184   "ImageQualityPage",
185   "Quality",
186 };
187 
188 /* keep sorted when changing */
189 static const char * allowed_finishing_options[] = {
190   "BindColor",
191   "BindEdge",
192   "BindType",
193   "BindWhen",
194   "Booklet",
195   "FoldType",
196   "FoldWhen",
197   "HPStaplerOptions",
198   "Jog",
199   "Slipsheet",
200   "Sorter",
201   "StapleLocation",
202   "StapleOrientation",
203   "StapleWhen",
204   "StapleX",
205   "StapleY",
206 };
207 
208 /* keep sorted when changing */
209 static const char *allowed_job_groups[] = {
210   "JobHandling",
211   "JobLog",
212 };
213 
214 /* keep sorted when changing */
215 static const char *allowed_finishing_groups[] = {
216   "Booklet",
217   "BookletCover",
218   "BookletModeOptions",
219   "FPFinishing1",
220   "FPFinishing2",
221   "FPFinishing3",
222   "FPFinishing4",
223   "Finishing",
224   "FinishingOptions",
225   "FinishingPage",
226   "HPBookletPanel",
227   "HPFinishing",
228   "HPFinishingOptions",
229   "HPFinishingPanel",
230 };
231 
232 /* keep sorted when changing */
233 static const char *allowed_installable_options_groups[] = {
234   "InstallableOptions",
235 };
236 
237 /* keep sorted when changing */
238 static const char *allowed_page_setup_groups[] = {
239   "HPMarginAndLayout",
240   "OutputControl",
241   "PaperHandling",
242   "Paper",
243   "Source",
244 };
245 
246 /* keep sorted when changing */
247 static const char *disallowed_ppd_options[] = {
248   "Collate",
249   "Copies",
250   "Duplex",
251   "HPManualDuplexOrientation",
252   "HPManualDuplexSwitch",
253   "OutputOrder",
254   "PageRegion"
255 };
256 
257 static int
258 strptr_cmp (const void *a,
259 	    const void *b)
260 {
261   char **aa = (char **)a;
262   char **bb = (char **)b;
263   return strcmp (*aa, *bb);
264 }
265 
266 static gboolean
267 string_in_table (gchar       *str,
268 		 const gchar *table[],
269 		 gint         table_len)
270 {
271   return bsearch (&str, table, table_len, sizeof (char *), (void *)strptr_cmp) != NULL;
272 }
273 
274 #define STRING_IN_TABLE(_str, _table) (string_in_table (_str, _table, G_N_ELEMENTS (_table)))
275 
276 static const gchar *
277 ppd_option_name_translate (ppd_option_t *option)
278 {
279   gint i;
280 
281   for (i = 0; i < G_N_ELEMENTS (ppd_option_translations); i++)
282     {
283       if (g_strcmp0 (ppd_option_translations[i].keyword, option->keyword) == 0)
284         {
285           if (ppd_option_translations[i].translation_context)
286             return g_dpgettext2(NULL, ppd_option_translations[i].translation_context, ppd_option_translations[i].translation);
287           else
288             return _(ppd_option_translations[i].translation);
289         }
290     }
291 
292   return option->text;
293 }
294 
295 static gint
296 grid_get_height (GtkWidget *grid)
297 {
298   GList *children;
299   GList *child;
300   gint   height = 0;
301   gint   top_attach = 0;
302   gint   max = 0;
303 
304   children = gtk_container_get_children (GTK_CONTAINER (grid));
305   for (child = children; child; child = g_list_next (child))
306     {
307       gtk_container_child_get (GTK_CONTAINER (grid), child->data,
308                                "top-attach", &top_attach,
309                                "height", &height,
310                                NULL);
311 
312       if (height + top_attach > max)
313         max = height + top_attach;
314     }
315 
316   g_list_free (children);
317 
318   return max;
319 }
320 
321 static gboolean
322 grid_is_empty (GtkWidget *grid)
323 {
324   GList *children;
325 
326   children = gtk_container_get_children (GTK_CONTAINER (grid));
327   if (children)
328     {
329       g_list_free (children);
330       return FALSE;
331     }
332   else
333     {
334       return TRUE;
335     }
336 }
337 
338 static GtkWidget *
339 ipp_option_add (IPPAttribute *attr_supported,
340                 IPPAttribute *attr_default,
341                 const gchar  *option_name,
342                 const gchar  *option_display_name,
343                 const gchar  *printer_name,
344                 GtkWidget    *grid,
345                 gboolean      sensitive)
346 {
347   GtkStyleContext *context;
348   GtkWidget       *widget;
349   GtkWidget       *label;
350   gint             position;
351 
352   widget = (GtkWidget *) pp_ipp_option_widget_new (attr_supported,
353                                                    attr_default,
354                                                    option_name,
355                                                    printer_name);
356   if (widget)
357     {
358       gtk_widget_show (widget);
359       gtk_widget_set_sensitive (widget, sensitive);
360       position = grid_get_height (grid);
361 
362       label = gtk_label_new (option_display_name);
363       gtk_widget_show (GTK_WIDGET (label));
364       gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
365       context = gtk_widget_get_style_context (label);
366       gtk_style_context_add_class (context, "dim-label");
367       gtk_widget_set_halign (label, GTK_ALIGN_END);
368       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
369       gtk_widget_set_margin_start (label, 10);
370       gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
371 
372       gtk_widget_set_margin_start (widget, 20);
373       gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
374     }
375 
376   return widget;
377 }
378 
379 static GtkWidget *
380 ppd_option_add (ppd_option_t  option,
381                 const gchar  *printer_name,
382                 GtkWidget    *grid,
383                 gboolean      sensitive)
384 {
385   GtkStyleContext *context;
386   GtkWidget       *widget;
387   GtkWidget       *label;
388   gint             position;
389 
390   widget = (GtkWidget *) pp_ppd_option_widget_new (&option, printer_name);
391   if (widget)
392     {
393       gtk_widget_show (widget);
394       gtk_widget_set_sensitive (widget, sensitive);
395       position = grid_get_height (grid);
396 
397       label = gtk_label_new (ppd_option_name_translate (&option));
398       gtk_widget_show (GTK_WIDGET (label));
399       gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
400       context = gtk_widget_get_style_context (label);
401       gtk_style_context_add_class (context, "dim-label");
402       gtk_widget_set_halign (label, GTK_ALIGN_END);
403       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
404       gtk_widget_set_margin_start (label, 10);
405       gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
406 
407       gtk_widget_set_margin_start (widget, 20);
408       gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
409     }
410 
411   return widget;
412 }
413 
414 static GtkWidget *
415 tab_grid_new ()
416 {
417   GtkWidget *grid;
418 
419   grid = gtk_grid_new ();
420   gtk_widget_show (GTK_WIDGET (grid));
421   gtk_container_set_border_width (GTK_CONTAINER (grid), 20);
422   gtk_grid_set_row_spacing (GTK_GRID (grid), 15);
423 
424   return grid;
425 }
426 
427 static void
428 tab_add (PpOptionsDialog *self,
429          const gchar     *tab_name,
430          GtkWidget       *grid)
431 {
432   GtkListStore *store;
433   GtkTreeIter   iter;
434   GtkWidget    *scrolled_window;
435   gboolean      unref_store = FALSE;
436   gint          id;
437 
438   if (!grid_is_empty (grid))
439     {
440       scrolled_window = gtk_scrolled_window_new (NULL, NULL);
441       gtk_widget_show (GTK_WIDGET (scrolled_window));
442       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
443                                       GTK_POLICY_NEVER,
444                                       GTK_POLICY_AUTOMATIC);
445       gtk_container_add (GTK_CONTAINER (scrolled_window), grid);
446 
447       id = gtk_notebook_append_page (self->notebook,
448                                      scrolled_window,
449                                      NULL);
450 
451       if (id >= 0)
452         {
453           store = GTK_LIST_STORE (gtk_tree_view_get_model (self->categories_treeview));
454           if (!store)
455             {
456               store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
457               unref_store = TRUE;
458             }
459 
460           gtk_list_store_append (store, &iter);
461           gtk_list_store_set (store, &iter,
462                               CATEGORY_IDS_COLUMN, id,
463                               CATEGORY_NAMES_COLUMN, tab_name,
464                               -1);
465 
466           if (unref_store)
467             {
468               gtk_tree_view_set_model (self->categories_treeview, GTK_TREE_MODEL (store));
469               g_object_unref (store);
470             }
471         }
472     }
473   else
474     {
475       g_object_ref_sink (grid);
476       g_object_unref (grid);
477     }
478 }
479 
480 static void
481 category_selection_changed_cb (PpOptionsDialog *self)
482 {
483   GtkTreeModel *model;
484   GtkTreeIter   iter;
485   gint          id = -1;
486 
487   if (gtk_tree_selection_get_selected (self->categories_selection, &model, &iter))
488     {
489       gtk_tree_model_get (model, &iter,
490 			  CATEGORY_IDS_COLUMN, &id,
491 			  -1);
492     }
493 
494   if (id >= 0)
495     {
496       gtk_notebook_set_current_page (self->notebook, id);
497     }
498 }
499 
500 static void
501 populate_options_real (PpOptionsDialog *self)
502 {
503   GtkTreeModel *model;
504   GtkTreeIter   iter;
505   ppd_file_t   *ppd_file;
506   GtkWidget    *grid;
507   GtkWidget    *general_tab_grid = tab_grid_new ();
508   GtkWidget    *page_setup_tab_grid = tab_grid_new ();
509   GtkWidget    *installable_options_tab_grid = tab_grid_new ();
510   GtkWidget    *job_tab_grid = tab_grid_new ();
511   GtkWidget    *image_quality_tab_grid = tab_grid_new ();
512   GtkWidget    *color_tab_grid = tab_grid_new ();
513   GtkWidget    *finishing_tab_grid = tab_grid_new ();
514   GtkWidget    *advanced_tab_grid = tab_grid_new ();
515   gint          i, j;
516 
517   gtk_spinner_stop (self->spinner);
518 
519   gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->main_box));
520 
521   if (self->ipp_attributes)
522     {
523       /* Add number-up option to Page Setup tab */
524       ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
525                                            "number-up-supported"),
526                       g_hash_table_lookup (self->ipp_attributes,
527                                            "number-up-default"),
528                       "number-up",
529                       /* Translators: This option sets number of pages printed on one sheet */
530                       _("Pages per side"),
531                       self->printer_name,
532                       page_setup_tab_grid,
533                       self->sensitive);
534 
535       /* Add sides option to Page Setup tab */
536       ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
537                                            "sides-supported"),
538                       g_hash_table_lookup (self->ipp_attributes,
539                                            "sides-default"),
540                       "sides",
541                       /* Translators: This option sets whether to print on both sides of paper */
542                       _("Two-sided"),
543                       self->printer_name,
544                       page_setup_tab_grid,
545                       self->sensitive);
546 
547       /* Add orientation-requested option to Page Setup tab */
548       ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
549                                            "orientation-requested-supported"),
550                       g_hash_table_lookup (self->ipp_attributes,
551                                            "orientation-requested-default"),
552                       "orientation-requested",
553                       /* Translators: This option sets orientation of print (portrait, landscape...) */
554                       _("Orientation"),
555                       self->printer_name,
556                       page_setup_tab_grid,
557                       self->sensitive);
558     }
559 
560   if (self->destination && self->ppd_filename)
561     {
562       ppd_file = ppdOpenFile (self->ppd_filename);
563       ppdLocalize (ppd_file);
564 
565       if (ppd_file)
566         {
567           ppdMarkDefaults (ppd_file);
568           cupsMarkOptions (ppd_file,
569                            self->destination->num_options,
570                            self->destination->options);
571 
572           for (i = 0; i < ppd_file->num_groups; i++)
573             {
574               for (j = 0; j < ppd_file->groups[i].num_options; j++)
575                 {
576                   grid = NULL;
577 
578                   if (STRING_IN_TABLE (ppd_file->groups[i].name,
579                                        allowed_color_groups))
580                     grid = color_tab_grid;
581                   else if (STRING_IN_TABLE (ppd_file->groups[i].name,
582                                             allowed_image_quality_groups))
583                     grid = image_quality_tab_grid;
584                   else if (STRING_IN_TABLE (ppd_file->groups[i].name,
585                                             allowed_job_groups))
586                     grid = job_tab_grid;
587                   else if (STRING_IN_TABLE (ppd_file->groups[i].name,
588                                             allowed_finishing_groups))
589                     grid = finishing_tab_grid;
590                   else if (STRING_IN_TABLE (ppd_file->groups[i].name,
591                                             allowed_installable_options_groups))
592                     grid = installable_options_tab_grid;
593                   else if (STRING_IN_TABLE (ppd_file->groups[i].name,
594                                             allowed_page_setup_groups))
595                     grid = page_setup_tab_grid;
596 
597                   if (!STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
598                                         disallowed_ppd_options))
599                     {
600                       if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
601                                                     allowed_color_options))
602                         grid = color_tab_grid;
603                       else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
604                                                          allowed_image_quality_options))
605                         grid = image_quality_tab_grid;
606                       else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
607                                                          allowed_finishing_options))
608                         grid = finishing_tab_grid;
609                       else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
610                                                          allowed_page_setup_options))
611                         grid = page_setup_tab_grid;
612 
613                       if (!grid)
614                         grid = advanced_tab_grid;
615 
616                       ppd_option_add (ppd_file->groups[i].options[j],
617                                       self->printer_name,
618                                       grid,
619                                       self->sensitive);
620                     }
621                 }
622             }
623 
624           ppdClose (ppd_file);
625         }
626     }
627 
628   self->ppd_filename_set = FALSE;
629   if (self->ppd_filename)
630     {
631       g_unlink (self->ppd_filename);
632       g_free (self->ppd_filename);
633       self->ppd_filename = NULL;
634     }
635 
636   self->destination_set = FALSE;
637   if (self->destination)
638     {
639       cupsFreeDests (1, self->destination);
640       self->destination = NULL;
641     }
642 
643   self->ipp_attributes_set = FALSE;
644   if (self->ipp_attributes)
645     {
646       g_hash_table_unref (self->ipp_attributes);
647       self->ipp_attributes = NULL;
648     }
649 
650   /* Translators: "General" tab contains general printer options */
651   tab_add (self, C_("Printer Option Group", "General"), general_tab_grid);
652 
653   /* Translators: "Page Setup" tab contains settings related to pages (page size, paper source, etc.) */
654   tab_add (self, C_("Printer Option Group", "Page Setup"), page_setup_tab_grid);
655 
656   /* Translators: "Installable Options" tab contains settings of presence of installed options (amount of RAM, duplex unit, etc.) */
657   tab_add (self, C_("Printer Option Group", "Installable Options"), installable_options_tab_grid);
658 
659   /* Translators: "Job" tab contains settings for jobs */
660   tab_add (self, C_("Printer Option Group", "Job"), job_tab_grid);
661 
662   /* Translators: "Image Quality" tab contains settings for quality of output print (e.g. resolution) */
663   tab_add (self, C_("Printer Option Group", "Image Quality"), image_quality_tab_grid);
664 
665   /* Translators: "Color" tab contains color settings (e.g. color printing) */
666   tab_add (self, C_("Printer Option Group", "Color"), color_tab_grid);
667 
668   /* Translators: "Finishing" tab contains finishing settings (e.g. booklet printing) */
669   tab_add (self, C_("Printer Option Group", "Finishing"), finishing_tab_grid);
670 
671   /* Translators: "Advanced" tab contains all others settings */
672   tab_add (self, C_("Printer Option Group", "Advanced"), advanced_tab_grid);
673 
674   /* Select the first option group */
675   if ((model = gtk_tree_view_get_model (self->categories_treeview)) != NULL &&
676       gtk_tree_model_get_iter_first (model, &iter))
677     gtk_tree_selection_select_iter (self->categories_selection, &iter);
678 }
679 
680 static void
681 printer_get_ppd_cb (const gchar *ppd_filename,
682                     gpointer     user_data)
683 {
684   PpOptionsDialog *self = (PpOptionsDialog *) user_data;
685 
686   if (self->ppd_filename)
687     {
688       g_unlink (self->ppd_filename);
689       g_free (self->ppd_filename);
690     }
691 
692   self->ppd_filename = g_strdup (ppd_filename);
693   self->ppd_filename_set = TRUE;
694 
695   if (self->destination_set &&
696       self->ipp_attributes_set)
697     {
698       populate_options_real (self);
699     }
700 }
701 
702 static void
703 get_named_dest_cb (cups_dest_t *dest,
704                    gpointer     user_data)
705 {
706   PpOptionsDialog *self = (PpOptionsDialog *) user_data;
707 
708   if (self->destination)
709     cupsFreeDests (1, self->destination);
710 
711   self->destination = dest;
712   self->destination_set = TRUE;
713 
714   if (self->ppd_filename_set &&
715       self->ipp_attributes_set)
716     {
717       populate_options_real (self);
718     }
719 }
720 
721 static void
722 get_ipp_attributes_cb (GHashTable *table,
723                        gpointer    user_data)
724 {
725   PpOptionsDialog *self = (PpOptionsDialog *) user_data;
726 
727   if (self->ipp_attributes)
728     g_hash_table_unref (self->ipp_attributes);
729 
730   self->ipp_attributes = g_hash_table_ref (table);
731   self->ipp_attributes_set = TRUE;
732 
733   if (self->ppd_filename_set &&
734       self->destination_set)
735     {
736       populate_options_real (self);
737     }
738 }
739 
740 static void
741 populate_options (PpOptionsDialog *self)
742 {
743   GtkTreeViewColumn  *column;
744   GtkCellRenderer    *renderer;
745   /*
746    * Options which we need to obtain through an IPP request
747    * to be able to fill the options dialog.
748    * *-supported - possible values of the option
749    * *-default - actual value of the option
750    */
751   const gchar        *attributes[] =
752     { "number-up-supported",
753       "number-up-default",
754       "sides-supported",
755       "sides-default",
756       "orientation-requested-supported",
757       "orientation-requested-default",
758       NULL};
759 
760   gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->spinner));
761 
762   renderer = gtk_cell_renderer_text_new ();
763 
764   column = gtk_tree_view_column_new_with_attributes ("Categories", renderer,
765                                                      "text", CATEGORY_NAMES_COLUMN, NULL);
766   gtk_tree_view_column_set_expand (column, TRUE);
767   gtk_tree_view_append_column (self->categories_treeview, column);
768 
769   gtk_spinner_start (self->spinner);
770 
771   printer_get_ppd_async (self->printer_name,
772                          NULL,
773                          0,
774                          printer_get_ppd_cb,
775                          self);
776 
777   get_named_dest_async (self->printer_name,
778                         get_named_dest_cb,
779                         self);
780 
781   get_ipp_attributes_async (self->printer_name,
782                             (gchar **) attributes,
783                             get_ipp_attributes_cb,
784                             self);
785 }
786 
787 static void
788 pp_maintenance_command_execute_cb (GObject      *source_object,
789                                    GAsyncResult *res,
790                                    gpointer      user_data)
791 {
792   pp_maintenance_command_execute_finish (PP_MAINTENANCE_COMMAND(source_object), res, NULL);
793 }
794 
795 static gchar *
796 get_testprint_filename (const gchar *datadir)
797 {
798   const gchar *testprint[] = { "/data/testprint",
799                                "/data/testprint.ps",
800                                NULL };
801   gchar       *filename = NULL;
802   gint         i;
803 
804   for (i = 0; testprint[i] != NULL; i++)
805     {
806       filename = g_strconcat (datadir, testprint[i], NULL);
807       if (g_access (filename, R_OK) == 0)
808         break;
809 
810       g_clear_pointer (&filename, g_free);
811     }
812 
813   return filename;
814 }
815 
816 static void
817 print_test_page_cb (GObject      *source_object,
818                     GAsyncResult *result,
819                     gpointer      user_data)
820 {
821   pp_printer_print_file_finish (PP_PRINTER (source_object),
822                                 result, NULL);
823 }
824 
825 static void
826 test_page_cb (PpOptionsDialog *self)
827 {
828   gint i;
829 
830   if (self->printer_name)
831     {
832       const gchar      *const dirs[] = { "/usr/share/cups",
833                                          "/usr/local/share/cups",
834                                          NULL };
835       const gchar      *datadir = NULL;
836       g_autofree gchar *filename = NULL;
837 
838       datadir = getenv ("CUPS_DATADIR");
839       if (datadir != NULL)
840         {
841           filename = get_testprint_filename (datadir);
842         }
843       else
844         {
845           for (i = 0; dirs[i] != NULL && filename == NULL; i++)
846             filename = get_testprint_filename (dirs[i]);
847         }
848 
849       if (filename != NULL)
850         {
851           g_autoptr(PpPrinter) printer = NULL;
852 
853           printer = pp_printer_new (self->printer_name);
854           pp_printer_print_file_async (printer,
855                                        filename,
856           /* Translators: Name of job which makes printer to print test page */
857                                        _("Test Page"),
858                                        NULL,
859                                        print_test_page_cb,
860                                        NULL);
861         }
862       else
863         {
864           g_autoptr(PpMaintenanceCommand) command = NULL;
865 
866           command = pp_maintenance_command_new (self->printer_name,
867                                                 "PrintSelfTestPage",
868                                                 NULL,
869           /* Translators: Name of job which makes printer to print test page */
870                                                 _("Test page"));
871 
872           pp_maintenance_command_execute_async (command, NULL, pp_maintenance_command_execute_cb, NULL);
873         }
874     }
875 }
876 
877 PpOptionsDialog *
878 pp_options_dialog_new (gchar   *printer_name,
879                        gboolean sensitive)
880 {
881   PpOptionsDialog *self;
882 
883   self = g_object_new (pp_options_dialog_get_type (),
884                        "use-header-bar", 1,
885                        NULL);
886 
887   self->printer_name = g_strdup (printer_name);
888 
889   self->sensitive = sensitive;
890 
891   gtk_window_set_title (GTK_WINDOW (self), printer_name);
892 
893   populate_options (self);
894 
895   return self;
896 }
897 
898 static void
899 pp_options_dialog_dispose (GObject *object)
900 {
901   PpOptionsDialog *self = PP_OPTIONS_DIALOG (object);
902 
903   g_free (self->printer_name);
904   self->printer_name = NULL;
905 
906   if (self->ppd_filename)
907     {
908       g_unlink (self->ppd_filename);
909       g_free (self->ppd_filename);
910       self->ppd_filename = NULL;
911     }
912 
913   if (self->destination)
914     {
915       cupsFreeDests (1, self->destination);
916       self->destination = NULL;
917     }
918 
919   if (self->ipp_attributes)
920     {
921       g_hash_table_unref (self->ipp_attributes);
922       self->ipp_attributes = NULL;
923     }
924 
925   G_OBJECT_CLASS (pp_options_dialog_parent_class)->dispose (object);
926 }
927 
928 void
929 pp_options_dialog_class_init (PpOptionsDialogClass *klass)
930 {
931   GObjectClass *object_class = G_OBJECT_CLASS (klass);
932   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
933 
934   object_class->dispose = pp_options_dialog_dispose;
935 
936   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/pp-options-dialog.ui");
937 
938   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_selection);
939   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_treeview);
940   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, main_box);
941   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, notebook);
942   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, spinner);
943   gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, stack);
944 
945   gtk_widget_class_bind_template_callback (widget_class, category_selection_changed_cb);
946   gtk_widget_class_bind_template_callback (widget_class, test_page_cb);
947 }
948 
949 void
950 pp_options_dialog_init (PpOptionsDialog *self)
951 {
952   gtk_widget_init_template (GTK_WIDGET (self));
953 }
954