#!/usr/bin/python

## system-config-printer
## Add Queue druid implementation

## Copyright (C) 2001-2003 Red Hat, Inc.
## Copyright (C) 2002-2003 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 gnome
import gtk
import gtk.glade
import gobject
import os
import re
import signal
import string
import time

import pysmb

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)

def complain (window, msg):
    """Put up an error 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 addQueue:
    """The GNOME druid implementation for adding a new queue."""

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

        # Widgets.
        self.window = xml.get_widget ('addQueueWindow')
        self.druid = xml.get_widget ('add_queue_druid')
        self.type_menu = xml.get_widget ('add_queue_type_menu')
        self.notebook = xml.get_widget ('add_queue_type_notebook')
        self.name_entry = xml.get_widget ('add_queue_name_entry')
        self.desc_entry = xml.get_widget ('add_queue_description_entry')
        self.device_view = xml.get_widget ('add_queue_device_view')
        self.custom_dialog = xml.get_widget ('customDeviceDialog')
        self.custom_device_entry = xml.get_widget ('device_entry')
        self.ipp_server_entry = xml.get_widget ('add_queue_ipp_server_entry')
        self.ipp_path_entry = xml.get_widget ('add_queue_ipp_path_entry')
        self.lpd_server_entry = xml.get_widget ('add_queue_lpd_server_entry')
        self.lpd_queue_entry = xml.get_widget ('add_queue_lpd_queue_entry')
        self.smb_view = xml.get_widget ('add_queue_smb_view')
        self.passwd_dialog = xml.get_widget ('passwdDialog')
        self.smb_workgroup_entry = xml.get_widget ('smb_workgroup_entry')
        self.smb_server_entry = xml.get_widget ('smb_server_entry')
        self.smb_share_entry = xml.get_widget ('smb_share_entry')
        self.smb_user_entry = xml.get_widget ('smb_user_entry')
        self.smb_passwd_entry = xml.get_widget ('smb_passwd_entry')
        self.ncp_server_entry = xml.get_widget ('add_queue_ncp_server_entry')
        self.ncp_user_entry = xml.get_widget ('add_queue_ncp_user_entry')
        self.ncp_queue_entry = xml.get_widget ('add_queue_ncp_queue_entry')
        self.ncp_passwd_entry = xml.get_widget ('add_queue_ncp_passwd_entry')
        self.jd_printer_entry = xml.get_widget ('add_queue_jd_printer_entry')
        self.jd_port_entry = xml.get_widget ('add_queue_jd_port_entry')
        self.select_model_label = xml.get_widget ('add_queue_model_label')
        self.mfr_menu = xml.get_widget ('add_queue_mfr_menu')
        self.printer_view = xml.get_widget ('add_queue_printer_view')
        self.notes_button = xml.get_widget ('add_queue_notes_button')

        # 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 SMB list.
        self.smb_store = gtk.TreeStore (str, # host or share
                                        str, # comment
                                        gobject.TYPE_PYOBJECT, # host dict
                                        str, # username
                                        str) # password)
        self.smb_view.set_model (self.smb_store)
        self.device_store.set_sort_column_id (0, gtk.SORT_ASCENDING)

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

        col = gtk.TreeViewColumn (_("Comment"), gtk.CellRendererText (),
                                  text=1)
        self.smb_view.append_column (col)

        # 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_add_queue_start_page_prepare',
                            self.prepare_start_page)
        xml.signal_connect ('on_add_queue_druid_cancel', self.destroy)
        xml.signal_connect ('on_add_queue_druid_help', self.help)
        xml.signal_connect ('on_add_queue_rescan_button_clicked',
                            self.rescan_button_clicked)
        xml.signal_connect ('on_add_queue_custom_button_clicked',
                            self.custom_button_clicked)
        xml.signal_connect ('on_add_queue_type_menu_changed',
                            self.type_menu_changed)
        xml.signal_connect ('on_add_queue_notes_button_clicked',
                            self.notes_button_clicked)
        xml.signal_connect ('on_add_queue_name_page_prepare',
                            self.prepare_name_page)
        xml.signal_connect ('on_add_queue_name_page_next',
                            self.validate_name_page)
        xml.signal_connect ('on_add_queue_type_page_prepare',
                            self.prepare_type_page)
        xml.signal_connect ('on_add_queue_smb_view_row_activated',
                            self.smb_view_row_activated)
        xml.signal_connect ('on_add_queue_smb_view_row_expanded',
                            self.smb_view_row_expanded)
        xml.signal_connect ('on_add_queue_smb_specify_clicked',
                            self.smb_specify_clicked)
        xml.signal_connect ('on_add_queue_type_page_next',
                            self.validate_type_page)
        xml.signal_connect ('on_add_queue_model_page_prepare',
                            self.prepare_model_page)
        xml.signal_connect ('on_add_queue_mfr_menu_changed',
                            self.mfr_menu_changed)
        xml.signal_connect ('on_add_queue_model_page_next',
                            self.validate_model_page)
        xml.signal_connect ('on_add_queue_finish_page_prepare',
                            self.prepare_finish_page)
        xml.signal_connect ('on_add_queue_finish_page_finish', self.finish)

        # Re-label the finish screen (bug 71725).
        self.druid.finish.set_label (_("Finish"))

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

	if self.parent.hidden:
	    gtk.mainquit ()

# ---------------------------
# Greying-out the main window
# ---------------------------
    def busy (self):
        """Set the druid 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 druid window sensitive."""
        self.window.window.set_cursor (ready_cursor)
        self.window.set_sensitive (gtk.TRUE)

#-----------------------------
# Make up a name to start with
#-----------------------------
    def make_up_name (self):
        """Make up a name to start with."""
        base = "printer"
        ext = ""

        (name_dict_dict, alias_dict_dict) = self.parent.conf.get_queues ()
        while (name_dict_dict.has_key (base + ext) or
               alias_dict_dict.has_key (base + ext)):
            if ext == "":
                ext = "1"
            else:
                ext = str (int (ext) + 1)

        return base + ext

#----------------
# Start the druid
#----------------
    def addQueueDruid (self, url = None):
        """Run the druid."""

        # Set the druid page to the start.
        self.druid.set_page (self.xml.get_widget ('add_queue_start_page'))

        # Context help tracking.
        self.help_page = "printconf-local-printer.html"

        # Set up the name page.
        self.name_entry.set_text (self.make_up_name ())
        self.desc_entry.set_text ('')

        # Set up the type page.
        self.type_menu.set_history (0)
        self.type_menu_changed (self.type_menu)
        # Local printer device tab
        self.populate_device_view ()
        # IPP tab
        self.ipp_server_entry.set_text ('')
        self.ipp_path_entry.set_text ('/printers/queue1')
        self.ipp_server_entry.grab_focus ()
        # LPD tab
        self.lpd_server_entry.set_text ('')
        self.lpd_queue_entry.set_text ('')
        self.lpd_server_entry.grab_focus ()
        # SMB tab
        # If samba isn't installed, but the user wants to proceed anyway,
        # smb_forced is set.
        self.smb_forced = gtk.FALSE
        # NCP tab
        self.ncp_server_entry.set_text ('')
        self.ncp_user_entry.set_text ('')
        self.ncp_queue_entry.set_text ('')
        self.ncp_passwd_entry.set_text ('')
        self.ncp_server_entry.grab_focus ()
        # JetDirect tab
        self.jd_printer_entry.set_text ('')
        self.jd_port_entry.set_text ('9100')
        self.jd_printer_entry.grab_focus ()

        # Set up the printer model page.
        self.mfr_list = self.parent.populate_mfr_optionmenu (self.mfr_menu)
        self.id_to_iter = {}
        self.parent.populate_model_store (self.printer_store,
                                          id_dict = self.id_to_iter,
                                          window = self.window.window)
        store = self.printer_store
        path = store.get_path (store.get_iter_first ())
        col = self.printer_view.get_column (0)
        self.printer_view.scroll_to_cell (path, col, gtk.TRUE, 0.5, 0)
        self.notes_button.set_sensitive (gtk.FALSE)

	# If we have a URL for the printer, pre-populate some of the forms.
	if url:
            if url.startswith ("smb:"):
                # Try to make sense of the URL.  It looks like this:
                #
                # smb://[[username:]password@][workgroup/]server/printer

                type1 = "^smb://([^:]+:)?([^@]+@)?([^/]+)/([^/]+)$"
                type2 = "^smb://([^:]+:)?([^@]+@)?([^/]+)/([^/]+)/([^/]+)$"
                type1_re = re.compile (type1)
                type2_re = re.compile (type2)

                match = type1_re.match (url)
                if match:
                    (user, password, server, share) = match.groups ()
                    workgroup = None
                else:
                    match = type2_re.match (url)
                    if match:
                        (user, password, workgroup,
                         server, share) =  match.groups ()

                if not match:
                    # Couldn't understand the URL
                    return

                if user:
                    user = user.rstrip (":")

                if password:
                    password = password.rstrip ("@")

                smb_type = self.parent.queue_types.\
                           index (self.parent.conf.queue_types.smb)
                self.type_menu.set_history (smb_type)
                t = self.type_menu.get_history ()
                if t != smb_type:
                    # smbclient not installed
                    return

                # Look for the right one already browsed
                store = self.smb_store
                found = 0
                for row in store:
                    server_iter = store.get_iter (row.path)
                    if (store.get_value (server_iter, 0).lower () ==
                        server.lower ()):
                        dict = store.get_value (server_iter, 2)
                        if (not workgroup or
                            not dict['GROUP'] or
                            dict['GROUP'].lower () == workgroup.lower ()):
                            found = 1
                        break

                if found:
                    while store.iter_has_child (server_iter):
                        i = store.iter_nth_child (server_iter, 0)
                        store.remove (i)
                    dict = store.get_value (server_iter, 2)
                    if not dict['GROUP']:
                        dict['GROUP'] = workgroup
                        store.set_value (server_iter, 2, dict)
                else:
                    dict = pysmb.get_host_info (server)
                    if workgroup:
                        dict['GROUP'] = workgroup

                    server_iter = store.append (None)
                    store.set_value (server_iter, 0, server)
                    store.set_value (server_iter, 2, dict)

                iter = store.append (server_iter)
                store.set_value (iter, 0, share)
                store.set_value (iter, 1, _("(Specified)"))
                store.set_value (iter, 3, user)
                store.set_value (iter, 4, password)
                view = self.smb_view
                self.expanding_row = 1
                view.expand_row (store.get_path (server_iter), gtk.TRUE)
                del self.expanding_row
                view.get_selection ().select_iter (iter)
                col = view.get_column (0)
                view.scroll_to_cell (store.get_path (iter), col,
                                     gtk.TRUE, 0.5, 0)

        # Run the druid.
        self.window.set_transient_for (self.parent.toplevel)
        self.window.set_position (gtk.WIN_POS_NONE)
        self.parent.ready ()
        self.window.show_all ()

#----------
# Show help
#----------
    def help (self, druid):
        """Help button handler.  Shows help page self.help_page."""
        gnome.url_show ("file://%s/%s" %
                        (self.parent.conf.conf.printconf_help_dir,
                         self.help_page))

#-----------
# Start page
#-----------
    def prepare_start_page (self, page, druid):
        """Prepare the start page.  Set the help context."""
        self.help_page = "printconf-local-printer.html"

#----------
# Name page
#----------
    def prepare_name_page (self, page, druid):
        """Prepare the name page.  Set the help context."""
        self.help_page = "printconf-local-printer.html"
        self.name_entry.grab_focus ()

    def validate_name_page (self, page, druid):
        """
        Validate the name page.

        Return value: whether there is a problem with the page.
        """

        name = self.name_entry.get_text ()

        # Is the name valid at all?
        if not self.parent.conf.valid_queue_name (name):
            complain (self.window, _("Invalid name"))
            self.name_entry.grab_focus ()
            return gtk.TRUE

        # 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):
            complain (self.window,
                      _("There is already a queue with that name."))
            self.name_entry.grab_focus ()
            return gtk.TRUE

        if alias_dict_dict.has_key (name):
            complain (self.window,
                      _("An existing queue has an alias of that name."))
            self.name_entry.grab_focus ()
            return gtk.TRUE

        return gtk.FALSE

#----------
# Type page
#----------
    def prepare_type_page (self, page, druid):
        """Prepare the type page.  Set the help context."""
        self.help_page = "printconf-local-printer.html"

    def rescan_button_clicked (self, button):
        """Handler for the rescan button."""
        self.populate_device_view (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)
        path = self.device_store.get_path (iter)
        col = self.device_view.get_column (0)
        self.device_view.scroll_to_cell (path, col, gtk.TRUE, 0.5, 0)

    def populate_device_view (self, force = None):
        """Find local printer devices and populate self.device_store."""
        self.local_devs = self.parent.conf.scan_local_printer_devices (force)
        store = self.device_store
        autoiter = []
        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 description:
                autoiter.append (iter)

        if len (autoiter) == 1:
            # We can only see one printer; that might be the one the
            # user wants.  Select it.
            iter = autoiter[0]
            self.device_view.get_selection ().select_iter (iter)
            path = store.get_path (iter)
            col = self.device_view.get_column (0)
            self.device_view.scroll_to_cell (path, col, gtk.TRUE, 0.5, 0)

    def type_menu_changed (self, optionmenu):
        """
        Handler for the type optionmenu.  Set the notepad page appropriately,
        as well as the help context.
        """

        which = self.type_menu.get_history ()
        self.help_page = self.parent.queue_type_help_pages[which]
        self.notebook.set_current_page (which)

        if which == self.parent.smb_page_index:
            type_space = self.parent.queue_types[which]
            if not type_space.check ():
                ask = gtk.MessageDialog (self.window, 0, gtk.MESSAGE_WARNING,
                                         gtk.BUTTONS_YES_NO,
                                         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.smb_forced = gtk.TRUE
                else:
                    self.type_menu.set_history (0)

            self.browse_smb_hosts ()

    def validate_type_page (self, page, druid):
        """
        Validate the type page.

        Return value: whether there is a problem with the page.
        """

        type_space = self.parent.queue_types[self.type_menu.get_history ()]
        if (not (type_space == self.parent.conf.queue_types.smb
                 and self.smb_forced)
            and not type_space.check ()):
            ask = gtk.MessageDialog (self.window, 0, gtk.MESSAGE_WARNING,
                                     gtk.BUTTONS_YES_NO, 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.type_menu.grab_focus ()
                return gtk.TRUE

        types = self.parent.conf.queue_types
        if type_space == types.local:
            if self.validate_local_type ():
                return gtk.TRUE
        elif type_space == types.ipp:
            if self.validate_ipp_type ():
                return gtk.TRUE
        elif type_space == types.lpd:
            if self.validate_lpd_type ():
                return gtk.TRUE
        elif type_space == types.smb:
            if self.validate_smb_type ():
                return gtk.TRUE
        elif type_space == types.ncp:
            if self.validate_ncp_type ():
                return gtk.TRUE
        elif type_space == types.jetdirect:
            if self.validate_jetdirect_type ():
                return gtk.TRUE

        if type_space == types.jetdirect:
            # This is a JetDirect printer.  Let's look for an SNMP
            # system.sysDescr.0 string in case we can automatically
            # detect which driver to use.
            self.busy ()
            id = self.detect_snmp_printer (self.jd_printer_entry.get_text ())
            self.ready ()
            if id == None:
                return gtk.FALSE

        elif type_space == types.local:
            # This is a local printer.  If it has an IEEE 1284 Device ID,
            # see if we can find the right printer driver.
            selection = self.device_view.get_selection ()
            store, iter = selection.get_selected ()
            dev = store.get_value (iter, 0)
            try:
                auto = self.local_devs[dev]["auto"]
                mfr = string.lower (auto["manufacturer"])
                mdl = string.lower (auto["model"])
                id = self.parent.conf.foomatic.autodetect_dict[mfr,mdl].id
            except:
                return gtk.FALSE
        else: # all other types
            return gtk.FALSE

        # We have foomatic information for a local or JetDirect printer.
        # We know the manufacturer and model names,
        # so choose the right manufacturer, select the
        # right model, and scroll the treeview to it.
        mfr = self.parent.conf.foomatic.id_dict[id].make
        self.mfr_menu.set_history (self.mfr_list.index (mfr))
        self.id_to_iter = {}
        iter = self.parent.populate_model_store (self.printer_store,
                                                 id_dict = self.id_to_iter,
                                                 mfr = mfr,
                                                 id = id,
                                                 window = self.window.window)
        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)

        # Shouldn't be needed.
        while gtk.events_pending ():
            gtk.mainiteration ()

        return gtk.FALSE

    def validate_local_type (self):
        """
        Validate the local type data.
        
        Return value: whether there is a problem.
        """

        selection = self.device_view.get_selection ()
        store, iter = selection.get_selected ()
        if not iter:
            complain (self.window, _("You must select a device."))
            return gtk.TRUE

        return gtk.FALSE

    def validate_ipp_type (self):
        """Validate the IPP type data.
        
        Return value: whether there is a problem.
        """

        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.ipp_server_entry.grab_focus ()
            return gtk.TRUE

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

        return gtk.FALSE

    def validate_lpd_type (self):
        """Validate the LPD type data.
        
        Return value: whether there is a problem.
        """

        lpd_server = self.lpd_server_entry.get_text ()
        lpd_queue = self.lpd_queue_entry.get_text ()

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

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

        return gtk.FALSE

    def validate_smb_type (self):
        """
        Validate the SMB data.
        
        Return value: whether there is a problem.
        """

        store, iter = self.smb_view.get_selection ().get_selected ()
        if not iter:
            complain (self.window,
                      _("You must choose an SMB share to print to."))
            return gtk.TRUE

        parent_iter = store.iter_parent (iter)
        if not parent_iter:
            complain (self.window,
                      _("You must choose a share, not a host."))
            return gtk.TRUE

        smb_share = "//%s/%s" % (store.get_value (parent_iter, 0),
                                 store.get_value (iter, 0))

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

        try:
            group = store.get_value (parent_iter, 2)['GROUP']
        except:
            group = ''

        user = store.get_value (iter, 3)
        passwd = store.get_value (iter, 4)

        orig_group = group
        server = store.get_value (parent_iter, 0)
        orig_share = store.get_value (iter, 0)
        self.smb_workgroup_entry.set_text (group)
        self.smb_server_entry.set_text (server)
        self.smb_server_entry.set_editable (gtk.FALSE)
        self.smb_share_entry.set_text (orig_share)
        self.smb_user_entry.set_text ('')
        self.smb_passwd_entry.set_text ('')
        if user:
            self.smb_user_entry.set_text (user)

        if passwd:
            self.smb_passwd_entry.set_text (passwd)

        self.passwd_dialog.set_transient_for (self.window)
        self.passwd_dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
        while 1:
            if self.smb_forced:
                accessible = gtk.TRUE
                break

            # Check we can actually connect, and if not collect the
            # username and password to use.
            self.window.window.set_cursor (busy_cursor)
            while gtk.events_pending ():
                gtk.mainiteration ()

            accessible = pysmb.printer_share_accessible (smb_share,
                                                         group = group,
                                                         user = user,
                                                         passwd = passwd)
            self.window.window.set_cursor (ready_cursor)

            if accessible:
                break

            if not user:
                self.smb_user_entry.grab_focus ()
            else:
                self.smb_passwd_entry.grab_focus ()

            response = self.passwd_dialog.run ()
            self.passwd_dialog.hide ()
            if (response == gtk.RESPONSE_CANCEL or
                response == gtk.RESPONSE_DELETE_EVENT):
                break

            group = self.smb_workgroup_entry.get_text ()
            if group != orig_group:
                dict = store.get_value (parent_iter, 2)
                dict['GROUP'] = group
                store.set_value (parent_iter, 2, dict)

            sharename = self.smb_share_entry.get_text ()
            store.set_value (iter, 0, sharename)
            smb_share = "//%s/%s" % (server, sharename)
            user = self.smb_user_entry.get_text ()
            passwd = self.smb_passwd_entry.get_text ()
            store.set_value (iter, 3, user)
            store.set_value (iter, 4, passwd)

            # Validate them!

        return not accessible

    def validate_ncp_type (self):
        """
        Validate NCP data.
        
        Return value: whether there is a problem.
        """

        ncp_server = self.ncp_server_entry.get_text ()
        ncp_queue = self.ncp_queue_entry.get_text ()

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

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

        return gtk.FALSE

    def validate_jetdirect_type (self):
        """
        Validate JetDirect data.
        
        Return value: Whether there is a problem.
        """

        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.jd_printer_entry.grab_focus ()
            return gtk.TRUE

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

        return gtk.FALSE

#-------------
# SMB browsing
#-------------
    def browse_smb_hosts (self):
        """Initialise the SMB tree store."""
        store = self.smb_store
        store.clear ()
        try:
	    self.window.window.set_cursor (busy_cursor)
        except:
	    pass

        while gtk.events_pending ():
            gtk.mainiteration ()

        hosts = pysmb.get_host_list ()
        for host in hosts.keys ():
            h = hosts[host]
            iter = store.append (None)
            store.set_value (iter, 0, h['NAME'])
            store.set_value (iter, 2, h)
            dummy = store.append (iter)

        try:
            self.window.window.set_cursor (ready_cursor)
        except:
            pass

    def smb_view_row_activated (self, view, path, column):
        """Handle double-clicks in the SMB tree view."""
        store = self.smb_store
        iter = store.get_iter (path)
        if store.iter_depth (iter):
            # This is a share, not a host.
            return

        if view.row_expanded (path):
            view.collapse_row (path)
        else:
            self.smb_view_row_expanded (view, iter, path)

        return

    def smb_view_row_expanded (self, view, iter, path):
        """Handler for expanding a row in the SMB tree view."""
        try:
            if self.expanding_row:
                return
        except:
            self.expanding_row = 1

        store = self.smb_store
        host = store.get_value (iter, 2)
        if host:
            self.window.window.set_cursor (busy_cursor)
            while gtk.events_pending ():
                gtk.mainiteration ()

            printers = pysmb.get_printer_list (host)

            while store.iter_has_child (iter):
                i = store.iter_nth_child (iter, 0)
                store.remove (i)

            for printer in printers.keys ():
                i = store.append (iter)
                store.set_value (i, 0, printer)
                store.set_value (i, 1, printers[printer])
                store.set_value (i, 3, '')
                store.set_value (i, 4, '')

            self.window.window.set_cursor (ready_cursor)

        view.expand_row (path, 0)
        del self.expanding_row

    def smb_specify_clicked (self, button):
        """Allow the user to specify the share."""
        share_re = re.compile ("^//([^/]+)/(.*)$")
        self.smb_workgroup_entry.set_text ('')
        self.smb_server_entry.set_text ('MACHINE')
        self.smb_server_entry.set_editable (gtk.TRUE)
        self.smb_share_entry.set_text ('share')
        self.smb_user_entry.set_text ('')
        self.smb_passwd_entry.set_text ('')
        self.smb_share_entry.grab_focus ()
        self.passwd_dialog.set_transient_for (self.window)
        self.passwd_dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
        while 1:
            response = self.passwd_dialog.run ()
            self.passwd_dialog.hide ()
            if (response == gtk.RESPONSE_CANCEL or
                response == gtk.RESPONSE_DELETE_EVENT):
                break

            group = self.smb_workgroup_entry.get_text ()
            server = self.smb_server_entry.get_text ()
            share = self.smb_share_entry.get_text ()
            user = self.smb_user_entry.get_text ()
            passwd = self.smb_passwd_entry.get_text ()

            share = share.replace ("\\", "/")
            user = user.replace ("\\", "/")
            match = share_re.match (share)
            if not match:
                share = '//' + server + '/' + share

            self.window.window.set_cursor (busy_cursor)
            while gtk.events_pending ():
                gtk.mainiteration ()
            accessible = pysmb.printer_share_accessible (share,
                                                         group = group,
                                                         user = user,
                                                         passwd = passwd)
            self.window.window.set_cursor (ready_cursor)

            if accessible or self.smb_forced:
                # Add it to the tree view.
                match = share_re.match (share)
                machine, sharename = match.groups ()

                store = self.smb_store
                found = 0
                for row in store:
                    parent_iter = store.get_iter (row.path)
                    if store.get_value (parent_iter, 0) == machine:
                        found = 1
                        break

                if found:
                    while store.iter_has_child (parent_iter):
                        i = store.iter_nth_child (parent_iter, 0)
                        store.remove (i)
                else:
                    dict = pysmb.get_host_info (machine)
                    if accessible and not dict['GROUP']:
                        dict['GROUP'] = accessible['GROUP']

                    parent_iter = store.append (None)
                    store.set_value (parent_iter, 0, machine)
                    store.set_value (parent_iter, 2, dict)

                iter = store.append (parent_iter)
                store.set_value (iter, 0, sharename)
                store.set_value (iter, 1, _("(Specified)"))
                store.set_value (iter, 3, user)
                store.set_value (iter, 4, passwd)
                view = self.smb_view
		self.expanding_row = 1
                view.expand_row (store.get_path (parent_iter), gtk.TRUE)
		del self.expanding_row
                view.get_selection ().select_iter (iter)
		col = view.get_column (0)
		view.scroll_to_cell (store.get_path (iter), col,
                                     gtk.TRUE, 0.5, 0)
                break

        return gtk.TRUE

#-----------
# Model page
#-----------
    def prepare_model_page (self, page, druid):
        """Prepare the model page.  Set the help context."""
        self.help_page = "printconf-select-model.html"

        label = _("Select the printer manufacturer and model.")

        type_space = self.parent.queue_types[self.type_menu.get_history ()]
        types = self.parent.conf.queue_types
        if type_space == types.lpd:
            label = _("Select the printer manufacturer and model. "
                      "Depending on how the remote queue is configured, "
                      "you may need to select the generic PostScript "
                      "option here.")

        self.select_model_label.set_text (label)

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

	self.id_to_iter = {}
        self.parent.populate_model_store (self.printer_store,
                                          id_dict = self.id_to_iter,
                                          mfr = mfr,
                                          window = self.window.window)
        col = self.printer_view.get_column (0)
        self.printer_view.scroll_to_cell ((0,), col, gtk.TRUE, 0.5, 0)

    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)
        d, id = self.printer_store.get_value (iter, 1)
        if id:
            notes_available = gtk.TRUE
        else:
            notes_available = gtk.FALSE

        self.notes_button.set_sensitive (notes_available)
        return not self.printer_store.iter_has_child (iter)

    def notes_button_clicked (self, button):
        """Handler for the notes button."""
        selection = self.printer_view.get_selection ()
        (store, iter) = selection.get_selected ()
        if not iter:
            return

        (type, printer_id) = store.get_value (iter, 1)
        if printer_id:
            try:
                driver = self.parent.conf.foomatic.id_dict[printer_id].driver
            except:
                driver = (self.parent.conf.foomatic.id_dict[printer_id].
                          drivers[0])

            self.parent.show_notes (printer_id, driver, self)

    def validate_model_page (self, page, druid):
        """
        Validate model page.

        Return value: whether there is a problem.
        """

	selection = self.printer_view.get_selection ()
	store, iter = selection.get_selected ()
	if not iter:
	    complain (self.window, _("You must select a printer model."))
	    return gtk.TRUE

        driver, id = store.get_value (iter, 1)
        if driver != self.parent.conf.drivers.foomatic:
            return gtk.FALSE

        id_dict = self.parent.conf.foomatic.id_dict
        try:
            foomatic_driver = id_dict[id].driver
        except:
            foomatic_driver = id_dict[id].drivers[0]

        blacklist = self.parent.conf.driver_blacklist
        try:
            black = blacklist.dict[foomatic_driver]
        except:
            return gtk.FALSE

        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 gtk.TRUE

	return gtk.FALSE

#------------
# Finish page
#------------
    def prepare_finish_page (self, pageedge, druid):
        """Prepare finish page text, and set context help."""

        self.help_page = "printconf-select-model.html#S2-PRINTING-CONFIRM"
        text = _("About to create the following queue:\n\n")
        text += _("Type: ")
        type_index = self.type_menu.get_history ()
        self.queue_type_space = self.parent.queue_types[type_index]
        text += self.queue_type_space.long_pretty_name + "\n"
        text += self.pull ()
        pageedge.set_text (text)

    def pull (self):
        """Pull in the (validated) data from all of the pages."""

        types = self.parent.conf.queue_types

        rerender = 0
        locale = "C"
        if os.environ.has_key ("LANG"):
            lang = os.environ["LANG"]
            if lang[0:2] == "zh" or lang[0:2] == "ko" or lang[0:2] == "ja":
                rerender = 1

            if lang[0:2] == "ja":
                locale = "ja_JP"

	    if lang[0:2] == "ko":
		locale = "ko_KR"

	    if lang[0:5] == "zh_CN":
		locale = "zh_CN"

	    if lang[0:5] == "zh_TW":
		locale = "zh_TW"

        data = {}
        flags = { "convert_text_to_Postscript" : 1,
                  "assume_data_is_text" : rerender,
                  "rerender_Postscript" : rerender}

	data["mf_flags"] = flags

        if locale != "C":
            data["filter_locale"] = locale

        data["queue_name"] = self.name_entry.get_text ()
        data["queue_description"] = self.desc_entry.get_text ()

        if self.queue_type_space == types.local:
            text = self.pull_local (data)
        elif self.queue_type_space == types.ipp:
            text = self.pull_ipp (data)
        elif self.queue_type_space == types.lpd:
            text = self.pull_lpd (data)
        elif self.queue_type_space == types.smb:
            text = self.pull_smb (data)
        elif self.queue_type_space == types.ncp:
            text = self.pull_ncp (data)
        elif self.queue_type_space == types.jetdirect:
            text = self.pull_jetdirect (data)

        text += "\n" + _("Printer: ")
        selection = self.printer_view.get_selection ()
        store, iter = selection.get_selected ()
        driver, id = store.get_value (iter, 1)
        self.driver_tuple = (driver, id)
        if id:
            f = self.parent.conf.foomatic.id_dict[id]
            text += f.make + " " + f.model
        else:
            text += driver.label

        self.queue_data = data
        return text

    def pull_local (self, data):
        """Pull local data."""
        selection = self.device_view.get_selection ()
        store, iter = selection.get_selected ()
        dev = store.get_value (iter, 0)
        data["local_printer_device"] = dev

        return _("Device: ") + dev

    def pull_ipp (self, data):
        """Pull IPP data."""
        data["ipp_server"] = self.ipp_server_entry.get_text ()
        data["ipp_port"] = "631"
        data["ipp_path"] = self.ipp_path_entry.get_text ()

        text = "ipp://%s:%s%s" % (data["ipp_server"],
                                      data["ipp_port"],
                                      data["ipp_path"])
        return text

    def pull_lpd (self, data):
        """Pull LPD data."""
        data["lpd_server"] = self.lpd_server_entry.get_text ()
        data["lpd_queue"] = self.lpd_queue_entry.get_text ()

        text = _("Queue: ") + data["lpd_queue"] + "@" + data["lpd_server"]
        return text

    def pull_smb (self, data):
        """Pull SMB data."""
        store, iter = self.smb_view.get_selection ().get_selected ()
        parent_iter = store.iter_parent (iter)
        smb_share = "//%s/%s" % (store.get_value (parent_iter, 0),
                                 store.get_value (iter, 0))

        h = store.get_value (parent_iter, 2)
        smb_user = store.get_value (iter, 3)
        smb_passwd = store.get_value (iter, 4)
        data["smb_share"] = smb_share
        data["smb_ip"] = h['IP']
        if h.has_key ('GROUP'):
            data["smb_workgroup"] = h['GROUP']
        else:
            data["smb_workgroup"] = ''
        data["smb_user"] = smb_user
        data["smb_password"] = smb_passwd

        return _("Share: ") + data["smb_share"]

    def pull_ncp (self, data):
        """Pull NCP data."""
        data["ncp_server"] = self.ncp_server_entry.get_text ()
        data["ncp_queue"] = self.ncp_queue_entry.get_text ()
        data["ncp_user"] = self.ncp_user_entry.get_text ()
        data["ncp_password"] = self.ncp_passwd_entry.get_text ()

        return _("Queue: ") + data["ncp_queue"] + "@" + data["ncp_server"]

    def pull_jetdirect (self, data):
        """Pull JetDirect data."""
        data["jetdirect_ip"] = self.jd_printer_entry.get_text ()
        data["jetdirect_port"] = self.jd_port_entry.get_text ()

        return _("Port: ") + data["jetdirect_ip"] + \
               ":" + data["jetdirect_port"]

#----------------------
# Finish button pressed
#----------------------
    def finish (self, pageedge, druid):
        """Handler for the finish button.  Add the queue."""
        driver, id = self.driver_tuple
        foomatic_tuple = None
        if driver == self.parent.conf.drivers.foomatic:
            id_dict = self.parent.conf.foomatic.id_dict
            printer = id_dict[id]
            try:
                foomatic_driver = printer.driver
            except:
                foomatic_driver = printer.drivers[0]

            foomatic_tuple = (printer, foomatic_driver)

        driver_tuple = (driver, foomatic_tuple)
        self.parent.conf.construct_queue (self.queue_type_space,
                                          self.queue_data, driver_tuple)

        queue_store = self.parent.queue_store
        iter = queue_store.append (None)
        name = self.queue_data["queue_name"]
        queue_store.set_value (iter, 1, name)
        queue_store.set_value (iter, 2, self.queue_data["queue_description"])
        self.parent.name_dict, self.parent.alias_dict = \
                               self.parent.conf.get_queues ()
        queue_store.set_value (iter, 3, self.parent.name_dict[name])
        self.parent.queue_view.get_selection ().select_iter (iter)
        self.parent.default_queue_name = self.parent.conf.\
                                         get_default_queue_name ()
        self.parent.fix_default_marker ()
        self.parent.queue_chosen (self.parent.queue_view)
        self.parent.need_to_apply_changes (gtk.TRUE)
        self.window.hide ()

        if os.access ("/usr/share/cups/data/testprint.ps", os.R_OK):
            if self.parent.hidden:
                # For --add-with-url we apply the changes anyway.
                self.parent.apply ()
		msg = _("Would you like to print a test page?")
            else:
		msg = _("Would you like to print a test page?\n"
                        "Answering 'yes' will apply all changes\n"
                        "and print a test page to this printer.")
            d = gtk.MessageDialog (self.parent.toplevel, 0,
                                   gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
                                   msg)
            d.set_transient_for (self.parent.toplevel)
            d.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
            response = d.run ()
            d.destroy ()
            if response == gtk.RESPONSE_YES:
                if not self.parent.hidden:
		    self.parent.apply ()

                # Give the print spooler a moment to sort itself out.
                # FIXME: This should be done in the initscript instead.
                time.sleep (10)

                self.parent.test (self.parent.cups_test)

#-----------------------
# SNMP printer detection
#-----------------------
    def detect_snmp_printer (self, agent):
        """Detect an SNMP printer by name."""
        def snmpwalk (agent, oid):
            opts = '-v 1 -c public -Ov'
            c = '/usr/bin/snmpwalk %s "$AGENT" "$OID" 2>/dev/null' % opts
            os.environ['AGENT'] = agent
            os.environ['OID'] = oid
            signal.signal (signal.SIGCHLD, signal.SIG_DFL)
            f = os.popen (c)
            return f.readlines ()

        id = None
        ps_capable = 0

        ls = snmpwalk (agent, 'system.sysObjectID.0')
        oid = None
        lookfor = "OID: "
        for l in ls:
            if l.startswith (lookfor):
                oid = l[len (lookfor):].rstrip ('\n')
                break

        if oid:
            ls = snmpwalk (agent, oid)
            # Look for an IEEE 1284 ID
            lookfor = "STRING: "
            for l in ls:
                if not l.startswith (lookfor):
                    continue

                # IEEE 1284 ID fields are separated by semi-colons
                if not l.find (";") != -1:
                    continue

                l = l[len (lookfor):].rstrip ('\n').strip ('"')
                deviceid = self.parent.conf.parse_ieee1284_deviceid (l)
                try:
                    tuple = (deviceid["manufacturer"].lower().strip (' '),
                             deviceid["model"].lower ().strip (' '))
                    id = self.parent.conf.foomatic.autodetect_dict[tuple].id
                    break
                except:
                    print "No IEEE 1284 match:", deviceid

                try:
                    cmdset = deviceid["cmdset"]
                    for each in cmdset.split (","):
                        if each.lower ().strip () == "postscript":
                            ps_capable = 1
                except:
                    pass

        if not id:
            ls = snmpwalk (agent, 'HOST-RESOURCES-MIB::hrDeviceDescr')
            lookfor = "STRING: "
            for l in ls:
                if not l.startswith (lookfor):
                    continue

                descr = l[len (lookfor):].rstrip ('\n')
                try:
                    id = self.parent.conf.foomatic.snmp_dict[descr].id
                    break
                except:
                    print "No SNMP match: '%s'" % descr

        if not id and ps_capable:
            try:
                make_model = self.parent.conf.foomatic.make_model_dict_dict
                id = make_model["Generic"]["PostScript Printer"].id
            except:
                pass

        return id

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