/* -*- 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
#include
#include
#include
#include "pp-options-dialog.h"
#include "pp-maintenance-command.h"
#include "pp-ppd-option-widget.h"
#include "pp-ipp-option-widget.h"
#include "pp-utils.h"
#include "pp-printer.h"
struct _PpOptionsDialog {
GtkDialog parent_instance;
GtkTreeSelection *categories_selection;
GtkTreeView *categories_treeview;
GtkBox *main_box;
GtkNotebook *notebook;
GtkSpinner *spinner;
GtkStack *stack;
gchar *printer_name;
gchar *ppd_filename;
gboolean ppd_filename_set;
cups_dest_t *destination;
gboolean destination_set;
GHashTable *ipp_attributes;
gboolean ipp_attributes_set;
gboolean sensitive;
};
G_DEFINE_TYPE (PpOptionsDialog, pp_options_dialog, GTK_TYPE_DIALOG)
enum
{
CATEGORY_IDS_COLUMN = 0,
CATEGORY_NAMES_COLUMN
};
/* These lists come from Gtk+ */
/* TODO: Only "Resolution" currently has a context to disambiguate it from
* the display settings. All of these should have contexts, but it
* was late in the release cycle and this partial solution was
* preferable. See:
* https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/414#note_446778
*/
static const struct {
const char *keyword;
const char *translation_context;
const char *translation;
} ppd_option_translations[] = {
{ "Duplex", NULL, N_("Two Sided") },
{ "MediaType", NULL, N_("Paper Type") },
{ "InputSlot", NULL, N_("Paper Source") },
{ "OutputBin", NULL, N_("Output Tray") },
{ "Resolution", "printing option", NC_("printing option", "Resolution") },
{ "PreFilter", NULL, N_("GhostScript pre-filtering") },
};
/* keep sorted when changing */
static const char *allowed_page_setup_options[] = {
"InputSlot",
"MediaType",
"OutputBin",
"PageSize",
};
/* keep sorted when changing */
static const char *allowed_color_options[] = {
"BRColorEnhancement",
"BRColorMatching",
"BRColorMatching",
"BRColorMode",
"BRGammaValue",
"BRImprovedGray",
"BlackSubstitution",
"ColorModel",
"HPCMYKInks",
"HPCSGraphics",
"HPCSImages",
"HPCSText",
"HPColorSmart",
"RPSBlackMode",
"RPSBlackOverPrint",
"Rcmyksimulation",
};
/* keep sorted when changing */
static const char *allowed_color_groups[] = {
"Color",
"Color1",
"Color2",
"ColorBalance",
"ColorPage",
"ColorSettings1",
"ColorSettings2",
"ColorSettings3",
"ColorSettings4",
"EPColorSettings",
"FPColorWise1",
"FPColorWise2",
"FPColorWise3",
"FPColorWise4",
"FPColorWise5",
"HPCMYKInksPanel",
"HPColorOptions",
"HPColorOptionsPanel",
"HPColorQualityOptionsPanel",
"ManualColor",
};
/* keep sorted when changing */
static const char *allowed_image_quality_options[] = {
"BRDocument",
"BRHalfTonePattern",
"BRNormalPrt",
"BRPrintQuality",
"BitsPerPixel",
"Darkness",
"Dithering",
"EconoMode",
"Economode",
"HPEconoMode",
"HPEdgeControl",
"HPGraphicsHalftone",
"HPHalftone",
"HPImagingOptions",
"HPLJDensity",
"HPPhotoHalftone",
"HPPrintQualityOptions",
"HPResolutionOptions",
"OutputMode",
"REt",
"RPSBitsPerPixel",
"RPSDitherType",
"Resolution",
"ScreenLock",
"Smoothing",
"TonerSaveMode",
"UCRGCRForImage",
};
/* keep sorted when changing */
static const char *allowed_image_quality_groups[] = {
"EPQualitySettings",
"FPImageQuality1",
"FPImageQuality2",
"FPImageQuality3",
"ImageQualityPage",
"Quality",
};
/* keep sorted when changing */
static const char * allowed_finishing_options[] = {
"BindColor",
"BindEdge",
"BindType",
"BindWhen",
"Booklet",
"FoldType",
"FoldWhen",
"HPStaplerOptions",
"Jog",
"Slipsheet",
"Sorter",
"StapleLocation",
"StapleOrientation",
"StapleWhen",
"StapleX",
"StapleY",
};
/* keep sorted when changing */
static const char *allowed_job_groups[] = {
"JobHandling",
"JobLog",
};
/* keep sorted when changing */
static const char *allowed_finishing_groups[] = {
"Booklet",
"BookletCover",
"BookletModeOptions",
"FPFinishing1",
"FPFinishing2",
"FPFinishing3",
"FPFinishing4",
"Finishing",
"FinishingOptions",
"FinishingPage",
"HPBookletPanel",
"HPFinishing",
"HPFinishingOptions",
"HPFinishingPanel",
};
/* keep sorted when changing */
static const char *allowed_installable_options_groups[] = {
"InstallableOptions",
};
/* keep sorted when changing */
static const char *allowed_page_setup_groups[] = {
"HPMarginAndLayout",
"OutputControl",
"PaperHandling",
"Paper",
"Source",
};
/* keep sorted when changing */
static const char *disallowed_ppd_options[] = {
"Collate",
"Copies",
"Duplex",
"HPManualDuplexOrientation",
"HPManualDuplexSwitch",
"OutputOrder",
"PageRegion"
};
static int
strptr_cmp (const void *a,
const void *b)
{
char **aa = (char **)a;
char **bb = (char **)b;
return strcmp (*aa, *bb);
}
static gboolean
string_in_table (gchar *str,
const gchar *table[],
gint table_len)
{
return bsearch (&str, table, table_len, sizeof (char *), (void *)strptr_cmp) != NULL;
}
#define STRING_IN_TABLE(_str, _table) (string_in_table (_str, _table, G_N_ELEMENTS (_table)))
static const gchar *
ppd_option_name_translate (ppd_option_t *option)
{
gint i;
for (i = 0; i < G_N_ELEMENTS (ppd_option_translations); i++)
{
if (g_strcmp0 (ppd_option_translations[i].keyword, option->keyword) == 0)
{
if (ppd_option_translations[i].translation_context)
return g_dpgettext2(NULL, ppd_option_translations[i].translation_context, ppd_option_translations[i].translation);
else
return _(ppd_option_translations[i].translation);
}
}
return option->text;
}
static gint
grid_get_height (GtkWidget *grid)
{
GList *children;
GList *child;
gint height = 0;
gint top_attach = 0;
gint max = 0;
children = gtk_container_get_children (GTK_CONTAINER (grid));
for (child = children; child; child = g_list_next (child))
{
gtk_container_child_get (GTK_CONTAINER (grid), child->data,
"top-attach", &top_attach,
"height", &height,
NULL);
if (height + top_attach > max)
max = height + top_attach;
}
g_list_free (children);
return max;
}
static gboolean
grid_is_empty (GtkWidget *grid)
{
GList *children;
children = gtk_container_get_children (GTK_CONTAINER (grid));
if (children)
{
g_list_free (children);
return FALSE;
}
else
{
return TRUE;
}
}
static GtkWidget *
ipp_option_add (IPPAttribute *attr_supported,
IPPAttribute *attr_default,
const gchar *option_name,
const gchar *option_display_name,
const gchar *printer_name,
GtkWidget *grid,
gboolean sensitive)
{
GtkStyleContext *context;
GtkWidget *widget;
GtkWidget *label;
gint position;
widget = (GtkWidget *) pp_ipp_option_widget_new (attr_supported,
attr_default,
option_name,
printer_name);
if (widget)
{
gtk_widget_show_all (widget);
gtk_widget_set_sensitive (widget, sensitive);
position = grid_get_height (grid);
label = gtk_label_new (option_display_name);
gtk_widget_show (GTK_WIDGET (label));
gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
context = gtk_widget_get_style_context (label);
gtk_style_context_add_class (context, "dim-label");
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start (label, 10);
gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
gtk_widget_set_margin_start (widget, 20);
gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
}
return widget;
}
static GtkWidget *
ppd_option_add (ppd_option_t option,
const gchar *printer_name,
GtkWidget *grid,
gboolean sensitive)
{
GtkStyleContext *context;
GtkWidget *widget;
GtkWidget *label;
gint position;
widget = (GtkWidget *) pp_ppd_option_widget_new (&option, printer_name);
if (widget)
{
gtk_widget_show_all (widget);
gtk_widget_set_sensitive (widget, sensitive);
position = grid_get_height (grid);
label = gtk_label_new (ppd_option_name_translate (&option));
gtk_widget_show (GTK_WIDGET (label));
gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
context = gtk_widget_get_style_context (label);
gtk_style_context_add_class (context, "dim-label");
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start (label, 10);
gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
gtk_widget_set_margin_start (widget, 20);
gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
}
return widget;
}
static GtkWidget *
tab_grid_new ()
{
GtkWidget *grid;
grid = gtk_grid_new ();
gtk_widget_show (GTK_WIDGET (grid));
gtk_container_set_border_width (GTK_CONTAINER (grid), 20);
gtk_grid_set_row_spacing (GTK_GRID (grid), 15);
return grid;
}
static void
tab_add (PpOptionsDialog *self,
const gchar *tab_name,
GtkWidget *grid)
{
GtkListStore *store;
GtkTreeIter iter;
GtkWidget *scrolled_window;
gboolean unref_store = FALSE;
gint id;
if (!grid_is_empty (grid))
{
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_show (GTK_WIDGET (scrolled_window));
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_container_add (GTK_CONTAINER (scrolled_window), grid);
id = gtk_notebook_append_page (self->notebook,
scrolled_window,
NULL);
if (id >= 0)
{
store = GTK_LIST_STORE (gtk_tree_view_get_model (self->categories_treeview));
if (!store)
{
store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
unref_store = TRUE;
}
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
CATEGORY_IDS_COLUMN, id,
CATEGORY_NAMES_COLUMN, tab_name,
-1);
if (unref_store)
{
gtk_tree_view_set_model (self->categories_treeview, GTK_TREE_MODEL (store));
g_object_unref (store);
}
}
}
else
{
g_object_ref_sink (grid);
g_object_unref (grid);
}
}
static void
category_selection_changed_cb (PpOptionsDialog *self)
{
GtkTreeModel *model;
GtkTreeIter iter;
gint id = -1;
if (gtk_tree_selection_get_selected (self->categories_selection, &model, &iter))
{
gtk_tree_model_get (model, &iter,
CATEGORY_IDS_COLUMN, &id,
-1);
}
if (id >= 0)
{
gtk_notebook_set_current_page (self->notebook, id);
}
}
static void
populate_options_real (PpOptionsDialog *self)
{
GtkTreeModel *model;
GtkTreeIter iter;
ppd_file_t *ppd_file;
GtkWidget *grid;
GtkWidget *general_tab_grid = tab_grid_new ();
GtkWidget *page_setup_tab_grid = tab_grid_new ();
GtkWidget *installable_options_tab_grid = tab_grid_new ();
GtkWidget *job_tab_grid = tab_grid_new ();
GtkWidget *image_quality_tab_grid = tab_grid_new ();
GtkWidget *color_tab_grid = tab_grid_new ();
GtkWidget *finishing_tab_grid = tab_grid_new ();
GtkWidget *advanced_tab_grid = tab_grid_new ();
gint i, j;
gtk_spinner_stop (self->spinner);
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->main_box));
if (self->ipp_attributes)
{
/* Add number-up option to Page Setup tab */
ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
"number-up-supported"),
g_hash_table_lookup (self->ipp_attributes,
"number-up-default"),
"number-up",
/* Translators: This option sets number of pages printed on one sheet */
_("Pages per side"),
self->printer_name,
page_setup_tab_grid,
self->sensitive);
/* Add sides option to Page Setup tab */
ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
"sides-supported"),
g_hash_table_lookup (self->ipp_attributes,
"sides-default"),
"sides",
/* Translators: This option sets whether to print on both sides of paper */
_("Two-sided"),
self->printer_name,
page_setup_tab_grid,
self->sensitive);
/* Add orientation-requested option to Page Setup tab */
ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
"orientation-requested-supported"),
g_hash_table_lookup (self->ipp_attributes,
"orientation-requested-default"),
"orientation-requested",
/* Translators: This option sets orientation of print (portrait, landscape...) */
_("Orientation"),
self->printer_name,
page_setup_tab_grid,
self->sensitive);
}
if (self->destination && self->ppd_filename)
{
ppd_file = ppdOpenFile (self->ppd_filename);
ppdLocalize (ppd_file);
if (ppd_file)
{
ppdMarkDefaults (ppd_file);
cupsMarkOptions (ppd_file,
self->destination->num_options,
self->destination->options);
for (i = 0; i < ppd_file->num_groups; i++)
{
for (j = 0; j < ppd_file->groups[i].num_options; j++)
{
grid = NULL;
if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_color_groups))
grid = color_tab_grid;
else if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_image_quality_groups))
grid = image_quality_tab_grid;
else if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_job_groups))
grid = job_tab_grid;
else if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_finishing_groups))
grid = finishing_tab_grid;
else if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_installable_options_groups))
grid = installable_options_tab_grid;
else if (STRING_IN_TABLE (ppd_file->groups[i].name,
allowed_page_setup_groups))
grid = page_setup_tab_grid;
if (!STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
disallowed_ppd_options))
{
if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
allowed_color_options))
grid = color_tab_grid;
else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
allowed_image_quality_options))
grid = image_quality_tab_grid;
else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
allowed_finishing_options))
grid = finishing_tab_grid;
else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
allowed_page_setup_options))
grid = page_setup_tab_grid;
if (!grid)
grid = advanced_tab_grid;
ppd_option_add (ppd_file->groups[i].options[j],
self->printer_name,
grid,
self->sensitive);
}
}
}
ppdClose (ppd_file);
}
}
self->ppd_filename_set = FALSE;
if (self->ppd_filename)
{
g_unlink (self->ppd_filename);
g_free (self->ppd_filename);
self->ppd_filename = NULL;
}
self->destination_set = FALSE;
if (self->destination)
{
cupsFreeDests (1, self->destination);
self->destination = NULL;
}
self->ipp_attributes_set = FALSE;
if (self->ipp_attributes)
{
g_hash_table_unref (self->ipp_attributes);
self->ipp_attributes = NULL;
}
/* Translators: "General" tab contains general printer options */
tab_add (self, C_("Printer Option Group", "General"), general_tab_grid);
/* Translators: "Page Setup" tab contains settings related to pages (page size, paper source, etc.) */
tab_add (self, C_("Printer Option Group", "Page Setup"), page_setup_tab_grid);
/* Translators: "Installable Options" tab contains settings of presence of installed options (amount of RAM, duplex unit, etc.) */
tab_add (self, C_("Printer Option Group", "Installable Options"), installable_options_tab_grid);
/* Translators: "Job" tab contains settings for jobs */
tab_add (self, C_("Printer Option Group", "Job"), job_tab_grid);
/* Translators: "Image Quality" tab contains settings for quality of output print (e.g. resolution) */
tab_add (self, C_("Printer Option Group", "Image Quality"), image_quality_tab_grid);
/* Translators: "Color" tab contains color settings (e.g. color printing) */
tab_add (self, C_("Printer Option Group", "Color"), color_tab_grid);
/* Translators: "Finishing" tab contains finishing settings (e.g. booklet printing) */
tab_add (self, C_("Printer Option Group", "Finishing"), finishing_tab_grid);
/* Translators: "Advanced" tab contains all others settings */
tab_add (self, C_("Printer Option Group", "Advanced"), advanced_tab_grid);
/* Select the first option group */
if ((model = gtk_tree_view_get_model (self->categories_treeview)) != NULL &&
gtk_tree_model_get_iter_first (model, &iter))
gtk_tree_selection_select_iter (self->categories_selection, &iter);
}
static void
printer_get_ppd_cb (const gchar *ppd_filename,
gpointer user_data)
{
PpOptionsDialog *self = (PpOptionsDialog *) 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 &&
self->ipp_attributes_set)
{
populate_options_real (self);
}
}
static void
get_named_dest_cb (cups_dest_t *dest,
gpointer user_data)
{
PpOptionsDialog *self = (PpOptionsDialog *) user_data;
if (self->destination)
cupsFreeDests (1, self->destination);
self->destination = dest;
self->destination_set = TRUE;
if (self->ppd_filename_set &&
self->ipp_attributes_set)
{
populate_options_real (self);
}
}
static void
get_ipp_attributes_cb (GHashTable *table,
gpointer user_data)
{
PpOptionsDialog *self = (PpOptionsDialog *) user_data;
if (self->ipp_attributes)
g_hash_table_unref (self->ipp_attributes);
self->ipp_attributes = table;
self->ipp_attributes_set = TRUE;
if (self->ppd_filename_set &&
self->destination_set)
{
populate_options_real (self);
}
}
static void
populate_options (PpOptionsDialog *self)
{
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
/*
* Options which we need to obtain through an IPP request
* to be able to fill the options dialog.
* *-supported - possible values of the option
* *-default - actual value of the option
*/
const gchar *attributes[] =
{ "number-up-supported",
"number-up-default",
"sides-supported",
"sides-default",
"orientation-requested-supported",
"orientation-requested-default",
NULL};
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->spinner));
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Categories", renderer,
"text", CATEGORY_NAMES_COLUMN, NULL);
gtk_tree_view_column_set_expand (column, TRUE);
gtk_tree_view_append_column (self->categories_treeview, column);
gtk_spinner_start (self->spinner);
printer_get_ppd_async (self->printer_name,
NULL,
0,
printer_get_ppd_cb,
self);
get_named_dest_async (self->printer_name,
get_named_dest_cb,
self);
get_ipp_attributes_async (self->printer_name,
(gchar **) attributes,
get_ipp_attributes_cb,
self);
}
static void
pp_maintenance_command_execute_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
pp_maintenance_command_execute_finish (command, res, NULL);
g_object_unref (command);
}
static gchar *
get_testprint_filename (const gchar *datadir)
{
const gchar *testprint[] = { "/data/testprint",
"/data/testprint.ps",
NULL };
gchar *filename = NULL;
gint i;
for (i = 0; testprint[i] != NULL; i++)
{
filename = g_strconcat (datadir, testprint[i], NULL);
if (g_access (filename, R_OK) == 0)
break;
g_clear_pointer (&filename, g_free);
}
return filename;
}
static void
print_test_page_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
pp_printer_print_file_finish (PP_PRINTER (source_object),
result, NULL);
g_object_unref (source_object);
}
static void
test_page_cb (PpOptionsDialog *self)
{
gint i;
if (self->printer_name)
{
const gchar *const dirs[] = { "/usr/share/cups",
"/usr/local/share/cups",
NULL };
const gchar *datadir = NULL;
g_autofree gchar *filename = NULL;
datadir = getenv ("CUPS_DATADIR");
if (datadir != NULL)
{
filename = get_testprint_filename (datadir);
}
else
{
for (i = 0; dirs[i] != NULL && filename == NULL; i++)
filename = get_testprint_filename (dirs[i]);
}
if (filename != NULL)
{
PpPrinter *printer;
printer = pp_printer_new (self->printer_name);
pp_printer_print_file_async (printer,
filename,
/* Translators: Name of job which makes printer to print test page */
_("Test Page"),
NULL,
print_test_page_cb,
NULL);
}
else
{
PpMaintenanceCommand *command;
command = pp_maintenance_command_new (self->printer_name,
"PrintSelfTestPage",
NULL,
/* 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, NULL);
}
}
}
PpOptionsDialog *
pp_options_dialog_new (gchar *printer_name,
gboolean sensitive)
{
PpOptionsDialog *self;
self = g_object_new (pp_options_dialog_get_type (),
"use-header-bar", 1,
NULL);
self->printer_name = g_strdup (printer_name);
self->sensitive = sensitive;
gtk_window_set_title (GTK_WINDOW (self), printer_name);
populate_options (self);
return self;
}
static void
pp_options_dialog_dispose (GObject *object)
{
PpOptionsDialog *self = PP_OPTIONS_DIALOG (object);
g_free (self->printer_name);
self->printer_name = NULL;
if (self->ppd_filename)
{
g_unlink (self->ppd_filename);
g_free (self->ppd_filename);
self->ppd_filename = NULL;
}
if (self->destination)
{
cupsFreeDests (1, self->destination);
self->destination = NULL;
}
if (self->ipp_attributes)
{
g_hash_table_unref (self->ipp_attributes);
self->ipp_attributes = NULL;
}
G_OBJECT_CLASS (pp_options_dialog_parent_class)->dispose (object);
}
void
pp_options_dialog_class_init (PpOptionsDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = pp_options_dialog_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/pp-options-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_selection);
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_treeview);
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, main_box);
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, notebook);
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, spinner);
gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, stack);
gtk_widget_class_bind_template_callback (widget_class, category_selection_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, test_page_cb);
}
void
pp_options_dialog_init (PpOptionsDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}