/*
 * Copyright (C) 2010 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 2 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.
 *
 */

#include <config.h>

#include "cc-printers-panel.h"
#include "cc-printers-resources.h"

#include <string.h>
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#include <polkit/polkit.h>
#include <gdesktop-enums.h>

#include <cups/cups.h>

#include <math.h>

#include "cc-editable-entry.h"
#include "pp-new-printer-dialog.h"
#include "pp-ppd-selection-dialog.h"
#include "pp-options-dialog.h"
#include "pp-jobs-dialog.h"
#include "pp-utils.h"
#include "pp-maintenance-command.h"

CC_PANEL_REGISTER (CcPrintersPanel, cc_printers_panel)

#define PRINTERS_PANEL_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_PRINTERS_PANEL, CcPrintersPanelPrivate))

#define SUPPLY_BAR_HEIGHT 20

#define EMPTY_TEXT "\xe2\x80\x94"

#define RENEW_INTERVAL        500
#define SUBSCRIPTION_DURATION 600

#define CUPS_DBUS_NAME      "org.cups.cupsd.Notifier"
#define CUPS_DBUS_PATH      "/org/cups/cupsd/Notifier"
#define CUPS_DBUS_INTERFACE "org.cups.cupsd.Notifier"

#define CUPS_STATUS_CHECK_INTERVAL 5

#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
#define HAVE_CUPS_1_6 1
#endif

#ifndef HAVE_CUPS_1_6
#define ippGetState(ipp) ipp->state
#define ippGetStatusCode(ipp) ipp->request.status.status_code
#define ippGetString(attr, element, language) attr->values[element].string.text
#endif

struct _CcPrintersPanelPrivate
{
  GtkBuilder *builder;

  cups_dest_t *dests;
  gchar **dest_model_names;
  gchar **ppd_file_names;
  int num_dests;
  int current_dest;

  int num_jobs;

  GdkRGBA background_color;

  GPermission *permission;

  GSettings *lockdown_settings;

  PpNewPrinterDialog   *pp_new_printer_dialog;
  PpPPDSelectionDialog *pp_ppd_selection_dialog;
  PpOptionsDialog      *pp_options_dialog;
  PpJobsDialog         *pp_jobs_dialog;

  GDBusProxy      *cups_proxy;
  GDBusConnection *cups_bus_connection;
  gint             subscription_id;
  guint            subscription_renewal_id;
  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;
  GCancellable *get_all_ppds_cancellable;

  gchar    *new_printer_name;
  gchar    *new_printer_location;
  gchar    *new_printer_make_and_model;
  gboolean  new_printer_on_network;
  gboolean  select_new_printer;

  gpointer dummy;
};

typedef struct
{
  gchar        *printer_name;
  GCancellable *cancellable;
} SetPPDItem;

static void update_jobs_count (CcPrintersPanel *self);
static void actualize_printers_list (CcPrintersPanel *self);
static void update_sensitivity (gpointer user_data);
static void printer_disable_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data);
static void printer_set_default_cb (GtkToggleButton *button, gpointer user_data);
static void detach_from_cups_notifier (gpointer data);
static void free_dests (CcPrintersPanel *self);

static void
cc_printers_panel_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
cc_printers_panel_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
cc_printers_panel_dispose (GObject *object)
{
  CcPrintersPanelPrivate *priv = CC_PRINTERS_PANEL (object)->priv;

  if (priv->pp_new_printer_dialog)
    g_clear_object (&priv->pp_new_printer_dialog);

  free_dests (CC_PRINTERS_PANEL (object));

  g_clear_pointer (&priv->new_printer_name, g_free);
  g_clear_pointer (&priv->new_printer_location, g_free);
  g_clear_pointer (&priv->new_printer_make_and_model, g_free);

  if (priv->builder)
    {
      g_object_unref (priv->builder);
      priv->builder = NULL;
    }

  if (priv->lockdown_settings)
    {
      g_object_unref (priv->lockdown_settings);
      priv->lockdown_settings = NULL;
    }

  if (priv->permission)
    {
      g_object_unref (priv->permission);
      priv->permission = NULL;
    }

  detach_from_cups_notifier (CC_PRINTERS_PANEL (object));

  if (priv->cups_status_check_id > 0)
    {
      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;
    }

  if (priv->get_all_ppds_cancellable)
    {
      g_cancellable_cancel (priv->get_all_ppds_cancellable);
      g_object_unref (priv->get_all_ppds_cancellable);
      priv->get_all_ppds_cancellable = NULL;
    }

  if (priv->driver_change_list)
    {
      GList *iter;

      for (iter = priv->driver_change_list; iter; iter = iter->next)
        {
          SetPPDItem *item = (SetPPDItem *) iter->data;

          g_cancellable_cancel (item->cancellable);
          g_object_unref (item->cancellable);
          g_free (item->printer_name);
          g_free (item);
        }

      g_list_free (priv->driver_change_list);
      priv->driver_change_list = NULL;
    }

  G_OBJECT_CLASS (cc_printers_panel_parent_class)->dispose (object);
}

static void
cc_printers_panel_finalize (GObject *object)
{
  G_OBJECT_CLASS (cc_printers_panel_parent_class)->finalize (object);
}

static GPermission *
cc_printers_panel_get_permission (CcPanel *panel)
{
  CcPrintersPanelPrivate *priv = CC_PRINTERS_PANEL (panel)->priv;

  return priv->permission;
}

static const char *
cc_printers_panel_get_help_uri (CcPanel *panel)
{
  return "help:gnome-help/printing";
}

static void
cc_printers_panel_class_init (CcPrintersPanelClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  CcPanelClass *panel_class = CC_PANEL_CLASS (klass);

  g_type_class_add_private (klass, sizeof (CcPrintersPanelPrivate));

  object_class->get_property = cc_printers_panel_get_property;
  object_class->set_property = cc_printers_panel_set_property;
  object_class->dispose = cc_printers_panel_dispose;
  object_class->finalize = cc_printers_panel_finalize;

  panel_class->get_permission = cc_printers_panel_get_permission;
  panel_class->get_help_uri = cc_printers_panel_get_help_uri;
}

static void
on_cups_notification (GDBusConnection *connection,
                      const char      *sender_name,
                      const char      *object_path,
                      const char      *interface_name,
                      const char      *signal_name,
                      GVariant        *parameters,
                      gpointer         user_data)
{
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  CcPrintersPanelPrivate *priv;
  gboolean                printer_is_accepting_jobs;
  gchar                  *printer_name = NULL;
  gchar                  *text = NULL;
  gchar                  *printer_uri = NULL;
  gchar                  *printer_state_reasons = NULL;
  gchar                  *job_state_reasons = NULL;
  gchar                  *job_name = NULL;
  guint                   job_id;
  gint                    printer_state;
  gint                    job_state;
  gint                    job_impressions_completed;
  static const char * const requested_attrs[] = {
    "job-printer-uri",
    "job-originating-user-name"};

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (g_strcmp0 (signal_name, "PrinterAdded") != 0 &&
      g_strcmp0 (signal_name, "PrinterDeleted") != 0 &&
      g_strcmp0 (signal_name, "PrinterStateChanged") != 0 &&
      g_strcmp0 (signal_name, "PrinterStopped") != 0 &&
      g_strcmp0 (signal_name, "JobCreated") != 0 &&
      g_strcmp0 (signal_name, "JobCompleted") != 0)
    return;

  if (g_variant_n_children (parameters) == 1)
    g_variant_get (parameters, "(&s)", &text);
  else if (g_variant_n_children (parameters) == 6)
    {
      g_variant_get (parameters, "(&s&s&su&sb)",
                     &text,
                     &printer_uri,
                     &printer_name,
                     &printer_state,
                     &printer_state_reasons,
                     &printer_is_accepting_jobs);
    }
  else if (g_variant_n_children (parameters) == 11)
    {
      g_variant_get (parameters, "(&s&s&su&sbuu&s&su)",
                     &text,
                     &printer_uri,
                     &printer_name,
                     &printer_state,
                     &printer_state_reasons,
                     &printer_is_accepting_jobs,
                     &job_id,
                     &job_state,
                     &job_state_reasons,
                     &job_name,
                     &job_impressions_completed);
    }

  if (g_strcmp0 (signal_name, "PrinterAdded") == 0 ||
      g_strcmp0 (signal_name, "PrinterDeleted") == 0 ||
      g_strcmp0 (signal_name, "PrinterStateChanged") == 0 ||
      g_strcmp0 (signal_name, "PrinterStopped") == 0)
    actualize_printers_list (self);
  else if (g_strcmp0 (signal_name, "JobCreated") == 0 ||
           g_strcmp0 (signal_name, "JobCompleted") == 0)
    {
      http_t *http;
      gchar  *job_uri;
      ipp_t  *request, *response;

      job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", job_id);
      if ((http = httpConnectEncrypt (cupsServer (), ippPort (),
                                     cupsEncryption ())) != NULL)
        {
          request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES);
          ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
                       "job-uri", NULL, job_uri);
          ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
                        "requesting-user-name", NULL, cupsUser ());
          ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                         "requested-attributes", G_N_ELEMENTS (requested_attrs), NULL, requested_attrs);
          response = cupsDoRequest (http, request, "/");

          if (response)
            {
              if (ippGetStatusCode (response) <= IPP_OK_CONFLICT)
                {
                  ipp_attribute_t *attr_username = NULL;
                  ipp_attribute_t *attr_printer_uri = NULL;

                  attr_username = ippFindAttribute(response, "job-originating-user-name", IPP_TAG_NAME);
                  attr_printer_uri = ippFindAttribute(response, "job-printer-uri", IPP_TAG_URI);
                  if (attr_username && attr_printer_uri &&
                      g_strcmp0 (ippGetString (attr_username, 0, NULL), cupsUser ()) == 0 &&
                      g_strrstr (ippGetString (attr_printer_uri, 0, NULL), "/") != 0 &&
                      priv->current_dest >= 0 &&
                      priv->current_dest < priv->num_dests &&
                      priv->dests != NULL &&
                      g_strcmp0 (g_strrstr (ippGetString (attr_printer_uri, 0, NULL), "/") + 1,
                                 priv->dests[priv->current_dest].name) == 0)
                    update_jobs_count (self);
                }
              ippDelete(response);
            }
          httpClose (http);
        }
      g_free (job_uri);
    }
}

static gboolean
renew_subscription (gpointer data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) data;
  static const char * const events[] = {
          "printer-added",
          "printer-deleted",
          "printer-stopped",
          "printer-state-changed",
          "job-created",
          "job-completed"};

  priv = PRINTERS_PANEL_PRIVATE (self);

  priv->subscription_id = renew_cups_subscription (priv->subscription_id,
                                                   events,
                                                   G_N_ELEMENTS (events),
                                                   SUBSCRIPTION_DURATION);

  if (priv->subscription_id > 0)
    return TRUE;
  else
    return FALSE;
}

static void
attach_to_cups_notifier (gpointer data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) data;
  GError                 *error = NULL;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (renew_subscription (self))
    {
      priv->subscription_renewal_id =
        g_timeout_add_seconds (RENEW_INTERVAL, renew_subscription, self);

      priv->cups_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
                                                        0,
                                                        NULL,
                                                        CUPS_DBUS_NAME,
                                                        CUPS_DBUS_PATH,
                                                        CUPS_DBUS_INTERFACE,
                                                        NULL,
                                                        &error);

      if (!priv->cups_proxy)
        {
          g_warning ("%s", error->message);
          g_error_free (error);
          return;
        }

      priv->cups_bus_connection = g_dbus_proxy_get_connection (priv->cups_proxy);

      priv->dbus_subscription_id =
        g_dbus_connection_signal_subscribe (priv->cups_bus_connection,
                                            NULL,
                                            CUPS_DBUS_INTERFACE,
                                            NULL,
                                            CUPS_DBUS_PATH,
                                            NULL,
                                            0,
                                            on_cups_notification,
                                            self,
                                            NULL);
    }
}

static void
detach_from_cups_notifier (gpointer data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) data;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->dbus_subscription_id != 0) {
    g_dbus_connection_signal_unsubscribe (priv->cups_bus_connection,
                                          priv->dbus_subscription_id);
    priv->dbus_subscription_id = 0;
  }

  cancel_cups_subscription (priv->subscription_id);
  priv->subscription_id = 0;

  if (priv->subscription_renewal_id != 0) {
    g_source_remove (priv->subscription_renewal_id);
    priv->subscription_renewal_id = 0;
  }

  if (priv->cups_proxy != NULL) {
    g_object_unref (priv->cups_proxy);
    priv->cups_proxy = NULL;
  }
}

static void
free_dests (CcPrintersPanel *self)
{
  CcPrintersPanelPrivate *priv;
  gint                    i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->num_dests > 0)
    {
      for (i = 0; i < priv->num_dests; i++)
        {
          g_free (priv->dest_model_names[i]);
          if (priv->ppd_file_names[i]) {
            g_unlink (priv->ppd_file_names[i]);
            g_free (priv->ppd_file_names[i]);
          }
        }
      g_free (priv->dest_model_names);
      g_free (priv->ppd_file_names);
      cupsFreeDests (priv->num_dests, priv->dests);
    }
  priv->dests = NULL;
  priv->num_dests = 0;
  priv->current_dest = -1;
  priv->dest_model_names = NULL;
  priv->ppd_file_names = NULL;
}

enum
{
  NOTEBOOK_INFO_PAGE = 0,
  NOTEBOOK_NO_PRINTERS_PAGE,
  NOTEBOOK_NO_CUPS_PAGE,
  NOTEBOOK_N_PAGES
};

enum
{
  PRINTER_ID_COLUMN,
  PRINTER_NAME_COLUMN,
  PRINTER_PAUSED_COLUMN,
  PRINTER_DEFAULT_ICON_COLUMN,
  PRINTER_ICON_COLUMN,
  PRINTER_N_COLUMNS
};

static void
printer_selection_changed_cb (GtkTreeSelection *selection,
                              gpointer          user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  GtkTreeModel           *model;
  cups_ptype_t            type = 0;
  GtkTreeIter             iter;
  GtkWidget              *widget;
  GtkWidget              *model_button;
  GtkWidget              *model_label;
  gboolean                is_accepting_jobs = TRUE;
  GValue                  value = G_VALUE_INIT;
  gchar                  *printer_make_and_model = NULL;
  gchar                  *printer_model = NULL;
  gchar                  *reason = NULL;
  gchar                 **printer_reasons = NULL;
  gchar                  *marker_types = NULL;
  gchar                  *printer_name = NULL;
  gchar                  *printer_icon = NULL;
  gchar                  *printer_type = NULL;
  gchar                  *supply_type = NULL;
  gchar                  *printer_uri = NULL;
  gchar                  *location = NULL;
  gchar                  *status = NULL;
  gchar                  *device_uri = NULL;
  gchar                  *printer_hostname = NULL;
  int                     printer_state = 3;
  int                     id = -1;
  int                     i, j;
  static const char * const reasons[] =
    {
      "toner-low",
      "toner-empty",
      "developer-low",
      "developer-empty",
      "marker-supply-low",
      "marker-supply-empty",
      "cover-open",
      "door-open",
      "media-low",
      "media-empty",
      "offline",
      "paused",
      "marker-waste-almost-full",
      "marker-waste-full",
      "opc-near-eol",
      "opc-life-over"
    };
  static const char * statuses[] =
    {
      /* Translators: The printer is low on toner */
      N_("Low on toner"),
      /* Translators: The printer has no toner left */
      N_("Out of toner"),
      /* Translators: "Developer" is a chemical for photo development,
       * http://en.wikipedia.org/wiki/Photographic_developer */
      N_("Low on developer"),
      /* Translators: "Developer" is a chemical for photo development,
       * http://en.wikipedia.org/wiki/Photographic_developer */
      N_("Out of developer"),
      /* Translators: "marker" is one color bin of the printer */
      N_("Low on a marker supply"),
      /* Translators: "marker" is one color bin of the printer */
      N_("Out of a marker supply"),
      /* Translators: One or more covers on the printer are open */
      N_("Open cover"),
      /* Translators: One or more doors on the printer are open */
      N_("Open door"),
      /* Translators: At least one input tray is low on media */
      N_("Low on paper"),
      /* Translators: At least one input tray is empty */
      N_("Out of paper"),
      /* Translators: The printer is offline */
      NC_("printer state", "Offline"),
      /* Translators: Someone has stopped the Printer */
      NC_("printer state", "Stopped"),
      /* Translators: The printer marker supply waste receptacle is almost full */
      N_("Waste receptacle almost full"),
      /* Translators: The printer marker supply waste receptacle is full */
      N_("Waste receptacle full"),
      /* Translators: Optical photo conductors are used in laser printers */
      N_("The optical photo conductor is near end of life"),
      /* Translators: Optical photo conductors are used in laser printers */
      N_("The optical photo conductor is no longer functioning")
    };

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      gtk_tree_model_get (model, &iter,
			  PRINTER_ID_COLUMN, &id,
			  PRINTER_NAME_COLUMN, &printer_name,
			  PRINTER_ICON_COLUMN, &printer_icon,
			  -1);
    }
  else
    id = -1;

  priv->current_dest = id;

  update_jobs_count (self);

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "notebook");
      if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) >= NOTEBOOK_NO_PRINTERS_PAGE)
        gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), NOTEBOOK_INFO_PAGE);

      for (i = 0; i < priv->dests[id].num_options; i++)
        {
          if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-location") == 0)
            location = g_strdup (priv->dests[priv->current_dest].options[i].value);
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-state") == 0)
            printer_state = atoi (priv->dests[priv->current_dest].options[i].value);
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-state-reasons") == 0)
            reason = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "marker-types") == 0)
            marker_types = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-make-and-model") == 0)
            printer_make_and_model = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-uri-supported") == 0)
            printer_uri = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-type") == 0)
            printer_type = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "device-uri") == 0)
            device_uri = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-is-accepting-jobs") == 0)
            {
              if (g_strcmp0 (priv->dests[priv->current_dest].options[i].value, "true") == 0)
                is_accepting_jobs = TRUE;
              else
                is_accepting_jobs = FALSE;
            }
        }

      if (priv->ppd_file_names[priv->current_dest] == NULL)
        priv->ppd_file_names[priv->current_dest] =
          g_strdup (cupsGetPPD (priv->dests[priv->current_dest].name));

      if (priv->dest_model_names[priv->current_dest] == NULL)
        priv->dest_model_names[priv->current_dest] =
          get_ppd_attribute (priv->ppd_file_names[priv->current_dest],
                             "ModelName");

      printer_model = g_strdup (priv->dest_model_names[priv->current_dest]);

      if (printer_model == NULL && printer_make_and_model)
        {
          gchar *breakpoint = NULL, *tmp = NULL, *tmp2 = NULL;
          gchar  backup;
          size_t length = 0;
          gchar *forbiden[] = {
              "foomatic",
              ",",
              "hpijs",
              "hpcups",
              "(recommended)",
              "postscript (recommended)",
              NULL };

          tmp = g_ascii_strdown (printer_make_and_model, -1);

          for (i = 0; i < g_strv_length (forbiden); i++)
            {
              tmp2 = g_strrstr (tmp, forbiden[i]);
              if (breakpoint == NULL || 
                  (tmp2 != NULL && tmp2 < breakpoint))
                breakpoint = tmp2;
            }

          if (breakpoint)
            {
              backup = *breakpoint;
              *breakpoint = '\0';
              length = strlen (tmp);
              *breakpoint = backup;
              g_free (tmp);

              if (length > 0)
                printer_model = g_strndup (printer_make_and_model, length);
            }
          else
            printer_model = g_strdup (printer_make_and_model);
        }

      if (priv->new_printer_name &&
          g_strcmp0 (priv->new_printer_name, printer_name) == 0)
        {
          /* Translators: Printer's state (printer is being configured right now) */
          status = g_strdup ( C_("printer state", "Configuring"));
        }

      /* Find the first of the most severe reasons
       * and show it in the status field
       */
      if (!status &&
          reason &&
          !g_str_equal (reason, "none"))
        {
          int errors = 0, warnings = 0, reports = 0;
          int error_index = -1, warning_index = -1, report_index = -1;

          printer_reasons = g_strsplit (reason, ",", -1);
          for (i = 0; i < g_strv_length (printer_reasons); i++)
            {
              for (j = 0; j < G_N_ELEMENTS (reasons); j++)
                if (strncmp (printer_reasons[i],
                             reasons[j],
                             strlen (reasons[j])) == 0)
                    {
                      if (g_str_has_suffix (printer_reasons[i], "-report"))
                        {
                          if (reports == 0)
                            report_index = j;
                          reports++;
                        }
                      else if (g_str_has_suffix (printer_reasons[i], "-warning"))
                        {
                          if (warnings == 0)
                            warning_index = j;
                          warnings++;
                        }
                      else
                        {
                          if (errors == 0)
                            error_index = j;
                          errors++;
                        }
                    }
            }
          g_strfreev (printer_reasons);

          if (error_index >= 0)
            status = g_strdup (_(statuses[error_index]));
          else if (warning_index >= 0)
            status = g_strdup (_(statuses[warning_index]));
          else if (report_index >= 0)
            status = g_strdup (_(statuses[report_index]));
        }

      if (status == NULL)
        {
          switch (printer_state)
            {
              case 3:
                if (is_accepting_jobs)
                  {
                    /* Translators: Printer's state (can start new job without waiting) */
                    status = g_strdup ( C_("printer state", "Ready"));
                  }
                else
                  {
                    /* Translators: Printer's state (printer is ready but doesn't accept new jobs) */
                    status = g_strdup ( C_("printer state", "Does not accept jobs"));
                  }
                break;
              case 4:
                /* Translators: Printer's state (jobs are processing) */
                status = g_strdup ( C_("printer state", "Processing"));
                break;
              case 5:
                /* Translators: Printer's state (no jobs can be processed) */
                status = g_strdup ( C_("printer state", "Stopped"));
                break;
            }
        }

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-icon");
      g_value_init (&value, G_TYPE_INT);
      g_object_get_property ((GObject *) widget, "icon-size", &value);

      if (printer_icon)
        {
          gtk_image_set_from_icon_name ((GtkImage *) widget, printer_icon, g_value_get_int (&value));
          g_free (printer_icon);
        }
      else
        gtk_image_set_from_icon_name ((GtkImage *) widget, "printer", g_value_get_int (&value));

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-name-label");

      if (printer_name)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), printer_name);
          g_free (printer_name);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-status-label");

      if (status)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), status);
          g_free (status);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-location-label");

      if (location)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), location);
          g_free (location);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);


      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)
        {
          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
        {
          gtk_button_set_label (GTK_BUTTON (model_button), EMPTY_TEXT);
          gtk_label_set_text (GTK_LABEL (model_label), EMPTY_TEXT);
        }


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-ip-address-label");

      if (printer_type)
        type = atoi (printer_type);

      printer_hostname = printer_get_hostname (type, device_uri, printer_uri);

      if (printer_hostname)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), printer_hostname);
          g_free (printer_hostname);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-disable-switch");

      g_signal_handlers_block_by_func (G_OBJECT (widget), printer_disable_cb, self);
      gtk_switch_set_active (GTK_SWITCH (widget), printer_state != 5 && is_accepting_jobs);
      g_signal_handlers_unblock_by_func (G_OBJECT (widget), printer_disable_cb, self);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-default-check-button");

      g_signal_handlers_block_by_func (G_OBJECT (widget), printer_set_default_cb, self);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), priv->dests[id].is_default);
      g_signal_handlers_unblock_by_func (G_OBJECT (widget), printer_set_default_cb, self);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "supply-drawing-area");
      gtk_widget_set_size_request (widget, -1, SUPPLY_BAR_HEIGHT);
      gtk_widget_queue_draw (widget);


      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "supply-label");

      if (marker_types && g_strrstr (marker_types, "toner") != NULL)
        /* Translators: Toner supply */
        supply_type = g_strdup ( _("Toner Level"));
      else if (marker_types && g_strrstr (marker_types, "ink") != NULL)
        /* Translators: Ink supply */
        supply_type = g_strdup ( _("Ink Level"));
      else
        /* Translators: By supply we mean ink, toner, staples, water, ... */
        supply_type = g_strdup ( _("Supply Level"));

      if (supply_type)
        {
          gtk_label_set_text (GTK_LABEL (widget), supply_type);
          g_free (supply_type);
        }
      else
        gtk_label_set_text (GTK_LABEL (widget), EMPTY_TEXT);
    }
  else
    {
      if (id == -1)
        {
          if (priv->new_printer_name &&
              g_strcmp0 (priv->new_printer_name, printer_name) == 0)
            {
              /* Translators: Printer's state (printer is being installed right now) */
              status = g_strdup ( C_("printer state", "Installing"));
              location = g_strdup (priv->new_printer_location);
              printer_model = g_strdup (priv->new_printer_make_and_model);

              widget = (GtkWidget*)
                gtk_builder_get_object (priv->builder, "notebook");
              if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) >= NOTEBOOK_NO_PRINTERS_PAGE)
                gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), NOTEBOOK_INFO_PAGE);
            }
        }

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-icon");
      g_value_init (&value, G_TYPE_INT);
      g_object_get_property ((GObject *) widget, "icon-size", &value);

      if (printer_icon)
        {
          gtk_image_set_from_icon_name ((GtkImage *) widget, printer_icon, g_value_get_int (&value));
          g_free (printer_icon);
        }
      else
        gtk_image_set_from_icon_name ((GtkImage *) widget, "printer", g_value_get_int (&value));

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-name-label");
      if (printer_name)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), printer_name);
          g_free (printer_name);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-status-label");
      if (status)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), status);
          g_free (status);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-location-label");

      if (location)
        {
          cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), location);
          g_free (location);
        }
      else
        cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);


      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)
        {
          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
        {
          gtk_button_set_label (GTK_BUTTON (model_button), EMPTY_TEXT);
          gtk_label_set_text (GTK_LABEL (model_label), EMPTY_TEXT);
        }

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-ip-address-label");
      cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-jobs-label");
      cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-disable-switch");

      g_signal_handlers_block_by_func (G_OBJECT (widget), printer_disable_cb, self);
      gtk_switch_set_active (GTK_SWITCH (widget), FALSE);
      g_signal_handlers_unblock_by_func (G_OBJECT (widget), printer_disable_cb, self);

      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "printer-default-check-button");

      g_signal_handlers_block_by_func (G_OBJECT (widget), printer_set_default_cb, self);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
      g_signal_handlers_unblock_by_func (G_OBJECT (widget), printer_set_default_cb, self);
    }

  update_sensitivity (self);
}

static void
actualize_printers_list (CcPrintersPanel *self)
{
  CcPrintersPanelPrivate *priv;
  GtkTreeSelection       *selection;
  GtkListStore           *store;
  cups_ptype_t            printer_type = 0;
  GtkTreeModel           *model;
  GtkTreeIter             selected_iter;
  GtkTreeView            *treeview;
  GtkTreeIter             iter;
  cups_job_t             *jobs = NULL;
  GtkWidget              *widget;
  gboolean                paused = FALSE;
  gboolean                selected_iter_set = FALSE;
  gboolean                valid = FALSE;
  http_t                 *http;
  gchar                  *current_printer_name = NULL;
  gchar                  *printer_icon_name = NULL;
  gchar                  *default_icon_name = NULL;
  gchar                  *device_uri = NULL;
  gint                    new_printer_position = 0;
  int                     current_dest = -1;
  int                     i, j;
  int                     num_jobs = 0;

  priv = PRINTERS_PANEL_PRIVATE (self);

  treeview = (GtkTreeView*)
    gtk_builder_get_object (priv->builder, "printers-treeview");

  if ((selection = gtk_tree_view_get_selection (treeview)) != NULL &&
      gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      gtk_tree_model_get (model, &iter,
			  PRINTER_NAME_COLUMN, &current_printer_name,
			  -1);
    }

  if (priv->new_printer_name &&
      priv->select_new_printer)
    {
      g_free (current_printer_name);
      current_printer_name = g_strdup (priv->new_printer_name);
      priv->select_new_printer = FALSE;
    }

  free_dests (self);
  priv->num_dests = cupsGetDests (&priv->dests);
  priv->dest_model_names = g_new0 (gchar *, priv->num_dests);
  priv->ppd_file_names = g_new0 (gchar *, priv->num_dests);

  store = gtk_list_store_new (PRINTER_N_COLUMNS,
                              G_TYPE_INT,
                              G_TYPE_STRING,
                              G_TYPE_BOOLEAN,
                              G_TYPE_STRING,
                              G_TYPE_STRING);

  if (priv->num_dests == 0 && !priv->new_printer_name)
    {
      widget = (GtkWidget*)
        gtk_builder_get_object (priv->builder, "notebook");

      http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
      if (http)
        {
          httpClose (http);
          gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), NOTEBOOK_NO_PRINTERS_PAGE);
        }
      else
        gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), NOTEBOOK_NO_CUPS_PAGE);

      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                          PRINTER_ID_COLUMN, 0,
      /* Translators: There are no printers available (none is configured or CUPS is not running) */
                          PRINTER_NAME_COLUMN, _("No printers available"),
                          PRINTER_PAUSED_COLUMN, TRUE,
                          PRINTER_DEFAULT_ICON_COLUMN, NULL,
                          PRINTER_ICON_COLUMN, NULL,
                          -1);
      gtk_widget_set_sensitive (GTK_WIDGET (treeview), FALSE);
    }
  else
    gtk_widget_set_sensitive (GTK_WIDGET (treeview), TRUE);

  for (i = 0; i < priv->num_dests; i++)
    {
      gchar *instance;

      if (priv->new_printer_name && new_printer_position >= 0)
        {
          gint comparison_result = g_ascii_strcasecmp (priv->dests[i].name, priv->new_printer_name);

          if (comparison_result < 0)
            new_printer_position = i + 1;
          else if (comparison_result == 0)
            new_printer_position = -1;
        }

      gtk_list_store_append (store, &iter);

      if (priv->dests[i].instance)
        {
          instance = g_strdup_printf ("%s / %s", priv->dests[i].name, priv->dests[i].instance);
        }
      else
        {
          instance = g_strdup (priv->dests[i].name);
        }

      for (j = 0; j < priv->dests[i].num_options; j++)
        {
          if (g_strcmp0 (priv->dests[i].options[j].name, "printer-state") == 0)
            paused = (g_strcmp0 (priv->dests[i].options[j].value, "5") == 0);
          else if (g_strcmp0 (priv->dests[i].options[j].name, "device-uri") == 0)
            device_uri = priv->dests[i].options[j].value;
          else if (g_strcmp0 (priv->dests[i].options[j].name, "printer-type") == 0)
            printer_type = atoi (priv->dests[i].options[j].value);
        }

      if (priv->dests[i].is_default)
        default_icon_name = g_strdup ("object-select-symbolic");
      else
        default_icon_name = NULL;

      if (printer_is_local (printer_type, device_uri))
        printer_icon_name = g_strdup ("printer");
      else
        printer_icon_name = g_strdup ("printer-network");

      gtk_list_store_set (store, &iter,
                          PRINTER_ID_COLUMN, i,
                          PRINTER_NAME_COLUMN, instance,
                          PRINTER_PAUSED_COLUMN, paused,
                          PRINTER_DEFAULT_ICON_COLUMN, default_icon_name,
                          PRINTER_ICON_COLUMN, printer_icon_name,
                          -1);

      if (g_strcmp0 (current_printer_name, instance) == 0)
        {
          current_dest = i;
          selected_iter = iter;
          selected_iter_set = TRUE;
        }

      g_free (instance);
      g_free (printer_icon_name);
      g_free (default_icon_name);
    }

  if (priv->new_printer_name && new_printer_position >= 0)
    {
      gtk_list_store_insert (store, &iter, new_printer_position);
      gtk_list_store_set (store, &iter,
                          PRINTER_ID_COLUMN, -1,
                          PRINTER_NAME_COLUMN, priv->new_printer_name,
                          PRINTER_PAUSED_COLUMN, TRUE,
                          PRINTER_DEFAULT_ICON_COLUMN, NULL,
                          PRINTER_ICON_COLUMN, priv->new_printer_on_network ?
                            "printer-network" : "printer",
                          -1);

      if (g_strcmp0 (current_printer_name, priv->new_printer_name) == 0)
        {
          selected_iter = iter;
          selected_iter_set = TRUE;
        }
    }

  g_signal_handlers_block_by_func (
    G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview))),
    printer_selection_changed_cb,
    self);

  gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));

  g_signal_handlers_unblock_by_func (
    G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview))),
    printer_selection_changed_cb,
    self);

  if (selected_iter_set)
    {
      priv->current_dest = current_dest;
      gtk_tree_selection_select_iter (
        gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)),
        &selected_iter);
    }
  else
    {
      num_jobs = cupsGetJobs (&jobs, NULL, 1, CUPS_WHICHJOBS_ALL);

      /* Select last used printer */
      if (num_jobs > 0)
        {
          for (i = 0; i < priv->num_dests; i++)
            if (g_strcmp0 (priv->dests[i].name, jobs[num_jobs - 1].dest) == 0)
              {
                priv->current_dest = i;
                break;
              }
          cupsFreeJobs (num_jobs, jobs);
        }

      /* Select default printer */
      if (priv->current_dest < 0)
        {
          for (i = 0; i < priv->num_dests; i++)
            if (priv->dests[i].is_default)
              {
                priv->current_dest = i;
                break;
              }
        }

      if (priv->current_dest >= 0)
        {
          gint id;
          valid = gtk_tree_model_get_iter_first ((GtkTreeModel *) store,
                                                 &selected_iter);

          while (valid)
            {
              gtk_tree_model_get ((GtkTreeModel *) store, &selected_iter,
                                  PRINTER_ID_COLUMN, &id,
                                  -1);
              if (id == priv->current_dest)
                break;

              valid = gtk_tree_model_iter_next ((GtkTreeModel *) store,
                                                &selected_iter);
            }

          gtk_tree_selection_select_iter (
            gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)),
            &selected_iter);
        }
      else if (priv->num_dests > 0)
        {
          /* Select first printer */
          gtk_tree_model_get_iter_first ((GtkTreeModel *) store,
                                         &selected_iter);

          gtk_tree_selection_select_iter (
            gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)),
            &selected_iter);
        }
    }

  g_free (current_printer_name);
  g_object_unref (store);

  update_sensitivity (self);
}

static void
set_cell_sensitivity_func (GtkTreeViewColumn *tree_column,
                           GtkCellRenderer   *cell,
                           GtkTreeModel      *tree_model,
                           GtkTreeIter       *iter,
                           gpointer           func_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) func_data;
  gboolean                paused = FALSE;

  priv = PRINTERS_PANEL_PRIVATE (self);

  gtk_tree_model_get (tree_model, iter, PRINTER_PAUSED_COLUMN, &paused, -1);

  if (priv->num_dests == 0)
    g_object_set (G_OBJECT (cell),
                  "ellipsize", PANGO_ELLIPSIZE_NONE,
                  "width-chars", -1,
                  NULL);
  else
    g_object_set (G_OBJECT (cell),
                  "ellipsize", PANGO_ELLIPSIZE_END,
                  "width-chars", 18,
                  NULL);

  g_object_set (cell, "sensitive", !paused, NULL);
}

static void
set_pixbuf_cell_sensitivity_func (GtkTreeViewColumn *tree_column,
                                  GtkCellRenderer   *cell,
                                  GtkTreeModel      *tree_model,
                                  GtkTreeIter       *iter,
                                  gpointer           func_data)
{
  gboolean paused = FALSE;

  gtk_tree_model_get (tree_model, iter, PRINTER_PAUSED_COLUMN, &paused, -1);
  g_object_set (cell, "sensitive", !paused, NULL);
}

static void
populate_printers_list (CcPrintersPanel *self)
{
  CcPrintersPanelPrivate *priv;
  GtkTreeViewColumn      *column;
  GtkCellRenderer        *icon_renderer;
  GtkCellRenderer        *icon_renderer2;
  GtkCellRenderer        *renderer;
  GtkWidget              *treeview;

  priv = PRINTERS_PANEL_PRIVATE (self);

  treeview = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printers-treeview");

  g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)),
                    "changed", G_CALLBACK (printer_selection_changed_cb), self);

  actualize_printers_list (self);


  icon_renderer = gtk_cell_renderer_pixbuf_new ();
  g_object_set (icon_renderer, "stock-size", gtk_icon_size_from_name ("cc-sidebar-list"), NULL);
  gtk_cell_renderer_set_padding (icon_renderer, 4, 4);
  column = gtk_tree_view_column_new_with_attributes ("Icon", icon_renderer,
                                                     "icon-name", PRINTER_ICON_COLUMN, NULL);
  gtk_tree_view_column_set_cell_data_func (column, icon_renderer, set_pixbuf_cell_sensitivity_func,
                                           self, NULL);
  gtk_tree_view_column_set_expand (column, FALSE);
  gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);


  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 18, NULL);
  column = gtk_tree_view_column_new_with_attributes ("Printer", renderer,
                                                     "text", PRINTER_NAME_COLUMN, NULL);
  gtk_tree_view_column_set_cell_data_func (column, renderer, set_cell_sensitivity_func,
                                           self, NULL);
  gtk_tree_view_column_set_expand (column, FALSE);
  gtk_tree_view_column_set_min_width (column, 120);
  gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);


  icon_renderer2 = gtk_cell_renderer_pixbuf_new ();
  g_object_set (G_OBJECT (icon_renderer2), "follow-state", TRUE, NULL);
  column = gtk_tree_view_column_new_with_attributes ("Default", icon_renderer2,
                                                     "icon-name", PRINTER_DEFAULT_ICON_COLUMN, NULL);
  gtk_tree_view_column_set_expand (column, FALSE);
  gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
}

enum
{
  JOB_ID_COLUMN,
  JOB_TITLE_COLUMN,
  JOB_STATE_COLUMN,
  JOB_CREATION_TIME_COLUMN,
  JOB_N_COLUMNS
};

static void
update_jobs_count (CcPrintersPanel *self)
{
  CcPrintersPanelPrivate *priv;
  cups_job_t             *jobs;
  GtkWidget              *widget;
  gchar                  *active_jobs = NULL;
  gint                    num_jobs;

  priv = PRINTERS_PANEL_PRIVATE (self);

  priv->num_jobs = -1;

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      priv->num_jobs = cupsGetJobs (&jobs, priv->dests[priv->current_dest].name, 1, CUPS_WHICHJOBS_ACTIVE);
      if (priv->num_jobs > 0)
        cupsFreeJobs (priv->num_jobs, jobs);

      num_jobs = priv->num_jobs < 0 ? 0 : (guint) priv->num_jobs;
      /* Translators: there is n active print jobs on this printer */
      active_jobs = g_strdup_printf (ngettext ("%u active", "%u active", num_jobs), num_jobs);
    }

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-jobs-label");

  if (active_jobs)
    {
      cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), active_jobs);
      g_free (active_jobs);
    }
  else
    cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT);

  if (priv->pp_jobs_dialog)
    {
      pp_jobs_dialog_update (priv->pp_jobs_dialog);
    }
}

static void
printer_disable_cb (GObject    *gobject,
                    GParamSpec *pspec,
                    gpointer    user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  gboolean                paused = FALSE;
  gboolean                is_accepting_jobs = TRUE;
  char                   *name = NULL;
  int                     i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      name = priv->dests[priv->current_dest].name;

      for (i = 0; i < priv->dests[priv->current_dest].num_options; i++)
        {
          if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-state") == 0)
            paused = (g_strcmp0 (priv->dests[priv->current_dest].options[i].value, "5") == 0);
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-is-accepting-jobs") == 0)
            {
              if (g_strcmp0 (priv->dests[priv->current_dest].options[i].value, "true") == 0)
                is_accepting_jobs = TRUE;
              else
                is_accepting_jobs = FALSE;
            }
        }
    }

  if (name)
    {
      if (!paused && is_accepting_jobs)
        {
          printer_set_enabled (name, FALSE);
          printer_set_accepting_jobs (name, FALSE, NULL);
        }
      else
        {
          if (paused)
            printer_set_enabled (name, TRUE);

          if (!is_accepting_jobs)
            printer_set_accepting_jobs (name, TRUE, NULL);
        }

      actualize_printers_list (self);
    }
}

typedef struct {
  gchar *color;
  gchar *type;
  gchar *name;
  gint   level;
} MarkerItem;

static gint
markers_cmp (gconstpointer a,
             gconstpointer b)
{
  MarkerItem *x = (MarkerItem*) a;
  MarkerItem *y = (MarkerItem*) b;

  if (x->level < y->level)
    return 1;
  else if (x->level == y->level)
    return 0;
  else
    return -1;
}

static void
rounded_rectangle (cairo_t *cr, double x, double y, double w, double h, double r)
{
    cairo_new_sub_path (cr);
    cairo_arc (cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
    cairo_arc (cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
    cairo_arc (cr, x + w - r, y + h - r, r, 0, M_PI / 2);
    cairo_arc (cr, x + r, y + h - r, r, M_PI / 2, M_PI);
    cairo_close_path (cr);
}

static gboolean
supply_levels_draw_cb (GtkWidget *widget,
                       cairo_t *cr,
                       gpointer user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  GtkStyleContext        *context;
  gchar                  *marker_levels = NULL;
  gchar                  *marker_colors = NULL;
  gchar                  *marker_names = NULL;
  gchar                  *marker_types = NULL;
  gchar                  *tooltip_text = NULL;
  gint                    width;
  gint                    height;
  int                     i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  width = gtk_widget_get_allocated_width (widget);
  height = gtk_widget_get_allocated_height (widget);

  cairo_rectangle (cr, 0.0, 0.0, width, height);
  gdk_cairo_set_source_rgba (cr, &priv->background_color);
  cairo_fill (cr);

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      for (i = 0; i < priv->dests[priv->current_dest].num_options; i++)
        {
          if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "marker-names") == 0)
            marker_names = g_strcompress (priv->dests[priv->current_dest].options[i].value);
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "marker-levels") == 0)
            marker_levels = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "marker-colors") == 0)
            marker_colors = priv->dests[priv->current_dest].options[i].value;
          else if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "marker-types") == 0)
            marker_types = priv->dests[priv->current_dest].options[i].value;
        }

      if (marker_levels && marker_colors && marker_names && marker_types)
        {
          GdkRGBA   border_color = {0.0, 0.0, 0.0, 1.0};
          GSList   *markers = NULL;
          GSList   *tmp_list = NULL;
          GValue    int_val = G_VALUE_INIT;
          gchar   **marker_levelsv = NULL;
          gchar   **marker_colorsv = NULL;
          gchar   **marker_namesv = NULL;
          gchar   **marker_typesv = NULL;
          gchar    *tmp = NULL;
          gint      border_radius = 0;

          context = gtk_widget_get_style_context ((GtkWidget *)
            gtk_builder_get_object (priv->builder, "printer-options-button"));
          gtk_style_context_get_border_color (context, 0, &border_color);
          gtk_style_context_get_property (
            context, GTK_STYLE_PROPERTY_BORDER_RADIUS, 0, &int_val);
          if (G_VALUE_HOLDS_INT (&int_val))
            border_radius = g_value_get_int (&int_val);

          widget = (GtkWidget*)
            gtk_builder_get_object (priv->builder, "supply-drawing-area");

          marker_levelsv = g_strsplit (marker_levels, ",", -1);
          marker_colorsv = g_strsplit (marker_colors, ",", -1);
          marker_namesv = g_strsplit (marker_names, ",", -1);
          marker_typesv = g_strsplit (marker_types, ",", -1);

          if (g_strv_length (marker_levelsv) == g_strv_length (marker_colorsv) &&
              g_strv_length (marker_colorsv) == g_strv_length (marker_namesv) &&
              g_strv_length (marker_namesv) == g_strv_length (marker_typesv))
            {
              for (i = 0; i < g_strv_length (marker_levelsv); i++)
                {
                  MarkerItem *marker;

                  if (g_strcmp0 (marker_typesv[i], "ink") == 0 ||
                      g_strcmp0 (marker_typesv[i], "toner") == 0)
                    {
                      marker = g_new0 (MarkerItem, 1);
                      marker->type = g_strdup (marker_typesv[i]);
                      marker->name = g_strdup (marker_namesv[i]);
                      marker->color = g_strdup (marker_colorsv[i]);
                      marker->level = atoi (marker_levelsv[i]);

                      markers = g_slist_prepend (markers, marker);
                    }
                }

              markers = g_slist_sort (markers, markers_cmp);

              for (tmp_list = markers; tmp_list; tmp_list = tmp_list->next)
                {
                  GdkRGBA color = {0.0, 0.0, 0.0, 1.0};
                  double  display_value;
                  int     value;

                  value = ((MarkerItem*) tmp_list->data)->level;

                  gdk_rgba_parse (&color, ((MarkerItem*) tmp_list->data)->color);

                  if (value > 0)
                    {
                      display_value = value / 100.0 * (width - 3.0);
                      gdk_cairo_set_source_rgba (cr, &color);
                      rounded_rectangle (cr, 1.5, 1.5, display_value, SUPPLY_BAR_HEIGHT - 3.0, border_radius);
                      cairo_fill (cr);
                    }

                  if (tooltip_text)
                    {
                      tmp = g_strdup_printf ("%s\n%s",
                                             tooltip_text,
                                             ((MarkerItem*) tmp_list->data)->name);
                      g_free (tooltip_text);
                      tooltip_text = tmp;
                      tmp = NULL;
                    }
                  else
                    tooltip_text = g_strdup_printf ("%s",
                                                    ((MarkerItem*) tmp_list->data)->name);
                }

              cairo_set_line_width (cr, 1.0);
              gdk_cairo_set_source_rgba (cr, &border_color);
              rounded_rectangle (cr, 1.5, 1.5, width - 3.0, SUPPLY_BAR_HEIGHT - 3.0, border_radius);
              cairo_stroke (cr);

              for (tmp_list = markers; tmp_list; tmp_list = tmp_list->next)
                {
                  g_free (((MarkerItem*) tmp_list->data)->name);
                  g_free (((MarkerItem*) tmp_list->data)->type);
                  g_free (((MarkerItem*) tmp_list->data)->color);
                }
              g_slist_free_full (markers, g_free);
            }

          g_strfreev (marker_levelsv);
          g_strfreev (marker_colorsv);
          g_strfreev (marker_namesv);
          g_strfreev (marker_typesv);
        }

      g_free (marker_names);

      if (tooltip_text)
        {
          gtk_widget_set_tooltip_text (widget, tooltip_text);
          g_free (tooltip_text);
        }
      else
        {
          gtk_widget_set_tooltip_text (widget, NULL);
          gtk_widget_set_has_tooltip (widget, FALSE);
        }
    }

  return TRUE;
}

static void
printer_set_default_cb (GtkToggleButton *button,
                        gpointer         user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  char                   *name = NULL;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    name = priv->dests[priv->current_dest].name;

  if (name)
    {
      printer_set_default (name);
      actualize_printers_list (self);

      g_signal_handlers_block_by_func (G_OBJECT (button), printer_set_default_cb, self);
      gtk_toggle_button_set_active (button, priv->dests[priv->current_dest].is_default);
      g_signal_handlers_unblock_by_func (G_OBJECT (button), printer_set_default_cb, self);
  }
}

static void
new_printer_dialog_pre_response_cb (PpNewPrinterDialog *dialog,
                                    const gchar        *device_name,
                                    const gchar        *device_location,
                                    const gchar        *device_make_and_model,
                                    gboolean            is_network_device,
                                    gpointer            user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;

  priv = PRINTERS_PANEL_PRIVATE (self);

  priv->new_printer_name = g_strdup (device_name);
  priv->new_printer_location = g_strdup (device_location);
  priv->new_printer_make_and_model = g_strdup (device_make_and_model);
  priv->new_printer_on_network = is_network_device;
  priv->select_new_printer = TRUE;

  actualize_printers_list (self);
}

static void
new_printer_dialog_response_cb (PpNewPrinterDialog *dialog,
                                gint                response_id,
                                gpointer            user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->pp_new_printer_dialog)
    g_clear_object (&priv->pp_new_printer_dialog);

  g_clear_pointer (&priv->new_printer_name, g_free);
  g_clear_pointer (&priv->new_printer_location, g_free);
  g_clear_pointer (&priv->new_printer_make_and_model, g_free);

  if (response_id == GTK_RESPONSE_REJECT)
    {
      GtkWidget *message_dialog;

      message_dialog = gtk_message_dialog_new (NULL,
                                               0,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE,
      /* Translators: Addition of the new printer failed. */
                                               _("Failed to add new printer."));
      g_signal_connect (message_dialog,
                        "response",
                        G_CALLBACK (gtk_widget_destroy),
                        NULL);
      gtk_widget_show (message_dialog);
    }

  actualize_printers_list (self);
}

static void
printer_add_cb (GtkToolButton *toolbutton,
                gpointer       user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  GtkWidget              *toplevel;

  priv = PRINTERS_PANEL_PRIVATE (self);

  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
  priv->pp_new_printer_dialog = PP_NEW_PRINTER_DIALOG (pp_new_printer_dialog_new (GTK_WINDOW (toplevel)));

  g_signal_connect (priv->pp_new_printer_dialog,
                    "pre-response",
                    G_CALLBACK (new_printer_dialog_pre_response_cb),
                    self);

  g_signal_connect (priv->pp_new_printer_dialog,
                    "response",
                    G_CALLBACK (new_printer_dialog_response_cb),
                    self);
}

static void
printer_remove_cb (GtkToolButton *toolbutton,
                   gpointer       user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  char                   *printer_name = NULL;

  priv = PRINTERS_PANEL_PRIVATE (self);

  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 && printer_delete (printer_name))
    actualize_printers_list (self);
}

static void
printer_name_edit_cb (GtkWidget *entry,
                      gpointer   user_data)
{
  CcPrintersPanelPrivate  *priv;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
  const gchar             *new_name;
  gchar                   *old_name = NULL;
  gint                     i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  new_name = cc_editable_entry_get_text (CC_EDITABLE_ENTRY (entry));

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    old_name = priv->dests[priv->current_dest].name;

  if (printer_rename (old_name, new_name))
    {
      free_dests (self);
      priv->num_dests = cupsGetDests (&priv->dests);
      priv->dest_model_names = g_new0 (gchar *, priv->num_dests);
      priv->ppd_file_names = g_new0 (gchar *, priv->num_dests);

      for (i = 0; i < priv->num_dests; i++)
        if (g_strcmp0 (priv->dests[i].name, new_name) == 0)
          {
            priv->current_dest  = i;
            break;
          }
    }

  actualize_printers_list (self);
}

static void
printer_location_edit_cb (GtkWidget *entry,
                          gpointer   user_data)
{
  CcPrintersPanelPrivate  *priv;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
  const gchar             *location;
  gchar                   *printer_name = NULL;

  priv = PRINTERS_PANEL_PRIVATE (self);

  location = cc_editable_entry_get_text (CC_EDITABLE_ENTRY (entry));

  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 && location &&
      printer_set_location (printer_name, location))
    actualize_printers_list (self);
}

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)
    {
      SetPPDItem *item = (SetPPDItem *) iter->data;

      if (g_strcmp0 (item->printer_name, printer_name) == 0)
        {
          priv->driver_change_list = g_list_remove_link (priv->driver_change_list, iter);

          g_object_unref (item->cancellable);
          g_free (item->printer_name);
          g_free (item);
          g_list_free (iter);
          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)
        {
          SetPPDItem *item;

          item = g_new0 (SetPPDItem, 1);
          item->printer_name = g_strdup (printer_name);
          item->cancellable = g_cancellable_new ();

          priv->driver_change_list =
            g_list_prepend (priv->driver_change_list, item);
          update_sensitivity (self);
          printer_set_ppd_file_async (printer_name,
                                      ppd_filename,
                                      item->cancellable,
                                      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)
        {
          SetPPDItem *item;

          item = g_new0 (SetPPDItem, 1);
          item->printer_name = g_strdup (printer_name);
          item->cancellable = g_cancellable_new ();

          priv->driver_change_list = g_list_prepend (priv->driver_change_list,
                                                     item);
          update_sensitivity (self);
          printer_set_ppd_async (printer_name,
                                 ppd_name,
                                 item->cancellable,
                                 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");
            }

          if (manufacturer == NULL)
            {
              manufacturer =
                get_ppd_attribute (priv->ppd_file_names[priv->current_dest],
                                   "Manufacturer");
            }

          if (manufacturer == NULL)
            {
              manufacturer = g_strdup ("Raw");
            }
        }

      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)
    {
      SetPPDItem *item;

      item = g_new0 (SetPPDItem, 1);
      item->printer_name = g_strdup (printer_name);
      item->cancellable = g_cancellable_new ();

      priv->driver_change_list = g_list_prepend (priv->driver_change_list,
                                                 item);
      update_sensitivity (self);
      printer_set_ppd_async (printer_name,
                             ppd_name,
                             item->cancellable,
                             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
pp_maintenance_command_execute_cb (GObject      *source_object,
                                   GAsyncResult *res,
                                   gpointer      user_data)
{
  PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
  GError               *error = NULL;

  pp_maintenance_command_execute_finish (command, res, &error);

  g_object_unref (command);
}

static void
test_page_cb (GtkButton *button,
              gpointer   user_data)
{
  CcPrintersPanelPrivate  *priv;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
  cups_ptype_t             type = 0;
  const gchar             *printer_type = NULL;
  gchar                   *printer_name = NULL;
  gint                     i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      printer_name = priv->dests[priv->current_dest].name;
      printer_type = cupsGetOption ("printer-type",
                                    priv->dests[priv->current_dest].num_options,
                                    priv->dests[priv->current_dest].options);
      if (printer_type)
        type = atoi (printer_type);
    }

  if (printer_name)
    {
      const gchar  *const dirs[] = { "/usr/share/cups",
                                     "/usr/local/share/cups",
                                     NULL };
      const gchar  *testprint[] = { "%s/data/testprint",
                                    "%s/data/testprint.ps",
                                    NULL };
      const gchar **pattern;
      const gchar  *datadir = NULL;
      http_t       *http = NULL;
      gchar        *printer_uri = NULL;
      gchar        *filename = NULL;
      gchar        *resource = NULL;
      ipp_t        *response = NULL;
      ipp_t        *request;

      if ((datadir = getenv ("CUPS_DATADIR")) != NULL)
        {
          for (pattern = testprint; *pattern != NULL; pattern++)
            {
              filename = g_strdup_printf (*pattern, datadir);
              if (g_access (filename, R_OK) == 0)
                break;
              else
                {
                  g_free (filename);
                  filename = NULL;
                }
            }
        }
      else
        {
          for (i = 0; (datadir = dirs[i]) != NULL && filename == NULL; i++)
            {
              for (pattern = testprint; *pattern != NULL; pattern++)
                {
                  filename = g_strdup_printf (*pattern, datadir);
                  if (g_access (filename, R_OK) == 0)
                    break;
                  else
                    {
                      g_free (filename);
                      filename = NULL;
                    }
                }
            }
        }

      if (filename)
        {
          if (type & CUPS_PRINTER_CLASS)
            {
              printer_uri = g_strdup_printf ("ipp://localhost/classes/%s", printer_name);
              resource = g_strdup_printf ("/classes/%s", printer_name);
            }
          else
            {
              printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", printer_name);
              resource = g_strdup_printf ("/printers/%s", printer_name);
            }

          http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
          if (http)
            {
              request = ippNewRequest (IPP_PRINT_JOB);
              ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
                            "printer-uri", NULL, printer_uri);
              ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
                            "requesting-user-name", NULL, cupsUser ());
              ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
              /* Translators: Name of job which makes printer to print test page */
                            "job-name", NULL, _("Test page"));
              response = cupsDoFileRequest (http, request, resource, filename);
              httpClose (http);
            }

          if (response)
            {
              if (ippGetState (response) == IPP_ERROR)
                g_warning ("An error has occured during printing of test page.");
              ippDelete (response);
            }

          g_free (filename);
          g_free (printer_uri);
          g_free (resource);
        }
      else
        {
          PpMaintenanceCommand *command;

          command = pp_maintenance_command_new (printer_name,
                                                "PrintSelfTestPage",
          /* Translators: Name of job which makes printer to print test page */
                                                _("Test page"));

          pp_maintenance_command_execute_async (command, NULL, pp_maintenance_command_execute_cb, self);
        }
    }
}

static void
update_sensitivity (gpointer user_data)
{
  CcPrintersPanelPrivate  *priv;
  GtkTreeSelection        *selection;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
  cups_ptype_t             type = 0;
  GtkTreeModel            *model;
  GtkTreeView             *treeview;
  GtkTreeIter              tree_iter;
  const char              *cups_server = NULL;
  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;
  gboolean                 is_new = FALSE;
  gboolean                 already_present_local;
  GList                   *iter;
  gchar                   *current_printer_name = NULL;
  gint                     i;

  priv = PRINTERS_PANEL_PRIVATE (self);

  is_authorized =
    priv->permission &&
    g_permission_get_allowed (G_PERMISSION (priv->permission)) &&
    priv->lockdown_settings &&
    !g_settings_get_boolean (priv->lockdown_settings, "disable-print-setup");

  printer_selected = priv->current_dest >= 0 &&
                     priv->current_dest < priv->num_dests &&
                     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;
              is_class = type & CUPS_PRINTER_CLASS;
              break;
            }
        }

      for (iter = priv->driver_change_list; iter; iter = iter->next)
        {
          SetPPDItem *item = (SetPPDItem *) iter->data;

          if (g_strcmp0 (item->printer_name, priv->dests[priv->current_dest].name) == 0)
            {
              is_changing_driver = TRUE;
            }
        }
    }

  treeview = (GtkTreeView*)
    gtk_builder_get_object (priv->builder, "printers-treeview");

  selection = gtk_tree_view_get_selection (treeview);
  if (selection &&
      gtk_tree_selection_get_selected (selection, &model, &tree_iter))
    {
      gtk_tree_model_get (model, &tree_iter,
                          PRINTER_NAME_COLUMN, &current_printer_name,
                          -1);
    }

  if (priv->new_printer_name &&
      g_strcmp0 (priv->new_printer_name, current_printer_name) == 0)
    {
      printer_selected = TRUE;
      is_discovered = FALSE;
      is_class = FALSE;
      is_new = TRUE;
    }

  g_free (current_printer_name);

  cups_server = cupsServer ();
  if (cups_server &&
      g_ascii_strncasecmp (cups_server, "localhost", 9) != 0 &&
      g_ascii_strncasecmp (cups_server, "127.0.0.1", 9) != 0 &&
      g_ascii_strncasecmp (cups_server, "::1", 3) != 0 &&
      cups_server[0] != '/')
    local_server = FALSE;

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "notebook");
  if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == NOTEBOOK_NO_CUPS_PAGE)
    no_cups = TRUE;

  already_present_local = local_server && !is_discovered && is_authorized && !is_new;

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-add-button");
  gtk_widget_set_sensitive (widget, local_server && is_authorized && !no_cups && !priv->new_printer_name);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-add-button2");
  gtk_widget_set_sensitive (widget, local_server && is_authorized && !no_cups && !priv->new_printer_name);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-remove-button");
  gtk_widget_set_sensitive (widget, already_present_local && printer_selected && !no_cups);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-disable-switch");
  gtk_widget_set_sensitive (widget, already_present_local);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-default-check-button");
  gtk_widget_set_sensitive (widget, is_authorized && !is_new);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "print-test-page-button");
  gtk_widget_set_sensitive (widget, printer_selected && !is_new);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-options-button");
  gtk_widget_set_sensitive (widget, printer_selected && local_server && !is_discovered &&
                            !priv->pp_options_dialog && !is_new);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-jobs-button");
  gtk_widget_set_sensitive (widget, printer_selected && !is_new);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-icon");
  gtk_widget_set_sensitive (widget, printer_selected);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-name-label");
  cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), already_present_local);

  widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-location-label");
  cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), already_present_local);

  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 (already_present_local && !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
on_permission_changed (GPermission *permission,
                       GParamSpec  *pspec,
                       gpointer     data)
{
  update_sensitivity (data);
}

static void
on_lockdown_settings_changed (GSettings  *settings,
                              const char *key,
                              gpointer    user_data)
{
  CcPrintersPanelPrivate  *priv;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;

  if (g_str_equal (key, "disable-print-setup") == FALSE)
    return;

  priv = PRINTERS_PANEL_PRIVATE (self);

#if 0
  /* FIXME */
  gtk_widget_set_sensitive (priv->lock_button,
    !g_settings_get_boolean (priv->lockdown_settings, "disable-print-setup"));
#endif

  on_permission_changed (priv->permission, NULL, user_data);
}

static void
printer_options_response_cb (GtkDialog *dialog,
                             gint       response_id,
                             gpointer   user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;

  priv = PRINTERS_PANEL_PRIVATE (self);

  pp_options_dialog_free (priv->pp_options_dialog);
  priv->pp_options_dialog = NULL;
  update_sensitivity (self);

  if (response_id == GTK_RESPONSE_OK)
    actualize_printers_list (self);
}

static void
printer_options_cb (GtkToolButton *toolbutton,
                    gpointer       user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  GtkWidget              *widget;
  gboolean                is_authorized;

  priv = PRINTERS_PANEL_PRIVATE (self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "main-vbox");

  is_authorized =
    priv->permission &&
    g_permission_get_allowed (G_PERMISSION (priv->permission)) &&
    priv->lockdown_settings &&
    !g_settings_get_boolean (priv->lockdown_settings, "disable-print-setup");

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    {
      priv->pp_options_dialog = pp_options_dialog_new (
        GTK_WINDOW (gtk_widget_get_toplevel (widget)),
        printer_options_response_cb,
        self,
        priv->dests[priv->current_dest].name,
        is_authorized);
      update_sensitivity (self);
    }
}

static gboolean
cups_status_check (gpointer user_data)
{
  CcPrintersPanelPrivate  *priv;
  CcPrintersPanel         *self = (CcPrintersPanel*) user_data;
  gboolean                 result = TRUE;
  http_t                  *http;

  priv = self->priv = PRINTERS_PANEL_PRIVATE (self);

  http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
  if (http)
    {
      httpClose (http);
      actualize_printers_list (self);
      attach_to_cups_notifier (self);
      priv->cups_status_check_id = 0;
      result = FALSE;
    }

  return result;
}

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;

  if (priv->pp_ppd_selection_dialog)
    pp_ppd_selection_dialog_set_ppd_list (priv->pp_ppd_selection_dialog,
                                          priv->all_ppds_list);

  g_object_unref (priv->get_all_ppds_cancellable);
  priv->get_all_ppds_cancellable = NULL;
}

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
jobs_dialog_response_cb (GtkDialog *dialog,
                         gint       response_id,
                         gpointer   user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;

  priv = PRINTERS_PANEL_PRIVATE (self);

  pp_jobs_dialog_free (priv->pp_jobs_dialog);
  priv->pp_jobs_dialog = NULL;
}

static void
printer_jobs_cb (GtkToolButton *toolbutton,
                 gpointer       user_data)
{
  CcPrintersPanelPrivate *priv;
  CcPrintersPanel        *self = (CcPrintersPanel*) user_data;
  GtkWidget              *widget;

  priv = PRINTERS_PANEL_PRIVATE (self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "main-vbox");

  if (priv->current_dest >= 0 &&
      priv->current_dest < priv->num_dests &&
      priv->dests != NULL)
    priv->pp_jobs_dialog = pp_jobs_dialog_new (
      GTK_WINDOW (gtk_widget_get_toplevel (widget)),
      jobs_dialog_response_cb,
      self,
      priv->dests[priv->current_dest].name);
}

static void
cc_printers_panel_init (CcPrintersPanel *self)
{
  CcPrintersPanelPrivate *priv;
  GtkWidget              *top_widget;
  GtkWidget              *widget;
  GError                 *error = NULL;
  http_t                 *http;
  gchar                  *objects[] = { "main-vbox", NULL };
  GtkStyleContext        *context;
  guint                   builder_result;

  priv = self->priv = PRINTERS_PANEL_PRIVATE (self);
  g_resources_register (cc_printers_get_resource ());

  /* initialize main data structure */
  priv->builder = gtk_builder_new ();
  priv->dests = NULL;
  priv->dest_model_names = NULL;
  priv->ppd_file_names = NULL;
  priv->num_dests = 0;
  priv->current_dest = -1;

  priv->num_jobs = 0;

  priv->pp_new_printer_dialog = NULL;
  priv->pp_options_dialog = NULL;

  priv->subscription_id = 0;
  priv->cups_status_check_id = 0;
  priv->subscription_renewal_id = 0;
  priv->cups_proxy = NULL;
  priv->cups_bus_connection = NULL;
  priv->dbus_subscription_id = 0;

  priv->new_printer_name = NULL;
  priv->new_printer_location = NULL;
  priv->new_printer_make_and_model = NULL;
  priv->new_printer_on_network = FALSE;
  priv->select_new_printer = FALSE;

  priv->permission = NULL;
  priv->lockdown_settings = NULL;

  priv->getting_ppd_names = FALSE;

  priv->all_ppds_list = NULL;
  priv->get_all_ppds_cancellable = NULL;

  priv->preferred_drivers = NULL;

  builder_result = gtk_builder_add_objects_from_resource (priv->builder,
                                                          "/org/gnome/control-center/printers/printers.ui",
                                                          objects, &error);

  if (builder_result == 0)
    {
      /* Translators: The XML file containing user interface can not be loaded */
      g_warning (_("Could not load ui: %s"), error->message);
      g_error_free (error);
      return;
    }

  /* add the top level widget */
  top_widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "main-vbox");

  /* connect signals */
  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-add-button");
  g_signal_connect (widget, "clicked", G_CALLBACK (printer_add_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-add-button2");
  g_signal_connect (widget, "clicked", G_CALLBACK (printer_add_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-remove-button");
  g_signal_connect (widget, "clicked", G_CALLBACK (printer_remove_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-disable-switch");
  g_signal_connect (widget, "notify::active", G_CALLBACK (printer_disable_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "supply-drawing-area");
  g_signal_connect (widget, "draw", G_CALLBACK (supply_levels_draw_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-default-check-button");
  g_signal_connect (widget, "toggled", G_CALLBACK (printer_set_default_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "print-test-page-button");
  g_signal_connect (widget, "clicked", G_CALLBACK (test_page_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-jobs-button");
  g_signal_connect (widget, "clicked", G_CALLBACK (printer_jobs_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-options-button");
  g_signal_connect (widget, "clicked", G_CALLBACK (printer_options_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-name-label");
  g_signal_connect (widget, "editing-done", G_CALLBACK (printer_name_edit_cb), self);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-location-label");
  g_signal_connect (widget, "editing-done", G_CALLBACK (printer_location_edit_cb), self);

  priv->lockdown_settings = g_settings_new ("org.gnome.desktop.lockdown");
  if (priv->lockdown_settings)
    g_signal_connect (priv->lockdown_settings,
                      "changed",
                      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*)
    gtk_builder_get_object (priv->builder, "printers-scrolledwindow");
  context = gtk_widget_get_style_context (widget);
  gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);

  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printers-toolbar");
  context = gtk_widget_get_style_context (widget);
  gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);


  /* Make model label and ip-address label selectable */
  widget = (GtkWidget*)
    gtk_builder_get_object (priv->builder, "printer-ip-address-label");
  cc_editable_entry_set_selectable (CC_EDITABLE_ENTRY (widget), TRUE);


  /* Add unlock button */
  priv->permission = (GPermission *)polkit_permission_new_sync (
    "org.opensuse.cupspkhelper.mechanism.all-edit", NULL, NULL, NULL);
  if (priv->permission != NULL)
    {
      g_signal_connect (priv->permission, "notify",
                        G_CALLBACK (on_permission_changed), self);
      on_permission_changed (priv->permission, NULL, self);
    }
  else
    g_warning ("Your system does not have the cups-pk-helper's policy \
\"org.opensuse.cupspkhelper.mechanism.all-edit\" installed. \
Please check your installation");

  gtk_style_context_get_background_color (gtk_widget_get_style_context (top_widget),
                                          GTK_STATE_FLAG_NORMAL,
                                          &priv->background_color);

  populate_printers_list (self);
  attach_to_cups_notifier (self);

  priv->get_all_ppds_cancellable = g_cancellable_new ();
  get_all_ppds_async (priv->get_all_ppds_cancellable,
                      get_all_ppds_async_cb,
                      self);

  http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
  if (!http)
    {
      priv->cups_status_check_id =
        g_timeout_add_seconds (CUPS_STATUS_CHECK_INTERVAL, cups_status_check, self);
    }
  else
    httpClose (http);

  gtk_container_add (GTK_CONTAINER (self), top_widget);
  gtk_widget_show_all (GTK_WIDGET (self));
}
