main.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. from Tkinter import *
  2. from PIL import Image, ImageTk
  3. import tkSimpleDialog
  4. import urllib
  5. import urllib2
  6. import json
  7. import time
  8. sys.path.append("interface/HUTN")
  9. sys.path.append("wrappers")
  10. from modelverse import *
  11. address = "http://127.0.0.1:8001"
  12. taskname = str(random.random())
  13. MAX_WIDTH = 500
  14. MAX_HEIGHT = 500
  15. MM_RENDERED = "MM_rendered_graphical"
  16. def rendered_formula(model_name, mapper_name):
  17. return "__RENDERED_%s__%s" % (model_name, mapper_name)
  18. init()
  19. login("admin", "admin")
  20. root = Tk()
  21. class Window(object):
  22. def __init__(self):
  23. self.canvas = Canvas(root, width=MAX_WIDTH, height=MAX_HEIGHT, bg="white")
  24. self.selected_model = StringVar(root)
  25. self.selected_mapper = StringVar(root)
  26. mm_buttons = []
  27. available_models = model_list()
  28. available_mappers = []
  29. self.model_options = OptionMenu(root, self.selected_model, *[i[0] for i in available_models])
  30. self.mapper_options = OptionMenu(root, self.selected_mapper, "", *available_mappers)
  31. self.reset_optionmenu(self.mapper_options, [], self.selected_mapper)
  32. bound_functions = {
  33. "open": self.open_model,
  34. "render": self.render_model,
  35. "instantiate": self.instantiate_model,
  36. "verify": self.verify_model,
  37. }
  38. buttons = []
  39. buttons.append(self.model_options)
  40. buttons.append(self.mapper_options)
  41. self.current_element = None
  42. for k, v in bound_functions.items():
  43. buttons.append(Button(root, text=k, command=v))
  44. for i, b in enumerate(buttons):
  45. b.grid(row=0, column=i, sticky="WENS")
  46. self.mm_buttons = []
  47. self.CS_to_AS = {}
  48. self.AS_to_CS = {}
  49. self.AS_to_TK = {}
  50. self.TK_to_CS = {}
  51. self.canvas.grid(row=2,column=0,columnspan=len(buttons))
  52. self.canvas.bind("<Button-1>", self.left_clicked)
  53. self.canvas.bind("<B1-Motion>", self.left_drag)
  54. self.canvas.bind("<ButtonRelease-1>", self.left_released)
  55. self.canvas.bind("<Button-2>", self.middle_clicked)
  56. self.canvas.bind("<Button-3>", self.right_clicked)
  57. def find_AS(self, x, y):
  58. # Find the group at this location
  59. def in_bbox(coord, bbox):
  60. if bbox[0] <= coord[0] <= bbox[2] and bbox[1] <= coord[1] <= bbox[3]:
  61. return True
  62. for elem in self.canvas.find_all():
  63. if in_bbox((x, y), self.canvas.bbox(elem)):
  64. return self.CS_to_AS[self.TK_to_CS[elem]]
  65. def move_group(self, elem, coords):
  66. # Change coordinates of all enclosed elements!
  67. # elem is an AS id of the group
  68. m_x, m_y = coords
  69. o_x, o_y = self.previous_coords
  70. d_x = m_x - o_x
  71. d_y = m_y - o_y
  72. for tkinter_obj in self.AS_to_TK[elem]:
  73. obj_coords = self.canvas.coords(tkinter_obj)
  74. if len(obj_coords) == 2:
  75. c_x, c_y = obj_coords
  76. self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y))
  77. elif len(obj_coords) == 4:
  78. c_x, c_y, c_x2, c_y2 = obj_coords
  79. self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y, c_x2 + d_x, c_y2 + d_y))
  80. def left_clicked(self, evt):
  81. # Left click means we select an element for dragging
  82. x, y = evt.x, evt.y
  83. self.original_coords = x, y
  84. self.previous_coords = x, y
  85. self.current_element = self.find_AS(x, y)
  86. def left_drag(self, evt):
  87. # Left drag means we are dragging something, but only if something is selected
  88. if self.current_element is not None:
  89. x, y = evt.x, evt.y
  90. self.move_group(self.current_element, (x, y))
  91. self.previous_coords = x, y
  92. def left_released(self, evt):
  93. # Left click means we select an element for dragging
  94. if self.current_element is not None:
  95. x, y = evt.x, evt.y
  96. d_x = x - self.original_coords[0]
  97. d_y = y - self.original_coords[1]
  98. self.move_group(self.current_element, (x, y))
  99. attrs = read_attrs(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element])
  100. print("Shift: " + str((d_x, d_y)))
  101. # Send new values to Modelverse!
  102. attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "x", attrs["x"] + d_x)
  103. attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "y", attrs["y"] + d_y)
  104. def middle_clicked(self, evt):
  105. # Middle clicked means we are modifying attributes of something
  106. x, y = evt.x, evt.y
  107. # Read available attrs and their values
  108. as_id = self.find_AS(x, y)
  109. attrs = read_attrs(self.selected_model.get(), as_id)
  110. attrs = {k: json.dumps(attrs[k]) for k in attrs}
  111. print("Got attrs: " + str(attrs))
  112. # Prompt for new values
  113. d = PromptDialog(root, attrs)
  114. if d.result is not None:
  115. # Change all attributes that have changed and send to Modelverse
  116. # TODO
  117. #TODO think about type checking
  118. print("New attributes: " + str(d.result))
  119. for k, v in d.result.items():
  120. v = json.loads(v)
  121. if v is None:
  122. attr_delete(self.selected_model.get(), as_id, k)
  123. else:
  124. attr_assign(self.selected_model.get(), as_id, k, v)
  125. def right_clicked(self, evt):
  126. # Right clicked means we are creating something
  127. x, y = evt.x, evt.y
  128. # Instantiate new element of currently using element
  129. pass
  130. def visualize(self, model):
  131. cache = {}
  132. hierarchy = {}
  133. parent = {}
  134. self.canvas.delete("all")
  135. self.CS_to_AS = {}
  136. self.AS_to_CS = {}
  137. self.AS_to_TK = {}
  138. self.TK_to_CS = {}
  139. def findXY(element_id):
  140. x = cache[element_id]["x"]
  141. y = cache[element_id]["y"]
  142. while element_id in parent:
  143. # Has a parent (group), so increment the XY values and keep going
  144. element_id = parent[element_id]
  145. x += cache[element_id]["x"]
  146. y += cache[element_id]["y"]
  147. return x, y
  148. def findGroup(element_id):
  149. try:
  150. while cache[element_id]["__asid"] is None:
  151. element_id = parent[element_id]
  152. return element_id
  153. except KeyError:
  154. # No configured __asid found in hierarchy
  155. print("ERROR")
  156. return None
  157. def findASID(element_id):
  158. return cache[findGroup(element_id)]["__asid"]
  159. for element in model:
  160. # Determine type of element
  161. cache[element["id"]] = element
  162. t = element["type"]
  163. if t == "contains":
  164. # Cache the containment links as well
  165. parent[element["__target"]] = element["__source"]
  166. hierarchy.setdefault(element["__source"], []).append(element["__target"])
  167. # Caches filled, so go over all elements, ignoring groups, and draw them relative to their enclosing groups
  168. for element in model:
  169. t = element["type"]
  170. if t in ["Rectangle", "Ellipse", "Line", "Text", "Figure"]:
  171. x, y = findXY(element["id"])
  172. as_ID = findASID(element["id"])
  173. cs_ID = element["id"]
  174. group_ID = findGroup(element["id"])
  175. elif t in ["Group", "contains", "renders"]:
  176. continue
  177. else:
  178. raise Exception(str(element))
  179. if t == "Rectangle":
  180. fillColour = element["fillColour"]
  181. width = element["width"]
  182. height = element["height"]
  183. lineWidth = element["lineWidth"]
  184. lineColour = element["lineColour"]
  185. tkinter_elem = self.canvas.create_rectangle(x, y, x+width, y+height, fill=fillColour, outline=lineColour)
  186. elif t == "Ellipse":
  187. fillColour = element["fillColour"]
  188. width = element["width"]
  189. height = element["height"]
  190. lineWidth = element["lineWidth"]
  191. lineColour = element["lineColour"]
  192. tkinter_elem = self.canvas.create_ellipse(x, y, x+width, y+height, fill=fillColour, outline=lineColour)
  193. elif t == "Line":
  194. targetX = element["targetX"]
  195. targetY = element["targetY"]
  196. lineWidth = element["lineWidth"]
  197. lineColour = element["lineColour"]
  198. tkinter_elem = self.canvas.create_line(x, y, targetX, targetY, fill=lineColour, width=lineWidth)
  199. elif t == "Text":
  200. lineWidth = element["lineWidth"]
  201. lineColour = element["lineColour"]
  202. text = element["text"]
  203. tkinter_elem = self.canvas.create_text(x, y, fill=lineColour, text=text)
  204. elif t == "Figure":
  205. # Fetch associated SVG figure
  206. raise NotImplementedError()
  207. self.TK_to_CS[tkinter_elem] = cs_ID
  208. self.CS_to_AS[cs_ID] = as_ID
  209. self.AS_to_CS[as_ID] = group_ID
  210. self.AS_to_TK.setdefault(as_ID, set([])).add(tkinter_elem)
  211. def reset_optionmenu(self, optionmenu, options, var):
  212. menu = optionmenu.children["menu"]
  213. menu.delete(0, "end")
  214. var.set("")
  215. for option in options:
  216. menu.add_command(label=option, command=lambda value=option: var.set(value))
  217. def read_available_mappers(self, instance_model):
  218. # Get instance model's type first, as transformations are defined on the type
  219. models = dict(model_list())
  220. type_model = models[instance_model]
  221. mappers = transformation_between(type_model, MM_RENDERED)
  222. return mappers
  223. def create_element(self, t):
  224. def create_elem():
  225. print("Create element of type " + str(t))
  226. return create_elem
  227. def render_model(self):
  228. try:
  229. model_exit()
  230. except InvalidMode:
  231. pass
  232. rendered = model_render(self.selected_model.get(), self.selected_mapper.get())
  233. #TODO visualize rendered model
  234. import json
  235. self.visualize(json.loads(rendered))
  236. def open_model(self):
  237. try:
  238. model_exit()
  239. except InvalidMode:
  240. pass
  241. available_mappers = self.read_available_mappers(self.selected_model.get())
  242. self.reset_optionmenu(self.mapper_options, available_mappers, self.selected_mapper)
  243. available_types = [i[0] for i in types_full(self.selected_model.get()) if i[1] == "Class"]
  244. for button in self.mm_buttons:
  245. button.destroy()
  246. self.mm_buttons = []
  247. for i, t in enumerate(available_types):
  248. self.mm_buttons.append(Button(root, text=t, command=lambda : self.create_element(t)))
  249. self.mm_buttons[-1].grid(row=1, column=i)
  250. def instantiate_model(self):
  251. try:
  252. model_exit()
  253. except InvalidMode:
  254. pass
  255. d = PromptDialog(root, ["Name:"])
  256. if d.result is not None:
  257. name = str(d.result["Name:"])
  258. model_add(name, self.selected_model.get())
  259. self.reset_optionmenu(self.available_models, model_list())
  260. self.selected_model.set(name)
  261. self.open_model()
  262. def verify_model(self):
  263. try:
  264. # Exit if necessary
  265. model_exit()
  266. except InvalidMode:
  267. pass
  268. print(verify(self.selected_model.get()))
  269. class PromptDialog(tkSimpleDialog.Dialog):
  270. def __init__(self, master, query):
  271. self.query = query
  272. tkSimpleDialog.Dialog.__init__(self, master)
  273. def body(self, master):
  274. self.entries = {}
  275. for i, q in enumerate(self.query.items()):
  276. Label(master, text=q[0]).grid(row=i, column=0)
  277. self.entries[q[0]] = Entry(master)
  278. if q[1] is not None:
  279. self.entries[q[0]].insert(END, q[1])
  280. self.entries[q[0]].grid(row=i, column=1)
  281. return None
  282. def apply(self):
  283. self.result = {i: self.entries[i].get() for i in self.query}
  284. window = Window()
  285. root.mainloop()