Package topo :: Package tkgui :: Module editor
[hide private]
[frames] | no frames]

Source Code for Module topo.tkgui.editor

   1  """ 
   2  The GUI model editor. 
   3   
   4  Tools:   for the editor menu bar 
   5  Objects: that can be manipulated in the canvas 
   6  Window:  window and canvas  
   7   
   8  Originally written by Alan Lindsay. 
   9   
  10  $Id: editor.py 11310 2010-07-27 16:56:14Z ceball $ 
  11  """ 
  12  __version__='$Revision: 8989 $' 
  13   
  14  from inspect import getdoc 
  15  import math 
  16   
  17  from Tkinter import Button, Label, Frame, TOP, LEFT, RIGHT, BOTTOM, E, LAST, FIRST, OptionMenu, StringVar,Canvas,X,GROOVE,RAISED,Checkbutton,Menu,Scrollbar, YES,Y,END,BOTH 
  18  from tkFileDialog import asksaveasfilename 
  19   
  20   
  21  import param 
  22  from param import tk,parameterized,normalize_path 
  23  from param.external import Combobox 
  24   
  25  import topo 
  26  from topo.command.analysis import update_activity 
  27  from topo.misc.util import shortclassname 
  28  from topo.base.simulation import EventProcessor 
  29   
  30  # Make sure at least some sheets or projections are available to 
  31  # choose from; rest will be available if some other module imports * 
  32  # from sheet and projection 
  33  from topo.base.sheet import Sheet 
  34  from topo.base.projection import Projection 
  35  from topo.base.cf import CFProjection 
  36   
  37   
  38  ########################################################################### 
  39  ## WINDOW 
  40  ########################################################################### 
  41   
  42  # These can be customized, e.g. in .topographicarc; they should probably be parameters 
  43  # somewhere. 
  44  canvas_width = 1200 
  45  scaling_factor = topo.sim.item_scale 
  46  enlarging_factor = 1.25 
  47   
  48  canvas_region = (0, 0, canvas_width, canvas_width) 
  49  """Size of the canvas, as a bounding box (xl yl xh yh).""" 
  50   
  51   
52 -class EditorCanvas(Canvas):
53 54 """ 55 EditorCanvas extends the Tk Canvas class. 56 There are 3 modes that determine the effect of mouse events in the Canvas 57 A Canvas can accept new objects, move objects and make connections 58 between them. The intended use of this class is as the main 59 canvas in a Topographica model-editing GUI. 60 """ 61
62 - def __init__(self, root = None, width = 600, height = 600):
63 Canvas.__init__(self, root, width = width, height = height,bg='white') 64 # bg = "white", bd = 2, relief = SUNKEN) 65 self.panel = Frame(root) 66 self.panel.pack(side = TOP, fill = X) 67 # Top bar of the canvas, allowing changes in size, display and to force refresh. 68 Button(self.panel,text="Refresh", command=self.refresh).pack(side=LEFT) 69 Button(self.panel,text="Reduce", command=self.reduce_scale).pack(side=LEFT) 70 Button(self.panel,text="Enlarge", command=self.enlarge_scale).pack(side=LEFT) 71 self.auto_refresh = False 72 self.console = topo.guimain 73 self.auto_refresh_checkbutton = Checkbutton(self.panel,text="Auto-refresh", 74 command=self.toggle_auto_refresh) 75 self.auto_refresh_checkbutton.pack(side=LEFT) 76 77 self.normalize_checkbutton = Checkbutton(self.panel, text="Normalize", 78 command=self.toggle_normalize) 79 80 self.normalize_checkbutton.pack(side=LEFT) 81 if EditorSheet.normalize == True: 82 self.normalize_checkbutton.select() 83 84 # retain the current focus in the canvas 85 self.scaling_factor = topo.sim.item_scale#scaling_factor 86 self.current_object = None 87 self.current_connection = None 88 self.focus = None 89 # list holding references to all the objects in the canvas 90 self.object_list = [] 91 # set the initial mode. 92 self.display_mode = 'video' 93 self.mode = "ARROW" 94 self.MAX_VIEWS = 5 95 # get the topo simulation 96 self.simulation = topo.sim 97 98 # create the menu widget used as a popup on objects and connections 99 self.option_add("*Menu.tearOff", "0") 100 self.item_menu = Menu(self) 101 self.view = Menu(self.item_menu) 102 # add property, toggle activity drawn on sheets, object draw ordering and delete entries to the menu. 103 self.item_menu.insert_command(END, label = 'Properties', 104 command = lambda: self.show_properties(self.focus)) 105 self.item_menu.add_cascade(label = 'Change View', menu = self.view, underline = 0) 106 self.item_menu.insert_command(END, label = 'Move Forward', 107 command = lambda: self.move_forward(self.focus)) 108 self.item_menu.insert_command(END, label = 'Move to Front', 109 command = lambda: self.move_to_front(self.focus)) 110 self.item_menu.insert_command(END, label = 'Move to Back', 111 command = lambda: self.move_to_back(self.focus)) 112 self.item_menu.insert_command(END, label = 'Delete', 113 command = lambda: self.delete_focus(self.focus)) 114 # the indexes of the menu items that are for objects only 115 self.object_indices = [2,3,4] 116 117 self.canvas_menu = Menu(self) 118 self.sheet_options = Menu(self.canvas_menu) 119 mode_options = Menu(self.canvas_menu) 120 self.canvas_menu.add_command(label = 'Export as PostScript image', command = self.save_snapshot) 121 self.canvas_menu.add_cascade(label = 'Select Mode', menu = mode_options) 122 mode_options.add_command(label = 'Video', command = lambda: self.set_display_mode('video')) 123 mode_options.add_command(label = 'Normal', command = lambda: self.set_display_mode('normal')) 124 mode_options.add_command(label = 'Printing', command = lambda: 125 self.set_display_mode('printing')) 126 self.canvas_menu.add_cascade(label = 'Sheet options', menu = self.sheet_options, underline = 0) 127 self.sheet_options.add_command(label = 'Toggle Density Grid', command = 128 self.toggle_object_density) 129 self.sheet_options.add_command(label = 'Toggle Activity', command = self.toggle_object_activity) 130 131 # bind key_press events in canvas. 132 self.bind('<KeyPress>', self.key_press) 133 # bind the possible left button events to the canvas. 134 self.bind('<Button-1>', self.left_click) 135 self.bind('<B1-Motion>', self.left_click_drag) 136 self.bind('<Double-1>', self.left_double_click) 137 self.bind('<ButtonRelease-1>', self.left_release) 138 # bind the possible right button events to the canvas. 139 self.bind('<<right-click>>', self.right_click) 140 # because right-click opens menu, a release event can only be flagged by the menu. 141 self.item_menu.bind('<<right-click-release>>', self.right_release) 142 143 # add scroll bar; horizontal and vertical 144 self.config(scrollregion = canvas_region) 145 vertical_scrollbar = Scrollbar(root) 146 horizontal_scrollbar = Scrollbar(root, orient = 'horizontal') 147 vertical_scrollbar.config(command = self.yview) 148 horizontal_scrollbar.config(command = self.xview) 149 self.config(yscrollcommand=vertical_scrollbar.set) 150 self.config(xscrollcommand=horizontal_scrollbar.set) 151 vertical_scrollbar.pack(side = RIGHT, fill = Y) 152 horizontal_scrollbar.pack(side = BOTTOM, fill = X)
153
154 - def key_press(self, event):
155 "What happens when a key is pressed." 156 self.change_mode(event.char)
157 158 159 # Left mouse button event handlers 160
161 - def left_click(self, event):
162 "What is to happen if the left button is pressed." 163 164 x,y = self.canvasx(event.x), self.canvasy(event.y) 165 {"ARROW" : self.init_move, # case Arrow mode 166 "MAKE" : self.none, # case Make mode 167 "CONNECTION" : self.init_connection # case Connection mode. 168 }[self.mode](x,y) # select function depending on mode
169
170 - def left_click_drag(self, event):
171 "What is to happen if the mouse is dragged while the left button is pressed." 172 173 x,y = self.canvasx(event.x), self.canvasy(event.y) 174 {"ARROW" : self.update_move, # case Arrow mode 175 "MAKE" : self.none, # case Make mode 176 "CONNECTION" : self.update_connection # case Connection mode. 177 }[self.mode](x,y) # select function depending on mode
178
179 - def left_release(self, event):
180 "What is to happen when the left mouse button is released." 181 182 x,y = self.canvasx(event.x), self.canvasy(event.y) 183 {"ARROW" : self.end_move, # case Arrow mode 184 "MAKE" : self.create_object, # case Make mode 185 "CONNECTION" : self.end_connection # case Connection mode. 186 }[self.mode](x,y) # select function depending on mode
187
188 - def left_double_click(self, event):
189 """ 190 What is to happen if the left button is double clicked. 191 The same for all modes - show the properties for the clicked item. 192 Gets object or connection at this point and gives it the focus. 193 """ 194 focus = self.get_xy(event.x, event.y) 195 if (focus != None): 196 focus.set_focus(True) 197 # show the object or connection's properties. 198 self.show_properties(focus)
199 200 201 # Right mouse button event handlers 202
203 - def right_click(self, event):
204 "What is to happen if the right button is pressed." 205 self.show_hang_list(event)
206
207 - def right_release(self, event):
208 "What is to happen when the right mouse button is released (bound to the menu)." 209 if (self.focus != None) : # remove focus. 210 self.focus.set_focus(False)
211 212 213 # Mode Methods 214
215 - def change_mode(self, char):
216 "Changes the mode of the canvas, i.e., what mouse events will do." 217 218 if not char in ('c', 'm', 'a') : return 219 # remove the focus from the previous toolbar item 220 {"ARROW" : self.arrow_tool.set_focus, # arrow toolbar item 221 "MAKE" : self.object_tool.set_focus, # object toolbar item 222 "CONNECTION" : self.connection_tool.set_focus # connection toolbar item 223 }[self.mode](False) # select function depending on mode 224 225 # determine the new mode and corresponding toolbar item. 226 if (char == 'c'): 227 mode = "CONNECTION" 228 bar = self.connection_tool 229 elif (char == 'm'): 230 mode = "MAKE" 231 bar = self.object_tool 232 elif (char == 'a'): 233 mode = "ARROW" 234 bar = self.arrow_tool 235 # set the focus of the toolbar item of the new mode and retain the note the new mode. 236 bar.set_focus(True) 237 self.mode = mode
238 239 240 # Panel methods 241
242 - def refresh(self):
243 for obj in self.object_list: 244 obj.set_focus(True) 245 obj.set_focus(False) 246 for obj in self.object_list: 247 connection_list = obj.from_connections[:] 248 connection_list.reverse() 249 for con in connection_list: 250 con.move()
251
252 - def enlarge_scale(self):
253 self.scaling_factor *= enlarging_factor 254 self.refresh()
255
256 - def reduce_scale(self):
257 self.scaling_factor /= enlarging_factor 258 self.refresh()
259
260 - def toggle_auto_refresh(self):
261 self.auto_refresh = not self.auto_refresh 262 if self.auto_refresh: 263 self.console.auto_refresh_panels.append(self) 264 else: 265 self.console.auto_refresh_panels.remove(self)
266
267 - def toggle_normalize(self):
270 271 272 273 # Object moving methods 274 # 275 # If an object is left clicked in the canvas, these methods allow 276 # it to be repositioned in the canvas. 277
278 - def init_move(self, x, y):
279 "Determine if click was on an object." 280 281 self.current_object = self.get_object_xy(x, y) 282 if (self.current_object != None) : 283 # if it was, give it the focus 284 self.current_object.set_focus(True)
285
286 - def update_move(self, x, y):
287 "If dragging an object, refresh its position" 288 289 if (self.current_object != None): 290 self.current_object.move(x, y)
291
292 - def end_move(self, x, y):
293 "If dropping an object, remove focus and refresh." 294 295 if (self.current_object != None): 296 self.current_object.set_focus(False) 297 self.current_object.move(x, y) 298 # redraw all the objects in the canavas and dereference 299 self.redraw_objects() 300 self.current_object = None
301 302 # Connection methods 303 # 304 # these methods allow a connection to be made between two objects in the canvas 305
306 - def init_connection(self, x, y):
307 "Determine if click was on an object, and retain if so." 308 309 current_object = self.get_object_xy(x, y) 310 if (current_object == None) : # if not change to ARROW mode 311 self.change_mode('a') 312 else : # if on an object, create a connection and give it the focus 313 self.current_connection = self.connection_tool.new_cover(current_object) 314 self.current_connection.set_focus(True)
315
316 - def update_connection(self, x, y):
317 "Update connection's position." 318 self.current_connection.update_position((x, y))
319
320 - def end_connection(self, x, y):
321 "Determine if the connection has been dropped on an object." 322 323 obj = self.get_object_xy(x, y) 324 if (obj != None) : # if an object, connect the objects and remove focus 325 if (self.current_connection != None): 326 connected = self.connection_tool.create_connection(self.current_connection, obj) 327 if connected: 328 self.current_connection.set_focus(False) 329 else : # if not an object, remove the connection 330 connected=False 331 if (self.current_connection != None): 332 self.current_connection.remove() 333 if connected: 334 self.redraw_objects() 335 # dereference 336 self.current_connection = None
337
338 - def get_connection_xy(self, x, y):
339 "Return connection at given x, y (None if no connection)." 340 341 for obj in self.object_list: 342 connection_list = obj.from_connections[:] 343 connection_list.reverse() 344 for con in connection_list: 345 if (con.in_bounds(x, y)): 346 return con 347 return None
348 349 # Object Methods 350
351 - def create_object(self, x, y) :
352 "Create a new object." 353 self.add_object(self.object_tool.create_node(x, y))
354
355 - def add_object(self, obj) :
356 "Add a new object to the Canvas." 357 358 self.object_list = [obj] + self.object_list 359
360 - def add_object_to_back(self, obj) :
361 "Add a new object to the Canvas at back of the list." 362 363 self.object_list = self.object_list + [obj] 364
365 - def remove_object(self, obj) :
366 "Remove an object from the canvas." 367 368 for i in range(len(self.object_list)) : 369 if (obj == self.object_list[i]) : break 370 else : return # object was not found 371 del self.object_list[i] 372 return i 373
374 - def toggle_object_density(self):
375 if EditorSheet.show_density: 376 EditorSheet.show_density = False 377 else: 378 EditorSheet.show_density = True 379 self.refresh()
380
381 - def toggle_object_activity(self):
382 if EditorSheet.view == 'activity': 383 EditorSheet.view = 'normal' 384 else: 385 EditorSheet.view = 'activity' 386 self.refresh()
387
388 - def get_object_xy(self, x, y) :
389 "Return object at given x, y (or None if no object)." 390 391 # search through the bounds of each object in the canvas. 392 # returns the first (nearest to front) object, None if no object at x,y 393 for obj in self.object_list: 394 if (obj.in_bounds(x, y)): 395 break 396 else : return None 397 return obj 398 399
400 - def show_properties(self, focus):
401 "Show properties of an object or connection, and remove the focus." 402 403 if (focus != None): 404 focus.show_properties() 405 focus.set_focus(False)
406
407 - def delete_focus(self, focus):
408 "Tell a connection or object to delete itself." 409 410 if (focus == None) : 411 pass 412 else: 413 focus.remove() 414 self.redraw_objects()
415 416 # Object Order Methods 417 # 418 # These methods ensure the ordering in the canvas window is held 419 # and allows manipulation of the order. 420
421 - def redraw_objects(self, index = None):
422 """ 423 Redraw all the objects in the canvas. 424 425 If non-None, the index specifies that only the objects below 426 that index need drawing. 427 """ 428 429 if (index == None or index < 0) : index = len(self.object_list) 430 for i in range(index ,0, -1): 431 self.object_list[i-1].draw()
432
433 - def move_to_front(self, obj):
434 index = self.remove_object(obj) 435 self.add_object(obj) 436 self.redraw_objects(index)
437
438 - def move_forward(self, obj):
439 for i in range(len(self.object_list)) : # find object index in list 440 if (obj == self.object_list[i]) : break 441 else : return # object was not found 442 # swap this object for the one higher in the canvas and redraw 443 a = self.object_list[(i-1) : (i+1)] 444 a.reverse() 445 self.object_list[(i-1):(i+1)] = a 446 self.redraw_objects(i+1)
447
448 - def move_to_back(self, obj):
449 self.remove_object(obj) 450 self.add_object_to_back(obj) 451 self.redraw_objects()
452 453 # Hang List Methods 454 # 455 # If there is an object or connection at the right clicked point, 456 # a popup menu is displayed, allowing for modifications to the 457 # particular obj/con. 458
459 - def show_hang_list(self, event):
460 461 # change to ARROW mode and get x, y mouse coords 462 self.change_mode('a') 463 x, y = self.canvasx(event.x), self.canvasy(event.y) 464 # get connection at this point 465 focus = self.get_connection_xy(x, y) 466 for i in range(self.MAX_VIEWS) : # max number of views 467 self.view.delete(END) 468 if (focus == None): 469 # if no connection, checks bounds of objects 470 focus = self.get_object_xy(x, y) 471 # fill in menu items that are just for objects 472 for i in self.object_indices: 473 self.item_menu.entryconfig(i, foreground = 'Black', activeforeground = 'Black') 474 else: 475 # gray out menu items that are just for objects 476 for i in self.object_indices: 477 self.item_menu.entryconfig(i,foreground = 'Gray', activeforeground = 'Gray') 478 if (focus != None): 479 for (label, function) in focus.viewing_choices: 480 self.view.add_command(label = label, command = function) 481 # give the connection or object the focus 482 focus.set_focus(True) 483 self.focus = focus 484 # create the popup menu at current mouse coord 485 self.item_menu.tk_popup(event.x_root, event.y_root) 486 else: 487 self.canvas_menu.tk_popup(event.x_root, event.y_root)
488 489 # Utility methods 490
491 - def save_snapshot(self):
492 POSTSCRIPT_FILETYPES = [('Encapsulated PostScript images','*.eps'), 493 ('PostScript images','*.ps'),('All files','*')] 494 snapshot_name = asksaveasfilename(filetypes=POSTSCRIPT_FILETYPES, 495 initialdir=normalize_path(), 496 initialfile=topo.sim.basename()+".ps") 497 498 if snapshot_name: 499 self.postscript(file=snapshot_name)
500
501 - def set_display_mode(self, mode):
502 self.display_mode = mode 503 for obj in self.object_list: 504 obj.set_mode(mode)
505
506 - def set_tool_bars(self, arrow_tool, connection_tool, object_tool):
507 # reference to the toolbar items, a tool is notified when the canvas is changed 508 # to the mode corresponding to it. 509 self.arrow_tool = arrow_tool 510 # connection tool supplies 'connection' objects that can draw themselves in the canvas 511 self.connection_tool = connection_tool 512 # object tool supplies 'object' objects that can draw themselves in the canvas 513 self.object_tool = object_tool 514 # initialise mode 515 self.change_mode('a')
516 517 # does nothing
518 - def none(self, x, y) : pass
519
520 - def get_xy(self, x, y):
521 "Returns the connection or object at this x, y position or None if there is not one." 522 523 # check for a connection 524 focus = self.get_connection_xy(x, y) 525 if (focus == None): 526 # if no connection, check bounds of objects 527 focus = self.get_object_xy(x, y) 528 return focus # return the first found or None
529 530 531 532 533 # JABALERT: I made this into parameterized.Parameterized to make self.warning work, 534 # but it should be changed to a PlotGroupPanel eventually
535 -class ModelEditor(parameterized.Parameterized):
536 """ 537 This class constructs the main editor window. It uses a instance 538 of GUICanvas as the main editing canvas and inserts the 539 three-option toolbar in a Frame along the left side of the window. 540 """ 541
542 - def __init__(self,master,**params):
543 parameterized.Parameterized.__init__(self,**params) 544 545 # create editor window and set title 546 root = tk.AppWindow(master) 547 root.title("Model Editor") 548 549 canvas_frame = Frame(root,bg = 'white') 550 canvas_frame.pack(side='right',fill = BOTH, expand = YES) 551 552 toolbar_frame = Frame(root, bg = 'light grey', bd = 2) 553 toolbar_frame.pack(side=LEFT,fill=Y) 554 555 self.canvas = EditorCanvas(canvas_frame) 556 self.canvas.pack(fill = BOTH, expand = YES) 557 558 559 # object/node = sheet 560 561 parameters_tool = ParametersTool(toolbar_frame) 562 arrow_tool=ArrowTool(self.canvas,toolbar_frame,parameters_tool) 563 object_tool=NodeTool(self.canvas,toolbar_frame,parameters_tool) 564 connection_tool=ConnectionTool(self.canvas,toolbar_frame,parameters_tool) 565 566 arrow_tool.pack(side='top') 567 object_tool.pack(side='top') 568 connection_tool.pack(side='top') 569 parameters_tool.pack(side='top') 570 571 572 # give the canvas a reference to the toolbars 573 self.canvas.set_tool_bars(arrow_tool, connection_tool, object_tool) 574 575 # give the canvas focus and import any objects and connections already in the simulation 576 self.canvas.focus_set() 577 578 579 # Grid layout defaults 580 self.xstart = 100 581 self.ystart = 100 582 self.next_x = self.xstart+100 583 self.next_y = self.ystart 584 self.xstep = 150 585 self.ystep = 150 586 587 self.import_model()
588
589 - def import_model(self):
590 # get a list of all the objects in the simulation 591 sim = self.canvas.simulation 592 node_dictionary = sim.objects(EventProcessor) 593 node_list = node_dictionary.values() 594 595 # create the editor covers for the nodes 596 for node in node_list: 597 # if the sheet has x,y coords, use them 598 if (hasattr(node,'layout_location') and node.layout_location!=(-1,-1)): 599 x, y = node.layout_location 600 # if not generate new coords on a grid layout 601 # Could use dot/graphviz to place the objects nicely 602 else: 603 x, y = self.next_x , self.next_y 604 self.next_y += self.ystep 605 if self.next_y > canvas_region[3]: 606 self.next_y = self.ystart 607 self.next_x += self.xstep 608 if self.next_x > canvas_region[2]: 609 self.next_x = self.xstart + 15 610 self.next_y = self.ystart + 15 611 612 # Could handle more EventProcessor subclasses here 613 if isinstance(node,Sheet): 614 editor_node = EditorSheet(self.canvas, node, (x, y), node.name) 615 else: 616 editor_node = EditorEP(self.canvas, node, (x, y), node.name) 617 618 node.layout_location=(x,y) 619 self.canvas.add_object(editor_node) 620 621 # create the editor covers for the connections 622 623 for editor_node in self.canvas.object_list: 624 for con in editor_node.simobj.out_connections: 625 # Could handle more EditorConnection subclasses here 626 if isinstance(con,CFProjection): 627 editor_connection = EditorProjection("", self.canvas, editor_node) 628 else: 629 editor_connection = EditorEPConnection("", self.canvas, editor_node) 630 631 # find the EditorNode that the proj connects to 632 for dest in self.canvas.object_list: 633 if (dest.simobj == con.dest): 634 # connect the connection to the destination node 635 editor_connection.connect(dest, con) 636 break 637 else: 638 self.warning("The model editor cannot draw connection", con.name, 639 "because", con.dest.name, "is not drawn in the editor.") 640 641 642 643 self.canvas.redraw_objects()
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 ########################################################################### 668 ## TOOLS 669 ########################################################################### 670
671 -class ArrowTool(Frame):
672 """ 673 ArrowTool is a selectable frame containing an arrow icon and a label. It is a 674 toolbar item in a ModelEditor that allows the user to change the GUICanvas to 675 'ARROW' mode. 676 """ 677
678 - def __init__(self, canvas, parent = None, parambar = None):
679 Frame.__init__(self, parent,bg = 'light grey', bd = 4, relief = RAISED) 680 self.canvas = canvas # hold canvas reference 681 self.parameter_tool = parambar # To display class properties and name 682 # label sets canvas mode 683 self.title_label = Label(self, text="Move:", bg ='light grey') 684 self.title_label.bind('<Button-1>', self.change_mode) 685 self.title_label.pack() 686 self.doc = 'Use the arrow tool to select and\nmove objects in the canvas around' 687 # arrow icon 688 self.icon = Canvas(self, width = 35, height = 30,bg = 'light grey') 689 self.icon.create_polygon(10,0, 10,22, 16,17, 22,29, 33,22, 25,13, 33,8, 690 fill = 'black', outline = 'white') 691 self.icon.pack() 692 self.icon.bind('<Button-1>', self.change_mode) # icon sets canvas mode 693 # pack in toolbar at top and fill out in X direction; click changes canvas mode 694 self.pack(side = TOP, fill = X) 695 self.bind('<Button-1>', self.change_mode)
696 697
698 - def change_mode(self, event):
699 self.canvas.change_mode('a') # (ARROW)
700
701 - def set_focus(self, focus):
702 "Change the background highlight to reflect whether this toolbar item is selected." 703 704 if (focus): 705 col = 'dark grey'; relief = GROOVE 706 if not(self.parameter_tool == None): 707 self.parameter_tool.set_focus('Arrow', None, self.doc) 708 else: 709 col = 'light grey'; relief = RAISED 710 711 # ALERT 712 self.config(bg = col, relief = relief) 713 self.title_label.config(bg = col) 714 self.icon.config(bg = col)
715 716 717 718 # hack to sort by precedence 719 # (won't need when modeleditor is plotgroup)
720 -def names_sorted_by_precedence(classes):
721 classes.sort(lambda x, y: cmp(-x.precedence,-y.precedence)) 722 return [class_.__name__ for class_ in classes]
723 724
725 -class NodeTool(Frame):
726 """ 727 NodeTool extends Frame. It is expected to be included in a topographica 728 model development GUI and functions as a self populating Node tool. 729 The available Sheet types are supplied to be selected from. This Tool supplies 730 a suitable Editor cover for a node and creates the corresponding topo object. 731 """ 732 # hack: we can do this properly when converting to a plotgrouppanel 733 default_sheet = 'CFSheet' 734
735 - def __init__(self, canvas, parent = None, parambar = None):
736 737 Frame.__init__(self, parent,bg = 'light grey', bd = 4, relief = RAISED) 738 self.canvas = canvas # hold canvas reference. 739 self.parameter_tool = parambar # To display class properties and name 740 # bind clicks, pack in toolbar at top and fill out in X direction 741 self.bind('<Button-1>', self.change_mode) 742 self.pack(side = TOP, fill = X) 743 # label sets canvas mode 744 745 # CebAlerT: make these 'move' 'add sheet of type' things etc 746 # look like buttons again, or -better - clean up the whole 747 # interface! 748 749 self.title_label = Label(self, text="Add sheet of type:",bg ='light grey') 750 self.title_label.bind('<Button-1>', self.change_mode) 751 self.title_label.pack() 752 self.doc = 'Use the sheet tool to click a\nsheet object into the canvas.' 753 # gets list of all the available sheets. 754 self.sheet_list = param.concrete_descendents(Sheet) 755 756 sheet_list = names_sorted_by_precedence(self.sheet_list.values()) 757 758 ## menu with list of available sheets 759 self.option_var = StringVar() 760 self.option_var.set(self.default_sheet) 761 self.current_option = self.option_var.get() 762 763 self.option_menu = Combobox(self,textvariable=self.option_var, 764 values=sheet_list,state='readonly') 765 self.option_menu.pack() 766 self.option_menu.bind('<Button-1>', self.change_mode) 767 768 self.option_var.trace_variable('w',self.set_option)
769 770 771 772 773 774 # Focus Methods 775
776 - def change_mode(self, option):
777 self.canvas.change_mode('m') # ('MAKE')
778
779 - def set_focus(self, focus):
780 "Change the background highlight to reflect whether this toolbar item is selected." 781 782 if (focus): 783 col = 'dark grey'; relief = GROOVE 784 if not(self.parameter_tool == None): 785 current_option = self.sheet_list[self.current_option] 786 name = str(current_option).split('.')[-1][:-2] 787 self.parameter_tool.set_focus(name, current_option, self.doc) 788 else: 789 col = 'light grey'; relief = RAISED 790 791 # ALERT 792 self.config(bg = col, relief = relief) 793 self.title_label.config(bg = col)
794 #self.option_menu.config(bg = col) 795 796 797 # Node Methods 798
799 - def create_node(self, x, y):
800 if self.parameter_tool.focus: 801 self.parameter_tool.update_parameters() 802 # get the current selection and create the new topo object 803 804 # CEBALERT: because Parameterized overwrites the name 805 # unless it's passed in params when the object is created, I 806 # pass the class name (set by ParametersFrameWithApply) here. 807 # Same goes for projections. (i.e. Allow people to set the name 808 # for a new sheet or projection.) 809 name=self.sheet_list[self.current_option].name 810 # Instead, should have a popup that asks for the name. 811 812 if name: 813 simobj = self.sheet_list[self.current_option](name=name) 814 else: 815 simobj = self.sheet_list[self.current_option]() 816 817 sim = self.canvas.simulation # get the current simulation 818 sim[simobj.name] = simobj 819 # create the cover for the simobj and return it. 820 return EditorSheet(self.canvas, simobj, (x, y), simobj.name)
821 822 823 # Util Methods 824
825 - def set_option(self,*args):
826 """ """ 827 self.current_option = self.option_var.get() 828 self.change_mode(None)
829 830 831 832 833 # JABHACKALERT: Currently only searches for topo.projection (connections have not been implemented yet).
834 -class ConnectionTool(Frame):
835 """ 836 ConnectionTool extends Frame. It is expected to be included in a topographica 837 model development GUI and functions as a self populating Connection toolbar. 838 The available Connection types are listed and the user can select one. 839 When a connection is formed between two nodes a topo.projection of the 840 specified type is instantiated and a reference to it is stored in it's Editor 841 cover. Allows user to change the EditorCanvas mode to 'CONNECTION' mode. 842 """ 843
844 - def __init__(self, canvas, parent = None, parambar = None):
845 # super constructor call. 846 Frame.__init__(self, parent, bg = 'light grey', bd = 4, relief = RAISED) 847 self.canvas = canvas # hold canvas reference. 848 self.parameter_tool = parambar # To display class properties and name 849 # bind clicks, pack in toolbar at top and fill out in X direction 850 self.bind('<Button-1>', self.change_mode) 851 self.pack(side = TOP, fill = X) 852 # label sets canvas mode 853 self.title_label = Label(self, text="Add projection of type:",bg ='light grey') 854 self.title_label.bind('<Button-1>', self.change_mode) 855 self.title_label.pack() 856 self.doc = 'Use the connection tool to\ndrag connections between objects' 857 # gets list of all the available projections. 858 self.proj_list = param.concrete_descendents(Projection) 859 proj_list = names_sorted_by_precedence(self.proj_list.values()) 860 861 862 ## menu with list of available projections 863 self.option_var = StringVar() 864 self.option_var.set(proj_list[0]) 865 self.current_option = self.option_var.get() 866 867 self.option_menu = Combobox(self,textvariable=self.option_var, 868 values=proj_list,state='readonly') 869 870 self.option_menu.pack() 871 self.option_menu.bind("<Button-1>",self.change_mode) 872 873 self.option_var.trace_variable('w',self.set_option)
874 875 876 877 # Canvas Topo Linking Methods 878
879 - def new_cover(self, from_node):
880 """ 881 Create an EditorProjection and return it. 882 883 If there is more than one representation for connections/ 884 projections, the returned object will depend on the current 885 selection. 886 """ 887 return EditorProjection("", self.canvas, from_node)
888
889 - def create_connection(self, editor_connection, node):
890 "Connects the editor connection and the topo simulation connection." 891 892 if self.parameter_tool.focus: 893 self.parameter_tool.update_parameters() 894 sim = self.canvas.simulation 895 from_node = editor_connection.from_node.simobj 896 to_node = node.simobj 897 con_type = self.proj_list[self.current_option] 898 con_name = con_type.name 899 # CEBHACKALERT: see alert about sheet name 900 901 # CEBHACKALERT: should probably catch a specific error? 902 try: 903 if con_name is not None: 904 con = sim.connect(from_node.name,to_node.name,connection_type=con_type,name=con_name) 905 else: 906 con = sim.connect(from_node.name,to_node.name,connection_type=con_type) 907 except Exception, e: 908 param.Parameterized().warning("Unable to connect these sheets with the given "+ self.current_option + " (" + str(e) +").") 909 editor_connection.remove() 910 return False 911 912 editor_connection.connect(node, con) 913 return True
914 915
916 - def set_option(self,*args):
917 """ """ 918 self.current_option = self.option_var.get() 919 self.change_mode(None)
920 921 922 # Focus Methods 923
924 - def change_mode(self, option):
925 self.canvas.change_mode('c') # ('CONNECTION')
926
927 - def set_focus(self, focus):
928 "Change the background highlight to reflect whether this toolbar item is selected." 929 930 if (focus): 931 col = 'dark grey'; relief = GROOVE 932 if not(self.parameter_tool == None): 933 current_option = self.proj_list[self.current_option] 934 name = str(current_option).split('.')[-1][:-2] 935 self.parameter_tool.set_focus(name, current_option, self.doc) 936 else: 937 col = 'light grey'; relief = RAISED 938 939 # ALERT 940 self.config(bg = col, relief = relief) 941 self.title_label.config(bg = col)
942 #self.option_menu.config(bg = col) 943 944
945 -class ParametersTool(Frame):
946
947 - def __init__(self, parent = None):
948 949 # CEBALERT: need to tidy up the title, positioning, etc. 950 Frame.__init__(self, parent) 951 self.focus = None 952 # label 953 self.title_label = Label(self) 954 self.title_label.pack(side = TOP) 955 self.doc_label = Label(self, font = ("Times", 12)) 956 self.doc_label.pack(side = TOP) 957 958 # CEBALERT: will the users think they have to press 'apply' rather than just clicking 959 # on the canvas to get the new object? 960 self.parameter_frame = tk.ParametersFrameWithApply(self)#,buttons_to_remove=['Close','Defaults']) 961 self.parameter_frame.hide_param('Close') 962 self.parameter_frame.hide_param('Defaults') 963 self.parameter_frame.pack(side=BOTTOM)
964
965 - def update_parameters(self):
966 self.parameter_frame.update_parameters()
967 968
969 - def set_focus(self, name, focus_class, doc = ''):
970 971 self.focus = name 972 973 self.title_label.config(text = name) 974 self.doc_label.config(text = doc) 975 976 if focus_class: 977 self.parameter_frame.set_PO(focus_class)
978 979 980 981 ########################################################################### 982 ## OBJECTS 983 ########################################################################### 984 985 # CEBALERT: should be a Parameterized
986 -class EditorObject(object):
987 """ 988 Anything that can be added and manipulated in an EditorCanvas. Every EditorCanvas 989 has a corresponding Topo object associated with it. An instance of this class can 990 have the focus. 991 """ 992 FROM = 0 993 TO = 1 994
995 - def __init__(self, name, canvas,**params):
996 self.canvas = canvas # retains a reference to the canvas 997 self.name = name # set the name of the sheet 998 self.focus = False # this does not have the focus 999 self.viewing_choices = []
1000
1001 - def draw(self):
1002 "Draw the object at the current x, y position." 1003 pass
1004
1005 - def objdoc(self):
1006 """Documentation string for this object.""" 1007 ### JABALERT: Should be expanded to allow a per-object description, 1008 ### and should be bound to the actual editor object as well. 1009 return self.name + " is of type " + \ 1010 shortclassname(self.simobj) + \ 1011 ":\n\n" + str(getdoc(self.simobj))
1012
1013 - def show_properties(self):
1014 "Show parameters frame for object." 1015 1016 parameter_window = tk.AppWindow(topo.guimain,status=True) 1017 #status=tk.StatusBar(parameter_window) 1018 #status.pack(side="bottom",fill='x',expand='yes') 1019 1020 parameter_window.title(self.name) 1021 balloon = tk.Balloon(parameter_window) 1022 1023 # import __main__;__main__.__dict__['AAA'] = parameter_window 1024 title = Label(parameter_window, text = self.name) 1025 title.pack(side = TOP) 1026 self.parameter_frame = tk.ParametersFrameWithApply( 1027 parameter_window, 1028 msg_handler=parameter_window.status) 1029 parameter_window.sizeright() 1030 1031 balloon.bind(title,self.objdoc()) 1032 self.parameter_window = parameter_window
1033
1034 - def update_parameters(self):
1035 self.parameter_frame.update_parameters()
1036
1037 - def okay_parameters(self, parameter_window):
1038 self.update_parameters() 1039 parameter_window.destroy()
1040
1041 - def set_focus(self, focus) : # set focus
1042 self.focus = focus
1043
1044 - def move(self):
1045 "Update position of object and redraw." 1046 pass
1047
1048 - def remove(self):
1049 "Remove this object from the canvas and from the Topographica simulation." 1050 pass
1051
1052 - def in_bounds(self, x, y) :
1053 "Return true if x,y lies within this gui object's boundary." 1054 pass 1055 1056 1057
1058 -class EditorNode(EditorObject):
1059 """ 1060 An EditorNode is used to cover any topographica node, presently this can only be a sheet. 1061 It is a sub class of EditorObject and supplies the methods required by any node to be used 1062 in a EditorCanvas. Extending classes will supply a draw method and other type specific 1063 attributes. 1064 """ 1065
1066 - def __init__(self, canvas, simobj, pos, name):
1067 EditorObject.__init__(self, name, canvas) 1068 self.from_connections = [] # connections from this node 1069 self.to_connections = [] # connections to this node 1070 self.x = pos[0] # set the x and y coords of the center of this node 1071 self.y = pos[1] 1072 self.mode = canvas.display_mode 1073 self.simobj = simobj
1074 1075 # Connection methods 1076
1077 - def attach_connection(self, con, from_to):
1078 if (from_to == self.FROM): 1079 if (con.from_node == con.to_node): 1080 self.from_connections = [con] + self.from_connections 1081 else: 1082 self.from_connections = self.from_connections + [con] 1083 else: 1084 if (con.from_node == con.to_node): 1085 self.to_connections = [con] + self.to_connections 1086 else: 1087 self.to_connections = self.to_connections + [con]
1088
1089 - def remove_connection(self, con, from_to) : # remove a connection to or from this node
1090 if (from_to): 1091 l = len(self.to_connections) 1092 for i in range(l): 1093 if (con == self.to_connections[i]) : break 1094 else : return 1095 del self.to_connections[i] 1096 else: 1097 l = len(self.from_connections) 1098 for i in range(l): 1099 if (con == self.from_connections[i]) : break 1100 else : return 1101 del self.from_connections[i]
1102 1103 1104 # Util methods 1105
1106 - def get_pos(self):
1107 return (self.x, self.y) # return center point of node
1108
1109 - def show_properties(self):
1110 EditorObject.show_properties(self) 1111 self.parameter_frame.set_PO(self.simobj) 1112 Label(self.parameter_window, text = '\n\nConnections').pack(side = TOP) 1113 connections = list(set(self.to_connections).union(set(self.from_connections))) 1114 connection_list = [con.name for con in connections] 1115 1116 self.connection_var = StringVar() 1117 self.connection_var.set(connection_list[0]) 1118 1119 connection_menu = OptionMenu(self.parameter_window,self.connection_var,*connection_list) 1120 1121 self.connection_var.trace_variable('w',self.view_connection_parameters) 1122 1123 connection_menu.pack(side = TOP)
1124
1125 - def view_connection_parameters(self, *args):
1126 con_sel = self.connection_var.get() 1127 for con in self.to_connections + self.from_connections: 1128 if con.name == con_sel: 1129 break 1130 else : 1131 return 1132 con.show_properties()
1133 1134 1135 1136 # JABALERT: Should probably combine this with EditorNode
1137 -class EditorEP(EditorNode):
1138 """ 1139 Represents any topo EventProcessor as a small, fixed-size oval by default. 1140 """ 1141
1142 - def __init__(self, canvas, simobj, pos, name):
1143 EditorNode.__init__(self, canvas, simobj, pos, name) 1144 simobj.layout_location = (self.x,self.y) # store the ed coords in the topo sheet 1145 self.set_bounds() 1146 1147 self.set_colours() 1148 col = self.colour[1] 1149 self.init_draw(col, False) # create a new parallelogram 1150 self.currentCol = col 1151 self.gradient = 1
1152 1153 1154 # Draw methods 1155
1156 - def set_focus(self, focus):
1157 for id in self.id: 1158 self.canvas.delete(id) 1159 self.canvas.delete(self.label) # remove label 1160 EditorNode.set_focus(self, focus) # call to super's set focus 1161 col = self.colour[not focus] 1162 self.init_draw(col, focus) # create new one with correct colour 1163 self.currentCol = col 1164 self.draw()
1165
1166 - def select_view(self, view_choice):
1167 self.view = view_choice 1168 self.set_focus(False) 1169 self.canvas.redraw_objects()
1170
1171 - def set_colours(self):
1172 colour = {'video':('dark red','black'), 1173 'normal':('slate blue', 'lavender'), 1174 'printing':('grey','white')} 1175 self.colour = colour[self.mode] # colours for drawing this node on the canvas
1176
1177 - def init_draw(self, colour, focus):
1178 if focus : label_colour = colour 1179 else : label_colour = 'black' 1180 h, w = 0.5*self.height, 0.5*self.width 1181 1182 x, y = self.x, self.y 1183 x1,y1 = (x-w,y-h) 1184 x2,y2 = (x+w,y+h) 1185 1186 self.id = [self.canvas.create_oval(x1,y1,x2,y2,fill=colour,outline="black")] 1187 dX = w + 5 1188 self.label = self.canvas.create_text(x - dX, y, anchor = E, fill = label_colour, text = self.name)
1189
1190 - def dec_to_hex_str(self, val, length):
1191 # expects a normalised value and maps it to a hex value of the given length 1192 max_val = pow(16, length) - 1 1193 fmt = '%%0%dx'%length 1194 return fmt % (val*max_val)
1195
1196 - def draw(self, x = 0, y = 0):
1197 # move the parallelogram and label by the given x, y coords (default redraw) 1198 if not(x == y == 0): 1199 for id in self.id: 1200 self.canvas.move(id, x, y) 1201 self.canvas.move(self.label, x, y) 1202 for id in self.id: 1203 self.canvas.tag_raise(id) 1204 self.canvas.tag_raise(self.label) 1205 # redraw the connections 1206 for con in self.to_connections : 1207 if (con.from_node == con.to_node): 1208 con.move() 1209 for con in self.to_connections: 1210 if (not(con.from_node == con.to_node)): 1211 con.move() 1212 for con in self.from_connections: 1213 if (not(con.from_node == con.to_node)): 1214 con.move()
1215 1216 1217 # Update methods 1218
1219 - def remove(self):
1220 l = len(self.from_connections) # remove all the connections from and to this sheet 1221 for index in range(l): 1222 self.from_connections[0].remove() 1223 l = len(self.to_connections) 1224 for index in range(l): 1225 self.to_connections[0].remove() 1226 for id in self.id: 1227 self.canvas.delete(id) 1228 self.canvas.delete(self.label) 1229 self.canvas.remove_object(self) # remove from canvas' object list 1230 del topo.sim[self.simobj.name] # actually delete the sheet
1231
1232 - def move(self, x, y):
1233 # the connections position is updated 1234 old = self.x, self.y 1235 self.x = x 1236 self.y = y 1237 self.simobj.layout_location = (self.x,self.y) # update topo sheet position 1238 self.draw(self.x - old[0], self.y - old[1])
1239 1240 1241 # Connection methods 1242
1243 - def remove_connection(self, con, from_to):
1244 EditorNode.remove_connection(self, con, from_to) 1245 if from_to: 1246 node = con.from_node 1247 else: 1248 node = con.to_node 1249 index = con.draw_index 1250 # Decrease the indexes of the connections between the same nodes and with a higher index. 1251 for connection in self.from_connections: 1252 if (node == connection.to_node and connection.draw_index >= index): 1253 connection.decrement_draw_index() 1254 if connection.to_node == connection.from_node : return 1255 for connection in self.to_connections : 1256 if (node == connection.from_node and connection.draw_index >= index): 1257 connection.decrement_draw_index()
1258
1259 - def get_connection_count(self, node):
1260 count = 0 1261 for con in self.from_connections: 1262 if (con.to_node == node): 1263 count += 1 1264 for con in self.to_connections: 1265 if (con.from_node == node): 1266 count += 1 1267 if node == self : count /= 2 1268 return count
1269 1270 1271 # Util methods 1272
1273 - def in_bounds(self, pos_x, pos_y):
1274 x = self.x - pos_x; y = self.y - pos_y 1275 1276 return ((pos_x> self.x-self.width) and (pos_x<= self.x+self.width) and 1277 (pos_y> self.y-self.height) and (pos_y<= self.y+self.height))
1278 1279
1280 - def set_bounds(self):
1281 # Default representation: fixed-size node 1282 self.width=15 1283 self.height=self.width*0.7
1284 1285
1286 - def set_mode(self, mode):
1287 self.mode = mode 1288 self.set_colours() 1289 1290 for id in self.id: 1291 self.canvas.delete(id) 1292 self.canvas.delete(self.label) # remove label 1293 self.init_draw(self.colour[not self.focus], self.focus) 1294 for con in self.to_connections: 1295 con.set_mode(mode) 1296 for con in self.from_connections: 1297 con.draw()
1298 1299 1300 1301
1302 -class EditorSheet(EditorEP):
1303 """ 1304 Represents any topo sheet. It is a subclass of EditorEP and fills in the 1305 methods that are not defined. It is represented by a Parallelogram in its 1306 Canvas. The colours used for drawing can be set. Uses bounding box to 1307 determine if x, y coord is within its boundary. 1308 """ 1309 normalize = param.Boolean(default=False) 1310 show_density = param.Boolean(default=False) 1311 view = param.ObjectSelector(default='activity',objects=['normal','activity']) 1312
1313 - def __init__(self, canvas, simobj, pos, name):
1314 # Should call EditorEP's constructor instead 1315 EditorNode.__init__(self, canvas, simobj, pos, name) 1316 simobj.layout_location = (self.x,self.y) # store the ed coords in the topo sheet 1317 self.element_count = self.matrix_element_count() 1318 self.set_bounds() 1319 1320 self.set_colours() 1321 col = self.colour[1] 1322 self.init_draw(col, False) # create a new parallelogram 1323 self.currentCol = col 1324 self.gradient = 1 1325 self.viewing_choices = [('Normal', lambda: self.select_view('normal')), 1326 ('Activity', lambda: self.select_view('activity'))]
1327 1328 1329 # Draw methods 1330
1331 - def set_focus(self, focus):
1332 for id in self.id: 1333 self.canvas.delete(id) 1334 self.canvas.delete(self.label) # remove label 1335 EditorNode.set_focus(self, focus) # call to super's set focus 1336 col = self.colour[not focus] 1337 self.init_draw(col, focus) # create new one with correct colour 1338 self.currentCol = col 1339 self.draw()
1340
1341 - def init_draw(self, colour, focus):
1342 self.id = [] 1343 if focus : label_colour = colour 1344 else : label_colour = 'black' 1345 factor = self.canvas.scaling_factor 1346 h, w = 0.5 * self.height * factor, 0.5 * self.width *factor 1347 if not(self.focus): 1348 if self.view == 'activity': 1349 colour = '' 1350 x, y = self.x - w + h, self.y - h 1351 # AL, the idea will be to allow any available plots to be shown on the sheet. 1352 # eg m = self.simobj.sheet_views['OrientationPreference'].view()[0] 1353 update_activity() 1354 m = self.simobj.sheet_views['Activity'].view()[0] 1355 if self.normalize == True: 1356 m = self.normalize_plot(m) 1357 matrix_width, matrix_height = self.element_count 1358 dX, dY = (w * 2)/ matrix_width, (h * 2) / matrix_height 1359 for i in range(matrix_height): 1360 for j in range(matrix_width): 1361 a = i * dY 1362 x1, y1 = x - a + (j * dX), y + a 1363 x2, y2 = x1 - dY, y1 + dY 1364 x3, x4 = x2 + dX, x1 + dX 1365 point = m[i][j] 1366 if point < 0 : point = 0.0 1367 if point > 1 : point = 1.0 1368 col = '#' + (self.dec_to_hex_str(point, 3)) * 3 1369 self.id = self.id + [self.canvas.create_polygon 1370 (x1, y1, x2, y2, x3, y2, x4, y1, fill = col, outline = col)] 1371 x, y = self.x, self.y 1372 x1,y1 = (x - w - h, y + h) 1373 x2,y2 = (x - w + h, y - h) 1374 x3,y3 = x2 + (w * 2), y2 1375 x4,y4 = x1 + (w * 2), y1 1376 self.id = self.id + [self.canvas.create_polygon(x1, y1, x2, y2, x3, y3, x4, y4, 1377 fill = colour , outline = "black")] 1378 dX = w + 5 1379 self.label = self.canvas.create_text(x - dX, y, anchor = E, fill = label_colour, text = self.name) 1380 # adds a density grid over the sheet 1381 if self.show_density: 1382 x, y = self.x - w + h, self.y - h 1383 matrix_width, matrix_height = self.element_count 1384 dX, dY = (w * 2)/ matrix_width, (h * 2) / matrix_height 1385 for i in range(matrix_height + 1): 1386 x1 = x - (i * dY) 1387 x2 = x1 + (w * 2) 1388 y1 = y + (i * dY) 1389 self.id = self.id + [self.canvas.create_line(x1, y1, x2, y1, fill = 'slate blue')] 1390 for j in range(matrix_width + 1): 1391 x1 = x + (j * dX) 1392 x2 = x1 - (h * 2) 1393 y1 = y 1394 y2 = y1 + (h * 2) 1395 self.id = self.id + [self.canvas.create_line(x1, y1, x2, y2, fill = 'slate blue')]
1396
1397 - def normalize_plot(self,a):
1398 """ 1399 Normalize an array s. 1400 In case of a constant array, ones is returned for value greater than zero, 1401 and zeros in case of value inferior or equal to zero. 1402 """ 1403 # AL is it possible to use the normalize method in plot? 1404 from numpy.oldnumeric import zeros, ones, Float, divide 1405 a_offset = a-min(a.ravel()) 1406 max_a_offset = max(a_offset.ravel()) 1407 if max_a_offset>0: 1408 a = divide(a_offset,float(max_a_offset)) 1409 else: 1410 if min(a.ravel())<=0: 1411 a=zeros(a.shape,Float) 1412 else: 1413 a=ones(a.shape,Float) 1414 return a
1415 1416 # Util methods 1417
1418 - def in_bounds(self, pos_x, pos_y) : # returns true if point lies in a bounding box
1419 # if the coord is pressed within the parallelogram representation this 1420 # returns true. 1421 # Get the parallelogram points and centers them around the given point 1422 x = self.x - pos_x; y = self.y - pos_y 1423 w = 0.5 * self.width * self.canvas.scaling_factor 1424 h = 0.5 * self.height * self.canvas.scaling_factor 1425 A = (x - w - h, y + h) 1426 B = (x - w + h, y - h) 1427 C = B[0] + (2 * w), B[1] 1428 D = A[0] + (2 * w), A[1] 1429 # calculate the line constants 1430 # As the gradient of the lines is 1 the calculation is simple. 1431 a_AB = A[1] + A[0] 1432 a_CD = C[1] + C[0] 1433 # The points are centered around the given coord, finding the 1434 # intersects with line y = 0 and ensuring that the left line 1435 # lies on the negative side of the point and the right line 1436 # lies on the positive side of the point determines that the 1437 # point is within the parallelogram. 1438 if ((D[1] >= 0) and (B[1] <= 0) and (a_AB <= 0) and (a_CD >= 0)): 1439 return True 1440 return False
1441 1442
1443 - def matrix_element_count(self):
1444 # returns the length and width of the matrix that holds this sheet's plot values 1445 l,b,r,t = self.simobj.bounds.aarect().lbrt() 1446 density = self.simobj.xdensity 1447 return int(density * (r - l)), int(density * (t - b))
1448
1449 - def set_bounds(self):
1450 # Use the default sheet bounds as to set the "normal" size 1451 # of SheetObject in the GUI, so simulations using very large 1452 # sheets still look normal. 1453 dl,db,dr,dt = self.simobj.__class__.nominal_bounds.aarect().lbrt() 1454 width_fact = 120.0 / (dr - dl) 1455 height_fact = 60.0 / (dt - db) 1456 l,b,r,t = self.simobj.bounds.aarect().lbrt() 1457 self.width = width_fact * (r - l) * self.canvas.scaling_factor 1458 self.height = height_fact * (t - b) * self.canvas.scaling_factor
1459 1460 1461 1462
1463 -class EditorConnection(EditorObject):
1464 1465 """ 1466 A connection formed between 2 EditorNodes on a EditorCanvas. A EditorConnection is used 1467 to cover any topographica connection (connection / projection), and extending 1468 classes will supply a draw method and other type specific attributes. 1469 """
1470 - def __init__(self, name, canvas, from_node):
1471 EditorObject.__init__(self, name, canvas) 1472 self.from_node = from_node # initial node selected 1473 self.to_node = None # updated when the user selects the second node - self.connect(..) 1474 # temporary point, for when the to connection node is undefined 1475 self.to_position = from_node.get_pos() 1476 self.mode = canvas.display_mode
1477 1478 1479 # Draw methods 1480
1481 - def set_focus(self, focus) : # give this connection the focus
1482 EditorObject.set_focus(self, focus) 1483 self.draw()
1484 1485 1486 # Update methods 1487
1488 - def move(self):
1489 # if one of the nodes connected by this connection move, then move by redrawing 1490 self.draw()
1491
1492 - def update_position(self, pos) : # update the temporary point
1493 self.to_position = pos 1494 self.draw() 1495
1496 - def connect(self, to_node, con) : # pass the node this connection is to
1497 self.simobj = con 1498 if (self.name == ""): 1499 self.name = con.name 1500 self.to_node = to_node # store a reference to the node this is connected to 1501 self.to_position = None 1502 self.from_node.attach_connection(self, self.FROM) # tell the sheets that they are connected. 1503 self.to_node.attach_connection(self, self.TO) 1504
1505 - def remove(self):
1506 # CEBALERT: there's no code here to handle GUI object 1507 # removal (though the EditorProjection subclass does have GUI 1508 # removal code, so presumably projections do get removed from 1509 # the screen. But I'm confused about what is treated as a 1510 # connection, and what as a projection in the model editor). 1511 if hasattr(self,'simobj'): 1512 self.simobj.remove()
1513 1514 1515 # Util methods 1516
1517 - def show_properties(self):
1518 EditorObject.show_properties(self) 1519 self.parameter_frame.set_PO(self.simobj)
1520 1521 1522 # JABALERT: Should probably combine this with EditorConnection
1523 -class EditorEPConnection(EditorConnection):
1524 """ 1525 Represents any topo EPConnection using a line with an arrow head in the middle. 1526 """
1527 - def __init__(self, name, canvas, from_node):
1528 EditorConnection.__init__(self, name, canvas, from_node) 1529 # if more than one connection between nodes, 1530 # this will reflect how to draw this connection 1531 self.draw_index = 0 1532 self.deviation = 0 1533 self.gradient = (1,1) 1534 self.id = (None,None) 1535 self.label = None 1536 self.balloon = tk.Balloon(canvas) 1537 self.set_colours() 1538 self.view = 'line' 1539 self.draw_fn = self.draw_line 1540 self.viewing_choices = [('Line', lambda: self.select_view('line'))] 1541 self.update_factor()
1542 1543 1544 # Draw methods 1545
1546 - def select_view(self, view_choice):
1547 self.view = view_choice 1548 self.move() 1549 self.set_focus(False)
1550
1551 - def set_colours(self):
1552 colours = {'video' : ('dark red', 'blue', 'yellow'), 1553 'normal': ('dark red', 'blue', 'yellow'), 1554 'printing': ('grey', 'black', 'black')} 1555 self.colour = colours[self.mode]
1556
1557 - def draw(self):
1558 # determine if connected to a second node, and find the correct from_position 1559 for id in self.id : # remove the old connection 1560 self.canvas.delete(id) 1561 self.canvas.delete(self.label) 1562 from_position = self.from_node.get_pos() # get the center points of the two nodes 1563 if (self.to_node == None) : # if not connected yet, use temporary point. 1564 to_position = self.to_position 1565 else: 1566 to_position = self.to_node.get_pos() 1567 1568 self.draw_fn(from_position, to_position)
1569
1570 - def draw_line(self,from_position, to_position):
1571 # set the colour to be used depending on whether connection has the focus. 1572 if (self.focus) : 1573 text_col = col = self.colour[0] 1574 lateral_colour = self.from_node.colour[0] 1575 else: 1576 text_col = 'black' 1577 col = self.colour[1] 1578 lateral_colour = '' 1579 middle = self.get_middle(from_position, to_position) 1580 factor = self.canvas.scaling_factor 1581 if (to_position == from_position) : # connection to and from the same node 1582 deviation = self.draw_index * 15 * factor 1583 x1 = to_position[0] - ((20 * factor) + deviation) 1584 y2 = to_position[1] 1585 x2 = x1 + (40 * factor) + (2 * deviation) 1586 y1 = y2 - ((30 * factor) + deviation) 1587 midX = self.get_middle((x1,0),(x2,0))[0] 1588 # create oval and an arrow head. 1589 self.id = (self.canvas.create_oval(x1, y1, x2, y2, outline = col), 1590 self.canvas.create_line(midX, y1, midX+1, y1, arrow = FIRST, fill = col)) 1591 # draw name label beside arrow head 1592 self.label = self.canvas.create_text(middle[0] - 1593 (20 + len(self.name)*3), middle[1] - (30 + deviation) , text = self.name) 1594 else : 1595 # create a line between the nodes - use 2 to make arrow in center. 1596 dev = self.deviation 1597 from_pos = from_position[0] + self.deviation, from_position[1] 1598 mid = middle[0] + 0.5 * dev, middle[1] 1599 self.id = (self.canvas.create_line(from_pos, mid , arrow = LAST, fill = col), 1600 self.canvas.create_line(mid, to_position, fill = col)) 1601 # draw name label 1602 dX = 20 * factor 1603 dY = self.draw_index * 20 * factor 1604 self.label = self.canvas.create_text(middle[0] - dX, 1605 middle[1] - dY, fill = text_col, text = self.name, anchor = E)
1606 1607 1608 # Update methods
1609 - def remove(self):
1610 if (self.to_node != None) : # if a connection had been made then remove it from the 'to' node 1611 self.to_node.remove_connection(self, self.TO) 1612 self.from_node.remove_connection(self, self.FROM) # and remove from 'from' node 1613 for id in self.id : # remove the representation from the canvas 1614 self.canvas.delete(id) 1615 self.canvas.delete(self.label) 1616 1617 # CEBALERT: see earlier alert about EditorObject not inheriting from object. 1618 EditorConnection.remove(self) #super(EditorProjection,self).remove()
1619 1620
1621 - def move(self):
1622 # if one of the nodes connected by this connection move, then move by redrawing 1623 self.gradient = self.calculate_gradient() 1624 self.update_factor() 1625 self.draw()
1626
1627 - def decrement_draw_index(self):
1628 self.draw_index -= 1 1629 if self.to_node == self.from_node: 1630 self.update_factor() 1631 else: 1632 self.connect_to_coord((self.from_node.width / 2) - 10)
1633
1634 - def connect(self, to_node, con):
1635 EditorConnection.connect(self, to_node, con) 1636 self.draw_index = self.from_node.get_connection_count(to_node)-1 1637 if (self.from_node == to_node): 1638 self.update_factor() 1639 else: 1640 self.connect_to_coord((self.from_node.width / 2) - 10) 1641 self.gradient = self.calculate_gradient() 1642 self.radius = self.get_radius()
1643 1644 # Util methods 1645
1646 - def get_middle(self, pos1, pos2) : # returns the middle of two points
1647 return (pos1[0] + (pos2[0] - pos1[0])*0.5, pos1[1] + (pos2[1] - pos1[1])*0.5)
1648
1649 - def get_radius(self):
1650 """Not implemented in this class""" 1651 return (0,0)
1652
1653 - def update_factor(self):
1654 pass
1655
1656 - def connect_to_coord(self, width):
1657 n = self.draw_index 1658 sign = math.pow(-1, n) 1659 self.deviation = sign * width + (-sign) * math.pow(0.5, math.ceil(0.5 * (n))) * width
1660 1661 # returns the gradients of the two lines making the opening 'v' part of the receptive field. 1662 # this depends on the draw_index, as it determines where the projection's representation begins.
1663 - def calculate_gradient(self):
1664 """Not implemented in this class""" 1665 return (1,1)
1666
1667 - def in_bounds(self, x, y) : # returns true if point lies in a bounding box
1668 factor = self.canvas.scaling_factor 1669 # If connections are represented as lines 1670 # currently uses an extent around the arrow head. 1671 to_position = self.to_node.get_pos() 1672 from_position = self.from_node.get_pos() 1673 if (self.to_node == self.from_node): 1674 dev = self.draw_index * 15 * factor 1675 middle = (to_position[0], to_position[1] - ((30 * factor) + dev)) 1676 else: 1677 dev = self.deviation * 0.5 1678 middle = self.get_middle(from_position, to_position) 1679 if ((x < middle[0] + 10 + dev) & (x > middle[0] - 10 + dev) & (y < middle[1] + 10) & (y > middle[1] - 10)): 1680 return True 1681 return False 1682
1683 - def set_mode(self, mode):
1684 self.mode = mode 1685 self.set_colours() 1686 self.draw()
1687 1688 1689
1690 -class EditorProjection(EditorEPConnection):
1691 """ 1692 Represents any topo CFProjection. It is a subclass of EditorEPConnection and fills 1693 in the methods that are not defined. Can be represented by a representation of a 1694 projection's receptive field or by a line with an arrow head in the middle; 1695 lateral projections are represented by a dotted ellipse around the center. 1696 Can determine if x,y coord is within the triangular receptive field or within an 1697 area around the arrow head. The same can be determined for a lateral projection 1698 ellipse. 1699 """ 1700
1701 - def __init__(self, name, canvas, from_node, receptive_field = True):
1702 EditorEPConnection.__init__(self, name, canvas, from_node) 1703 # if more than one connection between nodes, 1704 # this will reflect how to draw this connection 1705 self.normal_radius = 15 1706 self.radius = self.get_radius() 1707 self.receptive_field = receptive_field 1708 self.view = 'radius' 1709 self.viewing_choices = [('Field Radius', lambda: self.select_view('radius')), 1710 ('Line', lambda: self.select_view('line')), 1711 ('Fixed Size', lambda: self.select_view('normal'))]
1712 1713 1714 # Draw methods 1715
1716 - def draw(self):
1717 self.draw_fn = \ 1718 {'normal' : self.draw_normal, 1719 'line' : self.draw_line, 1720 'radius' : self.draw_radius 1721 }[self.view] 1722 EditorEPConnection.draw(self)
1723 1724
1725 - def draw_radius(self, from_position, to_position):
1726 # set the colour to be used depending on whether connection has the focus. 1727 if (self.focus) : 1728 text_col = col = self.colour[0] 1729 lateral_colour = self.from_node.colour[0] 1730 else: 1731 text_col = 'black' 1732 col = self.colour[1] 1733 lateral_colour = '' 1734 # midpoint of line 1735 middle = self.get_middle(from_position, to_position) 1736 factor = self.canvas.scaling_factor 1737 if (to_position == from_position) : # connection to and from the same node 1738 a, b = self.get_radius() 1739 x1 = to_position[0] - a 1740 y1 = to_position[1] + b 1741 x2 = to_position[0] + a 1742 y2 = to_position[1] - b 1743 self.id = (self.canvas.create_oval(x1, y1, x2, y2, fill = lateral_colour, 1744 dash = (2,2), outline = self.colour[2], width = 2), None) 1745 1746 # CEBALERT: as far as I know, this balloon binding never worked. 1747 # self.balloon.tagbind(self.canvas, self.id[0], self.name) 1748 1749 else : # connection between distinct nodes 1750 x1, y1 = to_position 1751 x2, y2 = from_position 1752 # this is for cases when the radius changes size. 1753 radius_x, radius_y = self.get_radius() 1754 self.id = (self.canvas.create_line(x1, y1, x2 - radius_x, y2, fill = col), 1755 self.canvas.create_line(x1, y1, x2 + radius_x, y2, fill = col), 1756 self.canvas.create_oval(x2 - radius_x, y2 - radius_y, 1757 x2 + radius_x, y2 + radius_y, outline = col)) 1758 # draw name label 1759 dX = 20 1760 dY = self.draw_index * 20 1761 self.label = self.canvas.create_text(middle[0] - dX, 1762 middle[1] - dY, fill = text_col, text = self.name, anchor = E)
1763 1764
1765 - def draw_normal(self, from_position, to_position):
1766 # set the colour to be used depending on whether connection has the focus. 1767 if (self.focus) : 1768 text_col = col = self.colour[0] 1769 lateral_colour = self.from_node.colour[0] 1770 else: 1771 text_col = 'black' 1772 col = self.colour[1] 1773 lateral_colour = '' 1774 # midpoint of line 1775 middle = self.get_middle(from_position, to_position) 1776 if (to_position == from_position) : # connection to and from the same node 1777 a, b = self.factor 1778 x1 = to_position[0] - a 1779 y1 = to_position[1] + b 1780 x2 = to_position[0] + a 1781 y2 = to_position[1] - b 1782 self.id = (self.canvas.create_oval(x1, y1, x2, y2, fill = lateral_colour, 1783 dash = (2,2), outline = self.colour[2], width = 2), None) 1784 1785 # self.balloon.tagbind(self.canvas, self.id[0], self.name) 1786 1787 else : # connection between distinct nodes 1788 x1, y1 = to_position 1789 x2, y2 = from_position 1790 x2 += self.deviation 1791 radius = self.normal_radius 1792 self.id = (self.canvas.create_line(x1, y1, x2 - radius, y2, fill = col), 1793 self.canvas.create_line(x1, y1, x2 + radius, y2, fill = col), 1794 self.canvas.create_oval(x2 - radius, y2 - (0.5 * radius), 1795 x2 + radius, y2 + (0.5 * radius), outline = col)) 1796 # draw name label 1797 dX = 20 1798 dY = self.draw_index * 20 1799 self.label = self.canvas.create_text(middle[0] - dX, 1800 middle[1] - dY, fill = text_col, text = self.name, anchor = E)
1801 1802 1803 # Util methods 1804
1805 - def get_radius(self):
1806 factor = self.canvas.scaling_factor 1807 if self.to_node == None: 1808 return (factor * self.normal_radius, factor * self.normal_radius * 1809 self.from_node.height / self.from_node.width) 1810 node = self.from_node 1811 node_bounds = node.simobj.bounds.aarect().lbrt() 1812 1813 try: 1814 bounds = self.simobj.bounds_template.lbrt() 1815 except AttributeError: 1816 return (factor * self.normal_radius, factor * self.normal_radius * 1817 self.from_node.height / self.from_node.width) 1818 radius_x = factor * (node.width / 2) * (bounds[2] - bounds[0]) / (node_bounds[2] - node_bounds[0]) 1819 radius_y = radius_x * node.height / node.width 1820 return radius_x, radius_y
1821 1822 # returns the size of the semimajor and semiminor axis of the ellipse representing 1823 # the draw_index-th lateral projection.
1824 - def get_factor(self):
1825 factor = self.canvas.scaling_factor 1826 w = factor * self.from_node.width; h = factor * self.from_node.height 1827 a = 20; n = (w / 2) - 10; b = (n - a) 1828 major = a + (b * (1 - pow(0.8, self.draw_index))) 1829 a = 20 * h / w; n = (h / 2) - 10; b = (n - a) 1830 minor = a + (b * (1 - pow(0.8, self.draw_index))) 1831 return major, minor
1832
1833 - def update_factor(self):
1834 self.factor = self.get_factor()
1835 1836 # returns the gradients of the two lines making the opening 'v' part of the receptive field. 1837 # this depends on the draw_index, as it determines where the projection's representation begins.
1838 - def calculate_gradient(self):
1839 factor = self.canvas.scaling_factor 1840 if self.view == 'radius': 1841 A = self.to_node.get_pos() 1842 T = self.from_node.get_pos() 1843 B = (T[0] - self.radius[0], T[1]) 1844 C = (T[0] + self.radius[0], T[1]) 1845 else: 1846 A = self.to_node.get_pos() 1847 T = (self.from_node.get_pos()[0] + self.deviation ,self.from_node.get_pos()[1]) 1848 B = (T[0] - (factor * self.normal_radius), T[1]) 1849 C = (T[0] + (factor * self.normal_radius), T[1]) 1850 den_BA = (A[0] - B[0]) 1851 if not(den_BA == 0): 1852 m_BA = (A[1] - B[1]) / den_BA 1853 else : 1854 m_BA = 99999 # AL - this should be a big number 1855 den_CA = (A[0] - C[0]) 1856 if not(den_CA == 0): 1857 m_CA = (A[1] - C[1]) / den_CA 1858 else : 1859 m_CA = 99999 # AL - this should be a big number 1860 return (m_BA, m_CA)
1861
1862 - def in_bounds(self, x, y) : # returns true if point lies in a bounding box
1863 factor = self.canvas.scaling_factor 1864 if self.view == 'line': 1865 return EditorEPConnection.in_bounds(self,x,y) 1866 else: 1867 # returns true if x, y lie inside the oval representing this lateral projection 1868 if (self.to_node == None or self.to_node == self.from_node): 1869 if self.view == 'radius': 1870 a, b = self.get_radius() 1871 else: 1872 a, b = self.factor 1873 x, y = x - self.to_node.get_pos()[0], y - self.to_node.get_pos()[1] 1874 if (x > a or x < -a): 1875 return False 1876 pY = math.sqrt(pow(b,2) * (1 - (pow(x,2)/pow(a,2)))) 1877 if (y > pY or y < -pY): 1878 return False 1879 return True 1880 # returns true if x, y lie inside the triangular receptive 1881 # field representing this projection get the points of the 1882 # triangular receptive field, centered around the x, y 1883 # point given 1884 to_position = self.to_node.get_pos() 1885 from_position = self.from_node.get_pos() 1886 if self.view == 'radius': 1887 A = (to_position[0] - x, to_position[1] - y) 1888 T = (from_position[0] + (self.deviation * factor) - x, from_position[1] - y) 1889 B = (T[0] - self.radius[0], T[1]) 1890 C = (T[0] + self.radius[0], T[1]) 1891 else: 1892 A = (to_position[0] - x, to_position[1] - y) 1893 T = (from_position[0] + (self.deviation * factor) - x, from_position[1] - y) 1894 B = (T[0] - (self.normal_radius * factor), T[1]) 1895 C = (T[0] + (self.normal_radius * factor), T[1]) 1896 # if the y coords lie outwith the boundaries, return false 1897 if (((A[1] < B[1]) and (B[1] < 0 or A[1] > 0)) or 1898 ((A[1] >= B[1]) and (B[1] > 0 or A[1] < 0))): 1899 return False 1900 # calculate the constant for the lines of the triangle 1901 a_BA = A[1] - (self.gradient[0] * A[0]) 1902 a_CA = A[1] - (self.gradient[1] * A[0]) 1903 # The points are centered around the given coord, finding 1904 # the intersects with line y = 0 and ensuring that the 1905 # left line lies on the negative side of the point and the 1906 # right line lies on the positive side of the point 1907 # determines that the point is within the triangle. 1908 if (((0 - a_CA) / self.gradient[1] >= 0) and ((0 - a_BA) / self.gradient[0] <= 0)): 1909 return True 1910 return False
1911