[IronLute-CVS] ironlute/tk IronLuteTk.py,NONE,1.1 __init__.py,NONE,1.1 bbox.py,NONE,1.1 commandMenu.
Status: Pre-Alpha
Brought to you by:
thejerf
From: Jeremy B. <th...@us...> - 2005-03-10 04:26:16
|
Update of /cvsroot/ironlute/ironlute/tk In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv27941/tk Added Files: IronLuteTk.py __init__.py bbox.py commandMenu.py dialogs.py handletag.py indicator.py outlineframe.py outlinewidget.py statusbar.py textnodewidget.py tkCommandMenu.py tkMenuBar.py tkclipboard.py tkposition.py Log Message: Welcome to Sourceforge, Iron Lute. --- NEW FILE: tkclipboard.py --- """Implements the Tk clipboard, with access to text only. May need to do some cross platform stuff. Windows may not be clipboardable without help.""" # FIXME: Make this a borg import os class TkClipboard: def __init__(self, master): self.master = master def setClipboard(self, s, obj = None): """Sets the clipboard to s, with option ref to obj. Sets the OS clipboard to the string s. If obj is attached, the clipboard will remember the obj. If the user requests the clipboard and the clipboard contents have not changed, TkClipboard will return obj instead of the string. This is how Iron Lute holds onto fancy node references in the clipboard.""" print repr(obj) if os.name == 'nt': self.master.clipboard_clear() self.master.clipboard_append(s) return if os.name == 'posix': self.clipboard = s self.master.selection_handle(self.handleSelection) self.master.selection_own(command=self.lostSelection) self.obj = obj def lostSelection(self, event = None): """Notices in X when the selection is lost.""" self.clipboard = None def handleSelection(self, offset, maxChars): """When another X application requests the clipboard, this gives it to them.""" return self.clipboard def getClipboard(self): """Returns the X selection, if any.""" if os.name == 'nt': try: contents = self.master.selection_get(selection="CLIPBOARD") if self.obj is not None and self.clipboard == \ contents: return self.obj else: return contents except: return "" if os.name == 'posix': try: contents = self.master.selection_get() print contents == self.clipboard if self.obj is not None and self.clipboard == \ contents: return self.obj else: return contents except: return "" --- NEW FILE: indicator.py --- """Indicator is the little indicator to the left of the text nodes.""" from Tkinter import * CLOSED = 0 OPEN = 1 CONFIG = 2 NOCHILDREN = 3 HIDDEN = 4 class Indicator(Canvas): """Indicators accept a height and a width. This default indicator uses no symbol for NONE, a closed black circle for CLOSED, closed green circle for CONFIG, and an open black circle for OPEN. Later we'll need to switch this to some sort of higher-level protocol that the node types can describe and the UI implement. """ def __init__(self, master, width, height, node, background = "white", foreground = "black", nochildren = "grey75", relief = FLAT, state = NOCHILDREN): Canvas.__init__(self, master = master, width = width, height = height, background = background, relief = relief, highlightthickness = 0, borderwidth = 0) self.state = None self.master = master self.node = node self.foreground = foreground self.background = background self.nochildren = nochildren self.width = width self.height = height self.object1 = None self.object2 = None self.setState(state) self.bind("<Button-1>", self.button1) self.bind("<Configure>", self.cancelEvent) def cancelEvent(self, event): """Cancels events we don't want bubbling up.""" return "break" def button1(self, event = None): self.node.button1(event) def setState(self, newState): if self.state == newState: return # no changes if self.object1 is not None: self.delete(self.object1) self.delete(self.object2) xcenter = self.width / 2 ycenter = self.height / 2 radius = min(xcenter, ycenter) / 2 # / 2 just looks nicer xmin = xcenter - radius xmax = xcenter + radius ymin = ycenter - radius ymax = ycenter + radius self.state = newState if newState == NOCHILDREN: self.object1 = self.create_arc((xmin, ymin, xmax, ymax), start = 0, extent = 180, fill = self.nochildren, outline = self.nochildren, style = CHORD) self.object2 = self.create_arc((xmin, ymin, xmax, ymax), start = 180, extent = 180, fill = self.nochildren, outline = self.nochildren, style = CHORD) if newState == CLOSED: self.object1 = self.create_arc((xmin, ymin, xmax, ymax), start = 0, extent = 180, fill = self.foreground, outline = self.foreground, style = CHORD) self.object2 = self.create_arc((xmin, ymin, xmax, ymax), start = 180, extent = 180, fill = self.foreground, outline = self.foreground, style = CHORD) if newState == OPEN: self.object1 = self.create_arc(xmin, ymin, xmax, ymax, start = 0, extent = 180, outline = self.foreground, style = ARC) self.object2 = self.create_arc(xmin, ymin, xmax, ymax, start = 180, extent = 180, outline = self.foreground, style = ARC) --- NEW FILE: bbox.py --- """bbox implements a class Bbox which wraps Tk bounding boxes so that I can access the components by name: .x, .y, .width, and .height.""" from types import * class Bbox: """Bbox wraps the Tk bounding box for my convenience, giving x, y, width, and height attributes.""" pos = {} pos["x"] = 0 pos["y"] = 1 pos["width"] = 2 pos["height"] = 3 def __init__(self, bbox = [0, 0, 0, 0]): try: if len(bbox) != 4: raise ValueError("Bounding boxes must be lists of four " "ints.") except TypeError: raise TypeError("Bounding box tried to be created from %r," " which is not a sequence." % bbox) for val in bbox: if type(val) != IntType: raise TypeError("Bounding boxes must be lists of four ints.") self.__dict__["bbox"] = list(bbox) def __getitem__(self, pos): return self.bbox[pos] def __setitem__(self, pos, value): self.__dict__["bbox"][pos] = value def __getattr__(self, name): if self.pos.has_key(name): return self.bbox[self.pos[name]] else: raise AttributeError("Attribute " + name + " not defined for bboxes.") def __setattr__(self, name, value): if type(value) != IntType: raise TypeError ( "Bboxes must recieve ints." ) if self.pos.has_key(name): self.__dict__["bbox"][self.pos[name]] = value else: raise AttributeError("Attribute " + name + " not defined for bboxes.") def __str__(self): return str(self.bbox) def __repr__(self): return str(self.bbox) def __hash__(self): return hash(self.bbox) --- NEW FILE: statusbar.py --- from __future__ import generators from Tkinter import Frame, Label, SUNKEN, W, X import Queue class StatusBar(Frame): """StatusBar implements a status bar that can have messages sent to it to be displayed, plus a duration for those messages to be displayed, unless pre-empted by later messages.""" def __init__(self, master, duration = 5): """Duration is the number of seconds the message will be displayed.""" Frame.__init__(self, master) self.label = Label(self, bd=1, relief=SUNKEN, anchor=W) self.label.pack(fill=X) self.duration = duration self.count = self.counter() self.current = None def set(self, format, *args): self.current = self.count.next() if len(args) == 0: self.label.config(text=format) else: self.label.config(text=format % args) self.label.update_idletasks() self.after(self.duration * 1000, self.clear, self.current) def clear(self, current): if current == self.current: self.label.config(text="") self.label.update_idletasks() def counter(self): i = 0 while 1: yield i i = i + 1 --- NEW FILE: tkCommandMenu.py --- """CommandMenu is a subclass of Menu, but instead of feeding it a series of commands, you feed it a command.CommandMenu and this menu automatically regenerates itself every time it is invoked, keeping up with its bound command.CommandMenu. Since it's a subclass of Menu, you can still create commands manually, but you need to do it in an override of updateMenu. Since the menu regenerates itself from scratch each time it's invoked, your changes will not hand around if only made in the constructor.""" from Tkinter import Menu, DISABLED, NORMAL import sys if ".." not in sys.path: sys.path.append("..") class CommandMenu(Menu): def __init__(self, cmds, outlineFrame, *args, **kwlist): # intercept keyword parameters we'd like if 'dispatcher' in kwlist: self.dispatcher = kwlist['dispatcher'] del kwlist['dispatcher'] else: self.dispatcher = None Menu.__init__(self, *args, **kwlist) self.cmds = cmds self.outlineFrame = outlineFrame self['postcommand'] = self.updateMenu def updateMenu(self): self.delete(0, 9999999) # just a big num to ensure we get everything curNode = self.outlineFrame.currentFocus.getNode() if curNode is None: context = [""] else: context = [x.classId for x in curNode.bases()] + [""] self.cmds.generateMenu(self, self.outlineFrame, context) def ilDisable(self): return def ilAddSeparator(self): self.add_separator() def ilAddCommand(self, command, accelerator = None, accelString = None, state = 1): """See command.py/CommandMenu.""" if accelerator is None: accelerator = "" else: accelerator = accelString if state: state = NORMAL else: state = DISABLED self.add_command(label=command.name, accelerator=accelerator, state=state, command=lambda c=command: self.outlineFrame.doCommand(c, self)) #for command in iter(self.cmds): # self.add_command(label=command.name, # command=lambda c=command: self.outlineFrame.doCommand(c)) # FIXME: This ought to be done by the 'command history', # whatever that will be. --- NEW FILE: outlineframe.py --- """OutlineFrame is the core of Iron Lute. This can be included in other things as needed. OutlineFrame manages the TextNodeWidgets underneath it, manages the scollbar, and managing command dispatching. """ from __future__ import generators from Tkinter import * import math import weakref from textnodewidget import TextNodeWidget, requestedLines from tkposition import TkPosition from bbox import Bbox import outline import constants from ilerrors import * import dispatcher [...1032 lines suppressed...] currentFocus = property(getFocus, setFocus, delFocus, "The currently-focused outline node.") # FIXME: Del this # Helps track down topNode setting errors if any #def getTopNode(self): # return self.__topNode #def setTopNode(self, node): # import traceback # self.__topNode = node #topNode = property(getTopNode, setTopNode) if __name__ == "__main__": root = Tk() import outline d = outline.BasicDocument() outlineframe = OutlineFrame(d, parent = root) outlineframe.pack() root.mainloop() --- NEW FILE: outlinewidget.py --- """ Outline widget manages the handle, hoisting, etc. TextNodes implement tag operations on the text, very similar to the current TextNodeWidget, only the former outline frame is now a text node, and the text nodes are now just objects. It is too late to try to implement this, but if I can carve a day out I bet I could crank out a prototype, in which case I'd be nearly done with the dev release! """ from Tkinter import * import tkFont import indicator import handletag import sys import os d = os.path.dirname(__file__) d = os.path.normpath(os.path.join(os.getcwd(), d)) if d not in sys.path: sys.path.append(d) d = os.path.normpath(os.path.join(d + os.pathsep, os.pardir)) if d not in sys.path: sys.path.append(d) del d import guiSupport import outline document = outline.quickMakeNodes(['sample text', ['flute', 'moo']]) rootHandle = document.getRootHandle() class OutlineWidget(Text, guiSupport.IronLuteOutlineFrame): def __init__(self, master, font = None): guiSupport.IronLuteOutlineFrame.__init__(self) self.master = master self.destroyed = 0 if font is None: try: self.font = tkFont.Font ( family="Georgia", size="15" ) except: self.font = tkFont.Font ( family="Times", size="14" ) else: self.font = font Text.__init__(self, master, wrap = WORD, undo = 0, font = self.font) self["highlightcolor"] = self["highlightbackground"] self["background"] = "white" self["padx"], self["pady"] = (0, 0) self["highlightthickness"] = 0 self["borderwidth"] = 0 self.scrollbar = Scrollbar(master, command = self.yview) self.scrollbar.pack(side = RIGHT, fill = Y) self["yscrollcommand"] = self.scrollbar.set self.commandExecuting = 0 self.fontMetrics = self.font.metrics() self.fontHeight = self.fontMetrics["linespace"] self.myTag = "myTag" # I want tags to "eat" chars, even if they are added just # before the tag starts, so I actually have to start the # tags at "%s.end" % (line - 1). To understand this better, # take out the elide hack here and try to add to an outline # at the very first position. self.insert("1.0", "\n") self.tag_add("elidehack", "1.0", "2.0") self.tag_config("elidehack", elide = 1) self.handleToTag = {} self.nameToTag = {} handle = rootHandle[0] self.handleToTag[handle] = handletag.HandleTag(self, handle, 2) handle = rootHandle[1] self.handleToTag[handle] = handletag.HandleTag(self, handle, 3) handle = handle[0] self.handleToTag[handle] = handletag.HandleTag(self, handle, 4) self.bind("<Key>", self.key) self.bind("<KeyRelease>", self.keyRelease) # Use tag_names(index) to determine what node is being used # and set Enter accordingly. def key(self, event): # Prevent people typing left of the insert marker if self.index(INSERT)[-2:] == ".0": self.mark_set(INSERT, INSERT + " +1char") return "break" def keyRelease(self, event): print self.index(INSERT) print self.tag_names(self.index(INSERT)) if __name__ == "__main__": root = Tk() myWidget = OutlineWidget(root) myWidget.pack() root.mainloop() --- NEW FILE: __init__.py --- """Imports all the tk stuff in.""" from Tkinter import * import tkFont from tkposition import TkPosition from bbox import Bbox from statusbar import StatusBar from textnodewidget import TextNodeWidget, _requestedLines from outlineframe import OutlineFrame from IronLuteTk import IronLuteTk, TkStartInit, TkFinishInit from dialogs import * from tkclipboard import TkClipboard from tkMenuBar import MenuBar --- NEW FILE: handletag.py --- """ handletag implements support for handling the tags associated with nodes, and the children of all nodes. """ import indicator import sys import Tkinter import os d = os.path.dirname(__file__) d = os.path.normpath(os.path.join(os.getcwd(), d)) if d not in sys.path: sys.path.append(d) d = os.path.normpath(os.path.join(d + os.pathsep, os.pardir)) if d not in sys.path: sys.path.append(d) del d import constants def tagNumbers(): i = 1 while 1: yield i i = i + 1 tagNumbers = tagNumbers() i = 0 class HandleTag(object): def __init__(self, outlineWidget, handle, lineNum): global i self.tagNumber = tagNumbers.next() self.outlineWidget = outlineWidget self.outlineWidget.handleToTag[handle] = self self.outlineWidget.nameToTag[str(self)] = self # Set up the indicater indent = (handle.depth() - 1) * constants.INDENT_WIDTH self.config(lmargin1 = 0 + indent, lmargin2 = outlineWidget.fontHeight + indent) # Make a line for ourselves outlineWidget.insert("%s.0" % lineNum, "\n") self.indicator = indicator.Indicator(outlineWidget, outlineWidget.fontHeight, outlineWidget.fontHeight, handle) outlineWidget.window_create("%s.0" % lineNum, align = Tkinter.CENTER, window = self.indicator) outlineWidget.tag_add(self, "%s.0" % lineNum, "%s.0" % (lineNum + 1)) self.handle = None self.hasText = False self.bindHandle(handle) outlineWidget.tag_raise(self) def getRange(self): ranges = self.outlineWidget.tag_ranges(self) if len(ranges) > 2: # FIXME: log print "More than one range for %s! (%s)" \ % (self, ranges) return ranges def bindHandle(self, handle): if self.handle is handle: return if self.hasText: # Clear out any text this might have, as a paranoia thing # FIXME: Check read only begin, end = self.getRange() self.outlineWidget.delete(begin + "+1char", end) # FIXME: factor begin, end = self.getRange() print begin self.outlineWidget.insert(begin + "+1char", handle.node.getData()) def cancelEvent(event): return "break" self.bind("<Enter>", cancelEvent) self.handle = handle def get(self): return self.outlineWidget.get(*self.getRange())[:-1] def bind(self, *args, **kwds): return self.outlineWidget.tag_bind(self, *args, **kwds) def config(self, **kwds): return self.outlineWidget.tag_config(self, **kwds) def __str__(self): return "node_%s" % self.tagNumber def __hash__(self): return self.tagNumber --- NEW FILE: tkposition.py --- from types import StringType, IntType; class TkPosition: """Wraps Tk positions for IronLute. Not generally useful. This class is a convenience class that takes in positions, allows sensible Python operations on it, and outputs positions. Since my app only has one line per widget, I assume that we're always on line 1 when I manipulate positions. (In particular, for decrementing purposes.""" def __init__(self, pos): self.line, self.col = self.convPos(pos) def convPos(self, pos): "Returns a tuple (row, col) from a Tk string." if "." not in str(pos): return (1, int(pos)) pos = str(pos) return (int(pos[:pos.find(".")]), int(pos[pos.find(".")+1:])) def pos(self): "Returns position in tuple form." return (self.line, self.col) def __repr__(self): return "%d.%d" % (self.line, self.col) def __str__(self): return "%d.%d" % (self.line, self.col) def __int__(self): return self.col def __cmp__(self, other): if type(self) != type(other) and type(other) != StringType: raise NotImplementedError("TkPositions can't be compared to anything but" " other TkPositions or strings.") s = self.pos() if type(self) == type(other): o = other.pos() else: o = self.convPos(other) if s[0] < o[0]: return -1 if s[0] > o[0]: return 1 return cmp(s[1], o[1]) def __hash__(self): return hash(str(self)) def __add__(self, i): if type(i) != IntType: raise TypeError("Can only add ints to TkPositions.") col = self.col + i if col < 0: col = 0 return TkPosition("%d.%d" % (self.line, col) ) def __sub__(self, i): if type(i) != IntType: raise TypeError("Can only subtract ints from TkPositions.") col = self.col - i if col < 0: col = 0 return TkPosition("%d.%d" % (self.line, col) ) --- NEW FILE: textnodewidget.py --- import sys if ".." not in sys.path: sys.path.append("..") from Tkinter import * from tkposition import TkPosition import tkCommandMenu from bbox import Bbox from indicator import * import outline from constants import * # These keys are passed through to the text widget directly and invoke # the processing keysToPassThrough = "abcdefghijklmnopqrstuvwxyz1234567890-=[]\;',./`" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"<>?~ " # Conversion dict, to take Tk keysyms and convert them to Dispatcher # key ids. [...1163 lines suppressed...] widthCounter += element if not firstWord: widthCounter += paddingWidth if debug: if not firstWord: lines[-1].append(padding) lines[-1].append(data[i]) if lineCounter >= maxLines: return maxLines, widthCounter if debug: return lineCounter, widthCounter, \ [''.join(x) for x in lines] return lineCounter, widthCounter # Redo requested lines for easier testing: # text, font, maxLines, maxWidth # return the lineCounter and the final width for comparision against # Tk's opinion. maxLines may be set to None --- NEW FILE: commandMenu.py --- """CommandMenu is a subclass of Menu, but instead of feeding it a series of commands, you feed it a command.CommandMenu and this menu automatically regenerates itself every time it is invoked, keeping up with its bound command.CommandMenu. Since it's a subclass of Menu, you can still create commands manually, but you need to do it in an override of updateMenu. Since the menu regenerates itself from scratch each time it's invoked, your changes will not hand around if only made in the constructor.""" from Tkinter import Menu class CommandMenu(Menu): def __init__(self, cmds, outlineFrame, *args, **kwlist): Menu.__init__(self, *args, **kwlist) self.cmds = cmds self.outlineFrame = outlineFrame self['postcommand'] = self.updateMenu def updateMenu(self): self.delete(0, 9999999) # just a big num to ensure we get everything for command in iter(self.cmds): self.add_command(label=command.name, command=command.classId) # FIXME: This ought to be done by the 'command history', # whatever that will be. --- NEW FILE: tkMenuBar.py --- """This is the menu for IronLute, which can be stuck anywhere.""" from Tkinter import * import guiSupport import tkCommandMenu class MenuBar(Menu, guiSupport.IronLuteMenu): """The Iron Lute menu bar.""" def __init__(self, outlineframe, dispatcher, *args, **kwargs): Menu.__init__(self, *args, **kwargs) guiSupport.IronLuteMenu.__init__(self, dispatcher) self.frame = outlineframe self.tkMenus = [] def createMenus(self): for menu in self.menus: m = tkCommandMenu.CommandMenu(menu, self.frame) #m['font'] = menufont self.add_cascade(label=menu.name, menu=m) self.tkMenus.append(m) def setFocus(self, nodeFocus, docFocus): if nodeFocus is None: self.entryconfigure(5, state=DISABLED) else: self.entryconfigure(5, state=NORMAL) if docFocus is None: self.entryconfigure(6, state=DISABLED) else: self.entryconfigure(6, state=NORMAL) --- NEW FILE: dialogs.py --- """Iron Lute does a lot of its own work, but there are some dialogs that must be supplied by the GUI toolkit. This file implements them for Tk. Contents: * FileSelectionDialog: Selects a file from the local filesystem. * simpleFileOpener: Takes in a simpleFile class and returns a simpleFile object to the user's specification. """ import FileDialog import os import sys if __name__ != "__main__": # add parent to path f = __file__ f = os.path.normpath(os.path.dirname(f) + os.sep + os.pardir) if f not in sys.path: sys.path.append(f) from tkSimpleDialog import Dialog from Tkinter import * import textnodewidget class FileSelectionDialog(FileDialog.FileDialog): """All implementations of FileSelectionDialog must have this interface: d = FileSelectionDialog(outlineFrame) filename = d.getFileSelection(dirToStartIn) and filename must be "None" (for anything that results in a file not being returned, most likely a cancel event), or a string containing the name of the selected file. (Only one file may be returned.) If your widget toolkit has no use for the outlineFrame reference, then just ignore it.""" def getFileSelection(self, startDir): return self.go(startDir) # FIXME: Ought to work in some validation code. class SimpleFileOpener(Dialog): """SimpleFileOpener provides a dialog box that takes in a simpleFile class type, and returns a created simpleFile object of that class. It uses the introspection capabilities of the simpleFile to do so.""" def __init__(self, master, simpleFileClass): self.simpleFileClass = simpleFileClass self.entries = [] Dialog.__init__(self, master, "Open New %s..." % simpleFileClass.name) def body(self, master): for param in self.simpleFileClass.paramList: paramName, paramDesc = param[:2] Label(master, text=paramName + ":").grid(row = i) self.entries.append(Entry(master)) self.entries[-1].grid(row = i, column = 1) return self.entries[0] def apply(self): params = [x.get() for x in self.entries] self.result = self.simpleFileClass(*params) class YesNoDialog(Dialog): """Asks a yes/no question.""" def __init__(self, master, question, summary = "", yesText = "Yes", noText = "No"): self.question = question self.yesText = yesText self.noText = noText if summary != "": summary = ": " + summary Dialog.__init__(self, master, "Yes/No" + summary) def body(self, master): t = "\n".join(wrap(self.question, 60)) Label(master, text=t).pack() box = Frame(master) w1 = Button(box, text = self.yesText, width = 10, command = self.yes, default = ACTIVE) w1.pack(side = LEFT, padx = 5, pady = 5) w2 = Button(box, text = self.noText, width = 10, command = self.no) w2.pack(side = LEFT, padx = 5, pady = 5) box.pack() return w1 def yes(self, event = None): self.answer = 1 self.exit() def no(self, event = None): self.answer = 0 self.exit() def exit(self): self.withdraw() self.update_idletasks() self.destroy() def buttonbox(self): return class tkGetKey(Dialog): """Get a keypress for a keyboard shortcut. Get the final result from tkGetKey.result, which will either have None for a cancelled dialog box or unacceptable input, or a string suitable for Dispatcher use.""" def __init__(self, master, name, *args, **kwargs): self.name = name self.shiftVar = IntVar() self.altVar = IntVar() self.controlVar = IntVar() self.result = None Dialog.__init__(self, master, "Select Keyboard Shortcut") def shift(self): return self.shiftVar.get() def alt(self): return self.altVar.get() def control(self): return self.controlVar.get() def body(self, master): self.boxLabel = Label(master, text="Select a " 'keyboard shortcut for\n"%s"' % self.name) self.boxLabel.grid(row = 0, column = 0, columnspan = 3) self.keyLabel = Label(master, text="Key:") self.keyLabel.grid(row = 1, column = 0, sticky = E) self.text = Text(master, width = 7, height = 1) self.text.grid(row = 1, column = 1, columnspan = 2, sticky = W) self.text.bind("<Key>", self.keyEvent) self.text.bind("<ButtonRelease-2>", self.cancelEvent) self.shift = Checkbutton(master, text="Shift", variable = self.shiftVar, command = self.updateKeypress) self.shift.grid(row = 2, column = 0) self.alt = Checkbutton(master, text="Alt", variable = self.altVar, command = self.updateKeypress) self.alt.grid(row = 2, column = 1) self.control = Checkbutton(master, text = "Control", variable = self.controlVar, command = self.updateKeypress) self.control.grid(row = 2, column = 2) return self.text def keyEvent(self, event): if event.keysym in textnodewidget.symsToIgnore: return if event.keysym in textnodewidget.keysymConvert: self.key = textnodewidget.keysymConvert[event.keysym] elif len(event.keysym) > 1: self.key = event.keysym else: self.key = event.char self.updateKeypress() return "break" def updateKeypress(self): try: self.text.delete("1.0", END) prefix = "" if self.shiftVar.get(): prefix += "S" if self.altVar.get(): prefix += "A" if self.controlVar.get(): prefix += "C" if prefix != "": prefix += "-" self.text.insert("1.0", prefix + self.key) except: pass def cancelEvent(self, event = None): return "break" def apply(self): """Processes the dialog box and returns a string suitable for the dispatcher.""" self.result = self.text.get("1.0", END) self.result = self.result.replace("\n", "") if self.result == "": self.result = None class ListChoice(Dialog): """List choice provides a simple drop-down list choice, and returns the selected object or objects in a list.""" def __init__(self, master, choices, caption = "Choice", text = "Choose one of the following:", multi = 0): """Choices is either a list of string choices, which will be directly returned by this class, or a list of [string, obj] choices, with the user choosing the string and this class returning the obj associated with that string directly to the class user. The list need not be homogenous.""" self.caption = caption self.choices = choices self.text = text self.multi = multi self.result = None Dialog.__init__(self, master) def body(self, master): l = Label(master, text = self.text) l.pack() if self.multi: lbtype = EXTENDED else: lbtype = SINGLE frame = Frame(master) scrollbar = Scrollbar(frame, orient=VERTICAL) lb = self.lb = Listbox(master, selectmode = lbtype, exportselection = 0, yscrollcommand = scrollbar.set) scrollbar.config(command=lb.yview) lb.pack(side = LEFT, fill = BOTH, expand = 1) scrollbar.pack(side = RIGHT, fill = Y, expand = 1) frame.pack() for choice in self.choices: try: caption = choice[0] except: caption = str(choice) lb.insert(END, caption) lb.pack() def apply(self): sel = [int(x) for x in self.lb.curselection()] result = [] for index in sel: try: result.append(self.choices[index][1]) except: result.append(self.choices[index]) self.result = result # This is a little oversophisticated for the use I'm putting it to, # but it's imported from another project so hopefully (cross my # fingers) it's well tested... if not, I want to know. ;-) import re # Not some of my best work. Then again, first pass attempts to clean # this up have failed to have any significant impact. It's just an # ugly algorithm with special cases sticking out everywhere. def wrap(s, width, maxlinecount = None, reserve = None): """Wraps a string in a fairly sophisticated way. s is the string that should be wrapped. width is the maximum allowable width. maxlinecount, if given, is the maximum number of lines that will be returned. reserve is the space reserved on the last line, if the string would end up going over the maxlinecount. If the maxlinecount is not exceeded, a list of strings, one for each line, is returned. If the maxlinecount is exceeded, a tuple is returned, where the first element is the remaining (unwrapped) string, and the second element is the list of wrapped strings. """ s = re.sub("\s+", " ", s) s = s.split(" ") lines = [] counter = 0 lastLine = 0 currentLine = "" if maxlinecount == 1: lastLine = 1 # Start making lines. for w in range(len(s)): word = s[w] l = len(word) if lastLine and reserve is not None: maxwidth = width - reserve else: maxwidth = width if counter + l < maxwidth and currentLine == "": currentLine = s[w] counter += l elif counter + l + 1 < maxwidth and currentLine != "": currentLine += " " + word counter += l + 1 else: # Spilling over limit, take action if maxlinecount == None: # always keep going if currentLine != "": lines.append(currentLine) currentLine = word while len(currentLine) > maxwidth: lines.append(currentLine[0:maxwidth]) currentLine = currentLine[maxwidth:] counter = len(currentLine) else: if currentLine != "": lines.append(currentLine) currentLine = word while len(currentLine) > maxwidth and len(lines) < maxlinecount: if len(lines) == maxlinecount - 1 and reserve is not None: maxwidth = width - reserve lines.append(word[0:maxwidth]) s[w] = s[w][maxwidth:] word = s[w] currentLine = word counter = len(currentLine) if len(lines) == maxlinecount: # hit the limit return (" ".join(s[w:]), lines) if len(lines) == maxlinecount - 1: lastLine = 1 if currentLine != "": lines.append(currentLine) return lines # Tuples: (input, output) testcases = [] testcases.append((("Hello, my name is Belvedere.", 10), ["Hello, my", "name is", "Belvedere."])) testcases.append((("longnameisnowhere,allcowerinfaceofthelongword", 10, 2, 5), ('re,allcowerinfaceofthelongword', ['longnameis', 'nowhe']))) testcases.append((("longnameisnowhere,allcowerinfaceofthelongword", 10, 2), ('lcowerinfaceofthelongword', ['longnameis', 'nowhere,al']))) def test(): for test in testcases: if apply(wrap, test[0]) != test[1]: raise ImportError("Error on wrap input in case: " + str(test[0]) +\ ", got " + str(apply(wrap, test[0])) + " instead" +\ " of " + str(test[1])) return # Ensure that wrap tests itself every time test() --- NEW FILE: IronLuteTk.py --- """IronLuteTk is responsible for knitting the various pieces of Iron Lute into a coherent application. It tracks open windows and terminates the program when they all close, and do other things that turn the outlineframe widget into a real application.""" from Tkinter import * import tkFont from statusbar import StatusBar from outlineframe import OutlineFrame from tkclipboard import TkClipboard import tkCommandMenu import tkMenuBar from types import * import guiSupport _a = None # This is a chance to do whatever configuration we want before # starting def TkStartInit(): global _a _a = Tk() try: # try to use Tile to make things prettier _a.tk.call('package', 'require', 'tile') _a.tk.call('namespace', 'import', '-force', 'ttk::*') for theme in ['xpnative', 'winnative', 'aqua', 'alt']: try: _a.tk.call("tile::setTheme", theme) break except: pass except: # no tile pass _a.withdraw() def TkFinishInit(): _a.mainloop() def TkEndApp(): _a.destroy() class IronLuteTk(Frame, guiSupport.IronLuteFrame): """IronLuteTk corresponds to one visible window frame on the screen. This is the Tk specific part of that. IronLuteTk binds to a DocumentView, which keeps track of the document and manages the saving and indicating of the top. """ def __init__(self, initialView = None): guiSupport.IronLuteFrame.__init__(self, initialView) width = 400 height = 350 master = Toplevel(width = width, height = height) self.clipboard = TkClipboard(self) self.master = master Frame.__init__(self, master, width=width, height=height, background="white") self.statusBar = StatusBar(self) self.setStatusBar(" ") reqheight = self.statusBar.winfo_reqheight() self.statusBar.reqheight = reqheight # self.documents tracks all documents included in this # window... maybe I should re-map that to being the # responsibilty of document objects them selves? # Note: Draw responsiblilities for who has to keep track of # what documents contain what documents, make sure a frame can # track all sub documents. I guess maybe it DOES belong in the # document classes, so I can ask for all children documents, # easily note deletions across all frames, and correct ask for # the "binding handle" in a document. self.resizing = 0 try: font = tkFont.Font ( family="Utopia", size="13" ) #font = tkFont.Font ( family="Times", size="10" ) menufont = tkFont.Font(family="Lucida", size="12") except: font = tkFont.Font ( family="Times", size="14" ) menufont = tkFont.Font(family="Times", size="12") self.frame = OutlineFrame(width, height - reqheight, parent = self, font = font, statusBar = self.statusBar, titleSetter = self) # Menu details self.menu = tkMenuBar.MenuBar(self.frame, self.dispatcher) self.frame.menu = self.menu master.config(menu = self.menu) # Create a temp menu command so we get the right size) self.menu.add_command(label=" ",command = lambda: 0) self.update_idletasks() reqheight += self.menu.winfo_reqheight() self.menu.delete(1) #self.frame.pack() if self.menu is not None: self.menu.createMenus() # Events self.master.bind("<Configure>", self.configure) self.statusBar.bind("<Configure>", lambda s: "break") self.bind("<Configure>", lambda s: "break") self.master.protocol("WM_DELETE_WINDOW", self.closeCheck) self.master.title(self.initialView.displayedDocument.title + " - Iron Lute") self.pack() self.configure((width, height)) def setStatusBar(self, text): self.statusBar.set(text) def setTitle(self, title): self.master.title(title) def terminateWindow(self): """Destroy this window's widget, if necessary.""" self.resizing = 1 # Don't worry about the configure event, # we're dying! (prevent exceptions) self.destroy() self.master.withdraw() self.master.destroy() def terminateApp(self): """End the entire application.""" _a.quit() def configure(self, eventOrSizeTuple): # handles window resizing # Re-arranging the widgets internally re-fires the configure # event, so we have to lock it down while we're re-arranging # the window. if type(eventOrSizeTuple) == TupleType: width, height = eventOrSizeTuple else: width = eventOrSizeTuple.width height = eventOrSizeTuple.height if not self.resizing: self.blockConfigEvent(1) reqheight = self.statusBar.reqheight self.frame.place(x = 0, y = 0, width = width, height = height - reqheight) self.frame.setWidthHeight(width, height - reqheight) self.statusBar.place(x = 0, y = height - reqheight, width = width) #self.frame.scrollbar.place(x = width - reqwidth, y = 0, height = # height - reqheight) self.place(x = 0, y = 0, width = width, height = height) self.master.update_idletasks() self.blockConfigEvent(0) def blockConfigEvent(self, val): self.resizing = val |