Container.py
# -*- coding: utf-8 -*-
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
# Container.py ---
# --------------------------------
# Copyright (c) 2020
# L. CAPOCCHI (capocchi@univ-corse.fr)
# SPE Lab - SISU Group - University of Corsica
# --------------------------------
# Version 2.0 last modified: 03/15/20
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
#
# GENERAL NOTES AND REMARKS:
#
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
#
# GLOBAL IMPORT
#
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
import builtins
if builtins.__dict__.get('GUI_FLAG',True):
import wx
import wx.lib.dragscroller
import wx.lib.dialogs
from wx.lib.newevent import NewEvent
_ = wx.GetTranslation
from pubsub import pub as Publisher
AttrUpdateEvent, EVT_ATTR_UPDATE = NewEvent()
### wx.color has been removed in wx. 2.9
if hasattr(wx, "Color"):
wx.Colour = wx.Color
else:
wx.Color = wx.Colour
if wx.VERSION_STRING >= '4.0':
wx.StockCursor = wx.Cursor
else:
import gettext
_ = gettext.gettext
import os
import sys
import copy
import re
import pickle
import zipfile
import array
import inspect
if not hasattr(inspect, 'getargspec'):
inspect.getargspec = inspect.getfullargspec
from abc import ABC
from tempfile import gettempdir
from traceback import format_exception
from math import * ### for eval
from collections import Counter
if builtins.__dict__.get('GUI_FLAG', True):
import ConnectDialog
import DiagramConstantsDialog
import SpreadSheet
import ZipManager
import DropTarget
import PlotGUI
import SimulationGUI
import PriorityGUI
import CheckerGUI
import PluginsGUI
import WizardGUI
### Color definition
RED = '#d91e1e'
RED_LIGHT = '#f2a2a2'
GREEN = '#90ee90'
BLACK = '#000000'
BLUE = '#add8e6'
ORANGE = '#ffa500'
import Components
if builtins.__dict__.get('GUI_FLAG', True):
import Menu
### Mixin
from Mixins.Attributable import Attributable
from Mixins.Achievable import Achievable
from Mixins.Resizeable import Resizeable
from Mixins.Rotatable import Rotatable
from Mixins.Connectable import Connectable
from Mixins.Plugable import Plugable
from Mixins.Structurable import Structurable
from Mixins.Savable import Savable
from Mixins.Selectable import Selectable
from Mixins.Abstractable import Abstractable
from Mixins.Iconizable import Iconizable, Icon
### for all dsp model build with old version of DEVSimPy
sys.modules['Savable'] = sys.modules['Mixins.Savable']
from Decorators import BuzyCursorNotification, Post_Undo
from Utilities import HEXToRGB, relpath, playSound, sendEvent, getInstance, FixedList, getObjectFromString, getTopLevelWindow, printOnStatusBar
from Patterns.Observer import Subject, Observer
if builtins.__dict__.get('GUI_FLAG',True):
from DetachedFrame import DetachedFrame
from AttributeEditor import AttributeEditor, QuickAttributeEditor
from PropertiesGridCtrl import PropertiesGridCtrl
#Global Stuff -------------------------------------------------
clipboard = []
PORT_RESOLUTION = True
##############################################################
# #
# GENERAL fUNCTIONS #
# #
##############################################################
def MsgBoxError(event, parent, msg):
""" Pop-up alert for error in the .py file of a model
"""
### if importation error
if isinstance(msg, str):
dial = wx.MessageDialog(parent, \
_('Error trying to import module : %s')%msg, \
_('Error Manager'), \
wx.OK | wx.ICON_ERROR)
dial.ShowModal()
### Error occurring into the constructor or during the simulation
elif isinstance(msg, tuple):
### find error info of the error
try:
typ, val, tb = msg
trace = format_exception(typ, val, tb)
# mainW = wx.GetApp().GetTopWindow()
### try to find if the error come from devs model
### paths in traceback
paths = [a for a in trace if a.split(',')[0].strip().startswith('File')]
### find if DOMAIN_PATH is in paths list (inverted because traceback begin by the end)
path = None
line = None
fct = None
### find if DOMAIN_PATH is in the first file path of the trace
p = paths[-1]
if DOMAIN_PATH in p or HOME_PATH not in p:
path,line,fct = p.split(',')[0:3]
except Exception as info:
path = None
line = None
fct = None
if path is not None:
python_path = "File: %s\n"%(path.split(' ')[-1])
else:
python_path = ""
if line is not None:
line_number = "Line: %s\n"%(line.split(' ')[-1])
else:
line_number = ""
if fct is not None:
fct = "Function: %s\n"%(fct.split('\n')[0])
else:
fct = ""
if path is not None:
### ask to correct error
dial = wx.MessageDialog(parent,\
_("Error: %s\n%s%s%s\nDo you want to remove this error?")%(str(val),str(python_path),str(fct),str(line_number)),\
_('Error Manager'), \
wx.YES_NO | wx.YES_DEFAULT | wx.ICON_ERROR)
if dial.ShowModal() == wx.ID_YES:
### delete " and cast to string
python_path = str(path.split(' ')[-1])[1:-1]
dir_name = os.path.dirname(python_path)
### create a temporary component to invoke editor windows
devscomp = Components.DEVSComponent()
devscomp.setDEVSPythonPath(python_path)
### instantiation of editor frame and go to the line of the corresponding error
editor_frame = Components.DEVSComponent.OnEditor(devscomp, event)
if zipfile.is_zipfile(dir_name): editor_frame.cb.model_path = dir_name
if editor_frame:
nb = editor_frame.GetNoteBook()
page = nb.GetCurrentPage()
pos = int(line.split(' ')[-1])
page.GotoLine(pos)
return True
else:
return False
else:
wx.MessageBox(_("There is errors in python file.\nTrying to translate error informations: %s %s %s")%(typ, val, tb), _("Error"), wx.OK|wx.ICON_ERROR)
def CheckClass(m):
""" Check if class is ok and return it.
"""
if inspect.isclass(m):
cls = m
args = Components.GetArgs(cls)
elif isinstance(m, Block):
tempdir = os.path.realpath(gettempdir())
if tempdir in os.path.dirname(m.python_path):
cls = ('','','')
else:
cls = Components.GetClass(m.python_path)
args = m.args
elif os.path.exists(m):
### if .amd or .cmd
if zipfile.is_zipfile(m):
#zf = ZipManager.Zip(m)
cls = Components.GetClass(os.path.join(m, ZipManager.getPythonModelFileName(m)))
### .py
else:
cls = Components.GetClass(m)
args = Components.GetArgs(cls)
elif m.startswith('http'):
cls = Components.GetClass(m)
args = Components.GetArgs(cls)
else:
cls = ("","","")
### check cls error
if isinstance(cls, tuple):
return cls
### check devs instance
devs = getInstance(cls, args)
### check instance error
return devs if isinstance(devs, (tuple, Exception)) else None
################################################################
# #
# GENERAL CLASS #
# #
################################################################
#-------------------------------------------------------------------------------
class Diagram(Savable, Structurable):
""" Diagram class.
"""
def __init__(self):
""" Constructor.
"""
Structurable.__init__(self)
# list of shapes in the diagram
self.shapes = []
self.parent = None
# shape priority for simulation
self.priority_list = []
# constants dico
self.constants_dico = {}
# devs Master model
self.devsModel = None
# list of number of Block and Port under the diagram
self.nbCodeBlock = 0
self.nbContainerBlock = 0
self.nbiPort = 0
self.nboPort = 0
# list of deleted id
self.deletedCodeBlockId = []
self.deletedContainerBlockId = []
self.deletediPortId = []
self.deletedoPortId = []
self.last_name_saved = ''
self.modify = False
def __getstate__(self):
"""Return state values to be pickled."""
### we copy a new state in order to dont lost the devs result of Scope for example.
new_state = self.__dict__.copy()
### delete devs instance (because is generate before the simulation)
new_state['devsModel'] = None
### set parent attribut for undo/redo
new_state['parent'] = None
return new_state
def __getattr__(self, name):
"""Called when an attribute lookup has not found the attribute in the usual places.
"""
if name == 'dump_attributes':
return ['shapes', 'priority_list', 'constants_dico']
#=======================================================================
elif name == 'dump_abstr_attributes':
return Abstractable.DUMP_ATTR if hasattr(self, 'layers') and hasattr(self, 'current_level') else []
#=======================================================================
else:
raise AttributeError(name)
@staticmethod
def makeDEVSGraph(diagram, D = {}, type = object):
""" Make a formated dictionnary to make the graph of the DEVS Network: {'S1': [{'C1': (1, 0)}, {'M': (0, 1)}], port 1 of S1 is connected to the port 0 of C1...
"""
# for all components in the diagram
for c in diagram.GetShapeList():
# if the component is the conncetionShape, then add the new element in the D dictionnary
if isinstance(c, ConnectionShape):
model1, portNumber1 = c.input
model2, portNumber2 = c.output
# return D with object representation
if type is object:
D.setdefault(model2,[]).append({model1: (portNumber2 ,portNumber1)})
if isinstance(model1, (iPort,oPort)):
D.setdefault(model1,[]).append({model2: (portNumber1 ,portNumber2)})
# return D with string representation
else:
label1 = model1.label
label2 = model2.label
D.setdefault(label2,[]).append({label1: (portNumber2 ,portNumber1)})
if isinstance(model1, (iPort,oPort)):
D.setdefault(label1,[]).append({label2: (portNumber1 ,portNumber2)})
#if the component is a container block achieve the recursivity
elif isinstance(c, ContainerBlock):
Diagram.makeDEVSGraph(c,D,type)
return D
@staticmethod
def makeDEVSInstance(diagram = None):
""" Return the DEVS instance of diagram. Iterations order is very important!
1. we make the codeblock devs instance
2. we make the devs port instance for all devsimpy port
3. we make Containerblock instance
4. we make the connection
"""
#import ReloadModule
#ReloadModule.recompile("DomainInterface.DomainBehavior")
#ReloadModule.recompile("DomainInterface.DomainStructure")
#ReloadModule.recompile("DomainInterface.MasterModel")
#import DomainInterface.MasterModel
### PyPDEVS work with this
#diagram.setDEVSModel(DomainInterface.MasterModel.Master())
### TODO to be tested with PyPDEVS !!!
# if isinstance(diagram.parent, ShapeCanvas):
# diagram.setDEVSModel(DomainInterface.MasterModel.Master())
# else:
# diagram.ClearAllPorts()
### if devs instance of diagram is not instantiated, we make it
### else one simulation has been performed then we clear all devs port instances
if diagram.getDEVSModel():
diagram.ClearAllPorts()
else:
import DomainInterface.MasterModel
diagram.setDEVSModel(DomainInterface.MasterModel.Master())
### shape list of diagram
shape_list = diagram.GetShapeList()
block_list = {c for c in shape_list if isinstance(c, Block)}
### for all codeBlock shape, we make the devs instance
for m in block_list:
### class object from python file
cls = Components.GetClass(m.python_path)
### Class is wrong ?
if isinstance(cls, (ImportError, tuple)) or cls is None:
sys.stdout.write(_('Error making DEVS instances for:\n%s (class:%s)\n'%(m.python_path,str(cls))))
return False
else:
### DEVS model recovery
devs = getInstance(cls, m.args)
### Is safe instantiation ?
if isinstance(devs, tuple):
return devs
else:
devs.name = m.label
if isinstance(m, CodeBlock):
### les ports des modeles couples sont pris en charge plus bas dans les iPorts et oPorts
## ajout des port par rapport aux ports graphiques
for i in range(m.input):
devs.addInPort(f'in_{i}')
for j in range(m.output):
devs.addOutPort(f'out_{j}')
### devs instance setting
m.setDEVSModel(devs)
m.setDEVSParent(diagram.getDEVSModel())
### allow to escape the check of the simulation running in PyPDEVS (src/DEVS.py line 565)
if hasattr(devs.parent, "fullName"):
del devs.parent.fullName
### adding
diagram.addSubModel(devs)
#### recursion
if isinstance(m, ContainerBlock):
###===================================================================
if hasattr(m, 'layers') and hasattr(m, 'current_level'):
### level is given by the first stored diagram because m.current is not updated by the spin control
level = m.layers[0].current_level
dia = m.layers[level]
m.shapes = dia.GetShapeList()
m.priority_list = dia.priority_list or []
m.constants_dico = dia.constants_dico or {}
###===================================================================
Diagram.makeDEVSInstance(m)
###============================================================================= Add abstraction level manager
if hasattr(diagram, 'current_level') and diagram.current_level>0:
### Add devs model dam to diagram
dam = diagram.DAM[diagram.current_level]
devs_dam = getObjectFromString(dam)
diagram.addSubModel(devs_dam)
### Add devs model uam to diagram
uam = diagram.UAM[diagram.current_level]
devs_uam = getObjectFromString(uam)
diagram.addSubModel(devs_uam)
### inputs/outpus of dam/uam are instantiate depending on the iPort/oPort of diagram 0
dia_0 = diagram.layers[0]
shapeL0 = dia_0.GetShapeList()
for m in (s for s in shapeL0 if isinstance(s, iPort)):
devs_dam.addInPort()
diagram.addInPort()
for m in (s for s in shapeL0 if isinstance(s, oPort)):
devs_uam.addOutPort()
diagram.addOutPort()
for m in (s for s in shape_list if isinstance(s, iPort)):
devs_dam.addOutPort()
for m in (s for s in shape_list if isinstance(s, oPort)):
devs_uam.addInPort()
###==================================================================================
### Add abstraction level manager
###==============================================================================
if hasattr(diagram, 'current_level') and diagram.current_level>0:
# for all iPort shape, we make the devs instance
for i,m in enumerate((s for s in shapeL0 if isinstance(s, iPort))):
p1 = diagram.getDEVSModel().IPorts[i]
p2 = devs_dam.IPorts[i]
Structurable.ConnectDEVSPorts(diagram, p1, p2)
###==============================================================================
else:
for m in (s for s in shape_list if isinstance(s, iPort)):
### add port to coupled model
diagram.addInPort()
assert(len(diagram.getIPorts()) <= diagram.input)
###==============================================================================
### Add abstraction level manager
if hasattr(diagram, 'current_level') and diagram.current_level>0:
# for all oPort shape, we make the devs instance
for i,m in enumerate((s for s in shapeL0 if isinstance(s, oPort))):
p1 = devs_uam.OPorts[i]
p2 = diagram.getDEVSModel().OPorts[i]
Structurable.ConnectDEVSPorts(diagram, p1, p2)
###===============================================================================
else:
for m in (s for s in shape_list if isinstance(s, oPort)):
### add port to coupled model
diagram.addOutPort()
assert(len(diagram.getOPorts()) <= diagram.output)
### Connections
for m in (s for s in shape_list if isinstance(s, ConnectionShape)):
m1,n1 = m.input
m2,n2 = m.output
if isinstance(m1, Block) and isinstance(m2, Block):
try:
p1 = m1.getDEVSModel().OPorts[n1]
except:
msg = _("It seems that the number of internal output ports (%d) of the coupled model %s is not enough!\nPlease check this.")%(len(m1.getDEVSModel().OPorts),m1.label)
sys.stdout.write(msg)
return msg
try:
p2 = m2.getDEVSModel().IPorts[n2]
except:
msg = _("It seems that the number of internal input ports (%d) of the coupled model %s is not enough!\nPlease check this.")%(len(m2.getDEVSModel().IPorts),m2.label)
sys.stdout.write(msg)
return msg
Structurable.ConnectDEVSPorts(diagram, p1, p2)
elif isinstance(m1, Block) and isinstance(m2, oPort):
### TODO insert devs_uam
p1 = m1.getDEVSModel().OPorts[n1]
###==============================================================================
### Add abstraction level manager
if hasattr(diagram, 'current_level') and diagram.current_level>0:
p2 = devs_uam.IPorts[m2.id]
else:
p2 = diagram.getDEVSModel().OPorts[m2.id]
###===============================================================================
Structurable.ConnectDEVSPorts(diagram, p1, p2)
#p1 = m1.getDEVSModel().OPorts[n1]
#p2 = diagram.getDEVSModel().OPorts[m2.id]
#Structurable.ConnectDEVSPorts(diagram, p1, p2)
elif isinstance(m1, iPort) and isinstance(m2, Block):
### TODO insert devs_dam
###==============================================================================
### Add abstraction level manager
if hasattr(diagram, 'current_level') and diagram.current_level>0:
p1 = devs_dam.OPorts[m1.id]
else:
p1 = diagram.getDEVSModel().IPorts[m1.id]
###===============================================================================
p2 = m2.getDEVSModel().IPorts[n2]
Structurable.ConnectDEVSPorts(diagram, p1, p2)
#p1 = diagram.getDEVSModel().IPorts[m1.id]
#p2 = m2.getDEVSModel().IPorts[n2]
#Structurable.ConnectDEVSPorts(diagram, p1, p2)
# elif isinstance(m1, iPort) and isinstance(m2, oPort):
# ###==============================================================================
# ### Add abstraction level manager
# if hasattr(diagram, 'current_level') and diagram.current_level>0:
# p1 = devs_dam.OPorts[m1.id]
# p2 = devs_dam.IPorts[m2.id]
# else:
# p1 = diagram.getDEVSModel().IPorts[m1.id]
# p2 = diagram.getDEVSModel().OPorts[m2.id]
# ###===============================================================================
# Structurable.ConnectDEVSPorts(diagram, p1, p2)
else:
msg = _('Direct connections between ports inside the coupled model %s have been founded.\n There are not considered by the simulation!\n'%(diagram.label))
sys.stdout.write(msg)
#return msg
### update priority_list from shape list
### Shape list can be increased or decreased (add or remove shape) without invoke the Priority list dialogue
diagram.updateDEVSPriorityList()
### reordered the componentSet of the master before the simulation
if diagram.priority_list:
L = []
devs = diagram.getDEVSModel()
# si l'utilisateur n'a pas definit d'ordre de priorité pour l'activation des modèles, on la construit
for label1 in diagram.priority_list:
for m in devs.getComponentSet():
label2 = m.getBlockModel().label
if label1 == label2:
L.append(m)
devs.setComponentSet(L)
return diagram.getDEVSModel()
def SetParent(self, parent):
"""
"""
assert isinstance(parent, ShapeCanvas)
self.parent = parent
def GetParent(self):
"""
"""
return self.parent
def GetGrandParent(self):
"""
"""
return self.GetParent().GetParent()
@BuzyCursorNotification
def LoadFile(self, fileName = None):
""" Function that load diagram from a file.
"""
load_file_result = Savable.LoadFile(self, fileName)
if isinstance(load_file_result, Exception):
### Exception propagation
return load_file_result
else:
# load constants (like Rs, Lms...) into the general builtin (to use it, <title>['Lms'] into the expr)
# give title by basename of filename
title = os.path.splitext(os.path.basename(fileName))[0]
# load constants into the general builtin
self.LoadConstants(title)
for shape in self.GetShapeList():
self.UpdateAddingCounter(shape)
return True
#@cond_decorator(builtins.__dict__.get('GUI_FLAG',True), StatusBarNotification('Load'))
def LoadConstants(self, label):
""" Load Constants to general builtin.
"""
if self.constants_dico != {}:
builtins.__dict__[label] = self.constants_dico
for s in [c for c in self.GetShapeList() if isinstance(c, ContainerBlock)]:
s.LoadConstants(s.label)
def OnPriority(self, parent):
""" Method that show the priorityGUI frame in order to define the activation priority of components
"""
shapes_list = [s.label for s in self.GetShapeList() if isinstance(s, Block)]
#list of all components
if self.priority_list == []:
self.priority_list = shapes_list
else:
### priority list manager
cpt = 1
lenght = len(shapes_list)
result = [None]*lenght
for s in shapes_list:
if s in self.priority_list :
try:
result[self.priority_list.index(s)]=s
except:
pass
else:
result[lenght-cpt] = s
cpt+=1
self.priority_list = [s for s in result if s is not None]
self.modify = True
self.parent.DiagramModified()
dlg = PriorityGUI.PriorityGUI(parent, wx.NewIdRef(), _("Priority Manager"), self.priority_list)
dlg.Bind(wx.EVT_CLOSE, self.OnClosePriorityGUI)
dlg.Show()
def OnInformation(self, event):
"""
"""
stat_dico = self.GetStat({'Atomic_nbr':0, 'Coupled_nbr':0, 'Connection_nbr':0, 'Deep_level':0, 'iPort_nbr':0, 'oPort_nbr':0})
msg = "".join( [_("Path: %s\n")%self.last_name_saved,
_("Number of atomic devs models: %d\n")%stat_dico['Atomic_nbr'],
_("Number of coupled devs models: %d\n")%stat_dico['Coupled_nbr'],
_("Number of coupling: %d\n")%stat_dico['Connection_nbr'],
_("Number of deep level (description hierarchy): %d\n")%stat_dico['Deep_level'],
_("Number of input port models: %d\n")%stat_dico['iPort_nbr'],
_("Number of output port models: %d\n")%stat_dico['oPort_nbr']]
)
dlg = wx.lib.dialogs.ScrolledMessageDialog(self.GetParent(), msg, _("Diagram Information"), style=wx.OK|wx.ICON_EXCLAMATION|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
dlg.ShowModal()
def OnClosePriorityGUI(self, event):
""" Method that update the self.priority_list and close the priorityGUI Frame
"""
obj = event.GetEventObject()
self.priority_list = [obj.listCtrl.GetItemText(i) for i in range(obj.listCtrl.GetItemCount())]
obj.Destroy()
### we can update the devs priority list during the simulation ;-)
self.updateDEVSPriorityList()
event.Skip()
def OnAddConstants(self, event):
""" Method that add constant parameters in order to simplify the modling codeBlock model
"""
obj = event.GetEventObject()
### conditional statement only for windows
win = obj.GetInvokingWindow() if isinstance(obj, wx.Menu) else obj
### event come from right click on the shapecanvas
if isinstance(win, ShapeCanvas):
win = win.GetParent()
if isinstance(win, DetachedFrame):
title = win.GetTitle()
else:
title = win.GetPageText(win.GetSelection())
### event come from Main application by the Diagram menu
else:
### needed for window
if not win: win = obj.GetWindow()
nb2 = win.GetDiagramNotebook()
title = nb2.GetPageText(nb2.GetSelection())
dlg = DiagramConstantsDialog.DiagramConstantsDialog(win, wx.NewIdRef(), title)
dlg.Populate(self.constants_dico)
if dlg.ShowModal() == wx.ID_OK:
self.constants_dico = dlg.GetData()
dlg.Destroy()
@BuzyCursorNotification
def checkDEVSInstance(self, diagram=None, D={}):
""" Recursive DEVS instance checker for a diagram.
@param diagram: diagram instance
@param D: Dictionary of models with the associated error
"""
### shape list of diagram
shape_list = set(diagram.GetShapeList())
#### for all codeBlock and containerBlock shapes, we make the devs instance
for m in [s for s in shape_list if isinstance(s, (CodeBlock, ContainerBlock))]:
D[m] = CheckClass(m)
## for all ContainerBlock shape, we make the devs instance and call the recursion
if isinstance(m, ContainerBlock):
self.checkDEVSInstance(m, D)
def DoCheck(self):
""" Check all models for validation
Return None if all models are ok, D else
"""
### dictionary composed by key = label of model and value = None if no error, exc_info() else
D = {}
self.checkDEVSInstance(self, D)
return D if [m for m in list(D.values()) if m != None] != [] else None
def OnCheck(self, event):
""" Check button has been clicked. We check if models which compose the diagram are valide.
"""
### if there are models in diagram
if self.GetCount() != 0:
obj = event.GetEventObject()
win = obj.GetTopLevelParent() if isinstance(obj, wx.ToolBar) else obj.GetWindow()
D = self.DoCheck()
### if there is no error
if D is None:
dial = wx.MessageDialog(win,
_('All DEVS model has been instantiated without error.\n\nDo you want simulate?'),
_('Error Manager'),
wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
if dial.ShowModal() == wx.ID_YES:
self.OnSimulation(event)
else:
frame = CheckerGUI.CheckerGUI(win, self.DoCheck())
frame.SetDiagram(self)
frame.Show()
### no models in diagram
else:
wx.MessageBox(_("Diagram is empty.\n\nPlease, drag-and-drop model from libraries control panel to build a diagram or load an existing diagram."),_('Error Manager'))
@BuzyCursorNotification
def OnSimulation(self, event):
""" Method calling the simulationGUI
"""
### if there are models in diagram
if self.GetCount() != 0 :
## window that contain the diagram which will be simulate
win = wx.GetApp().GetTopWindow()
obj = event.GetEventObject()
# obj = event.GetEventObject()
# win = obj.GetWindow() if isinstance(obj, wx.Menu) else obj.GetTopLevelParent()
# diagram which will be simulate
diagram = self
### Check if all models doesn't contain errors
D = self.DoCheck()
### if there is no error in models
if D is not None:
dial = wx.MessageDialog(win, \
_("There is errors in some models.\n\nDo you want to execute the error manager ?"), \
_('Simulation Manager'), \
wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
playSound(SIMULATION_ERROR_SOUND_PATH)
if dial.ShowModal() == wx.ID_YES:
frame = CheckerGUI.CheckerGUI(win, D)
frame.SetDiagram(self)
frame.Show()
return False
else:
### Check if models have the same label
L = diagram.GetLabelList([])
if len(L)!=len(set(L)):
model_with_same_label = [f"-{k}" for k,v in Counter(L).items() if v>1]
txt = "\n".join(model_with_same_label)
wx.MessageBox(_("It seems that the following models have a same label:\n\
%s\n\
\nIf you plan to use Flat simulation algorithm, all model must have a unique label.")%txt, _("Simulation Manager"))
### set the name of diagram
if isinstance(win, DetachedFrame):
title = win.GetTitle()
else:
nb2 = win.GetDiagramNotebook()
title =nb2.GetPageText(nb2.GetSelection()).rstrip()
diagram.label = os.path.splitext(os.path.basename(title))[0]
## delete all attached devs instances
diagram.Clean()
## make DEVS instance from diagram
master = Diagram.makeDEVSInstance(diagram)
if not master or isinstance(master,str):
wx.MessageBox(master, _("Simulation Manager"))
return False
## test of filename model attribute
elif all(model.bad_filename_path_flag for model in [m for m in diagram.GetShapeList() if isinstance(m, Block)] if hasattr(model, 'bad_filename_path_flag')):
dial = wx.MessageDialog(win, \
_("You don't make the simulation of the Master model.\nSome models have bad fileName path !"),\
_('Simulation Manager'), \
wx.OK | wx.ICON_EXCLAMATION)
dial.ShowModal()
return False
else:
# pluginmanager.trigger_event('START_DIAGRAM', parent = win, diagram = diagram)
### clear all log file
for fn in [f for f in os.listdir(gettempdir()) if f.endswith('.devsimpy.log')]:
os.remove(os.path.join(os.path.realpath(gettempdir()),fn))
## obj = event.GetEventObject()
# si invocation à partir du bouton dans la toolBar (apparition de la frame de simulation dans une fenetre)
if isinstance(obj, wx.ToolBar) or 'Diagram' in obj.GetTitle():
frame = SimulationGUI.SimulationDialog(win, wx.NewIdRef(), _(" %s Simulator"%diagram.label))
frame.SetMaster(master)
frame.Show()
## si invocation par le menu (apparition de la frame de simulation dans le panel)
elif isinstance(obj, (wx.Menu, wx.Frame)):
sizer3 = wx.BoxSizer(wx.VERTICAL)
win.panel3.Show()
win.SimDiag = SimulationGUI.SimulationDialog(win.panel3, wx.NewIdRef(), _("Simulation Manager"))
win.SimDiag.SetMaster(master)
sizer3.Add(win.SimDiag, 0, wx.EXPAND)
win.panel3.SetSizer(sizer3)
win.panel3.SetAutoLayout(True)
### title is Simulation because it must ne the same of the submenu in toolbar (for checking update)
nb1 = win.GetControlNotebook()
nb1.InsertPage(2, win.panel3, _("Simulation"), imageId = 2)
else:
sys.stdout.write(_("This option has not been implemented yet."))
return False
return True
else:
wx.MessageBox(_("Diagram is empty.\n\nPlease, drag-and-drop model from libraries control panel to build a diagram or load an existing diagram.."),_('Simulation Manager'))
return False
def AddShape(self, shape, after = None):
""" Method that insert shape into the diagram at the position 'after'
"""
index = self.shapes.index(after) if after else 0
self.UpdateAddingCounter(shape)
self.InsertShape(shape, index)
def InsertShape(self, shape, index = 0):
""" Method that insert shape into the diagram to the index position
"""
self.shapes.insert(index, shape)
self.modify = True
if self.parent:
self.parent.DiagramModified()
def DeleteShape(self, shape):
""" Method that delete all shape links
"""
### delete all shape connected with connection shape
for cs in [c for c in self.GetShapeList() if isinstance(c, ConnectionShape)]:
if cs.input is not None and cs.output is not None:
if shape in cs.input+cs.output:
self.shapes.remove(cs)
if isinstance(shape, Block):
if shape.label in self.priority_list:
### update priority list
self.priority_list.remove(shape.label)
if isinstance(shape, Block):
### update the devs componentSet
coupled_devs = self.getDEVSModel()
devs = shape.getDEVSModel()
if coupled_devs and devs in coupled_devs.getComponentSet():
coupled_devs.delToComponentSet([devs])
try:
### delete shape
self.shapes.remove(shape)
except ValueError:
sys.stdout.write(_("Error trying to remove %s")%shape)
### update the number of shape depending to its type
self.UpdateRemovingCounter(shape)
self.modify = True
self.parent.DiagramModified()
def UpdateRemovingCounter(self, shape):
""" Method that update the removed shape counter
"""
# update number of components
if isinstance(shape, CodeBlock):
self.deletedCodeBlockId.append(shape.id)
self.nbCodeBlock-=1
elif isinstance(shape, ContainerBlock):
self.deletedContainerBlockId.append(shape.id)
self.nbContainerBlock-=1
elif isinstance(shape, iPort):
self.deletediPortId.append(shape.id)
self.nbiPort-=1
elif isinstance(shape, oPort):
self.deletedoPortId.append(shape.id)
self.nboPort-=1
else:
pass
def UpdateAddingCounter(self, shape):
""" Method that update the added shape counter
"""
# gestion du nombre de shape
if isinstance(shape, CodeBlock):
shape.id=self.GetCodeBlockCount()
self.nbCodeBlock+=1
elif isinstance(shape, ContainerBlock):
shape.id=self.GetContainerBlockCount()
self.nbContainerBlock+=1
elif isinstance(shape, iPort):
self.nbiPort+=1
elif isinstance(shape, oPort):
self.nboPort+=1
else:
pass
def update(self, concret_subject = None):
""" Update method is invoked by notify method of Subject class
"""
### update shapes list in diagram with a delete of connexionShape which no longer exists (when QuickAttributeEditor change input or output of Block)
csList = [a for a in self.shapes if isinstance(a, ConnectionShape)]
for cs in csList:
index = cs.output[1]
model = cs.output[0]
### if index+1 is superiror to the new number of port (model.input)
if index+1 > model.input:
self.DeleteShape(cs)
index = cs.input[1]
model = cs.input[0]
### if index+1 is superiror to the new number of port (model.output)
if index+1 > model.output:
self.DeleteShape(cs)
def PopShape(self,index=-1):
""" Function that pop the shape at the index position
"""
return self.shapes.pop(index)
def DeleteAllShapes(self):
""" Method that delete all shapes
"""
del self.shapes[:]
self.modify = True
self.parent.DiagramModified()
def ChangeShapeOrder(self, shape, pos=0):
"""
"""
self.shapes.remove(shape)
self.shapes.insert(pos,shape)
def GetCount(self):
""" Function that return the number of shapes that composed the diagram
"""
return len(self.shapes)
def GetFlatBlockShapeList(self, l=[]):
""" Get the flat list of Block (Code and Container) shape using recursion process
"""
for shape in self.shapes:
if isinstance(shape, CodeBlock):
l.append(shape)
elif isinstance(shape, ContainerBlock):
l.append(shape)
shape.GetFlatBlockShapeList(l)
return l
def GetFlatCodeBlockShapeList(self):
""" Get the flat list of CodeBlock shapes using recursion process
"""
l = []
for shape in self.shapes:
if isinstance(shape, CodeBlock):
l.append(shape)
if isinstance(shape, ContainerBlock):
l.extend(shape.GetFlatCodeBlockShapeList())
return l
def GetShapeByLabel(self, label=''):
""" Function that return the shape instance from its label
"""
for m in self.GetFlatBlockShapeList():
if m.label.strip() == label.strip():
return m
sys.stderr.write(_("Block %s not found.\n"%(label)))
return False
def GetShapeList(self):
""" Function that return the shapes list
"""
return self.shapes
def GetConnectionShapeGenerator(self):
""" Function that return the connection shape generator
"""
return (s for s in self.shapes if isinstance(s, ConnectionShape))
def GetBlockCount(self):
""" Function that return the number of Block shape
"""
return self.GetCodeBlockCount()+self.GetContainerBlockCount()
def GetCodeBlockCount(self):
""" Function that return the number of codeBlock shape
"""
return self.deletedCodeBlockId.pop() if self.deletedCodeBlockId else self.nbCodeBlock
def GetContainerBlockCount(self):
""" Function that return the number of containerBlock shape
"""
return self.deletedContainerBlockId.pop() if self.deletedContainerBlockId else self.nbContainerBlock
def GetiPortCount(self):
""" Function that return the number of iPort shape
"""
return self.deletediPortId.pop() if self.deletediPortId else self.nbiPort
def GetoPortCount(self):
""" Function that return the number of oPort shape
"""
return self.deletedoPortId.pop() if self.deletedoPortId else self.nboPort
def Clean(self):
""" Clean DEVS instances attached to all block model in the diagram.
"""
if not self.devsModel:
return
for devs in [a for a in list(self.devsModel.getFlatComponentSet().values()) if hasattr(a, 'finish')]:
try:
Publisher.unsubscribe(devs.finish, "%d.finished"%(id(devs)))
except:
sys.stdout.write(_("Impossible to execute the finish method for the model %s!\n")%devs)
devs.finish(None)
self.devsModel.setComponentSet([])
for m in self.GetShapeList():
m.setDEVSModel(None)
if isinstance(m, ConnectionShape):
m.input[0].setDEVSModel(None)
m.output[0].setDEVSModel(None)
if isinstance(m, ContainerBlock):
m.Clean()
def GetStat(self, d):
""" Get information about diagram like the numbe rof atomic model or the number of link between models.
"""
first_coupled = False
for m in self.GetShapeList():
if isinstance(m, CodeBlock):
d['Atomic_nbr']+=1
elif isinstance(m, ContainerBlock):
d['Coupled_nbr']+=1
if not first_coupled:
first_coupled = True
d['Deep_level']+= 1
m.GetStat(d)
elif isinstance(m, ConnectionShape):
d['Connection_nbr']+=1
elif isinstance(m, iPort):
d['iPort_nbr']+=1
elif isinstance(m, oPort):
d['oPort_nbr']+=1
else:
sys.stdout.write('Unknowm model!\n')
return d
def GetLabelList(self, l=[]):
""" Get Labels of all models
"""
for m in [a for a in self.GetShapeList() if isinstance(a, Block)]:
l.append(m.label)
if isinstance(m, ContainerBlock):
m.GetLabelList(l)
return l
def GetName(self):
""" Return the last name saved
"""
return os.path.basename(self.last_name_saved)
# Generic Shape Event Handler---------------------------------------------------
class ShapeEvtHandler(ABC):
""" Handler class
"""
def OnLeftUp(self,event):
pass
def OnLeftDown(self,event):
pass
def leftUp(self,items):
pass
def OnLeftDClick(self,event):
pass
def OnRightUp(self,event):
pass
def OnRightDown(self,event):
pass
def OnRightDClick(self,event):
pass
def OnSelect(self,event):
pass
def OnDeselect(self,event):
pass
def OnMove(self,event):
pass
def OnResize(self,event):
pass
def OnConnect(self,event):
pass
# Generic Graphic items---------------------------------------------------------
class Shape(ShapeEvtHandler):
""" Shape class
"""
FILL = [BLUE]
def __init__(self, x=[], y=[]):
""" Constructor
"""
self.x = array.array('d',x) # list of x coord
self.y = array.array('d',y) # list of y coords
self.fill= Shape.FILL # fill color
self.pen = [self.fill[0] , 1, 100] # pen color and size / 100 = wx.PENSTYLE_SOLID
self.font = [FONT_SIZE, 74, 93, 700, u'Arial']
def draw(self, dc):
""" Draw method
"""
r, g, b = HEXToRGB(str(self.fill[0]))
brushclr = wx.Color(r, g, b, 128) # half transparent
try:
dc.SetPen(wx.Pen(self.pen[0], self.pen[1], self.pen[2]))
### for old model
except:
dc.SetPen(wx.Pen(self.pen[0], self.pen[1]))
dc.SetBrush(wx.Brush(brushclr))
try:
### adapt size font depending on the size of label
### begin with the max of font size (defined in preferences)
font = FONT_SIZE
### set the font in the dc in order to performed GetTextExtent
dc.SetFont(wx.Font(font, self.font[1], self.font[2], self.font[3], False, self.font[4]))
width_t, _ = dc.GetTextExtent(self.label)
### size of shape
width_s = self.x[1]-self.x[0]
### while the label with is sup of shape width, we reduce the font of the dc (thus the label size)
while(width_t > width_s):
font -=1
dc.SetFont(wx.Font(font, self.font[1], self.font[2], self.font[3], False, self.font[4]))
width_t, _ = dc.GetTextExtent(self.label)
### update the font
self.font[0]=font
except Exception:
try:
dc.SetFont(wx.Font(10, self.font[1], self.font[2], self.font[3], False, self.font[4]))
except Exception:
dc.SetFont(wx.Font(10, 74, 93, 700, False, u'Arial'))
def move(self,x,y):
""" Move method
"""
if not self.lock_flag:
# self.x = array.array('d', [v+x for v in self.x])
# self.y = array.array('d', [v+y for v in self.y])
self.x = [v+x for v in self.x]
self.y = [v+y for v in self.y]
#def OnResize(self):
# """ Resize method controled by ResizeNode move method
# """
# ### dynamic font size with 1O (pointSize) * width (pourcent)/ 100
# self.font[0] = int(FONT_SIZE * (self.x[1]-self.x[0]) / 100.0)
# pass
def lock(self):
self.lock_flag = True
def unlock(self):
self.lock_flag = False
def Copy(self):
""" Function that return the deep copy of shape
"""
return copy.deepcopy(self)
#-------------------------------------------------------------------------------
class LineShape(Shape):
"""
"""
def __init__(self, x1 = 20, y1 = 20, x2 = 50, y2 = 50):
""" Cosntructor
"""
Shape.__init__(self, [x1, x2] , [y1, y2])
def draw(self, dc):
""" Draw method
"""
Shape.draw(self, dc)
dc.DrawLine(self.x[0], self.y[0], self.x[1], self.y[1])
def HitTest(self, x, y):
""" Hitest method
"""
if x < min(self.x)-3:return False
if x > max(self.x)+3:return False
if y < min(self.y)-3:return False
if y > max(self.y)+3:return False
top = (x-self.x[0]) *(self.x[1] - self.x[0]) + (y-self.y[0])*(self.y[1]-self.y[0])
distsqr = pow(self.x[0]-self.x[1], 2)+pow(self.y[0]-self.y[1],2)
u = float(top)/float(distsqr)
newx = self.x[0] + u*(self.x[1]-self.x[0])
newy = self.y[0] + u*(self.y[1]-self.y[0])
dist = pow(pow(newx-x, 2) + pow(newy-y, 2), .5)
return False if dist > 7 else True
#-------------------------------------------------------------------------------
class RoundedRectangleShape(Shape):
""" RoundedRectangleShape class
"""
def __init__(self, x1 = 20, y1 = 20, x2 = 120, y2 = 120):
""" constructor
"""
Shape.__init__(self, [x1, x2] , [y1, y2])
def draw(self, dc):
""" Draw method
"""
Shape.draw(self,dc)
width,height=int(self.x[1]-self.x[0]), int(self.y[1]-self.y[0])
x,y=int(self.x[0]), int(self.y[0])
### Prepare label drawing
rect = wx.Rect(int(x),int(y), int(width), int(height))
r=4.0
if wx.VERSION_STRING < '4.0':
dc.DrawRoundedRectangleRect(rect, r)
else:
wx.DC.DrawRoundedRectangle(dc, rect, r)
#def GetRect(self):
#width,height=int(self.x[1]-self.x[0]), int(self.y[1]-self.y[0])
#return wx.Rect(self.x[0], self.y[0], width, height)
def HitTest(self, x, y):
""" Hitest method
"""
if x < self.x[0]: return False
if x > self.x[1]: return False
if y < self.y[0]: return False
if y > self.y[1]: return False
return True
#-------------------------------------------------------------------------------
class RectangleShape(Shape):
""" RectangleShape class
"""
def __init__(self,x = 20, y = 20, x2 = 120, y2 = 120):
""" Constructor
"""
Shape.__init__(self, [x,x2] , [y,y2])
def draw(self,dc):
""" Draw paint method
"""
Shape.draw(self,dc)
x,y = int(self.x[0]), int(self.y[0])
width, height = int(self.x[1]-self.x[0]), int(self.y[1]-self.y[0])
dc.DrawRectangle(x, y, width, height)
def HitTest(self, x, y):
""" Hitest method
"""
if x < self.x[0]: return False
if x > self.x[1]: return False
if y < self.y[0]: return False
if y > self.y[1]: return False
return True
#-------------------------------------------------------------------------------
class PolygonShape(Shape):
""" PolygonShape class
"""
def __init__(self,x = 20, y = 20, x2 = 120, y2 = 120):
""" Constructor
"""
Shape.__init__(self, [x,x2] , [y,y2])
def draw(self, dc):
"""
"""
Shape.draw(self, dc)
# dx = (self.x[1]-self.x[0])/2
dy = (self.y[1]-self.y[0])/2
p0 = wx.Point(int(self.x[0]), int(self.y[0]-dy/2))
p1 = wx.Point(int(self.x[0]), int(self.y[1]+dy/2))
p2 = wx.Point(int(self.x[1]), int(self.y[0]+dy))
offsetx = (self.x[1]-self.x[0])/2
dc.DrawPolygon((p0,p1,p2),offsetx)
def HitTest(self, x, y):
""" Hitest method
"""
if x < self.x[0]: return False
if x > self.x[1]: return False
if y < self.y[0]: return False
if y > self.y[1]: return False
return True
#-------------------------------------------------------------------------------
class CircleShape(Shape):
def __init__(self,x=20, y=20, x2=120, y2=120, r=30.0):
Shape.__init__(self, [x,x2], [y,y2])
self.r = r
def draw(self,dc):
Shape.draw(self,dc)
dc.SetFont(wx.Font(10, self.font[1],self.font[2], self.font[3], False, self.font[4]))
dc.DrawCircle(int((self.x[0]+self.x[1])/2), int((self.y[0]+self.y[1])/2), int(self.r))
def HitTest(self, x, y):
if x < self.x[0]: return False
if x > self.x[1]: return False
if y < self.y[0]: return False
if y > self.y[1]: return False
return True
#-------------------------------------------------------------------------------
class PointShape(Shape):
def __init__(self, x=20, y=20, size=4, type='rect'):
Shape.__init__(self, [x] , [y])
self.type = type
self.size = size
if self.type=='rondedrect':
self.graphic = RoundedRectangleShape(x-size,y-size,x+size,y+size)
elif self.type=='rect':
self.graphic = RectangleShape(x-size,y-size,x+size,y+size)
elif self.type=='circ':
self.graphic = CircleShape(x-size,y-size,x+size,y+size, size)
elif self.type=='poly':
self.graphic = PolygonShape(x-size,y-size,x+size,y+size)
self.graphic.pen = self.pen
self.graphic.fill = self.fill
def moveto(self,x,y):
"""
"""
self.x = x
self.y = y
size = self.size
self.graphic.x = [x-size, x+size]
self.graphic.y = [y-size, y+size]
def move(self,x,y):
"""
"""
# self.x = array.array('d', [v+x for v in self.x])
# self.y = array.array('d', [v+y for v in self.y])
self.x = [v+x for v in self.x]
self.y = [v+y for v in self.y]
self.graphic.move(x,y)
def HitTest(self, x, y):
return self.graphic.HitTest(x,y)
def draw(self,dc):
"""
"""
# Mac's DC is already the same as a GCDC, and it causes
# problems with the overlay if we try to use an actual
# wx.GCDC so don't try it.
if 'wxMac' not in wx.PlatformInfo:
dc = wx.GCDC(dc)
self.graphic.pen = self.pen
self.graphic.fill = self.fill
self.graphic.draw(dc)
if builtins.__dict__.get('GUI_FLAG',True):
#-------------------------------------------------------------------------------
class ShapeCanvas(wx.ScrolledWindow, Abstractable, Subject):
""" ShapeCanvas class.
"""
ID = 0
CONNECTOR_TYPE = 'direct'
def __init__(self,\
parent,\
id=wx.NewIdRef(), \
pos=wx.DefaultPosition, \
size=(-1,-1), \
style=wx.DEFAULT_FRAME_STYLE | wx.CLIP_CHILDREN, \
name="", \
diagram = None):
""" Construcotr
"""
#super(wx.ScrolledWindow, self).__init__(parent, id, pos, size, style, name)
wx.ScrolledWindow.__init__(self, parent, id, pos, size, style, name)
Abstractable.__init__(self, diagram)
Subject.__init__(self)
self.SetBackgroundColour(wx.WHITE)
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
self.name = name
self.parent = parent
self.diagram = diagram
self.nodes = []
self.currentPoint = [0, 0] # x and y of last mouse click
self.selectedShapes = []
self.scalex = 1.0
self.scaley = 1.0
self.SetScrollbars(50, 50, 50, 50)
ShapeCanvas.ID += 1
# Ruber Band Attributs
self.overlay = wx.Overlay()
self.permRect = None
self.selectionStart = None
self.timer = wx.Timer(self, wx.NewIdRef())
self.f = None
self.scroller = wx.lib.dragscroller.DragScroller(self)
self.stockUndo = FixedList(NB_HISTORY_UNDO)
self.stockRedo = FixedList(NB_HISTORY_UNDO)
### subject init
self.canvas = self
### improve drawing with not condsidering the resizeable node when connect blocks
self.resizeable_nedeed = True
self.refresh_need = True
### attach canvas to notebook 1 (for update)
try:
self.__state = {}
mainW = self.GetTopLevelParent()
mainW = isinstance(mainW, DetachedFrame) and wx.GetApp().GetTopWindow() or mainW
self.attach(mainW.GetControlNotebook())
except AttributeError:
sys.stdout.write(_('ShapeCanvas not attached to notebook 1\n'))
## un ShapeCanvas est Dropable
dt = DropTarget.DropTarget(self)
self.SetDropTarget(dt)
#Window Events
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_IDLE, self.OnIdle)
#Mouse Events
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
self.Bind(wx.EVT_RIGHT_DCLICK, self.OnRightDClick)
self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
#Key Events
self.Bind(wx.EVT_KEY_DOWN, self.keyPress)
### for quickattribute
self.Bind(wx.EVT_TIMER, self.OnTimer)
@Post_Undo
def AddShape(self, shape, after = None):
self.diagram.AddShape(shape, after)
self.UpdateShapes([shape])
def InsertShape(self, shape, index = 0):
self.diagram.InsertShape(shape, index)
@Post_Undo
def DeleteShape(self, shape):
self.diagram.DeleteShape(shape)
def RemoveShape(self, shape):
self.diagram.DeleteShape(shape)
@Post_Undo
def keyPress(self, event):
"""
"""
key = event.GetKeyCode()
controlDown = event.CmdDown()
shiftDown = event.ShiftDown()
if key == 316: # right
move = False
step = 1 if controlDown else 10
for m in self.getSelectedShapes():
m.move(step, 0)
move = True
if not self.diagram.modify:
self.diagram.modify = True
if not move: event.Skip()
elif key == 314: # left
move = False
step = 1 if controlDown else 10
for m in self.getSelectedShapes():
m.move(-step, 0)
move = True
if not self.diagram.modify:
self.diagram.modify = True
if not move: event.Skip()
elif key == 315: # -> up
move = False
step = 1 if controlDown else 10
for m in self.getSelectedShapes():
m.move(0, -step)
move = True
if not self.diagram.modify:
self.diagram.modify = True
if not move: event.Skip()
elif key == 317: # -> down
move = False
step = 1 if controlDown else 10
for m in self.getSelectedShapes():
m.move(0, step)
move = True
if not self.diagram.modify:
self.diagram.modify = True
if not move: event.Skip()
elif key == 90 and controlDown and not shiftDown: # Undo
mainW = self.GetTopLevelParent()
tb = mainW.FindWindowByName('tb')
### find the tool from toolBar thanks to id
for tool in mainW.tools:
if tool.GetId() == wx.ID_UNDO:
button = tool
break
if tb.GetToolEnabled(wx.ID_UNDO):
### send commandEvent to simulate undo action on the toolBar
sendEvent(tb, button, wx.CommandEvent(wx.EVT_TOOL.typeId))
event.Skip()
elif key == 90 and controlDown and shiftDown:# Redo
mainW = self.GetTopLevelParent()
tb = mainW.FindWindowByName('tb')
### find the tool from toolBar thanks to id
for tool in mainW.tools:
if tool.GetId() == wx.ID_REDO:
button = tool
break
if tb.GetToolEnabled(wx.ID_REDO):
### send commandEvent to simulate undo action on the toolBar
sendEvent(tb, button, wx.CommandEvent(wx.EVT_TOOL.typeId))
event.Skip()
elif key == 127: # DELETE
self.OnDelete(event)
event.Skip()
elif key == 67 and controlDown: # COPY
self.OnCopy(event)
event.Skip()
elif key == 86 and controlDown: # PASTE
self.OnPaste(event)
event.Skip()
elif key == 88 and controlDown: # CUT
self.OnCut(event)
event.Skip()
elif key == 65 and controlDown: # ALL
for item in self.diagram.shapes:
self.select(item)
event.Skip()
elif key == 82 and controlDown: # Rotate model on the right
for s in [shape for shape in self.getSelectedShapes() if not isinstance(shape, ConnectionShape)]:
s.OnRotateR(event)
event.Skip()
elif key == 76 and controlDown: # Rotate model on the left
for s in [shape for shape in self.getSelectedShapes() if not isinstance(shape, ConnectionShape)]:
s.OnRotateL(event)
event.Skip()
elif key == 9: # TAB
if len(self.diagram.shapes) == 0:
event.Skip()
return
shape = self.select()
if shape:
ind = self.diagram.shapes.index(shape[0])
self.deselect()
try:
self.select(self.diagram.shapes[ind+1])
except:
self.select(self.diagram.shapes[0])
else:
self.select(self.diagram.shapes[0])
event.Skip()
else:
event.Skip()
self.Refresh()
def getWidth(self):
"""
"""
return self.GetSize()[0]
def getHeight(self):
"""
"""
return self.GetSize()[1]
def DoDrawing(self, dc):
"""
"""
dc.SetUserScale(self.scalex, self.scaley)
shapes = self.diagram.GetShapeList()
nodes = self.nodes
### resizeable node not nedeed for connection process (dragging mouse with left button pressed - see when self.resizeable_nedeed is False)
if not self.resizeable_nedeed:
nodes = [n for n in nodes if not isinstance(n,ResizeableNode)]
items = iter(shapes + nodes)
for item in items:
try:
item.draw(dc)
except Exception as info:
sys.stderr.write(_("Draw error: %s \n")%info)
def OnEraseBackground(self, evt):
"""
Handles the wx.EVT_ERASE_BACKGROUND event
"""
# This is intentionally empty, because we are using the combination
# of wx.BufferedPaintDC + an empty OnEraseBackground event to
# reduce flicker
pass
#dc = evt.GetDC()
#if not dc:
#dc = wx.ClientDC(self)
#rect = self.GetUpdateRegion().GetBox()
#dc.SetClippingRect(rect)
def OnPaint(self, event):
"""
"""
#pdc = wx.PaintDC(self)
# If you want to reduce flicker, a good starting point is to
# use wx.BufferedPaintDC.
pdc = wx.BufferedPaintDC(self)
# Initialize the wx.BufferedPaintDC, assigning a background
# colour and a foreground colour (to draw the text)
backColour = self.GetBackgroundColour()
backBrush = wx.Brush(backColour, wx.BRUSHSTYLE_SOLID)
pdc.SetBackground(backBrush)
pdc.Clear()
### to insure the correct redraw when window is scolling
### http://markmail.org/thread/hytqkxhpdopwbbro#query:+page:1+mid:635dvk6ntxsky4my+state:results
self.PrepareDC(pdc)
self.DoDrawing(pdc)
del pdc
@Post_Undo
def OnLock(self, event):
"""
"""
for s in self.getSelectedShapes():
if hasattr(s,'lock'):
s.lock()
@Post_Undo
def OnUnLock(self, event):
"""
"""
for s in self.getSelectedShapes():
if hasattr(s,'unlock'):
s.unlock()
def OnRightDown(self, event):
""" Mouse Right Down event manager.
"""
# if the timer used for the port number shortcut is active, we stop it.
if self.timer.IsRunning():
self.timer.Stop()
# current shape
s = self.getInterceptedShape(event)
# clic on canvas
if self.isSelected(s):
### call OnRightDown of selected object
s.OnRightDown(event)
else:
menu = Menu.ShapeCanvasPopupMenu(self)
### Show popup_menu
self.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
### Focus on canvas
#wx.CallAfter(self.SetFocus)
event.Skip()
def GetNodeLists(self, source, target):
"""
"""
# deselect and select target in order to get its list of node (because the node are generated dynamicly)
self.deselect()
self.select(target)
self.select(source)
nodesList = [n for n in self.nodes if not isinstance(n, ResizeableNode)]
# list of node list for
sourceNodeList = [n for n in nodesList if n.item == source and isinstance(n, ONode)]
targetNodeList = [n for n in nodesList if n.item == target and isinstance(n, INode)]
self.deselect()
return (sourceNodeList, targetNodeList)
def GetBlockModel(self):
""" Return the generator of block model (not ConnectionShape)
"""
for m in self.diagram.shapes:
if not isinstance(m, ConnectionShape):
yield m
def OnConnectTo(self, event):
"""
"""
id = event.GetId()
menu = event.GetEventObject()
#source model from diagram
source = self.getSelectedShapes()[0]
# Model Name
targetName = menu.GetLabelText(id)
sourceName = source.label
# get target model from its name
for s in self.GetBlockModel():
if s.label == targetName:
target = s
break
### init source and taget node list
self.sourceNodeList, self.targetNodeList = self.GetNodeLists(source, target)
# Now we, if the nodes list are not empty, the connection can be proposed form ConnectDialog
if self.sourceNodeList and self.targetNodeList:
if len(self.sourceNodeList) == 1 and len(self.targetNodeList) == 1:
self.makeConnectionShape(self.sourceNodeList[0], self.targetNodeList[0])
else:
self.dlgConnection = ConnectDialog.ConnectDialog(wx.GetApp().GetTopWindow(), wx.NewIdRef(), _("Connection Manager"), sourceName, self.sourceNodeList, targetName, self.targetNodeList)
self.dlgConnection.Bind(wx.EVT_BUTTON, self.OnDisconnect, self.dlgConnection._button_disconnect)
self.dlgConnection.Bind(wx.EVT_BUTTON, self.OnConnect, self.dlgConnection._button_connect)
self.dlgConnection.Bind(wx.EVT_CLOSE, self.OnCloseConnectionDialog)
self.dlgConnection.Show()
self.DiagramModified()
def OnDisconnect(self, event):
""" Disconnect selected ports from connectDialog.
"""
label_source, label_target = self.dlgConnection.GetLabelSource(), self.dlgConnection.GetLabelTarget()
# dialog results
sp,tp = self.dlgConnection.GetSelectedIndex()
### local variables
snl = len(self.sourceNodeList)
tnl = len(self.targetNodeList)
### flag to inform if there are modifications
modify_flag = False
### if selected options are not 'All'
if sp == tp == 0:
for cs in list(self.diagram.GetConnectionShapeGenerator()):
if (cs.getInput()[0].label == label_source and cs.getOutput()[0].label == label_target):
self.RemoveShape(cs)
modify_flag = True
elif sp == 0:
for cs in list(self.diagram.GetConnectionShapeGenerator()):
if (cs.getInput()[0].label == label_source and cs.getOutput()[0].label == label_target and cs.getOutput()[1] == tp-1):
self.RemoveShape(cs)
modify_flag = True
elif tp == 0:
for cs in list(self.diagram.GetConnectionShapeGenerator()):
if (cs.getInput()[0].label == label_source and cs.getInput()[1] == sp-1 and cs.getOutput()[0].label == label_target):
self.RemoveShape(cs)
modify_flag = True
else:
for cs in list(self.diagram.GetConnectionShapeGenerator()):
if (cs.getInput()[1] == sp-1) and (cs.getOutput()[1] == tp-1):
self.RemoveShape(cs)
modify_flag = True
### shape has been modified
if modify_flag:
self.DiagramModified()
self.deselect()
self.Refresh()
def OnConnect(self, event):
""" Connect selected ports from connectDialog.
"""
# dialog results
sp,tp = self.dlgConnection.GetSelectedIndex()
### local variables
snl = len(self.sourceNodeList)
tnl = len(self.targetNodeList)
### all select are "all"
if sp == tp == 0:
for i in range(snl):
try:
sn = self.sourceNodeList[i]
tn = self.targetNodeList[i]
self.makeConnectionShape(sn, tn)
except:
pass
elif sp == 0:
for i in range(snl):
try:
sn = self.sourceNodeList[i]
tn = self.targetNodeList[tp]
self.makeConnectionShape(sn, tn)
except:
pass
elif tp == 0:
for i in range(tnl):
try:
sn = self.sourceNodeList[sp]
tn = self.targetNodeList[i]
self.makeConnectionShape(sn, tn)
except:
pass
else:
sn = self.sourceNodeList[sp-1]
tn = self.targetNodeList[tp-1]
self.makeConnectionShape(sn,tn)
self.Refresh()
@Post_Undo
def makeConnectionShape(self, sourceNode, targetNode):
""" Make new ConnectionShape from input number(sp) to output number (tp)
"""
### add the connexion to the diagram
ci = ConnectionShape()
self.diagram.shapes.insert(0, ci)
### connexion
if isinstance(sourceNode, ONode):
ci.setInput(sourceNode.item, sourceNode.index)
ci.x[0], ci.y[0] = sourceNode.item.getPortXY('output', sourceNode.index)
ci.x[1], ci.y[1] = targetNode.item.getPortXY('input', targetNode.index)
ci.setOutput(targetNode.item, targetNode.index)
else:
ci.setInput(targetNode.item, targetNode.index)
ci.x[1], ci.y[1] = sourceNode.item.getPortXY('output', sourceNode.index)
ci.x[0], ci.y[0] = targetNode.item.getPortXY('input', targetNode.index)
ci.setOutput(sourceNode.item, sourceNode.index)
### select the new connection
self.deselect()
self.select(ci)
def OnCloseConnectionDialog(self, event):
"""
"""
### deselection de la dernier connection creer
self.deselect()
self.Refresh()
### Destroy the dialog
try:
self.dlgConnection.Destroy()
except:
pass
event.Skip()
def OnMiddleDown(self, event):
"""
"""
self.scroller.Start(event.GetPosition())
def OnMiddleUp(self, event):
"""
"""
self.scroller.Stop()
def OnCopy(self, event):
"""
"""
del clipboard[:]
for m in self.select():
clipboard.append(m)
# main windows statusbar update
printOnStatusBar(self.GetTopLevelParent().statusbar, {0:_('Copy'), 1:''})
event.Skip()
#def OnScroll(self, event):
##"""
##"""
#event.Skip()
def OnProperties(self, event):
""" Properties sub menu has been clicked. Event is transmit to the model
"""
# pour passer la fenetre principale à OnProperties qui est deconnecte de l'application du faite de popup
event.SetEventObject(self)
for s in self.select():
s.OnProperties(event)
def OnLog(self, event):
""" Log sub menu has been clicked. Event is transmit to the model
"""
event.SetClientData(self.GetTopLevelParent())
for s in self.select():
s.OnLog(event)
def OnEditor(self, event):
""" Edition sub menu has been clicked. Event is transmit to the model
"""
event.SetClientData(self.GetTopLevelParent())
for s in self.select():
s.OnEditor(event)
@staticmethod
def StartWizard(parent):
""" New model menu has been pressed. Wizard is instanciate.
"""
### arguments of ModelGeneratorWizard when right clic appears in canvas
kargs = {'title' : _('DEVSimPy Model Generator'),
'parent' : parent,
'img_filename' : os.path.join('bitmaps', DEVSIMPY_PNG)}
### right clic appears in a library
if not isinstance(parent, ShapeCanvas):
### Get path of the selected lib in order to change the last step of wizard
### TODO: GetFocusedItem failed on Linux!
sdp = parent.GetItemPyData(parent.GetFocusedItem())
kargs['specific_domain_path']=sdp
gmwiz = WizardGUI.ModelGeneratorWizard(**kargs)
gmwiz.run()
### just for Mac
if not gmwiz.canceled_flag:
return gmwiz
else:
return None
def OnStartWizard(self, event):
"""
"""
obj = event.GetEventObject()
### if right clic on canvas
parent = self if isinstance(obj, Menu.ShapeCanvasPopupMenu) else wx.GetApp().GetTopWindow().GetControlNotebook().GetTree()
gmwiz = ShapeCanvas.StartWizard(parent)
return gmwiz
def OnRefreshModels(self, event):
""" New model menu has been pressed. Wizard is instanciate.
"""
diagram = self.GetDiagram()
for block in diagram.GetFlatBlockShapeList():
block.status_label = ""
color = CodeBlock.FILL if isinstance(block, CodeBlock) else ContainerBlock.FILL
block.fill = color
self.UpdateShapes([self])
def OnNewModel(self, event):
""" New model menu has been pressed. Wizard is instanciate.
"""
### mouse positions
xwindow, ywindow = wx.GetMousePosition()
xm,ym = self.ScreenToClient(wx.Point(int(xwindow), int(ywindow)))
gmwiz = self.OnStartWizard(event)
# if wizard is finished witout closing
if gmwiz :
m = Components.BlockFactory.CreateBlock( canvas = self,
x = xm,
y = ym,
label = gmwiz.label,
id = gmwiz.id,
inputs = gmwiz.inputs,
outputs = gmwiz.outputs,
python_file = gmwiz.python_path,
model_file = gmwiz.model_path,
specific_behavior = gmwiz.specific_behavior)
if m:
### save visual model
if gmwiz.overwrite_flag and isinstance(m, Block):
if m.SaveFile(gmwiz.model_path):
m.last_name_saved = gmwiz.model_path
else:
dlg = wx.MessageDialog(self, \
_('Error saving file %s\n')%os.path.basename(gmwiz.model_path), \
gmwiz.label, \
wx.OK | wx.ICON_ERROR)
dlg.ShowModal()
# Adding graphical model to diagram
self.AddShape(m)
sys.stdout.write(_("Adding DEVSimPy model: \n"))
sys.stdout.write(repr(m))
# try to update the library tree on left panel
#tree = wx.GetApp().GetTopWindow().tree
#tree.UpdateDomain(str(os.path.dirname(gmwiz.model_path)))
# focus
#wx.CallAfter(self.SetFocus)
# Cleanup
gmwiz.Destroy()
@BuzyCursorNotification
def OnPaste(self, event):
""" Paste menu has been clicked.
"""
D = {} # correspondance between the new and the paste model
L = [] # list of original connectionShape components
for m in clipboard:
if isinstance(m, ConnectionShape):
L.append(copy.copy(m))
else:
# make new shape
try:
newShape = m.Copy()
except:
sys.stdout.write(_('Error in Past'))
else:
# store correspondance (for coupling)
D[m]= newShape
# move new modele
newShape.x[0] += 35
newShape.x[1] += 35
newShape.y[0] += 35
newShape.y[1] += 35
#rename new model with number
if re.match(r'^([A-Za-z_])+([0-9]+)', newShape.label):
number_part = re.findall(r'\d+', newShape.label)[-1]
### increment the number
newShape.label = re.sub(r'([0-9]+)', str(int(number_part)+1), newShape.label)
### label has not number and we add it
else:
newShape.label = ''.join([newShape.label,'_1'])
### adding model
self.AddShape(newShape)
### adding connectionShape only if the connection is connected on the two side with blocks.
for cs in [a for a in L if a.input[0] in D and a.output[0] in D]:
cs.input = (D[cs.input[0]],cs.input[1])
cs.output = (D[cs.output[0]],cs.output[1])
self.AddShape(cs)
# specify the operation in status bar
printOnStatusBar(self.GetTopLevelParent().statusbar, {0:_('Paste'), 1:''})
event.Skip()
def OnCut(self, event):
""" Cut menu has been clicked. Copy and delete event.
"""
self.OnCopy(event)
self.OnDelete(event)
event.Skip()
def OnDelete(self, event):
""" Delete menu has been clicked. Delete all selected shape.
"""
if len(self.select()) > 1:
msg = _("Do you really want to delete all selected models?")
dlg = wx.MessageDialog(self, msg, _("Delete Manager"), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
if dlg.ShowModal() not in [wx.ID_NO, wx.ID_CANCEL]:
for s in self.select():
self.diagram.DeleteShape(s)
dlg.Destroy()
else:
for s in self.select():
name = _("Connexion") if isinstance(s, ConnectionShape) else s.label
msg = _("Do you really want to delete %s model?")%(name)
dlg = wx.MessageDialog(self, msg,
_("Delete Manager"),
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
if dlg.ShowModal() not in [wx.ID_NO, wx.ID_CANCEL]:
self.diagram.DeleteShape(s)
dlg.Destroy()
self.DiagramModified()
self.deselect()
def DiagramReplace(self, d):
"""
"""
self.diagram = d
self.DiagramModified()
self.deselect()
self.Refresh()
def OnRightUp(self,event):
"""
"""
try:
self.getInterceptedShape(event).OnRightUp(event)
except AttributeError:
pass
event.Skip()
def OnRightDClick(self,event):
"""
"""
try:
self.getInterceptedShape(event).OnRightDClick(event)
except AttributeError:
pass
event.Skip()
def OnLeftDClick(self,event):
"""
"""
model = self.getInterceptedShape(event)
if model:
try:
model.OnLeftDClick(event)
except Exception as info:
wx.MessageBox(_("An error is occured during double clic: %s")%info)
event.Skip()
def Undo(self):
"""
"""
mainW = self.GetTopLevelParent()
### dump solution
### if parent is not none, the dumps dont work because parent is copy of a class
try:
t = pickle.loads(self.stockUndo[-1])
### we add new undo of diagram has been modified or one of the shape in diagram sotried in stockUndo has been modified.
if any(objA.__dict__ != objB.__dict__ for objA in self.diagram.GetShapeList() for objB in t.GetShapeList()):
self.stockUndo.append(pickle.dumps(obj=self.diagram, protocol=0))
except IndexError:
### this is the first call of Undo and StockUndo is empty
self.stockUndo.append(pickle.dumps(obj=self.diagram, protocol=0))
except TypeError as info:
sys.stdout.write(_("Error trying to undo (TypeError): %s \n"%info))
except Exception as info:
sys.stdout.write(_("Error trying to undo: %s \n"%info))
else:
### just for init (white diagram)
if self.diagram.GetBlockCount()>=1:
### toolBar
tb = mainW.FindWindowByName('tb')
tb.EnableTool(wx.ID_UNDO, True)
self.diagram.parent = self
### note that the diagram is modified
self.diagram.modify = True
self.DiagramModified()
def OnLeftDown(self,event):
""" Left Down mouse bouton has been invoked in the canvas instance.
"""
if self.timer.IsRunning():
self.timer.Stop()
### get current shape
item = self.getInterceptedShape(event)
### clicked on empty space deselect all
if item is None:
self.deselect()
### recover focus
if wx.Window.FindFocus() != self:
self.SetFocus()
## Left mouse button down, change cursor to
## something else to denote event capture
if not self.HasCapture():
self.CaptureMouse()
self.overlay = wx.Overlay()
if isinstance(event,wx.MouseEvent):
# point = event.GetPosition()
point = self.getEventCoordinates(event)
self.selectionStart = point
elif item not in self.getSelectedShapes():
item.OnLeftDown(event) # send leftdown event to current shape
if isinstance(item, Selectable) and not event.ControlDown():
self.deselect()
self.select(item)
else:
for s in self.getSelectedShapes():
s.OnLeftDown(event) # send leftdown event to current shape
self.deselect(item)
### Update the nb1 panel properties only for Block and Port (call update in ControlNotebook)
win = getTopLevelWindow()
nb1 = win.GetControlNotebook()
pos = nb1.GetSelection()
txt = nb1.GetPageText(pos)
### Update the editor panel (on the right by default) when a block is selected
mgr = win.GetMGR()
mgr.GetPane("editor")
if (mgr.GetPane("editor").IsShown() or txt.startswith('Prop')) and isinstance(item, Attributable):
self.__state['model'] = item
self.__state['canvas'] = self
self.notify()
# self.Refresh()
event.Skip()
###
@Post_Undo
def OnLeftUp(self, event):
"""
"""
cursor = self.GetCursor()
if cursor != wx.StockCursor(wx.CURSOR_ARROW):
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
### intercepted shape
shape = self.getInterceptedShape(event)
### if connection is being, then we try to avoid the bug that appears when the mouse coursor and the end of the connection are superposed.
### So, x and y is the coordinate of the end of the connection and we try to find the shape targeted by the connection (Block or Port shape type)
### If HitTest between the point and the targeted shape and if the the getInterceptedShape dont return a Node (whiwh is the case when the cross appears to connect)
### the targeted shape is the returned.
if isinstance(shape, ConnectionShape):
cs = shape
x = cs.x[0] if not cs.input else cs.x[-1]
y = cs.y[0] if not cs.output else cs.y[-1]
for s in iter(s for s in self.diagram.shapes if isinstance(s, (Block,Port))):
if s.HitTest(x,y) and s != cs:
shape = s
break
### if cursor of mouse is in connectionShape (drag slowly), getInterceptedShape return the connectionShape Block.
### So, if the connectionShape is not connected, shape is none and the connectionShape is not drawed.
### If we left up on an existing connectionShape, nothing append
if isinstance(shape,ConnectionShape) and (not shape.input or not shape.output):
shape = None
self.resizeable_nedeed = True
### clic sur un block ou un node pour connection à block
if shape:
shape.OnLeftUp(event)
shape.leftUp(self.select())
remove = True
### connectionShape
cs = next(iter(s for s in self.select() if isinstance(s, ConnectionShape)),None)
if cs:
### empty connection manager
#for cs in [s for s in self.select() if isinstance(s, ConnectionShape)]:
### restore solid connection
if len(cs.pen)>2:
cs.pen[2]= 100 #wx.PENSTYLE_SOLID
### if left up on block
if None in (cs.output, cs.input):
### new link request
dlg = wx.TextEntryDialog(self, _('Choose the port number.\nIf doesn\'t exist, we create it.'),_('Coupling Manager'))
if cs.input is None:
if isinstance(shape,Port):
dlg.SetValue('0')
else:
if hasattr(shape, 'output'):
dlg.SetValue(str(shape.output))
else:
dlg.Destroy()
if dlg.ShowModal() == wx.ID_OK:
try:
val=int(dlg.GetValue())
### is not digit
except ValueError:
pass
else:
if val >= shape.output:
nn = shape.output
shape.output+=1
else:
nn = val
cs.input = (shape, nn)
### dont abort the link
remove = False
else:
if isinstance(shape,Port):
dlg.SetValue('0')
else:
if hasattr(shape, 'input'):
dlg.SetValue(str(shape.input))
else:
dlg.Destroy()
if dlg.ShowModal() == wx.ID_OK:
try:
val=int(dlg.GetValue())
### is not digit
except ValueError:
pass
else:
if val >= shape.input:
nn = shape.input
shape.input+=1
else:
nn = val
cs.output = (shape, nn)
### dont abort the link
remove = False
if remove:
self.diagram.DeleteShape(cs)
self.deselect()
else:
### transformation de la connection en zigzag
pass
### click on canvas
else:
### Rubber Band with overlay
## User released left button, change cursor back
if self.HasCapture():
try:
self.ReleaseMouse()
except:
sys.stdout.write(_("Error in Release Mouse!"))
else:
self.permRect = None
if isinstance(event,wx.MouseEvent):
point = self.getEventCoordinates(event)
if self.selectionStart != point:
self.permRect = wx.Rect(self.selectionStart, point)
if self.permRect:
self.selectionStart = None
self.overlay.Reset()
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
## gestion des shapes qui sont dans le rectangle permRect
for s in self.diagram.GetShapeList():
x,y = self.getScalledCoordinates(s.x[0],s.y[0])
w = (s.x[1]-s.x[0])*self.scalex
h = (s.y[1]-s.y[0])*self.scaley
recS = wx.Rect(int(x),int(y),int(w),int(h))
# rect hit test
if self.permRect.Contains(recS):
self.select(s)
else:
### shape is None and we remove the connectionShape
for item in [s for s in self.select() if isinstance(s, ConnectionShape)]:
self.diagram.DeleteShape(item)
self.deselect()
self.Refresh()
event.Skip()
def OnTimer(self, event):
"""
"""
if self.f:
self.f.Show()
def OnMouseEnter(self, event):
"""
"""
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
def OnMouseLeave(self, event):
"""
"""
pass
#self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
def DiagramModified(self):
""" Modification printing in statusbar and modify value manager.
This method manage the propagation of modification.
from window where modifications are performed to DEVSimPy main window.
"""
if not self.diagram.modify:
return
### window where modification is performed
win = self.GetTopLevelParent()
if isinstance(win, DetachedFrame):
### main window
mainW = wx.GetApp().GetTopWindow()
if not isinstance(mainW, DetachedFrame) and hasattr(mainW, 'GetDiagramNotebook'):
nb2 = mainW.GetDiagramNotebook()
s = nb2.GetSelection()
canvas = nb2.GetPage(s)
diagram = canvas.GetDiagram()
### modifying propagation
diagram.modify = True
### update general shapes
canvas.UpdateShapes()
label = nb2.GetPageText(s)
### modified windows dictionary
D = {win.GetTitle(): win, label: mainW}
else:
D={}
else:
nb2 = win.GetDiagramNotebook()
s = nb2.GetSelection()
canvas = nb2.GetPage(s)
diagram = canvas.GetDiagram()
diagram.modify = True
label = nb2.GetPageText(s)
D = {label : win}
### update the text of the notebook tab to notifiy that the file is modified
nb2.SetPageText(nb2.GetSelection(), "%s*"%label.replace('*',''))
### statusbar printing
for string,win in list(D.items()):
printOnStatusBar(win.statusbar, {0:"%s %s"%(string.replace('*','') ,_("modified")), 1:diagram.last_name_saved, 2:''})
### update the toolbar
tb = win.GetToolBar()
tb.EnableTool(Menu.ID_SAVE, self.diagram.modify)
def ShowQuickAttributeEditor(self, selectedShape:list)->None:
""" Show a quick dialog with spin to change the number of imput and output of the block.
"""
mainW = self.GetTopLevelParent()
flag = True
### find if window exists on the top of model, then inactive the QuickAttributeEditor
for win in [w for w in mainW.GetChildren() if w.IsTopLevel()]:
if win.IsActive():
flag = False
if self.f:
self.f.Close()
self.f = None
if flag:
# mouse positions
xwindow, ywindow = wx.GetMousePosition()
xm,ym = self.ScreenToClient(wx.Point(int(xwindow), int(ywindow)))
for s in {m for m in selectedShape if isinstance(m, Block)}:
x = s.x[0]*self.scalex
y = s.y[0]*self.scaley
w = s.x[1]*self.scalex-x
h = s.y[1]*self.scaley-y
# if mousse over hight the shape
try:
if (x<=xm and xm < x+w) and (y<=ym and ym < y+h):
#if self.isSelected(s) and flag:
self.f = QuickAttributeEditor(self, wx.NewIdRef(), s)
self.timer.Start(1000)
break
else:
if self.timer.IsRunning():
self.timer.Stop()
except AttributeError as info:
raise AttributeError(_("use >= wx-2.8-gtk-unicode library: %s")%info)
def OnIdle(self,event):
"""
"""
if self.refresh_need:
self.Refresh(False)
###
def OnMotion(self, event):
""" Motion manager.
"""
### current cursor
cursor = self.GetCursor()
sc = self.getSelectedShapes()
self.refresh_need = True
self.resizeable_nedeed = True
if event.Dragging() and event.LeftIsDown():
self.diagram.modify = False
if len(sc) == 0:
# User is dragging the mouse, check if
# left button is down
if self.HasCapture():
if cursor != wx.StockCursor(wx.CURSOR_ARROW):
cursor = wx.StockCursor(wx.CURSOR_ARROW)
dc = wx.ClientDC(self)
odc = wx.DCOverlay(self.overlay, dc)
odc.Clear()
ctx = wx.GraphicsContext.Create(dc)
ctx.SetPen(wx.GREY_PEN)
ctx.SetBrush(wx.Brush(wx.Colour(229,229,229,80)))
try:
ctx.DrawRectangle(*wx.Rect(self.selectionStart, event.GetPosition()))
except TypeError:
pass
del odc
### no need to refresh in order to qhpw the rectangle
self.refresh_need = False
# else:
# self.Refresh(False)
else:
point = self.getEventCoordinates(event)
x = point[0] - self.currentPoint[0]
y = point[1] - self.currentPoint[1]
if cursor != wx.StockCursor(wx.CURSOR_HAND):
cursor = wx.StockCursor(wx.CURSOR_HAND)
for s in sc:
s.move(x,y)
### change cursor when resizing model
if isinstance(s, ResizeableNode) and cursor != wx.StockCursor(wx.CURSOR_SIZING):
cursor = wx.StockCursor(wx.CURSOR_SIZING)
### change cursor when connectionShape hit a node
elif isinstance(s, ConnectionShape):
### dot trace to prepare connection
if len(s.pen)>2:
s.pen[2]= wx.PENSTYLE_DOT
if cursor != wx.StockCursor(wx.CURSOR_HAND):
cursor = wx.StockCursor(wx.CURSOR_HAND)
for node in [n for n in self.nodes if isinstance(n, ConnectableNode) and n.HitTest(point[0], point[1])]:
if cursor != wx.StockCursor(wx.CURSOR_CROSS):
cursor = wx.StockCursor(wx.CURSOR_CROSS)
self.resizeable_nedeed = False
### update the cursor
self.SetCursor(cursor)
### update modify
self.diagram.modify = True
### update current point
self.currentPoint = point
### refresh all canvas with Flicker effect corrected in OnPaint and OnEraseBackground
# self.Refresh(False)
# pop-up to change the number of ports
else:
point = self.getEventCoordinates(event)
### restor the cursor with arrow
if cursor != wx.StockCursor(wx.CURSOR_ARROW):
cursor = wx.StockCursor(wx.CURSOR_ARROW)
for node in self.nodes:
if node.HitTest(point[0],point[1]):
### change the cursor when node is pointed
if isinstance(node, ResizeableNode) and cursor != wx.StockCursor(wx.CURSOR_SIZING):
cursor = wx.StockCursor(wx.CURSOR_SIZING)
elif isinstance(node, ConnectableNode) and cursor != wx.StockCursor(wx.CURSOR_CROSS):
cursor = wx.StockCursor(wx.CURSOR_CROSS)
### update the cursor
self.SetCursor(cursor)
if len(sc) != 0:
### show the quick attribut editor
self.ShowQuickAttributeEditor(sc)
event.Skip()
#self.DiagramModified()
# def SetDiagram(self, diagram):
# """ Setter for diagram attribute.
# """
# self.diagram = diagram def SetDiagram(self, diagram):
# """ Setter for diagram attribute.
# """
# self.diagram = diagram
def GetDiagram(self):
""" Return Diagram instance.
"""
return self.diagram
def getInterceptedShape(self, event, exclude=[]):
""" Return the intercepted current shape.
"""
# get coordinate of click in our coordinate system
if isinstance(event, wx.MouseEvent):
point = self.getEventCoordinates(event)
self.currentPoint = point
elif isinstance(event, wx.Point):
point = event
self.currentPoint = point
else:
return None
# Look to see if an item is selected
for item in self.nodes + self.diagram.shapes:
if item.HitTest(point[0], point[1]) and item not in exclude:
return item
return None
def GetXY(self, m, x, y):
""" Give x and y of model m into canvas.
"""
dx = (m.x[1]-m.x[0])
dy = (m.y[1]-m.y[0])
ux,uy = self.getScalledCoordinates(x,y)
#ux, uy = canvas.CalcUnscrolledPosition(x-dx, y-dy)
return (ux-dx,uy-dy)
def getScalledCoordinates(self, x, y):
""" Return coordiante depending on the zoom.
"""
originX, originY = self.GetViewStart()
unitX, unitY = self.GetScrollPixelsPerUnit()
return ((x + (originX * unitX))/ self.scalex, (y + (originY * unitY))/ self.scaley)
def getEventCoordinates(self, event):
""" Return the coordinates from event.
"""
return self.getScalledCoordinates(event.GetX(),event.GetY())
def getSelectedShapes(self):
""" Retrun the list of selected object on the canvas (Connectable nodes are excluded)
"""
return self.selectedShapes
def isSelected(self, s):
""" Check of shape s is selected.
If s is a ConnectableNode object, it implies that is visible and then selected !
"""
return (s) and (s in self.getSelectedShapes()) or isinstance(s, ConnectableNode)
def getName(self):
""" Return the name
"""
return self.name
def deselect(self, item=None):
""" Deselect all shapes
"""
if item is None:
for s in self.getSelectedShapes():
s.OnDeselect(None)
del self.selectedShapes[:]
del self.nodes[:]
else:
self.nodes = [ n for n in self.nodes if n.item != item]
self.selectedShapes = [ n for n in self.getSelectedShapes() if n != item]
item.OnDeselect(None)
### selectionne un shape
def select(self, item=None):
""" Select the models in param item
"""
if item is None:
return self.getSelectedShapes()
if isinstance(item, Node):
del self.selectedShapes[:]
self.selectedShapes.append(item) # items here is a single node
return
if not self.isSelected(item):
self.selectedShapes.append(item)
item.OnSelect(None)
if isinstance(item, Connectable):
### display the label of input ports if exist
for n in range(item.input):
self.nodes.append(INode(item, n, self, item.getInputLabel(n)))
### display the label of output ports if exist
for n in range(item.output):
self.nodes.append(ONode(item, n, self, item.getOutputLabel(n)))
### display the label of port attahced to the connection shape (make more easy the correspondance of the link)
if isinstance(item, ConnectionShape):
if item.input:
block, n = item.input
self.nodes.append(ONode(block, n, self, block.getOutputLabel(n)))
if item.output:
block, n = item.output
self.nodes.append(INode(block, n, self, block.getInputLabel(n)))
if isinstance(item, Resizeable):
self.nodes.extend([ResizeableNode(item, n, self) for n in range(len(item.x))])
return
###
def UpdateShapes(self, L=None):
""" Method that update the graphic of the models composing the parameter list.
"""
# update all models in canvas
if L is None:
L = self.diagram.shapes
# select all models in selectedList and refresh canvas
for m in [a for a in L if self.isSelected(a)]:
self.deselect(m)
self.select(m)
self.Refresh()
### selection sur le canvas les ONodes car c'est le seul moyen d'y accéder pour effectuer l'appartenance avec les modèles
def showOutputs(self, item=None):
""" Populate nodes list with output ports.
"""
if item:
self.nodes.extend([ONode(item, n, self, item.getInputLabel(n)) for n in range(item.output)])
else:
for s in [a for a in self.diagram.shapes if isinstance(a, Connectable)]:
self.nodes.extend([ONode(s, n, self, s.getInputLabel(n)) for n in range(s.output)])
### selection sur le canvas les INodes car c'est le seul moyen d'y accéder pour effectuer l'appartenance avec les modèles
def showInputs(self,item=None):
""" Populate nodes list with output ports.
"""
if item:
self.nodes.extend([INode(item, n, self, item.getInputLabel(n)) for n in range(item.input)])
else:
for s in [a for a in self.diagram.shapes if isinstance(a, Connectable)]:
self.nodes.extend([INode(s, n, self, s.getInputLabel(n)) for n in range(s.input)])
def GetState(self):
return self.__state
#-----------------------------------------------------------
class LinesShape(Shape):
""" Line Shape Class.
"""
def __init__(self, line):
""" Constructor.
"""
Shape.__init__(self)
self.fill = [RED]
self.x = array.array('d', line.x)
self.y = array.array('d', line.y)
def draw(self, dc):
""" Drawing line.
"""
Shape.draw(self, dc)
L = list(zip(self.x,self.y)) #list(map(lambda a,b: (a,b), self.x, self.y))
### line width
w = self.x[1] - self.x[0]
### update L depending of the connector type
if ShapeCanvas.CONNECTOR_TYPE == 'linear':
### left moving
if w > 0:
### output port
if self.input:
L.insert(1, (self.x[0]+w/10, self.y[0]))
L.insert(2, (self.x[1]-w/10, self.y[1]))
else:
L.insert(1, (self.x[0]+w/10, self.y[0]))
L.insert(2, (self.x[1]-w/10, self.y[1]))
### right moving
else:
### output port
if self.input:
L.insert(1, (self.x[0]-w/10, self.y[0]))
L.insert(2, (self.x[1]-w/10, self.y[1]))
else:
L.insert(1, (self.x[0]+w/10, self.y[0]))
L.insert(2, (self.x[1]+w/10, self.y[1]))
elif ShapeCanvas.CONNECTOR_TYPE == 'square':
L.insert(1,(self.x[0]+w/2, self.y[0]))
L.insert(2,(self.x[0]+w/2, self.y[1]))
else:
pass
integer_list = [(int(x), int(y)) for x, y in L]
dc.DrawLines(integer_list)
### pour le rectangle en bout de connexion
dc.SetBrush(wx.Brush(RED_LIGHT))
pt1 = wx.Point(int(self.x[-1]-10/2), int(self.y[-1]-10/2))
pt2 = wx.Point(int(self.x[0]-10/2), int(self.y[0]-10/2))
wx.DC.DrawRectangle(dc, pt1, wx.Size(10, 10))
wx.DC.DrawRectangle(dc, pt2, wx.Size(10, 10))
#dc.DrawPolygon(( wx.Point(self.x[-1]-10, self.y[-1]-10),
# wx.Point(self.x[-1]-10, self.y[-1]+10),
# wx.Point(self.x[-1], self.y[-1]),
# wx.Point(self.x[-1]-10, self.y[-1]-10)))
def HitTest(self, x, y):
"""
"""
if x < min(self.x)-3:return False
if x > max(self.x)+3:return False
if y < min(self.y)-3:return False
if y > max(self.y)+3:return False
ind = 0
try:
while 1:
x1 = self.x[ind]
y1 = self.y[ind]
x2 = self.x[ind+1]
y2 = self.y[ind+1]
top= (x-x1) *(x2 - x1) + (y-y1)*(y2-y1)
distsqr = pow(x1-x2, 2) + pow(y1-y2, 2)
u = float(top)/float(distsqr)
newx = x1 + u*(x2-x1)
newy = y1 + u*(y2-y1)
dist = pow(pow(newx-x, 2) + pow(newy-y, 2),.5)
if dist < 7:
return True
ind = ind + 1
except:
pass
return False
def OnLeftDClick(self, event):
""" Left click has been invoked.
"""
### canvas containing LinesShape
canvas = event.GetEventObject()
try:
### mouse positions
if wx.VERSION_STRING < '4.0':
x,y = event.GetPositionTuple()
else:
xwindow, ywindow = wx.GetMousePosition()
x,y = canvas.ScreenToClient(wx.Point(int(xwindow), int(ywindow)))
except Exception as info:
sys.stdout.write(_("Error in OnLeftDClick for %s : %s\n")%(self,info))
else:
### add point at the position according to the possible zoom (use of getScalledCoordinates)
self.AddPoint(canvas.getScalledCoordinates(x,y))
event.Skip()
def HasPoint(self, point):
""" Point is included in line ?
"""
x,y = point
return (x in self.x) and (y in self.y)
def AddPoint(self, point = (0,0)):
""" Add point under LineShape.
"""
x,y = point
# insertion sur les morceaux de droites d'affines
for i in range(len(self.x)-1):
x1 = self.x[i]
x2 = self.x[i+1]
y1 = self.y[i]
y2 = self.y[i+1]
if (x1<=x<=x2 and y1<=y<=y2) or ((x1<=x<=x2 and y2<y<y1) or (x2<=x<=x1 and y1<=y<=y2) or (x2<=x<=x1 and y2<=y<=y1)):
self.x.insert(i+1, x)
self.y.insert(i+1, y)
#### cassure des locks
self.unlock()
break
# Mixins------------------------------------------------------------------------
###---------------------------------------------------------------------------------------------------------
# NOTE: Testable << object :: Testable mixin is needed to manage tests files and tests executions. It add the OnTestEditor event for the tests files edition
class Testable(object):
# NOTE: Testable :: OnTestEditor => new event for AMD model. Open tests files in editor
def OnTestEditor(self, event):
"""
"""
L = self.GetTestFile()
### create Editor with BDD files in tab
if L:
#model_path = os.path.dirname(self.python_path)
# TODO: Testable :: OnTestEditor => Fix Editor importation
import Editor
#mainW = wx.GetApp().GetTopWindow()
### Editor instanciation and configuration---------------------
editorFrame = Editor.GetEditor(
None,
wx.NewIdRef(),
'Features',
file_type="test"
)
for i,s in enumerate([os.path.join(self.model_path, l) for l in L]):
editorFrame.AddEditPage(L[i], s)
editorFrame.Show()
### -----------------------------------------------------------
def GetTestFile(self):
""" Get Test file only for AMD model
"""
# If selected model is AMD
if self.isAMD():
# Create tests files is doesn't exist
if not ZipManager.Zip.HasTests(self.model_path):
self.CreateTestsFiles()
### list of BDD files
L = ZipManager.Zip.GetTests(self.model_path)
return L
return []
# NOTE: Testable :: isAMD => Test if the model is an AMD and if it's well-formed
def isAMD(self):
fn = os.path.dirname(self.python_path)
return zipfile.is_zipfile(fn) and fn.endswith(('.amd')) if os.path.isfile(fn) else False
def isCMD(self):
fn = os.path.dirname(self.python_path)
return zipfile.is_zipfile(fn) and fn.endswith(('.cmd')) if os.path.isfile(fn) else False
def isPYC(self):
return self.python_path.endswith('.pyc') if os.path.isfile(self.python_path) else False
def isPY(self):
return self.python_path.endswith('.py') if os.path.isfile(self.python_path) else False
# NOTE: Testable :: CreateTestsFiles => AMD tests files creation
def CreateTestsFiles(self):
devsPath = os.path.dirname(self.python_path)
name = os.path.splitext(os.path.basename(self.python_path))[0]
zf = ZipManager.Zip(devsPath)
feat, steps, env = self.CreateFeature(), self.CreateSteps(), Testable.CreateEnv()
zf.Update([os.path.join('BDD', feat), os.path.join('BDD', steps), os.path.join('BDD', env)])
if os.path.exists(feat): os.remove(feat)
if os.path.exists(steps): os.remove(steps)
if os.path.exists(env): os.remove(env)
#if not zf.HasTests():
#files = zf.GetTests()
#if not '%s.feature'%name in files:
#feat = self.CreateFeature()
#zf.Update([os.path.join('BDD', feat)])
#os.remove(feat)
#if not 'steps.py' in files:
#steps = self.CreateSteps()
#zf.Update([os.path.join('BDD',steps)])
#os.remove(steps)
#if not 'environment.py' in files:
#env = self.CreateEnv()
#zf.Update([os.path.join('BDD',env)])
#os.remove(env)
# NOTE: Testable :: CreateFeature => Feature file creation
def CreateFeature(self):
name = os.path.splitext(os.path.basename(self.python_path))[0]
feature = "%s.feature"%name
with open(feature, 'w+') as feat:
feat.write("# -*- coding: utf-8 -*-\n")
return feature
# NOTE: Testable :: CreateSteps => Steps file creation
def CreateSteps(self):
steps = "steps.py"
with open(steps, 'w+') as step:
step.write("# -*- coding: utf-8 -*-\n")
return steps
# NOTE: Testable :: CreateEnv => Environment file creation
@staticmethod
def CreateEnv(path=None):
if path:
environment = os.path.join(path, 'environment.py')
else:
environment = "environment.py"
with open(environment, 'w+') as env:
env.write("# -*- coding: utf-8 -*-\n")
return environment
# NOTE: Testable :: GetTempTests => Create tests on temporary folder for execution
def GetTempTests(self, global_env=None):
if not global_env: global_env = False
### Useful vars definition-----------------------------------------------------------------
model_path = os.path.dirname(self.python_path)
basename = os.path.basename(self.python_path)
name = os.path.splitext(basename)[0]
tests_files = ZipManager.Zip.GetTests(model_path)
### ---------------------------------------------------------------------------------------
tempdir = os.path.realpath(gettempdir())
### Folder hierarchy construction----------------------------------------------------------
feat_dir = os.path.join(tempdir, "features")
steps_dir = os.path.join(feat_dir, "steps")
if not os.path.exists(feat_dir):
os.mkdir(feat_dir)
if not os.path.exists(steps_dir):
os.mkdir(steps_dir)
### ---------------------------------------------------------------------------------------
### AMD unzip------------------------------------------------------------------------------
amd_dir = os.path.join(tempdir, "AtomicDEVS")
if not os.path.exists(amd_dir):
os.mkdir(amd_dir)
### ---------------------------------------------------------------------------------------
### Tests code retriever-------------------------------------------------------------------
importer = zipfile.ZipFile(model_path)
feat_name = [t for t in tests_files if t.endswith('.feature')][0]
featInfo = importer.getinfo(feat_name)
feat_code = importer.read(featInfo)
steps_name = [t for t in tests_files if t.endswith('steps.py')][0]
stepsInfo = importer.getinfo(steps_name)
steps_code = importer.read(stepsInfo)
if not global_env:
environment_name = [t for t in tests_files if t.endswith('environment.py')][0]
envInfo = importer.getinfo(environment_name)
env_code = importer.read(envInfo)
else:
environment_name = os.path.join(tempdir, 'environment.py')
with open(environment_name, 'r+') as global_env_code:
env_code = global_env_code.read()
importer.close()
### ---------------------------------------------------------------------------------------
### AMD code retriever---------------------------------------------------------------------
importer = zipfile.ZipFile(model_path)
amd_name = ZipManager.getPythonModelFileName(model_path)
amd_info = importer.getinfo(amd_name)
amd_code = importer.read(amd_info)
### ---------------------------------------------------------------------------------------
### Tests files creation in temporary directory--------------------------------------------
tempFeature = os.path.join(feat_dir, "%s.feature"%name)
tempEnv = os.path.join(feat_dir, "environment.py")
tempSteps = os.path.join(steps_dir, "%s_steps.py"%name)
tempAMD = os.path.join(amd_dir, amd_name)
with open(tempFeature, 'w+') as feat:
feat.write(feat_code)
with open(tempSteps, 'w+') as steps:
steps.write(steps_code)
with open(tempEnv, 'w+') as env:
env.write(env_code)
with open(tempAMD, 'w+') as AMD:
AMD.write(amd_code)
### ---------------------------------------------------------------------------------------
return tempFeature, tempSteps, tempEnv
# NOTE: Testable :: RemoveTempTests => Remove tests on temporary folder
@staticmethod
def RemoveTempTests():
tempdir = os.path.realpath(gettempdir())
feat_dir = os.path.join(tempdir, 'features')
if os.path.exists(feat_dir):
for root, dirs, files in os.walk(feat_dir, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs: os.rmdir(os.path.join(root, name))
os.rmdir(feat_dir)
amd_dir = os.path.join(tempdir, 'AtomicDEVS')
if os.path.exists(amd_dir):
for root, dirs, files in os.walk(amd_dir, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(amd_dir)
#---------------------------------------------------------
class ConnectionShape(LinesShape, Resizeable, Selectable, Structurable):
""" ConnectionShape class
"""
def __init__(self):
""" Constructor
"""
LinesShape.__init__(self, LineShape(0,0,1,1))
Resizeable.__init__(self)
Structurable.__init__(self)
#Convertible.__init__(self, 'direct')
self.input = None
self.output = None
self.touch_list = []
self.lock_flag = False # move lock
def __setstate__(self, state):
""" Restore state from the unpickled state values.
"""
####################################" Just for old model
if 'touch_list' not in state: state['touch_list'] = []
if 'font' not in state: state['font'] = [FONT_SIZE, 74, 93, 700, u'Arial']
##############################################
self.__dict__.update(state)
def setInput(self, item, index):
"""
"""
self.input = (item, index)
def setOutput(self, item, index):
"""
"""
self.output = (item, index)
def getInput(self):
"""
"""
return self.input
def getOutput(self):
"""
"""
return self.output
def draw(self, dc):
"""
"""
if self.input:
self.x[0], self.y[0] = self.input[0].getPortXY('output', self.input[1])
if self.output:
self.x[-1],self.y[-1] = self.output[0].getPortXY('input', self.output[1])
LinesShape.draw(self,dc)
def lock(self):
"""
"""
if self.input and self.output:
host1 = self.input[0]
host2 = self.output[0]
try: host1.lock()
except: pass
try: host2.lock()
except: pass
def unlock(self):
"""
"""
if self.input and self.output:
host1 = self.input[0]
host2 = self.output[0]
try: host1.unlock()
except: pass
try: host2.unlock()
except: pass
def OnLeftDClick(self, event):
""" Left Double click has been invoked.
"""
### redirect to LinesShape handler (default)
LinesShape.OnLeftDClick(self, event)
event.Skip()
###
def OnRightDown(self, event):
""" Right down event has been invoked.
"""
menu = Menu.ShapePopupMenu(self, event)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
def __del__(self):
pass
#Basic Graphical Components-----------------------------------------------------
class Block(RoundedRectangleShape, Connectable, Resizeable, Selectable, Attributable, Rotatable, Plugable, Observer, Testable, Savable):
""" Generic Block class.
"""
def __init__(self, label = 'Block', nb_inputs = 1, nb_outputs = 1):
""" Constructor
"""
RoundedRectangleShape.__init__(self)
Resizeable.__init__(self)
Connectable.__init__(self, nb_inputs, nb_outputs)
Attributable.__init__(self)
Selectable.__init__(self)
Rotatable.__init__(self)
self.AddAttributes(Attributable.GRAPHICAL_ATTR)
self.label = label
self.label_pos = 'center'
self.image_path = ""
self.id = 0
self.nb_copy = 0 # nombre de fois que le bloc est copié (pour le label des blocks copiés
self.last_name_saved = ""
self.lock_flag = False # move lock
self.bad_filename_path_flag = False
###
def draw(self, dc):
"""
"""
# Mac's DC is already the same as a GCDC, and it causes
# problems with the overlay if we try to use an actual
# wx.GCDC so don't try it.
if 'wxMac' not in wx.PlatformInfo:
dc = wx.GCDC(dc)
### Draw rectangle shape
RoundedRectangleShape.draw(self, dc)
### Prepare label drawing
w,h = dc.GetTextExtent(self.label)
mx = int((self.x[0] + self.x[1]-w)/2)
if self.label_pos == 'bottom':
### bottom
my = int(self.y[1])
elif self.label_pos == 'top':
### top
my = int(self.y[0]-h)
else:
### center
my = int((self.y[0] + self.y[1]-h)/2)
### with and height of rectangle
self.w = self.x[1]- self.x[0]
self.h = self.y[1]- self.y[0]
if self.image_path != "" and not os.path.exists(self.image_path):
print(_(f"{self.image_path} does not exists in the {self.label} model!"))
self.image_path = ""
else:
### Draw background picture
if os.path.isabs(self.image_path):
dir_name = os.path.dirname(self.image_path)
if zipfile.is_zipfile(dir_name):
image_name = os.path.basename(self.image_path)
image_path = os.path.join(gettempdir(), image_name)
sourceZip = zipfile.ZipFile(dir_name, 'r')
sourceZip.extract(str(image_name), gettempdir())
sourceZip.close()
else:
image_path = self.image_path
if image_path != "" and not os.path.exists(image_path):
print(_(f"{image_path} does not exists in the {self.label} model!"))
image_path = ""
else:
img = wx.Image(image_path)
img_scaled = img.Scale(int(self.w), int(self.h), wx.IMAGE_QUALITY_HIGH)
wxbmp = img_scaled.ConvertToBitmap()
dc.DrawBitmap(wxbmp, int(self.x[0]), int(self.y[0]), True)
### Draw lock picture
if self.lock_flag:
img = wx.Bitmap(os.path.join(ICON_PATH_16_16, 'lock.png'), wx.BITMAP_TYPE_ANY)
dc.DrawBitmap(img, int(self.x[0]), int(self.y[0]))
### Draw filename path flag picture
if self.bad_filename_path_flag:
img = wx.Bitmap(os.path.join(ICON_PATH_16_16, 'flag_exclamation.png'), wx.BITMAP_TYPE_ANY)
dc.DrawBitmap(img, int(self.x[0]+15), int(self.y[0]))
#img = wx.Bitmap(os.path.join(ICON_PATH_16_16, 'atomic3.png'), wx.BITMAP_TYPE_ANY)
#dc.DrawBitmap( img, self.x[0]+30, self.y[0] )
### Draw label
dc.DrawText(self.label, int(mx), int(my))
if hasattr(self,'status_label'):
dc.DrawText(self.status_label, int(mx), int(my+20))
else:
self.status_label = ""
#def OnResize(self):
#Shape.OnResize(self)
###
def OnLeftUp(self, event):
event.Skip()
###
def leftUp(self, items):
pass
###
def OnRightDown(self, event):
""" Right down event has been invoked.
"""
menu = Menu.ShapePopupMenu(self, event)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
###
#def OnLeftDown(self, event):
# """
# """
### Change the label of block
#if event.ControlDown():
# Selectable.OnRenameFromClick(self, event)
# event.Skip()
###
def OnProperties(self, event):
"""
"""
canvas = event.GetEventObject()
f = AttributeEditor(canvas.GetParent(), wx.NewIdRef(), self, canvas)
f.Show()
def OnPluginsManager(self, event):
"""
"""
canvas = event.GetEventObject()
f = PluginsGUI.ModelPluginsManager( parent=canvas.GetParent(),
id=wx.NewIdRef(),
title =_('%s - plugin manager')%self.label,
size = (700,500),
style = wx.LC_REPORT| wx.DEFAULT_FRAME_STYLE | wx.CLIP_CHILDREN,
model= self)
f.Show()
def OnExport(self, event):
""" Method that export Block.
OnExport is invoked from Menu.py file and the id of sub_menu allows the selection of the appropriate save method in SaveFile (implemented in Savable.py)
"""
mainW = wx.GetApp().GetTopWindow()
parent = event.GetClientData()
domain_path = os.path.dirname(self.model_path)
itemId = event.GetId()
menu = event.GetEventObject()
path = None
### Export by using right clic menu
if isinstance(menu, wx.Menu):
menuItem = menu.FindItemById(itemId)
ext = menuItem.GetItemLabel().lower()
### export (save) by using save button of DetachedFrame
else:
ext = 'cmd'
wcd = _('%s Files (*.%s)|*.%s|All files (*)|*')%(ext.upper(), ext, ext)
save_dlg = wx.FileDialog(parent,
message = _('Export file as...'),
defaultDir = domain_path,
defaultFile = str(self.label)+'.%s'%ext,
wildcard = wcd,
style = wx.SAVE | wx.OVERWRITE_PROMPT)
if save_dlg.ShowModal() == wx.ID_OK:
path = os.path.normpath(save_dlg.GetPath())
label = os.path.basename(path)
save_dlg.Destroy()
### export (save) by using save button of DetachedFrame
#else:
# path = self.model_path
# label = os.path.basename(path)
if path:
try:
### Block is Savable
self.SaveFile(path)
printOnStatusBar(mainW.statusbar, {0:_('%s Exported')%label, 1:''})
except IOError as error:
dlg = wx.MessageDialog(parent, \
_('Error exported file %s\n')%error, \
label, \
wx.OK | wx.ICON_ERROR)
dlg.ShowModal()
def update(self, concret_subject = None):
""" Update method to respond to notify call.
"""
state = concret_subject.GetState()
### for all properties
for prop in state:
val = state[prop]
# if behavioral propertie
if prop in self.args:
self.args[prop] = val
# si attribut comportemental definit
# (donc au moins une simulation sur le modele, parce que les instances DEVS ne sont faites qu'à la simulation)
# alors on peut mettre a jour dynamiquement pendant la simulation :-)
# attention necessite une local copy dans le constructeur des model DEVS (generalement le cas lorsqu'on veux réutiliser les param du constructeur dans les methodes)
devs = self.getDEVSModel()
if devs is not None:
setattr(devs, prop, val)
### if graphical properties, we update the canvas
elif val != getattr(self, prop):
if prop == 'label':
canvas = concret_subject.canvas
diagram = canvas.GetDiagram()
if val != "" and ' ' not in val:
new_label = val
old_label = getattr(self, prop)
### update priority list
if old_label in diagram.priority_list:
### find index of label priority list and replace it
i = diagram.priority_list.index(old_label)
diagram.priority_list[i] = new_label
### clear custom port labels if their number change
# if prop == 'input':
# self.setInputLabels({})
# if prop == 'output':
# self.setOutputLabels({})
### clear manager : direct update only for image_path propertie
if val not in ('',[],{}) or (prop == 'image_path' and val == ""):
canvas = concret_subject.canvas
setattr(self, prop, val)
if isinstance(canvas, ShapeCanvas):
canvas.UpdateShapes([self])
else:
sys.stderr.write(_('Canvas not updated (has been deleted!)'))
return state
###
def __repr__(self):
"""
"""
return "".join([_("\n\t Label: %s\n")%self.label,
_("\t Input/Output: %s,%s\n")%(str(self.input), str(self.output))]
)
#---------------------------------------------------------
class CodeBlock(Achievable, Block, Iconizable):
""" CodeBlock(label, inputs, outputs)
"""
###
def __init__(self, label = 'CodeBlock', nb_inputs = 1, nb_outputs = 1):
""" Constructor.
"""
Block.__init__(self, label, nb_inputs, nb_outputs)
Achievable.__init__(self)
Iconizable.__init__(self, ['trash', 'edit'])
###
def __setstate__(self, state):
""" Restore state from the unpickled state values.
"""
python_path = state['python_path']
model_path = state['model_path']
# image_path = state['image_path']
new_class = None
dir_name = os.path.basename(DOMAIN_PATH)
### if the model path is wrong
if model_path != '':
if not os.path.exists(model_path):
# try to find it in the Domain (firstly)
if dir_name in python_path:
path = os.path.join(os.path.dirname(DOMAIN_PATH), relpath(str(model_path[model_path.index(dir_name):]).strip('[]')))
### perhaps path is wrong due to a change in the Domain lib !
### Try to find the model by its name in the Domain directories (multipe path can be occur)
# if not os.path.exists(path):
# file_name = os.path.basename(path)
# for root, dirs, files in os.walk(DOMAIN_PATH):
# for name in files:
# if name == file_name:
# path = os.path.abspath(os.path.join(root, name))
### try to find it in exportedPathList (after Domain check)
if not os.path.exists(path) and builtins.__dict__.get('GUI_FLAG',True):
import wx
mainW = wx.GetApp().GetTopWindow()
for p in mainW.exportPathsList:
lib_name = os.path.basename(p)
if lib_name in path:
path = p+path.split(lib_name)[-1]
### if path is always wrong, flag is visible
if not os.path.exists(path):
state['bad_filename_path_flag'] = True
else:
state['model_path'] = path
python_filename = os.path.basename(python_path)
if str(python_filename).find('\\'):
### wrong basename :
### os.path.basename does not work when executed on Unix
### with a Windows path
python_path = python_path.replace('\\','/')
python_filename = os.path.basename(python_path)
state['python_path'] = os.path.join(state['model_path'] , python_filename)
if not state['python_path'].endswith('.py'):
### Is this up-to-date???
### we find the python file using re module
### because path can comes from windows and then sep is not the same and os.path.basename don't work !
state['python_path'] = os.path.join(path, re.findall("([\w]*[%s])*([\w]*.py)"%os.sep, python_path)[0][-1])
else:
state['bad_filename_path_flag'] = True
### load enventual Plugin
if 'plugins' in state and builtins.__dict__.get('GUI_FLAG',True):
wx.CallAfter(self.LoadPlugins, (state['model_path']))
### test if args from construcor in python file stored in library (on disk) and args from stored model in dsp are the same
if os.path.exists(python_path) or zipfile.is_zipfile(os.path.dirname(python_path)):
cls = Components.GetClass(state['python_path'])
### TODO
### local package imported into amd or cmd generates error ! (fcts...)
if cls and not isinstance(cls, tuple):
try:
args_from_stored_constructor_py = inspect.getargspec(cls.__init__).args[1:]
except ValueError:
constructor = inspect.signature(cls.__init__)
parameters = constructor.parameters
args_from_stored_constructor_py = [name for name, _ in parameters.items() if name != 'self']
args_from_stored_block_model = state['args']
L = list(set(args_from_stored_constructor_py).symmetric_difference(set(args_from_stored_block_model)))
if L:
for arg in L:
if not arg in args_from_stored_constructor_py:
sys.stdout.write(_("Warning: %s come is old ('%s' arg is deprecated). We update it...\n"%(state['python_path'],arg)))
del state['args'][arg]
else:
try:
arg_values = inspect.getargspec(cls.__init__).defaults
except ValueError:
constructor = inspect.signature(cls.__init__)
parameters = constructor.parameters
arg_values = [parameter.default for parameter in parameters.values() if parameter.default != inspect.Parameter.empty]
finally:
index = args_from_stored_constructor_py.index(arg)
state['args'].update({arg:arg_values[index]})
### Class redefinition if the class inherite to QuickScope, To_Disk or MessagesCollector
### find all members that is class
try:
module = sys.modules[cls.__name__]
except :
module = inspect.getmodule(cls)
finally:
clsmembers = inspect.getmembers(module, inspect.isclass)
names = [t[0] for t in clsmembers]
### if model inherite of ScopeGUI, it requires to redefine the class with the ScopeGUI class
if 'To_Disk' in names or 'MessagesCollector' in names:
new_class = DiskGUI
elif ('QuickScope' in names):
state['xlabel'] = ""
state['ylabel'] = ""
new_class = ScopeGUI
else:
new_class = None
else:
sys.stderr.write(_("Error in setstate for CodeBlock class which is %s\n"%str(cls)))
state['bad_filename_path_flag'] = False
### if the python path is wrong
if not os.path.exists(python_path) :#and zipfile.is_zipfile(os.path.dirname(python_path))):
### if the model path is empty (for pure python file - not .amd or .cmd)
if model_path == '' :
path = python_path
### if DOMAIN is in python_path
if dir_name in python_path:
### try to find in DOMAIN directory
path = os.path.join(os.path.dirname(DOMAIN_PATH), relpath(str(python_path[python_path.index(dir_name):]).strip('[]')))
### try to find it in exportedPathList (after Domain check) and recent opened file
if not os.path.exists(path) and builtins.__dict__.get('GUI_FLAG',True):
import wx
mainW = wx.GetApp().GetTopWindow()
if hasattr(mainW,'exportPathsList') and hasattr(mainW,'openFileList'):
for p in mainW.exportPathsList+mainW.openFileList:
lib_name = os.path.basename(p)
if lib_name !='' and lib_name in path:
path = p+path.split(lib_name)[-1]
break
else:
### try to find if python_path contains a directory wich is also in Domain
### subdirectories of Domain
subdirectories = os.listdir(DOMAIN_PATH)
### for all directories if the directory is in python_path (excluding the file .py (-1))
for dir in subdirectories:
if dir in python_path.split(os.sep)[0:-1]:
### yes, the python_path is wrong but we find that in the Domain there is a directory with the same name
a = python_path.split(dir+os.sep)
path = os.path.join(DOMAIN_PATH,dir,a[-1])
break
### try to find the python_path in recent opened file directory
if not os.path.exists(path) and builtins.__dict__.get('GUI_FLAG',True):
import wx
mainW = wx.GetApp().GetTopWindow()
if hasattr(mainW,'exportPathsList') and hasattr(mainW,'openFileList'):
for a in [os.path.dirname(p) for p in mainW.exportPathsList+mainW.openFileList]:
p = os.path.join(a,os.path.basename(python_path))
if os.path.exists(p):
path = p
break
### if path is always wrong, flag is visible
if os.path.exists(path):
state['python_path'] = path
else:
state['bad_filename_path_flag'] = True
### for .cmd or .amd
else:
if not os.path.exists(state['model_path']) :
state['bad_filename_path_flag'] = True
# else:
# with zipfile.ZipFile(model_path) as zf:
# ### find all python files
# for file in zf.namelist():
# path = os.path.join(model_path, os.path.basename(model_path).replace('.amd','.py').replace('.cmd','.py'))
# #cls = GetClass(path)
# if file.endswith(".py"):# and (issubclass(cls, DomainBehavior) or issubclass(cls, DomainStructure)):
# if os.path.exists(model_path):
# state['python_path'] = path
# else:
# state['bad_filename_path_flag'] = True
#state['python_path'] = os.path.join(model_path, os.path.basename(model_path).replace('.amd','.py').replace('.cmd','.py'))
#state['bad_filename_path_flag'] = file != state['python_path']
### if the fileName attribut dont exist, we define it into the current devsimpy directory (then the user can change it from Property panel)
### args is loaded before for .amd and .cmd. See Load function to intercept args.
if 'args' in state: #and model_path == '' :
### find all word containning 'filename' without considering the casse
m = [re.match('[a-zA-Z]*filename[_-a-zA-Z0-9]*',s, re.IGNORECASE) for s in list(state['args'].keys())]
filename_list = [a.group(0) for a in [s for s in m if s is not None]]
### for all filename attr
for name in filename_list:
fn = state['args'][name]
if not os.path.exists(fn):
#fn_dn = os.path.dirname(fn)
fn_bn = os.path.basename(relpath(fn))
### try to redefi[ne the path
### if Domain is in the path
if dir_name in fn:
fn = os.path.join(os.path.dirname(DOMAIN_PATH), relpath(str(fn[fn.index(dir_name):]).strip('[]')))
### try to find the filename in the recent opened recent file directory or exported lib diretory
else:
### for no-gui compatibility
if builtins.__dict__.get('GUI_FLAG',True):
import wx
mainW = wx.GetApp().GetTopWindow()
if hasattr(mainW,'exportPathsList') and hasattr(mainW,'openFileList'):
for a in [os.path.dirname(p) for p in mainW.exportPathsList+mainW.openFileList]:
path = os.path.join(a,fn_bn)
if os.path.exists(path):
fn = path
break
### show flag icon on the block only for the file with extension (input file)
if not os.path.exists(fn) and os.path.splitext(fn)[-1] != '':
state['bad_filename_path_flag'] = True
state['args'][name] = fn
####################################" Just for old model
if 'bad_filename_path_flag' not in state: state['bad_filename_path_flag'] = False
if 'lock_flag' not in state: state['lock_flag'] = False
if 'image_path' not in state:
state['image_path'] = ""
state['attributes'].insert(3,'image_path')
if 'font' not in state: state['font'] = [FONT_SIZE, 74, 93, 700, u'Arial']
if 'font' not in state['attributes']: state['attributes'].insert(3,'font')
if 'selected' not in state: state['selected'] = False
if 'label_pos' not in state: state['label_pos'] = 'center'
if 'input_direction' not in state: state['input_direction'] = 'ouest'
if 'output_direction' not in state: state['output_direction'] = 'est'
if '_input_labels' not in state: state['_input_labels'] = {}
if '_output_labels' not in state: state['_output_labels'] = {}
##############################################
self.__dict__.update(state)
if new_class:
self.__class__ = new_class
###
def __getstate__(self):
"""Return state values to be pickled."""
return Achievable.__getstate__(self)
###
def __getattr__(self, name):
"""Called when an attribute lookup has not found the attribute in the usual places
"""
if name == 'dump_attributes':
#return ['model_path', 'python_path', 'args'] + self.GetAttributes()
return ['args'] + Connectable.DUMP_ATTR + self.GetAttributes()
#=======================================================================
elif name == 'dump_abstr_attributes':
### Atomic model has no abstract attributes
return []
#======================================================================
elif name == 'icons':
return {}
else:
raise AttributeError(name)
###
def draw(self, dc):
""" Draw the block on DC.
"""
if self.selected:
### inform about the nature of the block using icon
name = 'atomic3' if self.model_path != "" else 'pythonFile' if self.python_path.endswith('.py') else 'pyc'
icon = Icon(name, (4,2))
img = wx.Bitmap(icon.getImagePath(), wx.BITMAP_TYPE_ANY)
x,y = int(self.x[0]+icon.getOffSet('x')), int(self.y[0]+icon.getOffSet('y'))
dc.DrawBitmap(img, x, y)
### Draw the right icons (see constructor and Iconizable)
for name in self.getDisplayedIconNames():
icon = self.getIcon(name)
img = wx.Bitmap(icon.getImagePath(), wx.BITMAP_TYPE_ANY)
x,y = int(self.x[1]+icon.getOffSet('x')), int(self.y[0]+icon.getOffSet('y'))
dc.DrawBitmap(img, x, y)
Block.draw(self, dc)
###
def OnLeftDown(self, event):
""" On Left Click has been done.
"""
mouse_x, mouse_y = event.GetX(), event.GetY()
clicked_icon_name = self.getClickedIconName(self.x, self.y, mouse_x, mouse_y)
if clicked_icon_name == 'edit':
try:
self.OnEditor(event)
except AttributeError:
pass
elif clicked_icon_name == 'trash':
canvas = event.GetEventObject()
try:
canvas.OnDelete(self)
except AttributeError:
pass
else:
pass
event.Skip()
###
def OnLeftDClick(self, event):
""" On left double click event has been invoked.
"""
if event.ControlDown():
Selectable.OnRenameFromClick(self, event)
else:
self.OnProperties(event)
event.Skip()
###
def OnSelect(self, event):
"""
"""
self.selected = True
###
def OnDeselect(self, event):
"""
"""
self.selected = False
def update(self, concret_subject = None):
""" Notify has been invocked
"""
state = Block.update(self, concret_subject)
if isinstance(concret_subject, PropertiesGridCtrl):
### table and dico of bad flag field (pink colored)
table = concret_subject.GetTable()
bad_flag_dico = table.bad_flag
### set of edited fied and set of bad fied (pink for example)
edited_field_set = set(state)
bad_flag_set = set(bad_flag_dico.keys())
### if intersection is total, all bad field are has been edited and we test at the end of the loop if all of the paths are right.
if len(bad_flag_set.intersection(edited_field_set)) == len(bad_flag_set):
for prop in state:
### Update the filename flag
m = [re.match('[a-zA-Z_]*ilename[_-a-zA-Z0-9]*',prop, re.IGNORECASE)]
filename_list = [a.group(0) for a in [s for s in m if s is not None]]
### for all filename attr
for name in filename_list:
val = state[prop]
# if behavioral propertie
if prop in self.args:
### is abs fileName ?
if os.path.isabs(val):
### if there is an extention, then if the field path exist we color in red and update the bad_filename_path_flag
bad_flag_dico.update({prop:not os.path.exists(val) and os.path.splitext(val)[-1] == ''})
self.bad_filename_path_flag = True in list(bad_flag_dico.values())
###
def __repr__(self):
""" Text representation.
"""
return "".join([Block.__repr__(self),
_("\t DEVS module path: %s\n")%str(self.python_path),
_("\t DEVSimPy model path: %s\n")%str(self.model_path),
_("\t DEVSimPy image path: %s\n")%str(self.image_path)]
)
#---------------------------------------------------------
class ContainerBlock(Block, Iconizable, Diagram):
""" ContainerBlock(label, inputs, outputs)
"""
FILL = [GREEN]
###
def __init__(self, label = 'ContainerBlock', nb_inputs = 1, nb_outputs = 1):
""" Constructor
"""
Block.__init__(self, label, nb_inputs, nb_outputs)
Iconizable.__init__(self, ['trash'])
Diagram.__init__(self)
self.fill = ContainerBlock.FILL
###
def __setstate__(self, state):
""" Restore state from the unpickled state values.
"""
python_path = state['python_path']
model_path = state['model_path']
dir_name = os.path.basename(DOMAIN_PATH)
### if the model path is wrong
if model_path != '':
if not os.path.exists(model_path):
### try to find it in the Domain (firstly)
if dir_name in python_path:
path = os.path.join(os.path.dirname(DOMAIN_PATH), relpath(str(model_path[model_path.index(dir_name):]).strip('[]')))
### try to find it in exportedPathList (after Domain check)
if not os.path.exists(path) and builtins.__dict__.get('GUI_FLAG',True):
import wx
mainW = wx.GetApp().GetTopWindow()
for p in mainW.exportPathsList:
lib_name = os.path.basename(p)
if lib_name in path:
path = p+path.split(lib_name)[-1]
### if path is always wrong, flag is visible
if not os.path.exists(path):
state['bad_filename_path_flag'] = True
else:
state['model_path'] = path
python_filename = os.path.basename(python_path)
if str(python_filename).find('\\'):
### wrong basename :
### os.path.basename does not work when executed on Unix
### with a Windows path
python_path = python_path.replace('\\','/')
python_filename = os.path.basename(python_path)
state['python_path'] = os.path.join(state['model_path'] , python_filename)
if not state['python_path'].endswith('.py'):
### Is this up-to-date???
### we find the python file using re module
### because path can comes from windows and then sep is not the same and os.path.basename don't work !
state['python_path'] = os.path.join(path, re.findall("([\w]*[%s])*([\w]*.py)"%os.sep, python_path)[0][-1])
else:
state['bad_filename_path_flag'] = True
### load enventual Plugin
if 'plugins' in state and builtins.__dict__.get('GUI_FLAG',True):
wx.CallAfter(self.LoadPlugins, (state['model_path']))
### test if args from construcor in python file stored in library (on disk) and args from stored model in dsp are the same
if os.path.exists(python_path) or zipfile.is_zipfile(os.path.dirname(python_path)):
cls = Components.GetClass(state['python_path'])
if not isinstance(cls, tuple):
try:
args_from_stored_constructor_py = inspect.getargspec(cls.__init__).args[1:]
except:
constructor = inspect.signature(cls.__init__)
parameters = constructor.parameters
args_from_stored_constructor_py = [name for name, _ in parameters.items() if name != 'self']
args_from_stored_block_model = state['args']
if args_from_stored_block_model:
L = list(set(args_from_stored_constructor_py).symmetric_difference( set(args_from_stored_block_model)))
if L:
for arg in L:
if not arg in args_from_stored_constructor_py:
sys.stdout.write(_("Warning: %s come is old ('%s' arg is deprecated). We update it...\n"%(state['python_path'],arg)))
del state['args'][arg]
else:
try:
arg_values = inspect.getargspec(cls.__init__).defaults
except ValueError:
constructor = inspect.signature(cls.__init__)
parameters = constructor.parameters
arg_values = [parameter.default for parameter in parameters.values() if parameter.default != inspect.Parameter.empty]
finally:
index = args_from_stored_constructor_py.index(arg)
state['args'].update({arg:arg_values[index]})
else:
#sys.stderr.write(_("args is None in setstate for ContainerBlock: %s\n"%str(cls)))
state['args'] = {}
else:
sys.stderr.write(_("Error in setstate for ContainerBlock: %s\n"%str(cls)))
### if the model path is empty and the python path is wrong
elif not (os.path.exists(python_path) and zipfile.is_zipfile(os.path.dirname(python_path))):
if dir_name in python_path:
path = os.path.join(os.path.dirname(DOMAIN_PATH), relpath(str(python_path[python_path.index(dir_name):]).strip('[]')))
state['python_path'] = path
if not os.path.exists(path):
state['bad_filename_path_flag'] = True
else:
state['bad_filename_path_flag'] = True
####################################" Just for old model
if 'bad_filename_path_flag' not in state: state['bad_filename_path_flag'] = False
if 'lock_flag' not in state: state['lock_flag'] = False
if 'parent' not in state: state['parent'] = None
if 'image_path' not in state:
state['image_path'] = ""
state['attributes'].insert(3,'image_path')
if 'font' not in state: state['font'] = [FONT_SIZE, 74, 93, 700, u'Arial']
if 'font' not in state['attributes']: state['attributes'].insert(3,'font')
if 'selected' not in state: state['selected'] = False
if 'label_pos' not in state:state['label_pos'] = 'center'
if 'input_direction' not in state: state['input_direction'] = 'ouest'
if 'output_direction' not in state: state['output_direction'] = 'est'
if '_input_labels' not in state: state['_intput_labels'] = {}
if '_output_labels' not in state: state['_output_labels'] = {}
#if isinstance(state['shapes'],dict): state['shapes'] = list(state['shapes'].values())
#####################################
self.__dict__.update(state)
def __getstate__(self):
"""Return state values to be pickled."""
#return Structurable.__getstate__(self)
return Diagram.__getstate__(self)
def __getattr__(self, name):
"""Called when an attribute lookup has not found the attribute in the usual places
"""
if name == 'dump_attributes':
#return ['shapes', 'priority_list', 'constants_dico', 'model_path', 'python_path','args'] + self.GetAttributes()
return ['shapes', 'priority_list', 'constants_dico','args'] + Connectable.DUMP_ATTR +self.GetAttributes()
#======================================================================
elif name == 'dump_abstr_attributes':
return Abstractable.DUMP_ATTR if hasattr(self, 'layers') and hasattr(self, 'current_level') else []
#======================================================================
#======================================================================
elif name == 'icons':
return {}
else:
raise AttributeError(name)
###
def draw(self, dc):
"""
"""
if self.selected:
### inform about the nature of the block using icon
icon = Icon('coupled3', (4,2))
img = wx.Bitmap(icon.getImagePath(), wx.BITMAP_TYPE_ANY)
x,y = int(self.x[0]+icon.getOffSet('x')), int(self.y[0]+icon.getOffSet('y'))
dc.DrawBitmap(img, x, y)
### Draw the number of devs models inside
n = str(self.GetBlockCount())
dc.DrawText(n, int(self.x[1]-15-len(n)), int(self.y[1]-20))
### Draw the right icons (see constructor and Iconizable)
for name in self.getDisplayedIconNames():
icon = self.getIcon(name)
img = wx.Bitmap(icon.getImagePath(), wx.BITMAP_TYPE_ANY)
x,y = int(self.x[1]+icon.getOffSet('x')), int(self.y[0]+icon.getOffSet('y'))
dc.DrawBitmap(img, x, y)
Block.draw(self, dc)
###
def OnLeftDown(self, event):
""" On Left Click has been done.
"""
mouse_x, mouse_y = event.GetX(), event.GetY()
clicked_icon_name = self.getClickedIconName(self.x, self.y, mouse_x, mouse_y)
if clicked_icon_name == 'edit':
try:
self.OnEditor(event)
except AttributeError:
pass
elif clicked_icon_name == 'trash':
canvas = event.GetEventObject()
try:
canvas.OnDelete(self)
except AttributeError:
pass
else:
pass
event.Skip()
###
def OnSelect(self, event):
"""
"""
self.selected = True
###
def OnDeselect(self, event):
"""
"""
self.selected = False
###
def OnLeftDClick(self, event):
""" Left Double Click Event Handel
"""
### CtrL is Down
if event.ControlDown():
Selectable.OnRenameFromClick(self, event)
else:
canvas = event.GetEventObject()
canvas.deselect()
mainW = wx.GetApp().GetTopWindow()
frame = DetachedFrame(parent = mainW, title = ''.join([canvas.name,' - ',self.label]), diagram = self, name = self.label)
frame.SetIcon(mainW.GetIcon())
frame.Show()
event.Skip()
def __repr__(self):
return "".join([Block.__repr__(self),
_("\t DEVS module path: %s\n"%str(self.python_path)),
_("\t DEVSimPy model path: %s\n")%str(self.model_path),
_("\t DEVSimPy image path: %s\n")%str(self.image_path)]
)
#---------------------------------------------------------
# Nodes
class Node(PointShape):
""" Node(item, index, cf, type)
Node class for connection between model.
"""
def __init__(self, item, index, cf, t='rect'):
""" Construcotr.
"""
self.item = item ### parent Block
self.index = index ### number of port
self.cf = cf ### parent canvas
self.label = "" ### label of port
self.font_size = 10 ### font size
self.lock_flag = False # move lock
PointShape.__init__(self, type=t)
# def showProperties(self):
# """ Call item properties.
# """
# self.item.showProperties
class ConnectableNode(Node):
""" ConnectableNode(item, index, cf)
"""
def __init__(self, item, index, cf):
""" Constructor.
"""
Node.__init__(self, item, index, cf, t='circ')
def OnLeftDown(self, event):
""" Left Down click has been invoked
"""
### Ctrl button is pressed
if event.ControlDown():
### change the label of node
self.OnEditLabel(event)
else:
### deselect the block to delete the info flag
self.cf.deselect(self.item)
event.Skip()
def OnEditLabel(self, event):
""" Function called by the OnRightDown call event function.
"""
### old label
old_label = self.label
### is INode ?
isINode = isinstance(self, INode)
### ask tne new label
d = wx.TextEntryDialog(None, _('New label for the %s port %d:'%("input" if isINode else "output",self.index)), value = old_label, style=wx.OK)
if d.ShowModal() == wx.ID_OK:
### new label
new_label = d.GetValue()
### only if new and old label are different
if new_label != old_label:
self.label = new_label
self.font_size = 20
if isINode:
self.item.addInputLabels(self.index, self.label)
else:
self.item.addOutputLabels(self.index, self.label)
def HitTest(self,x,y):
""" Collision detection method.
"""
### old model can produce an error
try:
r = self.graphic.r
xx = self.x[0] if isinstance(self.x, array.array) else self.x
yy = self.y[0] if isinstance(self.y, array.array) else self.y
return not ((x < xx-r or x > xx+r) or (y < yy-r or y > yy+r))
except Exception as info:
sys.stdout.write(_("Error in Hitest for %s : %s\n")%(self,info))
return False
class INode(ConnectableNode):
""" INode(item, index, cf)
"""
def __init__(self, item, index, cf, label=None):
""" Constructor.
"""
ConnectableNode.__init__(self, item, index, cf)
self.label = f"in{self.index}" if not label else label
def move(self, x, y):
""" Move method.
"""
self.cf.deselect()
ci = ConnectionShape()
ci.setOutput(self.item, self.index)
ci.x[0], ci.y[0] = self.item.getPortXY('input', self.index)
self.cf.diagram.shapes.insert(0, ci)
self.cf.showOutputs()
self.cf.select(ci)
def OnRightDown(self, event):
""" Left Down click has been invoked.
"""
### dialog to ask new port label
menu = Menu.NodePopupMenu(self)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
def leftUp(self, items):
""" Left up action has been invocked.
"""
cs = items[0]
#if self.item in cs.touch_list:
#index = cs.touch_list.index(self.item)
#del cs.touch_list[index]
if len(items) == 1 and isinstance(cs, ConnectionShape) and cs.output is None:
cs.setOutput(self.item, self.index)
#cs.ChangeForm(ShapeCanvas.CONNECTOR_TYPE)
def draw(self, dc):
""" Drawing method.
"""
x,y = self.item.getPortXY('input', self.index)
self.moveto(x, y)
self.fill = [GREEN]
dc.SetFont(wx.Font(self.font_size, 70, 0, 400))
### prot number
#dc.SetPen(wx.Pen(wx.NamedColour('black'), 20))
#dc.DrawText(str(self.index), self.x-self.graphic.r, self.y-self.graphic.r-2)
### position of label - not for Port model (inside containerBlock)
if not isinstance(self.item, Port):
### prepare label position
if self.item.input_direction == 'ouest':
xl = x-self.graphic.r-8*len(self.label)
yl = y-8
elif self.item.input_direction == 'est':
xl = x+6
yl = y-8
elif self.item.input_direction == 'nord':
xl = x
yl = y-18
else:
xl = x
yl = y+2
### Draw label in port
dc.DrawText(self.label, int(xl), int(yl))
### Drawing
PointShape.draw(self, dc)
class ONode(ConnectableNode):
""" ONode(item, index, cf).
"""
def __init__(self, item, index, cf, label=None):
""" Constructor.
"""
ConnectableNode.__init__(self, item, index, cf)
self.label = "out%d"%self.index if not label else label
def move(self, x, y):
""" Moving method.
"""
self.cf.deselect()
ci = ConnectionShape()
ci.setInput(self.item, self.index)
ci.x[1], ci.y[1] = self.item.getPortXY('output', self.index)
self.cf.diagram.shapes.insert(0, ci)
self.cf.showInputs()
self.cf.select(ci)
def OnRightDown(self, event):
""" Left Down click has been invoked.
"""
### dialog to ask new port label
menu = Menu.NodePopupMenu(self)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
def leftUp(self, items):
""" Left up action has been invocked.
"""
cs = items[0]
#if self.item in cs.touch_list:
#index = cs.touch_list.index(self.item)
#del cs.touch_list[index]
if len(items) == 1 and isinstance(cs, ConnectionShape) and cs.input is None:
cs.setInput(self.item, self.index)
#cs.ChangeForm(ShapeCanvas.CONNECTOR_TYPE)
def draw(self, dc):
""" Drawing method.
"""
x,y = self.item.getPortXY('output', self.index)
self.moveto(x, y)
self.fill = [RED]
dc.SetFont(wx.Font(self.font_size, 70, 0, 400))
#dc.SetPen(wx.Pen(wx.NamedColour('black'), 20))
### prot number
#dc.DrawText(str(self.index), self.x-self.graphic.r, self.y-self.graphic.r-2)
### position of label
if not isinstance(self.item, Port):
### perapre label position
if self.item.output_direction == 'est':
xl = x+6
yl = y-8
elif self.item.output_direction == 'ouest':
xl = x-self.graphic.r-8*len(self.label)
yl = y-8
elif self.item.output_direction == 'nord':
xl = x
yl = y-20
else:
xl = x
yl = y-2
### Draw label above port
dc.DrawText(self.label, int(xl), int(yl))
### Drawing
PointShape.draw(self, dc)
###
class ResizeableNode(Node):
""" Resizeable(item, index, cf, type).
"""
def __init__(self, item, index, cf, t = 'rect'):
""" Constructor.
"""
Node.__init__(self, item, index, cf, t)
self.fill = [BLACK]
def draw(self, dc):
""" Drawing method.
"""
try:
self.moveto(self.item.x[self.index], self.item.y[self.index])
except IndexError:
pass
PointShape.draw(self, dc)
def move(self, x, y):
""" Moving method.
"""
lines_shape = self.item
if self.index == 0:
X = abs(self.item.x[1] - self.item.x[0]-x)
Y = abs(self.item.y[1] - self.item.y[0]-y)
else:
X = abs(self.item.x[1]+x - self.item.x[0])
Y = abs(self.item.y[1]+y - self.item.y[0])
### if no lock
if not lines_shape.lock_flag:
### Block and minimal size (50,50) or not Block
if (isinstance(self.item, Block) and X >= 50 and Y >= 50) or not isinstance(self.item, Block):
self.item.x[self.index] += x
self.item.y[self.index] += y
#self.item.OnResize()
def OnDeleteNode(self, event):
"""
"""
if isinstance(self.item, ConnectionShape):
for x in self.item.x:
if x-3 <= event.GetX() <= x+3:
y = self.item.y[self.item.x.index(x)]
if y-3 <= event.GetY() <= y+3:
self.item.x.remove(x)
self.item.y.remove(y)
###
def OnRightDown(self, event):
""" Right down event has been invoked.
"""
menu = Menu.ShapePopupMenu(self, event)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
def HitTest(self,x,y):
""" Collision detection method.
"""
return self.graphic.HitTest(x,y)
#---------------------------------------------------------
class Port(CircleShape, Connectable, Selectable, Attributable, Rotatable, Observer):
""" Port(x1, y1, x2, y2, label).
"""
def __init__(self, x1, y1, x2, y2, label = 'Port'):
""" Constructor.
"""
CircleShape.__init__(self, x1, y1, x2, y2, 30.0)
Connectable.__init__(self)
Attributable.__init__(self)
Rotatable.__init__(self)
self.SetAttributes(Attributable.GRAPHICAL_ATTR[0:4])
self.label = label
### TODO: move to args
self.AddAttribute('id')
#self.id = 0
self.args = {}
self.lock_flag = False # move lock
def __setstate__(self, state):
""" Restore state from the unpickled state values.
"""
####################################" Just for old model
if 'r' not in state: state['r'] = 30.0
if 'font' not in state: state['font'] = [FONT_SIZE, 74, 93, 700, u'Arial']
if 'label_pos' not in state:
state['label_pos'] = 'center'
state['attributes'].insert(1,'label_pos')
if 'output_direction' not in state: state['output_direction'] ="est"
if 'input_direction' not in state: state['input_direction'] = "ouest"
if '_input_labels' not in state: state['_input_labels'] = {}
if '_output_labels' not in state: state['_output_labels'] = {}
##############################################
self.__dict__.update(state)
def draw(self, dc):
""" Drawing method.
"""
# Mac's DC is already the same as a GCDC, and it causes
# problems with the overlay if we try to use an actual
# wx.GCDC so don't try it.
if 'wxMac' not in wx.PlatformInfo:
dc = wx.GCDC(dc)
CircleShape.draw(self, dc)
w,h = dc.GetTextExtent(self.label)
### label position manager
if self.label_pos == 'bottom':
### bottom
my = int(self.y[1])
elif self.label_pos == 'top':
### top
my = int(self.y[1]-(self.r*2)-14)
else:
my = int((self.y[0] + self.y[1])/2.0)-int(h/2.0)
mx = int(self.x[0])+2
dc.DrawText(self.label, int(mx), int(my))
if self.lock_flag:
img = wx.Bitmap(os.path.join(ICON_PATH_16_16, 'lock.png'),wx.BITMAP_TYPE_ANY)
dc.DrawBitmap(img, int(self.x[0]+w/3), int(self.y[0]))
def leftUp(self, event):
""" Left up event has been invoked.
"""
pass
###
def OnRightDown(self, event):
""" Right down event has been invoked.
"""
menu = Menu.ShapePopupMenu(self, event)
### Show popup_menu
canvas = event.GetEventObject()
canvas.PopupMenu(menu, event.GetPosition())
### destroy menu local variable
menu.Destroy()
event.Skip()
# ###
# def OnLeftDown(self, event):
# """ Left down event has been invoked.
# """
# ### Rename the shape with CtrL+LeftDown
# if event.ControlDown():
# Selectable.OnRenameFromClick(self, event)
# event.Skip()
def OnProperties(self, event):
""" Properties of port has been invoked.
"""
canvas = event.GetEventObject()
f = AttributeEditor(canvas.GetParent(), wx.NewIdRef(), self, canvas)
f.Show()
###
def OnLeftDClick(self, event):
""" Left double click event has been invoked.
"""
if event.ControlDown():
Selectable.OnRenameFromClick(self, event)
else:
self.OnProperties(event)
event.Skip()
def update(self, concret_subject = None):
""" Update function linked to notify function (observer pattern).
"""
state = concret_subject.GetState()
for prop in state:
val = state[prop]
canvas = concret_subject.canvas
if val != getattr(self, prop):
setattr(self, prop, val)
canvas.UpdateShapes([self])
def __repr__(self):
"""
"""
s=_("\t Label: %s\n")%self.label
return s
#------------------------------------------------------------------
class iPort(Port):
""" IPort(label) for ContainerBlock (coupled model)
"""
def __init__(self, label = 'iPort'):
""" Constructor.
"""
Port.__init__(self, 50, 60, 100, 120, label)
self.fill= [GREEN]
#self.AddAttribute('id')
self.label_pos = 'bottom'
self.input = 0
self.output = 1
def getDEVSModel(self):
return self
def setDEVSModel(self, devs):
self = devs
def __repr__(self):
s = Port.__repr__(self)
s+="\t id: %d \n"%self.id
return s
#----------------------------------------------------------------
class oPort(Port):
""" OPort(label) for ContainerBlock (coupled model)
"""
def __init__(self, label = 'oPort'):
""" Construcotr
"""
Port.__init__(self, 50, 60, 100, 120, label)
self.fill = [RED]
#self.AddAttribute('id')
self.label_pos = 'bottom'
self.input = 1
self.output = 0
def getDEVSModel(self):
return self
def setDEVSModel(self, devs):
self = devs
def __repr__(self):
s = Port.__repr__(self)
s+="\t id: %d \n"%self.id
return s
#--------------------------------------------------
class ScopeGUI(CodeBlock):
""" ScopeGUI(label)
"""
def __init__(self, label = 'QuickScope'):
""" Constructor
"""
CodeBlock.__init__(self, label, 1, 0)
self.fill = [ORANGE]
### enable edition on properties panel
self.AddAttribute("xlabel")
self.AddAttribute("ylabel")
def OnLeftDClick(self, event):
""" Left Double Click has been appeared.
"""
# If the frame is call before the simulation process, the atomicModel is not instanciate (Instanciation delegate to the makeDEVSconnection after the run of the simulation process)
devs = self.getDEVSModel()
if devs:
canvas = event.GetEventObject()
# Call the PlotManager which plot on the canvas depending the atomicModel.fusion option
PlotGUI.PlotManager(canvas, self.label, devs, self.xlabel, self.ylabel)
else:
CodeBlock.OnLeftDClick(self, event)
event.Skip()
#------------------------------------------------
class DiskGUI(CodeBlock):
""" DiskGUI(label)
"""
def __init__(self, label='DiskGUI'):
""" Constructor.
"""
CodeBlock.__init__(self, label, 1, 0)
self.fill = [ORANGE]
def OnLeftDClick(self, event):
""" Left Double Click has been appeared.
"""
devs = self.getDEVSModel()
if devs:
frame= SpreadSheet.Newt( wx.GetApp().GetTopWindow(),
wx.NewIdRef(),
_("SpreadSheet %s")%self.label,
devs,
devs.comma if hasattr(devs, 'comma') else " ")
frame.Center()
frame.Show()
else:
CodeBlock.OnLeftDClick(self, event)
event.Skip()