#!/usr/bin/python """ ASK Notation Editor TODO: a child forwards motion requests to the parent immediately; every (moveable) node needs a parent (but not every parent is a (moveable) node!) The parent is entirely responsible for the child's motion The parent / space also maintains the links for that space - the child does not directly know what it is linked to. Need to implememnt Space Need to split large and small Nodes into different classes, and implement list, set nodes? How to hide mouse cursor, for *relative* drag ? Curvy links, and auto-layout! """ from Tkinter import * from Canvas import Line, Rectangle, Oval, CanvasText, Group from math import sqrt, atan2, pi, sin, cos, floor from random import randint from string import split class Editor(Frame): def __init__(self, parent=None): Frame.__init__(self, parent) Pack.config(self) self.create_widgets() self.HANDLED = 0 def create_widgets(self): self.master.title("ASK Notation Editor") self.pack(fill=BOTH, expand=Y) # entry commands = {"delete":self.command_delete, "destroy":self.command_destroy, "exit":self.command_exit, "quit":self.command_exit} self.entry = CommandEntry(self, commands) self.entry.pack(fill=X, expand=N, side=BOTTOM) Tk.focus(self.entry) # canvas self.canvas = Canvas(self, width=800, height=600, background="blue") self.canvas.pack(fill=BOTH, expand=Y, side=TOP) # menus self.menu = Menu(tearoff=0) self.menu.add_command(label="Move", command=self.move) self.menu.add_command(label="Link") Text(self.menu, height=1) self.menu1 = Menu(tearoff=0) self.menu1.add_command(label="Move") self.menu1.add_command(label="Link") self.menu.add_cascade(menu=self.menu1, label="Fred") self.menu.add_cascade(menu=self.menu1, label="Wilma") self.canvas.bind("", self.toggle_menu) self.canvas.bind("", self.scroll_canvas_start) self.canvas.bind("", self.scroll_canvas) def move(self): print "hello!" def scroll_canvas_start(self, event): if self.HANDLED: self.HANDLED = 0 return self.scrolling = 0 self.canvas.scan_mark(event.x, event.y) def scroll_canvas(self, event): if self.HANDLED: self.HANDLED = 0 return self.scrolling = 1 self.canvas.scan_dragto(event.x, event.y) def toggle_menu(self, event): if not self.scrolling: if self.menu.winfo_ismapped(): self.menu.unpost() else: x = self.canvas.winfo_rootx() + event.x y = self.canvas.winfo_rooty() + event.y self.menu.post(x - 4, y - 4) def command_delete(self, words): tags = self.canvas.gettags(CURRENT) for tag in tags: if tag[:5] == 'Group': Node.by_group[tag].delete() break def command_destroy(self, words): print "destroy" None def command_exit(self, words): sys.exit(0) class CommandEntry(Entry): def __init__(self, parent, commands): Entry.__init__(self, parent) self.commands = commands # command shortcuts for command in commands.keys(): func = commands[command] while 1: command = command[:-1] if not command: break try: if commands[command]: commands[command] = None else: break except KeyError: commands[command] = func self.bind("", self.enter) def enter(self, event): words = split(self.get()) if words: command, words = words[0], words[1:] self.delete(0, 'end') func = None try: func = self.commands[command] if func: func(words) else: self.error("" % command) except KeyError: self.error("" % command) def error(self, error): self.delete(0, "end") self.insert(0, error) self.bind("", self.clear_error) def clear_error(self, event): self.delete(0, "end") self.unbind("") class Node: id = 0 by_id = {} # do we need this? by_group = {} def __init__(self, name=None, x=0, y=0, parent=None, size=5, fill="red"): self.id = Node.id Node.by_id[self.id] = self # deletion? Node.id = Node.id + 1 if not name: name = self.id self.parent = parent self.name = name self.children = {} self.group = None # indicates not showing self.fill = fill self.size = size self.x = 0 self.y = 0 self.move_to(x, y) self.links = {} # these are *child* links, not links to this node, key: node name / id, value: link name / id if parent: parent.add_child(self) def delete(self): for child in self.children.values(): child.delete() self.canvas.delete(self.group) # not finished! def show(self, canvas, parent_x=0, parent_y=0):#, ancestors=[]): self.canvas = canvas x = self.x = self.x + parent_x y = self.y = self.y + parent_y r = self.size self.group = Group(canvas) Node.by_group["%s" % self.group] = self # ancestors.append(self.id) self.body = Oval(canvas, x-r, y-r, x+r, y+r, fill=self.fill)#, tags=[ancestors]) self.label = CanvasText(canvas, x, y, text=self.name, fill="white")#, tags=[ancestors]) self.label_x = self.label_y = 0 self.group.addtag_withtag(self.body) self.group.addtag_withtag(self.label) for child in self.children.values(): child.show(canvas, x, y)#, ancestors) # ancestors.pop() self.group.bind("", self.mouse_down) self.group.bind("", self.mouse_move) self.group.bind("", self.mouse_up) def hide(self): self.canvas = self.group = None def add_child(self, child): self.children[child.name] = child def move_by(self, dx, dy): self.x = self.x + dx self.y = self.y + dy if self.group: self.group.move(dx, dy) for child in self.children.values(): child.move_by(dx, dy) def move_to(self, x, y): self.move_by(x - self.x, y - self.y) def mouse_down(self, event): editor.HANDLED = 1 Node.off_x = self.x - event.x Node.off_y = self.y - event.y def mouse_move(self, event): editor.HANDLED = 1 self.move_to(event.x + Node.off_x, event.y + Node.off_y) if self.parent: self.parent.constrain(self) # this is not necessary? def mouse_up(self, event): if self.parent: self.parent.constrain(self) def constrain(self, child): r = self.size dx = child.x - self.x dy = child.y - self.y d = sqrt(dx*dx + dy*dy) # beware of division by zero!! dx = dx * r / d dy = dy * r / d a = atan2(dx, -dy)*180/pi a = floor(a / 30 + 0.5)*30 a = a*pi/180 child.move_to(self.x + dx, self.y + dy) child.out_angle(a) def out_angle(self, a): i = int(floor(a*180/pi / 45 + 0.5)) anchor = ["s", "sw", "w", "nw", "n", "ne", "e", "se"][i % 8] self.label.config(anchor=anchor) r = self.size new_x = r*sin(a) new_y = -r*cos(a) self.label.move(new_x - self.label_x, new_y - self.label_y) self.label_x = new_x self.label_y = new_y editor = Editor() for i in range(0, 5): node = Node("Sam", 20, 20, size=20) Node("Sophie", randint(-14,14), randint(-14,14), node, fill="yellow") Node("Jackie", randint(-14,14), randint(-14,14), node, fill="green") Node("Fred", randint(-14,14), randint(-14,14), node, fill="black") node.show(editor.canvas) editor.mainloop()