from Tkinter import * from PIL import Image, ImageTk import tkSimpleDialog import urllib import urllib2 import json import time sys.path.append("interface/HUTN") sys.path.append("wrappers") from modelverse import * address = "http://127.0.0.1:8001" taskname = str(random.random()) MAX_WIDTH = 500 MAX_HEIGHT = 500 MM_RENDERED = "MM_rendered_graphical" def rendered_formula(model_name, mapper_name): return "__RENDERED_%s__%s" % (model_name, mapper_name) init() login("admin", "admin") root = Tk() class Window(object): def __init__(self): self.canvas = Canvas(root, width=MAX_WIDTH, height=MAX_HEIGHT, bg="white") self.selected_model = StringVar(root) self.selected_mapper = StringVar(root) mm_buttons = [] available_models = model_list() available_mappers = [] self.model_options = OptionMenu(root, self.selected_model, *[i[0] for i in available_models]) self.mapper_options = OptionMenu(root, self.selected_mapper, "", *available_mappers) self.reset_optionmenu(self.mapper_options, [], self.selected_mapper) bound_functions = { "open": self.open_model, "render": self.render_model, "instantiate": self.instantiate_model, "verify": self.verify_model, } buttons = [] buttons.append(self.model_options) buttons.append(self.mapper_options) self.current_element = None for k, v in bound_functions.items(): buttons.append(Button(root, text=k, command=v)) for i, b in enumerate(buttons): b.grid(row=0, column=i, sticky="WENS") self.mm_buttons = [] self.CS_to_AS = {} self.AS_to_CS = {} self.AS_to_TK = {} self.TK_to_CS = {} self.canvas.grid(row=2,column=0,columnspan=len(buttons)) self.canvas.bind("", self.left_clicked) self.canvas.bind("", self.left_drag) self.canvas.bind("", self.left_released) self.canvas.bind("", self.middle_clicked) self.canvas.bind("", self.right_clicked) def find_AS(self, x, y): # Find the group at this location def in_bbox(coord, bbox): if bbox[0] <= coord[0] <= bbox[2] and bbox[1] <= coord[1] <= bbox[3]: return True for elem in self.canvas.find_all(): if in_bbox((x, y), self.canvas.bbox(elem)): return self.CS_to_AS[self.TK_to_CS[elem]] def move_group(self, elem, coords): # Change coordinates of all enclosed elements! # elem is an AS id of the group m_x, m_y = coords o_x, o_y = self.previous_coords d_x = m_x - o_x d_y = m_y - o_y for tkinter_obj in self.AS_to_TK[elem]: obj_coords = self.canvas.coords(tkinter_obj) if len(obj_coords) == 2: c_x, c_y = obj_coords self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y)) elif len(obj_coords) == 4: c_x, c_y, c_x2, c_y2 = obj_coords self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y, c_x2 + d_x, c_y2 + d_y)) def left_clicked(self, evt): # Left click means we select an element for dragging x, y = evt.x, evt.y self.original_coords = x, y self.previous_coords = x, y self.current_element = self.find_AS(x, y) def left_drag(self, evt): # Left drag means we are dragging something, but only if something is selected if self.current_element is not None: x, y = evt.x, evt.y self.move_group(self.current_element, (x, y)) self.previous_coords = x, y def left_released(self, evt): # Left click means we select an element for dragging if self.current_element is not None: x, y = evt.x, evt.y d_x = x - self.original_coords[0] d_y = y - self.original_coords[1] self.move_group(self.current_element, (x, y)) attrs = read_attrs(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element]) print("Shift: " + str((d_x, d_y))) # Send new values to Modelverse! attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "x", attrs["x"] + d_x) attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "y", attrs["y"] + d_y) def middle_clicked(self, evt): # Middle clicked means we are modifying attributes of something x, y = evt.x, evt.y # Read available attrs and their values as_id = self.find_AS(x, y) attrs = read_attrs(self.selected_model.get(), as_id) attrs = {k: json.dumps(attrs[k]) for k in attrs} print("Got attrs: " + str(attrs)) # Prompt for new values d = PromptDialog(root, attrs) if d.result is not None: # Change all attributes that have changed and send to Modelverse # TODO #TODO think about type checking print("New attributes: " + str(d.result)) for k, v in d.result.items(): v = json.loads(v) if v is None: attr_delete(self.selected_model.get(), as_id, k) else: attr_assign(self.selected_model.get(), as_id, k, v) def right_clicked(self, evt): # Right clicked means we are creating something x, y = evt.x, evt.y # Instantiate new element of currently using element pass def visualize(self, model): cache = {} hierarchy = {} parent = {} self.canvas.delete("all") self.CS_to_AS = {} self.AS_to_CS = {} self.AS_to_TK = {} self.TK_to_CS = {} def findXY(element_id): x = cache[element_id]["x"] y = cache[element_id]["y"] while element_id in parent: # Has a parent (group), so increment the XY values and keep going element_id = parent[element_id] x += cache[element_id]["x"] y += cache[element_id]["y"] return x, y def findGroup(element_id): try: while cache[element_id]["__asid"] is None: element_id = parent[element_id] return element_id except KeyError: # No configured __asid found in hierarchy print("ERROR") return None def findASID(element_id): return cache[findGroup(element_id)]["__asid"] for element in model: # Determine type of element cache[element["id"]] = element t = element["type"] if t == "contains": # Cache the containment links as well parent[element["__target"]] = element["__source"] hierarchy.setdefault(element["__source"], []).append(element["__target"]) # Caches filled, so go over all elements, ignoring groups, and draw them relative to their enclosing groups for element in model: t = element["type"] if t in ["Rectangle", "Ellipse", "Line", "Text", "Figure"]: x, y = findXY(element["id"]) as_ID = findASID(element["id"]) cs_ID = element["id"] group_ID = findGroup(element["id"]) elif t in ["Group", "contains", "renders"]: continue else: raise Exception(str(element)) if t == "Rectangle": fillColour = element["fillColour"] width = element["width"] height = element["height"] lineWidth = element["lineWidth"] lineColour = element["lineColour"] tkinter_elem = self.canvas.create_rectangle(x, y, x+width, y+height, fill=fillColour, outline=lineColour) elif t == "Ellipse": fillColour = element["fillColour"] width = element["width"] height = element["height"] lineWidth = element["lineWidth"] lineColour = element["lineColour"] tkinter_elem = self.canvas.create_ellipse(x, y, x+width, y+height, fill=fillColour, outline=lineColour) elif t == "Line": targetX = element["targetX"] targetY = element["targetY"] lineWidth = element["lineWidth"] lineColour = element["lineColour"] tkinter_elem = self.canvas.create_line(x, y, targetX, targetY, fill=lineColour, width=lineWidth) elif t == "Text": lineWidth = element["lineWidth"] lineColour = element["lineColour"] text = element["text"] tkinter_elem = self.canvas.create_text(x, y, fill=lineColour, text=text) elif t == "Figure": # Fetch associated SVG figure raise NotImplementedError() self.TK_to_CS[tkinter_elem] = cs_ID self.CS_to_AS[cs_ID] = as_ID self.AS_to_CS[as_ID] = group_ID self.AS_to_TK.setdefault(as_ID, set([])).add(tkinter_elem) def reset_optionmenu(self, optionmenu, options, var): menu = optionmenu.children["menu"] menu.delete(0, "end") var.set("") for option in options: menu.add_command(label=option, command=lambda value=option: var.set(value)) def read_available_mappers(self, instance_model): # Get instance model's type first, as transformations are defined on the type models = dict(model_list()) type_model = models[instance_model] mappers = transformation_between(type_model, MM_RENDERED) return mappers def create_element(self, t): def create_elem(): print("Create element of type " + str(t)) return create_elem def render_model(self): try: model_exit() except InvalidMode: pass rendered = model_render(self.selected_model.get(), self.selected_mapper.get()) #TODO visualize rendered model import json self.visualize(json.loads(rendered)) def open_model(self): try: model_exit() except InvalidMode: pass available_mappers = self.read_available_mappers(self.selected_model.get()) self.reset_optionmenu(self.mapper_options, available_mappers, self.selected_mapper) available_types = [i[0] for i in types_full(self.selected_model.get()) if i[1] == "Class"] for button in self.mm_buttons: button.destroy() self.mm_buttons = [] for i, t in enumerate(available_types): self.mm_buttons.append(Button(root, text=t, command=lambda : self.create_element(t))) self.mm_buttons[-1].grid(row=1, column=i) def instantiate_model(self): try: model_exit() except InvalidMode: pass d = PromptDialog(root, ["Name:"]) if d.result is not None: name = str(d.result["Name:"]) model_add(name, self.selected_model.get()) self.reset_optionmenu(self.available_models, model_list()) self.selected_model.set(name) self.open_model() def verify_model(self): try: # Exit if necessary model_exit() except InvalidMode: pass print(verify(self.selected_model.get())) class PromptDialog(tkSimpleDialog.Dialog): def __init__(self, master, query): self.query = query tkSimpleDialog.Dialog.__init__(self, master) def body(self, master): self.entries = {} for i, q in enumerate(self.query.items()): Label(master, text=q[0]).grid(row=i, column=0) self.entries[q[0]] = Entry(master) if q[1] is not None: self.entries[q[0]].insert(END, q[1]) self.entries[q[0]].grid(row=i, column=1) return None def apply(self): self.result = {i: self.entries[i].get() for i in self.query} window = Window() root.mainloop()