"""
The GUI model editor.
Tools: for the editor menu bar
Objects: that can be manipulated in the canvas
Window: window and canvas
Originally written by Alan Lindsay.
"""
from inspect import getdoc
import math
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
from tkFileDialog import asksaveasfilename
import param
from param import parameterized,normalize_path
import paramtk as tk
from paramtk.tilewrapper import Combobox
import topo
from topo.command.analysis import update_activity
from topo.misc.util import shortclassname
from topo.base.simulation import EventProcessor
# Make sure at least some sheets or projections are available to
# choose from; rest will be available if some other module imports *
# from sheet and projection
from topo.base.sheet import Sheet
from topo.base.projection import Projection
from topo.base.cf import CFProjection
###########################################################################
## WINDOW
###########################################################################
# These can be customized, e.g. in .topographicarc; they should probably be parameters
# somewhere.
canvas_width = 1200
scaling_factor = topo.sim.item_scale
enlarging_factor = 1.25
canvas_region = (0, 0, canvas_width, canvas_width)
"""Size of the canvas, as a bounding box (xl yl xh yh)."""
[docs]class EditorCanvas(Canvas):
"""
EditorCanvas extends the Tk Canvas class.
There are 3 modes that determine the effect of mouse events in the Canvas
A Canvas can accept new objects, move objects and make connections
between them. The intended use of this class is as the main
canvas in a Topographica model-editing GUI.
"""
def __init__(self, root = None, width = 600, height = 600):
Canvas.__init__(self, root, width = width, height = height,bg='white')
# bg = "white", bd = 2, relief = SUNKEN)
self.panel = Frame(root)
self.panel.pack(side = TOP, fill = X)
# Top bar of the canvas, allowing changes in size, display and to force refresh.
Button(self.panel,text="Refresh", command=self.refresh).pack(side=LEFT)
Button(self.panel,text="Reduce", command=self.reduce_scale).pack(side=LEFT)
Button(self.panel,text="Enlarge", command=self.enlarge_scale).pack(side=LEFT)
self.auto_refresh = False
self.console = topo.guimain
self.auto_refresh_checkbutton = Checkbutton(self.panel,text="Auto-refresh",
command=self.toggle_auto_refresh)
self.auto_refresh_checkbutton.pack(side=LEFT)
self.normalize_checkbutton = Checkbutton(self.panel, text="Normalize",
command=self.toggle_normalize)
self.normalize_checkbutton.pack(side=LEFT)
if EditorSheet.normalize == True:
self.normalize_checkbutton.select()
self.node_labels_checkbutton = Checkbutton(self.panel, text="Node labels",
command=self.toggle_node_labels)
self.node_labels_checkbutton.pack(side=LEFT)
if EditorNode.show_label == True:
self.node_labels_checkbutton.select()
self.connection_labels_checkbutton = Checkbutton(self.panel, text="Connection labels",
command=self.toggle_connection_labels)
self.connection_labels_checkbutton.pack(side=LEFT)
if EditorConnection.show_label == True:
self.connection_labels_checkbutton.select()
# retain the current focus in the canvas
self.scaling_factor = topo.sim.item_scale#scaling_factor
self.current_object = None
self.current_connection = None
self.focus = None
# list holding references to all the objects in the canvas
self.object_list = []
# set the initial mode.
self.display_mode = 'video'
self.mode = "ARROW"
self.MAX_VIEWS = 5
# get the topo simulation
self.simulation = topo.sim
# create the menu widget used as a popup on objects and connections
self.option_add("*Menu.tearOff", "0")
self.item_menu = Menu(self)
self.view = Menu(self.item_menu)
# add property, toggle activity drawn on sheets, object draw ordering and delete entries to the menu.
self.item_menu.insert_command(END, label = 'Properties',
command = lambda: self.show_properties(self.focus))
self.item_menu.add_cascade(label = 'Change View', menu = self.view, underline = 0)
self.item_menu.insert_command(END, label = 'Move Forward',
command = lambda: self.move_forward(self.focus))
self.item_menu.insert_command(END, label = 'Move to Front',
command = lambda: self.move_to_front(self.focus))
self.item_menu.insert_command(END, label = 'Move to Back',
command = lambda: self.move_to_back(self.focus))
self.item_menu.insert_command(END, label = 'Delete',
command = lambda: self.delete_focus(self.focus))
# the indexes of the menu items that are for objects only
self.object_indices = [2,3,4]
self.canvas_menu = Menu(self)
self.sheet_options = Menu(self.canvas_menu)
mode_options = Menu(self.canvas_menu)
self.canvas_menu.add_command(label = 'Export as PostScript image', command = self.save_snapshot)
self.canvas_menu.add_cascade(label = 'Select Mode', menu = mode_options)
mode_options.add_command(label = 'Video', command = lambda: self.set_display_mode('video'))
mode_options.add_command(label = 'Normal', command = lambda: self.set_display_mode('normal'))
mode_options.add_command(label = 'Printing', command = lambda:
self.set_display_mode('printing'))
self.canvas_menu.add_cascade(label = 'Sheet options', menu = self.sheet_options, underline = 0)
self.sheet_options.add_command(label = 'Toggle Density Grid', command =
self.toggle_object_density)
self.sheet_options.add_command(label = 'Toggle Activity', command = self.toggle_object_activity)
# bind key_press events in canvas.
self.bind('<KeyPress>', self.key_press)
# bind the possible left button events to the canvas.
self.bind('<Button-1>', self.left_click)
self.bind('<B1-Motion>', self.left_click_drag)
self.bind('<Double-1>', self.left_double_click)
self.bind('<ButtonRelease-1>', self.left_release)
# bind the possible right button events to the canvas.
self.bind('<<right-click>>', self.right_click)
# because right-click opens menu, a release event can only be flagged by the menu.
self.item_menu.bind('<<right-click-release>>', self.right_release)
# add scroll bar; horizontal and vertical
self.config(scrollregion = canvas_region)
vertical_scrollbar = Scrollbar(root)
horizontal_scrollbar = Scrollbar(root, orient = 'horizontal')
vertical_scrollbar.config(command = self.yview)
horizontal_scrollbar.config(command = self.xview)
self.config(yscrollcommand=vertical_scrollbar.set)
self.config(xscrollcommand=horizontal_scrollbar.set)
vertical_scrollbar.pack(side = RIGHT, fill = Y)
horizontal_scrollbar.pack(side = BOTTOM, fill = X)
[docs] def key_press(self, event):
"What happens when a key is pressed."
self.change_mode(event.char)
# Left mouse button event handlers
[docs] def left_click(self, event):
"What is to happen if the left button is pressed."
x,y = self.canvasx(event.x), self.canvasy(event.y)
{"ARROW" : self.init_move, # case Arrow mode
"MAKE" : self.none, # case Make mode
"CONNECTION" : self.init_connection # case Connection mode.
}[self.mode](x,y) # select function depending on mode
[docs] def left_click_drag(self, event):
"What is to happen if the mouse is dragged while the left button is pressed."
x,y = self.canvasx(event.x), self.canvasy(event.y)
{"ARROW" : self.update_move, # case Arrow mode
"MAKE" : self.none, # case Make mode
"CONNECTION" : self.update_connection # case Connection mode.
}[self.mode](x,y) # select function depending on mode
[docs] def left_release(self, event):
"What is to happen when the left mouse button is released."
x,y = self.canvasx(event.x), self.canvasy(event.y)
{"ARROW" : self.end_move, # case Arrow mode
"MAKE" : self.create_object, # case Make mode
"CONNECTION" : self.end_connection # case Connection mode.
}[self.mode](x,y) # select function depending on mode
[docs] def left_double_click(self, event):
"""
What is to happen if the left button is double clicked.
The same for all modes - show the properties for the clicked item.
Gets object or connection at this point and gives it the focus.
"""
focus = self.get_xy(event.x, event.y)
if (focus != None):
focus.set_focus(True)
# show the object or connection's properties.
self.show_properties(focus)
# Right mouse button event handlers
[docs] def right_click(self, event):
"What is to happen if the right button is pressed."
self.show_hang_list(event)
[docs] def right_release(self, event):
"What is to happen when the right mouse button is released (bound to the menu)."
if (self.focus != None) : # remove focus.
self.focus.set_focus(False)
# Mode Methods
[docs] def change_mode(self, char):
"Changes the mode of the canvas, i.e., what mouse events will do."
if not char in ('c', 'm', 'a') : return
# remove the focus from the previous toolbar item
{"ARROW" : self.arrow_tool.set_focus, # arrow toolbar item
"MAKE" : self.object_tool.set_focus, # object toolbar item
"CONNECTION" : self.connection_tool.set_focus # connection toolbar item
}[self.mode](False) # select function depending on mode
# determine the new mode and corresponding toolbar item.
if (char == 'c'):
mode = "CONNECTION"
bar = self.connection_tool
elif (char == 'm'):
mode = "MAKE"
bar = self.object_tool
elif (char == 'a'):
mode = "ARROW"
bar = self.arrow_tool
# set the focus of the toolbar item of the new mode and retain the note the new mode.
bar.set_focus(True)
self.mode = mode
# Panel methods
def refresh(self):
for obj in self.object_list:
obj.set_focus(True)
obj.set_focus(False)
for obj in self.object_list:
connection_list = obj.from_connections[:]
connection_list.reverse()
for con in connection_list:
con.move()
def enlarge_scale(self):
self.scaling_factor *= enlarging_factor
self.refresh()
def reduce_scale(self):
self.scaling_factor /= enlarging_factor
self.refresh()
def toggle_auto_refresh(self):
self.auto_refresh = not self.auto_refresh
if self.auto_refresh:
self.console.auto_refresh_panels.append(self)
else:
self.console.auto_refresh_panels.remove(self)
def toggle_normalize(self):
EditorSheet.normalize = not EditorSheet.normalize
self.refresh()
def toggle_node_labels(self):
EditorNode.show_label = not EditorNode.show_label
self.refresh()
def toggle_connection_labels(self):
EditorConnection.show_label = not EditorConnection.show_label
self.refresh()
# Object moving methods
#
# If an object is left clicked in the canvas, these methods allow
# it to be repositioned in the canvas.
[docs] def init_move(self, x, y):
"Determine if click was on an object."
self.current_object = self.get_object_xy(x, y)
if (self.current_object != None) :
# if it was, give it the focus
self.current_object.set_focus(True)
[docs] def update_move(self, x, y):
"If dragging an object, refresh its position"
if (self.current_object != None):
self.current_object.move(x, y)
[docs] def end_move(self, x, y):
"If dropping an object, remove focus and refresh."
if (self.current_object != None):
self.current_object.set_focus(False)
self.current_object.move(x, y)
# redraw all the objects in the canavas and dereference
self.redraw_objects()
self.current_object = None
# Connection methods
#
# these methods allow a connection to be made between two objects in the canvas
[docs] def init_connection(self, x, y):
"Determine if click was on an object, and retain if so."
current_object = self.get_object_xy(x, y)
if (current_object == None) : # if not change to ARROW mode
self.change_mode('a')
else : # if on an object, create a connection and give it the focus
self.current_connection = self.connection_tool.new_cover(current_object)
self.current_connection.set_focus(True)
[docs] def update_connection(self, x, y):
"Update connection's position."
self.current_connection.update_position((x, y))
[docs] def end_connection(self, x, y):
"Determine if the connection has been dropped on an object."
obj = self.get_object_xy(x, y)
if (obj != None) : # if an object, connect the objects and remove focus
if (self.current_connection != None):
connected = self.connection_tool.create_connection(self.current_connection, obj)
if connected:
self.current_connection.set_focus(False)
else : # if not an object, remove the connection
connected=False
if (self.current_connection != None):
self.current_connection.remove()
if connected:
self.redraw_objects()
# dereference
self.current_connection = None
[docs] def get_connection_xy(self, x, y):
"Return connection at given x, y (None if no connection)."
for obj in self.object_list:
connection_list = obj.from_connections[:]
connection_list.reverse()
for con in connection_list:
if (con.in_bounds(x, y)):
return con
return None
# Object Methods
[docs] def create_object(self, x, y) :
"Create a new object."
self.add_object(self.object_tool.create_node(x, y))
[docs] def add_object(self, obj) :
"Add a new object to the Canvas."
self.object_list = [obj] + self.object_list
[docs] def add_object_to_back(self, obj) :
"Add a new object to the Canvas at back of the list."
self.object_list = self.object_list + [obj]
[docs] def remove_object(self, obj) :
"Remove an object from the canvas."
for i in range(len(self.object_list)) :
if (obj == self.object_list[i]) : break
else : return # object was not found
del self.object_list[i]
return i
def toggle_object_density(self):
if EditorSheet.show_density:
EditorSheet.show_density = False
else:
EditorSheet.show_density = True
self.refresh()
def toggle_object_activity(self):
if EditorSheet.view == 'activity':
EditorSheet.view = 'normal'
else:
EditorSheet.view = 'activity'
self.refresh()
[docs] def get_object_xy(self, x, y) :
"Return object at given x, y (or None if no object)."
# search through the bounds of each object in the canvas.
# returns the first (nearest to front) object, None if no object at x,y
for obj in self.object_list:
if (obj.in_bounds(x, y)):
break
else : return None
return obj
[docs] def show_properties(self, focus):
"Show properties of an object or connection, and remove the focus."
if (focus != None):
focus.show_properties()
focus.set_focus(False)
[docs] def delete_focus(self, focus):
"Tell a connection or object to delete itself."
if (focus == None) :
pass
else:
focus.remove()
self.redraw_objects()
# Object Order Methods
#
# These methods ensure the ordering in the canvas window is held
# and allows manipulation of the order.
[docs] def redraw_objects(self, index = None):
"""
Redraw all the objects in the canvas.
If non-None, the index specifies that only the objects below
that index need drawing.
"""
if (index == None or index < 0) : index = len(self.object_list)
for i in range(index ,0, -1):
self.object_list[i-1].draw()
def move_to_front(self, obj):
index = self.remove_object(obj)
self.add_object(obj)
self.redraw_objects(index)
def move_forward(self, obj):
for i in range(len(self.object_list)) : # find object index in list
if (obj == self.object_list[i]) : break
else : return # object was not found
# swap this object for the one higher in the canvas and redraw
a = self.object_list[(i-1) : (i+1)]
a.reverse()
self.object_list[(i-1):(i+1)] = a
self.redraw_objects(i+1)
def move_to_back(self, obj):
self.remove_object(obj)
self.add_object_to_back(obj)
self.redraw_objects()
# Hang List Methods
#
# If there is an object or connection at the right clicked point,
# a popup menu is displayed, allowing for modifications to the
# particular obj/con.
def show_hang_list(self, event):
# change to ARROW mode and get x, y mouse coords
self.change_mode('a')
x, y = self.canvasx(event.x), self.canvasy(event.y)
# get connection at this point
focus = self.get_connection_xy(x, y)
for i in range(self.MAX_VIEWS) : # max number of views
self.view.delete(END)
if (focus == None):
# if no connection, checks bounds of objects
focus = self.get_object_xy(x, y)
# fill in menu items that are just for objects
for i in self.object_indices:
self.item_menu.entryconfig(i, foreground = 'Black', activeforeground = 'Black')
else:
# gray out menu items that are just for objects
for i in self.object_indices:
self.item_menu.entryconfig(i,foreground = 'Gray', activeforeground = 'Gray')
if (focus != None):
for (label, function) in focus.viewing_choices:
self.view.add_command(label = label, command = function)
# give the connection or object the focus
focus.set_focus(True)
self.focus = focus
# create the popup menu at current mouse coord
self.item_menu.tk_popup(event.x_root, event.y_root)
else:
self.canvas_menu.tk_popup(event.x_root, event.y_root)
# Utility methods
def save_snapshot(self):
POSTSCRIPT_FILETYPES = [('Encapsulated PostScript images','*.eps'),
('PostScript images','*.ps'),('All files','*')]
snapshot_name = asksaveasfilename(filetypes=POSTSCRIPT_FILETYPES,
initialdir=normalize_path(),
initialfile=topo.sim.basename()+".ps")
if snapshot_name:
self.postscript(file=snapshot_name)
def set_display_mode(self, mode):
self.display_mode = mode
for obj in self.object_list:
obj.set_mode(mode)
def set_tool_bars(self, arrow_tool, connection_tool, object_tool):
# reference to the toolbar items, a tool is notified when the canvas is changed
# to the mode corresponding to it.
self.arrow_tool = arrow_tool
# connection tool supplies 'connection' objects that can draw themselves in the canvas
self.connection_tool = connection_tool
# object tool supplies 'object' objects that can draw themselves in the canvas
self.object_tool = object_tool
# initialise mode
self.change_mode('a')
# does nothing
def none(self, x, y) : pass
[docs] def get_xy(self, x, y):
"Returns the connection or object at this x, y position or None if there is not one."
# check for a connection
focus = self.get_connection_xy(x, y)
if (focus == None):
# if no connection, check bounds of objects
focus = self.get_object_xy(x, y)
return focus # return the first found or None
# JABALERT: I made this into parameterized.Parameterized to make self.warning work,
# but it should be changed to a PlotGroupPanel eventually
[docs]class ModelEditor(parameterized.Parameterized):
"""
This class constructs the main editor window. It uses a instance
of GUICanvas as the main editing canvas and inserts the
three-option toolbar in a Frame along the left side of the window.
"""
def __init__(self,master,**params):
parameterized.Parameterized.__init__(self,**params)
# create editor window and set title
root = tk.AppWindow(master)
root.title("Model Editor")
canvas_frame = Frame(root,bg = 'white')
canvas_frame.pack(side='right',fill = BOTH, expand = YES)
toolbar_frame = Frame(root, bg = 'light grey', bd = 2)
toolbar_frame.pack(side=LEFT,fill=Y)
self.canvas = EditorCanvas(canvas_frame)
self.canvas.pack(fill = BOTH, expand = YES)
# object/node = sheet
parameters_tool = ParametersTool(toolbar_frame)
arrow_tool=ArrowTool(self.canvas,toolbar_frame,parameters_tool)
object_tool=NodeTool(self.canvas,toolbar_frame,parameters_tool)
connection_tool=ConnectionTool(self.canvas,toolbar_frame,parameters_tool)
arrow_tool.pack(side='top')
object_tool.pack(side='top')
connection_tool.pack(side='top')
parameters_tool.pack(side='top')
# give the canvas a reference to the toolbars
self.canvas.set_tool_bars(arrow_tool, connection_tool, object_tool)
# give the canvas focus and import any objects and connections already in the simulation
self.canvas.focus_set()
# Grid layout defaults
self.xstart = 100
self.ystart = 100
self.next_x = self.xstart+100
self.next_y = self.ystart
self.xstep = 150
self.ystep = 150
self.import_model()
def import_model(self):
# get a list of all the objects in the simulation
sim = self.canvas.simulation
node_dictionary = sim.objects(EventProcessor)
node_list = node_dictionary.values()
# create the editor covers for the nodes
for node in node_list:
# if the sheet has x,y coords, use them
if (hasattr(node,'layout_location') and node.layout_location!=(-1,-1)):
x, y = node.layout_location
# if not generate new coords on a grid layout
# Could use dot/graphviz to place the objects nicely
else:
x, y = self.next_x , self.next_y
self.next_y += self.ystep
if self.next_y > canvas_region[3]:
self.next_y = self.ystart
self.next_x += self.xstep
if self.next_x > canvas_region[2]:
self.next_x = self.xstart + 15
self.next_y = self.ystart + 15
# Could handle more EventProcessor subclasses here
if isinstance(node,Sheet):
editor_node = EditorSheet(self.canvas, node, (x, y), node.name)
else:
editor_node = EditorEP(self.canvas, node, (x, y), node.name)
node.layout_location=(x,y)
self.canvas.add_object(editor_node)
# create the editor covers for the connections
for editor_node in self.canvas.object_list:
for con in editor_node.simobj.out_connections:
# Could handle more EditorConnection subclasses here
if isinstance(con,CFProjection):
editor_connection = EditorProjection("", self.canvas, editor_node)
else:
editor_connection = EditorEPConnection("", self.canvas, editor_node)
# find the EditorNode that the proj connects to
for dest in self.canvas.object_list:
if (dest.simobj == con.dest):
# connect the connection to the destination node
editor_connection.connect(dest, con)
break
else:
self.warning("The model editor cannot draw connection", con.name,
"because", con.dest.name, "is not drawn in the editor.")
self.canvas.redraw_objects()
###########################################################################
## TOOLS
###########################################################################
def names_sorted_by_precedence(classes):
classes.sort(lambda x, y: cmp(-x.precedence,-y.precedence))
return [class_.__name__ for class_ in classes]
class ParametersTool(Frame):
def __init__(self, parent = None):
# CEBALERT: need to tidy up the title, positioning, etc.
Frame.__init__(self, parent)
self.focus = None
# label
self.title_label = Label(self)
self.title_label.pack(side = TOP)
self.doc_label = Label(self, font = ("Times", 12))
self.doc_label.pack(side = TOP)
# CEBALERT: will the users think they have to press 'apply' rather than just clicking
# on the canvas to get the new object?
self.parameter_frame = tk.ParametersFrameWithApply(self)#,buttons_to_remove=['Close','Defaults'])
self.parameter_frame.hide_param('Close')
self.parameter_frame.hide_param('Defaults')
self.parameter_frame.pack(side=BOTTOM)
def update_parameters(self):
self.parameter_frame.update_parameters()
def set_focus(self, name, focus_class, doc = ''):
self.focus = name
self.title_label.config(text = name)
self.doc_label.config(text = doc)
if focus_class:
self.parameter_frame.set_PO(focus_class)
###########################################################################
## OBJECTS
###########################################################################
# CEBALERT: should be a Parameterized
[docs]class EditorObject(object):
"""
Anything that can be added and manipulated in an EditorCanvas. Every EditorCanvas
has a corresponding Topo object associated with it. An instance of this class can
have the focus.
"""
FROM = 0
TO = 1
def __init__(self, name, canvas,**params):
self.canvas = canvas # retains a reference to the canvas
self.name = name # set the name of the sheet
self.focus = False # this does not have the focus
self.viewing_choices = []
self.label=None
[docs] def draw(self):
"Draw the object at the current x, y position."
pass
[docs] def objdoc(self):
"""Documentation string for this object."""
### JABALERT: Should be expanded to allow a per-object description,
### and should be bound to the actual editor object as well.
return self.name + " is of type " + \
shortclassname(self.simobj) + \
":\n\n" + str(getdoc(self.simobj))
[docs] def show_properties(self):
"Show parameters frame for object."
parameter_window = tk.AppWindow(topo.guimain,status=True)
#status=tk.StatusBar(parameter_window)
#status.pack(side="bottom",fill='x',expand='yes')
parameter_window.title(self.name)
balloon = tk.Balloon(parameter_window)
# import __main__;__main__.__dict__['AAA'] = parameter_window
title = Label(parameter_window, text = self.name)
title.pack(side = TOP)
self.parameter_frame = tk.ParametersFrameWithApply(
parameter_window,
msg_handler=parameter_window.status)
parameter_window.sizeright()
balloon.bind(title,self.objdoc())
self.parameter_window = parameter_window
def update_parameters(self):
self.parameter_frame.update_parameters()
def okay_parameters(self, parameter_window):
self.update_parameters()
parameter_window.destroy()
def set_focus(self, focus) : # set focus
self.focus = focus
[docs] def move(self):
"Update position of object and redraw."
pass
[docs] def remove(self):
"Remove this object from the canvas and from the Topographica simulation."
pass
[docs] def in_bounds(self, x, y) :
"Return true if x,y lies within this gui object's boundary."
pass
[docs]class EditorNode(EditorObject):
"""
An EditorNode is used to cover any topographica node, presently this can only be a sheet.
It is a sub class of EditorObject and supplies the methods required by any node to be used
in a EditorCanvas. Extending classes will supply a draw method and other type specific
attributes.
"""
show_label= param.Boolean(default=True,
doc="Whether to show a textual label for this object")
def __init__(self, canvas, simobj, pos, name):
EditorObject.__init__(self, name, canvas)
self.from_connections = [] # connections from this node
self.to_connections = [] # connections to this node
self.x = pos[0] # set the x and y coords of the center of this node
self.y = pos[1]
self.mode = canvas.display_mode
self.simobj = simobj
# Connection methods
def attach_connection(self, con, from_to):
if (from_to == self.FROM):
if (con.from_node == con.to_node):
self.from_connections = [con] + self.from_connections
else:
self.from_connections = self.from_connections + [con]
else:
if (con.from_node == con.to_node):
self.to_connections = [con] + self.to_connections
else:
self.to_connections = self.to_connections + [con]
def remove_connection(self, con, from_to) : # remove a connection to or from this node
if (from_to):
l = len(self.to_connections)
for i in range(l):
if (con == self.to_connections[i]) : break
else : return
del self.to_connections[i]
else:
l = len(self.from_connections)
for i in range(l):
if (con == self.from_connections[i]) : break
else : return
del self.from_connections[i]
# Util methods
def get_pos(self):
return (self.x, self.y) # return center point of node
def show_properties(self):
EditorObject.show_properties(self)
self.parameter_frame.set_PO(self.simobj)
Label(self.parameter_window, text = '\n\nConnections').pack(side = TOP)
connections = list(set(self.to_connections).union(set(self.from_connections)))
connection_list = [con.name for con in connections]
self.connection_var = StringVar()
self.connection_var.set(connection_list[0])
connection_menu = OptionMenu(self.parameter_window,self.connection_var,*connection_list)
self.connection_var.trace_variable('w',self.view_connection_parameters)
connection_menu.pack(side = TOP)
def view_connection_parameters(self, *args):
con_sel = self.connection_var.get()
for con in self.to_connections + self.from_connections:
if con.name == con_sel:
break
else :
return
con.show_properties()
# JABALERT: Should probably combine this with EditorNode
[docs]class EditorEP(EditorNode):
"""
Represents any topo EventProcessor as a small, fixed-size oval by default.
"""
def __init__(self, canvas, simobj, pos, name):
EditorNode.__init__(self, canvas, simobj, pos, name)
simobj.layout_location = (self.x,self.y) # store the ed coords in the topo sheet
self.set_bounds()
self.set_colours()
col = self.colour[1]
self.init_draw(col, False) # create a new parallelogram
self.currentCol = col
self.gradient = 1
# Draw methods
def set_focus(self, focus):
for id in self.id:
self.canvas.delete(id)
self.canvas.delete(self.label) # remove label
EditorNode.set_focus(self, focus) # call to super's set focus
col = self.colour[not focus]
self.init_draw(col, focus) # create new one with correct colour
self.currentCol = col
self.draw()
def select_view(self, view_choice):
self.view = view_choice
self.set_focus(False)
self.canvas.redraw_objects()
def set_colours(self):
colour = {'video':('dark red','black'),
'normal':('slate blue', 'lavender'),
'printing':('grey','white')}
self.colour = colour[self.mode] # colours for drawing this node on the canvas
def init_draw(self, colour, focus):
if focus : label_colour = colour
else : label_colour = 'black'
h, w = 0.5*self.height, 0.5*self.width
x, y = self.x, self.y
x1,y1 = (x-w,y-h)
x2,y2 = (x+w,y+h)
self.id = [self.canvas.create_oval(x1,y1,x2,y2,fill=colour,outline="black")]
dX = w + 5
if (self.show_label):
self.label = self.canvas.create_text(x - dX, y, anchor = E, fill = label_colour, text = self.name)
def dec_to_hex_str(self, val, length):
# expects a normalised value and maps it to a hex value of the given length
max_val = pow(16, length) - 1
fmt = '%%0%dx'%length
return fmt % (val*max_val)
def draw(self, x = 0, y = 0):
# move the parallelogram and label by the given x, y coords (default redraw)
if not(x == y == 0):
for id in self.id:
self.canvas.move(id, x, y)
if (self.show_label):
self.canvas.move(self.label, x, y)
for id in self.id:
self.canvas.tag_raise(id)
if (self.show_label):
self.canvas.tag_raise(self.label)
# redraw the connections
for con in self.to_connections :
if (con.from_node == con.to_node):
con.move()
for con in self.to_connections:
if (not(con.from_node == con.to_node)):
con.move()
for con in self.from_connections:
if (not(con.from_node == con.to_node)):
con.move()
# Update methods
def remove(self):
l = len(self.from_connections) # remove all the connections from and to this sheet
for index in range(l):
self.from_connections[0].remove()
l = len(self.to_connections)
for index in range(l):
self.to_connections[0].remove()
for id in self.id:
self.canvas.delete(id)
self.canvas.delete(self.label)
self.canvas.remove_object(self) # remove from canvas' object list
del topo.sim[self.simobj.name] # actually delete the sheet
def move(self, x, y):
# the connections position is updated
old = self.x, self.y
self.x = x
self.y = y
self.simobj.layout_location = (self.x,self.y) # update topo sheet position
self.draw(self.x - old[0], self.y - old[1])
# Connection methods
def remove_connection(self, con, from_to):
EditorNode.remove_connection(self, con, from_to)
if from_to:
node = con.from_node
else:
node = con.to_node
index = con.draw_index
# Decrease the indexes of the connections between the same nodes and with a higher index.
for connection in self.from_connections:
if (node == connection.to_node and connection.draw_index >= index):
connection.decrement_draw_index()
if connection.to_node == connection.from_node : return
for connection in self.to_connections :
if (node == connection.from_node and connection.draw_index >= index):
connection.decrement_draw_index()
def get_connection_count(self, node):
count = 0
for con in self.from_connections:
if (con.to_node == node):
count += 1
for con in self.to_connections:
if (con.from_node == node):
count += 1
if node == self : count /= 2
return count
# Util methods
def in_bounds(self, pos_x, pos_y):
return ((pos_x> self.x-self.width) and (pos_x<= self.x+self.width) and
(pos_y> self.y-self.height) and (pos_y<= self.y+self.height))
def set_bounds(self):
# Default representation: fixed-size node
self.width=15
self.height=self.width*0.7
def set_mode(self, mode):
self.mode = mode
self.set_colours()
for id in self.id:
self.canvas.delete(id)
self.canvas.delete(self.label) # remove label
self.init_draw(self.colour[not self.focus], self.focus)
for con in self.to_connections:
con.set_mode(mode)
for con in self.from_connections:
con.draw()
[docs]class EditorSheet(EditorEP):
"""
Represents any topo sheet. It is a subclass of EditorEP and fills in the
methods that are not defined. It is represented by a Parallelogram in its
Canvas. The colours used for drawing can be set. Uses bounding box to
determine if x, y coord is within its boundary.
"""
normalize = param.Boolean(default=False)
show_density = param.Boolean(default=False)
view = param.ObjectSelector(default='activity',objects=['normal','activity'])
def __init__(self, canvas, simobj, pos, name):
# Should call EditorEP's constructor instead
EditorNode.__init__(self, canvas, simobj, pos, name)
simobj.layout_location = (self.x,self.y) # store the ed coords in the topo sheet
self.element_count = self.matrix_element_count()
self.set_bounds()
self.set_colours()
col = self.colour[1]
self.init_draw(col, False) # create a new parallelogram
self.currentCol = col
self.gradient = 1
self.viewing_choices = [('Normal', lambda: self.select_view('normal')),
('Activity', lambda: self.select_view('activity'))]
# Draw methods
def set_focus(self, focus):
for id in self.id:
self.canvas.delete(id)
self.canvas.delete(self.label) # remove label
EditorNode.set_focus(self, focus) # call to super's set focus
col = self.colour[not focus]
self.init_draw(col, focus) # create new one with correct colour
self.currentCol = col
self.draw()
def init_draw(self, colour, focus):
self.id = []
if focus : label_colour = colour
else : label_colour = 'black'
factor = self.canvas.scaling_factor
h, w = 0.5 * self.height * factor, 0.5 * self.width *factor
if not(self.focus):
if self.view == 'activity':
colour = ''
x, y = self.x - w + h, self.y - h
# AL, the idea will be to allow any available plots to be shown on the sheet.
# eg m = self.simobj.sheet_views['OrientationPreference'].view()[0]
update_activity()
m = self.simobj.views.Maps['ActivityBuffer'].last.data
if self.normalize == True:
m = self.normalize_plot(m)
matrix_width, matrix_height = self.element_count
dX, dY = (w * 2)/ matrix_width, (h * 2) / matrix_height
for i in range(matrix_height):
for j in range(matrix_width):
a = i * dY
x1, y1 = x - a + (j * dX), y + a
x2, y2 = x1 - dY, y1 + dY
x3, x4 = x2 + dX, x1 + dX
point = m[i,j]
if point < 0 : point = 0.0
if point > 1 : point = 1.0
col = '#' + (self.dec_to_hex_str(point, 3)) * 3
self.id = self.id + [self.canvas.create_polygon
(x1, y1, x2, y2, x3, y2, x4, y1, fill = col, outline = col)]
x, y = self.x, self.y
x1,y1 = (x - w - h, y + h)
x2,y2 = (x - w + h, y - h)
x3,y3 = x2 + (w * 2), y2
x4,y4 = x1 + (w * 2), y1
self.id = self.id + [self.canvas.create_polygon(x1, y1, x2, y2, x3, y3, x4, y4,
fill = colour , outline = "black")]
dX = w + 5
if (self.show_label):
self.label = self.canvas.create_text(x - dX, y, anchor = E, fill = label_colour, text = self.name)
# adds a density grid over the sheet
if self.show_density:
x, y = self.x - w + h, self.y - h
matrix_width, matrix_height = self.element_count
dX, dY = (w * 2)/ matrix_width, (h * 2) / matrix_height
for i in range(matrix_height + 1):
x1 = x - (i * dY)
x2 = x1 + (w * 2)
y1 = y + (i * dY)
self.id = self.id + [self.canvas.create_line(x1, y1, x2, y1, fill = 'slate blue')]
for j in range(matrix_width + 1):
x1 = x + (j * dX)
x2 = x1 - (h * 2)
y1 = y
y2 = y1 + (h * 2)
self.id = self.id + [self.canvas.create_line(x1, y1, x2, y2, fill = 'slate blue')]
[docs] def normalize_plot(self,a):
"""
Normalize an array s.
In case of a constant array, ones is returned for value greater than zero,
and zeros in case of value inferior or equal to zero.
"""
# AL is it possible to use the normalize method in plot?
import numpy as np
a_offset = a-min(a.ravel())
max_a_offset = max(a_offset.ravel())
if max_a_offset>0:
a = np.divide(a_offset,float(max_a_offset))
else:
if min(a.ravel())<=0:
a=np.zeros(a.shape, dtype=np.float)
else:
a=np.ones(a.shape, dtype=np.float)
return a
# Util methods
def in_bounds(self, pos_x, pos_y) : # returns true if point lies in a bounding box
# if the coord is pressed within the parallelogram representation this
# returns true.
# Get the parallelogram points and centers them around the given point
x = self.x - pos_x; y = self.y - pos_y
w = 0.5 * self.width * self.canvas.scaling_factor
h = 0.5 * self.height * self.canvas.scaling_factor
A = (x - w - h, y + h)
B = (x - w + h, y - h)
C = B[0] + (2 * w), B[1]
D = A[0] + (2 * w), A[1]
# calculate the line constants
# As the gradient of the lines is 1 the calculation is simple.
a_AB = A[1] + A[0]
a_CD = C[1] + C[0]
# The points are centered around the given coord, finding the
# intersects with line y = 0 and ensuring that the left line
# lies on the negative side of the point and the right line
# lies on the positive side of the point determines that the
# point is within the parallelogram.
if ((D[1] >= 0) and (B[1] <= 0) and (a_AB <= 0) and (a_CD >= 0)):
return True
return False
def matrix_element_count(self):
# returns the length and width of the matrix that holds this sheet's plot values
l,b,r,t = self.simobj.bounds.aarect().lbrt()
density = self.simobj.xdensity
return int(density * (r - l)), int(density * (t - b))
def set_bounds(self):
# Use the default sheet bounds as to set the "normal" size
# of SheetObject in the GUI, so simulations using very large
# sheets still look normal.
dl,db,dr,dt = self.simobj.__class__.nominal_bounds.aarect().lbrt()
width_fact = 120.0 / (dr - dl)
height_fact = 60.0 / (dt - db)
l,b,r,t = self.simobj.bounds.aarect().lbrt()
self.width = width_fact * (r - l) * self.canvas.scaling_factor
self.height = height_fact * (t - b) * self.canvas.scaling_factor
[docs]class EditorConnection(EditorObject):
"""
A connection formed between 2 EditorNodes on a EditorCanvas. A EditorConnection is used
to cover any topographica connection (connection / projection), and extending
classes will supply a draw method and other type specific attributes.
"""
show_label= param.Boolean(default=True,
doc="Whether to show a textual label for this object")
def __init__(self, name, canvas, from_node):
EditorObject.__init__(self, name, canvas)
self.from_node = from_node # initial node selected
self.to_node = None # updated when the user selects the second node - self.connect(..)
# temporary point, for when the to connection node is undefined
self.to_position = from_node.get_pos()
self.mode = canvas.display_mode
# Draw methods
def set_focus(self, focus) : # give this connection the focus
EditorObject.set_focus(self, focus)
self.draw()
# Update methods
def move(self):
# if one of the nodes connected by this connection move, then move by redrawing
self.draw()
def update_position(self, pos) : # update the temporary point
self.to_position = pos
self.draw()
def connect(self, to_node, con) : # pass the node this connection is to
self.simobj = con
if (self.name == ""):
self.name = con.name
self.to_node = to_node # store a reference to the node this is connected to
self.to_position = None
self.from_node.attach_connection(self, self.FROM) # tell the sheets that they are connected.
self.to_node.attach_connection(self, self.TO)
def remove(self):
# CEBALERT: there's no code here to handle GUI object
# removal (though the EditorProjection subclass does have GUI
# removal code, so presumably projections do get removed from
# the screen. But I'm confused about what is treated as a
# connection, and what as a projection in the model editor).
if hasattr(self,'simobj'):
self.simobj.remove()
# Util methods
def show_properties(self):
EditorObject.show_properties(self)
self.parameter_frame.set_PO(self.simobj)
# JABALERT: Should probably combine this with EditorConnection
[docs]class EditorEPConnection(EditorConnection):
"""
Represents any topo EPConnection using a line with an arrow head in the middle.
"""
def __init__(self, name, canvas, from_node):
EditorConnection.__init__(self, name, canvas, from_node)
# if more than one connection between nodes,
# this will reflect how to draw this connection
self.draw_index = 0
self.deviation = 0
self.gradient = (1,1)
self.id = (None,None)
self.balloon = tk.Balloon(canvas)
self.set_colours()
self.view = 'line'
self.draw_fn = self.draw_line
self.viewing_choices = [('Line', lambda: self.select_view('line'))]
self.update_factor()
# Draw methods
def select_view(self, view_choice):
self.view = view_choice
self.move()
self.set_focus(False)
def set_colours(self):
colours = {'video' : ('dark red', 'blue', 'yellow'),
'normal': ('dark red', 'blue', 'yellow'),
'printing': ('grey', 'black', 'black')}
self.colour = colours[self.mode]
def draw(self):
# determine if connected to a second node, and find the correct from_position
for id in self.id : # remove the old connection
self.canvas.delete(id)
self.canvas.delete(self.label)
from_position = self.from_node.get_pos() # get the center points of the two nodes
if (self.to_node == None) : # if not connected yet, use temporary point.
to_position = self.to_position
else:
to_position = self.to_node.get_pos()
self.draw_fn(from_position, to_position)
def draw_line(self,from_position, to_position):
# set the colour to be used depending on whether connection has the focus.
if (self.focus) :
text_col = col = self.colour[0]
else:
text_col = 'black'
col = self.colour[1]
middle = self.get_middle(from_position, to_position)
factor = self.canvas.scaling_factor
if (to_position == from_position) : # connection to and from the same node
deviation = self.draw_index * 15 * factor
x1 = to_position[0] - ((20 * factor) + deviation)
y2 = to_position[1]
x2 = x1 + (40 * factor) + (2 * deviation)
y1 = y2 - ((30 * factor) + deviation)
midX = self.get_middle((x1,0),(x2,0))[0]
# create oval and an arrow head.
self.id = (self.canvas.create_oval(x1, y1, x2, y2, outline = col),
self.canvas.create_line(midX, y1, midX+1, y1, arrow = FIRST, fill = col))
# draw name label beside arrow head
if (self.show_label):
self.label = self.canvas.create_text(middle[0] -
(20 + len(self.name)*3), middle[1] - (30 + deviation) , text = self.name)
else :
# create a line between the nodes - use 2 to make arrow in center.
dev = self.deviation
from_pos = from_position[0] + self.deviation, from_position[1]
mid = middle[0] + 0.5 * dev, middle[1]
self.id = (self.canvas.create_line(from_pos, mid , arrow = LAST, fill = col),
self.canvas.create_line(mid, to_position, fill = col))
# draw name label
dX = 20 * factor
dY = self.draw_index * 20 * factor
if (self.show_label):
self.label = self.canvas.create_text(middle[0] - dX,
middle[1] - dY, fill = text_col, text = self.name, anchor = E)
# Update methods
def remove(self):
if (self.to_node != None) : # if a connection had been made then remove it from the 'to' node
self.to_node.remove_connection(self, self.TO)
self.from_node.remove_connection(self, self.FROM) # and remove from 'from' node
for id in self.id : # remove the representation from the canvas
self.canvas.delete(id)
self.canvas.delete(self.label)
# CEBALERT: see earlier alert about EditorObject not inheriting from object.
EditorConnection.remove(self) #super(EditorProjection,self).remove()
def move(self):
# if one of the nodes connected by this connection move, then move by redrawing
self.gradient = self.calculate_gradient()
self.update_factor()
self.draw()
def decrement_draw_index(self):
self.draw_index -= 1
if self.to_node == self.from_node:
self.update_factor()
else:
self.connect_to_coord((self.from_node.width / 2) - 10)
def connect(self, to_node, con):
EditorConnection.connect(self, to_node, con)
self.draw_index = self.from_node.get_connection_count(to_node)-1
if (self.from_node == to_node):
self.update_factor()
else:
self.connect_to_coord((self.from_node.width / 2) - 10)
self.gradient = self.calculate_gradient()
self.radius = self.get_radius()
# Util methods
def get_middle(self, pos1, pos2) : # returns the middle of two points
return (pos1[0] + (pos2[0] - pos1[0])*0.5, pos1[1] + (pos2[1] - pos1[1])*0.5)
[docs] def get_radius(self):
"""Not implemented in this class"""
return (0,0)
def update_factor(self):
pass
def connect_to_coord(self, width):
n = self.draw_index
sign = math.pow(-1, n)
self.deviation = sign * width + (-sign) * math.pow(0.5, math.ceil(0.5 * (n))) * width
# returns the gradients of the two lines making the opening 'v' part of the receptive field.
# this depends on the draw_index, as it determines where the projection's representation begins.
[docs] def calculate_gradient(self):
"""Not implemented in this class"""
return (1,1)
def in_bounds(self, x, y) : # returns true if point lies in a bounding box
factor = self.canvas.scaling_factor
# If connections are represented as lines
# currently uses an extent around the arrow head.
to_position = self.to_node.get_pos()
from_position = self.from_node.get_pos()
if (self.to_node == self.from_node):
dev = self.draw_index * 15 * factor
middle = (to_position[0], to_position[1] - ((30 * factor) + dev))
else:
dev = self.deviation * 0.5
middle = self.get_middle(from_position, to_position)
if ((x < middle[0] + 10 + dev) & (x > middle[0] - 10 + dev) & (y < middle[1] + 10) & (y > middle[1] - 10)):
return True
return False
def set_mode(self, mode):
self.mode = mode
self.set_colours()
self.draw()
[docs]class EditorProjection(EditorEPConnection):
"""
Represents any topo CFProjection. It is a subclass of EditorEPConnection and fills
in the methods that are not defined. Can be represented by a representation of a
projection's receptive field or by a line with an arrow head in the middle;
lateral projections are represented by a dotted ellipse around the center.
Can determine if x,y coord is within the triangular receptive field or within an
area around the arrow head. The same can be determined for a lateral projection
ellipse.
"""
def __init__(self, name, canvas, from_node, receptive_field = True):
EditorEPConnection.__init__(self, name, canvas, from_node)
# if more than one connection between nodes,
# this will reflect how to draw this connection
self.normal_radius = 15
self.radius = self.get_radius()
self.receptive_field = receptive_field
self.view = 'radius'
self.viewing_choices = [('Field Radius', lambda: self.select_view('radius')),
('Line', lambda: self.select_view('line')),
('Fixed Size', lambda: self.select_view('normal'))]
# Draw methods
def draw(self):
self.draw_fn = \
{'normal' : self.draw_normal,
'line' : self.draw_line,
'radius' : self.draw_radius
}[self.view]
EditorEPConnection.draw(self)
def draw_radius(self, from_position, to_position):
# set the colour to be used depending on whether connection has the focus.
if (self.focus) :
text_col = col = self.colour[0]
lateral_colour = self.from_node.colour[0]
else:
text_col = 'black'
col = self.colour[1]
lateral_colour = ''
# midpoint of line
middle = self.get_middle(from_position, to_position)
if (to_position == from_position) : # connection to and from the same node
a, b = self.get_radius()
x1 = to_position[0] - a
y1 = to_position[1] + b
x2 = to_position[0] + a
y2 = to_position[1] - b
self.id = (self.canvas.create_oval(x1, y1, x2, y2, fill = lateral_colour,
dash = (2,2), outline = self.colour[2], width = 2), None)
# CEBALERT: as far as I know, this balloon binding never worked.
# self.balloon.tagbind(self.canvas, self.id[0], self.name)
else : # connection between distinct nodes
x1, y1 = to_position
x2, y2 = from_position
# this is for cases when the radius changes size.
radius_x, radius_y = self.get_radius()
self.id = (self.canvas.create_line(x1, y1, x2 - radius_x, y2, fill = col),
self.canvas.create_line(x1, y1, x2 + radius_x, y2, fill = col),
self.canvas.create_oval(x2 - radius_x, y2 - radius_y,
x2 + radius_x, y2 + radius_y, outline = col))
# draw name label
dX = 20
dY = self.draw_index * 20
if (self.show_label):
self.label = self.canvas.create_text(middle[0] - dX,
middle[1] - dY, fill = text_col, text = self.name, anchor = E)
def draw_normal(self, from_position, to_position):
# set the colour to be used depending on whether connection has the focus.
if (self.focus) :
text_col = col = self.colour[0]
lateral_colour = self.from_node.colour[0]
else:
text_col = 'black'
col = self.colour[1]
lateral_colour = ''
# midpoint of line
middle = self.get_middle(from_position, to_position)
if (to_position == from_position) : # connection to and from the same node
a, b = self.factor
x1 = to_position[0] - a
y1 = to_position[1] + b
x2 = to_position[0] + a
y2 = to_position[1] - b
self.id = (self.canvas.create_oval(x1, y1, x2, y2, fill = lateral_colour,
dash = (2,2), outline = self.colour[2], width = 2), None)
# self.balloon.tagbind(self.canvas, self.id[0], self.name)
else : # connection between distinct nodes
x1, y1 = to_position
x2, y2 = from_position
x2 += self.deviation
radius = self.normal_radius
self.id = (self.canvas.create_line(x1, y1, x2 - radius, y2, fill = col),
self.canvas.create_line(x1, y1, x2 + radius, y2, fill = col),
self.canvas.create_oval(x2 - radius, y2 - (0.5 * radius),
x2 + radius, y2 + (0.5 * radius), outline = col))
# draw name label
dX = 20
dY = self.draw_index * 20
if (self.show_label):
self.label = self.canvas.create_text(middle[0] - dX,
middle[1] - dY, fill = text_col, text = self.name, anchor = E)
# Util methods
def get_radius(self):
factor = self.canvas.scaling_factor
if self.to_node == None:
return (factor * self.normal_radius, factor * self.normal_radius *
self.from_node.height / self.from_node.width)
node = self.from_node
node_bounds = node.simobj.bounds.aarect().lbrt()
try:
bounds = self.simobj.bounds_template.lbrt()
except AttributeError:
return (factor * self.normal_radius, factor * self.normal_radius *
self.from_node.height / self.from_node.width)
radius_x = factor * (node.width / 2) * (bounds[2] - bounds[0]) / (node_bounds[2] - node_bounds[0])
radius_y = radius_x * node.height / node.width
return radius_x, radius_y
# returns the size of the semimajor and semiminor axis of the ellipse representing
# the draw_index-th lateral projection.
def get_factor(self):
factor = self.canvas.scaling_factor
w = factor * self.from_node.width; h = factor * self.from_node.height
a = 20; n = (w / 2) - 10; b = (n - a)
major = a + (b * (1 - pow(0.8, self.draw_index)))
a = 20 * h / w; n = (h / 2) - 10; b = (n - a)
minor = a + (b * (1 - pow(0.8, self.draw_index)))
return major, minor
def update_factor(self):
self.factor = self.get_factor()
# returns the gradients of the two lines making the opening 'v' part of the receptive field.
# this depends on the draw_index, as it determines where the projection's representation begins.
def calculate_gradient(self):
factor = self.canvas.scaling_factor
if self.view == 'radius':
A = self.to_node.get_pos()
T = self.from_node.get_pos()
B = (T[0] - self.radius[0], T[1])
C = (T[0] + self.radius[0], T[1])
else:
A = self.to_node.get_pos()
T = (self.from_node.get_pos()[0] + self.deviation ,self.from_node.get_pos()[1])
B = (T[0] - (factor * self.normal_radius), T[1])
C = (T[0] + (factor * self.normal_radius), T[1])
den_BA = (A[0] - B[0])
if not(den_BA == 0):
m_BA = (A[1] - B[1]) / den_BA
else :
m_BA = 99999 # AL - this should be a big number
den_CA = (A[0] - C[0])
if not(den_CA == 0):
m_CA = (A[1] - C[1]) / den_CA
else :
m_CA = 99999 # AL - this should be a big number
return (m_BA, m_CA)
def in_bounds(self, x, y) : # returns true if point lies in a bounding box
factor = self.canvas.scaling_factor
if self.view == 'line':
return EditorEPConnection.in_bounds(self,x,y)
else:
# returns true if x, y lie inside the oval representing this lateral projection
if (self.to_node == None or self.to_node == self.from_node):
if self.view == 'radius':
a, b = self.get_radius()
else:
a, b = self.factor
x, y = x - self.to_node.get_pos()[0], y - self.to_node.get_pos()[1]
if (x > a or x < -a):
return False
pY = math.sqrt(pow(b,2) * (1 - (pow(x,2)/pow(a,2))))
if (y > pY or y < -pY):
return False
return True
# returns true if x, y lie inside the triangular receptive
# field representing this projection get the points of the
# triangular receptive field, centered around the x, y
# point given
to_position = self.to_node.get_pos()
from_position = self.from_node.get_pos()
if self.view == 'radius':
A = (to_position[0] - x, to_position[1] - y)
T = (from_position[0] + (self.deviation * factor) - x, from_position[1] - y)
B = (T[0] - self.radius[0], T[1])
#C = (T[0] + self.radius[0], T[1])
else:
A = (to_position[0] - x, to_position[1] - y)
T = (from_position[0] + (self.deviation * factor) - x, from_position[1] - y)
B = (T[0] - (self.normal_radius * factor), T[1])
#C = (T[0] + (self.normal_radius * factor), T[1])
# if the y coords lie outwith the boundaries, return false
if (((A[1] < B[1]) and (B[1] < 0 or A[1] > 0)) or
((A[1] >= B[1]) and (B[1] > 0 or A[1] < 0))):
return False
# calculate the constant for the lines of the triangle
a_BA = A[1] - (self.gradient[0] * A[0])
a_CA = A[1] - (self.gradient[1] * A[0])
# The points are centered around the given coord, finding
# the intersects with line y = 0 and ensuring that the
# left line lies on the negative side of the point and the
# right line lies on the positive side of the point
# determines that the point is within the triangle.
if (((0 - a_CA) / self.gradient[1] >= 0) and ((0 - a_BA) / self.gradient[0] <= 0)):
return True
return False