[gnome-control-center] printer: Add "Options" dialog



commit 7ae3027b472422cdae47c09dd0420b297dacf089
Author: Marek Kasik <mkasik redhat com>
Date:   Mon Jul 16 15:25:10 2012 +0200

    printer: Add "Options" dialog
    
    Add Options dialog which allows users to set more options than current dialog.
    The dialog reads printer's PPD file and add its options to the dialog together
    with some preselected IPP options (#678637).
    All operations in the dialog are asynchronous.
    During implementation of this, the option for setting allowed users was removed
    because this is not suitable for this panel (the option is intended for
    administrators).

 panels/printers/Makefile.am            |    7 +
 panels/printers/cc-printers-panel.c    |  343 ++----------
 panels/printers/options-dialog.ui      |  182 ++++++
 panels/printers/pp-ipp-option-widget.c |  605 ++++++++++++++++++++
 panels/printers/pp-ipp-option-widget.h |   70 +++
 panels/printers/pp-options-dialog.c    |  984 ++++++++++++++++++++++++++++++++
 panels/printers/pp-options-dialog.h    |   42 ++
 panels/printers/pp-ppd-option-widget.c |  624 ++++++++++++++++++++
 panels/printers/pp-ppd-option-widget.h |   63 ++
 panels/printers/pp-utils.c             |  285 +++++++++
 panels/printers/pp-utils.h             |   25 +
 11 files changed, 2937 insertions(+), 293 deletions(-)
---
diff --git a/panels/printers/Makefile.am b/panels/printers/Makefile.am
index dab5ae5..249ce69 100644
--- a/panels/printers/Makefile.am
+++ b/panels/printers/Makefile.am
@@ -4,6 +4,7 @@ uidir = $(pkgdatadir)/ui/printers
 dist_ui_DATA = \
 	new-printer-dialog.ui	\
 	ppd-selection-dialog.ui	\
+	options-dialog.ui	\
 	printers.ui
 
 INCLUDES = 						\
@@ -22,10 +23,16 @@ libprinters_la_SOURCES =		\
 	printers-module.c		\
 	pp-utils.c			\
 	pp-utils.h			\
+	pp-ppd-option-widget.c		\
+	pp-ppd-option-widget.h		\
+	pp-ipp-option-widget.c		\
+	pp-ipp-option-widget.h		\
 	pp-new-printer-dialog.c		\
 	pp-new-printer-dialog.h		\
 	pp-ppd-selection-dialog.c	\
 	pp-ppd-selection-dialog.h	\
+	pp-options-dialog.c	\
+	pp-options-dialog.h	\
 	cc-printers-panel.c		\
 	cc-printers-panel.h
 
diff --git a/panels/printers/cc-printers-panel.c b/panels/printers/cc-printers-panel.c
index 63fc803..72cbc9a 100644
--- a/panels/printers/cc-printers-panel.c
+++ b/panels/printers/cc-printers-panel.c
@@ -34,6 +34,7 @@
 #include "cc-editable-entry.h"
 #include "pp-new-printer-dialog.h"
 #include "pp-ppd-selection-dialog.h"
+#include "pp-options-dialog.h"
 #include "pp-utils.h"
 
 G_DEFINE_DYNAMIC_TYPE (CcPrintersPanel, cc_printers_panel, CC_TYPE_PANEL)
@@ -73,10 +74,6 @@ struct _CcPrintersPanelPrivate
   int num_jobs;
   int current_job;
 
-  gchar **allowed_users;
-  int num_allowed_users;
-  int current_allowed_user;
-
   GdkRGBA background_color;
 
   GPermission *permission;
@@ -85,6 +82,7 @@ struct _CcPrintersPanelPrivate
 
   PpNewPrinterDialog *pp_new_printer_dialog;
   PpPPDSelectionDialog *pp_ppd_selection_dialog;
+  PpOptionsDialog *pp_options_dialog;
 
   GDBusProxy      *cups_proxy;
   GDBusConnection *cups_bus_connection;
@@ -106,7 +104,6 @@ struct _CcPrintersPanelPrivate
 
 static void actualize_jobs_list (CcPrintersPanel *self);
 static void actualize_printers_list (CcPrintersPanel *self);
-static void actualize_allowed_users_list (CcPrintersPanel *self);
 static void update_sensitivity (gpointer user_data);
 static void printer_disable_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data);
 static void printer_set_default_cb (GtkToggleButton *button, gpointer user_data);
@@ -143,7 +140,6 @@ static void
 cc_printers_panel_dispose (GObject *object)
 {
   CcPrintersPanelPrivate *priv = CC_PRINTERS_PANEL (object)->priv;
-  int                     i;
 
   if (priv->pp_new_printer_dialog)
     {
@@ -159,16 +155,6 @@ cc_printers_panel_dispose (GObject *object)
   priv->num_jobs = 0;
   priv->current_job = -1;
 
-  if (priv->num_allowed_users > 0)
-    {
-      for (i = 0; i < priv->num_allowed_users; i++)
-        g_free (priv->allowed_users[i]);
-      g_free (priv->allowed_users);
-    }
-  priv->allowed_users = NULL;
-  priv->num_allowed_users = 0;
-  priv->current_allowed_user = -1;
-
   if (priv->builder)
     {
       g_object_unref (priv->builder);
@@ -224,12 +210,6 @@ cc_printers_panel_get_permission (CcPanel *panel)
   return priv->permission;
 }
 
-static const char *
-cc_printers_panel_get_help_uri (CcPanel *panel)
-{
-  return "help:gnome-help/printing";
-}
-
 static void
 cc_printers_panel_class_init (CcPrintersPanelClass *klass)
 {
@@ -244,7 +224,6 @@ cc_printers_panel_class_init (CcPrintersPanelClass *klass)
   object_class->finalize = cc_printers_panel_finalize;
 
   panel_class->get_permission = cc_printers_panel_get_permission;
-  panel_class->get_help_uri = cc_printers_panel_get_help_uri;
 }
 
 static void
@@ -646,8 +625,6 @@ printer_selection_changed_cb (GtkTreeSelection *selection,
       actualize_jobs_list (self);
     }
 
-  actualize_allowed_users_list (self);
-
   if (priv->current_dest >= 0 &&
       priv->current_dest < priv->num_dests &&
       priv->dests != NULL)
@@ -1499,108 +1476,6 @@ populate_jobs_list (CcPrintersPanel *self)
   actualize_jobs_list (self);
 }
 
-enum
-{
-  ALLOWED_USERS_ID_COLUMN,
-  ALLOWED_USERS_NAME_COLUMN,
-  ALLOWED_USERS_N_COLUMNS
-};
-
-static void
-actualize_allowed_users_list (CcPrintersPanel *self)
-{
-  CcPrintersPanelPrivate *priv;
-  GtkListStore           *store;
-  GtkTreeView            *treeview;
-  GtkTreeIter             iter;
-  int                     i;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  treeview = (GtkTreeView*)
-    gtk_builder_get_object (priv->builder, "allowed-users-treeview");
-
-  if (priv->allowed_users)
-    {
-      for (i = 0; i < priv->num_allowed_users; i++)
-        g_free (priv->allowed_users[i]);
-      g_free (priv->allowed_users);
-      priv->allowed_users = NULL;
-      priv->num_allowed_users = 0;
-    }
-
-  priv->current_allowed_user = -1;
-
-  if (priv->current_dest >= 0 &&
-      priv->current_dest < priv->num_dests &&
-      priv->dests != NULL)
-    priv->num_allowed_users = ccGetAllowedUsers (&priv->allowed_users, priv->dests[priv->current_dest].name);
-
-  store = gtk_list_store_new (ALLOWED_USERS_N_COLUMNS, G_TYPE_INT, G_TYPE_STRING);
-
-  for (i = 0; i < priv->num_allowed_users; i++)
-    {
-      gtk_list_store_append (store, &iter);
-      gtk_list_store_set (store, &iter,
-                          ALLOWED_USERS_ID_COLUMN, i,
-                          ALLOWED_USERS_NAME_COLUMN, priv->allowed_users[i],
-                          -1);
-    }
-
-  gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));
-  g_object_unref (store);
-}
-
-static void
-allowed_users_selection_changed_cb (GtkTreeSelection *selection,
-                                    gpointer          user_data)
-{
-  CcPrintersPanelPrivate *priv;
-  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
-  GtkTreeModel           *model;
-  GtkTreeIter             iter;
-  int                     id = -1;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  if (gtk_tree_selection_get_selected (selection, &model, &iter))
-    gtk_tree_model_get (model, &iter,
-			ALLOWED_USERS_ID_COLUMN, &id,
-			-1);
-  else
-    id = -1;
-
-  priv->current_allowed_user = id;
-}
-
-static void
-populate_allowed_users_list (CcPrintersPanel *self)
-{
-
-  CcPrintersPanelPrivate *priv;
-  GtkTreeViewColumn      *column;
-  GtkCellRenderer        *renderer;
-  GtkTreeView            *treeview;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  actualize_allowed_users_list (self);
-
-  treeview = (GtkTreeView*)
-    gtk_builder_get_object (priv->builder, "allowed-users-treeview");
-
-  gtk_tree_view_set_headers_visible (treeview, FALSE);
-
-  renderer = gtk_cell_renderer_text_new ();
-
-  column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
-                                                     "text", ALLOWED_USERS_NAME_COLUMN, NULL);
-  gtk_tree_view_append_column (treeview, column);
-
-  g_signal_connect (gtk_tree_view_get_selection (treeview),
-                    "changed", G_CALLBACK (allowed_users_selection_changed_cb), self);
-}
-
 static void
 job_process_cb (GtkButton *button,
                 gpointer   user_data)
@@ -1933,134 +1808,6 @@ supply_levels_draw_cb (GtkWidget *widget,
 }
 
 static void
-allowed_user_remove_cb (GtkToolButton *button,
-                        gpointer       user_data)
-{
-  CcPrintersPanelPrivate *priv;
-  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
-  char                   *printer_name = NULL;
-  char                   **names = NULL;
-  char                   *name = NULL;
-  int                     i, j;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  if (priv->current_allowed_user >= 0 &&
-      priv->current_allowed_user < priv->num_allowed_users &&
-      priv->allowed_users != NULL)
-    name = priv->allowed_users[priv->current_allowed_user];
-
-  if (priv->current_dest >= 0 &&
-      priv->current_dest < priv->num_dests &&
-      priv->dests != NULL)
-    printer_name = priv->dests[priv->current_dest].name;
-
-  if (name && printer_name)
-    {
-      names = g_new0 (gchar*, priv->num_allowed_users);
-      j = 0;
-      for (i = 0; i < (priv->num_allowed_users); i++)
-        {
-          if (i != priv->current_allowed_user)
-            {
-              names[j] = priv->allowed_users[i];
-              j++;
-            }
-        }
-
-      printer_set_users (printer_name, names, TRUE);
-      actualize_allowed_users_list (self);
-
-      g_free (names);
-  }
-}
-
-static void
-allowed_user_add_cb (GtkCellRendererText *renderer,
-                     gchar               *path,
-                     gchar               *new_text,
-                     gpointer             user_data)
-{
-  CcPrintersPanelPrivate  *priv;
-  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
-  char                    *printer_name = NULL;
-  char                   **names = NULL;
-  int                      i;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  g_signal_handlers_disconnect_by_func (G_OBJECT (renderer),
-                                        allowed_user_add_cb,
-                                        self);
-  g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL);
-
-  if (priv->current_dest >= 0 &&
-      priv->current_dest < priv->num_dests &&
-      priv->dests != NULL)
-    printer_name = priv->dests[priv->current_dest].name;
-
-  if (new_text && new_text[0] != '\0' && printer_name)
-    {
-      names = g_new0 (char *, priv->num_allowed_users + 2);
-      for (i = 0; i < (priv->num_allowed_users); i++)
-        names[i] = priv->allowed_users[i];
-      names[priv->num_allowed_users] = new_text;
-
-      printer_set_users (printer_name, names, TRUE);
-
-      g_free (names);
-    }
-
-  actualize_allowed_users_list (self);
-}
-
-static void
-allowed_user_add_button_cb (GtkToolButton *button,
-                            gpointer       user_data)
-{
-  CcPrintersPanelPrivate *priv;
-  GtkTreeViewColumn      *column;
-  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
-  GtkListStore           *liststore;
-  GtkTreeView            *treeview;
-  GtkTreeIter             iter;
-  GtkTreePath            *path;
-  GList                  *renderers;
-
-  priv = PRINTERS_PANEL_PRIVATE (self);
-
-  treeview = (GtkTreeView*)
-    gtk_builder_get_object (priv->builder, "allowed-users-treeview");
-
-  liststore = (GtkListStore*)
-    gtk_tree_view_get_model (treeview);
-
-  gtk_list_store_prepend (liststore, &iter);
-  column = gtk_tree_view_get_column (treeview, 0);
-  path = gtk_tree_model_get_path (GTK_TREE_MODEL (liststore), &iter);
-  renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
-
-  if (column && renderers)
-    {
-      g_signal_connect (G_OBJECT (renderers->data),
-                        "edited",
-                        G_CALLBACK (allowed_user_add_cb),
-                        self);
-
-      g_object_set (renderers->data, "editable", TRUE, NULL);
-      gtk_widget_grab_focus (GTK_WIDGET (treeview));
-      gtk_tree_view_set_cursor_on_cell (treeview,
-                                        path,
-                                        column,
-                                        GTK_CELL_RENDERER (renderers->data),
-                                        TRUE);
-    }
-
-  g_list_free (renderers);
-  gtk_tree_path_free (path);
-}
-
-static void
 printer_set_default_cb (GtkToggleButton *button,
                         gpointer         user_data)
 {
@@ -2888,17 +2635,12 @@ update_sensitivity (gpointer user_data)
   widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-default-check-button");
   gtk_widget_set_sensitive (widget, is_authorized);
 
-  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "allowed-user-add-button");
-  gtk_widget_set_sensitive (widget, local_server && !is_discovered && is_authorized);
-
-  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "allowed-user-remove-button");
-  gtk_widget_set_sensitive (widget, local_server && !is_discovered && is_authorized);
-
   widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "print-test-page-button");
   gtk_widget_set_sensitive (widget, printer_selected);
 
   widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-options-button");
-  gtk_widget_set_sensitive (widget, printer_selected);
+  gtk_widget_set_sensitive (widget, printer_selected && local_server && !is_discovered &&
+                            !priv->pp_options_dialog);
 
   widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-jobs-button");
   gtk_widget_set_sensitive (widget, printer_selected);
@@ -2987,18 +2729,55 @@ switch_to_jobs_cb (GtkButton *button,
 }
 
 static void
-switch_to_options_cb (GtkButton *button,
-                      gpointer   user_data)
+printer_options_response_cb (GtkDialog *dialog,
+                             gint       response_id,
+                             gpointer   user_data)
 {
-  CcPrintersPanelPrivate  *priv;
-  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
-  GtkWidget               *widget;
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  pp_options_dialog_free (priv->pp_options_dialog);
+  priv->pp_options_dialog = NULL;
+  update_sensitivity (self);
+
+  if (response_id == GTK_RESPONSE_OK)
+    actualize_printers_list (self);
+}
+
+static void
+printer_options_cb (GtkToolButton *toolbutton,
+                    gpointer       user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GtkWidget              *widget;
+  gboolean                is_authorized;
 
   priv = PRINTERS_PANEL_PRIVATE (self);
 
   widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "notebook");
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), NOTEBOOK_OPTIONS_PAGE);
+    gtk_builder_get_object (priv->builder, "main-vbox");
+
+  is_authorized =
+    priv->permission &&
+    g_permission_get_allowed (G_PERMISSION (priv->permission)) &&
+    priv->lockdown_settings &&
+    !g_settings_get_boolean (priv->lockdown_settings, "disable-print-setup");
+
+  if (priv->current_dest >= 0 &&
+      priv->current_dest < priv->num_dests &&
+      priv->dests != NULL)
+    {
+      priv->pp_options_dialog = pp_options_dialog_new (
+        GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+        printer_options_response_cb,
+        self,
+        priv->dests[priv->current_dest].name,
+        is_authorized);
+      update_sensitivity (self);
+    }
 }
 
 static gboolean
@@ -3100,11 +2879,8 @@ cc_printers_panel_init (CcPrintersPanel *self)
   priv->num_jobs = 0;
   priv->current_job = -1;
 
-  priv->allowed_users = NULL;
-  priv->num_allowed_users = 0;
-  priv->current_allowed_user = -1;
-
   priv->pp_new_printer_dialog = NULL;
+  priv->pp_options_dialog = NULL;
 
   priv->subscription_id = 0;
   priv->cups_status_check_id = 0;
@@ -3169,14 +2945,6 @@ cc_printers_panel_init (CcPrintersPanel *self)
   g_signal_connect (widget, "notify::active", G_CALLBACK (printer_disable_cb), self);
 
   widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "allowed-user-remove-button");
-  g_signal_connect (widget, "clicked", G_CALLBACK (allowed_user_remove_cb), self);
-
-  widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "allowed-user-add-button");
-  g_signal_connect (widget, "clicked", G_CALLBACK (allowed_user_add_button_cb), self);
-
-  widget = (GtkWidget*)
     gtk_builder_get_object (priv->builder, "supply-drawing-area");
   g_signal_connect (widget, "draw", G_CALLBACK (supply_levels_draw_cb), self);
 
@@ -3202,7 +2970,7 @@ cc_printers_panel_init (CcPrintersPanel *self)
 
   widget = (GtkWidget*)
     gtk_builder_get_object (priv->builder, "printer-options-button");
-  g_signal_connect (widget, "clicked", G_CALLBACK (switch_to_options_cb), self);
+  g_signal_connect (widget, "clicked", G_CALLBACK (printer_options_cb), self);
 
   widget = (GtkWidget*)
     gtk_builder_get_object (priv->builder, "printer-name-label");
@@ -3237,16 +3005,6 @@ cc_printers_panel_init (CcPrintersPanel *self)
   gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
 
   widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "allowed-users-scrolledwindow");
-  context = gtk_widget_get_style_context (widget);
-  gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
-
-  widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "allowed-users-toolbar");
-  context = gtk_widget_get_style_context (widget);
-  gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
-
-  widget = (GtkWidget*)
     gtk_builder_get_object (priv->builder, "queue-scrolledwindow");
   context = gtk_widget_get_style_context (widget);
   gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
@@ -3283,7 +3041,6 @@ Please check your installation");
 
   populate_printers_list (self);
   populate_jobs_list (self);
-  populate_allowed_users_list (self);
   attach_to_cups_notifier (self);
 
   priv->getting_all_ppds = TRUE;
diff --git a/panels/printers/options-dialog.ui b/panels/printers/options-dialog.ui
new file mode 100644
index 0000000..f100335
--- /dev/null
+++ b/panels/printers/options-dialog.ui
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkDialog" id="options-dialog">
+    <property name="width_request">500</property>
+    <property name="height_request">400</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes"> </property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="main-vbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkAlignment" id="content-alignment">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkBox" id="box1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkLabel" id="options-title">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Options</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="scrolled-window1">
+                        <property name="width_request">120</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="hscrollbar_policy">never</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkTreeView" id="options-categories-treeview">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="headers_visible">False</property>
+                            <child internal-child="selection">
+                              <object class="GtkTreeSelection" id="treeview-selection1"/>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkNotebook" id="options-notebook">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tab_pos">left</property>
+                        <property name="show_tabs">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="pack_type">end</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action-area1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkBox" id="box3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="width_request">24</property>
+                    <property name="height_request">24</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkSpinner" id="options-spinner">
+                        <property name="can_focus">False</property>
+                        <property name="no_show_all">True</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="progress-label">
+                    <property name="can_focus">False</property>
+                    <property name="no_show_all">True</property>
+                    <property name="label" translatable="yes">Loading options...</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">10</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="options-close-button">
+                <property name="label" translatable="yes">Close</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">options-close-button</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkSizeGroup" id="sizegroup1"/>
+</interface>
diff --git a/panels/printers/pp-ipp-option-widget.c b/panels/printers/pp-ipp-option-widget.c
new file mode 100644
index 0000000..8ee3ba3
--- /dev/null
+++ b/panels/printers/pp-ipp-option-widget.c
@@ -0,0 +1,605 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <glib/gi18n-lib.h>
+
+#include "pp-ipp-option-widget.h"
+#include "pp-utils.h"
+
+#define PP_IPP_OPTION_WIDGET_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), PP_TYPE_IPP_OPTION_WIDGET, PpIPPOptionWidgetPrivate))
+
+static void pp_ipp_option_widget_finalize (GObject *object);
+
+static gboolean construct_widget   (PpIPPOptionWidget *widget);
+static void     update_widget      (PpIPPOptionWidget *widget);
+static void     update_widget_real (PpIPPOptionWidget *widget);
+
+struct PpIPPOptionWidgetPrivate
+{
+  GtkWidget *switch_button;
+  GtkWidget *spin_button;
+  GtkWidget *combo;
+  GtkWidget *box;
+
+  IPPAttribute *option_supported;
+  IPPAttribute *option_default;
+
+  gchar *printer_name;
+  gchar *option_name;
+
+  GHashTable *ipp_attribute;
+};
+
+G_DEFINE_TYPE (PpIPPOptionWidget, pp_ipp_option_widget, GTK_TYPE_HBOX)
+
+static const struct {
+  const char *keyword;
+  const char *choice;
+  const char *translation;
+} ipp_choice_translations[] = {
+  /* Translators: this is an option of "Two Sided" */
+  { "sides", "one-sided", N_("One Sided") },
+  /* Translators: this is an option of "Two Sided" */
+  { "sides", "two-sided-long-edge", N_("Long Edge (Standard)") },
+  /* Translators: this is an option of "Two Sided" */
+  { "sides", "two-sided-short-edge", N_("Short Edge (Flip)") },
+  /* Translators: this is an option of "Orientation" */
+  { "orientation-requested", "3", N_("Portrait") },
+  /* Translators: this is an option of "Orientation" */
+  { "orientation-requested", "4", N_("Landscape") },
+  /* Translators: this is an option of "Orientation" */
+  { "orientation-requested", "5", N_("Reverse landscape") },
+  /* Translators: this is an option of "Orientation" */
+  { "orientation-requested", "6", N_("Reverse portrait") },
+};
+
+static const gchar *
+ipp_choice_translate (const gchar *option,
+                      const gchar *choice)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (ipp_choice_translations); i++)
+    {
+      if (g_strcmp0 (ipp_choice_translations[i].keyword, option) == 0 &&
+	  g_strcmp0 (ipp_choice_translations[i].choice, choice) == 0)
+	return _(ipp_choice_translations[i].translation);
+    }
+
+  return choice;
+}
+
+static void
+pp_ipp_option_widget_class_init (PpIPPOptionWidgetClass *class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = pp_ipp_option_widget_finalize;
+
+  g_type_class_add_private (class, sizeof (PpIPPOptionWidgetPrivate));
+}
+
+static void
+pp_ipp_option_widget_init (PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate *priv;
+
+  priv = widget->priv = PP_IPP_OPTION_WIDGET_GET_PRIVATE (widget);
+
+  priv->switch_button = NULL;
+  priv->spin_button = NULL;
+  priv->combo = NULL;
+  priv->box = NULL;
+
+  priv->printer_name = NULL;
+  priv->option_name = NULL;
+
+  priv->option_supported = NULL;
+  priv->option_default = NULL;
+
+  priv->ipp_attribute = NULL;
+}
+
+static void
+pp_ipp_option_widget_finalize (GObject *object)
+{
+  PpIPPOptionWidget *widget = PP_IPP_OPTION_WIDGET (object);
+  PpIPPOptionWidgetPrivate *priv = widget->priv;
+
+  if (priv)
+    {
+      if (priv->option_name)
+        {
+          g_free (priv->option_name);
+          priv->option_name = NULL;
+        }
+
+      if (priv->printer_name)
+        {
+          g_free (priv->printer_name);
+          priv->printer_name = NULL;
+        }
+
+      if (priv->option_supported)
+        {
+          ipp_attribute_free (priv->option_supported);
+          priv->option_supported = NULL;
+        }
+
+      if (priv->option_default)
+        {
+          ipp_attribute_free (priv->option_default);
+          priv->option_default = NULL;
+        }
+
+      if (priv->ipp_attribute)
+        {
+          g_hash_table_unref (priv->ipp_attribute);
+          priv->ipp_attribute = NULL;
+        }
+    }
+
+  G_OBJECT_CLASS (pp_ipp_option_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+pp_ipp_option_widget_new (IPPAttribute *attr_supported,
+                          IPPAttribute *attr_default,
+                          const gchar  *option_name,
+                          const gchar  *printer)
+{
+  PpIPPOptionWidgetPrivate *priv;
+  PpIPPOptionWidget        *widget = NULL;
+
+  if (attr_supported && option_name && printer)
+    {
+      widget = g_object_new (PP_TYPE_IPP_OPTION_WIDGET, NULL);
+
+      priv = PP_IPP_OPTION_WIDGET_GET_PRIVATE (widget);
+
+      priv->printer_name = g_strdup (printer);
+      priv->option_name = g_strdup (option_name);
+      priv->option_supported = ipp_attribute_copy (attr_supported);
+      priv->option_default = ipp_attribute_copy (attr_default);
+
+      if (construct_widget (widget))
+        {
+          update_widget_real (widget);
+        }
+      else
+        {
+          g_object_ref_sink (widget);
+          g_object_unref (widget);
+          widget = NULL;
+        }
+    }
+
+  return (GtkWidget *) widget;
+}
+
+enum {
+  NAME_COLUMN,
+  VALUE_COLUMN,
+  N_COLUMNS
+};
+
+static GtkWidget *
+combo_box_new (void)
+{
+  GtkCellRenderer *cell;
+  GtkListStore    *store;
+  GtkWidget       *combo_box;
+
+  combo_box = gtk_combo_box_new ();
+
+  store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+  g_object_unref (store);
+
+  cell = gtk_cell_renderer_text_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
+                                  "text", NAME_COLUMN,
+                                  NULL);
+
+  return combo_box;
+}
+
+static void
+combo_box_append (GtkWidget   *combo,
+                  const gchar *display_text,
+                  const gchar *value)
+{
+  GtkTreeModel *model;
+  GtkListStore *store;
+  GtkTreeIter   iter;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+  store = GTK_LIST_STORE (model);
+
+  gtk_list_store_append (store, &iter);
+  gtk_list_store_set (store, &iter,
+                      NAME_COLUMN, display_text,
+                      VALUE_COLUMN, value,
+                      -1);
+}
+
+struct ComboSet {
+  GtkComboBox *combo;
+  const gchar *value;
+};
+
+static gboolean
+set_cb (GtkTreeModel *model,
+        GtkTreePath  *path,
+        GtkTreeIter  *iter,
+        gpointer      data)
+{
+  struct ComboSet *set_data = data;
+  gboolean         found;
+  char            *value;
+
+  gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
+  found = (strcmp (value, set_data->value) == 0);
+  g_free (value);
+
+  if (found)
+    gtk_combo_box_set_active_iter (set_data->combo, iter);
+
+  return found;
+}
+
+static void
+combo_box_set (GtkWidget   *combo,
+               const gchar *value)
+{
+  struct ComboSet  set_data;
+  GtkTreeModel    *model;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+  set_data.combo = GTK_COMBO_BOX (combo);
+  set_data.value = value;
+  gtk_tree_model_foreach (model, set_cb, &set_data);
+}
+
+static char *
+combo_box_get (GtkWidget *combo)
+{
+  GtkTreeModel *model;
+  GtkTreeIter   iter;
+  gchar        *value = NULL;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+     gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
+
+  return value;
+}
+
+static void
+printer_add_option_async_cb (gboolean success,
+                             gpointer user_data)
+{
+  update_widget (user_data);
+}
+
+static void
+switch_changed_cb (GtkWidget         *switch_button,
+                   GParamSpec        *pspec,
+                   PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **values;
+
+  values = g_new0 (gchar *, 2);
+
+  if (gtk_switch_get_active (GTK_SWITCH (switch_button)))
+    values[0] = g_strdup ("True");
+  else
+    values[0] = g_strdup ("False");
+
+  printer_add_option_async (priv->printer_name,
+                            priv->option_name,
+                            values,
+                            TRUE,
+                            NULL,
+                            printer_add_option_async_cb,
+                            widget);
+
+  g_strfreev (values);
+}
+
+static void
+combo_changed_cb (GtkWidget         *combo,
+                  PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **values;
+
+  values = g_new0 (gchar *, 2);
+  values[0] = combo_box_get (combo);
+
+  printer_add_option_async (priv->printer_name,
+                            priv->option_name,
+                            values,
+                            TRUE,
+                            NULL,
+                            printer_add_option_async_cb,
+                            widget);
+
+  g_strfreev (values);
+}
+
+static void
+spin_button_changed_cb (GtkWidget         *spin_button,
+                        PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **values;
+
+  values = g_new0 (gchar *, 2);
+  values[0] = g_strdup_printf ("%d", gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin_button)));
+
+  printer_add_option_async (priv->printer_name,
+                            priv->option_name,
+                            values,
+                            TRUE,
+                            NULL,
+                            printer_add_option_async_cb,
+                            widget);
+
+  g_strfreev (values);
+}
+
+static gboolean
+construct_widget (PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate *priv = widget->priv;
+  gboolean                  trivial_option = FALSE;
+  gboolean                  result = FALSE;
+  gchar                    *value;
+  gint                      i;
+
+  if (priv->option_supported)
+    {
+      switch (priv->option_supported->attribute_type)
+        {
+          case IPP_ATTRIBUTE_TYPE_INTEGER:
+            if (priv->option_supported->num_of_values <= 1)
+              trivial_option = TRUE;
+            break;
+
+          case IPP_ATTRIBUTE_TYPE_STRING:
+            if (priv->option_supported->num_of_values <= 1)
+              trivial_option = TRUE;
+            break;
+
+          case IPP_ATTRIBUTE_TYPE_RANGE:
+            if (priv->option_supported->attribute_values[0].lower_range ==
+                priv->option_supported->attribute_values[0].upper_range)
+              trivial_option = TRUE;
+            break;
+        }
+
+      if (!trivial_option)
+        {
+          switch (priv->option_supported->attribute_type)
+            {
+              case IPP_ATTRIBUTE_TYPE_BOOLEAN:
+                  priv->switch_button = gtk_switch_new ();
+
+                  gtk_box_pack_start (GTK_BOX (widget), priv->switch_button, FALSE, FALSE, 0);
+                  g_signal_connect (priv->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), widget);
+                  break;
+
+              case IPP_ATTRIBUTE_TYPE_INTEGER:
+                  priv->combo = combo_box_new ();
+
+                  for (i = 0; i < priv->option_supported->num_of_values; i++)
+                    {
+                      value = g_strdup_printf ("%d", priv->option_supported->attribute_values[i].integer_value);
+                      combo_box_append (priv->combo,
+                                        ipp_choice_translate (priv->option_name,
+                                                              value),
+                                        value);
+                      g_free (value);
+                    }
+
+                  gtk_box_pack_start (GTK_BOX (widget), priv->combo, FALSE, FALSE, 0);
+                  g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed_cb), widget);
+                  break;
+
+              case IPP_ATTRIBUTE_TYPE_STRING:
+                  priv->combo = combo_box_new ();
+
+                  for (i = 0; i < priv->option_supported->num_of_values; i++)
+                    combo_box_append (priv->combo,
+                                      ipp_choice_translate (priv->option_name,
+                                                            priv->option_supported->attribute_values[i].string_value),
+                                      priv->option_supported->attribute_values[i].string_value);
+
+                  gtk_box_pack_start (GTK_BOX (widget), priv->combo, FALSE, FALSE, 0);
+                  g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed_cb), widget);
+                  break;
+
+              case IPP_ATTRIBUTE_TYPE_RANGE:
+                  priv->spin_button = gtk_spin_button_new_with_range (
+                                        priv->option_supported->attribute_values[0].lower_range,
+                                        priv->option_supported->attribute_values[0].upper_range,
+                                        1);
+
+                  gtk_box_pack_start (GTK_BOX (widget), priv->spin_button, FALSE, FALSE, 0);
+                  g_signal_connect (priv->spin_button, "value-changed", G_CALLBACK (spin_button_changed_cb), widget);
+                  break;
+
+              default:
+                  break;
+            }
+
+          result = TRUE;
+        }
+    }
+
+  return result;
+}
+
+static void
+update_widget_real (PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate *priv = widget->priv;
+  IPPAttribute             *attr = NULL;
+  gchar                    *value;
+  gchar                    *attr_name;
+
+  if (priv->option_default)
+    {
+      attr = ipp_attribute_copy (priv->option_default);
+
+      ipp_attribute_free (priv->option_default);
+      priv->option_default = NULL;
+    }
+  else if (priv->ipp_attribute)
+    {
+      attr_name = g_strdup_printf ("%s-default", priv->option_name);
+      attr = ipp_attribute_copy (g_hash_table_lookup (priv->ipp_attribute, attr_name));
+
+      g_free (attr_name);
+      g_hash_table_unref (priv->ipp_attribute);
+      priv->ipp_attribute = NULL;
+    }
+
+  switch (priv->option_supported->attribute_type)
+    {
+      case IPP_ATTRIBUTE_TYPE_BOOLEAN:
+        g_signal_handlers_block_by_func (priv->switch_button, switch_changed_cb, widget);
+
+        if (attr && attr->num_of_values > 0 &&
+            attr->attribute_type == IPP_ATTRIBUTE_TYPE_BOOLEAN)
+          {
+            gtk_switch_set_active (GTK_SWITCH (priv->switch_button),
+                                   attr->attribute_values[0].boolean_value);
+          }
+
+        g_signal_handlers_unblock_by_func (priv->switch_button, switch_changed_cb, widget);
+        break;
+
+      case IPP_ATTRIBUTE_TYPE_INTEGER:
+        g_signal_handlers_block_by_func (priv->combo, combo_changed_cb, widget);
+
+        if (attr && attr->num_of_values > 0 &&
+            attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
+          {
+            value = g_strdup_printf ("%d", attr->attribute_values[0].integer_value);
+            combo_box_set (priv->combo, value);
+            g_free (value);
+          }
+        else
+          {
+            value = g_strdup_printf ("%d", priv->option_supported->attribute_values[0].integer_value);
+            combo_box_set (priv->combo, value);
+            g_free (value);
+          }
+
+        g_signal_handlers_unblock_by_func (priv->combo, combo_changed_cb, widget);
+        break;
+
+      case IPP_ATTRIBUTE_TYPE_STRING:
+        g_signal_handlers_block_by_func (priv->combo, combo_changed_cb, widget);
+
+        if (attr && attr->num_of_values > 0 &&
+            attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING)
+          {
+            combo_box_set (priv->combo, attr->attribute_values[0].string_value);
+          }
+        else
+          {
+            combo_box_set (priv->combo, priv->option_supported->attribute_values[0].string_value);
+          }
+
+        g_signal_handlers_unblock_by_func (priv->combo, combo_changed_cb, widget);
+        break;
+
+      case IPP_ATTRIBUTE_TYPE_RANGE:
+        g_signal_handlers_block_by_func (priv->spin_button, spin_button_changed_cb, widget);
+
+        if (attr && attr->num_of_values > 0 &&
+            attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
+          {
+            gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->spin_button),
+                                       attr->attribute_values[0].integer_value);
+          }
+        else
+          {
+            gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->spin_button),
+                                       priv->option_supported->attribute_values[0].lower_range);
+          }
+
+        g_signal_handlers_unblock_by_func (priv->spin_button, spin_button_changed_cb, widget);
+        break;
+
+      default:
+        break;
+    }
+
+  ipp_attribute_free (attr);
+}
+
+static void
+get_ipp_attributes_cb (GHashTable *table,
+                       gpointer    user_data)
+{
+  PpIPPOptionWidget        *widget = (PpIPPOptionWidget *) user_data;
+  PpIPPOptionWidgetPrivate *priv = widget->priv;
+
+  if (priv->ipp_attribute)
+    g_hash_table_unref (priv->ipp_attribute);
+
+  priv->ipp_attribute = table;
+
+  update_widget_real (widget);
+}
+
+static void
+update_widget (PpIPPOptionWidget *widget)
+{
+  PpIPPOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **attributes_names;
+
+  attributes_names = g_new0 (gchar *, 2);
+  attributes_names[0] = g_strdup_printf ("%s-default", priv->option_name);
+
+  get_ipp_attributes_async (priv->printer_name,
+                            attributes_names,
+                            get_ipp_attributes_cb,
+                            widget);
+
+  g_strfreev (attributes_names);
+}
diff --git a/panels/printers/pp-ipp-option-widget.h b/panels/printers/pp-ipp-option-widget.h
new file mode 100644
index 0000000..343ebc0
--- /dev/null
+++ b/panels/printers/pp-ipp-option-widget.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#ifndef __PP_IPP_OPTION_WIDGET_H__
+#define __PP_IPP_OPTION_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_IPP_OPTION_WIDGET                  (pp_ipp_option_widget_get_type ())
+#define PP_IPP_OPTION_WIDGET(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), PP_TYPE_IPP_OPTION_WIDGET, PpIPPOptionWidget))
+#define PP_IPP_OPTION_WIDGET_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass),  PP_TYPE_IPP_OPTION_WIDGET, PpIPPOptionWidgetClass))
+#define PP_IS_IPP_OPTION_WIDGET(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PP_TYPE_IPP_OPTION_WIDGET))
+#define PP_IS_IPP_OPTION_WIDGET_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass),  PP_TYPE_IPP_OPTION_WIDGET))
+#define PP_IPP_OPTION_WIDGET_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj),  PP_TYPE_IPP_OPTION_WIDGET, PpIPPOptionWidgetClass))
+
+typedef struct _PpIPPOptionWidget         PpIPPOptionWidget;
+typedef struct _PpIPPOptionWidgetClass    PpIPPOptionWidgetClass;
+typedef struct PpIPPOptionWidgetPrivate   PpIPPOptionWidgetPrivate;
+
+struct _PpIPPOptionWidget
+{
+  GtkHBox parent_instance;
+
+  PpIPPOptionWidgetPrivate *priv;
+};
+
+struct _PpIPPOptionWidgetClass
+{
+  GtkHBoxClass parent_class;
+
+  void (*changed) (PpIPPOptionWidget *widget);
+};
+
+typedef void (*IPPOptionCallback) (GtkWidget *widget,
+                                   gpointer   user_data);
+
+GType	     pp_ipp_option_widget_get_type  (void) G_GNUC_CONST;
+
+GtkWidget   *pp_ipp_option_widget_new (IPPAttribute *attr_supported,
+                                       IPPAttribute *attr_default,
+                                       const gchar  *option_name,
+                                       const gchar  *printer);
+
+G_END_DECLS
+
+#endif /* __PP_IPP_OPTION_WIDGET_H__ */
diff --git a/panels/printers/pp-options-dialog.c b/panels/printers/pp-options-dialog.c
new file mode 100644
index 0000000..ea9c8af
--- /dev/null
+++ b/panels/printers/pp-options-dialog.c
@@ -0,0 +1,984 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-options-dialog.h"
+#include "pp-ppd-option-widget.h"
+#include "pp-ipp-option-widget.h"
+#include "pp-utils.h"
+
+struct _PpOptionsDialog {
+  GtkBuilder *builder;
+  GtkWidget  *parent;
+
+  GtkWidget  *dialog;
+
+  UserResponseCallback user_callback;
+  gpointer             user_data;
+
+  gchar       *printer_name;
+
+  gchar       *ppd_filename;
+  gboolean     ppd_filename_set;
+
+  cups_dest_t *destination;
+  gboolean     destination_set;
+
+  GHashTable  *ipp_attributes;
+  gboolean     ipp_attributes_set;
+
+  gboolean     populating_dialog;
+
+  GtkResponseType response;
+
+  gboolean sensitive;
+};
+
+static void pp_options_dialog_hide (PpOptionsDialog *dialog);
+
+enum
+{
+  CATEGORY_IDS_COLUMN = 0,
+  CATEGORY_NAMES_COLUMN
+};
+
+/* These lists come from Gtk+ */
+static const struct {
+  const char *keyword;
+  const char *translation;
+} ppd_option_translations[] = {
+  { "Duplex", N_("Two Sided") },
+  { "MediaType", N_("Paper Type") },
+  { "InputSlot", N_("Paper Source") },
+  { "OutputBin", N_("Output Tray") },
+  { "Resolution", N_("Resolution") },
+  { "PreFilter", N_("GhostScript pre-filtering") },
+};
+
+/* keep sorted when changing */
+static const char *page_setup_option_whitelist[] = {
+  "InputSlot",
+  "MediaType",
+  "OutputBin",
+  "PageSize",
+};
+
+/* keep sorted when changing */
+static const char *color_option_whitelist[] = {
+  "BRColorEnhancement",
+  "BRColorMatching",
+  "BRColorMatching",
+  "BRColorMode",
+  "BRGammaValue",
+  "BRImprovedGray",
+  "BlackSubstitution",
+  "ColorModel",
+  "HPCMYKInks",
+  "HPCSGraphics",
+  "HPCSImages",
+  "HPCSText",
+  "HPColorSmart",
+  "RPSBlackMode",
+  "RPSBlackOverPrint",
+  "Rcmyksimulation",
+};
+
+/* keep sorted when changing */
+static const char *color_group_whitelist[] = {
+  "Color",
+  "Color1",
+  "Color2",
+  "ColorBalance",
+  "ColorPage",
+  "ColorSettings1",
+  "ColorSettings2",
+  "ColorSettings3",
+  "ColorSettings4",
+  "EPColorSettings",
+  "FPColorWise1",
+  "FPColorWise2",
+  "FPColorWise3",
+  "FPColorWise4",
+  "FPColorWise5",
+  "HPCMYKInksPanel",
+  "HPColorOptions",
+  "HPColorOptionsPanel",
+  "HPColorQualityOptionsPanel",
+  "ManualColor",
+};
+
+/* keep sorted when changing */
+static const char *image_quality_option_whitelist[] = {
+  "BRDocument",
+  "BRHalfTonePattern",
+  "BRNormalPrt",
+  "BRPrintQuality",
+  "BitsPerPixel",
+  "Darkness",
+  "Dithering",
+  "EconoMode",
+  "Economode",
+  "HPEconoMode",
+  "HPEdgeControl",
+  "HPGraphicsHalftone",
+  "HPHalftone",
+  "HPImagingOptions",
+  "HPLJDensity",
+  "HPPhotoHalftone",
+  "HPPrintQualityOptions",
+  "HPResolutionOptions",
+  "OutputMode",
+  "REt",
+  "RPSBitsPerPixel",
+  "RPSDitherType",
+  "Resolution",
+  "ScreenLock",
+  "Smoothing",
+  "TonerSaveMode",
+  "UCRGCRForImage",
+};
+
+/* keep sorted when changing */
+static const char *image_quality_group_whitelist[] = {
+  "EPQualitySettings",
+  "FPImageQuality1",
+  "FPImageQuality2",
+  "FPImageQuality3",
+  "ImageQualityPage",
+  "Quality",
+};
+
+/* keep sorted when changing */
+static const char * finishing_option_whitelist[] = {
+  "BindColor",
+  "BindEdge",
+  "BindType",
+  "BindWhen",
+  "Booklet",
+  "FoldType",
+  "FoldWhen",
+  "HPStaplerOptions",
+  "Jog",
+  "Slipsheet",
+  "Sorter",
+  "StapleLocation",
+  "StapleOrientation",
+  "StapleWhen",
+  "StapleX",
+  "StapleY",
+};
+
+/* keep sorted when changing */
+static const char *job_group_whitelist[] = {
+  "JobHandling",
+  "JobLog",
+};
+
+/* keep sorted when changing */
+static const char *finishing_group_whitelist[] = {
+  "Booklet",
+  "BookletCover",
+  "BookletModeOptions",
+  "FPFinishing1",
+  "FPFinishing2",
+  "FPFinishing3",
+  "FPFinishing4",
+  "Finishing",
+  "FinishingOptions",
+  "FinishingPage",
+  "HPBookletPanel",
+  "HPFinishing",
+  "HPFinishingOptions",
+  "HPFinishingPanel",
+};
+
+/* keep sorted when changing */
+static const char *installable_options_group_whitelist[] = {
+  "InstallableOptions",
+};
+
+/* keep sorted when changing */
+static const char *page_setup_group_whitelist[] = {
+  "HPMarginAndLayout",
+  "OutputControl",
+  "PaperHandling",
+  "Paper",
+  "Source",
+};
+
+/* keep sorted when changing */
+static const char *ppd_option_blacklist[] = {
+  "Collate",
+  "Copies",
+  "Duplex",
+  "HPManualDuplexOrientation",
+  "HPManualDuplexSwitch",
+  "OutputOrder",
+  "PageRegion"
+};
+
+static int
+strptr_cmp (const void *a,
+	    const void *b)
+{
+  char **aa = (char **)a;
+  char **bb = (char **)b;
+  return strcmp (*aa, *bb);
+}
+
+static gboolean
+string_in_table (gchar       *str,
+		 const gchar *table[],
+		 gint         table_len)
+{
+  return bsearch (&str, table, table_len, sizeof (char *), (void *)strptr_cmp) != NULL;
+}
+
+#define STRING_IN_TABLE(_str, _table) (string_in_table (_str, _table, G_N_ELEMENTS (_table)))
+
+static gchar *
+ppd_option_name_translate (ppd_option_t *option)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (ppd_option_translations); i++)
+    {
+      if (g_strcmp0 (ppd_option_translations[i].keyword, option->keyword) == 0)
+	return g_strdup (_(ppd_option_translations[i].translation));
+    }
+
+  return g_strdup (option->text);
+}
+
+static gint
+grid_get_height (GtkWidget *grid)
+{
+  GList *children;
+  GList *child;
+  gint   height = 0;
+  gint   top_attach = 0;
+  gint   max = 0;
+
+  children = gtk_container_get_children (GTK_CONTAINER (grid));
+  for (child = children; child; child = g_list_next (child))
+    {
+      gtk_container_child_get (GTK_CONTAINER (grid), child->data,
+                               "top-attach", &top_attach,
+                               "height", &height,
+                               NULL);
+
+      if (height + top_attach > max)
+        max = height + top_attach;
+    }
+
+  g_list_free (children);
+
+  return max;
+}
+
+static gboolean
+grid_is_empty (GtkWidget *grid)
+{
+  GList *children;
+
+  children = gtk_container_get_children (GTK_CONTAINER (grid));
+  if (children)
+    {
+      g_list_free (children);
+      return FALSE;
+    }
+  else
+    {
+      return TRUE;
+    }
+}
+
+static GtkWidget *
+ipp_option_add (IPPAttribute *attr_supported,
+                IPPAttribute *attr_default,
+                const gchar  *option_name,
+                const gchar  *option_display_name,
+                const gchar  *printer_name,
+                GtkWidget    *grid,
+                gboolean      sensitive)
+{
+  GtkStyleContext *context;
+  GtkWidget       *widget;
+  GtkWidget       *label;
+  gint             position;
+
+  widget = (GtkWidget *) pp_ipp_option_widget_new (attr_supported,
+                                                   attr_default,
+                                                   option_name,
+                                                   printer_name);
+  if (widget)
+    {
+      gtk_widget_set_sensitive (widget, sensitive);
+      position = grid_get_height (grid);
+
+      label = gtk_label_new (option_display_name);
+      context = gtk_widget_get_style_context (label);
+      gtk_style_context_add_class (context, "dim-label");
+      gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+      gtk_widget_set_margin_left (label, 10);
+      gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
+
+      gtk_widget_set_margin_left (widget, 20);
+      gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
+    }
+
+  return widget;
+}
+
+static GtkWidget *
+ppd_option_add (ppd_option_t  option,
+                const gchar  *printer_name,
+                GtkWidget    *grid,
+                gboolean      sensitive)
+{
+  GtkStyleContext *context;
+  GtkWidget       *widget;
+  GtkWidget       *label;
+  gint             position;
+
+  widget = (GtkWidget *) pp_ppd_option_widget_new (&option, printer_name);
+  if (widget)
+    {
+      gtk_widget_set_sensitive (widget, sensitive);
+      position = grid_get_height (grid);
+
+      label = gtk_label_new (ppd_option_name_translate (&option));
+      context = gtk_widget_get_style_context (label);
+      gtk_style_context_add_class (context, "dim-label");
+      gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+      gtk_widget_set_margin_left (label, 10);
+      gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
+
+      gtk_widget_set_margin_left (widget, 20);
+      gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
+    }
+
+  return widget;
+}
+
+static GtkWidget *
+tab_grid_new ()
+{
+  GtkWidget *grid;
+
+  grid = gtk_grid_new ();
+  gtk_container_set_border_width (GTK_CONTAINER (grid), 20);
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 15);
+
+  return grid;
+}
+
+static void
+tab_add (const gchar *tab_name,
+         GtkWidget   *options_notebook,
+         GtkTreeView *treeview,
+         GtkWidget   *grid)
+{
+  GtkListStore *store;
+  GtkTreeIter   iter;
+  GtkWidget    *scrolled_window;
+  gboolean      unref_store = FALSE;
+  gint          id;
+
+  if (!grid_is_empty (grid))
+    {
+      scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+                                      GTK_POLICY_NEVER,
+                                      GTK_POLICY_AUTOMATIC);
+      gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window),
+                                             grid);
+
+      id = gtk_notebook_append_page (GTK_NOTEBOOK (options_notebook),
+                                     scrolled_window,
+                                     NULL);
+
+      if (id >= 0)
+        {
+          store = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
+          if (!store)
+            {
+              store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+              unref_store = TRUE;
+            }
+
+          gtk_list_store_append (store, &iter);
+          gtk_list_store_set (store, &iter,
+                              CATEGORY_IDS_COLUMN, id,
+                              CATEGORY_NAMES_COLUMN, tab_name,
+                              -1);
+
+          if (unref_store)
+            {
+              gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));
+              g_object_unref (store);
+            }
+        }
+    }
+  else
+    {
+      g_object_ref_sink (grid);
+      g_object_unref (grid);
+    }
+}
+
+static void
+category_selection_changed_cb (GtkTreeSelection *selection,
+                               gpointer          user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog *) user_data;
+  GtkTreeModel    *model;
+  GtkTreeIter      iter;
+  GtkWidget       *options_notebook;
+  gint             id = -1;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      gtk_tree_model_get (model, &iter,
+			  CATEGORY_IDS_COLUMN, &id,
+			  -1);
+    }
+
+  if (id >= 0)
+    {
+      options_notebook = (GtkWidget*)
+        gtk_builder_get_object (dialog->builder, "options-notebook");
+
+      gtk_notebook_set_current_page (GTK_NOTEBOOK (options_notebook), id);
+    }
+}
+
+static void
+populate_options_real (PpOptionsDialog *dialog)
+{
+  GtkTreeSelection *selection;
+  GtkTreeModel     *model;
+  GtkTreeView      *treeview;
+  GtkTreeIter       iter;
+  ppd_file_t       *ppd_file;
+  GtkWidget        *notebook;
+  GtkWidget        *grid;
+  GtkWidget        *general_tab_grid = tab_grid_new ();
+  GtkWidget        *page_setup_tab_grid = tab_grid_new ();
+  GtkWidget        *installable_options_tab_grid = tab_grid_new ();
+  GtkWidget        *job_tab_grid = tab_grid_new ();
+  GtkWidget        *image_quality_tab_grid = tab_grid_new ();
+  GtkWidget        *color_tab_grid = tab_grid_new ();
+  GtkWidget        *finishing_tab_grid = tab_grid_new ();
+  GtkWidget        *advanced_tab_grid = tab_grid_new ();
+  GtkWidget        *widget;
+  gint              i, j;
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "options-spinner");
+  gtk_widget_hide (widget);
+  gtk_spinner_stop (GTK_SPINNER (widget));
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "progress-label");
+  gtk_widget_hide (widget);
+
+  treeview = (GtkTreeView *)
+    gtk_builder_get_object (dialog->builder, "options-categories-treeview");
+
+  notebook = (GtkWidget *)
+    gtk_builder_get_object (dialog->builder, "options-notebook");
+
+  if (dialog->ipp_attributes)
+    {
+      /* Add number-up option to Page Setup tab */
+      ipp_option_add (g_hash_table_lookup (dialog->ipp_attributes,
+                                           "number-up-supported"),
+                      g_hash_table_lookup (dialog->ipp_attributes,
+                                           "number-up-default"),
+                      "number-up",
+                      /* Translators: This option sets number of pages printed on one sheet */
+                      _("Pages per side"),
+                      dialog->printer_name,
+                      page_setup_tab_grid,
+                      dialog->sensitive);
+
+      /* Add sides option to Page Setup tab */
+      ipp_option_add (g_hash_table_lookup (dialog->ipp_attributes,
+                                           "sides-supported"),
+                      g_hash_table_lookup (dialog->ipp_attributes,
+                                           "sides-default"),
+                      "sides",
+                      /* Translators: This option sets whether to print on both sides of paper */
+                      _("Two-sided"),
+                      dialog->printer_name,
+                      page_setup_tab_grid,
+                      dialog->sensitive);
+
+      /* Add orientation-requested option to Page Setup tab */
+      ipp_option_add (g_hash_table_lookup (dialog->ipp_attributes,
+                                           "orientation-requested-supported"),
+                      g_hash_table_lookup (dialog->ipp_attributes,
+                                           "orientation-requested-default"),
+                      "orientation-requested",
+                      /* Translators: This option sets orientation of print (portrait, landscape...) */
+                      _("Orientation"),
+                      dialog->printer_name,
+                      page_setup_tab_grid,
+                      dialog->sensitive);
+    }
+
+  if (dialog->destination && dialog->ppd_filename)
+    {
+      ppd_file = ppdOpenFile (dialog->ppd_filename);
+      ppdLocalize (ppd_file);
+
+      if (ppd_file)
+        {
+          ppdMarkDefaults (ppd_file);
+          cupsMarkOptions (ppd_file,
+                           dialog->destination->num_options,
+                           dialog->destination->options);
+
+          for (i = 0; i < ppd_file->num_groups; i++)
+            {
+              for (j = 0; j < ppd_file->groups[i].num_options; j++)
+                {
+                  grid = NULL;
+
+                  if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                       color_group_whitelist))
+                    grid = color_tab_grid;
+                  else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                            image_quality_group_whitelist))
+                    grid = image_quality_tab_grid;
+                  else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                            job_group_whitelist))
+                    grid = job_tab_grid;
+                  else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                            finishing_group_whitelist))
+                    grid = finishing_tab_grid;
+                  else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                            installable_options_group_whitelist))
+                    grid = installable_options_tab_grid;
+                  else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+                                            page_setup_group_whitelist))
+                    grid = page_setup_tab_grid;
+
+                  if (!STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+                                        ppd_option_blacklist))
+                    {
+                      if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+                                                    color_option_whitelist))
+                        grid = color_tab_grid;
+                      else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+                                                         image_quality_option_whitelist))
+                        grid = image_quality_tab_grid;
+                      else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+                                                         finishing_option_whitelist))
+                        grid = finishing_tab_grid;
+                      else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+                                                         page_setup_option_whitelist))
+                        grid = page_setup_tab_grid;
+
+                      if (!grid)
+                        grid = advanced_tab_grid;
+
+                      ppd_option_add (ppd_file->groups[i].options[j],
+                                      dialog->printer_name,
+                                      grid,
+                                      dialog->sensitive);
+                    }
+                }
+            }
+
+          ppdClose (ppd_file);
+        }
+    }
+
+  dialog->ppd_filename_set = FALSE;
+  if (dialog->ppd_filename)
+    {
+      g_unlink (dialog->ppd_filename);
+      g_free (dialog->ppd_filename);
+      dialog->ppd_filename = NULL;
+    }
+
+  dialog->destination_set = FALSE;
+  if (dialog->destination)
+    {
+      cupsFreeDests (1, dialog->destination);
+      dialog->destination = NULL;
+    }
+
+  dialog->ipp_attributes_set = FALSE;
+  if (dialog->ipp_attributes)
+    {
+      g_hash_table_unref (dialog->ipp_attributes);
+      dialog->ipp_attributes = NULL;
+    }
+
+  /* Translators: "General" tab contains general printer options */
+  tab_add (C_("Printer Option Group", "General"), notebook, treeview, general_tab_grid);
+
+  /* Translators: "Page Setup" tab contains settings related to pages (page size, paper source, etc.) */
+  tab_add (C_("Printer Option Group", "Page Setup"), notebook, treeview, page_setup_tab_grid);
+
+  /* Translators: "Installable Options" tab contains settings of presence of installed options (amount of RAM, duplex unit, etc.) */
+  tab_add (C_("Printer Option Group", "Installable Options"), notebook, treeview, installable_options_tab_grid);
+
+  /* Translators: "Job" tab contains settings for jobs */
+  tab_add (C_("Printer Option Group", "Job"), notebook, treeview, job_tab_grid);
+
+  /* Translators: "Image Quality" tab contains settings for quality of output print (e.g. resolution) */
+  tab_add (C_("Printer Option Group", "Image Quality"), notebook, treeview, image_quality_tab_grid);
+
+  /* Translators: "Color" tab contains color settings (e.g. color printing) */
+  tab_add (C_("Printer Option Group", "Color"), notebook, treeview, color_tab_grid);
+
+  /* Translators: "Finishing" tab contains finishing settings (e.g. booklet printing) */
+  tab_add (C_("Printer Option Group", "Finishing"), notebook, treeview, finishing_tab_grid);
+
+  /* Translators: "Advanced" tab contains all others settings */
+  tab_add (C_("Printer Option Group", "Advanced"), notebook, treeview, advanced_tab_grid);
+
+  gtk_widget_show_all (GTK_WIDGET (notebook));
+
+  /* Select the first option group */
+  if ((selection = gtk_tree_view_get_selection (treeview)) != NULL)
+    {
+      g_signal_connect (selection,
+                        "changed",
+                        G_CALLBACK (category_selection_changed_cb), dialog);
+
+      if ((model = gtk_tree_view_get_model (treeview)) != NULL &&
+          gtk_tree_model_get_iter_first (model, &iter))
+        gtk_tree_selection_select_iter (selection, &iter);
+    }
+
+  dialog->populating_dialog = FALSE;
+  if (dialog->response != GTK_RESPONSE_NONE)
+    {
+      dialog->user_callback (GTK_DIALOG (dialog->dialog), dialog->response, dialog->user_data);
+    }
+}
+
+static void
+printer_get_ppd_cb (const gchar *ppd_filename,
+                    gpointer     user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog *) user_data;
+
+  if (dialog->ppd_filename)
+    {
+      g_unlink (dialog->ppd_filename);
+      g_free (dialog->ppd_filename);
+    }
+
+  dialog->ppd_filename = g_strdup (ppd_filename);
+  dialog->ppd_filename_set = TRUE;
+
+  if (dialog->destination_set &&
+      dialog->ipp_attributes_set)
+    {
+      populate_options_real (dialog);
+    }
+}
+
+static void
+get_named_dest_cb (cups_dest_t *dest,
+                   gpointer     user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog *) user_data;
+
+  if (dialog->destination)
+    cupsFreeDests (1, dialog->destination);
+
+  dialog->destination = dest;
+  dialog->destination_set = TRUE;
+
+  if (dialog->ppd_filename_set &&
+      dialog->ipp_attributes_set)
+    {
+      populate_options_real (dialog);
+    }
+}
+
+static void
+get_ipp_attributes_cb (GHashTable *table,
+                       gpointer    user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog *) user_data;
+
+  if (dialog->ipp_attributes)
+    g_hash_table_unref (dialog->ipp_attributes);
+
+  dialog->ipp_attributes = table;
+  dialog->ipp_attributes_set = TRUE;
+
+  if (dialog->ppd_filename_set &&
+      dialog->destination_set)
+    {
+      populate_options_real (dialog);
+    }
+}
+
+static void
+populate_options (PpOptionsDialog *dialog)
+{
+  GtkTreeViewColumn  *column;
+  GtkCellRenderer    *renderer;
+  GtkTreeView        *treeview;
+  GtkWidget          *widget;
+  /*
+   * Options which we need to obtain through an IPP request
+   * to be able to fill the options dialog.
+   * *-supported - possible values of the option
+   * *-default - actual value of the option
+   */
+  const gchar        *attributes[] =
+    { "number-up-supported",
+      "number-up-default",
+      "sides-supported",
+      "sides-default",
+      "orientation-requested-supported",
+      "orientation-requested-default",
+      NULL};
+
+  treeview = (GtkTreeView *)
+    gtk_builder_get_object (dialog->builder, "options-categories-treeview");
+
+  renderer = gtk_cell_renderer_text_new ();
+
+  column = gtk_tree_view_column_new_with_attributes ("Categories", renderer,
+                                                     "text", CATEGORY_NAMES_COLUMN, NULL);
+  gtk_tree_view_column_set_expand (column, TRUE);
+  gtk_tree_view_append_column (treeview, column);
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "options-spinner");
+  gtk_widget_show (widget);
+  gtk_spinner_start (GTK_SPINNER (widget));
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "progress-label");
+  gtk_widget_show (widget);
+
+  printer_get_ppd_async (dialog->printer_name,
+                         printer_get_ppd_cb,
+                         dialog);
+
+  get_named_dest_async (dialog->printer_name,
+                        get_named_dest_cb,
+                        dialog);
+
+  get_ipp_attributes_async (dialog->printer_name,
+                            (gchar **) attributes,
+                            get_ipp_attributes_cb,
+                            dialog);
+}
+
+/*
+ * Modify padding of the content area of the GtkDialog
+ * so it is aligned with the action area.
+ */
+static void
+update_alignment_padding (GtkWidget     *widget,
+                          GtkAllocation *allocation,
+                          gpointer       user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog*) user_data;
+  GtkAllocation    allocation1, allocation2;
+  GtkWidget       *action_area;
+  GtkWidget       *content_area;
+  gint             offset_left, offset_right;
+  guint            padding_left, padding_right,
+                   padding_top, padding_bottom;
+
+  action_area = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "dialog-action-area1");
+  gtk_widget_get_allocation (action_area, &allocation2);
+
+  content_area = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "content-alignment");
+  gtk_widget_get_allocation (content_area, &allocation1);
+
+  offset_left = allocation2.x - allocation1.x;
+  offset_right = (allocation1.x + allocation1.width) -
+                 (allocation2.x + allocation2.width);
+
+  gtk_alignment_get_padding  (GTK_ALIGNMENT (content_area),
+                              &padding_top, &padding_bottom,
+                              &padding_left, &padding_right);
+  if (allocation1.x >= 0 && allocation2.x >= 0)
+    {
+      if (offset_left > 0 && offset_left != padding_left)
+        gtk_alignment_set_padding (GTK_ALIGNMENT (content_area),
+                                   padding_top, padding_bottom,
+                                   offset_left, padding_right);
+
+      gtk_alignment_get_padding  (GTK_ALIGNMENT (content_area),
+                                  &padding_top, &padding_bottom,
+                                  &padding_left, &padding_right);
+      if (offset_right > 0 && offset_right != padding_right)
+        gtk_alignment_set_padding (GTK_ALIGNMENT (content_area),
+                                   padding_top, padding_bottom,
+                                   padding_left, offset_right);
+    }
+}
+
+static void
+options_dialog_response_cb (GtkDialog *_dialog,
+                            gint       response_id,
+                            gpointer   user_data)
+{
+  PpOptionsDialog *dialog = (PpOptionsDialog*) user_data;
+
+  pp_options_dialog_hide (dialog);
+  dialog->response = response_id;
+
+  if (!dialog->populating_dialog)
+    dialog->user_callback (GTK_DIALOG (dialog->dialog), response_id, dialog->user_data);
+}
+
+PpOptionsDialog *
+pp_options_dialog_new (GtkWindow            *parent,
+                       UserResponseCallback  user_callback,
+                       gpointer              user_data,
+                       gchar                *printer_name,
+                       gboolean              sensitive)
+{
+  PpOptionsDialog *dialog;
+  GtkWidget       *widget;
+  GError          *error = NULL;
+  gchar           *objects[] = { "options-dialog", NULL };
+  guint            builder_result;
+  gchar           *title;
+
+  dialog = g_new0 (PpOptionsDialog, 1);
+
+  dialog->builder = gtk_builder_new ();
+  dialog->parent = GTK_WIDGET (parent);
+
+  builder_result = gtk_builder_add_objects_from_file (dialog->builder,
+                                                      DATADIR"/options-dialog.ui",
+                                                      objects, &error);
+
+  if (builder_result == 0)
+    {
+      g_warning ("Could not load ui: %s", error->message);
+      g_error_free (error);
+      return NULL;
+    }
+
+  dialog->dialog = (GtkWidget *) gtk_builder_get_object (dialog->builder, "options-dialog");
+  gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), GTK_WINDOW (parent));
+
+  dialog->user_callback = user_callback;
+  dialog->user_data = user_data;
+
+  dialog->printer_name = g_strdup (printer_name);
+
+  dialog->ppd_filename = NULL;
+  dialog->ppd_filename_set = FALSE;
+
+  dialog->destination = NULL;
+  dialog->destination_set = FALSE;
+
+  dialog->ipp_attributes = NULL;
+  dialog->ipp_attributes_set = FALSE;
+
+  dialog->response = GTK_RESPONSE_NONE;
+
+  dialog->sensitive = sensitive;
+
+  /* connect signals */
+  g_signal_connect (dialog->dialog, "response", G_CALLBACK (options_dialog_response_cb), dialog);
+  g_signal_connect (dialog->dialog, "size-allocate", G_CALLBACK (update_alignment_padding), dialog);
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "options-title");
+  /* Translators: Options of given printer (e.g. "MyPrinter Options") */
+  title = g_strdup_printf (_("%s Options"), printer_name);
+  gtk_label_set_label (GTK_LABEL (widget), title);
+  g_free (title);
+
+  gtk_widget_show_all (GTK_WIDGET (dialog->dialog));
+
+  dialog->populating_dialog = TRUE;
+  populate_options (dialog);
+
+  return dialog;
+}
+
+void
+pp_options_dialog_free (PpOptionsDialog *dialog)
+{
+  gtk_widget_destroy (GTK_WIDGET (dialog->dialog));
+  dialog->dialog = NULL;
+
+  g_object_unref (dialog->builder);
+  dialog->builder = NULL;
+
+  g_free (dialog->printer_name);
+  dialog->printer_name = NULL;
+
+  if (dialog->ppd_filename)
+    {
+      g_unlink (dialog->ppd_filename);
+      g_free (dialog->ppd_filename);
+      dialog->ppd_filename = NULL;
+    }
+
+  if (dialog->destination)
+    {
+      cupsFreeDests (1, dialog->destination);
+      dialog->destination = NULL;
+    }
+
+  if (dialog->ipp_attributes)
+    {
+      g_hash_table_unref (dialog->ipp_attributes);
+      dialog->ipp_attributes = NULL;
+    }
+
+  g_free (dialog);
+}
+
+static void
+pp_options_dialog_hide (PpOptionsDialog *dialog)
+{
+  gtk_widget_hide (GTK_WIDGET (dialog->dialog));
+}
diff --git a/panels/printers/pp-options-dialog.h b/panels/printers/pp-options-dialog.h
new file mode 100644
index 0000000..9ac3992
--- /dev/null
+++ b/panels/printers/pp-options-dialog.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#ifndef __PP_OPTIONS_DIALOG_H__
+#define __PP_OPTIONS_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _PpOptionsDialog PpOptionsDialog;
+
+typedef void (*UserResponseCallback) (GtkDialog *dialog, gint response_id, gpointer user_data);
+
+PpOptionsDialog *pp_options_dialog_new  (GtkWindow            *parent,
+                                         UserResponseCallback  user_callback,
+                                         gpointer              user_data,
+                                         gchar                *printer_name,
+                                         gboolean              sensitive);
+void             pp_options_dialog_free (PpOptionsDialog      *dialog);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/printers/pp-ppd-option-widget.c b/panels/printers/pp-ppd-option-widget.c
new file mode 100644
index 0000000..22cb301
--- /dev/null
+++ b/panels/printers/pp-ppd-option-widget.c
@@ -0,0 +1,624 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "pp-ppd-option-widget.h"
+#include "pp-utils.h"
+
+#define PP_PPD_OPTION_WIDGET_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), PP_TYPE_PPD_OPTION_WIDGET, PpPPDOptionWidgetPrivate))
+
+static void pp_ppd_option_widget_finalize (GObject *object);
+
+static gboolean construct_widget   (PpPPDOptionWidget *widget);
+static void     update_widget      (PpPPDOptionWidget *widget);
+static void     update_widget_real (PpPPDOptionWidget *widget);
+
+struct PpPPDOptionWidgetPrivate
+{
+  GtkWidget *switch_button;
+  GtkWidget *combo;
+  GtkWidget *image;
+  GtkWidget *box;
+
+  ppd_option_t *option;
+
+  gchar *printer_name;
+  gchar *option_name;
+
+  cups_dest_t *destination;
+  gboolean     destination_set;
+
+  gchar    *ppd_filename;
+  gboolean  ppd_filename_set;
+};
+
+G_DEFINE_TYPE (PpPPDOptionWidget, pp_ppd_option_widget, GTK_TYPE_HBOX)
+
+/* This list comes from Gtk+ */
+static const struct {
+  const char *keyword;
+  const char *choice;
+  const char *translation;
+} ppd_choice_translations[] = {
+  { "Duplex", "None", N_("One Sided") },
+  /* Translators: this is an option of "Two Sided" */
+  { "Duplex", "DuplexNoTumble", N_("Long Edge (Standard)") },
+  /* Translators: this is an option of "Two Sided" */
+  { "Duplex", "DuplexTumble", N_("Short Edge (Flip)") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "Auto", N_("Auto Select") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "AutoSelect", N_("Auto Select") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "Default", N_("Printer Default") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "None", N_("Printer Default") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "PrinterDefault", N_("Printer Default") },
+  /* Translators: this is an option of "Paper Source" */
+  { "InputSlot", "Unspecified", N_("Auto Select") },
+  /* Translators: this is an option of "Resolution" */
+  { "Resolution", "default", N_("Printer Default") },
+  /* Translators: this is an option of "GhostScript" */
+  { "PreFilter", "EmbedFonts", N_("Embed GhostScript fonts only") },
+  /* Translators: this is an option of "GhostScript" */
+  { "PreFilter", "Level1", N_("Convert to PS level 1") },
+  /* Translators: this is an option of "GhostScript" */
+  { "PreFilter", "Level2", N_("Convert to PS level 2") },
+  /* Translators: this is an option of "GhostScript" */
+  { "PreFilter", "No", N_("No pre-filtering") },
+};
+
+static ppd_option_t *
+cups_option_copy (ppd_option_t *option)
+{
+  ppd_option_t *result;
+  gint          i;
+
+  result = g_new0 (ppd_option_t, 1);
+
+  *result = *option;
+
+  result->choices = g_new (ppd_choice_t, result->num_choices);
+  for (i = 0; i < result->num_choices; i++)
+    {
+      result->choices[i] = option->choices[i];
+      result->choices[i].code = g_strdup (option->choices[i].code);
+      result->choices[i].option = result;
+    }
+
+  return result;
+}
+
+static void
+cups_option_free (ppd_option_t *option)
+{
+  gint i;
+
+  if (option)
+    {
+      for (i = 0; i < option->num_choices; i++)
+        g_free (option->choices[i].code);
+
+      g_free (option->choices);
+      g_free (option);
+    }
+}
+
+static void
+pp_ppd_option_widget_class_init (PpPPDOptionWidgetClass *class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = pp_ppd_option_widget_finalize;
+
+  g_type_class_add_private (class, sizeof (PpPPDOptionWidgetPrivate));
+}
+
+static void
+pp_ppd_option_widget_init (PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate *priv;
+
+  priv = widget->priv = PP_PPD_OPTION_WIDGET_GET_PRIVATE (widget);
+
+  priv->switch_button = NULL;
+  priv->combo = NULL;
+  priv->image = NULL;
+  priv->box = NULL;
+
+  priv->printer_name = NULL;
+  priv->option_name = NULL;
+
+  priv->destination = NULL;
+  priv->destination_set = FALSE;
+
+  priv->ppd_filename = NULL;
+  priv->ppd_filename_set = FALSE;
+}
+
+static void
+pp_ppd_option_widget_finalize (GObject *object)
+{
+  PpPPDOptionWidget        *widget = PP_PPD_OPTION_WIDGET (object);
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+
+  if (priv)
+    {
+      if (priv->option)
+        {
+          cups_option_free (priv->option);
+          priv->option = NULL;
+        }
+
+      if (priv->printer_name)
+        {
+          g_free (priv->printer_name);
+          priv->printer_name = NULL;
+        }
+
+      if (priv->option_name)
+        {
+          g_free (priv->printer_name);
+          priv->printer_name = NULL;
+        }
+
+      if (priv->destination)
+        {
+          cupsFreeDests (1, priv->destination);
+          priv->destination = NULL;
+        }
+
+      if (priv->ppd_filename)
+        {
+          g_unlink (priv->ppd_filename);
+          g_free (priv->ppd_filename);
+          priv->ppd_filename = NULL;
+        }
+    }
+
+  G_OBJECT_CLASS (pp_ppd_option_widget_parent_class)->finalize (object);
+}
+
+static const gchar *
+ppd_choice_translate (ppd_choice_t *choice)
+{
+  const gchar *keyword = choice->option->keyword;
+  gint         i;
+
+  for (i = 0; i < G_N_ELEMENTS (ppd_choice_translations); i++)
+    {
+      if (g_strcmp0 (ppd_choice_translations[i].keyword, keyword) == 0 &&
+	  g_strcmp0 (ppd_choice_translations[i].choice, choice->choice) == 0)
+	return _(ppd_choice_translations[i].translation);
+    }
+
+  return choice->text;
+}
+
+GtkWidget *
+pp_ppd_option_widget_new (ppd_option_t *option,
+                          const gchar  *printer_name)
+{
+  PpPPDOptionWidgetPrivate *priv;
+  PpPPDOptionWidget        *widget = NULL;
+
+  if (option && printer_name)
+    {
+      widget = g_object_new (PP_TYPE_PPD_OPTION_WIDGET, NULL);
+
+      priv = PP_PPD_OPTION_WIDGET_GET_PRIVATE (widget);
+
+      priv->printer_name = g_strdup (printer_name);
+      priv->option = cups_option_copy (option);
+      priv->option_name = g_strdup (option->keyword);
+
+      if (construct_widget (widget))
+        {
+          update_widget_real (widget);
+        }
+      else
+        {
+          g_object_ref_sink (widget);
+          g_object_unref (widget);
+          widget = NULL;
+        }
+    }
+
+  return (GtkWidget *) widget;
+}
+
+enum {
+  NAME_COLUMN,
+  VALUE_COLUMN,
+  N_COLUMNS
+};
+
+static GtkWidget *
+combo_box_new (void)
+{
+  GtkCellRenderer *cell;
+  GtkListStore    *store;
+  GtkWidget       *combo_box;
+
+  combo_box = gtk_combo_box_new ();
+
+  store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+  g_object_unref (store);
+
+  cell = gtk_cell_renderer_text_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
+                                  "text", NAME_COLUMN,
+                                  NULL);
+
+  return combo_box;
+}
+
+static void
+combo_box_append (GtkWidget   *combo,
+                  const gchar *display_text,
+                  const gchar *value)
+{
+  GtkTreeModel *model;
+  GtkListStore *store;
+  GtkTreeIter   iter;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+  store = GTK_LIST_STORE (model);
+
+  gtk_list_store_append (store, &iter);
+  gtk_list_store_set (store, &iter,
+                      NAME_COLUMN, display_text,
+                      VALUE_COLUMN, value,
+                      -1);
+}
+
+struct ComboSet {
+  GtkComboBox *combo;
+  const gchar *value;
+};
+
+static gboolean
+set_cb (GtkTreeModel *model,
+        GtkTreePath  *path,
+        GtkTreeIter  *iter,
+        gpointer      data)
+{
+  struct ComboSet *set_data = data;
+  gboolean         found;
+  char            *value;
+
+  gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
+  found = (strcmp (value, set_data->value) == 0);
+  g_free (value);
+
+  if (found)
+    gtk_combo_box_set_active_iter (set_data->combo, iter);
+
+  return found;
+}
+
+static void
+combo_box_set (GtkWidget   *combo,
+               const gchar *value)
+{
+  struct ComboSet  set_data;
+  GtkTreeModel    *model;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+  set_data.combo = GTK_COMBO_BOX (combo);
+  set_data.value = value;
+  gtk_tree_model_foreach (model, set_cb, &set_data);
+}
+
+static char *
+combo_box_get (GtkWidget *combo)
+{
+  GtkTreeModel *model;
+  GtkTreeIter   iter;
+  gchar        *value = NULL;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+     gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
+
+  return value;
+}
+
+static void
+printer_add_option_async_cb (gboolean success,
+                             gpointer user_data)
+{
+  update_widget (user_data);
+}
+
+static void
+switch_changed_cb (GtkWidget         *switch_button,
+                   GParamSpec        *pspec,
+                   PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **values;
+
+  values = g_new0 (gchar *, 2);
+
+  if (gtk_switch_get_active (GTK_SWITCH (switch_button)))
+    values[0] = g_strdup ("True");
+  else
+    values[0] = g_strdup ("False");
+
+  printer_add_option_async (priv->printer_name,
+                            priv->option_name,
+                            values,
+                            FALSE,
+                            NULL,
+                            printer_add_option_async_cb,
+                            widget);
+
+  g_strfreev (values);
+}
+
+static void
+combo_changed_cb (GtkWidget         *combo,
+                  PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate  *priv = widget->priv;
+  gchar                    **values;
+
+  values = g_new0 (gchar *, 2);
+  values[0] = combo_box_get (combo);
+
+  printer_add_option_async (priv->printer_name,
+                            priv->option_name,
+                            values,
+                            FALSE,
+                            NULL,
+                            printer_add_option_async_cb,
+                            widget);
+
+  g_strfreev (values);
+}
+
+static gboolean
+construct_widget (PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+  gint                      i;
+
+  /* Don't show options which has only one choice */
+  if (priv->option && priv->option->num_choices > 1)
+    {
+      switch (priv->option->ui)
+        {
+          case PPD_UI_BOOLEAN:
+              priv->switch_button = gtk_switch_new ();
+              g_signal_connect (priv->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), widget);
+              gtk_box_pack_start (GTK_BOX (widget), priv->switch_button, FALSE, FALSE, 0);
+              break;
+
+          case PPD_UI_PICKONE:
+              priv->combo = combo_box_new ();
+
+              for (i = 0; i < priv->option->num_choices; i++)
+                {
+                  combo_box_append (priv->combo,
+                                    ppd_choice_translate (&priv->option->choices[i]),
+                                    priv->option->choices[i].choice);
+                }
+
+              gtk_box_pack_start (GTK_BOX (widget), priv->combo, FALSE, FALSE, 0);
+              g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed_cb), widget);
+              break;
+
+          case PPD_UI_PICKMANY:
+              priv->combo = combo_box_new ();
+
+              for (i = 0; i < priv->option->num_choices; i++)
+                {
+                  combo_box_append (priv->combo,
+                                    ppd_choice_translate (&priv->option->choices[i]),
+                                    priv->option->choices[i].choice);
+                }
+
+              gtk_box_pack_start (GTK_BOX (widget), priv->combo, TRUE, TRUE, 0);
+              g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed_cb), widget);
+              break;
+
+          default:
+              break;
+        }
+
+      priv->image = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_MENU);
+      if (!priv->image)
+        priv->image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU);
+      gtk_box_pack_start (GTK_BOX (widget), priv->image, FALSE, FALSE, 0);
+      gtk_widget_set_no_show_all (GTK_WIDGET (priv->image), TRUE);
+
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+static void
+update_widget_real (PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+  ppd_option_t             *option = NULL, *iter;
+  ppd_file_t               *ppd_file;
+  gchar                    *value = NULL;
+  gint                      i;
+
+  if (priv->option)
+    {
+      option = cups_option_copy (priv->option);
+      cups_option_free (priv->option);
+      priv->option = NULL;
+    }
+  else if (priv->ppd_filename)
+    {
+      ppd_file = ppdOpenFile (priv->ppd_filename);
+      ppdLocalize (ppd_file);
+
+      if (ppd_file)
+        {
+          ppdMarkDefaults (ppd_file);
+          cupsMarkOptions (ppd_file,
+                           priv->destination->num_options,
+                           priv->destination->options);
+
+          for (iter = ppdFirstOption(ppd_file); iter; iter = ppdNextOption(ppd_file))
+            {
+              if (g_str_equal (iter->keyword, priv->option_name))
+                {
+                  option = cups_option_copy (iter);
+                  break;
+                }
+            }
+
+          ppdClose (ppd_file);
+        }
+
+      g_unlink (priv->ppd_filename);
+      g_free (priv->ppd_filename);
+      priv->ppd_filename = NULL;
+    }
+
+  if (option)
+    {
+      for (i = 0; i < option->num_choices; i++)
+        if (option->choices[i].marked)
+          value = g_strdup (option->choices[i].choice);
+
+      if (value == NULL)
+        value = g_strdup (option->defchoice);
+
+      if (value)
+        {
+          switch (option->ui)
+            {
+              case PPD_UI_BOOLEAN:
+                g_signal_handlers_block_by_func (priv->switch_button, switch_changed_cb, widget);
+                if (g_ascii_strcasecmp (value, "True") == 0)
+                  gtk_switch_set_active (GTK_SWITCH (priv->switch_button), TRUE);
+                else
+                  gtk_switch_set_active (GTK_SWITCH (priv->switch_button), FALSE);
+                g_signal_handlers_unblock_by_func (priv->switch_button, switch_changed_cb, widget);
+                break;
+
+              case PPD_UI_PICKONE:
+                g_signal_handlers_block_by_func (priv->combo, combo_changed_cb, widget);
+                combo_box_set (priv->combo, value);
+                g_signal_handlers_unblock_by_func (priv->combo, combo_changed_cb, widget);
+                break;
+
+              case PPD_UI_PICKMANY:
+                g_signal_handlers_block_by_func (priv->combo, combo_changed_cb, widget);
+                combo_box_set (priv->combo, value);
+                g_signal_handlers_unblock_by_func (priv->combo, combo_changed_cb, widget);
+                break;
+
+              default:
+                break;
+            }
+
+          g_free (value);
+        }
+
+      if (option->conflicted)
+        gtk_widget_show (priv->image);
+      else
+        gtk_widget_hide (priv->image);
+    }
+
+  cups_option_free (option);
+}
+
+static void
+get_named_dest_cb (cups_dest_t *dest,
+                   gpointer     user_data)
+{
+  PpPPDOptionWidget        *widget = (PpPPDOptionWidget *) user_data;
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+
+  if (priv->destination)
+    cupsFreeDests (1, priv->destination);
+
+  priv->destination = dest;
+  priv->destination_set = TRUE;
+
+  if (priv->ppd_filename_set)
+    {
+      update_widget_real (widget);
+    }
+}
+
+static void
+printer_get_ppd_cb (const gchar *ppd_filename,
+                    gpointer     user_data)
+{
+  PpPPDOptionWidget        *widget = (PpPPDOptionWidget *) user_data;
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+
+  if (priv->ppd_filename)
+    {
+      g_unlink (priv->ppd_filename);
+      g_free (priv->ppd_filename);
+    }
+
+  priv->ppd_filename = g_strdup (ppd_filename);
+  priv->ppd_filename_set = TRUE;
+
+  if (priv->destination_set)
+    {
+      update_widget_real (widget);
+    }
+}
+
+static void
+update_widget (PpPPDOptionWidget *widget)
+{
+  PpPPDOptionWidgetPrivate *priv = widget->priv;
+
+  get_named_dest_async (priv->printer_name,
+                        get_named_dest_cb,
+                        widget);
+
+  printer_get_ppd_async (priv->printer_name,
+                         printer_get_ppd_cb,
+                         widget);
+}
diff --git a/panels/printers/pp-ppd-option-widget.h b/panels/printers/pp-ppd-option-widget.h
new file mode 100644
index 0000000..82b57dd
--- /dev/null
+++ b/panels/printers/pp-ppd-option-widget.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Marek Kasik <mkasik redhat com>
+ */
+
+#ifndef __PP_PPD_OPTION_WIDGET_H__
+#define __PP_PPD_OPTION_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_PPD_OPTION_WIDGET                  (pp_ppd_option_widget_get_type ())
+#define PP_PPD_OPTION_WIDGET(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), PP_TYPE_PPD_OPTION_WIDGET, PpPPDOptionWidget))
+#define PP_PPD_OPTION_WIDGET_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass),  PP_TYPE_PPD_OPTION_WIDGET, PpPPDOptionWidgetClass))
+#define PP_IS_PPD_OPTION_WIDGET(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PP_TYPE_PPD_OPTION_WIDGET))
+#define PP_IS_PPD_OPTION_WIDGET_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass),  PP_TYPE_PPD_OPTION_WIDGET))
+#define PP_PPD_OPTION_WIDGET_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj),  PP_TYPE_PPD_OPTION_WIDGET, PpPPDOptionWidgetClass))
+
+typedef struct _PpPPDOptionWidget         PpPPDOptionWidget;
+typedef struct _PpPPDOptionWidgetClass    PpPPDOptionWidgetClass;
+typedef struct PpPPDOptionWidgetPrivate   PpPPDOptionWidgetPrivate;
+
+struct _PpPPDOptionWidget
+{
+  GtkHBox parent_instance;
+
+  PpPPDOptionWidgetPrivate *priv;
+};
+
+struct _PpPPDOptionWidgetClass
+{
+  GtkHBoxClass parent_class;
+
+  void (*changed) (PpPPDOptionWidget *widget);
+};
+
+GType	     pp_ppd_option_widget_get_type (void) G_GNUC_CONST;
+
+GtkWidget   *pp_ppd_option_widget_new      (ppd_option_t *source,
+                                            const gchar  *printer_name);
+
+G_END_DECLS
+
+#endif /* __PP_PPD_OPTION_WIDGET_H__ */
diff --git a/panels/printers/pp-utils.c b/panels/printers/pp-utils.c
index c78b70f..2750a34 100644
--- a/panels/printers/pp-utils.c
+++ b/panels/printers/pp-utils.c
@@ -4195,3 +4195,288 @@ get_standard_manufacturers_name (gchar *name)
 
   return result;
 }
+
+typedef struct
+{
+  gchar        *printer_name;
+  gchar        *result;
+  PGPCallback   callback;
+  gpointer      user_data;
+  GMainContext *context;
+} PGPData;
+
+static gboolean
+printer_get_ppd_idle_cb (gpointer user_data)
+{
+  PGPData *data = (PGPData *) user_data;
+
+  data->callback (data->result, data->user_data);
+
+  return FALSE;
+}
+
+static void
+printer_get_ppd_data_free (gpointer user_data)
+{
+  PGPData *data = (PGPData *) user_data;
+
+  if (data->context)
+    g_main_context_unref (data->context);
+  g_free (data->result);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+printer_get_ppd_cb (gpointer user_data)
+{
+  PGPData *data = (PGPData *) user_data;
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+                         printer_get_ppd_idle_cb,
+                         data,
+                         printer_get_ppd_data_free);
+  g_source_attach (idle_source, data->context);
+  g_source_unref (idle_source);
+}
+
+static gpointer
+printer_get_ppd_func (gpointer user_data)
+{
+  PGPData *data = (PGPData *) user_data;
+
+  data->result = g_strdup (cupsGetPPD (data->printer_name));
+
+  printer_get_ppd_cb (data);
+
+  return NULL;
+}
+
+void
+printer_get_ppd_async (const gchar *printer_name,
+                       PGPCallback  callback,
+                       gpointer     user_data)
+{
+  PGPData *data;
+  GThread *thread;
+  GError  *error = NULL;
+
+  data = g_new0 (PGPData, 1);
+  data->printer_name = g_strdup (printer_name);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->context = g_main_context_ref_thread_default ();
+
+  thread = g_thread_try_new ("printer-get-ppd",
+                             printer_get_ppd_func,
+                             data,
+                             &error);
+
+  if (!thread)
+    {
+      g_warning ("%s", error->message);
+      callback (NULL, user_data);
+
+      g_error_free (error);
+      printer_get_ppd_data_free (data);
+    }
+  else
+    {
+      g_thread_unref (thread);
+    }
+}
+
+typedef struct
+{
+  gchar        *printer_name;
+  cups_dest_t  *result;
+  GNDCallback   callback;
+  gpointer      user_data;
+  GMainContext *context;
+} GNDData;
+
+static gboolean
+get_named_dest_idle_cb (gpointer user_data)
+{
+  GNDData *data = (GNDData *) user_data;
+
+  data->callback (data->result, data->user_data);
+
+  return FALSE;
+}
+
+static void
+get_named_dest_data_free (gpointer user_data)
+{
+  GNDData *data = (GNDData *) user_data;
+
+  if (data->context)
+    g_main_context_unref (data->context);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+get_named_dest_cb (gpointer user_data)
+{
+  GNDData *data = (GNDData *) user_data;
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+                         get_named_dest_idle_cb,
+                         data,
+                         get_named_dest_data_free);
+  g_source_attach (idle_source, data->context);
+  g_source_unref (idle_source);
+}
+
+static gpointer
+get_named_dest_func (gpointer user_data)
+{
+  GNDData *data = (GNDData *) user_data;
+
+  data->result = cupsGetNamedDest (CUPS_HTTP_DEFAULT, data->printer_name, NULL);
+
+  get_named_dest_cb (data);
+
+  return NULL;
+}
+
+void
+get_named_dest_async (const gchar *printer_name,
+                      GNDCallback  callback,
+                      gpointer     user_data)
+{
+  GNDData *data;
+  GThread *thread;
+  GError  *error = NULL;
+
+  data = g_new0 (GNDData, 1);
+  data->printer_name = g_strdup (printer_name);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->context = g_main_context_ref_thread_default ();
+
+  thread = g_thread_try_new ("get-named-dest",
+                             get_named_dest_func,
+                             data,
+                             &error);
+
+  if (!thread)
+    {
+      g_warning ("%s", error->message);
+      callback (NULL, user_data);
+
+      g_error_free (error);
+      get_named_dest_data_free (data);
+    }
+  else
+    {
+      g_thread_unref (thread);
+    }
+}
+
+typedef struct
+{
+  GCancellable *cancellable;
+  PAOCallback   callback;
+  gpointer      user_data;
+} PAOData;
+
+static void
+printer_add_option_async_dbus_cb (GObject      *source_object,
+                                  GAsyncResult *res,
+                                  gpointer      user_data)
+{
+  GVariant *output;
+  gboolean  success = FALSE;
+  PAOData  *data = (PAOData *) user_data;
+  GError   *error = NULL;
+
+  output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+                                          res,
+                                          &error);
+  g_object_unref (source_object);
+
+  if (output)
+    {
+      const gchar *ret_error;
+
+      g_variant_get (output, "(&s)", &ret_error);
+      if (ret_error[0] != '\0')
+        g_warning ("%s", ret_error);
+      else
+        success = TRUE;
+
+      g_variant_unref (output);
+    }
+  else
+    {
+      if (error->code != G_IO_ERROR_CANCELLED)
+        g_warning ("%s", error->message);
+      g_error_free (error);
+    }
+
+  data->callback (success, data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data);
+}
+
+void
+printer_add_option_async (const gchar   *printer_name,
+                          const gchar   *option_name,
+                          gchar        **values,
+                          gboolean       set_default,
+                          GCancellable  *cancellable,
+                          PAOCallback    callback,
+                          gpointer       user_data)
+{
+  GVariantBuilder  array_builder;
+  GDBusConnection *bus;
+  PAOData         *data;
+  GError          *error = NULL;
+  gint             i;
+
+  bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+  if (!bus)
+   {
+     g_warning ("Failed to get system bus: %s", error->message);
+     g_error_free (error);
+     callback (FALSE, user_data);
+     return;
+   }
+
+  g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+  if (values)
+    {
+      for (i = 0; values[i]; i++)
+        g_variant_builder_add (&array_builder, "s", values[i]);
+    }
+
+  data = g_new0 (PAOData, 1);
+  data->cancellable = cancellable;
+  data->callback = callback;
+  data->user_data = user_data;
+
+  g_dbus_connection_call (bus,
+                          MECHANISM_BUS,
+                          "/",
+                          MECHANISM_BUS,
+                          set_default ? "PrinterAddOptionDefault" :
+                                        "PrinterAddOption",
+                          g_variant_new ("(ssas)",
+                                         printer_name,
+                                         option_name,
+                                         &array_builder),
+                          G_VARIANT_TYPE ("(s)"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          -1,
+                          cancellable,
+                          printer_add_option_async_dbus_cb,
+                          data);
+}
diff --git a/panels/printers/pp-utils.h b/panels/printers/pp-utils.h
index 8eeaced..5b4b71e 100644
--- a/panels/printers/pp-utils.h
+++ b/panels/printers/pp-utils.h
@@ -206,6 +206,31 @@ void        ipp_attribute_free (IPPAttribute *attr);
 
 gchar      *get_standard_manufacturers_name (gchar *name);
 
+typedef void (*PGPCallback) (const gchar *ppd_filename,
+                             gpointer     user_data);
+
+void        printer_get_ppd_async (const gchar *printer_name,
+                                   PGPCallback  callback,
+                                   gpointer     user_data);
+
+typedef void (*GNDCallback) (cups_dest_t *destination,
+                             gpointer     user_data);
+
+void        get_named_dest_async (const gchar *printer_name,
+                                  GNDCallback  callback,
+                                  gpointer     user_data);
+
+typedef void (*PAOCallback) (gboolean success,
+                             gpointer user_data);
+
+void        printer_add_option_async (const gchar   *printer_name,
+                                      const gchar   *option_name,
+                                      gchar        **values,
+                                      gboolean       set_default,
+                                      GCancellable  *cancellable,
+                                      PAOCallback    callback,
+                                      gpointer       user_data);
+
 G_END_DECLS
 
 #endif /* __PP_UTILS_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]