#!/usr/bin/python

## system-config-printer
## Edit Queue dialog implementation

## Copyright (C) 2001-2004 Red Hat, Inc.
## Copyright (C) 2002-2004 Tim Waugh <twaugh@redhat.com>

## 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., 675 Mass Ave, Cambridge, MA 02139, USA.

import gettext
import gtk
import gtk.glade
import gnome
import gobject
import os
import pyalchemist
import re
import string

domain = 'printconf'
from rhpl.translate import _, N_
gtk.glade.bindtextdomain (domain, '/usr/share/locale')

busy_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
ready_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)

import locale
def C_float(fstr):
    """
    Convert a floating point number expressed as a string that
    uses '.' as the radix char into a float.
    """
    return float (str (fstr).
		  replace (".", locale.nl_langinfo (locale.RADIXCHAR)))

def complain (window, msg):
    """Put up an error message dialog."""
    d = gtk.MessageDialog (window, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
    d.set_transient_for (window)
    d.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
    d.run ()
    d.destroy ()

class editQueue:
    """The dialog implementation for editing an existing queue."""

    def __init__ (self, parent, xml):
        self.parent = parent

        k = {"columns": _("Number of columns on a page"),
             "cpi": _("Characters per inch"),
             "job-billing": _("Billing label"),
             "job-hold-until": _("HH:MM:SS or '%s'.\n"
                                     "Use GMT.") % "indefinite",
             "job-sheets": _("Banner pages (start,end):\n"
                                 "for example, '%s'") % "standard",
             "landscape": _("Landscape (%s or %s)") % ("true",
                                                           "false"),
             "lpi": _("Lines per inch"),
             "number-up": _("Number of document pages per\n"
                                "printed page: 1, 2, 4, 6, 9, or 16"),
             "page-top": _("Top margin in pt (1/72 in)"),
             "page-left": _("Left margin in pt (1/72 in)"),
             "page-right": _("Right margin in pt (1/72 in)"),
             "page-bottom": _("Bottom margin in pt (1/72 in)"),
             "page-border": _("'%s', '%s', '%s'\n"
                                  "'%s' or '%s'") % ("none",
                                                     "single",
                                                     "single-thick",
                                                     "double",
                                                     "double-thick"),
             "page-label": _("Page label"),
             "prettyprint": _("Pretty-print text (%s or %s)") % \
                                ("true", "false"),
             "scaling": _("Scaling (percentage)"),
             "wrap": _("Word-wrapping (%s or %s)") % ("true",
                                                          "false")}
        self.known_options = k

        self.sheet_types = [ "none",
                             "standard",
                             "classified",
                             "confidential",
                             "secret",
                             "topsecret",
                             "unclassified" ]

        # Widgets.
        self.window = xml.get_widget ('editQueueDialog')
        self.notebook = xml.get_widget ('edit_queue_notebook')
        self.type_menu = xml.get_widget ('edit_queue_type_menu')
        self.type_notebook = xml.get_widget ('edit_queue_type_notebook')
        self.name_entry = xml.get_widget ('edit_queue_name_entry')
        self.desc_entry = xml.get_widget ('edit_queue_description_entry')
        self.device_view = xml.get_widget ('edit_queue_device_view')
	self.ipp_server_entry = xml.get_widget ('edit_queue_ipp_server_entry')
	self.ipp_path_entry = xml.get_widget ('edit_queue_ipp_path_entry')
	self.lpd_server_entry = xml.get_widget ('edit_queue_lpd_server_entry')
	self.lpd_queue_entry = xml.get_widget ('edit_queue_lpd_queue_entry')
	self.rfc1179_cb = xml.get_widget ('edit_queue_lpd_rfc1179_checkbutton')
	self.smb_share_entry = xml.get_widget ('edit_queue_smb_share_entry')
	self.smb_user_entry = xml.get_widget ('edit_queue_smb_user_entry')
	self.smb_host_entry = xml.get_widget ('edit_queue_smb_host_entry')
	self.smb_passwd_entry = xml.get_widget ('edit_queue_smb_passwd_entry')
	self.smb_group_entry = xml.get_widget ('edit_queue_smb_group_entry')
	self.smb_lf_cb = xml.get_widget ('edit_queue_smb_lf_checkbutton')
	self.ncp_server_entry = xml.get_widget ('edit_queue_ncp_server_entry')
	self.ncp_user_entry = xml.get_widget ('edit_queue_ncp_user_entry')
	self.ncp_queue_entry = xml.get_widget ('edit_queue_ncp_queue_entry')
	self.ncp_passwd_entry = xml.get_widget ('edit_queue_ncp_passwd_entry')
	self.jd_printer_entry = xml.get_widget ('edit_queue_jd_printer_entry')
	self.jd_port_entry = xml.get_widget ('edit_queue_jd_port_entry')
        self.start_banner_menu = xml.get_widget ('start_banner_menu')
        self.end_banner_menu = xml.get_widget ('end_banner_menu')
        self.imageable_top_spinbutton = xml.get_widget \
                                        ('imageable_top_spinbutton')
        self.imageable_left_spinbutton = xml.get_widget \
                                         ('imageable_left_spinbutton')
        self.imageable_right_spinbutton = xml.get_widget \
                                          ('imageable_right_spinbutton')
        self.imageable_bottom_spinbutton = xml.get_widget \
                                           ('imageable_bottom_spinbutton')
        self.options_view = xml.get_widget ('edit_queue_options_view')
        self.opt_edit_button = xml.get_widget ('queue_options_edit_button')
        self.opt_remove_button = xml.get_widget ('queue_options_remove_button')
        self.mfr_menu = xml.get_widget ('edit_queue_mfr_menu')
        self.printer_view = xml.get_widget ('edit_queue_printer_view')
        self.drivers_menu = xml.get_widget ('drivers_menu')
        self.recommended_label = xml.get_widget ('recommended_label')
	self.notes_button = xml.get_widget ('edit_queue_notes_button')
        self.options_table = xml.get_widget ('edit_queue_driver_options_table')
	self.custom_dialog = xml.get_widget ('customDeviceDialog')
	self.custom_device_entry = xml.get_widget ('device_entry')
        self.option_dialog = xml.get_widget ('optionDialog')
        self.option_combo = xml.get_widget ('queue_option_combo')
        self.option_value_label = xml.get_widget ('queue_option_value_label')
        self.option_value_entry = xml.get_widget ('queue_option_value_entry')

        self.margin_widgets = { "top": self.imageable_top_spinbutton,
                                "left": self.imageable_left_spinbutton,
                                "right": self.imageable_right_spinbutton,
                                "bottom": self.imageable_bottom_spinbutton }

        # Storage for the device list.
        self.device_store = gtk.TreeStore (str, str)
        self.device_store.set_sort_column_id (0, gtk.SORT_ASCENDING)
        self.device_view.set_model (self.device_store)
        self.device_view.set_search_column (0)

        # Device list columns.
        col = gtk.TreeViewColumn (_("Device"), gtk.CellRendererText (),
                                  text=0)
        col.set_resizable (gtk.TRUE)
        col.set_sort_column_id (0)
        self.device_view.append_column (col)

        col = gtk.TreeViewColumn (_("Description"), gtk.CellRendererText (),
                                  text=1)
        col.set_resizable (gtk.TRUE)
        col.set_sort_column_id (1)
        self.device_view.append_column (col)

        # Storage for the queue options list.
        self.options_store = gtk.TreeStore (str, str)
        self.options_view.set_model (self.options_store)

        # Queue options list columns.
        col = gtk.TreeViewColumn (_("Option name"), gtk.CellRendererText (),
                                  text = 0)
        col.set_resizable (gtk.TRUE)
        col.set_sort_column_id (0)
        self.options_view.append_column (col)

        col = gtk.TreeViewColumn (_("Value"), gtk.CellRendererText (),
                                  text = 1)
        col.set_resizable (gtk.TRUE)
        self.options_view.append_column (col)

        self.options_store.set_sort_column_id (0, gtk.SORT_ASCENDING)

        # Storage for the printer list.
        self.printer_store = gtk.TreeStore (str, gobject.TYPE_PYOBJECT)
        self.printer_view.set_model (self.printer_store)

        # Printer list columns.
        col = gtk.TreeViewColumn (_("Model"), gtk.CellRendererText (),
                                  text=0)
        col.set_resizable (gtk.TRUE)
        col.set_sort_column_id (0)
        self.printer_view.append_column (col)

        # Printer select function
        slct = self.printer_view.get_selection ()
        slct.set_select_function (self.printer_select_function)

        # Signals.
        self.window.connect ('destroy', self.destroy)
        xml.signal_connect ('on_edit_queue_type_menu_changed',
                            self.type_menu_changed)
        xml.signal_connect ('on_edit_queue_rescan_devices_button_clicked',
                            self.rescan_devices_button_clicked)
	xml.signal_connect ('on_edit_queue_custom_button_clicked',
			    self.custom_button_clicked)
	xml.signal_connect ('on_autoselect_driver_button_clicked',
			    self.autoselect_driver)
        xml.signal_connect ('on_queue_options_add_button_clicked',
                            self.add_queue_option)
        xml.signal_connect ('on_queue_options_edit_button_clicked',
                            self.edit_queue_option)
        xml.signal_connect ('on_queue_options_remove_button_clicked',
                            self.remove_queue_option)
        xml.signal_connect ('on_queue_options_defaults_button_clicked',
                            self.default_queue_options)
        xml.signal_connect ('on_options_view_cursor_changed',
                            self.option_cursor_changed)
        xml.signal_connect ('on_options_view_row_activated',
                            self.edit_queue_option)
        xml.signal_connect ('on_queue_option_combo_list_selection_changed',
                            self.option_combo_list_selection_changed)
        xml.signal_connect ('on_edit_queue_mfr_menu_changed',
                            self.mfr_menu_changed)
        xml.signal_connect ('on_edit_queue_printer_view_cursor_changed',
                            self.printer_model_selected)
        xml.signal_connect ('on_drivers_menu_changed',
                            self.drivers_menu_changed)
        xml.signal_connect ('on_edit_queue_notes_button_clicked',
                            self.notes_button_clicked)
        xml.signal_connect ('on_edit_queue_notebook_switch_page',
                            self.notebook_switch_page)

# -------------------------------
# Handle the window being deleted
# -------------------------------
    def destroy (self, dialog):
        """Callback for the window being deleted."""
        dialog.hide ()

# ---------------------------
# Greying-out the main window
# ---------------------------
    def busy (self):
        """Set the dialog window insensitive."""
        self.window.set_sensitive (gtk.FALSE)
        self.window.window.set_cursor (busy_cursor)
        while gtk.events_pending():
            gtk.mainiteration()

    def ready (self):
        """Set the dialog window sensitive."""
        self.window.window.set_cursor (ready_cursor)
        self.window.set_sensitive (gtk.TRUE)

#---------------
# Run the dialog
#---------------
    def editQueueDialog (self, iter):
	"""
	Run a dialog for editing the queue.

	iter: Iter from queue tree.
	"""

        # Set main notebook to first page.
        self.notebook.set_current_page (0)

        # Fill in the name page.
	self.queue_tree_iter = iter
        name = self.parent.queue_store.get_value (iter, 1)
        self.queue = self.parent.name_dict[name]["queue"]
        self.name_entry.set_text (name)

        try:
            self.desc_entry.set_text (self.queue["queue_description"].value)
        except:
            self.desc_entry.set_text ('')

        # Fill in the type page.

	# Local device tab
        self.rescan_devices ()
        # IPP tab defaults
        self.ipp_server_entry.set_text ('')
        self.ipp_path_entry.set_text ('/printers/queue1')
	# LPD tab defaults
	self.lpd_server_entry.set_text ('')
	self.lpd_queue_entry.set_text ('')
	self.rfc1179_cb.set_active (gtk.FALSE)
	# SMB tab defaults
	self.smb_share_entry.set_text ('')
	self.smb_user_entry.set_text ('')
	self.smb_host_entry.set_text ('')
	self.smb_passwd_entry.set_text ('')
	self.smb_group_entry.set_text ('')
	self.smb_lf_cb.set_active (gtk.FALSE)
	# NCP tab defaults
	self.ncp_server_entry.set_text ('')
	self.ncp_user_entry.set_text ('')
	self.ncp_queue_entry.set_text ('')
	self.ncp_passwd_entry.set_text ('')
	# JetDirect tab defaults
	self.jd_printer_entry.set_text ('')
	self.jd_port_entry.set_text ('9100')

	# Set type values from configuration
	type = self.queue["queue_type"].value
	data = self.queue["queue_data"]
	if type == "LOCAL":
	    # If it's a currently visible device we've already selected it
	    # (in rescan_devices).  So we just need to deal with devices
	    # that we didn't already see.
	    selection = self.device_view.get_selection ()
	    store, iter = selection.get_selected ()
	    if not iter:
		# This is a device we don't know about.
		dev = data["local_printer_device"].value
		iter = store.append (None)
		store.set_value (iter, 0, dev)
		store.set_value (iter, 1, _("Custom device"))
		selection.select_iter (iter)
        elif type == "IPP":
            self.ipp_server_entry.set_text (data["ipp_server"].value)
            self.ipp_path_entry.set_text (data["ipp_path"].value)
	elif type == "LPD":
	    self.lpd_server_entry.set_text (data["lpd_server"].value)
	    self.lpd_queue_entry.set_text (data["lpd_queue"].value)
	    self.rfc1179_cb.set_active (data["lpd_strict_rfc1179"].value)
	elif type == "SMB":
	    self.smb_share_entry.set_text (data["smb_share"].value)
	    self.smb_user_entry.set_text (data["smb_user"].value)
	    self.smb_host_entry.set_text (data["smb_ip"].value)
	    self.smb_passwd_entry.set_text (data["smb_password"].value)
	    self.smb_group_entry.set_text (data["smb_workgroup"].value)
	    self.smb_lf_cb.set_active (data["smb_translate"].value)
	elif type == "NCP":
	    self.ncp_server_entry.set_text (data["ncp_server"].value)
	    self.ncp_user_entry.set_text (data["ncp_user"].value)
	    self.ncp_queue_entry.set_text (data["ncp_queue"].value)
	    self.ncp_passwd_entry.set_text (data["ncp_password"].value)
	elif type == "JETDIRECT":
	    self.jd_printer_entry.set_text (data["jetdirect_ip"].value)
	    self.jd_port_entry.set_text (data["jetdirect_port"].value)

	self.typedata = self.parent.conf.NameSpace ()
	self.parent.conf.typespace_setup (self.queue, self.typedata)

        self.type_menu.set_history (self.parent.queue_type_names.
				    index(self.queue["queue_type"].value))
	self.type_menu_changed (self.type_menu)

        # Fill in the queue options page.

        # banner sheets
        try:
            jobsheets = self.queue["jobsheets"]
        except:
            pass

        sheets = { "start": self.start_banner_menu,
                   "end": self.end_banner_menu }
        for each in sheets.keys ():
            menu = sheets[each]
            try:
                sheet = jobsheets[each].value
                menu.set_history (self.sheet_types.index (sheet))
            except:
                menu.set_history (0)

        # imageable area margins
        m = {}
        try:
            margins = self.queue["margins"]
            m["left"] = margins["left"].value
            m["top"] = margins["top"].value
            m["right"] = margins["right"].value
            m["bottom"] = margins["bottom"].value
        except:
            m = self.parent.conf.conf.default_margins

        for each in self.margin_widgets.keys ():
            self.margin_widgets[each].set_value (m[each])

        # filter options
        try:
            lpoptions = self.queue["lpoptions"]
        except:
            lpoptions = {}

        store = self.options_store
        store.clear ()
        for opt in lpoptions.keys ():
            iter = store.append (None)
            store.set_value (iter, 0, opt)
            store.set_value (iter, 1, lpoptions[opt])

        for b in [self.opt_edit_button, self.opt_remove_button]:
            b.set_sensitive (gtk.FALSE)

        # Prepare the driver options page.
        self.driver_namespace = self.parent.conf.NameSpace ()
        self.parent.conf.driverspace_setup (self.queue, self.driver_namespace)
        self.write_driver_options ()
	self.driver_options_up_to_date = 1

        # Fill in the driver page.
        self.mfr_list = self.parent.populate_mfr_optionmenu (self.mfr_menu,
                                                             self.queue)
	self.id_to_iter = {}
        iter = self.parent.populate_model_store (self.printer_store,
                                                 self.queue,
                                                 id_dict = self.id_to_iter,
                                                 window = self.window.window)
        self.select_printer_iter (iter)

        # Run the dialog.
        self.window.set_transient_for (self.parent.toplevel)
        self.window.set_position (gtk.WIN_POS_NONE)
        self.parent.ready ()
	backup_ctx = self.parent.conf.queue_edit.dynamic_queue_ctx.copy ()
	while 1:
	    response = self.window.run ()

	    # Deal with the consequences.
	    if response == gtk.RESPONSE_OK:
		if (self.validate_name_and_pull () or
		    self.validate_type_and_pull () or
                    self.validate_options_and_pull () or
		    self.validate_driver ()):
		    continue

		if not self.driver_options_up_to_date:
		    self.window.set_sensitive (gtk.FALSE)
		    while gtk.events_pending():
			gtk.mainiteration()

		    self.write_driver_options ()

		    self.window.set_sensitive (gtk.TRUE)

		self.read_driver_options ()

		self.parent.conf.driverspace_apply (self.queue,
						    self.driver_namespace)

		# Adjust the queue tree to reflect the new name if it's
		# changed.
		self.parent.queue_store.set_value (self.queue_tree_iter, 1,
						   self.queue.name)
                self.parent.queue_store.set_value (self.queue_tree_iter, 2,
                                                   self.
                                                   queue["queue_description"])
		break

	    elif (response == gtk.RESPONSE_CANCEL or
                  response == gtk.RESPONSE_DELETE_EVENT):
		self.parent.conf.dynamic_queue_ctx = backup_ctx
		break

	    elif response == gtk.RESPONSE_HELP:
		page = self.notebook.get_current_page ()
		if page == 1:
		    html = "printconf-modify.html#S2-PRINTING-EDIT-QUEUETYPE"
		elif page == 2:
		    html = "printconf-modify.html#S2-PRINTING-EDIT-DRIVER"
		elif page == 3:
		    html = "printconf-modify.html#S2-PRINTING-EDIT-DRIVER-OPTIONS"
		else:
		    html = "printconf-modify.html#S2-PRINTING-EDIT-NAMES"

		gnome.url_show ("file://%s/%s" %
				(self.parent.conf.conf.printconf_help_dir,
				 html))
            elif response == 1: # 'Sharing...' button
                # Make the dialog transient for us, not the main window.
                parent_window = self.parent.toplevel
                self.parent.toplevel = self.window
                self.parent.sharing_button_clicked ()
                self.parent.toplevel = parent_window

        self.destroy (self.window)
	return response == gtk.RESPONSE_OK

#-------------------------------------
# Helper functions used by this module
#-------------------------------------
    def select_printer_iter (self, iter):
	"""
	Select iter, scrolling to the newly-selected row.  Call
	self.printer_model_selected.
	"""

        self.printer_view.get_selection ().select_iter (iter)
        path = self.printer_store.get_path (iter)
        col = self.printer_view.get_column (0)
        self.printer_view.scroll_to_cell (path, col, gtk.TRUE, 0.5, 0)
        self.printer_model_selected (self.printer_view)

    def rescan_devices (self, force = None):
	"""
	Rescan the local printer devices.

	force: true if we want to force a rescan.
	"""

        select = None
        try:
            if self.queue["queue_type"].value == "LOCAL":
                select = self.queue["queue_data"]["local_printer_device"].value
        except:
            pass

        self.populate_device_view (select, force)

    def show_driver_page (self):
	"""Set the notebook to the driver page."""
	self.notebook.set_current_page (3)

    def populate_device_view (self, select = None, force = None):
	"""
	Populate the list of local printer devices.

	force: true if a scan should be forced.
	"""

        self.local_devs = self.parent.conf.scan_local_printer_devices (force)
	store = self.device_store
        store.clear ()
        for dev in self.local_devs.keys ():
            iter = store.append (None)
            store.set_value (iter, 0, dev)
            try:
                auto = self.local_devs[dev]["auto"]
		description = auto.get("desc")
                if not description:
                    description = "%s %s" % (auto["manufacturer"],
                                             auto["model"])
            except:
                description = ""

            store.set_value (iter, 1, description)
            if dev == select:
                self.device_view.get_selection ().select_iter (iter)

#----------------
# Queue type page
#----------------
    def rescan_devices_button_clicked (self, button):
	"""Handle the rescan button."""
        self.rescan_devices (force = 1)

    def custom_button_clicked (self, button):
	"""Handle the custom device button."""
	dialog = self.custom_dialog
	self.custom_device_entry.set_text ('')
        dialog.set_transient_for (self.window)
        dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
	response = dialog.run ()
	device = self.custom_device_entry.get_text ()
	dialog.hide ()
	if response != gtk.RESPONSE_OK or not device:
	    return

	if not os.access (device, os.W_OK):
	    complain (self.window,
		      _("'%s' does not exist, or is not writable.") % device)
	    return

	iter = self.device_store.append (None)
	self.device_store.set_value (iter, 0, device)
	self.device_store.set_value (iter, 1, _("Custom device"))
	self.device_view.get_selection ().select_iter (iter)

    def autoselect_driver (self, button):
	"""Handler for the autoselect button."""
	selection = self.device_view.get_selection ()
	store, iter = selection.get_selected ()
	if not iter:
	    return

	dev = store.get_value (iter, 0)
	try:
	    mfr = string.lower (self.local_devs[dev]["auto"]["manufacturer"])
	    mdl = string.lower (self.local_devs[dev]["auto"]["model"])
	except:
            complain (self.window, _("Can't determine the printer model "
                                     "attached to this device."))
	    return

	try:
	    id = self.parent.conf.foomatic.autodetect_dict[mfr,mdl].id
	except:
            complain (self.window, _("I don't know enough about this "
                                     "printer model to choose a driver."))
	    return

	iter = self.id_to_iter[id]
	self.select_printer_iter (iter)
	self.show_driver_page ()

    def type_menu_changed (self, optionmenu):
	"""Set the notebook page to match the optionmenu."""
	index = optionmenu.get_history ()
        self.type_notebook.set_current_page (index)
	self.typedata.queue_type_space = self.parent.queue_types[index]

#-------------------
# Queue options page
#-------------------
    def add_or_edit_queue_option (self, iter = None):
        """Run the queue option dialog.
        iter: None if adding option, otherwise options_store iter of item
        to be edited."""

        store = self.options_store
        d = self.option_dialog

        known_options = []
        current_options = []
	try:
	    this_name = store.get_value (iter, 0)
	except:
	    this_name = None

        for row in store:
            i = store.get_iter (row.path)
	    name = store.get_value (i, 0)
	    if this_name == name:
		continue

            current_options.append (store.get_value (i, 0))

        for opt in self.known_options.keys ():
            try:
                if current_options.index (opt):
                    continue
            except:
                known_options.append (opt)

        known_options.sort ()
        self.option_combo.set_popdown_strings (known_options)

        if iter:
            d.set_title (_("Edit queue option"))
            self.option_combo.entry.set_text (this_name)
            self.option_value_entry.set_text (store.get_value (iter, 1))
            self.option_value_entry.grab_focus ()
        else:
            d.set_title (_("Add queue option"))
            self.option_combo.entry.set_text ('')
            self.option_value_entry.set_text ('')

        d.set_transient_for (self.window)
        d.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
        while 1:
            response = d.run ()
            if response == gtk.RESPONSE_OK:
                # Validate
                option = self.option_combo.entry.get_text ()
                value = self.option_value_entry.get_text ()

                valid_option_re = re.compile ("^[a-zA-Z_][-a-zA-Z0-9_]*$")
                if not valid_option_re.match (option):
                    complain (d, _("Invalid option name"))
                    self.option_combo.entry.grab_focus ()
                    continue

                if value == "":
                    value = "true"

                valid_value_re = re.compile ("^[-a-zA-Z0-9_.]*$")
                if not valid_value_re.match (value):
                    complain (d, _("Invalid value"))
                    self.option_value_entry.grab_focus ()
                    continue

                if not iter:
                    # Check for duplicates and remove if necessary
                    for row in store:
                        iter = store.get_iter (row.path)
                        if store.get_value (iter, 0) == option:
                            store.remove (iter)

                    # Add the new option
                    iter = store.append (None)

                store.set_value (iter, 0, option)
                store.set_value (iter, 1, value)
                self.options_view.get_selection ().select_iter (iter)
                for b in [self.opt_edit_button, self.opt_remove_button]:
                    b.set_sensitive (gtk.TRUE)

                break

            elif (response == gtk.RESPONSE_CANCEL or
                  response == gtk.RESPONSE_DELETE_EVENT):
                break

            elif response == gtk.RESPONSE_HELP:
                gnome.url_show ("file://%s/index.html" %
                                self.parent.conf.conf.printconf_help_dir)

        d.hide ()

    def add_queue_option (self, button):
        """Handler for queue options add button."""
        self.add_or_edit_queue_option ()

    def edit_queue_option (self, *args):
        """Handler for queue options edit button."""
        store, iter = self.options_view.get_selection ().get_selected ()
        self.add_or_edit_queue_option (iter)

    def remove_queue_option (self, button):
        """Handler for queue options remove button."""
        store, iter = self.options_view.get_selection ().get_selected ()
        store.remove (iter)
        for b in [self.opt_edit_button, self.opt_remove_button]:
            b.set_sensitive (gtk.FALSE)

    def default_queue_options (self, button):
        """Handler for queue options defaults button."""
        store = self.options_store
        store.clear ()
        lpoptions = self.parent.conf.conf.default_lpoptions
        for option in lpoptions.keys ():
            iter = store.append (None)
            store.set_value (iter, 0, option)
            store.set_value (iter, 1, lpoptions[option])

        for b in [self.opt_edit_button, self.opt_remove_button]:
            b.set_sensitive (gtk.FALSE)

    def option_cursor_changed (self, treeview):
        """Handler for options treeview cursor changed."""
        for b in [self.opt_edit_button, self.opt_remove_button]:
            b.set_sensitive (gtk.TRUE)

    def option_combo_list_selection_changed (self, list):
        """Handler for queue option combo list selection changed."""
        k = self.option_combo.entry.get_text ()
        if self.known_options.has_key (k):
            label = self.known_options[k]
        else:
            label = _("Value for this option")

        self.option_value_label.set_text (label)

#--------------------------
# Printer model/driver page
#--------------------------
    def printer_select_function (self, path):
        """Don't allow this path to be selected unless it is a leaf."""
        iter = self.printer_store.get_iter (path)
        return not self.printer_store.iter_has_child (iter)

    def mfr_menu_changed (self, menu):
        """Update the model list."""
        try:
            mfr = self.mfr_list[menu.get_history ()]
        except:
            return

	self.id_to_iter = {}
        iter = self.parent.populate_model_store (self.printer_store,
                                                 self.queue,
                                                 id_dict = self.id_to_iter,
                                                 mfr = mfr,
                                                 window = self.window.window)

        if not iter:
            iter = self.printer_store.get_iter_first ()

        self.select_printer_iter (iter)

    def printer_model_selected (self, treeview):
        """A printer model has been selected.  Fill in the driver menu."""
        make = self.mfr_list[self.mfr_menu.get_history ()]
        selection = treeview.get_selection ()
        store, model_iter = selection.get_selected ()
        d, id = store.get_value (model_iter, 1)
        if id:
            model = store.get_value (model_iter, 0)
            menu = gtk.Menu ()

            try:
                if (self.parent.conf.foomatic.make_model_dict_dict
                    [make][model].id ==
                    self.queue["filter_data"]["printer_id"].value):
                    this_driver = self.queue["filter_data"]["gs_driver"].value
                else:
                    this_driver = None
            except:
                this_driver = None

	    try:
		recommended = (self.parent.conf.foomatic.
			       make_model_dict_dict[make][model].driver)
	    except:
		recommended = (self.parent.conf.foomatic.
			       make_model_dict_dict[make][model].drivers[0])

            driver_index = None
            self.drivers = (self.parent.conf.foomatic.
                            make_model_dict_dict[make][model].drivers)
            for driver in self.drivers:
                menuitem = gtk.MenuItem (driver)
                menu.add (menuitem)
                menuitem.show ()
                menuitem.set_sensitive (gtk.TRUE)

                n = self.drivers.index (driver)
                if driver == recommended:
                    self.recommended_driver = n

		if this_driver != None:
		    if driver == this_driver:
			driver_index = n

		elif driver == recommended:
		    driver_index = n

	    if driver_index == None:
		driver_index = self.recommended_driver

            self.drivers_menu.set_sensitive (gtk.TRUE)
            self.drivers_menu.set_menu (menu)
            if driver_index != None:
                self.drivers_menu.set_history (driver_index)

	    self.notes_button.set_sensitive (gtk.TRUE)
        else:
	    self.notes_button.set_sensitive (gtk.FALSE)
            self.recommended_driver = -1
            self.drivers_menu.set_sensitive (gtk.FALSE)
            menu = gtk.Menu ()
	    menuitem = gtk.MenuItem (_("None"))
	    menu.add (menuitem)
	    menuitem.show ()
	    menuitem.set_sensitive (gtk.TRUE)
	    self.drivers_menu.set_menu (menu)
            self.drivers_menu_changed (self.drivers_menu)

    def drivers_menu_changed (self, optionmenu):
	"""Determine whether this is the recommended driver."""
        try:
            if self.recommended_driver == optionmenu.get_history ():
                self.recommended_label.\
                set_label (_('(this is the\nrecommended driver)'))
            else:
                self.recommended_label.\
                set_label (_('(recommended\ndriver is %s)')
                           % self.drivers[self.recommended_driver])
        except:
            self.recommended_label.set_label ('')

        selection = self.printer_view.get_selection ()
        store, iter = selection.get_selected ()
        type, printer_id = store.get_value (iter, 1)

        # Update self.driver_namespace to reflect the current settings.
	self.driver_namespace.f_type = type.filter_type
	try:
	    self.driver_namespace.foomatic.mf_type = type.mf_type
	except:
	    pass

	if type == self.parent.conf.drivers.foomatic:
	    gs_driver = self.drivers[optionmenu.get_history ()]
	    self.driver_namespace.foomatic.printer_id = printer_id
	    self.driver_namespace.foomatic.gs_driver = gs_driver
        else:
            self.recommended_label.set_label ('')

	# Driver options need redoing now.
	self.driver_options_up_to_date = 0

    def notes_button_clicked (self, button):
	"""Handler for the notes button."""
        selection = self.printer_view.get_selection ()
        store, iter = selection.get_selected ()
        type, printer_id = store.get_value (iter, 1)

        if printer_id:
            selected_driver = self.drivers_menu.get_history ()
            driver = self.drivers[selected_driver]
            self.parent.show_notes (printer_id, driver, self)

#--------------------
# Driver options page
#--------------------
    def wipe_driver_options (self):
        # Clear out the current options.
        for child in self.options_table.get_children ():
            self.options_table.remove (child)

        self.options_table.resize (1, 2)

        self.widget_list = []

    def write_driver_options (self):
        self.wipe_driver_options ()

        option_list = (self.parent.conf.
                       generate_option_list (self.driver_namespace))

        self.widget_list = []
        for option_tuple in option_list:
            (opt_type, en_shortname,
             prettyname, dict, default, type_data) = option_tuple
            def_val = dict.get ((en_shortname, opt_type), default)

            label_widget = gtk.Label (prettyname)
            label_widget.set_alignment (0.0, 0.5)

            if opt_type == "bool":
                data_widget = gtk.CheckButton ()
                data_widget.set_active (int (def_val))

            elif opt_type == "int":
                (max, min) = type_data
                val = int (def_val)
                adjustment = gtk.Adjustment (val, lower = int (min),
                                             upper = int (max),
                                             step_incr = 1, page_incr = 10)
                data_widget = gtk.SpinButton (adjustment)
                data_widget.set_digits (0)

            elif opt_type == "float":
                (max, min) = type_data
                val = C_float (def_val)
                adjustment = gtk.Adjustment (val, lower = C_float (min),
                                             upper = C_float (max),
                                             step_incr = 0.1, page_incr = 1)
                data_widget = gtk.SpinButton (adjustment)
                data_widget.set_digits (1)

            elif opt_type == "enum":
                val_list = type_data
                data_widget = gtk.Combo ()
                data_widget.entry.set_editable (0)
                set = def_val
                for (val, val_label) in val_list:
                    item = gtk.ListItem (label = val_label)
                    item.set_data ("value", val)
                    data_widget.list.append_items ([item])
                    item.show ()

                    if val == set:
                        item.select ()
            else:
                raise RuntimeError, "unknown type %s" % opt_type

            self.widget_list.append ((label_widget, data_widget, option_tuple))

        if not len (self.widget_list):
	    # Need to beautify this.
            label = gtk.Label (
                _("There are no options available for this driver."))
            self.options_table.resize (1, 1)
            self.options_table.attach (label, 0, 1, 0, 1, gtk.FILL)
        else:
            self.options_table.resize (len (self.widget_list), 2)
            for i in range (len (self.widget_list)):
                (label_widget, data_widget) = self.widget_list[i][:2]
                self.options_table.attach (label_widget, 0, 1, i, i+1,gtk.FILL)
                self.options_table.attach (data_widget, 1, 2, i, i+1)

        self.options_table.show_all ()

    def read_driver_options (self):
        for (label_widget, data_widget, option_tuple) in self.widget_list:
            (opt_type, en_shortname,
             prettyname, dict, default, type_data) = option_tuple
            key = (en_shortname, opt_type)

	    is_default = 1

            if opt_type == "bool":
                dict[key] = data_widget.get_active ()
		if bool (int (dict[key])) != bool (int (default)):
		    is_default = 0

            elif opt_type == "int":
                dict[key] = data_widget.get_value_as_int ()
		if int (dict[key]) != int (default):
		    is_default = 0

            elif opt_type == "float":
                dict[key] = data_widget.get_value ()
		if C_float (dict[key]) != C_float (default):
		    is_default = 0

            elif opt_type == "enum":
                dict[key] = data_widget.list.get_selection ()[0].\
			    get_data ("value")
		if dict[key] != default:
		    is_default = 0

#-------------------------
# Handle the main notebook
#-------------------------
    def notebook_switch_page (self, notebook, cobject, pageindex):
	"""Handler for the main notebook page-switch."""
        if pageindex < 4:
            return

        # User flipped to driver options page.
	# Did they change the driver since last time?
	if self.driver_options_up_to_date:
	    return

        notebook.set_sensitive (gtk.FALSE)
        while gtk.events_pending():
            gtk.mainiteration()

        self.write_driver_options ()
	self.driver_options_up_to_date = 1

        # Now safe to use.
        notebook.set_sensitive (gtk.TRUE)

#-----------
# Validation
#-----------
    def validate_name_and_pull (self):
	"""Returns true if validation failed."""
	
	def grab ():
	    self.notebook.set_current_page (0)
	    self.name_entry.grab_focus ()

	name = self.name_entry.get_text ()

	# Is the name even valid at all?
	if not self.parent.conf.valid_queue_name (name):
	    grab ()
	    complain (self.window, _("Invalid name"))
	    return 1

	# Is there already a queue (or alias) of that name?
	name_dict_dict, alias_dict_dict = self.parent.conf.get_queues ()
	if (name_dict_dict.has_key (name) and
	    name_dict_dict[name]["queue"] != self.queue):
	    grab ()
	    complain (self.window,
		      _("There is already a queue with that name."))
	    return 1

	if (alias_dict_dict.has_key (name) and
	    alias_dict_dict[name]["queue"] != self.queue):
	    grab ()
	    complain (self.window,
		      _("An existing queue has an alias of that name."))
	    return 1

	self.queue.name = name

        self.queue["queue_description"] = self.desc_entry.get_text ()
	return 0

    def show_type_page (self):
	self.notebook.set_current_page (1)

    def validate_type_and_pull (self):
	"""Returns true if validation failed."""
	
	# Check for missing packages.
	if not self.typedata.queue_type_space.check ():
	    ask = gtk.MessageDialog (self.window, 0, gtk.MESSAGE_WARNING,
				     gtk.BUTTONS_YES_NO,
				     self.typedata.queue_type_space.message)
            ask.set_transient_for (self.window)
            ask.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
	    response = ask.run ()
	    ask.destroy ()
	    if response != gtk.RESPONSE_YES:
		self.show_type_page ()
		return 1

	types = self.parent.conf.queue_types
	if self.typedata.queue_type_space == types.local:
	    if self.validate_local_type_and_pull ():
		return 1
        elif self.typedata.queue_type_space == types.ipp:
            if self.validate_ipp_type_and_pull ():
                return 1
	elif self.typedata.queue_type_space == types.lpd:
	    if self.validate_lpd_type_and_pull ():
		return 1
	elif self.typedata.queue_type_space == types.smb:
	    if self.validate_smb_type_and_pull ():
		return 1
	elif self.typedata.queue_type_space == types.ncp:
	    if self.validate_ncp_type_and_pull ():
		return 1
	elif self.typedata.queue_type_space == types.jetdirect:
            if self.validate_jetdirect_type_and_pull ():
                return 1

	# Push the type data back into the queue.
	self.parent.conf.typespace_apply (self.queue, self.typedata)
	return 0

    def validate_local_type_and_pull (self):
	"""Returns true if validation failed."""
	selection = self.device_view.get_selection ()
	store, iter = selection.get_selected ()
	if not iter:
	    complain (self.window, _("You must select a device."))
	    self.show_type_page ()
	    self.device_view.grab_focus ()
	    return 1

	local_printer_device = store.get_value (iter, 0)
	self.typedata.data["local_printer_device"] = local_printer_device
	return 0

    def validate_ipp_type_and_pull (self):
	"""Returns true if validation failed."""
	ipp_server = self.ipp_server_entry.get_text ()
	ipp_path = self.ipp_path_entry.get_text ()

	if not ipp_server:
	    complain (self.window, _("You must specify a server."))
	    self.show_type_page ()
	    self.ipp_server_entry.grab_focus ()
	    return 1

	if not ipp_path:
	    complain (self.window, _("You must specify a path."))
	    self.show_type_page ()
	    self.ipp_path_entry.grab_focus ()
	    return 1

	self.typedata.data["ipp_server"] = ipp_server
        self.typedata.data["ipp_port"] = "631"
	self.typedata.data["ipp_path"] = ipp_path
	return 0

    def validate_lpd_type_and_pull (self):
	"""Returns true if validation failed."""
	lpd_server = self.lpd_server_entry.get_text ()
	lpd_queue = self.lpd_queue_entry.get_text ()
	rfc1179 = self.rfc1179_cb.get_active ()

	if not lpd_server:
	    complain (self.window, _("You must specify a server."))
	    self.show_type_page ()
	    self.lpd_server_entry.grab_focus ()
	    return 1

	if not lpd_queue:
	    complain (self.window, _("You must specify a queue."))
	    self.show_type_page ()
	    self.lpd_queue_entry.grab_focus ()
	    return 1

	self.typedata.data["lpd_server"] = lpd_server
	self.typedata.data["lpd_queue"] = lpd_queue
	self.typedata.data["lpd_strict_rfc1179"] = rfc1179
	return 0

    def validate_smb_type_and_pull (self):
	"""Returns true if validation failed."""
	smb_share = self.smb_share_entry.get_text ()
	smb_user = self.smb_user_entry.get_text ()
	smb_ip = self.smb_host_entry.get_text ()
	smb_password = self.smb_passwd_entry.get_text ()
	smb_workgroup = self.smb_group_entry.get_text ()
	smb_translate = self.smb_lf_cb.get_active ()

	if not smb_share:
	    complain (self.window,
		      _("You must specify an SMB share to print to."))
	    self.show_type_page ()
	    self.smb_share_entry.grab_focus ()
	    return 1

       	self.typedata.data["smb_share"] = smb_share
	self.typedata.data["smb_user"] = smb_user
	self.typedata.data["smb_ip"] = smb_ip
	self.typedata.data["smb_password"] = smb_password
	self.typedata.data["smb_workgroup"] = smb_workgroup
	self.typedata.data["smb_translate"] = smb_translate
	return 0

    def validate_ncp_type_and_pull (self):
	"""Returns true if validation failed."""
	ncp_server = self.ncp_server_entry.get_text ()
	ncp_queue = self.ncp_queue_entry.get_text ()
	ncp_user = self.ncp_user_entry.get_text ()
	ncp_password = self.ncp_passwd_entry.get_text ()

	if not ncp_server:
	    complain (self.window,
		      _("You must specify an NCP server to print to."))
	    self.show_type_page ()
	    self.ncp_server_entry.grab_focus ()
	    return 1

	if not ncp_queue:
	    complain (self.window,
		      _("You must specify a queue on the NCP server."))
	    self.show_type_page ()
	    self.ncp_queue_entry.grab_focus ()
	    return 1

	self.typedata.data["ncp_server"] = ncp_server
	self.typedata.data["ncp_queue"] = ncp_queue
	self.typedata.data["ncp_user"] = ncp_user
	self.typedata.data["ncp_password"] = ncp_password
	return 0

    def validate_jetdirect_type_and_pull (self):
	"""Returns true if validation failed."""
	jetdirect_ip = self.jd_printer_entry.get_text ()
	port = self.jd_port_entry.get_text ()

	if not jetdirect_ip:
	    complain (self.window,
		      _("You must specify a JetDirect printer to print to."))
	    self.show_type_page ()
	    self.jd_printer_entry.grab_focus ()
	    return 1

	try:
	    jetdirect_port = int (port)
	except:
	    complain (self.window, _("You must specify an IP port number."))
	    self.show_type_page ()
	    self.jd_port_entry.grab_focus ()
	    return 1

	self.typedata.data["jetdirect_ip"] = jetdirect_ip
	self.typedata.data["jetdirect_port"] = jetdirect_port
	return 0

    def validate_options_and_pull (self):
        """Returns true if validation failed."""

        # Actually validation was already done when options were added
        # or edited.  So just pull.

        # banner pages
        try:
            self.queue["jobsheets"].unlink ()
        except:
            pass

        jobsheets = self.queue.addData (pyalchemist.AdmListType, "jobsheets")
        start = jobsheets.addData (pyalchemist.AdmStringType, "start")
        start.value = self.sheet_types[self.start_banner_menu.get_history ()]
        end = jobsheets.addData (pyalchemist.AdmStringType, "end")
        end.value = self.sheet_types[self.end_banner_menu.get_history ()]

        # imageable area margins
        try:
            self.queue["margins"].unlink ()
        except:
            pass

        margins = self.queue.addData (pyalchemist.AdmListType, "margins")
        for each in self.margin_widgets.keys ():
            c = margins.addData (pyalchemist.AdmIntType, each)
            c.value = self.margin_widgets[each].get_value ()

        # filter options
        try:
            self.queue["lpoptions"].unlink ()
        except:
            pass

        lpoptions = self.queue.addData (pyalchemist.AdmListType, "lpoptions")
        store = self.options_store
        for row in store:
            iter = store.get_iter (row.path)
            opt = lpoptions.addData (pyalchemist.AdmStringType,
                                     store.get_value (iter, 0))
            opt.value = store.get_value (iter, 1)

        return gtk.FALSE

    def validate_driver (self):
	"""Returns true if validation failed."""

	selection = self.printer_view.get_selection ()
	store, model_iter = selection.get_selected ()
	make_iter = store.iter_parent (model_iter)
	if not make_iter:
	    return 0

	# Check the blacklist.
	selected_driver = self.drivers_menu.get_history ()
	driver = self.drivers[selected_driver]
	blacklist = self.parent.conf.driver_blacklist
	try:
	    black = blacklist.dict[driver]
	except:
	    return 0

	if not black.check ():
	    ask = gtk.MessageDialog (self.window, 0, gtk.MESSAGE_WARNING,
                                     gtk.BUTTONS_YES_NO, black.message)
            ask.set_transient_for (self.window)
            ask.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
            response = ask.run ()
            ask.destroy ()
            if response != gtk.RESPONSE_YES:
                return 1

	return 0

# Local Variables:
# py-indent-offset: 4
# End:
