[gnome-control-center] printers: Allow users to change printer's PPD file



commit dd7479caeace07e44d72cd310753a87a44e3e578
Author: Marek Kasik <mkasik redhat com>
Date:   Mon Jul 16 11:10:02 2012 +0200

    printers: Allow users to change printer's PPD file
    
    This commit adds popup window which when activated allows
    user to select PPD from local database of installed PPDs,
    select directly PPD from filesystem or select one from 3
    recommended PPDs (#678637).
    The popup is activated by clicking on model field (the panel
    has to be unlocked). It starts to search for the best PPDs
    available immediately after its popup.
    All operations are asynchronous.

 panels/printers/Makefile.am               |   15 +-
 panels/printers/cc-printers-panel.c       |  641 +++++++++++-
 panels/printers/pp-ppd-selection-dialog.c |  444 ++++++++
 panels/printers/pp-ppd-selection-dialog.h |   46 +
 panels/printers/pp-utils.c                | 1635 +++++++++++++++++++++++++++++
 panels/printers/pp-utils.h                |   90 ++
 panels/printers/ppd-selection-dialog.ui   |  206 ++++
 panels/printers/printers.ui               |   75 ++-
 8 files changed, 3119 insertions(+), 33 deletions(-)
---
diff --git a/panels/printers/Makefile.am b/panels/printers/Makefile.am
index 0dade8c..dab5ae5 100644
--- a/panels/printers/Makefile.am
+++ b/panels/printers/Makefile.am
@@ -3,6 +3,7 @@ cappletname = printers
 uidir = $(pkgdatadir)/ui/printers
 dist_ui_DATA = \
 	new-printer-dialog.ui	\
+	ppd-selection-dialog.ui	\
 	printers.ui
 
 INCLUDES = 						\
@@ -18,12 +19,14 @@ ccpanelsdir = $(PANELS_DIR)
 ccpanels_LTLIBRARIES = libprinters.la
 
 libprinters_la_SOURCES =		\
-	printers-module.c	\
-	pp-utils.c	\
-	pp-utils.h	\
-	pp-new-printer-dialog.c	\
-	pp-new-printer-dialog.h	\
-	cc-printers-panel.c	\
+	printers-module.c		\
+	pp-utils.c			\
+	pp-utils.h			\
+	pp-new-printer-dialog.c		\
+	pp-new-printer-dialog.h		\
+	pp-ppd-selection-dialog.c	\
+	pp-ppd-selection-dialog.h	\
+	cc-printers-panel.c		\
 	cc-printers-panel.h
 
 libprinters_la_LIBADD = $(PRINTERS_PANEL_LIBS) $(PANEL_LIBS) $(CUPS_LIBS)
diff --git a/panels/printers/cc-printers-panel.c b/panels/printers/cc-printers-panel.c
index 0ea51c4..63fc803 100644
--- a/panels/printers/cc-printers-panel.c
+++ b/panels/printers/cc-printers-panel.c
@@ -33,6 +33,7 @@
 
 #include "cc-editable-entry.h"
 #include "pp-new-printer-dialog.h"
+#include "pp-ppd-selection-dialog.h"
 #include "pp-utils.h"
 
 G_DEFINE_DYNAMIC_TYPE (CcPrintersPanel, cc_printers_panel, CC_TYPE_PANEL)
@@ -83,6 +84,7 @@ struct _CcPrintersPanelPrivate
   GSettings *lockdown_settings;
 
   PpNewPrinterDialog *pp_new_printer_dialog;
+  PpPPDSelectionDialog *pp_ppd_selection_dialog;
 
   GDBusProxy      *cups_proxy;
   GDBusConnection *cups_bus_connection;
@@ -91,13 +93,21 @@ struct _CcPrintersPanelPrivate
   guint            cups_status_check_id;
   guint            dbus_subscription_id;
 
+  GtkWidget    *popup_menu;
+  GList        *driver_change_list;
+  GCancellable *get_ppd_name_cancellable;
+  gboolean      getting_ppd_names;
+  PPDList      *all_ppds_list;
+  GHashTable   *preferred_drivers;
+  gboolean      getting_all_ppds;
+
   gpointer dummy;
 };
 
 static void actualize_jobs_list (CcPrintersPanel *self);
 static void actualize_printers_list (CcPrintersPanel *self);
 static void actualize_allowed_users_list (CcPrintersPanel *self);
-static void actualize_sensitivity (gpointer user_data);
+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);
 static void detach_from_cups_notifier (gpointer data);
@@ -180,7 +190,22 @@ cc_printers_panel_dispose (GObject *object)
   detach_from_cups_notifier (CC_PRINTERS_PANEL (object));
 
   if (priv->cups_status_check_id > 0)
-    g_source_remove (priv->cups_status_check_id);
+    {
+      g_source_remove (priv->cups_status_check_id);
+      priv->cups_status_check_id = 0;
+    }
+
+  if (priv->all_ppds_list)
+    {
+      ppd_list_free (priv->all_ppds_list);
+      priv->all_ppds_list = NULL;
+    }
+
+  if (priv->preferred_drivers)
+    {
+      g_hash_table_unref (priv->preferred_drivers);
+      priv->preferred_drivers = NULL;
+    }
 
   G_OBJECT_CLASS (cc_printers_panel_parent_class)->dispose (object);
 }
@@ -502,6 +527,8 @@ printer_selection_changed_cb (GtkTreeSelection *selection,
   cups_ptype_t            type = 0;
   GtkTreeIter             iter;
   GtkWidget              *widget;
+  GtkWidget              *model_button;
+  GtkWidget              *model_label;
   gboolean                sensitive;
   GValue                  value = G_VALUE_INIT;
   gchar                  *printer_make_and_model = NULL;
@@ -814,16 +841,23 @@ printer_selection_changed_cb (GtkTreeSelection *selection,
         cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);
 
 
-      widget = (GtkWidget*)
+      model_button = (GtkWidget*)
+        gtk_builder_get_object (priv->builder, "printer-model-button");
+
+      model_label = (GtkWidget*)
         gtk_builder_get_object (priv->builder, "printer-model-label");
 
       if (printer_model)
         {
-          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), printer_model);
+          gtk_button_set_label (GTK_BUTTON (model_button), printer_model);
+          gtk_label_set_text (GTK_LABEL (model_label), printer_model);
           g_free (printer_model);
         }
       else
-        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);
+        {
+          gtk_button_set_label (GTK_BUTTON (model_button), EMPTY_TEXT);
+          gtk_label_set_text (GTK_LABEL (model_label), EMPTY_TEXT);
+        }
 
 
       widget = (GtkWidget*)
@@ -918,8 +952,12 @@ printer_selection_changed_cb (GtkTreeSelection *selection,
       cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), "");
 
       widget = (GtkWidget*)
+        gtk_builder_get_object (priv->builder, "printer-model-button");
+      gtk_button_set_label (GTK_BUTTON (widget), "");
+
+      widget = (GtkWidget*)
         gtk_builder_get_object (priv->builder, "printer-model-label");
-      cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), "");
+      gtk_label_set_text (GTK_LABEL (widget), "");
 
       widget = (GtkWidget*)
         gtk_builder_get_object (priv->builder, "printer-ip-address-label");
@@ -930,7 +968,7 @@ printer_selection_changed_cb (GtkTreeSelection *selection,
       cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), "");
     }
 
-  actualize_sensitivity (self);
+  update_sensitivity (self);
 }
 
 static void
@@ -1157,7 +1195,7 @@ actualize_printers_list (CcPrintersPanel *self)
   g_free (current_printer_instance);
   g_object_unref (store);
 
-  actualize_sensitivity (self);
+  update_sensitivity (self);
 }
 
 static void
@@ -2180,6 +2218,475 @@ printer_location_edit_cb (GtkWidget *entry,
 }
 
 static void
+set_ppd_cb (gchar    *printer_name,
+            gboolean  success,
+            gpointer  user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GList                  *iter;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  for (iter = priv->driver_change_list; iter; iter = iter->next)
+    {
+      if (g_strcmp0 ((gchar *) iter->data, printer_name) == 0)
+        {
+          priv->driver_change_list = g_list_remove_link (priv->driver_change_list, iter);
+          g_list_free_full (iter, g_free);
+          break;
+        }
+    }
+
+  update_sensitivity (self);
+
+  if (success)
+    {
+      actualize_printers_list (self);
+    }
+
+  g_free (printer_name);
+}
+
+static void
+select_ppd_manually (GtkMenuItem *menuitem,
+                     gpointer     user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GtkFileFilter          *filter;
+  GtkWidget              *dialog;
+  gchar                  *printer_name = NULL;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  gtk_menu_shell_cancel (GTK_MENU_SHELL (priv->popup_menu));
+
+  dialog = gtk_file_chooser_dialog_new (_("Select PPD File"),
+                                        NULL,
+                                        GTK_FILE_CHOOSER_ACTION_OPEN,
+                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                        GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                                        NULL);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter,
+    _("PostScript Printer Description files (*.ppd, *.PPD, *.ppd.gz, *.PPD.gz, *.PPD.GZ)"));
+  gtk_file_filter_add_pattern (filter, "*.ppd");
+  gtk_file_filter_add_pattern (filter, "*.PPD");
+  gtk_file_filter_add_pattern (filter, "*.ppd.gz");
+  gtk_file_filter_add_pattern (filter, "*.PPD.gz");
+  gtk_file_filter_add_pattern (filter, "*.PPD.GZ");
+
+  gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+    {
+      gchar *ppd_filename;
+
+      ppd_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+
+      if (priv->current_dest >= 0 &&
+          priv->current_dest < priv->num_dests &&
+          priv->dests != NULL)
+        printer_name = priv->dests[priv->current_dest].name;
+
+      if (printer_name && ppd_filename)
+        {
+          priv->driver_change_list =
+            g_list_prepend (priv->driver_change_list, g_strdup (printer_name));
+          update_sensitivity (self);
+          printer_set_ppd_file_async (printer_name,
+                                      ppd_filename,
+                                      NULL,
+                                      set_ppd_cb,
+                                      user_data);
+        }
+
+      g_free (ppd_filename);
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+ppd_selection_dialog_response_cb (GtkDialog *dialog,
+                                  gint       response_id,
+                                  gpointer   user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  gchar                  *printer_name = NULL;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  if (response_id == GTK_RESPONSE_OK)
+    {
+      gchar *ppd_name;
+
+      ppd_name = pp_ppd_selection_dialog_get_ppd_name (priv->pp_ppd_selection_dialog);
+
+      if (priv->current_dest >= 0 &&
+          priv->current_dest < priv->num_dests &&
+          priv->dests != NULL)
+        printer_name = priv->dests[priv->current_dest].name;
+
+      if (printer_name && ppd_name)
+        {
+          priv->driver_change_list = g_list_prepend (priv->driver_change_list,
+                                                     g_strdup (printer_name));
+          update_sensitivity (self);
+          printer_set_ppd_async (printer_name,
+                                 ppd_name,
+                                 NULL,
+                                 set_ppd_cb,
+                                 user_data);
+        }
+
+      g_free (ppd_name);
+    }
+
+  pp_ppd_selection_dialog_free (priv->pp_ppd_selection_dialog);
+  priv->pp_ppd_selection_dialog = NULL;
+}
+
+static void
+select_ppd_in_dialog (GtkMenuItem *menuitem,
+                      gpointer     user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GtkWidget              *widget;
+  gchar                  *device_id = NULL;
+  gchar                  *manufacturer = NULL;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (priv->builder, "main-vbox");
+
+  if (!priv->pp_ppd_selection_dialog)
+    {
+      if (priv->current_dest >= 0 &&
+          priv->current_dest < priv->num_dests)
+        {
+          device_id =
+            get_ppd_attribute (priv->ppd_file_names[priv->current_dest],
+                               "1284DeviceID");
+
+          if (device_id)
+            {
+              manufacturer = get_tag_value (device_id, "mfg");
+              if (!manufacturer)
+                manufacturer = get_tag_value (device_id, "manufacturer");
+            }
+          else
+            {
+              manufacturer =
+                get_ppd_attribute (priv->ppd_file_names[priv->current_dest],
+                                   "Manufacturer");
+            }
+        }
+
+      priv->pp_ppd_selection_dialog = pp_ppd_selection_dialog_new (
+        GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+        priv->all_ppds_list,
+        manufacturer,
+        ppd_selection_dialog_response_cb,
+        self);
+
+      g_free (manufacturer);
+      g_free (device_id);
+    }
+}
+
+static void
+set_ppd_from_list (GtkMenuItem *menuitem,
+                   gpointer     user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  gchar                  *printer_name = NULL;
+  gchar                  *ppd_name;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  ppd_name = (gchar *) g_object_get_data (G_OBJECT (menuitem), "ppd-name");
+
+  if (priv->current_dest >= 0 &&
+      priv->current_dest < priv->num_dests &&
+      priv->dests != NULL)
+    printer_name = priv->dests[priv->current_dest].name;
+
+  if (printer_name && ppd_name)
+    {
+      priv->driver_change_list = g_list_prepend (priv->driver_change_list,
+                                                 g_strdup (printer_name));
+      update_sensitivity (self);
+      printer_set_ppd_async (printer_name,
+                             ppd_name,
+                             NULL,
+                             set_ppd_cb,
+                             user_data);
+    }
+}
+
+static void
+ppd_names_free (gpointer user_data)
+{
+  PPDName **names = (PPDName **) user_data;
+  gint      i;
+
+  if (names)
+    {
+      for (i = 0; names[i]; i++)
+        {
+          g_free (names[i]->ppd_name);
+          g_free (names[i]->ppd_display_name);
+          g_free (names[i]);
+        }
+
+      g_free (names);
+    }
+}
+
+static void
+get_ppd_names_cb (PPDName     **names,
+                  const gchar  *printer_name,
+                  gboolean      cancelled,
+                  gpointer      user_data)
+{
+  CcPrintersPanelPrivate  *priv;
+  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
+  GtkWidget               *informal = NULL;
+  GtkWidget               *placeholders[3];
+  GtkWidget               *spinner;
+  gpointer                 value = NULL;
+  gboolean                 found = FALSE;
+  PPDName                **hash_names = NULL;
+  GList                   *children, *iter;
+  gint                     i;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  priv->getting_ppd_names = FALSE;
+
+  for (i = 0; i < 3; i++)
+    placeholders[i] = NULL;
+
+  children = gtk_container_get_children (GTK_CONTAINER (priv->popup_menu));
+  if (children)
+    {
+      for (iter = children; iter; iter = iter->next)
+        {
+          if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"),
+                         "informal") == 0)
+              informal = GTK_WIDGET (iter->data);
+          else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"),
+                              "placeholder1") == 0)
+              placeholders[0] = GTK_WIDGET (iter->data);
+          else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"),
+                              "placeholder2") == 0)
+              placeholders[1] = GTK_WIDGET (iter->data);
+          else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"),
+                              "placeholder3") == 0)
+              placeholders[2] = GTK_WIDGET (iter->data);
+        }
+
+      g_list_free (children);
+    }
+
+  if (!priv->preferred_drivers)
+    {
+      priv->preferred_drivers = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                       g_free, ppd_names_free);
+    }
+
+  if (!cancelled &&
+      !g_hash_table_lookup_extended (priv->preferred_drivers,
+                                     printer_name, NULL, NULL))
+    g_hash_table_insert (priv->preferred_drivers, g_strdup (printer_name), names);
+
+  if (priv->preferred_drivers &&
+      g_hash_table_lookup_extended (priv->preferred_drivers,
+                                    printer_name, NULL, &value))
+    {
+      hash_names = (PPDName **) value;
+      if (hash_names)
+        {
+          for (i = 0; hash_names[i]; i++)
+            {
+              if (placeholders[i])
+                {
+                  gtk_menu_item_set_label (GTK_MENU_ITEM (placeholders[i]),
+                                           hash_names[i]->ppd_display_name);
+                  g_object_set_data_full (G_OBJECT (placeholders[i]),
+                                          "ppd-name",
+                                          g_strdup (hash_names[i]->ppd_name),
+                                              g_free);
+                  g_signal_connect (placeholders[i],
+                                    "activate",
+                                    G_CALLBACK (set_ppd_from_list),
+                                    self);
+                  gtk_widget_set_sensitive (GTK_WIDGET (placeholders[i]), TRUE);
+                  gtk_widget_show (placeholders[i]);
+                }
+            }
+
+          found = TRUE;
+        }
+      else
+        {
+          found = FALSE;
+        }
+    }
+
+  if (informal)
+    {
+      gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (informal), FALSE);
+
+      spinner = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (informal));
+      if (spinner)
+        gtk_spinner_stop (GTK_SPINNER (spinner));
+
+      if (found)
+        gtk_widget_hide (informal);
+      else
+        gtk_menu_item_set_label (GTK_MENU_ITEM (informal), _("No suitable driver found"));
+    }
+
+  gtk_widget_show_all (priv->popup_menu);
+
+  update_sensitivity (self);
+}
+
+static void
+popup_menu_done (GtkMenuShell *menushell,
+                 gpointer      user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  if (priv->get_ppd_name_cancellable)
+    {
+      g_cancellable_cancel (priv->get_ppd_name_cancellable);
+      g_object_unref (priv->get_ppd_name_cancellable);
+      priv->get_ppd_name_cancellable = NULL;
+    }
+}
+
+static void
+popup_model_menu_cb (GtkButton *button,
+                     gpointer   user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GtkWidget              *spinner;
+  GtkWidget              *item;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  priv->popup_menu = gtk_menu_new ();
+  g_signal_connect (priv->popup_menu,
+                    "selection-done",
+                    G_CALLBACK (popup_menu_done),
+                    user_data);
+
+  /*
+   * These placeholders are a workaround for a situation
+   * when we want to actually append new menu item in a callback.
+   * But unfortunately it is not possible to connect to "activate"
+   * signal of such menu item (appended after gtk_menu_popup()).
+   */
+  item = gtk_image_menu_item_new_with_label ("");
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("placeholder1"), g_free);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+  gtk_widget_set_no_show_all (item, TRUE);
+  gtk_widget_hide (item);
+
+  item = gtk_image_menu_item_new_with_label ("");
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("placeholder2"), g_free);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+  gtk_widget_set_no_show_all (item, TRUE);
+  gtk_widget_hide (item);
+
+  item = gtk_image_menu_item_new_with_label ("");
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("placeholder3"), g_free);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+  gtk_widget_set_no_show_all (item, TRUE);
+  gtk_widget_hide (item);
+
+  item = gtk_image_menu_item_new_with_label (_("Searching for preferred drivers..."));
+  spinner = gtk_spinner_new ();
+  gtk_spinner_start (GTK_SPINNER (spinner));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), spinner);
+  gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("informal"), g_free);
+  gtk_widget_set_sensitive (item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+  gtk_widget_set_no_show_all (item, TRUE);
+  gtk_widget_show (item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  item = gtk_menu_item_new_with_label (_("Select from database..."));
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("ppd-select"), g_free);
+  g_signal_connect (item, "activate", G_CALLBACK (select_ppd_in_dialog), self);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  item = gtk_menu_item_new_with_label (_("Provide PPD File..."));
+  g_object_set_data_full (G_OBJECT (item), "purpose",
+                          g_strdup ("ppdfile-select"), g_free);
+  g_signal_connect (item, "activate", G_CALLBACK (select_ppd_manually), self);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  gtk_widget_show_all (priv->popup_menu);
+
+  gtk_menu_popup (GTK_MENU (priv->popup_menu),
+                  NULL, NULL, NULL, NULL, 0,
+                  gtk_get_current_event_time());
+
+  if (priv->current_dest >= 0 &&
+      priv->current_dest < priv->num_dests &&
+      priv->dests != NULL)
+    {
+      if (priv->preferred_drivers &&
+          g_hash_table_lookup_extended (priv->preferred_drivers,
+                                        priv->dests[priv->current_dest].name,
+                                        NULL, NULL))
+        {
+          get_ppd_names_cb (NULL,
+                            priv->dests[priv->current_dest].name,
+                            FALSE,
+                            user_data);
+        }
+      else
+        {
+          priv->get_ppd_name_cancellable = g_cancellable_new ();
+          priv->getting_ppd_names = TRUE;
+          get_ppd_names_async (priv->dests[priv->current_dest].name,
+                               3,
+                               priv->get_ppd_name_cancellable,
+                               get_ppd_names_cb,
+                               user_data);
+
+          update_sensitivity (self);
+        }
+    }
+}
+
+static void
 test_page_cb (GtkButton *button,
               gpointer   user_data)
 {
@@ -2303,7 +2810,7 @@ test_page_cb (GtkButton *button,
 }
 
 static void
-actualize_sensitivity (gpointer user_data)
+update_sensitivity (gpointer user_data)
 {
   CcPrintersPanelPrivate  *priv;
   CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
@@ -2312,9 +2819,12 @@ actualize_sensitivity (gpointer user_data)
   GtkWidget               *widget;
   gboolean                 is_authorized;
   gboolean                 is_discovered = FALSE;
+  gboolean                 is_class = FALSE;
+  gboolean                 is_changing_driver = FALSE;
   gboolean                 printer_selected;
   gboolean                 local_server = TRUE;
   gboolean                 no_cups = FALSE;
+  GList                   *iter;
   gint                     i;
 
   priv = PRINTERS_PANEL_PRIVATE (self);
@@ -2330,15 +2840,26 @@ actualize_sensitivity (gpointer user_data)
                      priv->dests != NULL;
 
   if (printer_selected)
-    for (i = 0; i < priv->dests[priv->current_dest].num_options; i++)
-      {
-        if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-type") == 0)
-          {
-            type = atoi (priv->dests[priv->current_dest].options[i].value);
-            is_discovered = type & CUPS_PRINTER_DISCOVERED;
-            break;
-          }
-      }
+    {
+      for (i = 0; i < priv->dests[priv->current_dest].num_options; i++)
+        {
+          if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-type") == 0)
+            {
+              type = atoi (priv->dests[priv->current_dest].options[i].value);
+              is_discovered = type & CUPS_PRINTER_DISCOVERED;
+              is_class = type & CUPS_PRINTER_CLASS;
+              break;
+            }
+        }
+
+      for (iter = priv->driver_change_list; iter; iter = iter->next)
+        {
+          if (g_strcmp0 ((gchar *) iter->data, priv->dests[priv->current_dest].name) == 0)
+            {
+              is_changing_driver = TRUE;
+            }
+        }
+    }
 
   cups_server = cupsServer ();
   if (cups_server &&
@@ -2390,6 +2911,19 @@ actualize_sensitivity (gpointer user_data)
 
   widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-location-label");
   cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), local_server && !is_discovered && is_authorized);
+
+  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-model-notebook");
+  if (is_changing_driver)
+    {
+      gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 2);
+    }
+  else
+    {
+      if (local_server && !is_discovered && is_authorized && !is_class && !priv->getting_ppd_names)
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 0);
+      else
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 1);
+    }
 }
 
 static void
@@ -2397,7 +2931,7 @@ on_permission_changed (GPermission *permission,
                        GParamSpec  *pspec,
                        gpointer     data)
 {
-  actualize_sensitivity (data);
+  update_sensitivity (data);
 }
 
 static void
@@ -2491,6 +3025,56 @@ cups_status_check (gpointer user_data)
 }
 
 static void
+get_all_ppds_async_cb (PPDList  *ppds,
+                       gpointer  user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+
+  priv = self->priv = PRINTERS_PANEL_PRIVATE (self);
+
+  priv->all_ppds_list = ppds;
+
+  priv->getting_all_ppds = FALSE;
+
+  if (priv->pp_ppd_selection_dialog)
+    pp_ppd_selection_dialog_set_ppd_list (priv->pp_ppd_selection_dialog,
+                                          priv->all_ppds_list);
+}
+
+static void
+update_label_padding (GtkWidget     *widget,
+                      GtkAllocation *allocation,
+                      gpointer       user_data)
+{
+  CcPrintersPanelPrivate *priv;
+  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
+  GtkAllocation           allocation1, allocation2;
+  GtkWidget              *label;
+  GtkWidget              *sublabel;
+  gint                    offset;
+  gint                    pad;
+
+  priv = PRINTERS_PANEL_PRIVATE (self);
+
+  sublabel = gtk_bin_get_child (GTK_BIN (widget));
+  if (sublabel)
+    {
+      gtk_widget_get_allocation (widget, &allocation1);
+      gtk_widget_get_allocation (sublabel, &allocation2);
+
+      offset = allocation2.x - allocation1.x;
+
+      label = (GtkWidget*)
+        gtk_builder_get_object (priv->builder, "printer-model-label");
+
+      gtk_misc_get_padding  (GTK_MISC (label), &pad, NULL);
+      if (offset != pad)
+        gtk_misc_set_padding (GTK_MISC (label), offset, 0);
+    }
+}
+
+static void
 cc_printers_panel_init (CcPrintersPanel *self)
 {
   CcPrintersPanelPrivate *priv;
@@ -2532,6 +3116,13 @@ cc_printers_panel_init (CcPrintersPanel *self)
   priv->permission = NULL;
   priv->lockdown_settings = NULL;
 
+  priv->getting_ppd_names = FALSE;
+
+  priv->all_ppds_list = NULL;
+  priv->getting_all_ppds = FALSE;
+
+  priv->preferred_drivers = NULL;
+
   builder_result = gtk_builder_add_objects_from_file (priv->builder,
                                                       DATADIR"/printers.ui",
                                                       objects, &error);
@@ -2628,6 +3219,11 @@ cc_printers_panel_init (CcPrintersPanel *self)
                       G_CALLBACK (on_lockdown_settings_changed),
                       self);
 
+  widget = (GtkWidget*)
+    gtk_builder_get_object (priv->builder, "printer-model-button");
+  g_signal_connect (widget, "clicked", G_CALLBACK (popup_model_menu_cb), self);
+  g_signal_connect (widget, "size-allocate", G_CALLBACK (update_label_padding), self);
+
 
   /* Set junctions */
   widget = (GtkWidget*)
@@ -2666,10 +3262,6 @@ cc_printers_panel_init (CcPrintersPanel *self)
     gtk_builder_get_object (priv->builder, "printer-ip-address-label");
   cc_editable_entry_set_selectable (CC_EDITABLE_ENTRY (widget), TRUE);
 
-  widget = (GtkWidget*)
-    gtk_builder_get_object (priv->builder, "printer-model-label");
-  cc_editable_entry_set_selectable (CC_EDITABLE_ENTRY (widget), TRUE);
-
 
   /* Add unlock button */
   priv->permission = (GPermission *)polkit_permission_new_sync (
@@ -2694,6 +3286,9 @@ Please check your installation");
   populate_allowed_users_list (self);
   attach_to_cups_notifier (self);
 
+  priv->getting_all_ppds = TRUE;
+  get_all_ppds_async (get_all_ppds_async_cb, self);
+
   http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
   if (!http)
     {
diff --git a/panels/printers/pp-ppd-selection-dialog.c b/panels/printers/pp-ppd-selection-dialog.c
new file mode 100644
index 0000000..2498319
--- /dev/null
+++ b/panels/printers/pp-ppd-selection-dialog.c
@@ -0,0 +1,444 @@
+/* -*- 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-ppd-selection-dialog.h"
+
+static void pp_ppd_selection_dialog_hide (PpPPDSelectionDialog *dialog);
+
+enum
+{
+  PPD_NAMES_COLUMN = 0,
+  PPD_DISPLAY_NAMES_COLUMN
+};
+
+enum
+{
+  PPD_MANUFACTURERS_NAMES_COLUMN = 0,
+  PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN
+};
+
+
+struct _PpPPDSelectionDialog {
+  GtkBuilder *builder;
+  GtkWidget  *parent;
+  GtkWidget  *dialog;
+
+  UserResponseCallback user_callback;
+  gpointer             user_data;
+
+  gchar           *ppd_name;
+  GtkResponseType  response;
+  gchar           *manufacturer;
+
+  PPDList *list;
+};
+
+static void
+manufacturer_selection_changed_cb (GtkTreeSelection *selection,
+                                   gpointer          user_data)
+{
+  PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog *) user_data;
+  GtkListStore         *store;
+  GtkTreeModel         *model;
+  GtkTreeIter           iter;
+  GtkTreeView          *models_treeview;
+  gchar                *manufacturer_name = NULL;
+  gint                  i, index;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      gtk_tree_model_get (model, &iter,
+			  PPD_MANUFACTURERS_NAMES_COLUMN, &manufacturer_name,
+			  -1);
+    }
+
+  if (manufacturer_name)
+    {
+      index = -1;
+      for (i = 0; i < dialog->list->num_of_manufacturers; i++)
+        {
+          if (g_strcmp0 (manufacturer_name,
+                         dialog->list->manufacturers[i]->manufacturer_name) == 0)
+            {
+              index = i;
+              break;
+            }
+        }
+
+      if (index >= 0)
+        {
+          models_treeview = (GtkTreeView*)
+            gtk_builder_get_object (dialog->builder, "ppd-selection-models-treeview");
+
+          store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+          for (i = 0; i < dialog->list->manufacturers[index]->num_of_ppds; i++)
+            {
+              gtk_list_store_append (store, &iter);
+              gtk_list_store_set (store, &iter,
+                                  PPD_NAMES_COLUMN, dialog->list->manufacturers[index]->ppds[i]->ppd_name,
+                                  PPD_DISPLAY_NAMES_COLUMN, dialog->list->manufacturers[index]->ppds[i]->ppd_display_name,
+                                  -1);
+            }
+
+          gtk_tree_view_set_model (models_treeview, GTK_TREE_MODEL (store));
+          g_object_unref (store);
+        }
+
+      g_free (manufacturer_name);
+    }
+}
+
+static void
+model_selection_changed_cb (GtkTreeSelection *selection,
+                            gpointer          user_data)
+{
+  PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog *) user_data;
+  GtkTreeModel         *model;
+  GtkTreeIter           iter;
+  GtkWidget            *widget;
+  gchar                *model_name = NULL;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      gtk_tree_model_get (model, &iter,
+                          PPD_NAMES_COLUMN, &model_name,
+			  -1);
+    }
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "ppd-selection-select-button");
+
+  if (model_name)
+    {
+      gtk_widget_set_sensitive (widget, TRUE);
+      g_free (model_name);
+    }
+  else
+    {
+      gtk_widget_set_sensitive (widget, FALSE);
+    }
+}
+
+static void
+fill_ppds_list (PpPPDSelectionDialog *dialog)
+{
+  GtkTreeSelection *selection;
+  GtkListStore     *store;
+  GtkTreePath      *path;
+  GtkTreeView      *treeview;
+  GtkTreeIter       iter;
+  GtkTreeIter      *preselect_iter = NULL;
+  GtkWidget        *widget;
+  gint              i;
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "ppd-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, "ppd-selection-manufacturers-treeview");
+
+  if (dialog->list)
+    {
+      store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+      for (i = 0; i < dialog->list->num_of_manufacturers; i++)
+        {
+          gtk_list_store_append (store, &iter);
+          gtk_list_store_set (store, &iter,
+                              PPD_MANUFACTURERS_NAMES_COLUMN, dialog->list->manufacturers[i]->manufacturer_name,
+                              PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, dialog->list->manufacturers[i]->manufacturer_display_name,
+                              -1);
+
+          if (g_strcmp0 (dialog->manufacturer,
+                         dialog->list->manufacturers[i]->manufacturer_display_name) == 0)
+            {
+              preselect_iter = gtk_tree_iter_copy (&iter);
+            }
+        }
+
+      gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));
+
+      if (preselect_iter &&
+          (selection = gtk_tree_view_get_selection (treeview)) != NULL)
+        {
+          gtk_tree_selection_select_iter (selection, preselect_iter);
+          path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), preselect_iter);
+          gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.0);
+          gtk_tree_path_free (path);
+          gtk_tree_iter_free (preselect_iter);
+        }
+
+      g_object_unref (store);
+    }
+}
+
+static void
+populate_dialog (PpPPDSelectionDialog *dialog)
+{
+  GtkTreeViewColumn *column;
+  GtkCellRenderer   *renderer;
+  GtkTreeView       *manufacturers_treeview;
+  GtkTreeView       *models_treeview;
+  GtkWidget         *widget;
+
+  manufacturers_treeview = (GtkTreeView*)
+    gtk_builder_get_object (dialog->builder, "ppd-selection-manufacturers-treeview");
+
+  renderer = gtk_cell_renderer_text_new ();
+
+  /* Translators: Name of column showing printer manufacturers */
+  column = gtk_tree_view_column_new_with_attributes (_("Manufacturers"), renderer,
+                                                     "text", PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, NULL);
+  gtk_tree_view_column_set_expand (column, TRUE);
+  gtk_tree_view_append_column (manufacturers_treeview, column);
+
+
+  models_treeview = (GtkTreeView*)
+    gtk_builder_get_object (dialog->builder, "ppd-selection-models-treeview");
+
+  renderer = gtk_cell_renderer_text_new ();
+
+  /* Translators: Name of column showing printer drivers */
+  column = gtk_tree_view_column_new_with_attributes (_("Drivers"), renderer,
+                                                     "text", PPD_DISPLAY_NAMES_COLUMN, NULL);
+  gtk_tree_view_column_set_expand (column, TRUE);
+  gtk_tree_view_append_column (models_treeview, column);
+
+
+  g_signal_connect (gtk_tree_view_get_selection (models_treeview),
+                    "changed", G_CALLBACK (model_selection_changed_cb), dialog);
+
+  g_signal_connect (gtk_tree_view_get_selection (manufacturers_treeview),
+                    "changed", G_CALLBACK (manufacturer_selection_changed_cb), dialog);
+
+  gtk_widget_show_all (dialog->dialog);
+
+  if (!dialog->list)
+    {
+      widget = (GtkWidget*)
+        gtk_builder_get_object (dialog->builder, "ppd-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);
+    }
+  else
+    {
+      fill_ppds_list (dialog);
+    }
+}
+
+static void
+ppd_selection_dialog_response_cb (GtkDialog *dialog,
+                                  gint       response_id,
+                                  gpointer   user_data)
+{
+  PpPPDSelectionDialog *ppd_selection_dialog = (PpPPDSelectionDialog*) user_data;
+  GtkTreeSelection     *selection;
+  GtkTreeModel         *model;
+  GtkTreeView          *models_treeview;
+  GtkTreeIter           iter;
+
+  pp_ppd_selection_dialog_hide (ppd_selection_dialog);
+
+  ppd_selection_dialog->response = response_id;
+  if (response_id == GTK_RESPONSE_OK)
+    {
+      models_treeview = (GtkTreeView*)
+        gtk_builder_get_object (ppd_selection_dialog->builder, "ppd-selection-models-treeview");
+
+      if (models_treeview)
+        {
+          selection = gtk_tree_view_get_selection (models_treeview);
+
+          if (selection)
+            {
+              if (gtk_tree_selection_get_selected (selection, &model, &iter))
+                {
+                  gtk_tree_model_get (model, &iter,
+                                      PPD_NAMES_COLUMN, &ppd_selection_dialog->ppd_name,
+            			  -1);
+                }
+            }
+        }
+    }
+
+  ppd_selection_dialog->user_callback (GTK_DIALOG (ppd_selection_dialog->dialog),
+                                       response_id,
+                                       ppd_selection_dialog->user_data);
+}
+
+static void
+update_alignment_padding (GtkWidget     *widget,
+                          GtkAllocation *allocation,
+                          gpointer       user_data)
+{
+  PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog*) user_data;
+  GtkAllocation         allocation2;
+  GtkWidget            *action_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);
+
+  offset_left = allocation2.x - allocation->x;
+  offset_right = (allocation->x + allocation->width) -
+                 (allocation2.x + allocation2.width);
+
+  gtk_alignment_get_padding  (GTK_ALIGNMENT (widget),
+                              &padding_top, &padding_bottom,
+                              &padding_left, &padding_right);
+  if (allocation->x >= 0 && allocation2.x >= 0)
+    {
+      if (offset_left > 0 && offset_left != padding_left)
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget),
+                                   padding_top, padding_bottom,
+                                   offset_left, padding_right);
+
+      gtk_alignment_get_padding  (GTK_ALIGNMENT (widget),
+                                  &padding_top, &padding_bottom,
+                                  &padding_left, &padding_right);
+      if (offset_right > 0 && offset_right != padding_right)
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget),
+                                   padding_top, padding_bottom,
+                                   padding_left, offset_right);
+    }
+}
+
+PpPPDSelectionDialog *
+pp_ppd_selection_dialog_new (GtkWindow            *parent,
+                             PPDList              *ppd_list,
+                             gchar                *manufacturer,
+                             UserResponseCallback  user_callback,
+                             gpointer              user_data)
+{
+  PpPPDSelectionDialog *dialog;
+  GtkWidget            *widget;
+  GError               *error = NULL;
+  gchar                *objects[] = { "ppd-selection-dialog", NULL };
+  guint                 builder_result;
+
+  dialog = g_new0 (PpPPDSelectionDialog, 1);
+
+  dialog->builder = gtk_builder_new ();
+  dialog->parent = GTK_WIDGET (parent);
+
+  builder_result = gtk_builder_add_objects_from_file (dialog->builder,
+                                                      DATADIR"/ppd-selection-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, "ppd-selection-dialog");
+  dialog->user_callback = user_callback;
+  dialog->user_data = user_data;
+
+  dialog->response = GTK_RESPONSE_NONE;
+  dialog->list = ppd_list_copy (ppd_list);
+
+  dialog->manufacturer = get_standard_manufacturers_name (manufacturer);
+
+  /* connect signals */
+  g_signal_connect (dialog->dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+  g_signal_connect (dialog->dialog, "response", G_CALLBACK (ppd_selection_dialog_response_cb), dialog);
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "content-alignment");
+  g_signal_connect (widget, "size-allocate", G_CALLBACK (update_alignment_padding), dialog);
+
+  widget = (GtkWidget*)
+    gtk_builder_get_object (dialog->builder, "ppd-spinner");
+  gtk_spinner_start (GTK_SPINNER (widget));
+
+  populate_dialog (dialog);
+
+  gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), GTK_WINDOW (parent));
+  gtk_window_present (GTK_WINDOW (dialog->dialog));
+  gtk_widget_show_all (GTK_WIDGET (dialog->dialog));
+
+  return dialog;
+}
+
+void
+pp_ppd_selection_dialog_free (PpPPDSelectionDialog *dialog)
+{
+  gtk_widget_destroy (GTK_WIDGET (dialog->dialog));
+
+  g_object_unref (dialog->builder);
+
+  g_free (dialog->ppd_name);
+
+  g_free (dialog->manufacturer);
+
+  g_free (dialog);
+}
+
+gchar *
+pp_ppd_selection_dialog_get_ppd_name (PpPPDSelectionDialog *dialog)
+{
+  return g_strdup (dialog->ppd_name);
+}
+
+void
+pp_ppd_selection_dialog_set_ppd_list (PpPPDSelectionDialog *dialog,
+                                      PPDList              *list)
+{
+  dialog->list = list;
+  fill_ppds_list (dialog);
+}
+
+static void
+pp_ppd_selection_dialog_hide (PpPPDSelectionDialog *dialog)
+{
+  gtk_widget_hide (GTK_WIDGET (dialog->dialog));
+}
diff --git a/panels/printers/pp-ppd-selection-dialog.h b/panels/printers/pp-ppd-selection-dialog.h
new file mode 100644
index 0000000..2ea05d0
--- /dev/null
+++ b/panels/printers/pp-ppd-selection-dialog.h
@@ -0,0 +1,46 @@
+/* -*- 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_SELECTION_DIALOG_H__
+#define __PP_PPD_SELECTION_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+typedef struct _PpPPDSelectionDialog PpPPDSelectionDialog;
+
+typedef void (*UserResponseCallback) (GtkDialog *dialog, gint response_id, gpointer user_data);
+
+PpPPDSelectionDialog *pp_ppd_selection_dialog_new          (GtkWindow                 *parent,
+                                                            PPDList                   *ppd_list,
+                                                            gchar                     *manufacturer,
+                                                            UserResponseCallback       user_callback,
+                                                            gpointer                   user_data);
+gchar                *pp_ppd_selection_dialog_get_ppd_name (PpPPDSelectionDialog      *dialog);
+void                  pp_ppd_selection_dialog_set_ppd_list (PpPPDSelectionDialog      *dialog,
+                                                            PPDList                   *list);
+void                  pp_ppd_selection_dialog_free         (PpPPDSelectionDialog      *dialog);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/printers/pp-utils.c b/panels/printers/pp-utils.c
index 5263af6..c78b70f 100644
--- a/panels/printers/pp-utils.c
+++ b/panels/printers/pp-utils.c
@@ -35,6 +35,8 @@
 #define SCP_PATH  "/org/fedoraproject/Config/Printing"
 #define SCP_IFACE "org.fedoraproject.Config.Printing"
 
+#define DBUS_TIMEOUT 120000
+
 gchar *
 get_tag_value (const gchar *tag_string, const gchar *tag_name)
 {
@@ -2560,3 +2562,1636 @@ printer_set_default_media_size (const gchar *printer_name)
       g_error_free (error);
     }
 }
+
+
+typedef struct
+{
+  gchar        *printer_name;
+  gchar       **attributes_names;
+  GHashTable   *result;
+  GIACallback   callback;
+  gpointer      user_data;
+  GMainContext *context;
+} GIAData;
+
+static gboolean
+get_ipp_attributes_idle_cb (gpointer user_data)
+{
+  GIAData *data = (GIAData *) user_data;
+
+  data->callback (data->result, data->user_data);
+
+  return FALSE;
+}
+
+static void
+get_ipp_attributes_data_free (gpointer user_data)
+{
+  GIAData *data = (GIAData *) user_data;
+
+  if (data->context)
+    g_main_context_unref (data->context);
+  g_free (data->printer_name);
+  if (data->attributes_names)
+    g_strfreev (data->attributes_names);
+  g_free (data);
+}
+
+static void
+get_ipp_attributes_cb (gpointer user_data)
+{
+  GIAData *data = (GIAData *) user_data;
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+                         get_ipp_attributes_idle_cb,
+                         data,
+                         get_ipp_attributes_data_free);
+  g_source_attach (idle_source, data->context);
+  g_source_unref (idle_source);
+}
+
+static void
+ipp_attribute_free2 (gpointer attr)
+{
+  IPPAttribute *attribute = (IPPAttribute *) attr;
+  ipp_attribute_free (attribute);
+}
+
+static gpointer
+get_ipp_attributes_func (gpointer user_data)
+{
+  ipp_attribute_t  *attr = NULL;
+  GIAData          *data = (GIAData *) user_data;
+  ipp_t            *request;
+  ipp_t            *response = NULL;
+  gchar            *printer_uri;
+  char            **requested_attrs = NULL;
+  gint              i, j, length = 0;
+
+  printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", data->printer_name);
+
+  if (data->attributes_names)
+    {
+      length = g_strv_length (data->attributes_names);
+
+      requested_attrs = g_new0 (char *, length);
+      for (i = 0; data->attributes_names[i]; i++)
+        requested_attrs[i] = g_strdup (data->attributes_names[i]);
+
+      request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
+      ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                    "printer-uri", NULL, printer_uri);
+      ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                     "requested-attributes", length, NULL, (const char **) requested_attrs);
+      response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+    }
+
+  if (response)
+    {
+      if (response->request.status.status_code <= IPP_OK_CONFLICT)
+        {
+          for (j = 0; j < length; j++)
+            {
+              attr = ippFindAttribute (response, requested_attrs[j], IPP_TAG_ZERO);
+              if (attr && attr->num_values > 0 && attr->value_tag != IPP_TAG_NOVALUE)
+                {
+                  IPPAttribute *attribute;
+
+                  attribute = g_new0 (IPPAttribute, 1);
+                  attribute->attribute_name = g_strdup (requested_attrs[j]);
+                  attribute->attribute_values = g_new0 (IPPAttributeValue, attr->num_values);
+                  attribute->num_of_values = attr->num_values;
+
+                  if (attr->value_tag == IPP_TAG_INTEGER ||
+                      attr->value_tag == IPP_TAG_ENUM)
+                    {
+                      attribute->attribute_type = IPP_ATTRIBUTE_TYPE_INTEGER;
+
+                      for (i = 0; i < attr->num_values; i++)
+                        attribute->attribute_values[i].integer_value = attr->values[i].integer;
+                    }
+                  else if (attr->value_tag == IPP_TAG_NAME ||
+                           attr->value_tag == IPP_TAG_STRING ||
+                           attr->value_tag == IPP_TAG_TEXT ||
+                           attr->value_tag == IPP_TAG_URI ||
+                           attr->value_tag == IPP_TAG_KEYWORD ||
+                           attr->value_tag == IPP_TAG_URISCHEME)
+                    {
+                      attribute->attribute_type = IPP_ATTRIBUTE_TYPE_STRING;
+
+                      for (i = 0; i < attr->num_values; i++)
+                        attribute->attribute_values[i].string_value = g_strdup (attr->values[i].string.text);
+                    }
+                  else if (attr->value_tag == IPP_TAG_RANGE)
+                    {
+                      attribute->attribute_type = IPP_ATTRIBUTE_TYPE_RANGE;
+
+                      for (i = 0; i < attr->num_values; i++)
+                        {
+                          attribute->attribute_values[i].lower_range = attr->values[i].range.lower;
+                          attribute->attribute_values[i].upper_range = attr->values[i].range.upper;
+                        }
+                    }
+                  else if (attr->value_tag == IPP_TAG_BOOLEAN)
+                    {
+                      attribute->attribute_type = IPP_ATTRIBUTE_TYPE_BOOLEAN;
+
+                      for (i = 0; i < attr->num_values; i++)
+                        attribute->attribute_values[i].boolean_value = attr->values[i].boolean;
+                    }
+
+                  if (!data->result)
+                    data->result = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, ipp_attribute_free2);
+
+                  g_hash_table_insert (data->result, g_strdup (requested_attrs[j]), attribute);
+                }
+            }
+        }
+
+      ippDelete (response);
+    }
+
+
+  for (i = 0; i < length; i++)
+    g_free (requested_attrs[i]);
+  g_free (requested_attrs);
+
+  g_free (printer_uri);
+
+  get_ipp_attributes_cb (data);
+
+  return NULL;
+}
+
+void
+get_ipp_attributes_async (const gchar  *printer_name,
+                          gchar       **attributes_names,
+                          GIACallback   callback,
+                          gpointer      user_data)
+{
+  GIAData *data;
+  GThread *thread;
+  GError  *error = NULL;
+
+  data = g_new0 (GIAData, 1);
+  data->printer_name = g_strdup (printer_name);
+  data->attributes_names = g_strdupv (attributes_names);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->context = g_main_context_ref_thread_default ();
+
+  thread = g_thread_try_new ("get-ipp-attributes",
+                             get_ipp_attributes_func,
+                             data,
+                             &error);
+
+  if (!thread)
+    {
+      g_warning ("%s", error->message);
+      callback (NULL, user_data);
+
+      g_error_free (error);
+      get_ipp_attributes_data_free (data);
+    }
+  else
+    {
+      g_thread_unref (thread);
+    }
+}
+
+IPPAttribute *
+ipp_attribute_copy (IPPAttribute *attr)
+{
+  IPPAttribute *result = NULL;
+  gint          i;
+
+  if (attr)
+    {
+      result = g_new0 (IPPAttribute, 1);
+
+      *result = *attr;
+      result->attribute_name = g_strdup (attr->attribute_name);
+      result->attribute_values = g_new0 (IPPAttributeValue, attr->num_of_values);
+      for (i = 0; i < attr->num_of_values; i++)
+        {
+          result->attribute_values[i] = attr->attribute_values[i];
+          if (attr->attribute_values[i].string_value)
+            result->attribute_values[i].string_value = g_strdup (attr->attribute_values[i].string_value);
+        }
+    }
+
+  return result;
+}
+
+void
+ipp_attribute_free (IPPAttribute *attr)
+{
+  gint i;
+
+  if (attr)
+    {
+      for (i = 0; i < attr->num_of_values; i++)
+        g_free (attr->attribute_values[i].string_value);
+
+      g_free (attr->attribute_values);
+      g_free (attr->attribute_name);
+      g_free (attr);
+    }
+}
+
+
+
+typedef struct
+{
+  gchar        *printer_name;
+  gchar        *ppd_copy;
+  GCancellable *cancellable;
+  PSPCallback   callback;
+  gpointer      user_data;
+} PSPData;
+
+static void
+printer_set_ppd_async_dbus_cb (GObject      *source_object,
+                               GAsyncResult *res,
+                               gpointer      user_data)
+{
+  GVariant *output;
+  gboolean  result = FALSE;
+  PSPData  *data = (PSPData *) 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
+        result = TRUE;
+
+      g_variant_unref (output);
+    }
+  else
+    {
+      if (error->code != G_IO_ERROR_CANCELLED)
+        g_warning ("%s", error->message);
+      g_error_free (error);
+    }
+
+  data->callback (g_strdup (data->printer_name),
+                  result,
+                  data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+
+  if (data->ppd_copy)
+    {
+      g_unlink (data->ppd_copy);
+      g_free (data->ppd_copy);
+    }
+
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+/*
+ * Set ppd for given printer.
+ * Don't use this for classes, just for printers.
+ */
+void
+printer_set_ppd_async (const gchar  *printer_name,
+                       const gchar  *ppd_name,
+                       GCancellable *cancellable,
+                       PSPCallback   callback,
+                       gpointer      user_data)
+{
+  GDBusConnection *bus;
+  PSPData         *data;
+  GError          *error = NULL;
+
+  data = g_new0 (PSPData, 1);
+  if (cancellable)
+    data->cancellable = g_object_ref (cancellable);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->printer_name = g_strdup (printer_name);
+
+  if (printer_name == NULL ||
+      printer_name[0] == '\0')
+    {
+      goto out;
+    }
+
+  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);
+      goto out;
+    }
+
+  g_dbus_connection_call (bus,
+                          MECHANISM_BUS,
+                          "/",
+                          MECHANISM_BUS,
+                          "PrinterAdd",
+                          g_variant_new ("(sssss)",
+                                         printer_name,
+                                         "",
+                                         ppd_name,
+                                         "",
+                                         ""),
+                          G_VARIANT_TYPE ("(s)"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          -1,
+                          data->cancellable,
+                          printer_set_ppd_async_dbus_cb,
+                          data);
+
+  return;
+
+out:
+  callback (g_strdup (printer_name), FALSE, user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+printer_set_ppd_file_async_scb (GObject      *source_object,
+                                GAsyncResult *res,
+                                gpointer      user_data)
+{
+  GDBusConnection *bus;
+  gboolean         success;
+  PSPData         *data = (PSPData *) user_data;
+  GError          *error = NULL;
+
+  success = g_file_copy_finish (G_FILE (source_object),
+                                res,
+                                &error);
+  g_object_unref (source_object);
+
+  if (!success)
+    {
+      g_warning ("%s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  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);
+      goto out;
+    }
+
+  g_dbus_connection_call (bus,
+                          MECHANISM_BUS,
+                          "/",
+                          MECHANISM_BUS,
+                          "PrinterAddWithPpdFile",
+                          g_variant_new ("(sssss)",
+                                         data->printer_name,
+                                         "",
+                                         data->ppd_copy,
+                                         "",
+                                         ""),
+                          G_VARIANT_TYPE ("(s)"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          -1,
+                          data->cancellable,
+                          printer_set_ppd_async_dbus_cb,
+                          data);
+
+  return;
+
+out:
+  data->callback (g_strdup (data->printer_name), FALSE, data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->printer_name);
+  g_free (data->ppd_copy);
+  g_free (data);
+}
+
+/*
+ * Set ppd for given printer.
+ * Don't use this for classes, just for printers.
+ */
+void
+printer_set_ppd_file_async (const gchar  *printer_name,
+                            const gchar  *ppd_filename,
+                            GCancellable *cancellable,
+                            PSPCallback   callback,
+                            gpointer      user_data)
+{
+  GFileIOStream *stream;
+  PSPData       *data;
+  GFile         *source_ppd_file;
+  GFile         *destination_ppd_file;
+
+  data = g_new0 (PSPData, 1);
+  if (cancellable)
+    data->cancellable = g_object_ref (cancellable);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->printer_name = g_strdup (printer_name);
+
+  if (printer_name == NULL ||
+      printer_name[0] == '\0')
+    {
+      goto out;
+    }
+
+  /*
+   * We need to copy the PPD to temp directory at first.
+   * This is needed because of SELinux.
+   */
+  source_ppd_file = g_file_new_for_path (ppd_filename);
+  destination_ppd_file = g_file_new_tmp ("g-c-c-XXXXXX.ppd", &stream, NULL);
+  g_object_unref (stream);
+  data->ppd_copy = g_strdup (g_file_get_path (destination_ppd_file));
+
+  g_file_copy_async (source_ppd_file,
+                     destination_ppd_file,
+                     G_FILE_COPY_OVERWRITE,
+                     G_PRIORITY_DEFAULT,
+                     cancellable,
+                     NULL,
+                     NULL,
+                     printer_set_ppd_file_async_scb,
+                     data);
+
+  g_object_unref (destination_ppd_file);
+
+  return;
+
+out:
+  callback (g_strdup (printer_name), FALSE, user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+
+
+typedef void (*GPACallback) (gchar    **attribute_values,
+                             gpointer   user_data);
+
+typedef struct
+{
+  gchar         *attribute_name;
+  gchar        **ppds_names;
+  gchar        **result;
+  GPACallback    callback;
+  gpointer       user_data;
+  GMainContext  *context;
+} GPAData;
+
+static gboolean
+get_ppds_attribute_idle_cb (gpointer user_data)
+{
+  GPAData *data = (GPAData *) user_data;
+
+  data->callback (data->result, data->user_data);
+
+  return FALSE;
+}
+
+static void
+get_ppds_attribute_data_free (gpointer user_data)
+{
+  GPAData *data = (GPAData *) user_data;
+
+  if (data->context)
+    g_main_context_unref (data->context);
+  g_free (data->attribute_name);
+  g_strfreev (data->ppds_names);
+  g_free (data);
+}
+
+static void
+get_ppds_attribute_cb (gpointer user_data)
+{
+  GPAData *data = (GPAData *) user_data;
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+                         get_ppds_attribute_idle_cb,
+                         data,
+                         get_ppds_attribute_data_free);
+  g_source_attach (idle_source, data->context);
+  g_source_unref (idle_source);
+}
+
+static gpointer
+get_ppds_attribute_func (gpointer user_data)
+{
+  ppd_file_t  *ppd_file;
+  ppd_attr_t  *ppd_attr;
+  GPAData     *data = (GPAData *) user_data;
+  gchar       *ppd_filename;
+  gint         i;
+
+  data->result = g_new0 (gchar *, g_strv_length (data->ppds_names) + 1);
+  for (i = 0; data->ppds_names[i]; i++)
+    {
+      ppd_filename = g_strdup (cupsGetServerPPD (CUPS_HTTP_DEFAULT, data->ppds_names[i]));
+      if (ppd_filename)
+        {
+          ppd_file = ppdOpenFile (ppd_filename);
+          if (ppd_file)
+            {
+              ppd_attr = ppdFindAttr (ppd_file, data->attribute_name, NULL);
+              if (ppd_attr != NULL)
+                data->result[i] = g_strdup (ppd_attr->value);
+
+              ppdClose (ppd_file);
+            }
+
+          g_unlink (ppd_filename);
+          g_free (ppd_filename);
+        }
+    }
+
+  get_ppds_attribute_cb (data);
+
+  return NULL;
+}
+
+/*
+ * Get values of requested PPD attribute for given PPDs.
+ */
+static void
+get_ppds_attribute_async (gchar       **ppds_names,
+                          gchar        *attribute_name,
+                          GPACallback   callback,
+                          gpointer      user_data)
+{
+  GPAData *data;
+  GThread *thread;
+  GError  *error = NULL;
+
+  if (!ppds_names || !attribute_name)
+    callback (NULL, user_data);
+
+  data = g_new0 (GPAData, 1);
+  data->ppds_names = g_strdupv (ppds_names);
+  data->attribute_name = g_strdup (attribute_name);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->context = g_main_context_ref_thread_default ();
+
+  thread = g_thread_try_new ("get-ppds-attribute",
+                             get_ppds_attribute_func,
+                             data,
+                             &error);
+
+  if (!thread)
+    {
+      g_warning ("%s", error->message);
+      callback (NULL, user_data);
+
+      g_error_free (error);
+      get_ppds_attribute_data_free (data);
+    }
+  else
+    {
+      g_thread_unref (thread);
+    }
+}
+
+
+
+typedef void (*GDACallback) (gchar    *device_id,
+                             gchar    *device_make_and_model,
+                             gchar    *device_uri,
+                             gpointer  user_data);
+
+typedef struct
+{
+  gchar        *printer_name;
+  gchar        *device_uri;
+  GCancellable *cancellable;
+  GList        *backend_list;
+  GDACallback   callback;
+  gpointer      user_data;
+} GDAData;
+
+typedef struct
+{
+  gchar         *printer_name;
+  gint           count;
+  PPDName      **result;
+  GCancellable  *cancellable;
+  GPNCallback    callback;
+  gpointer       user_data;
+} GPNData;
+
+static void
+get_ppd_names_async_cb (gchar    **attribute_values,
+                        gpointer   user_data)
+{
+  GPNData *data = (GPNData *) user_data;
+  gint     i;
+
+  if (g_cancellable_is_cancelled (data->cancellable))
+    {
+      g_strfreev (attribute_values);
+
+      for (i = 0; data->result[i]; i++)
+        {
+          g_free (data->result[i]->ppd_name);
+          g_free (data->result[i]);
+        }
+
+      g_free (data->result);
+      data->result = NULL;
+
+      goto out;
+    }
+
+  if (attribute_values)
+    {
+      for (i = 0; attribute_values[i]; i++)
+        data->result[i]->ppd_display_name = attribute_values[i];
+
+      g_free (attribute_values);
+    }
+
+out:
+  data->callback (data->result,
+                  data->printer_name,
+                  g_cancellable_is_cancelled (data->cancellable),
+                  data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+get_ppd_names_async_dbus_scb (GObject      *source_object,
+                              GAsyncResult *res,
+                              gpointer      user_data)
+{
+  GVariant  *output;
+  PPDName   *ppd_item;
+  PPDName  **result = NULL;
+  GPNData   *data = (GPNData *) user_data;
+  GError    *error = NULL;
+  GList     *driver_list = NULL;
+  GList     *iter;
+  gint       i, j, n = 0;
+  static const char * const match_levels[] = {
+             "exact-cmd",
+             "exact",
+             "close",
+             "generic",
+             "none"};
+
+  output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+                                          res,
+                                          &error);
+  g_object_unref (source_object);
+
+  if (output)
+    {
+      GVariant *array;
+
+      g_variant_get (output, "(@a(ss))",
+                     &array);
+
+      if (array)
+        {
+          GVariantIter *iter;
+          GVariant     *item;
+          gchar        *driver;
+          gchar        *match;
+
+          for (j = 0; j < G_N_ELEMENTS (match_levels) && n < data->count; j++)
+            {
+              g_variant_get (array,
+                             "a(ss)",
+                             &iter);
+
+              while ((item = g_variant_iter_next_value (iter)))
+                {
+                  g_variant_get (item,
+                                 "(ss)",
+                                 &driver,
+                                 &match);
+
+                  if (g_str_equal (match, match_levels[j]) && n < data->count)
+                    {
+                      ppd_item = g_new0 (PPDName, 1);
+                      ppd_item->ppd_name = g_strdup (driver);
+
+                      if (g_strcmp0 (match, "exact-cmd") == 0)
+                        ppd_item->ppd_match_level = PPD_EXACT_CMD_MATCH;
+                      else if (g_strcmp0 (match, "exact") == 0)
+                        ppd_item->ppd_match_level = PPD_EXACT_MATCH;
+                      else if (g_strcmp0 (match, "close") == 0)
+                        ppd_item->ppd_match_level = PPD_CLOSE_MATCH;
+                      else if (g_strcmp0 (match, "generic") == 0)
+                        ppd_item->ppd_match_level = PPD_GENERIC_MATCH;
+                      else if (g_strcmp0 (match, "none") == 0)
+                        ppd_item->ppd_match_level = PPD_NO_MATCH;
+
+                      driver_list = g_list_append (driver_list, ppd_item);
+
+                      n++;
+                    }
+
+                  g_free (driver);
+                  g_free (match);
+                  g_variant_unref (item);
+                }
+            }
+
+          g_variant_unref (array);
+        }
+
+      g_variant_unref (output);
+    }
+  else
+    {
+      if (error->code != G_IO_ERROR_CANCELLED)
+        g_warning ("%s", error->message);
+      g_error_free (error);
+    }
+
+  if (n > 0)
+    {
+      result = g_new0 (PPDName *, n + 1);
+      i = 0;
+      for (iter = driver_list; iter; iter = iter->next)
+        {
+          result[i] = iter->data;
+          i++;
+        }
+    }
+
+  if (result)
+    {
+      gchar **ppds_names;
+
+      data->result = result;
+
+      ppds_names = g_new0 (gchar *, n + 1);
+      for (i = 0; i < n; i++)
+        ppds_names[i] = g_strdup (result[i]->ppd_name);
+
+      get_ppds_attribute_async (ppds_names,
+                                "NickName",
+                                get_ppd_names_async_cb,
+                                data);
+
+      g_strfreev (ppds_names);
+    }
+  else
+    {
+      data->callback (NULL,
+                      data->printer_name,
+                      g_cancellable_is_cancelled (data->cancellable),
+                      data->user_data);
+
+      if (data->cancellable)
+        g_object_unref (data->cancellable);
+      g_free (data->printer_name);
+      g_free (data);
+    }
+}
+
+static void
+get_device_attributes_cb (gchar    *device_id,
+                          gchar    *device_make_and_model,
+                          gchar    *device_uri,
+                          gpointer  user_data)
+{
+  GDBusConnection *bus;
+  GError          *error = NULL;
+  GPNData         *data = (GPNData *) user_data;
+
+  if (g_cancellable_is_cancelled (data->cancellable))
+    goto out;
+
+  if (!device_id || !device_make_and_model || !device_uri)
+    goto out;
+
+  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  if (!bus)
+    {
+      g_warning ("Failed to get system bus: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  g_dbus_connection_call (bus,
+                          SCP_BUS,
+                          SCP_PATH,
+                          SCP_IFACE,
+                          "GetBestDrivers",
+                          g_variant_new ("(sss)",
+                                         device_id,
+                                         device_make_and_model,
+                                         device_uri),
+                          G_VARIANT_TYPE ("(a(ss))"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          DBUS_TIMEOUT,
+                          data->cancellable,
+                          get_ppd_names_async_dbus_scb,
+                          data);
+
+  return;
+
+out:
+  data->callback (NULL,
+                  data->printer_name,
+                  g_cancellable_is_cancelled (data->cancellable),
+                  data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+get_device_attributes_async_dbus_cb (GObject      *source_object,
+                                     GAsyncResult *res,
+                                     gpointer      user_data)
+
+{
+  GVariant *output;
+  GDAData  *data = (GDAData *) user_data;
+  GError   *error = NULL;
+  GList    *tmp;
+  gchar    *device_id = NULL;
+  gchar    *device_make_and_model = 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;
+      GVariant    *devices_variant = NULL;
+
+      g_variant_get (output, "(&s a{ss})",
+                     &ret_error,
+                     &devices_variant);
+
+      if (ret_error[0] != '\0')
+        {
+          g_warning ("%s", ret_error);
+        }
+
+      if (devices_variant)
+        {
+          GVariantIter *iter;
+          GVariant     *item;
+          gint          index = -1;
+
+          if (data->device_uri)
+            {
+              gchar *key;
+              gchar *value;
+              gchar *number;
+              gchar *endptr;
+              gchar *suffix;
+
+              g_variant_get (devices_variant,
+                             "a{ss}",
+                             &iter);
+
+              while ((item = g_variant_iter_next_value (iter)))
+                {
+                  g_variant_get (item,
+                                 "{ss}",
+                                 &key,
+                                 &value);
+
+                  if (g_str_equal (value, data->device_uri))
+                    {
+                      number = g_strrstr (key, ":");
+                      if (number != NULL)
+                        {
+                          number++;
+                          index = g_ascii_strtoll (number, &endptr, 10);
+                          if (index == 0 && endptr == (number))
+                            index = -1;
+                        }
+                    }
+
+                  g_free (key);
+                  g_free (value);
+                  g_variant_unref (item);
+                }
+
+              suffix = g_strdup_printf (":%d", index);
+
+              g_variant_get (devices_variant,
+                             "a{ss}",
+                             &iter);
+
+              while ((item = g_variant_iter_next_value (iter)))
+                {
+                  gchar *key;
+                  gchar *value;
+
+                  g_variant_get (item,
+                                 "{ss}",
+                                 &key,
+                                 &value);
+
+                  if (g_str_has_suffix (key, suffix))
+                    {
+                      if (g_str_has_prefix (key, "device-id"))
+                        {
+                          device_id = g_strdup (value);
+                        }
+
+                      if (g_str_has_prefix (key, "device-make-and-model"))
+                        {
+                          device_make_and_model = g_strdup (value);
+                        }
+                    }
+
+                  g_free (key);
+                  g_free (value);
+                  g_variant_unref (item);
+                }
+
+              g_free (suffix);
+            }
+
+          g_variant_unref (devices_variant);
+        }
+
+      g_variant_unref (output);
+    }
+  else
+    {
+      if (error->code != G_IO_ERROR_CANCELLED)
+        g_warning ("%s", error->message);
+      g_error_free (error);
+    }
+
+  if (!device_id || !device_make_and_model)
+    {
+      GVariantBuilder include_scheme_builder;
+
+      g_free (device_id);
+      g_free (device_make_and_model);
+
+      device_id = NULL;
+      device_make_and_model = NULL;
+
+      if (data->backend_list && !g_cancellable_is_cancelled (data->cancellable))
+        {
+          g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as"));
+          g_variant_builder_add (&include_scheme_builder, "s", data->backend_list->data);
+
+          tmp = data->backend_list;
+          data->backend_list = g_list_remove_link (data->backend_list, tmp);
+          g_list_free_full (tmp, g_free);
+
+          g_dbus_connection_call (G_DBUS_CONNECTION (g_object_ref (source_object)),
+                                  MECHANISM_BUS,
+                                  "/",
+                                  MECHANISM_BUS,
+                                  "DevicesGet",
+                                  g_variant_new ("(iiasas)",
+                                                 0,
+                                                 0,
+                                                 &include_scheme_builder,
+                                                 NULL),
+                                  G_VARIANT_TYPE ("(sa{ss})"),
+                                  G_DBUS_CALL_FLAGS_NONE,
+                                  DBUS_TIMEOUT,
+                                  data->cancellable,
+                                  get_device_attributes_async_dbus_cb,
+                                  user_data);
+          return;
+        }
+    }
+
+  g_object_unref (source_object);
+
+  if (data->backend_list)
+    {
+      g_list_free_full (data->backend_list, g_free);
+      data->backend_list = NULL;
+    }
+
+  data->callback (device_id,
+                  device_make_and_model,
+                  data->device_uri,
+                  data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->device_uri);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+static void
+get_device_attributes_async_scb (GHashTable *result,
+                                 gpointer    user_data)
+{
+  GDBusConnection *bus;
+  GVariantBuilder  include_scheme_builder;
+  IPPAttribute    *attr;
+  GDAData         *data = (GDAData *) user_data;
+  GError          *error = NULL;
+  GList           *tmp;
+  gint             i;
+  const gchar     *backends[] =
+    {"hpfax", "ncp", "beh", "bluetooth", "snmp",
+     "dnssd", "hp", "ipp", "lpd", "parallel",
+     "serial", "socket", "usb", NULL};
+
+  if (result)
+    {
+      attr = g_hash_table_lookup (result, "device-uri");
+      if (attr && attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING &&
+          attr->num_of_values > 0)
+      data->device_uri = g_strdup (attr->attribute_values[0].string_value);
+      g_hash_table_unref (result);
+    }
+
+  if (g_cancellable_is_cancelled (data->cancellable))
+    goto out;
+
+  if (!data->device_uri)
+    goto out;
+
+  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);
+      goto out;
+    }
+
+  for (i = 0; backends[i]; i++)
+    data->backend_list = g_list_prepend (data->backend_list, g_strdup (backends[i]));
+
+  g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as"));
+  g_variant_builder_add (&include_scheme_builder, "s", data->backend_list->data);
+
+  tmp = data->backend_list;
+  data->backend_list = g_list_remove_link (data->backend_list, tmp);
+  g_list_free_full (tmp, g_free);
+
+  g_dbus_connection_call (g_object_ref (bus),
+                          MECHANISM_BUS,
+                          "/",
+                          MECHANISM_BUS,
+                          "DevicesGet",
+                          g_variant_new ("(iiasas)",
+                                         0,
+                                         0,
+                                         &include_scheme_builder,
+                                         NULL),
+                          G_VARIANT_TYPE ("(sa{ss})"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          DBUS_TIMEOUT,
+                          data->cancellable,
+                          get_device_attributes_async_dbus_cb,
+                          data);
+
+  return;
+
+out:
+  data->callback (NULL, NULL, NULL, data->user_data);
+
+  if (data->cancellable)
+    g_object_unref (data->cancellable);
+  g_free (data->device_uri);
+  g_free (data->printer_name);
+  g_free (data);
+}
+
+/*
+ * Get device-id, device-make-and-model and device-uri for given printer.
+ */
+static void
+get_device_attributes_async (const gchar  *printer_name,
+                             GCancellable *cancellable,
+                             GDACallback   callback,
+                             gpointer      user_data)
+{
+  GDAData  *data;
+  gchar   **attributes;
+
+  if (!printer_name)
+   {
+     callback (NULL, NULL, NULL, user_data);
+
+     return;
+   }
+
+  data = g_new0 (GDAData, 1);
+  data->printer_name = g_strdup (printer_name);
+  if (cancellable)
+    data->cancellable = g_object_ref (cancellable);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  attributes = g_new0 (gchar *, 2);
+  attributes[0] = g_strdup ("device-uri");
+
+  get_ipp_attributes_async (printer_name,
+                            attributes,
+                            get_device_attributes_async_scb,
+                            data);
+
+  g_strfreev (attributes);
+}
+
+/*
+ * Return "count" best matching driver names for given printer.
+ */
+void
+get_ppd_names_async (gchar        *printer_name,
+                     gint          count,
+                     GCancellable *cancellable,
+                     GPNCallback   callback,
+                     gpointer      user_data)
+{
+  GPNData *data;
+
+  if (!printer_name)
+    {
+      callback (NULL, NULL, TRUE, user_data);
+      return;
+    }
+
+  data = g_new0 (GPNData, 1);
+  data->printer_name = g_strdup (printer_name);
+  data->count = count;
+  if (cancellable)
+    data->cancellable = g_object_ref (cancellable);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  /*
+   * We have to find out device-id for this printer at first.
+   */
+  get_device_attributes_async (printer_name,
+                               cancellable,
+                               get_device_attributes_cb,
+                               data);
+}
+
+typedef struct
+{
+  PPDList      *result;
+  GAPCallback   callback;
+  gpointer      user_data;
+  GMainContext *context;
+} GAPData;
+
+static gboolean
+get_all_ppds_idle_cb (gpointer user_data)
+{
+  GAPData *data = (GAPData *) user_data;
+
+  data->callback (data->result, data->user_data);
+
+  return FALSE;
+}
+
+static void
+get_all_ppds_data_free (gpointer user_data)
+{
+  GAPData *data = (GAPData *) user_data;
+
+  if (data->context)
+    g_main_context_unref (data->context);
+  g_free (data);
+}
+
+static void
+get_all_ppds_cb (gpointer user_data)
+{
+  GAPData *data = (GAPData *) user_data;
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+                         get_all_ppds_idle_cb,
+                         data,
+                         get_all_ppds_data_free);
+  g_source_attach (idle_source, data->context);
+  g_source_unref (idle_source);
+}
+
+static const struct {
+  const char *normalized_name;
+  const char *display_name;
+} manufacturers_names[] = {
+  { "apollo", "Apollo" },
+  { "brother", "Brother" },
+  { "canon", "Canon" },
+  { "dell", "Dell" },
+  { "epson", "Epson" },
+  { "gelsprinter", "GelSprinter" },
+  { "generic", "Generic" },
+  { "gestetner", "Gestetner" },
+  { "hewlett packard", "Hewlett-Packard" },
+  { "hp", "Hewlett-Packard" },
+  { "ibm", "IBM" },
+  { "imagistics", "Imagistics" },
+  { "infoprint", "InfoPrint" },
+  { "infotec", "Infotec" },
+  { "konica minolta", "Minolta" },
+  { "kyocera", "Kyocera" },
+  { "kyocera mita", "Kyocera" },
+  { "lanier", "Lanier" },
+  { "lexmark international", "Lexmark" },
+  { "lexmark", "Lexmark" },
+  { "minolta", "Minolta" },
+  { "minolta qms", "Minolta" },
+  { "nec", "NEC" },
+  { "nrg", "NRG" },
+  { "oce", "Oce" },
+  { "oki", "Oki" },
+  { "oki data corp", "Oki" },
+  { "panasonic", "Panasonic" },
+  { "ricoh", "Ricoh" },
+  { "samsung", "Samsung" },
+  { "savin", "Savin" },
+  { "sharp", "Sharp" },
+  { "sony", "Sony" },
+  { "tektronix", "Tektronix" },
+  { "toshiba tec corp.", "Toshiba" },
+  { "xerox", "Xerox" },
+};
+
+static gpointer
+get_all_ppds_func (gpointer user_data)
+{
+  ipp_attribute_t *attr;
+  GHashTable      *ppds_hash = NULL;
+  GHashTable      *manufacturers_hash = NULL;
+  GAPData         *data = (GAPData *) user_data;
+  PPDName         *item;
+  ipp_t           *request;
+  ipp_t           *response;
+  GList           *list;
+  gchar           *ppd_make_and_model;
+  gchar           *ppd_device_id;
+  gchar           *ppd_name;
+  gchar           *ppd_product;
+  gchar           *mfg;
+  gchar           *mfg_normalized;
+  gchar           *mdl;
+  gchar           *manufacturer_display_name;
+  gint             i, j;
+
+  request = ippNewRequest (CUPS_GET_PPDS);
+  response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+
+  if (response &&
+      response->request.status.status_code <= IPP_OK_CONFLICT)
+    {
+      /*
+       * This hash contains names of manufacturers as keys and
+       * values are GLists of PPD names.
+       */
+      ppds_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+      /*
+       * This hash contains all possible names of manufacturers as keys
+       * and values are just first occurences of their equivalents.
+       * This is for mapping of e.g. "Hewlett Packard" and "HP" to the same name
+       * (the one which comes first).
+       */
+      manufacturers_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+      for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++)
+        {
+          g_hash_table_insert (manufacturers_hash,
+                               g_strdup (manufacturers_names[i].normalized_name),
+                               g_strdup (manufacturers_names[i].display_name));
+        }
+
+      for (attr = response->attrs; attr != NULL; attr = attr->next)
+        {
+          while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER)
+            attr = attr->next;
+
+          if (attr == NULL)
+            break;
+
+          ppd_device_id = NULL;
+          ppd_make_and_model = NULL;
+          ppd_name = NULL;
+          ppd_product = NULL;
+
+          while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER)
+            {
+              if (g_strcmp0 (attr->name, "ppd-device-id") == 0 &&
+                  attr->value_tag == IPP_TAG_TEXT)
+                ppd_device_id = attr->values[0].string.text;
+              else if (g_strcmp0 (attr->name, "ppd-make-and-model") == 0 &&
+                       attr->value_tag == IPP_TAG_TEXT)
+                ppd_make_and_model = attr->values[0].string.text;
+              else if (g_strcmp0 (attr->name, "ppd-name") == 0 &&
+                       attr->value_tag == IPP_TAG_NAME)
+                ppd_name = attr->values[0].string.text;
+              else if (g_strcmp0 (attr->name, "ppd-product") == 0 &&
+                       attr->value_tag == IPP_TAG_TEXT)
+                ppd_product = attr->values[0].string.text;
+
+              attr = attr->next;
+            }
+
+          if (ppd_make_and_model && ppd_name && ppd_product && ppd_device_id)
+            {
+              mfg = get_tag_value (ppd_device_id, "mfg");
+              if (!mfg)
+                mfg = get_tag_value (ppd_device_id, "manufacturer");
+              mfg_normalized = normalize (mfg);
+
+              if (mfg_normalized)
+                {
+                  manufacturer_display_name = g_hash_table_lookup (manufacturers_hash, mfg_normalized);
+                  if (!manufacturer_display_name)
+                    {
+                      g_hash_table_insert (manufacturers_hash, mfg_normalized, mfg);
+                    }
+                  else
+                    {
+                      g_free (mfg_normalized);
+                      mfg_normalized = normalize (manufacturer_display_name);
+                    }
+
+                  item = g_new0 (PPDName, 1);
+                  item->ppd_name = g_strdup (ppd_name);
+
+                  mdl = get_tag_value (ppd_device_id, "mdl");
+                  if (!mdl)
+                    mdl = get_tag_value (ppd_device_id, "model");
+
+                  if (!item->ppd_display_name &&
+                      ppd_make_and_model &&
+                      ppd_make_and_model[0] != '\0')
+                    {
+                      item->ppd_display_name = g_strdup (ppd_make_and_model);
+                    }
+
+                  if (!item->ppd_display_name &&
+                      ppd_product &&
+                      ppd_product[0] != '\0')
+                    {
+                      item->ppd_display_name = g_strdup (ppd_product);
+                    }
+
+                  if (!item->ppd_display_name &&
+                      mdl && mdl[0] != '\0')
+                    {
+                      item->ppd_display_name = mdl;
+                    }
+                  else
+                    {
+                      g_free (mdl);
+                    }
+
+                  item->ppd_match_level = -1;
+
+                  list = g_hash_table_lookup (ppds_hash, mfg_normalized);
+                  if (list)
+                    {
+                      list = g_list_append (list, item);
+                    }
+                  else
+                    {
+                      list = g_list_append (list, item);
+                      g_hash_table_insert (ppds_hash, mfg_normalized, list);
+                    }
+                }
+            }
+
+          if (attr == NULL)
+            break;
+        }
+    }
+
+  if (response)
+    ippDelete(response);
+
+  if (ppds_hash &&
+      manufacturers_hash)
+    {
+      GHashTableIter  iter;
+      gpointer        key;
+      gpointer        value;
+      GList          *ppd_item;
+      GList          *sort_list = NULL;
+      GList          *list_iter;
+      gchar          *name;
+
+      data->result = g_new0 (PPDList, 1);
+      data->result->num_of_manufacturers = g_hash_table_size (ppds_hash);
+      data->result->manufacturers = g_new0 (PPDManufacturerItem *, data->result->num_of_manufacturers);
+
+      g_hash_table_iter_init (&iter, ppds_hash);
+      while (g_hash_table_iter_next (&iter, &key, &value))
+        {
+          sort_list = g_list_append (sort_list, g_strdup (key));
+        }
+
+      /* Sort list of manufacturers */
+      sort_list = g_list_sort (sort_list, (GCompareFunc) g_strcmp0);
+
+      /*
+       * Fill resulting list of lists (list of manufacturers where
+       * each item contains list of PPD names)
+       */
+      i = 0;
+      for (list_iter = sort_list; list_iter; list_iter = list_iter->next)
+        {
+          name = (gchar *) list_iter->data;
+          value = g_hash_table_lookup (ppds_hash, name);
+
+          data->result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1);
+          data->result->manufacturers[i]->manufacturer_name = g_strdup (name);
+          data->result->manufacturers[i]->manufacturer_display_name = g_strdup (g_hash_table_lookup (manufacturers_hash, name));
+          data->result->manufacturers[i]->num_of_ppds = g_list_length ((GList *) value);
+          data->result->manufacturers[i]->ppds = g_new0 (PPDName *, data->result->manufacturers[i]->num_of_ppds);
+
+          for (ppd_item = (GList *) value, j = 0; ppd_item; ppd_item = ppd_item->next, j++)
+            {
+              data->result->manufacturers[i]->ppds[j] = ppd_item->data;
+            }
+
+          g_list_free ((GList *) value);
+
+          i++;
+        }
+
+      g_list_free_full (sort_list, g_free);
+      g_hash_table_destroy (ppds_hash);
+      g_hash_table_destroy (manufacturers_hash);
+    }
+
+  get_all_ppds_cb (data);
+
+  return NULL;
+}
+
+/*
+ * Get names of all installed PPDs sorted by manufacturers names.
+ */
+void
+get_all_ppds_async (GAPCallback callback,
+                    gpointer    user_data)
+{
+  GAPData *data;
+  GThread *thread;
+  GError  *error = NULL;
+
+  data = g_new0 (GAPData, 1);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->context = g_main_context_ref_thread_default ();
+
+  thread = g_thread_try_new ("get-all-ppds",
+                             get_all_ppds_func,
+                             data,
+                             &error);
+
+  if (!thread)
+    {
+      g_warning ("%s", error->message);
+      callback (NULL, user_data);
+
+      g_error_free (error);
+      get_all_ppds_data_free (data);
+    }
+  else
+    {
+      g_thread_unref (thread);
+    }
+}
+
+PPDList *
+ppd_list_copy (PPDList *list)
+{
+  PPDList *result = NULL;
+  gint     i, j;
+
+  if (list)
+    {
+      result = g_new0 (PPDList, 1);
+      result->num_of_manufacturers = list->num_of_manufacturers;
+      result->manufacturers = g_new0 (PPDManufacturerItem *, list->num_of_manufacturers);
+
+      for (i = 0; i < result->num_of_manufacturers; i++)
+        {
+          result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1);
+          result->manufacturers[i]->num_of_ppds = list->manufacturers[i]->num_of_ppds;
+          result->manufacturers[i]->ppds = g_new0 (PPDName *, result->manufacturers[i]->num_of_ppds);
+
+          result->manufacturers[i]->manufacturer_display_name =
+            g_strdup (list->manufacturers[i]->manufacturer_display_name);
+
+          result->manufacturers[i]->manufacturer_name =
+            g_strdup (list->manufacturers[i]->manufacturer_name);
+
+          for (j = 0; j < result->manufacturers[i]->num_of_ppds; j++)
+            {
+              result->manufacturers[i]->ppds[j] = g_new0 (PPDName, 1);
+
+              result->manufacturers[i]->ppds[j]->ppd_display_name =
+                g_strdup (list->manufacturers[i]->ppds[j]->ppd_display_name);
+
+              result->manufacturers[i]->ppds[j]->ppd_name =
+                g_strdup (list->manufacturers[i]->ppds[j]->ppd_name);
+
+              result->manufacturers[i]->ppds[j]->ppd_match_level =
+                list->manufacturers[i]->ppds[j]->ppd_match_level;
+            }
+        }
+    }
+
+  return result;
+}
+
+void
+ppd_list_free (PPDList *list)
+{
+  gint i, j;
+
+  if (list)
+    {
+      for (i = 0; i < list->num_of_manufacturers; i++)
+        {
+          for (j = 0; j < list->manufacturers[i]->num_of_ppds; j++)
+            {
+              g_free (list->manufacturers[i]->ppds[j]->ppd_name);
+              g_free (list->manufacturers[i]->ppds[j]->ppd_display_name);
+              g_free (list->manufacturers[i]->ppds[j]);
+            }
+
+          g_free (list->manufacturers[i]->manufacturer_name);
+          g_free (list->manufacturers[i]->manufacturer_display_name);
+          g_free (list->manufacturers[i]->ppds);
+          g_free (list->manufacturers[i]);
+        }
+
+      g_free (list->manufacturers);
+      g_free (list);
+    }
+}
+
+gchar *
+get_standard_manufacturers_name (gchar *name)
+{
+  gchar *normalized_name;
+  gchar *result = NULL;
+  gint   i;
+
+  if (name)
+    {
+      normalized_name = normalize (name);
+
+      for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++)
+        {
+          if (g_strcmp0 (manufacturers_names[i].normalized_name, normalized_name) == 0)
+            {
+              result = g_strdup (manufacturers_names[i].display_name);
+              break;
+            }
+        }
+
+      g_free (normalized_name);
+    }
+
+  return result;
+}
diff --git a/panels/printers/pp-utils.h b/panels/printers/pp-utils.h
index f5ea27f..8eeaced 100644
--- a/panels/printers/pp-utils.h
+++ b/panels/printers/pp-utils.h
@@ -40,9 +40,24 @@ enum
 typedef struct
 {
   gchar *ppd_name;
+  gchar *ppd_display_name;
   gint   ppd_match_level;
 } PPDName;
 
+typedef struct
+{
+  gchar    *manufacturer_name;
+  gchar    *manufacturer_display_name;
+  PPDName **ppds;
+  gsize     num_of_ppds;
+} PPDManufacturerItem;
+
+typedef struct
+{
+  PPDManufacturerItem **manufacturers;
+  gsize                 num_of_manufacturers;
+} PPDList;
+
 gchar      *get_tag_value (const gchar *tag_string,
                            const gchar *tag_name);
 
@@ -116,6 +131,81 @@ gchar      *printer_get_hostname (cups_ptype_t  printer_type,
 
 void        printer_set_default_media_size (const gchar *printer_name);
 
+typedef void (*PSPCallback) (gchar    *printer_name,
+                             gboolean  success,
+                             gpointer  user_data);
+
+void        printer_set_ppd_async (const gchar  *printer_name,
+                                   const gchar  *ppd_name,
+                                   GCancellable *cancellable,
+                                   PSPCallback   callback,
+                                   gpointer      user_data);
+
+void        printer_set_ppd_file_async (const gchar *printer_name,
+                                        const gchar *ppd_filename,
+                                        GCancellable *cancellable,
+                                        PSPCallback   callback,
+                                        gpointer      user_data);
+
+typedef void (*GPNCallback) (PPDName     **names,
+                             const gchar  *printer_name,
+                             gboolean      cancelled,
+                             gpointer      user_data);
+
+void        get_ppd_names_async (gchar        *printer_name,
+                                 gint          count,
+                                 GCancellable *cancellable,
+                                 GPNCallback   callback,
+                                 gpointer      user_data);
+
+typedef void (*GAPCallback) (PPDList  *ppds,
+                             gpointer  user_data);
+
+void        get_all_ppds_async (GAPCallback callback,
+                                gpointer    user_data);
+
+PPDList    *ppd_list_copy (PPDList *list);
+void        ppd_list_free (PPDList *list);
+
+enum
+{
+  IPP_ATTRIBUTE_TYPE_INTEGER = 0,
+  IPP_ATTRIBUTE_TYPE_STRING,
+  IPP_ATTRIBUTE_TYPE_RANGE,
+  IPP_ATTRIBUTE_TYPE_BOOLEAN
+};
+
+typedef struct
+{
+  gboolean  boolean_value;
+  gchar    *string_value;
+  gint      integer_value;
+  gint      lower_range;
+  gint      upper_range;
+} IPPAttributeValue;
+
+typedef struct
+{
+  gchar             *attribute_name;
+  IPPAttributeValue *attribute_values;
+  gint               num_of_values;
+  gint               attribute_type;
+} IPPAttribute;
+
+typedef void (*GIACallback) (GHashTable *table,
+                             gpointer    user_data);
+
+void        get_ipp_attributes_async (const gchar  *printer_name,
+                                      gchar       **attributes_names,
+                                      GIACallback   callback,
+                                      gpointer      user_data);
+
+IPPAttribute *ipp_attribute_copy (IPPAttribute *attr);
+
+void        ipp_attribute_free (IPPAttribute *attr);
+
+gchar      *get_standard_manufacturers_name (gchar *name);
+
 G_END_DECLS
 
 #endif /* __PP_UTILS_H */
diff --git a/panels/printers/ppd-selection-dialog.ui b/panels/printers/ppd-selection-dialog.ui
new file mode 100644
index 0000000..e0def83
--- /dev/null
+++ b/panels/printers/ppd-selection-dialog.ui
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkDialog" id="ppd-selection-dialog">
+    <property name="width_request">500</property>
+    <property name="height_request">350</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 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="GtkButton" id="ppd-selection-cancel-button">
+                <property name="label" translatable="yes">Cancel</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">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ppd-selection-select-button">
+                <property name="label" translatable="yes">Select</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</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">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box2">
+                <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="ppd-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 drivers database...</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">2</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <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="GtkBox" id="box3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="scrolledwindow1">
+                        <property name="width_request">140</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="ppd-selection-manufacturers-treeview">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child internal-child="selection">
+                              <object class="GtkTreeSelection" id="treeview-selection"/>
+                            </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="GtkScrolledWindow" id="scrolledwindow2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkTreeView" id="ppd-selection-models-treeview">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child internal-child="selection">
+                              <object class="GtkTreeSelection" id="treeview-selection1"/>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <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">Select Printer Driver</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</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">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">ppd-selection-cancel-button</action-widget>
+      <action-widget response="-5">ppd-selection-select-button</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkSizeGroup" id="sizegroup1"/>
+</interface>
diff --git a/panels/printers/printers.ui b/panels/printers/printers.ui
index ff9157c..75b1483 100644
--- a/panels/printers/printers.ui
+++ b/panels/printers/printers.ui
@@ -315,17 +315,84 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="CcEditableEntry" id="printer-model-label">
+                      <object class="GtkNotebook" id="printer-model-notebook">
                         <property name="visible">True</property>
-                        <property name="xalign">0</property>
-                        <property name="text">---</property>
+                        <property name="can_focus">True</property>
+                        <property name="show_tabs">False</property>
+                        <child>
+                          <object class="GtkButton" id="printer-model-button">
+                            <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>
+                            <property name="relief">none</property>
+                            <property name="xalign">0</property>
+                          </object>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label7">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label" translatable="yes">page 1</property>
+                          </object>
+                          <packing>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="printer-model-label">
+                            <property name="visible">True</property>
+                            <property name="selectable">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label13">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label" translatable="yes">page 2</property>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label17">
+                            <property name="visible">True</property>
+                            <property name="sensitive">False</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">10</property>
+                            <property name="label" translatable="yes">Setting new driver...</property>
+                          </object>
+                          <packing>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label16">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label" translatable="yes">page 3</property>
+                          </object>
+                          <packing>
+                            <property name="position">2</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
                         <property name="right_attach">3</property>
                         <property name="top_attach">3</property>
                         <property name="bottom_attach">4</property>
-                        <property name="y_options">GTK_FILL</property>
                       </packing>
                     </child>
                     <child>



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