#!/usr/bin/env python

#############################################################################
##
## Copyright 2007-2008 Canonical Ltd
## Author: Jonathan Riddell <jriddell@ubuntu.com>
##
## Includes code from System Config Printer
## Copyright 2007 Tim Waugh <twaugh@redhat.com>
## Copyright 2007 Red Hat, Inc.
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of 
## the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
#############################################################################

"""
A systray applet to show that documents are being printed, show printer warnings and errors and a GUI for hal-cups-utils automatic setup for new printers.

It is a Qt port of the applet from Red Hat's System Config Printer
http://cyberelk.net/tim/software/system-config-printer/
svn co http://svn.fedorahosted.org/svn/system-config-printer/trunk
"""

import os
import subprocess
import sys

SYSTEM_CONFIG_PRINTER_DIR = "/usr/share/system-config-printer"

MIN_REFRESH_INTERVAL = 1 # seconds
CONNECTING_TIMEOUT = 60 # seconds

import time

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic
from PyKDE4.kdecore import i18n, i18nc, i18np, i18ncp, ki18n, KAboutData, KCmdLineArgs, KCmdLineOptions, KStandardDirs, KLocalizedString
from PyKDE4.kdeui import KApplication, KXmlGuiWindow, KStandardAction, KIcon, KToggleAction

if QFile.exists(SYSTEM_CONFIG_PRINTER_DIR + "/ppds.py"):
    AUTOCONFIGURE = True
else:
    AUTOCONFIGURE = False

def translate(self, prop):
    """reimplement method from uic to change it to use gettext"""
    if prop.get("notr", None) == "true":
        return self._cstring(prop)
    else:
        if prop.text is None:
            return ""
        text = prop.text.encode("UTF-8")
        return i18n(text)

uic.properties.Properties._string = translate

import cups

import dbus
import dbus.mainloop.qt
import dbus.service

class MainWindow(KXmlGuiWindow):
    """Our main GUI dialogue, overridden so that closing it doesn't quit the app"""

    def closeEvent(self, event):
        event.ignore()
        self.hide()

class PrintersWindow(QWidget):
    """The printer status dialogue, overridden so that closing it doesn't quit the app and to untick the show menu entry"""

    def __init__(self, applet):
        QWidget.__init__(self)
        self.applet = applet

    def closeEvent(self, event):
        event.ignore()
        self.applet.on_printer_status_delete_event()
        self.hide()

class StateReason:
    REPORT=1
    WARNING=2
    ERROR=3

    LEVEL_ICON={
        REPORT: "dialog-info",
        WARNING: "dialog-warning",
        ERROR: "dialog-error"
        }

    def __init__(self, printer, reason):
        self.printer = printer
        self.reason = reason
        self.level = None
        self.canonical_reason = None

    def get_printer (self):
        return self.printer

    def get_level (self):
        if self.level != None:
            return self.level

        if (self.reason.endswith ("-report") or
            self.reason == "connecting-to-device"):
            self.level = self.REPORT
        elif self.reason.endswith ("-warning"):
            self.level = self.WARNING
        else:
            self.level = self.ERROR
        return self.level

    def get_reason (self):
        if self.canonical_reason:
            return self.canonical_reason

        level = self.get_level ()
        reason = self.reason
        if level == self.WARNING and reason.endswith ("-warning"):
            reason = reason[:-8]
        elif level == self.ERROR and reason.endswith ("-error"):
            reason = reason[:-6]
        self.canonical_reason = reason
        return self.canonical_reason

    def get_description (self):
        messages = {
            'toner-low': (i18n("Toner low"),
                          ki18n("Printer '%1' is low on toner.")),
            'toner-empty': (i18n("Toner empty"),
                            ki18n("Printer '%1' has no toner left.")),
            'cover-open': (i18n("Cover open"),
                           ki18n("The cover is open on printer '%1'.")),
            'door-open': (i18n("Door open"),
                          ki18n("The door is open on printer '%1'.")),
            'media-low': (i18n("Paper low"),
                          ki18n("Printer '%1' is low on paper.")),
            'media-empty': (i18n("Out of paper"),
                            ki18n("Printer '%1' is out of paper.")),
            'marker-supply-low': (i18n("Ink low"),
                                  ki18n("Printer '%1' is low on ink.")),
            'marker-supply-empty': (i18n("Ink empty"),
                                    ki18n("Printer '%1' has no ink left.")),
            'connecting-to-device': (i18n("Not connected?"),
                                     ki18n("Printer '%1' may not be connected.")),
            }
        try:
            (title, text) = messages[self.get_reason ()]
            text = text.subs (self.get_printer ()).toString ()
        except KeyError:
            if self.get_level () == self.REPORT:
                title = i18n("Printer report")
            elif self.get_level () == self.WARNING:
                title = i18n("Printer warning")
            elif self.get_level () == self.ERROR:
                title = i18n("Printer error")
            text = i18n("Printer '%1': '%2'.", self.get_printer (), self.get_reason ())
        return (title, text)

    def get_tuple (self):
        return (self.get_level (), self.get_printer (), self.get_reason ())

    def __cmp__(self, other):
        if other == None:
            return 1
        if other.get_level () != self.get_level ():
            return self.get_level () < other.get_level ()
        if other.get_printer () != self.get_printer ():
            return other.get_printer () < self.get_printer ()
        return other.get_reason () < self.get_reason ()

def collect_printer_state_reasons (connection):
    result = []
    printers = connection.getPrinters ()
    for name, printer in printers.iteritems ():
        reasons = printer["printer-state-reasons"]
        if type (reasons) == str:
            # Work around a bug that was fixed in pycups-1.9.20.
            reasons = [reasons]
        for reason in reasons:
            if reason == "none":
                break
            if (reason.startswith ("moving-to-paused") or
                reason.startswith ("paused") or
                reason.startswith ("shutdown") or
                reason.startswith ("stopping") or
                reason.startswith ("stopped-partly")):
                continue
            result.append (StateReason (name, reason))
    return result

def worst_printer_state_reason (connection, printer_reasons=None):
    """Fetches the printer list and checks printer-state-reason for
    each printer, returning a StateReason for the most severe
    printer-state-reason, or None."""
    worst_reason = None
    if printer_reasons == None:
        printer_reasons = collect_printer_state_reasons (connection)
    for reason in printer_reasons:
        if worst_reason == None:
            worst_reason = reason
            continue
        if reason > worst_reason:
            worst_reason = reason

    return worst_reason


class JobManager(QObject):
    """our main class creates the systray icon and the dialogues and refreshes the dialogues for new information"""
    def __init__(self, parent = None):
        QObject.__init__(self)

        self.will_refresh = False # whether timeout is set
        self.last_refreshed = 0
        self.trayicon = True
        self.suppress_icon_hide = False
        self.which_jobs = "not-completed"
        self.hidden = False
        self.jobs = {}
        self.jobiters = {}
        self.will_update_job_creation_times = False # whether timeout is set
        self.update_job_creation_times_timer = QTimer(self)
        self.connect(self.update_job_creation_times_timer, SIGNAL("timeout()"), self.update_job_creation_times)
        self.statusbar_set = False
        self.reasons_seen = {}
        self.connecting_to_device = {} # dict of printer->time first seen
        self.still_connecting = set()
        self.special_status_icon = False

        #Use local files if in current directory
        if os.path.exists("printer-applet.ui"):
            APPDIR = QDir.currentPath()
        else:
            file =  KStandardDirs.locate("appdata", "printer-applet.ui")
            APPDIR = file.left(file.lastIndexOf("/"))

        self.mainWindow = MainWindow()
        uic.loadUi(APPDIR + "/" + "printer-applet.ui", self.mainWindow)

        self.printersWindow = PrintersWindow(self)
        uic.loadUi(APPDIR + "/" + "printer-applet-printers.ui", self.printersWindow)

        self.sysTray = QSystemTrayIcon(KIcon("printer"), self.mainWindow)
        #self.sysTray.show()
        self.connect(self.sysTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.showMainWindow)

        self.menu = QMenu()
        self.menu.addAction(i18n("_Hide").replace("_", ""), self.on_icon_hide_activate)
        self.menu.addAction(KIcon("application-exit"), i18n("Quit"), self.on_icon_quit_activate)
        self.sysTray.setContextMenu(self.menu)

        self.mainWindow.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self.mainWindow.treeWidget, SIGNAL("customContextMenuRequested(const QPoint&)"), self.on_treeview_button_press_event)
        #self.connect(self.mainWindow.treeWidget, SIGNAL("itemClicked(QTreeWidgetItem*, int)"), self.printItemClicked)
        self.rightClickMenu = QMenu(self.mainWindow.treeWidget)
        self.cancel = self.rightClickMenu.addAction(i18n("Cancel"), self.on_job_cancel_activate)
        self.hold = self.rightClickMenu.addAction(i18n("_Hold").replace("_",""), self.on_job_hold_activate)
        self.release = self.rightClickMenu.addAction(i18n("_Release").replace("_",""), self.on_job_release_activate)
        self.reprint = self.rightClickMenu.addAction(i18n("Re_print").replace("_",""), self.on_job_reprint_activate)

        closeAction = KStandardAction.close(self.hideMainWindow, self.mainWindow.actionCollection());

        refreshAction = self.mainWindow.actionCollection().addAction("refresh")
        refreshAction.setIcon( KIcon("view-refresh") )
        refreshAction.setText( i18n( "&Refresh" ) )
        refreshAction.setShortcut(QKeySequence(Qt.Key_F5))
        self.connect(refreshAction, SIGNAL("triggered(bool)"), self.refresh);

        showCompletedJobsAction = KToggleAction("Show Completed Jobs", self.mainWindow)
        self.mainWindow.actionCollection().addAction("show_completed_jobs", showCompletedJobsAction)
        self.connect(showCompletedJobsAction, SIGNAL("triggered(bool)"), self.on_show_completed_jobs_activate);

        showPrinterStatusAction = KToggleAction("Show Printer Status", self.mainWindow)
        self.mainWindow.actionCollection().addAction("show_printer_status", showPrinterStatusAction)
        self.connect(showPrinterStatusAction, SIGNAL("triggered(bool)"), self.on_show_printer_status_activate);

        self.mainWindow.createGUI(APPDIR + "/printer-appletui.rc")

        cups.setPasswordCB(self.cupsPasswdCallback)

        dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True)

        try:
            bus = dbus.SystemBus()
        except:
            print >> sys.stderr, "%s: printer-applet failed to connect to system D-Bus"
            sys.exit (1)

        if AUTOCONFIGURE:
            notification = NewPrinterNotification(bus, self)

        # D-Bus
        bus.add_signal_receiver (self.handle_dbus_signal,
                                 path="/com/redhat/PrinterSpooler",
                                 dbus_interface="com.redhat.PrinterSpooler")
        self.refresh()

    """Used in gtk frontend to set magnifing glass icon when configuring printer, I don't have a suitable icon so using bubbles instead
    # Handle "special" status icon
    def set_special_statusicon (self, iconname):
        self.special_status_icon = True
        self.statusicon.set_from_icon_name (iconname)
        self.set_statusicon_visibility ()

    def unset_special_statusicon (self):
        self.special_status_icon = False
        self.statusicon.set_from_pixbuf (self.saved_statusicon_pixbuf)
    """

    def notify_new_printer (self, printer, title, text):
        self.hidden = False
        self.showMessage(title, text)

    def showMessage(self, title, message):
        """show a message, delayed slightly to ensure the systray is visible else it appears in the wrong place
        Gtk uses libnotify, for Qt we just show the message directly"""
        self.sysTray.show()
        self.sysTrayTitle = title
        self.sysTrayMessage = message
        QTimer.singleShot(1000, self.showSysTrayMessage)

    def showSysTrayMessage(self):
        self.sysTray.showMessage(self.sysTrayTitle, self.sysTrayMessage)

    """unused, see set_special_statusicon
    def set_statusicon_from_pixbuf (self, pb):
        self.saved_statusicon_pixbuf = pb
        if not self.special_status_icon:
            self.statusicon.set_from_pixbuf (pb)
    """

    """unused, see MainWindow and PrintersWindow
    def on_delete_event(self, *args):
        if self.trayicon:
            self.MainWindow.hide ()
            if self.show_printer_status.get_active ():
                self.PrintersWindow.hide ()
        else:
            self.loop.quit ()
        return True
    """

    def on_printer_status_delete_event(self):
        self.mainWindow.actionShow_Printer_Status.setChecked(False)

    def cupsPasswdCallback(self, querystring):
        (text, ok) = QInputDialog.getText(self.mainWindow, i18n("Password required"), querystring, QLineEdit.Password)
        if ok:
            print "ok"
            return text
        return ''

    def show_IPP_Error(self, exception, message):
        if exception == cups.IPP_NOT_AUTHORIZED:
            error_text = ('<span weight="bold" size="larger">' +
                          i18n('Not authorized') + '</span>\n\n' +
                          i18n('The password may be incorrect.'))
        else:
            error_text = ('<span weight="bold" size="larger">' +
                          i18n('CUPS server error') + '</span>\n\n' +
                          i18n("There was an error during the CUPS "\
                            "operation: '%1'.", message))
        #fix Gtk's non-HTML for Qt
        error_text = error_text.replace("\n", "<br />")
        error_text = error_text.replace("span", "strong")
        QMessageBox.critical(self.mainWindow, i18n("Error"), error_text)

    """
    def toggle_window_display(self, icon):
    """
    #FIXME, hide printer status window?
    def hideMainWindow(self):
        self.mainWindow.hide()

    def showMainWindow(self, activationReason):
        if activationReason == QSystemTrayIcon.Trigger:
            if self.mainWindow.isVisible():
                self.mainWindow.hide()
            else:
                self.mainWindow.show()
                self.refresh()

    def on_show_completed_jobs_activate(self, activated):
        if activated:
            self.which_jobs = "all"
        else:
            self.which_jobs = "not-completed"
        self.refresh()

    def on_show_printer_status_activate(self, activated):
        if activated:
            self.printersWindow.show()
        else:
            self.printersWindow.hide()

    def check_still_connecting(self):
        """Timer callback to check on connecting-to-device reasons."""
        c = cups.Connection ()
        printer_reasons = collect_printer_state_reasons (c)
        del c

        if self.update_connecting_devices (printer_reasons):
            self.refresh ()

        # Don't run this callback again.
        return False

    def update_connecting_devices(self, printer_reasons=[]):
        """Updates connecting_to_device dict and still_connecting set.
        Returns True if a device has been connecting too long."""
        time_now = time.time ()
        connecting_to_device = {}
        trouble = False
        for reason in printer_reasons:
            if reason.get_reason () == "connecting-to-device":
                # Build a new connecting_to_device dict.  If our existing
                # dict already has an entry for this printer, use that.
                printer = reason.get_printer ()
                t = self.connecting_to_device.get (printer, time_now)
                connecting_to_device[printer] = t
                if time_now - t >= CONNECTING_TIMEOUT:
                    trouble = True

        # Clear any previously-notified errors that are now fine.
        remove = set()
        for printer in self.still_connecting:
            if not self.connecting_to_device.has_key (printer):
                remove.add (printer)

        self.still_connecting = self.still_connecting.difference (remove)

        self.connecting_to_device = connecting_to_device
        return trouble

    def check_state_reasons(self, connection, my_printers=set()):
        printer_reasons = collect_printer_state_reasons (connection)

        # Look for any new reasons since we last checked.
        old_reasons_seen_keys = self.reasons_seen.keys ()
        reasons_now = set()
        need_recheck = False
        for reason in printer_reasons:
            tuple = reason.get_tuple ()
            printer = reason.get_printer ()
            reasons_now.add (tuple)
            if not self.reasons_seen.has_key (tuple):
                # New reason.
                iter = QTreeWidgetItem(self.printersWindow.treeWidget)
                #iter.setText(0, reason.get_level ())
                iter.setText(0, reason.get_printer ())
                title, text = reason.get_description ()
                iter.setText(1, text)
                self.printersWindow.treeWidget.addTopLevelItem(iter)

                self.reasons_seen[tuple] = iter
                if (reason.get_reason () == "connecting-to-device" and
                    not self.connecting_to_device.has_key (printer)):
                    # First time we've seen this.
                    need_recheck = True

        if need_recheck:
            # Check on them again in a minute's time.
            QTimer.singleShot(CONNECTING_TIMEOUT * 1000, self.check_still_connecting)

        self.update_connecting_devices (printer_reasons)
        items = self.reasons_seen.keys ()
        for tuple in items:
            if not tuple in reasons_now:
                # Reason no longer present.
                iter = self.reasons_seen[tuple]
                index = self.mainWindow.treeWidget.indexOfTopLevelItem(iter)
                self.mainWindow.treeWidget.takeTopLevelItem(index)
                del self.reasons_seen[tuple]
        # Update statusbar and icon with most severe printer reason
        # across all printers.
        self.icon_has_emblem = False
        reason = worst_printer_state_reason (connection, printer_reasons)
        if reason != None and reason.get_level () >= StateReason.WARNING:
            title, text = reason.get_description ()
            #if self.statusbar_set:
            #    self.statusbar.pop (0)
            self.mainWindow.statusBar().showMessage(text)
            #self.statusbar.push (0, text)
            self.worst_reason_text = text
            self.statusbar_set = True

            if self.trayicon:
                icon = StateReason.LEVEL_ICON[reason.get_level ()]
                emblem = QPixmap(KIcon(icon).pixmap(16, 16))
                pixbuf = QPixmap(KIcon("printer").pixmap(22, 22))
                painter = QPainter(pixbuf)
                painter.drawPixmap(pixbuf.width()-emblem.width(),pixbuf.height()-emblem.height(),emblem)
                painter.end()
                self.sysTray.setIcon(QIcon(pixbuf))
                self.icon_has_emblem = True
        else:
            # No errors
            if self.statusbar_set:
                #self.statusbar.pop (0)
                self.mainWindow.statusBar().clearMessage()
                self.statusbar_set = False

    """not using notifications in qt frontend
    def on_notification_closed(self, notify):
    """

    def update_job_creation_times(self):
        now = time.time ()
        need_update = False
        for job, data in self.jobs.iteritems():
            if self.jobs.has_key (job):
                iter = self.jobiters[job]

            t = "Unknown"
            if data.has_key ('time-at-creation'):
                created = data['time-at-creation']
                ago = now - created
                if ago > 86400:
                    t = time.ctime (created)
                elif ago > 3600:
                    need_update = True
                    hours = int (ago / 3600)
                    mins = int ((ago % 3600) / 60)
                    if mins > 0:
                        th = unicode(i18ncp("%1 in the '%1 and %2 ago' message below", "1 hour", "%1 hours", hours), 'utf-8')
                        tm = unicode(i18ncp("%2 in the '%1 and %2 ago' message below", "1 minute", "%1 minutes", hours), 'utf-8')
                        t = i18nc("Arguments are formatted hours and minutes from the messages above", "%1 and %2 ago", th, tm)
                    else:
                        t = i18np("1 hour ago", "%1 hours ago", hours)
                else:
                    need_update = True
                    mins = int(ago / 60)
                    t = i18np("a minute ago", "%1 minutes ago", mins)

            #self.store.set_value (iter, 4, t)
            iter.setText(4, t)

        if need_update and not self.will_update_job_creation_times:
            self.update_job_creation_times_timer.setInterval(60 * 1000)
            self.update_job_creation_times_timer.start()
            self.will_update_job_creation_times = True

        if not need_update:
            self.update_job_creation_times_timer.stop()
            self.will_update_job_creation_times = False

        # Return code controls whether the timeout will recur.
        return self.will_update_job_creation_times

    def refresh(self):
        """updates the print dialogue"""
        now = time.time ()
        if (now - self.last_refreshed) < MIN_REFRESH_INTERVAL:
            if self.will_refresh:
                return

            #gobject.timeout_add (MIN_REFRESH_INTERVAL * 1000,
            #                     self.refresh)
            QTimer.singleShot(MIN_REFRESH_INTERVAL * 1000, self.update_job_creation_times)
            self.will_refresh = True
            return

        self.will_refresh = False
        self.last_refreshed = now

        try:
            c = cups.Connection ()
            jobs = c.getJobs (which_jobs=self.which_jobs, my_jobs=True)
        except cups.IPPError, (e, m):
            self.show_IPP_Error (e, m)
            return
        except RuntimeError:
            return

        if self.which_jobs == "not-completed":
            num_jobs = len (jobs)
        else:
            try:
                num_jobs = len (c.getJobs (my_jobs=True))
            except cups.IPPError, (e, m):
                self.show_IPP_Error (e, m)
                return
            except RuntimeError:
                return

        if self.trayicon:
            self.num_jobs = num_jobs
            if self.hidden and self.num_jobs != self.num_jobs_when_hidden:
                self.hidden = False
            if num_jobs == 0:
                tooltip = i18n("No documents queued")
                #FIXMEself.set_statusicon_from_pixbuf (self.icon_no_jobs)
            else:
                tooltip = i18np("1 document queued", "%1 documents queued", num_jobs)
                #self.set_statusicon_from_pixbuf (self.icon_jobs)

        my_printers = set()
        for job, data in jobs.iteritems ():
            state = data.get ('job-state', cups.IPP_JOB_CANCELED)
            if state >= cups.IPP_JOB_CANCELED:
                continue
            uri = data.get ('job-printer-uri', '/')
            i = uri.rfind ('/')
            my_printers.add (uri[i + 1:])

        self.check_state_reasons (c, my_printers)
        del c

        if self.trayicon:
            # If there are no jobs but there is a printer
            # warning/error indicated by the icon, set the icon
            # tooltip to the reason description.
            if self.num_jobs == 0 and self.icon_has_emblem:
                tooltip = self.worst_reason_text

            self.sysTray.setToolTip (tooltip)
            self.set_statusicon_visibility ()

        for job in self.jobs:
            if not jobs.has_key (job):
                #self.store.remove (self.jobiters[job])
                index = self.mainWindow.treeWidget.indexOfTopLevelItem(self.jobiters[job])
                self.mainWindow.treeWidget.takeTopLevelItem(index)
                del self.jobiters[job]

        for job, data in jobs.iteritems():
            if self.jobs.has_key (job):
                iter = self.jobiters[job]
            else:
                iter = QTreeWidgetItem(self.mainWindow.treeWidget)
                iter.setText(0, str(job))
                iter.setText(1, data.get('job-name', 'Unknown'))
                self.mainWindow.treeWidget.addTopLevelItem(iter)
                self.jobiters[job] = iter

            uri = data.get('job-printer-uri', '')
            i = uri.rfind ('/')
            if i != -1:
                printer = uri[i + 1:]
            iter.setText(2, printer)

            if data.has_key ('job-k-octets'):
                size = str (data['job-k-octets']) + 'k'
            else:
                size = 'Unknown'
            iter.setText(3, size)
            #self.store.set_value (iter, 3, size)

            state = None
            if data.has_key ('job-state'):
                try:
                    jstate = data['job-state']
                    s = int (jstate)
                    state = { cups.IPP_JOB_PENDING:i18nc("Job state", "Pending"),
                              cups.IPP_JOB_HELD:i18nc("Job state", "Held"),
                              cups.IPP_JOB_PROCESSING: i18nc("Job state", "Processing"),
                              cups.IPP_JOB_STOPPED: i18nc("Job state", "Stopped"),
                              cups.IPP_JOB_CANCELED: i18nc("Job state", "Canceled"),
                              cups.IPP_JOB_ABORTED: i18nc("Job state", "Aborted"),
                              cups.IPP_JOB_COMPLETED: i18nc("Job state", "Completed") }[s]
                except ValueError:
                    pass
                except IndexError:
                    pass    
            if state == None:
                state = i18nc("Job state", "Unknown")
            iter.setText(5, state)
            columns = self.mainWindow.treeWidget.columnCount()
            for i in range(columns):
                self.mainWindow.treeWidget.resizeColumnToContents(i)

        self.jobs = jobs
        self.update_job_creation_times ()

    def set_statusicon_visibility (self):
        if self.trayicon:
            if self.suppress_icon_hide:
                # Avoid hiding the icon if we've been woken up to notify
                # about a new printer.
                self.suppress_icon_hide = False
                return

            if (not self.hidden) and (self.num_jobs > 0 or self.icon_has_emblem) or self.special_status_icon:
                self.sysTray.show()
            else:
                self.sysTray.hide()

    def on_treeview_button_press_event(self, postition):
        # Right-clicked.
        items = self.mainWindow.treeWidget.selectedItems ()
        print "items" + str(items)
        print len(items)
        if len(items) != 1:
            return
        print "selected: " + str(items)
        iter = items[0]
        if iter == None:
            return

        self.jobid = int(iter.text(0))
        job = self.jobs[self.jobid]
        self.cancel.setEnabled (True)
        self.hold.setEnabled (True)
        self.release.setEnabled (True)
        self.reprint.setEnabled (True)
        if job.has_key ('job-state'):
            s = job['job-state']
            print s, "jobstate"
            if s >= cups.IPP_JOB_CANCELED:
                self.cancel.setEnabled (False)
            if s != cups.IPP_JOB_PENDING and s != cups.IPP_JOB_PROCESSING:
                self.hold.setEnabled (False)
            if s != cups.IPP_JOB_HELD:
                self.release.setEnabled (False)
            if (s != cups.IPP_JOB_CANCELED or
                not job.get('job-preserved', False)):
                self.reprint.setEnabled (False)
        self.rightClickMenu.popup(QCursor.pos())

    def on_icon_popupmenu(self, icon, button, time):
        self.icon_popupmenu.popup (None, None, None, button, time)

    def on_icon_hide_activate(self):
        self.num_jobs_when_hidden = self.num_jobs
        self.hidden = True
        self.set_statusicon_visibility ()

    def on_icon_quit_activate(self):
        app.quit()

    def on_job_cancel_activate(self):
        try:
            c = cups.Connection ()
            c.cancelJob (self.jobid)
            del c
        except cups.IPPError, (e, m):
            self.show_IPP_Error (e, m)
            self.refresh()
            return
        except RuntimeError:
            return
        self.refresh()

    def on_job_hold_activate(self):
        try:
            c = cups.Connection ()
            c.setJobHoldUntil (self.jobid, "indefinite")
            del c
        except cups.IPPError, (e, m):
            self.show_IPP_Error (e, m)
            self.refresh()
            return
        except RuntimeError:
            return
        self.refresh()

    def on_job_release_activate(self):
        try:
            c = cups.Connection ()
            c.setJobHoldUntil (self.jobid, "no-hold")
            del c
        except cups.IPPError, (e, m):
            self.show_IPP_Error (e, m)
            self.refresh()
            return
        except RuntimeError:
            return
        self.refresh()

    def on_job_reprint_activate(self):
        try:
            c = cups.Connection ()
            c.restartJob (self.jobid)
            del c
        except cups.IPPError, (e, m):
            self.show_IPP_Error (e, m)
            self.refresh()
            return
        except RuntimeError:
            return

        self.refresh ()

    def on_refresh_activate(self, menuitem):
        self.refresh ()

    def handle_dbus_signal(self, *args):
        self.refresh ()

    ## Printer status window
    """FIXME
    def set_printer_status_icon (self, column, cell, model, iter, *user_data):
        level = model.get_value (iter, 0)
        icon = StateReason.LEVEL_ICON[level]
        theme = gtk.icon_theme_get_default ()
        try:
            pixbuf = theme.load_icon (icon, 22, 0)
            cell.set_property("pixbuf", pixbuf)
        except gobject.GError, exc:
            pass # Couldn't load icon
    """
    """FIXME
    def set_printer_status_name (self, column, cell, model, iter, *user_data):
        cell.set_property("text", model.get_value (iter, 1))
    """

####
#### NewPrinterNotification DBus server (the 'new' way).  Note: this interface
#### is not final yet.
####
PDS_PATH="/com/redhat/NewPrinterNotification"
PDS_IFACE="com.redhat.NewPrinterNotification"
PDS_OBJ="com.redhat.NewPrinterNotification"
class NewPrinterNotification(dbus.service.Object):
    """listen for dbus signals"""
    STATUS_SUCCESS = 0
    STATUS_MODEL_MISMATCH = 1
    STATUS_GENERIC_DRIVER = 2
    STATUS_NO_DRIVER = 3

    def __init__ (self, bus, jobmanager):
        self.bus = bus
        self.getting_ready = 0
        self.jobmanager = jobmanager
        bus_name = dbus.service.BusName (PDS_OBJ, bus=bus)
        dbus.service.Object.__init__ (self, bus_name, PDS_PATH)
        #self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))

    """
    def wake_up (self):
        global waitloop, runloop, jobmanager
        do_imports ()
        if jobmanager == None:
            waitloop.quit ()
            runloop = gobject.MainLoop ()
            jobmanager = JobManager(bus, runloop,
                                    service_running=service_running,
                                    trayicon=trayicon, suppress_icon_hide=True)
    """
    
    @dbus.service.method(PDS_IFACE, in_signature='', out_signature='')
    def GetReady (self):
        """hal-cups-utils is settings up a new printer"""
        self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))
    """
        self.wake_up ()
        if self.getting_ready == 0:
            jobmanager.set_special_statusicon (SEARCHING_ICON)

        self.getting_ready += 1
        gobject.timeout_add (60 * 1000, self.timeout_ready)

    def timeout_ready (self):
        global jobmanager
        if self.getting_ready > 0:
            self.getting_ready -= 1
        if self.getting_ready == 0:
            jobmanager.unset_special_statusicon ()

        return False
    """

    # When I plug in my printer HAL calls this with these args:
    #status: 0
    #name: PSC_1400_series
    #mfg: HP
    #mdl: PSC 1400 series
    #des:
    #cmd: LDL,MLC,PML,DYN
    @dbus.service.method(PDS_IFACE, in_signature='isssss', out_signature='')
    def NewPrinter (self, status, name, mfg, mdl, des, cmd):
        """hal-cups-utils has set up a new printer"""
        """
        print "status: " + str(status)
        print "name: " + name
        print "mfg: " + mfg
        print "mdl: " + mdl
        print "des: " + des
        print "cmd: " + cmd
        """

        c = cups.Connection ()
        try:
            printer = c.getPrinters ()[name]
        except KeyError:
            return
        del c

        sys.path.append (SYSTEM_CONFIG_PRINTER_DIR)
        from ppds import ppdMakeModelSplit
        (make, model) = ppdMakeModelSplit (printer['printer-make-and-model'])
        driver = make + " " + model
        if status < self.STATUS_GENERIC_DRIVER:
            title = i18n("Printer added")
        else:
            title = i18n("Missing printer driver")

        if status == self.STATUS_SUCCESS:
            text = i18n("'%1' is ready for printing.", name)
        else: # Model mismatch
            text = i18n("'%1' has been added, using the '%2' driver.", name, driver)

        self.jobmanager.notify_new_printer (name, title, text)


if __name__ == "__main__":
    """start the application.  TODO, gtk frontend does clever things here to not start the GUI until it has to"""
    appName     = "printer-applet"
    catalogue   = "printer-applet"
    programName = ki18n("Printer Applet")
    version     = "1.2"
    description = ki18n("Applet to view current print jobs and configure new printers")
    license     = KAboutData.License_GPL
    copyright   = ki18n("2007-2008 Canonical Ltd")
    text        = KLocalizedString()
    homePage    = "http://utils.kde.org/projects/printer-applet"
    bugEmail    = ""

    aboutData   = KAboutData (appName, catalogue, programName, version, description,
                                license, copyright, text, homePage, bugEmail)

    aboutData.addAuthor(ki18n("Jonathan Riddell"), ki18n("Author"))
    aboutData.addAuthor(ki18n("Tim Waugh/Red Hat"), ki18n("System Config Printer Author"))

    options = KCmdLineOptions()
    options.add("show", ki18n("Show even when nothing printing"))

    KCmdLineArgs.init(sys.argv, aboutData)
    KCmdLineArgs.addCmdLineOptions(options)

    app = KApplication()

    args = KCmdLineArgs.parsedArgs()

    app.setWindowIcon(KIcon("printer"))
    if app.isSessionRestored():
         sys.exit(1)
    applet = JobManager()
    if args.isSet("show"):
        applet.mainWindow.show()
        applet.sysTray.show()
    sys.exit(app.exec_())
