# Copyright (c) 2001-2004 Twisted Matrix Laboratories. # See LICENSE for details. # """Implementation module for the graphical version of the `mktap` command. """ # System imports import Tkinter, tkMessageBox, tkFileDialog, StringIO, os import traceback # Twisted imports from twisted.application import service from twisted.internet import tksupport, reactor from twisted.scripts import mktap from twisted.python import usage, reflect from twisted.copyright import version class TkMkAppFrame(Tkinter.Frame): """ A frame with all the necessary widgets to configure a Twisted Application. """ # Plugin currently selected coil = None # Options instance currently displayed options = None # Frame options are displayed in optFrame = None def __init__(self, master, coil): Tkinter.Frame.__init__(self, master) self.setupMkTap() self.reset(coil) def setupMkTap(self): # Create all of the "mktap" option widgets appFrame = Tkinter.Frame(self) f = Tkinter.Frame(appFrame) listLabel = Tkinter.Label(f, text='TAp Format') self.typeList = Tkinter.Listbox(f, background='white') self.typeList['height'] = 3 for t in ('pickle', 'xml', 'source'): self.typeList.insert(Tkinter.END, t) self.typeList.selection_set(0) listLabel.pack(side=Tkinter.TOP) self.typeList.pack(side=Tkinter.TOP) f.pack(side=Tkinter.LEFT, anchor=Tkinter.N) f = Tkinter.Frame(appFrame) tapLabel = Tkinter.Label(f, text='TAp Filename') tapButton = Tkinter.Button(f, text="Choose", command=self.pickTapFile) self.tapfile = Tkinter.Entry(f, background='white') tapLabel.pack(side=Tkinter.LEFT) self.tapfile.pack(side=Tkinter.LEFT) tapButton.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.TOP, anchor=Tkinter.E) f = Tkinter.Frame(appFrame) nameLabel = Tkinter.Label(f, text='Application Process Name') self.appname = Tkinter.Entry(f, background='white') nameLabel.pack(side=Tkinter.LEFT) self.appname.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.TOP, anchor=Tkinter.E) f = Tkinter.Frame(appFrame) encLabel = Tkinter.Label(f, text='Passphrase') self.passphrase = Tkinter.Entry(f, background='white') encLabel.pack(side=Tkinter.LEFT) self.passphrase.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.TOP, anchor=Tkinter.E) f = Tkinter.Frame(appFrame) self.append = Tkinter.BooleanVar() appLabel = Tkinter.Label(f, text='Append') appButton = Tkinter.Checkbutton(f, variable=self.append) appLabel.pack(side=Tkinter.LEFT) appButton.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.LEFT, anchor=Tkinter.E) f = Tkinter.Frame(appFrame) s = Tkinter.StringVar() s.set(not hasattr(os, 'getuid') and '0' or str(os.getuid())) uidLabel = Tkinter.Label(f, text='UID') self.uid = Tkinter.Entry(f, text=s, background='white') uidLabel.pack(side=Tkinter.LEFT) self.uid.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.BOTTOM) f = Tkinter.Frame(appFrame) s = Tkinter.StringVar() s.set(not hasattr(os, 'getgid') and '0' or str(os.getgid())) gidLabel = Tkinter.Label(f, text='GID') self.gid = Tkinter.Entry(f, text=s, background='white') gidLabel.pack(side=Tkinter.LEFT) self.gid.pack(side=Tkinter.LEFT) f.pack(side=Tkinter.BOTTOM) appFrame.grid(row=0, column=0, columnspan=3, sticky=Tkinter.N + Tkinter.S) def pickTapFile(self): r = tkFileDialog.askopenfilename() if r: self.tapfile.delete(0, Tkinter.END) self.tapfile.insert(Tkinter.END, r) def reset(self, coil): """ Remove the existing coil-specific widgets and then create and add new ones based on the given plugin object. """ if coil is self.coil: return try: opt = coil.load().Options() except: f = StringIO.StringIO() traceback.print_exc(file=f) # XXX - Why is this so narrow? tkMessageBox.showerror(title="Options Error", message=f.getvalue(), parent=self) return if self.optFrame: self.optFrame.forget() self.optFrame.destroy() self.optFrame = None self.coil = coil self.options = opt self.optFrame = TkConfigFrame(self, self.options) self.optFrame.grid(row=1, column=0) # self.tapfile.delete(0, Tkinter.END) # try: # self.tapfile.insert(Tkinter.END, self.coil.tapname) # except AttributeError: # self.tapfile.insert(Tkinter.END, self.coil.name) def copyOptions(self): # Snarf the data out of the widgets and place them into the Options # instance. extra = self.optFrame.updateConfig(self.options) self.options['filename'] = self.tapfile.get() self.options['appname'] = self.appname.get() self.options['passphrase'] = self.passphrase.get() self.options['append'] = self.append.get() self.options['encrypted'] = len(self.options['passphrase']) self.options['uid'] = int(self.uid.get()) self.options['gid'] = int(self.gid.get()) try: self.options['type'] = self.typeList.curselection()[0] except IndexError: raise usage.UsageError("Select a TAp Format") self.options['help'] = 0 if extra: try: # XXX - this is wrong. It needs to respect quotes, etc. self.options.parseArgs(extra.split()) except TypeError: raise usage.UsageError("Wrong number of extra arguments") self.options.postOptions() def createApplication(self): if not self.options: tkMessageBox.showerror(message="Select an Application first") return try: self.copyOptions() except usage.UsageError, e: tkMessageBox.showerror(message=str(e)) return exists = os.path.exists(self.options['filename']) if self.options['append'] and exists: a = service.loadApplication( self.options['filename'], self.options['type'], self.options['passphrase'] ) else: if exists: overwrite = tkMessageBox.askyesno(title='File Exists', message='Overwrite?') if not overwrite: return a = service.Application(self.coil.name, self.options['uid'], self.options['gid']) try: s = mktap.makeService( self.coil.load(), self.options['appname'], self.options ) except usage.UsageError: f = StringIO.StringIO() traceback.print_stack(file=f) tkMessageBox.showerror(title="Usage Error", message=f.getvalue(), parent=self) else: try: mktap.addToApplication( s, self.coil.name, self.options['append'], self.options['appname'], self.options['type'], self.options['encrypted'], self.options['uid'], self.options['gid'], ) except: f = StringIO.StringIO() traceback.print_exc(file=f) print f.getvalue() tkMessageBox.showerror(title="Usage Error", message=f.getvalue(), parent=self) else: filename = self.options['filename'] if not filename: filename = self.coil.name tkMessageBox.showinfo(message="Wrote " + filename) def destroy(self): reactor.crash() Tkinter.Frame.destroy(self) # # This class was written based on code from Drew "drewp" Pertulla # () - without his help, tkmktap # would be an ugly POS. # class ParameterLine(Tkinter.Frame): def __init__(self, master, lines, label, desc, default, cmd, **kw): Tkinter.Frame.__init__(self, master, relief='raised', bd=1, **kw) self.lines = lines l = Tkinter.Label( self, text=label, wraplen=200, width=30, anchor='w', justify='left' ) s = Tkinter.StringVar() if default: s.set(default) self.entry = Tkinter.Entry(self, text=s, background='white') self.flag = label more = Tkinter.Button( self, text='+', command=lambda f = cmd, a = label, b = default, c = desc: f(a, b, c) ) l.pack(side=Tkinter.LEFT, fill='y') self.entry.pack(side=Tkinter.LEFT) more.pack(side=Tkinter.LEFT) l.bind("", self.highlight) l.bind("", self.unhighlight) l.bind("", self.press) l.bind("", self.release) l.bind("", self.motion) def highlight(self, ev, hicolor = 'gray90'): # make the label light up when you mouseover ev.widget._oldcolor = self.cget('bg') ev.widget.config(bg=hicolor) def unhighlight(self, ev): # make the label return to its old setting try: ev.widget.config(bg=ev.widget._oldcolor) del ev.widget._oldcolor except: pass # make the frame change order when you drag it (by its label) def press(self, ev): # save old attrs self._oldrelief = self.cget('relief'), self.cget('bd') # thicken the border self.config(relief='raised', bd=3) def motion(self, ev): this = self.lines.index(self) framey = ev.y + self.winfo_y() # get mouse y coord in parent frame replace = this # replace will be the index of the row to swap with for i, l in zip(range(len(self.lines)), self.lines): y1 = l.winfo_y() y2 = y1 + l.winfo_height() if y1 < framey < y2: replace = i if replace != this: # we moved over another row-- swap them self.lines[replace], self.lines[this] = self.lines[this], self.lines[replace] # and re-assign all rows in the new order for i, l in zip(range(len(self.lines)), self.lines): l.grid(row=i, column=0) def release(self, ev): # restore the old border width try: rel, bd = self._oldrelief self.config(relief=rel, bd=bd) del self._oldrelief except: pass class TkConfigFrame(Tkinter.Frame): optFrame = None paramFrame = None commandFrame = None subCmdFrame = None previousCommand = None optFlags = None paramLines = None def __init__(self, master, options): Tkinter.Frame.__init__(self, master) self.options = options self.setupOptFlags() self.setupOptParameters() self.setupSubCommands() self.setupExtra() def getOptFlags(self): return self.optFlags def getOptParameters(self): r = [] for p in self.paramLines: r.append((p.flag, p.entry.get())) return r def updateConfig(self, options): for (opt, var) in self.getOptFlags(): var = var.get() if not var: continue # XXX - this is poor - add a '-' button to remove options f = getattr(options, 'opt_' + opt, None) if f: f() else: options[opt] = var for (opt, var) in self.getOptParameters(): if not var: continue # XXX - this is poor - add a '-' button to remove options f = getattr(options, 'opt_' + opt, None) if f: f(var) else: options[opt] = var return self.extra.get() def setupOptFlags(self): self.optFlags = [] flags = [] if hasattr(self.options, 'optFlags'): flags.extend(self.options.optFlags) d = {} soFar = {} for meth in reflect.prefixedMethodNames(self.options.__class__, 'opt_'): full = 'opt_' + meth func = getattr(self.options, full) if not usage.flagFunction(func) or meth in ('help', 'version'): continue if soFar.has_key(func): continue soFar[func] = 1 existing = d.setdefault(func, meth) if existing != meth: if len(existing) < len(meth): d[func] = meth for (func, name) in d.items(): flags.append((name, None, func.__doc__)) if len(flags): self.optFrame = f = Tkinter.Frame(self) for (flag, _, desc) in flags: b = Tkinter.BooleanVar() c = Tkinter.Checkbutton(f, text=desc, variable=b, wraplen=200) c.pack(anchor=Tkinter.W) self.optFlags.append((flag, b)) f.grid(row=1, column=1) def setupOptParameters(self): params = [] if hasattr(self.options, 'optParameters'): params.extend(self.options.optParameters) d = {} soFar = {} for meth in reflect.prefixedMethodNames(self.options.__class__, 'opt_'): full = 'opt_' + meth func = getattr(self.options, full) if usage.flagFunction(func) or soFar.has_key(func): continue soFar[func] = 1 existing = d.setdefault(func, meth) if existing != meth: if len(existing) < len(meth): d[func] = meth for (func, name) in d.items(): params.append((name, None, None, func.__doc__)) if len(params): self.paramFrame = Tkinter.Frame(self) self.paramLines = [] for (flag, _, default, desc) in params: try: default = self.options[flag] except KeyError: pass self.makeField(flag, default, desc) self.paramFrame.grid(row=1, column=2) def makeField(self, flag, default, desc): line = ParameterLine( self.paramFrame, self.paramLines, flag, desc, default, cmd=self.makeField ) self.paramLines.append(line) line.grid(row=len(self.paramLines), column=0) def setupSubCommands(self): self.optMap = {} if hasattr(self.options, 'subCommands'): self.commandFrame = f = Tkinter.Frame(self) self.cmdList = Tkinter.Listbox(f) for (cmd, _, opt, desc) in self.options.subCommands: self.cmdList.insert(Tkinter.END, cmd) self.optMap[cmd] = opt() self.cmdList.pack() self.subCmdPoll = reactor.callLater(0.1, self.pollSubCommands) f.grid(row=1, column=3) def setupExtra(self): f = Tkinter.Frame(self) l = Tkinter.Label(f, text='Extra Options') self.extra = Tkinter.Entry(f, background='white') l.pack() self.extra.pack(fill='y') f.grid(row=2, column=1, columnspan=2) def pollSubCommands(self): s = self.cmdList.curselection() if len(s): s = s[0] if s != self.previousCommand: if self.subOptFrame: self.subOptFrame.forget() self.subOptFrame.destroy() self.subOptFrame = TkConfigFrame(self.commandFrame, self.optMap[s]) self.subOptFrame.pack() self.subCmdPoll = reactor.callLater(0.1, self.pollSubCommands) class TkAppMenu(Tkinter.Menu): def __init__(self, master, create, callback, items): Tkinter.Menu.__init__(self, master) cmdMenu = Tkinter.Menu(self) self.add_cascade(label="Actions", menu=cmdMenu) cmdMenu.add_command(label='Create', command=create) cmdMenu.add_separator() cmdMenu.add_command(label='Quit', command=reactor.crash) tapMenu = Tkinter.Menu(self) self.add_cascade(label="Applications", menu=tapMenu) for item in items: tapMenu.add_command( label=item, command=lambda i=item, c = callback: c(i) ) def run(): taps = mktap.loadPlugins() r = Tkinter.Tk() r.withdraw() keyList = taps.keys() keyList.sort() config = TkMkAppFrame(r, None) menu = TkAppMenu( r, config.createApplication, lambda i, d = taps, c = config: c.reset(d[i]), keyList ) config.pack() r['menu'] = menu r.title('Twisted Application Maker ' + version) r.deiconify() tksupport.install(r) reactor.run() if __name__ == '__main__': run()