root / pykoticon / trunk / bin / pykoticon @ 105

Revision 105, 12.2 kB (checked in by jerome, 18 years ago)

Generic modal dialog now works.

  • Property svn:keywords set to Id
RevLine 
[47]1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
[89]4# PyKotIcon - Client side helper for PyKota
[47]5#
[89]6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
[47]7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
[53]21# $Id$
[47]22#
23#
24
25import sys
[63]26import os
[47]27import urllib
28import urllib2
[65]29import locale
30import gettext
[87]31import socket
32import threading
[88]33import xmlrpclib
[87]34import SimpleXMLRPCServer
[47]35
[87]36import time
37
[65]38if sys.platform == "win32" :
39    isWindows = 1
40    try :
41        import win32api
42    except ImportError :   
43        raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them."
[76]44    else :   
[85]45        iconsdir = os.path.split(sys.argv[0])[0]
[65]46else :       
47    isWindows = 0
[76]48    iconsdir = "/usr/share/pykoticon"   # TODO : change this
[65]49    import pwd
[57]50   
[58]51try :   
52    import wx
53    hasWxPython = 1
54except ImportError :   
55    hasWxPython = 0
[75]56    raise ImportError, "wxPython is missing. Please install it."
[58]57   
[65]58def getCurrentUserName() :
59    """Retrieves the current user's name."""
60    if isWindows :
61        return win32api.GetUserName()
62    else :   
63        try :
64            return pwd.getpwuid(os.geteuid())[0]
65        except :
66            return "** Unknown **"
67       
[87]68class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) :
69    """My own server class."""
[88]70    allow_reuse_address = True
71    def __init__(self, frame, printserver, localport, debug=False) :
72        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, ('0.0.0.0', localport))
[87]73        self.printServer = printserver
74        self.frame = frame
[88]75        self.debug = debug
[87]76        loop = threading.Thread(target=self.mainloop)
77        loop.start()
[56]78       
[88]79    def logDebug(self, message) :   
80        """Logs a debug message if debug mode is active."""
81        if self.debug :
82            sys.stderr.write("%s\n" % message)
83           
[87]84    def export_quitApplication(self) :   
85        """Makes the application quit."""
[88]86        self.logDebug("Remote host asked to close the application.")
[87]87        self.frame.quitEvent.set()
[92]88        wx.CallAfter(self.frame.OnClose, None)
[87]89        return True
[55]90       
[103]91    def export_askDatas(self, labels, varnames, varvalues) :
92        """Asks some textual datas defined by a list of labels, a list of variables' names and a list of variables values in a mapping."""
93        wx.CallAfter(self.frame.askDatas, labels, varnames, varvalues)
[102]94        # ugly, isn't it ?
[105]95        while self.frame.dialogAnswer is None :
96            time.sleep(0.1)
[102]97        # TODO : add value extraction and return a mapping
[105]98        retcode = self.frame.dialogAnswer   
[102]99        self.frame.dialogAnswer = None # prepare for next call, just in case
[105]100        return retcode
[102]101       
[100]102    def export_showDialog(self, message, yesno) :
103        """Opens a notification or confirmation dialog."""
104        wx.CallAfter(self.frame.showDialog, message, yesno)
[92]105        # ugly, isn't it ?
[100]106        while self.frame.dialogAnswer is None :
[97]107            time.sleep(0.1)
[100]108        retcode = self.frame.dialogAnswer   
109        self.frame.dialogAnswer = None # prepare for next call, just in case
[91]110        return retcode
111       
[88]112    def export_nop(self) :   
113        """Does nothing, but allows a clean shutdown from the frame itself."""
114        self.logDebug("No operation !")
115        return True
116       
[87]117    def verify_request(self, request, client_address) :
118        """Ensures that requests which don't come from the print server are rejected."""
119        (client, port) = client_address
120        if socket.gethostbyname(self.printServer) == client :
[88]121            self.logDebug("%s accepted." % client)
[87]122            return True
123        else :
124            # Unauthorized access !
[88]125            self.logDebug("%s rejected." % client)
[87]126            return False
[55]127       
[87]128    def _dispatch(self, method, params) :   
129        """Ensure that only export_* methods are available."""
130        return getattr(self, "export_%s" % method)(*params)
[55]131       
[87]132    def mainloop(self) :
133        """XML-RPC Server's main loop."""
[102]134        self.register_function(self.export_askDatas)
[100]135        self.register_function(self.export_showDialog)
[87]136        self.register_function(self.export_quitApplication)
[88]137        self.register_function(self.export_nop)
[87]138        while not self.frame.quitEvent.isSet() :
139            self.handle_request()
[88]140        self.server_close()   
[87]141        sys.exit(0)
[47]142   
[105]143class GenericInputDialog(wx.Dialog) :
[104]144    """Generic input dialog box."""
145    def __init__(self, parent, id, labels, varnames, varvalues):
[105]146        wx.Dialog.__init__(self, parent, id, \
[104]147               _("PyKotIcon data input"), \
[105]148               style = wx.CAPTION \
149                     | wx.THICK_FRAME \
[104]150                     | wx.STAY_ON_TOP \
[105]151                     | wx.DIALOG_MODAL)
152
[104]153        vsizer = wx.BoxSizer(wx.VERTICAL)
154        for i in range(len(varnames)) :
155            varname = varnames[i]
156            try :
157                label = labels[i]
158            except IndexError :   
159                label = ""
160            labelid = wx.NewId()   
161            varid = wx.NewId()
162            label = wx.StaticText(self, labelid, label)
163            variable = wx.TextCtrl(self, varid, varvalues.get(varname, ""))
164            hsizer = wx.BoxSizer(wx.HORIZONTAL)
165            hsizer.Add(label, 0, wx.ALIGN_CENTER | wx.ALIGN_RIGHT | wx.ALL, 5)
166            hsizer.Add(variable, 0, wx.ALIGN_CENTER | wx.ALIGN_LEFT | wx.ALL, 5)
167            vsizer.Add(hsizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
168           
[105]169        okbutton = wx.Button(self, wx.ID_OK, "OK")   
[104]170        vsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
[105]171       
[104]172        self.SetAutoLayout(True)
173        self.SetSizerAndFit(vsizer)
174        self.Layout()
175       
[87]176class PyKotIcon(wx.Frame):
[63]177    """Main class."""
178    def __init__(self, parent, id):
[100]179        self.dialogAnswer = None
[104]180        wx.Frame.__init__(self, parent, id, \
181               _("PyKotIcon info for %s") % getCurrentUserName(), \
[87]182               size = (-1, -1), \
[104]183               style = wx.DEFAULT_FRAME_STYLE \
184                     | wx.SIZE_AUTO_HEIGHT \
185                     | wx.SIZE_AUTO_WIDTH \
186                     | wx.NO_FULL_REPAINT_ON_RESIZE)
187        try :             
188            self.tbicon = wx.TaskBarIcon()
189        except AttributeError :   
190            self.tbicon = None # No taskbar icon facility
191       
192        self.greenicon = wx.Icon(os.path.join(iconsdir, "pykoticon-green.ico"), \
193                                  wx.BITMAP_TYPE_ICO)
194        self.redicon = wx.Icon(os.path.join(iconsdir, "pykoticon-red.ico"), \
195                                  wx.BITMAP_TYPE_ICO)
196       
197        self.SetIcon(self.greenicon)
198        if self.tbicon is not None :
199            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
200            wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
201            wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
202       
203            self.TBMENU_RESTORE = wx.NewId()
204            self.TBMENU_CLOSE = wx.NewId()
205            wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
206                                              self.OnTaskBarActivate)
207            wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
208                                              self.OnTaskBarClose)
209            self.menu = wx.wxMenu()
210            self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
211            self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
212       
213        wx.EVT_ICONIZE(self, self.OnIconify)
214        wx.EVT_CLOSE(self, self.OnClose)
[91]215        self.Show(True)
[64]216       
[59]217    def OnIconify(self, event) :
[104]218        if not self.IsIconized() :
219            self.Iconize(True)
220        #self.Hide()
[58]221
[59]222    def OnTaskBarActivate(self, event) :
[104]223        if self.IsIconized() :
224            self.Iconize(False)
[59]225        if not self.IsShown() :
[58]226            self.Show(True)
227        self.Raise()
228
[64]229    def OnClose(self, event) :
[88]230        self.closeServer()
[58]231        if hasattr(self, "menu") :
[64]232            self.menu.Destroy()
[58]233            del self.menu
[87]234        if hasattr(self, "tbicon") and self.tbicon :
[58]235            self.tbicon.Destroy()
236            del self.tbicon
237        self.Destroy()
238
[91]239    def OnTaskBarMenu(self, event) :
[104]240        if self.tbicon :
241            self.tbicon.PopupMenu(self.menu)
[58]242
[91]243    def OnTaskBarClose(self, event) :
[58]244        self.Close()
[91]245       
[100]246    def showDialog(self, message, yesno) :
247        """Opens a notification dialog."""
248        self.dialogAnswer = None
249        if yesno :
250            caption = _("Confirmation")
251            style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION
252        else :
253            caption = _("Information")
254            style = wx.OK | wx.ICON_INFORMATION
255        style |= wx.STAY_ON_TOP   
256        dialog = wx.MessageDialog(self, message, caption, style)
257        self.dialogAnswer = ((dialog.ShowModal() == wx.ID_NO) and "CANCEL") or "OK"
258        dialog.Destroy()
[91]259       
[103]260    def askDatas(self, labels, varnames, varvalues) :
[102]261        """Opens a dialog box asking for data entry."""
262        # use it this way : self.askDatas(["Username", "Password", "Billing code"], ["username", "password", "billingcode"])
[105]263        self.dialogAnswer = None
264        dialog = GenericInputDialog(self, wx.ID_ANY, labels, varnames, varvalues)
265        self.dialogAnswer = ((dialog.ShowModal() == wx.ID_OK) and "OK") or "CANCEL"
266        print "Result ===> %s" % self.dialogAnswer
267        dialog.Destroy()
[104]268       
269    def closeServer(self) :   
270        """Tells the xml-rpc server to exit."""
271        if not self.quitEvent.isSet() :
272            self.quitEvent.set()
273        server = xmlrpclib.ServerProxy("http://localhost:%s" % self.port)   
274        try :
275            # wake the server with an empty request
276            # for it to see the event object
277            # which has just been set
278            server.nop()
279        except :   
280            # Probably already stopped
281            pass
282       
283    def postInit(self, printserver, localport) :   
284        """Starts the XML-RPC server."""
285        self.quitEvent = threading.Event()
286        self.port = localport
287        self.server = MyXMLRPCServer(self, printserver, localport, debug=True)
288   
[92]289
[72]290class PyKotIconApp(wx.PySimpleApp):
[58]291    def OnInit(self) :
[104]292        self.frame = PyKotIcon(None, wx.ID_ANY)
[58]293        return True
294       
[87]295    def postInit(self, printserver, localport) :   
[81]296        """Continues processing."""
[87]297        self.frame.postInit(printserver, localport)
[104]298        #self.frame.Show(True)
[81]299       
[87]300def main(printserver, localport):
[63]301    """Program's entry point."""
[65]302    try :
303        locale.setlocale(locale.LC_ALL, "")
304    except (locale.Error, IOError) :
305        sys.stderr.write("Problem while setting locale.\n")
306    try :
307        gettext.install("pykoticon")
308    except :
309        gettext.NullTranslations().install()
[63]310    app = PyKotIconApp()
[87]311    try :
312        localport = int(localport)   
313    except (TypeError, ValueError) :   
314        raise ValueError, "Invalid TCP port parameter %s\n" % localport
315    app.postInit(printserver, localport)
[63]316    app.MainLoop()
317   
[58]318def crashed() :   
319    """Minimal crash method."""
320    import traceback
321    lines = []
322    for line in traceback.format_exception(*sys.exc_info()) :
323        lines.extend([l for l in line.split("\n") if l])
324    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
325    sys.stderr.write(msg)
326    sys.stderr.flush()
[47]327   
[58]328if __name__ == '__main__':
[81]329    if len(sys.argv) >= 2 :
330        arg = sys.argv[1]
331        if arg in ("-v", "--version") :   
[87]332            print "0.3"
[81]333        elif arg in ("-h", "--help") :   
[93]334            sys.stderr.write("usage : pykoticon  pykota_server_hostname_or_ip_address  localTCPPort\n")
[81]335        else :
[87]336            main(*sys.argv[1:3])
[58]337    else :   
[93]338        sys.stderr.write("usage : pykoticon  pykota_server_hostname_or_ip_address  localTCPPort\n")
Note: See TracBrowser for help on using the browser.