Changeset 3439 for pykoticon/trunk/bin/pykoticon
- Timestamp:
- 10/06/08 00:29:29 (15 years ago)
- Files:
-
- 1 modified
Legend:
- Unmodified
- Added
- Removed
-
pykoticon/trunk/bin/pykoticon
r180 r3439 1 1 #! /usr/bin/env python 2 # -*- coding: ISO-8859-15 -*-2 # -*- coding: iso-8859-15 -*- 3 3 4 4 """PyKotIcon is a generic, networked, cross-platform dialog box manager.""" … … 16 16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 17 # GNU General Public License for more details. 18 # 18 # 19 19 # You should have received a copy of the GNU General Public License 20 20 # along with this program; if not, write to the Free Software … … 45 45 try : 46 46 import optparse 47 except ImportError : 47 except ImportError : 48 48 sys.stderr.write("You need Python v2.3 or higher for PyKotIcon to work.\nAborted.\n") 49 49 sys.exit(-1) … … 53 53 try : 54 54 import win32api 55 except ImportError : 55 except ImportError : 56 56 raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them." 57 else : 57 else : 58 58 iconsdir = os.path.split(sys.argv[0])[0] 59 else : 59 else : 60 60 isWindows = False 61 61 iconsdir = "/usr/share/pykoticon" # TODO : change this 62 62 import pwd 63 64 try : 63 64 try : 65 65 import wx 66 66 hasWxPython = True 67 except ImportError : 67 except ImportError : 68 68 hasWxPython = False 69 69 raise ImportError, "wxPython is missing. Please install it." 70 70 71 71 aboutbox = """PyKotIcon v%(__version__)s (c) 2003-2006 %(__author__)s - %(__author_email__)s 72 72 … … 98 98 try : 99 99 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) 100 except TypeError : 100 except TypeError : 101 101 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, False, None) 102 102 SocketServer.ThreadingTCPServer.__init__(self, addr, requestHandler) 103 103 104 104 class MyXMLRPCServer(ThreadedXMLRPCServer) : 105 105 """My own server class.""" … … 119 119 loop = threading.Thread(target=self.mainloop) 120 120 loop.start() 121 122 def logDebug(self, message) : 121 122 def logDebug(self, message) : 123 123 """Logs a debug message if debug mode is active.""" 124 124 if self.debug : 125 125 sys.stderr.write("%s\n" % message) 126 126 127 127 def getAnswerFromCache(self, key) : 128 128 """Tries to extract a value from the cache and returns it if still valid.""" 129 129 cacheentry = self.cache.get(key) 130 130 if cacheentry is not None : 131 (birth, value) = cacheentry 131 (birth, value) = cacheentry 132 132 if (time.time() - birth) < self.cacheduration : 133 133 self.logDebug("Cache hit for %s" % str(key)) 134 134 return value # NB : we don't extend the life of this entry 135 else : 135 else : 136 136 self.logDebug("Cache expired for %s" % str(key)) 137 else : 137 else : 138 138 self.logDebug("Cache miss for %s" % str(key)) 139 139 return None 140 141 def storeAnswerInCache(self, key, value) : 140 141 def storeAnswerInCache(self, key, value) : 142 142 """Stores an entry in the cache.""" 143 143 self.cache[key] = (time.time(), value) 144 144 self.logDebug("Cache store for %s" % str(key)) 145 145 146 146 def export_askDatas(self, labels, varnames, varvalues) : 147 147 """Asks some textual datas defined by a list of labels, a list of variables' names and a list of variables values in a mapping.""" … … 149 149 for (key, value) in varvalues.items() : 150 150 values[key] = self.frame.UTF8ToUserCharset(value.data) 151 cachekey = tuple(values.items()) 151 cachekey = tuple(values.items()) 152 152 retcode = self.getAnswerFromCache(cachekey) 153 153 if (retcode is None) or (not retcode["isValid"]) : … … 158 158 while self.frame.dialogAnswer is None : 159 159 time.sleep(0.1) 160 retcode = self.frame.dialogAnswer 160 retcode = self.frame.dialogAnswer 161 161 for (key, value) in retcode.items() : 162 162 if key != "isValid" : … … 165 165 self.storeAnswerInCache(cachekey, retcode) 166 166 return retcode 167 168 def export_quitApplication(self) : 167 168 def export_quitApplication(self) : 169 169 """Makes the application quit.""" 170 170 self.frame.quitEvent.set() 171 171 wx.CallAfter(self.frame.OnClose, None) 172 172 return True 173 173 174 174 def export_showDialog(self, message, yesno) : 175 175 """Opens a notification or confirmation dialog.""" … … 178 178 while self.frame.dialogAnswer is None : 179 179 time.sleep(0.1) 180 retcode = self.frame.dialogAnswer 180 retcode = self.frame.dialogAnswer 181 181 self.frame.dialogAnswer = None # prepare for next call, just in case 182 182 return retcode 183 184 def export_nop(self) : 183 184 def export_nop(self) : 185 185 """Does nothing, but allows a clean shutdown from the frame itself.""" 186 186 return True 187 188 def _dispatch(self, method, params) : 187 188 def _dispatch(self, method, params) : 189 189 """Ensure that only export_* methods are available.""" 190 190 return getattr(self, "export_%s" % method)(*params) 191 192 def handle_error(self, request, client_address) : 191 192 def handle_error(self, request, client_address) : 193 193 """Doesn't display an ugly traceback in case an error occurs.""" 194 194 self.logDebug("An exception occured while handling an incoming request from %s:%s" % (client_address[0], client_address[1])) 195 195 196 196 def verify_request(self, request, client_address) : 197 197 """Ensures that requests which don't come from the print server are rejected.""" … … 204 204 self.logDebug("%s rejected." % client) 205 205 return False 206 206 207 207 def mainloop(self) : 208 208 """XML-RPC Server's main loop.""" … … 213 213 while not self.frame.quitEvent.isSet() : 214 214 self.handle_request() 215 self.server_close() 215 self.server_close() 216 216 sys.exit(0) 217 218 217 218 219 219 class GenericInputDialog(wx.Dialog) : 220 220 """Generic input dialog box.""" … … 233 233 try : 234 234 label = labels[i] 235 except IndexError : 235 except IndexError : 236 236 label = "" 237 labelid = wx.NewId() 237 labelid = wx.NewId() 238 238 varid = wx.NewId() 239 239 labelst = wx.StaticText(self, labelid, label) … … 242 242 else : 243 243 variable = wx.TextCtrl(self, varid, varvalues.get(varname, "")) 244 self.variables.append(variable) 244 self.variables.append(variable) 245 245 hsizer = wx.BoxSizer(wx.HORIZONTAL) 246 246 hsizer.Add(labelst, 0, wx.ALIGN_CENTER | wx.ALIGN_RIGHT | wx.ALL, 5) 247 247 hsizer.Add(variable, 0, wx.ALIGN_CENTER | wx.ALIGN_LEFT | wx.ALL, 5) 248 248 vsizer.Add(hsizer, 0, wx.ALIGN_CENTER | wx.ALL, 5) 249 250 okbutton = wx.Button(self, wx.ID_OK, "OK") 249 250 okbutton = wx.Button(self, wx.ID_OK, "OK") 251 251 vsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 252 252 if self.variables : … … 255 255 self.SetSizerAndFit(vsizer) 256 256 self.Layout() 257 258 257 258 259 259 class PyKotIcon(wx.Frame): 260 260 """Main class.""" … … 265 265 size = (0, 0), \ 266 266 style = wx.FRAME_NO_TASKBAR | wx.NO_FULL_REPAINT_ON_RESIZE) 267 267 268 268 def getCurrentUserName(self) : 269 269 """Retrieves the current user's name.""" … … 271 271 if isWindows : 272 272 return win32api.GetUserName() 273 else : 273 else : 274 274 return pwd.getpwuid(os.geteuid())[0] 275 275 except : 276 276 return "** Unknown **" 277 277 278 278 def OnIconify(self, event) : 279 279 """Iconify/De-iconify the application.""" … … 299 299 self.Destroy() 300 300 return True 301 else : 301 else : 302 302 # self.quitIsForbidden() 303 303 return False … … 310 310 """React to close from the taskbar.""" 311 311 self.Close() 312 313 def quitIsForbidden(self) : 312 313 def quitIsForbidden(self) : 314 314 """Displays a message indicating that quitting the application is not allowed.""" 315 315 message = _("Sorry, this was forbidden by your system administrator.") … … 319 319 dialog.ShowModal() 320 320 dialog.Destroy() 321 322 def OnAbout(self, event) : 321 322 def OnAbout(self, event) : 323 323 """Displays the about box.""" 324 324 dialog = wx.MessageDialog(self, aboutbox % globals(), \ 325 325 _("About"), \ 326 326 wx.OK | wx.ICON_INFORMATION) 327 dialog.Raise() 327 dialog.Raise() 328 328 dialog.ShowModal() 329 329 dialog.Destroy() 330 330 331 331 def showDialog(self, message, yesno) : 332 332 """Opens a notification dialog.""" … … 338 338 caption = _("Information") 339 339 style = wx.OK | wx.ICON_INFORMATION 340 style |= wx.STAY_ON_TOP 340 style |= wx.STAY_ON_TOP 341 341 dialog = wx.MessageDialog(self, message, caption, style) 342 342 dialog.Raise() 343 343 self.dialogAnswer = ((dialog.ShowModal() == wx.ID_NO) and "CANCEL") or "OK" 344 344 dialog.Destroy() 345 345 346 346 def askDatas(self, labels, varnames, varvalues) : 347 347 """Opens a dialog box asking for data entry.""" … … 354 354 for i in range(len(varnames)) : 355 355 retvalues[varnames[i]] = dialog.variables[i].GetValue() 356 else : 356 else : 357 357 retvalues["isValid"] = False 358 358 for k in varvalues.keys() : … … 360 360 self.dialogAnswer = retvalues 361 361 dialog.Destroy() 362 363 def closeServer(self) : 362 363 def closeServer(self) : 364 364 """Tells the xml-rpc server to exit.""" 365 365 if not self.quitEvent.isSet() : 366 366 self.quitEvent.set() 367 server = xmlrpclib.ServerProxy("http://localhost:%s" % self.options.port) 367 server = xmlrpclib.ServerProxy("http://localhost:%s" % self.options.port) 368 368 try : 369 # wake the server with an empty request 369 # wake the server with an empty request 370 370 # for it to see the event object 371 371 # which has just been set 372 372 server.nop() 373 except : 373 except : 374 374 # Probably already stopped 375 375 pass 376 377 def postInit(self, charset, options, arguments) : 376 377 def postInit(self, charset, options, arguments) : 378 378 """Starts the XML-RPC server.""" 379 379 self.charset = charset 380 380 self.options = options 381 381 382 382 self.tbicon = wx.TaskBarIcon() 383 383 self.greenicon = wx.Icon(os.path.join(iconsdir, "pykoticon-green.ico"), \ … … 386 386 wx.BITMAP_TYPE_ICO) 387 387 self.tbicon.SetIcon(self.greenicon, "PyKotIcon") 388 388 389 389 wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate) 390 390 wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu) 391 391 392 392 self.TBMENU_ABOUT = wx.NewId() 393 393 self.TBMENU_RESTORE = wx.NewId() … … 408 408 self.Show(True) 409 409 self.Hide() 410 410 411 411 self.quitEvent = threading.Event() 412 412 self.server = MyXMLRPCServer(self, options, arguments) 413 413 414 414 def UTF8ToUserCharset(self, text) : 415 415 """Converts from UTF-8 to user's charset.""" 416 416 if text is not None : 417 417 try : 418 return text.decode("UTF-8").encode(self.charset, "replace") 419 except (UnicodeError, AttributeError) : 418 return text.decode("UTF-8").encode(self.charset, "replace") 419 except (UnicodeError, AttributeError) : 420 420 try : 421 421 # Maybe already in Unicode 422 return text.encode(self.charset, "replace") 422 return text.encode(self.charset, "replace") 423 423 except (UnicodeError, AttributeError) : 424 424 pass # Don't know what to do 425 425 return text 426 426 427 427 def userCharsetToUTF8(self, text) : 428 428 """Converts from user's charset to UTF-8.""" … … 432 432 except (UnicodeError, AttributeError) : 433 433 try : 434 return text.decode(self.charset, "replace").encode("UTF-8") 435 except (UnicodeError, AttributeError) : 434 return text.decode(self.charset, "replace").encode("UTF-8") 435 except (UnicodeError, AttributeError) : 436 436 try : 437 437 # Maybe already in Unicode 438 return text.encode("UTF-8", "replace") 438 return text.encode("UTF-8", "replace") 439 439 except (UnicodeError, AttributeError) : 440 440 pass # Don't know what to do 441 441 return text 442 442 443 443 444 444 class PyKotIconApp(wx.App): … … 448 448 self.SetTopWindow(self.frame) 449 449 return True 450 451 def postInit(self, charset, options, arguments) : 450 451 def postInit(self, charset, options, arguments) : 452 452 """Continues processing.""" 453 453 self.frame.postInit(charset, options, arguments) 454 455 454 455 456 456 def main() : 457 457 """Program's entry point.""" … … 464 464 language = language or "C" 465 465 charset = ((sys.platform != "win32") and charset) or locale.getpreferredencoding() 466 466 467 467 # translation stuff 468 468 try : … … 474 474 except : 475 475 gettext.NullTranslations().install() 476 477 476 477 478 478 parser = optparse.OptionParser(usage="pykoticon [options] server1 [server2 ...]") 479 parser.add_option("-v", "--version", 480 action="store_true", 479 parser.add_option("-v", "--version", 480 action="store_true", 481 481 dest="version", 482 482 help=_("show PyKotIcon's version number and exit.")) 483 parser.add_option("-c", "--cache", 484 type="int", 485 default=0, 483 parser.add_option("-c", "--cache", 484 type="int", 485 default=0, 486 486 dest="cache", 487 487 help=_("the duration of the cache in seconds to keep input forms' datas in memory. Defaults to 0 second, meaning no cache.")) 488 parser.add_option("-d", "--debug", 489 action="store_true", 488 parser.add_option("-d", "--debug", 489 action="store_true", 490 490 dest="debug", 491 491 help=_("activate debug mode.")) 492 parser.add_option("-p", "--port", 493 type="int", 494 default=7654, 492 parser.add_option("-p", "--port", 493 type="int", 494 default=7654, 495 495 dest="port", 496 496 help=_("the TCP port PyKotIcon will listen to, default is 7654.")) 497 parser.add_option("-q", "--allowquit", 498 action="store_true", 497 parser.add_option("-q", "--allowquit", 498 action="store_true", 499 499 dest="allowquit", 500 500 help=_("allow the end user to close the application.")) … … 505 505 if not (1024 <= options.port <= 65535) : 506 506 sys.stderr.write(_("The TCP port number specified for --port must be between 1024 and 65535.\n")) 507 elif not (0 <= options.cache <= 86400) : 507 elif not (0 <= options.cache <= 86400) : 508 508 sys.stderr.write(_("The duration specified for --cache must be between 0 and 86400 seconds.\n")) 509 else : 509 else : 510 510 app = PyKotIconApp() 511 511 app.postInit(charset, options, arguments) 512 512 app.MainLoop() 513 514 513 514 515 515 if __name__ == '__main__': 516 516 main() 517 517