/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2012 Red Hat, Inc,
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 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, see .
*
* Author: Marek Kasik
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include "pp-ppd-option-widget.h"
#include "pp-utils.h"
static void pp_ppd_option_widget_finalize (GObject *object);
static gboolean construct_widget (PpPPDOptionWidget *self);
static void update_widget (PpPPDOptionWidget *self);
static void update_widget_real (PpPPDOptionWidget *self);
struct _PpPPDOptionWidget
{
GtkBox parent_instance;
GtkWidget *switch_button;
GtkWidget *combo;
GtkWidget *image;
GtkWidget *box;
ppd_option_t *option;
gchar *printer_name;
gchar *option_name;
cups_dest_t *destination;
gboolean destination_set;
gchar *ppd_filename;
gboolean ppd_filename_set;
GCancellable *cancellable;
};
G_DEFINE_TYPE (PpPPDOptionWidget, pp_ppd_option_widget, GTK_TYPE_BOX)
/* This list comes from Gtk+ */
static const struct {
const char *keyword;
const char *choice;
const char *translation;
} ppd_choice_translations[] = {
{ "Duplex", "None", N_("One Sided") },
/* Translators: this is an option of "Two Sided" */
{ "Duplex", "DuplexNoTumble", N_("Long Edge (Standard)") },
/* Translators: this is an option of "Two Sided" */
{ "Duplex", "DuplexTumble", N_("Short Edge (Flip)") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "Auto", N_("Auto Select") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "AutoSelect", N_("Auto Select") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "Default", N_("Printer Default") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "None", N_("Printer Default") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "PrinterDefault", N_("Printer Default") },
/* Translators: this is an option of "Paper Source" */
{ "InputSlot", "Unspecified", N_("Auto Select") },
/* Translators: this is an option of "Resolution" */
{ "Resolution", "default", N_("Printer Default") },
/* Translators: this is an option of "GhostScript" */
{ "PreFilter", "EmbedFonts", N_("Embed GhostScript fonts only") },
/* Translators: this is an option of "GhostScript" */
{ "PreFilter", "Level1", N_("Convert to PS level 1") },
/* Translators: this is an option of "GhostScript" */
{ "PreFilter", "Level2", N_("Convert to PS level 2") },
/* Translators: this is an option of "GhostScript" */
{ "PreFilter", "No", N_("No pre-filtering") },
};
static ppd_option_t *
cups_option_copy (ppd_option_t *option)
{
ppd_option_t *result;
gint i;
result = g_new0 (ppd_option_t, 1);
*result = *option;
result->choices = g_new (ppd_choice_t, result->num_choices);
for (i = 0; i < result->num_choices; i++)
{
result->choices[i] = option->choices[i];
result->choices[i].code = g_strdup (option->choices[i].code);
result->choices[i].option = result;
}
return result;
}
static void
cups_option_free (ppd_option_t *option)
{
gint i;
if (option)
{
for (i = 0; i < option->num_choices; i++)
g_free (option->choices[i].code);
g_free (option->choices);
g_free (option);
}
}
static void
pp_ppd_option_widget_class_init (PpPPDOptionWidgetClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->finalize = pp_ppd_option_widget_finalize;
}
static void
pp_ppd_option_widget_init (PpPPDOptionWidget *self)
{
gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
GTK_ORIENTATION_HORIZONTAL);
}
static void
pp_ppd_option_widget_finalize (GObject *object)
{
PpPPDOptionWidget *self = PP_PPD_OPTION_WIDGET (object);
g_cancellable_cancel (self->cancellable);
if (self->ppd_filename)
g_unlink (self->ppd_filename);
g_clear_pointer (&self->option, cups_option_free);
g_clear_pointer (&self->printer_name, g_free);
g_clear_pointer (&self->option_name, g_free);
if (self->destination)
{
cupsFreeDests (1, self->destination);
self->destination = NULL;
}
g_clear_pointer (&self->ppd_filename, g_free);
g_clear_object (&self->cancellable);
G_OBJECT_CLASS (pp_ppd_option_widget_parent_class)->finalize (object);
}
static const gchar *
ppd_choice_translate (ppd_choice_t *choice)
{
const gchar *keyword = choice->option->keyword;
gint i;
for (i = 0; i < G_N_ELEMENTS (ppd_choice_translations); i++)
{
if (g_strcmp0 (ppd_choice_translations[i].keyword, keyword) == 0 &&
g_strcmp0 (ppd_choice_translations[i].choice, choice->choice) == 0)
return _(ppd_choice_translations[i].translation);
}
return choice->text;
}
GtkWidget *
pp_ppd_option_widget_new (ppd_option_t *option,
const gchar *printer_name)
{
PpPPDOptionWidget *self = NULL;
if (option && printer_name)
{
self = g_object_new (PP_TYPE_PPD_OPTION_WIDGET, NULL);
self->printer_name = g_strdup (printer_name);
self->option = cups_option_copy (option);
self->option_name = g_strdup (option->keyword);
if (construct_widget (self))
{
update_widget_real (self);
}
else
{
g_object_ref_sink (self);
g_object_unref (self);
self = NULL;
}
}
return (GtkWidget *) self;
}
enum {
NAME_COLUMN,
VALUE_COLUMN,
N_COLUMNS
};
static GtkWidget *
combo_box_new (void)
{
GtkCellRenderer *cell;
GtkListStore *store;
GtkWidget *combo_box;
combo_box = gtk_combo_box_new ();
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
g_object_unref (store);
cell = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
"text", NAME_COLUMN,
NULL);
return combo_box;
}
static void
combo_box_append (GtkWidget *combo,
const gchar *display_text,
const gchar *value)
{
GtkTreeModel *model;
GtkListStore *store;
GtkTreeIter iter;
model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
store = GTK_LIST_STORE (model);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
NAME_COLUMN, display_text,
VALUE_COLUMN, value,
-1);
}
struct ComboSet {
GtkComboBox *combo;
const gchar *value;
};
static gboolean
set_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer data)
{
struct ComboSet *set_data = data;
g_autofree gchar *value = NULL;
gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
if (strcmp (value, set_data->value) == 0)
{
gtk_combo_box_set_active_iter (set_data->combo, iter);
return TRUE;
}
return FALSE;
}
static void
combo_box_set (GtkWidget *combo,
const gchar *value)
{
struct ComboSet set_data;
GtkTreeModel *model;
model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
set_data.combo = GTK_COMBO_BOX (combo);
set_data.value = value;
gtk_tree_model_foreach (model, set_cb, &set_data);
}
static char *
combo_box_get (GtkWidget *combo)
{
GtkTreeModel *model;
GtkTreeIter iter;
gchar *value = NULL;
model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
return value;
}
static void
printer_add_option_async_cb (gboolean success,
gpointer user_data)
{
PpPPDOptionWidget *self = user_data;
update_widget (user_data);
g_clear_object (&self->cancellable);
}
static void
switch_changed_cb (PpPPDOptionWidget *self)
{
gchar **values;
values = g_new0 (gchar *, 2);
if (gtk_switch_get_active (GTK_SWITCH (self->switch_button)))
values[0] = g_strdup ("True");
else
values[0] = g_strdup ("False");
if (self->cancellable)
{
g_cancellable_cancel (self->cancellable);
g_object_unref (self->cancellable);
}
self->cancellable = g_cancellable_new ();
printer_add_option_async (self->printer_name,
self->option_name,
values,
FALSE,
self->cancellable,
printer_add_option_async_cb,
self);
g_strfreev (values);
}
static void
combo_changed_cb (PpPPDOptionWidget *self)
{
gchar **values;
values = g_new0 (gchar *, 2);
values[0] = combo_box_get (self->combo);
if (self->cancellable)
{
g_cancellable_cancel (self->cancellable);
g_object_unref (self->cancellable);
}
self->cancellable = g_cancellable_new ();
printer_add_option_async (self->printer_name,
self->option_name,
values,
FALSE,
self->cancellable,
printer_add_option_async_cb,
self);
g_strfreev (values);
}
static gboolean
construct_widget (PpPPDOptionWidget *self)
{
gint i;
/* Don't show options which has only one choice */
if (self->option && self->option->num_choices > 1)
{
switch (self->option->ui)
{
case PPD_UI_BOOLEAN:
self->switch_button = gtk_switch_new ();
g_signal_connect_object (self->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), self, G_CONNECT_SWAPPED);
gtk_box_pack_start (GTK_BOX (self), self->switch_button, FALSE, FALSE, 0);
break;
case PPD_UI_PICKONE:
self->combo = combo_box_new ();
for (i = 0; i < self->option->num_choices; i++)
{
combo_box_append (self->combo,
ppd_choice_translate (&self->option->choices[i]),
self->option->choices[i].choice);
}
gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
break;
case PPD_UI_PICKMANY:
self->combo = combo_box_new ();
for (i = 0; i < self->option->num_choices; i++)
{
combo_box_append (self->combo,
ppd_choice_translate (&self->option->choices[i]),
self->option->choices[i].choice);
}
gtk_box_pack_start (GTK_BOX (self), self->combo, TRUE, TRUE, 0);
g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
break;
default:
break;
}
self->image = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_MENU);
if (!self->image)
self->image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_MENU);
gtk_box_pack_start (GTK_BOX (self), self->image, FALSE, FALSE, 0);
gtk_widget_set_no_show_all (GTK_WIDGET (self->image), TRUE);
return TRUE;
}
else
{
return FALSE;
}
}
static void
update_widget_real (PpPPDOptionWidget *self)
{
ppd_option_t *option = NULL, *iter;
ppd_file_t *ppd_file;
gint i;
if (self->option)
{
option = cups_option_copy (self->option);
cups_option_free (self->option);
self->option = NULL;
}
else if (self->ppd_filename)
{
ppd_file = ppdOpenFile (self->ppd_filename);
ppdLocalize (ppd_file);
if (ppd_file)
{
ppdMarkDefaults (ppd_file);
for (iter = ppdFirstOption(ppd_file); iter; iter = ppdNextOption(ppd_file))
{
if (g_str_equal (iter->keyword, self->option_name))
{
option = cups_option_copy (iter);
break;
}
}
ppdClose (ppd_file);
}
g_unlink (self->ppd_filename);
g_free (self->ppd_filename);
self->ppd_filename = NULL;
}
if (option)
{
g_autofree gchar *value = NULL;
for (i = 0; i < option->num_choices; i++)
if (option->choices[i].marked)
value = g_strdup (option->choices[i].choice);
if (value == NULL)
value = g_strdup (option->defchoice);
if (value)
{
switch (option->ui)
{
case PPD_UI_BOOLEAN:
g_signal_handlers_block_by_func (self->switch_button, switch_changed_cb, self);
if (g_ascii_strcasecmp (value, "True") == 0)
gtk_switch_set_active (GTK_SWITCH (self->switch_button), TRUE);
else
gtk_switch_set_active (GTK_SWITCH (self->switch_button), FALSE);
g_signal_handlers_unblock_by_func (self->switch_button, switch_changed_cb, self);
break;
case PPD_UI_PICKONE:
g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
combo_box_set (self->combo, value);
g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
break;
case PPD_UI_PICKMANY:
g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
combo_box_set (self->combo, value);
g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
break;
default:
break;
}
}
if (option->conflicted)
gtk_widget_show (self->image);
else
gtk_widget_hide (self->image);
}
cups_option_free (option);
}
static void
get_named_dest_cb (cups_dest_t *dest,
gpointer user_data)
{
PpPPDOptionWidget *self = user_data;
if (self->destination)
cupsFreeDests (1, self->destination);
self->destination = dest;
self->destination_set = TRUE;
if (self->ppd_filename_set)
{
update_widget_real (self);
}
}
static void
printer_get_ppd_cb (const gchar *ppd_filename,
gpointer user_data)
{
PpPPDOptionWidget *self = user_data;
if (self->ppd_filename)
{
g_unlink (self->ppd_filename);
g_free (self->ppd_filename);
}
self->ppd_filename = g_strdup (ppd_filename);
self->ppd_filename_set = TRUE;
if (self->destination_set)
{
update_widget_real (self);
}
}
static void
update_widget (PpPPDOptionWidget *self)
{
self->ppd_filename_set = FALSE;
self->destination_set = FALSE;
get_named_dest_async (self->printer_name,
get_named_dest_cb,
self);
printer_get_ppd_async (self->printer_name,
NULL,
0,
printer_get_ppd_cb,
self);
}