Index: pykota/trunk/conf/pykota.conf.sample
===================================================================
--- pykota/trunk/conf/pykota.conf.sample (revision 2622)
+++ pykota/trunk/conf/pykota.conf.sample (revision 2631)
@@ -346,5 +346,5 @@
 # Should we launch a command to overwrite the job's ticket ?
 # This allows a command to overwrite the username and/or the
-# billing code used, or to deny the job.
+# billing code used, or to deny or cancel the job.
 # If unset no command is launched and the job's username and
 # billing code are used as they are received.
@@ -356,4 +356,5 @@
 #    BILLINGCODE=the_billingcode_we_want_to_overwrite_with
 #    DENY 
+#    CANCEL
 #
 # NB : the output is entirely read, and the latest value
@@ -364,6 +365,6 @@
 # If only BILLINGCODE= lines are printed, the username is
 # used unchanged.
-# If DENY is output, but is followed by new USERNAME= or
-# BILLINGCODE= lines, the job is not denied.
+# If DENY or CANCEL is output, but is followed by new USERNAME= or
+# BILLINGCODE= lines, the job is not denied nor cancelled.
 #
 # This value can be set either globally or on a per printer basis
@@ -388,4 +389,6 @@
 # If both are defined, the printer option has priority.
 #
+# unknown_billingcode : deny
+# unknown_billingcode : deny(/path/to/some/script)
 # unknown_billingcode : create
 
Index: pykota/trunk/pykota/version.py
===================================================================
--- pykota/trunk/pykota/version.py (revision 2622)
+++ pykota/trunk/pykota/version.py (revision 2631)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.24alpha7_unofficial"
+__version__ = "1.24alpha8_unofficial"
 
 __doc__ = "PyKota : a complete Printing Quota Solution for CUPS."
Index: pykota/trunk/cgi-bin/printquota.cgi
===================================================================
--- pykota/trunk/cgi-bin/printquota.cgi (revision 2622)
+++ pykota/trunk/cgi-bin/printquota.cgi (revision 2631)
@@ -234,4 +234,6 @@
                         elif job.JobAction == "PROBLEM" :
                             oddevenclass = "problem"
+                        elif job.JobAction == "CANCEL" :
+                            oddevenclass = "cancel"
                         username_url = '<a href="%s?%s">%s</a>' % (os.environ.get("SCRIPT_NAME", ""), urllib.urlencode({"history" : 1, "username" : job.UserName}), job.UserName)
                         printername_url = '<a href="%s?%s">%s</a>' % (os.environ.get("SCRIPT_NAME", ""), urllib.urlencode({"history" : 1, "printername" : job.PrinterName}), job.PrinterName)
Index: pykota/trunk/bin/pkbanner
===================================================================
--- pykota/trunk/bin/pkbanner (revision 2627)
+++ pykota/trunk/bin/pkbanner (revision 2631)
@@ -188,4 +188,10 @@
         elif action == "WARN" :    
             action = _("Allowed with Warning")
+        elif action == "PROBLEM" :    
+            # should never occur
+            action = _("Problem")
+        elif action == "CANCEL" :    
+            # should never occur
+            action = _("Cancelled")
         ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
         
Index: pykota/trunk/bin/cupspykota
===================================================================
--- pykota/trunk/bin/cupspykota (revision 2624)
+++ pykota/trunk/bin/cupspykota (revision 2631)
@@ -296,4 +296,7 @@
                     self.logdebug("Seen DENY command.")
                     action = "DENY"
+                elif line == "CANCEL" :
+                    self.logdebug("Seen CANCEL command.")
+                    action = "CANCEL"
                 elif line.startswith("USERNAME=") :    
                     username = line.split("=", 1)[1].strip()
@@ -307,7 +310,10 @@
             
             # now overwrite the job's ticket if new data was supplied
-            if action :
+            if action == "DENY" :
                 self.Action = action
                 self.Reason = _("You are not allowed to print at this time.")
+            elif action == "CANCEL" :
+                self.Action = action
+                self.Reason = _("Print job cancelled.")
             if username :
                 self.UserName = username
@@ -891,5 +897,5 @@
         self.exportPhaseInfo("BEFORE")
         
-        if self.Action != "DENY" : 
+        if self.Action not in ("DENY", "CANCEL") : 
             if self.Printer.MaxJobSize and (self.softwareJobSize > self.Printer.MaxJobSize) :
                 # This printer was set to refuse jobs this large.
@@ -900,5 +906,5 @@
                 self.Reason = _("You are not allowed to print so many pages on printer %s at this time.") % self.PrinterName
             
-        if self.Action != "DENY" :
+        if self.Action not in ("DENY", "CANCEL") :
             if self.User.LimitBy == "noprint" :
                 self.printInfo(_("User %s is not allowed to print at this time.") % self.UserName, "warn")
@@ -906,5 +912,5 @@
                 self.Reason = _("Your account settings forbid you to print at this time.")
                 
-        if self.Action != "DENY" :
+        if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
             # need to extract the billing code information from the database.
@@ -913,5 +919,5 @@
             self.getBillingCode()
             
-        if self.Action != "DENY" :
+        if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
             # need to check if the job is a dupe or not, and what to do then.
@@ -920,5 +926,5 @@
             self.checkIfDupe()
                     
-        if self.Action != "DENY" :
+        if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
             # need to check the user's print quota on the current printer.
@@ -962,4 +968,6 @@
         if self.Action == "DENY" :
             self.printInfo(_("Job denied, no accounting will be done."))
+        elif self.Action == "CANCEL" :    
+            self.printInfo(_("Job cancelled, no accounting will be done."))
         else :
             self.printInfo(_("Job accounting begins."))
@@ -990,4 +998,6 @@
         if self.Action == "DENY" :
             self.printInfo(_("Job denied, no accounting has been done."))
+        elif self.Action == "CANCEL" :    
+            self.printInfo(_("Job cancelled, no accounting has been done."))
         else :
             self.deinstallSigTermHandler()
@@ -1020,4 +1030,7 @@
                 self.JobSize = 0
                 self.printInfo(_("Job size forced to 0 because printing is denied."))
+            elif self.Action == "CANCEL" :     
+                self.JobSize = 0
+                self.printInfo(_("Job size forced to 0 because printing was cancelled."))
             else :    
                 self.UserPQuota.resetDenyBannerCounter()
@@ -1028,5 +1041,6 @@
             self.printInfo(_("Job size : %i") % self.JobSize)
             
-            if (self.Action == "PROBLEM") and (onbackenderror == "NOCHARGE") :
+            if ((self.Action == "PROBLEM") and (onbackenderror == "NOCHARGE")) or \
+                (self.Action in ("DENY", "CANCEL")) :
                 self.JobPrice = 0.0
             elif (self.User.LimitBy == "nochange") or self.Printer.PassThrough :
@@ -1048,5 +1062,6 @@
             
             if hasattr(self, "BillingCode") and self.BillingCode and self.BillingCode.Exists :
-                if (self.Action != "PROBLEM") or (onbackenderror == "CHARGE") :
+                if (self.Action in ("ALLOW", "WARN")) or \
+                   ((self.Action == "PROBLEM") and (onbackenderror == "CHARGE")) :
                     self.BillingCode.consume(self.JobSize, self.JobPrice)
                     self.printInfo(_("Billing code %s was updated.") % self.BillingCode.BillingCode)
Index: pykota/trunk/initscripts/ldap/pykota.schema
===================================================================
--- pykota/trunk/initscripts/ldap/pykota.schema (revision 2585)
+++ pykota/trunk/initscripts/ldap/pykota.schema (revision 2631)
@@ -104,5 +104,5 @@
 # pykotaAction
 attributetype ( 1.3.6.1.4.1.16868.1.1.14 NAME 'pykotaAction'
-        DESC 'Was the job allowed, denied, or failed : ( "ALLOW" |  "DENY" | "PROBLEM" )'
+        DESC 'Was the job allowed, denied, warned, cancelled or failed : ( "ALLOW" |  "DENY" | "WARN" | "CANCEL" | "PROBLEM" )'
         EQUALITY caseExactIA5Match
         SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
Index: pykota/trunk/NEWS
===================================================================
--- pykota/trunk/NEWS (revision 2627)
+++ pykota/trunk/NEWS (revision 2631)
@@ -22,4 +22,10 @@
 PyKota NEWS :
        
+    - 1.24alpha8 :
+    
+        - Now subprocesses launched through the overwrite_jobticket directive
+          can also output 'CANCEL', to represent the user's own choice to
+          cancel the print job.
+          
     - 1.24alpha7 :
      
Index: pykota/trunk/stylesheets/pykota.css
===================================================================
--- pykota/trunk/stylesheets/pykota.css (revision 2622)
+++ pykota/trunk/stylesheets/pykota.css (revision 2631)
@@ -44,4 +44,8 @@
       }
       
+.cancel {     
+        background-color: #BEDBED;
+      }
+      
 .pykotacolsheader {      
         background-color: #AAAAAA;
