# # A simple drawing tool demonstrating # the use of Statecharts to specify behaviour # # Hans Vangheluwe # McGill University SOCS # February 2002 # import sys from Tkinter import * class Editor(Frame): ############################################################################################################################# # def __init__(self, master=None, height=30, width=70): # initialize superclass Frame.__init__(self, master) # pack into master self.pack(fill=BOTH, expand=1) # Tk String variables for radiobuttons # as well as possible and initial values self.triangleImg=PhotoImage(file="Triangle.gif") self.rectangleImg=PhotoImage(file="Rectangle.gif") self.circleImg=PhotoImage(file="Circle.gif") self.shapes = [ ("Triangle", self.triangleImg), ("Rectangle", self.rectangleImg), ("Circle", self.circleImg)] self.shapeSelected = StringVar() self.shapeSelected.set("Triangle") self.modes = [ "Insert", "Move", "Delete"] self.modeSelected = StringVar() self.modeSelected.set("Insert") self.moveMode = "Idle" # becomes "Moving" # once MousePress in "Move" mode # becomes "Idle" again # once MouseRelease in "Move" mode # Set up buttons, labels, ... # # Draw the shape selection self.shapeSelect = Frame(self) self.w = Label(self.shapeSelect, text="Shapes", relief=GROOVE, width=10) self.w.pack(side=TOP, padx = 2, pady=2) for text, img in self.shapes: self.b = Radiobutton(self.shapeSelect, text=text, image= img, variable=self.shapeSelected, bg="white", activebackground="#F0F0F0", value=text, indicatoron = 0, width = 60, height=50, wraplength = 60, # for text ) # note: use bitmap="@triangle.xbm" for bitmap rather than img self.b.pack(side=TOP , padx = 2, pady = 2) self.modeDisplay = Label(self.shapeSelect, textvar = self.modeSelected, relief=GROOVE, width=10, fg="blue") self.modeDisplay.pack(side=BOTTOM, padx = 2, pady = 6) self.shapeSelect.pack(side=LEFT, fill=Y) # draw "mode" selection self.modeSelect = Frame(self) for text in self.modes: self.b = Radiobutton(self.modeSelect, text=text, variable=self.modeSelected, value=text, width = 10, wraplength = 60) self.b.pack(side=LEFT, padx = 2, pady = 2) # draw Exit button is drawn in the modeSelect frame too self.exitButton=Button(self.modeSelect, text="Exit", command=self.exitButtonQuit, width = 10, wraplength = 60) self.exitButton.pack(side=LEFT, padx = 5, pady = 2) self.modeSelect.pack(side=BOTTOM, fill=Y) # draw the canvas self.drawing = Canvas(self, bg="white", width="10c", height="10c",) self.drawing.focus_set() self.drawing.pack(fill=BOTH, expand = 1) # Register Event bindings self.drawing.bind("", self.onDrawingMouse1Press) self.drawing.bind("", self.onDrawingMouse1Release) self.drawing.bind("", self.onDrawingMouse1Motion) # Note: may be more efficient to bind only after onDrawingMouse1Press # and subsequent unbind when onDrawingMouse1Release ############################################################################################################################# # The Event callbacks def exitButtonQuit(self): # we may wish to put cleanup actions here print "Editor window was closed (by pushing the Exit button)" self.master.destroy() ############################################################################################################################# # The Event callbacks def onDrawingMouse1Press(self, event): self.modeString = self.modeSelected.get() if self.modeString == "Insert": self.shapeSelectedString = self.shapeSelected.get() if self.shapeSelectedString == "Triangle": self.drawTriangle(event) elif self.shapeSelectedString == "Rectangle": self.drawRectangle(event) elif self.shapeSelectedString == "Circle": self.drawCircle(event) else: print "Unknown shape" elif self.modeString == "Delete": # naievely delete whatever is closest # should check whether close enough to be considered for deletion self.drawing.delete(self.drawing.find_closest(event.x, event.y)) elif self.modeString == "Move": self.moveMode = "Moving" self.movingObject = self.drawing.find_closest(event.x, event.y) # now ready to start moving (onDrawingMouse1Motion) else: print "Unknown mode" ############################################################################################################################# # The Event callbacks def onDrawingMouse1Release(self, event): self.modeString = self.modeSelected.get() if self.modeString == "Moving": self.moveMode = "Idle" ############################################################################################################################# # The Event callbacks def onDrawingMouse1Motion(self, event): self.modeString = self.modeSelected.get() if self.modeString == "Move": # else: ignore motion # other option: draw crosshairs if self.moveMode == "Moving": self.oldCoords=self.drawing.coords(self.movingObject) self.drawing.move(self.movingObject, event.x-self.oldCoords[0], event.y-self.oldCoords[1]) ############################################################################################################################# # Drawing the shapes def drawTriangle(self, event, width=40, height=20): self.drawing.create_polygon(event.x-width/2, event.y+height/2, event.x, event.y-height/2, event.x+width/2, event.y+height/2, fill="", outline="black") ############################################################################################################################# # def drawRectangle(self, event, width=40, height=20): self.drawing.create_rectangle(event.x, event.y, event.x+width, event.y+height) ############################################################################################################################# # def drawCircle(self, event, width=30, height=20): self.drawing.create_oval(event.x, event.y, event.x+width, event.y+height) ############################################################################################################################# # def wmQuit(): # we may wish to put cleanup actions here print "Editor window was closed (by Window Manager)" sys.exit() if __name__ == "__main__": # root window root = Tk() root.protocol("WM_DELETE_WINDOW", wmQuit) root.title("Simple drawing tool") Editor(root) # To test OO encapsulation, add another editor # Editor(root) root.mainloop()