--- a/Beremiz.py Wed Mar 13 12:34:55 2013 +0900
+++ b/Beremiz.py Wed Jul 31 10:45:07 2013 +0900
@@ -130,7 +130,7 @@
AddBitmapFolder(os.path.join(extension_folder, "images"))
execfile(extfilename, locals())
-import wx.lib.buttons, wx.lib.statbmp
+import wx.lib.buttons, wx.lib.statbmp, wx.stc
import cPickle
import types, time, re, platform, time, traceback, commands
@@ -146,6 +146,8 @@
from editors.DataTypeEditor import DataTypeEditor
from util.MiniTextControler import MiniTextControler
from util.ProcessLogger import ProcessLogger
+from controls.LogViewer import LogViewer
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
from ProjectController import ProjectController, MATIEC_ERROR_MODEL, ITEM_CONFNODE
@@ -176,7 +178,17 @@
if self._bitmap:
dc.DrawBitmap(self._bitmap, 0, 0, True)
-
+if wx.Platform == '__WXMSW__':
+ faces = {
+ 'mono' : 'Courier New',
+ 'size' : 8,
+ }
+else:
+ faces = {
+ 'mono' : 'Courier',
+ 'size' : 10,
+ }
+
from threading import Lock,Timer,currentThread
MainThread = currentThread().ident
REFRESH_PERIOD = 0.1
@@ -184,10 +196,9 @@
class LogPseudoFile:
""" Base class for file like objects to facilitate StdOut for the Shell."""
def __init__(self, output, risecall):
- self.red_white = wx.TextAttr("RED", "WHITE")
- self.red_yellow = wx.TextAttr("RED", "YELLOW")
- self.black_white = wx.TextAttr("BLACK", "WHITE")
- self.default_style = None
+ self.red_white = 1
+ self.red_yellow = 2
+ self.black_white = wx.stc.STC_STYLE_DEFAULT
self.output = output
self.risecall = risecall
# to prevent rapid fire on rising log panel
@@ -242,14 +253,21 @@
self.lock.acquire()
for s, style in self.stack:
if style is None : style=self.black_white
- if self.default_style != style:
- self.output.SetDefaultStyle(style)
- self.default_style = style
+ if style != self.black_white:
+ self.output.StartStyling(self.output.GetLength(), 0xff)
+
+ # Temporary deactivate read only mode on StyledTextCtrl for
+ # adding text. It seems that text modifications, even
+ # programmatically, are disabled in StyledTextCtrl when read
+ # only is active
+ self.output.SetReadOnly(False)
self.output.AppendText(s)
- self.output.ScrollLines(s.count('\n')+1)
+ self.output.SetReadOnly(True)
+
+ if style != self.black_white:
+ self.output.SetStyling(len(s), style)
self.stack = []
self.lock.release()
- self.output.ShowPosition(self.output.GetLastPosition())
self.output.Thaw()
self.LastRefreshTime = gettime()
try:
@@ -258,7 +276,7 @@
pass
newtime = time.time()
if newtime - self.rising_timer > 1:
- self.risecall()
+ self.risecall(self.output)
self.rising_timer = newtime
def write_warning(self, s):
@@ -272,10 +290,15 @@
wx.GetApp().Yield()
def flush(self):
- self.output.SetValue("")
+ # Temporary deactivate read only mode on StyledTextCtrl for clearing
+ # text. It seems that text modifications, even programmatically, are
+ # disabled in StyledTextCtrl when read only is active
+ self.output.SetReadOnly(False)
+ self.output.SetText("")
+ self.output.SetReadOnly(True)
def isatty(self):
- return false
+ return False
[ID_BEREMIZ, ID_BEREMIZMAINSPLITTER,
ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE,
@@ -287,7 +310,7 @@
CONFNODEMENU_POSITION = 3
class Beremiz(IDEFrame):
-
+
def _init_utils(self):
self.ConfNodeMenu = wx.Menu(title='')
self.RecentProjectsMenu = wx.Menu(title='')
@@ -339,10 +362,13 @@
def _init_coll_AddMenu_Items(self, parent):
IDEFrame._init_coll_AddMenu_Items(self, parent, False)
- new_id = wx.NewId()
- AppendMenu(parent, help='', id=new_id,
- kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
- self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id)
+
+ # Disable add resource until matiec is able to handle multiple ressource definition
+ #new_id = wx.NewId()
+ #AppendMenu(parent, help='', id=new_id,
+ # kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
+ #self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id)
+
for name, XSDClass, help in ProjectController.CTNChildrenTypes:
new_id = wx.NewId()
AppendMenu(parent, help='', id=new_id,
@@ -354,6 +380,15 @@
kind=wx.ITEM_NORMAL, text=_(u'About'))
self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
+ def _init_coll_ConnectionStatusBar_Fields(self, parent):
+ parent.SetFieldsCount(3)
+
+ parent.SetStatusText(number=0, text='')
+ parent.SetStatusText(number=1, text='')
+ parent.SetStatusText(number=2, text='')
+
+ parent.SetStatusWidths([-1, 300, 200])
+
def _init_ctrls(self, prnt):
IDEFrame._init_ctrls(self, prnt)
@@ -378,14 +413,40 @@
self.SetAcceleratorTable(wx.AcceleratorTable(accels))
- self.LogConsole = wx.TextCtrl(id=ID_BEREMIZLOGCONSOLE, value='',
+ self.LogConsole = CustomStyledTextCtrl(id=ID_BEREMIZLOGCONSOLE,
name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
- size=wx.Size(0, 0), style=wx.TE_MULTILINE|wx.TE_RICH2)
- self.LogConsole.Bind(wx.EVT_LEFT_DCLICK, self.OnLogConsoleDClick)
- self.MainTabs["LogConsole"] = (self.LogConsole, _("Log Console"))
+ size=wx.Size(0, 0))
+ self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged)
+ self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged)
+ self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI)
+ self.LogConsole.SetReadOnly(True)
+ self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR)
+
+ # Define Log Console styles
+ self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+ self.LogConsole.StyleClearAll()
+ self.LogConsole.StyleSetSpec(1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces)
+ self.LogConsole.StyleSetSpec(2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)
+
+ # Define Log Console markers
+ self.LogConsole.SetMarginSensitive(1, True)
+ self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
+ self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
+
+ self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
+
+ self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
+ self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
+
+ self.MainTabs["LogConsole"] = (self.LogConsole, _("Console"))
self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"])
#self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT)
-
+
+ self.LogViewer = LogViewer(self.BottomNoteBook, self)
+ self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log"))
+ self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"])
+ #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)
+
StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
@@ -398,9 +459,13 @@
self.AUIManager.Update()
+ self.ConnectionStatusBar = wx.StatusBar(self, style=wx.ST_SIZEGRIP)
+ self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
+ self.SetStatusBar(self.ConnectionStatusBar)
+
def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
IDEFrame.__init__(self, parent, debug)
- self.Log = LogPseudoFile(self.LogConsole,self.RiseLogConsole)
+ self.Log = LogPseudoFile(self.LogConsole,self.SelectTab)
self.local_runtime = None
self.runtime_port = None
@@ -433,15 +498,6 @@
if projectOpen is not None:
projectOpen = DecodeFileSystemPath(projectOpen, False)
- if (self.EnableSaveProjectState() and ctr is None and
- projectOpen is None and self.Config.HasEntry("currenteditedproject")):
- try:
- projectOpen = DecodeFileSystemPath(self.Config.Read("currenteditedproject"))
- if projectOpen == "":
- projectOpen = None
- except:
- projectOpen = None
-
if projectOpen is not None and os.path.isdir(projectOpen):
self.CTR = ProjectController(self, self.Log)
self.Controler = self.CTR
@@ -472,9 +528,6 @@
self.RefreshAll()
self.LogConsole.SetFocus()
- def RiseLogConsole(self):
- self.BottomNoteBook.SetSelection(self.BottomNoteBook.GetPageIndex(self.LogConsole))
-
def RefreshTitle(self):
name = _("Beremiz")
if self.CTR is not None:
@@ -527,21 +580,37 @@
wnd = self
InspectionTool().Show(wnd, True)
- def OnLogConsoleDClick(self, event):
- wx.CallAfter(self.SearchLineForError)
+ def OnLogConsoleFocusChanged(self, event):
+ self.RefreshEditMenu()
event.Skip()
- def SearchLineForError(self):
+ def OnLogConsoleUpdateUI(self, event):
+ self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
+ event.Skip()
+
+ def OnLogConsoleMarginClick(self, event):
+ line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
+ wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx))
+ event.Skip()
+
+ def OnLogConsoleModified(self, event):
+ line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
+ line = self.LogConsole.GetLine(line_idx)
+ if line:
+ result = MATIEC_ERROR_MODEL.match(line)
+ if result is not None:
+ self.LogConsole.MarkerAdd(line_idx, 0)
+ event.Skip()
+
+ def SearchLineForError(self, line):
if self.CTR is not None:
- text = self.LogConsole.GetRange(0, self.LogConsole.GetInsertionPoint())
- line = self.LogConsole.GetLineText(len(text.splitlines()) - 1)
result = MATIEC_ERROR_MODEL.match(line)
if result is not None:
first_line, first_column, last_line, last_column, error = result.groups()
infos = self.CTR.ShowError(self.Log,
(int(first_line), int(first_column)),
(int(last_line), int(last_column)))
-
+
## Function displaying an Error dialog in PLCOpenEditor.
# @return False if closing cancelled.
def CheckSaveBeforeClosing(self, title=_("Close Project")):
@@ -590,6 +659,10 @@
return IDEFrame.LoadTab(self, notebook, page_infos)
def OnCloseFrame(self, event):
+ for evt_type in [wx.EVT_SET_FOCUS,
+ wx.EVT_KILL_FOCUS,
+ wx.stc.EVT_STC_UPDATEUI]:
+ self.LogConsole.Unbind(evt_type)
if self.CTR is None or self.CheckSaveBeforeClosing(_("Close Application")):
if self.CTR is not None:
self.CTR.KillDebugThread()
@@ -597,13 +670,6 @@
self.SaveLastState()
- if self.CTR is not None:
- project_path = os.path.realpath(self.CTR.GetProjectPath())
- else:
- project_path = ""
- self.Config.Write("currenteditedproject", EncodeFileSystemPath(project_path))
- self.Config.Flush()
-
event.Skip()
else:
event.Veto()
@@ -728,6 +794,9 @@
def RefreshEditMenu(self):
IDEFrame.RefreshEditMenu(self)
+ if self.FindFocus() == self.LogConsole:
+ self.EditMenu.Enable(wx.ID_COPY, True)
+ self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True)
if self.CTR is not None:
selected = self.TabsOpened.GetSelection()
@@ -781,6 +850,10 @@
def GetConfigEntry(self, entry_name, default):
return cPickle.loads(str(self.Config.Read(entry_name, cPickle.dumps(default))))
+ def ResetConnectionStatusBar(self):
+ for field in xrange(self.ConnectionStatusBar.GetFieldsCount()):
+ self.ConnectionStatusBar.SetStatusText('', field)
+
def ResetView(self):
IDEFrame.ResetView(self)
self.ConfNodeInfos = {}
@@ -790,6 +863,7 @@
self.Log.flush()
if self.EnableDebug:
self.DebugVariablePanel.SetDataProducer(None)
+ self.ResetConnectionStatusBar()
def RefreshConfigRecentProjects(self, projectpath):
try:
@@ -808,10 +882,6 @@
IDEFrame.ResetPerspective(self)
self.RefreshStatusToolBar()
- def RestoreLastLayout(self):
- IDEFrame.RestoreLastLayout(self)
- self.RefreshStatusToolBar()
-
def OnNewProjectMenu(self, event):
if self.CTR is not None and not self.CheckSaveBeforeClosing():
return
@@ -878,8 +948,6 @@
self.RefreshConfigRecentProjects(projectpath)
if self.EnableDebug:
self.DebugVariablePanel.SetDataProducer(self.CTR)
- if self.EnableSaveProjectState():
- self.LoadProjectLayout()
self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
else:
self.ResetView()
@@ -893,7 +961,6 @@
if self.CTR is not None and not self.CheckSaveBeforeClosing():
return
- self.SaveProjectLayout()
self.ResetView()
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
self.RefreshAll()
@@ -917,7 +984,6 @@
self.CTR.SaveProjectAs()
self.RefreshAll()
self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
- event.Skip()
def OnQuitMenu(self, event):
self.Close()
@@ -933,10 +999,7 @@
IDEFrame.OnProjectTreeItemBeginEdit(self, event)
def OnProjectTreeRightUp(self, event):
- if wx.Platform == '__WXMSW__':
- item = event.GetItem()
- else:
- item, flags = self.ProjectTree.HitTest(wx.Point(event.GetX(), event.GetY()))
+ item = event.GetItem()
item_infos = self.ProjectTree.GetPyData(item)
if item_infos["type"] == ITEM_CONFNODE:
@@ -964,6 +1027,11 @@
confnode_menu.Destroy()
event.Skip()
+ elif item_infos["type"] != ITEM_PROJECT:
+ parent = self.ProjectTree.GetItemParent(item)
+ parent_name = self.ProjectTree.GetItemText(parent)
+ if item_infos["type"] != ITEM_RESOURCE or parent_name == _("Resources"):
+ IDEFrame.OnProjectTreeRightUp(self, event)
else:
IDEFrame.OnProjectTreeRightUp(self, event)
@@ -980,15 +1048,15 @@
IDEFrame.OnProjectTreeItemActivated(self, event)
def ProjectTreeItemSelect(self, select_item):
- name = self.ProjectTree.GetItemText(select_item)
- item_infos = self.ProjectTree.GetPyData(select_item)
- if item_infos["type"] == ITEM_CONFNODE:
- item_infos["confnode"]._OpenView(onlyopened=True)
- elif item_infos["type"] == ITEM_PROJECT:
- self.CTR._OpenView(onlyopened=True)
- else:
- IDEFrame.ProjectTreeItemSelect(self, select_item)
-
+ if select_item is not None and select_item.IsOk():
+ name = self.ProjectTree.GetItemText(select_item)
+ item_infos = self.ProjectTree.GetPyData(select_item)
+ if item_infos["type"] == ITEM_CONFNODE:
+ item_infos["confnode"]._OpenView(onlyopened=True)
+ elif item_infos["type"] == ITEM_PROJECT:
+ self.CTR._OpenView(onlyopened=True)
+ else:
+ IDEFrame.ProjectTreeItemSelect(self, select_item)
def SelectProjectTreeItem(self, tagname):
if self.ProjectTree is not None:
@@ -999,13 +1067,13 @@
if tagname == "Project":
self.SelectedItem = root
self.ProjectTree.SelectItem(root)
- wx.CallAfter(self.ResetSelectedItem)
+ self.ResetSelectedItem()
else:
return self.RecursiveProjectTreeItemSelection(root,
[(word, ITEM_CONFNODE) for word in tagname.split(".")])
elif words[0] == "R":
return self.RecursiveProjectTreeItemSelection(root, [(words[2], ITEM_RESOURCE)])
- else:
+ elif not os.path.exists(words[0]):
IDEFrame.SelectProjectTreeItem(self, tagname)
def GetAddConfNodeFunction(self, name, confnode=None):
@@ -1065,6 +1133,7 @@
#-------------------------------------------------------------------------------
# Exception Handler
#-------------------------------------------------------------------------------
+import threading, traceback
Max_Traceback_List_Size = 20
@@ -1159,11 +1228,26 @@
#sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
sys.excepthook = handle_exception
+ init_old = threading.Thread.__init__
+ def init(self, *args, **kwargs):
+ init_old(self, *args, **kwargs)
+ run_old = self.run
+ def run_with_except_hook(*args, **kw):
+ try:
+ run_old(*args, **kw)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ sys.excepthook(*sys.exc_info())
+ self.run = run_with_except_hook
+ threading.Thread.__init__ = init
+
if __name__ == '__main__':
# Install a exception handle for bug reports
AddExceptHook(os.getcwd(),updateinfo_url)
frame = Beremiz(None, projectOpen, buildpath)
- splash.Close()
+ if splash:
+ splash.Close()
frame.Show()
app.MainLoop()
--- a/Beremiz_service.py Wed Mar 13 12:34:55 2013 +0900
+++ b/Beremiz_service.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,7 +23,7 @@
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os, sys, getopt
-from threading import Thread,Timer
+from threading import Thread
def usage():
print """
@@ -114,6 +114,8 @@
import gettext
CWD = os.path.split(os.path.realpath(__file__))[0]
+ def Bpath(*args):
+ return os.path.join(CWD,*args)
# Get folder containing translation files
localedir = os.path.join(CWD,"locale")
@@ -138,109 +140,9 @@
if __name__ == '__main__':
__builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
- try:
- from wx.lib.embeddedimage import PyEmbeddedImage
- except:
- import cStringIO
- import base64
-
- class PyEmbeddedImage:
- def __init__(self, image_string):
- stream = cStringIO.StringIO(base64.b64decode(image_string))
- self.Image = wx.ImageFromStream(stream)
- def GetImage(self):
- return self.Image
-
- defaulticon = PyEmbeddedImage(
- "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABc5J"
- "REFUSIl9lW1MW9cZx3/n2vf6BQO2MZiXGBISILCVUEUlitYpjaKpXZJ1XZZ2kzJVY9r6IeLD"
- "pGTaNG3KtGmNNGlbpW3VFhRp0l6aZCllpVUqtVNJtBFKE5QXLxCjpCYEY7DBr9hcm3vPPgQY"
- "IQmPdKR7/vd5/v/n5dxzhZSSNeYBOoGDQGcoFPINDAyUDQ0NOUdGRmyGYSiBQGCpoaGhuGnT"
- "psShQ4f6WltbewEBVAK3gCBgrjJKKZFSKlLKeillt5Ty40gkMnnw4MFFQG60ysrKZHd3dyoe"
- "j//bNM0Le/fuPd/e3r5lmRMpJWK5ghrgFeBIT09P4/Hjx73pdFo47HaaNlfRutnJru0OKsoE"
- "E3GVqaSNa6EUw1dvIKWkoqKCrVu3FoeHh9WamppfRiKRn6wUYAUcwE7g2e7u7vrTp09XGIZB"
- "W1Mdv3qtmoBPrG0hHVsMhKLj6nqOqOWn/Pjnv2dgYIC5uTl1uSM71/pbgUbg6bNnz/rPnDnj"
- "dzoddO0P8Oo+jY2suDDD1Zv9DA1dfghXVbVBCFEqpcwAKEDTxMSE58SJE8+oqsq3nq/l1X0a"
- "QihYtNLHLqRET03wuYp7fO9r26mpKlsVUBSl0W63V6/shZTyyIEDB344Njb21JYaG7/5bgkA"
- "Dm8zTS/+7bHZLy0mSN+7yNztt8nPjYHFwfvXDf1P70zZ0ok0LS0tZy9fvvxNAGswGFQnJyef"
- "KnM5+NHLzuUDsrFZ7R68zS/hrGon1PcNMPI0BIzs9tcCNvNfDqxW64uqqvqKxWJc6e3trVVV"
- "leaAk6ryJ5N/9tH3GXv7Je7/5xermN3diMPXCkDfgrkg3UU0txWLxeLw+/1fB1BGR0frbTYb"
- "TXWWDbNeysUoZKbIRIZBPviOzKU8ejLMHyPFcMprrweQ7iUAXC7XPiGEak2lUk02m42mWn1D"
- "gfrnTiKNIrbyzSAUjEKWCx+/Mf+HyELBrLBvBhAIKDdgGsrLy+sAv1UIUa1pGv7yxQ0FbGX1"
- "D+0LQmHW7fVavE5Mo/gAFCCcoOs6NpvNA7gVRVGCmqYRz1hXg7NFU39rjshawjcuvs4P+o/y"
- "24uvE1+I4VCdfGfXUb76+VdWfQQCkbJSKBQoFApJTdMsCvApQDSlAjCTN7I/y5CNllpq1wqE"
- "YmPciIzwwdi7BKevreK7Gp5dfbYoFoozJrquo+v6rMViWbQCV4QQzGTsQJY3kzIhvFpgfYte"
- "7jhCMp9kk7uep+ueWcWj6f8Xqioq8ck0xcIS6XT6vpRy3gqMqKpqRBfKLLNF1ZRV6YBiPDrw"
- "vduefwTL6hl6b74FgFVR0T4rJTU3jcvlymcymal8Ph+z9vf3p7u6uv5y/vz5bw994ld2fmUH"
- "7nYFRVG4Gb3Guv8FpmmQzCcIJ+5w8c5HRFL3UYRC+ZKX633j6LpObW3tDcMwrsODq4Jbt27V"
- "HT58+N7o6KgCYHfY2f2lXfi+6CJbnsAwjUeyXzFFKLgdHqb+mmL8xh22bduWmJycfHN2dvbX"
- "uVwuoQC0tbXlKisrYytBi/lFZsKzOErtTyQWCOxWO36ljvl/FLk+dJOSkhJTUZR35+fn+3K5"
- "XAIeXNcASz6fbxzwrxDYVQdqpARvs498IYchDUxpogiBVVFxqE7U/5Zx4c8fEo/FKS0tlR0d"
- "HZ8ODg6+l06nr6zwrAp4PJ6Qpmlf2L9/fywYDFaOXB0RI1dHaGpuoq29Fa1Uxe62YeZMInei"
- "jAY/IRqNAtDZ2blUV1fXPzg4+F5VVdU/H6p0eYjqsWPHvnz37t0XwuHw7d27d4eTyeTvLl26"
- "FJiamnpim6qrq9mzZ094fHz875FI5J3p6ekr631WBARgaWlpCezYsePeuXPnzFAo5Dp58uS+"
- "dDp91GKxNBYKBW82m3Vomqa7XK7pbDYbnJmZuR2LxYL5fP79WCyWeeys1h/D9e97enqsp06d"
- "8mWzWU+xWPTkcjmXaZpxwzDCsVhsbqNggP8BMJOU3UUUf+0AAAAASUVORK5CYII=")
-
- #----------------------------------------------------------------------
- starticon = PyEmbeddedImage(
- "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABbpJ"
- "REFUSIl9lltsFNcdxn9nZnbHs15fd23j9TXYC0UCKzEhMQ+oIS2g1kQ1pbFStX0opFWsovSh"
- "rUqp2pS2ioTUolaKFOGHqGkiJcKRuDhOaZRiZCsCXyBgCBBfMfbu+oa9s17wzuzl9MH24mDD"
- "XzoPc/6fft+c72jOGSGlZEVlAU8D9cB20zQ9HR0duRcvXszq7e01EomEUlFREa+srLR8Pl+g"
- "sbHx3zk5ORcAFfACA8Bt4CFUSomUUkgpS6SUB6SUH5umOXLgwIEHqqrKJfGao7S0VB49ejRo"
- "2/YnUsrT+/fvb66pqSldYiKlRCytoBB4Gfjx6dOnq5qamjwTExOKqqqU+QrYUJFN7QY32Qbc"
- "vSeYCGtcux1i5M5dAPx+P1VVVQvnzp0ziouLfx8MBt9cXoAGZABbgZ1HjhwpO378eEEymaSi"
- "tIBjPy9lU5nKoyWExF2yjy+mN3HsH+/Q3d3NwMCAsZTI9pVaDXgK2Hr27Nn85ubmEpdh8IMX"
- "ffxirwshVrGXHBQSC/dIRvoZGuz/WkvTtHIhhCGlXABQgI2Tk5P5hw8f3uZwOGj8VjGHXnoC"
- "HJCpJFbkLtr8FXbX+XC79HRPVVW/qqre9LtIKX/S0NDwy76+vq1lhTr/fM2NAmTk+fHv/dea"
- "BlZkDHP0PHODH2NHg1gykw8/X7Dfb7vjTNgJqqurT3R1db0GoF2/fl0fGhqqdWca/K7RhZLO"
- "WSBU55oGGXlVZORVkeV7nsFPDqKL+9TWJCI3n9rojX2mYhjGj4QQv5FSziunTp0qdjqd4hvl"
- "Lnz5j49lrPMNhv7zM6b63knPuQpryMj3A9A2L++nvDaZXheqqrrXrVu3D0C5detWudPpxO/T"
- "Hk8HYnOD3J+8yr3bH6XnZNImHg3xfsgenfHo5QAyJwFAdnb2HiGEppmmWa3rOhtKrCcalNT9"
- "llTSwvBsXISn4nRdbJ5/czRsWvlGhQAEYtFg0kl2dnYZUKgB5U6nk5L82BMNXIU1X3uOWFH5"
- "eWIuy/YYWcjU4qQAxQ22bWMYhgfIU1RV/UrXdWaiDyOyUiLROktoJfDtC8fZfWQbb//v75ix"
- "MDlGnvjVC3+gflNDWiMQKPMalmVh2/a8w+HQFKAHIBR2ABCOS+uN6cTMoFstXmlwZbSba7tv"
- "8hfzT7z+7k+ZnZ0BoK5yR1qjCBV7MoVt29i2PaWqqq0BvUIIQqYORHlrKj6R9BoVj0b04oY9"
- "nEt+yvz3Y5yR/+Xap3XsDb/EtvV1aY1DdTA7HsW2bCKRyLiUclYBelRVldNWAfPSm4oV5ZQJ"
- "Vn/G9Zv2oWt6Ous7e4K81XiC1wNNBO6OIWKgB7Mwp000TYuFw+GxWCw2qbS2tk7k5uae/eDD"
- "Fn594p6SFyxRCjKLUBWF8fBoegTNMVLLm/kwdMyGGON/nePLklv0dl/Cii3gdrtvAzdg8aig"
- "vb296uDBgwMjIyMCwFvoZXv9NvRnIKqHSckUyQdJrtfexPqm5LGVAuNdVaofcCVywfpexLYD"
- "CsDOnTvnioqKzGXdzNQMV9tvkJEyUITyeOAjpYyAc9gxYc/GWyK2HYDF4xog6fV6h1i8FwCo"
- "LK/EncwhkWGxEH9AXLMXM2H1CpQBifI3yeapZ+70d43+cSo4+95yL23g8XiGFUWp3bVrV/Ty"
- "5ctZnR2ddHZ08uxzz1K9eT1GRhJls1gFlsfieK+WpJ5e/3z7pcuXzmia1rJSs3xlOg8dOvTD"
- "8fHx7wQCgb4tW7bMm6b55/Pnz+eGw+FFGJDT5iT1XRWlfxHMZ06+/Vz9dCAQeG9kZKR1x44d"
- "nSdPnkyuZSAArbq6eqOiKAP9/f3xlpaWgra2tlei0eiryWSyKGKa2TcaL+muwcxU5aDf9Gi+"
- "L0Oh0BehUOiaZVlnAoHAzFr7Ih75bVnVb2pqcvf09Phi0ei6+/rUC6lw1k0p5bSUctThcIwP"
- "Dw/HnwT4P6CDl+TMvD0JAAAAAElFTkSuQmCC")
-
- #----------------------------------------------------------------------
- stopicon = PyEmbeddedImage(
- "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABPRJ"
- "REFUSImdlllsVGUUx3/f/e4sd5iZLjNt6XSFdtgkjWFRePABDaCBGgjamIg81CU0aoxbRHww"
- "+EDkhWjEB5rYGEMUxQTCJg8EoQ2BbgrFCNJWltplgC63naEzd+bO50NLLVAq4STfwz3nfP/f"
- "PSf3O98VSikmmQ94HFgDLDdNM1BfX5955swZX0tLi5FKpbSSkpJkaWlpIhQKdVdVVX2XkZFx"
- "EpBAEGgHLgH/iSqlUEoJpVSBUqpaKXXYNM0r1dXVt6WUajx5ylVYWKi2bdvWY1nWUaXUgQ0b"
- "NtRWVFQUjmuilEKMV5ALvAhsPHDgQFlNTU2gr69Pk1JSFMphTomfRXO8+A243i/oG9I5f6mX"
- "K1evAxAOhykrKxs9duyYkZ+f/0lPT8/2OwXogBtYDKzYunVr0c6dO3Ns26akMIcdbxQyv0hy"
- "rwmh8Bas5/eb89nxRR1NTU20t7cb4x1ZPjlXB2YBiw8ePJhdW1tb4DEMXng6xJtrPQhxn/Y4"
- "QSM12o89fJnOjst3hXRdLxZCGEqpUQANmBuJRLK3bNmy1OFwUPVMPm9VTiMOqLRNYvg6+shv"
- "rFoWwutxTcSklGEpZXDiXZRSr6xbt+6dtra2xUW5Lr7c7EUD3Flhwmu/nRKQGO7CvHaCwY7D"
- "WNEeEmoGe0+PWnuOXHWmrBTl5eW7GxsbNwPoFy5ccHV2di7yzjD4uMqDNtFngZDOKQHurDLc"
- "WWX4Qk/ScfRVXCLGoorU8J+z5gbjxyWGYbwshPhQKTWi7d+/P9/pdIp5xR5C2Q9uS1fDp3T+"
- "8jo32uomfJ7cCtzZYQCOjKhYOmgxI+hBSumdOXPmegDt4sWLxU6nk3BIf7A6EB/sIBY5R/+l"
- "nyd8yrZIRnvZ02tduxVwFQOojBQAfr9/tRBC103TLHe5XMwpSEwLKFj2EWk7gRGYOyaeTtJ4"
- "pnZk+7UhM5FtlAhAIMYAESd+v78IyNWBYqfTSUF2fFqAJ7firufhRFSdTg36rIDhQ6XHnAI0"
- "L1iWhWEYASBLl1L+JaWcfSuqk+u3AUikRer4ADffg/w7gt80fs35r34k3BYh2xNAarooAJ4d"
- "vsHgaP8EWMR17GiaVo8r0+Fw6DrQDDzXO+RgQSjBUFIlPh+wB0vLZD6TrLWrkWRXB29fGAK6"
- "pql1rNXVmrCklJYGtAgh6DXHDsuuG8k+O9M5895tq+atpSwwZ9o2TjZlWTGl1IAGNEsp1c1E"
- "DiMqmI7nZRQJ7j/G6xZWMS/vsYcGkEzG4vF4RDt06FBfZmbmwR/27uOD3f1aVk+BljMjD6lp"
- "/DN07a4VTYw8tL4rrQZgbNixadOm90+dOvX82cZmcbaxmWBukOVrlvJudw1R1xDp8a+kuPM6"
- "Gx8S4LXtCIwNO1asWDGYl5dn3gneunGLc7/+gTttoAntQRrTmgMmpimAHQwGOycnlBaX4rUz"
- "8LszMRweXLr7kWB35oMdCAT+1jRt0cqVK6Otra2+hvoGGuobWPLEEsoXzkbPkLhvR4CBRwJY"
- "Xq/3SGVlZbq7u7utsrJyxDTNz06cOJHZ0tRCS1MLAKuRwNQT9v8AyV27dn1fXl7eqmlae11d"
- "XXLfvn0/+Xy+l6LR6Gu2befFYjFfzrk2FzeHp7mK7jdxz2/LffGamhpvc3NzyLKsbFd3z1PG"
- "aHyBTKdjum0POGzbFAp7qo0xVOtJZdf/C/wRDnL5FYGSAAAAAElFTkSuQmCC")
+ defaulticon = wx.Image(Bpath("images", "brz.png"))
+ starticon = wx.Image(Bpath("images", "icoplay24.png"))
+ stopicon = wx.Image(Bpath("images", "icostop24.png"))
class ParamsEntryDialog(wx.TextEntryDialog):
if wx.VERSION < (2, 6, 0):
@@ -349,12 +251,10 @@
def OnTaskBarStartPLC(self, evt):
if self.pyroserver.plcobj is not None:
self.pyroserver.plcobj.StartPLC()
- evt.Skip()
def OnTaskBarStopPLC(self, evt):
if self.pyroserver.plcobj is not None:
Thread(target=self.pyroserver.plcobj.StopPLC).start()
- evt.Skip()
def OnTaskBarChangeInterface(self, evt):
dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip_addr)
@@ -364,7 +264,6 @@
if dlg.ShowModal() == wx.ID_OK:
self.pyroserver.ip_addr = dlg.GetValue()
self.pyroserver.Stop()
- evt.Skip()
def OnTaskBarChangePort(self, evt):
dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
@@ -372,14 +271,12 @@
if dlg.ShowModal() == wx.ID_OK:
self.pyroserver.port = int(dlg.GetValue())
self.pyroserver.Stop()
- evt.Skip()
def OnTaskBarChangeWorkingDir(self, evt):
dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
if dlg.ShowModal() == wx.ID_OK:
self.pyroserver.workdir = dlg.GetPath()
self.pyroserver.Stop()
- evt.Skip()
def OnTaskBarChangeName(self, evt):
dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name)
@@ -387,19 +284,17 @@
if dlg.ShowModal() == wx.ID_OK:
self.pyroserver.name = dlg.GetValue()
self.pyroserver.Restart()
- evt.Skip()
def _LiveShellLocals(self):
if self.pyroserver.plcobj is not None:
- return {"locals":self.pyroserver.plcobj.python_threads_vars}
+ return {"locals":self.pyroserver.plcobj.python_runtime_vars}
else:
return {}
-
+
def OnTaskBarLiveShell(self, evt):
from wx import py
frame = py.crust.CrustFrame(**self._LiveShellLocals())
frame.Show()
- evt.Skip()
def OnTaskBarWXInspector(self, evt):
# Activate the widget inspection tool
@@ -409,22 +304,20 @@
wnd = wx.GetApp()
InspectionTool().Show(wnd, True)
-
- evt.Skip()
def OnTaskBarQuit(self, evt):
- Thread(target=self.pyroserver.Quit).start()
+ if wx.Platform == '__WXMSW__':
+ Thread(target=self.pyroserver.Quit).start()
self.RemoveIcon()
- wx.CallAfter(wx.GetApp().Exit)
- evt.Skip()
+ wx.CallAfter(wx.GetApp().ExitMainLoop)
def UpdateIcon(self, plcstatus):
if plcstatus is "Started" :
- currenticon = self.MakeIcon(starticon.GetImage())
+ currenticon = self.MakeIcon(starticon)
elif plcstatus is "Stopped":
- currenticon = self.MakeIcon(stopicon.GetImage())
+ currenticon = self.MakeIcon(stopicon)
else:
- currenticon = self.MakeIcon(defaulticon.GetImage())
+ currenticon = self.MakeIcon(defaulticon)
self.SetIcon(currenticon, "Beremiz Service")
from runtime import PLCObject, PLCprint, ServicePublisher
@@ -436,8 +329,8 @@
def default_evaluator(tocall, *args, **kwargs):
try:
res=(tocall(*args,**kwargs), None)
- except Exception,exp:
- res=(None, exp)
+ except Exception:
+ res=(None, sys.exc_info())
return res
class Server():
@@ -465,6 +358,8 @@
def Quit(self):
self.continueloop = False
+ if self.plcobj is not None:
+ self.plcobj.UnLoadPLC()
self.Stop()
def Start(self):
@@ -487,7 +382,7 @@
self.servicepublisher = ServicePublisher.ServicePublisher()
self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
- if self.autostart:
+ if self.autostart and self.plcobj.GetPLCstatus()[0] != "Empty":
self.plcobj.StartPLC()
sys.stdout.flush()
@@ -495,7 +390,8 @@
self.daemon.requestLoop()
def Stop(self):
- self.plcobj.StopPLC()
+ if self.plcobj is not None:
+ self.plcobj.StopPLC()
if self.servicepublisher is not None:
self.servicepublisher.UnRegisterService()
self.servicepublisher = None
@@ -710,6 +606,31 @@
else:
pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, website=website)
+# Exception hooks s
+import threading, traceback
+def LogException(*exp):
+ if pyroserver.plcobj is not None:
+ pyroserver.plcobj.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
+ else:
+ traceback.print_exception(*exp)
+
+sys.excepthook = LogException
+def installThreadExcepthook():
+ init_old = threading.Thread.__init__
+ def init(self, *args, **kwargs):
+ init_old(self, *args, **kwargs)
+ run_old = self.run
+ def run_with_except_hook(*args, **kw):
+ try:
+ run_old(*args, **kw)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ sys.excepthook(*sys.exc_info())
+ self.run = run_with_except_hook
+ threading.Thread.__init__ = init
+installThreadExcepthook()
+
if havetwisted or havewx:
pyro_thread=Thread(target=pyroserver.Loop)
pyro_thread.start()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/CodeFileTreeNode.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,200 @@
+import os
+from xml.dom import minidom
+import cPickle
+
+from xmlclass import GenerateClassesFromXSDstring, UpdateXMLClassGlobals
+
+from PLCControler import UndoBuffer
+
+CODEFILE_XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="%(codefile_name)s">
+ <xsd:complexType>
+ <xsd:sequence>
+ %(includes_section)s
+ <xsd:element name="variables">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ <xsd:attribute name="class" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="input"/>
+ <xsd:enumeration value="memory"/>
+ <xsd:enumeration value="output"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="initial" type="xsd:string" use="optional" default=""/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ %(sections)s
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="CodeText">
+ <xsd:annotation>
+ <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
+ </xsd:sequence>
+ </xsd:complexType>
+</xsd:schema>"""
+
+SECTION_TAG_ELEMENT = "<xsd:element name=\"%s\" type=\"CodeText\"/>"
+
+class CodeFile:
+
+ CODEFILE_NAME = "CodeFile"
+ SECTIONS_NAMES = []
+
+ def __init__(self):
+ sections_str = {"codefile_name": self.CODEFILE_NAME}
+ if "includes" in self.SECTIONS_NAMES:
+ sections_str["includes_section"] = SECTION_TAG_ELEMENT % "includes"
+ else:
+ sections_str["includes_section"] = ""
+ sections_str["sections"] = "\n".join(
+ [SECTION_TAG_ELEMENT % name
+ for name in self.SECTIONS_NAMES if name != "includes"])
+
+ self.CodeFileClasses = GenerateClassesFromXSDstring(
+ CODEFILE_XSD % sections_str)
+
+ filepath = self.CodeFileName()
+
+ self.CodeFile = self.CodeFileClasses[self.CODEFILE_NAME]()
+ if os.path.isfile(filepath):
+ xmlfile = open(filepath, 'r')
+ tree = minidom.parse(xmlfile)
+ xmlfile.close()
+
+ for child in tree.childNodes:
+ if child.nodeType == tree.ELEMENT_NODE and child.nodeName in [self.CODEFILE_NAME]:
+ self.CodeFile.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
+ self.CreateCodeFileBuffer(True)
+ else:
+ self.CreateCodeFileBuffer(False)
+ self.OnCTNSave()
+
+ def GetBaseTypes(self):
+ return self.GetCTRoot().GetBaseTypes()
+
+ def GetDataTypes(self, basetypes = False):
+ return self.GetCTRoot().GetDataTypes(basetypes=basetypes)
+
+ def GenerateNewName(self, format, start_idx):
+ return self.GetCTRoot().GenerateNewName(
+ None, None, format, start_idx,
+ dict([(var.getname().upper(), True)
+ for var in self.CodeFile.variables.getvariable()]))
+
+ def SetVariables(self, variables):
+ self.CodeFile.variables.setvariable([])
+ for var in variables:
+ variable = self.CodeFileClasses["variables_variable"]()
+ variable.setname(var["Name"])
+ variable.settype(var["Type"])
+ variable.setinitial(var["Initial"])
+ self.CodeFile.variables.appendvariable(variable)
+
+ def GetVariables(self):
+ datas = []
+ for var in self.CodeFile.variables.getvariable():
+ datas.append({"Name" : var.getname(),
+ "Type" : var.gettype(),
+ "Initial" : var.getinitial()})
+ return datas
+
+ def SetTextParts(self, parts):
+ for section in self.SECTIONS_NAMES:
+ section_code = parts.get(section)
+ if section_code is not None:
+ getattr(self.CodeFile, section).settext(section_code)
+
+ def GetTextParts(self):
+ return dict([(section, getattr(self.CodeFile, section).gettext())
+ for section in self.SECTIONS_NAMES])
+
+ def CTNTestModified(self):
+ return self.ChangesToSave or not self.CodeFileIsSaved()
+
+ def OnCTNSave(self, from_project_path=None):
+ filepath = self.CodeFileName()
+
+ text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ text += self.CodeFile.generateXMLText(self.CODEFILE_NAME, 0)
+
+ xmlfile = open(filepath,"w")
+ xmlfile.write(text.encode("utf-8"))
+ xmlfile.close()
+
+ self.MarkCodeFileAsSaved()
+ return True
+
+ def CTNGlobalInstances(self):
+ current_location = self.GetCurrentLocation()
+ return [(variable.getname(),
+ variable.gettype(),
+ variable.getinitial())
+ for variable in self.CodeFile.variables.variable]
+
+#-------------------------------------------------------------------------------
+# Current Buffering Management Functions
+#-------------------------------------------------------------------------------
+
+ def cPickle_loads(self, str_obj):
+ UpdateXMLClassGlobals(self.CodeFileClasses)
+ return cPickle.loads(str_obj)
+
+ def cPickle_dumps(self, obj):
+ UpdateXMLClassGlobals(self.CodeFileClasses)
+ return cPickle.dumps(obj)
+
+ """
+ Return a copy of the codefile model
+ """
+ def Copy(self, model):
+ return self.cPickle_loads(self.cPickle_dumps(model))
+
+ def CreateCodeFileBuffer(self, saved):
+ self.Buffering = False
+ self.CodeFileBuffer = UndoBuffer(self.cPickle_dumps(self.CodeFile), saved)
+
+ def BufferCodeFile(self):
+ self.CodeFileBuffer.Buffering(self.cPickle_dumps(self.CodeFile))
+
+ def StartBuffering(self):
+ self.Buffering = True
+
+ def EndBuffering(self):
+ if self.Buffering:
+ self.CodeFileBuffer.Buffering(self.cPickle_dumps(self.CodeFile))
+ self.Buffering = False
+
+ def MarkCodeFileAsSaved(self):
+ self.EndBuffering()
+ self.CodeFileBuffer.CurrentSaved()
+
+ def CodeFileIsSaved(self):
+ return self.CodeFileBuffer.IsCurrentSaved() and not self.Buffering
+
+ def LoadPrevious(self):
+ self.EndBuffering()
+ self.CodeFile = self.cPickle_loads(self.CodeFileBuffer.Previous())
+
+ def LoadNext(self):
+ self.CodeFile = self.cPickle_loads(self.CodeFileBuffer.Next())
+
+ def GetBufferState(self):
+ first = self.CodeFileBuffer.IsFirst() and not self.Buffering
+ last = self.CodeFileBuffer.IsLast()
+ return not first, not last
+
--- a/ConfigTreeNode.py Wed Mar 13 12:34:55 2013 +0900
+++ b/ConfigTreeNode.py Wed Jul 31 10:45:07 2013 +0900
@@ -73,10 +73,12 @@
def ConfNodePath(self):
return os.path.join(self.CTNParent.ConfNodePath(), self.CTNType)
- def CTNPath(self,CTNName=None):
+ def CTNPath(self,CTNName=None,project_path=None):
if not CTNName:
CTNName = self.CTNName()
- return os.path.join(self.CTNParent.CTNPath(),
+ if not project_path:
+ project_path = self.CTNParent.CTNPath()
+ return os.path.join(project_path,
CTNName + NameTypeSeparator + self.CTNType)
def CTNName(self):
@@ -113,7 +115,7 @@
def RemoteExec(self, script, **kwargs):
return self.CTNParent.RemoteExec(script, **kwargs)
- def OnCTNSave(self):
+ def OnCTNSave(self, from_project_path=None):
#Default, do nothing and return success
return True
@@ -148,14 +150,16 @@
parts = path.split(".", 1)
if self.MandatoryParams and parts[0] == self.MandatoryParams[0]:
self.MandatoryParams[1].setElementValue(parts[1], value)
+ value = self.MandatoryParams[1].getElementInfos(parts[0], parts[1])["value"]
elif self.CTNParams and parts[0] == self.CTNParams[0]:
self.CTNParams[1].setElementValue(parts[1], value)
+ value = self.CTNParams[1].getElementInfos(parts[0], parts[1])["value"]
return value, False
def CTNMakeDir(self):
os.mkdir(self.CTNPath())
- def CTNRequestSave(self):
+ def CTNRequestSave(self, from_project_path=None):
if self.GetCTRoot().CheckProjectPathPerm(False):
# If confnode do not have corresponding directory
ctnpath = self.CTNPath()
@@ -178,7 +182,7 @@
XMLFile.close()
# Call the confnode specific OnCTNSave method
- result = self.OnCTNSave()
+ result = self.OnCTNSave(from_project_path)
if not result:
return _("Error while saving \"%s\"\n")%self.CTNPath()
@@ -186,7 +190,10 @@
self.ChangesToSave = False
# go through all children and do the same
for CTNChild in self.IterChildren():
- result = CTNChild.CTNRequestSave()
+ CTNChildPath = None
+ if from_project_path is not None:
+ CTNChildPath = CTNChild.CTNPath(project_path=from_project_path)
+ result = CTNChild.CTNRequestSave(CTNChildPath)
if result:
return result
return None
@@ -465,6 +472,8 @@
shutil.rmtree(CTNInstance.CTNPath())
# Remove child of Children
self.Children[CTNInstance.CTNType].remove(CTNInstance)
+ if len(self.Children[CTNInstance.CTNType]) == 0:
+ self.Children.pop(CTNInstance.CTNType)
# Forget it... (View have to refresh)
def CTNRemove(self):
--- a/IDEFrame.py Wed Mar 13 12:34:55 2013 +0900
+++ b/IDEFrame.py Wed Jul 31 10:45:07 2013 +0900
@@ -22,7 +22,8 @@
from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
from editors.DataTypeEditor import DataTypeEditor
from PLCControler import *
-from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, DebugVariablePanel, SearchResultPanel
+from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, SearchResultPanel
+from controls.DebugVariablePanel import DebugVariablePanel
from dialogs import ProjectDialog, PouDialog, PouTransitionDialog, PouActionDialog, FindInPouDialog, SearchInProjectDialog
from util.BitmapLibrary import GetBitmap
@@ -210,15 +211,13 @@
def GetDeleteElementFunction(remove_function, parent_type=None, check_function=None):
def DeleteElementFunction(self, selected):
name = self.ProjectTree.GetItemText(selected)
- if check_function is None or not check_function(self.Controler, name):
+ if check_function is None or check_function(name):
if parent_type is not None:
item_infos = self.ProjectTree.GetPyData(selected)
parent_name = item_infos["tagname"].split("::")[1]
remove_function(self.Controler, parent_name, name)
else:
remove_function(self.Controler, name)
- else:
- self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!")%name)
return DeleteElementFunction
if wx.Platform == '__WXMSW__':
@@ -308,8 +307,6 @@
class IDEFrame(wx.Frame):
- Starting = False
-
# Compatibility function for wx versions < 2.6
if wx.VERSION < (2, 6, 0):
def Bind(self, event, function, id = None):
@@ -457,7 +454,6 @@
style=wx.DEFAULT_FRAME_STYLE)
self.SetClientSize(wx.Size(1000, 600))
self.Bind(wx.EVT_ACTIVATE, self.OnActivated)
- self.Bind(wx.EVT_SIZE, self.OnResize)
self.TabsImageList = wx.ImageList(31, 16)
self.TabsImageListIndexes = {}
@@ -526,22 +522,18 @@
self.ProjectTree = CustomTree(id=ID_PLCOPENEDITORPROJECTTREE,
name='ProjectTree', parent=self.ProjectPanel,
pos=wx.Point(0, 0), size=wx.Size(0, 0),
- style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_EDIT_LABELS)
+ style=wx.SUNKEN_BORDER,
+ agwStyle=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.TR_EDIT_LABELS)
self.ProjectTree.SetBackgroundBitmap(GetBitmap("custom_tree_background"),
wx.ALIGN_RIGHT|wx.ALIGN_BOTTOM)
add_menu = wx.Menu()
self._init_coll_AddMenu_Items(add_menu)
self.ProjectTree.SetAddMenu(add_menu)
- if wx.Platform == '__WXMSW__':
- self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnProjectTreeRightUp,
- id=ID_PLCOPENEDITORPROJECTTREE)
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnProjectTreeItemSelected,
- id=ID_PLCOPENEDITORPROJECTTREE)
- else:
- self.ProjectTree.Bind(wx.EVT_RIGHT_UP, self.OnProjectTreeRightUp)
- self.ProjectTree.Bind(wx.EVT_LEFT_UP, self.OnProjectTreeLeftUp)
- self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnProjectTreeItemChanging,
- id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnProjectTreeRightUp,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.ProjectTree.Bind(wx.EVT_LEFT_UP, self.OnProjectTreeLeftUp)
+ self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnProjectTreeItemChanging,
+ id=ID_PLCOPENEDITORPROJECTTREE)
self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnProjectTreeBeginDrag,
id=ID_PLCOPENEDITORPROJECTTREE)
self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnProjectTreeItemBeginEdit,
@@ -550,6 +542,7 @@
id=ID_PLCOPENEDITORPROJECTTREE)
self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnProjectTreeItemActivated,
id=ID_PLCOPENEDITORPROJECTTREE)
+ self.ProjectTree.Bind(wx.EVT_MOTION, self.OnProjectTreeMotion)
#-----------------------------------------------------------------------
# Creating PLCopen Project POU Instance Variables Panel
@@ -677,12 +670,24 @@
self.CurrentEditorToolBar = []
self.CurrentMenu = None
self.SelectedItem = None
+ self.LastToolTipItem = None
self.SearchParams = None
self.Highlights = {}
self.DrawingMode = FREEDRAWING_MODE
#self.DrawingMode = DRIVENDRAWING_MODE
self.AuiTabCtrl = []
- self.DefaultPerspective = None
+
+ # Save default perspective
+ notebooks = {}
+ for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+ (self.BottomNoteBook, "bottomnotebook"),
+ (self.RightNoteBook, "rightnotebook")]:
+ notebooks[entry_name] = self.SaveTabLayout(notebook)
+ self.DefaultPerspective = {
+ "perspective": self.AUIManager.SavePerspective(),
+ "notebooks": notebooks,
+ }
+
# Initialize Printing configuring elements
self.PrintData = wx.PrintData()
@@ -693,13 +698,11 @@
self.PageSetupData.SetMarginBottomRight(wx.Point(10, 20))
self.SetRefreshFunctions()
+ self.SetDeleteFunctions()
def __del__(self):
self.FindDialog.Destroy()
- def ResetStarting(self):
- self.Starting = False
-
def Show(self):
wx.Frame.Show(self)
wx.CallAfter(self.RestoreLastState)
@@ -709,59 +712,21 @@
wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
event.Skip()
+ def SelectTab(self, tab):
+ for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
+ idx = notebook.GetPageIndex(tab)
+ if idx != wx.NOT_FOUND and idx != notebook.GetSelection():
+ notebook.SetSelection(idx)
+ return
+
#-------------------------------------------------------------------------------
# Saving and restoring frame organization functions
#-------------------------------------------------------------------------------
- def OnResize(self, event):
- if self.Starting:
- self.RestoreLastLayout()
- event.Skip()
-
- def EnableSaveProjectState(self):
- return False
-
- def GetProjectConfiguration(self):
- projects = {}
- try:
- if self.Config.HasEntry("projects"):
- projects = cPickle.loads(str(self.Config.Read("projects")))
- except:
- pass
-
- return projects.get(
- EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
-
- def SavePageState(self, page):
- state = page.GetState()
- if state is not None:
- if self.Config.HasEntry("projects"):
- projects = cPickle.loads(str(self.Config.Read("projects")))
- else:
- projects = {}
-
- project_infos = projects.setdefault(
- EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
- editors_state = project_infos.setdefault("editors_state", {})
-
- if page.IsDebugging():
- editors_state[page.GetInstancePath()] = state
- else:
- editors_state[page.GetTagName()] = state
-
- self.Config.Write("projects", cPickle.dumps(projects))
- self.Config.Flush()
-
def GetTabInfos(self, tab):
- if isinstance(tab, EditorPanel):
- if tab.IsDebugging():
- return ("debug", tab.GetInstancePath())
- else:
- return ("editor", tab.GetTagName())
- else:
- for page_name, (page_ref, page_title) in self.MainTabs.iteritems():
- if page_ref == tab:
- return ("main", page_name)
+ for page_name, (page_ref, page_title) in self.MainTabs.iteritems():
+ if page_ref == tab:
+ return ("main", page_name)
return None
def SaveTabLayout(self, notebook):
@@ -854,115 +819,19 @@
if self.Config.HasEntry("framesize"):
frame_size = cPickle.loads(str(self.Config.Read("framesize")))
- self.Starting = True
if frame_size is None:
self.Maximize()
else:
self.SetClientSize(frame_size)
- wx.CallAfter(self.RestoreLastLayout)
-
- def RestoreLastLayout(self):
- notebooks = {}
- for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
- (self.BottomNoteBook, "bottomnotebook"),
- (self.RightNoteBook, "rightnotebook")]:
- notebooks[entry_name] = self.SaveTabLayout(notebook)
- self.DefaultPerspective = {
- "perspective": self.AUIManager.SavePerspective(),
- "notebooks": notebooks,
- }
-
- try:
- if self.Config.HasEntry("perspective"):
- self.AUIManager.LoadPerspective(unicode(self.Config.Read("perspective")))
-
- if self.Config.HasEntry("notebooks"):
- notebooks = cPickle.loads(str(self.Config.Read("notebooks")))
-
- for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
- for idx in xrange(notebook.GetPageCount()):
- notebook.RemovePage(0)
-
- for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
- (self.BottomNoteBook, "bottomnotebook"),
- (self.RightNoteBook, "rightnotebook")]:
- self.LoadTabLayout(notebook, notebooks.get(entry_name))
- except:
- self.ResetPerspective()
-
- if self.EnableSaveProjectState():
- self.LoadProjectLayout()
-
- self._Refresh(EDITORTOOLBAR)
-
- if wx.Platform == '__WXMSW__':
- wx.CallAfter(self.ResetStarting)
- else:
- self.ResetStarting()
- wx.CallAfter(self.RefreshEditor)
-
+
def SaveLastState(self):
if not self.IsMaximized():
self.Config.Write("framesize", cPickle.dumps(self.GetClientSize()))
elif self.Config.HasEntry("framesize"):
self.Config.DeleteEntry("framesize")
- notebooks = {}
- for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
- (self.BottomNoteBook, "bottomnotebook"),
- (self.RightNoteBook, "rightnotebook")]:
- notebooks[entry_name] = self.SaveTabLayout(notebook)
- self.Config.Write("notebooks", cPickle.dumps(notebooks))
-
- pane = self.AUIManager.GetPane(self.TabsOpened)
- if pane.IsMaximized():
- self.AUIManager.RestorePane(pane)
- self.Config.Write("perspective", self.AUIManager.SavePerspective())
-
- if self.EnableSaveProjectState():
- self.SaveProjectLayout()
-
- for i in xrange(self.TabsOpened.GetPageCount()):
- self.SavePageState(self.TabsOpened.GetPage(i))
-
self.Config.Flush()
- def SaveProjectLayout(self):
- if self.Controler is not None:
- tabs = []
-
- projects = {}
- try:
- if self.Config.HasEntry("projects"):
- projects = cPickle.loads(str(self.Config.Read("projects")))
- except:
- pass
-
- project_infos = projects.setdefault(
- EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
- project_infos["tabs"] = self.SaveTabLayout(self.TabsOpened)
- if self.EnableDebug:
- project_infos["debug_vars"] = self.DebugVariablePanel.GetDebugVariables()
-
- self.Config.Write("projects", cPickle.dumps(projects))
- self.Config.Flush()
-
- def LoadProjectLayout(self):
- if self.Controler is not None:
- project = self.GetProjectConfiguration()
-
- try:
- if project.has_key("tabs"):
- self.LoadTabLayout(self.TabsOpened, project["tabs"])
- except:
- self.DeleteAllPages()
-
- if self.EnableDebug:
- #try:
- self.DebugVariablePanel.SetDebugVariables(project.get("debug_vars", []))
- #except:
- # self.DebugVariablePanel.ResetView()
-
#-------------------------------------------------------------------------------
# General Functions
#-------------------------------------------------------------------------------
@@ -998,8 +867,6 @@
window = self.TabsOpened.GetPage(selected)
if window.CheckSaveBeforeClosing():
- if self.EnableSaveProjectState():
- self.SavePageState(window)
# Refresh all window elements that have changed
wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
@@ -1010,8 +877,12 @@
event.Veto()
- def GetCopyBuffer(self):
+ def GetCopyBuffer(self, primary_selection=False):
data = None
+ if primary_selection and wx.Platform == '__WXMSW__':
+ return data
+ else:
+ wx.TheClipboard.UsePrimarySelection(primary_selection)
if wx.TheClipboard.Open():
dataobj = wx.TextDataObject()
if wx.TheClipboard.GetData(dataobj):
@@ -1019,7 +890,11 @@
wx.TheClipboard.Close()
return data
- def SetCopyBuffer(self, text):
+ def SetCopyBuffer(self, text, primary_selection=False):
+ if primary_selection and wx.Platform == '__WXMSW__':
+ return
+ else:
+ wx.TheClipboard.UsePrimarySelection(primary_selection)
if wx.TheClipboard.Open():
data = wx.TextDataObject()
data.SetText(text)
@@ -1317,20 +1192,29 @@
elif isinstance(control, wx.ComboBox):
control.SetMark(0, control.GetLastPosition() + 1)
- DeleteFunctions = {
- ITEM_DATATYPE: GetDeleteElementFunction(PLCControler.ProjectRemoveDataType, check_function=PLCControler.DataTypeIsUsed),
- ITEM_POU: GetDeleteElementFunction(PLCControler.ProjectRemovePou, check_function=PLCControler.PouIsUsed),
- ITEM_TRANSITION: GetDeleteElementFunction(PLCControler.ProjectRemovePouTransition, ITEM_POU),
- ITEM_ACTION: GetDeleteElementFunction(PLCControler.ProjectRemovePouAction, ITEM_POU),
- ITEM_CONFIGURATION: GetDeleteElementFunction(PLCControler.ProjectRemoveConfiguration),
- ITEM_RESOURCE: GetDeleteElementFunction(PLCControler.ProjectRemoveConfigurationResource, ITEM_CONFIGURATION)
- }
+ def SetDeleteFunctions(self):
+ self.DeleteFunctions = {
+ ITEM_DATATYPE: GetDeleteElementFunction(
+ PLCControler.ProjectRemoveDataType,
+ check_function=self.CheckDataTypeIsUsedBeforeDeletion),
+ ITEM_POU: GetDeleteElementFunction(
+ PLCControler.ProjectRemovePou,
+ check_function=self.CheckPouIsUsedBeforeDeletion),
+ ITEM_TRANSITION: GetDeleteElementFunction(
+ PLCControler.ProjectRemovePouTransition, ITEM_POU),
+ ITEM_ACTION: GetDeleteElementFunction(
+ PLCControler.ProjectRemovePouAction, ITEM_POU),
+ ITEM_CONFIGURATION: GetDeleteElementFunction(
+ PLCControler.ProjectRemoveConfiguration),
+ ITEM_RESOURCE: GetDeleteElementFunction(
+ PLCControler.ProjectRemoveConfigurationResource, ITEM_CONFIGURATION)
+ }
def OnDeleteMenu(self, event):
window = self.FindFocus()
if window == self.ProjectTree or window is None:
selected = self.ProjectTree.GetSelection()
- if selected.IsOk():
+ if selected is not None and selected.IsOk():
function = self.DeleteFunctions.get(self.ProjectTree.GetPyData(selected)["type"], None)
if function is not None:
function(self, selected)
@@ -1372,7 +1256,7 @@
result = self.Controler.SearchInProject(criteria)
self.ClearSearchResults()
self.SearchResultPanel.SetSearchResults(criteria, result)
- self.BottomNoteBook.SetSelection(self.BottomNoteBook.GetPageIndex(self.SearchResultPanel))
+ self.SelectTab(self.SearchResultPanel)
#-------------------------------------------------------------------------------
# Display Menu Functions
@@ -1455,33 +1339,31 @@
notebook.SetSelection(notebook.GetPageIndex(tab))
def OnPouSelectedChanging(self, event):
- if not self.Starting:
- selected = self.TabsOpened.GetSelection()
- if selected >= 0:
- window = self.TabsOpened.GetPage(selected)
- if not window.IsDebugging():
- window.ResetBuffer()
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ window = self.TabsOpened.GetPage(selected)
+ if not window.IsDebugging():
+ window.ResetBuffer()
event.Skip()
def OnPouSelectedChanged(self, event):
- if not self.Starting:
- selected = self.TabsOpened.GetSelection()
- if selected >= 0:
- window = self.TabsOpened.GetPage(selected)
- tagname = window.GetTagName()
- if not window.IsDebugging():
- wx.CallAfter(self.SelectProjectTreeItem, tagname)
- wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname)
- window.RefreshView()
- self.EnsureTabVisible(self.LibraryPanel)
- else:
- instance_path = window.GetInstancePath()
- if tagname == "":
- instance_path = instance_path.rsplit(".", 1)[0]
- tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
- self.EnsureTabVisible(self.DebugVariablePanel)
- wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname, instance_path)
- wx.CallAfter(self._Refresh, FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ window = self.TabsOpened.GetPage(selected)
+ tagname = window.GetTagName()
+ if not window.IsDebugging():
+ self.SelectProjectTreeItem(tagname)
+ self.PouInstanceVariablesPanel.SetPouType(tagname)
+ window.RefreshView()
+ self.EnsureTabVisible(self.LibraryPanel)
+ else:
+ instance_path = window.GetInstancePath()
+ if tagname == "":
+ instance_path = instance_path.rsplit(".", 1)[0]
+ tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
+ self.EnsureTabVisible(self.DebugVariablePanel)
+ wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname, instance_path)
+ wx.CallAfter(self._Refresh, FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
event.Skip()
def RefreshEditor(self):
@@ -1544,30 +1426,44 @@
#-------------------------------------------------------------------------------
def RefreshProjectTree(self):
+ # Extract current selected item tagname
+ selected = self.ProjectTree.GetSelection()
+ if selected is not None and selected.IsOk():
+ item_infos = self.ProjectTree.GetPyData(selected)
+ tagname = item_infos.get("tagname", None)
+ else:
+ tagname = None
+
+ # Refresh treectrl items according to project infos
infos = self.Controler.GetProjectInfos()
root = self.ProjectTree.GetRootItem()
- if not root.IsOk():
+ if root is None or not root.IsOk():
root = self.ProjectTree.AddRoot(infos["name"])
self.GenerateProjectTreeBranch(root, infos)
self.ProjectTree.Expand(root)
-
- def ResetSelectedItem(self):
- self.SelectedItem = None
-
- def GenerateProjectTreeBranch(self, root, infos):
+
+ # Select new item corresponding to previous selected item
+ if tagname is not None:
+ self.SelectProjectTreeItem(tagname)
+
+ def GenerateProjectTreeBranch(self, root, infos, item_alone=False):
to_delete = []
item_name = infos["name"]
if infos["type"] in ITEMS_UNEDITABLE:
if len(infos["values"]) == 1:
- return self.GenerateProjectTreeBranch(root, infos["values"][0])
+ return self.GenerateProjectTreeBranch(root, infos["values"][0], True)
item_name = _(item_name)
self.ProjectTree.SetItemText(root, item_name)
self.ProjectTree.SetPyData(root, infos)
highlight_colours = self.Highlights.get(infos.get("tagname", None), (wx.WHITE, wx.BLACK))
self.ProjectTree.SetItemBackgroundColour(root, highlight_colours[0])
self.ProjectTree.SetItemTextColour(root, highlight_colours[1])
+ self.ProjectTree.SetItemExtraImage(root, None)
if infos["type"] == ITEM_POU:
- self.ProjectTree.SetItemImage(root, self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])])
+ self.ProjectTree.SetItemImage(root,
+ self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])])
+ if item_alone:
+ self.ProjectTree.SetItemExtraImage(root, self.Controler.GetPouType(infos["name"]))
elif infos.has_key("icon") and infos["icon"] is not None:
icon_name = infos["icon"]
if not self.TreeImageDict.has_key(icon_name):
@@ -1576,56 +1472,48 @@
elif self.TreeImageDict.has_key(infos["type"]):
self.ProjectTree.SetItemImage(root, self.TreeImageDict[infos["type"]])
- if wx.VERSION >= (2, 6, 0):
- item, root_cookie = self.ProjectTree.GetFirstChild(root)
- else:
- item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
for values in infos["values"]:
if values["type"] not in ITEMS_UNEDITABLE or len(values["values"]) > 0:
- if not item.IsOk():
+ if item is None or not item.IsOk():
item = self.ProjectTree.AppendItem(root, "")
- if wx.Platform != '__WXMSW__':
- item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
self.GenerateProjectTreeBranch(item, values)
item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
- while item.IsOk():
+ while item is not None and item.IsOk():
to_delete.append(item)
item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
for item in to_delete:
self.ProjectTree.Delete(item)
+ TagNamePartsItemTypes = {
+ "D": [ITEM_DATATYPE],
+ "P": [ITEM_POU],
+ "T": [ITEM_POU, ITEM_TRANSITION],
+ "A": [ITEM_POU, ITEM_ACTION],
+ "C": [ITEM_CONFIGURATION],
+ "R": [ITEM_CONFIGURATION, ITEM_RESOURCE]}
+
def SelectProjectTreeItem(self, tagname):
+ result = False
if self.ProjectTree is not None:
root = self.ProjectTree.GetRootItem()
- if root.IsOk():
+ if root is not None and root.IsOk():
words = tagname.split("::")
- if words[0] == "D":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_DATATYPE)])
- elif words[0] == "P":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU)])
- elif words[0] == "T":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_TRANSITION)])
- elif words[0] == "A":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_ACTION)])
- elif words[0] == "C":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION)])
- elif words[0] == "R":
- return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION), (words[2], ITEM_RESOURCE)])
- return False
+ result = self.RecursiveProjectTreeItemSelection(root,
+ zip(words[1:], self.TagNamePartsItemTypes.get(words[0], [])))
+ return result
def RecursiveProjectTreeItemSelection(self, root, items):
found = False
- if wx.VERSION >= (2, 6, 0):
- item, root_cookie = self.ProjectTree.GetFirstChild(root)
- else:
- item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
- while item.IsOk() and not found:
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
+ while item is not None and item.IsOk() and not found:
item_infos = self.ProjectTree.GetPyData(item)
if (item_infos["name"].split(":")[-1].strip(), item_infos["type"]) == items[0]:
if len(items) == 1:
self.SelectedItem = item
- wx.CallAfter(self.ProjectTree.SelectItem, item)
- wx.CallAfter(self.ResetSelectedItem)
+ self.ProjectTree.SelectItem(item)
+ self.ResetSelectedItem()
return True
else:
found = self.RecursiveProjectTreeItemSelection(item, items[1:])
@@ -1634,11 +1522,15 @@
item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
return found
+ def ResetSelectedItem(self):
+ self.SelectedItem = None
+
def OnProjectTreeBeginDrag(self, event):
- if wx.Platform == '__WXMSW__':
- self.SelectedItem = event.GetItem()
- if self.SelectedItem is not None and self.ProjectTree.GetPyData(self.SelectedItem)["type"] == ITEM_POU:
- block_name = self.ProjectTree.GetItemText(self.SelectedItem)
+ selected_item = (self.SelectedItem
+ if self.SelectedItem is not None
+ else event.GetItem())
+ if selected_item.IsOk() and self.ProjectTree.GetPyData(selected_item)["type"] == ITEM_POU:
+ block_name = self.ProjectTree.GetItemText(selected_item)
block_type = self.Controler.GetPouType(block_name)
if block_type != "program":
data = wx.TextDataObject(str((block_name, block_type, "")))
@@ -1682,7 +1574,7 @@
if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames() if name != old_name]:
message = _("\"%s\" pou already exists!")%new_name
abort = True
- elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
if messageDialog.ShowModal() == wx.ID_NO:
abort = True
@@ -1696,7 +1588,7 @@
elif item_infos["type"] == ITEM_TRANSITION:
if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
message = _("A POU named \"%s\" already exists!")%new_name
- elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames(pou_name) if name != old_name]:
message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
else:
words = item_infos["tagname"].split("::")
@@ -1707,7 +1599,7 @@
elif item_infos["type"] == ITEM_ACTION:
if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
message = _("A POU named \"%s\" already exists!")%new_name
- elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames(pou_name) if name != old_name]:
message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
else:
words = item_infos["tagname"].split("::")
@@ -1724,7 +1616,7 @@
if messageDialog.ShowModal() == wx.ID_NO:
abort = True
messageDialog.Destroy()
- elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
if messageDialog.ShowModal() == wx.ID_NO:
abort = True
@@ -1743,7 +1635,7 @@
if messageDialog.ShowModal() == wx.ID_NO:
abort = True
messageDialog.Destroy()
- elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
if messageDialog.ShowModal() == wx.ID_NO:
abort = True
@@ -1780,23 +1672,52 @@
event.Skip()
def ProjectTreeItemSelect(self, select_item):
- name = self.ProjectTree.GetItemText(select_item)
- item_infos = self.ProjectTree.GetPyData(select_item)
- if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
- ITEM_CONFIGURATION, ITEM_RESOURCE,
- ITEM_TRANSITION, ITEM_ACTION]:
- self.EditProjectElement(item_infos["type"], item_infos["tagname"], True)
- self.PouInstanceVariablesPanel.SetPouType(item_infos["tagname"])
+ if select_item is not None and select_item.IsOk():
+ name = self.ProjectTree.GetItemText(select_item)
+ item_infos = self.ProjectTree.GetPyData(select_item)
+ if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
+ ITEM_CONFIGURATION, ITEM_RESOURCE,
+ ITEM_TRANSITION, ITEM_ACTION]:
+ self.EditProjectElement(item_infos["type"], item_infos["tagname"], True)
+ self.PouInstanceVariablesPanel.SetPouType(item_infos["tagname"])
def OnProjectTreeLeftUp(self, event):
if self.SelectedItem is not None:
self.ProjectTree.SelectItem(self.SelectedItem)
self.ProjectTreeItemSelect(self.SelectedItem)
- wx.CallAfter(self.ResetSelectedItem)
+ self.ResetSelectedItem()
event.Skip()
- def OnProjectTreeItemSelected(self, event):
- self.ProjectTreeItemSelect(event.GetItem())
+ def OnProjectTreeMotion(self, event):
+ if not event.Dragging():
+ pt = wx.Point(event.GetX(), event.GetY())
+ item, flags = self.ProjectTree.HitTest(pt)
+ if item is not None and item.IsOk() and flags & wx.TREE_HITTEST_ONITEMLABEL:
+ item_infos = self.ProjectTree.GetPyData(item)
+ if item != self.LastToolTipItem and self.LastToolTipItem is not None:
+ self.ProjectTree.SetToolTip(None)
+ self.LastToolTipItem = None
+ if (self.LastToolTipItem != item and
+ item_infos["type"] in [ITEM_POU, ITEM_TRANSITION, ITEM_ACTION]):
+ bodytype = self.Controler.GetEditedElementBodyType(
+ item_infos["tagname"])
+ if item_infos["type"] == ITEM_POU:
+ block_type = {
+ "program": _("Program"),
+ "functionBlock": _("Function Block"),
+ "function": _("Function")
+ }[self.Controler.GetPouType(item_infos["name"])]
+ elif item_infos["type"] == ITEM_TRANSITION:
+ block_type = "Transition"
+ else:
+ block_type = "Action"
+ self.LastToolTipItem = item
+ wx.CallAfter(self.ProjectTree.SetToolTipString,
+ "%s : %s : %s" % (
+ block_type, bodytype, item_infos["name"]))
+ elif self.LastToolTipItem is not None:
+ self.ProjectTree.SetToolTip(None)
+ self.LastToolTipItem = None
event.Skip()
def OnProjectTreeItemChanging(self, event):
@@ -1861,16 +1782,6 @@
new_window.SetIcon(GetBitmap("DATATYPE"))
self.AddPage(new_window, "")
if new_window is not None:
- if self.EnableSaveProjectState():
- project_infos = self.GetProjectConfiguration()
- if project_infos.has_key("editors_state"):
- if new_window.IsDebugging():
- state = project_infos["editors_state"].get(new_window.GetInstancePath())
- else:
- state = project_infos["editors_state"].get(tagname)
- if state is not None:
- wx.CallAfter(new_window.SetState, state)
-
openedidx = self.IsOpened(tagname)
old_selected = self.TabsOpened.GetSelection()
if old_selected != openedidx:
@@ -1885,10 +1796,7 @@
return new_window
def OnProjectTreeRightUp(self, event):
- if wx.Platform == '__WXMSW__':
- item = event.GetItem()
- else:
- item, flags = self.ProjectTree.HitTest(wx.Point(event.GetX(), event.GetY()))
+ item = event.GetItem()
self.ProjectTree.SelectItem(item)
self.ProjectTreeItemSelect(item)
name = self.ProjectTree.GetItemText(item)
@@ -1931,8 +1839,8 @@
menu = wx.Menu(title='')
new_id = wx.NewId()
AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Transition"))
- parent = self.ProjectTree.GetItemParent(item)["type"]
- parent_type = self.ProjectTree.GetPyData(parent)
+ parent = self.ProjectTree.GetItemParent(item)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
while parent_type != ITEM_POU:
parent = self.ProjectTree.GetItemParent(parent)
parent_type = self.ProjectTree.GetPyData(parent)["type"]
@@ -1958,11 +1866,15 @@
while parent_type not in [ITEM_CONFIGURATION, ITEM_PROJECT]:
parent = self.ProjectTree.GetItemParent(parent)
parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ parent_name = None
if parent_type == ITEM_PROJECT:
- parent_name = None
+ config_names = self.Controler.GetProjectConfigNames()
+ if len(config_names) > 0:
+ parent_name = config_names[0]
else:
parent_name = self.ProjectTree.GetItemText(parent)
- self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(parent_name), id=new_id)
+ if parent_name is not None:
+ self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(parent_name), id=new_id)
else:
if item_infos["type"] == ITEM_POU:
@@ -2013,6 +1925,8 @@
self.PopupMenu(menu)
menu.Destroy()
+ self.ResetSelectedItem()
+
event.Skip()
@@ -2073,13 +1987,6 @@
icon = GetBitmap("ACTION", bodytype)
if new_window is not None:
- if self.EnableSaveProjectState():
- project_infos = self.GetProjectConfiguration()
- if project_infos.has_key("editors_state"):
- state = project_infos["editors_state"].get(instance_path)
- if state is not None:
- wx.CallAfter(new_window.SetState, state)
-
new_window.SetIcon(icon)
self.AddPage(new_window, "")
new_window.RefreshView()
@@ -2108,14 +2015,14 @@
elif isinstance(editor, GraphicViewer):
editor.ResetView(True)
else:
- editor.RefreshView()
+ editor.SubscribeAllDataConsumers()
elif editor.IsDebugging():
- editor.RegisterVariables()
- self.DebugVariablePanel.UnregisterObsoleteData()
-
- def AddDebugVariable(self, iec_path, force=False):
+ editor.SubscribeAllDataConsumers()
+ self.DebugVariablePanel.SubscribeAllDataConsumers()
+
+ def AddDebugVariable(self, iec_path, force=False, graph=False):
if self.EnableDebug:
- self.DebugVariablePanel.InsertValue(iec_path, force=force)
+ self.DebugVariablePanel.InsertValue(iec_path, force=force, graph=graph)
self.EnsureTabVisible(self.DebugVariablePanel)
#-------------------------------------------------------------------------------
@@ -2349,7 +2256,7 @@
def OnAddPouMenu(event):
dialog = PouDialog(self, pou_type)
dialog.SetPouNames(self.Controler.GetProjectPouNames())
- dialog.SetPouElementNames(self.Controler.GetProjectPouVariables())
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames())
dialog.SetValues({"pouName": self.Controler.GenerateNewName(None, None, "%s%%d" % pou_type)})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
@@ -2364,7 +2271,7 @@
def OnAddTransitionMenu(event):
dialog = PouTransitionDialog(self)
dialog.SetPouNames(self.Controler.GetProjectPouNames())
- dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames(pou_name))
dialog.SetValues({"transitionName": self.Controler.GenerateNewName(None, None, "transition%d")})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
@@ -2379,7 +2286,7 @@
def OnAddActionMenu(event):
dialog = PouActionDialog(self)
dialog.SetPouNames(self.Controler.GetProjectPouNames())
- dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames(pou_name))
dialog.SetValues({"actionName": self.Controler.GenerateNewName(None, None, "action%d")})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
@@ -2437,9 +2344,7 @@
result = self.Controler.PastePou(pou_type, pou_xml)
if not isinstance(result, TupleType):
- message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
+ self.ShowErrorMessage(result)
else:
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
self.EditProjectElement(ITEM_POU, result[0])
@@ -2448,20 +2353,39 @@
# Remove Project Elements Functions
#-------------------------------------------------------------------------------
+ def CheckElementIsUsedBeforeDeletion(self, check_function, title, name):
+ if not check_function(name):
+ return True
+
+ dialog = wx.MessageDialog(self,
+ _("\"%s\" is used by one or more POUs. Do you wish to continue?") % name,
+ title, wx.YES_NO|wx.ICON_QUESTION)
+ answer = dialog.ShowModal()
+ dialog.Destroy()
+ return answer == wx.ID_YES
+
+ def CheckDataTypeIsUsedBeforeDeletion(self, name):
+ return self.CheckElementIsUsedBeforeDeletion(
+ self.Controler.DataTypeIsUsed,
+ _("Remove Datatype"), name)
+
+ def CheckPouIsUsedBeforeDeletion(self, name):
+ return self.CheckElementIsUsedBeforeDeletion(
+ self.Controler.PouIsUsed,
+ _("Remove Pou"), name)
+
def OnRemoveDataTypeMenu(self, event):
selected = self.ProjectTree.GetSelection()
if self.ProjectTree.GetPyData(selected)["type"] == ITEM_DATATYPE:
name = self.ProjectTree.GetItemText(selected)
- if not self.Controler.DataTypeIsUsed(name):
+ if self.CheckDataTypeIsUsedBeforeDeletion(name):
self.Controler.ProjectRemoveDataType(name)
tagname = self.Controler.ComputeDataTypeName(name)
idx = self.IsOpened(tagname)
if idx is not None:
self.TabsOpened.DeletePage(idx)
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE)
- else:
- self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
-
+
def OnRenamePouMenu(self, event):
selected = self.ProjectTree.GetSelection()
if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
@@ -2471,15 +2395,13 @@
selected = self.ProjectTree.GetSelection()
if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
name = self.ProjectTree.GetItemText(selected)
- if not self.Controler.PouIsUsed(name):
+ if self.CheckPouIsUsedBeforeDeletion(name):
self.Controler.ProjectRemovePou(name)
tagname = self.Controler.ComputePouName(name)
idx = self.IsOpened(tagname)
if idx is not None:
self.TabsOpened.DeletePage(idx)
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
- else:
- self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
def OnRemoveTransitionMenu(self, event):
selected = self.ProjectTree.GetSelection()
--- a/NativeLib.xml Wed Mar 13 12:34:55 2013 +0900
+++ b/NativeLib.xml Wed Jul 31 10:45:07 2013 +0900
@@ -74,8 +74,7 @@
<ST>
<![CDATA[IF TRIG AND NOT TRIG0 THEN
{{
- extern int LogMessage(uint8_t, uint8_t*, uint32_t);
- LogMessage(GetFbVar(LEVEL),GetFbVar(MSG, .body),GetFbVar(MSG, .len));
+ LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
}}
END_IF;
TRIG0:=TRIG;
--- a/PLCControler.py Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCControler.py Wed Jul 31 10:45:07 2013 +0900
@@ -266,8 +266,8 @@
return [config.getname() for config in project.getconfigurations()]
return []
- # Return project pou variables
- def GetProjectPouVariables(self, pou_name = None, debug = False):
+ # Return project pou variable names
+ def GetProjectPouVariableNames(self, pou_name = None, debug = False):
variables = []
project = self.GetProject(debug)
if project is not None:
@@ -561,7 +561,7 @@
instances.append(var_path)
else:
pou = project.getpou(var_type)
- if pou is not None:
+ if pou is not None and project.ElementIsUsedBy(pou_type, var_type):
instances.extend(
self.RecursiveSearchPouInstances(
project, pou_type, var_path,
@@ -596,7 +596,7 @@
if pou_type == words[1]:
instances.append(pou_path)
pou = project.getpou(pou_type)
- if pou is not None:
+ if pou is not None and project.ElementIsUsedBy(words[1], pou_type):
instances.extend(
self.RecursiveSearchPouInstances(
project, words[1], pou_path,
@@ -649,14 +649,38 @@
return self.RecursiveGetPouInstanceTagName(project, vartype, parts[1:], debug)
return None
+ def GetGlobalInstanceTagName(self, project, element, parts, debug = False):
+ for varlist in element.getglobalVars():
+ for variable in varlist.getvariable():
+ if variable.getname() == parts[0]:
+ vartype_content = variable.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ if len(parts) == 1:
+ return self.ComputePouName(
+ vartype_content["value"].getname())
+ else:
+ return self.RecursiveGetPouInstanceTagName(
+ project,
+ vartype_content["value"].getname(),
+ parts[1:], debug)
+ return None
+
def GetPouInstanceTagName(self, instance_path, debug = False):
+ project = self.GetProject(debug)
parts = instance_path.split(".")
if len(parts) == 1:
return self.ComputeConfigurationName(parts[0])
elif len(parts) == 2:
+ for config in project.getconfigurations():
+ if config.getname() == parts[0]:
+ result = self.GetGlobalInstanceTagName(project,
+ config,
+ parts[1:],
+ debug)
+ if result is not None:
+ return result
return self.ComputeConfigurationResourceName(parts[0], parts[1])
else:
- project = self.GetProject(debug)
for config in project.getconfigurations():
if config.getname() == parts[0]:
for resource in config.getresource():
@@ -674,6 +698,14 @@
project,
pou_instance.gettypeName(),
parts[3:], debug)
+ return self.GetGlobalInstanceTagName(project,
+ resource,
+ parts[2:],
+ debug)
+ return self.GetGlobalInstanceTagName(project,
+ config,
+ parts[1:],
+ debug)
return None
def GetInstanceInfos(self, instance_path, debug = False):
@@ -1261,6 +1293,16 @@
tempvar["Documentation"] = ""
return tempvar
+
+ # Add a global var to configuration to configuration
+ def AddConfigurationGlobalVar(self, config_name, type, var_name,
+ location="", description=""):
+ if self.Project is not None:
+ # Found the configuration corresponding to name
+ configuration = self.Project.getconfiguration(config_name)
+ if configuration is not None:
+ # Set configuration global vars
+ configuration.addglobalVar(type, var_name, location, description)
# Replace the configuration globalvars by those given
def SetConfigurationGlobalVars(self, name, vars):
@@ -1289,6 +1331,20 @@
vars.append(tempvar)
return vars
+ # Return configuration variable names
+ def GetConfigurationVariableNames(self, config_name = None, debug = False):
+ variables = []
+ project = self.GetProject(debug)
+ if project is not None:
+ for configuration in self.Project.getconfigurations():
+ if config_name is None or config_name == configuration.getname():
+ variables.extend(
+ [var.getname() for var in reduce(
+ lambda x, y: x + y, [varlist.getvariable()
+ for varlist in configuration.globalVars],
+ [])])
+ return variables
+
# Replace the resource globalvars by those given
def SetConfigurationResourceGlobalVars(self, config_name, name, vars):
if self.Project is not None:
@@ -1316,6 +1372,23 @@
vars.append(tempvar)
return vars
+ # Return resource variable names
+ def GetConfigurationResourceVariableNames(self,
+ config_name = None, resource_name = None, debug = False):
+ variables = []
+ project = self.GetProject(debug)
+ if project is not None:
+ for configuration in self.Project.getconfigurations():
+ if config_name is None or config_name == configuration.getname():
+ for resource in configuration.getresource():
+ if resource_name is None or resource.getname() == resource_name:
+ variables.extend(
+ [var.getname() for var in reduce(
+ lambda x, y: x + y, [varlist.getvariable()
+ for varlist in resource.globalVars],
+ [])])
+ return variables
+
# Recursively generate element name tree for a structured variable
def GenerateVarTree(self, typename, debug = False):
project = self.GetProject(debug)
@@ -1492,24 +1565,29 @@
def GetConfigurationExtraVariables(self):
global_vars = []
- for var_name, var_type in self.GetConfNodeGlobalInstances():
+ for var_name, var_type, var_initial in self.GetConfNodeGlobalInstances():
tempvar = plcopen.varListPlain_variable()
tempvar.setname(var_name)
tempvartype = plcopen.dataType()
if var_type in self.GetBaseTypes():
if var_type == "STRING":
- var_type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ tempvartype.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
elif var_type == "WSTRING":
- var_type.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
+ tempvartype.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
else:
- var_type.setcontent({"name" : var_type, "value" : None})
+ tempvartype.setcontent({"name" : var_type, "value" : None})
else:
tempderivedtype = plcopen.derivedTypes_derived()
tempderivedtype.setname(var_type)
tempvartype.setcontent({"name" : "derived", "value" : tempderivedtype})
tempvar.settype(tempvartype)
+ if var_initial != "":
+ value = plcopen.value()
+ value.setvalue(var_initial)
+ tempvar.setinitialValue(value)
+
global_vars.append(tempvar)
return global_vars
@@ -1531,7 +1609,7 @@
result_blocktype["inputs"] = [(i[0], "ANY", i[2]) for i in result_blocktype["inputs"]]
result_blocktype["outputs"] = [(o[0], "ANY", o[2]) for o in result_blocktype["outputs"]]
return result_blocktype
- result_blocktype = blocktype
+ result_blocktype = blocktype.copy()
if result_blocktype is not None:
return result_blocktype
project = self.GetProject(debug)
@@ -2071,7 +2149,13 @@
def GetEditedElementVariables(self, tagname, debug = False):
words = tagname.split("::")
if words[0] in ["P","T","A"]:
- return self.GetProjectPouVariables(words[1], debug)
+ return self.GetProjectPouVariableNames(words[1], debug)
+ elif words[0] in ["C", "R"]:
+ names = self.GetConfigurationVariableNames(words[1], debug)
+ if words[0] == "R":
+ names.extend(self.GetConfigurationResourceVariableNames(
+ words[1], words[2], debug))
+ return names
return []
def GetEditedElementCopy(self, tagname, debug = False):
@@ -2095,15 +2179,20 @@
text += instance_copy.generateXMLText(name.split("_")[-1], 0)
return text
- def GenerateNewName(self, tagname, name, format, exclude={}, debug=False):
+ def GenerateNewName(self, tagname, name, format, start_idx=0, exclude={}, debug=False):
names = exclude.copy()
if tagname is not None:
- names.update(dict([(varname.upper(), True) for varname in self.GetEditedElementVariables(tagname, debug)]))
- element = self.GetEditedElement(tagname, debug)
- if element is not None:
- for instance in element.getinstances():
- if isinstance(instance, (plcopen.sfcObjects_step, plcopen.commonObjects_connector, plcopen.commonObjects_continuation)):
- names[instance.getname().upper()] = True
+ names.update(dict([(varname.upper(), True)
+ for varname in self.GetEditedElementVariables(tagname, debug)]))
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None and element.getbodyType() not in ["ST", "IL"]:
+ for instance in element.getinstances():
+ if isinstance(instance, (plcopen.sfcObjects_step,
+ plcopen.commonObjects_connector,
+ plcopen.commonObjects_continuation)):
+ names[instance.getname().upper()] = True
else:
project = self.GetProject(debug)
if project is not None:
@@ -2122,7 +2211,7 @@
for resource in config.getresource():
names[resource.getname().upper()] = True
- i = 0
+ i = start_idx
while name is None or names.get(name.upper(), False):
name = (format%i)
i += 1
@@ -2152,7 +2241,7 @@
text = "<paste>%s</paste>"%text
try:
- tree = minidom.parseString(text)
+ tree = minidom.parseString(text.encode("utf-8"))
except:
return _("Invalid plcopen element(s)!!!")
instances = []
@@ -2178,12 +2267,19 @@
blocktype = instance.gettypeName()
if element_type == "function":
return _("FunctionBlock \"%s\" can't be pasted in a Function!!!")%blocktype
- blockname = self.GenerateNewName(tagname, blockname, "%s%%d"%blocktype, debug=debug)
+ blockname = self.GenerateNewName(tagname,
+ blockname,
+ "%s%%d"%blocktype,
+ debug=debug)
exclude[blockname] = True
instance.setinstanceName(blockname)
self.AddEditedElementPouVar(tagname, blocktype, blockname)
elif child.nodeName == "step":
- stepname = self.GenerateNewName(tagname, instance.getname(), "Step%d", exclude, debug)
+ stepname = self.GenerateNewName(tagname,
+ instance.getname(),
+ "Step%d",
+ exclude=exclude,
+ debug=debug)
exclude[stepname] = True
instance.setname(stepname)
localid = instance.getlocalId()
--- a/PLCGenerator.py Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCGenerator.py Wed Jul 31 10:45:07 2013 +0900
@@ -106,9 +106,9 @@
# Compute value according to type given
def ComputeValue(self, value, var_type):
base_type = self.Controler.GetBaseType(var_type)
- if base_type == "STRING":
+ if base_type == "STRING" and not value.startswith("'") and not value.endswith("'"):
return "'%s'"%value
- elif base_type == "WSTRING":
+ elif base_type == "WSTRING" and not value.startswith('"') and not value.endswith('"'):
return "\"%s\""%value
return value
@@ -654,7 +654,16 @@
self.Interface.append((varTypeNames[varlist["name"]], option, False, variables))
if len(located) > 0:
self.Interface.append((varTypeNames[varlist["name"]], option, True, located))
-
+
+ LITERAL_TYPES = {
+ "T": "TIME",
+ "D": "DATE",
+ "TOD": "TIME_OF_DAY",
+ "DT": "DATE_AND_TIME",
+ "2": None,
+ "8": None,
+ "16": None,
+ }
def ComputeConnectionTypes(self, pou):
body = pou.getbody()
if isinstance(body, ListType):
@@ -681,7 +690,9 @@
elif var_type is None:
parts = expression.split("#")
if len(parts) > 1:
- var_type = parts[0]
+ literal_prefix = parts[0].upper()
+ var_type = self.LITERAL_TYPES.get(literal_prefix,
+ literal_prefix)
elif expression.startswith("'"):
var_type = "STRING"
elif expression.startswith('"'):
@@ -883,17 +894,18 @@
otherInstances["outVariables&coils"].sort(SortInstances)
otherInstances["blocks"].sort(SortInstances)
instances = [instance for (executionOrderId, instance) in orderedInstances]
- instances.extend(otherInstances["connectors"] + otherInstances["outVariables&coils"] + otherInstances["blocks"])
+ instances.extend(otherInstances["outVariables&coils"] + otherInstances["blocks"] + otherInstances["connectors"])
for instance in instances:
if isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
connections = instance.connectionPointIn.getconnections()
if connections is not None:
expression = self.ComputeExpression(body, connections)
- self.Program += [(self.CurrentIndent, ()),
- (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
- (" := ", ())]
- self.Program += expression
- self.Program += [(";\n", ())]
+ if expression is not None:
+ self.Program += [(self.CurrentIndent, ()),
+ (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
+ (" := ", ())]
+ self.Program += expression
+ self.Program += [(";\n", ())]
elif isinstance(instance, plcopen.fbdObjects_block):
block_type = instance.gettypeName()
self.ParentGenerator.GeneratePouProgram(block_type)
@@ -902,20 +914,27 @@
block_infos = self.GetBlockType(block_type)
if block_infos is None:
raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
- block_infos["generate"](self, instance, block_infos, body, None)
+ try:
+ block_infos["generate"](self, instance, block_infos, body, None)
+ except ValueError, e:
+ raise PLCGenException, e.message
elif isinstance(instance, plcopen.commonObjects_connector):
connector = instance.getname()
if self.ComputedConnectors.get(connector, None):
- continue
- self.ComputedConnectors[connector] = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
+ continue
+ expression = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
+ if expression is not None:
+ self.ComputedConnectors[connector] = expression
elif isinstance(instance, plcopen.ldObjects_coil):
connections = instance.connectionPointIn.getconnections()
if connections is not None:
coil_info = (self.TagName, "coil", instance.getlocalId())
- expression = self.ExtractModifier(instance, self.ComputeExpression(body, connections), coil_info)
- self.Program += [(self.CurrentIndent, ())]
- self.Program += [(instance.getvariable(), coil_info + ("reference",))]
- self.Program += [(" := ", ())] + expression + [(";\n", ())]
+ expression = self.ComputeExpression(body, connections)
+ if expression is not None:
+ expression = self.ExtractModifier(instance, expression, coil_info)
+ self.Program += [(self.CurrentIndent, ())]
+ self.Program += [(instance.getvariable(), coil_info + ("reference",))]
+ self.Program += [(" := ", ())] + expression + [(";\n", ())]
def FactorizePaths(self, paths):
same_paths = {}
@@ -961,7 +980,10 @@
block_infos = self.GetBlockType(block_type)
if block_infos is None:
raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
- paths.append(str(block_infos["generate"](self, next, block_infos, body, connection, order, to_inout)))
+ try:
+ paths.append(str(block_infos["generate"](self, next, block_infos, body, connection, order, to_inout)))
+ except ValueError, e:
+ raise PLCGenException, e.message
elif isinstance(next, plcopen.commonObjects_continuation):
name = next.getname()
computed_value = self.ComputedConnectors.get(name, None)
@@ -978,8 +1000,9 @@
connections = connector.connectionPointIn.getconnections()
if connections is not None:
expression = self.ComputeExpression(body, connections, order)
- self.ComputedConnectors[name] = expression
- paths.append(str(expression))
+ if expression is not None:
+ self.ComputedConnectors[name] = expression
+ paths.append(str(expression))
else:
raise PLCGenException, _("No connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
elif isinstance(next, plcopen.ldObjects_contact):
@@ -1022,6 +1045,8 @@
def ComputeExpression(self, body, connections, order = False, to_inout = False):
paths = self.GeneratePaths(connections, body, order, to_inout)
+ if len(paths) == 0:
+ return None
if len(paths) > 1:
factorized_paths = self.FactorizePaths(paths)
if len(factorized_paths) > 1:
@@ -1245,9 +1270,10 @@
connections = instance.connectionPointIn.getconnections()
if connections is not None:
expression = self.ComputeExpression(transitionBody, connections)
- transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
- self.SFCComputedBlocks += self.Program
- self.Program = []
+ if expression is not None:
+ transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+ self.SFCComputedBlocks += self.Program
+ self.Program = []
if not transition_infos.has_key("content"):
raise PLCGenException, _("Transition \"%s\" body must contain an output variable or coil referring to its name") % transitionValues["value"]
self.TagName = previous_tagname
@@ -1258,9 +1284,10 @@
connections = transition.getconnections()
if connections is not None:
expression = self.ComputeExpression(body, connections)
- transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
- self.SFCComputedBlocks += self.Program
- self.Program = []
+ if expression is not None:
+ transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+ self.SFCComputedBlocks += self.Program
+ self.Program = []
for step in steps:
self.GenerateSFCStep(step, pou)
step_name = step.getname()
--- a/PLCOpenEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCOpenEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -82,6 +82,7 @@
from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
from editors.Viewer import Viewer
from PLCControler import PLCControler
+from dialogs import ProjectDialog
#-------------------------------------------------------------------------------
# PLCOpenEditor Main Class
@@ -303,7 +304,6 @@
self.LibraryPanel.SetController(controler)
self.ProjectTree.Enable(True)
self.PouInstanceVariablesPanel.SetController(controler)
- self.LoadProjectLayout()
self._Refresh(PROJECTTREE, LIBRARYTREE)
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
dialog.Destroy()
@@ -314,7 +314,6 @@
def OnCloseProjectMenu(self, event):
if not self.CheckSaveBeforeClosing():
return
- self.SaveProjectLayout()
self.ResetView()
self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
--- a/ProjectController.py Wed Mar 13 12:34:55 2013 +0900
+++ b/ProjectController.py Wed Jul 31 10:45:07 2013 +0900
@@ -21,7 +21,7 @@
from editors.FileManagementPanel import FileManagementPanel
from editors.ProjectNodeEditor import ProjectNodeEditor
from editors.IECCodeViewer import IECCodeViewer
-from graphics import DebugViewer
+from editors.DebugViewer import DebugViewer
from dialogs import DiscoveryDialog
from PLCControler import PLCControler
from plcopen.structures import IEC_KEYWORDS
@@ -85,9 +85,9 @@
PLCControler.__init__(self)
self.MandatoryParams = None
- self.SetAppFrame(frame, logger)
self._builder = None
self._connector = None
+ self.SetAppFrame(frame, logger)
self.iec2c_path = os.path.join(base_folder, "matiec", "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else ""))
self.ieclib_path = os.path.join(base_folder, "matiec", "lib")
@@ -113,7 +113,6 @@
self.DebugThread = None
self.debug_break = False
self.previous_plcstate = None
- self.previous_log_count = [None]*LogLevelsCount
# copy ConfNodeMethods so that it can be later customized
self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
@@ -137,6 +136,8 @@
self.StatusTimer = None
if frame is not None:
+ frame.LogViewer.SetLogSource(self._connector)
+
# Timer to pull PLC status
ID_STATUSTIMER = wx.NewId()
self.StatusTimer = wx.Timer(self.AppFrame, ID_STATUSTIMER)
@@ -213,27 +214,33 @@
def SetParamsAttribute(self, path, value):
if path.startswith("BeremizRoot.TargetType.") and self.BeremizRoot.getTargetType().getcontent() is None:
self.BeremizRoot.setTargetType(self.GetTarget())
- return ConfigTreeNode.SetParamsAttribute(self, path, value)
+ res = ConfigTreeNode.SetParamsAttribute(self, path, value)
+ if path.startswith("BeremizRoot.Libraries."):
+ wx.CallAfter(self.RefreshConfNodesBlockLists)
+ return res
# helper func to check project path write permission
def CheckProjectPathPerm(self, dosave=True):
if CheckPathPerm(self.ProjectPath):
return True
- dialog = wx.MessageDialog(self.AppFrame,
- _('You must have permission to work on the project\nWork on a project copy ?'),
- _('Error'),
- wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
- answer = dialog.ShowModal()
- dialog.Destroy()
- if answer == wx.ID_YES:
- if self.SaveProjectAs():
- self.AppFrame.RefreshTitle()
- self.AppFrame.RefreshFileMenu()
- self.AppFrame.RefreshPageTitles()
- return True
+ if self.AppFrame is not None:
+ dialog = wx.MessageDialog(self.AppFrame,
+ _('You must have permission to work on the project\nWork on a project copy ?'),
+ _('Error'),
+ wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
+ answer = dialog.ShowModal()
+ dialog.Destroy()
+ if answer == wx.ID_YES:
+ if self.SaveProjectAs():
+ self.AppFrame.RefreshTitle()
+ self.AppFrame.RefreshFileMenu()
+ self.AppFrame.RefreshPageTitles()
+ return True
return False
- def _getProjectFilesPath(self):
+ def _getProjectFilesPath(self, project_path=None):
+ if project_path is not None:
+ return os.path.join(project_path, "project_files")
projectfiles_path = os.path.join(self.GetProjectPath(), "project_files")
if not os.path.exists(projectfiles_path):
os.mkdir(projectfiles_path)
@@ -281,7 +288,7 @@
"""
if os.path.basename(ProjectPath) == "":
ProjectPath = os.path.dirname(ProjectPath)
- # Verify that project contains a PLCOpen program
+ # Verify that project contains a PLCOpen program
plc_file = os.path.join(ProjectPath, "plc.xml")
if not os.path.isfile(plc_file):
return _("Chosen folder doesn't contain a program. It's not a valid project!")
@@ -310,9 +317,11 @@
if os.path.exists(self._getBuildPath()):
self.EnableMethod("_Clean", True)
- if os.path.isfile(self._getIECrawcodepath()):
+ if os.path.isfile(self._getIECcodepath()):
self.ShowMethod("_showIECcode", True)
-
+
+ self.UpdateMethodsFromPLCStatus()
+
return None
def RecursiveConfNodeInfos(self, confnode):
@@ -321,6 +330,7 @@
values.append(
{"name": "%s: %s" % (CTNChild.GetFullIEC_Channel(),
CTNChild.CTNName()),
+ "tagname": CTNChild.CTNFullName(),
"type": ITEM_CONFNODE,
"confnode": CTNChild,
"icon": CTNChild.GetIconName(),
@@ -345,14 +355,19 @@
self.ClearChildren()
self.ResetAppFrame(None)
- def SaveProject(self):
+ def SaveProject(self, from_project_path=None):
if self.CheckProjectPathPerm(False):
+ if from_project_path is not None:
+ old_projectfiles_path = self._getProjectFilesPath(from_project_path)
+ if os.path.isdir(old_projectfiles_path):
+ shutil.copytree(old_projectfiles_path,
+ self._getProjectFilesPath(self.ProjectPath))
self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
- result = self.CTNRequestSave()
+ result = self.CTNRequestSave(from_project_path)
if result:
self.logger.write_error(result)
- def SaveProjectAs(self, dosave=True):
+ def SaveProjectAs(self):
# Ask user to choose a path with write permissions
if wx.Platform == '__WXMSW__':
path = os.getenv("USERPROFILE")
@@ -364,9 +379,8 @@
if answer == wx.ID_OK:
newprojectpath = dirdialog.GetPath()
if os.path.isdir(newprojectpath):
- self.ProjectPath = newprojectpath
- if dosave:
- self.SaveProject()
+ self.ProjectPath, old_project_path = newprojectpath, self.ProjectPath
+ self.SaveProject(old_project_path)
self._setBuildPath(self.BuildPath)
return True
return False
@@ -599,6 +613,15 @@
return False
# transform those base names to full names with path
C_files = map(lambda filename:os.path.join(buildpath, filename), C_files)
+
+ # prepend beremiz include to configuration header
+ H_files = [ fname for fname in result.splitlines() if fname[-2:]==".h" or fname[-2:]==".H" ]
+ H_files.remove("LOCATED_VARIABLES.h")
+ H_files = map(lambda filename:os.path.join(buildpath, filename), H_files)
+ for H_file in H_files:
+ with file(H_file, 'r') as original: data = original.read()
+ with file(H_file, 'w') as modified: modified.write('#include "beremiz.h"\n' + data)
+
self.logger.write(_("Extracting Located Variables...\n"))
# Keep track of generated located variables for later use by self._Generate_C
self.PLCGeneratedLocatedVars = self.GetLocations()
@@ -776,7 +799,7 @@
return debug_code
- def Generate_plc_common_main(self):
+ def Generate_plc_main(self):
"""
Use confnodes layout given in LocationCFilesAndCFLAGS to
generate glue code that dispatch calls to all confnodes
@@ -785,19 +808,19 @@
# in retreive, publish, init, cleanup
locstrs = map(lambda x:"_".join(map(str,x)),
[loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls])
-
+
# Generate main, based on template
if not self.BeremizRoot.getDisable_Extensions():
- plc_main_code = targets.GetCode("plc_common_main") % {
+ plc_main_code = targets.GetCode("plc_main_head") % {
"calls_prototypes":"\n".join([(
"int __init_%(s)s(int argc,char **argv);\n"+
"void __cleanup_%(s)s(void);\n"+
"void __retrieve_%(s)s(void);\n"+
"void __publish_%(s)s(void);")%{'s':locstr} for locstr in locstrs]),
"retrieve_calls":"\n ".join([
- "__retrieve_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
+ "__retrieve_%s();"%locstr for locstr in locstrs]),
"publish_calls":"\n ".join([ #Call publish in reverse order
- "__publish_%s();"%locstr for locstr in locstrs]),
+ "__publish_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
"init_calls":"\n ".join([
"init_level=%d; "%(i+1)+
"if((res = __init_%s(argc,argv))){"%locstr +
@@ -808,7 +831,7 @@
"__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
}
else:
- plc_main_code = targets.GetCode("plc_common_main") % {
+ plc_main_code = targets.GetCode("plc_main_head") % {
"calls_prototypes":"\n",
"retrieve_calls":"\n",
"publish_calls":"\n",
@@ -816,6 +839,7 @@
"cleanup_calls":"\n"
}
plc_main_code += targets.GetTargetCode(self.GetTarget().getcontent()["name"])
+ plc_main_code += targets.GetCode("plc_main_tail")
return plc_main_code
@@ -890,13 +914,16 @@
# Now we can forget ExtraFiles (will close files object)
del ExtraFiles
+ # Header file for extensions
+ open(os.path.join(buildpath,"beremiz.h"), "w").write(targets.GetHeader())
+
# Template based part of C code generation
# files are stacked at the beginning, as files of confnode tree root
for generator, filename, name in [
# debugger code
(self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
# init/cleanup/retrieve/publish, run and align code
- (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]:
+ (self.Generate_plc_main,"plc_main.c","Common runtime")]:
try:
# Do generate
code = generator()
@@ -966,7 +993,7 @@
if self._IECCodeView is None:
plc_file = self._getIECcodepath()
- self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", None, None, instancepath=name)
+ self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
self._IECCodeView.SetTextSyntax("ALL")
self._IECCodeView.SetKeywords(IEC_KEYWORDS)
try:
@@ -986,7 +1013,7 @@
if self._IECRawCodeView is None:
controler = MiniTextControler(self._getIECrawcodepath(), self)
- self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", None, controler, instancepath=name)
+ self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name)
self._IECRawCodeView.SetTextSyntax("ALL")
self._IECRawCodeView.SetKeywords(IEC_KEYWORDS)
self._IECRawCodeView.RefreshView()
@@ -1077,36 +1104,10 @@
self.CompareLocalAndRemotePLC()
def UpdatePLCLog(self, log_count):
- if log_count :
- to_console = []
- for level, count, prev in zip(xrange(LogLevelsCount), log_count,self.previous_log_count):
- if count is not None and prev != count:
- # XXX replace dump to console with dedicated log panel.
- dump_end = max( # request message sent after the last one we already got
- prev - 1 if prev is not None else -1,
- count - 100) # 100 is purely arbitrary number
- # dedicated panel should only ask for a small range,
- # depending on how user navigate in the panel
- # and only ask for last one in follow mode
- for msgidx in xrange(count-1, dump_end,-1):
- answer = self._connector.GetLogMessage(level, msgidx)
- if answer is not None :
- msg, tick, tv_sec, tv_nsec = answer
- to_console.insert(0,(
- (tv_sec, tv_nsec),
- '%d|%s.%9.9d|%s(%s)'%(
- int(tick),
- str(datetime.fromtimestamp(tv_sec)),
- tv_nsec,
- msg,
- LogLevels[level])))
- else:
- break;
- self.previous_log_count[level] = count
- if to_console:
- to_console.sort()
- self.logger.write("\n".join(zip(*to_console)[1]+('',)))
-
+ if log_count:
+ if self.AppFrame is not None:
+ self.AppFrame.LogViewer.SetLogCounters(log_count)
+
def UpdateMethodsFromPLCStatus(self):
status = None
if self._connector is not None:
@@ -1115,7 +1116,7 @@
status, log_count = PLCstatus
self.UpdatePLCLog(log_count)
if status is None:
- self._connector = None
+ self._SetConnector(None, False)
status = "Disconnected"
if(self.previous_plcstate != status):
for args in {
@@ -1133,16 +1134,18 @@
("_Disconnect", False)],
}.get(status,[]):
self.ShowMethod(*args)
- {"Broken": self.logger.write_error,
- None: lambda x: None}.get(
- status, self.logger.write)(_("PLC state is \"%s\"\n")%_(status))
self.previous_plcstate = status
if self.AppFrame is not None:
self.AppFrame.RefreshStatusToolBar()
-
+ if status == "Disconnected":
+ self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 1)
+ self.AppFrame.ConnectionStatusBar.SetStatusText('', 2)
+ else:
+ self.AppFrame.ConnectionStatusBar.SetStatusText(
+ _("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), 1)
+ self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 2)
+
def PullPLCStatusProc(self, event):
- if self._connector is None:
- self.StatusTimer.Stop()
self.UpdateMethodsFromPLCStatus()
def RegisterDebugVarToConnector(self):
@@ -1179,16 +1182,23 @@
self.TracedIECPath = []
self._connector.SetTraceVariablesList([])
self.IECdebug_lock.release()
-
+
+ def IsPLCStarted(self):
+ return self.previous_plcstate == "Started"
+
def ReArmDebugRegisterTimer(self):
if self.DebugTimer is not None:
self.DebugTimer.cancel()
-
- # Timer to prevent rapid-fire when registering many variables
- # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
- self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
- # Rearm anti-rapid-fire timer
- self.DebugTimer.start()
+
+ # Prevent to call RegisterDebugVarToConnector when PLC is not started
+ # If an output location var is forced it's leads to segmentation fault in runtime
+ # Links between PLC located variables and real variables are not ready
+ if self.IsPLCStarted():
+ # Timer to prevent rapid-fire when registering many variables
+ # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
+ self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
+ # Rearm anti-rapid-fire timer
+ self.DebugTimer.start()
def GetDebugIECVariableType(self, IECPath):
Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
@@ -1227,13 +1237,15 @@
IECdebug_data = self.IECdebug_datas.get(IECPath, None)
if IECdebug_data is not None:
IECdebug_data[0].pop(callableobj,None)
+ if len(IECdebug_data[0]) == 0:
+ self.IECdebug_datas.pop(IECPath)
self.IECdebug_lock.release()
self.ReArmDebugRegisterTimer()
def UnsubscribeAllDebugIECVariable(self):
self.IECdebug_lock.acquire()
- IECdebug_data = {}
+ self.IECdebug_datas = {}
self.IECdebug_lock.release()
self.ReArmDebugRegisterTimer()
@@ -1303,6 +1315,7 @@
else:
plc_status = None
debug_getvar_retry += 1
+ #print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
if plc_status == "Started":
self.IECdebug_lock.acquire()
if len(debug_vars) == len(self.TracedIECPath):
@@ -1341,7 +1354,6 @@
def _connect_debug(self):
self.previous_plcstate = None
- self.previous_log_count = [None]*LogLevelsCount
if self.AppFrame:
self.AppFrame.ResetGraphicViewers()
self.RegisterDebugVarToConnector()
@@ -1373,6 +1385,19 @@
wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+ def _SetConnector(self, connector, update_status=True):
+ self._connector = connector
+ if self.AppFrame is not None:
+ self.AppFrame.LogViewer.SetLogSource(connector)
+ if connector is not None:
+ # Start the status Timer
+ self.StatusTimer.Start(milliseconds=500, oneShot=False)
+ else:
+ # Stop the status Timer
+ self.StatusTimer.Stop()
+ if update_status:
+ wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+
def _Connect(self):
# don't accept re-connetion if already connected
if self._connector is not None:
@@ -1417,7 +1442,7 @@
# Get connector from uri
try:
- self._connector = connectors.ConnectorFactory(uri, self)
+ self._SetConnector(connectors.ConnectorFactory(uri, self))
except Exception, msg:
self.logger.write_error(_("Exception while connecting %s!\n")%uri)
self.logger.write_error(traceback.format_exc())
@@ -1442,9 +1467,6 @@
#self.logger.write(_("PLC is %s\n")%status)
- # Start the status Timer
- self.StatusTimer.Start(milliseconds=500, oneShot=False)
-
if self.previous_plcstate in ["Started","Stopped"]:
if self.DebugAvailable() and self.GetIECProgramsAndVariables():
self.logger.write(_("Debugger ready\n"))
@@ -1477,9 +1499,7 @@
def _Disconnect(self):
- self._connector = None
- self.StatusTimer.Stop()
- wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+ self._SetConnector(None)
def _Transfer(self):
# Get the last build PLC's
@@ -1522,8 +1542,6 @@
else:
self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
- self.previous_log_count = [None]*LogLevelsCount
-
wx.CallAfter(self.UpdateMethodsFromPLCStatus)
StatusMethods = [
--- a/c_ext/CFileEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/c_ext/CFileEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,839 +1,44 @@
-import keyword
-import wx
-import wx.grid
import wx.stc as stc
-import wx.lib.buttons
-from controls import CustomGrid, CustomTable
-from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
-from util.BitmapLibrary import GetBitmap
+from controls.CustomStyledTextCtrl import faces
+from editors.CodeFileEditor import CodeFileEditor, CodeEditor
-if wx.Platform == '__WXMSW__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Courier New',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 10,
- 'size2': 8,
- }
-else:
- faces = { 'times': 'Times',
- 'mono' : 'Courier',
- 'helv' : 'Helvetica',
- 'other': 'new century schoolbook',
- 'size' : 12,
- 'size2': 10,
- }
+class CppEditor(CodeEditor):
+ KEYWORDS = ["asm", "auto", "bool", "break", "case", "catch", "char", "class",
+ "const", "const_cast", "continue", "default", "delete", "do", "double",
+ "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false",
+ "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable",
+ "namespace", "new", "operator", "private", "protected", "public", "register",
+ "reinterpret_cast", "return", "short", "signed", "sizeof", "static",
+ "static_cast", "struct", "switch", "template", "this", "throw", "true", "try",
+ "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual",
+ "void", "volatile", "wchar_t", "while"]
+ COMMENT_HEADER = "/"
+
+ def SetCodeLexer(self):
+ self.SetLexer(stc.STC_LEX_CPP)
+
+ self.StyleSetSpec(stc.STC_C_COMMENT, 'fore:#408060,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_COMMENTLINE, 'fore:#408060,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_COMMENTDOC, 'fore:#408060,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_NUMBER, 'fore:#0076AE,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_WORD, 'bold,fore:#800056,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_STRING, 'fore:#2a00ff,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_PREPROCESSOR, 'bold,fore:#800056,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_OPERATOR, 'bold,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_C_STRINGEOL, 'back:#FFD5FF,size:%(size)d' % faces)
-def AppendMenu(parent, help, id, kind, text):
- if wx.VERSION >= (2, 6, 0):
- parent.Append(help=help, id=id, kind=kind, text=text)
- else:
- parent.Append(helpString=help, id=id, kind=kind, item=text)
+#-------------------------------------------------------------------------------
+# CFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
-
-[ID_CPPEDITOR,
-] = [wx.NewId() for _init_ctrls in range(1)]
-
-CPP_KEYWORDS = ["asm", "auto", "bool", "break", "case", "catch", "char", "class",
- "const", "const_cast", "continue", "default", "delete", "do", "double",
- "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false",
- "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable",
- "namespace", "new", "operator", "private", "protected", "public", "register",
- "reinterpret_cast", "return", "short", "signed", "sizeof", "static",
- "static_cast", "struct", "switch", "template", "this", "throw", "true", "try",
- "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual",
- "void", "volatile", "wchar_t", "while"]
-
-def GetCursorPos(old, new):
- old_length = len(old)
- new_length = len(new)
- common_length = min(old_length, new_length)
- i = 0
- for i in xrange(common_length):
- if old[i] != new[i]:
- break
- if old_length < new_length:
- if common_length > 0 and old[i] != new[i]:
- return i + new_length - old_length
- else:
- return i + new_length - old_length + 1
- elif old_length > new_length or i < min(old_length, new_length) - 1:
- if common_length > 0 and old[i] != new[i]:
- return i
- else:
- return i + 1
- else:
- return None
-
-class CppEditor(stc.StyledTextCtrl):
-
- fold_symbols = 3
+class CFileEditor(CodeFileEditor):
- def __init__(self, parent, name, window, controler):
- stc.StyledTextCtrl.__init__(self, parent, ID_CPPEDITOR, wx.DefaultPosition,
- wx.Size(-1, 300), 0)
-
- self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
- self.SetMarginWidth(1, 25)
-
- self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
- self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
-
- self.SetLexer(stc.STC_LEX_CPP)
- self.SetKeyWords(0, " ".join(CPP_KEYWORDS))
-
- self.SetProperty("fold", "1")
- self.SetProperty("tab.timmy.whinge.level", "1")
- self.SetMargins(0,0)
-
- self.SetViewWhiteSpace(False)
- #self.SetBufferedDraw(False)
- #self.SetViewEOL(True)
- #self.SetEOLMode(stc.STC_EOL_CRLF)
- #self.SetUseAntiAliasing(True)
-
- self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
- self.SetEdgeColumn(78)
-
- # Setup a margin to hold fold markers
- #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
- self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
- self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
- self.SetMarginSensitive(2, True)
- self.SetMarginWidth(2, 12)
-
- if self.fold_symbols == 0:
- # Arrow pointing right for contracted folders, arrow pointing down for expanded
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
- elif self.fold_symbols == 1:
- # Plus for contracted folders, minus for expanded
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
- elif self.fold_symbols == 2:
- # Like a flattened tree control using circular headers and curved joins
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040")
-
- elif self.fold_symbols == 3:
- # Like a flattened tree control using square headers
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
- self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
-
-
- self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
- self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
-
- # Make some styles, The lexer defines what each style is used for, we
- # just have to define what each style looks like. This set is adapted from
- # Scintilla sample property files.
-
- # Global default styles for all languages
- self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
- self.StyleClearAll() # Reset all to be like the default
-
- # Global default styles for all languages
- self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
- self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
- self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
- self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
- self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
-
- self.StyleSetSpec(stc.STC_C_COMMENT, 'fore:#408060')
- self.StyleSetSpec(stc.STC_C_COMMENTLINE, 'fore:#408060')
- self.StyleSetSpec(stc.STC_C_COMMENTDOC, 'fore:#408060')
- self.StyleSetSpec(stc.STC_C_NUMBER, 'fore:#0076AE')
- self.StyleSetSpec(stc.STC_C_WORD, 'bold,fore:#800056')
- self.StyleSetSpec(stc.STC_C_STRING, 'fore:#2a00ff')
- self.StyleSetSpec(stc.STC_C_PREPROCESSOR, 'bold,fore:#800056')
- self.StyleSetSpec(stc.STC_C_OPERATOR, 'bold')
- self.StyleSetSpec(stc.STC_C_STRINGEOL, 'back:#FFD5FF')
-
- # register some images for use in the AutoComplete box.
- #self.RegisterImage(1, images.getSmilesBitmap())
- self.RegisterImage(1,
- wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(16,16)))
- self.RegisterImage(2,
- wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
- self.RegisterImage(3,
- wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
-
- # Indentation size
- self.SetTabWidth(2)
- self.SetUseTabs(0)
-
- self.Controler = controler
- self.ParentWindow = window
-
- self.DisableEvents = True
- self.Name = name
- self.CurrentAction = None
-
- self.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
-
- self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_CPPEDITOR)
- self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
- self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_CPPEDITOR)
-
- def OnModification(self, event):
- if not self.DisableEvents:
- mod_type = event.GetModificationType()
- if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
- if mod_type&wx.stc.STC_MOD_BEFOREINSERT:
- if self.CurrentAction == None:
- self.StartBuffering()
- elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
- self.CurrentAction = ("Add", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
- elif mod_type&wx.stc.STC_MOD_BEFOREDELETE:
- if self.CurrentAction == None:
- self.StartBuffering()
- elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
- self.CurrentAction = ("Delete", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
- event.Skip()
-
- def OnDoDrop(self, event):
- self.ResetBuffer()
- wx.CallAfter(self.RefreshModel)
- event.Skip()
-
- # Buffer the last model state
- def RefreshBuffer(self):
- self.Controler.BufferCFile()
- if self.ParentWindow is not None:
- self.ParentWindow.RefreshTitle()
- self.ParentWindow.RefreshFileMenu()
- self.ParentWindow.RefreshEditMenu()
- self.ParentWindow.RefreshPageTitles()
-
- def StartBuffering(self):
- self.Controler.StartBuffering()
- if self.ParentWindow is not None:
- self.ParentWindow.RefreshTitle()
- self.ParentWindow.RefreshFileMenu()
- self.ParentWindow.RefreshEditMenu()
- self.ParentWindow.RefreshPageTitles()
-
- def ResetBuffer(self):
- if self.CurrentAction != None:
- self.Controler.EndBuffering()
- self.CurrentAction = None
-
- def RefreshView(self):
- self.ResetBuffer()
- self.DisableEvents = True
- old_cursor_pos = self.GetCurrentPos()
- old_text = self.GetText()
- new_text = self.Controler.GetPartText(self.Name)
- self.SetText(new_text)
- new_cursor_pos = GetCursorPos(old_text, new_text)
- if new_cursor_pos != None:
- self.GotoPos(new_cursor_pos)
- else:
- self.GotoPos(old_cursor_pos)
- self.ScrollToColumn(0)
- self.EmptyUndoBuffer()
- self.DisableEvents = False
-
- self.Colourise(0, -1)
-
- def DoGetBestSize(self):
- return self.ParentWindow.GetPanelBestSize()
-
- def RefreshModel(self):
- self.Controler.SetPartText(self.Name, self.GetText())
-
- def OnKeyPressed(self, event):
- if self.CallTipActive():
- self.CallTipCancel()
- key = event.GetKeyCode()
-
- if key == 32 and event.ControlDown():
- pos = self.GetCurrentPos()
-
- # Tips
- if event.ShiftDown():
- pass
-## self.CallTipSetBackground("yellow")
-## self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
-## 'show some suff, maybe parameters..\n\n'
-## 'fubar(param1, param2)')
- # Code completion
- else:
- self.AutoCompSetIgnoreCase(False) # so this needs to match
-
- # Images are specified with a appended "?type"
- self.AutoCompShow(0, " ".join([word + "?1" for word in CPP_KEYWORDS]))
- else:
- event.Skip()
-
- def OnKillFocus(self, event):
- self.AutoCompCancel()
- event.Skip()
-
- def OnUpdateUI(self, evt):
- # check for matching braces
- braceAtCaret = -1
- braceOpposite = -1
- charBefore = None
- caretPos = self.GetCurrentPos()
-
- if caretPos > 0:
- charBefore = self.GetCharAt(caretPos - 1)
- styleBefore = self.GetStyleAt(caretPos - 1)
-
- # check before
- if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
- braceAtCaret = caretPos - 1
-
- # check after
- if braceAtCaret < 0:
- charAfter = self.GetCharAt(caretPos)
- styleAfter = self.GetStyleAt(caretPos)
-
- if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
- braceAtCaret = caretPos
-
- if braceAtCaret >= 0:
- braceOpposite = self.BraceMatch(braceAtCaret)
-
- if braceAtCaret != -1 and braceOpposite == -1:
- self.BraceBadLight(braceAtCaret)
- else:
- self.BraceHighlight(braceAtCaret, braceOpposite)
- #pt = self.PointFromPosition(braceOpposite)
- #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
- #print pt
- #self.Refresh(False)
-
-
- def OnMarginClick(self, evt):
- # fold and unfold as needed
- if evt.GetMargin() == 2:
- if evt.GetShift() and evt.GetControl():
- self.FoldAll()
- else:
- lineClicked = self.LineFromPosition(evt.GetPosition())
-
- if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
- if evt.GetShift():
- self.SetFoldExpanded(lineClicked, True)
- self.Expand(lineClicked, True, True, 1)
- elif evt.GetControl():
- if self.GetFoldExpanded(lineClicked):
- self.SetFoldExpanded(lineClicked, False)
- self.Expand(lineClicked, False, True, 0)
- else:
- self.SetFoldExpanded(lineClicked, True)
- self.Expand(lineClicked, True, True, 100)
- else:
- self.ToggleFold(lineClicked)
-
-
- def FoldAll(self):
- lineCount = self.GetLineCount()
- expanding = True
-
- # find out if we are folding or unfolding
- for lineNum in range(lineCount):
- if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
- expanding = not self.GetFoldExpanded(lineNum)
- break
-
- lineNum = 0
-
- while lineNum < lineCount:
- level = self.GetFoldLevel(lineNum)
- if level & stc.STC_FOLDLEVELHEADERFLAG and \
- (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
-
- if expanding:
- self.SetFoldExpanded(lineNum, True)
- lineNum = self.Expand(lineNum, True)
- lineNum = lineNum - 1
- else:
- lastChild = self.GetLastChild(lineNum, -1)
- self.SetFoldExpanded(lineNum, False)
-
- if lastChild > lineNum:
- self.HideLines(lineNum+1, lastChild)
-
- lineNum = lineNum + 1
+ CONFNODEEDITOR_TABS = [
+ (_("C code"), "_create_CodePanel")]
+ CODE_EDITOR = CppEditor
- def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
- lastChild = self.GetLastChild(line, level)
- line = line + 1
-
- while line <= lastChild:
- if force:
- if visLevels > 0:
- self.ShowLines(line, line)
- else:
- self.HideLines(line, line)
- else:
- if doExpand:
- self.ShowLines(line, line)
-
- if level == -1:
- level = self.GetFoldLevel(line)
-
- if level & stc.STC_FOLDLEVELHEADERFLAG:
- if force:
- if visLevels > 1:
- self.SetFoldExpanded(line, True)
- else:
- self.SetFoldExpanded(line, False)
-
- line = self.Expand(line, doExpand, force, visLevels-1)
-
- else:
- if doExpand and self.GetFoldExpanded(line):
- line = self.Expand(line, True, force, visLevels-1)
- else:
- line = self.Expand(line, False, force, visLevels-1)
- else:
- line = line + 1
-
- return line
-
- def Cut(self):
- self.ResetBuffer()
- self.DisableEvents = True
- self.CmdKeyExecute(wx.stc.STC_CMD_CUT)
- self.DisableEvents = False
- self.RefreshModel()
- self.RefreshBuffer()
-
- def Copy(self):
- self.CmdKeyExecute(wx.stc.STC_CMD_COPY)
-
- def Paste(self):
- self.ResetBuffer()
- self.DisableEvents = True
- self.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
- self.DisableEvents = False
- self.RefreshModel()
- self.RefreshBuffer()
-
-
-#-------------------------------------------------------------------------------
-# Helper for VariablesGrid values
-#-------------------------------------------------------------------------------
-
-class VariablesTable(CustomTable):
-
- def GetValue(self, row, col):
- if row < self.GetNumberRows():
- if col == 0:
- return row + 1
- else:
- return str(self.data[row].get(self.GetColLabelValue(col, False), ""))
-
- def _updateColAttrs(self, grid):
- """
- wxGrid -> update the column attributes to add the
- appropriate renderer given the column name.
-
- Otherwise default to the default renderer.
- """
-
- typelist = None
- accesslist = None
- for row in range(self.GetNumberRows()):
- for col in range(self.GetNumberCols()):
- editor = None
- renderer = None
- colname = self.GetColLabelValue(col, False)
-
- if colname == "Name":
- editor = wx.grid.GridCellTextEditor()
- elif colname == "Class":
- editor = wx.grid.GridCellChoiceEditor()
- editor.SetParameters("input,memory,output")
- elif colname == "Type":
- pass
- else:
- grid.SetReadOnly(row, col, True)
-
- grid.SetCellEditor(row, col, editor)
- grid.SetCellRenderer(row, col, renderer)
-
- grid.SetCellBackgroundColour(row, col, wx.WHITE)
- self.ResizeRow(grid, row)
-
-
-class VariablesEditor(wx.Panel):
-
- def __init__(self, parent, window, controler):
- wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
-
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=4)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
-
- self.VariablesGrid = CustomGrid(self, size=wx.Size(-1, 300), style=wx.VSCROLL)
- self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
- self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
- self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
- main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
-
- controls_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(controls_sizer, border=5, flag=wx.TOP|wx.ALIGN_RIGHT)
-
- for name, bitmap, help in [
- ("AddVariableButton", "add_element", _("Add variable")),
- ("DeleteVariableButton", "remove_element", _("Remove variable")),
- ("UpVariableButton", "up", _("Move variable up")),
- ("DownVariableButton", "down", _("Move variable down"))]:
- button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
- size=wx.Size(28, 28), style=wx.NO_BORDER)
- button.SetToolTipString(help)
- setattr(self, name, button)
- controls_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-
- self.SetSizer(main_sizer)
-
- self.ParentWindow = window
- self.Controler = controler
-
- self.VariablesDefaultValue = {"Name" : "", "Class" : "input", "Type" : ""}
- self.Table = VariablesTable(self, [], ["#", "Name", "Class", "Type"])
- self.ColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
- self.ColSizes = [40, 200, 150, 150]
- self.VariablesGrid.SetTable(self.Table)
- self.VariablesGrid.SetButtons({"Add": self.AddVariableButton,
- "Delete": self.DeleteVariableButton,
- "Up": self.UpVariableButton,
- "Down": self.DownVariableButton})
-
- def _AddVariable(new_row):
- self.Table.InsertRow(new_row, self.VariablesDefaultValue.copy())
- self.RefreshModel()
- self.RefreshView()
- return new_row
- setattr(self.VariablesGrid, "_AddRow", _AddVariable)
-
- def _DeleteVariable(row):
- self.Table.RemoveRow(row)
- self.RefreshModel()
- self.RefreshView()
- setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
-
- def _MoveVariable(row, move):
- new_row = self.Table.MoveRow(row, move)
- if new_row != row:
- self.RefreshModel()
- self.RefreshView()
- return new_row
- setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
-
- self.VariablesGrid.SetRowLabelSize(0)
- for col in range(self.Table.GetNumberCols()):
- attr = wx.grid.GridCellAttr()
- attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
- self.VariablesGrid.SetColAttr(col, attr)
- self.VariablesGrid.SetColSize(col, self.ColSizes[col])
- self.Table.ResetView(self.VariablesGrid)
-
- def RefreshModel(self):
- self.Controler.SetVariables(self.Table.GetData())
- self.RefreshBuffer()
-
- # Buffer the last model state
- def RefreshBuffer(self):
- self.Controler.BufferCFile()
- self.ParentWindow.RefreshTitle()
- self.ParentWindow.RefreshFileMenu()
- self.ParentWindow.RefreshEditMenu()
- self.ParentWindow.RefreshPageTitles()
-
- def RefreshView(self):
- self.Table.SetData(self.Controler.GetVariables())
- self.Table.ResetView(self.VariablesGrid)
- self.VariablesGrid.RefreshButtons()
-
- def DoGetBestSize(self):
- return self.ParentWindow.GetPanelBestSize()
-
- def OnVariablesGridCellChange(self, event):
- self.RefreshModel()
- wx.CallAfter(self.RefreshView)
- event.Skip()
-
- def OnVariablesGridEditorShown(self, event):
- row, col = event.GetRow(), event.GetCol()
- if self.Table.GetColLabelValue(col, False) == "Type":
- type_menu = wx.Menu(title='')
- base_menu = wx.Menu(title='')
- for base_type in self.Controler.GetBaseTypes():
- new_id = wx.NewId()
- AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
- self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
- type_menu.AppendMenu(wx.NewId(), "Base Types", base_menu)
- datatype_menu = wx.Menu(title='')
- for datatype in self.Controler.GetDataTypes(basetypes=False, only_locatables=True):
- new_id = wx.NewId()
- AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
- self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
- type_menu.AppendMenu(wx.NewId(), "User Data Types", datatype_menu)
- rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
-
- self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
- type_menu.Destroy()
- event.Veto()
- else:
- event.Skip()
-
- def GetVariableTypeFunction(self, base_type):
- def VariableTypeFunction(event):
- row = self.VariablesGrid.GetGridCursorRow()
- self.Table.SetValueByName(row, "Type", base_type)
- self.Table.ResetView(self.VariablesGrid)
- self.RefreshModel()
- self.RefreshView()
- event.Skip()
- return VariableTypeFunction
-
- def OnVariablesGridCellLeftClick(self, event):
- if event.GetCol() == 0:
- row = event.GetRow()
- num = 0
- if self.Table.GetValueByName(row, "Class") == "input":
- dir = "%I"
- for i in xrange(row):
- if self.Table.GetValueByName(i, "Class") == "input":
- num += 1
- elif self.Table.GetValueByName(row, "Class") == "memory":
- dir = "%M"
- for i in xrange(row):
- if self.Table.GetValueByName(i, "Class") == "memory":
- num += 1
- else:
- dir = "%Q"
- for i in xrange(row):
- if self.Table.GetValueByName(i, "Class") == "output":
- num += 1
- data_type = self.Table.GetValueByName(row, "Type")
- var_name = self.Table.GetValueByName(row, "Name")
- base_location = ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation()))
- location = "%s%s%s.%d"%(dir, self.Controler.GetSizeOfType(data_type), base_location, num)
- data = wx.TextDataObject(str((location, "location", data_type, var_name, "")))
- dragSource = wx.DropSource(self.VariablesGrid)
- dragSource.SetData(data)
- dragSource.DoDragDrop()
- return
- event.Skip()
-
-
-#-------------------------------------------------------------------------------
-# SVGUIEditor Main Frame Class
-#-------------------------------------------------------------------------------
-
-CFILE_PARTS = [
- ("Includes", CppEditor),
- ("Variables", VariablesEditor),
- ("Globals", CppEditor),
- ("Init", CppEditor),
- ("CleanUp", CppEditor),
- ("Retrieve", CppEditor),
- ("Publish", CppEditor),
-]
-
-class FoldPanelCaption(wx.lib.buttons.GenBitmapTextToggleButton):
-
- def GetBackgroundBrush(self, dc):
- colBg = self.GetBackgroundColour()
- brush = wx.Brush(colBg, wx.SOLID)
- if self.style & wx.BORDER_NONE:
- myAttr = self.GetDefaultAttributes()
- parAttr = self.GetParent().GetDefaultAttributes()
- myDef = colBg == myAttr.colBg
- parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
- if myDef and parDef:
- if wx.Platform == "__WXMAC__":
- brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
- elif wx.Platform == "__WXMSW__":
- if self.DoEraseBackground(dc):
- brush = None
- elif myDef and not parDef:
- colBg = self.GetParent().GetBackgroundColour()
- brush = wx.Brush(colBg, wx.SOLID)
- return brush
-
- def DrawLabel(self, dc, width, height, dx=0, dy=0):
- bmp = self.bmpLabel
- if bmp is not None: # if the bitmap is used
- if self.bmpDisabled and not self.IsEnabled():
- bmp = self.bmpDisabled
- if self.bmpFocus and self.hasFocus:
- bmp = self.bmpFocus
- if self.bmpSelected and not self.up:
- bmp = self.bmpSelected
- bw,bh = bmp.GetWidth(), bmp.GetHeight()
- hasMask = bmp.GetMask() is not None
- else:
- bw = bh = 0 # no bitmap -> size is zero
-
- dc.SetFont(self.GetFont())
- if self.IsEnabled():
- dc.SetTextForeground(self.GetForegroundColour())
- else:
- dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
-
- label = self.GetLabel()
- tw, th = dc.GetTextExtent(label) # size of text
-
- if bmp is not None:
- dc.DrawBitmap(bmp, width - bw - 2, (height-bh)/2, hasMask) # draw bitmap if available
-
- dc.DrawText(label, 2, (height-th)/2) # draw the text
-
- dc.SetPen(wx.Pen(self.GetForegroundColour()))
- dc.SetBrush(wx.TRANSPARENT_BRUSH)
- dc.DrawRectangle(0, 0, width, height)
-
-class CFileEditor(ConfTreeNodeEditor):
-
- CONFNODEEDITOR_TABS = [
- (_("C code"), "_create_CCodeEditor")]
-
- def _create_CCodeEditor(self, prnt):
- self.CCodeEditor = wx.ScrolledWindow(prnt,
- style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
- self.CCodeEditor.Bind(wx.EVT_SIZE, self.OnCCodeEditorResize)
-
- self.Panels = {}
- self.MainSizer = wx.BoxSizer(wx.VERTICAL)
-
- for idx, (name, panel_class) in enumerate(CFILE_PARTS):
- button_id = wx.NewId()
- button = FoldPanelCaption(id=button_id, name='FoldPanelCaption_%s' % name,
- label=name, bitmap=GetBitmap("CollapsedIconData"),
- parent=self.CCodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 20), style=wx.NO_BORDER|wx.ALIGN_LEFT)
- button.SetBitmapSelected(GetBitmap("ExpandedIconData"))
- button.Bind(wx.EVT_BUTTON, self.GenPanelButtonCallback(name), id=button_id)
- self.MainSizer.AddWindow(button, 0, border=0, flag=wx.TOP|wx.GROW)
-
- if panel_class == VariablesEditor:
- panel = VariablesEditor(self.CCodeEditor, self.ParentWindow, self.Controler)
- else:
- panel = panel_class(self.CCodeEditor, name, self.ParentWindow, self.Controler)
- self.MainSizer.AddWindow(panel, 0, border=0, flag=wx.BOTTOM|wx.GROW)
- panel.Hide()
-
- self.Panels[name] = {"button": button, "panel": panel, "expanded": False, "row": 2 * idx + 1}
-
- self.CCodeEditor.SetSizer(self.MainSizer)
-
- return self.CCodeEditor
-
- def __init__(self, parent, controler, window):
- ConfTreeNodeEditor.__init__(self, parent, controler, window)
-
- def GetBufferState(self):
- return self.Controler.GetBufferState()
-
- def Undo(self):
- self.Controler.LoadPrevious()
- self.RefreshView()
-
- def Redo(self):
- self.Controler.LoadNext()
- self.RefreshView()
-
- def RefreshView(self):
- ConfTreeNodeEditor.RefreshView(self)
-
- for infos in self.Panels.itervalues():
- infos["panel"].RefreshView()
-
- self.RefreshCCodeEditorScrollbars()
-
- def GenPanelButtonCallback(self, name):
- def PanelButtonCallback(event):
- self.TogglePanel(name)
- return PanelButtonCallback
-
- def ExpandPanel(self, name):
- infos = self.Panels.get(name, None)
- if infos is not None and not infos["expanded"]:
- infos["expanded"] = True
- infos["button"].SetToggle(True)
- infos["panel"].Show()
-
- self.RefreshSizerLayout()
-
- def CollapsePanel(self, name):
- infos = self.Panels.get(name, None)
- if infos is not None and infos["expanded"]:
- infos["expanded"] = False
- infos["button"].SetToggle(False)
- infos["panel"].Hide()
-
- self.RefreshSizerLayout()
-
- def TogglePanel(self, name):
- infos = self.Panels.get(name, None)
- if infos is not None:
- infos["expanded"] = not infos["expanded"]
- infos["button"].SetToggle(infos["expanded"])
- if infos["expanded"]:
- infos["panel"].Show()
- else:
- infos["panel"].Hide()
-
- self.RefreshSizerLayout()
-
- def RefreshSizerLayout(self):
- self.MainSizer.Layout()
- self.RefreshCCodeEditorScrollbars()
-
- def RefreshCCodeEditorScrollbars(self):
- self.CCodeEditor.GetBestSize()
- xstart, ystart = self.CCodeEditor.GetViewStart()
- window_size = self.CCodeEditor.GetClientSize()
- maxx, maxy = self.MainSizer.GetMinSize()
- posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
- posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
- self.CCodeEditor.Scroll(posx, posy)
- self.CCodeEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
- maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
-
- def OnCCodeEditorResize(self, event):
- self.RefreshCCodeEditorScrollbars()
- event.Skip()
-
--- a/c_ext/c_ext.py Wed Mar 13 12:34:55 2013 +0900
+++ b/c_ext/c_ext.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,19 +1,10 @@
+
import os
-from xml.dom import minidom
-import cPickle
-
-from xmlclass import *
from CFileEditor import CFileEditor
-from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT
+from CodeFileTreeNode import CodeFile
-CFileClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "cext_xsd.xsd"))
-
-TYPECONVERSION = {"BOOL" : "X", "SINT" : "B", "INT" : "W", "DINT" : "D", "LINT" : "L",
- "USINT" : "B", "UINT" : "W", "UDINT" : "D", "ULINT" : "L", "REAL" : "D", "LREAL" : "L",
- "STRING" : "B", "BYTE" : "B", "WORD" : "W", "DWORD" : "D", "LWORD" : "L", "WSTRING" : "W"}
-
-class CFile:
+class CFile(CodeFile):
XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="CExtension">
@@ -24,142 +15,25 @@
</xsd:element>
</xsd:schema>
"""
+ CODEFILE_NAME = "CFile"
+ SECTIONS_NAMES = [
+ "includes",
+ "globals",
+ "initFunction",
+ "cleanUpFunction",
+ "retrieveFunction",
+ "publishFunction"]
EditorType = CFileEditor
- def __init__(self):
- filepath = self.CFileName()
-
- self.CFile = CFileClasses["CFile"]()
- if os.path.isfile(filepath):
- xmlfile = open(filepath, 'r')
- tree = minidom.parse(xmlfile)
- xmlfile.close()
-
- for child in tree.childNodes:
- if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "CFile":
- self.CFile.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
- self.CreateCFileBuffer(True)
- else:
- self.CreateCFileBuffer(False)
- self.OnCTNSave()
-
+ def GenerateClassesFromXSDstring(self, xsd_string):
+ return GenerateClassesFromXSDstring(xsd_string)
+
def GetIconName(self):
return "Cfile"
- def CFileName(self):
+ def CodeFileName(self):
return os.path.join(self.CTNPath(), "cfile.xml")
-
- def GetBaseTypes(self):
- return self.GetCTRoot().GetBaseTypes()
-
- def GetDataTypes(self, basetypes = False, only_locatables = False):
- return self.GetCTRoot().GetDataTypes(basetypes=basetypes, only_locatables=only_locatables)
-
- def GetSizeOfType(self, type):
- return TYPECONVERSION.get(self.GetCTRoot().GetBaseType(type), None)
-
- def SetVariables(self, variables):
- self.CFile.variables.setvariable([])
- for var in variables:
- variable = CFileClasses["variables_variable"]()
- variable.setname(var["Name"])
- variable.settype(var["Type"])
- variable.setclass(var["Class"])
- self.CFile.variables.appendvariable(variable)
- def GetVariables(self):
- datas = []
- for var in self.CFile.variables.getvariable():
- datas.append({"Name" : var.getname(), "Type" : var.gettype(), "Class" : var.getclass()})
- return datas
-
- def GetVariableLocationTree(self):
- '''See ConfigTreeNode.GetVariableLocationTree() for a description.'''
-
- current_location = ".".join(map(str, self.GetCurrentLocation()))
-
- vars = []
- input = memory = output = 0
- for var in self.CFile.variables.getvariable():
- var_size = self.GetSizeOfType(var.gettype())
- var_location = ""
- if var.getclass() == "input":
- var_class = LOCATION_VAR_INPUT
- if var_size is not None:
- var_location = "%%I%s%s.%d"%(var_size, current_location, input)
- input += 1
- elif var.getclass() == "memory":
- var_class = LOCATION_VAR_INPUT
- if var_size is not None:
- var_location = "%%M%s%s.%d"%(var_size, current_location, memory)
- memory += 1
- else:
- var_class = LOCATION_VAR_OUTPUT
- if var_size is not None:
- var_location = "%%Q%s%s.%d"%(var_size, current_location, output)
- output += 1
- vars.append({"name": var.getname(),
- "type": var_class,
- "size": var_size,
- "IEC_type": var.gettype(),
- "var_name": var.getname(),
- "location": var_location,
- "description": "",
- "children": []})
-
- return {"name": self.BaseParams.getName(),
- "type": LOCATION_CONFNODE,
- "location": self.GetFullIEC_Channel(),
- "children": vars}
-
- def SetPartText(self, name, text):
- if name == "Includes":
- self.CFile.includes.settext(text)
- elif name == "Globals":
- self.CFile.globals.settext(text)
- elif name == "Init":
- self.CFile.initFunction.settext(text)
- elif name == "CleanUp":
- self.CFile.cleanUpFunction.settext(text)
- elif name == "Retrieve":
- self.CFile.retrieveFunction.settext(text)
- elif name == "Publish":
- self.CFile.publishFunction.settext(text)
-
- def GetPartText(self, name):
- if name == "Includes":
- return self.CFile.includes.gettext()
- elif name == "Globals":
- return self.CFile.globals.gettext()
- elif name == "Init":
- return self.CFile.initFunction.gettext()
- elif name == "CleanUp":
- return self.CFile.cleanUpFunction.gettext()
- elif name == "Retrieve":
- return self.CFile.retrieveFunction.gettext()
- elif name == "Publish":
- return self.CFile.publishFunction.gettext()
- return ""
-
- def CTNTestModified(self):
- return self.ChangesToSave or not self.CFileIsSaved()
-
- def OnCTNSave(self):
- filepath = self.CFileName()
-
- text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
- extras = {"xmlns":"http://www.w3.org/2001/XMLSchema",
- "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
- "xsi:schemaLocation" : "cext_xsd.xsd"}
- text += self.CFile.generateXMLText("CFile", 0, extras)
-
- xmlfile = open(filepath,"w")
- xmlfile.write(text.encode("utf-8"))
- xmlfile.close()
-
- self.MarkCFileAsSaved()
- return True
-
def CTNGenerate_C(self, buildpath, locations):
"""
Generate C code
@@ -178,64 +52,49 @@
location_str = "_".join(map(str, current_location))
text = "/* Code generated by Beremiz c_ext confnode */\n\n"
+ text += "#include <stdio.h>\n\n"
# Adding includes
text += "/* User includes */\n"
- text += self.CFile.includes.gettext()
+ text += self.CodeFile.includes.gettext().strip()
text += "\n"
- text += '#include "iec_types.h"'
-
+ text += '#include "iec_types_all.h"\n\n'
+
# Adding variables
- vars = []
- inputs = memories = outputs = 0
- for variable in self.CFile.variables.variable:
- var = {"Name" : variable.getname(), "Type" : variable.gettype()}
- if variable.getclass() == "input":
- var["location"] = "__I%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, inputs)
- inputs += 1
- elif variable.getclass() == "memory":
- var["location"] = "__M%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, memories)
- memories += 1
- else:
- var["location"] = "__Q%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, outputs)
- outputs += 1
- vars.append(var)
- text += "/* Beremiz c_ext confnode user variables definition */\n"
- base_types = self.GetCTRoot().GetBaseTypes()
- for var in vars:
- if var["Type"] in base_types:
- prefix = "IEC_"
- else:
- prefix = ""
- text += "%s%s beremiz%s;\n"%(prefix, var["Type"], var["location"])
- text += "%s%s *%s = &beremiz%s;\n"%(prefix, var["Type"], var["location"], var["location"])
+ config = self.GetCTRoot().GetProjectConfigNames()[0]
text += "/* User variables reference */\n"
- for var in vars:
- text += "#define %s beremiz%s\n"%(var["Name"], var["location"])
+ for variable in self.CodeFile.variables.variable:
+ var_infos = {
+ "name": variable.getname(),
+ "global": "%s__%s" % (config.upper(),
+ variable.getname().upper()),
+ "type": "__IEC_%s_t" % variable.gettype()}
+ text += "extern %(type)s %(global)s;\n" % var_infos
+ text += "#define %(name)s %(global)s.value\n" % var_infos
text += "\n"
# Adding user global variables and routines
text += "/* User internal user variables and routines */\n"
- text += self.CFile.globals.gettext()
+ text += self.CodeFile.globals.gettext().strip()
+ text += "\n"
# Adding Beremiz confnode functions
text += "/* Beremiz confnode functions */\n"
text += "int __init_%s(int argc,char **argv)\n{\n"%location_str
- text += self.CFile.initFunction.gettext()
- text += " return 0;\n"
- text += "\n}\n\n"
+ text += self.CodeFile.initFunction.gettext().strip()
+ text += " return 0;\n}\n\n"
text += "void __cleanup_%s(void)\n{\n"%location_str
- text += self.CFile.cleanUpFunction.gettext()
+ text += self.CodeFile.cleanUpFunction.gettext().strip()
text += "\n}\n\n"
text += "void __retrieve_%s(void)\n{\n"%location_str
- text += self.CFile.retrieveFunction.gettext()
+ text += self.CodeFile.retrieveFunction.gettext().strip()
text += "\n}\n\n"
text += "void __publish_%s(void)\n{\n"%location_str
- text += self.CFile.publishFunction.gettext()
+ text += self.CodeFile.publishFunction.gettext().strip()
text += "\n}\n\n"
Gen_Cfile_path = os.path.join(buildpath, "CFile_%s.c"%location_str)
@@ -247,48 +106,3 @@
return [(Gen_Cfile_path, str(self.CExtension.getCFLAGS() + matiec_flags))],str(self.CExtension.getLDFLAGS()),True
-
-#-------------------------------------------------------------------------------
-# Current Buffering Management Functions
-#-------------------------------------------------------------------------------
-
- """
- Return a copy of the cfile model
- """
- def Copy(self, model):
- return cPickle.loads(cPickle.dumps(model))
-
- def CreateCFileBuffer(self, saved):
- self.Buffering = False
- self.CFileBuffer = UndoBuffer(cPickle.dumps(self.CFile), saved)
-
- def BufferCFile(self):
- self.CFileBuffer.Buffering(cPickle.dumps(self.CFile))
-
- def StartBuffering(self):
- self.Buffering = True
-
- def EndBuffering(self):
- if self.Buffering:
- self.CFileBuffer.Buffering(cPickle.dumps(self.CFile))
- self.Buffering = False
-
- def MarkCFileAsSaved(self):
- self.EndBuffering()
- self.CFileBuffer.CurrentSaved()
-
- def CFileIsSaved(self):
- return self.CFileBuffer.IsCurrentSaved() and not self.Buffering
-
- def LoadPrevious(self):
- self.EndBuffering()
- self.CFile = cPickle.loads(self.CFileBuffer.Previous())
-
- def LoadNext(self):
- self.CFile = cPickle.loads(self.CFileBuffer.Next())
-
- def GetBufferState(self):
- first = self.CFileBuffer.IsFirst() and not self.Buffering
- last = self.CFileBuffer.IsLast()
- return not first, not last
-
--- a/c_ext/cext_xsd.xsd Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1" ?>
-<xsd:schema targetNamespace="cext_xsd.xsd"
- xmlns:cext="cext_xsd.xsd"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- elementFormDefault="qualified"
- attributeFormDefault="unqualified">
-
- <xsd:element name="CFile">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="includes" type="cext:CCode"/>
- <xsd:element name="variables">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
- <xsd:complexType>
- <xsd:attribute name="name" type="xsd:string" use="required"/>
- <xsd:attribute name="type" type="xsd:string" use="required"/>
- <xsd:attribute name="class" use="required">
- <xsd:simpleType>
- <xsd:restriction base="xsd:string">
- <xsd:enumeration value="input"/>
- <xsd:enumeration value="memory"/>
- <xsd:enumeration value="output"/>
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="globals" type="cext:CCode"/>
- <xsd:element name="initFunction" type="cext:CCode"/>
- <xsd:element name="cleanUpFunction" type="cext:CCode"/>
- <xsd:element name="retrieveFunction" type="cext:CCode"/>
- <xsd:element name="publishFunction" type="cext:CCode"/>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:complexType name="CCode">
- <xsd:annotation>
- <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
- </xsd:sequence>
- </xsd:complexType>
-</xsd:schema>
--- a/canfestival/NetworkEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/NetworkEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -2,7 +2,7 @@
import wx
from subindextable import EditingPanel
-from networkedit import NetworkEditorTemplate
+from networkeditortemplate import NetworkEditorTemplate
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
[ID_NETWORKEDITOR,
--- a/canfestival/SlaveEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/SlaveEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -2,7 +2,7 @@
import wx
from subindextable import EditingPanel
-from nodeeditor import NodeEditorTemplate
+from nodeeditortemplate import NodeEditorTemplate
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
[ID_SLAVEEDITORCONFNODEMENUNODEINFOS, ID_SLAVEEDITORCONFNODEMENUDS301PROFILE,
--- a/canfestival/canfestival.py Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/canfestival.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,4 +1,4 @@
-import os, sys
+import os, sys, shutil
base_folder = os.path.split(sys.path[0])[0]
CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
@@ -9,12 +9,12 @@
from nodelist import NodeList
from nodemanager import NodeManager
import config_utils, gen_cfile, eds_utils
-from networkedit import networkedit
-from objdictedit import objdictedit
import canfestival_config as local_canfestival_config
from ConfigTreeNode import ConfigTreeNode
from commondialogs import CreateNodeDialog
-
+from subindextable import IECTypeConversion, SizeConversion
+
+from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
from SlaveEditor import SlaveEditor, MasterViewer
from NetworkEditor import NetworkEditor
@@ -26,6 +26,32 @@
AddCatalog(os.path.join(CanFestivalPath, "objdictgen", "locale"))
#--------------------------------------------------
+# Location Tree Helper
+#--------------------------------------------------
+
+def GetSlaveLocationTree(slave_node, current_location, name):
+ entries = []
+ for index, subindex, size, entry_name in slave_node.GetMapVariableList():
+ subentry_infos = slave_node.GetSubentryInfos(index, subindex)
+ typeinfos = slave_node.GetEntryInfos(subentry_infos["type"])
+ if typeinfos:
+ entries.append({
+ "name": "0x%4.4x-0x%2.2x: %s" % (index, subindex, entry_name),
+ "type": LOCATION_VAR_MEMORY,
+ "size": size,
+ "IEC_type": IECTypeConversion.get(typeinfos["name"]),
+ "var_name": "%s_%4.4x_%2.2x" % ("_".join(name.split()), index, subindex),
+ "location": "%s%s"%(SizeConversion[size], ".".join(map(str, current_location +
+ (index, subindex)))),
+ "description": "",
+ "children": []})
+ return {"name": name,
+ "type": LOCATION_CONFNODE,
+ "location": ".".join([str(i) for i in current_location]) + ".x",
+ "children": entries
+ }
+
+#--------------------------------------------------
# SLAVE
#--------------------------------------------------
@@ -128,7 +154,7 @@
def CTNTestModified(self):
return self.ChangesToSave or self.OneFileHasChanged()
- def OnCTNSave(self):
+ def OnCTNSave(self, from_project_path=None):
return self.SaveCurrentInFile(self.GetSlaveODPath())
def SetParamsAttribute(self, path, value):
@@ -139,7 +165,13 @@
self._View.SetBusId(self.GetCurrentLocation())
return result
-
+
+ def GetVariableLocationTree(self):
+ current_location = self.GetCurrentLocation()
+ return GetSlaveLocationTree(self.CurrentNode,
+ self.GetCurrentLocation(),
+ self.BaseParams.getName())
+
def CTNGenerate_C(self, buildpath, locations):
"""
Generate C code
@@ -277,7 +309,23 @@
if refresh_network and self._View is not None:
wx.CallAfter(self._View.RefreshBufferState)
return value, refresh
-
+
+ def GetVariableLocationTree(self):
+ current_location = self.GetCurrentLocation()
+ nodeindexes = self.SlaveNodes.keys()
+ nodeindexes.sort()
+ return {"name": self.BaseParams.getName(),
+ "type": LOCATION_CONFNODE,
+ "location": self.GetFullIEC_Channel(),
+ "children": [GetSlaveLocationTree(self.Manager.GetCurrentNodeCopy(),
+ current_location,
+ _("Local entries"))] +
+ [GetSlaveLocationTree(self.SlaveNodes[nodeid]["Node"],
+ current_location + (nodeid,),
+ self.SlaveNodes[nodeid]["Name"])
+ for nodeid in nodeindexes]
+ }
+
_GeneratedMasterView = None
def _ShowGeneratedMaster(self):
self._OpenView("Generated master")
@@ -330,8 +378,11 @@
def CTNTestModified(self):
return self.ChangesToSave or self.HasChanged()
- def OnCTNSave(self):
+ def OnCTNSave(self, from_project_path=None):
self.SetRoot(self.CTNPath())
+ if from_project_path is not None:
+ shutil.copytree(self.GetEDSFolder(from_project_path),
+ self.GetEDSFolder())
return self.SaveProject() is None
def CTNGenerate_C(self, buildpath, locations):
@@ -412,7 +463,8 @@
if can_driver not in can_drivers :
can_driver = can_drivers[0]
can_drv_ext = self.GetCTRoot().GetBuilder().extension
- can_driver_name = "libcanfestival_" + can_driver + can_drv_ext
+ can_drv_prefix = self.GetCTRoot().GetBuilder().dlopen_prefix
+ can_driver_name = can_drv_prefix + "libcanfestival_" + can_driver + can_drv_ext
else:
can_driver_name = ""
--- a/canfestival/config_utils.py Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/config_utils.py Wed Jul 31 10:45:07 2013 +0900
@@ -118,23 +118,24 @@
@return: a tuple of value and number of parameters to add to DCF
"""
+ dcfdata=[]
# Create entry for RPDO or TPDO parameters and Disable PDO
- dcfdata = LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)
- # Set Transmit type synchrone
- dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)
- # Re-Enable PDO
- # ---- INDEX ----- --- SUBINDEX ---- ----- SIZE ------ ------ DATA ------
- dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)
- nbparams = 3
+ # ---- INDEX ----- --- SUBINDEX ---- ----- SIZE ------ ------ DATA ------
+ dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)]
+ # Set Transmit type
+ dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)]
if len(pdomapping) > 0:
- dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)
- nbparams += 1
+ # Disable Mapping
+ dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(0x00, 1)]
# Map Variables
for subindex, (name, loc_infos) in enumerate(pdomapping):
value = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8) + loc_infos["size"]
- dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)
- nbparams += 1
- return dcfdata, nbparams
+ dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)]
+ # Re-enable Mapping
+ dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)]
+ # Re-Enable PDO
+ dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)]
+ return "".join(dcfdata), len(dcfdata)
class ConciseDCFGenerator:
--- a/connectors/PYRO/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/connectors/PYRO/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -45,6 +45,7 @@
from util.Zeroconf import Zeroconf
r = Zeroconf()
i=r.getServiceInfo(service_type, location)
+ if i is None : raise Exception, "'%s' not found"%location
ip = str(socket.inet_ntoa(i.getAddress()))
port = str(i.getPort())
newlocation = ip+':'+port
@@ -72,18 +73,18 @@
def catcher_func(*args,**kwargs):
try:
return func(*args,**kwargs)
- except Pyro.errors.ProtocolError, e:
- pass
except Pyro.errors.ConnectionClosedError, e:
confnodesroot.logger.write_error("Connection lost!\n")
- confnodesroot._connector = None
+ confnodesroot._SetConnector(None)
+ except Pyro.errors.ProtocolError, e:
+ confnodesroot.logger.write_error("Pyro exception: "+str(e)+"\n")
except Exception,e:
#confnodesroot.logger.write_error(traceback.format_exc())
errmess = ''.join(Pyro.util.getPyroTraceback(e))
confnodesroot.logger.write_error(errmess+"\n")
print errmess
- confnodesroot._connector = None
- return default
+ confnodesroot._SetConnector(None)
+ return default
return catcher_func
# Check connection is effective.
--- a/controls/CustomGrid.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/CustomGrid.py Wed Jul 31 10:45:07 2013 +0900
@@ -93,7 +93,7 @@
self.Table.InsertRow(new_row, self.DefaultValue.copy())
self.Table.ResetView(self)
if new_row is not None:
- self.SetSelectedRow(new_row)
+ self.SetSelectedCell(new_row, col)
def DeleteRow(self):
self.CloseEditControl()
@@ -105,7 +105,8 @@
else:
self.Table.RemoveRow(row)
self.Table.ResetView(self)
- self.SetSelectedRow(min(row, self.Table.GetNumberRows() - 1))
+ if self.Table.GetNumberRows() > 0:
+ self.SetSelectedCell(min(row, self.Table.GetNumberRows() - 1), col)
def MoveRow(self, row, move):
self.CloseEditControl()
@@ -117,10 +118,9 @@
if new_row != row:
self.Table.ResetView(self)
if new_row != row:
- self.SetSelectedRow(new_row)
+ self.SetSelectedCell(new_row, col)
- def SetSelectedRow(self, row):
- col = self.GetGridCursorCol()
+ def SetSelectedCell(self, row, col):
self.SetGridCursor(row, col)
self.MakeCellVisible(row, col)
self.RefreshButtons()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomStyledTextCtrl.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import wx
+import wx.stc
+
+if wx.Platform == '__WXMSW__':
+ faces = { 'times': 'Times New Roman',
+ 'mono' : 'Courier New',
+ 'helv' : 'Arial',
+ 'other': 'Comic Sans MS',
+ 'size' : 10,
+ }
+else:
+ faces = { 'times': 'Times',
+ 'mono' : 'Courier',
+ 'helv' : 'Helvetica',
+ 'other': 'new century schoolbook',
+ 'size' : 12,
+ }
+
+NAVIGATION_KEYS = [
+ wx.WXK_END,
+ wx.WXK_HOME,
+ wx.WXK_LEFT,
+ wx.WXK_UP,
+ wx.WXK_RIGHT,
+ wx.WXK_DOWN,
+ wx.WXK_PAGEUP,
+ wx.WXK_PAGEDOWN,
+ wx.WXK_NUMPAD_HOME,
+ wx.WXK_NUMPAD_LEFT,
+ wx.WXK_NUMPAD_UP,
+ wx.WXK_NUMPAD_RIGHT,
+ wx.WXK_NUMPAD_DOWN,
+ wx.WXK_NUMPAD_PAGEUP,
+ wx.WXK_NUMPAD_PAGEDOWN,
+ wx.WXK_NUMPAD_END]
+
+def GetCursorPos(old, new):
+ if old == "":
+ return 0
+ old_length = len(old)
+ new_length = len(new)
+ common_length = min(old_length, new_length)
+ i = 0
+ for i in xrange(common_length):
+ if old[i] != new[i]:
+ break
+ if old_length < new_length:
+ if common_length > 0 and old[i] != new[i]:
+ return i + new_length - old_length
+ else:
+ return i + new_length - old_length + 1
+ elif old_length > new_length or i < min(old_length, new_length) - 1:
+ if common_length > 0 and old[i] != new[i]:
+ return i
+ else:
+ return i + 1
+ else:
+ return None
+
+class CustomStyledTextCtrl(wx.stc.StyledTextCtrl):
+
+ def __init__(self, *args, **kwargs):
+ wx.stc.StyledTextCtrl.__init__(self, *args, **kwargs)
+
+ self.Bind(wx.EVT_MOTION, self.OnMotion)
+
+ def OnMotion(self, event):
+ if wx.Platform == '__WXMSW__':
+ if not event.Dragging():
+ x, y = event.GetPosition()
+ margin_width = reduce(
+ lambda x, y: x + y,
+ [self.GetMarginWidth(i)
+ for i in xrange(3)],
+ 0)
+ if x <= margin_width:
+ self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+ else:
+ self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
+ else:
+ event.Skip()
+ else:
+ event.Skip()
+
+ def AppendText(self, text):
+ self.GotoPos(self.GetLength())
+ self.AddText(text)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomToolTip.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from controls.CustomStyledTextCtrl import faces
+
+TOOLTIP_MAX_CHARACTERS = 30 # Maximum number of characters by line in ToolTip
+TOOLTIP_MAX_LINE = 5 # Maximum number of line in ToolTip
+TOOLTIP_WAIT_PERIOD = 0.5 # Wait period before displaying tooltip in second
+
+#-------------------------------------------------------------------------------
+# Custom ToolTip
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom tool tip
+"""
+
+class CustomToolTip(wx.PopupWindow):
+
+ def __init__(self, parent, tip, restricted=True):
+ """
+ Constructor
+ @param parent: Parent window
+ @param tip: Tip text (may be multiline)
+ @param restricted: Tool tip must follow size restriction in line and
+ characters number defined (default True)
+ """
+ wx.PopupWindow.__init__(self, parent)
+
+ self.Restricted = restricted
+
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ self.SetTip(tip)
+
+ # Initialize text font style
+ self.Font = wx.Font(
+ faces["size"],
+ wx.SWISS,
+ wx.NORMAL,
+ wx.NORMAL,
+ faceName = faces["mono"])
+
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+
+ def SetFont(self, font):
+ """
+ Set tool tip text font style
+ @param font: wx.Font object containing font style
+ """
+ self.Font = font
+ self.RefreshTip()
+
+ def SetTip(self, tip):
+ """
+ Set tool tip text
+ @param tip: Tool tip text
+ """
+ if self.Restricted:
+ # Compute tip text line return
+ self.Tip = []
+ for line in tip.splitlines():
+ if line != "":
+ words = line.split()
+ new_line = words[0]
+ for word in words[1:]:
+ # Add word to line
+ if len(new_line + " " + word) <= \
+ TOOLTIP_MAX_CHARACTERS:
+ new_line += " " + word
+ # Create new line
+ else:
+ self.Tip.append(new_line)
+ new_line = word
+ self.Tip.append(new_line)
+ else:
+ self.Tip.append(line)
+
+ # Restrict number of lines
+ if len(self.Tip) > TOOLTIP_MAX_LINE:
+ self.Tip = self.Tip[:TOOLTIP_MAX_LINE]
+
+ # Add ... to the end of last line to indicate that tool tip
+ # text is too long
+ if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
+ self.Tip[-1] += "..."
+ else:
+ self.Tip[-1] = self.Tip[-1]\
+ [:TOOLTIP_MAX_CHARACTERS - 3] + "..."
+ else:
+ self.Tip = tip.splitlines()
+
+ # Prevent to call wx method in non-wx threads
+ wx.CallAfter(self.RefreshTip)
+
+ def SetToolTipPosition(self, pos):
+ """
+ Set tool tip position
+ @param pos: New tool tip position
+ """
+ # Get screen size to prevent tool tip to go out of the screen
+ screen_width, screen_height = wx.GetDisplaySize()
+
+ # Calculate position of tool tip to stay in screen limits
+ tip_width, tip_height = self.GetToolTipSize()
+ self.SetPosition(wx.Point(
+ max(0, min(pos.x, screen_width - tip_width)),
+ max(0, min(pos.y, screen_height - tip_height))))
+
+ def GetToolTipSize(self):
+ """
+ Get tool tip size according to tip text and restriction
+ @return: wx.Size(tool_tip_width, tool_tip_height)
+ """
+ max_width = max_height = 0
+
+ # Create a memory DC for calculating text extent
+ dc = wx.MemoryDC()
+ dc.SetFont(self.Font)
+
+ # Compute max tip text size
+ for line in self.Tip:
+ w, h = dc.GetTextExtent(line)
+ max_width = max(max_width, w)
+ max_height += h
+
+ return wx.Size(max_width + 4, max_height + 4)
+
+ def RefreshTip(self):
+ """
+ Refresh tip on screen
+ """
+ # Prevent to call this function if tool tip destroyed
+ if self:
+ # Refresh tool tip size and position
+ self.SetClientSize(self.GetToolTipSize())
+
+ # Redraw tool tip
+ self.Refresh()
+
+ def OnPaint(self, event):
+ """
+ Callback for Paint Event
+ @param event: Paint event
+ """
+ # Get buffered paint DC for tool tip
+ dc = wx.AutoBufferedPaintDC(self)
+ dc.Clear()
+
+ # Set DC drawing style
+ dc.SetPen(wx.BLACK_PEN)
+ dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
+ dc.SetFont(self.Font)
+
+ # Draw Tool tip
+ dc.BeginDrawing()
+ tip_width, tip_height = self.GetToolTipSize()
+
+ # Draw background rectangle
+ dc.DrawRectangle(0, 0, tip_width, tip_height)
+
+ # Draw tool tip text
+ line_offset = 0
+ for line in self.Tip:
+ dc.DrawText(line, 2, line_offset + 2)
+ line_width, line_height = dc.GetTextExtent(line)
+ line_offset += line_height
+
+ dc.EndDrawing()
+
+ event.Skip()
--- a/controls/CustomTree.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/CustomTree.py Wed Jul 31 10:45:07 2013 +0900
@@ -16,12 +16,30 @@
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import wx
+import wx.lib.agw.customtreectrl as CT
-class CustomTree(wx.TreeCtrl):
+from util.BitmapLibrary import GetBitmap
+
+# Customize CustomTreeItem for adding icon on item left
+CT.GenericTreeItem._ExtraImage = None
+
+def SetExtraImage(self, image):
+ self._type = (1 if image is not None else 0)
+ self._ExtraImage = image
+
+CT.GenericTreeItem.SetExtraImage = SetExtraImage
+
+_DefaultGetCurrentCheckedImage = CT.GenericTreeItem.GetCurrentCheckedImage
+def GetCurrentCheckedImage(self):
+ if self._ExtraImage is not None:
+ return self._ExtraImage
+ return _DefaultGetCurrentCheckedImage(self)
+CT.GenericTreeItem.GetCurrentCheckedImage = GetCurrentCheckedImage
+
+class CustomTree(CT.CustomTreeCtrl):
def __init__(self, *args, **kwargs):
- wx.TreeCtrl.__init__(self, *args, **kwargs)
- self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ CT.CustomTreeCtrl.__init__(self, *args, **kwargs)
self.BackgroundBitmap = None
self.BackgroundAlign = wx.ALIGN_LEFT|wx.ALIGN_TOP
@@ -29,18 +47,30 @@
self.AddMenu = None
self.Enabled = False
- if wx.Platform == '__WXMSW__':
- self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
- else:
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_SIZE, self.OnResize)
- self.Bind(wx.EVT_SCROLL, self.OnScroll)
+ self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
def SetBackgroundBitmap(self, bitmap, align):
self.BackgroundBitmap = bitmap
self.BackgroundAlign = align
+ def SetImageListCheck(self, sizex, sizey, imglist=None):
+ CT.CustomTreeCtrl.SetImageListCheck(self, sizex, sizey, imglist=None)
+
+ self.ExtraImages = {}
+ for image in ["function", "functionBlock", "program"]:
+ self.ExtraImages[image] = self._imageListCheck.Add(GetBitmap(image.upper()))
+
+ def SetItemExtraImage(self, item, bitmap):
+ dc = wx.ClientDC(self)
+ image = self.ExtraImages.get(bitmap)
+ if image is not None:
+ item.SetExtraImage(image)
+ else:
+ item.SetExtraImage(None)
+ self.CalculateSize(item, dc)
+ self.RefreshLine(item)
+
def SetAddMenu(self, add_menu):
self.AddMenu = add_menu
@@ -67,19 +97,6 @@
return wx.Rect(x, y, bitmap_size[0], bitmap_size[1])
- def RefreshBackground(self, refresh_base=False):
- dc = wx.ClientDC(self)
- dc.Clear()
-
- bitmap_rect = self.GetBitmapRect()
- dc.DrawBitmap(self.BackgroundBitmap, bitmap_rect.x, bitmap_rect.y)
-
- if refresh_base:
- self.Refresh(False)
-
- def OnEraseBackground(self, event):
- self.RefreshBackground(True)
-
def OnLeftUp(self, event):
if self.Enabled:
pos = event.GetPosition()
@@ -88,17 +105,26 @@
bitmap_rect = self.GetBitmapRect()
if (bitmap_rect.InsideXY(pos.x, pos.y) or
flags & wx.TREE_HITTEST_NOWHERE) and self.AddMenu is not None:
- self.PopupMenuXY(self.AddMenu, pos.x, pos.y)
+ wx.CallAfter(self.PopupMenuXY, self.AddMenu, pos.x, pos.y)
+ event.Skip()
+
+ def OnEraseBackground(self, event):
+ dc = event.GetDC()
+
+ if not dc:
+ dc = wx.ClientDC(self)
+ rect = self.GetUpdateRegion().GetBox()
+ dc.SetClippingRect(rect)
+
+ dc.Clear()
+
+ bitmap_rect = self.GetBitmapRect()
+ dc.DrawBitmap(self.BackgroundBitmap, bitmap_rect.x, bitmap_rect.y)
+
+ def OnScroll(self, event):
+ wx.CallAfter(self.Refresh)
event.Skip()
- def OnScroll(self, event):
- self.RefreshBackground(True)
- event.Skip()
-
- def OnResize(self, event):
- self.RefreshBackground(True)
- event.Skip()
-
- def OnPaint(self, event):
- self.RefreshBackground()
- event.Skip()
\ No newline at end of file
+ def OnSize(self, event):
+ CT.CustomTreeCtrl.OnSize(self, event)
+ self.Refresh()
--- a/controls/DebugVariablePanel.py Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,2261 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard.
-#
-#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-from types import TupleType, ListType, FloatType
-from time import time as gettime
-import math
-import numpy
-import binascii
-
-import wx
-import wx.lib.buttons
-
-try:
- import matplotlib
- matplotlib.use('WX')
- import matplotlib.pyplot
- from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
- from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
- from matplotlib.backends.backend_agg import FigureCanvasAgg
- from mpl_toolkits.mplot3d import Axes3D
- color_cycle = ['r', 'b', 'g', 'm', 'y', 'k']
- cursor_color = '#800080'
- USE_MPL = True
-except:
- USE_MPL = False
-
-from graphics import DebugDataConsumer, DebugViewer, REFRESH_PERIOD
-from controls import CustomGrid, CustomTable
-from dialogs.ForceVariableDialog import ForceVariableDialog
-from util.BitmapLibrary import GetBitmap
-
-def AppendMenu(parent, help, id, kind, text):
- parent.Append(help=help, id=id, kind=kind, text=text)
-
-def GetDebugVariablesTableColnames():
- _ = lambda x : x
- return [_("Variable"), _("Value")]
-
-CRC_SIZE = 8
-CRC_MASK = 2 ** CRC_SIZE - 1
-
-class VariableTableItem(DebugDataConsumer):
-
- def __init__(self, parent, variable):
- DebugDataConsumer.__init__(self)
- self.Parent = parent
- self.Variable = variable
- self.RefreshVariableType()
- self.Value = ""
-
- def __del__(self):
- self.Parent = None
-
- def SetVariable(self, variable):
- if self.Parent and self.Variable != variable:
- self.Variable = variable
- self.RefreshVariableType()
- self.Parent.RefreshView()
-
- def GetVariable(self, mask=None):
- variable = self.Variable
- if mask is not None:
- parts = variable.split('.')
- mask = mask + ['*'] * max(0, len(parts) - len(mask))
- last = None
- variable = ""
- for m, v in zip(mask, parts):
- if m == '*':
- if last == '*':
- variable += '.'
- variable += v
- elif last is None or last == '*':
- variable += '..'
- last = m
- return variable
-
- def RefreshVariableType(self):
- self.VariableType = self.Parent.GetDataType(self.Variable)
- if USE_MPL:
- self.ResetData()
-
- def GetVariableType(self):
- return self.VariableType
-
- def GetData(self, start_tick=None, end_tick=None):
- if USE_MPL and self.IsNumVariable():
- if len(self.Data) == 0:
- return self.Data
-
- start_idx = end_idx = None
- if start_tick is not None:
- start_idx = self.GetNearestData(start_tick, -1)
- if end_tick is not None:
- end_idx = self.GetNearestData(end_tick, 1)
- if start_idx is None:
- start_idx = 0
- if end_idx is not None:
- return self.Data[start_idx:end_idx + 1]
- else:
- return self.Data[start_idx:]
-
- return None
-
- def GetRawValue(self, idx):
- if self.VariableType in ["STRING", "WSTRING"] and idx < len(self.RawData):
- return self.RawData[idx][0]
- return ""
-
- def GetRange(self):
- return self.MinValue, self.MaxValue
-
- def ResetData(self):
- if self.IsNumVariable():
- self.Data = numpy.array([]).reshape(0, 3)
- if self.VariableType in ["STRING", "WSTRING"]:
- self.RawData = []
- self.MinValue = None
- self.MaxValue = None
- else:
- self.Data = None
-
- def IsNumVariable(self):
- return (self.Parent.IsNumType(self.VariableType) or
- self.VariableType in ["STRING", "WSTRING"])
-
- def NewValue(self, tick, value, forced=False):
- if USE_MPL and self.IsNumVariable():
- if self.VariableType in ["STRING", "WSTRING"]:
- num_value = binascii.crc32(value) & CRC_MASK
- else:
- num_value = float(value)
- if self.MinValue is None:
- self.MinValue = num_value
- else:
- self.MinValue = min(self.MinValue, num_value)
- if self.MaxValue is None:
- self.MaxValue = num_value
- else:
- self.MaxValue = max(self.MaxValue, num_value)
- forced_value = float(forced)
- if self.VariableType in ["STRING", "WSTRING"]:
- raw_data = (value, forced_value)
- if len(self.RawData) == 0 or self.RawData[-1] != raw_data:
- extra_value = len(self.RawData)
- self.RawData.append(raw_data)
- else:
- extra_value = len(self.RawData) - 1
- else:
- extra_value = forced_value
- self.Data = numpy.append(self.Data, [[float(tick), num_value, extra_value]], axis=0)
- self.Parent.HasNewData = True
- DebugDataConsumer.NewValue(self, tick, value, forced)
-
- def SetForced(self, forced):
- if self.Forced != forced:
- self.Forced = forced
- self.Parent.HasNewData = True
-
- def SetValue(self, value):
- if (self.VariableType == "STRING" and value.startswith("'") and value.endswith("'") or
- self.VariableType == "WSTRING" and value.startswith('"') and value.endswith('"')):
- value = value[1:-1]
- if self.Value != value:
- self.Value = value
- self.Parent.HasNewData = True
-
- def GetValue(self, tick=None, raw=False):
- if tick is not None and self.IsNumVariable():
- if len(self.Data) > 0:
- idx = numpy.argmin(abs(self.Data[:, 0] - tick))
- if self.VariableType in ["STRING", "WSTRING"]:
- value, forced = self.RawData[int(self.Data[idx, 2])]
- if not raw:
- if self.VariableType == "STRING":
- value = "'%s'" % value
- else:
- value = '"%s"' % value
- return value, forced
- else:
- value = self.Data[idx, 1]
- if not raw and isinstance(value, FloatType):
- value = "%.6g" % value
- return value, self.Data[idx, 2]
- return self.Value, self.IsForced()
- elif not raw:
- if self.VariableType == "STRING":
- return "'%s'" % self.Value
- elif self.VariableType == "WSTRING":
- return '"%s"' % self.Value
- elif isinstance(self.Value, FloatType):
- return "%.6g" % self.Value
- return self.Value
-
- def GetNearestData(self, tick, adjust):
- if USE_MPL and self.IsNumVariable():
- ticks = self.Data[:, 0]
- new_cursor = numpy.argmin(abs(ticks - tick))
- if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0:
- new_cursor -= 1
- elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(ticks):
- new_cursor += 1
- return new_cursor
- return None
-
-class DebugVariableTable(CustomTable):
-
- def GetValue(self, row, col):
- if row < self.GetNumberRows():
- return self.GetValueByName(row, self.GetColLabelValue(col, False))
- return ""
-
- def SetValue(self, row, col, value):
- if col < len(self.colnames):
- self.SetValueByName(row, self.GetColLabelValue(col, False), value)
-
- def GetValueByName(self, row, colname):
- if row < self.GetNumberRows():
- if colname == "Variable":
- return self.data[row].GetVariable()
- elif colname == "Value":
- return self.data[row].GetValue()
- return ""
-
- def SetValueByName(self, row, colname, value):
- if row < self.GetNumberRows():
- if colname == "Variable":
- self.data[row].SetVariable(value)
- elif colname == "Value":
- self.data[row].SetValue(value)
-
- def IsForced(self, row):
- if row < self.GetNumberRows():
- return self.data[row].IsForced()
- return False
-
- def IsNumVariable(self, row):
- if row < self.GetNumberRows():
- return self.data[row].IsNumVariable()
- return False
-
- def _updateColAttrs(self, grid):
- """
- wx.grid.Grid -> update the column attributes to add the
- appropriate renderer given the column name.
-
- Otherwise default to the default renderer.
- """
-
- for row in range(self.GetNumberRows()):
- for col in range(self.GetNumberCols()):
- colname = self.GetColLabelValue(col, False)
- if colname == "Value":
- if self.IsForced(row):
- grid.SetCellTextColour(row, col, wx.BLUE)
- else:
- grid.SetCellTextColour(row, col, wx.BLACK)
- grid.SetReadOnly(row, col, True)
- self.ResizeRow(grid, row)
-
- def AppendItem(self, data):
- self.data.append(data)
-
- def InsertItem(self, idx, data):
- self.data.insert(idx, data)
-
- def RemoveItem(self, idx):
- self.data.pop(idx)
-
- def MoveItem(self, idx, new_idx):
- self.data.insert(new_idx, self.data.pop(idx))
-
- def GetItem(self, idx):
- return self.data[idx]
-
-class DebugVariableDropTarget(wx.TextDropTarget):
-
- def __init__(self, parent, control=None):
- wx.TextDropTarget.__init__(self)
- self.ParentWindow = parent
- self.ParentControl = control
-
- def __del__(self):
- self.ParentWindow = None
- self.ParentControl = None
-
- def OnDragOver(self, x, y, d):
- if self.ParentControl is not None:
- self.ParentControl.OnMouseDragging(x, y)
- else:
- self.ParentWindow.RefreshHighlight(x, y)
- return wx.TextDropTarget.OnDragOver(self, x, y, d)
-
- def OnDropText(self, x, y, data):
- message = None
- try:
- values = eval(data)
- except:
- message = _("Invalid value \"%s\" for debug variable")%data
- values = None
- if not isinstance(values, TupleType):
- message = _("Invalid value \"%s\" for debug variable")%data
- values = None
-
- if message is not None:
- wx.CallAfter(self.ShowMessage, message)
- elif values is not None and values[1] == "debug":
- if isinstance(self.ParentControl, CustomGrid):
- x, y = self.ParentControl.CalcUnscrolledPosition(x, y)
- row = self.ParentControl.YToRow(y - self.ParentControl.GetColLabelSize())
- if row == wx.NOT_FOUND:
- row = self.ParentWindow.Table.GetNumberRows()
- self.ParentWindow.InsertValue(values[0], row, force=True)
- elif self.ParentControl is not None:
- width, height = self.ParentControl.GetSize()
- target_idx = self.ParentControl.GetIndex()
- merge_type = GRAPH_PARALLEL
- if isinstance(self.ParentControl, DebugVariableGraphic):
- if self.ParentControl.Is3DCanvas():
- if y > height / 2:
- target_idx += 1
- if len(values) > 1 and values[2] == "move":
- self.ParentWindow.MoveValue(values[0], target_idx)
- else:
- self.ParentWindow.InsertValue(values[0], target_idx, force=True)
-
- else:
- rect = self.ParentControl.GetAxesBoundingBox()
- if rect.InsideXY(x, y):
- merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
- if merge_rect.InsideXY(x, y):
- merge_type = GRAPH_ORTHOGONAL
- wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
- else:
- if y > height / 2:
- target_idx += 1
- if len(values) > 2 and values[2] == "move":
- self.ParentWindow.MoveValue(values[0], target_idx)
- else:
- self.ParentWindow.InsertValue(values[0], target_idx, force=True)
- else:
- if y > height / 2:
- target_idx += 1
- if len(values) > 2 and values[2] == "move":
- self.ParentWindow.MoveValue(values[0], target_idx)
- else:
- self.ParentWindow.InsertValue(values[0], target_idx, force=True)
-
- elif len(values) > 2 and values[2] == "move":
- self.ParentWindow.MoveValue(values[0])
- else:
- self.ParentWindow.InsertValue(values[0], force=True)
-
- def OnLeave(self):
- self.ParentWindow.ResetHighlight()
- return wx.TextDropTarget.OnLeave(self)
-
- def ShowMessage(self, message):
- dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
- dialog.ShowModal()
- dialog.Destroy()
-
-if USE_MPL:
- MILLISECOND = 1000000
- SECOND = 1000 * MILLISECOND
- MINUTE = 60 * SECOND
- HOUR = 60 * MINUTE
- DAY = 24 * HOUR
-
- ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
- RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
- TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
- [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
- [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
-
- GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
-
- SCROLLBAR_UNIT = 10
-
- #CANVAS_HIGHLIGHT_TYPES
- [HIGHLIGHT_NONE,
- HIGHLIGHT_BEFORE,
- HIGHLIGHT_AFTER,
- HIGHLIGHT_LEFT,
- HIGHLIGHT_RIGHT,
- HIGHLIGHT_RESIZE] = range(6)
-
- HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
- HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
- HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
- HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
-
- #CANVAS_SIZE_TYPES
- [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
-
- DEFAULT_CANVAS_HEIGHT = 200.
- CANVAS_BORDER = (20., 10.)
- CANVAS_PADDING = 8.5
- VALUE_LABEL_HEIGHT = 17.
- AXES_LABEL_HEIGHT = 12.75
-
- def compute_mask(x, y):
- mask = []
- for xp, yp in zip(x, y):
- if xp == yp:
- mask.append(xp)
- else:
- mask.append("*")
- return mask
-
- def NextTick(variables):
- next_tick = None
- for item, data in variables:
- if len(data) > 0:
- if next_tick is None:
- next_tick = data[0][0]
- else:
- next_tick = min(next_tick, data[0][0])
- return next_tick
-
- def OrthogonalData(item, start_tick, end_tick):
- data = item.GetData(start_tick, end_tick)
- min_value, max_value = item.GetRange()
- if min_value is not None and max_value is not None:
- center = (min_value + max_value) / 2.
- range = max(1.0, max_value - min_value)
- else:
- center = 0.5
- range = 1.0
- return data, center - range * 0.55, center + range * 0.55
-
- class GraphButton():
-
- def __init__(self, x, y, bitmap, callback):
- self.Position = wx.Point(x, y)
- self.Bitmap = bitmap
- self.Shown = True
- self.Enabled = True
- self.Callback = callback
-
- def __del__(self):
- self.callback = None
-
- def GetSize(self):
- return self.Bitmap.GetSize()
-
- def SetPosition(self, x, y):
- self.Position = wx.Point(x, y)
-
- def Show(self):
- self.Shown = True
-
- def Hide(self):
- self.Shown = False
-
- def IsShown(self):
- return self.Shown
-
- def Enable(self):
- self.Enabled = True
-
- def Disable(self):
- self.Enabled = False
-
- def IsEnabled(self):
- return self.Enabled
-
- def HitTest(self, x, y):
- if self.Shown and self.Enabled:
- w, h = self.Bitmap.GetSize()
- rect = wx.Rect(self.Position.x, self.Position.y, w, h)
- if rect.InsideXY(x, y):
- return True
- return False
-
- def ProcessCallback(self):
- if self.Callback is not None:
- wx.CallAfter(self.Callback)
-
- def Draw(self, dc):
- if self.Shown and self.Enabled:
- dc.DrawBitmap(self.Bitmap, self.Position.x, self.Position.y, True)
-
- class DebugVariableViewer:
-
- def __init__(self, window, items=[]):
- self.ParentWindow = window
- self.Items = items
-
- self.Highlight = HIGHLIGHT_NONE
- self.Buttons = []
-
- def __del__(self):
- self.ParentWindow = None
-
- def GetIndex(self):
- return self.ParentWindow.GetViewerIndex(self)
-
- def GetItem(self, variable):
- for item in self.Items:
- if item.GetVariable() == variable:
- return item
- return None
-
- def GetItems(self):
- return self.Items
-
- def GetVariables(self):
- if len(self.Items) > 1:
- variables = [item.GetVariable() for item in self.Items]
- if self.GraphType == GRAPH_ORTHOGONAL:
- return tuple(variables)
- return variables
- return self.Items[0].GetVariable()
-
- def AddItem(self, item):
- self.Items.append(item)
-
- def RemoveItem(self, item):
- if item in self.Items:
- self.Items.remove(item)
-
- def Clear(self):
- for item in self.Items:
- self.ParentWindow.RemoveDataConsumer(item)
- self.Items = []
-
- def IsEmpty(self):
- return len(self.Items) == 0
-
- def UnregisterObsoleteData(self):
- for item in self.Items[:]:
- iec_path = item.GetVariable().upper()
- if self.ParentWindow.GetDataType(iec_path) is None:
- self.ParentWindow.RemoveDataConsumer(item)
- self.RemoveItem(item)
- else:
- self.ParentWindow.AddDataConsumer(iec_path, item)
- item.RefreshVariableType()
-
- def ResetData(self):
- for item in self.Items:
- item.ResetData()
-
- def RefreshViewer(self):
- pass
-
- def SetHighlight(self, highlight):
- if self.Highlight != highlight:
- self.Highlight = highlight
- return True
- return False
-
- def GetButtons(self):
- return self.Buttons
-
- def HandleButtons(self, x, y):
- for button in self.GetButtons():
- if button.HitTest(x, y):
- button.ProcessCallback()
- return True
- return False
-
- def IsOverButton(self, x, y):
- for button in self.GetButtons():
- if button.HitTest(x, y):
- return True
- return False
-
- def ShowButtons(self, show):
- for button in self.Buttons:
- if show:
- button.Show()
- else:
- button.Hide()
- self.RefreshButtonsState()
- self.ParentWindow.ForceRefresh()
-
- def RefreshButtonsState(self, refresh_positions=False):
- if self:
- width, height = self.GetSize()
- if refresh_positions:
- offset = 0
- buttons = self.Buttons[:]
- buttons.reverse()
- for button in buttons:
- if button.IsShown() and button.IsEnabled():
- w, h = button.GetSize()
- button.SetPosition(width - 5 - w - offset, 5)
- offset += w + 2
- self.ParentWindow.ForceRefresh()
-
- def DrawCommonElements(self, dc, buttons=None):
- width, height = self.GetSize()
-
- dc.SetPen(HIGHLIGHT_DROP_PEN)
- dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
- if self.Highlight in [HIGHLIGHT_BEFORE]:
- dc.DrawLine(0, 1, width - 1, 1)
- elif self.Highlight in [HIGHLIGHT_AFTER]:
- dc.DrawLine(0, height - 1, width - 1, height - 1)
-
- if buttons is None:
- buttons = self.Buttons
- for button in buttons:
- button.Draw(dc)
-
- if self.ParentWindow.IsDragging():
- destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
- srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
- if destBBox.width > 0 and destBBox.height > 0:
- srcPanel = self.ParentWindow.DraggingAxesPanel
- srcBBox = srcPanel.GetAxesBoundingBox()
-
- if destBBox.x == 0:
- srcX = srcBBox.x - srcPos.x
- else:
- srcX = srcBBox.x
- if destBBox.y == 0:
- srcY = srcBBox.y - srcPos.y
- else:
- srcY = srcBBox.y
-
- srcBmp = _convert_agg_to_wx_bitmap(srcPanel.get_renderer(), None)
- srcDC = wx.MemoryDC()
- srcDC.SelectObject(srcBmp)
-
- dc.Blit(destBBox.x, destBBox.y,
- int(destBBox.width), int(destBBox.height),
- srcDC, srcX, srcY)
-
- def OnEnter(self, event):
- self.ShowButtons(True)
- event.Skip()
-
- def OnLeave(self, event):
- if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
- x, y = event.GetPosition()
- width, height = self.GetSize()
- if (x <= 0 or x >= width - 1 or
- y <= 0 or y >= height - 1):
- self.ShowButtons(False)
- event.Skip()
-
- def OnCloseButton(self):
- wx.CallAfter(self.ParentWindow.DeleteValue, self)
-
- def OnForceButton(self):
- wx.CallAfter(self.ForceValue, self.Items[0])
-
- def OnReleaseButton(self):
- wx.CallAfter(self.ReleaseValue, self.Items[0])
-
- def OnResizeWindow(self, event):
- wx.CallAfter(self.RefreshButtonsState, True)
- event.Skip()
-
- def OnMouseDragging(self, x, y):
- xw, yw = self.GetPosition()
- self.ParentWindow.RefreshHighlight(x + xw, y + yw)
-
- def OnDragging(self, x, y):
- width, height = self.GetSize()
- if y < height / 2:
- if self.ParentWindow.IsViewerFirst(self):
- self.SetHighlight(HIGHLIGHT_BEFORE)
- else:
- self.SetHighlight(HIGHLIGHT_NONE)
- self.ParentWindow.HighlightPreviousViewer(self)
- else:
- self.SetHighlight(HIGHLIGHT_AFTER)
-
- def OnResize(self, event):
- wx.CallAfter(self.RefreshButtonsState, True)
- event.Skip()
-
- def ForceValue(self, item):
- iec_path = item.GetVariable().upper()
- iec_type = self.ParentWindow.GetDataType(iec_path)
- if iec_type is not None:
- dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
- if dialog.ShowModal() == wx.ID_OK:
- self.ParentWindow.ForceDataValue(iec_path, dialog.GetValue())
-
- def ReleaseValue(self, item):
- iec_path = item.GetVariable().upper()
- self.ParentWindow.ReleaseDataValue(iec_path)
-
-
- class DebugVariableText(DebugVariableViewer, wx.Panel):
-
- def __init__(self, parent, window, items=[]):
- DebugVariableViewer.__init__(self, window, items)
-
- wx.Panel.__init__(self, parent)
- self.SetBackgroundColour(wx.WHITE)
- self.SetDropTarget(DebugVariableDropTarget(window, self))
- self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
- self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
- self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
- self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
- self.Bind(wx.EVT_SIZE, self.OnResize)
- self.Bind(wx.EVT_PAINT, self.OnPaint)
-
- self.SetMinSize(wx.Size(0, 25))
-
- self.Buttons.append(
- GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
- self.Buttons.append(
- GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
- self.Buttons.append(
- GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
-
- self.ShowButtons(False)
-
- def RefreshViewer(self):
- width, height = self.GetSize()
- bitmap = wx.EmptyBitmap(width, height)
-
- dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
- dc.Clear()
- dc.BeginDrawing()
-
- gc = wx.GCDC(dc)
-
- item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
- w, h = gc.GetTextExtent(item_name)
- gc.DrawText(item_name, 20, (height - h) / 2)
-
- if self.Items[0].IsForced():
- gc.SetTextForeground(wx.BLUE)
- self.Buttons[0].Disable()
- self.Buttons[1].Enable()
- else:
- self.Buttons[1].Disable()
- self.Buttons[0].Enable()
- self.RefreshButtonsState(True)
-
- item_value = self.Items[0].GetValue()
- w, h = gc.GetTextExtent(item_value)
- gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
-
- self.DrawCommonElements(gc)
-
- gc.EndDrawing()
-
- def OnLeftDown(self, event):
- width, height = self.GetSize()
- item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
- w, h = self.GetTextExtent(item_name)
- x, y = event.GetPosition()
- rect = wx.Rect(20, (height - h) / 2, w, h)
- if rect.InsideXY(x, y):
- data = wx.TextDataObject(str((self.Items[0].GetVariable(), "debug", "move")))
- dragSource = wx.DropSource(self)
- dragSource.SetData(data)
- dragSource.DoDragDrop()
- else:
- event.Skip()
-
- def OnLeftUp(self, event):
- x, y = event.GetPosition()
- wx.CallAfter(self.HandleButtons, x, y)
- event.Skip()
-
- def OnPaint(self, event):
- self.RefreshViewer()
- event.Skip()
-
- class DebugVariableGraphic(DebugVariableViewer, FigureCanvas):
-
- def __init__(self, parent, window, items, graph_type):
- DebugVariableViewer.__init__(self, window, items)
-
- self.CanvasSize = SIZE_MAXI
- self.GraphType = graph_type
- self.CursorTick = None
- self.MouseStartPos = None
- self.StartCursorTick = None
- self.CanvasStartSize = None
- self.ContextualButtons = []
- self.ContextualButtonsItem = None
-
- self.Figure = matplotlib.figure.Figure(facecolor='w')
- self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
-
- FigureCanvas.__init__(self, parent, -1, self.Figure)
- self.SetBackgroundColour(wx.WHITE)
- self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
- self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
- self.Bind(wx.EVT_SIZE, self.OnResize)
-
- self.SetMinSize(wx.Size(200, 200))
- self.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
- self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
- self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
- self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
- self.mpl_connect('scroll_event', self.OnCanvasScroll)
-
- for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
- ["minimize_graph", "middle_graph", "maximize_graph"]):
- self.Buttons.append(GraphButton(0, 0, GetBitmap(bitmap), self.GetOnChangeSizeButton(size)))
- self.Buttons.append(
- GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportGraphButton))
- self.Buttons.append(
- GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
-
- self.ResetGraphics()
- self.RefreshLabelsPosition(200)
- self.ShowButtons(False)
-
- def draw(self, drawDC=None):
- FigureCanvasAgg.draw(self)
-
- self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
- self.bitmap.UseAlpha()
- width, height = self.GetSize()
- bbox = self.GetAxesBoundingBox()
-
- destDC = wx.MemoryDC()
- destDC.SelectObject(self.bitmap)
-
- destGC = wx.GCDC(destDC)
-
- destGC.BeginDrawing()
- if self.Highlight == HIGHLIGHT_RESIZE:
- destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
- destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
- destGC.DrawRectangle(0, height - 5, width, 5)
- else:
- destGC.SetPen(HIGHLIGHT_DROP_PEN)
- destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
- if self.Highlight == HIGHLIGHT_LEFT:
- destGC.DrawRectangle(bbox.x, bbox.y,
- bbox.width / 2, bbox.height)
- elif self.Highlight == HIGHLIGHT_RIGHT:
- destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y,
- bbox.width / 2, bbox.height)
-
- self.DrawCommonElements(destGC, self.Buttons + self.ContextualButtons)
-
- destGC.EndDrawing()
-
- self._isDrawn = True
- self.gui_repaint(drawDC=drawDC)
-
- def GetButtons(self):
- return self.Buttons + self.ContextualButtons
-
- def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
- if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
- self.DismissContextualButtons()
-
- if self.ContextualButtonsItem is None:
- self.ContextualButtonsItem = item
-
- if self.ContextualButtonsItem.IsForced():
- self.ContextualButtons.append(
- GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
- else:
- self.ContextualButtons.append(
- GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
- self.ContextualButtons.append(
- GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportItemGraphButton))
- self.ContextualButtons.append(
- GraphButton(0, 0, GetBitmap("delete_graph"), self.OnRemoveItemButton))
-
- offset = 0
- buttons = self.ContextualButtons[:]
- if style in [wx.TOP, wx.LEFT]:
- buttons.reverse()
- for button in buttons:
- w, h = button.GetSize()
- if style in [wx.LEFT, wx.RIGHT]:
- if style == wx.LEFT:
- x = rect.x - w - offset
- else:
- x = rect.x + rect.width + offset
- y = rect.y + (rect.height - h) / 2
- offset += w
- else:
- x = rect.x + (rect.width - w ) / 2
- if style == wx.TOP:
- y = rect.y - h - offset
- else:
- y = rect.y + rect.height + offset
- offset += h
- button.SetPosition(x, y)
- self.ParentWindow.ForceRefresh()
-
- def DismissContextualButtons(self):
- if self.ContextualButtonsItem is not None:
- self.ContextualButtonsItem = None
- self.ContextualButtons = []
- self.ParentWindow.ForceRefresh()
-
- def IsOverContextualButton(self, x, y):
- for button in self.ContextualButtons:
- if button.HitTest(x, y):
- return True
- return False
-
- def SetMinSize(self, size):
- wx.Window.SetMinSize(self, size)
- wx.CallAfter(self.RefreshButtonsState)
-
- def GetOnChangeSizeButton(self, size):
- def OnChangeSizeButton():
- self.CanvasSize = size
- self.SetCanvasSize(200, self.CanvasSize)
- return OnChangeSizeButton
-
- def OnExportGraphButton(self):
- self.ExportGraph()
-
- def OnForceButton(self):
- wx.CallAfter(self.ForceValue,
- self.ContextualButtonsItem)
- self.DismissContextualButtons()
-
- def OnReleaseButton(self):
- wx.CallAfter(self.ReleaseValue,
- self.ContextualButtonsItem)
- self.DismissContextualButtons()
-
- def OnExportItemGraphButton(self):
- wx.CallAfter(self.ExportGraph,
- self.ContextualButtonsItem)
- self.DismissContextualButtons()
-
- def OnRemoveItemButton(self):
- wx.CallAfter(self.ParentWindow.DeleteValue, self,
- self.ContextualButtonsItem)
- self.DismissContextualButtons()
-
- def RefreshButtonsState(self, refresh_positions=False):
- if self:
- width, height = self.GetSize()
- min_width, min_height = self.GetCanvasMinSize()
- for button, size in zip(self.Buttons,
- [min_height, SIZE_MIDDLE, SIZE_MAXI]):
- if size == height and button.IsEnabled():
- button.Disable()
- refresh_positions = True
- elif not button.IsEnabled():
- button.Enable()
- refresh_positions = True
- if refresh_positions:
- offset = 0
- buttons = self.Buttons[:]
- buttons.reverse()
- for button in buttons:
- if button.IsShown() and button.IsEnabled():
- w, h = button.GetSize()
- button.SetPosition(width - 5 - w - offset, 5)
- offset += w + 2
- self.ParentWindow.ForceRefresh()
-
- def RefreshLabelsPosition(self, height):
- canvas_ratio = 1. / height
- graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height)
-
- self.Figure.subplotpars.update(
- top= 1.0 - CANVAS_BORDER[1] * canvas_ratio,
- bottom= CANVAS_BORDER[0] * canvas_ratio)
-
- if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
- num_item = len(self.Items)
- for idx in xrange(num_item):
- if not self.Is3DCanvas():
- self.AxesLabels[idx].set_position(
- (0.05,
- 1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio))
- self.Labels[idx].set_position(
- (0.95,
- CANVAS_PADDING * graph_ratio +
- (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
- else:
- self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio))
- self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio))
- self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio))
- self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio))
-
- self.Figure.subplots_adjust()
-
- def GetCanvasMinSize(self):
- return wx.Size(200,
- CANVAS_BORDER[0] + CANVAS_BORDER[1] +
- 2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
-
- def SetCanvasSize(self, width, height):
- height = max(height, self.GetCanvasMinSize()[1])
- self.SetMinSize(wx.Size(width, height))
- self.RefreshLabelsPosition(height)
- self.ParentWindow.RefreshGraphicsSizer()
-
- def GetAxesBoundingBox(self, absolute=False):
- width, height = self.GetSize()
- ax, ay, aw, ah = self.figure.gca().get_position().bounds
- bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
- aw * width + 2, ah * height + 1)
- if absolute:
- xw, yw = self.GetPosition()
- bbox.x += xw
- bbox.y += yw
- return bbox
-
- def OnCanvasButtonPressed(self, event):
- width, height = self.GetSize()
- x, y = event.x, height - event.y
- if not self.IsOverButton(x, y):
- if event.inaxes == self.Axes:
- item_idx = None
- for i, t in ([pair for pair in enumerate(self.AxesLabels)] +
- [pair for pair in enumerate(self.Labels)]):
- (x0, y0), (x1, y1) = t.get_window_extent().get_points()
- rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
- if rect.InsideXY(x, y):
- item_idx = i
- break
- if item_idx is not None:
- self.ShowButtons(False)
- self.DismissContextualButtons()
- xw, yw = self.GetPosition()
- self.ParentWindow.StartDragNDrop(self,
- self.Items[item_idx], x + xw, y + yw, x + xw, y + yw)
- elif not self.Is3DCanvas():
- self.MouseStartPos = wx.Point(x, y)
- if event.button == 1 and event.inaxes == self.Axes:
- self.StartCursorTick = self.CursorTick
- self.HandleCursorMove(event)
- elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
- width, height = self.GetSize()
- start_tick, end_tick = self.ParentWindow.GetRange()
- self.StartCursorTick = start_tick
-
- elif event.button == 1 and event.y <= 5:
- self.MouseStartPos = wx.Point(x, y)
- self.CanvasStartSize = self.GetSize()
-
- def OnCanvasButtonReleased(self, event):
- if self.ParentWindow.IsDragging():
- width, height = self.GetSize()
- xw, yw = self.GetPosition()
- self.ParentWindow.StopDragNDrop(
- self.ParentWindow.DraggingAxesPanel.Items[0].GetVariable(),
- xw + event.x,
- yw + height - event.y)
- else:
- self.MouseStartPos = None
- self.StartCursorTick = None
- self.CanvasStartSize = None
- width, height = self.GetSize()
- self.HandleButtons(event.x, height - event.y)
- if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
- self.SetCursor(wx.NullCursor)
- self.ParentWindow.ForceRefresh()
-
- def OnCanvasMotion(self, event):
- width, height = self.GetSize()
- if self.ParentWindow.IsDragging():
- xw, yw = self.GetPosition()
- self.ParentWindow.MoveDragNDrop(
- xw + event.x,
- yw + height - event.y)
- else:
- if not self.Is3DCanvas():
- if event.button == 1 and self.CanvasStartSize is None:
- if event.inaxes == self.Axes:
- if self.MouseStartPos is not None:
- self.HandleCursorMove(event)
- elif self.MouseStartPos is not None and len(self.Items) == 1:
- xw, yw = self.GetPosition()
- self.ParentWindow.SetCursorTick(self.StartCursorTick)
- self.ParentWindow.StartDragNDrop(self,
- self.Items[0],
- event.x + xw, height - event.y + yw,
- self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
- elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
- start_tick, end_tick = self.ParentWindow.GetRange()
- rect = self.GetAxesBoundingBox()
- self.ParentWindow.SetCanvasPosition(
- self.StartCursorTick + (self.MouseStartPos.x - event.x) *
- (end_tick - start_tick) / rect.width)
-
- if event.button == 1 and self.CanvasStartSize is not None:
- width, height = self.GetSize()
- self.SetCanvasSize(width,
- self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
-
- elif event.button in [None, "up", "down"]:
- if self.GraphType == GRAPH_PARALLEL:
- orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
- elif len(self.AxesLabels) > 0:
- orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
- else:
- orientation = [wx.LEFT] * len(self.Labels)
- item_idx = None
- item_style = None
- for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] +
- [pair for pair in enumerate(self.Labels)],
- orientation):
- (x0, y0), (x1, y1) = t.get_window_extent().get_points()
- rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
- if rect.InsideXY(event.x, height - event.y):
- item_idx = i
- item_style = style
- break
- if item_idx is not None:
- self.PopupContextualButtons(self.Items[item_idx], rect, item_style)
- return
- if not self.IsOverContextualButton(event.x, height - event.y):
- self.DismissContextualButtons()
-
- if event.y <= 5:
- if self.SetHighlight(HIGHLIGHT_RESIZE):
- self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
- self.ParentWindow.ForceRefresh()
- else:
- if self.SetHighlight(HIGHLIGHT_NONE):
- self.SetCursor(wx.NullCursor)
- self.ParentWindow.ForceRefresh()
-
- def OnCanvasScroll(self, event):
- if event.inaxes is not None:
- if self.GraphType == GRAPH_ORTHOGONAL:
- start_tick, end_tick = self.ParentWindow.GetRange()
- tick = (start_tick + end_tick) / 2.
- else:
- tick = event.xdata
- self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
-
- def OnDragging(self, x, y):
- width, height = self.GetSize()
- bbox = self.GetAxesBoundingBox()
- if bbox.InsideXY(x, y) and not self.Is3DCanvas():
- rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
- if rect.InsideXY(x, y):
- self.SetHighlight(HIGHLIGHT_LEFT)
- else:
- self.SetHighlight(HIGHLIGHT_RIGHT)
- elif y < height / 2:
- if self.ParentWindow.IsViewerFirst(self):
- self.SetHighlight(HIGHLIGHT_BEFORE)
- else:
- self.SetHighlight(HIGHLIGHT_NONE)
- self.ParentWindow.HighlightPreviousViewer(self)
- else:
- self.SetHighlight(HIGHLIGHT_AFTER)
-
- def OnLeave(self, event):
- if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
- self.SetCursor(wx.NullCursor)
- self.ParentWindow.ForceRefresh()
- DebugVariableViewer.OnLeave(self, event)
-
- def HandleCursorMove(self, event):
- start_tick, end_tick = self.ParentWindow.GetRange()
- cursor_tick = None
- if self.GraphType == GRAPH_ORTHOGONAL:
- x_data = self.Items[0].GetData(start_tick, end_tick)
- y_data = self.Items[1].GetData(start_tick, end_tick)
- if len(x_data) > 0 and len(y_data) > 0:
- length = min(len(x_data), len(y_data))
- d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
- cursor_tick = x_data[numpy.argmin(d), 0]
- else:
- data = self.Items[0].GetData(start_tick, end_tick)
- if len(data) > 0:
- cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
- if cursor_tick is not None:
- self.ParentWindow.SetCursorTick(cursor_tick)
-
- def OnAxesMotion(self, event):
- if self.Is3DCanvas():
- current_time = gettime()
- if current_time - self.LastMotionTime > REFRESH_PERIOD:
- self.LastMotionTime = current_time
- Axes3D._on_move(self.Axes, event)
-
- def ResetGraphics(self):
- self.Figure.clear()
- if self.Is3DCanvas():
- self.Axes = self.Figure.gca(projection='3d')
- self.Axes.set_color_cycle(['b'])
- self.LastMotionTime = gettime()
- setattr(self.Axes, "_on_move", self.OnAxesMotion)
- self.Axes.mouse_init()
- self.Axes.tick_params(axis='z', labelsize='small')
- else:
- self.Axes = self.Figure.gca()
- self.Axes.set_color_cycle(color_cycle)
- self.Axes.tick_params(axis='x', labelsize='small')
- self.Axes.tick_params(axis='y', labelsize='small')
- self.Plots = []
- self.VLine = None
- self.HLine = None
- self.Labels = []
- self.AxesLabels = []
- if not self.Is3DCanvas():
- text_func = self.Axes.text
- else:
- text_func = self.Axes.text2D
- if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
- num_item = len(self.Items)
- for idx in xrange(num_item):
- if num_item == 1:
- color = 'k'
- else:
- color = color_cycle[idx % len(color_cycle)]
- if not self.Is3DCanvas():
- self.AxesLabels.append(
- text_func(0, 0, "", size='small',
- verticalalignment='top',
- color=color,
- transform=self.Axes.transAxes))
- self.Labels.append(
- text_func(0, 0, "", size='large',
- horizontalalignment='right',
- color=color,
- transform=self.Axes.transAxes))
- else:
- self.AxesLabels.append(
- self.Axes.text(0, 0, "", size='small',
- transform=self.Axes.transAxes))
- self.Labels.append(
- self.Axes.text(0, 0, "", size='large',
- horizontalalignment='right',
- transform=self.Axes.transAxes))
- self.AxesLabels.append(
- self.Axes.text(0, 0, "", size='small',
- rotation='vertical',
- verticalalignment='bottom',
- transform=self.Axes.transAxes))
- self.Labels.append(
- self.Axes.text(0, 0, "", size='large',
- rotation='vertical',
- verticalalignment='top',
- transform=self.Axes.transAxes))
- width, height = self.GetSize()
- self.RefreshLabelsPosition(height)
-
- def AddItem(self, item):
- DebugVariableViewer.AddItem(self, item)
- self.ResetGraphics()
-
- def RemoveItem(self, item):
- DebugVariableViewer.RemoveItem(self, item)
- if not self.IsEmpty():
- if len(self.Items) == 1:
- self.GraphType = GRAPH_PARALLEL
- self.ResetGraphics()
-
- def UnregisterObsoleteData(self):
- DebugVariableViewer.UnregisterObsoleteData(self)
- if not self.IsEmpty():
- self.ResetGraphics()
-
- def Is3DCanvas(self):
- return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
-
- def SetCursorTick(self, cursor_tick):
- self.CursorTick = cursor_tick
-
- def RefreshViewer(self, refresh_graphics=True):
-
- if refresh_graphics:
- start_tick, end_tick = self.ParentWindow.GetRange()
-
- if self.GraphType == GRAPH_PARALLEL:
- min_value = max_value = None
-
- for idx, item in enumerate(self.Items):
- data = item.GetData(start_tick, end_tick)
- if data is not None:
- item_min_value, item_max_value = item.GetRange()
- if min_value is None:
- min_value = item_min_value
- elif item_min_value is not None:
- min_value = min(min_value, item_min_value)
- if max_value is None:
- max_value = item_max_value
- elif item_max_value is not None:
- max_value = max(max_value, item_max_value)
-
- if len(self.Plots) <= idx:
- self.Plots.append(
- self.Axes.plot(data[:, 0], data[:, 1])[0])
- else:
- self.Plots[idx].set_data(data[:, 0], data[:, 1])
-
- if min_value is not None and max_value is not None:
- y_center = (min_value + max_value) / 2.
- y_range = max(1.0, max_value - min_value)
- else:
- y_center = 0.5
- y_range = 1.0
- x_min, x_max = start_tick, end_tick
- y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
-
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
- if self.VLine is None:
- self.VLine = self.Axes.axvline(self.CursorTick, color=cursor_color)
- else:
- self.VLine.set_xdata((self.CursorTick, self.CursorTick))
- self.VLine.set_visible(True)
- else:
- if self.VLine is not None:
- self.VLine.set_visible(False)
- else:
- min_start_tick = reduce(max, [item.GetData()[0, 0]
- for item in self.Items
- if len(item.GetData()) > 0], 0)
- start_tick = max(start_tick, min_start_tick)
- end_tick = max(end_tick, min_start_tick)
- x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
- y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
- if self.CursorTick is not None:
- x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True)
- y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True)
- length = 0
- if x_data is not None and y_data is not None:
- length = min(len(x_data), len(y_data))
- if len(self.Items) < 3:
- if x_data is not None and y_data is not None:
- if len(self.Plots) == 0:
- self.Plots.append(
- self.Axes.plot(x_data[:, 1][:length],
- y_data[:, 1][:length])[0])
- else:
- self.Plots[0].set_data(
- x_data[:, 1][:length],
- y_data[:, 1][:length])
-
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
- if self.VLine is None:
- self.VLine = self.Axes.axvline(x_cursor, color=cursor_color)
- else:
- self.VLine.set_xdata((x_cursor, x_cursor))
- if self.HLine is None:
- self.HLine = self.Axes.axhline(y_cursor, color=cursor_color)
- else:
- self.HLine.set_ydata((y_cursor, y_cursor))
- self.VLine.set_visible(True)
- self.HLine.set_visible(True)
- else:
- if self.VLine is not None:
- self.VLine.set_visible(False)
- if self.HLine is not None:
- self.HLine.set_visible(False)
- else:
- while len(self.Axes.lines) > 0:
- self.Axes.lines.pop()
- z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
- if self.CursorTick is not None:
- z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True)
- if x_data is not None and y_data is not None and z_data is not None:
- length = min(length, len(z_data))
- self.Axes.plot(x_data[:, 1][:length],
- y_data[:, 1][:length],
- zs = z_data[:, 1][:length])
- self.Axes.set_zlim(z_min, z_max)
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
- for kwargs in [{"xs": numpy.array([x_min, x_max])},
- {"ys": numpy.array([y_min, y_max])},
- {"zs": numpy.array([z_min, z_max])}]:
- for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
- ("ys", numpy.array([y_cursor, y_cursor])),
- ("zs", numpy.array([z_cursor, z_cursor]))]:
- kwargs.setdefault(param, value)
- kwargs["color"] = cursor_color
- self.Axes.plot(**kwargs)
-
- self.Axes.set_xlim(x_min, x_max)
- self.Axes.set_ylim(y_min, y_max)
-
- variable_name_mask = self.ParentWindow.GetVariableNameMask()
- if self.CursorTick is not None:
- values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
- else:
- values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
- labels = [item.GetVariable(variable_name_mask) for item in self.Items]
- styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
- if self.Is3DCanvas():
- for idx, label_func in enumerate([self.Axes.set_xlabel,
- self.Axes.set_ylabel,
- self.Axes.set_zlabel]):
- label_func(labels[idx], fontdict={'size': 'small','color': color_cycle[idx]})
- else:
- for label, text in zip(self.AxesLabels, labels):
- label.set_text(text)
- for label, value, style in zip(self.Labels, values, styles):
- label.set_text(value)
- label.set_style(style)
-
- self.draw()
-
- def ExportGraph(self, item=None):
- if item is not None:
- variables = [(item, [entry for entry in item.GetData()])]
- else:
- variables = [(item, [entry for entry in item.GetData()])
- for item in self.Items]
- self.ParentWindow.CopyDataToClipboard(variables)
-
-class DebugVariablePanel(wx.Panel, DebugViewer):
-
- def __init__(self, parent, producer, window):
- wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
- self.SetBackgroundColour(wx.WHITE)
-
- self.ParentWindow = window
-
- DebugViewer.__init__(self, producer, True)
-
- self.HasNewData = False
-
- if USE_MPL:
- main_sizer = wx.BoxSizer(wx.VERTICAL)
-
- self.Ticks = numpy.array([])
- self.RangeValues = None
- self.StartTick = 0
- self.Fixed = False
- self.Force = False
- self.CursorTick = None
- self.DraggingAxesPanel = None
- self.DraggingAxesBoundingBox = None
- self.DraggingAxesMousePos = None
- self.VariableNameMask = []
-
- self.GraphicPanels = []
-
- graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
-
- range_label = wx.StaticText(self, label=_('Range:'))
- graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
-
- self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
- self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
- graphics_button_sizer.AddWindow(self.CanvasRange, 1,
- border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
-
- for name, bitmap, help in [
- ("CurrentButton", "current", _("Go to current value")),
- ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
- button = wx.lib.buttons.GenBitmapButton(self,
- bitmap=GetBitmap(bitmap),
- size=wx.Size(28, 28), style=wx.NO_BORDER)
- button.SetToolTipString(help)
- setattr(self, name, button)
- self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
- graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-
- self.CanvasPosition = wx.ScrollBar(self,
- size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
- self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK,
- self.OnPositionChanging, self.CanvasPosition)
- self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP,
- self.OnPositionChanging, self.CanvasPosition)
- self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN,
- self.OnPositionChanging, self.CanvasPosition)
- self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP,
- self.OnPositionChanging, self.CanvasPosition)
- self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN,
- self.OnPositionChanging, self.CanvasPosition)
- main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
-
- self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
-
- self.TickLabel = wx.StaticText(self)
- self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
-
- self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
- self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
-
- self.TickTimeLabel = wx.StaticText(self)
- self.TickSizer.AddWindow(self.TickTimeLabel)
-
- self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
- self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
- self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
- self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
- main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
-
- self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
- self.GraphicsWindow.SetSizer(self.GraphicsSizer)
-
- self.RefreshCanvasRange()
-
- else:
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(1)
-
- button_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(button_sizer, border=5,
- flag=wx.ALIGN_RIGHT|wx.ALL)
-
- for name, bitmap, help in [
- ("DeleteButton", "remove_element", _("Remove debug variable")),
- ("UpButton", "up", _("Move debug variable up")),
- ("DownButton", "down", _("Move debug variable down"))]:
- button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
- size=wx.Size(28, 28), style=wx.NO_BORDER)
- button.SetToolTipString(help)
- setattr(self, name, button)
- button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-
- self.VariablesGrid = CustomGrid(self, size=wx.Size(-1, 150), style=wx.VSCROLL)
- self.VariablesGrid.SetDropTarget(DebugVariableDropTarget(self, self.VariablesGrid))
- self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK,
- self.OnVariablesGridCellRightClick)
- self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
- self.OnVariablesGridCellLeftClick)
- main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
-
- self.Table = DebugVariableTable(self, [], GetDebugVariablesTableColnames())
- self.VariablesGrid.SetTable(self.Table)
- self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
- "Up": self.UpButton,
- "Down": self.DownButton})
-
- def _AddVariable(new_row):
- return self.VariablesGrid.GetGridCursorRow()
- setattr(self.VariablesGrid, "_AddRow", _AddVariable)
-
- def _DeleteVariable(row):
- item = self.Table.GetItem(row)
- self.RemoveDataConsumer(item)
- self.Table.RemoveItem(row)
- self.RefreshView()
- setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
-
- def _MoveVariable(row, move):
- new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
- if new_row != row:
- self.Table.MoveItem(row, new_row)
- self.RefreshView()
- return new_row
- setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
-
- self.VariablesGrid.SetRowLabelSize(0)
-
- self.GridColSizes = [200, 100]
-
- for col in range(self.Table.GetNumberCols()):
- attr = wx.grid.GridCellAttr()
- attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
- self.VariablesGrid.SetColAttr(col, attr)
- self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
-
- self.Table.ResetView(self.VariablesGrid)
- self.VariablesGrid.RefreshButtons()
-
- self.SetSizer(main_sizer)
-
- def SetDataProducer(self, producer):
- DebugViewer.SetDataProducer(self, producer)
-
- if USE_MPL:
- if self.DataProducer is not None:
- self.Ticktime = self.DataProducer.GetTicktime()
- self.RefreshCanvasRange()
- else:
- self.Ticktime = 0
-
- def RefreshNewData(self, *args, **kwargs):
- if self.HasNewData or self.Force:
- self.HasNewData = False
- self.RefreshView(only_values=True)
- DebugViewer.RefreshNewData(self, *args, **kwargs)
-
- def NewDataAvailable(self, tick, *args, **kwargs):
- if USE_MPL and tick is not None:
- if len(self.Ticks) == 0:
- self.StartTick = tick
- self.Ticks = numpy.append(self.Ticks, [tick])
- if not self.Fixed or tick < self.StartTick + self.CurrentRange:
- self.StartTick = max(self.StartTick, tick - self.CurrentRange)
- if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
- self.Force = True
- DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
-
- def ForceRefresh(self):
- self.Force = True
- wx.CallAfter(self.NewDataAvailable, None, True)
-
- def RefreshGraphicsSizer(self):
- self.GraphicsSizer.Clear()
-
- for panel in self.GraphicPanels:
- self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
-
- self.GraphicsSizer.Layout()
- self.RefreshGraphicsWindowScrollbars()
-
- def SetCanvasPosition(self, tick):
- tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
- self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
- self.Fixed = True
- self.RefreshCanvasPosition()
- self.ForceRefresh()
-
- def SetCursorTick(self, cursor_tick):
- self.CursorTick = cursor_tick
- self.Fixed = True
- self.ResetCursorTick()
-
- def ResetCursorTick(self):
- self.CursorTick = None
- self.ResetCursorTick()
-
- def ResetCursorTick(self):
- for panel in self.GraphicPanels:
- if isinstance(panel, DebugVariableGraphic):
- panel.SetCursorTick(self.CursorTick)
- self.ForceRefresh()
-
- def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
- if len(panel.GetItems()) > 1:
- self.DraggingAxesPanel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
- self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
- width, height = panel.GetSize()
- self.DraggingAxesPanel.SetSize(wx.Size(width, height))
- self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
- else:
- self.DraggingAxesPanel = panel
- self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True)
- self.DraggingAxesMousePos = wx.Point(
- x_mouse_start - self.DraggingAxesBoundingBox.x,
- y_mouse_start - self.DraggingAxesBoundingBox.y)
- self.MoveDragNDrop(x_mouse, y_mouse)
-
- def MoveDragNDrop(self, x_mouse, y_mouse):
- self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
- self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
- self.RefreshHighlight(x_mouse, y_mouse)
-
- def RefreshHighlight(self, x_mouse, y_mouse):
- for idx, panel in enumerate(self.GraphicPanels):
- x, y = panel.GetPosition()
- width, height = panel.GetSize()
- rect = wx.Rect(x, y, width, height)
- if (rect.InsideXY(x_mouse, y_mouse) or
- idx == 0 and y_mouse < 0 or
- idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
- panel.OnDragging(x_mouse - x, y_mouse - y)
- else:
- panel.SetHighlight(HIGHLIGHT_NONE)
- if wx.Platform == "__WXMSW__":
- self.RefreshView()
- else:
- self.ForceRefresh()
-
- def ResetHighlight(self):
- for panel in self.GraphicPanels:
- panel.SetHighlight(HIGHLIGHT_NONE)
- if wx.Platform == "__WXMSW__":
- self.RefreshView()
- else:
- self.ForceRefresh()
-
- def IsDragging(self):
- return self.DraggingAxesPanel is not None
-
- def GetDraggingAxesClippingRegion(self, panel):
- x, y = panel.GetPosition()
- width, height = panel.GetSize()
- bbox = wx.Rect(x, y, width, height)
- bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
- bbox.x -= x
- bbox.y -= y
- return bbox
-
- def GetDraggingAxesPosition(self, panel):
- x, y = panel.GetPosition()
- return wx.Point(self.DraggingAxesBoundingBox.x - x,
- self.DraggingAxesBoundingBox.y - y)
-
- def StopDragNDrop(self, variable, x_mouse, y_mouse):
- if self.DraggingAxesPanel not in self.GraphicPanels:
- self.DraggingAxesPanel.Destroy()
- self.DraggingAxesPanel = None
- self.DraggingAxesBoundingBox = None
- self.DraggingAxesMousePos = None
- for idx, panel in enumerate(self.GraphicPanels):
- panel.SetHighlight(HIGHLIGHT_NONE)
- xw, yw = panel.GetPosition()
- width, height = panel.GetSize()
- bbox = wx.Rect(xw, yw, width, height)
- if bbox.InsideXY(x_mouse, y_mouse):
- panel.ShowButtons(True)
- merge_type = GRAPH_PARALLEL
- if isinstance(panel, DebugVariableText) or panel.Is3DCanvas():
- if y_mouse > yw + height / 2:
- idx += 1
- wx.CallAfter(self.MoveValue, variable, idx)
- else:
- rect = panel.GetAxesBoundingBox(True)
- if rect.InsideXY(x_mouse, y_mouse):
- merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
- if merge_rect.InsideXY(x_mouse, y_mouse):
- merge_type = GRAPH_ORTHOGONAL
- wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
- else:
- if y_mouse > yw + height / 2:
- idx += 1
- wx.CallAfter(self.MoveValue, variable, idx)
- self.ForceRefresh()
- return
- width, height = self.GraphicsWindow.GetVirtualSize()
- rect = wx.Rect(0, 0, width, height)
- if rect.InsideXY(x_mouse, y_mouse):
- wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels))
- self.ForceRefresh()
-
- def RefreshView(self, only_values=False):
- if USE_MPL:
- self.RefreshCanvasPosition()
-
- width, height = self.GraphicsWindow.GetVirtualSize()
- bitmap = wx.EmptyBitmap(width, height)
- dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
- dc.Clear()
- dc.BeginDrawing()
- if self.DraggingAxesPanel is not None:
- destBBox = self.DraggingAxesBoundingBox
- srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
-
- srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
- srcDC = wx.MemoryDC()
- srcDC.SelectObject(srcBmp)
-
- dc.Blit(destBBox.x, destBBox.y,
- int(destBBox.width), int(destBBox.height),
- srcDC, srcBBox.x, srcBBox.y)
- dc.EndDrawing()
-
- if not self.Fixed or self.Force:
- self.Force = False
- refresh_graphics = True
- else:
- refresh_graphics = False
-
- if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
- self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
- for panel in self.GraphicPanels:
- if isinstance(panel, DebugVariableGraphic):
- panel.RefreshViewer(refresh_graphics)
- else:
- panel.RefreshViewer()
-
- if self.CursorTick is not None:
- tick = self.CursorTick
- elif len(self.Ticks) > 0:
- tick = self.Ticks[-1]
- else:
- tick = None
- if tick is not None:
- self.TickLabel.SetLabel("Tick: %d" % tick)
- if self.Ticktime > 0:
- tick_duration = int(tick * self.Ticktime)
- not_null = False
- duration = ""
- for value, format in [(tick_duration / DAY, "%dd"),
- ((tick_duration % DAY) / HOUR, "%dh"),
- ((tick_duration % HOUR) / MINUTE, "%dm"),
- ((tick_duration % MINUTE) / SECOND, "%ds")]:
-
- if value > 0 or not_null:
- duration += format % value
- not_null = True
-
- duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND)
- self.TickTimeLabel.SetLabel("t: %s" % duration)
- else:
- self.TickTimeLabel.SetLabel("")
- else:
- self.TickLabel.SetLabel("")
- self.TickTimeLabel.SetLabel("")
- self.TickSizer.Layout()
- else:
- self.Freeze()
-
- if only_values:
- for col in xrange(self.Table.GetNumberCols()):
- if self.Table.GetColLabelValue(col, False) == "Value":
- for row in xrange(self.Table.GetNumberRows()):
- self.VariablesGrid.SetCellValue(row, col, str(self.Table.GetValueByName(row, "Value")))
- else:
- self.Table.ResetView(self.VariablesGrid)
- self.VariablesGrid.RefreshButtons()
-
- self.Thaw()
-
- def UnregisterObsoleteData(self):
- if USE_MPL:
- if self.DataProducer is not None:
- self.Ticktime = self.DataProducer.GetTicktime()
- self.RefreshCanvasRange()
-
- for panel in self.GraphicPanels:
- panel.UnregisterObsoleteData()
- if panel.IsEmpty():
- if panel.HasCapture():
- panel.ReleaseMouse()
- self.GraphicPanels.remove(panel)
- panel.Destroy()
-
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- self.ForceRefresh()
-
- else:
- items = [(idx, item) for idx, item in enumerate(self.Table.GetData())]
- items.reverse()
- for idx, item in items:
- iec_path = item.GetVariable().upper()
- if self.GetDataType(iec_path) is None:
- self.RemoveDataConsumer(item)
- self.Table.RemoveItem(idx)
- else:
- self.AddDataConsumer(iec_path, item)
- item.RefreshVariableType()
- self.Freeze()
- self.Table.ResetView(self.VariablesGrid)
- self.VariablesGrid.RefreshButtons()
- self.Thaw()
-
- def ResetView(self):
- self.DeleteDataConsumers()
- if USE_MPL:
- self.Fixed = False
- for panel in self.GraphicPanels:
- panel.Destroy()
- self.GraphicPanels = []
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- else:
- self.Table.Empty()
- self.Freeze()
- self.Table.ResetView(self.VariablesGrid)
- self.VariablesGrid.RefreshButtons()
- self.Thaw()
-
- def RefreshCanvasRange(self):
- if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES:
- self.RangeValues = RANGE_VALUES
- self.CanvasRange.Clear()
- for text, value in RANGE_VALUES:
- self.CanvasRange.Append(text)
- self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0])
- self.CurrentRange = RANGE_VALUES[0][1]
- self.RefreshView(True)
- elif self.Ticktime != 0 and self.RangeValues != TIME_RANGE_VALUES:
- self.RangeValues = TIME_RANGE_VALUES
- self.CanvasRange.Clear()
- for text, value in TIME_RANGE_VALUES:
- self.CanvasRange.Append(text)
- self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0])
- self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime
- self.RefreshView(True)
-
- def RefreshCanvasPosition(self):
- if len(self.Ticks) > 0:
- pos = int(self.StartTick - self.Ticks[0])
- range = int(self.Ticks[-1] - self.Ticks[0])
- else:
- pos = 0
- range = 0
- self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
-
- def GetForceVariableMenuFunction(self, iec_path, item):
- iec_type = self.GetDataType(iec_path)
- def ForceVariableFunction(event):
- if iec_type is not None:
- dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
- if dialog.ShowModal() == wx.ID_OK:
- self.ForceDataValue(iec_path, dialog.GetValue())
- return ForceVariableFunction
-
- def GetReleaseVariableMenuFunction(self, iec_path):
- def ReleaseVariableFunction(event):
- self.ReleaseDataValue(iec_path)
- return ReleaseVariableFunction
-
- def OnVariablesGridCellLeftClick(self, event):
- if event.GetCol() == 0:
- row = event.GetRow()
- data = wx.TextDataObject(str((self.Table.GetValueByName(row, "Variable"), "debug")))
- dragSource = wx.DropSource(self.VariablesGrid)
- dragSource.SetData(data)
- dragSource.DoDragDrop()
- event.Skip()
-
- def OnVariablesGridCellRightClick(self, event):
- row, col = event.GetRow(), event.GetCol()
- if self.Table.GetColLabelValue(col, False) == "Value":
- iec_path = self.Table.GetValueByName(row, "Variable").upper()
-
- menu = wx.Menu(title='')
-
- new_id = wx.NewId()
- AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Force value"))
- self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.Table.GetItem(row)), id=new_id)
-
- new_id = wx.NewId()
- AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Release value"))
- self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), id=new_id)
-
- if self.Table.IsForced(row):
- menu.Enable(new_id, True)
- else:
- menu.Enable(new_id, False)
-
- self.PopupMenu(menu)
-
- menu.Destroy()
- event.Skip()
-
- def ChangeRange(self, dir, tick=None):
- current_range = self.CurrentRange
- current_range_idx = self.CanvasRange.GetSelection()
- new_range_idx = max(0, min(current_range_idx + dir, len(self.RangeValues) - 1))
- if new_range_idx != current_range_idx:
- self.CanvasRange.SetSelection(new_range_idx)
- if self.Ticktime == 0:
- self.CurrentRange = self.RangeValues[new_range_idx][1]
- else:
- self.CurrentRange = self.RangeValues[new_range_idx][1] / self.Ticktime
- if len(self.Ticks) > 0:
- if tick is None:
- tick = self.StartTick + self.CurrentRange / 2.
- new_start_tick = tick - (tick - self.StartTick) * self.CurrentRange / current_range
- self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
- self.Fixed = self.StartTick < self.Ticks[-1] - self.CurrentRange
- self.ForceRefresh()
-
- def RefreshRange(self):
- if len(self.Ticks) > 0:
- if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
- self.Fixed = False
- if self.Fixed:
- self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
- else:
- self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
- self.ForceRefresh()
-
- def OnRangeChanged(self, event):
- try:
- if self.Ticktime == 0:
- self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
- else:
- self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] / self.Ticktime
- except ValueError, e:
- self.CanvasRange.SetValue(str(self.CurrentRange))
- wx.CallAfter(self.RefreshRange)
- event.Skip()
-
- def OnCurrentButton(self, event):
- if len(self.Ticks) > 0:
- self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
- self.Fixed = False
- self.CursorTick = None
- self.ResetCursorTick()
- event.Skip()
-
- def CopyDataToClipboard(self, variables):
- text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
- next_tick = NextTick(variables)
- while next_tick is not None:
- values = []
- for item, data in variables:
- if len(data) > 0:
- if next_tick == data[0][0]:
- var_type = item.GetVariableType()
- if var_type in ["STRING", "WSTRING"]:
- value = item.GetRawValue(int(data.pop(0)[2]))
- if var_type == "STRING":
- values.append("'%s'" % value)
- else:
- values.append('"%s"' % value)
- else:
- values.append("%.3f" % data.pop(0)[1])
- else:
- values.append("")
- else:
- values.append("")
- text += "%d;%s;\n" % (next_tick, ";".join(values))
- next_tick = NextTick(variables)
- self.ParentWindow.SetCopyBuffer(text)
-
- def OnExportGraphButton(self, event):
- variables = []
- if USE_MPL:
- items = []
- for panel in self.GraphicPanels:
- items.extend(panel.GetItems())
- else:
- items = self.Table.GetData()
- for item in items:
- if item.IsNumVariable():
- variables.append((item, [entry for entry in item.GetData()]))
- wx.CallAfter(self.CopyDataToClipboard, variables)
- event.Skip()
-
- def OnPositionChanging(self, event):
- if len(self.Ticks) > 0:
- self.StartTick = self.Ticks[0] + event.GetPosition()
- self.Fixed = True
- self.ForceRefresh()
- event.Skip()
-
- def GetRange(self):
- return self.StartTick, self.StartTick + self.CurrentRange
-
- def GetViewerIndex(self, viewer):
- if viewer in self.GraphicPanels:
- return self.GraphicPanels.index(viewer)
- return None
-
- def IsViewerFirst(self, viewer):
- return viewer == self.GraphicPanels[0]
-
- def IsViewerLast(self, viewer):
- return viewer == self.GraphicPanels[-1]
-
- def HighlightPreviousViewer(self, viewer):
- if self.IsViewerFirst(viewer):
- return
- idx = self.GetViewerIndex(viewer)
- if idx is None:
- return
- self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
-
- def ResetVariableNameMask(self):
- items = []
- for panel in self.GraphicPanels:
- items.extend(panel.GetItems())
- if len(items) > 1:
- self.VariableNameMask = reduce(compute_mask,
- [item.GetVariable().split('.') for item in items])
- elif len(items) > 0:
- self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
- else:
- self.VariableNameMask = []
- self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
- self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
-
- def GetVariableNameMask(self):
- return self.VariableNameMask
-
- def InsertValue(self, iec_path, idx = None, force=False):
- if USE_MPL:
- for panel in self.GraphicPanels:
- if panel.GetItem(iec_path) is not None:
- return
- if idx is None:
- idx = len(self.GraphicPanels)
- else:
- for item in self.Table.GetData():
- if iec_path == item.GetVariable():
- return
- if idx is None:
- idx = self.Table.GetNumberRows()
- item = VariableTableItem(self, iec_path)
- result = self.AddDataConsumer(iec_path.upper(), item)
- if result is not None or force:
-
- if USE_MPL:
- if item.IsNumVariable():
- panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
- if self.CursorTick is not None:
- panel.SetCursorTick(self.CursorTick)
- else:
- panel = DebugVariableText(self.GraphicsWindow, self, [item])
- if idx is not None:
- self.GraphicPanels.insert(idx, panel)
- else:
- self.GraphicPanels.append(panel)
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- else:
- self.Table.InsertItem(idx, item)
-
- self.ForceRefresh()
-
- def MoveValue(self, iec_path, idx = None):
- if idx is None:
- idx = len(self.GraphicPanels)
- source_panel = None
- item = None
- for panel in self.GraphicPanels:
- item = panel.GetItem(iec_path)
- if item is not None:
- source_panel = panel
- break
- if source_panel is not None:
- source_panel.RemoveItem(item)
- if source_panel.IsEmpty():
- if source_panel.HasCapture():
- source_panel.ReleaseMouse()
- self.GraphicPanels.remove(source_panel)
- source_panel.Destroy()
-
- if item.IsNumVariable():
- panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
- if self.CursorTick is not None:
- panel.SetCursorTick(self.CursorTick)
- else:
- panel = DebugVariableText(self.GraphicsWindow, self, [item])
- self.GraphicPanels.insert(idx, panel)
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- self.ForceRefresh()
-
- def MergeGraphs(self, source, target_idx, merge_type, force=False):
- source_item = None
- source_panel = None
- for panel in self.GraphicPanels:
- source_item = panel.GetItem(source)
- if source_item is not None:
- source_panel = panel
- break
- if source_item is None:
- item = VariableTableItem(self, source)
- if item.IsNumVariable():
- result = self.AddDataConsumer(source.upper(), item)
- if result is not None or force:
- source_item = item
- if source_item is not None and source_item.IsNumVariable():
- target_panel = self.GraphicPanels[target_idx]
- graph_type = target_panel.GraphType
- if target_panel != source_panel:
- if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
- merge_type == GRAPH_ORTHOGONAL and
- (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
- graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
- return
-
- if source_panel is not None:
- source_panel.RemoveItem(source_item)
- if source_panel.IsEmpty():
- if source_panel.HasCapture():
- source_panel.ReleaseMouse()
- self.GraphicPanels.remove(source_panel)
- source_panel.Destroy()
- elif (merge_type != graph_type and len(target_panel.Items) == 2):
- target_panel.RemoveItem(source_item)
- else:
- target_panel = None
-
- if target_panel is not None:
- target_panel.AddItem(source_item)
- target_panel.GraphType = merge_type
- target_panel.ResetGraphics()
-
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- self.ForceRefresh()
-
- def DeleteValue(self, source_panel, item=None):
- source_idx = self.GetViewerIndex(source_panel)
- if source_idx is not None:
-
- if item is None:
- source_panel.Clear()
- source_panel.Destroy()
- self.GraphicPanels.remove(source_panel)
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- else:
- source_panel.RemoveItem(item)
- if source_panel.IsEmpty():
- source_panel.Destroy()
- self.GraphicPanels.remove(source_panel)
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- self.ForceRefresh()
-
- def GetDebugVariables(self):
- if USE_MPL:
- return [panel.GetVariables() for panel in self.GraphicPanels]
- else:
- return [item.GetVariable() for item in self.Table.GetData()]
-
- def SetDebugVariables(self, variables):
- if USE_MPL:
- for variable in variables:
- if isinstance(variable, (TupleType, ListType)):
- items = []
- for iec_path in variable:
- item = VariableTableItem(self, iec_path)
- if not item.IsNumVariable():
- continue
- self.AddDataConsumer(iec_path.upper(), item)
- items.append(item)
- if isinstance(variable, ListType):
- panel = DebugVariableGraphic(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
- elif isinstance(variable, TupleType) and len(items) <= 3:
- panel = DebugVariableGraphic(self.GraphicsWindow, self, items, GRAPH_ORTHOGONAL)
- else:
- continue
- self.GraphicPanels.append(panel)
- self.ResetVariableNameMask()
- self.RefreshGraphicsSizer()
- else:
- self.InsertValue(variable, force=True)
- self.ForceRefresh()
- else:
- for variable in variables:
- if isinstance(variable, (ListType, TupleType)):
- for iec_path in variable:
- self.InsertValue(iec_path, force=True)
- else:
- self.InsertValue(variable, force=True)
-
- def ResetGraphicsValues(self):
- if USE_MPL:
- self.Ticks = numpy.array([])
- self.StartTick = 0
- self.Fixed = False
- for panel in self.GraphicPanels:
- panel.ResetData()
-
- def RefreshGraphicsWindowScrollbars(self):
- xstart, ystart = self.GraphicsWindow.GetViewStart()
- window_size = self.GraphicsWindow.GetClientSize()
- vwidth, vheight = self.GraphicsSizer.GetMinSize()
- posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
- posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
- self.GraphicsWindow.Scroll(posx, posy)
- self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
- vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
-
- def OnGraphicsWindowResize(self, event):
- self.RefreshGraphicsWindowScrollbars()
- event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,955 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+import math
+import numpy
+
+import wx
+import wx.lib.buttons
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+
+from editors.DebugViewer import DebugViewer
+from util.BitmapLibrary import GetBitmap
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableTextViewer import DebugVariableTextViewer
+from DebugVariableGraphicViewer import *
+
+MILLISECOND = 1000000 # Number of nanosecond in a millisecond
+SECOND = 1000 * MILLISECOND # Number of nanosecond in a second
+MINUTE = 60 * SECOND # Number of nanosecond in a minute
+HOUR = 60 * MINUTE # Number of nanosecond in a hour
+DAY = 24 * HOUR # Number of nanosecond in a day
+
+# List of values possible for graph range
+# Format is [(time_in_plain_text, value_in_nanosecond),...]
+RANGE_VALUES = \
+ [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
+ [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
+ [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
+ [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
+
+# Scrollbar increment in pixel
+SCROLLBAR_UNIT = 10
+
+def compute_mask(x, y):
+ return [(xp if xp == yp else "*")
+ for xp, yp in zip(x, y)]
+
+def NextTick(variables):
+ next_tick = None
+ for item, data in variables:
+ if len(data) == 0:
+ continue
+
+ next_tick = (data[0][0]
+ if next_tick is None
+ else min(next_tick, data[0][0]))
+
+ return next_tick
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Panel Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Graphic
+Panel
+"""
+
+class DebugVariableDropTarget(wx.TextDropTarget):
+
+ def __init__(self, window):
+ """
+ Constructor
+ @param window: Reference to the Debug Variable Panel
+ """
+ wx.TextDropTarget.__init__(self)
+ self.ParentWindow = window
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Panel
+ self.ParentWindow = None
+
+ def OnDragOver(self, x, y, d):
+ """
+ Function called when mouse is dragged over Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param d: Suggested default for return value
+ """
+ # Signal Debug Variable Panel to refresh highlight giving mouse position
+ self.ParentWindow.RefreshHighlight(x, y)
+ return wx.TextDropTarget.OnDragOver(self, x, y, d)
+
+ def OnDropText(self, x, y, data):
+ """
+ Function called when mouse is released in Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param data: Text associated to drag'n drop
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+
+ message = None
+
+ # Check that data is valid regarding DebugVariablePanel
+ try:
+ values = eval(data)
+ if not isinstance(values, TupleType):
+ raise ValueError
+ except:
+ message = _("Invalid value \"%s\" for debug variable")%data
+ values = None
+
+ # Display message if data is invalid
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ # Data contain a reference to a variable to debug
+ elif values[1] == "debug":
+
+ # Drag'n Drop is an internal is an internal move inside Debug
+ # Variable Panel
+ if len(values) > 2 and values[2] == "move":
+ self.ParentWindow.MoveValue(values[0])
+
+ # Drag'n Drop was initiated by another control of Beremiz
+ else:
+ self.ParentWindow.InsertValue(values[0], force=True)
+
+ def OnLeave(self):
+ """
+ Function called when mouse is leave Drop Target
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+ return wx.TextDropTarget.OnLeave(self)
+
+ def ShowMessage(self, message):
+ """
+ Show error message in Error Dialog
+ @param message: Error message to display
+ """
+ dialog = wx.MessageDialog(self.ParentWindow,
+ message,
+ _("Error"),
+ wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Panel Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a graphs
+"""
+
+class DebugVariableGraphicPanel(wx.Panel, DebugViewer):
+
+ def __init__(self, parent, producer, window):
+ """
+ Constructor
+ @param parent: Reference to the parent wx.Window
+ @param producer: Object receiving debug value and dispatching them to
+ consumers
+ @param window: Reference to Beremiz frame
+ """
+ wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
+
+ # Save Reference to Beremiz frame
+ self.ParentWindow = window
+
+ # Variable storing flag indicating that variable displayed in table
+ # received new value and then table need to be refreshed
+ self.HasNewData = False
+
+ # Variable storing flag indicating that refresh has been forced, and
+ # that next time refresh is possible, it will be done even if no new
+ # data is available
+ self.Force = False
+
+ self.SetBackgroundColour(wx.WHITE)
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.Ticks = numpy.array([]) # List of tick received
+ self.StartTick = 0 # Tick starting range of data displayed
+ self.Fixed = False # Flag that range of data is fixed
+ self.CursorTick = None # Tick of cursor for displaying values
+
+ self.DraggingAxesPanel = None
+ self.DraggingAxesBoundingBox = None
+ self.DraggingAxesMousePos = None
+ self.VetoScrollEvent = False
+
+ self.VariableNameMask = []
+
+ self.GraphicPanels = []
+
+ graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
+
+ range_label = wx.StaticText(self, label=_('Range:'))
+ graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
+ graphics_button_sizer.AddWindow(self.CanvasRange, 1,
+ border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
+
+ self.CanvasRange.Clear()
+ default_range_idx = 0
+ for idx, (text, value) in enumerate(RANGE_VALUES):
+ self.CanvasRange.Append(text)
+ if text == "1s":
+ default_range_idx = idx
+ self.CanvasRange.SetSelection(default_range_idx)
+
+ for name, bitmap, help in [
+ ("CurrentButton", "current", _("Go to current value")),
+ ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
+ button = wx.lib.buttons.GenBitmapButton(self,
+ bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
+ graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+
+ self.CanvasPosition = wx.ScrollBar(self,
+ size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN,
+ self.OnPositionChanging, self.CanvasPosition)
+ main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
+
+ self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
+
+ self.TickLabel = wx.StaticText(self)
+ self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
+
+ self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
+ self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
+
+ self.TickTimeLabel = wx.StaticText(self)
+ self.TickSizer.AddWindow(self.TickTimeLabel)
+
+ self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
+ self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
+ self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
+ self.GraphicsWindow.Bind(wx.EVT_ERASE_BACKGROUND, self.OnGraphicsWindowEraseBackground)
+ self.GraphicsWindow.Bind(wx.EVT_PAINT, self.OnGraphicsWindowPaint)
+ self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
+ self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
+
+ main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
+
+ self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
+ self.GraphicsWindow.SetSizer(self.GraphicsSizer)
+
+ DebugViewer.__init__(self, producer, True)
+
+ self.SetSizer(main_sizer)
+
+ def SetTickTime(self, ticktime=0):
+ """
+ Set Ticktime for calculate data range according to time range selected
+ @param ticktime: Ticktime to apply to range (default: 0)
+ """
+ # Save ticktime
+ self.Ticktime = ticktime
+
+ # Set ticktime to millisecond if undefined
+ if self.Ticktime == 0:
+ self.Ticktime = MILLISECOND
+
+ # Calculate range to apply to data
+ self.CurrentRange = RANGE_VALUES[
+ self.CanvasRange.GetSelection()][1] / self.Ticktime
+
+ def SetDataProducer(self, producer):
+ """
+ Set Data Producer
+ @param producer: Data Producer
+ """
+ DebugViewer.SetDataProducer(self, producer)
+
+ # Set ticktime if data producer is available
+ if self.DataProducer is not None:
+ self.SetTickTime(self.DataProducer.GetTicktime())
+
+ def RefreshNewData(self, *args, **kwargs):
+ """
+ Called to refresh Panel according to values received by variables
+ Can receive any parameters (not used here)
+ """
+ # Refresh graphs if new data is available or refresh is forced
+ if self.HasNewData or self.Force:
+ self.HasNewData = False
+ self.RefreshView()
+
+ DebugViewer.RefreshNewData(self, *args, **kwargs)
+
+ def NewDataAvailable(self, tick, *args, **kwargs):
+ """
+ Called by DataProducer for each tick captured or by panel to refresh
+ graphs
+ @param tick: PLC tick captured
+ All other parameters are passed to refresh function
+ """
+ # If tick given
+ if tick is not None:
+ self.HasNewData = True
+
+ # Save tick as start tick for range if data is still empty
+ if len(self.Ticks) == 0:
+ self.StartTick = tick
+
+ # Add tick to list of ticks received
+ self.Ticks = numpy.append(self.Ticks, [tick])
+
+ # Update start tick for range if range follow ticks received
+ if not self.Fixed or tick < self.StartTick + self.CurrentRange:
+ self.StartTick = max(self.StartTick, tick - self.CurrentRange)
+
+ # Force refresh if graph is fixed because range of data received
+ # is too small to fill data range selected
+ if self.Fixed and \
+ self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+ self.Force = True
+
+ DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
+
+ def ForceRefresh(self):
+ """
+ Called to force refresh of graphs
+ """
+ self.Force = True
+ wx.CallAfter(self.NewDataAvailable, None, True)
+
+ def SetCursorTick(self, cursor_tick):
+ """
+ Set Cursor for displaying values of items at a tick given
+ @param cursor_tick: Tick of cursor
+ """
+ # Save cursor tick
+ self.CursorTick = cursor_tick
+ self.Fixed = cursor_tick is not None
+ self.UpdateCursorTick()
+
+ def MoveCursorTick(self, move):
+ if self.CursorTick is not None:
+ cursor_tick = max(self.Ticks[0],
+ min(self.CursorTick + move,
+ self.Ticks[-1]))
+ cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
+ if self.Ticks[cursor_tick_idx] == self.CursorTick:
+ cursor_tick_idx = max(0,
+ min(cursor_tick_idx + abs(move) / move,
+ len(self.Ticks) - 1))
+ self.CursorTick = self.Ticks[cursor_tick_idx]
+ self.StartTick = max(self.Ticks[
+ numpy.argmin(numpy.abs(self.Ticks -
+ self.CursorTick + self.CurrentRange))],
+ min(self.StartTick, self.CursorTick))
+ self.RefreshCanvasPosition()
+ self.UpdateCursorTick()
+
+ def ResetCursorTick(self):
+ self.CursorTick = None
+ self.Fixed = False
+ self.UpdateCursorTick()
+
+ def UpdateCursorTick(self):
+ for panel in self.GraphicPanels:
+ if isinstance(panel, DebugVariableGraphicViewer):
+ panel.SetCursorTick(self.CursorTick)
+ self.ForceRefresh()
+
+ def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
+ if len(panel.GetItems()) > 1:
+ self.DraggingAxesPanel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+ self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
+ width, height = panel.GetSize()
+ self.DraggingAxesPanel.SetSize(wx.Size(width, height))
+ self.DraggingAxesPanel.ResetGraphics()
+ self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
+ else:
+ self.DraggingAxesPanel = panel
+ self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(parent_coordinate=True)
+ self.DraggingAxesMousePos = wx.Point(
+ x_mouse_start - self.DraggingAxesBoundingBox.x,
+ y_mouse_start - self.DraggingAxesBoundingBox.y)
+ self.MoveDragNDrop(x_mouse, y_mouse)
+
+ def MoveDragNDrop(self, x_mouse, y_mouse):
+ self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
+ self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
+ self.RefreshHighlight(x_mouse, y_mouse)
+
+ def RefreshHighlight(self, x_mouse, y_mouse):
+ for idx, panel in enumerate(self.GraphicPanels):
+ x, y = panel.GetPosition()
+ width, height = panel.GetSize()
+ rect = wx.Rect(x, y, width, height)
+ if (rect.InsideXY(x_mouse, y_mouse) or
+ idx == 0 and y_mouse < 0 or
+ idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
+ panel.RefreshHighlight(x_mouse - x, y_mouse - y)
+ else:
+ panel.SetHighlight(HIGHLIGHT_NONE)
+ if wx.Platform == "__WXMSW__":
+ self.RefreshView()
+ else:
+ self.ForceRefresh()
+
+ def ResetHighlight(self):
+ for panel in self.GraphicPanels:
+ panel.SetHighlight(HIGHLIGHT_NONE)
+ if wx.Platform == "__WXMSW__":
+ self.RefreshView()
+ else:
+ self.ForceRefresh()
+
+ def IsDragging(self):
+ return self.DraggingAxesPanel is not None
+
+ def GetDraggingAxesClippingRegion(self, panel):
+ x, y = panel.GetPosition()
+ width, height = panel.GetSize()
+ bbox = wx.Rect(x, y, width, height)
+ bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
+ bbox.x -= x
+ bbox.y -= y
+ return bbox
+
+ def GetDraggingAxesPosition(self, panel):
+ x, y = panel.GetPosition()
+ return wx.Point(self.DraggingAxesBoundingBox.x - x,
+ self.DraggingAxesBoundingBox.y - y)
+
+ def StopDragNDrop(self, variable, x_mouse, y_mouse):
+ if self.DraggingAxesPanel not in self.GraphicPanels:
+ self.DraggingAxesPanel.Destroy()
+ self.DraggingAxesPanel = None
+ self.DraggingAxesBoundingBox = None
+ self.DraggingAxesMousePos = None
+ for idx, panel in enumerate(self.GraphicPanels):
+ panel.SetHighlight(HIGHLIGHT_NONE)
+ xw, yw = panel.GetPosition()
+ width, height = panel.GetSize()
+ bbox = wx.Rect(xw, yw, width, height)
+ if bbox.InsideXY(x_mouse, y_mouse):
+ panel.ShowButtons(True)
+ merge_type = GRAPH_PARALLEL
+ if isinstance(panel, DebugVariableTextViewer) or panel.Is3DCanvas():
+ if y_mouse > yw + height / 2:
+ idx += 1
+ wx.CallAfter(self.MoveValue, variable, idx, True)
+ else:
+ rect = panel.GetAxesBoundingBox(True)
+ if rect.InsideXY(x_mouse, y_mouse):
+ merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
+ if merge_rect.InsideXY(x_mouse, y_mouse):
+ merge_type = GRAPH_ORTHOGONAL
+ wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
+ else:
+ if y_mouse > yw + height / 2:
+ idx += 1
+ wx.CallAfter(self.MoveValue, variable, idx, True)
+ self.ForceRefresh()
+ return
+ width, height = self.GraphicsWindow.GetVirtualSize()
+ rect = wx.Rect(0, 0, width, height)
+ if rect.InsideXY(x_mouse, y_mouse):
+ wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
+ self.ForceRefresh()
+
+ def RefreshGraphicsSizer(self):
+ self.GraphicsSizer.Clear()
+
+ for panel in self.GraphicPanels:
+ self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
+
+ self.GraphicsSizer.Layout()
+ self.RefreshGraphicsWindowScrollbars()
+
+ def RefreshView(self):
+ self.RefreshCanvasPosition()
+
+ width, height = self.GraphicsWindow.GetVirtualSize()
+ bitmap = wx.EmptyBitmap(width, height)
+ dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
+ dc.Clear()
+ dc.BeginDrawing()
+ if self.DraggingAxesPanel is not None:
+ destBBox = self.DraggingAxesBoundingBox
+ srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
+
+ srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
+ srcDC = wx.MemoryDC()
+ srcDC.SelectObject(srcBmp)
+
+ dc.Blit(destBBox.x, destBBox.y,
+ int(destBBox.width), int(destBBox.height),
+ srcDC, srcBBox.x, srcBBox.y)
+ dc.EndDrawing()
+
+ if not self.Fixed or self.Force:
+ self.Force = False
+ refresh_graphics = True
+ else:
+ refresh_graphics = False
+
+ if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
+ self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
+ for panel in self.GraphicPanels:
+ if isinstance(panel, DebugVariableGraphicViewer):
+ panel.RefreshViewer(refresh_graphics)
+ else:
+ panel.RefreshViewer()
+
+ if self.CursorTick is not None:
+ tick = self.CursorTick
+ elif len(self.Ticks) > 0:
+ tick = self.Ticks[-1]
+ else:
+ tick = None
+ if tick is not None:
+ self.TickLabel.SetLabel("Tick: %d" % tick)
+ tick_duration = int(tick * self.Ticktime)
+ not_null = False
+ duration = ""
+ for value, format in [(tick_duration / DAY, "%dd"),
+ ((tick_duration % DAY) / HOUR, "%dh"),
+ ((tick_duration % HOUR) / MINUTE, "%dm"),
+ ((tick_duration % MINUTE) / SECOND, "%ds")]:
+
+ if value > 0 or not_null:
+ duration += format % value
+ not_null = True
+
+ duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND)
+ self.TickTimeLabel.SetLabel("t: %s" % duration)
+ else:
+ self.TickLabel.SetLabel("")
+ self.TickTimeLabel.SetLabel("")
+ self.TickSizer.Layout()
+
+ def SubscribeAllDataConsumers(self):
+ DebugViewer.SubscribeAllDataConsumers(self)
+
+ if self.DataProducer is not None:
+ if self.DataProducer is not None:
+ self.SetTickTime(self.DataProducer.GetTicktime())
+
+ self.ResetCursorTick()
+
+ for panel in self.GraphicPanels[:]:
+ panel.SubscribeAllDataConsumers()
+ if panel.ItemsIsEmpty():
+ if panel.HasCapture():
+ panel.ReleaseMouse()
+ self.GraphicPanels.remove(panel)
+ panel.Destroy()
+
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ self.ForceRefresh()
+
+ def ResetView(self):
+ self.UnsubscribeAllDataConsumers()
+
+ self.Fixed = False
+ for panel in self.GraphicPanels:
+ panel.Destroy()
+ self.GraphicPanels = []
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+
+ def SetCanvasPosition(self, tick):
+ tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
+ self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
+ self.Fixed = True
+ self.RefreshCanvasPosition()
+ self.ForceRefresh()
+
+ def RefreshCanvasPosition(self):
+ if len(self.Ticks) > 0:
+ pos = int(self.StartTick - self.Ticks[0])
+ range = int(self.Ticks[-1] - self.Ticks[0])
+ else:
+ pos = 0
+ range = 0
+ self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
+
+ def ChangeRange(self, dir, tick=None):
+ current_range = self.CurrentRange
+ current_range_idx = self.CanvasRange.GetSelection()
+ new_range_idx = max(0, min(current_range_idx + dir, len(RANGE_VALUES) - 1))
+ if new_range_idx != current_range_idx:
+ self.CanvasRange.SetSelection(new_range_idx)
+ self.CurrentRange = RANGE_VALUES[new_range_idx][1] / self.Ticktime
+ if len(self.Ticks) > 0:
+ if tick is None:
+ tick = self.StartTick + self.CurrentRange / 2.
+ new_start_tick = min(tick - (tick - self.StartTick) * self.CurrentRange / current_range,
+ self.Ticks[-1] - self.CurrentRange)
+ self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
+ self.Fixed = new_start_tick < self.Ticks[-1] - self.CurrentRange
+ self.ForceRefresh()
+
+ def RefreshRange(self):
+ if len(self.Ticks) > 0:
+ if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+ self.Fixed = False
+ if self.Fixed:
+ self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
+ else:
+ self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+ self.ForceRefresh()
+
+ def OnRangeChanged(self, event):
+ try:
+ self.CurrentRange = RANGE_VALUES[self.CanvasRange.GetSelection()][1] / self.Ticktime
+ except ValueError, e:
+ self.CanvasRange.SetValue(str(self.CurrentRange))
+ wx.CallAfter(self.RefreshRange)
+ event.Skip()
+
+ def OnCurrentButton(self, event):
+ if len(self.Ticks) > 0:
+ self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+ self.ResetCursorTick()
+ event.Skip()
+
+ def CopyDataToClipboard(self, variables):
+ text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
+ next_tick = NextTick(variables)
+ while next_tick is not None:
+ values = []
+ for item, data in variables:
+ if len(data) > 0:
+ if next_tick == data[0][0]:
+ var_type = item.GetVariableType()
+ if var_type in ["STRING", "WSTRING"]:
+ value = item.GetRawValue(int(data.pop(0)[2]))
+ if var_type == "STRING":
+ values.append("'%s'" % value)
+ else:
+ values.append('"%s"' % value)
+ else:
+ values.append("%.3f" % data.pop(0)[1])
+ else:
+ values.append("")
+ else:
+ values.append("")
+ text += "%d;%s;\n" % (next_tick, ";".join(values))
+ next_tick = NextTick(variables)
+ self.ParentWindow.SetCopyBuffer(text)
+
+ def OnExportGraphButton(self, event):
+ items = reduce(lambda x, y: x + y,
+ [panel.GetItems() for panel in self.GraphicPanels],
+ [])
+ variables = [(item, [entry for entry in item.GetData()])
+ for item in items
+ if item.IsNumVariable()]
+ wx.CallAfter(self.CopyDataToClipboard, variables)
+ event.Skip()
+
+ def OnPositionChanging(self, event):
+ if len(self.Ticks) > 0:
+ self.StartTick = self.Ticks[0] + event.GetPosition()
+ self.Fixed = True
+ self.ForceRefresh()
+ event.Skip()
+
+ def GetRange(self):
+ return self.StartTick, self.StartTick + self.CurrentRange
+
+ def GetViewerIndex(self, viewer):
+ if viewer in self.GraphicPanels:
+ return self.GraphicPanels.index(viewer)
+ return None
+
+ def IsViewerFirst(self, viewer):
+ return viewer == self.GraphicPanels[0]
+
+ def HighlightPreviousViewer(self, viewer):
+ if self.IsViewerFirst(viewer):
+ return
+ idx = self.GetViewerIndex(viewer)
+ if idx is None:
+ return
+ self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
+
+ def ResetVariableNameMask(self):
+ items = []
+ for panel in self.GraphicPanels:
+ items.extend(panel.GetItems())
+ if len(items) > 1:
+ self.VariableNameMask = reduce(compute_mask,
+ [item.GetVariable().split('.') for item in items])
+ elif len(items) > 0:
+ self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
+ else:
+ self.VariableNameMask = []
+ self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
+ self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
+
+ def GetVariableNameMask(self):
+ return self.VariableNameMask
+
+ def InsertValue(self, iec_path, idx = None, force=False, graph=False):
+ for panel in self.GraphicPanels:
+ if panel.GetItem(iec_path) is not None:
+ if graph and isinstance(panel, DebugVariableTextViewer):
+ self.ToggleViewerType(panel)
+ return
+ if idx is None:
+ idx = len(self.GraphicPanels)
+ item = DebugVariableItem(self, iec_path, True)
+ result = self.AddDataConsumer(iec_path.upper(), item)
+ if result is not None or force:
+
+ self.Freeze()
+ if item.IsNumVariable() and graph:
+ panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+ if self.CursorTick is not None:
+ panel.SetCursorTick(self.CursorTick)
+ else:
+ panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+ if idx is not None:
+ self.GraphicPanels.insert(idx, panel)
+ else:
+ self.GraphicPanels.append(panel)
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ self.Thaw()
+ self.ForceRefresh()
+
+ def MoveValue(self, iec_path, idx = None, graph=False):
+ if idx is None:
+ idx = len(self.GraphicPanels)
+ source_panel = None
+ item = None
+ for panel in self.GraphicPanels:
+ item = panel.GetItem(iec_path)
+ if item is not None:
+ source_panel = panel
+ break
+ if source_panel is not None:
+ source_panel_idx = self.GraphicPanels.index(source_panel)
+
+ if (len(source_panel.GetItems()) == 1):
+
+ if source_panel_idx < idx:
+ self.GraphicPanels.insert(idx, source_panel)
+ self.GraphicPanels.pop(source_panel_idx)
+ elif source_panel_idx > idx:
+ self.GraphicPanels.pop(source_panel_idx)
+ self.GraphicPanels.insert(idx, source_panel)
+ else:
+ return
+
+ else:
+ source_panel.RemoveItem(item)
+ source_size = source_panel.GetSize()
+ if item.IsNumVariable() and graph:
+ panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+ panel.SetCanvasHeight(source_size.height)
+ if self.CursorTick is not None:
+ panel.SetCursorTick(self.CursorTick)
+
+ else:
+ panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+
+ self.GraphicPanels.insert(idx, panel)
+
+ if source_panel.ItemsIsEmpty():
+ if source_panel.HasCapture():
+ source_panel.ReleaseMouse()
+ source_panel.Destroy()
+ self.GraphicPanels.remove(source_panel)
+
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ self.ForceRefresh()
+
+ def MergeGraphs(self, source, target_idx, merge_type, force=False):
+ source_item = None
+ source_panel = None
+ for panel in self.GraphicPanels:
+ source_item = panel.GetItem(source)
+ if source_item is not None:
+ source_panel = panel
+ break
+ if source_item is None:
+ item = DebugVariableItem(self, source, True)
+ if item.IsNumVariable():
+ result = self.AddDataConsumer(source.upper(), item)
+ if result is not None or force:
+ source_item = item
+ if source_item is not None and source_item.IsNumVariable():
+ if source_panel is not None:
+ source_size = source_panel.GetSize()
+ else:
+ source_size = None
+ target_panel = self.GraphicPanels[target_idx]
+ graph_type = target_panel.GraphType
+ if target_panel != source_panel:
+ if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
+ merge_type == GRAPH_ORTHOGONAL and
+ (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
+ graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
+ return
+
+ if source_panel is not None:
+ source_panel.RemoveItem(source_item)
+ if source_panel.ItemsIsEmpty():
+ if source_panel.HasCapture():
+ source_panel.ReleaseMouse()
+ source_panel.Destroy()
+ self.GraphicPanels.remove(source_panel)
+ elif (merge_type != graph_type and len(target_panel.Items) == 2):
+ target_panel.RemoveItem(source_item)
+ else:
+ target_panel = None
+
+ if target_panel is not None:
+ target_panel.AddItem(source_item)
+ target_panel.GraphType = merge_type
+ size = target_panel.GetSize()
+ if merge_type == GRAPH_ORTHOGONAL:
+ target_panel.SetCanvasHeight(size.width)
+ elif source_size is not None and source_panel != target_panel:
+ target_panel.SetCanvasHeight(size.height + source_size.height)
+ else:
+ target_panel.SetCanvasHeight(size.height)
+ target_panel.ResetGraphics()
+
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ self.ForceRefresh()
+
+ def DeleteValue(self, source_panel, item=None):
+ source_idx = self.GetViewerIndex(source_panel)
+ if source_idx is not None:
+
+ if item is None:
+ source_panel.ClearItems()
+ source_panel.Destroy()
+ self.GraphicPanels.remove(source_panel)
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ else:
+ source_panel.RemoveItem(item)
+ if source_panel.ItemsIsEmpty():
+ source_panel.Destroy()
+ self.GraphicPanels.remove(source_panel)
+ self.ResetVariableNameMask()
+ self.RefreshGraphicsSizer()
+ if len(self.GraphicPanels) == 0:
+ self.Fixed = False
+ self.ResetCursorTick()
+ self.ForceRefresh()
+
+ def ToggleViewerType(self, panel):
+ panel_idx = self.GetViewerIndex(panel)
+ if panel_idx is not None:
+ self.GraphicPanels.remove(panel)
+ items = panel.GetItems()
+ if isinstance(panel, DebugVariableGraphicViewer):
+ for idx, item in enumerate(items):
+ new_panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+ self.GraphicPanels.insert(panel_idx + idx, new_panel)
+ else:
+ new_panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
+ self.GraphicPanels.insert(panel_idx, new_panel)
+ panel.Destroy()
+ self.RefreshGraphicsSizer()
+ self.ForceRefresh()
+
+ def ResetGraphicsValues(self):
+ self.Ticks = numpy.array([])
+ self.StartTick = 0
+ for panel in self.GraphicPanels:
+ panel.ResetItemsData()
+ self.ResetCursorTick()
+
+ def RefreshGraphicsWindowScrollbars(self):
+ xstart, ystart = self.GraphicsWindow.GetViewStart()
+ window_size = self.GraphicsWindow.GetClientSize()
+ vwidth, vheight = self.GraphicsSizer.GetMinSize()
+ posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
+ posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
+ self.GraphicsWindow.Scroll(posx, posy)
+ self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
+ vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
+
+ def OnGraphicsWindowEraseBackground(self, event):
+ pass
+
+ def OnGraphicsWindowPaint(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def OnGraphicsWindowResize(self, event):
+ size = self.GetSize()
+ for panel in self.GraphicPanels:
+ panel_size = panel.GetSize()
+ if (isinstance(panel, DebugVariableGraphicViewer) and
+ panel.GraphType == GRAPH_ORTHOGONAL and
+ panel_size.width == panel_size.height):
+ panel.SetCanvasHeight(size.width)
+ self.RefreshGraphicsWindowScrollbars()
+ self.GraphicsSizer.Layout()
+ event.Skip()
+
+ def OnGraphicsWindowMouseWheel(self, event):
+ if self.VetoScrollEvent:
+ self.VetoScrollEvent = False
+ else:
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,1410 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+from time import time as gettime
+import numpy
+
+import wx
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from mpl_toolkits.mplot3d import Axes3D
+
+from editors.DebugViewer import REFRESH_PERIOD
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableViewer import *
+from GraphButton import GraphButton
+
+# Graph variable display type
+GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
+
+# Canvas height
+[SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
+
+CANVAS_BORDER = (20., 10.) # Border height on at bottom and top of graph
+CANVAS_PADDING = 8.5 # Border inside graph where no label is drawn
+VALUE_LABEL_HEIGHT = 17. # Height of variable label in graph
+AXES_LABEL_HEIGHT = 12.75 # Height of variable value in graph
+
+# Colors used cyclically for graph curves
+COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
+# Color for graph cursor
+CURSOR_COLOR = '#800080'
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Viewer Helpers
+#-------------------------------------------------------------------------------
+
+def merge_ranges(ranges):
+ """
+ Merge variables data range in a list to return a range of minimal min range
+ value and maximal max range value extended of 10% for keeping a padding
+ around graph in canvas
+ @param ranges: [(range_min_value, range_max_value),...]
+ @return: merged_range_min_value, merged_range_max_value
+ """
+ # Get minimal and maximal range value
+ min_value = max_value = None
+ for range_min, range_max in ranges:
+ # Update minimal range value
+ if min_value is None:
+ min_value = range_min
+ elif range_min is not None:
+ min_value = min(min_value, range_min)
+
+ # Update maximal range value
+ if max_value is None:
+ max_value = range_max
+ elif range_min is not None:
+ max_value = max(max_value, range_max)
+
+ # Calculate range center and width if at least one valid range is defined
+ if min_value is not None and max_value is not None:
+ center = (min_value + max_value) / 2.
+ range_size = max(1.0, max_value - min_value)
+
+ # Set default center and with if no valid range is defined
+ else:
+ center = 0.5
+ range_size = 1.0
+
+ # Return range expended from 10 %
+ return center - range_size * 0.55, center + range_size * 0.55
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Viewer Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Graphic
+Viewer
+"""
+
+class DebugVariableGraphicDropTarget(wx.TextDropTarget):
+
+ def __init__(self, parent, window):
+ """
+ Constructor
+ @param parent: Reference to Debug Variable Graphic Viewer
+ @param window: Reference to the Debug Variable Panel
+ """
+ wx.TextDropTarget.__init__(self)
+ self.ParentControl = parent
+ self.ParentWindow = window
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Graphic Viewer and Debug Variable
+ # Panel
+ self.ParentControl = None
+ self.ParentWindow = None
+
+ def OnDragOver(self, x, y, d):
+ """
+ Function called when mouse is dragged over Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param d: Suggested default for return value
+ """
+ # Signal parent that mouse is dragged over
+ self.ParentControl.OnMouseDragging(x, y)
+
+ return wx.TextDropTarget.OnDragOver(self, x, y, d)
+
+ def OnDropText(self, x, y, data):
+ """
+ Function called when mouse is released in Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param data: Text associated to drag'n drop
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+
+ message = None
+
+ # Check that data is valid regarding DebugVariablePanel
+ try:
+ values = eval(data)
+ if not isinstance(values, TupleType):
+ raise ValueError
+ except:
+ message = _("Invalid value \"%s\" for debug variable")%data
+ values = None
+
+ # Display message if data is invalid
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ # Data contain a reference to a variable to debug
+ elif values[1] == "debug":
+ target_idx = self.ParentControl.GetIndex()
+
+ # If mouse is dropped in graph canvas bounding box and graph is
+ # not 3D canvas, graphs will be merged
+ rect = self.ParentControl.GetAxesBoundingBox()
+ if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
+ # Default merge type is parallel
+ merge_type = GRAPH_PARALLEL
+
+ # If mouse is dropped in left part of graph canvas, graph
+ # wall be merged orthogonally
+ merge_rect = wx.Rect(rect.x, rect.y,
+ rect.width / 2., rect.height)
+ if merge_rect.InsideXY(x, y):
+ merge_type = GRAPH_ORTHOGONAL
+
+ # Merge graphs
+ wx.CallAfter(self.ParentWindow.MergeGraphs,
+ values[0], target_idx,
+ merge_type, force=True)
+
+ else:
+ width, height = self.ParentControl.GetSize()
+
+ # Get Before which Viewer the variable has to be moved or added
+ # according to the position of mouse in Viewer.
+ if y > height / 2:
+ target_idx += 1
+
+ # Drag'n Drop is an internal is an internal move inside Debug
+ # Variable Panel
+ if len(values) > 2 and values[2] == "move":
+ self.ParentWindow.MoveValue(values[0],
+ target_idx)
+
+ # Drag'n Drop was initiated by another control of Beremiz
+ else:
+ self.ParentWindow.InsertValue(values[0],
+ target_idx,
+ force=True)
+
+ def OnLeave(self):
+ """
+ Function called when mouse is leave Drop Target
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+ return wx.TextDropTarget.OnLeave(self)
+
+ def ShowMessage(self, message):
+ """
+ Show error message in Error Dialog
+ @param message: Error message to display
+ """
+ dialog = wx.MessageDialog(self.ParentWindow,
+ message,
+ _("Error"),
+ wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a graphs
+"""
+
+class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
+
+ def __init__(self, parent, window, items, graph_type):
+ """
+ Constructor
+ @param parent: Parent wx.Window of DebugVariableText
+ @param window: Reference to the Debug Variable Panel
+ @param items: List of DebugVariableItem displayed by Viewer
+ @param graph_type: Graph display type (Parallel or orthogonal)
+ """
+ DebugVariableViewer.__init__(self, window, items)
+
+ self.GraphType = graph_type # Graph type display
+ self.CursorTick = None # Tick of the graph cursor
+
+ # Mouse position when start dragging
+ self.MouseStartPos = None
+ # Tick when moving tick start
+ self.StartCursorTick = None
+ # Canvas size when starting to resize canvas
+ self.CanvasStartSize = None
+
+ # List of current displayed contextual buttons
+ self.ContextualButtons = []
+ # Reference to item for which contextual buttons was displayed
+ self.ContextualButtonsItem = None
+
+ # Flag indicating that zoom fit current displayed data range or whole
+ # data range if False
+ self.ZoomFit = False
+
+ # Create figure for drawing graphs
+ self.Figure = matplotlib.figure.Figure(facecolor='w')
+ # Defined border around figure in canvas
+ self.Figure.subplotpars.update(top=0.95, left=0.1,
+ bottom=0.1, right=0.95)
+
+ FigureCanvas.__init__(self, parent, -1, self.Figure)
+ self.SetWindowStyle(wx.WANTS_CHARS)
+ self.SetBackgroundColour(wx.WHITE)
+
+ # Bind wx events
+ self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
+ self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+ self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
+ self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+
+ # Set canvas min size
+ canvas_size = self.GetCanvasMinSize()
+ self.SetMinSize(canvas_size)
+
+ # Define Viewer drop target
+ self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
+
+ # Connect matplotlib events
+ self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
+ self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
+ self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
+ self.mpl_connect('scroll_event', self.OnCanvasScroll)
+
+ # Add buttons for zooming on current displayed data range
+ self.Buttons.append(
+ GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
+
+ # Add buttons for changing canvas size with predefined height
+ for size, bitmap in zip(
+ [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
+ ["minimize_graph", "middle_graph", "maximize_graph"]):
+ self.Buttons.append(
+ GraphButton(0, 0, bitmap,
+ self.GetOnChangeSizeButton(size)))
+
+ # Add buttons for exporting graph values to clipboard and close graph
+ for bitmap, callback in [
+ ("export_graph_mini", self.OnExportGraphButton),
+ ("delete_graph", self.OnCloseButton)]:
+ self.Buttons.append(GraphButton(0, 0, bitmap, callback))
+
+ # Update graphs elements
+ self.ResetGraphics()
+ self.RefreshLabelsPosition(canvas_size.height)
+
+ def AddItem(self, item):
+ """
+ Add an item to the list of items displayed by Viewer
+ @param item: Item to add to the list
+ """
+ DebugVariableViewer.AddItem(self, item)
+ self.ResetGraphics()
+
+ def RemoveItem(self, item):
+ """
+ Remove an item from the list of items displayed by Viewer
+ @param item: Item to remove from the list
+ """
+ DebugVariableViewer.RemoveItem(self, item)
+
+ # If list of items is not empty
+ if not self.ItemsIsEmpty():
+ # Return to parallel graph if there is only one item
+ # especially if it's actually orthogonal
+ if len(self.Items) == 1:
+ self.GraphType = GRAPH_PARALLEL
+ self.ResetGraphics()
+
+ def SetCursorTick(self, cursor_tick):
+ """
+ Set cursor tick
+ @param cursor_tick: Cursor tick
+ """
+ self.CursorTick = cursor_tick
+
+ def SetZoomFit(self, zoom_fit):
+ """
+ Set flag indicating that zoom fit current displayed data range
+ @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
+ """
+ # Flag is different from the actual one
+ if zoom_fit != self.ZoomFit:
+ # Save new flag value
+ self.ZoomFit = zoom_fit
+
+ # Update button for zoom fit bitmap
+ self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
+
+ # Refresh canvas
+ self.RefreshViewer()
+
+ def SubscribeAllDataConsumers(self):
+ """
+ Function that unsubscribe and remove every item that store values of
+ a variable that doesn't exist in PLC anymore
+ """
+ DebugVariableViewer.SubscribeAllDataConsumers(self)
+
+ # Graph still have data to display
+ if not self.ItemsIsEmpty():
+ # Reset flag indicating that zoom fit current displayed data range
+ self.SetZoomFit(False)
+
+ self.ResetGraphics()
+
+ def Is3DCanvas(self):
+ """
+ Return if Viewer is a 3D canvas
+ @return: True if Viewer is a 3D canvas
+ """
+ return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
+
+ def GetButtons(self):
+ """
+ Return list of buttons defined in Viewer
+ @return: List of buttons
+ """
+ # Add contextual buttons to default buttons
+ return self.Buttons + self.ContextualButtons
+
+ def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
+ """
+ Show contextual menu for item aside a label of this item defined
+ by the bounding box of label in figure
+ @param item: Item for which contextual is shown
+ @param rect: Bounding box of label aside which drawing menu
+ @param direction: Direction in which buttons must be drawn
+ """
+ # Return immediately if contextual menu for item is already shown
+ if self.ContextualButtonsItem == item:
+ return
+
+ # Close already shown contextual menu
+ self.DismissContextualButtons()
+
+ # Save item for which contextual menu is shown
+ self.ContextualButtonsItem = item
+
+ # If item variable is forced, add button for release variable to
+ # contextual menu
+ if self.ContextualButtonsItem.IsForced():
+ self.ContextualButtons.append(
+ GraphButton(0, 0, "release", self.OnReleaseItemButton))
+
+ # Add other buttons to contextual menu
+ for bitmap, callback in [
+ ("force", self.OnForceItemButton),
+ ("export_graph_mini", self.OnExportItemGraphButton),
+ ("delete_graph", self.OnRemoveItemButton)]:
+ self.ContextualButtons.append(
+ GraphButton(0, 0, bitmap, callback))
+
+ # If buttons are shown at left side or upper side of rect, positions
+ # will be set in reverse order
+ buttons = self.ContextualButtons[:]
+ if direction in [wx.TOP, wx.LEFT]:
+ buttons.reverse()
+
+ # Set contextual menu buttons position aside rect depending on
+ # direction given
+ offset = 0
+ for button in buttons:
+ w, h = button.GetSize()
+ if direction in [wx.LEFT, wx.RIGHT]:
+ x = rect.x + (- w - offset
+ if direction == wx.LEFT
+ else rect.width + offset)
+ y = rect.y + (rect.height - h) / 2
+ offset += w
+ else:
+ x = rect.x + (rect.width - w ) / 2
+ y = rect.y + (- h - offset
+ if direction == wx.TOP
+ else rect.height + offset)
+ offset += h
+ button.SetPosition(x, y)
+ button.Show()
+
+ # Refresh canvas
+ self.ParentWindow.ForceRefresh()
+
+ def DismissContextualButtons(self):
+ """
+ Close current shown contextual menu
+ """
+ # Return immediately if no contextual menu is shown
+ if self.ContextualButtonsItem is None:
+ return
+
+ # Reset variables corresponding to contextual menu
+ self.ContextualButtonsItem = None
+ self.ContextualButtons = []
+
+ # Refresh canvas
+ self.ParentWindow.ForceRefresh()
+
+ def IsOverContextualButton(self, x, y):
+ """
+ Return if point is over one contextual button of Viewer
+ @param x: X coordinate of point
+ @param y: Y coordinate of point
+ @return: contextual button where point is over
+ """
+ for button in self.ContextualButtons:
+ if button.HitTest(x, y):
+ return button
+ return None
+
+ def ExportGraph(self, item=None):
+ """
+ Export item(s) data to clipboard in CSV format
+ @param item: Item from which data to export, all items if None
+ (default None)
+ """
+ self.ParentWindow.CopyDataToClipboard(
+ [(item, [entry for entry in item.GetData()])
+ for item in (self.Items
+ if item is None
+ else [item])])
+
+ def OnZoomFitButton(self):
+ """
+ Function called when Viewer Zoom Fit button is pressed
+ """
+ # Toggle zoom fit flag value
+ self.SetZoomFit(not self.ZoomFit)
+
+ def GetOnChangeSizeButton(self, height):
+ """
+ Function that generate callback function for change Viewer height to
+ pre-defined height button
+ @param height: Height that change Viewer to
+ @return: callback function
+ """
+ def OnChangeSizeButton():
+ self.SetCanvasHeight(height)
+ return OnChangeSizeButton
+
+ def OnExportGraphButton(self):
+ """
+ Function called when Viewer Export button is pressed
+ """
+ # Export data of every item in Viewer
+ self.ExportGraph()
+
+ def OnForceItemButton(self):
+ """
+ Function called when contextual menu Force button is pressed
+ """
+ # Open dialog for forcing item variable value
+ self.ForceValue(self.ContextualButtonsItem)
+ # Close contextual menu
+ self.DismissContextualButtons()
+
+ def OnReleaseItemButton(self):
+ """
+ Function called when contextual menu Release button is pressed
+ """
+ # Release item variable value
+ self.ReleaseValue(self.ContextualButtonsItem)
+ # Close contextual menu
+ self.DismissContextualButtons()
+
+ def OnExportItemGraphButton(self):
+ """
+ Function called when contextual menu Export button is pressed
+ """
+ # Export data of item variable
+ self.ExportGraph(self.ContextualButtonsItem)
+ # Close contextual menu
+ self.DismissContextualButtons()
+
+ def OnRemoveItemButton(self):
+ """
+ Function called when contextual menu Remove button is pressed
+ """
+ # Remove item from Viewer
+ wx.CallAfter(self.ParentWindow.DeleteValue, self,
+ self.ContextualButtonsItem)
+ # Close contextual menu
+ self.DismissContextualButtons()
+
+ def HandleCursorMove(self, event):
+ """
+ Update Cursor position according to mouse position and graph type
+ @param event: Mouse event
+ """
+ start_tick, end_tick = self.ParentWindow.GetRange()
+ cursor_tick = None
+ items = self.ItemsDict.values()
+
+ # Graph is orthogonal
+ if self.GraphType == GRAPH_ORTHOGONAL:
+ # Extract items data displayed in canvas figure
+ start_tick = max(start_tick, self.GetItemsMinCommonTick())
+ end_tick = max(end_tick, start_tick)
+ x_data = items[0].GetData(start_tick, end_tick)
+ y_data = items[1].GetData(start_tick, end_tick)
+
+ # Search for the nearest point from mouse position
+ if len(x_data) > 0 and len(y_data) > 0:
+ length = min(len(x_data), len(y_data))
+ d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
+ (y_data[:length,1]-event.ydata) ** 2)
+
+ # Set cursor tick to the tick of this point
+ cursor_tick = x_data[numpy.argmin(d), 0]
+
+ # Graph is parallel
+ else:
+ # Extract items tick
+ data = items[0].GetData(start_tick, end_tick)
+
+ # Search for point that tick is the nearest from mouse X position
+ # and set cursor tick to the tick of this point
+ if len(data) > 0:
+ cursor_tick = data[numpy.argmin(
+ numpy.abs(data[:,0] - event.xdata)), 0]
+
+ # Update cursor tick
+ if cursor_tick is not None:
+ self.ParentWindow.SetCursorTick(cursor_tick)
+
+ def OnCanvasButtonPressed(self, event):
+ """
+ Function called when a button of mouse is pressed
+ @param event: Mouse event
+ """
+ # Get mouse position, graph Y coordinate is inverted in matplotlib
+ # comparing to wx
+ width, height = self.GetSize()
+ x, y = event.x, height - event.y
+
+ # Return immediately if mouse is over a button
+ if self.IsOverButton(x, y):
+ return
+
+ # Mouse was clicked inside graph figure
+ if event.inaxes == self.Axes:
+
+ # Find if it was on an item label
+ item_idx = None
+ # Check every label paired with corresponding item
+ for i, t in ([pair for pair in enumerate(self.AxesLabels)] +
+ [pair for pair in enumerate(self.Labels)]):
+ # Get label bounding box
+ (x0, y0), (x1, y1) = t.get_window_extent().get_points()
+ rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
+ # Check if mouse was over label
+ if rect.InsideXY(x, y):
+ item_idx = i
+ break
+
+ # If an item label have been clicked
+ if item_idx is not None:
+ # Hide buttons and contextual buttons
+ self.ShowButtons(False)
+ self.DismissContextualButtons()
+
+ # Start a drag'n drop from mouse position in wx coordinate of
+ # parent
+ xw, yw = self.GetPosition()
+ self.ParentWindow.StartDragNDrop(self,
+ self.ItemsDict.values()[item_idx],
+ x + xw, y + yw, # Current mouse position
+ x + xw, y + yw) # Mouse position when button was clicked
+
+ # Don't handle mouse button if canvas is 3D and let matplotlib do
+ # the default behavior (rotate 3D axes)
+ elif not self.Is3DCanvas():
+ # Save mouse position when clicked
+ self.MouseStartPos = wx.Point(x, y)
+
+ # Mouse button was left button, start moving cursor
+ if event.button == 1:
+ # Save current tick in case a drag'n drop is initiate to
+ # restore it
+ self.StartCursorTick = self.CursorTick
+
+ self.HandleCursorMove(event)
+
+ # Mouse button is middle button and graph is parallel, start
+ # moving graph along X coordinate (tick)
+ elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
+ self.StartCursorTick = self.ParentWindow.GetRange()[0]
+
+ # Mouse was clicked outside graph figure and over resize highlight with
+ # left button, start resizing Viewer
+ elif event.button == 1 and event.y <= 5:
+ self.MouseStartPos = wx.Point(x, y)
+ self.CanvasStartSize = height
+
+ def OnCanvasButtonReleased(self, event):
+ """
+ Function called when a button of mouse is released
+ @param event: Mouse event
+ """
+ # If a drag'n drop is in progress, stop it
+ if self.ParentWindow.IsDragging():
+ width, height = self.GetSize()
+ xw, yw = self.GetPosition()
+ item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
+ # Give mouse position in wx coordinate of parent
+ self.ParentWindow.StopDragNDrop(item.GetVariable(),
+ xw + event.x, yw + height - event.y)
+
+ else:
+ # Reset any move in progress
+ self.MouseStartPos = None
+ self.CanvasStartSize = None
+
+ # Handle button under mouse if it exist
+ width, height = self.GetSize()
+ self.HandleButton(event.x, height - event.y)
+
+ def OnCanvasMotion(self, event):
+ """
+ Function called when a button of mouse is moved over Viewer
+ @param event: Mouse event
+ """
+ width, height = self.GetSize()
+
+ # If a drag'n drop is in progress, move canvas dragged
+ if self.ParentWindow.IsDragging():
+ xw, yw = self.GetPosition()
+ # Give mouse position in wx coordinate of parent
+ self.ParentWindow.MoveDragNDrop(
+ xw + event.x,
+ yw + height - event.y)
+
+ # If a Viewer resize is in progress, change Viewer size
+ elif event.button == 1 and self.CanvasStartSize is not None:
+ width, height = self.GetSize()
+ self.SetCanvasHeight(
+ self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
+
+ # If no button is pressed, show or hide contextual buttons or resize
+ # highlight
+ elif event.button is None:
+ # Compute direction for items label according graph type
+ if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
+ directions = [wx.RIGHT] * len(self.AxesLabels) + \
+ [wx.LEFT] * len(self.Labels)
+ elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
+ directions = [wx.RIGHT, wx.TOP, # Directions for AxesLabels
+ wx.LEFT, wx.BOTTOM] # Directions for Labels
+ else: # Graph is orthogonal in 3D
+ directions = [wx.LEFT] * len(self.Labels)
+
+ # Find if mouse is over an item label
+ item_idx = None
+ menu_direction = None
+ for (i, t), dir in zip(
+ [pair for pair in enumerate(self.AxesLabels)] +
+ [pair for pair in enumerate(self.Labels)],
+ directions):
+ # Check every label paired with corresponding item
+ (x0, y0), (x1, y1) = t.get_window_extent().get_points()
+ rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
+ # Check if mouse was over label
+ if rect.InsideXY(event.x, height - event.y):
+ item_idx = i
+ menu_direction = dir
+ break
+
+ # If mouse is over an item label,
+ if item_idx is not None:
+ self.PopupContextualButtons(
+ self.ItemsDict.values()[item_idx],
+ rect, menu_direction)
+ return
+
+ # If mouse isn't over a contextual menu, hide the current shown one
+ # if it exists
+ if self.IsOverContextualButton(event.x, height - event.y) is None:
+ self.DismissContextualButtons()
+
+ # Update resize highlight
+ if event.y <= 5:
+ if self.SetHighlight(HIGHLIGHT_RESIZE):
+ self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
+ self.ParentWindow.ForceRefresh()
+ else:
+ if self.SetHighlight(HIGHLIGHT_NONE):
+ self.SetCursor(wx.NullCursor)
+ self.ParentWindow.ForceRefresh()
+
+ # Handle buttons if canvas is not 3D
+ elif not self.Is3DCanvas():
+
+ # If left button is pressed
+ if event.button == 1:
+
+ # Mouse is inside graph figure
+ if event.inaxes == self.Axes:
+
+ # If a cursor move is in progress, update cursor position
+ if self.MouseStartPos is not None:
+ self.HandleCursorMove(event)
+
+ # Mouse is outside graph figure, cursor move is in progress and
+ # there is only one item in Viewer, start a drag'n drop
+ elif self.MouseStartPos is not None and len(self.Items) == 1:
+ xw, yw = self.GetPosition()
+ self.ParentWindow.SetCursorTick(self.StartCursorTick)
+ self.ParentWindow.StartDragNDrop(self,
+ self.ItemsDict.values()[0],
+ # Current mouse position
+ event.x + xw, height - event.y + yw,
+ # Mouse position when button was clicked
+ self.MouseStartPos.x + xw,
+ self.MouseStartPos.y + yw)
+
+ # If middle button is pressed and moving graph along X coordinate
+ # is in progress
+ elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
+ self.MouseStartPos is not None:
+ start_tick, end_tick = self.ParentWindow.GetRange()
+ rect = self.GetAxesBoundingBox()
+
+ # Move graph along X coordinate
+ self.ParentWindow.SetCanvasPosition(
+ self.StartCursorTick +
+ (self.MouseStartPos.x - event.x) *
+ (end_tick - start_tick) / rect.width)
+
+ def OnCanvasScroll(self, event):
+ """
+ Function called when a wheel mouse is use in Viewer
+ @param event: Mouse event
+ """
+ # Change X range of graphs if mouse is in canvas figure and ctrl is
+ # pressed
+ if event.inaxes is not None and event.guiEvent.ControlDown():
+
+ # Calculate position of fixed tick point according to graph type
+ # and mouse position
+ if self.GraphType == GRAPH_ORTHOGONAL:
+ start_tick, end_tick = self.ParentWindow.GetRange()
+ tick = (start_tick + end_tick) / 2.
+ else:
+ tick = event.xdata
+ self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
+
+ # Vetoing event to prevent parent panel to be scrolled
+ self.ParentWindow.VetoScrollEvent = True
+
+ def OnLeftDClick(self, event):
+ """
+ Function called when a left mouse button is double clicked
+ @param event: Mouse event
+ """
+ # Check that double click was done inside figure
+ pos = event.GetPosition()
+ rect = self.GetAxesBoundingBox()
+ if rect.InsideXY(pos.x, pos.y):
+ # Reset Cursor tick to value before starting clicking
+ self.ParentWindow.SetCursorTick(self.StartCursorTick)
+ # Toggle to text Viewer(s)
+ self.ParentWindow.ToggleViewerType(self)
+
+ else:
+ event.Skip()
+
+ # Cursor tick move for each arrow key
+ KEY_CURSOR_INCREMENT = {
+ wx.WXK_LEFT: -1,
+ wx.WXK_RIGHT: 1,
+ wx.WXK_UP: 10,
+ wx.WXK_DOWN: -10}
+
+ def OnKeyDown(self, event):
+ """
+ Function called when key is pressed
+ @param event: wx.KeyEvent
+ """
+ # If cursor is shown and arrow key is pressed, move cursor tick
+ if self.CursorTick is not None:
+ move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
+ if move is not None:
+ self.ParentWindow.MoveCursorTick(move)
+ event.Skip()
+
+ def OnLeave(self, event):
+ """
+ Function called when mouse leave Viewer
+ @param event: wx.MouseEvent
+ """
+ # If Viewer is not resizing, reset resize highlight
+ if self.CanvasStartSize is None:
+ self.SetHighlight(HIGHLIGHT_NONE)
+ self.SetCursor(wx.NullCursor)
+ DebugVariableViewer.OnLeave(self, event)
+ else:
+ event.Skip()
+
+ def GetCanvasMinSize(self):
+ """
+ Return the minimum size of Viewer so that all items label can be
+ displayed
+ @return: wx.Size containing Viewer minimum size
+ """
+ # The minimum height take in account the height of all items, padding
+ # inside figure and border around figure
+ return wx.Size(200,
+ CANVAS_BORDER[0] + CANVAS_BORDER[1] +
+ 2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
+
+ def SetCanvasHeight(self, height):
+ """
+ Set Viewer size checking that it respects Viewer minimum size
+ @param height: Viewer height
+ """
+ min_width, min_height = self.GetCanvasMinSize()
+ height = max(height, min_height)
+ self.SetMinSize(wx.Size(min_width, height))
+ self.RefreshLabelsPosition(height)
+ self.ParentWindow.RefreshGraphicsSizer()
+
+ def GetAxesBoundingBox(self, parent_coordinate=False):
+ """
+ Return figure bounding box in wx coordinate
+ @param parent_coordinate: True if use parent coordinate (default False)
+ """
+ # Calculate figure bounding box. Y coordinate is inverted in matplotlib
+ # figure comparing to wx panel
+ width, height = self.GetSize()
+ ax, ay, aw, ah = self.figure.gca().get_position().bounds
+ bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
+ aw * width + 2, ah * height + 1)
+
+ # If parent_coordinate, add Viewer position in parent
+ if parent_coordinate:
+ xw, yw = self.GetPosition()
+ bbox.x += xw
+ bbox.y += yw
+
+ return bbox
+
+ def RefreshHighlight(self, x, y):
+ """
+ Refresh Viewer highlight according to mouse position
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ """
+ width, height = self.GetSize()
+
+ # Mouse is over Viewer figure and graph is not 3D
+ bbox = self.GetAxesBoundingBox()
+ if bbox.InsideXY(x, y) and not self.Is3DCanvas():
+ rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
+ # Mouse is over Viewer left part of figure
+ if rect.InsideXY(x, y):
+ self.SetHighlight(HIGHLIGHT_LEFT)
+
+ # Mouse is over Viewer right part of figure
+ else:
+ self.SetHighlight(HIGHLIGHT_RIGHT)
+
+ # Mouse is over upper part of Viewer
+ elif y < height / 2:
+ # Viewer is upper one in Debug Variable Panel, show highlight
+ if self.ParentWindow.IsViewerFirst(self):
+ self.SetHighlight(HIGHLIGHT_BEFORE)
+
+ # Viewer is not the upper one, show highlight in previous one
+ # It prevents highlight to move when mouse leave one Viewer to
+ # another
+ else:
+ self.SetHighlight(HIGHLIGHT_NONE)
+ self.ParentWindow.HighlightPreviousViewer(self)
+
+ # Mouse is over lower part of Viewer
+ else:
+ self.SetHighlight(HIGHLIGHT_AFTER)
+
+ def OnAxesMotion(self, event):
+ """
+ Function overriding default function called when mouse is dragged for
+ rotating graph preventing refresh to be called too quickly
+ @param event: Mouse event
+ """
+ if self.Is3DCanvas():
+ # Call default function at most 10 times per second
+ current_time = gettime()
+ if current_time - self.LastMotionTime > REFRESH_PERIOD:
+ self.LastMotionTime = current_time
+ Axes3D._on_move(self.Axes, event)
+
+ def GetAddTextFunction(self):
+ """
+ Return function for adding text in figure according to graph type
+ @return: Function adding text to figure
+ """
+ text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text)
+ def AddText(*args, **kwargs):
+ args = [0, 0, ""]
+ kwargs["transform"] = self.Axes.transAxes
+ return text_func(*args, **kwargs)
+ return AddText
+
+ def ResetGraphics(self):
+ """
+ Reset figure and graphical elements displayed in it
+ Called any time list of items or graph type change
+ """
+ # Clear figure from any axes defined
+ self.Figure.clear()
+
+ # Add 3D projection if graph is in 3D
+ if self.Is3DCanvas():
+ self.Axes = self.Figure.gca(projection='3d')
+ self.Axes.set_color_cycle(['b'])
+
+ # Override function to prevent too much refresh when graph is
+ # rotated
+ self.LastMotionTime = gettime()
+ setattr(self.Axes, "_on_move", self.OnAxesMotion)
+
+ # Init graph mouse event so that graph can be rotated
+ self.Axes.mouse_init()
+
+ # Set size of Z axis labels
+ self.Axes.tick_params(axis='z', labelsize='small')
+
+ else:
+ self.Axes = self.Figure.gca()
+ self.Axes.set_color_cycle(COLOR_CYCLE)
+
+ # Set size of X and Y axis labels
+ self.Axes.tick_params(axis='x', labelsize='small')
+ self.Axes.tick_params(axis='y', labelsize='small')
+
+ # Init variables storing graphical elements added to figure
+ self.Plots = [] # List of curves
+ self.VLine = None # Vertical line for cursor
+ self.HLine = None # Horizontal line for cursor (only orthogonal 2D)
+ self.AxesLabels = [] # List of items variable path text label
+ self.Labels = [] # List of items text label
+
+ # Get function to add a text in figure according to graph type
+ add_text_func = self.GetAddTextFunction()
+
+ # Graph type is parallel or orthogonal in 3D
+ if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
+ num_item = len(self.Items)
+ for idx in xrange(num_item):
+
+ # Get color from color cycle (black if only one item)
+ color = ('k' if num_item == 1
+ else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
+
+ # In 3D graph items variable label are not displayed as text
+ # in figure, but as axis title
+ if not self.Is3DCanvas():
+ # Items variable labels are in figure upper left corner
+ self.AxesLabels.append(
+ add_text_func(size='small', color=color,
+ verticalalignment='top'))
+
+ # Items variable labels are in figure lower right corner
+ self.Labels.append(
+ add_text_func(size='large', color=color,
+ horizontalalignment='right'))
+
+ # Graph type is orthogonal in 2D
+ else:
+ # X coordinate labels are in figure lower side
+ self.AxesLabels.append(add_text_func(size='small'))
+ self.Labels.append(
+ add_text_func(size='large',
+ horizontalalignment='right'))
+
+ # Y coordinate labels are vertical and in figure left side
+ self.AxesLabels.append(
+ add_text_func(size='small', rotation='vertical',
+ verticalalignment='bottom'))
+ self.Labels.append(
+ add_text_func(size='large', rotation='vertical',
+ verticalalignment='top'))
+
+ # Refresh position of labels according to Viewer size
+ width, height = self.GetSize()
+ self.RefreshLabelsPosition(height)
+
+ def RefreshLabelsPosition(self, height):
+ """
+ Function called when mouse leave Viewer
+ @param event: wx.MouseEvent
+ """
+ # Figure position like text position in figure are expressed is ratio
+ # canvas size and figure size. As we want that border around figure and
+ # text position in figure don't change when canvas size change, we
+ # expressed border and text position in pixel on screen and apply the
+ # ratio calculated hereafter to get border and text position in
+ # matplotlib coordinate
+ canvas_ratio = 1. / height # Divide by canvas height in pixel
+ graph_ratio = 1. / (
+ (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
+ * height) # Divide by figure height in pixel
+
+ # Update position of figure (keeping up and bottom border the same
+ # size)
+ self.Figure.subplotpars.update(
+ top= 1.0 - CANVAS_BORDER[1] * canvas_ratio,
+ bottom= CANVAS_BORDER[0] * canvas_ratio)
+
+ # Update position of items labels
+ if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
+ num_item = len(self.Items)
+ for idx in xrange(num_item):
+
+ # In 3D graph items variable label are not displayed
+ if not self.Is3DCanvas():
+ # Items variable labels are in figure upper left corner
+ self.AxesLabels[idx].set_position(
+ (0.05,
+ 1.0 - (CANVAS_PADDING +
+ AXES_LABEL_HEIGHT * idx) * graph_ratio))
+
+ # Items variable labels are in figure lower right corner
+ self.Labels[idx].set_position(
+ (0.95,
+ CANVAS_PADDING * graph_ratio +
+ (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
+ else:
+ # X coordinate labels are in figure lower side
+ self.AxesLabels[0].set_position(
+ (0.1, CANVAS_PADDING * graph_ratio))
+ self.Labels[0].set_position(
+ (0.95, CANVAS_PADDING * graph_ratio))
+
+ # Y coordinate labels are vertical and in figure left side
+ self.AxesLabels[1].set_position(
+ (0.05, 2 * CANVAS_PADDING * graph_ratio))
+ self.Labels[1].set_position(
+ (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
+
+ # Update subplots
+ self.Figure.subplots_adjust()
+
+ def RefreshViewer(self, refresh_graphics=True):
+ """
+ Function called to refresh displayed by matplotlib canvas
+ @param refresh_graphics: Flag indicating that graphs have to be
+ refreshed (False: only label values have to be refreshed)
+ """
+ # Refresh graphs if needed
+ if refresh_graphics:
+ # Get tick range of values to display
+ start_tick, end_tick = self.ParentWindow.GetRange()
+
+ # Graph is parallel
+ if self.GraphType == GRAPH_PARALLEL:
+ # Init list of data range for each variable displayed
+ ranges = []
+
+ # Get data and range for each variable displayed
+ for idx, item in enumerate(self.Items):
+ data, min_value, max_value = item.GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Check that data is not empty
+ if data is not None:
+ # Add variable range to list of variable data range
+ ranges.append((min_value, max_value))
+
+ # Add plot to canvas if not yet created
+ if len(self.Plots) <= idx:
+ self.Plots.append(
+ self.Axes.plot(data[:, 0], data[:, 1])[0])
+
+ # Set data to already created plot in canvas
+ else:
+ self.Plots[idx].set_data(data[:, 0], data[:, 1])
+
+ # Get X and Y axis ranges
+ x_min, x_max = start_tick, end_tick
+ y_min, y_max = merge_ranges(ranges)
+
+ # Display cursor in canvas if a cursor tick is defined and it is
+ # include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
+
+ # Define a vertical line to display cursor position if no
+ # line is already defined
+ if self.VLine is None:
+ self.VLine = self.Axes.axvline(self.CursorTick,
+ color=CURSOR_COLOR)
+
+ # Set value of vertical line if already defined
+ else:
+ self.VLine.set_xdata((self.CursorTick, self.CursorTick))
+ self.VLine.set_visible(True)
+
+ # Hide vertical line if cursor tick is not defined or reset
+ elif self.VLine is not None:
+ self.VLine.set_visible(False)
+
+ # Graph is orthogonal
+ else:
+ # Update tick range, removing ticks that don't have a value for
+ # each variable
+ start_tick = max(start_tick, self.GetItemsMinCommonTick())
+ end_tick = max(end_tick, start_tick)
+ items = self.ItemsDict.values()
+
+ # Get data and range for first variable (X coordinate)
+ x_data, x_min, x_max = items[0].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+ # Get data and range for second variable (Y coordinate)
+ y_data, y_min, y_max = items[1].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Normalize X and Y coordinates value range
+ x_min, x_max = merge_ranges([(x_min, x_max)])
+ y_min, y_max = merge_ranges([(y_min, y_max)])
+
+ # Get X and Y coordinates for cursor if cursor tick is defined
+ if self.CursorTick is not None:
+ x_cursor, x_forced = items[0].GetValue(
+ self.CursorTick, raw=True)
+ y_cursor, y_forced = items[1].GetValue(
+ self.CursorTick, raw=True)
+
+ # Get common data length so that each value has an x and y
+ # coordinate
+ length = (min(len(x_data), len(y_data))
+ if x_data is not None and y_data is not None
+ else 0)
+
+ # Graph is orthogonal 2D
+ if len(self.Items) < 3:
+
+ # Check that x and y data are not empty
+ if x_data is not None and y_data is not None:
+
+ # Add plot to canvas if not yet created
+ if len(self.Plots) == 0:
+ self.Plots.append(
+ self.Axes.plot(x_data[:, 1][:length],
+ y_data[:, 1][:length])[0])
+
+ # Set data to already created plot in canvas
+ else:
+ self.Plots[0].set_data(
+ x_data[:, 1][:length],
+ y_data[:, 1][:length])
+
+ # Display cursor in canvas if a cursor tick is defined and it is
+ # include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
+
+ # Define a vertical line to display cursor x coordinate
+ # if no line is already defined
+ if self.VLine is None:
+ self.VLine = self.Axes.axvline(x_cursor,
+ color=CURSOR_COLOR)
+ # Set value of vertical line if already defined
+ else:
+ self.VLine.set_xdata((x_cursor, x_cursor))
+
+
+ # Define a horizontal line to display cursor y
+ # coordinate if no line is already defined
+ if self.HLine is None:
+ self.HLine = self.Axes.axhline(y_cursor,
+ color=CURSOR_COLOR)
+ # Set value of horizontal line if already defined
+ else:
+ self.HLine.set_ydata((y_cursor, y_cursor))
+
+ self.VLine.set_visible(True)
+ self.HLine.set_visible(True)
+
+ # Hide vertical and horizontal line if cursor tick is not
+ # defined or reset
+ else:
+ if self.VLine is not None:
+ self.VLine.set_visible(False)
+ if self.HLine is not None:
+ self.HLine.set_visible(False)
+
+ # Graph is orthogonal 3D
+ else:
+ # Remove all plots already defined in 3D canvas
+ while len(self.Axes.lines) > 0:
+ self.Axes.lines.pop()
+
+ # Get data and range for third variable (Z coordinate)
+ z_data, z_min, z_max = items[2].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Normalize Z coordinate value range
+ z_min, z_max = merge_ranges([(z_min, z_max)])
+
+ # Check that x, y and z data are not empty
+ if (x_data is not None and y_data is not None and
+ z_data is not None):
+
+ # Get common data length so that each value has an x, y
+ # and z coordinate
+ length = min(length, len(z_data))
+
+ # Add plot to canvas
+ self.Axes.plot(x_data[:, 1][:length],
+ y_data[:, 1][:length],
+ zs = z_data[:, 1][:length])
+
+ # Display cursor in canvas if a cursor tick is defined and
+ # it is include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
+
+ # Get Z coordinate for cursor
+ z_cursor, z_forced = items[2].GetValue(
+ self.CursorTick, raw=True)
+
+ # Add 3 lines parallel to x, y and z axis to display
+ # cursor position in 3D
+ for kwargs in [{"xs": numpy.array([x_min, x_max])},
+ {"ys": numpy.array([y_min, y_max])},
+ {"zs": numpy.array([z_min, z_max])}]:
+ for param, value in [
+ ("xs", numpy.array([x_cursor, x_cursor])),
+ ("ys", numpy.array([y_cursor, y_cursor])),
+ ("zs", numpy.array([z_cursor, z_cursor]))]:
+ kwargs.setdefault(param, value)
+ kwargs["color"] = CURSOR_COLOR
+ self.Axes.plot(**kwargs)
+
+ # Set Z axis limits
+ self.Axes.set_zlim(z_min, z_max)
+
+ # Set X and Y axis limits
+ self.Axes.set_xlim(x_min, x_max)
+ self.Axes.set_ylim(y_min, y_max)
+
+ # Get value and forced flag for each variable displayed in graph
+ # If cursor tick is not defined get value and flag of last received
+ # or get value and flag of variable at cursor tick
+ values, forced = apply(zip, [
+ (item.GetValue(self.CursorTick)
+ if self.CursorTick is not None
+ else (item.GetValue(), item.IsForced()))
+ for item in self.Items])
+
+ # Get path of each variable displayed simplified using panel variable
+ # name mask
+ labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask())
+ for item in self.Items]
+
+ # Get style for each variable according to
+ styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
+
+ # Graph is orthogonal 3D, set variables path as 3D axis label
+ if self.Is3DCanvas():
+ for idx, label_func in enumerate([self.Axes.set_xlabel,
+ self.Axes.set_ylabel,
+ self.Axes.set_zlabel]):
+ label_func(labels[idx], fontdict={'size': 'small',
+ 'color': COLOR_CYCLE[idx]})
+
+ # Graph is not orthogonal 3D, set variables path in axes labels
+ else:
+ for label, text in zip(self.AxesLabels, labels):
+ label.set_text(text)
+
+ # Set value label text and style according to value and forced flag for
+ # each variable displayed
+ for label, value, style in zip(self.Labels, values, styles):
+ label.set_text(value)
+ label.set_style(style)
+
+ # Refresh figure
+ self.draw()
+
+ def draw(self, drawDC=None):
+ """
+ Render the figure.
+ """
+ # Render figure using agg
+ FigureCanvasAgg.draw(self)
+
+ # Get bitmap of figure rendered
+ self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
+ self.bitmap.UseAlpha()
+
+ # Create DC for rendering graphics in bitmap
+ destDC = wx.MemoryDC()
+ destDC.SelectObject(self.bitmap)
+
+ # Get Graphics Context for DC, for anti-aliased and transparent
+ # rendering
+ destGC = wx.GCDC(destDC)
+
+ destGC.BeginDrawing()
+
+ # Get canvas size and figure bounding box in canvas
+ width, height = self.GetSize()
+ bbox = self.GetAxesBoundingBox()
+
+ # If highlight to display is resize, draw thick grey line at bottom
+ # side of canvas
+ if self.Highlight == HIGHLIGHT_RESIZE:
+ destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
+ destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
+ destGC.DrawRectangle(0, height - 5, width, 5)
+
+ # If highlight to display is merging graph, draw 50% transparent blue
+ # rectangle on left or right part of figure depending on highlight type
+ elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
+ destGC.SetPen(HIGHLIGHT_DROP_PEN)
+ destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
+
+ x_offset = (bbox.width / 2
+ if self.Highlight == HIGHLIGHT_RIGHT
+ else 0)
+ destGC.DrawRectangle(bbox.x + x_offset, bbox.y,
+ bbox.width / 2, bbox.height)
+
+ # Draw other Viewer common elements
+ self.DrawCommonElements(destGC, self.GetButtons())
+
+ destGC.EndDrawing()
+
+ self._isDrawn = True
+ self.gui_repaint(drawDC=drawDC)
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableItem.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,373 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import numpy
+import binascii
+
+from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR
+
+#-------------------------------------------------------------------------------
+# Constant for calculate CRC for string variables
+#-------------------------------------------------------------------------------
+
+STRING_CRC_SIZE = 8
+STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1
+
+#-------------------------------------------------------------------------------
+# Debug Variable Item Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that consumes debug values for PLC variable and
+stores received values for displaying them in graphic panel or table
+"""
+
+class DebugVariableItem(DebugDataConsumer):
+
+ def __init__(self, parent, variable, store_data=False):
+ """
+ Constructor
+ @param parent: Reference to debug variable panel
+ @param variable: Path of variable to debug
+ """
+ DebugDataConsumer.__init__(self)
+
+ self.Parent = parent
+ self.Variable = variable
+ self.StoreData = store_data
+
+ # Get Variable data type
+ self.RefreshVariableType()
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Reset reference to debug variable panel
+ self.Parent = None
+
+ def SetVariable(self, variable):
+ """
+ Set path of variable
+ @param variable: Path of variable to debug
+ """
+ if self.Parent is not None and self.Variable != variable:
+ # Store variable path
+ self.Variable = variable
+ # Get Variable data type
+ self.RefreshVariableType()
+
+ # Refresh debug variable panel
+ self.Parent.RefreshView()
+
+ def GetVariable(self, mask=None):
+ """
+ Return path of variable
+ @param mask: Mask to apply to variable path [var_name, '*',...]
+ @return: String containing masked variable path
+ """
+ # Apply mask to variable name
+ if mask is not None:
+ # '#' correspond to parts that are different between all items
+
+ # Extract variable path parts
+ parts = self.Variable.split('.')
+ # Adjust mask size to size of variable path
+ mask = mask + ['*'] * max(0, len(parts) - len(mask))
+
+ # Store previous mask
+ last = None
+ # Init masked variable path
+ variable = ""
+
+ for m, p in zip(mask, parts):
+ # Part is not masked, add part prefixed with '.' is previous
+ # wasn't masked
+ if m == '*':
+ variable += ('.' if last == '*' else '') + p
+
+ # Part is mask, add '..' if first or previous wasn't masked
+ elif last is None or last == '*':
+ variable += '..'
+
+ last = m
+
+ return variable
+
+ return self.Variable
+
+ def RefreshVariableType(self):
+ """
+ Get and store variable data type
+ """
+ self.VariableType = self.Parent.GetDataType(self.Variable)
+ # Reset data stored
+ self.ResetData()
+
+ def GetVariableType(self):
+ """
+ Return variable data type
+ @return: Variable data type
+ """
+ return self.VariableType
+
+ def GetData(self, start_tick=None, end_tick=None):
+ """
+ Return data stored contained in given range
+ @param start_tick: Start tick of given range (default None, first data)
+ @param end_tick: end tick of given range (default None, last data)
+ @return: Data as numpy.array([(tick, value, forced),...])
+ """
+ # Return immediately if data empty or none
+ if self.Data is None or len(self.Data) == 0:
+ return self.Data
+
+ # Find nearest data outside given range indexes
+ start_idx = (self.GetNearestData(start_tick, -1)
+ if start_tick is not None
+ else 0)
+ end_idx = (self.GetNearestData(end_tick, 1)
+ if end_tick is not None
+ else len(self.Data))
+
+ # Return data between indexes
+ return self.Data[start_idx:end_idx]
+
+ def GetRawValue(self, index):
+ """
+ Return raw value at given index for string variables
+ @param index: Variable value index
+ @return: Variable data type
+ """
+ if (self.VariableType in ["STRING", "WSTRING"] and
+ index < len(self.RawData)):
+ return self.RawData[index][0]
+ return ""
+
+ def GetValueRange(self):
+ """
+ Return variable value range
+ @return: (minimum_value, maximum_value)
+ """
+ return self.MinValue, self.MaxValue
+
+ def GetDataAndValueRange(self, start_tick, end_tick, full_range=True):
+ """
+ Return variable data and value range for a given tick range
+ @param start_tick: Start tick of given range (default None, first data)
+ @param end_tick: end tick of given range (default None, last data)
+ @param full_range: Value range is calculated on whole data (False: only
+ calculated on data in given range)
+ @return: (numpy.array([(tick, value, forced),...]),
+ min_value, max_value)
+ """
+ # Get data in given tick range
+ data = self.GetData(start_tick, end_tick)
+
+ # Value range is calculated on whole data
+ if full_range:
+ return data, self.MinValue, self.MaxValue
+
+ # Check that data in given range is not empty
+ values = data[:, 1]
+ if len(values) > 0:
+ # Return value range for data in given tick range
+ return (data,
+ data[numpy.argmin(values), 1],
+ data[numpy.argmax(values), 1])
+
+ # Return default values
+ return data, None, None
+
+ def ResetData(self):
+ """
+ Reset data stored when store data option enabled
+ """
+ if self.StoreData and self.IsNumVariable():
+ # Init table storing data
+ self.Data = numpy.array([]).reshape(0, 3)
+
+ # Init table storing raw data if variable is strin
+ self.RawData = ([]
+ if self.VariableType in ["STRING", "WSTRING"]
+ else None)
+
+ # Init Value range variables
+ self.MinValue = None
+ self.MaxValue = None
+
+ else:
+ self.Data = None
+
+ # Init variable value
+ self.Value = ""
+
+ def IsNumVariable(self):
+ """
+ Return if variable data type is numeric. String variables are
+ considered as numeric (string CRC)
+ @return: True if data type is numeric
+ """
+ return (self.Parent.IsNumType(self.VariableType) or
+ self.VariableType in ["STRING", "WSTRING"])
+
+ def NewValue(self, tick, value, forced=False):
+ """
+ Function called by debug thread when a new debug value is available
+ @param tick: PLC tick when value was captured
+ @param value: Value captured
+ @param forced: Forced flag, True if value is forced (default: False)
+ """
+ DebugDataConsumer.NewValue(self, tick, value, forced, raw=None)
+
+ if self.Data is not None:
+ # String data value is CRC
+ num_value = (binascii.crc32(value) & STRING_CRC_MASK
+ if self.VariableType in ["STRING", "WSTRING"]
+ else float(value))
+
+ # Update variable range values
+ self.MinValue = (min(self.MinValue, num_value)
+ if self.MinValue is not None
+ else num_value)
+ self.MaxValue = (max(self.MaxValue, num_value)
+ if self.MaxValue is not None
+ else num_value)
+
+ # Translate forced flag to float for storing in Data table
+ forced_value = float(forced)
+
+ # In the case of string variables, we store raw string value and
+ # forced flag in raw data table. Only changes in this two values
+ # are stored. Index to the corresponding raw value is stored in
+ # data third column
+ if self.VariableType in ["STRING", "WSTRING"]:
+ raw_data = (value, forced_value)
+ if len(self.RawData) == 0 or self.RawData[-1] != raw_data:
+ extra_value = len(self.RawData)
+ self.RawData.append(raw_data)
+ else:
+ extra_value = len(self.RawData) - 1
+
+ # In other case, data third column is forced flag
+ else:
+ extra_value = forced_value
+
+ # Add New data to stored data table
+ self.Data = numpy.append(self.Data,
+ [[float(tick), num_value, extra_value]], axis=0)
+
+ # Signal to debug variable panel to refresh
+ self.Parent.HasNewData = True
+
+ def SetForced(self, forced):
+ """
+ Update Forced flag
+ @param forced: New forced flag
+ """
+ # Store forced flag
+ if self.Forced != forced:
+ self.Forced = forced
+
+ # Signal to debug variable panel to refresh
+ self.Parent.HasNewData = True
+
+ def SetValue(self, value):
+ """
+ Update value.
+ @param value: New value
+ """
+ # Remove quote and double quote surrounding string value to get raw value
+ if (self.VariableType == "STRING" and
+ value.startswith("'") and value.endswith("'") or
+ self.VariableType == "WSTRING" and
+ value.startswith('"') and value.endswith('"')):
+ value = value[1:-1]
+
+ # Store variable value
+ if self.Value != value:
+ self.Value = value
+
+ # Signal to debug variable panel to refresh
+ self.Parent.HasNewData = True
+
+ def GetValue(self, tick=None, raw=False):
+ """
+ Return current value or value and forced flag for tick given
+ @return: Current value or value and forced flag
+ """
+ # If tick given and stored data option enabled
+ if tick is not None and self.Data is not None:
+
+ # Return current value and forced flag if data empty
+ if len(self.Data) == 0:
+ return self.Value, self.IsForced()
+
+ # Get index of nearest data from tick given
+ idx = self.GetNearestData(tick, 0)
+
+ # Get value and forced flag at given index
+ value, forced = self.RawData[int(self.Data[idx, 2])] \
+ if self.VariableType in ["STRING", "WSTRING"] \
+ else self.Data[idx, 1:3]
+
+ # Get raw value if asked
+ if not raw:
+ value = TYPE_TRANSLATOR.get(
+ self.VariableType, str)(value)
+
+ return value, forced
+
+ # Return raw value if asked
+ if not raw and self.VariableType in ["STRING", "WSTRING"]:
+ return TYPE_TRANSLATOR.get(
+ self.VariableType, str)(self.Value)
+ return self.Value
+
+ def GetNearestData(self, tick, adjust):
+ """
+ Return index of nearest data from tick given
+ @param tick: Tick where find nearest data
+ @param adjust: Constraint for data position from tick
+ -1: older than tick
+ 1: newer than tick
+ 0: doesn't matter
+ @return: Index of nearest data
+ """
+ # Return immediately if data is empty
+ if self.Data is None:
+ return None
+
+ # Extract data ticks
+ ticks = self.Data[:, 0]
+
+ # Get nearest data from tick
+ idx = numpy.argmin(abs(ticks - tick))
+
+ # Adjust data index according to constraint
+ if (adjust < 0 and ticks[idx] > tick and idx > 0 or
+ adjust > 0 and ticks[idx] < tick and idx < len(ticks)):
+ idx += adjust
+
+ return idx
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableTablePanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,509 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+
+import wx
+import wx.lib.buttons
+
+from editors.DebugViewer import DebugViewer
+from controls import CustomGrid, CustomTable
+from dialogs.ForceVariableDialog import ForceVariableDialog
+from util.BitmapLibrary import GetBitmap
+
+from DebugVariableItem import DebugVariableItem
+
+def GetDebugVariablesTableColnames():
+ """
+ Function returning table column header labels
+ @return: List of labels [col_label,...]
+ """
+ _ = lambda x : x
+ return [_("Variable"), _("Value")]
+
+#-------------------------------------------------------------------------------
+# Debug Variable Table Panel
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom table storing value to display in Debug Variable
+Table Panel grid
+"""
+
+class DebugVariableTable(CustomTable):
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ return self.GetValueByName(row, self.GetColLabelValue(col, False))
+ return ""
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.SetValueByName(row, self.GetColLabelValue(col, False), value)
+
+ def GetValueByName(self, row, colname):
+ if row < self.GetNumberRows():
+ if colname == "Variable":
+ return self.data[row].GetVariable()
+ elif colname == "Value":
+ return self.data[row].GetValue()
+ return ""
+
+ def SetValueByName(self, row, colname, value):
+ if row < self.GetNumberRows():
+ if colname == "Variable":
+ self.data[row].SetVariable(value)
+ elif colname == "Value":
+ self.data[row].SetValue(value)
+
+ def IsForced(self, row):
+ if row < self.GetNumberRows():
+ return self.data[row].IsForced()
+ return False
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Value":
+ if self.IsForced(row):
+ grid.SetCellTextColour(row, col, wx.BLUE)
+ else:
+ grid.SetCellTextColour(row, col, wx.BLACK)
+ grid.SetReadOnly(row, col, True)
+ self.ResizeRow(grid, row)
+
+ def RefreshValues(self, grid):
+ for col in xrange(self.GetNumberCols()):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Value":
+ for row in xrange(self.GetNumberRows()):
+ grid.SetCellValue(row, col, str(self.data[row].GetValue()))
+ if self.IsForced(row):
+ grid.SetCellTextColour(row, col, wx.BLUE)
+ else:
+ grid.SetCellTextColour(row, col, wx.BLACK)
+
+ def AppendItem(self, item):
+ self.data.append(item)
+
+ def InsertItem(self, idx, item):
+ self.data.insert(idx, item)
+
+ def RemoveItem(self, item):
+ self.data.remove(item)
+
+ def MoveItem(self, idx, new_idx):
+ self.data.insert(new_idx, self.data.pop(idx))
+
+ def GetItem(self, idx):
+ return self.data[idx]
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Table Panel Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Table Panel
+"""
+
+class DebugVariableTableDropTarget(wx.TextDropTarget):
+
+ def __init__(self, parent):
+ """
+ Constructor
+ @param window: Reference to the Debug Variable Panel
+ """
+ wx.TextDropTarget.__init__(self)
+ self.ParentWindow = parent
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Panel
+ self.ParentWindow = None
+
+ def OnDropText(self, x, y, data):
+ """
+ Function called when mouse is dragged over Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param data: Text associated to drag'n drop
+ """
+ message = None
+
+ # Check that data is valid regarding DebugVariablePanel
+ try:
+ values = eval(data)
+ if not isinstance(values, TupleType):
+ raise ValueError
+ except:
+ message = _("Invalid value \"%s\" for debug variable") % data
+ values = None
+
+ # Display message if data is invalid
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ # Data contain a reference to a variable to debug
+ elif values[1] == "debug":
+ grid = self.ParentWindow.VariablesGrid
+
+ # Get row where variable was dropped
+ x, y = grid.CalcUnscrolledPosition(x, y)
+ row = grid.YToRow(y - grid.GetColLabelSize())
+
+ # If no row found add variable at table end
+ if row == wx.NOT_FOUND:
+ row = self.ParentWindow.Table.GetNumberRows()
+
+ # Add variable to table
+ self.ParentWindow.InsertValue(values[0], row, force=True)
+
+ def ShowMessage(self, message):
+ """
+ Show error message in Error Dialog
+ @param message: Error message to display
+ """
+ dialog = wx.MessageDialog(self.ParentWindow,
+ message,
+ _("Error"),
+ wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Table Panel
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a panel displaying debug variable values in a table
+"""
+
+class DebugVariableTablePanel(wx.Panel, DebugViewer):
+
+ def __init__(self, parent, producer, window):
+ """
+ Constructor
+ @param parent: Reference to the parent wx.Window
+ @param producer: Object receiving debug value and dispatching them to
+ consumers
+ @param window: Reference to Beremiz frame
+ """
+ wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
+
+ # Save Reference to Beremiz frame
+ self.ParentWindow = window
+
+ # Variable storing flag indicating that variable displayed in table
+ # received new value and then table need to be refreshed
+ self.HasNewData = False
+
+ DebugViewer.__init__(self, producer, True)
+
+ # Construction of window layout by creating controls and sizers
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(button_sizer, border=5,
+ flag=wx.ALIGN_RIGHT|wx.ALL)
+
+ # Creation of buttons for navigating in table
+
+ for name, bitmap, help in [
+ ("DeleteButton", "remove_element", _("Remove debug variable")),
+ ("UpButton", "up", _("Move debug variable up")),
+ ("DownButton", "down", _("Move debug variable down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self,
+ bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+
+ # Creation of grid and associated table
+
+ self.VariablesGrid = CustomGrid(self,
+ size=wx.Size(-1, 150), style=wx.VSCROLL)
+ # Define grid drop target
+ self.VariablesGrid.SetDropTarget(DebugVariableTableDropTarget(self))
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK,
+ self.OnVariablesGridCellRightClick)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
+ self.OnVariablesGridCellLeftClick)
+ main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+
+ self.Table = DebugVariableTable(self, [],
+ GetDebugVariablesTableColnames())
+ self.VariablesGrid.SetTable(self.Table)
+ self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
+ "Up": self.UpButton,
+ "Down": self.DownButton})
+
+ # Definition of function associated to navigation buttons
+
+ def _AddVariable(new_row):
+ return self.VariablesGrid.GetGridCursorRow()
+ setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+
+ def _DeleteVariable(row):
+ item = self.Table.GetItem(row)
+ self.RemoveDataConsumer(item)
+ self.Table.RemoveItem(item)
+ self.RefreshView()
+ setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+
+ def _MoveVariable(row, move):
+ new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
+ if new_row != row:
+ self.Table.MoveItem(row, new_row)
+ self.RefreshView()
+ return new_row
+ setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+
+ # Initialization of grid layout
+
+ self.VariablesGrid.SetRowLabelSize(0)
+
+ self.GridColSizes = [200, 100]
+
+ for col in range(self.Table.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
+ self.VariablesGrid.SetColAttr(col, attr)
+ self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
+
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+
+ self.SetSizer(main_sizer)
+
+ def RefreshNewData(self, *args, **kwargs):
+ """
+ Called to refresh Table according to values received by variables
+ Can receive any parameters (not used here)
+ """
+ # Refresh 'Value' column of table if new data have been received since
+ # last refresh
+ if self.HasNewData:
+ self.HasNewData = False
+ self.RefreshView(only_values=True)
+ DebugViewer.RefreshNewData(self, *args, **kwargs)
+
+ def RefreshView(self, only_values=False):
+ """
+ Function refreshing table layout and values
+ @param only_values: True if only 'Value' column need to be updated
+ """
+ # Block refresh until table layout and values are completely updated
+ self.Freeze()
+
+ # Update only 'value' column from table
+ if only_values:
+ self.Table.RefreshValues(self.VariablesGrid)
+
+ # Update complete table layout refreshing table navigation buttons
+ # state according to
+ else:
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+
+ self.Thaw()
+
+ def ResetView(self):
+ """
+ Function removing all variables denugged from table
+ @param only_values: True if only 'Value' column need to be updated
+ """
+ # Unsubscribe all variables debugged
+ self.UnsubscribeAllDataConsumers()
+
+ # Clear table content
+ self.Table.Empty()
+
+ # Update table layout
+ self.Freeze()
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+ self.Thaw()
+
+ def SubscribeAllDataConsumers(self):
+ """
+ Function refreshing table layout and values
+ @param only_values: True if only 'Value' column need to be updated
+ """
+ DebugViewer.SubscribeAllDataConsumers(self)
+
+ # Navigate through variable displayed in table, removing those that
+ # doesn't exist anymore in PLC
+ for item in self.Table.GetData()[:]:
+ iec_path = item.GetVariable()
+ if self.GetDataType(iec_path) is None:
+ self.RemoveDataConsumer(item)
+ self.Table.RemoveItem(idx)
+ else:
+ self.AddDataConsumer(iec_path.upper(), item)
+ item.RefreshVariableType()
+
+ # Update table layout
+ self.Freeze()
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+ self.Thaw()
+
+ def GetForceVariableMenuFunction(self, item):
+ """
+ Function returning callback function for contextual menu 'Force' item
+ @param item: Debug Variable item where contextual menu was opened
+ @return: Callback function
+ """
+ def ForceVariableFunction(event):
+ # Get variable path and data type
+ iec_path = item.GetVariable()
+ iec_type = self.GetDataType(iec_path)
+
+ # Return immediately if not data type found
+ if iec_type is None:
+ return
+
+ # Open dialog for entering value to force variable
+ dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
+
+ # If valid value entered, force variable
+ if dialog.ShowModal() == wx.ID_OK:
+ self.ForceDataValue(iec_path.upper(), dialog.GetValue())
+
+ return ForceVariableFunction
+
+ def GetReleaseVariableMenuFunction(self, iec_path):
+ """
+ Function returning callback function for contextual menu 'Release' item
+ @param iec_path: Debug Variable path where contextual menu was opened
+ @return: Callback function
+ """
+ def ReleaseVariableFunction(event):
+ # Release variable
+ self.ReleaseDataValue(iec_path)
+ return ReleaseVariableFunction
+
+ def OnVariablesGridCellLeftClick(self, event):
+ """
+ Called when left mouse button is pressed on a table cell
+ @param event: wx.grid.GridEvent
+ """
+ # Initiate a drag and drop if the cell clicked was in 'Variable' column
+ if self.Table.GetColLabelValue(event.GetCol(), False) == "Variable":
+ item = self.Table.GetItem(event.GetRow())
+ data = wx.TextDataObject(str((item.GetVariable(), "debug")))
+ dragSource = wx.DropSource(self.VariablesGrid)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+
+ event.Skip()
+
+ def OnVariablesGridCellRightClick(self, event):
+ """
+ Called when right mouse button is pressed on a table cell
+ @param event: wx.grid.GridEvent
+ """
+ # Open a contextual menu if the cell clicked was in 'Value' column
+ if self.Table.GetColLabelValue(event.GetCol(), False) == "Value":
+ row = event.GetRow()
+
+ # Get variable path
+ item = self.Table.GetItem(row)
+ iec_path = item.GetVariable().upper()
+
+ # Create contextual menu
+ menu = wx.Menu(title='')
+
+ # Add menu items
+ for text, enable, callback in [
+ (_("Force value"), True,
+ self.GetForceVariableMenuFunction(item)),
+ # Release menu item is enabled only if variable is forced
+ (_("Release value"), self.Table.IsForced(row),
+ self.GetReleaseVariableMenuFunction(iec_path))]:
+
+ new_id = wx.NewId()
+ menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=text)
+ menu.Enable(new_id, enable)
+ self.Bind(wx.EVT_MENU, callback, id=new_id)
+
+ # Popup contextual menu
+ self.PopupMenu(menu)
+
+ menu.Destroy()
+ event.Skip()
+
+ def InsertValue(self, iec_path, index=None, force=False, graph=False):
+ """
+ Insert a new variable to debug in table
+ @param iec_path: Variable path to debug
+ @param index: Row where insert the variable in table (default None,
+ insert at last position)
+ @param force: Force insertion of variable even if not defined in
+ producer side
+ @param graph: Values must be displayed in graph canvas (Do nothing,
+ here for compatibility with Debug Variable Graphic Panel)
+ """
+ # Return immediately if variable is already debugged
+ for item in self.Table.GetData():
+ if iec_path == item.GetVariable():
+ return
+
+ # Insert at last position if index not defined
+ if index is None:
+ index = self.Table.GetNumberRows()
+
+ # Subscribe variable to producer
+ item = DebugVariableItem(self, iec_path)
+ result = self.AddDataConsumer(iec_path.upper(), item)
+
+ # Insert variable in table if subscription done or insertion forced
+ if result is not None or force:
+ self.Table.InsertItem(index, item)
+ self.RefreshView()
+
+ def ResetGraphicsValues(self):
+ """
+ Called to reset graphics values when PLC is started
+ (Nothing to do because no graphic values here. Defined for
+ compatibility with Debug Variable Graphic Panel)
+ """
+ pass
+
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+
+import wx
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableViewer import DebugVariableViewer
+from GraphButton import GraphButton
+
+#-------------------------------------------------------------------------------
+# Debug Variable Text Viewer Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Text Viewer
+"""
+
+class DebugVariableTextDropTarget(wx.TextDropTarget):
+
+ def __init__(self, parent, window):
+ """
+ Constructor
+ @param parent: Reference to Debug Variable Text Viewer
+ @param window: Reference to the Debug Variable Panel
+ """
+ wx.TextDropTarget.__init__(self)
+ self.ParentControl = parent
+ self.ParentWindow = window
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Text Viewer and Debug Variable
+ # Panel
+ self.ParentControl = None
+ self.ParentWindow = None
+
+ def OnDragOver(self, x, y, d):
+ """
+ Function called when mouse is dragged over Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param d: Suggested default for return value
+ """
+ # Signal parent that mouse is dragged over
+ self.ParentControl.OnMouseDragging(x, y)
+
+ return wx.TextDropTarget.OnDragOver(self, x, y, d)
+
+ def OnDropText(self, x, y, data):
+ """
+ Function called when mouse is released in Drop Target
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ @param data: Text associated to drag'n drop
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+
+ message = None
+
+ # Check that data is valid regarding DebugVariablePanel
+ try:
+ values = eval(data)
+ if not isinstance(values, TupleType):
+ raise ValueError
+ except:
+ message = _("Invalid value \"%s\" for debug variable") % data
+ values = None
+
+ # Display message if data is invalid
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ # Data contain a reference to a variable to debug
+ elif values[1] == "debug":
+
+ # Get Before which Viewer the variable has to be moved or added
+ # according to the position of mouse in Viewer.
+ width, height = self.ParentControl.GetSize()
+ target_idx = self.ParentControl.GetIndex()
+ if y > height / 2:
+ target_idx += 1
+
+ # Drag'n Drop is an internal is an internal move inside Debug
+ # Variable Panel
+ if len(values) > 2 and values[2] == "move":
+ self.ParentWindow.MoveValue(values[0],
+ target_idx)
+
+ # Drag'n Drop was initiated by another control of Beremiz
+ else:
+ self.ParentWindow.InsertValue(values[0],
+ target_idx,
+ force=True)
+
+ def OnLeave(self):
+ """
+ Function called when mouse is leave Drop Target
+ """
+ # Signal Debug Variable Panel to reset highlight
+ self.ParentWindow.ResetHighlight()
+
+ return wx.TextDropTarget.OnLeave(self)
+
+ def ShowMessage(self, message):
+ """
+ Show error message in Error Dialog
+ @param message: Error message to display
+ """
+ dialog = wx.MessageDialog(self.ParentWindow,
+ message,
+ _("Error"),
+ wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Text Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a text
+"""
+
+class DebugVariableTextViewer(DebugVariableViewer, wx.Panel):
+
+ def __init__(self, parent, window, items=[]):
+ """
+ Constructor
+ @param parent: Parent wx.Window of DebugVariableText
+ @param window: Reference to the Debug Variable Panel
+ @param items: List of DebugVariableItem displayed by Viewer
+ """
+ DebugVariableViewer.__init__(self, window, items)
+
+ wx.Panel.__init__(self, parent)
+ # Set panel background colour
+ self.SetBackgroundColour(wx.WHITE)
+ # Define panel drop target
+ self.SetDropTarget(DebugVariableTextDropTarget(self, window))
+
+ # Bind 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_ENTER_WINDOW, self.OnEnter)
+ self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+
+ # Define panel min size for parent sizer layout
+ self.SetMinSize(wx.Size(0, 25))
+
+ # Add buttons to Viewer
+ for bitmap, callback in [("force", self.OnForceButton),
+ ("release", self.OnReleaseButton),
+ ("delete_graph", self.OnCloseButton)]:
+ self.Buttons.append(GraphButton(0, 0, bitmap, callback))
+
+ def RefreshViewer(self):
+ """
+ Method that refresh the content displayed by Viewer
+ """
+ # Create buffered DC for drawing in panel
+ width, height = self.GetSize()
+ bitmap = wx.EmptyBitmap(width, height)
+ dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
+ dc.Clear()
+
+ # Get Graphics Context for DC, for anti-aliased and transparent
+ # rendering
+ gc = wx.GCDC(dc)
+
+ gc.BeginDrawing()
+
+ # Get first item
+ item = self.ItemsDict.values()[0]
+
+ # Get item variable path masked according Debug Variable Panel mask
+ item_path = item.GetVariable(
+ self.ParentWindow.GetVariableNameMask())
+
+ # Draw item variable path at Viewer left side
+ w, h = gc.GetTextExtent(item_path)
+ gc.DrawText(item_path, 20, (height - h) / 2)
+
+ # Update 'Release' button state and text color according to item forced
+ # flag value
+ item_forced = item.IsForced()
+ self.Buttons[1].Enable(item_forced)
+ self.RefreshButtonsPosition()
+ if item_forced:
+ gc.SetTextForeground(wx.BLUE)
+
+ # Draw item current value at right side of Viewer
+ item_value = item.GetValue()
+ w, h = gc.GetTextExtent(item_value)
+ gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
+
+ # Draw other Viewer common elements
+ self.DrawCommonElements(gc)
+
+ gc.EndDrawing()
+
+ def OnLeftDown(self, event):
+ """
+ Function called when mouse left button is pressed
+ @param event: wx.MouseEvent
+ """
+ # Get first item
+ item = self.ItemsDict.values()[0]
+
+ # Calculate item path bounding box
+ width, height = self.GetSize()
+ item_path = item.GetVariable(
+ self.ParentWindow.GetVariableNameMask())
+ w, h = self.GetTextExtent(item_path)
+
+ # Test if mouse has been pressed in this bounding box. In that case
+ # start a move drag'n drop of item variable
+ x, y = event.GetPosition()
+ item_path_bbox = wx.Rect(20, (height - h) / 2, w, h)
+ if item_path_bbox.InsideXY(x, y):
+ self.ShowButtons(False)
+ data = wx.TextDataObject(str((item.GetVariable(), "debug", "move")))
+ dragSource = wx.DropSource(self)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+
+ # In other case handle event normally
+ else:
+ event.Skip()
+
+ def OnLeftUp(self, event):
+ """
+ Function called when mouse left button is released
+ @param event: wx.MouseEvent
+ """
+ # Execute callback on button under mouse pointer if it exists
+ x, y = event.GetPosition()
+ wx.CallAfter(self.HandleButton, x, y)
+ event.Skip()
+
+ def OnLeftDClick(self, event):
+ """
+ Function called when mouse left button is double clicked
+ @param event: wx.MouseEvent
+ """
+ # Only numeric variables can be toggled to graph canvas
+ if self.ItemsDict.values()[0].IsNumVariable():
+ self.ParentWindow.ToggleViewerType(self)
+
+ def OnPaint(self, event):
+ """
+ Function called when redrawing Viewer content is needed
+ @param event: wx.PaintEvent
+ """
+ self.RefreshViewer()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,426 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from collections import OrderedDict
+
+import wx
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+
+from dialogs.ForceVariableDialog import ForceVariableDialog
+
+# Viewer highlight types
+[HIGHLIGHT_NONE,
+ HIGHLIGHT_BEFORE,
+ HIGHLIGHT_AFTER,
+ HIGHLIGHT_LEFT,
+ HIGHLIGHT_RIGHT,
+ HIGHLIGHT_RESIZE] = range(6)
+
+# Viewer highlight styles
+HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
+HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
+HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
+HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
+
+#-------------------------------------------------------------------------------
+# Base Debug Variable Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic viewer that display a list of variable values
+This class has to be inherited to effectively display variable values
+"""
+
+class DebugVariableViewer:
+
+ def __init__(self, window, items=[]):
+ """
+ Constructor
+ @param window: Reference to the Debug Variable Panel
+ @param items: List of DebugVariableItem displayed by Viewer
+ """
+ self.ParentWindow = window
+ self.ItemsDict = OrderedDict([(item.GetVariable(), item)
+ for item in items])
+ self.Items = self.ItemsDict.viewvalues()
+
+ # Variable storing current highlight displayed in Viewer
+ self.Highlight = HIGHLIGHT_NONE
+ # List of buttons
+ self.Buttons = []
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Panel
+ self.ParentWindow = None
+
+ def GetIndex(self):
+ """
+ Return position of Viewer in Debug Variable Panel
+ @return: Position of Viewer
+ """
+ return self.ParentWindow.GetViewerIndex(self)
+
+ def GetItem(self, variable):
+ """
+ Return item storing values of a variable
+ @param variable: Variable path
+ @return: Item storing values of this variable
+ """
+ return self.ItemsDict.get(variable, None)
+
+ def GetItems(self):
+ """
+ Return items displayed by Viewer
+ @return: List of items displayed in Viewer
+ """
+ return self.ItemsDict.values()
+
+ def AddItem(self, item):
+ """
+ Add an item to the list of items displayed by Viewer
+ @param item: Item to add to the list
+ """
+ self.ItemsDict[item.GetVariable()] = item
+
+ def RemoveItem(self, item):
+ """
+ Remove an item from the list of items displayed by Viewer
+ @param item: Item to remove from the list
+ """
+ self.ItemsDict.pop(item.GetVariable(), None)
+
+ def ClearItems(self):
+ """
+ Clear list of items displayed by Viewer
+ """
+ # Unsubscribe every items of the list
+ for item in self.Items:
+ self.ParentWindow.RemoveDataConsumer(item)
+
+ # Clear list
+ self.ItemsDict.clear()
+
+ def ItemsIsEmpty(self):
+ """
+ Return if list of items displayed by Viewer is empty
+ @return: True if list is empty
+ """
+ return len(self.Items) == 0
+
+ def SubscribeAllDataConsumers(self):
+ """
+ Function that unsubscribe and remove every item that store values of
+ a variable that doesn't exist in PLC anymore
+ """
+ for item in self.ItemsDict.values()[:]:
+ iec_path = item.GetVariable()
+
+ # Check that variablepath exist in PLC
+ if self.ParentWindow.GetDataType(iec_path) is None:
+ # If not, unsubscribe and remove it
+ self.ParentWindow.RemoveDataConsumer(item)
+ self.RemoveItem(item)
+ else:
+ # If it exist, resubscribe and refresh data type
+ self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
+ item.RefreshVariableType()
+
+ def ResetItemsData(self):
+ """
+ Reset data stored in every items displayed in Viewer
+ """
+ for item in self.Items:
+ item.ResetData()
+
+ def GetItemsMinCommonTick(self):
+ """
+ Return the minimum tick common to all iems displayed in Viewer
+ @return: Minimum common tick between items
+ """
+ return reduce(max, [item.GetData()[0, 0]
+ for item in self.Items
+ if len(item.GetData()) > 0], 0)
+
+ def RefreshViewer(self):
+ """
+ Method that refresh the content displayed by Viewer
+ Need to be overridden by inherited classes
+ """
+ pass
+
+ def SetHighlight(self, highlight):
+ """
+ Set Highlight type displayed in Viewer
+ @return: True if highlight has changed
+ """
+ # Return immediately if highlight don't change
+ if self.Highlight == highlight:
+ return False
+
+ self.Highlight = highlight
+ return True
+
+ def GetButtons(self):
+ """
+ Return list of buttons defined in Viewer
+ @return: List of buttons
+ """
+ return self.Buttons
+
+ def IsOverButton(self, x, y):
+ """
+ Return if point is over one button of Viewer
+ @param x: X coordinate of point
+ @param y: Y coordinate of point
+ @return: button where point is over
+ """
+ for button in self.GetButtons():
+ if button.HitTest(x, y):
+ return button
+ return None
+
+ def HandleButton(self, x, y):
+ """
+ Search for the button under point and if found execute associated
+ callback
+ @param x: X coordinate of point
+ @param y: Y coordinate of point
+ @return: True if a button was found and callback executed
+ """
+ button = self.IsOverButton(x, y)
+ if button is None:
+ return False
+
+ button.ProcessCallback()
+ return True
+
+ def ShowButtons(self, show):
+ """
+ Set display state of buttons in Viewer
+ @param show: Display state (True if buttons must be displayed)
+ """
+ # Change display of every buttons
+ for button in self.Buttons:
+ button.Show(show)
+
+ # Refresh button positions
+ self.RefreshButtonsPosition()
+ self.RefreshViewer()
+
+ def RefreshButtonsPosition(self):
+ """
+ Function that refresh buttons position in Viewer
+ """
+ # Get Viewer size
+ width, height = self.GetSize()
+
+ # Buttons are align right so we calculate buttons positions in
+ # reverse order
+ buttons = self.Buttons[:]
+ buttons.reverse()
+
+ # Position offset on x coordinate
+ x_offset = 0
+ for button in buttons:
+ # Buttons are stacked right, removing those that are not active
+ if button.IsEnabled():
+ # Update button position according to button width and offset
+ # on x coordinate
+ w, h = button.GetSize()
+ button.SetPosition(width - 5 - w - x_offset, 5)
+ # Update offset on x coordinate
+ x_offset += w + 2
+
+ def DrawCommonElements(self, dc, buttons=None):
+ """
+ Function that draw common graphics for every Viewers
+ @param dc: wx.DC object corresponding to Device context where drawing
+ common graphics
+ @param buttons: List of buttons to draw if different from default
+ (default None)
+ """
+ # Get Viewer size
+ width, height = self.GetSize()
+
+ # Set dc styling for drop before or drop after highlight
+ dc.SetPen(HIGHLIGHT_DROP_PEN)
+ dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
+
+ # Draw line at upper side of Viewer if highlight is drop before
+ if self.Highlight == HIGHLIGHT_BEFORE:
+ dc.DrawLine(0, 1, width - 1, 1)
+
+ # Draw line at lower side of Viewer if highlight is drop before
+ elif self.Highlight == HIGHLIGHT_AFTER:
+ dc.DrawLine(0, height - 1, width - 1, height - 1)
+
+ # If no specific buttons are defined, get default buttons
+ if buttons is None:
+ buttons = self.Buttons
+ # Draw buttons
+ for button in buttons:
+ button.Draw(dc)
+
+ # If graph dragging is processing
+ if self.ParentWindow.IsDragging():
+ destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
+ srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
+ if destBBox.width > 0 and destBBox.height > 0:
+ srcPanel = self.ParentWindow.DraggingAxesPanel
+ srcBBox = srcPanel.GetAxesBoundingBox()
+
+ srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0)
+ srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0)
+
+ srcBmp = _convert_agg_to_wx_bitmap(
+ srcPanel.get_renderer(), None)
+ srcDC = wx.MemoryDC()
+ srcDC.SelectObject(srcBmp)
+
+ dc.Blit(destBBox.x, destBBox.y,
+ int(destBBox.width), int(destBBox.height),
+ srcDC, srcX, srcY)
+
+ def OnEnter(self, event):
+ """
+ Function called when entering Viewer
+ @param event: wx.MouseEvent
+ """
+ # Display buttons
+ self.ShowButtons(True)
+ event.Skip()
+
+ def OnLeave(self, event):
+ """
+ Function called when leaving Viewer
+ @param event: wx.MouseEvent
+ """
+ # Hide buttons
+ self.ShowButtons(False)
+ event.Skip()
+
+ def OnCloseButton(self):
+ """
+ Function called when Close button is pressed
+ """
+ wx.CallAfter(self.ParentWindow.DeleteValue, self)
+
+ def OnForceButton(self):
+ """
+ Function called when Force button is pressed
+ """
+ self.ForceValue(self.ItemsDict.values()[0])
+
+ def OnReleaseButton(self):
+ """
+ Function called when Release button is pressed
+ """
+ self.ReleaseValue(self.ItemsDict.values()[0])
+
+ def OnMouseDragging(self, x, y):
+ """
+ Function called when mouse is dragged over Viewer
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ """
+ xw, yw = self.GetPosition()
+ # Refresh highlight in Debug Variable Panel (highlight can be displayed
+ # in another Viewer
+ self.ParentWindow.RefreshHighlight(x + xw, y + yw)
+
+ def RefreshHighlight(self, x, y):
+ """
+ Function called by Debug Variable Panel asking Viewer to refresh
+ highlight according to mouse position
+ @param x: X coordinate of mouse pointer
+ @param y: Y coordinate of mouse pointer
+ """
+ # Get Viewer size
+ width, height = self.GetSize()
+
+ # Mouse is in the first half of Viewer
+ if y < height / 2:
+ # If Viewer is the upper one, draw drop before highlight
+ if self.ParentWindow.IsViewerFirst(self):
+ self.SetHighlight(HIGHLIGHT_BEFORE)
+
+ # Else draw drop after highlight in previous Viewer
+ else:
+ self.SetHighlight(HIGHLIGHT_NONE)
+ self.ParentWindow.HighlightPreviousViewer(self)
+
+ # Mouse is in the second half of Viewer, draw drop after highlight
+ else:
+ self.SetHighlight(HIGHLIGHT_AFTER)
+
+ def OnEraseBackground(self, event):
+ """
+ Function called when Viewer background is going to be erase
+ @param event: wx.EraseEvent
+ """
+ # Prevent flicker on Windows
+ pass
+
+ def OnResize(self, event):
+ """
+ Function called when Viewer size changed
+ @param event: wx.ResizeEvent
+ """
+ # Refresh button positions
+ self.RefreshButtonsPosition()
+ self.ParentWindow.ForceRefresh()
+ event.Skip()
+
+ def ForceValue(self, item):
+ """
+ Force value of item given
+ @param item: Item to force value
+ """
+ # Check variable data type
+ iec_path = item.GetVariable()
+ iec_type = self.ParentWindow.GetDataType(iec_path)
+ # Return immediately if not found
+ if iec_type is None:
+ return
+
+ # Open a dialog to enter varaible forced value
+ dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
+ if dialog.ShowModal() == wx.ID_OK:
+ self.ParentWindow.ForceDataValue(iec_path.upper(),
+ dialog.GetValue())
+
+ def ReleaseValue(self, item):
+ """
+ Release value of item given
+ @param item: Item to release value
+ """
+ self.ParentWindow.ReleaseDataValue(
+ item.GetVariable().upper())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/GraphButton.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+# Custom button for Graphic Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom button for graphic Viewer
+"""
+
+class GraphButton():
+
+ def __init__(self, x, y, bitmap, callback):
+ """
+ Constructor
+ @param x: X coordinate of Button in Graphic Viewer
+ @param y: Y coordinate of Button in Graphic Viewer
+ @param bitmap: Name of bitmap to use for button
+ @param callback: Reference to function to call when button is pressed
+ """
+ # Save button position
+ self.SetPosition(x, y)
+ # Set button bitmap
+ self.SetBitmap(bitmap)
+
+ # By default button is hide and enabled
+ self.Shown = False
+ self.Enabled = True
+
+ # Save reference to callback function
+ self.Callback = callback
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to callback function
+ self.callback = None
+
+ def SetBitmap(self, bitmap):
+ """
+ Set bitmap to use for button
+ @param bitmap: Name of bitmap to use for button
+ """
+ # Get wx.Bitmap object corresponding to bitmap
+ self.Bitmap = GetBitmap(bitmap)
+
+ def GetSize(self):
+ """
+ Return size of button
+ @return: wx.Size object containing button size
+ """
+ # Button size is size of bitmap
+ return self.Bitmap.GetSize()
+
+ def SetPosition(self, x, y):
+ """
+ Set button position
+ @param x: X coordinate of Button in Graphic Viewer
+ @param y: Y coordinate of Button in Graphic Viewer
+ """
+ self.Position = wx.Point(x, y)
+
+ def Show(self, show=True):
+ """
+ Mark if button to be displayed in Graphic Viewer
+ @param show: True if button to be displayed in Graphic Viewer
+ (default True)
+ """
+ self.Shown = show
+
+ def Hide(self):
+ """
+ Hide button from Graphic Viewer
+ """
+ self.Show(False)
+
+ def IsShown(self):
+ """
+ Return if button is displayed in Graphic Viewer
+ @return: True if button is displayed in Graphic Viewer
+ """
+ return self.Shown
+
+ def Enable(self, enable=True):
+ """
+ Mark if button is active in Graphic Viewer
+ @param enable: True if button is active in Graphic Viewer
+ (default True)
+ """
+ self.Enabled = enable
+
+ def Disable(self):
+ """
+ Deactivate button in Graphic Viewer
+ """
+ self.Enabled = False
+
+ def IsEnabled(self):
+ """
+ Return if button is active in Graphic Viewer
+ @return: True if button is active in Graphic Viewer
+ """
+ return self.Enabled
+
+ def HitTest(self, x, y):
+ """
+ Test if point is inside button
+ @param x: X coordinate of point
+ @param y: Y coordinate of point
+ @return: True if button is active and displayed and point is inside
+ button
+ """
+ # Return immediately if button is hidden or inactive
+ if not (self.IsShown() and self.IsEnabled()):
+ return False
+
+ # Test if point is inside button
+ w, h = self.Bitmap.GetSize()
+ rect = wx.Rect(self.Position.x, self.Position.y, w, h)
+ return rect.InsideXY(x, y)
+
+ def ProcessCallback(self):
+ """
+ Call callback function if defined
+ """
+ if self.Callback is not None:
+ self.Callback()
+
+ def Draw(self, dc):
+ """
+ Draw button in Graphic Viewer
+ @param dc: wx.DC object corresponding to Graphic Viewer device context
+ """
+ # Only draw button if button is active and displayed
+ if self.Shown and self.Enabled:
+ dc.DrawBitmap(self.Bitmap, self.Position.x, self.Position.y, True)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,4 @@
+try:
+ from DebugVariableGraphicPanel import DebugVariableGraphicPanel as DebugVariablePanel
+except:
+ from DebugVariableTablePanel import DebugVariableTablePanel as DebugVariablePanel
\ No newline at end of file
--- a/controls/DurationCellEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/DurationCellEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -98,10 +98,11 @@
'''
Grid cell editor that uses DurationCellControl to display an edit button.
'''
- def __init__(self, table):
+ def __init__(self, table, colname):
wx.grid.PyGridCellEditor.__init__(self)
self.Table = table
+ self.Colname = colname
def __del__(self):
self.CellControl = None
@@ -114,15 +115,15 @@
def BeginEdit(self, row, col, grid):
self.CellControl.Enable()
- self.CellControl.SetValue(self.Table.GetValueByName(row, 'Interval'))
+ self.CellControl.SetValue(self.Table.GetValueByName(row, self.Colname))
self.CellControl.SetFocus()
def EndEdit(self, row, col, grid):
duration = self.CellControl.GetValue()
- old_duration = self.Table.GetValueByName(row, 'Interval')
+ old_duration = self.Table.GetValueByName(row, self.Colname)
changed = duration != old_duration
if changed:
- self.Table.SetValueByName(row, 'Interval', duration)
+ self.Table.SetValueByName(row, self.Colname, duration)
self.CellControl.Disable()
return changed
--- a/controls/FolderTree.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/FolderTree.py Wed Jul 31 10:45:07 2013 +0900
@@ -203,19 +203,23 @@
event.Veto()
def OnTreeEndLabelEdit(self, event):
- old_filepath = self.GetPath(event.GetItem())
- new_filepath = os.path.join(os.path.split(old_filepath)[0], event.GetLabel())
- if new_filepath != old_filepath:
- if not os.path.exists(new_filepath):
- os.rename(old_filepath, new_filepath)
- event.Skip()
- else:
- message = wx.MessageDialog(self,
- _("File '%s' already exists!") % event.GetLabel(),
- _("Error"), wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
- event.Veto()
+ new_name = event.GetLabel()
+ if new_name != "":
+ old_filepath = self.GetPath(event.GetItem())
+ new_filepath = os.path.join(os.path.split(old_filepath)[0], new_name)
+ if new_filepath != old_filepath:
+ if not os.path.exists(new_filepath):
+ os.rename(old_filepath, new_filepath)
+ event.Skip()
+ else:
+ message = wx.MessageDialog(self,
+ _("File '%s' already exists!") % new_name,
+ _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
def OnFilterChanged(self, event):
self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
--- a/controls/LibraryPanel.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/LibraryPanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -34,28 +34,50 @@
# Library Panel
#-------------------------------------------------------------------------------
+"""
+Class that implements a panel displaying a tree containing an hierarchical list
+of functions and function blocks available in project an a search control for
+quickly find one functions or function blocks in this list and a text control
+displaying informations about selected functions or function blocks
+"""
+
class LibraryPanel(wx.Panel):
def __init__(self, parent, enable_drag=False):
+ """
+ Constructor
+ @param parent: Parent wx.Window of LibraryPanel
+ @param enable_drag: Flag indicating that function or function block can
+ be drag'n drop from LibraryPanel (default: False)
+ """
wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+ # Define LibraryPanel main sizer
main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
main_sizer.AddGrowableCol(0)
main_sizer.AddGrowableRow(1)
+ # Add SearchCtrl to main sizer
self.SearchCtrl = wx.SearchCtrl(self)
+ # Add a button with a magnifying glass, essentially to show that this
+ # control is for searching in tree
self.SearchCtrl.ShowSearchButton(True)
self.Bind(wx.EVT_TEXT, self.OnSearchCtrlChanged, self.SearchCtrl)
self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
- self.OnSearchButtonClick, self.SearchCtrl)
+ self.OnSearchButtonClick, self.SearchCtrl)
+ # Bind keyboard event on SearchCtrl text control to catch UP and DOWN
+ # for search previous and next occurrence
search_textctrl = self.SearchCtrl.GetChildren()[0]
search_textctrl.Bind(wx.EVT_CHAR, self.OnKeyDown)
main_sizer.AddWindow(self.SearchCtrl, flag=wx.GROW)
+ # Add Splitter window for tree and block comment to main sizer
splitter_window = wx.SplitterWindow(self)
splitter_window.SetSashGravity(1.0)
main_sizer.AddWindow(splitter_window, flag=wx.GROW)
+ # Add TreeCtrl for functions and function blocks library in splitter
+ # window
self.Tree = wx.TreeCtrl(splitter_window,
size=wx.Size(0, 0),
style=wx.TR_HAS_BUTTONS|
@@ -65,166 +87,336 @@
wx.TR_LINES_AT_ROOT)
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemSelected, self.Tree)
self.Tree.Bind(wx.EVT_CHAR, self.OnKeyDown)
+ # If drag'n drop is enabled, bind event generated when a drag begins on
+ # tree to start a drag'n drop
if enable_drag:
self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, self.Tree)
+ # Add TextCtrl for function and function block informations
self.Comment = wx.TextCtrl(splitter_window, size=wx.Size(0, 80),
style=wx.TE_READONLY|wx.TE_MULTILINE)
splitter_window.SplitHorizontally(self.Tree, self.Comment, -80)
self.SetSizer(main_sizer)
-
+
+ # Reference to the project controller
self.Controller = None
-
+
+ # Variable storing functions and function blocks library to display
self.BlockList = None
def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to project controller
self.Controller = None
def SetController(self, controller):
+ """
+ Set reference to project controller
+ @param controller: Reference to project controller
+ """
self.Controller = controller
def SetBlockList(self, blocklist):
+ """
+ Set function and function block library to display in TreeCtrl
+ @param blocklist: Function and function block library
+ """
+ # Save functions and function blocks library
self.BlockList = blocklist
+ # Refresh TreeCtrl values
self.RefreshTree()
def SetFocus(self):
+ """
+ Called to give focus to LibraryPanel
+ Override wx.Window SetFocus method
+ """
+ # Give focus to SearchCtrl
self.SearchCtrl.SetFocus()
def ResetTree(self):
+ """
+ Reset LibraryPanel values displayed in controls
+ """
+ # Clear SearchCtrl, TreeCtrl and TextCtrl
self.SearchCtrl.SetValue("")
self.Tree.DeleteAllItems()
self.Comment.SetValue("")
def RefreshTree(self):
- if self.Controller is not None:
- to_delete = []
- selected_name = None
- selected = self.Tree.GetSelection()
- if selected.IsOk():
- selected_pydata = self.Tree.GetPyData(selected)
- if selected_pydata is not None and selected_pydata["type"] != CATEGORY:
- selected_name = self.Tree.GetItemText(selected)
- if self.BlockList is not None:
- blocktypes = self.BlockList
- else:
- blocktypes = self.Controller.GetBlockTypes()
+ """
+ Refresh LibraryPanel values displayed in controls
+ """
+ # Get function and function blocks library
+ blocktypes = self.BlockList
+ if blocktypes is None and self.Controller is not None:
+ # Get library from project controller if not defined
+ blocktypes = self.Controller.GetBlockTypes()
+
+ # Refresh TreeCtrl values if a library is defined
+ if blocktypes is not None:
+ # List that will contain tree items to be deleted when TreeCtrl
+ # will be refreshed
+ items_to_delete = []
+
+ # Get current selected item for selected it when values refreshed
+ selected_item = self.Tree.GetSelection()
+ selected_pydata = (self.Tree.GetPyData(selected_item)
+ if selected_item.IsOk() and
+ selected_item != self.Tree.GetRootItem()
+ else None)
+ # Don't save selected item if it is a category
+ selected_infos = ((self.Tree.GetItemText(selected_item),
+ selected_pydata["inputs"])
+ if selected_pydata is not None and
+ selected_pydata["type"] == BLOCK
+ else (None, None))
+
+ # Get TreeCtrl root item (hidden)
root = self.Tree.GetRootItem()
if not root.IsOk():
+ # Create root if not present
root = self.Tree.AddRoot("")
+
+ # Iterate over functions and function blocks library categories and
+ # add a tree item to root item for each of them
+
+ # Get first child under root item
category_item, root_cookie = self.Tree.GetFirstChild(root)
for category in blocktypes:
+ # Store category name in a local variable to prevent script
+ # extracting translated strings for gettext to consider "name"
+ # to be translated
category_name = category["name"]
- if not category_item.IsOk():
+
+ # Tree item already exists, set item label
+ if category_item.IsOk():
+ self.Tree.SetItemText(category_item, _(category_name))
+
+ # Tree item doesn't exist, add new one to root
+ else:
category_item = self.Tree.AppendItem(root, _(category_name))
+ # On Windows, needs to get next child of root to have a
+ # reference to the newly added tree item
if wx.Platform != '__WXMSW__':
- category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
- else:
- self.Tree.SetItemText(category_item, _(category_name))
+ category_item, root_cookie = \
+ self.Tree.GetNextChild(root, root_cookie)
+
+ # Set data associated to tree item (only save that item is a
+ # category)
self.Tree.SetPyData(category_item, {"type" : CATEGORY})
- blocktype_item, category_cookie = self.Tree.GetFirstChild(category_item)
+
+ # Iterate over functions and function blocks defined in library
+ # category add a tree item to category tree item for each of
+ # them
+
+ # Get first child under category tree item
+ blocktype_item, category_cookie = \
+ self.Tree.GetFirstChild(category_item)
for blocktype in category["list"]:
- if not blocktype_item.IsOk():
- blocktype_item = self.Tree.AppendItem(category_item, blocktype["name"])
+
+ # Tree item already exists, set item label
+ if blocktype_item.IsOk():
+ self.Tree.SetItemText(blocktype_item, blocktype["name"])
+
+ # Tree item doesn't exist, add new one to category item
+ else:
+ blocktype_item = self.Tree.AppendItem(
+ category_item, blocktype["name"])
+ # See comment when adding category
if wx.Platform != '__WXMSW__':
- blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
- else:
- self.Tree.SetItemText(blocktype_item, blocktype["name"])
+ blocktype_item, category_cookie = \
+ self.Tree.GetNextChild(category_item,
+ category_cookie)
+
+ # Define data to associate to block tree item
+ comment = blocktype["comment"]
block_data = {"type" : BLOCK,
"block_type" : blocktype["type"],
- "inputs" : tuple([type for name, type, modifier in blocktype["inputs"]]),
- "extension" : None}
- if blocktype["extensible"]:
- block_data["extension"] = len(blocktype["inputs"])
+ "inputs" : tuple([type
+ for name, type, modifier
+ in blocktype["inputs"]]),
+ "extension" : (len(blocktype["inputs"])
+ if blocktype["extensible"]
+ else None),
+ "comment": _(comment) +
+ blocktype.get("usage", "")}
self.Tree.SetPyData(blocktype_item, block_data)
- if selected_name == blocktype["name"]:
+
+ # Select block tree item in tree if it corresponds to
+ # previously selected one
+ if selected_infos == (blocktype["name"],
+ blocktype["inputs"]):
self.Tree.SelectItem(blocktype_item)
- comment = blocktype["comment"]
- self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
- blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
+
+ # Update TextCtrl value
+ self.Comment.SetValue(block_data["comment"])
+
+ # Get next block tree item under category tree item
+ blocktype_item, category_cookie = \
+ self.Tree.GetNextChild(category_item, category_cookie)
+
+ # Add every remaining tree item under category tree item after
+ # updating all block items to the list of items to delete
while blocktype_item.IsOk():
- to_delete.append(blocktype_item)
- blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
- category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ items_to_delete.append(blocktype_item)
+ blocktype_item, category_cookie = \
+ self.Tree.GetNextChild(category_item, category_cookie)
+
+ # Get next category tree item under root item
+ category_item, root_cookie = \
+ self.Tree.GetNextChild(root, root_cookie)
+
+ # Add every remaining tree item under root item after updating all
+ # category items to the list of items to delete
while category_item.IsOk():
- to_delete.append(category_item)
- category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
- for item in to_delete:
+ items_to_delete.append(category_item)
+ category_item, root_cookie = \
+ self.Tree.GetNextChild(root, root_cookie)
+
+ # Remove all items in list of items to delete from TreeCtrl
+ for item in items_to_delete:
self.Tree.Delete(item)
def GetSelectedBlock(self):
- selected = self.Tree.GetSelection()
- if (selected.IsOk() and
- self.Tree.GetItemParent(selected) != self.Tree.GetRootItem() and
- selected != self.Tree.GetRootItem()):
- selected_data = self.Tree.GetPyData(selected)
- return {"type": self.Tree.GetItemText(selected),
- "inputs": selected_data["inputs"]}
- return None
+ """
+ Get selected block informations
+ @return: {"type": block_type_name, "inputs": [input_type,...]} or None
+ if no block selected
+ """
+ # Get selected item associated data in tree
+ selected_item = self.Tree.GetSelection()
+ selected_pydata = (self.Tree.GetPyData(selected_item)
+ if selected_item.IsOk() and
+ selected_item != self.Tree.GetRootItem()
+ else None)
+
+ # Return value is None if selected tree item is root or a category
+ return ({"type": self.Tree.GetItemText(selected_item),
+ "inputs": selected_pydata["inputs"]}
+ if selected_pydata is not None and
+ selected_pydata["type"] == BLOCK
+ else None)
def SelectTreeItem(self, name, inputs):
+ """
+ Select Tree item corresponding to block informations given
+ @param name: Block type name
+ @param inputs: List of block inputs type [input_type,...]
+ """
+ # Find tree item corresponding to block informations
item = self.FindTreeItem(self.Tree.GetRootItem(), name, inputs)
if item is not None and item.IsOk():
+ # Select tree item found
self.Tree.SelectItem(item)
self.Tree.EnsureVisible(item)
- def FindTreeItem(self, root, name, inputs = None):
- if root.IsOk():
- pydata = self.Tree.GetPyData(root)
- if pydata is not None:
- type_inputs = pydata.get("inputs", None)
- type_extension = pydata.get("extension", None)
- if inputs is not None and type_inputs is not None:
- if type_extension is not None:
- same_inputs = type_inputs == inputs[:type_extension]
- else:
- same_inputs = type_inputs == inputs
- else:
- same_inputs = True
- if pydata is not None and self.Tree.GetItemText(root) == name and same_inputs:
- return root
+ def FindTreeItem(self, item, name, inputs = None):
+ """
+ Find Tree item corresponding to block informations given
+ Function is recursive
+ @param item: Item to test
+ @param name: Block type name
+ @param inputs: List of block inputs type [input_type,...]
+ """
+ # Return immediately if item isn't valid
+ if not item.IsOk():
+ return None
+
+ # Get data associated to item to test
+ item_pydata = self.Tree.GetPyData(item)
+ if item_pydata is not None and item_pydata["type"] == BLOCK:
+ # Only test item corresponding to block
+
+ # Test if block inputs type are the same than those given
+ type_inputs = item_pydata.get("inputs", None)
+ type_extension = item_pydata.get("extension", None)
+ if inputs is not None and type_inputs is not None:
+ same_inputs = reduce(
+ lambda x, y: x and y,
+ map(
+ lambda x: x[0]==x[1] or x[0]=='ANY' or x[1]=='ANY',
+ zip(type_inputs,
+ (inputs[:type_extension]
+ if type_extension is not None
+ else inputs))),
+ True)
else:
- if wx.VERSION < (2, 6, 0):
- item, root_cookie = self.Tree.GetFirstChild(root, 0)
- else:
- item, root_cookie = self.Tree.GetFirstChild(root)
- while item.IsOk():
- result = self.FindTreeItem(item, name, inputs)
- if result:
- return result
- item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ same_inputs = True
+
+ # Return item if block data corresponds to informations given
+ if self.Tree.GetItemText(item) == name and same_inputs:
+ return item
+
+ # Test item children if item doesn't correspond
+ child, child_cookie = self.Tree.GetFirstChild(item)
+ while child.IsOk():
+ result = self.FindTreeItem(child, name, inputs)
+ if result:
+ return result
+ child, child_cookie = self.Tree.GetNextChild(item, child_cookie)
+
return None
def SearchInTree(self, value, mode="first"):
+ """
+ Search in Tree and select item that name contains string given
+ @param value: String contained in block name to find
+ @param mode: Search mode ('first', 'previous' or 'next')
+ (default: 'first')
+ @return: True if an item was found
+ """
+ # Return immediately if root isn't valid
root = self.Tree.GetRootItem()
if not root.IsOk():
return False
- if mode == "first":
+ # Set function to navigate in Tree item sibling according to search
+ # mode defined
+ sibling_function = (self.Tree.GetPrevSibling
+ if mode == "previous"
+ else self.Tree.GetNextSibling)
+
+ # Get current selected item (for next and previous mode)
+ item = self.Tree.GetSelection()
+ if not item.IsOk() or mode == "first":
item, item_cookie = self.Tree.GetFirstChild(root)
selected = None
else:
- item = self.Tree.GetSelection()
selected = item
- if not item.IsOk():
- item, item_cookie = self.Tree.GetFirstChild(root)
+
+ # Navigate through tree items until one matching found or reach tree
+ # starting or ending
while item.IsOk():
+
+ # Get item data to get item type
item_pydata = self.Tree.GetPyData(item)
+
+ # Item is a block category
if item_pydata["type"] == CATEGORY:
- if mode == "previous":
- child = self.Tree.GetLastChild(item)
- else:
- child, child_cookie = self.Tree.GetFirstChild(item)
- if child.IsOk():
- item = child
- elif mode == "previous":
- item = self.Tree.GetPrevSibling(item)
- else:
- item = self.Tree.GetNextSibling(item)
+
+ # Get category first or last child according to search mode
+ # defined
+ child = (self.Tree.GetLastChild(item)
+ if mode == "previous"
+ else self.Tree.GetFirstChild(item)[0])
+
+ # If category has no child, go to sibling category
+ item = (child if child.IsOk() else sibling_function(item))
+
+ # Item is a block
else:
+
+ # Extract item block name
name = self.Tree.GetItemText(item)
- if name.upper().startswith(value.upper()) and item != selected:
+ # Test if block name contains string given
+ if name.upper().find(value.upper()) != -1 and item != selected:
+ # Select block and collapse all categories other than block
+ # category
child, child_cookie = self.Tree.GetFirstChild(root)
while child.IsOk():
self.Tree.CollapseAllChildren(child)
@@ -233,63 +425,91 @@
self.Tree.EnsureVisible(item)
return True
- elif mode == "previous":
- previous = self.Tree.GetPrevSibling(item)
- if previous.IsOk():
- item = previous
- else:
- parent = self.Tree.GetItemParent(item)
- item = self.Tree.GetPrevSibling(parent)
-
- else:
- next = self.Tree.GetNextSibling(item)
- if next.IsOk():
- item = next
- else:
- parent = self.Tree.GetItemParent(item)
- item = self.Tree.GetNextSibling(parent)
+ # Go to next item sibling if block not found
+ next = sibling_function(item)
+
+ # If category has no other child, go to next category sibling
+ item = (next
+ if next.IsOk()
+ else sibling_function(self.Tree.GetItemParent(item)))
+
return False
def OnSearchCtrlChanged(self, event):
+ """
+ Called when SearchCtrl text control value changed
+ @param event: TextCtrl change event
+ """
+ # Search for block containing SearchCtrl value in 'first' mode
self.SearchInTree(self.SearchCtrl.GetValue())
event.Skip()
def OnSearchButtonClick(self, event):
+ """
+ Called when SearchCtrl search button was clicked
+ @param event: Button clicked event
+ """
+ # Search for block containing SearchCtrl value in 'next' mode
self.SearchInTree(self.SearchCtrl.GetValue(), "next")
event.Skip()
def OnTreeItemSelected(self, event):
- selected = event.GetItem()
- pydata = self.Tree.GetPyData(selected)
- if pydata is not None and pydata["type"] != CATEGORY:
- blocktype = self.Controller.GetBlockType(self.Tree.GetItemText(selected), pydata["inputs"])
- if blocktype:
- comment = blocktype["comment"]
- self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
- else:
- self.Comment.SetValue("")
- else:
- self.Comment.SetValue("")
+ """
+ Called when tree item is selected
+ @param event: wx.TreeEvent
+ """
+ # Update TextCtrl value with block selected usage
+ item_pydata = self.Tree.GetPyData(event.GetItem())
+ self.Comment.SetValue(
+ item_pydata["comment"]
+ if item_pydata is not None and item_pydata["type"] == BLOCK
+ else "")
+
+ # Call extra function defined when tree item is selected
if getattr(self, "_OnTreeItemSelected", None) is not None:
self._OnTreeItemSelected(event)
+
event.Skip()
def OnTreeBeginDrag(self, event):
- selected = event.GetItem()
- pydata = self.Tree.GetPyData(selected)
- if pydata is not None and pydata["type"] == BLOCK:
- data = wx.TextDataObject(str((self.Tree.GetItemText(selected),
- pydata["block_type"], "", pydata["inputs"])))
+ """
+ Called when a drag is started in tree
+ @param event: wx.TreeEvent
+ """
+ selected_item = event.GetItem()
+ item_pydata = self.Tree.GetPyData(selected_item)
+
+ # Item dragged is a block
+ if item_pydata is not None and item_pydata["type"] == BLOCK:
+ # Start a drag'n drop
+ data = wx.TextDataObject(str(
+ (self.Tree.GetItemText(selected_item),
+ item_pydata["block_type"],
+ "",
+ item_pydata["inputs"])))
dragSource = wx.DropSource(self.Tree)
dragSource.SetData(data)
dragSource.DoDragDrop()
def OnKeyDown(self, event):
+ """
+ Called when key is pressed in SearchCtrl text control
+ @param event: wx.KeyEvent
+ """
+ # Get event keycode and value in SearchCtrl
keycode = event.GetKeyCode()
search_value = self.SearchCtrl.GetValue()
+
+ # Up key was pressed and SearchCtrl isn't empty, search for block in
+ # 'previous' mode
if keycode == wx.WXK_UP and search_value != "":
self.SearchInTree(search_value, "previous")
+
+ # Down key was pressed and SearchCtrl isn't empty, search for block in
+ # 'next' mode
elif keycode == wx.WXK_DOWN and search_value != "":
self.SearchInTree(search_value, "next")
+
+ # Handle key normally
else:
event.Skip()
--- a/controls/LocationCellEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/LocationCellEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -85,13 +85,36 @@
dialog = BrowseLocationsDialog(self, self.VarType, self.Controller)
if dialog.ShowModal() == wx.ID_OK:
infos = dialog.GetValues()
-
+ else:
+ infos = None
+ dialog.Destroy()
+
+ if infos is not None:
+ location = infos["location"]
# set the location
- self.Location.SetValue(infos["location"])
+ if not infos["location"].startswith("%"):
+ dialog = wx.SingleChoiceDialog(self,
+ _("Select a variable class:"), _("Variable class"),
+ ["Input", "Output", "Memory"],
+ wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ selected = dialog.GetSelection()
+ else:
+ selected = None
+ dialog.Destroy()
+ if selected is None:
+ self.Location.SetFocus()
+ return
+ if selected == 0:
+ location = "%I" + location
+ elif selected == 1:
+ location = "%Q" + location
+ else:
+ location = "%M" + location
+
+ self.Location.SetValue(location)
self.VarType = infos["IEC_type"]
- dialog.Destroy()
-
self.Location.SetFocus()
def OnLocationChar(self, event):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/LogViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,826 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from datetime import datetime
+from time import time as gettime
+import numpy
+
+import wx
+
+from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
+from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
+from targets.typemapping import LogLevelsCount, LogLevels
+from util.BitmapLibrary import GetBitmap
+
+THUMB_SIZE_RATIO = 1. / 8.
+
+def ArrowPoints(direction, width, height, xoffset, yoffset):
+ if direction == wx.TOP:
+ return [wx.Point(xoffset + 1, yoffset + height - 2),
+ wx.Point(xoffset + width / 2, yoffset + 1),
+ wx.Point(xoffset + width - 1, yoffset + height - 2)]
+ else:
+ return [wx.Point(xoffset + 1, yoffset - height + 1),
+ wx.Point(xoffset + width / 2, yoffset - 2),
+ wx.Point(xoffset + width - 1, yoffset - height + 1)]
+
+class LogScrollBar(wx.Panel):
+
+ def __init__(self, parent, size):
+ wx.Panel.__init__(self, parent, size=size)
+ self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+ self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
+ self.Bind(wx.EVT_MOTION, self.OnMotion)
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+
+ self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
+ self.ThumbScrollingStartPos = None
+
+ def GetRangeRect(self):
+ width, height = self.GetClientSize()
+ return wx.Rect(0, width, width, height - 2 * width)
+
+ def GetThumbRect(self):
+ width, height = self.GetClientSize()
+ range_rect = self.GetRangeRect()
+ thumb_size = range_rect.height * THUMB_SIZE_RATIO
+ thumb_range = range_rect.height - thumb_size
+ thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
+ thumb_start = int(thumb_center_position - thumb_size / 2.)
+ thumb_end = int(thumb_center_position + thumb_size / 2.)
+ return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
+
+ def RefreshThumbPosition(self, thumb_position=None):
+ if thumb_position is None:
+ thumb_position = self.ThumbPosition
+ if self.Parent.IsMessagePanelTop():
+ thumb_position = max(0., thumb_position)
+ if self.Parent.IsMessagePanelBottom():
+ thumb_position = min(0., thumb_position)
+ if thumb_position != self.ThumbPosition:
+ self.ThumbPosition = thumb_position
+ self.Parent.SetScrollSpeed(self.ThumbPosition)
+ self.Refresh()
+
+ def OnLeftDown(self, event):
+ self.CaptureMouse()
+ posx, posy = event.GetPosition()
+ width, height = self.GetClientSize()
+ range_rect = self.GetRangeRect()
+ thumb_rect = self.GetThumbRect()
+ if range_rect.InsideXY(posx, posy):
+ if thumb_rect.InsideXY(posx, posy):
+ self.ThumbScrollingStartPos = wx.Point(posx, posy)
+ elif posy < thumb_rect.y:
+ self.Parent.ScrollToLast()
+ elif posy > thumb_rect.y + thumb_rect.height:
+ self.Parent.ScrollToFirst()
+ elif posy < width:
+ self.Parent.ScrollMessagePanelByPage(1)
+ elif posy > height - width:
+ self.Parent.ScrollMessagePanelByPage(-1)
+ event.Skip()
+
+ def OnLeftUp(self, event):
+ self.ThumbScrollingStartPos = None
+ self.RefreshThumbPosition(0.)
+ if self.HasCapture():
+ self.ReleaseMouse()
+ event.Skip()
+
+ def OnMotion(self, event):
+ if event.Dragging() and self.ThumbScrollingStartPos is not None:
+ posx, posy = event.GetPosition()
+ width, height = self.GetClientSize()
+ range_rect = self.GetRangeRect()
+ thumb_size = range_rect.height * THUMB_SIZE_RATIO
+ thumb_range = range_rect.height - thumb_size
+ self.RefreshThumbPosition(
+ max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
+ event.Skip()
+
+ def OnResize(self, event):
+ self.Refresh()
+ event.Skip()
+
+ def OnEraseBackground(self, event):
+ pass
+
+ def OnPaint(self, event):
+ dc = wx.BufferedPaintDC(self)
+ dc.Clear()
+ dc.BeginDrawing()
+
+ gc = wx.GCDC(dc)
+
+ width, height = self.GetClientSize()
+
+ gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
+ gc.SetBrush(wx.GREY_BRUSH)
+
+ gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
+ gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
+
+ gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
+ gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
+
+ thumb_rect = self.GetThumbRect()
+ exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
+ thumb_rect.width, thumb_rect.height)
+ if self.Parent.IsMessagePanelTop():
+ exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
+ if self.Parent.IsMessagePanelBottom():
+ exclusion_rect.height = height - width - exclusion_rect.y
+ if exclusion_rect != thumb_rect:
+ colour = wx.NamedColour("LIGHT GREY")
+ gc.SetPen(wx.Pen(colour))
+ gc.SetBrush(wx.Brush(colour))
+
+ gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y,
+ exclusion_rect.width, exclusion_rect.height)
+
+ gc.SetPen(wx.GREY_PEN)
+ gc.SetBrush(wx.GREY_BRUSH)
+
+ gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
+
+ gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
+
+ gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
+ thumb_rect.width, thumb_rect.height)
+
+ dc.EndDrawing()
+ event.Skip()
+
+BUTTON_SIZE = (30, 15)
+
+class LogButton():
+
+ def __init__(self, label, callback):
+ self.Position = wx.Point(0, 0)
+ self.Size = wx.Size(*BUTTON_SIZE)
+ self.Label = label
+ self.Shown = True
+ self.Callback = callback
+
+ def __del__(self):
+ self.callback = None
+
+ def GetSize(self):
+ return self.Size
+
+ def SetPosition(self, x, y):
+ self.Position = wx.Point(x, y)
+
+ def HitTest(self, x, y):
+ rect = wx.Rect(self.Position.x, self.Position.y,
+ self.Size.width, self.Size.height)
+ if rect.InsideXY(x, y):
+ return True
+ return False
+
+ def ProcessCallback(self):
+ if self.Callback is not None:
+ wx.CallAfter(self.Callback)
+
+ def Draw(self, dc):
+ dc.SetPen(wx.TRANSPARENT_PEN)
+ dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
+
+ dc.DrawRectangle(self.Position.x, self.Position.y,
+ self.Size.width, self.Size.height)
+
+ w, h = dc.GetTextExtent(self.Label)
+ dc.DrawText(self.Label,
+ self.Position.x + (self.Size.width - w) / 2,
+ self.Position.y + (self.Size.height - h) / 2)
+
+DATE_INFO_SIZE = 10
+MESSAGE_INFO_SIZE = 18
+
+class LogMessage:
+
+ def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
+ self.Date = datetime.utcfromtimestamp(tv_sec)
+ self.Seconds = self.Date.second + tv_nsec * 1e-9
+ self.Date = self.Date.replace(second=0)
+ self.Timestamp = tv_sec + tv_nsec * 1e-9
+ self.Level = level
+ self.LevelBitmap = level_bitmap
+ self.Message = msg
+ self.DrawDate = True
+
+ def __cmp__(self, other):
+ if self.Date == other.Date:
+ return cmp(self.Seconds, other.Seconds)
+ return cmp(self.Date, other.Date)
+
+ def GetFullText(self):
+ date = self.Date.replace(second=int(self.Seconds))
+ nsec = (self.Seconds % 1.) * 1e9
+ return "%s at %s.%9.9d:\n%s" % (
+ LogLevels[self.Level],
+ str(date), nsec,
+ self.Message)
+
+ def Draw(self, dc, offset, width, draw_date):
+ if draw_date:
+ datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
+ dw, dh = dc.GetTextExtent(datetime_text)
+ dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
+ offset += DATE_INFO_SIZE
+
+ seconds_text = "%12.9f" % self.Seconds
+ sw, sh = dc.GetTextExtent(seconds_text)
+ dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
+
+ bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
+ dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
+
+ text = self.Message.replace("\n", " ")
+ mw, mh = dc.GetTextExtent(text)
+ dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
+
+ def GetHeight(self, draw_date):
+ if draw_date:
+ return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
+ return MESSAGE_INFO_SIZE
+
+SECOND = 1
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+DAY = 24 * HOUR
+
+CHANGE_TIMESTAMP_BUTTONS = [(_("1d"), DAY),
+ (_("1h"), HOUR),
+ (_("1m"), MINUTE),
+ (_("1s"), SECOND)]
+
+class LogViewer(DebugViewer, wx.Panel):
+
+ def __init__(self, parent, window):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
+ DebugViewer.__init__(self, None, False, False)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
+
+ self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.MessageFilter.Append(_("All"))
+ levels = LogLevels[:3]
+ levels.reverse()
+ for level in levels:
+ self.MessageFilter.Append(_(level))
+ self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
+ filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+
+ self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
+ self.SearchMessage.ShowSearchButton(True)
+ self.SearchMessage.ShowCancelButton(True)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
+ self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
+ self.OnSearchMessageSearchButtonClick, self.SearchMessage)
+ self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
+ self.OnSearchMessageCancelButtonClick, self.SearchMessage)
+ filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+
+ self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.CleanButton.SetToolTipString(_("Clean log messages"))
+ self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
+ filter_sizer.AddWindow(self.CleanButton)
+
+ message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
+ message_panel_sizer.AddGrowableCol(0)
+ message_panel_sizer.AddGrowableRow(0)
+ main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+
+ self.MessagePanel = wx.Panel(self)
+ if wx.Platform == '__WXMSW__':
+ self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
+ else:
+ self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
+ self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
+ self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
+ self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
+ self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion)
+ self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow)
+ self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
+ self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
+ self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
+ self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
+ message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
+
+ self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
+ message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
+
+ self.SetSizer(main_sizer)
+
+ self.LeftButtons = []
+ for label, callback in [("+" + text, self.GenerateOnDurationButton(duration))
+ for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
+ self.LeftButtons.append(LogButton(label, callback))
+
+ self.RightButtons = []
+ for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration))
+ for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
+ self.RightButtons.append(LogButton(label, callback))
+
+ self.MessageFilter.SetSelection(0)
+ self.LogSource = None
+ self.ResetLogMessages()
+ self.ParentWindow = window
+
+ self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
+ self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
+ self.CurrentFilter = self.LevelFilters[0]
+ self.CurrentSearchValue = ""
+
+ self.ScrollSpeed = 0.
+ self.LastStartTime = None
+ self.ScrollTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
+
+ self.LastMousePos = None
+ self.MessageToolTip = None
+ self.MessageToolTipTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
+
+ def __del__(self):
+ self.ScrollTimer.Stop()
+
+ def ResetLogMessages(self):
+ self.previous_log_count = [None]*LogLevelsCount
+ self.OldestMessages = []
+ self.LogMessages = []
+ self.LogMessagesTimestamp = numpy.array([])
+ self.CurrentMessage = None
+ self.HasNewData = False
+
+ def SetLogSource(self, log_source):
+ self.LogSource = log_source
+ self.CleanButton.Enable(self.LogSource is not None)
+ if log_source is not None:
+ self.ResetLogMessages()
+ self.RefreshView()
+
+ def GetLogMessageFromSource(self, msgidx, level):
+ if self.LogSource is not None:
+ answer = self.LogSource.GetLogMessage(level, msgidx)
+ if answer is not None:
+ msg, tick, tv_sec, tv_nsec = answer
+ return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
+ return None
+
+ def SetLogCounters(self, log_count):
+ new_messages = []
+ for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
+ if count is not None and prev != count:
+ if prev is None:
+ dump_end = max(-1, count - 10)
+ oldest_message = (-1, None)
+ else:
+ dump_end = prev - 1
+ for msgidx in xrange(count-1, dump_end,-1):
+ new_message = self.GetLogMessageFromSource(msgidx, level)
+ if new_message is None:
+ if prev is None:
+ oldest_message = (-1, None)
+ break
+ if prev is None:
+ oldest_message = (msgidx, new_message)
+ if len(new_messages) == 0:
+ new_messages = [new_message]
+ else:
+ new_messages.insert(0, new_message)
+ else:
+ new_messages.insert(0, new_message)
+ if prev is None and len(self.OldestMessages) <= level:
+ self.OldestMessages.append(oldest_message)
+ self.previous_log_count[level] = count
+ new_messages.sort()
+ if len(new_messages) > 0:
+ self.HasNewData = True
+ if self.CurrentMessage is not None:
+ current_is_last = self.GetNextMessage(self.CurrentMessage)[0] is None
+ else:
+ current_is_last = True
+ for new_message in new_messages:
+ self.LogMessages.append(new_message)
+ self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
+ if current_is_last:
+ self.ScrollToLast(False)
+ self.ResetMessageToolTip()
+ self.MessageToolTipTimer.Stop()
+ self.ParentWindow.SelectTab(self)
+ self.NewDataAvailable(None)
+
+ def FilterLogMessage(self, message, timestamp=None):
+ return (message.Level in self.CurrentFilter and
+ message.Message.find(self.CurrentSearchValue) != -1 and
+ (timestamp is None or message.Timestamp < timestamp))
+
+ def GetMessageByTimestamp(self, timestamp):
+ if self.CurrentMessage is not None:
+ msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
+ message = self.LogMessages[msgidx]
+ if self.FilterLogMessage(message) and message.Timestamp > timestamp:
+ return self.GetPreviousMessage(msgidx, timestamp)
+ return message, msgidx
+ return None, None
+
+ def GetNextMessage(self, msgidx):
+ while msgidx < len(self.LogMessages) - 1:
+ message = self.LogMessages[msgidx + 1]
+ if self.FilterLogMessage(message):
+ return message, msgidx + 1
+ msgidx += 1
+ return None, None
+
+ def GetPreviousMessage(self, msgidx, timestamp=None):
+ message = None
+ while 0 < msgidx < len(self.LogMessages):
+ message = self.LogMessages[msgidx - 1]
+ if self.FilterLogMessage(message, timestamp):
+ return message, msgidx - 1
+ msgidx -= 1
+ if len(self.LogMessages) > 0:
+ message = self.LogMessages[0]
+ for idx, msg in self.OldestMessages:
+ if msg is not None and msg > message:
+ message = msg
+ while message is not None:
+ level = message.Level
+ oldest_msgidx, oldest_message = self.OldestMessages[level]
+ if oldest_msgidx > 0:
+ message = self.GetLogMessageFromSource(oldest_msgidx - 1, level)
+ if message is not None:
+ self.OldestMessages[level] = (oldest_msgidx - 1, message)
+ else:
+ self.OldestMessages[level] = (-1, None)
+ else:
+ message = None
+ self.OldestMessages[level] = (-1, None)
+ if message is not None:
+ message_idx = 0
+ while (message_idx < len(self.LogMessages) and
+ self.LogMessages[message_idx] < message):
+ message_idx += 1
+ if len(self.LogMessages) > 0:
+ current_message = self.LogMessages[self.CurrentMessage]
+ else:
+ current_message = message
+ self.LogMessages.insert(message_idx, message)
+ self.LogMessagesTimestamp = numpy.insert(
+ self.LogMessagesTimestamp,
+ [message_idx],
+ [message.Timestamp])
+ self.CurrentMessage = self.LogMessages.index(current_message)
+ if message_idx == 0 and self.FilterLogMessage(message, timestamp):
+ return message, 0
+ for idx, msg in self.OldestMessages:
+ if msg is not None and (message is None or msg > message):
+ message = msg
+ return None, None
+
+ def RefreshNewData(self, *args, **kwargs):
+ if self.HasNewData:
+ self.HasNewData = False
+ self.RefreshView()
+ DebugViewer.RefreshNewData(self, *args, **kwargs)
+
+ def RefreshView(self):
+ width, height = self.MessagePanel.GetClientSize()
+ bitmap = wx.EmptyBitmap(width, height)
+ dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
+ dc.Clear()
+ dc.BeginDrawing()
+
+ if self.CurrentMessage is not None:
+
+ dc.SetFont(self.Font)
+
+ for button in self.LeftButtons + self.RightButtons:
+ button.Draw(dc)
+
+ message_idx = self.CurrentMessage
+ message = self.LogMessages[message_idx]
+ draw_date = True
+ offset = 5
+ while offset < height and message is not None:
+ message.Draw(dc, offset, width, draw_date)
+ offset += message.GetHeight(draw_date)
+
+ previous_message, message_idx = self.GetPreviousMessage(message_idx)
+ if previous_message is not None:
+ draw_date = message.Date != previous_message.Date
+ message = previous_message
+
+ dc.EndDrawing()
+
+ self.MessageScrollBar.RefreshThumbPosition()
+
+ def IsMessagePanelTop(self, message_idx=None):
+ if message_idx is None:
+ message_idx = self.CurrentMessage
+ if message_idx is not None:
+ return self.GetNextMessage(message_idx)[0] is None
+ return True
+
+ def IsMessagePanelBottom(self, message_idx=None):
+ if message_idx is None:
+ message_idx = self.CurrentMessage
+ if message_idx is not None:
+ width, height = self.MessagePanel.GetClientSize()
+ offset = 5
+ message = self.LogMessages[message_idx]
+ draw_date = True
+ while message is not None and offset < height:
+ offset += message.GetHeight(draw_date)
+ previous_message, message_idx = self.GetPreviousMessage(message_idx)
+ if previous_message is not None:
+ draw_date = message.Date != previous_message.Date
+ message = previous_message
+ return offset < height
+ return True
+
+ def ScrollMessagePanel(self, scroll):
+ if self.CurrentMessage is not None:
+ message = self.LogMessages[self.CurrentMessage]
+ while scroll > 0 and message is not None:
+ message, msgidx = self.GetNextMessage(self.CurrentMessage)
+ if message is not None:
+ self.CurrentMessage = msgidx
+ scroll -= 1
+ while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
+ message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
+ if message is not None:
+ self.CurrentMessage = msgidx
+ scroll += 1
+ self.RefreshView()
+
+ def ScrollMessagePanelByPage(self, page):
+ if self.CurrentMessage is not None:
+ width, height = self.MessagePanel.GetClientSize()
+ message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
+ self.ScrollMessagePanel(page * message_per_page)
+
+ def ScrollMessagePanelByTimestamp(self, seconds):
+ if self.CurrentMessage is not None:
+ current_message = self.LogMessages[self.CurrentMessage]
+ message, msgidx = self.GetMessageByTimestamp(current_message.Timestamp + seconds)
+ if message is None or self.IsMessagePanelBottom(msgidx):
+ self.ScrollToFirst()
+ else:
+ if seconds > 0 and self.CurrentMessage == msgidx and msgidx < len(self.LogMessages) - 1:
+ msgidx += 1
+ self.CurrentMessage = msgidx
+ self.RefreshView()
+
+ def ResetMessagePanel(self):
+ if len(self.LogMessages) > 0:
+ self.CurrentMessage = len(self.LogMessages) - 1
+ message = self.LogMessages[self.CurrentMessage]
+ while message is not None and not self.FilterLogMessage(message):
+ message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
+ self.RefreshView()
+
+ def OnMessageFilterChanged(self, event):
+ self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
+ self.ResetMessagePanel()
+ event.Skip()
+
+ def OnSearchMessageChanged(self, event):
+ self.CurrentSearchValue = self.SearchMessage.GetValue()
+ self.ResetMessagePanel()
+ event.Skip()
+
+ def OnSearchMessageSearchButtonClick(self, event):
+ self.CurrentSearchValue = self.SearchMessage.GetValue()
+ self.ResetMessagePanel()
+ event.Skip()
+
+ def OnSearchMessageCancelButtonClick(self, event):
+ self.CurrentSearchValue = ""
+ self.SearchMessage.SetValue("")
+ self.ResetMessagePanel()
+ event.Skip()
+
+ def OnCleanButton(self, event):
+ if self.LogSource is not None:
+ self.LogSource.ResetLogCount()
+ self.ResetLogMessages()
+ self.RefreshView()
+ event.Skip()
+
+ def GenerateOnDurationButton(self, duration):
+ def OnDurationButton():
+ self.ScrollMessagePanelByTimestamp(duration)
+ return OnDurationButton
+
+ def GetCopyMessageToClipboardFunction(self, message):
+ def CopyMessageToClipboardFunction(event):
+ self.ParentWindow.SetCopyBuffer(message.GetFullText())
+ return CopyMessageToClipboardFunction
+
+ def GetMessageByScreenPos(self, posx, posy):
+ if self.CurrentMessage is not None:
+ width, height = self.MessagePanel.GetClientSize()
+ message_idx = self.CurrentMessage
+ message = self.LogMessages[message_idx]
+ draw_date = True
+ offset = 5
+
+ while offset < height and message is not None:
+ if draw_date:
+ offset += DATE_INFO_SIZE
+
+ if offset <= posy < offset + MESSAGE_INFO_SIZE:
+ return message
+
+ offset += MESSAGE_INFO_SIZE
+
+ previous_message, message_idx = self.GetPreviousMessage(message_idx)
+ if previous_message is not None:
+ draw_date = message.Date != previous_message.Date
+ message = previous_message
+ return None
+
+ def OnMessagePanelLeftUp(self, event):
+ if self.CurrentMessage is not None:
+ posx, posy = event.GetPosition()
+ for button in self.LeftButtons + self.RightButtons:
+ if button.HitTest(posx, posy):
+ button.ProcessCallback()
+ break
+ event.Skip()
+
+ def OnMessagePanelRightUp(self, event):
+ message = self.GetMessageByScreenPos(*event.GetPosition())
+ if message is not None:
+ menu = wx.Menu(title='')
+
+ new_id = wx.NewId()
+ menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
+ self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
+
+ self.MessagePanel.PopupMenu(menu)
+ menu.Destroy()
+ event.Skip()
+
+ def OnMessagePanelLeftDCLick(self, event):
+ message = self.GetMessageByScreenPos(*event.GetPosition())
+ if message is not None:
+ self.SearchMessage.SetFocus()
+ self.SearchMessage.SetValue(message.Message)
+ event.Skip()
+
+ def ResetMessageToolTip(self):
+ if self.MessageToolTip is not None:
+ self.MessageToolTip.Destroy()
+ self.MessageToolTip = None
+
+ def OnMessageToolTipTimer(self, event):
+ if self.LastMousePos is not None:
+ message = self.GetMessageByScreenPos(*self.LastMousePos)
+ if message is not None:
+ tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
+ tooltip_pos.x += 10
+ tooltip_pos.y += 10
+ self.MessageToolTip = CustomToolTip(self.MessagePanel, message.GetFullText(), False)
+ self.MessageToolTip.SetFont(self.Font)
+ self.MessageToolTip.SetToolTipPosition(tooltip_pos)
+ self.MessageToolTip.Show()
+ event.Skip()
+
+ def OnMessagePanelMotion(self, event):
+ if not event.Dragging():
+ self.ResetMessageToolTip()
+ self.LastMousePos = event.GetPosition()
+ self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+ event.Skip()
+
+ def OnMessagePanelLeaveWindow(self, event):
+ self.ResetMessageToolTip()
+ self.LastMousePos = None
+ self.MessageToolTipTimer.Stop()
+ event.Skip()
+
+ def OnMessagePanelMouseWheel(self, event):
+ self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
+ event.Skip()
+
+ def OnMessagePanelEraseBackground(self, event):
+ pass
+
+ def OnMessagePanelPaint(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def OnMessagePanelResize(self, event):
+ width, height = self.MessagePanel.GetClientSize()
+ offset = 2
+ for button in self.LeftButtons:
+ button.SetPosition(offset, 2)
+ w, h = button.GetSize()
+ offset += w + 2
+ offset = width - 2
+ for button in self.RightButtons:
+ w, h = button.GetSize()
+ button.SetPosition(offset - w, 2)
+ offset -= w + 2
+ if self.IsMessagePanelBottom():
+ self.ScrollToFirst()
+ else:
+ self.RefreshView()
+ event.Skip()
+
+ def OnScrollTimer(self, event):
+ if self.ScrollSpeed != 0.:
+ speed_norm = abs(self.ScrollSpeed)
+ period = REFRESH_PERIOD / speed_norm
+ self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
+ self.LastStartTime = gettime()
+ self.ScrollTimer.Start(int(period * 1000), True)
+ event.Skip()
+
+ def SetScrollSpeed(self, speed):
+ if speed == 0.:
+ self.ScrollTimer.Stop()
+ else:
+ speed_norm = abs(speed)
+ period = REFRESH_PERIOD / speed_norm
+ current_time = gettime()
+ if self.LastStartTime is not None:
+ elapsed_time = current_time - self.LastStartTime
+ if elapsed_time > period:
+ self.ScrollMessagePanel(-speed_norm / speed)
+ self.LastStartTime = current_time
+ else:
+ period -= elapsed_time
+ else:
+ self.LastStartTime = current_time
+ self.ScrollTimer.Start(int(period * 1000), True)
+ self.ScrollSpeed = speed
+
+ def ScrollToLast(self, refresh=True):
+ if len(self.LogMessages) > 0:
+ self.CurrentMessage = len(self.LogMessages) - 1
+ message = self.LogMessages[self.CurrentMessage]
+ if not self.FilterLogMessage(message):
+ message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
+ if refresh:
+ self.RefreshView()
+
+ def ScrollToFirst(self):
+ if len(self.LogMessages) > 0:
+ message_idx = 0
+ message = self.LogMessages[message_idx]
+ if not self.FilterLogMessage(message):
+ next_message, msgidx = self.GetNextMessage(message_idx)
+ if next_message is not None:
+ message_idx = msgidx
+ message = next_message
+ while message is not None:
+ message, msgidx = self.GetPreviousMessage(message_idx)
+ if message is not None:
+ message_idx = msgidx
+ message = self.LogMessages[message_idx]
+ if self.FilterLogMessage(message):
+ while message is not None:
+ message, msgidx = self.GetNextMessage(message_idx)
+ if message is not None:
+ if not self.IsMessagePanelBottom(msgidx):
+ break
+ message_idx = msgidx
+ self.CurrentMessage = message_idx
+ else:
+ self.CurrentMessage = None
+ self.RefreshView()
--- a/controls/PouInstanceVariablesPanel.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/PouInstanceVariablesPanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -52,11 +52,10 @@
self.InstanceChoice = wx.ComboBox(self, size=wx.Size(0, 0), style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.OnInstanceChoiceChanged,
self.InstanceChoice)
- self.InstanceChoice.Bind(wx.EVT_LEFT_DOWN, self.OnInstanceChoiceLeftDown)
self.DebugButton = wx.lib.buttons.GenBitmapButton(self,
bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER)
- self.ParentButton.SetToolTipString(_("Debug instance"))
+ self.DebugButton.SetToolTipString(_("Debug instance"))
self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick,
self.DebugButton)
@@ -74,6 +73,7 @@
self.VariablesList.Bind(CT.EVT_TREE_ITEM_ACTIVATED,
self.OnVariablesListItemActivated)
self.VariablesList.Bind(wx.EVT_LEFT_DOWN, self.OnVariablesListLeftDown)
+ self.VariablesList.Bind(wx.EVT_KEY_DOWN, self.OnVariablesListKeyDown)
buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
buttons_sizer.AddWindow(self.ParentButton)
@@ -112,16 +112,21 @@
self.RefreshView()
def SetPouType(self, tagname, pou_instance=None):
- if self.Controller is not None:
- self.PouTagName = tagname
- if self.PouTagName == "Project":
+ if self.Controller is not None:
+ if tagname == "Project":
config_name = self.Controller.GetProjectMainConfigurationName()
if config_name is not None:
- self.PouTagName = self.Controller.ComputeConfigurationName(config_name)
+ tagname = self.Controller.ComputeConfigurationName(config_name)
if pou_instance is not None:
self.PouInstance = pou_instance
-
- self.RefreshView()
+
+ if self.PouTagName != tagname:
+ self.PouTagName = tagname
+ self.RefreshView()
+ else:
+ self.RefreshInstanceChoice()
+ else:
+ self.RefreshView()
def ResetView(self):
self.Controller = None
@@ -133,9 +138,8 @@
self.RefreshView()
def RefreshView(self):
+ self.Freeze()
self.VariablesList.DeleteAllItems()
- self.InstanceChoice.Clear()
- self.InstanceChoice.SetValue("")
if self.Controller is not None and self.PouTagName is not None:
self.PouInfos = self.Controller.GetPouVariables(self.PouTagName, self.Debug)
@@ -173,6 +177,7 @@
bitmap=GetBitmap("debug_instance"),
size=wx.Size(28, 28), style=wx.NO_BORDER)
self.Bind(wx.EVT_BUTTON, self.GenDebugButtonCallback(var_infos), debug_button)
+ debug_button.Bind(wx.EVT_LEFT_DCLICK, self.GenDebugButtonDClickCallback(var_infos))
buttons.append(debug_button)
button_num = len(buttons)
@@ -194,6 +199,15 @@
self.VariablesList.SetItemImage(item, self.ParentWindow.GetTreeImage(var_infos["class"]))
self.VariablesList.SetPyData(item, var_infos)
+ self.RefreshInstanceChoice()
+ self.RefreshButtons()
+
+ self.Thaw()
+
+ def RefreshInstanceChoice(self):
+ self.InstanceChoice.Clear()
+ self.InstanceChoice.SetValue("")
+ if self.Controller is not None and self.PouInfos is not None:
instances = self.Controller.SearchPouInstances(self.PouTagName, self.Debug)
for instance in instances:
self.InstanceChoice.Append(instance)
@@ -207,9 +221,7 @@
else:
self.PouInstance = None
self.InstanceChoice.SetValue(_("Select an instance"))
-
- self.RefreshButtons()
-
+
def RefreshButtons(self):
enabled = self.InstanceChoice.GetSelection() != -1
self.ParentButton.Enable(enabled and self.PouInfos["class"] != ITEM_CONFIGURATION)
@@ -278,6 +290,18 @@
event.Skip()
return DebugButtonCallback
+ def GenDebugButtonDClickCallback(self, infos):
+ def DebugButtonDClickCallback(event):
+ if self.InstanceChoice.GetSelection() != -1:
+ if infos["class"] in ITEMS_VARIABLE:
+ self.ParentWindow.AddDebugVariable(
+ "%s.%s" % (self.InstanceChoice.GetStringSelection(),
+ infos["name"]),
+ force=True,
+ graph=True)
+ event.Skip()
+ return DebugButtonDClickCallback
+
def GenGraphButtonCallback(self, infos):
def GraphButtonCallback(event):
if self.InstanceChoice.GetSelection() != -1:
@@ -321,21 +345,27 @@
event.Skip()
def OnVariablesListItemActivated(self, event):
- if self.InstanceChoice.GetSelection() != -1:
- instance_path = self.InstanceChoice.GetStringSelection()
- selected_item = event.GetItem()
- if selected_item is not None and selected_item.IsOk():
- item_infos = self.VariablesList.GetPyData(selected_item)
- if item_infos is not None and item_infos["class"] not in ITEMS_VARIABLE:
- if item_infos["class"] == ITEM_RESOURCE:
+ selected_item = event.GetItem()
+ if selected_item is not None and selected_item.IsOk():
+ item_infos = self.VariablesList.GetPyData(selected_item)
+ if item_infos is not None and item_infos["class"] not in ITEMS_VARIABLE:
+ instance_path = self.InstanceChoice.GetStringSelection()
+ if item_infos["class"] == ITEM_RESOURCE:
+ if instance_path != "":
tagname = self.Controller.ComputeConfigurationResourceName(
instance_path,
item_infos["name"])
else:
- tagname = self.Controller.ComputePouName(item_infos["type"])
- item_path = "%s.%s" % (instance_path, item_infos["name"])
- wx.CallAfter(self.SetPouType, tagname, item_path)
- wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, tagname)
+ tagname = None
+ else:
+ tagname = self.Controller.ComputePouName(item_infos["type"])
+ if tagname is not None:
+ if instance_path != "":
+ item_path = "%s.%s" % (instance_path, item_infos["name"])
+ else:
+ item_path = None
+ self.SetPouType(tagname, item_path)
+ self.ParentWindow.SelectProjectTreeItem(tagname)
event.Skip()
def OnVariablesListLeftDown(self, event):
@@ -347,12 +377,17 @@
if item is not None and flags & CT.TREE_HITTEST_ONITEMLABEL:
item_infos = self.VariablesList.GetPyData(item)
if item_infos is not None and item_infos["class"] in ITEMS_VARIABLE:
+ self.ParentWindow.EnsureTabVisible(
+ self.ParentWindow.DebugVariablePanel)
item_path = "%s.%s" % (instance_path, item_infos["name"])
data = wx.TextDataObject(str((item_path, "debug")))
dragSource = wx.DropSource(self.VariablesList)
dragSource.SetData(data)
dragSource.DoDragDrop()
event.Skip()
-
- def OnInstanceChoiceLeftDown(self, event):
- event.Skip()
+
+ def OnVariablesListKeyDown(self, event):
+ keycode = event.GetKeyCode()
+ if keycode != wx.WXK_LEFT:
+ event.Skip()
+
--- a/controls/TextCtrlAutoComplete.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/TextCtrlAutoComplete.py Wed Jul 31 10:45:07 2013 +0900
@@ -28,28 +28,23 @@
MAX_ITEM_COUNT = 10
MAX_ITEM_SHOWN = 6
if wx.Platform == '__WXMSW__':
- ITEM_INTERVAL_HEIGHT = 3
+ LISTBOX_BORDER_HEIGHT = 2
+ LISTBOX_INTERVAL_HEIGHT = 0
else:
- ITEM_INTERVAL_HEIGHT = 6
-
-if wx.Platform == '__WXMSW__':
- popupclass = wx.PopupTransientWindow
-else:
- popupclass = wx.PopupWindow
-
-class PopupWithListbox(popupclass):
+ LISTBOX_BORDER_HEIGHT = 4
+ LISTBOX_INTERVAL_HEIGHT = 6
+
+class PopupWithListbox(wx.PopupWindow):
def __init__(self, parent, choices=[]):
- popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
+ wx.PopupWindow.__init__(self, parent, wx.BORDER_SIMPLE)
self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
- if not wx.Platform == '__WXMSW__':
- self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
- self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
-
+
self.SetChoices(choices)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+ self.ListBox.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+ self.ListBox.Bind(wx.EVT_MOTION, self.OnMotion)
def SetChoices(self, choices):
max_text_width = 0
@@ -64,10 +59,14 @@
itemcount = min(len(choices), MAX_ITEM_SHOWN)
width = self.Parent.GetSize()[0]
- height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
+ height = max_text_height * itemcount + \
+ LISTBOX_INTERVAL_HEIGHT * max(0, itemcount - 1) + \
+ 2 * LISTBOX_BORDER_HEIGHT
if max_text_width + 10 > width:
height += 15
size = wx.Size(width, height)
+ if wx.Platform == '__WXMSW__':
+ size.width -= 2
self.ListBox.SetSize(size)
self.SetClientSize(size)
@@ -87,28 +86,28 @@
def GetSelection(self):
return self.ListBox.GetStringSelection()
- def ProcessLeftDown(self, event):
+ def OnLeftDown(self, event):
selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
+ parent_size = self.Parent.GetSize()
+ parent_rect = wx.Rect(0, -parent_size[1], parent_size[0], parent_size[1])
if selected != wx.NOT_FOUND:
wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
- return False
-
- def OnListBoxClick(self, event):
- selected = event.GetSelection()
- if selected != wx.NOT_FOUND:
- wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
- event.Skip()
-
- def OnKeyDown(self, event):
- self.Parent.ProcessEvent(event)
-
- def OnDismiss(self):
- self.Parent.listbox = None
- wx.CallAfter(self.Parent.DismissListBox)
+ elif parent_rect.InsideXY(event.m_x, event.m_y):
+ result, x, y = self.Parent.HitTest(wx.Point(event.m_x, event.m_y + parent_size[1]))
+ if result != wx.TE_HT_UNKNOWN:
+ self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
+ else:
+ wx.CallAfter(self.Parent.DismissListBox)
+ event.Skip()
+
+ def OnMotion(self, event):
+ self.ListBox.SetSelection(
+ self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)))
+ event.Skip()
class TextCtrlAutoComplete(wx.TextCtrl):
- def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
+ def __init__ (self, parent, choices=None, dropDownClick=True,
element_path=None, **therest):
"""
Constructor works just like wx.TextCtrl except you can pass in a
@@ -119,11 +118,11 @@
therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
wx.TextCtrl.__init__(self, parent, **therest)
- self.AppFrame = appframe
#Some variables
self._dropDownClick = dropDownClick
self._lastinsertionpoint = None
+ self._hasfocus = False
self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
self.element_path = element_path
@@ -148,9 +147,6 @@
self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
- def __del__(self):
- self.AppFrame = None
-
def ChangeValue(self, value):
wx.TextCtrl.ChangeValue(self, value)
self.RefreshListBoxChoices()
@@ -171,7 +167,11 @@
else:
self.listbox.MoveSelection(-1)
elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
- self.SetValueFromSelected(self.listbox.GetSelection())
+ selected = self.listbox.GetSelection()
+ if selected != "":
+ self.SetValueFromSelected(selected)
+ else:
+ event.Skip()
elif event.GetKeyCode() == wx.WXK_ESCAPE:
self.DismissListBox()
else:
@@ -182,7 +182,9 @@
event.Skip()
def OnClickToggleUp(self, event):
- if self.GetInsertionPoint() == self._lastinsertionpoint:
+ if not self._hasfocus:
+ self._hasfocus = True
+ elif self.GetInsertionPoint() == self._lastinsertionpoint:
wx.CallAfter(self.PopupListBox)
self._lastinsertionpoint = None
event.Skip()
@@ -197,6 +199,7 @@
config.Flush()
self.SetChoices(listentries)
self.DismissListBox()
+ self._hasfocus = False
event.Skip()
def SetChoices(self, choices):
@@ -207,13 +210,13 @@
return self._choices
def SetValueFromSelected(self, selected):
- """
- Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
- Will do nothing if no item is selected in the wx.ListCtrl.
- """
- if selected != "":
+ """
+ Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
+ Will do nothing if no item is selected in the wx.ListCtrl.
+ """
+ if selected != "":
self.SetValue(selected)
- self.DismissListBox()
+ self.DismissListBox()
def RefreshListBoxChoices(self):
if self.listbox is not None:
@@ -229,22 +232,18 @@
# depending on available screen space...
pos = self.ClientToScreen((0, 0))
sz = self.GetSize()
+ if wx.Platform == '__WXMSW__':
+ pos.x -= 2
+ pos.y -= 2
self.listbox.Position(pos, (0, sz[1]))
self.RefreshListBoxChoices()
- if wx.Platform == '__WXMSW__':
- self.listbox.Popup()
- else:
- self.listbox.Show()
- self.AppFrame.EnableScrolling(False)
+ self.listbox.Show()
def DismissListBox(self):
if self.listbox is not None:
- if wx.Platform == '__WXMSW__':
- self.listbox.Dismiss()
- else:
- self.listbox.Destroy()
+ if self.listbox.ListBox.HasCapture():
+ self.listbox.ListBox.ReleaseMouse()
+ self.listbox.Destroy()
self.listbox = None
- self.AppFrame.EnableScrolling(True)
-
--- a/controls/VariablePanel.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/VariablePanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -42,15 +42,15 @@
# Helpers
#-------------------------------------------------------------------------------
-def AppendMenu(parent, help, id, kind, text):
- if wx.VERSION >= (2, 6, 0):
- parent.Append(help=help, id=id, kind=kind, text=text)
- else:
- parent.Append(helpString=help, id=id, kind=kind, item=text)
-
+def AppendMenu(parent, help, id, kind, text):
+ if wx.VERSION >= (2, 6, 0):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+ else:
+ parent.Append(helpString=help, id=id, kind=kind, item=text)
+
[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-] = range(10)
+] = range(10)
def GetVariableTableColnames(location):
_ = lambda x : x
@@ -89,6 +89,7 @@
}
LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
+VARIABLE_NAME_SUFFIX_MODEL = re.compile("([0-9]*)$")
#-------------------------------------------------------------------------------
# Variables Panel Table
@@ -166,11 +167,8 @@
elif col != 0 and self.GetValueByName(row, "Edit"):
grid.SetReadOnly(row, col, False)
if colname == "Name":
- if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
- grid.SetReadOnly(row, col, True)
- else:
- editor = wx.grid.GridCellTextEditor()
- renderer = wx.grid.GridCellStringRenderer()
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
elif colname == "Initial Value":
if var_class not in ["External", "InOut"]:
if self.Parent.Controler.IsEnumeratedType(var_type):
@@ -188,13 +186,11 @@
else:
grid.SetReadOnly(row, col, True)
elif colname == "Class":
- if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
+ if len(self.Parent.ClassList) == 1:
grid.SetReadOnly(row, col, True)
else:
editor = wx.grid.GridCellChoiceEditor()
excluded = []
- if self.Parent.PouIsUsed:
- excluded.extend(["Input","Output","InOut"])
if self.Parent.IsFunctionBlockType(var_type):
excluded.extend(["Local","Temp"])
editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
@@ -302,22 +298,61 @@
self.ParentWindow.Table.SetValue(row, col, values[0])
self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
self.ParentWindow.SaveValues()
- elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
- element_type in ["config", "resource"] and values[1] == "location"):
+ elif (element_type not in ["config", "resource", "function"] and values[1] == "Global" and
+ self.ParentWindow.Filter in ["All", "Interface", "External"] or
+ element_type != "function" and values[1] == "location"):
if values[1] == "location":
var_name = values[3]
else:
var_name = values[0]
tagname = self.ParentWindow.GetTagName()
- if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+ if var_name.upper() in [name.upper()
+ for name in self.ParentWindow.Controler.\
+ GetProjectPouNames(self.ParentWindow.Debug)]:
message = _("\"%s\" pou already exists!")%var_name
- elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+ elif not var_name.upper() in [name.upper()
+ for name in self.ParentWindow.Controler.\
+ GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
var_infos = self.ParentWindow.DefaultValue.copy()
var_infos["Name"] = var_name
var_infos["Type"] = values[2]
if values[1] == "location":
- var_infos["Class"] = "Global"
- var_infos["Location"] = values[0]
+ location = values[0]
+ if not location.startswith("%"):
+ dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow,
+ _("Select a variable class:"), _("Variable class"),
+ ["Input", "Output", "Memory"],
+ wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ selected = dialog.GetSelection()
+ else:
+ selected = None
+ dialog.Destroy()
+ if selected is None:
+ return
+ if selected == 0:
+ location = "%I" + location
+ elif selected == 1:
+ location = "%Q" + location
+ else:
+ location = "%M" + location
+ if element_type == "functionBlock":
+ configs = self.ParentWindow.Controler.GetProjectConfigNames(
+ self.ParentWindow.Debug)
+ if len(configs) == 0:
+ return
+ if not var_name.upper() in [name.upper()
+ for name in self.ParentWindow.Controler.\
+ GetConfigurationVariableNames(configs[0])]:
+ self.ParentWindow.Controler.AddConfigurationGlobalVar(
+ configs[0], values[2], var_name, location, "")
+ var_infos["Class"] = "External"
+ else:
+ if element_type == "program":
+ var_infos["Class"] = "Local"
+ else:
+ var_infos["Class"] = "Global"
+ var_infos["Location"] = location
else:
var_infos["Class"] = "External"
var_infos["Number"] = len(self.ParentWindow.Values)
@@ -486,34 +521,56 @@
self.VariablesGrid.SetEditable(not self.Debug)
def _AddVariable(new_row):
- if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
+ if new_row > 0:
+ row_content = self.Values[new_row - 1].copy()
+
+ result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content["Name"])
+ if result is not None:
+ name = row_content["Name"][:result.start(1)]
+ suffix = result.group(1)
+ if suffix != "":
+ start_idx = int(suffix)
+ else:
+ start_idx = 0
+ else:
+ name = row_content["Name"]
+ start_idx = 0
+ else:
+ row_content = None
+ start_idx = 0
+ name = "LocalVar"
+
+ if row_content is not None and row_content["Edit"]:
+ row_content = self.Values[new_row - 1].copy()
+ else:
row_content = self.DefaultValue.copy()
if self.Filter in self.DefaultTypes:
row_content["Class"] = self.DefaultTypes[self.Filter]
else:
row_content["Class"] = self.Filter
- if self.Filter == "All" and len(self.Values) > 0:
- self.Values.insert(new_row, row_content)
- else:
- self.Values.append(row_content)
- new_row = self.Table.GetNumberRows()
- self.SaveValues()
- self.RefreshValues()
- return new_row
- return self.VariablesGrid.GetGridCursorRow()
+
+ row_content["Name"] = self.Controler.GenerateNewName(
+ self.TagName, None, name + "%d", start_idx)
+
+ if self.Filter == "All" and len(self.Values) > 0:
+ self.Values.insert(new_row, row_content)
+ else:
+ self.Values.append(row_content)
+ new_row = self.Table.GetNumberRows()
+ self.SaveValues()
+ self.RefreshValues()
+ return new_row
setattr(self.VariablesGrid, "_AddRow", _AddVariable)
def _DeleteVariable(row):
- if (self.Table.GetValueByName(row, "Edit") and
- (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+ if self.Table.GetValueByName(row, "Edit"):
self.Values.remove(self.Table.GetRow(row))
self.SaveValues()
self.RefreshValues()
setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
def _MoveVariable(row, move):
- if (self.Filter == "All" and
- (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+ if self.Filter == "All":
new_row = max(0, min(row + move, len(self.Values) - 1))
if new_row != row:
self.Values.insert(new_row, self.Values.pop(row))
@@ -532,12 +589,10 @@
if table_length > 0:
row = self.VariablesGrid.GetGridCursorRow()
row_edit = self.Table.GetValueByName(row, "Edit")
- if self.PouIsUsed:
- row_class = self.Table.GetValueByName(row, "Class")
- self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
- self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
- self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
- self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
+ self.AddButton.Enable(not self.Debug)
+ self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit))
+ self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All"))
+ self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All"))
setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
self.VariablesGrid.SetRowLabelSize(0)
@@ -572,10 +627,8 @@
words = self.TagName.split("::")
if self.ElementType == "config":
- self.PouIsUsed = False
self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
elif self.ElementType == "resource":
- self.PouIsUsed = False
self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
else:
if self.ElementType == "function":
@@ -584,7 +637,6 @@
self.ReturnType.Append(data_type)
returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
description = self.Controler.GetPouDescription(words[1])
- self.PouIsUsed = self.Controler.PouIsUsed(words[1])
self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
if returnType is not None:
@@ -625,7 +677,7 @@
new_description = self.Description.GetValue()
if new_description != old_description:
self.Controler.SetPouDescription(words[1], new_description)
- self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
event.Skip()
def OnClassFilter(self, event):
@@ -666,13 +718,12 @@
if old_value != "":
self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
self.Controler.BufferProject()
- self.ParentWindow.RefreshView(variablepanel = False)
- self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
- event.Skip()
+ wx.CallAfter(self.ParentWindow.RefreshView, False)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
else:
self.SaveValues()
if colname == "Class":
- self.ParentWindow.RefreshView(variablepanel = False)
+ wx.CallAfter(self.ParentWindow.RefreshView, False)
elif colname == "Location":
wx.CallAfter(self.ParentWindow.RefreshView)
@@ -761,7 +812,7 @@
self.SaveValues(False)
self.ParentWindow.RefreshView(variablepanel = False)
self.Controler.BufferProject()
- self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
return VariableTypeFunction
def VariableArrayTypeFunction(self, event):
@@ -775,7 +826,7 @@
self.SaveValues(False)
self.ParentWindow.RefreshView(variablepanel = False)
self.Controler.BufferProject()
- self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
dialog.Destroy()
def OnVariablesGridCellLeftClick(self, event):
@@ -811,7 +862,7 @@
self.Controler.SetPouInterfaceVars(words[1], self.Values)
if buffer:
self.Controler.BufferProject()
- self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
#-------------------------------------------------------------------------------
# Highlights showing functions
--- a/controls/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -38,3 +38,6 @@
from SearchResultPanel import SearchResultPanel
from TextCtrlAutoComplete import TextCtrlAutoComplete
from FolderTree import FolderTree
+from LogViewer import LogViewer
+from CustomStyledTextCtrl import CustomStyledTextCtrl
+from CustomToolTip import CustomToolTip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/BlockPreviewDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,303 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+from graphics.GraphicCommons import FREEDRAWING_MODE
+
+#-------------------------------------------------------------------------------
+# Dialog with preview for graphic block
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic dialog containing a preview panel for displaying
+graphic created by dialog
+"""
+
+class BlockPreviewDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, tagname, size, title):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param size: wx.Size object containing size of dialog
+ @param title: Title of dialog frame
+ """
+ wx.Dialog.__init__(self, parent, size=size, title=title)
+
+ # Save reference to
+ self.Controller = controller
+ self.TagName = tagname
+
+ # Label for preview
+ self.PreviewLabel = wx.StaticText(self, label=_('Preview:'))
+
+ # Create Preview panel
+ self.Preview = wx.Panel(self, style=wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.WHITE)
+
+ # Add function to preview panel so that it answers to graphic elements
+ # like Viewer
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "GetBlockType", controller.GetBlockType)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+
+ # Bind paint event on Preview panel
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+
+ # Add default dialog buttons sizer
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ self.ButtonSizer.GetAffirmativeButton())
+
+ self.Element = None # Graphic element to display in preview
+ self.MinElementSize = None # Graphic element minimal size
+
+ # Variable containing the graphic element name when dialog is opened
+ self.DefaultElementName = None
+
+ # List of variables defined in POU {var_name: (var_class, var_type),...}
+ self.VariableList = {}
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to project controller
+ self.Controller = None
+
+ def _init_sizers(self, main_rows, main_growable_row,
+ left_rows, left_growable_row,
+ right_rows, right_growable_row):
+ """
+ Initialize common sizers
+ @param main_rows: Number of rows in main sizer
+ @param main_growable_row: Row that is growable in main sizer, None if no
+ row is growable
+ @param left_rows: Number of rows in left grid sizer
+ @param left_growable_row: Row that is growable in left grid sizer, None
+ if no row is growable
+ @param right_rows: Number of rows in right grid sizer
+ @param right_growable_row: Row that is growable in right grid sizer,
+ None if no row is growable
+ """
+ # Create dialog main sizer
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0,
+ rows=main_rows, vgap=10)
+ self.MainSizer.AddGrowableCol(0)
+ if main_growable_row is not None:
+ self.MainSizer.AddGrowableRow(main_growable_row)
+
+ # Create a sizer for dividing parameters in two columns
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.MainSizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ # Create a sizer for left column
+ self.LeftGridSizer = wx.FlexGridSizer(cols=1, hgap=0,
+ rows=left_rows, vgap=5)
+ self.LeftGridSizer.AddGrowableCol(0)
+ if left_growable_row is not None:
+ self.LeftGridSizer.AddGrowableRow(left_growable_row)
+ column_sizer.AddSizer(self.LeftGridSizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ # Create a sizer for right column
+ self.RightGridSizer = wx.FlexGridSizer(cols=1, hgap=0,
+ rows=right_rows, vgap=0)
+ self.RightGridSizer.AddGrowableCol(0)
+ if right_growable_row is not None:
+ self.RightGridSizer.AddGrowableRow(right_growable_row)
+ column_sizer.AddSizer(self.RightGridSizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ self.SetSizer(self.MainSizer)
+
+ def SetMinElementSize(self, size):
+ """
+ Define minimal graphic element size
+ @param size: Tuple containing minimal size (width, height)
+ """
+ self.MinElementSize = size
+
+ def GetMinElementSize(self):
+ """
+ Get minimal graphic element size
+ @return: Tuple containing minimal size (width, height) or None if no
+ element defined
+ May be overridden by inherited classes
+ """
+ if self.Element is None:
+ return None
+
+ return self.Element.GetMinSize()
+
+ def SetPreviewFont(self, font):
+ """
+ Set font of Preview panel
+ @param font: wx.Font object containing font style
+ """
+ self.Preview.SetFont(font)
+
+ def RefreshVariableList(self):
+ """
+ Extract list of variables defined in POU
+ """
+ # Get list of variables defined in POU
+ self.VariableList = {
+ var["Name"]: (var["Class"], var["Type"])
+ for var in self.Controller.GetEditedElementInterfaceVars(
+ self.TagName)
+ if var["Edit"]}
+
+ # Add POU name to variable list if POU is a function
+ returntype = self.Controller.GetEditedElementInterfaceReturnType(
+ self.TagName)
+ if returntype is not None:
+ self.VariableList[
+ self.Controller.GetEditedElementName(self.TagName)] = \
+ ("Output", returntype)
+
+ # Add POU name if POU is a transition
+ words = self.TagName.split("::")
+ if words[0] == "T":
+ self.VariableList[words[2]] = ("Output", "BOOL")
+
+ def TestElementName(self, element_name):
+ """
+ Test displayed graphic element name
+ @param element_name: Graphic element name
+ """
+ # Variable containing error message format
+ message_format = None
+ # Get graphic element name in upper case
+ uppercase_element_name = element_name.upper()
+
+ # Test if graphic element name is a valid identifier
+ if not TestIdentifier(element_name):
+ message_format = _("\"%s\" is not a valid identifier!")
+
+ # Test that graphic element name isn't a keyword
+ elif uppercase_element_name in IEC_KEYWORDS:
+ message_format = _("\"%s\" is a keyword. It can't be used!")
+
+ # Test that graphic element name isn't a POU name
+ elif uppercase_element_name in self.Controller.GetProjectPouNames():
+ message_format = _("\"%s\" pou already exists!")
+
+ # Test that graphic element name isn't already used in POU by a variable
+ # or another graphic element
+ elif ((self.DefaultElementName is None or
+ self.DefaultElementName.upper() != uppercase_element_name) and
+ uppercase_element_name in self.Controller.\
+ GetEditedElementVariables(self.TagName)):
+ message_format = _("\"%s\" element for this pou already exists!")
+
+ # If an error have been identify, show error message dialog
+ if message_format is not None:
+ self.ShowErrorMessage(message_format % element_name)
+ # Test failed
+ return False
+
+ # Test succeed
+ return True
+
+ def ShowErrorMessage(self, message):
+ """
+ Show an error message dialog over this dialog
+ @param message: Error message to display
+ """
+ dialog = wx.MessageDialog(self, message,
+ _("Error"),
+ wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+ def OnOK(self, event):
+ """
+ Called when dialog OK button is pressed
+ Need to be overridden by inherited classes to check that dialog values
+ are valid
+ @param event: wx.Event from OK button
+ """
+ # Close dialog
+ self.EndModal(wx.ID_OK)
+
+ def RefreshPreview(self):
+ """
+ Refresh preview panel of graphic element
+ May be overridden by inherited classes
+ """
+ # Init preview panel paint device context
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+
+ # Return immediately if no graphic element defined
+ if self.Element is None:
+ return
+
+ # Calculate block size according to graphic element min size due to its
+ # parameters and graphic element min size defined
+ min_width, min_height = self.GetMinElementSize()
+ width = max(self.MinElementSize[0], min_width)
+ height = max(self.MinElementSize[1], min_height)
+ self.Element.SetSize(width, height)
+
+ # Get element position and bounding box to center in preview
+ posx, posy = self.Element.GetPosition()
+ bbox = self.Element.GetBoundingBox()
+
+ # Get Preview panel size
+ client_size = self.Preview.GetClientSize()
+
+ # If graphic element is too big to be displayed in preview panel,
+ # calculate preview panel scale so that graphic element fit inside
+ scale = (max(float(bbox.width) / client_size.width,
+ float(bbox.height) / client_size.height) * 1.1
+ if bbox.width * 1.1 > client_size.width or
+ bbox.height * 1.1 > client_size.height
+ else 1.0)
+ dc.SetUserScale(1.0 / scale, 1.0 / scale)
+
+ # Center graphic element in preview panel
+ x = int(client_size.width * scale - bbox.width) / 2 + posx - bbox.x
+ y = int(client_size.height * scale - bbox.height) / 2 + posy - bbox.y
+ self.Element.SetPosition(x, y)
+
+ # Draw graphic element
+ self.Element.Draw(dc)
+
+ def OnPaint(self, event):
+ """
+ Called when Preview panel need to be redraw
+ @param event: wx.PaintEvent
+ """
+ self.RefreshPreview()
+ event.Skip()
+
\ No newline at end of file
--- a/dialogs/ConnectionDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/ConnectionDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,178 +24,196 @@
import wx
-from graphics import *
+from graphics.GraphicCommons import CONNECTOR, CONTINUATION
+from graphics.FBD_Objects import FBD_Connector
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Create New Connection Dialog
+# Set Connection Parameters Dialog
#-------------------------------------------------------------------------------
-class ConnectionDialog(wx.Dialog):
-
- def __init__(self, parent, controller, apply_button=False):
- wx.Dialog.__init__(self, parent,
+"""
+Class that implements a dialog for defining parameters of a connection graphic
+element
+"""
+
+class ConnectionDialog(BlockPreviewDialog):
+
+ def __init__(self, parent, controller, tagname, apply_button=False):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param apply_button: Enable button for applying connector modification
+ to all connector having the same name in POU (default: False)
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
size=wx.Size(350, 220), title=_('Connection Properties'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
-
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
+ # Init common sizers
+ self._init_sizers(2, 0, 5, None, 2, 1)
+
+ # Create label for connection type
type_label = wx.StaticText(self, label=_('Type:'))
- left_gridsizer.AddWindow(type_label, flag=wx.GROW)
-
- self.ConnectorRadioButton = wx.RadioButton(self,
- label=_('Connector'), style=wx.RB_GROUP)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectorRadioButton)
- self.ConnectorRadioButton.SetValue(True)
- left_gridsizer.AddWindow(self.ConnectorRadioButton, flag=wx.GROW)
-
- self.ConnectionRadioButton = wx.RadioButton(self, label=_('Continuation'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
- left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+
+ # Create radio buttons for selecting connection type
+ self.TypeRadioButtons = {}
+ first = True
+ for type, label in [(CONNECTOR, _('Connector')),
+ (CONTINUATION, _('Continuation'))]:
+ radio_button = wx.RadioButton(self, label=label,
+ style=(wx.RB_GROUP if first else 0))
+ radio_button.SetValue(first)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+ self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+ self.TypeRadioButtons[type] = radio_button
+ first = False
+
+ # Create label for connection name
name_label = wx.StaticText(self, label=_('Name:'))
- left_gridsizer.AddWindow(name_label, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+
+ # Create text control for defining connection name
self.ConnectionName = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.ConnectionName)
- left_gridsizer.AddWindow(self.ConnectionName, flag=wx.GROW)
-
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
- main_sizer.AddSizer(button_sizer, border=20,
+ self.LeftGridSizer.AddWindow(self.ConnectionName, flag=wx.GROW)
+
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+ # Add button for applying connection name modification to all connection
+ # of POU
if apply_button:
self.ApplyToAllButton = wx.Button(self, label=_("Propagate Name"))
self.ApplyToAllButton.SetToolTipString(
_("Apply name modification to all continuations with the same name"))
self.Bind(wx.EVT_BUTTON, self.OnApplyToAll, self.ApplyToAllButton)
- button_sizer.AddWindow(self.ApplyToAllButton)
-
- self.SetSizer(main_sizer)
-
- self.Connection = None
- self.MinConnectionSize = None
-
- self.PouNames = []
- self.PouElementNames = []
-
- self.ConnectorRadioButton.SetFocus()
-
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def SetMinConnectionSize(self, size):
- self.MinConnectionSize = size
+ self.ButtonSizer.AddWindow(self.ApplyToAllButton,
+ border=(3 if wx.Platform == '__WXMSW__' else 10),
+ flag=wx.LEFT)
+ else:
+ self.ConnectionName.ChangeValue(
+ controller.GenerateNewName(
+ tagname, None, "Connection%d", 0))
+
+ # Connector radio button is default control having keyboard focus
+ self.TypeRadioButtons[CONNECTOR].SetFocus()
+
+ def GetConnectionType(self):
+ """
+ Return type selected for connection
+ @return: Type selected (CONNECTOR or CONTINUATION)
+ """
+ return (CONNECTOR
+ if self.TypeRadioButtons[CONNECTOR].GetValue()
+ else CONTINUATION)
def SetValues(self, values):
+ """
+ Set default connection parameters
+ @param values: Connection parameters values
+ """
+ # For each parameters defined, set corresponding control value
for name, value in values.items():
+
+ # Parameter is connection type
if name == "type":
- if value == CONNECTOR:
- self.ConnectorRadioButton.SetValue(True)
- elif value == CONTINUATION:
- self.ConnectionRadioButton.SetValue(True)
+ self.TypeRadioButtons[value].SetValue(True)
+
+ # Parameter is connection name
elif name == "name":
self.ConnectionName.SetValue(value)
+
+ # Refresh preview panel
self.RefreshPreview()
def GetValues(self):
- values = {}
- if self.ConnectorRadioButton.GetValue():
- values["type"] = CONNECTOR
- else:
- values["type"] = CONTINUATION
- values["name"] = self.ConnectionName.GetValue()
- values["width"], values["height"] = self.Connection.GetSize()
+ """
+ Return connection parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
+ values = {
+ "type": self.GetConnectionType(),
+ "name": self.ConnectionName.GetValue()}
+ values["width"], values["height"] = self.Element.GetSize()
return values
- def SetPouNames(self, pou_names):
- self.PouNames = [pou_name.upper() for pou_name in pou_names]
-
- def SetPouElementNames(self, element_names):
- self.PouElementNames = [element_name.upper() for element_name in element_names]
-
- def TestName(self):
+ def TestConnectionName(self):
+ """
+ Test that connection name is valid
+ @return: True if connection name is valid
+ """
message = None
+
+ # Get connection name typed by user
connection_name = self.ConnectionName.GetValue()
+
+ # Test that a name have been defined
if connection_name == "":
message = _("Form isn't complete. Name must be filled!")
- elif not TestIdentifier(connection_name):
- message = _("\"%s\" is not a valid identifier!") % connection_name
- elif connection_name.upper() in IEC_KEYWORDS:
- message = _("\"%s\" is a keyword. It can't be used!") % connection_name
- elif connection_name.upper() in self.PouNames:
- message = _("\"%s\" pou already exists!") % connection_name
- elif connection_name.upper() in self.PouElementNames:
- message = _("\"%s\" element for this pou already exists!") % connection_name
+
+ # If an error have been identify, show error message dialog
if message is not None:
- dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
- dialog.ShowModal()
- dialog.Destroy()
+ self.ShowErrorMessage(message)
+ # Test failed
return False
- return True
+
+ # Return result of element name test
+ return self.TestElementName(connection_name)
def OnOK(self, event):
- if self.TestName():
+ """
+ Called when dialog OK button is pressed
+ Test if connection name is valid
+ @param event: wx.Event from OK button
+ """
+ # Close dialog if connection name is valid
+ if self.TestConnectionName():
self.EndModal(wx.ID_OK)
def OnApplyToAll(self, event):
- if self.TestName():
+ """
+ Called when Apply To All button is pressed
+ Test if connection name is valid
+ @param event: wx.Event from OK button
+ """
+ # Close dialog if connection name is valid
+ if self.TestConnectionName():
self.EndModal(wx.ID_YESTOALL)
def OnTypeChanged(self, event):
+ """
+ Called when connection type changed
+ @param event: wx.RadioButtonEvent
+ """
self.RefreshPreview()
event.Skip()
def OnNameChanged(self, event):
+ """
+ Called when connection name value changed
+ @param event: wx.TextEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- if self.ConnectorRadioButton.GetValue():
- self.Connection = FBD_Connector(self.Preview, CONNECTOR, self.ConnectionName.GetValue())
- else:
- self.Connection = FBD_Connector(self.Preview, CONTINUATION, self.ConnectionName.GetValue())
- width, height = self.MinConnectionSize
- min_width, min_height = self.Connection.GetMinSize()
- width, height = max(min_width, width), max(min_height, height)
- self.Connection.SetSize(width, height)
- clientsize = self.Preview.GetClientSize()
- x = (clientsize.width - width) / 2
- y = (clientsize.height - height) / 2
- self.Connection.SetPosition(x, y)
- self.Connection.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Set graphic element displayed, creating a FBD connection element
+ self.Element = FBD_Connector(self.Preview,
+ self.GetConnectionType(),
+ self.ConnectionName.GetValue())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
+
\ No newline at end of file
--- a/dialogs/FBDBlockDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FBDBlockDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -22,254 +22,331 @@
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import re
+
import wx
-from graphics import *
+from graphics.FBD_Objects import FBD_Block
from controls.LibraryPanel import LibraryPanel
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Create New Block Dialog
+# Helpers
#-------------------------------------------------------------------------------
-class FBDBlockDialog(wx.Dialog):
-
- def __init__(self, parent, controller):
- wx.Dialog.__init__(self, parent,
+def GetBlockTypeDefaultNameModel(blocktype):
+ return re.compile("%s[0-9]+" % blocktype if blocktype is not None else ".*")
+
+#-------------------------------------------------------------------------------
+# Set Block Parameters Dialog
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a dialog for defining parameters of a FBD block graphic
+element
+"""
+
+class FBDBlockDialog(BlockPreviewDialog):
+
+ def __init__(self, parent, controller, tagname):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
size=wx.Size(600, 450), title=_('Block Properties'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
-
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-
+ # Init common sizers
+ self._init_sizers(2, 0, 1, 0, 3, 2)
+
+ # Create static box around library panel
type_staticbox = wx.StaticBox(self, label=_('Type:'))
left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL)
- column_sizer.AddSizer(left_staticboxsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
+ self.LeftGridSizer.AddSizer(left_staticboxsizer, border=5, flag=wx.GROW)
+
+ # Create Library panel and add it to static box
self.LibraryPanel = LibraryPanel(self)
- self.LibraryPanel.SetController(controller)
+ # Set function to call when selection in Library panel changed
setattr(self.LibraryPanel, "_OnTreeItemSelected",
self.OnLibraryTreeItemSelected)
left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5,
flag=wx.GROW|wx.TOP)
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(2)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
+ # Create sizer for other block parameters
top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5)
top_right_gridsizer.AddGrowableCol(1)
- right_gridsizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
-
+ self.RightGridSizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
+
+ # Create label for block name
name_label = wx.StaticText(self, label=_('Name:'))
top_right_gridsizer.AddWindow(name_label,
flag=wx.ALIGN_CENTER_VERTICAL)
+ # Create text control for defining block name
self.BlockName = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.BlockName)
top_right_gridsizer.AddWindow(self.BlockName, flag=wx.GROW)
+ # Create label for extended block input number
inputs_label = wx.StaticText(self, label=_('Inputs:'))
top_right_gridsizer.AddWindow(inputs_label,
flag=wx.ALIGN_CENTER_VERTICAL)
+ # Create spin control for defining extended block input number
self.Inputs = wx.SpinCtrl(self, min=2, max=20,
style=wx.SP_ARROW_KEYS)
self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, self.Inputs)
top_right_gridsizer.AddWindow(self.Inputs, flag=wx.GROW)
- execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
+ # Create label for block execution order
+ execution_order_label = wx.StaticText(self,
+ label=_('Execution Order:'))
top_right_gridsizer.AddWindow(execution_order_label,
flag=wx.ALIGN_CENTER_VERTICAL)
+ # Create spin control for defining block execution order
self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
- self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
+ self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged,
+ self.ExecutionOrder)
top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
- execution_control_label = wx.StaticText(self, label=_('Execution Control:'))
+ # Create label for block execution control
+ execution_control_label = wx.StaticText(self,
+ label=_('Execution Control:'))
top_right_gridsizer.AddWindow(execution_control_label,
flag=wx.ALIGN_CENTER_VERTICAL)
+ # Create check box to enable block execution control
self.ExecutionControl = wx.CheckBox(self)
- self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, self.ExecutionControl)
+ self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged,
+ self.ExecutionControl)
top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW)
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "GetBlockType", controller.GetBlockType)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
- main_sizer.AddSizer(button_sizer, border=20,
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
-
- self.Controller = controller
-
+ # Dictionary containing correspondence between parameter exchanged and
+ # control to fill with parameter value
+ self.ParamsControl = {
+ "extension": self.Inputs,
+ "executionOrder": self.ExecutionOrder,
+ "executionControl": self.ExecutionControl
+ }
+
+ # Init controls value and sensibility
self.BlockName.SetValue("")
self.BlockName.Enable(False)
self.Inputs.Enable(False)
- self.Block = None
- self.MinBlockSize = None
- self.First = True
-
- self.PouNames = []
- self.PouElementNames = []
-
+
+ # Variable containing last name typed
+ self.CurrentBlockName = None
+
+ # Refresh Library panel values
+ self.LibraryPanel.SetBlockList(controller.GetBlockTypes(tagname))
self.LibraryPanel.SetFocus()
- def __del__(self):
- self.Controller = None
-
- def SetBlockList(self, blocklist):
- self.LibraryPanel.SetBlockList(blocklist)
-
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def OnOK(self, event):
- message = None
- selected = self.LibraryPanel.GetSelectedBlock()
- block_name = self.BlockName.GetValue()
- name_enabled = self.BlockName.IsEnabled()
- if selected is None:
- message = _("Form isn't complete. Valid block type must be selected!")
- elif name_enabled and block_name == "":
- message = _("Form isn't complete. Name must be filled!")
- elif name_enabled and not TestIdentifier(block_name):
- message = _("\"%s\" is not a valid identifier!") % block_name
- elif name_enabled and block_name.upper() in IEC_KEYWORDS:
- message = _("\"%s\" is a keyword. It can't be used!") % block_name
- elif name_enabled and block_name.upper() in self.PouNames:
- message = _("\"%s\" pou already exists!") % block_name
- elif name_enabled and block_name.upper() in self.PouElementNames:
- message = _("\"%s\" element for this pou already exists!") % block_name
- if message is not None:
- dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
- dialog.ShowModal()
- dialog.Destroy()
- else:
- self.EndModal(wx.ID_OK)
-
- def SetMinBlockSize(self, size):
- self.MinBlockSize = size
-
- def SetPouNames(self, pou_names):
- self.PouNames = [pou_name.upper() for pou_name in pou_names]
-
- def SetPouElementNames(self, element_names):
- self.PouElementNames = [element_name.upper() for element_name in element_names]
-
def SetValues(self, values):
+ """
+ Set default block parameters
+ @param values: Block parameters values
+ """
+ # Extract block type defined in parameters
blocktype = values.get("type", None)
+
+ # Define regular expression for determine if block name is block
+ # default name
+ default_name_model = GetBlockTypeDefaultNameModel(blocktype)
+
+ # For each parameters defined, set corresponding control value
+ for name, value in values.items():
+
+ # Parameter is block name
+ if name == "name":
+ if value != "":
+ # Set default graphic element name for testing
+ self.DefaultElementName = value
+
+ # Test if block name is type default block name and save
+ # block name if not (name have been typed by user)
+ if default_name_model.match(value) is None:
+ self.CurrentBlockName = value
+
+ self.BlockName.ChangeValue(value)
+
+ # Set value of other controls
+ else:
+ control = self.ParamsControl.get(name, None)
+ if control is not None:
+ control.SetValue(value)
+
+ # Select block type in library panel
if blocktype is not None:
- self.LibraryPanel.SelectTreeItem(blocktype, values.get("inputs", None))
- for name, value in values.items():
- if name == "name":
- self.BlockName.SetValue(value)
- elif name == "extension":
- self.Inputs.SetValue(value)
- elif name == "executionOrder":
- self.ExecutionOrder.SetValue(value)
- elif name == "executionControl":
- self.ExecutionControl.SetValue(value)
+ self.LibraryPanel.SelectTreeItem(blocktype,
+ values.get("inputs", None))
+
+ # Refresh preview panel
self.RefreshPreview()
def GetValues(self):
+ """
+ Return block parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
values = self.LibraryPanel.GetSelectedBlock()
if self.BlockName.IsEnabled() and self.BlockName.GetValue() != "":
values["name"] = self.BlockName.GetValue()
- values["width"], values["height"] = self.Block.GetSize()
- values["extension"] = self.Inputs.GetValue()
- values["executionOrder"] = self.ExecutionOrder.GetValue()
- values["executionControl"] = self.ExecutionControl.GetValue()
+ values["width"], values["height"] = self.Element.GetSize()
+ values.update({
+ name: control.GetValue()
+ for name, control in self.ParamsControl.iteritems()})
return values
-
+
+ def OnOK(self, event):
+ """
+ Called when dialog OK button is pressed
+ Test if parameters defined are valid
+ @param event: wx.Event from OK button
+ """
+ message = None
+
+ # Get block type selected
+ selected = self.LibraryPanel.GetSelectedBlock()
+
+ # Get block type name and if block is a function block
+ block_name = self.BlockName.GetValue()
+ name_enabled = self.BlockName.IsEnabled()
+
+ # Test that a type has been selected for block
+ if selected is None:
+ message = _("Form isn't complete. Valid block type must be selected!")
+
+ # Test, if block is a function block, that a name have been defined
+ elif name_enabled and block_name == "":
+ message = _("Form isn't complete. Name must be filled!")
+
+ # Show error message if an error is detected
+ if message is not None:
+ self.ShowErrorMessage(message)
+
+ # Test block name validity if necessary
+ elif not name_enabled or self.TestElementName(block_name):
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.OnOK(self, event)
+
def OnLibraryTreeItemSelected(self, event):
+ """
+ Called when block type selected in library panel
+ @param event: wx.TreeEvent
+ """
+ # Get type selected in library panel
values = self.LibraryPanel.GetSelectedBlock()
- if values is not None:
- blocktype = self.Controller.GetBlockType(values["type"], values["inputs"])
- else:
- blocktype = None
+
+ # Get block type informations
+ blocktype = (self.Controller.GetBlockType(values["type"],
+ values["inputs"])
+ if values is not None else None)
+
+ # Set input number spin control according to block type informations
if blocktype is not None:
self.Inputs.SetValue(len(blocktype["inputs"]))
self.Inputs.Enable(blocktype["extensible"])
- self.BlockName.Enable(blocktype["type"] != "function")
- wx.CallAfter(self.RefreshPreview)
+ else:
+ self.Inputs.SetValue(2)
+ self.Inputs.Enable(False)
+
+ # Update block name with default value if block type is a function and
+ # current block name wasn't typed by user
+ if blocktype is not None and blocktype["type"] != "function":
+ self.BlockName.Enable(True)
+
+ if self.CurrentBlockName is None:
+ # Generate new block name according to block type, taking
+ # default element name if it was already a default name for this
+ # block type
+ default_name_model = GetBlockTypeDefaultNameModel(values["type"])
+ block_name = (
+ self.DefaultElementName
+ if (self.DefaultElementName is not None and
+ default_name_model.match(self.DefaultElementName))
+ else self.Controller.GenerateNewName(
+ self.TagName, None, values["type"]+"%d", 0))
+ else:
+ block_name = self.CurrentBlockName
+
+ self.BlockName.ChangeValue(block_name)
else:
self.BlockName.Enable(False)
- self.Inputs.Enable(False)
- self.Inputs.SetValue(2)
- wx.CallAfter(self.ErasePreview)
+ self.BlockName.ChangeValue("")
+
+ # Refresh preview panel
+ self.RefreshPreview()
def OnNameChanged(self, event):
+ """
+ Called when block name value changed
+ @param event: wx.TextEvent
+ """
if self.BlockName.IsEnabled():
+ # Save block name typed by user
+ self.CurrentBlockName = self.BlockName.GetValue()
self.RefreshPreview()
event.Skip()
def OnInputsChanged(self, event):
+ """
+ Called when block inputs number changed
+ @param event: wx.SpinEvent
+ """
if self.Inputs.IsEnabled():
self.RefreshPreview()
event.Skip()
def OnExecutionOrderChanged(self, event):
+ """
+ Called when block execution order value changed
+ @param event: wx.SpinEvent
+ """
self.RefreshPreview()
event.Skip()
def OnExecutionControlChanged(self, event):
+ """
+ Called when block execution control value changed
+ @param event: wx.SpinEvent
+ """
self.RefreshPreview()
event.Skip()
- def ErasePreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.Clear()
- self.Block = None
-
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Get type selected in library panel
values = self.LibraryPanel.GetSelectedBlock()
+
+ # If a block type is selected in library panel
if values is not None:
- if self.BlockName.IsEnabled():
- blockname = self.BlockName.GetValue()
- else:
- blockname = ""
- self.Block = FBD_Block(self.Preview, values["type"],
- blockname,
+ # Set graphic element displayed, creating a FBD block element
+ self.Element = FBD_Block(self.Preview, values["type"],
+ (self.BlockName.GetValue()
+ if self.BlockName.IsEnabled()
+ else ""),
extension = self.Inputs.GetValue(),
inputs = values["inputs"],
executionControl = self.ExecutionControl.GetValue(),
executionOrder = self.ExecutionOrder.GetValue())
- width, height = self.MinBlockSize
- min_width, min_height = self.Block.GetMinSize()
- width, height = max(min_width, width), max(min_height, height)
- self.Block.SetSize(width, height)
- clientsize = self.Preview.GetClientSize()
- x = (clientsize.width - width) / 2
- y = (clientsize.height - height) / 2
- self.Block.SetPosition(x, y)
- self.Block.Draw(dc)
+
+ # Reset graphic element displayed
else:
- self.Block = None
-
- def OnPaint(self, event):
- if self.Block is not None:
- self.RefreshPreview()
- event.Skip()
+ self.Element = None
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/FBDVariableDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FBDVariableDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,12 +24,16 @@
import wx
-from graphics import *
+from graphics.GraphicCommons import INPUT, INOUT, OUTPUT
+from graphics.FBD_Objects import FBD_Variable
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
# Helpers
#-------------------------------------------------------------------------------
+# Dictionaries containing correspondence between variable block class and string
+# to be shown in Class combo box in both sense
VARIABLE_CLASSES_DICT = {INPUT : _("Input"),
INOUT : _("InOut"),
OUTPUT : _("Output")}
@@ -37,223 +41,250 @@
[(value, key) for key, value in VARIABLE_CLASSES_DICT.iteritems()])
#-------------------------------------------------------------------------------
-# Create New Variable Dialog
-#-------------------------------------------------------------------------------
-
-class FBDVariableDialog(wx.Dialog):
-
- def __init__(self, parent, controller, transition = ""):
- wx.Dialog.__init__(self, parent,
+# Set Variable Parameters Dialog
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a dialog for defining parameters of a FBD variable graphic
+element
+"""
+
+class FBDVariableDialog(BlockPreviewDialog):
+
+ def __init__(self, parent, controller, tagname, exclude_input=False):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param exclude_input: Exclude input from variable class selection
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
size=wx.Size(400, 380), title=_('Variable Properties'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(2)
-
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
+ # Init common sizers
+ self._init_sizers(4, 2, 4, None, 3, 2)
+
+ # Create label for variable class
class_label = wx.StaticText(self, label=_('Class:'))
- left_gridsizer.AddWindow(class_label, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(class_label, flag=wx.GROW)
+
+ # Create a combo box for defining variable class
self.Class = wx.ComboBox(self, style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.OnClassChanged, self.Class)
- left_gridsizer.AddWindow(self.Class, flag=wx.GROW)
-
- expression_label = wx.StaticText(self, label=_('Expression:'))
- left_gridsizer.AddWindow(expression_label, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(self.Class, flag=wx.GROW)
+
+ # Create label for variable execution order
+ execution_order_label = wx.StaticText(self,
+ label=_('Execution Order:'))
+ self.LeftGridSizer.AddWindow(execution_order_label, flag=wx.GROW)
+
+ # Create spin control for defining variable execution order
+ self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged,
+ self.ExecutionOrder)
+ self.LeftGridSizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+
+ # Create label for variable expression
+ name_label = wx.StaticText(self, label=_('Expression:'))
+ self.RightGridSizer.AddWindow(name_label, border=5,
+ flag=wx.GROW|wx.BOTTOM)
+
+ # Create text control for defining variable expression
self.Expression = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnExpressionChanged, self.Expression)
- left_gridsizer.AddWindow(self.Expression, flag=wx.GROW)
-
- execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
- left_gridsizer.AddWindow(execution_order_label, flag=wx.GROW)
-
- self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
- self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
- left_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
-
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
- name_label = wx.StaticText(self, label=_('Name:'))
- right_gridsizer.AddWindow(name_label, flag=wx.GROW)
-
- self.VariableName = wx.ListBox(self, size=wx.Size(0, 0),
+ self.RightGridSizer.AddWindow(self.Expression, flag=wx.GROW)
+
+ # Create a list box to selected variable expression in the list of
+ # variables defined in POU
+ self.VariableName = wx.ListBox(self, size=wx.Size(0, 120),
style=wx.LB_SINGLE|wx.LB_SORT)
self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
- right_gridsizer.AddWindow(self.VariableName, flag=wx.GROW)
-
- preview_label = wx.StaticText(self, label=_('Preview:'))
- main_sizer.AddWindow(preview_label, border=20,
+ self.RightGridSizer.AddWindow(self.VariableName, flag=wx.GROW)
+
+ # Add preview panel and associated label to sizers
+ self.MainSizer.AddWindow(self.PreviewLabel, border=20,
flag=wx.GROW|wx.LEFT|wx.RIGHT)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- main_sizer.AddWindow(self.Preview, border=20,
+ self.MainSizer.AddWindow(self.Preview, border=20,
flag=wx.GROW|wx.LEFT|wx.RIGHT)
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
- main_sizer.AddSizer(button_sizer, border=20,
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
-
- self.Transition = transition
- self.Variable = None
- self.VarList = []
- self.MinVariableSize = None
-
- for choice in VARIABLE_CLASSES_DICT.itervalues():
- self.Class.Append(choice)
- self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[INPUT])
-
+ # Set options that can be selected in class combo box
+ for var_class, choice in VARIABLE_CLASSES_DICT.iteritems():
+ if not exclude_input or var_class != INPUT:
+ self.Class.Append(choice)
+ self.Class.SetSelection(0)
+
+ # Extract list of variables defined in POU
+ self.RefreshVariableList()
+
+ # Refresh values in name list box
self.RefreshNameList()
+
+ # Class combo box is default control having keyboard focus
self.Class.SetFocus()
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
def RefreshNameList(self):
- selected = self.VariableName.GetStringSelection()
- var_class = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+ """
+ Called to refresh names in name list box
+ """
+ # Get variable class to select POU variable applicable
+ var_class = VARIABLE_CLASSES_DICT_REVERSE[
+ self.Class.GetStringSelection()]
+
+ # Refresh names in name list box by selecting variables in POU variables
+ # list that can be applied to variable class
self.VariableName.Clear()
- self.VariableName.Append("")
- for name, var_type, value_type in self.VarList:
+ for name, (var_type, value_type) in self.VariableList.iteritems():
if var_type != "Input" or var_class == INPUT:
self.VariableName.Append(name)
- if selected != "" and self.VariableName.FindString(selected) != wx.NOT_FOUND:
+
+ # Get variable expression and select corresponding value in name list
+ # box if it exists
+ selected = self.Expression.GetValue()
+ if (selected != "" and
+ self.VariableName.FindString(selected) != wx.NOT_FOUND):
self.VariableName.SetStringSelection(selected)
- self.Expression.Enable(False)
else:
- self.VariableName.SetStringSelection("")
- #self.Expression.Enable(var_class == INPUT)
+ self.VariableName.SetSelection(wx.NOT_FOUND)
+
+ # Disable name list box if no name present inside
self.VariableName.Enable(self.VariableName.GetCount() > 0)
- def SetMinVariableSize(self, size):
- self.MinVariableSize = size
-
- def SetVariables(self, vars):
- self.VarList = vars
- self.RefreshNameList()
-
def SetValues(self, values):
- value_type = values.get("type", None)
- value_name = values.get("name", None)
- if value_type:
- self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[value_type])
+ """
+ Set default variable parameters
+ @param values: Variable parameters values
+ """
+
+ # Get class parameter value
+ var_class = values.get("class", None)
+ if var_class is not None:
+ # Set class selected in class combo box
+ self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[var_class])
+ # Refresh names in name list box according to var class
self.RefreshNameList()
- if value_name:
- if self.VariableName.FindString(value_name) != wx.NOT_FOUND:
- self.VariableName.SetStringSelection(value_name)
- self.Expression.Enable(False)
- else:
- self.Expression.SetValue(value_name)
- self.VariableName.Enable(False)
- if "executionOrder" in values:
- self.ExecutionOrder.SetValue(values["executionOrder"])
+
+ # For each parameters defined, set corresponding control value
+ for name, value in values.items():
+
+ # Parameter is variable expression
+ if name == "expression":
+ # Set expression text control value
+ self.Expression.ChangeValue(value)
+ # Select corresponding text in name list box if it exists
+ if self.VariableName.FindString(value) != wx.NOT_FOUND:
+ self.VariableName.SetStringSelection(value)
+ else:
+ self.VariableName.SetSelection(wx.NOT_FOUND)
+
+ # Parameter is variable execution order
+ elif name == "executionOrder":
+ self.ExecutionOrder.SetValue(value)
+
+ # Refresh preview panel
self.RefreshPreview()
def GetValues(self):
- values = {}
- values["type"] = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+ """
+ Return block parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
expression = self.Expression.GetValue()
- if self.Expression.IsEnabled() and expression != "":
- values["name"] = expression
- else:
- values["name"] = self.VariableName.GetStringSelection()
- values["value_type"] = None
- for var_name, var_type, value_type in self.VarList:
- if var_name == values["name"]:
- values["value_type"] = value_type
- values["width"], values["height"] = self.Variable.GetSize()
- values["executionOrder"] = self.ExecutionOrder.GetValue()
+ values = {
+ "class": VARIABLE_CLASSES_DICT_REVERSE[
+ self.Class.GetStringSelection()],
+ "expression": expression,
+ "var_type": self.VariableList.get(expression, (None, None))[1],
+ "executionOrder": self.ExecutionOrder.GetValue()}
+ values["width"], values["height"] = self.Element.GetSize()
return values
def OnOK(self, event):
+ """
+ Called when dialog OK button is pressed
+ Test if parameters defined are valid
+ @param event: wx.Event from OK button
+ """
message = None
- expression = self.Expression.GetValue()
- if self.Expression.IsEnabled():
- value = expression
- else:
- value = self.VariableName.GetStringSelection()
+
+ # Test that an expression have been selected or typed by user
+ value = self.Expression.GetValue()
if value == "":
message = _("At least a variable or an expression must be selected!")
- elif value.upper() in IEC_KEYWORDS:
- message = _("\"%s\" is a keyword. It can't be used!") % value
+
+ # Show error message if an error is detected
if message is not None:
- message = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
+ self.ShowErrorMessage(message)
+
else:
- self.EndModal(wx.ID_OK)
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.OnOK(self, event)
def OnClassChanged(self, event):
+ """
+ Called when variable class value changed
+ @param event: wx.ComboBoxEvent
+ """
+ # Refresh name list box values
self.RefreshNameList()
+
self.RefreshPreview()
event.Skip()
def OnNameChanged(self, event):
- if self.VariableName.GetStringSelection() != "":
- self.Expression.Enable(False)
- elif VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()] == INPUT:
- self.Expression.Enable(True)
+ """
+ Called when name selected in name list box changed
+ @param event: wx.ListBoxEvent
+ """
+ # Change expression test control value to the value selected in name
+ # list box if value selected is valid
+ if self.VariableName.GetSelection() != wx.NOT_FOUND:
+ self.Expression.ChangeValue(self.VariableName.GetStringSelection())
+
self.RefreshPreview()
event.Skip()
def OnExpressionChanged(self, event):
- if self.Expression.GetValue() != "":
- self.VariableName.Enable(False)
- else:
- self.VariableName.Enable(True)
+ """
+ Called when expression text control is changed by user
+ @param event: wx.ListBoxEvent
+ """
+ # Select the corresponding value in name list box if it exists
+ self.VariableName.SetSelection(
+ self.VariableName.FindString(self.Expression.GetValue()))
+
self.RefreshPreview()
event.Skip()
def OnExecutionOrderChanged(self, event):
+ """
+ Called when block execution control value changed
+ @param event: wx.SpinEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- expression = self.Expression.GetValue()
- if self.Expression.IsEnabled() and expression != "":
- name = expression
- else:
- name = self.VariableName.GetStringSelection()
- type = ""
- for var_name, var_type, value_type in self.VarList:
- if var_name == name:
- type = value_type
- classtype = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
- self.Variable = FBD_Variable(self.Preview, classtype, name, type, executionOrder = self.ExecutionOrder.GetValue())
- width, height = self.MinVariableSize
- min_width, min_height = self.Variable.GetMinSize()
- width, height = max(min_width, width), max(min_height, height)
- self.Variable.SetSize(width, height)
- clientsize = self.Preview.GetClientSize()
- x = (clientsize.width - width) / 2
- y = (clientsize.height - height) / 2
- self.Variable.SetPosition(x, y)
- self.Variable.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Get expression value to put in FBD variable element
+ name = self.Expression.GetValue()
+
+ # Set graphic element displayed, creating a FBD variable element
+ self.Element = FBD_Variable(self.Preview,
+ VARIABLE_CLASSES_DICT_REVERSE[
+ self.Class.GetStringSelection()],
+ name,
+ self.VariableList.get(name, ("", ""))[1],
+ executionOrder = self.ExecutionOrder.GetValue())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
+
+
\ No newline at end of file
--- a/dialogs/FindInPouDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FindInPouDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -97,6 +97,7 @@
flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT)
self.FindButton = wx.Button(panel, label=_("Find"))
+ self.FindButton.SetDefault()
self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
@@ -109,7 +110,8 @@
self.ParentWindow = parent
self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
-
+
+ self.FindPattern.SetFocus()
self.RefreshButtonsState()
def RefreshButtonsState(self):
--- a/dialogs/LDElementDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/LDElementDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,198 +24,172 @@
import wx
-from graphics import *
+from graphics.GraphicCommons import CONTACT_NORMAL, CONTACT_REVERSE, \
+ CONTACT_RISING, CONTACT_FALLING, COIL_NORMAL, COIL_REVERSE, COIL_SET, \
+ COIL_RESET, COIL_RISING, COIL_FALLING
+from graphics.LD_Objects import LD_Contact, LD_Coil
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Edit Ladder Element Properties Dialog
+# Set Ladder Element Parmeters Dialog
#-------------------------------------------------------------------------------
-class LDElementDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a LD contact or coil
+graphic element
+"""
+
+class LDElementDialog(BlockPreviewDialog):
- def __init__(self, parent, controller, type):
- if type == "contact":
- wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
- title=_("Edit Contact Values"))
- else:
- wx.Dialog.__init__(self, parent, size=wx.Size(350, 310),
- title=_("Edit Coil Values"))
+ def __init__(self, parent, controller, tagname, type):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param type: Type of LD element ('contact or 'coil')
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
+ size=wx.Size(350, 280 if type == "contact" else 330),
+ title=(_("Edit Contact Values")
+ if type == "contact"
+ else _("Edit Coil Values")))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
+ # Init common sizers
+ self._init_sizers(2, 0,
+ (7 if type == "contact" else 9), None, 2, 1)
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+ # Create label for LD element modifier
+ modifier_label = wx.StaticText(self, label=_('Modifier:'))
+ self.LeftGridSizer.AddWindow(modifier_label, border=5,
+ flag=wx.GROW|wx.BOTTOM)
- if type == "contact":
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=0)
- else:
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=9, vgap=0)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
+ # Create radio buttons for selecting LD element modifier
+ self.ModifierRadioButtons = {}
+ first = True
+ element_modifiers = ([CONTACT_NORMAL, CONTACT_REVERSE,
+ CONTACT_RISING, CONTACT_FALLING]
+ if type == "contact"
+ else [COIL_NORMAL, COIL_REVERSE, COIL_SET,
+ COIL_RESET, COIL_RISING, COIL_FALLING])
+ modifiers_label = [_("Normal"), _("Negated")] + \
+ ([_("Set"), _("Reset")] if type == "coil" else []) + \
+ [_("Rising Edge"), _("Falling Edge")]
- modifier_label = wx.StaticText(self, label=_('Modifier:'))
- left_gridsizer.AddWindow(modifier_label, border=5, flag=wx.GROW|wx.BOTTOM)
+ for modifier, label in zip(element_modifiers, modifiers_label):
+ radio_button = wx.RadioButton(self, label=label,
+ style=(wx.RB_GROUP if first else 0))
+ radio_button.SetValue(first)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnModifierChanged, radio_button)
+ self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+ self.ModifierRadioButtons[modifier] = radio_button
+ first = False
- self.Normal = wx.RadioButton(self, label=_("Normal"), style=wx.RB_GROUP)
- self.Normal.SetValue(True)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Normal)
- left_gridsizer.AddWindow(self.Normal, flag=wx.GROW)
+ # Create label for LD element variable
+ element_variable_label = wx.StaticText(self, label=_('Variable:'))
+ self.LeftGridSizer.AddWindow(element_variable_label, border=5,
+ flag=wx.GROW|wx.TOP)
- self.Negated = wx.RadioButton(self, label=_("Negated"))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Negated)
- left_gridsizer.AddWindow(self.Negated, flag=wx.GROW)
+ # Create a combo box for defining LD element variable
+ self.ElementVariable = wx.ComboBox(self, style=wx.CB_READONLY|wx.CB_SORT)
+ self.Bind(wx.EVT_COMBOBOX, self.OnVariableChanged,
+ self.ElementVariable)
+ self.LeftGridSizer.AddWindow(self.ElementVariable, border=5,
+ flag=wx.GROW|wx.TOP)
- if type != "contact":
- self.Set = wx.RadioButton(self, label=_("Set"))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Set)
- left_gridsizer.AddWindow(self.Set, flag=wx.GROW)
-
- self.Reset = wx.RadioButton(self, label=_("Reset"))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Reset)
- left_gridsizer.AddWindow(self.Reset, flag=wx.GROW)
-
- self.RisingEdge = wx.RadioButton(self, label=_("Rising Edge"))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RisingEdge)
- left_gridsizer.AddWindow(self.RisingEdge, flag=wx.GROW)
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
- self.FallingEdge = wx.RadioButton(self, label=_("Falling Edge"))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.FallingEdge)
- left_gridsizer.AddWindow(self.FallingEdge, flag=wx.GROW)
-
- element_name_label = wx.StaticText(self, label=_('Name:'))
- left_gridsizer.AddWindow(element_name_label, border=5, flag=wx.GROW|wx.TOP)
-
- self.ElementName = wx.ComboBox(self, style=wx.CB_READONLY)
- self.Bind(wx.EVT_COMBOBOX, self.OnNameChanged, self.ElementName)
- left_gridsizer.AddWindow(self.ElementName, border=5, flag=wx.GROW|wx.TOP)
-
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- main_sizer.AddSizer(button_sizer, border=20,
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
+ # Save LD element class
+ self.ElementClass = (LD_Contact if type == "contact" else LD_Coil)
- if type == "contact":
- self.Element = LD_Contact(self.Preview, CONTACT_NORMAL, "")
- else:
- self.Element = LD_Coil(self.Preview, COIL_NORMAL, "")
+ # Extract list of variables defined in POU
+ self.RefreshVariableList()
- self.Type = type
+ # Set values in ElementVariable
+ for name, (var_type, value_type) in self.VariableList.iteritems():
+ # Only select BOOL variable and avoid input for coil
+ if (type == "contact" or var_type != "Input") and \
+ value_type == "BOOL":
+ self.ElementVariable.Append(name)
+ self.ElementVariable.Enable(self.ElementVariable.GetCount() > 0)
- self.Normal.SetFocus()
+ # Normal radio button is default control having keyboard focus
+ self.ModifierRadioButtons[element_modifiers[0]].SetFocus()
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def SetElementSize(self, size):
- min_width, min_height = self.Element.GetMinSize()
- width, height = max(min_width, size[0]), max(min_height, size[1])
- self.Element.SetSize(width, height)
-
- def SetVariables(self, vars):
- self.ElementName.Clear()
- for name in vars:
- self.ElementName.Append(name)
- self.ElementName.Enable(self.ElementName.GetCount() > 0)
+ def GetElementModifier(self):
+ """
+ Return modifier selected for LD element
+ @return: Modifier selected (None if not found)
+ """
+ # Go through radio buttons and return modifier associated to the one
+ # that is selected
+ for modifier, control in self.ModifierRadioButtons.iteritems():
+ if control.GetValue():
+ return modifier
+ return None
def SetValues(self, values):
+ """
+ Set default LD element parameters
+ @param values: LD element parameters values
+ """
+ # For each parameters defined, set corresponding control value
for name, value in values.items():
- if name == "name":
- self.Element.SetName(value)
- self.ElementName.SetStringSelection(value)
- elif name == "type":
- self.Element.SetType(value)
- if self.Type == "contact":
- if value == CONTACT_NORMAL:
- self.Normal.SetValue(True)
- elif value == CONTACT_REVERSE:
- self.Negated.SetValue(True)
- elif value == CONTACT_RISING:
- self.RisingEdge.SetValue(True)
- elif value == CONTACT_FALLING:
- self.FallingEdge.SetValue(True)
- elif self.Type == "coil":
- if value == COIL_NORMAL:
- self.Normal.SetValue(True)
- elif value == COIL_REVERSE:
- self.Negated.SetValue(True)
- elif value == COIL_SET:
- self.Set.SetValue(True)
- elif value == COIL_RESET:
- self.Reset.SetValue(True)
- elif value == COIL_RISING:
- self.RisingEdge.SetValue(True)
- elif value == COIL_FALLING:
- self.FallingEdge.SetValue(True)
+
+ # Parameter is LD element variable
+ if name == "variable":
+ self.ElementVariable.SetStringSelection(value)
+
+ # Set value of other controls
+ elif name == "modifier":
+ self.ModifierRadioButtons[value].SetValue(True)
+
+ # Refresh preview panel
+ self.RefreshPreview()
def GetValues(self):
- values = {}
- values["name"] = self.Element.GetName()
- values["type"] = self.Element.GetType()
+ """
+ Return LD element parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
+ values = {
+ "variable": self.ElementVariable.GetValue(),
+ "modifier": self.GetElementModifier()}
values["width"], values["height"] = self.Element.GetSize()
return values
- def OnTypeChanged(self, event):
- if self.Type == "contact":
- if self.Normal.GetValue():
- self.Element.SetType(CONTACT_NORMAL)
- elif self.Negated.GetValue():
- self.Element.SetType(CONTACT_REVERSE)
- elif self.RisingEdge.GetValue():
- self.Element.SetType(CONTACT_RISING)
- elif self.FallingEdge.GetValue():
- self.Element.SetType(CONTACT_FALLING)
- elif self.Type == "coil":
- if self.Normal.GetValue():
- self.Element.SetType(COIL_NORMAL)
- elif self.Negated.GetValue():
- self.Element.SetType(COIL_REVERSE)
- elif self.Set.GetValue():
- self.Element.SetType(COIL_SET)
- elif self.Reset.GetValue():
- self.Element.SetType(COIL_RESET)
- elif self.RisingEdge.GetValue():
- self.Element.SetType(COIL_RISING)
- elif self.FallingEdge.GetValue():
- self.Element.SetType(COIL_FALLING)
+ def OnModifierChanged(self, event):
+ """
+ Called when LD element modifier changed
+ @param event: wx.RadioButtonEvent
+ """
self.RefreshPreview()
event.Skip()
- def OnNameChanged(self, event):
- self.Element.SetName(self.ElementName.GetStringSelection())
+ def OnVariableChanged(self, event):
+ """
+ Called when LD element associated variable changed
+ @param event: wx.ComboBoxEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- clientsize = self.Preview.GetClientSize()
- width, height = self.Element.GetSize()
- self.Element.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
- self.Element.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Set graphic element displayed, creating a LD element
+ self.Element = self.ElementClass(
+ self.Preview,
+ self.GetElementModifier(),
+ self.ElementVariable.GetStringSelection())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/LDPowerRailDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/LDPowerRailDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,126 +23,142 @@
import wx
-from graphics import *
+from graphics.GraphicCommons import LEFTRAIL, RIGHTRAIL, LD_LINE_SIZE
+from graphics.LD_Objects import LD_PowerRail
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Edit Ladder Power Rail Properties Dialog
+# Set Ladder Power Rail Parameters Dialog
#-------------------------------------------------------------------------------
-class LDPowerRailDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a power rail graphic
+element
+"""
+
+class LDPowerRailDialog(BlockPreviewDialog):
- def __init__(self, parent, controller, type = LEFTRAIL, number = 1):
- wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
- title=_('Power Rail Properties'))
+ def __init__(self, parent, controller, tagname):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
+ size=wx.Size(350, 260), title=_('Power Rail Properties'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
+ # Init common sizers
+ self._init_sizers(2, 0, 5, None, 2, 1)
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+ # Create label for connection type
+ type_label = wx.StaticText(self, label=_('Type:'))
+ self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
+ # Create radio buttons for selecting power rail type
+ self.TypeRadioButtons = {}
+ first = True
+ for type, label in [(LEFTRAIL, _('Left PowerRail')),
+ (RIGHTRAIL, _('Right PowerRail'))]:
+ radio_button = wx.RadioButton(self, label=label,
+ style=(wx.RB_GROUP if first else 0))
+ radio_button.SetValue(first)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+ self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+ self.TypeRadioButtons[type] = radio_button
+ first = False
- type_label = wx.StaticText(self, label=_('Type:'))
- left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+ # Create label for power rail pin number
+ pin_number_label = wx.StaticText(self, label=_('Pin number:'))
+ self.LeftGridSizer.AddWindow(pin_number_label, flag=wx.GROW)
- self.LeftPowerRail = wx.RadioButton(self,
- label=_('Left PowerRail'), style=wx.RB_GROUP)
- self.LeftPowerRail.SetValue(True)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.LeftPowerRail)
- left_gridsizer.AddWindow(self.LeftPowerRail, flag=wx.GROW)
-
- self.RightPowerRail = wx.RadioButton(self, label=_('Right PowerRail'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RightPowerRail)
- left_gridsizer.AddWindow(self.RightPowerRail, flag=wx.GROW)
-
- pin_number_label = wx.StaticText(self, label=_('Pin number:'))
- left_gridsizer.AddWindow(pin_number_label, flag=wx.GROW)
-
+ # Create spin control for defining power rail pin number
self.PinNumber = wx.SpinCtrl(self, min=1, max=50,
style=wx.SP_ARROW_KEYS)
self.Bind(wx.EVT_SPINCTRL, self.OnPinNumberChanged, self.PinNumber)
- left_gridsizer.AddWindow(self.PinNumber, flag=wx.GROW)
+ self.LeftGridSizer.AddWindow(self.PinNumber, flag=wx.GROW)
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- main_sizer.AddSizer(button_sizer, border=20,
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
-
- self.Type = type
- if type == LEFTRAIL:
- self.LeftPowerRail.SetValue(True)
- elif type == RIGHTRAIL:
- self.RightPowerRail.SetValue(True)
- self.PinNumber.SetValue(number)
-
- self.PowerRailMinSize = (0, 0)
- self.PowerRail = None
-
- self.LeftPowerRail.SetFocus()
-
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def SetMinSize(self, size):
- self.PowerRailMinSize = size
- self.RefreshPreview()
+ # Left Power Rail radio button is default control having keyboard focus
+ self.TypeRadioButtons[LEFTRAIL].SetFocus()
+
+ def GetMinElementSize(self):
+ """
+ Get minimal graphic element size
+ @return: Tuple containing minimal size (width, height) or None if no
+ element defined
+ """
+ return self.Element.GetMinSize(True)
+
+ def GetPowerRailType(self):
+ """
+ Return type selected for power rail
+ @return: Type selected (LEFTRAIL or RIGHTRAIL)
+ """
+ return (LEFTRAIL
+ if self.TypeRadioButtons[LEFTRAIL].GetValue()
+ else RIGHTRAIL)
+
+ def SetValues(self, values):
+ """
+ Set default power rail parameters
+ @param values: Power rail parameters values
+ """
+ # For each parameters defined, set corresponding control value
+ for name, value in values.items():
+
+ # Parameter is power rail type
+ if name == "type":
+ self.TypeRadioButtons[value].SetValue(True)
+
+ # Parameter is power rail pin number
+ elif name == "pin_number":
+ self.PinNumber.SetValue(value)
def GetValues(self):
- values = {}
- values["type"] = self.Type
- values["number"] = self.PinNumber.GetValue()
- values["width"], values["height"] = self.PowerRail.GetSize()
+ """
+ Return power rail parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
+ values = {
+ "type": self.GetPowerRailType(),
+ "pin_number": self.PinNumber.GetValue()}
+ values["width"], values["height"] = self.Element.GetSize()
return values
def OnTypeChanged(self, event):
- if self.LeftPowerRail.GetValue():
- self.Type = LEFTRAIL
- elif self.RightPowerRail.GetValue():
- self.Type = RIGHTRAIL
+ """
+ Called when power rail type changed
+ @param event: wx.RadioButtonEvent
+ """
self.RefreshPreview()
event.Skip()
def OnPinNumberChanged(self, event):
+ """
+ Called when power rail pin number value changed
+ @param event: wx.SpinEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- self.PowerRail = LD_PowerRail(self.Preview, self.Type, connectors = self.PinNumber.GetValue())
- min_width, min_height = 2, LD_LINE_SIZE * self.PinNumber.GetValue()
- width, height = max(min_width, self.PowerRailMinSize[0]), max(min_height, self.PowerRailMinSize[1])
- self.PowerRail.SetSize(width, height)
- clientsize = self.Preview.GetClientSize()
- self.PowerRail.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
- self.PowerRail.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+
+ # Set graphic element displayed, creating a power rail element
+ self.Element = LD_PowerRail(self.Preview,
+ self.GetPowerRailType(),
+ connectors = self.PinNumber.GetValue())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/SFCDivergenceDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCDivergenceDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,141 +23,132 @@
import wx
-from graphics import *
+from graphics.GraphicCommons import SELECTION_DIVERGENCE, \
+ SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE
+from graphics.SFC_Objects import SFC_Divergence
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
# Create New Divergence Dialog
#-------------------------------------------------------------------------------
-class SFCDivergenceDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters for creating a new
+divergence graphic element
+"""
+
+class SFCDivergenceDialog(BlockPreviewDialog):
- def __init__(self, parent, controller):
- wx.Dialog.__init__(self, parent, size=wx.Size(500, 300),
+ def __init__(self, parent, controller, tagname):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
+ size=wx.Size(500, 300),
title=_('Create a new divergence or convergence'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
+ # Init common sizers
+ self._init_sizers(2, 0, 7, None, 2, 1)
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+ # Create label for divergence type
+ type_label = wx.StaticText(self, label=_('Type:'))
+ self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
- type_label = wx.StaticText(self, label=_('Type:'))
- left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+ # Create radio buttons for selecting divergence type
+ self.TypeRadioButtons = {}
+ first = True
+ for type, label in [
+ (SELECTION_DIVERGENCE, _('Selection Divergence')),
+ (SELECTION_CONVERGENCE, _('Selection Convergence')),
+ (SIMULTANEOUS_DIVERGENCE, _('Simultaneous Divergence')),
+ (SIMULTANEOUS_CONVERGENCE, _('Simultaneous Convergence'))]:
+ radio_button = wx.RadioButton(self, label=label,
+ style=(wx.RB_GROUP if first else 0))
+ radio_button.SetValue(first)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+ self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+ self.TypeRadioButtons[type] = radio_button
+ first = False
- self.SelectionDivergence = wx.RadioButton(self,
- label=_('Selection Divergence'), style=wx.RB_GROUP)
- self.SelectionDivergence.SetValue(True)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
- self.SelectionDivergence)
- left_gridsizer.AddWindow(self.SelectionDivergence, flag=wx.GROW)
-
- self.SelectionConvergence = wx.RadioButton(self,
- label=_('Selection Convergence'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
- self.SelectionConvergence)
- left_gridsizer.AddWindow(self.SelectionConvergence, flag=wx.GROW)
-
- self.SimultaneousDivergence = wx.RadioButton(self,
- label=_('Simultaneous Divergence'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
- self.SimultaneousDivergence)
- left_gridsizer.AddWindow(self.SimultaneousDivergence, flag=wx.GROW)
-
- self.SimultaneousConvergence = wx.RadioButton(self,
- label=_('Simultaneous Convergence'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
- self.SimultaneousConvergence)
- left_gridsizer.AddWindow(self.SimultaneousConvergence, flag=wx.GROW)
-
+ # Create label for number of divergence sequences
sequences_label = wx.StaticText(self,
label=_('Number of sequences:'))
- left_gridsizer.AddWindow(sequences_label, flag=wx.GROW)
+ self.LeftGridSizer.AddWindow(sequences_label, flag=wx.GROW)
+ # Create spin control for defining number of divergence sequences
self.Sequences = wx.SpinCtrl(self, min=2, max=20)
self.Bind(wx.EVT_SPINCTRL, self.OnSequencesChanged, self.Sequences)
- left_gridsizer.AddWindow(self.Sequences, flag=wx.GROW)
+ self.LeftGridSizer.AddWindow(self.Sequences, flag=wx.GROW)
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.Preview = wx.Panel(self, style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- main_sizer.AddSizer(button_sizer, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
-
- self.SetSizer(main_sizer)
-
- self.Divergence = None
- self.MinSize = (0, 0)
-
- self.SelectionDivergence.SetFocus()
+ # Selection divergence radio button is default control having keyboard
+ # focus
+ self.TypeRadioButtons[SELECTION_DIVERGENCE].SetFocus()
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
+ def GetMinElementSize(self):
+ """
+ Get minimal graphic element size
+ @return: Tuple containing minimal size (width, height) or None if no
+ element defined
+ """
+ return self.Element.GetMinSize(True)
+
+ def GetDivergenceType(self):
+ """
+ Return type selected for SFC divergence
+ @return: Type selected (None if not found)
+ """
+ # Go through radio buttons and return type associated to the one that
+ # is selected
+ for type, control in self.TypeRadioButtons.iteritems():
+ if control.GetValue():
+ return type
+ return None
def GetValues(self):
- values = {}
- if self.SelectionDivergence.GetValue():
- values["type"] = SELECTION_DIVERGENCE
- elif self.SelectionConvergence.GetValue():
- values["type"] = SELECTION_CONVERGENCE
- elif self.SimultaneousDivergence.GetValue():
- values["type"] = SIMULTANEOUS_DIVERGENCE
- else:
- values["type"] = SIMULTANEOUS_CONVERGENCE
- values["number"] = self.Sequences.GetValue()
- return values
-
- def SetMinSize(self, size):
- self.MinSize = size
+ """
+ Set default SFC divergence parameters
+ @param values: Divergence parameters values
+ """
+ return {"type": self.GetDivergenceType(),
+ "number": self.Sequences.GetValue()}
def OnTypeChanged(self, event):
+ """
+ Called when SFC divergence type changed
+ @param event: wx.RadioButtonEvent
+ """
self.RefreshPreview()
event.Skip()
def OnSequencesChanged(self, event):
+ """
+ Called when SFC divergence number of sequences changed
+ @param event: wx.SpinEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- if self.SelectionDivergence.GetValue():
- self.Divergence = SFC_Divergence(self.Preview, SELECTION_DIVERGENCE, self.Sequences.GetValue())
- elif self.SelectionConvergence.GetValue():
- self.Divergence = SFC_Divergence(self.Preview, SELECTION_CONVERGENCE, self.Sequences.GetValue())
- elif self.SimultaneousDivergence.GetValue():
- self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_DIVERGENCE, self.Sequences.GetValue())
- else:
- self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_CONVERGENCE, self.Sequences.GetValue())
- width, height = self.Divergence.GetSize()
- min_width, min_height = max(width, self.MinSize[0]), max(height, self.MinSize[1])
- self.Divergence.SetSize(min_width, min_height)
- clientsize = self.Preview.GetClientSize()
- x = (clientsize.width - min_width) / 2
- y = (clientsize.height - min_height) / 2
- self.Divergence.SetPosition(x, y)
- self.Divergence.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Set graphic element displayed, creating a SFC divergence
+ self.Element = SFC_Divergence(self.Preview,
+ self.GetDivergenceType(),
+ self.Sequences.GetValue())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
+
\ No newline at end of file
--- a/dialogs/SFCStepDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCStepDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,183 +23,164 @@
import wx
-from graphics import *
+from graphics.SFC_Objects import SFC_Step
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Edit Step Content Dialog
+# Set SFC Step Parameters Dialog
#-------------------------------------------------------------------------------
-class SFCStepDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a SFC step graphic
+element
+"""
+
+class SFCStepDialog(BlockPreviewDialog):
- def __init__(self, parent, controller, initial = False):
- wx.Dialog.__init__(self, parent, title=_('Edit Step'),
- size=wx.Size(400, 250))
+ def __init__(self, parent, controller, tagname, initial=False):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param initial: True if step is initial (default: False)
+ """
+ BlockPreviewDialog.__init__(self,parent, controller, tagname,
+ size=wx.Size(400, 250), title=_('Edit Step'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
+ # Init common sizers
+ self._init_sizers(2, 0, 6, None, 2, 1)
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+ # Create label for SFC step name
+ name_label = wx.StaticText(self, label=_('Name:'))
+ self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
- name_label = wx.StaticText(self, label=_('Name:'))
- left_gridsizer.AddWindow(name_label, flag=wx.GROW)
-
+ # Create text control for defining SFC step name
self.StepName = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.StepName)
- left_gridsizer.AddWindow(self.StepName, flag=wx.GROW)
+ self.LeftGridSizer.AddWindow(self.StepName, flag=wx.GROW)
+ # Create label for SFC step connectors
connectors_label = wx.StaticText(self, label=_('Connectors:'))
- left_gridsizer.AddWindow(connectors_label, flag=wx.GROW)
+ self.LeftGridSizer.AddWindow(connectors_label, flag=wx.GROW)
- self.Input = wx.CheckBox(self, label=_("Input"))
- self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Input)
- left_gridsizer.AddWindow(self.Input, flag=wx.GROW)
+ # Create check boxes for defining connectors available on SFC step
+ self.ConnectorsCheckBox = {}
+ for name, label in [("input", _("Input")),
+ ("output", _("Output")),
+ ("action", _("Action"))]:
+ check_box = wx.CheckBox(self, label=label)
+ self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, check_box)
+ self.LeftGridSizer.AddWindow(check_box, flag=wx.GROW)
+ self.ConnectorsCheckBox[name] = check_box
- self.Output = wx.CheckBox(self, label=_("Output"))
- self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Output)
- left_gridsizer.AddWindow(self.Output, flag=wx.GROW)
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
- self.Action = wx.CheckBox(self, label=_("Action"))
- self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Action)
- left_gridsizer.AddWindow(self.Action, flag=wx.GROW)
-
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "RefreshStepModel", lambda x:None)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(
- wx.OK|wx.CANCEL|wx.CENTRE)
- self.Bind(wx.EVT_BUTTON, self.OnOK,
- button_sizer.GetAffirmativeButton())
- main_sizer.AddSizer(button_sizer, border=20,
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
+ # Save flag that indicates that step is initial
+ self.Initial = initial
- self.Step = None
- self.Initial = initial
- self.MinStepSize = None
-
- self.PouNames = []
- self.Variables = []
- self.StepNames = []
+ # Set default name for step
+ self.StepName.ChangeValue(controller.GenerateNewName(
+ tagname, None, "Step%d", 0))
+ # Step name text control is default control having keyboard focus
self.StepName.SetFocus()
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def OnOK(self, event):
- message = None
- step_name = self.StepName.GetValue()
- if step_name == "":
- message = _("You must type a name!")
- elif not TestIdentifier(step_name):
- message = _("\"%s\" is not a valid identifier!") % step_name
- elif step_name.upper() in IEC_KEYWORDS:
- message = _("\"%s\" is a keyword. It can't be used!") % step_name
- elif step_name.upper() in self.PouNames:
- message = _("A POU named \"%s\" already exists!") % step_name
- elif step_name.upper() in self.Variables:
- message = _("A variable with \"%s\" as name already exists in this pou!") % step_name
- elif step_name.upper() in self.StepNames:
- message = _("\"%s\" step already exists!") % step_name
- if message is not None:
- dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
- dialog.ShowModal()
- dialog.Destroy()
- else:
- self.EndModal(wx.ID_OK)
-
- def SetMinStepSize(self, size):
- self.MinStepSize = size
-
- def SetPouNames(self, pou_names):
- self.PouNames = [pou_name.upper() for pou_name in pou_names]
-
- def SetVariables(self, variables):
- self.Variables = [var["Name"].upper() for var in variables]
-
- def SetStepNames(self, step_names):
- self.StepNames = [step_name.upper() for step_name in step_names]
-
def SetValues(self, values):
- value_name = values.get("name", None)
- if value_name:
- self.StepName.SetValue(value_name)
- else:
- self.StepName.SetValue("")
- self.Input.SetValue(values.get("input", False))
- self.Output.SetValue(values.get("output", False))
- self.Action.SetValue(values.get("action", False))
+ """
+ Set default block parameters
+ @param values: Block parameters values
+ """
+ # For each parameters defined, set corresponding control value
+ for name, value in values.items():
+
+ # Parameter is step name
+ if name == "name":
+ self.StepName.ChangeValue(value)
+
+ # Set value of other controls
+ else:
+ control = self.ConnectorsCheckBox.get(name, None)
+ if control is not None:
+ control.SetValue(value)
+
+ # Refresh preview panel
self.RefreshPreview()
def GetValues(self):
- values = {}
- values["name"] = self.StepName.GetValue()
- values["input"] = self.Input.IsChecked()
- values["output"] = self.Output.IsChecked()
- values["action"] = self.Action.IsChecked()
- values["width"], values["height"] = self.Step.GetSize()
+ """
+ Return step parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
+ values = {"name": self.StepName.GetValue()}
+ values.update({
+ name: control.IsChecked()
+ for name, control in self.ConnectorsCheckBox.iteritems()})
+ values["width"], values["height"] = self.Element.GetSize()
return values
+ def OnOK(self, event):
+ """
+ Called when dialog OK button is pressed
+ Test if step name defined is valid
+ @param event: wx.Event from OK button
+ """
+ message = None
+
+ # Get step name typed by user
+ step_name = self.StepName.GetValue()
+
+ # Test that a name have been defined
+ if step_name == "":
+ message = _("Form isn't complete. Name must be filled!")
+
+ # If an error have been identify, show error message dialog
+ if message is not None:
+ self.ShowErrorMessage(message)
+
+ # Test step name validity
+ elif self.TestElementName(step_name):
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.OnOK(self, event)
+
def OnConnectorsChanged(self, event):
+ """
+ Called when a step connector value changed
+ @param event: wx.CheckBoxEvent
+ """
self.RefreshPreview()
event.Skip()
def OnNameChanged(self, event):
+ """
+ Called when step name value changed
+ @param event: wx.TextEvent
+ """
self.RefreshPreview()
event.Skip()
def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- self.Step = SFC_Step(self.Preview, self.StepName.GetValue(), self.Initial)
- if self.Input.IsChecked():
- self.Step.AddInput()
- else:
- self.Step.RemoveInput()
- if self.Output.IsChecked():
- self.Step.AddOutput()
- else:
- self.Step.RemoveOutput()
- if self.Action.IsChecked():
- self.Step.AddAction()
- else:
- self.Step.RemoveAction()
- width, height = self.MinStepSize
- min_width, min_height = self.Step.GetMinSize()
- width, height = max(min_width, width), max(min_height, height)
- self.Step.SetSize(width, height)
- clientsize = self.Preview.GetClientSize()
- x = (clientsize.width - width) / 2
- y = (clientsize.height - height) / 2
- self.Step.SetPosition(x, y)
- self.Step.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Set graphic element displayed, creating a SFC step element
+ self.Element = SFC_Step(self.Preview,
+ self.StepName.GetValue(),
+ self.Initial)
+
+ # Update connectors of SFC step element according to check boxes value
+ for name, control in self.ConnectorsCheckBox.iteritems():
+ if control.IsChecked():
+ getattr(self.Element, "Add" + name.capitalize())()
+ else:
+ getattr(self.Element, "Remove" + name.capitalize())()
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/SFCTransitionDialog.py Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCTransitionDialog.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,221 +23,215 @@
import wx
-from graphics import *
+from graphics.SFC_Objects import SFC_Transition
+from BlockPreviewDialog import BlockPreviewDialog
#-------------------------------------------------------------------------------
-# Edit Transition Content Dialog
+# Set Transition Parameters Dialog
#-------------------------------------------------------------------------------
-class SFCTransitionDialog(wx.Dialog):
-
- def __init__(self, parent, controller, connection):
- self.Connection = connection
-
- wx.Dialog.__init__(self, parent,
+"""
+Class that implements a dialog for defining parameters of a transition graphic
+element
+"""
+
+class SFCTransitionDialog(BlockPreviewDialog):
+
+ def __init__(self, parent, controller, tagname, connection=True):
+ """
+ Constructor
+ @param parent: Parent wx.Window of dialog for modal
+ @param controller: Reference to project controller
+ @param tagname: Tagname of project POU edited
+ @param connection: True if transition value can be defined by a
+ connection (default: True)
+ """
+ BlockPreviewDialog.__init__(self, parent, controller, tagname,
size=wx.Size(350, 300), title=_('Edit transition'))
- main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
- main_sizer.AddGrowableCol(0)
- main_sizer.AddGrowableRow(0)
-
- column_sizer = wx.BoxSizer(wx.HORIZONTAL)
- main_sizer.AddSizer(column_sizer, border=20,
- flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-
- left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=8, vgap=5)
- left_gridsizer.AddGrowableCol(0)
- column_sizer.AddSizer(left_gridsizer, 1, border=5,
- flag=wx.GROW|wx.RIGHT)
-
+ # Init common sizers
+ self._init_sizers(2, 0, 8, None, 2, 1)
+
+ # Create label for transition type
type_label = wx.StaticText(self, label=_('Type:'))
- left_gridsizer.AddWindow(type_label, flag=wx.GROW)
-
- self.ReferenceRadioButton = wx.RadioButton(self,
- label=_('Reference'), style=wx.RB_GROUP)
- self.ReferenceRadioButton.SetValue(True)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ReferenceRadioButton)
- left_gridsizer.AddWindow(self.ReferenceRadioButton, flag=wx.GROW)
-
- self.Reference = wx.ComboBox(self, style=wx.CB_READONLY)
- self.Bind(wx.EVT_COMBOBOX, self.OnReferenceChanged, self.Reference)
- left_gridsizer.AddWindow(self.Reference, flag=wx.GROW)
-
- self.InlineRadioButton = wx.RadioButton(self, label=_('Inline'))
- self.InlineRadioButton.SetValue(False)
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.InlineRadioButton)
- left_gridsizer.AddWindow(self.InlineRadioButton, flag=wx.GROW)
-
- self.Inline = wx.TextCtrl(self)
- self.Inline.Enable(False)
- self.Bind(wx.EVT_TEXT, self.OnInlineChanged, self.Inline)
- left_gridsizer.AddWindow(self.Inline, flag=wx.GROW)
-
- self.ConnectionRadioButton = wx.RadioButton(self, label=_('Connection'))
- self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
- self.ConnectionRadioButton.SetValue(False)
- if not self.Connection:
- self.ConnectionRadioButton.Hide()
- left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+
+ # Create combo box for selecting reference value
+ reference = wx.ComboBox(self, style=wx.CB_READONLY)
+ reference.Append("")
+ for transition in controller.GetEditedElementTransitions(tagname):
+ reference.Append(transition)
+ self.Bind(wx.EVT_COMBOBOX, self.OnReferenceChanged, reference)
+
+ # Create Text control for defining inline value
+ inline = wx.TextCtrl(self)
+ self.Bind(wx.EVT_TEXT, self.OnInlineChanged, inline)
+
+ # Create radio buttons for selecting power rail type
+ self.TypeRadioButtons = {}
+ first = True
+ for type, label, control in [('reference', _('Reference'), reference),
+ ('inline', _('Inline'), inline),
+ ('connection', _('Connection'), None)]:
+ radio_button = wx.RadioButton(self, label=label,
+ style=(wx.RB_GROUP if first else 0))
+ radio_button.SetValue(first)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+ self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+ if control is not None:
+ control.Enable(first)
+ self.LeftGridSizer.AddWindow(control, flag=wx.GROW)
+ self.TypeRadioButtons[type] = (radio_button, control)
+ first = False
+
+ # Create label for transition priority
priority_label = wx.StaticText(self, label=_('Priority:'))
- left_gridsizer.AddWindow(priority_label, flag=wx.GROW)
-
+ self.LeftGridSizer.AddWindow(priority_label, flag=wx.GROW)
+
+ # Create spin control for defining priority value
self.Priority = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
self.Bind(wx.EVT_TEXT, self.OnPriorityChanged, self.Priority)
- left_gridsizer.AddWindow(self.Priority, flag=wx.GROW)
-
- right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- right_gridsizer.AddGrowableCol(0)
- right_gridsizer.AddGrowableRow(1)
- column_sizer.AddSizer(right_gridsizer, 1, border=5,
- flag=wx.GROW|wx.LEFT)
-
- preview_label = wx.StaticText(self, label=_('Preview:'))
- right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
- self.Preview = wx.Panel(self,
- style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
- self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
- setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
- setattr(self.Preview, "RefreshTransitionModel", lambda x:None)
- setattr(self.Preview, "GetScaling", lambda:None)
- setattr(self.Preview, "IsOfType", controller.IsOfType)
- self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
- right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-
- button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
- self.Bind(wx.EVT_BUTTON, self.OnOK,
- button_sizer.GetAffirmativeButton())
- main_sizer.AddSizer(button_sizer, border=20,
+ self.LeftGridSizer.AddWindow(self.Priority, flag=wx.GROW)
+
+ # Add preview panel and associated label to sizers
+ self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+ self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ # Add buttons sizer to sizers
+ self.MainSizer.AddSizer(self.ButtonSizer, border=20,
flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self.SetSizer(main_sizer)
-
- self.Transition = None
- self.MinTransitionSize = None
-
+ # Reference radio button is default control having keyboard focus
+ self.TypeRadioButtons["reference"][0].SetFocus()
+
+ def GetTransitionType(self):
+ """
+ Return type selected for SFC transition and associated value
+ @return: Type selected and associated value (None if no value)
+ """
+ # Go through radio buttons and return type and value associated to the
+ # one that is selected
+ for type, (radio, control) in self.TypeRadioButtons.iteritems():
+ if radio.GetValue():
+ if isinstance(control, wx.ComboBox):
+ return type, control.GetStringSelection()
+ elif isinstance(control, wx.TextCtrl):
+ return type, control.GetValue()
+ else:
+ return type, None
+ return None, None
+
+ def SetValues(self, values):
+ """
+ Set default SFC transition parameters
+ @param values: Transition parameters values
+ """
+ # Extract transition value according to type
+ type_value = values.get("value", None)
+
+ # For each parameters defined, set corresponding control value
+ for name, value in values.items():
+
+ # Parameter is SFC transition priority
+ if name == "priority":
+ self.Priority.SetValue(values["priority"])
+
+ # Parameter is SFC transition type
+ elif name == "type":
+ for type, (radio, control) in self.TypeRadioButtons.iteritems():
+ radio.SetValue(type == value)
+ if control is not None:
+ # Enable associated control to type and set value
+ control.Enable(type == value)
+ if type == value:
+ if isinstance(control, wx.ComboBox):
+ control.SetStringSelection(type_value)
+ elif isinstance(control, wx.TextCtrl):
+ control.ChangeValue(type_value)
+
+ # Refresh preview panel
+ self.RefreshPreview()
+
+ def GetValues(self):
+ """
+ Return SFC transition parameters defined in dialog
+ @return: {parameter_name: parameter_value,...}
+ """
+ values = {"priority" : self.Priority.GetValue()}
+ values["type"], values["value"] = self.GetTransitionType()
+ values["width"], values["height"] = self.Element.GetSize()
+ return values
+
+ def OnOK(self, event):
+ """
+ Called when dialog OK button is pressed
+ Test if parameters defined are valid
+ @param event: wx.Event from OK button
+ """
+ message = None
+
+ # Get transition type and value associated
+ type, value = self.GetTransitionType()
+
+ # Test that value associated to type is defined
+ if type != "connection" and value == "":
+ message = _("Form isn't complete. %s must be filled!") % type
+
+ # Show error message if an error is detected
+ if message is not None:
+ self.ShowErrorMessage(message)
+
+ else:
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.OnOK(self, event)
+
+ def OnTypeChanged(self, event):
+ """
+ Called when transition type changed
+ @param event: wx.RadioButtonEvent
+ """
+ # Refresh sensibility of control associated to transition types
+ for type, (radio, control) in self.TypeRadioButtons.iteritems():
+ if control is not None:
+ control.Enable(radio.GetValue())
+
+ # Refresh preview panel
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnReferenceChanged(self, event):
+ """
+ Called when SFC transition reference value changed
+ @param event: wx.ComboBoxEvent
+ """
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnInlineChanged(self, event):
+ """
+ Called when SFC transition inline value changed
+ @param event: wx.TextEvent
+ """
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnPriorityChanged(self, event):
+ """
+ Called when block inputs number changed
+ @param event: wx.SpinEvent
+ """
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ """
+ Refresh preview panel of graphic element
+ Override BlockPreviewDialog function
+ """
+ # Set graphic element displayed, creating a SFC transition
self.Element = SFC_Transition(self.Preview)
-
- self.ReferenceRadioButton.SetFocus()
-
- def SetPreviewFont(self, font):
- self.Preview.SetFont(font)
-
- def SetElementSize(self, size):
- min_width, min_height = self.Element.GetMinSize()
- width, height = max(min_width, size[0]), max(min_height, size[1])
- self.Element.SetSize(width, height)
-
- def OnOK(self, event):
- error = []
- if self.ReferenceRadioButton.GetValue() and self.Reference.GetStringSelection() == "":
- error.append(_("Reference"))
- if self.InlineRadioButton.GetValue() and self.Inline.GetValue() == "":
- error.append(_("Inline"))
- if len(error) > 0:
- text = ""
- for i, item in enumerate(error):
- if i == 0:
- text += item
- elif i == len(error) - 1:
- text += _(" and %s")%item
- else:
- text += _(", %s")%item
- dialog = wx.MessageDialog(self, _("Form isn't complete. %s must be filled!")%text, _("Error"), wx.OK|wx.ICON_ERROR)
- dialog.ShowModal()
- dialog.Destroy()
- else:
- self.EndModal(wx.ID_OK)
-
- def OnTypeChanged(self, event):
- if self.ReferenceRadioButton.GetValue():
- self.Element.SetType("reference", self.Reference.GetStringSelection())
- self.Reference.Enable(True)
- self.Inline.Enable(False)
- elif self.InlineRadioButton.GetValue():
- self.Element.SetType("inline", self.Inline.GetValue())
- self.Reference.Enable(False)
- self.Inline.Enable(True)
- else:
- self.Element.SetType("connection")
- self.Reference.Enable(False)
- self.Inline.Enable(False)
- self.RefreshPreview()
- event.Skip()
-
- def OnReferenceChanged(self, event):
- self.Element.SetType("reference", self.Reference.GetStringSelection())
- self.RefreshPreview()
- event.Skip()
-
- def OnInlineChanged(self, event):
- self.Element.SetType("inline", self.Inline.GetValue())
- self.RefreshPreview()
- event.Skip()
-
- def OnPriorityChanged(self, event):
- self.Element.SetPriority(int(self.Priority.GetValue()))
- self.RefreshPreview()
- event.Skip()
-
- def SetTransitions(self, transitions):
- self.Reference.Append("")
- for transition in transitions:
- self.Reference.Append(transition)
-
- def SetValues(self, values):
- if values["type"] == "reference":
- self.ReferenceRadioButton.SetValue(True)
- self.InlineRadioButton.SetValue(False)
- self.ConnectionRadioButton.SetValue(False)
- self.Reference.Enable(True)
- self.Inline.Enable(False)
- self.Reference.SetStringSelection(values["value"])
- self.Element.SetType("reference", values["value"])
- elif values["type"] == "inline":
- self.ReferenceRadioButton.SetValue(False)
- self.InlineRadioButton.SetValue(True)
- self.ConnectionRadioButton.SetValue(False)
- self.Reference.Enable(False)
- self.Inline.Enable(True)
- self.Inline.SetValue(values["value"])
- self.Element.SetType("inline", values["value"])
- elif values["type"] == "connection" and self.Connection:
- self.ReferenceRadioButton.SetValue(False)
- self.InlineRadioButton.SetValue(False)
- self.ConnectionRadioButton.SetValue(True)
- self.Reference.Enable(False)
- self.Inline.Enable(False)
- self.Element.SetType("connection")
- self.Priority.SetValue(values["priority"])
- self.Element.SetPriority(values["priority"])
- self.RefreshPreview()
-
- def GetValues(self):
- values = {"priority" : int(self.Priority.GetValue())}
- if self.ReferenceRadioButton.GetValue():
- values["type"] = "reference"
- values["value"] = self.Reference.GetStringSelection()
- elif self.InlineRadioButton.GetValue():
- values["type"] = "inline"
- values["value"] = self.Inline.GetValue()
- else:
- values["type"] = "connection"
- values["value"] = None
- return values
-
- def RefreshPreview(self):
- dc = wx.ClientDC(self.Preview)
- dc.SetFont(self.Preview.GetFont())
- dc.Clear()
- clientsize = self.Preview.GetClientSize()
- posx, posy = self.Element.GetPosition()
- rect = self.Element.GetBoundingBox()
- diffx, diffy = posx - rect.x, posy - rect.y
- self.Element.SetPosition((clientsize.width - rect.width) / 2 + diffx, (clientsize.height - rect.height) / 2 + diffy)
- self.Element.Draw(dc)
-
- def OnPaint(self, event):
- self.RefreshPreview()
- event.Skip()
+ self.Element.SetType(*self.GetTransitionType())
+ self.Element.SetPriority(self.Priority.GetValue())
+
+ # Call BlockPreviewDialog function
+ BlockPreviewDialog.RefreshPreview(self)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/CodeFileEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,866 @@
+import re
+
+import wx
+import wx.grid
+import wx.stc as stc
+import wx.lib.buttons
+
+from plcopen.plcopen import TestTextElement
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+from controls import CustomGrid, CustomTable
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from util.BitmapLibrary import GetBitmap
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl, faces, GetCursorPos, NAVIGATION_KEYS
+from controls.VariablePanel import VARIABLE_NAME_SUFFIX_MODEL
+from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
+
+[STC_CODE_ERROR, STC_CODE_SEARCH_RESULT,
+ STC_CODE_SECTION] = range(15, 18)
+
+HIGHLIGHT_TYPES = {
+ ERROR_HIGHLIGHT: STC_CODE_ERROR,
+ SEARCH_RESULT_HIGHLIGHT: STC_CODE_SEARCH_RESULT,
+}
+
+EDGE_COLUMN = 80
+
+class CodeEditor(CustomStyledTextCtrl):
+
+ KEYWORDS = []
+ COMMENT_HEADER = ""
+
+ def __init__(self, parent, window, controler):
+ CustomStyledTextCtrl.__init__(self, parent, -1, wx.DefaultPosition,
+ wx.Size(-1, 300), 0)
+
+ self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
+ self.SetMarginWidth(1, 25)
+
+ self.SetProperty("fold", "1")
+ self.SetProperty("tab.timmy.whinge.level", "1")
+ self.SetMargins(0,0)
+
+ self.SetViewWhiteSpace(False)
+
+ self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
+ self.SetEdgeColumn(EDGE_COLUMN)
+
+ # Setup a margin to hold fold markers
+ self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
+ self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
+ self.SetMarginSensitive(2, True)
+ self.SetMarginWidth(2, 12)
+
+ # Like a flattened tree control using square headers
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
+ self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
+
+ self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
+ self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
+ self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
+
+ # Make some styles, The lexer defines what each style is used for, we
+ # just have to define what each style looks like. This set is adapted from
+ # Scintilla sample property files.
+
+ # Global default styles for all languages
+ self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+ self.StyleClearAll() # Reset all to be like the default
+
+ # Global default styles for all languages
+ self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+ self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size)d" % faces)
+ self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
+ self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
+ self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
+
+ # Highlighting styles
+ self.StyleSetSpec(STC_CODE_ERROR, 'fore:#FF0000,back:#FFFF00,size:%(size)d' % faces)
+ self.StyleSetSpec(STC_CODE_SEARCH_RESULT, 'fore:#FFFFFF,back:#FFA500,size:%(size)d' % faces)
+
+ # Section style
+ self.StyleSetSpec(STC_CODE_SECTION, 'fore:#808080,size:%(size)d')
+ self.StyleSetChangeable(STC_CODE_SECTION, False)
+
+ # Indentation size
+ self.SetTabWidth(4)
+ self.SetUseTabs(0)
+
+ self.SetCodeLexer()
+ self.SetKeyWords(0, " ".join(self.KEYWORDS))
+
+ self.Controler = controler
+ self.ParentWindow = window
+
+ self.DisableEvents = True
+ self.CurrentAction = None
+
+ self.ResetSearchResults()
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+
+ self.SectionsComments = {}
+ for section in self.Controler.SECTIONS_NAMES:
+ section_comment = " %s section " % (section)
+ len_headers = EDGE_COLUMN - len(section_comment)
+ section_comment = self.COMMENT_HEADER * (len_headers / 2) + \
+ section_comment + \
+ self.COMMENT_HEADER * (len_headers - len_headers / 2)
+
+ self.SectionsComments[section] = {
+ "comment": section_comment,
+ }
+
+ for i, section in enumerate(self.Controler.SECTIONS_NAMES):
+ section_infos = self.SectionsComments[section]
+ if i + 1 < len(self.Controler.SECTIONS_NAMES):
+ section_end = self.SectionsComments[
+ self.Controler.SECTIONS_NAMES[i + 1]]["comment"]
+ else:
+ section_end = "$"
+ section_infos["pattern"] = re.compile(
+ section_infos["comment"] + "(.*)" +
+ section_end, re.DOTALL)
+
+ self.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
+
+ self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop)
+ self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
+ self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification)
+
+ def SetCodeLexer(self):
+ pass
+
+ def ResetSearchResults(self):
+ self.Highlights = []
+ self.SearchParams = None
+ self.SearchResults = None
+ self.CurrentFindHighlight = None
+
+ def OnModification(self, event):
+ if not self.DisableEvents:
+ mod_type = event.GetModificationType()
+ if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
+ if mod_type&wx.stc.STC_MOD_BEFOREINSERT:
+ if self.CurrentAction == None:
+ self.StartBuffering()
+ elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
+ self.Controler.EndBuffering()
+ self.StartBuffering()
+ self.CurrentAction = ("Add", event.GetPosition())
+ wx.CallAfter(self.RefreshModel)
+ elif mod_type&wx.stc.STC_MOD_BEFOREDELETE:
+ if self.CurrentAction == None:
+ self.StartBuffering()
+ elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
+ self.Controler.EndBuffering()
+ self.StartBuffering()
+ self.CurrentAction = ("Delete", event.GetPosition())
+ wx.CallAfter(self.RefreshModel)
+ wx.CallAfter(self.RefreshSectionStyling)
+ event.Skip()
+
+ def OnDoDrop(self, event):
+ try:
+ values = eval(event.GetDragText())
+ except:
+ values = event.GetDragText()
+ if isinstance(values, tuple):
+ message = None
+ if values[3] == self.Controler.GetCurrentLocation():
+ self.ResetBuffer()
+ event.SetDragText(values[0])
+ wx.CallAfter(self.RefreshModel)
+ else:
+ event.SetDragText("")
+ else:
+ self.ResetBuffer()
+ wx.CallAfter(self.RefreshModel)
+ event.Skip()
+
+ # Buffer the last model state
+ def RefreshBuffer(self):
+ self.Controler.BufferCodeFile()
+ if self.ParentWindow is not None:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+ self.ParentWindow.RefreshPageTitles()
+
+ def StartBuffering(self):
+ self.Controler.StartBuffering()
+ if self.ParentWindow is not None:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+ self.ParentWindow.RefreshPageTitles()
+
+ def ResetBuffer(self):
+ if self.CurrentAction != None:
+ self.Controler.EndBuffering()
+ self.CurrentAction = None
+
+ def GetCodeText(self):
+ parts = self.Controler.GetTextParts()
+ text = ""
+ for section in self.Controler.SECTIONS_NAMES:
+ section_comments = self.SectionsComments[section]
+ text += section_comments["comment"]
+ if parts[section] == "":
+ text += "\n"
+ else:
+ if not parts[section].startswith("\n"):
+ text += "\n"
+ text += parts[section]
+ if not parts[section].endswith("\n"):
+ text += "\n"
+ return text
+
+ def RefreshView(self, scroll_to_highlight=False):
+ self.ResetBuffer()
+ self.DisableEvents = True
+ old_cursor_pos = self.GetCurrentPos()
+ line = self.GetFirstVisibleLine()
+ column = self.GetXOffset()
+ old_text = self.GetText()
+ new_text = self.GetCodeText()
+ if old_text != new_text:
+ self.SetText(new_text)
+ new_cursor_pos = GetCursorPos(old_text, new_text)
+ self.LineScroll(column, line)
+ if new_cursor_pos != None:
+ self.GotoPos(new_cursor_pos)
+ else:
+ self.GotoPos(old_cursor_pos)
+ self.EmptyUndoBuffer()
+ self.DisableEvents = False
+
+ self.RefreshSectionStyling()
+
+ self.ShowHighlights()
+
+ def RefreshSectionStyling(self):
+ self.Colourise(0, -1)
+
+ text = self.GetText()
+ for line in xrange(self.GetLineCount()):
+ self.SetLineState(line, 0)
+
+ for section in self.Controler.SECTIONS_NAMES:
+ section_comments = self.SectionsComments[section]
+ start_pos = text.find(section_comments["comment"])
+ end_pos = start_pos + len(section_comments["comment"])
+ self.StartStyling(start_pos, 0xff)
+ self.SetStyling(end_pos - start_pos, STC_CODE_SECTION)
+ self.SetLineState(self.LineFromPosition(start_pos), 1)
+
+ self.StartStyling(end_pos, 0x00)
+ self.SetStyling(len(self.GetText()) - end_pos, stc.STC_STYLE_DEFAULT)
+
+ def DoGetBestSize(self):
+ return self.ParentWindow.GetPanelBestSize()
+
+ def RefreshModel(self):
+ text = self.GetText()
+ parts = {}
+ for section in self.Controler.SECTIONS_NAMES:
+ section_comments = self.SectionsComments[section]
+ result = section_comments["pattern"].search(text)
+ if result is not None:
+ parts[section] = result.group(1)
+ else:
+ parts[section] = ""
+ self.Controler.SetTextParts(parts)
+ self.ResetSearchResults()
+
+ def OnKeyPressed(self, event):
+ if self.CallTipActive():
+ self.CallTipCancel()
+ key = event.GetKeyCode()
+ current_pos = self.GetCurrentPos()
+ selected = self.GetSelection()
+ text_selected = selected[0] != selected[1]
+
+ # Test if caret is before Windows like new line
+ text = self.GetText()
+ if current_pos < len(text) and ord(text[current_pos]) == 13:
+ newline_size = 2
+ else:
+ newline_size = 1
+
+ # Disable to type any character in section header lines
+ if (self.GetLineState(self.LineFromPosition(current_pos)) and
+ not text_selected and
+ key not in NAVIGATION_KEYS + [
+ wx.WXK_RETURN,
+ wx.WXK_NUMPAD_ENTER]):
+ return
+
+ # Disable to delete line between code and header lines
+ elif (self.GetCurLine()[0].strip() != "" and not text_selected and
+ (key == wx.WXK_BACK and
+ self.GetLineState(self.LineFromPosition(max(0, current_pos - 1))) or
+ key in [wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE] and
+ self.GetLineState(self.LineFromPosition(min(len(text), current_pos + newline_size))))):
+ return
+
+ elif key == 32 and event.ControlDown():
+ pos = self.GetCurrentPos()
+
+ # Tips
+ if event.ShiftDown():
+ pass
+ # Code completion
+ else:
+ self.AutoCompSetIgnoreCase(False) # so this needs to match
+
+ keywords = self.KEYWORDS + [var["Name"]
+ for var in self.Controler.GetVariables()]
+ keywords.sort()
+ self.AutoCompShow(0, " ".join(keywords))
+ else:
+ event.Skip()
+
+ def OnKillFocus(self, event):
+ self.AutoCompCancel()
+ event.Skip()
+
+ def OnUpdateUI(self, event):
+ # check for matching braces
+ braceAtCaret = -1
+ braceOpposite = -1
+ charBefore = None
+ caretPos = self.GetCurrentPos()
+
+ if caretPos > 0:
+ charBefore = self.GetCharAt(caretPos - 1)
+ styleBefore = self.GetStyleAt(caretPos - 1)
+
+ # check before
+ if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
+ braceAtCaret = caretPos - 1
+
+ # check after
+ if braceAtCaret < 0:
+ charAfter = self.GetCharAt(caretPos)
+ styleAfter = self.GetStyleAt(caretPos)
+
+ if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
+ braceAtCaret = caretPos
+
+ if braceAtCaret >= 0:
+ braceOpposite = self.BraceMatch(braceAtCaret)
+
+ if braceAtCaret != -1 and braceOpposite == -1:
+ self.BraceBadLight(braceAtCaret)
+ else:
+ self.BraceHighlight(braceAtCaret, braceOpposite)
+
+ selected_text = self.GetSelectedText()
+ if selected_text:
+ self.ParentWindow.SetCopyBuffer(selected_text, True)
+
+ def OnMarginClick(self, event):
+ # fold and unfold as needed
+ if evt.GetMargin() == 2:
+ if evt.GetShift() and evt.GetControl():
+ self.FoldAll()
+ else:
+ lineClicked = self.LineFromPosition(evt.GetPosition())
+
+ if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
+ if evt.GetShift():
+ self.SetFoldExpanded(lineClicked, True)
+ self.Expand(lineClicked, True, True, 1)
+ elif evt.GetControl():
+ if self.GetFoldExpanded(lineClicked):
+ self.SetFoldExpanded(lineClicked, False)
+ self.Expand(lineClicked, False, True, 0)
+ else:
+ self.SetFoldExpanded(lineClicked, True)
+ self.Expand(lineClicked, True, True, 100)
+ else:
+ self.ToggleFold(lineClicked)
+ event.Skip()
+
+ def FoldAll(self):
+ lineCount = self.GetLineCount()
+ expanding = True
+
+ # find out if we are folding or unfolding
+ for lineNum in range(lineCount):
+ if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
+ expanding = not self.GetFoldExpanded(lineNum)
+ break
+
+ lineNum = 0
+
+ while lineNum < lineCount:
+ level = self.GetFoldLevel(lineNum)
+ if level & stc.STC_FOLDLEVELHEADERFLAG and \
+ (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
+
+ if expanding:
+ self.SetFoldExpanded(lineNum, True)
+ lineNum = self.Expand(lineNum, True)
+ lineNum = lineNum - 1
+ else:
+ lastChild = self.GetLastChild(lineNum, -1)
+ self.SetFoldExpanded(lineNum, False)
+
+ if lastChild > lineNum:
+ self.HideLines(lineNum+1, lastChild)
+
+ lineNum = lineNum + 1
+
+
+
+ def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
+ lastChild = self.GetLastChild(line, level)
+ line = line + 1
+
+ while line <= lastChild:
+ if force:
+ if visLevels > 0:
+ self.ShowLines(line, line)
+ else:
+ self.HideLines(line, line)
+ else:
+ if doExpand:
+ self.ShowLines(line, line)
+
+ if level == -1:
+ level = self.GetFoldLevel(line)
+
+ if level & stc.STC_FOLDLEVELHEADERFLAG:
+ if force:
+ if visLevels > 1:
+ self.SetFoldExpanded(line, True)
+ else:
+ self.SetFoldExpanded(line, False)
+
+ line = self.Expand(line, doExpand, force, visLevels-1)
+
+ else:
+ if doExpand and self.GetFoldExpanded(line):
+ line = self.Expand(line, True, force, visLevels-1)
+ else:
+ line = self.Expand(line, False, force, visLevels-1)
+ else:
+ line = line + 1
+
+ return line
+
+ def Cut(self):
+ self.ResetBuffer()
+ self.DisableEvents = True
+ self.CmdKeyExecute(wx.stc.STC_CMD_CUT)
+ self.DisableEvents = False
+ self.RefreshModel()
+ self.RefreshBuffer()
+
+ def Copy(self):
+ self.CmdKeyExecute(wx.stc.STC_CMD_COPY)
+ self.ParentWindow.RefreshEditMenu()
+
+ def Paste(self):
+ self.ResetBuffer()
+ self.DisableEvents = True
+ self.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
+ self.DisableEvents = False
+ self.RefreshModel()
+ self.RefreshBuffer()
+
+ def Find(self, direction, search_params):
+ if self.SearchParams != search_params:
+ self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+
+ self.SearchParams = search_params
+ criteria = {
+ "raw_pattern": search_params["find_pattern"],
+ "pattern": re.compile(search_params["find_pattern"]),
+ "case_sensitive": search_params["case_sensitive"],
+ "regular_expression": search_params["regular_expression"],
+ "filter": "all"}
+
+ self.SearchResults = [
+ (start, end, SEARCH_RESULT_HIGHLIGHT)
+ for start, end, text in
+ TestTextElement(self.GetText(), criteria)]
+ self.CurrentFindHighlight = None
+
+ if len(self.SearchResults) > 0:
+ if self.CurrentFindHighlight is not None:
+ old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+ if self.SearchParams["wrap"]:
+ idx = (old_idx + direction) % len(self.SearchResults)
+ else:
+ idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+ if idx != old_idx:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = self.SearchResults[idx]
+ self.AddHighlight(*self.CurrentFindHighlight)
+ else:
+ caret_pos = self.GetCurrentPos()
+ if self.SearchParams["wrap"]:
+ self.CurrentFindHighlight = self.SearchResults[0]
+ else:
+ self.CurrentFindHighlight = None
+ for start, end, highlight_type in self.SearchResults:
+ if start[0] == 0:
+ highlight_start_pos = start[1]
+ else:
+ highlight_start_pos = self.GetLineEndPosition(start[0] - 1) + start[1] + 1
+ if highlight_start_pos >= caret_pos:
+ self.CurrentFindHighlight = (start, end, highlight_type)
+ break
+ if self.CurrentFindHighlight is not None:
+ self.AddHighlight(*self.CurrentFindHighlight)
+
+ self.ScrollToLine(self.CurrentFindHighlight[0][0])
+
+ else:
+ if self.CurrentFindHighlight is not None:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = None
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.RefreshView(True)
+ event.Skip()
+
+ def ClearHighlights(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = []
+ else:
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if highlight_type is not None:
+ self.Highlights = [(start, end, highlight) for (start, end, highlight) in self.Highlights if highlight != highlight_type]
+ self.RefreshView()
+
+ def AddHighlight(self, start, end, highlight_type):
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if highlight_type is not None:
+ self.Highlights.append((start, end, highlight_type))
+ self.GotoPos(self.PositionFromLine(start[0]) + start[1])
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+ self.RefreshView()
+
+ def RemoveHighlight(self, start, end, highlight_type):
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if (highlight_type is not None and
+ (start, end, highlight_type) in self.Highlights):
+ self.Highlights.remove((start, end, highlight_type))
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ShowHighlights(self):
+ for start, end, highlight_type in self.Highlights:
+ if start[0] == 0:
+ highlight_start_pos = start[1]
+ else:
+ highlight_start_pos = self.GetLineEndPosition(start[0] - 1) + start[1] + 1
+ if end[0] == 0:
+ highlight_end_pos = end[1] + 1
+ else:
+ highlight_end_pos = self.GetLineEndPosition(end[0] - 1) + end[1] + 2
+ self.StartStyling(highlight_start_pos, 0xff)
+ self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
+ self.StartStyling(highlight_end_pos, 0x00)
+ self.SetStyling(len(self.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
+
+
+#-------------------------------------------------------------------------------
+# Helper for VariablesGrid values
+#-------------------------------------------------------------------------------
+
+class VariablesTable(CustomTable):
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ if col == 0:
+ return row + 1
+ else:
+ return str(self.data[row].get(self.GetColLabelValue(col, False), ""))
+
+ def _updateColAttrs(self, grid):
+ """
+ wxGrid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ typelist = None
+ accesslist = None
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col, False)
+
+ if colname in ["Name", "Initial"]:
+ editor = wx.grid.GridCellTextEditor()
+ elif colname == "Class":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters("input,memory,output")
+ elif colname == "Type":
+ pass
+ else:
+ grid.SetReadOnly(row, col, True)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ grid.SetCellBackgroundColour(row, col, wx.WHITE)
+ self.ResizeRow(grid, row)
+
+
+class VariablesEditor(wx.Panel):
+
+ def __init__(self, parent, window, controler):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=4)
+ main_sizer.AddGrowableCol(1)
+ main_sizer.AddGrowableRow(0)
+
+ controls_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.AddSizer(controls_sizer, border=5, flag=wx.ALL)
+
+ for name, bitmap, help in [
+ ("AddVariableButton", "add_element", _("Add variable")),
+ ("DeleteVariableButton", "remove_element", _("Remove variable")),
+ ("UpVariableButton", "up", _("Move variable up")),
+ ("DownVariableButton", "down", _("Move variable down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ controls_sizer.AddWindow(button, border=5, flag=wx.BOTTOM)
+
+ self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
+ main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+
+ self.SetSizer(main_sizer)
+
+ self.ParentWindow = window
+ self.Controler = controler
+
+ self.VariablesDefaultValue = {"Name" : "", "Type" : "INT", "Initial": ""}
+ self.Table = VariablesTable(self, [], ["#", "Name", "Type", "Initial"])
+ self.ColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+ self.ColSizes = [40, 200, 150, 150]
+ self.VariablesGrid.SetTable(self.Table)
+ self.VariablesGrid.SetButtons({"Add": self.AddVariableButton,
+ "Delete": self.DeleteVariableButton,
+ "Up": self.UpVariableButton,
+ "Down": self.DownVariableButton})
+
+ def _AddVariable(new_row):
+ if new_row > 0:
+ row_content = self.Table.data[new_row - 1].copy()
+ result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content["Name"])
+ if result is not None:
+ name = row_content["Name"][:result.start(1)]
+ suffix = result.group(1)
+ if suffix != "":
+ start_idx = int(suffix)
+ else:
+ start_idx = 0
+ else:
+ name = row_content["Name"]
+ start_idx = 0
+ row_content["Name"] = self.Controler.GenerateNewName(
+ name + "%d", start_idx)
+ else:
+ row_content = self.VariablesDefaultValue.copy()
+ self.Table.InsertRow(new_row, row_content)
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+
+ def _DeleteVariable(row):
+ self.Table.RemoveRow(row)
+ self.RefreshModel()
+ self.RefreshView()
+ setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+
+ def _MoveVariable(row, move):
+ new_row = self.Table.MoveRow(row, move)
+ if new_row != row:
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+
+ self.VariablesGrid.SetRowLabelSize(0)
+ for col in range(self.Table.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
+ self.VariablesGrid.SetColAttr(col, attr)
+ self.VariablesGrid.SetColSize(col, self.ColSizes[col])
+ self.Table.ResetView(self.VariablesGrid)
+
+ def RefreshModel(self):
+ self.Controler.SetVariables(self.Table.GetData())
+ self.RefreshBuffer()
+
+ # Buffer the last model state
+ def RefreshBuffer(self):
+ self.Controler.BufferCodeFile()
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+ self.ParentWindow.RefreshPageTitles()
+
+ def RefreshView(self):
+ self.Table.SetData(self.Controler.GetVariables())
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+
+ def DoGetBestSize(self):
+ return self.ParentWindow.GetPanelBestSize()
+
+ def OnVariablesGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ colname = self.Table.GetColLabelValue(col, False)
+ value = self.Table.GetValue(row, col)
+ message = None
+
+ if colname == "Name" and value != "":
+ if not TestIdentifier(value):
+ message = _("\"%s\" is not a valid identifier!") % value
+ elif value.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % value
+ elif value.upper() in [var["Name"].upper()
+ for var_row, var in enumerate(self.Table.data)
+ if var_row != row]:
+ message = _("A variable with \"%s\" as name already exists!") % value
+ else:
+ self.RefreshModel()
+ wx.CallAfter(self.RefreshView)
+ else:
+ self.RefreshModel()
+ wx.CallAfter(self.RefreshView)
+
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def OnVariablesGridEditorShown(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ if self.Table.GetColLabelValue(col, False) == "Type":
+ type_menu = wx.Menu(title='')
+ base_menu = wx.Menu(title='')
+ for base_type in self.Controler.GetBaseTypes():
+ new_id = wx.NewId()
+ base_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
+ type_menu.AppendMenu(wx.NewId(), "Base Types", base_menu)
+ datatype_menu = wx.Menu(title='')
+ for datatype in self.Controler.GetDataTypes():
+ new_id = wx.NewId()
+ datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
+ type_menu.AppendMenu(wx.NewId(), "User Data Types", datatype_menu)
+ rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
+
+ self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
+ type_menu.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def GetVariableTypeFunction(self, base_type):
+ def VariableTypeFunction(event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ self.Table.SetValueByName(row, "Type", base_type)
+ self.Table.ResetView(self.VariablesGrid)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+ return VariableTypeFunction
+
+ def OnVariablesGridCellLeftClick(self, event):
+ if event.GetCol() == 0:
+ row = event.GetRow()
+ data_type = self.Table.GetValueByName(row, "Type")
+ var_name = self.Table.GetValueByName(row, "Name")
+ data = wx.TextDataObject(str((var_name, "Global", data_type,
+ self.Controler.GetCurrentLocation())))
+ dragSource = wx.DropSource(self.VariablesGrid)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+ return
+ event.Skip()
+
+
+#-------------------------------------------------------------------------------
+# CodeFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
+
+class CodeFileEditor(ConfTreeNodeEditor):
+
+ CONFNODEEDITOR_TABS = []
+ CODE_EDITOR = None
+
+ def _create_CodePanel(self, prnt):
+ self.CodeEditorPanel = wx.SplitterWindow(prnt)
+ self.CodeEditorPanel.SetMinimumPaneSize(1)
+
+ self.VariablesPanel = VariablesEditor(self.CodeEditorPanel,
+ self.ParentWindow, self.Controler)
+
+ if self.CODE_EDITOR is not None:
+ self.CodeEditor = self.CODE_EDITOR(self.CodeEditorPanel,
+ self.ParentWindow, self.Controler)
+
+ self.CodeEditorPanel.SplitHorizontally(self.VariablesPanel,
+ self.CodeEditor, 150)
+ else:
+ self.CodeEditorPanel.Initialize(self.VariablesPanel)
+
+ return self.CodeEditorPanel
+
+ def __init__(self, parent, controler, window):
+ ConfTreeNodeEditor.__init__(self, parent, controler, window)
+
+ wx.CallAfter(self.CodeEditorPanel.SetSashPosition, 150)
+
+ def GetBufferState(self):
+ return self.Controler.GetBufferState()
+
+ def Undo(self):
+ self.Controler.LoadPrevious()
+ self.RefreshView()
+
+ def Redo(self):
+ self.Controler.LoadNext()
+ self.RefreshView()
+
+ def RefreshView(self):
+ ConfTreeNodeEditor.RefreshView(self)
+
+ self.VariablesPanel.RefreshView()
+ self.CodeEditor.RefreshView()
+
+ def Find(self, direction, search_params):
+ self.CodeEditor.Find(direction, search_params)
+
\ No newline at end of file
--- a/editors/ConfTreeNodeEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ConfTreeNodeEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -29,24 +29,12 @@
}
SCROLLBAR_UNIT = 10
-WINDOW_COLOUR = wx.Colour(240, 240, 240)
CWD = os.path.split(os.path.realpath(__file__))[0]
def Bpath(*args):
return os.path.join(CWD,*args)
-# Some helpers to tweak GenBitmapTextButtons
-# TODO: declare customized classes instead.
-gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
-gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,))
-
-def make_genbitmaptogglebutton_flat(button):
- button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button)
- button.labelDelta = 0
- button.SetBezelWidth(0)
- button.SetUseFocusIndicator(False)
-
# Patch wx.lib.imageutils so that gray is supported on alpha images
import wx.lib.imageutils
from wx.lib.imageutils import grayOut as old_grayOut
@@ -116,7 +104,7 @@
dc.DrawText(label, pos_x, pos_y) # draw the text
-class GenStaticBitmap(wx.lib.statbmp.GenStaticBitmap):
+class GenStaticBitmap(wx.StaticBitmap):
""" Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32,
and accept image name as __init__ parameter, fail silently if file do not exist"""
def __init__(self, parent, ID, bitmapname,
@@ -124,20 +112,15 @@
style = 0,
name = "genstatbmp"):
- wx.lib.statbmp.GenStaticBitmap.__init__(self, parent, ID,
- GetBitmap(bitmapname),
+ bitmap = GetBitmap(bitmapname)
+ if bitmap is None:
+ bitmap = wx.EmptyBitmap(0, 0)
+
+ wx.StaticBitmap.__init__(self, parent, ID,
+ bitmap,
pos, size,
style,
name)
-
- def OnPaint(self, event):
- dc = wx.PaintDC(self)
- colour = self.GetParent().GetBackgroundColour()
- dc.SetPen(wx.Pen(colour))
- dc.SetBrush(wx.Brush(colour ))
- dc.DrawRectangle(0, 0, *dc.GetSizeTuple())
- if self._bitmap:
- dc.DrawBitmap(self._bitmap, 0, 0, True)
class ConfTreeNodeEditor(EditorPanel):
@@ -147,57 +130,21 @@
def _init_Editor(self, parent):
tabs_num = len(self.CONFNODEEDITOR_TABS)
- if self.SHOW_PARAMS:
+ if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
tabs_num += 1
- if tabs_num > 1:
+ if tabs_num > 1 or self.SHOW_BASE_PARAMS:
self.Editor = wx.Panel(parent,
style=wx.SUNKEN_BORDER|wx.SP_3D)
- main_sizer = wx.BoxSizer(wx.VERTICAL)
-
- self.ConfNodeNoteBook = wx.Notebook(self.Editor)
- parent = self.ConfNodeNoteBook
- main_sizer.AddWindow(self.ConfNodeNoteBook, 1, flag=wx.GROW)
-
- self.Editor.SetSizer(main_sizer)
- else:
- self.ConfNodeNoteBook = None
- self.Editor = None
-
- for title, create_func_name in self.CONFNODEEDITOR_TABS:
- editor = getattr(self, create_func_name)(parent)
- if self.ConfNodeNoteBook is not None:
- self.ConfNodeNoteBook.AddPage(editor, title)
- else:
- self.Editor = editor
-
- if self.SHOW_PARAMS:
-
- panel_style = wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL
- if self.ConfNodeNoteBook is None:
- panel_style |= wx.SUNKEN_BORDER
- self.ParamsEditor = wx.ScrolledWindow(parent,
- style=panel_style)
- self.ParamsEditor.SetBackgroundColour(WINDOW_COLOUR)
- self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnWindowResize)
- self.ParamsEditor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
-
- # Variable allowing disabling of ParamsEditor scroll when Popup shown
- self.ScrollingEnabled = True
+ self.MainSizer = wx.BoxSizer(wx.VERTICAL)
if self.SHOW_BASE_PARAMS:
- self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- self.ParamsEditorSizer.AddGrowableCol(0)
- self.ParamsEditorSizer.AddGrowableRow(1)
-
- self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
-
baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
- self.ParamsEditorSizer.AddSizer(baseparamseditor_sizer, border=5,
- flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
-
- self.FullIECChannel = wx.StaticText(self.ParamsEditor, -1)
+ self.MainSizer.AddSizer(baseparamseditor_sizer, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ self.FullIECChannel = wx.StaticText(self.Editor, -1)
self.FullIECChannel.SetFont(
wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL,
wx.BOLD, faceName = faces["helv"]))
@@ -208,20 +155,20 @@
baseparamseditor_sizer.AddSizer(updownsizer, border=5,
flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
- self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.ParamsEditor,
+ self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.Editor,
bitmap=GetBitmap('IECCDown'), size=wx.Size(16, 16), style=wx.NO_BORDER)
self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
self.IECCUpButton)
updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
- self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.ParamsEditor,
+ self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.Editor,
bitmap=GetBitmap('IECCUp'), size=wx.Size(16, 16), style=wx.NO_BORDER)
self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1),
self.IECCDownButton)
updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
- self.ConfNodeName = wx.TextCtrl(self.ParamsEditor,
- size=wx.Size(150, 25), style=wx.NO_BORDER)
+ self.ConfNodeName = wx.TextCtrl(self.Editor,
+ size=wx.Size(150, 25))
self.ConfNodeName.SetFont(
wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL,
wx.BOLD, faceName = faces["helv"]))
@@ -234,10 +181,42 @@
buttons_sizer = self.GenerateMethodButtonSizer()
baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
+ if tabs_num > 1:
+ self.ConfNodeNoteBook = wx.Notebook(self.Editor)
+ parent = self.ConfNodeNoteBook
+ self.MainSizer.AddWindow(self.ConfNodeNoteBook, 1, flag=wx.GROW)
else:
- self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
- self.ParamsEditorSizer.AddGrowableCol(0)
- self.ParamsEditorSizer.AddGrowableRow(0)
+ parent = self.Editor
+ self.ConfNodeNoteBook = None
+
+ self.Editor.SetSizer(self.MainSizer)
+ else:
+ self.ConfNodeNoteBook = None
+ self.Editor = None
+
+ for title, create_func_name in self.CONFNODEEDITOR_TABS:
+ editor = getattr(self, create_func_name)(parent)
+ if self.ConfNodeNoteBook is not None:
+ self.ConfNodeNoteBook.AddPage(editor, title)
+ elif self.SHOW_BASE_PARAMS:
+ self.MainSizer.AddWindow(editor, 1, flag=wx.GROW)
+ else:
+ self.Editor = editor
+
+ if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
+
+ panel_style = wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL
+ if self.ConfNodeNoteBook is None and parent != self.Editor:
+ panel_style |= wx.SUNKEN_BORDER
+ self.ParamsEditor = wx.ScrolledWindow(parent,
+ style=panel_style)
+ self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnParamsEditorResize)
+ self.ParamsEditor.Bind(wx.EVT_SCROLLWIN, self.OnParamsEditorScroll)
+
+ self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
+ self.ParamsEditorSizer.AddGrowableCol(0)
+ self.ParamsEditorSizer.AddGrowableRow(0)
+ self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
@@ -247,6 +226,8 @@
if self.ConfNodeNoteBook is not None:
self.ConfNodeNoteBook.AddPage(self.ParamsEditor, _("Config"))
+ elif self.SHOW_BASE_PARAMS:
+ self.MainSizer.AddWindow(self.ParamsEditor, 1, flag=wx.GROW)
else:
self.Editor = self.ParamsEditor
else:
@@ -287,19 +268,17 @@
def RefreshView(self):
EditorPanel.RefreshView(self)
+ if self.SHOW_BASE_PARAMS:
+ self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
+ self.RefreshIECChannelControlsState()
if self.ParamsEditor is not None:
- if self.SHOW_BASE_PARAMS:
- self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
- self.RefreshIECChannelControlsState()
self.RefreshConfNodeParamsSizer()
self.RefreshScrollbars()
- def EnableScrolling(self, enable):
- self.ScrollingEnabled = enable
-
def RefreshIECChannelControlsState(self):
self.FullIECChannel.SetLabel(self.Controler.GetFullIEC_Channel())
self.IECCDownButton.Enable(self.Controler.BaseParams.getIEC_Channel() > 0)
+ self.MainSizer.Layout()
def RefreshConfNodeParamsSizer(self):
self.Freeze()
@@ -320,7 +299,7 @@
for confnode_method in self.Controler.ConfNodeMethods:
if "method" in confnode_method and confnode_method.get("shown",True):
- button = GenBitmapTextButton(self.ParamsEditor,
+ button = GenBitmapTextButton(self.Editor,
bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")),
label=confnode_method["name"], style=wx.NO_BORDER)
button.SetFont(normal_bt_font)
@@ -484,7 +463,6 @@
choices = self.ParentWindow.GetConfigEntry(element_path, [""])
textctrl = TextCtrlAutoComplete(name=element_infos["name"],
parent=self.ParamsEditor,
- appframe=self,
choices=choices,
element_path=element_path,
size=wx.Size(300, -1))
@@ -492,7 +470,9 @@
boxsizer.AddWindow(textctrl)
if element_infos["value"] is not None:
textctrl.ChangeValue(str(element_infos["value"]))
- textctrl.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(textctrl, element_path))
+ callback = self.GetTextCtrlCallBackFunction(textctrl, element_path)
+ textctrl.Bind(wx.EVT_TEXT_ENTER, callback)
+ textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
first = False
@@ -502,13 +482,12 @@
res = self.SetConfNodeParamsAttribute("BaseParams.IEC_Channel", confnode_IECChannel + dir)
wx.CallAfter(self.RefreshIECChannelControlsState)
wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE)
- wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
event.Skip()
return OnConfNodeTreeItemChannelChanged
def SetConfNodeParamsAttribute(self, *args, **kwargs):
res, StructChanged = self.Controler.SetParamsAttribute(*args, **kwargs)
- if StructChanged:
+ if StructChanged and self.ParamsEditor is not None:
wx.CallAfter(self.RefreshConfNodeParamsSizer)
wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU)
return res
@@ -547,8 +526,8 @@
if res != textctrl.GetValue():
if isinstance(textctrl, wx.SpinCtrl):
textctrl.SetValue(res)
- else:
- textctrl.ChangeValue(res)
+ elif res is not None:
+ textctrl.ChangeValue(str(res))
if refresh:
wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE, PAGETITLES)
wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
@@ -585,11 +564,14 @@
self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
- def OnWindowResize(self, event):
+ def OnParamsEditorResize(self, event):
self.RefreshScrollbars()
event.Skip()
- def OnMouseWheel(self, event):
- if self.ScrollingEnabled:
- event.Skip()
-
+ def OnParamsEditorScroll(self, event):
+ control = self.ParamsEditor.FindFocus()
+ if isinstance(control, TextCtrlAutoComplete):
+ control.DismissListBox()
+ self.Refresh()
+ event.Skip()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/DebugViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,348 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from threading import Lock, Timer
+from time import time as gettime
+
+import wx
+
+REFRESH_PERIOD = 0.1 # Minimum time between 2 refresh
+DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers
+
+#-------------------------------------------------------------------------------
+# Debug Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements common behavior of every viewers able to display debug
+values
+"""
+
+class DebugViewer:
+
+ def __init__(self, producer, debug, subscribe_tick=True):
+ """
+ Constructor
+ @param producer: Object receiving debug value and dispatching them to
+ consumers
+ @param debug: Flag indicating that Viewer is debugging
+ @param subscribe_tick: Flag indicating that viewer need tick value to
+ synchronize
+ """
+ self.Debug = debug
+ self.SubscribeTick = subscribe_tick
+
+ # Flag indicating that consumer value update inhibited
+ # (DebugViewer is refreshing)
+ self.Inhibited = False
+
+ # List of data consumers subscribed to DataProducer
+ self.DataConsumers = {}
+
+ # Time stamp indicating when last refresh have been initiated
+ self.LastRefreshTime = gettime()
+ # Flag indicating that DebugViewer has acquire common debug lock
+ self.HasAcquiredLock = False
+ # Lock for access to the two preceding variable
+ self.AccessLock = Lock()
+
+ # Timer to refresh Debug Viewer one last time in the case that a new
+ # value have been received during refresh was inhibited and no one
+ # after refresh was activated
+ self.LastRefreshTimer = None
+ # Lock for access to the timer
+ self.TimerAccessLock = Lock()
+
+ # Set DataProducer and subscribe tick if needed
+ self.SetDataProducer(producer)
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Unsubscribe all data consumers
+ self.UnsubscribeAllDataConsumers()
+
+ # Delete reference to DataProducer
+ self.DataProducer = None
+
+ # Stop last refresh timer
+ if self.LastRefreshTimer is not None:
+ self.LastRefreshTimer.cancel()
+
+ # Release Common debug lock if DebugViewer has acquired it
+ if self.HasAcquiredLock:
+ DEBUG_REFRESH_LOCK.release()
+
+ def SetDataProducer(self, producer):
+ """
+ Set Data Producer
+ @param producer: Data Producer
+ """
+ # In the case that tick need to be subscribed and DebugViewer is
+ # debugging
+ if self.SubscribeTick and self.Debug:
+
+ # Subscribe tick to new data producer
+ if producer is not None:
+ producer.SubscribeDebugIECVariable("__tick__", self)
+
+ # Unsubscribe tick from old data producer
+ if getattr(self, "DataProducer", None) is not None:
+ self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
+
+ # Save new data producer
+ self.DataProducer = producer
+
+ def IsDebugging(self):
+ """
+ Get flag indicating if Debug Viewer is debugging
+ @return: Debugging flag
+ """
+ return self.Debug
+
+ def Inhibit(self, inhibit):
+ """
+ Set consumer value update inhibit flag
+ @param inhibit: Inhibit flag
+ """
+ # Inhibit every data consumers in list
+ for consumer, iec_path in self.DataConsumers.iteritems():
+ consumer.Inhibit(inhibit)
+
+ # Save inhibit flag
+ self.Inhibited = inhibit
+
+ def AddDataConsumer(self, iec_path, consumer):
+ """
+ Subscribe data consumer to DataProducer
+ @param iec_path: Path in PLC of variable needed by data consumer
+ @param consumer: Data consumer to subscribe
+ @return: List of value already received [(tick, data),...] (None if
+ subscription failed)
+ """
+ # Return immediately if no DataProducer defined
+ if self.DataProducer is None:
+ return None
+
+ # Subscribe data consumer to DataProducer
+ result = self.DataProducer.SubscribeDebugIECVariable(
+ iec_path, consumer)
+ if result is not None and consumer != self:
+
+ # Store data consumer if successfully subscribed and inform
+ # consumer of variable data type
+ self.DataConsumers[consumer] = iec_path
+ consumer.SetDataType(self.GetDataType(iec_path))
+
+ return result
+
+ def RemoveDataConsumer(self, consumer):
+ """
+ Unsubscribe data consumer from DataProducer
+ @param consumer: Data consumer to unsubscribe
+ """
+ # Remove consumer from data consumer list
+ iec_path = self.DataConsumers.pop(consumer, None)
+
+ # Unsubscribe consumer from DataProducer
+ if iec_path is not None:
+ self.DataProducer.UnsubscribeDebugIECVariable(
+ iec_path, consumer)
+
+ def SubscribeAllDataConsumers(self):
+ """
+ Called to Subscribe all data consumers contained in DebugViewer.
+ May be overridden by inherited classes.
+ """
+ # Subscribe tick if needed
+ if self.SubscribeTick and self.Debug and self.DataProducer is not None:
+ self.DataProducer.SubscribeDebugIECVariable("__tick__", self)
+
+ def UnsubscribeAllDataConsumers(self, tick=True):
+ """
+ Called to Unsubscribe all data consumers.
+ """
+ if self.DataProducer is not None:
+
+ # Unscribe tick if needed
+ if self.SubscribeTick and tick and self.Debug:
+ self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
+
+ # Unsubscribe all data consumers in list
+ for consumer, iec_path in self.DataConsumers.iteritems():
+ self.DataProducer.UnsubscribeDebugIECVariable(
+ iec_path, consumer)
+
+ self.DataConsumers = {}
+
+ def GetDataType(self, iec_path):
+ """
+ Return variable data type.
+ @param iec_path: Path in PLC of variable
+ @return: variable data type (None if not found)
+ """
+ if self.DataProducer is not None:
+
+ # Search for variable informations in project compilation files
+ data_type = self.DataProducer.GetDebugIECVariableType(
+ iec_path.upper())
+ if data_type is not None:
+ return data_type
+
+ # Search for variable informations in project data
+ infos = self.DataProducer.GetInstanceInfos(iec_path)
+ if infos is not None:
+ return infos["type"]
+
+ return None
+
+ def IsNumType(self, data_type):
+ """
+ Indicate if data type given is a numeric data type
+ @param data_type: Data type to test
+ @return: True if data type given is numeric
+ """
+ if self.DataProducer is not None:
+ return self.DataProducer.IsNumType(data_type)
+
+ return False
+
+ def ForceDataValue(self, iec_path, value):
+ """
+ Force PLC variable value
+ @param iec_path: Path in PLC of variable to force
+ @param value: Value forced
+ """
+ if self.DataProducer is not None:
+ self.DataProducer.ForceDebugIECVariable(iec_path, value)
+
+ def ReleaseDataValue(self, iec_path):
+ """
+ Release PLC variable value
+ @param iec_path: Path in PLC of variable to release
+ """
+ if self.DataProducer is not None:
+ self.DataProducer.ReleaseDebugIECVariable(iec_path)
+
+ def NewDataAvailable(self, tick, *args, **kwargs):
+ """
+ Called by DataProducer for each tick captured
+ @param tick: PLC tick captured
+ All other parameters are passed to refresh function
+ """
+ # Stop last refresh timer
+ self.TimerAccessLock.acquire()
+ if self.LastRefreshTimer is not None:
+ self.LastRefreshTimer.cancel()
+ self.LastRefreshTimer=None
+ self.TimerAccessLock.release()
+
+ # Only try to refresh DebugViewer if it is visible on screen and not
+ # already refreshing
+ if self.IsShown() and not self.Inhibited:
+
+ # Try to get acquire common refresh lock if minimum period between
+ # two refresh has expired
+ if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \
+ DEBUG_REFRESH_LOCK.acquire(False):
+ self.StartRefreshing(*args, **kwargs)
+
+ # If common lock wasn't acquired for any reason, restart last
+ # refresh timer
+ else:
+ self.StartLastRefreshTimer(*args, **kwargs)
+
+ # In the case that DebugViewer isn't visible on screen and has already
+ # acquired common refresh lock, reset DebugViewer
+ elif not self.IsShown() and self.HasAcquiredLock:
+ DebugViewer.RefreshNewData(self)
+
+ def ShouldRefresh(self, *args, **kwargs):
+ """
+ Callback function called when last refresh timer expired
+ All parameters are passed to refresh function
+ """
+ # Cancel if DebugViewer is not visible on screen
+ if self and self.IsShown():
+
+ # Try to acquire common refresh lock
+ if DEBUG_REFRESH_LOCK.acquire(False):
+ self.StartRefreshing(*args, **kwargs)
+
+ # Restart last refresh timer if common refresh lock acquired failed
+ else:
+ self.StartLastRefreshTimer(*args, **kwargs)
+
+ def StartRefreshing(self, *args, **kwargs):
+ """
+ Called to initiate a refresh of DebugViewer
+ All parameters are passed to refresh function
+ """
+ # Update last refresh time stamp and flag for common refresh
+ # lock acquired
+ self.AccessLock.acquire()
+ self.HasAcquiredLock = True
+ self.LastRefreshTime = gettime()
+ self.AccessLock.release()
+
+ # Inhibit data consumer value update
+ self.Inhibit(True)
+
+ # Initiate DebugViewer refresh
+ wx.CallAfter(self.RefreshNewData, *args, **kwargs)
+
+ def StartLastRefreshTimer(self, *args, **kwargs):
+ """
+ Called to start last refresh timer for the minimum time between 2
+ refresh
+ All parameters are passed to refresh function
+ """
+ self.TimerAccessLock.acquire()
+ self.LastRefreshTimer = Timer(
+ REFRESH_PERIOD, self.ShouldRefresh, args, kwargs)
+ self.LastRefreshTimer.start()
+ self.TimerAccessLock.release()
+
+ def RefreshNewData(self, *args, **kwargs):
+ """
+ Called to refresh DebugViewer according to values received by data
+ consumers
+ May be overridden by inherited classes
+ Can receive any parameters depending on what is needed by inherited
+ class
+ """
+ if self:
+ # Activate data consumer value update
+ self.Inhibit(False)
+
+ # Release common refresh lock if acquired and update
+ # last refresh time
+ self.AccessLock.acquire()
+ if self.HasAcquiredLock:
+ DEBUG_REFRESH_LOCK.release()
+ self.HasAcquiredLock = False
+ if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
+ self.LastRefreshTime = gettime()
+ self.AccessLock.release()
--- a/editors/EditorPanel.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/EditorPanel.py Wed Jul 31 10:45:07 2013 +0900
@@ -39,7 +39,6 @@
def _init_ctrls(self, parent):
wx.SplitterWindow.__init__(self, parent,
style=wx.SUNKEN_BORDER|wx.SP_3D)
- self.SetNeedUpdating(True)
self.SetMinimumPaneSize(1)
self._init_MenuItems()
@@ -88,12 +87,6 @@
def SetIcon(self, icon):
self.Icon = icon
- def GetState(self):
- return None
-
- def SetState(self, state):
- pass
-
def IsViewing(self, tagname):
return self.GetTagName() == tagname
--- a/editors/GraphicViewer.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/GraphicViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -29,8 +29,9 @@
import wx.lib.plot as plot
import wx.lib.buttons
-from graphics.GraphicCommons import DebugViewer, MODE_SELECTION, MODE_MOTION
-from EditorPanel import EditorPanel
+from graphics.GraphicCommons import MODE_SELECTION, MODE_MOTION
+from editors.DebugViewer import DebugViewer
+from editors.EditorPanel import EditorPanel
from util.BitmapLibrary import GetBitmap
colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan',
--- a/editors/IECCodeViewer.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/IECCodeViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,9 +1,17 @@
from editors.TextViewer import TextViewer
+from plcopen.plcopen import TestTextElement
class IECCodeViewer(TextViewer):
def __del__(self):
TextViewer.__del__(self)
if getattr(self, "_OnClose"):
- self._OnClose(self)
\ No newline at end of file
+ self._OnClose(self)
+
+ def Paste(self):
+ if self.Controler is not None:
+ TextViewer.Paste(self)
+
+ def Search(self, criteria):
+ return [((self.TagName, "body", 0),) + result for result in TestTextElement(self.Editor.GetText(), criteria)]
\ No newline at end of file
--- a/editors/LDViewer.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/LDViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -184,17 +184,17 @@
def RefreshView(self, variablepanel=True, selection=None):
Viewer.RefreshView(self, variablepanel, selection)
- wx.CallAfter(self.Refresh)
- for i, rung in enumerate(self.Rungs):
- bbox = rung.GetBoundingBox()
- if i < len(self.RungComments):
- if self.RungComments[i]:
- pos = self.RungComments[i].GetPosition()
- if pos[1] > bbox.y:
- self.RungComments.insert(i, None)
- else:
- self.RungComments.insert(i, None)
-
+ if self.GetDrawingMode() != FREEDRAWING_MODE:
+ for i, rung in enumerate(self.Rungs):
+ bbox = rung.GetBoundingBox()
+ if i < len(self.RungComments):
+ if self.RungComments[i]:
+ pos = self.RungComments[i].GetPosition()
+ if pos[1] > bbox.y:
+ self.RungComments.insert(i, None)
+ else:
+ self.RungComments.insert(i, None)
+
def loadInstance(self, instance, ids, selection):
Viewer.loadInstance(self, instance, ids, selection)
if self.GetDrawingMode() != FREEDRAWING_MODE:
--- a/editors/ProjectNodeEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ProjectNodeEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -34,9 +34,8 @@
ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
buttons_sizer = self.GenerateMethodButtonSizer()
- self.ParamsEditorSizer.InsertSizer(0, buttons_sizer, 0, border=5,
- flag=wx.LEFT|wx.RIGHT|wx.TOP)
- self.ParamsEditorSizer.Layout()
+ self.MainSizer.InsertSizer(0, buttons_sizer, 0, border=5, flag=wx.ALL)
+ self.MainSizer.Layout()
self.VariableEditor = self.VariableEditorPanel
@@ -56,9 +55,8 @@
def RefreshView(self, variablepanel=True):
ConfTreeNodeEditor.RefreshView(self)
- if variablepanel:
- self.VariableEditor.RefreshView()
- #self.ProjectProperties.RefreshView()
+ self.VariableEditorPanel.RefreshView()
+ self.ProjectProperties.RefreshView()
def GetBufferState(self):
return self.Controler.GetBufferState()
--- a/editors/ResourceEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ResourceEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -140,7 +140,7 @@
editor = wx.grid.GridCellTextEditor()
renderer = wx.grid.GridCellStringRenderer()
elif colname == "Interval":
- editor = DurationCellEditor(self)
+ editor = DurationCellEditor(self, colname)
renderer = wx.grid.GridCellStringRenderer()
if self.GetValueByName(row, "Triggering") != "Cyclic":
grid.SetReadOnly(row, col, True)
@@ -154,7 +154,7 @@
grid.SetReadOnly(row, col, True)
elif colname == "Triggering":
editor = wx.grid.GridCellChoiceEditor()
- editor.SetParameters(",".join([""] + map(_, GetTaskTriggeringOptions())))
+ editor.SetParameters(",".join(map(_, GetTaskTriggeringOptions())))
elif colname == "Type":
editor = wx.grid.GridCellChoiceEditor()
editor.SetParameters(self.Parent.TypeList)
--- a/editors/TextViewer.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/TextViewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -31,6 +31,7 @@
from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
from plcopen.structures import ST_BLOCK_START_KEYWORDS, ST_BLOCK_END_KEYWORDS, IEC_BLOCK_START_KEYWORDS, IEC_BLOCK_END_KEYWORDS, LOCATIONDATATYPES
from EditorPanel import EditorPanel
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl, faces, GetCursorPos, NAVIGATION_KEYS
#-------------------------------------------------------------------------------
# Textual programs Viewer class
@@ -52,20 +53,6 @@
[ID_TEXTVIEWER, ID_TEXTVIEWERTEXTCTRL,
] = [wx.NewId() for _init_ctrls in range(2)]
-if wx.Platform == '__WXMSW__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Courier New',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 10,
- }
-else:
- faces = { 'times': 'Times',
- 'mono' : 'Courier',
- 'helv' : 'Helvetica',
- 'other': 'new century schoolbook',
- 'size' : 12,
- }
re_texts = {}
re_texts["letter"] = "[A-Za-z]"
re_texts["digit"] = "[0-9]"
@@ -79,27 +66,6 @@
SEARCH_RESULT_HIGHLIGHT: STC_PLC_SEARCH_RESULT,
}
-def GetCursorPos(old, new):
- old_length = len(old)
- new_length = len(new)
- common_length = min(old_length, new_length)
- i = 0
- for i in xrange(common_length):
- if old[i] != new[i]:
- break
- if old_length < new_length:
- if common_length > 0 and old[i] != new[i]:
- return i + new_length - old_length
- else:
- return i + new_length - old_length + 1
- elif old_length > new_length or i < min(old_length, new_length) - 1:
- if common_length > 0 and old[i] != new[i]:
- return i
- else:
- return i + 1
- else:
- return None
-
def LineStartswith(line, symbols):
return reduce(lambda x, y: x or y, map(lambda x: line.startswith(x), symbols), False)
@@ -115,7 +81,7 @@
event(self, function)
def _init_Editor(self, prnt):
- self.Editor = wx.stc.StyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL,
+ self.Editor = CustomStyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL,
parent=prnt, name="TextViewer", size=wx.Size(0, 0), style=0)
self.Editor.ParentWindow = self
@@ -175,6 +141,7 @@
self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, id=ID_TEXTVIEWERTEXTCTRL)
self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
+ self.Editor.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
self.Editor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
if self.Controler is not None:
self.Editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
@@ -196,14 +163,13 @@
self.DisableEvents = True
self.TextSyntax = None
self.CurrentAction = None
- self.Highlights = []
- self.SearchParams = None
- self.SearchResults = None
- self.CurrentFindHighlight = None
+
self.InstancePath = instancepath
self.ContextStack = []
self.CallStack = []
+ self.ResetSearchResults()
+
self.RefreshHighlightsTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
@@ -247,13 +213,12 @@
def GetCurrentPos(self):
return self.Editor.GetCurrentPos()
- def GetState(self):
- return {"cursor_pos": self.Editor.GetCurrentPos()}
-
- def SetState(self, state):
- if self and state.has_key("cursor_pos"):
- self.Editor.GotoPos(state.get("cursor_pos", 0))
-
+ def ResetSearchResults(self):
+ self.Highlights = []
+ self.SearchParams = None
+ self.SearchResults = None
+ self.CurrentFindHighlight = None
+
def OnModification(self, event):
if not self.DisableEvents:
mod_type = event.GetModificationType()
@@ -452,17 +417,20 @@
self.ResetBuffer()
self.DisableEvents = True
old_cursor_pos = self.GetCurrentPos()
+ line = self.Editor.GetFirstVisibleLine()
+ column = self.Editor.GetXOffset()
old_text = self.GetText()
new_text = self.Controler.GetEditedElementText(self.TagName, self.Debug)
- self.SetText(new_text)
- new_cursor_pos = GetCursorPos(old_text, new_text)
- if new_cursor_pos != None:
- self.Editor.GotoPos(new_cursor_pos)
- else:
- self.Editor.GotoPos(old_cursor_pos)
- self.Editor.ScrollToColumn(0)
- self.RefreshJumpList()
- self.Editor.EmptyUndoBuffer()
+ if old_text != new_text:
+ self.SetText(new_text)
+ new_cursor_pos = GetCursorPos(old_text, new_text)
+ self.Editor.LineScroll(column, line)
+ if new_cursor_pos != None:
+ self.Editor.GotoPos(new_cursor_pos)
+ else:
+ self.Editor.GotoPos(old_cursor_pos)
+ self.RefreshJumpList()
+ self.Editor.EmptyUndoBuffer()
self.DisableEvents = False
self.RefreshVariableTree()
@@ -585,7 +553,8 @@
else:
self.SetStyling(current_pos - last_styled_pos, 31)
last_styled_pos = current_pos
- state = SPACE
+ if state != DPRAGMA:
+ state = SPACE
line = ""
line_number += 1
self.RefreshLineFolding(line_number)
@@ -617,7 +586,7 @@
state = SPACE
elif state == DPRAGMA:
if line.endswith("}}"):
- self.SetStyling(current_pos - last_styled_pos + 2, 31)
+ self.SetStyling(current_pos - last_styled_pos + 1, 31)
last_styled_pos = current_pos + 1
state = SPACE
elif (line.endswith("'") or line.endswith('"')) and state not in [STRING, WSTRING]:
@@ -751,7 +720,13 @@
if self.Editor.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG:
self.Editor.ToggleFold(line)
event.Skip()
-
+
+ def OnUpdateUI(self, event):
+ selected = self.Editor.GetSelectedText()
+ if self.ParentWindow and selected != "":
+ self.ParentWindow.SetCopyBuffer(selected, True)
+ event.Skip()
+
def Cut(self):
self.ResetBuffer()
self.DisableEvents = True
@@ -762,6 +737,8 @@
def Copy(self):
self.Editor.CmdKeyExecute(wx.stc.STC_CMD_COPY)
+ if self.ParentWindow:
+ self.ParentWindow.RefreshEditMenu()
def Paste(self):
self.ResetBuffer()
@@ -771,6 +748,9 @@
self.RefreshModel()
self.RefreshBuffer()
+ def Search(self, criteria):
+ return self.Controler.SearchInPou(self.TagName, criteria, self.Debug)
+
def Find(self, direction, search_params):
if self.SearchParams != search_params:
self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
@@ -786,7 +766,8 @@
self.SearchResults = [
(infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)
for infos, start, end, text in
- self.Controler.SearchInPou(self.TagName, criteria, self.Debug)]
+ self.Search(criteria)]
+ self.CurrentFindHighlight = None
if len(self.SearchResults) > 0:
if self.CurrentFindHighlight is not None:
@@ -811,13 +792,15 @@
def RefreshModel(self):
self.RefreshJumpList()
self.Controler.SetEditedElementText(self.TagName, self.GetText())
+ self.ResetSearchResults()
def OnKeyDown(self, event):
+ key = event.GetKeyCode()
if self.Controler is not None:
if self.Editor.CallTipActive():
self.Editor.CallTipCancel()
- key = event.GetKeyCode()
+
key_handled = False
line = self.Editor.GetCurrentLine()
@@ -871,6 +854,8 @@
key_handled = True
if not key_handled:
event.Skip()
+ elif key in NAVIGATION_KEYS:
+ event.Skip()
def OnKillFocus(self, event):
self.Editor.AutoCompCancel()
--- a/editors/Viewer.py Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/Viewer.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,7 @@
import re
import math
-import time
+from time import time as gettime
from types import TupleType
from threading import Lock
@@ -35,6 +35,7 @@
from dialogs import *
from graphics import *
+from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
from EditorPanel import EditorPanel
SCROLLBAR_UNIT = 10
@@ -74,7 +75,11 @@
'size' : 12,
}
-ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, 7)]
+if wx.Platform == '__WXMSW__':
+ MAX_ZOOMIN = 4
+else:
+ MAX_ZOOMIN = 7
+ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, MAX_ZOOMIN)]
def GetVariableCreationFunction(variable_type):
def variableCreationFunction(viewer, id, specific_values):
@@ -266,6 +271,7 @@
self.ParentWindow.RefreshScrollBars()
self.ParentWindow.RefreshVisibleElements()
self.ParentWindow.RefreshVariablePanel()
+ self.ParentWindow.ParentWindow.RefreshPouInstanceVariablesPanel()
self.ParentWindow.Refresh(False)
elif values[1] == "location":
if pou_type == "program":
@@ -313,6 +319,7 @@
if not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
self.ParentWindow.Controler.AddEditedElementPouExternalVar(tagname, values[2], var_name)
self.ParentWindow.RefreshVariablePanel()
+ self.ParentWindow.ParentWindow.RefreshPouInstanceVariablesPanel()
self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, var_name, values[2])
elif values[1] == "Constant":
self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, values[0], None)
@@ -371,7 +378,7 @@
manipulating graphic elements
"""
-class Viewer(EditorPanel, DebugViewer, DebugDataConsumer):
+class Viewer(EditorPanel, DebugViewer):
if wx.VERSION < (2, 6, 0):
def Bind(self, event, function, id = None):
@@ -433,15 +440,18 @@
(ID_ALIGN_BOTTOM, wx.ITEM_NORMAL, _(u'Bottom'), '', self.OnAlignBottomMenu)])
# Add Wire Menu items to the given menu
- def AddWireMenuItems(self, menu, delete=False):
- [ID_ADD_SEGMENT, ID_DELETE_SEGMENT] = [wx.NewId() for i in xrange(2)]
+ def AddWireMenuItems(self, menu, delete=False, replace=False):
+ [ID_ADD_SEGMENT, ID_DELETE_SEGMENT, ID_REPLACE_WIRE,
+ ] = [wx.NewId() for i in xrange(3)]
# Create menu items
self.AddMenuItems(menu, [
(ID_ADD_SEGMENT, wx.ITEM_NORMAL, _(u'Add Wire Segment'), '', self.OnAddSegmentMenu),
- (ID_DELETE_SEGMENT, wx.ITEM_NORMAL, _(u'Delete Wire Segment'), '', self.OnDeleteSegmentMenu)])
-
+ (ID_DELETE_SEGMENT, wx.ITEM_NORMAL, _(u'Delete Wire Segment'), '', self.OnDeleteSegmentMenu),
+ (ID_REPLACE_WIRE, wx.ITEM_NORMAL, _(u'Replace Wire by connections'), '', self.OnReplaceWireMenu)])
+
menu.Enable(ID_DELETE_SEGMENT, delete)
+ menu.Enable(ID_REPLACE_WIRE, replace)
# Add Divergence Menu items to the given menu
def AddDivergenceMenuItems(self, menu, delete=False):
@@ -552,7 +562,6 @@
EditorPanel.__init__(self, parent, tagname, window, controler, debug)
DebugViewer.__init__(self, controler, debug)
- DebugDataConsumer.__init__(self)
# Adding a rubberband to Viewer
self.rubberBand = RubberBand(viewer=self)
@@ -574,6 +583,12 @@
self.InstancePath = instancepath
self.StartMousePos = None
self.StartScreenPos = None
+
+ # Prevent search for highlighted element to be called too often
+ self.LastHighlightCheckTime = gettime()
+ # Prevent search for element producing tooltip to be called too often
+ self.LastToolTipCheckTime = gettime()
+
self.Buffering = False
# Initialize Cursors
@@ -608,7 +623,7 @@
self.MiniTextDC.SetFont(wx.Font(faces["size"] * 0.75, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["helv"]))
self.CurrentScale = None
- self.SetScale(len(ZOOM_FACTORS) / 2, False)
+ self.SetScale(ZOOM_FACTORS.index(1.0), False)
self.RefreshHighlightsTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
@@ -718,18 +733,6 @@
def GetViewScale(self):
return self.ViewScale
- def GetState(self):
- return {"position": self.Editor.GetViewStart(),
- "zoom": self.CurrentScale}
-
- def SetState(self, state):
- if self:
- if state.has_key("zoom"):
- self.SetScale(state["zoom"])
- if state.has_key("position"):
- self.Scroll(*state["position"])
- self.RefreshVisibleElements()
-
def GetLogicalDC(self, buffered=False):
if buffered:
bitmap = wx.EmptyBitmap(*self.Editor.GetClientSize())
@@ -748,7 +751,14 @@
self.Editor.RefreshRect(rect, eraseBackground)
def Scroll(self, x, y):
+ if self.Debug and wx.Platform == '__WXMSW__':
+ self.Editor.Freeze()
self.Editor.Scroll(x, y)
+ if self.Debug:
+ if wx.Platform == '__WXMSW__':
+ self.Editor.Thaw()
+ else:
+ self.Editor.Refresh()
def GetScrollPos(self, orientation):
return self.Editor.GetScrollPos(orientation)
@@ -852,11 +862,13 @@
def GetElementIECPath(self, element):
iec_path = None
instance_path = self.GetInstancePath(True)
- if isinstance(element, Wire) and element.EndConnected is not None:
- block = element.EndConnected.GetParentBlock()
+ if isinstance(element, (Wire, Connector)):
+ if isinstance(element, Wire):
+ element = element.EndConnected
+ block = element.GetParentBlock()
if isinstance(block, FBD_Block):
blockname = block.GetName()
- connectorname = element.EndConnected.GetName()
+ connectorname = element.GetName()
if blockname != "":
iec_path = "%s.%s.%s"%(instance_path, blockname, connectorname)
else:
@@ -898,10 +910,10 @@
self.ToolTipElement = None
def Flush(self):
- self.DeleteDataConsumers()
+ self.UnsubscribeAllDataConsumers(tick=False)
for block in self.Blocks.itervalues():
block.Flush()
-
+
# Remove all elements
def CleanView(self):
for block in self.Blocks.itervalues():
@@ -1001,7 +1013,7 @@
self.PagePen = wx.TRANSPARENT_PEN
if refresh:
self.RefreshVisibleElements()
- self.Refresh(False)
+ self.Editor.Refresh(False)
#-------------------------------------------------------------------------------
@@ -1053,6 +1065,10 @@
self.RefreshRect(self.GetScrolledRect(refresh_rect), False)
else:
DebugViewer.RefreshNewData(self)
+
+ def SubscribeAllDataConsumers(self):
+ self.RefreshView()
+ DebugViewer.SubscribeAllDataConsumers(self)
# Refresh Viewer elements
def RefreshView(self, variablepanel=True, selection=None):
@@ -1062,7 +1078,7 @@
self.AddDataConsumer("%s.Q" % self.InstancePath.upper(), self)
if self.ToolTipElement is not None:
- self.ToolTipElement.ClearToolTip()
+ self.ToolTipElement.DestroyToolTip()
self.ToolTipElement = None
self.Inhibit(True)
@@ -1079,6 +1095,12 @@
instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, exclude = ids, debug = self.Debug)
if instance is not None:
self.loadInstance(instance, ids, selection)
+
+ if (selection is not None and
+ isinstance(self.SelectedElement, Graphic_Group)):
+ self.SelectedElement.RefreshWireExclusion()
+ self.SelectedElement.RefreshBoundingBox()
+
self.RefreshScrollBars()
for wire in self.Wires:
@@ -1096,14 +1118,21 @@
if self.Debug:
for block in self.Blocks.itervalues():
block.SpreadCurrent()
- iec_path = self.GetElementIECPath(block)
- if iec_path is not None:
- self.AddDataConsumer(iec_path.upper(), block)
+ if isinstance(block, FBD_Block):
+ for output_connector in block.GetConnectors()["outputs"]:
+ if len(output_connector.GetWires()) == 0:
+ iec_path = self.GetElementIECPath(output_connector)
+ if iec_path is not None:
+ self.AddDataConsumer(iec_path.upper(), output_connector)
+ else:
+ iec_path = self.GetElementIECPath(block)
+ if iec_path is not None:
+ self.AddDataConsumer(iec_path.upper(), block)
self.Inhibit(False)
self.RefreshVisibleElements()
self.ShowHighlights()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def GetPreviousSteps(self, connectors):
steps = []
@@ -1183,11 +1212,11 @@
if self.SelectedElement is None:
self.SelectedElement = element
elif isinstance(self.SelectedElement, Graphic_Group):
- self.SelectedElement.SelectElement(element)
+ self.SelectedElement.AddElement(element)
else:
group = Graphic_Group(self)
- group.SelectElement(self.SelectedElement)
- group.SelectElement(element)
+ group.AddElement(self.SelectedElement)
+ group.AddElement(element)
self.SelectedElement = group
# Load instance from given informations
@@ -1245,50 +1274,81 @@
element.SetPosition(instance["x"], instance["y"])
element.SetSize(instance["width"], instance["height"])
for i, output_connector in enumerate(instance["outputs"]):
- if i < len(connectors["outputs"]):
+ if isinstance(element, FBD_Block):
+ connector = element.GetConnector(
+ wx.Point(*output_connector["position"]),
+ output_name = output_connector["name"])
+ elif i < len(connectors["outputs"]):
connector = connectors["outputs"][i]
+ else:
+ connector = None
+ if connector is not None:
if output_connector.get("negated", False):
connector.SetNegated(True)
if output_connector.get("edge", "none") != "none":
connector.SetEdge(output_connector["edge"])
- connector.SetPosition(wx.Point(*output_connector["position"]))
+ if connectors["outputs"].index(connector) == i:
+ connector.SetPosition(wx.Point(*output_connector["position"]))
for i, input_connector in enumerate(instance["inputs"]):
- if i < len(connectors["inputs"]):
+ if isinstance(element, FBD_Block):
+ connector = element.GetConnector(
+ wx.Point(*input_connector["position"]),
+ input_name = input_connector["name"])
+ elif i < len(connectors["inputs"]):
connector = connectors["inputs"][i]
- connector.SetPosition(wx.Point(*input_connector["position"]))
+ else:
+ connector = None
+ if connector is not None:
+ if connectors["inputs"].index(connector) == i:
+ connector.SetPosition(wx.Point(*input_connector["position"]))
if input_connector.get("negated", False):
connector.SetNegated(True)
if input_connector.get("edge", "none") != "none":
connector.SetEdge(input_connector["edge"])
- self.CreateWires(connector, instance["id"], input_connector["links"], ids, selection)
+ if not self.CreateWires(connector, instance["id"], input_connector["links"], ids, selection):
+ element.RefreshModel()
+ element.RefreshConnectors()
if selection is not None and selection[0].get(instance["id"], False):
self.SelectInGroup(element)
def CreateWires(self, start_connector, id, links, ids, selection=None):
+ links_connected = True
for link in links:
refLocalId = link["refLocalId"]
- if refLocalId is not None:
- if refLocalId not in ids:
- new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
- if new_instance is not None:
- self.loadInstance(new_instance, ids, selection)
- connected = self.FindElementById(refLocalId)
- if connected is not None:
- points = link["points"]
- end_connector = connected.GetConnector(wx.Point(points[-1][0], points[-1][1]), link["formalParameter"])
- if end_connector is not None:
- wire = Wire(self)
- wire.SetPoints(points)
- start_connector.Connect((wire, 0), False)
- end_connector.Connect((wire, -1), False)
- wire.ConnectStartPoint(None, start_connector)
- wire.ConnectEndPoint(None, end_connector)
- self.AddWire(wire)
- if selection is not None and (\
- selection[1].get((id, refLocalId), False) or \
- selection[1].get((refLocalId, id), False)):
- self.SelectInGroup(wire)
-
+ if refLocalId is None:
+ links_connected = False
+ continue
+
+ if refLocalId not in ids:
+ new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
+ if new_instance is not None:
+ self.loadInstance(new_instance, ids, selection)
+
+ connected = self.FindElementById(refLocalId)
+ if connected is None:
+ links_connected = False
+ continue
+
+ points = link["points"]
+ end_connector = connected.GetConnector(wx.Point(points[-1][0], points[-1][1]), link["formalParameter"])
+ if end_connector is not None:
+ wire = Wire(self)
+ wire.SetPoints(points)
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ connected.RefreshConnectors()
+ self.AddWire(wire)
+ if selection is not None and (\
+ selection[1].get((id, refLocalId), False) or \
+ selection[1].get((refLocalId, id), False)):
+ self.SelectInGroup(wire)
+ else:
+ links_connected = False
+
+ return links_connected
+
def IsOfType(self, type, reference):
return self.Controler.IsOfType(type, reference, self.Debug)
@@ -1356,8 +1416,7 @@
if self.SelectedElement is not None:
self.SelectedElement.SetSelected(False)
self.SelectedElement = Graphic_Group(self)
- for element in self.GetElements():
- self.SelectedElement.SelectElement(element)
+ self.SelectedElement.SetElements(self.GetElements())
self.SelectedElement.SetSelected(True)
#-------------------------------------------------------------------------------
@@ -1449,7 +1508,18 @@
def PopupWireMenu(self, delete=True):
menu = wx.Menu(title='')
- self.AddWireMenuItems(menu, delete)
+
+ # If Check that wire can be replace by connections or abort
+ connected = self.SelectedElement.GetConnected()
+ start_connector = (
+ self.SelectedElement.GetEndConnected()
+ if self.SelectedElement.GetStartConnected() in connected
+ else self.SelectedElement.GetStartConnected())
+
+ self.AddWireMenuItems(menu, delete,
+ start_connector.GetDirection() == EAST and
+ not isinstance(start_connector.GetParentBlock(), SFC_Step))
+
menu.AppendSeparator()
self.AddDefaultMenuItems(menu, block=True)
self.Editor.PopupMenu(menu)
@@ -1487,37 +1557,37 @@
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(ALIGN_LEFT, None)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnAlignCenterMenu(self, event):
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(ALIGN_CENTER, None)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnAlignRightMenu(self, event):
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(ALIGN_RIGHT, None)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnAlignTopMenu(self, event):
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(None, ALIGN_TOP)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnAlignMiddleMenu(self, event):
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(None, ALIGN_MIDDLE)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnAlignBottomMenu(self, event):
if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
self.SelectedElement.AlignElements(None, ALIGN_BOTTOM)
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnNoModifierMenu(self, event):
if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
@@ -1552,7 +1622,111 @@
if self.SelectedElement is not None and self.IsWire(self.SelectedElement):
self.SelectedElement.DeleteSegment()
self.SelectedElement.Refresh()
-
+
+ def OnReplaceWireMenu(self, event):
+ # Check that selected element is a wire before applying replace
+ if (self.SelectedElement is not None and
+ self.IsWire(self.SelectedElement)):
+
+ # Get wire redraw bbox to erase it from screen
+ wire = self.SelectedElement
+ redraw_rect = wire.GetRedrawRect()
+
+ # Get connector at both ends of wire
+ connected = wire.GetConnected()
+ if wire.GetStartConnected() in connected:
+ start_connector = wire.GetEndConnected()
+ end_connector = wire.GetStartConnected()
+ wire.UnConnectStartPoint()
+ point_to_connect = 0
+ else:
+ start_connector = wire.GetStartConnected()
+ end_connector = wire.GetEndConnected()
+ wire.UnConnectEndPoint()
+ point_to_connect = -1
+
+ # Get a new default connection name
+ connection_name = self.Controler.GenerateNewName(
+ self.TagName, None, "Connection%d", 0)
+
+ # Create a connector to connect to wire
+ id = self.GetNewId()
+ connection = FBD_Connector(self, CONNECTOR, connection_name, id)
+ connection.SetSize(*self.GetScaledSize(*connection.GetMinSize()))
+
+ # Calculate position of connector at the right of start connector
+ connector = connection.GetConnectors()["inputs"][0]
+ rel_pos = connector.GetRelPosition()
+ direction = connector.GetDirection()
+ start_point = start_connector.GetPosition(False)
+ end_point = (start_point[0] + LD_WIRE_SIZE, start_point[1])
+ connection.SetPosition(end_point[0] - rel_pos[0],
+ end_point[1] - rel_pos[1])
+
+ # Connect connector to wire
+ connector.Connect((wire, point_to_connect))
+ if point_to_connect == 0:
+ wire.SetPoints([end_point, start_point])
+ else:
+ wire.SetPoints([start_point, end_point])
+ # Update redraw bbox with new wire trace so that it will be redraw
+ # on screen
+ redraw_rect.Union(wire.GetRedrawRect())
+
+ # Add connector to Viewer and model
+ self.AddBlock(connection)
+ self.Controler.AddEditedElementConnection(self.TagName, id,
+ CONNECTOR)
+ connection.RefreshModel()
+ # Update redraw bbox with new connector bbox so that it will be
+ # drawn on screen
+ redraw_rect.Union(connection.GetRedrawRect())
+
+ # Add new continuation
+ id = self.GetNewId()
+ connection = FBD_Connector(self, CONTINUATION, connection_name, id)
+ connection.SetSize(*self.GetScaledSize(*connection.GetMinSize()))
+
+ # Calculate position of connection at the left of end connector
+ connector = connection.GetConnectors()["outputs"][0]
+ rel_pos = connector.GetRelPosition()
+ direction = connector.GetDirection()
+ end_point = end_connector.GetPosition(False)
+ start_point = (end_point[0] - LD_WIRE_SIZE, end_point[1])
+ connection.SetPosition(start_point[0] - rel_pos[0],
+ start_point[1] - rel_pos[1])
+
+ # Add Wire to Viewer and connect it to blocks
+ new_wire = Wire(self,
+ [wx.Point(*start_point), connector.GetDirection()],
+ [wx.Point(*end_point), end_connector.GetDirection()])
+ self.AddWire(new_wire)
+ connector.Connect((new_wire, 0), False)
+ end_connector.Connect((new_wire, -1), False)
+ new_wire.ConnectStartPoint(None, connector)
+ new_wire.ConnectEndPoint(None, end_connector)
+ # Update redraw bbox with new wire bbox so that it will be drawn on
+ # screen
+ redraw_rect.Union(new_wire.GetRedrawRect())
+
+ # Add connection to Viewer and model
+ self.AddBlock(connection)
+ self.Controler.AddEditedElementConnection(self.TagName, id,
+ CONTINUATION)
+ connection.RefreshModel()
+ # Update redraw bbox with new connection bbox so that it will be
+ # drawn on screen
+ redraw_rect.Union(connection.GetRedrawRect())
+
+ # Refresh model for new wire
+ end_connector.RefreshParentBlock()
+
+ # Redraw
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.RefreshRect(self.GetScrolledRect(redraw_rect), False)
+
def OnAddBranchMenu(self, event):
if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
self.AddDivergenceBranch(self.SelectedElement)
@@ -1577,7 +1751,7 @@
self.SelectedElement.Delete()
self.SelectedElement = None
self.RefreshBuffer()
- self.Refresh(False)
+ self.Editor.Refresh(False)
def OnClearExecutionOrderMenu(self, event):
self.Controler.ClearEditedElementExecutionOrder(self.TagName)
@@ -1594,6 +1768,12 @@
wx.CallAfter(func, self.rubberBand.GetCurrentExtent(), *args)
return AddMenuCallBack
+ def GetAddToWireMenuCallBack(self, func, *args):
+ args += (self.SelectedElement,)
+ def AddToWireMenuCallBack(event):
+ func(wx.Rect(0, 0, 0, 0), *args)
+ return AddToWireMenuCallBack
+
def GetClipboardCallBack(self, func):
def ClipboardCallback(event):
wx.CallAfter(func)
@@ -1604,23 +1784,32 @@
#-------------------------------------------------------------------------------
def OnViewerMouseEvent(self, event):
- if not event.Entering():
- self.ResetBuffer()
+ self.ResetBuffer()
+ if event.Leaving() and self.ToolTipElement is not None:
+ self.ToolTipElement.DestroyToolTip()
+ elif (not event.Entering() and
+ gettime() - self.LastToolTipCheckTime > REFRESH_PERIOD):
+ self.LastToolTipCheckTime = gettime()
element = None
if not event.Leaving() and not event.LeftUp() and not event.LeftDClick():
- element = self.FindElement(event, True, False)
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ element = self.FindBlockConnector(pos)
+ if element is None or len(element.GetWires()) > 0:
+ element = self.FindElement(event, True, False)
if self.ToolTipElement is not None:
- self.ToolTipElement.ClearToolTip()
+ self.ToolTipElement.DestroyToolTip()
self.ToolTipElement = element
if self.ToolTipElement is not None:
tooltip_pos = self.Editor.ClientToScreen(event.GetPosition())
tooltip_pos.x += 10
tooltip_pos.y += 10
- self.ToolTipElement.CreateToolTip(tooltip_pos)
+ self.ToolTipElement.DisplayToolTip(tooltip_pos)
event.Skip()
def OnViewerLeftDown(self, event):
self.Editor.CaptureMouse()
+ self.StartMousePos = event.GetPosition()
if self.Mode == MODE_SELECTION:
dc = self.GetLogicalDC()
pos = event.GetLogicalPosition(dc)
@@ -1674,10 +1863,15 @@
elif not self.Debug and connector is not None and not event.ControlDown():
self.DrawingWire = True
scaled_pos = GetScaledEventPosition(event, dc, self.Scaling)
- if (connector.GetDirection() == EAST):
- wire = Wire(self, [wx.Point(pos.x, pos.y), EAST], [wx.Point(scaled_pos.x, scaled_pos.y), WEST])
- else:
- wire = Wire(self, [wx.Point(pos.x, pos.y), WEST], [wx.Point(scaled_pos.x, scaled_pos.y), EAST])
+ directions = {
+ EAST: [EAST, WEST],
+ WEST: [WEST, EAST],
+ NORTH: [NORTH, SOUTH],
+ SOUTH: [SOUTH, NORTH]}[connector.GetDirection()]
+ wire = Wire(self, *map(list, zip(
+ [wx.Point(pos.x, pos.y),
+ wx.Point(scaled_pos.x, scaled_pos.y)],
+ directions)))
wire.oldPos = scaled_pos
wire.Handle = (HANDLE_POINT, 0)
wire.ProcessDragging(0, 0, event, None)
@@ -1691,6 +1885,7 @@
self.HighlightedElement = wire
self.RefreshVisibleElements()
self.SelectedElement.SetHighlighted(True)
+ self.SelectedElement.StartConnected.HighlightParentBlock(True)
else:
if self.SelectedElement is not None and self.SelectedElement != element:
self.SelectedElement.SetSelected(False)
@@ -1698,7 +1893,6 @@
if element is not None:
self.SelectedElement = element
if self.Debug:
- self.StartMousePos = event.GetPosition()
Graphic_Element.OnLeftDown(self.SelectedElement, event, dc, self.Scaling)
else:
self.SelectedElement.OnLeftDown(event, dc, self.Scaling)
@@ -1712,7 +1906,6 @@
self.rubberBand.Reset()
self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
elif self.Mode == MODE_MOTION:
- self.StartMousePos = event.GetPosition()
self.StartScreenPos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
event.Skip()
@@ -1783,14 +1976,72 @@
self.SelectedElement.HighlightPoint(pos)
self.RefreshBuffer()
elif connector is None or self.SelectedElement.GetDragging():
- self.DrawingWire = False
- rect = self.SelectedElement.GetRedrawRect()
- wire = self.SelectedElement
- self.SelectedElement = self.SelectedElement.StartConnected.GetParentBlock()
- self.SelectedElement.SetSelected(True)
- rect.Union(self.SelectedElement.GetRedrawRect())
- wire.Delete()
- self.RefreshRect(self.GetScrolledRect(rect), False)
+ start_connector = self.SelectedElement.GetStartConnected()
+ start_direction = start_connector.GetDirection()
+
+ items = []
+
+ if self.CurrentLanguage == "SFC" and start_direction == SOUTH:
+ items.extend([
+ (_(u'Initial Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, True)),
+ (_(u'Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, False)),
+ (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, False)),
+ (_(u'Divergence'), self.GetAddToWireMenuCallBack(self.AddNewDivergence)),
+ (_(u'Jump'), self.GetAddToWireMenuCallBack(self.AddNewJump)),
+ ])
+
+ elif start_direction == EAST:
+
+ if isinstance(start_connector.GetParentBlock(), SFC_Step):
+ items.append(
+ (_(u'Action Block'), self.GetAddToWireMenuCallBack(self.AddNewActionBlock))
+ )
+ else:
+ items.extend([
+ (_(u'Block'), self.GetAddToWireMenuCallBack(self.AddNewBlock)),
+ (_(u'Variable'), self.GetAddToWireMenuCallBack(self.AddNewVariable, True)),
+ (_(u'Connection'), self.GetAddToWireMenuCallBack(self.AddNewConnection)),
+ ])
+
+ if self.CurrentLanguage != "FBD":
+ items.append(
+ (_(u'Contact'), self.GetAddToWireMenuCallBack(self.AddNewContact))
+ )
+ if self.CurrentLanguage == "LD":
+ items.extend([
+ (_(u'Coil'), self.GetAddToWireMenuCallBack(self.AddNewCoil)),
+ (_(u'Power Rail'), self.GetAddToWireMenuCallBack(self.AddNewPowerRail)),
+ ])
+ if self.CurrentLanguage == "SFC":
+ items.append(
+ (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, True))
+ )
+
+ if len(items) > 0:
+ if self.Editor.HasCapture():
+ self.Editor.ReleaseMouse()
+
+ # Popup contextual menu
+ menu = wx.Menu()
+ self.AddMenuItems(menu,
+ [(wx.NewId(), wx.ITEM_NORMAL, text, '', callback)
+ for text, callback in items])
+ self.PopupMenu(menu)
+
+ self.SelectedElement.StartConnected.HighlightParentBlock(False)
+ if self.DrawingWire:
+ self.DrawingWire = False
+ rect = self.SelectedElement.GetRedrawRect()
+ wire = self.SelectedElement
+ self.SelectedElement = self.SelectedElement.StartConnected.GetParentBlock()
+ self.SelectedElement.SetSelected(True)
+ rect.Union(self.SelectedElement.GetRedrawRect())
+ wire.Delete()
+ self.RefreshRect(self.GetScrolledRect(rect), False)
+ else:
+ self.SelectedElement.SetSelected(True)
+ if not self.SelectedElement.IsConnectedCompatible():
+ self.SelectedElement.SetValid(False)
else:
if self.Debug:
Graphic_Element.OnLeftUp(self.SelectedElement, event, dc, self.Scaling)
@@ -1798,7 +2049,6 @@
self.SelectedElement.OnLeftUp(event, dc, self.Scaling)
wx.CallAfter(self.SetCurrentCursor, 0)
elif self.Mode == MODE_MOTION:
- self.StartMousePos = None
self.StartScreenPos = None
if self.Mode != MODE_SELECTION and not self.SavedMode:
wx.CallAfter(self.ParentWindow.ResetCurrentMode)
@@ -1853,7 +2103,7 @@
event.Skip()
def OnViewerLeftDClick(self, event):
- element = self.FindElement(event, connectors=False)
+ element = self.FindElement(event)
if self.Mode == MODE_SELECTION and element is not None:
if self.SelectedElement is not None and self.SelectedElement != element:
self.SelectedElement.SetSelected(False)
@@ -1866,15 +2116,24 @@
if self.Debug:
if isinstance(self.SelectedElement, FBD_Block):
- instance_type = self.SelectedElement.GetType()
- pou_type = {
- "program": ITEM_PROGRAM,
- "functionBlock": ITEM_FUNCTIONBLOCK,
- }.get(self.Controler.GetPouType(instance_type))
- if pou_type is not None and instance_type in self.Controler.GetProjectPouNames(self.Debug):
- self.ParentWindow.OpenDebugViewer(pou_type,
- "%s.%s"%(self.GetInstancePath(True), self.SelectedElement.GetName()),
- self.Controler.ComputePouName(instance_type))
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ connector = self.SelectedElement.TestConnector(pos, EAST)
+ if connector is not None and len(connector.GetWires()) == 0:
+ iec_path = self.GetElementIECPath(connector)
+ if iec_path is not None:
+ self.ParentWindow.OpenDebugViewer(
+ ITEM_VAR_LOCAL, iec_path, connector.GetType())
+ else:
+ instance_type = self.SelectedElement.GetType()
+ pou_type = {
+ "program": ITEM_PROGRAM,
+ "functionBlock": ITEM_FUNCTIONBLOCK,
+ }.get(self.Controler.GetPouType(instance_type))
+ if pou_type is not None and instance_type in self.Controler.GetProjectPouNames(self.Debug):
+ self.ParentWindow.OpenDebugViewer(pou_type,
+ "%s.%s"%(self.GetInstancePath(True), self.SelectedElement.GetName()),
+ self.Controler.ComputePouName(instance_type))
else:
iec_path = self.GetElementIECPath(self.SelectedElement)
if iec_path is not None:
@@ -1925,13 +2184,15 @@
self.RefreshScrollBars()
self.RefreshVisibleElements()
else:
- if not event.Dragging():
+ if (not event.Dragging() and
+ gettime() - self.LastHighlightCheckTime > REFRESH_PERIOD):
+ self.LastHighlightCheckTime = gettime()
highlighted = self.FindElement(event, connectors=False)
if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
self.HighlightedElement.SetHighlighted(False)
self.HighlightedElement = None
if highlighted is not None:
- if isinstance(highlighted, (Wire, Graphic_Group)):
+ if not self.Debug and isinstance(highlighted, (Wire, Graphic_Group)):
highlighted.HighlightPoint(pos)
if self.HighlightedElement != highlighted:
highlighted.SetHighlighted(True)
@@ -1947,16 +2208,25 @@
self.SelectedElement.GeneratePoints()
if movex != 0 or movey != 0:
self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
- else:
+ elif not self.Debug:
self.SelectedElement.HighlightPoint(pos)
else:
movex, movey = self.SelectedElement.OnMotion(event, dc, self.Scaling)
if movex != 0 or movey != 0:
self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+ self.RefreshVisibleElements()
elif self.Debug and self.StartMousePos is not None and event.Dragging():
pos = event.GetPosition()
if abs(self.StartMousePos.x - pos.x) > 5 or abs(self.StartMousePos.y - pos.y) > 5:
- iec_path = self.GetElementIECPath(self.SelectedElement)
+ element = self.SelectedElement
+ if isinstance(self.SelectedElement, FBD_Block):
+ dc = self.GetLogicalDC()
+ connector = self.SelectedElement.TestConnector(
+ wx.Point(dc.DeviceToLogicalX(self.StartMousePos.x),
+ dc.DeviceToLogicalY(self.StartMousePos.y)))
+ if connector is not None:
+ element = connector
+ iec_path = self.GetElementIECPath(element)
if iec_path is not None:
self.StartMousePos = None
if self.HighlightedElement is not None:
@@ -1973,8 +2243,6 @@
event.Skip()
def OnLeaveViewer(self, event):
- if self.StartScreenPos is None:
- self.StartMousePos = None
if self.SelectedElement is not None and self.SelectedElement.GetDragging():
event.Skip()
elif self.HighlightedElement is not None:
@@ -2053,6 +2321,7 @@
self.StartBuffering()
self.SelectedElement.RefreshModel()
self.RefreshScrollBars()
+ self.RefreshVisibleElements()
self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
elif not self.Debug and keycode == wx.WXK_SPACE and self.SelectedElement is not None and self.SelectedElement.Dragging:
if self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement):
@@ -2099,7 +2368,7 @@
self.RefreshBuffer()
self.RefreshScrollBars()
self.RefreshVisibleElements()
- self.Refresh(False)
+ self.Editor.Refresh(False)
#-------------------------------------------------------------------------------
# Model adding functions
@@ -2111,13 +2380,39 @@
height = round(float(height) / float(self.Scaling[1]) + 0.4) * self.Scaling[1]
return width, height
- def AddNewBlock(self, bbox):
- dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+ def AddNewElement(self, element, bbox, wire=None, connector=None):
+ min_width, min_height = (element.GetMinSize(True)
+ if isinstance(element, (LD_PowerRail,
+ SFC_Divergence))
+ else element.GetMinSize())
+ element.SetSize(*self.GetScaledSize(
+ max(bbox.width, min_width), max(bbox.height, min_height)))
+ if wire is not None:
+ if connector is None:
+ connector = element.GetConnectors()["inputs"][0]
+ point = wire.GetPoint(-1)
+ rel_pos = connector.GetRelPosition()
+ direction = connector.GetDirection()
+ element.SetPosition(
+ point[0] - rel_pos[0] - direction[0] * CONNECTOR_SIZE,
+ point[1] - rel_pos[1] - direction[1] * CONNECTOR_SIZE,
+ )
+ connector.Connect((wire, -1))
+ wire.Refresh()
+ self.DrawingWire = False
+ else:
+ element.SetPosition(bbox.x, bbox.y)
+ self.AddBlock(element)
+ element.RefreshModel()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ element.Refresh()
+
+ def AddNewBlock(self, bbox, wire=None):
+ dialog = FBDBlockDialog(self.ParentWindow, self.Controler, self.TagName)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
- dialog.SetMinBlockSize((bbox.width, bbox.height))
+ dialog.SetMinElementSize((bbox.width, bbox.height))
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
values = dialog.GetValues()
@@ -2126,78 +2421,54 @@
values["extension"], values["inputs"],
executionControl = values["executionControl"],
executionOrder = values["executionOrder"])
- block.SetPosition(bbox.x, bbox.y)
- block.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(block)
self.Controler.AddEditedElementBlock(self.TagName, id, values["type"], values.get("name", None))
- self.RefreshBlockModel(block)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- self.RefreshVariablePanel()
- self.ParentWindow.RefreshPouInstanceVariablesPanel()
- block.Refresh()
+ connector = None
+ for input_connector in block.GetConnectors()["inputs"]:
+ if input_connector.IsCompatible(
+ wire.GetStartConnectedType()):
+ connector = input_connector
+ break
+ self.AddNewElement(block, bbox, wire, connector)
dialog.Destroy()
- def AddNewVariable(self, bbox):
- words = self.TagName.split("::")
- if words[0] == "T":
- dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
- else:
- dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+ def AddNewVariable(self, bbox, exclude_input=False, wire=None):
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler, self.TagName, exclude_input)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetMinVariableSize((bbox.width, bbox.height))
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Edit"]:
- varlist.append((var["Name"], var["Class"], var["Type"]))
- returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
- if returntype:
- varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
- dialog.SetVariables(varlist)
+ dialog.SetMinElementSize((bbox.width, bbox.height))
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
values = dialog.GetValues()
- variable = FBD_Variable(self, values["type"], values["name"], values["value_type"], id)
- variable.SetPosition(bbox.x, bbox.y)
- variable.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(variable)
- self.Controler.AddEditedElementVariable(self.TagName, id, values["type"])
- self.RefreshVariableModel(variable)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- variable.Refresh()
+ variable = FBD_Variable(self, values["class"], values["expression"], values["var_type"], id)
+ variable.SetExecutionOrder(values["executionOrder"])
+ self.Controler.AddEditedElementVariable(self.TagName, id, values["class"])
+ self.AddNewElement(variable, bbox, wire)
dialog.Destroy()
- def AddNewConnection(self, bbox):
- dialog = ConnectionDialog(self.ParentWindow, self.Controler)
- dialog.SetPreviewFont(self.GetFont())
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
- dialog.SetMinConnectionSize((bbox.width, bbox.height))
- if dialog.ShowModal() == wx.ID_OK:
+ def AddNewConnection(self, bbox, wire=None):
+ if wire is not None:
+ values = {
+ "type": CONNECTOR,
+ "name": self.Controler.GenerateNewName(
+ self.TagName, None, "Connection%d", 0)}
+ else:
+ dialog = ConnectionDialog(self.ParentWindow, self.Controler, self.TagName)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinElementSize((bbox.width, bbox.height))
+ values = (dialog.GetValues()
+ if dialog.ShowModal() == wx.ID_OK
+ else None)
+ dialog.Destroy()
+ if values is not None:
id = self.GetNewId()
- values = dialog.GetValues()
connection = FBD_Connector(self, values["type"], values["name"], id)
- connection.SetPosition(bbox.x, bbox.y)
- connection.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(connection)
self.Controler.AddEditedElementConnection(self.TagName, id, values["type"])
- self.RefreshConnectionModel(connection)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- connection.Refresh()
- dialog.Destroy()
-
+ self.AddNewElement(connection, bbox, wire)
+
def AddNewComment(self, bbox):
- if wx.VERSION >= (2, 5, 0):
- dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL|wx.TE_MULTILINE)
- else:
- dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL)
+ dialog = wx.TextEntryDialog(self.ParentWindow,
+ _("Edit comment"),
+ _("Please enter comment text"),
+ "", wx.OK|wx.CANCEL|wx.TE_MULTILINE)
dialog.SetClientSize(wx.Size(400, 200))
if dialog.ShowModal() == wx.ID_OK:
value = dialog.GetValue()
@@ -2215,158 +2486,112 @@
comment.Refresh()
dialog.Destroy()
- def AddNewContact(self, bbox):
- dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+ def AddNewContact(self, bbox, wire=None):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "contact")
dialog.SetPreviewFont(self.GetFont())
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Type"] == "BOOL":
- varlist.append(var["Name"])
- dialog.SetVariables(varlist)
- dialog.SetValues({"name":"","type":CONTACT_NORMAL})
- dialog.SetElementSize((bbox.width, bbox.height))
+ dialog.SetMinElementSize((bbox.width, bbox.height))
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
values = dialog.GetValues()
- contact = LD_Contact(self, values["type"], values["name"], id)
- contact.SetPosition(bbox.x, bbox.y)
- contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(contact)
+ contact = LD_Contact(self, values["modifier"], values["variable"], id)
self.Controler.AddEditedElementContact(self.TagName, id)
- self.RefreshContactModel(contact)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- contact.Refresh()
+ self.AddNewElement(contact, bbox, wire)
dialog.Destroy()
- def AddNewCoil(self, bbox):
- dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+ def AddNewCoil(self, bbox, wire=None):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "coil")
dialog.SetPreviewFont(self.GetFont())
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Class"] != "Input" and var["Type"] == "BOOL":
- varlist.append(var["Name"])
- returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
- if returntype == "BOOL":
- varlist.append(self.Controler.GetEditedElementName(self.TagName))
- dialog.SetVariables(varlist)
- dialog.SetValues({"name":"","type":COIL_NORMAL})
- dialog.SetElementSize((bbox.width, bbox.height))
+ dialog.SetMinElementSize((bbox.width, bbox.height))
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
values = dialog.GetValues()
- coil = LD_Coil(self, values["type"], values["name"], id)
- coil.SetPosition(bbox.x, bbox.y)
- coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(coil)
+ coil = LD_Coil(self, values["modifier"], values["variable"], id)
self.Controler.AddEditedElementCoil(self.TagName, id)
- self.RefreshCoilModel(coil)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- coil.Refresh()
+ self.AddNewElement(coil, bbox, wire)
dialog.Destroy()
- def AddNewPowerRail(self, bbox):
- dialog = LDPowerRailDialog(self.ParentWindow, self.Controler)
+ def AddNewPowerRail(self, bbox, wire=None):
+ if wire is not None:
+ values = {
+ "type": RIGHTRAIL,
+ "pin_number": 1}
+ else:
+ dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, self.TagName)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinElementSize((bbox.width, bbox.height))
+ values = (dialog.GetValues()
+ if dialog.ShowModal() == wx.ID_OK
+ else None)
+ dialog.Destroy()
+ if values is not None:
+ id = self.GetNewId()
+ powerrail = LD_PowerRail(self, values["type"], id, values["pin_number"])
+ self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
+ self.AddNewElement(powerrail, bbox, wire)
+
+ def AddNewStep(self, bbox, initial=False, wire=None):
+ if wire is not None:
+ values = {
+ "name": self.Controler.GenerateNewName(
+ self.TagName, None, "Step%d", 0),
+ "input": True,
+ "output": False,
+ "action":False}
+ else:
+ dialog = SFCStepDialog(self.ParentWindow, self.Controler, self.TagName, initial)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinElementSize((bbox.width, bbox.height))
+ values = (dialog.GetValues()
+ if dialog.ShowModal() == wx.ID_OK
+ else None)
+ dialog.Destroy()
+ if values is not None:
+ id = self.GetNewId()
+ step = SFC_Step(self, values["name"], initial, id)
+ self.Controler.AddEditedElementStep(self.TagName, id)
+ for connector in ["input", "output", "action"]:
+ getattr(step, ("Add"
+ if values[connector]
+ else "Remove") + connector.capitalize())()
+ self.AddNewElement(step, bbox, wire)
+
+ def AddNewTransition(self, bbox, connection=False, wire=None):
+ if wire is not None and connection:
+ values = {
+ "type": "connection",
+ "value": None,
+ "priority": 0}
+ else:
+ dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.TagName, self.GetDrawingMode() == FREEDRAWING_MODE)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinElementSize((bbox.width, bbox.height))
+ values = (dialog.GetValues()
+ if dialog.ShowModal() == wx.ID_OK
+ else None)
+ dialog.Destroy()
+ if values is not None:
+ id = self.GetNewId()
+ transition = SFC_Transition(self, values["type"], values["value"], values["priority"], id)
+ self.Controler.AddEditedElementTransition(self.TagName, id)
+ if connection:
+ connector = transition.GetConditionConnector()
+ else:
+ connector = transition.GetConnectors()["inputs"][0]
+ self.AddNewElement(transition, bbox, wire, connector)
+
+ def AddNewDivergence(self, bbox, wire=None):
+ dialog = SFCDivergenceDialog(self.ParentWindow, self.Controler, self.TagName)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetMinSize((bbox.width, bbox.height))
- if dialog.ShowModal() == wx.ID_OK:
- id = self.GetNewId()
- values = dialog.GetValues()
- powerrail = LD_PowerRail(self, values["type"], id, values["number"])
- powerrail.SetPosition(bbox.x, bbox.y)
- powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
- self.AddBlock(powerrail)
- self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
- self.RefreshPowerRailModel(powerrail)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- powerrail.Refresh()
- dialog.Destroy()
-
- def AddNewStep(self, bbox, initial = False):
- dialog = SFCStepDialog(self.ParentWindow, self.Controler, initial)
- dialog.SetPreviewFont(self.GetFont())
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
- dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step)])
- dialog.SetMinStepSize((bbox.width, bbox.height))
- if dialog.ShowModal() == wx.ID_OK:
- id = self.GetNewId()
- values = dialog.GetValues()
- step = SFC_Step(self, values["name"], initial, id)
- if values["input"]:
- step.AddInput()
- else:
- step.RemoveInput()
- if values["output"]:
- step.AddOutput()
- else:
- step.RemoveOutput()
- if values["action"]:
- step.AddAction()
- else:
- step.RemoveAction()
- step.SetPosition(bbox.x, bbox.y)
- min_width, min_height = step.GetMinSize()
- step.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
- self.AddBlock(step)
- self.Controler.AddEditedElementStep(self.TagName, id)
- self.RefreshStepModel(step)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- step.Refresh()
- dialog.Destroy()
-
- def AddNewTransition(self, bbox):
- dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
- dialog.SetPreviewFont(self.GetFont())
- dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
- if dialog.ShowModal() == wx.ID_OK:
- id = self.GetNewId()
- values = dialog.GetValues()
- transition = SFC_Transition(self, values["type"], values["value"], values["priority"], id)
- transition.SetPosition(bbox.x, bbox.y)
- min_width, min_height = transition.GetMinSize()
- transition.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
- self.AddBlock(transition)
- self.Controler.AddEditedElementTransition(self.TagName, id)
- self.RefreshTransitionModel(transition)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- transition.Refresh()
- dialog.Destroy()
-
- def AddNewDivergence(self, bbox):
- dialog = SFCDivergenceDialog(self.ParentWindow, self.Controler)
- dialog.SetPreviewFont(self.GetFont())
- dialog.SetMinSize((bbox.width, bbox.height))
+ dialog.SetMinElementSize((bbox.width, bbox.height))
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
values = dialog.GetValues()
divergence = SFC_Divergence(self, values["type"], values["number"], id)
- divergence.SetPosition(bbox.x, bbox.y)
- min_width, min_height = divergence.GetMinSize(True)
- divergence.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
- self.AddBlock(divergence)
self.Controler.AddEditedElementDivergence(self.TagName, id, values["type"])
- self.RefreshDivergenceModel(divergence)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- divergence.Refresh()
+ self.AddNewElement(divergence, bbox, wire)
dialog.Destroy()
- def AddNewJump(self, bbox):
+ def AddNewJump(self, bbox, wire=None):
choices = []
for block in self.Blocks.itervalues():
if isinstance(block, SFC_Step):
@@ -2376,39 +2601,21 @@
choices, wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
if dialog.ShowModal() == wx.ID_OK:
id = self.GetNewId()
- value = dialog.GetStringSelection()
- jump = SFC_Jump(self, value, id)
- jump.SetPosition(bbox.x, bbox.y)
- min_width, min_height = jump.GetMinSize()
- jump.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
- self.AddBlock(jump)
+ jump = SFC_Jump(self, dialog.GetStringSelection(), id)
self.Controler.AddEditedElementJump(self.TagName, id)
- self.RefreshJumpModel(jump)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- jump.Refresh()
+ self.AddNewElement(jump, bbox, wire)
dialog.Destroy()
- def AddNewActionBlock(self, bbox):
+ def AddNewActionBlock(self, bbox, wire=None):
dialog = ActionBlockDialog(self.ParentWindow)
dialog.SetQualifierList(self.Controler.GetQualifierTypes())
dialog.SetActionList(self.Controler.GetEditedElementActions(self.TagName, self.Debug))
dialog.SetVariableList(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
if dialog.ShowModal() == wx.ID_OK:
- actions = dialog.GetValues()
id = self.GetNewId()
- actionblock = SFC_ActionBlock(self, actions, id)
- actionblock.SetPosition(bbox.x, bbox.y)
- min_width, min_height = actionblock.GetMinSize()
- actionblock.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
- self.AddBlock(actionblock)
+ actionblock = SFC_ActionBlock(self, dialog.GetValues(), id)
self.Controler.AddEditedElementActionBlock(self.TagName, id)
- self.RefreshActionBlockModel(actionblock)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.RefreshVisibleElements()
- actionblock.Refresh()
+ self.AddNewElement(actionblock, bbox, wire)
dialog.Destroy()
#-------------------------------------------------------------------------------
@@ -2416,15 +2623,9 @@
#-------------------------------------------------------------------------------
def EditBlockContent(self, block):
- dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+ dialog = FBDBlockDialog(self.ParentWindow, self.Controler, self.TagName)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- variable_names = self.Controler.GetEditedElementVariables(self.TagName, self.Debug)
- if block.GetName() != "":
- variable_names.remove(block.GetName())
- dialog.SetPouElementNames(variable_names)
- dialog.SetMinBlockSize(block.GetSize())
+ dialog.SetMinElementSize(block.GetSize())
old_values = {"name" : block.GetName(),
"type" : block.GetType(),
"extension" : block.GetExtension(),
@@ -2456,38 +2657,24 @@
dialog.Destroy()
def EditVariableContent(self, variable):
- words = self.TagName.split("::")
- if words[0] == "T":
- dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
- else:
- dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler, self.TagName)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetMinVariableSize(variable.GetSize())
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Edit"]:
- varlist.append((var["Name"], var["Class"], var["Type"]))
- returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
- if returntype:
- varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
- dialog.SetVariables(varlist)
- old_values = {"name" : variable.GetName(), "type" : variable.GetType(),
- "executionOrder" : variable.GetExecutionOrder()}
+ dialog.SetMinElementSize(variable.GetSize())
+ old_values = {"expression" : variable.GetName(), "class" : variable.GetType(),
+ "executionOrder" : variable.GetExecutionOrder()}
dialog.SetValues(old_values)
if dialog.ShowModal() == wx.ID_OK:
new_values = dialog.GetValues()
rect = variable.GetRedrawRect(1, 1)
- variable.SetName(new_values["name"])
- variable.SetType(new_values["type"], new_values["value_type"])
+ variable.SetName(new_values["expression"])
+ variable.SetType(new_values["class"], new_values["var_type"])
variable.SetSize(*self.GetScaledSize(new_values["width"], new_values["height"]))
variable.SetExecutionOrder(new_values["executionOrder"])
rect = rect.Union(variable.GetRedrawRect())
- if old_values["type"] != new_values["type"]:
+ if old_values["class"] != new_values["class"]:
id = variable.GetId()
self.Controler.RemoveEditedElementInstance(self.TagName, id)
- self.Controler.AddEditedElementVariable(self.TagName, id, new_values["type"])
+ self.Controler.AddEditedElementVariable(self.TagName, id, new_values["class"])
self.RefreshVariableModel(variable)
self.RefreshBuffer()
if old_values["executionOrder"] != new_values["executionOrder"]:
@@ -2499,11 +2686,9 @@
dialog.Destroy()
def EditConnectionContent(self, connection):
- dialog = ConnectionDialog(self.ParentWindow, self.Controler, True)
+ dialog = ConnectionDialog(self.ParentWindow, self.Controler, self.TagName, True)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
- dialog.SetMinConnectionSize(connection.GetSize())
+ dialog.SetMinElementSize(connection.GetSize())
values = {"name" : connection.GetName(), "type" : connection.GetType()}
dialog.SetValues(values)
result = dialog.ShowModal()
@@ -2533,23 +2718,16 @@
connection.Refresh(rect)
def EditContactContent(self, contact):
- dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "contact")
dialog.SetPreviewFont(self.GetFont())
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Type"] == "BOOL":
- varlist.append(var["Name"])
- dialog.SetVariables(varlist)
- values = {"name" : contact.GetName(), "type" : contact.GetType()}
- dialog.SetValues(values)
- dialog.SetElementSize(contact.GetSize())
+ dialog.SetMinElementSize(contact.GetSize())
+ dialog.SetValues({"variable" : contact.GetName(),
+ "modifier" : contact.GetType()})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
rect = contact.GetRedrawRect(1, 1)
- contact.SetName(values["name"])
- contact.SetType(values["type"])
+ contact.SetName(values["variable"])
+ contact.SetType(values["modifier"])
contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
rect = rect.Union(contact.GetRedrawRect())
self.RefreshContactModel(contact)
@@ -2560,26 +2738,16 @@
dialog.Destroy()
def EditCoilContent(self, coil):
- dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "coil")
dialog.SetPreviewFont(self.GetFont())
- varlist = []
- vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
- if vars:
- for var in vars:
- if var["Class"] != "Input" and var["Type"] == "BOOL":
- varlist.append(var["Name"])
- returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
- if returntype == "BOOL":
- varlist.append(self.Controler.GetEditedElementName(self.TagName))
- dialog.SetVariables(varlist)
- values = {"name" : coil.GetName(), "type" : coil.GetType()}
- dialog.SetValues(values)
- dialog.SetElementSize(coil.GetSize())
+ dialog.SetMinElementSize(coil.GetSize())
+ dialog.SetValues({"variable" : coil.GetName(),
+ "modifier" : coil.GetType()})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
rect = coil.GetRedrawRect(1, 1)
- coil.SetName(values["name"])
- coil.SetType(values["type"])
+ coil.SetName(values["variable"])
+ coil.SetType(values["modifier"])
coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
rect = rect.Union(coil.GetRedrawRect())
self.RefreshCoilModel(coil)
@@ -2590,23 +2758,21 @@
dialog.Destroy()
def EditPowerRailContent(self, powerrail):
- connectors = powerrail.GetConnectors()
- type = powerrail.GetType()
- if type == LEFTRAIL:
- pin_number = len(connectors["outputs"])
- else:
- pin_number = len(connectors["inputs"])
- dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, type, pin_number)
+ dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, self.TagName)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetMinSize(powerrail.GetSize())
+ dialog.SetMinElementSize(powerrail.GetSize())
+ powerrail_type = powerrail.GetType()
+ dialog.SetValues({
+ "type": powerrail.GetType(),
+ "pin_number": len(powerrail.GetConnectors()[
+ ("outputs" if powerrail_type == LEFTRAIL else "inputs")])})
if dialog.ShowModal() == wx.ID_OK:
- old_type = powerrail.GetType()
values = dialog.GetValues()
rect = powerrail.GetRedrawRect(1, 1)
- powerrail.SetType(values["type"], values["number"])
+ powerrail.SetType(values["type"], values["pin_number"])
powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
rect = rect.Union(powerrail.GetRedrawRect())
- if old_type != values["type"]:
+ if powerrail_type != values["type"]:
id = powerrail.GetId()
self.Controler.RemoveEditedElementInstance(self.TagName, id)
self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
@@ -2618,18 +2784,15 @@
dialog.Destroy()
def EditStepContent(self, step):
- dialog = SFCStepDialog(self.ParentWindow, self.Controler, step.GetInitial())
+ dialog = SFCStepDialog(self.ParentWindow, self.Controler, self.TagName, step.GetInitial())
dialog.SetPreviewFont(self.GetFont())
- dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
- dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
- dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step) and block.GetName() != step.GetName()])
- dialog.SetMinStepSize(step.GetSize())
- values = {"name" : step.GetName()}
+ dialog.SetMinElementSize(step.GetSize())
connectors = step.GetConnectors()
- values["input"] = len(connectors["inputs"]) > 0
- values["output"] = len(connectors["outputs"]) > 0
- values["action"] = step.GetActionConnector() != None
- dialog.SetValues(values)
+ dialog.SetValues({
+ "name" : step.GetName(),
+ "input": len(connectors["inputs"]) > 0,
+ "output": len(connectors["outputs"]) > 0,
+ "action": step.GetActionConnector() != None})
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
rect = step.GetRedrawRect(1, 1)
@@ -2655,11 +2818,10 @@
step.Refresh(rect)
def EditTransitionContent(self, transition):
- dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
+ dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.TagName, self.GetDrawingMode() == FREEDRAWING_MODE)
dialog.SetPreviewFont(self.GetFont())
- dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
+ dialog.SetMinElementSize(transition.GetSize())
dialog.SetValues({"type":transition.GetType(),"value":transition.GetCondition(), "priority":transition.GetPriority()})
- dialog.SetElementSize(transition.GetSize())
if dialog.ShowModal() == wx.ID_OK:
values = dialog.GetValues()
rect = transition.GetRedrawRect(1, 1)
@@ -2714,10 +2876,11 @@
dialog.Destroy()
def EditCommentContent(self, comment):
- if wx.VERSION >= (2, 5, 0):
- dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL|wx.TE_MULTILINE)
- else:
- dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL)
+ dialog = wx.TextEntryDialog(self.ParentWindow,
+ _("Edit comment"),
+ _("Please enter comment text"),
+ comment.GetContent(),
+ wx.OK|wx.CANCEL|wx.TE_MULTILINE)
dialog.SetClientSize(wx.Size(400, 200))
if dialog.ShowModal() == wx.ID_OK:
value = dialog.GetValue()
@@ -3091,7 +3254,11 @@
if blocktype is None:
blocktype = "Block"
format = "%s%%d" % blocktype
- return self.Controler.GenerateNewName(self.TagName, None, format, exclude, self.Debug)
+ return self.Controler.GenerateNewName(self.TagName,
+ None,
+ format,
+ exclude=exclude,
+ debug=self.Debug)
def IsNamedElement(self, element):
return isinstance(element, FBD_Block) and element.GetName() != "" or isinstance(element, SFC_Step)
@@ -3178,6 +3345,7 @@
blocks.append((block, (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)))
blocks.sort(sort_blocks)
self.SearchResults.extend([infos for block, infos in blocks])
+ self.CurrentFindHighlight = None
if len(self.SearchResults) > 0:
if self.CurrentFindHighlight is not None:
@@ -3245,15 +3413,27 @@
#-------------------------------------------------------------------------------
def OnScrollWindow(self, event):
- if self.Editor.HasCapture() and self.StartMousePos:
+ if self.Editor.HasCapture() and self.StartMousePos is not None:
return
if wx.Platform == '__WXMSW__':
wx.CallAfter(self.RefreshVisibleElements)
+ self.Editor.Freeze()
+ wx.CallAfter(self.Editor.Thaw)
elif event.GetOrientation() == wx.HORIZONTAL:
self.RefreshVisibleElements(xp = event.GetPosition())
else:
self.RefreshVisibleElements(yp = event.GetPosition())
- event.Skip()
+
+ # Handle scroll in debug to fully redraw area and ensuring
+ # instance path is fully draw without flickering
+ if self.Debug and wx.Platform != '__WXMSW__':
+ x, y = self.GetViewStart()
+ if event.GetOrientation() == wx.HORIZONTAL:
+ self.Scroll(event.GetPosition(), y)
+ else:
+ self.Scroll(x, event.GetPosition())
+ else:
+ event.Skip()
def OnScrollStop(self, event):
self.RefreshScrollBars()
@@ -3276,7 +3456,7 @@
yp = max(0, min(y - rotation * 3, self.Editor.GetVirtualSize()[1] / self.Editor.GetScrollPixelsPerUnit()[1]))
self.RefreshVisibleElements(yp = yp)
self.Scroll(x, yp)
-
+
def OnMoveWindow(self, event):
self.RefreshScrollBars()
self.RefreshVisibleElements()
@@ -3334,21 +3514,28 @@
if not printing:
if self.Debug:
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+
is_action = self.TagName.split("::")[0] == "A"
text = _("Debug: %s") % self.InstancePath
if is_action and self.Value is not None:
text += " ("
- dc.DrawText(text, 2, 2)
+ text_offset_x, text_offset_y = self.CalcUnscrolledPosition(2, 2)
+ dc.DrawText(text, text_offset_x, text_offset_y)
if is_action and self.Value is not None:
value_text = self.VALUE_TRANSLATION[self.Value]
tw, th = dc.GetTextExtent(text)
if self.Value:
dc.SetTextForeground(wx.GREEN)
- dc.DrawText(value_text, tw + 2, 2)
+ dc.DrawText(value_text, text_offset_x + tw, text_offset_y)
if self.Value:
dc.SetTextForeground(wx.BLACK)
vw, vh = dc.GetTextExtent(value_text)
- dc.DrawText(")", tw + vw + 4, 2)
+ dc.DrawText(")", text_offset_x + tw + vw + 2, text_offset_y)
+
+ dc.SetUserScale(scalex, scaley)
+
if self.rubberBand.IsShown():
self.rubberBand.Draw(dc)
dc.EndDrawing()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/DebugDataConsumer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import datetime
+
+#-------------------------------------------------------------------------------
+# Date and Time conversion function
+#-------------------------------------------------------------------------------
+
+SECOND = 1000000 # Number of microseconds in one second
+MINUTE = 60 * SECOND # Number of microseconds in one minute
+HOUR = 60 * MINUTE # Number of microseconds in one hour
+DAY = 24 * HOUR # Number of microseconds in one day
+
+# Date corresponding to Epoch (1970 January the first)
+DATE_ORIGIN = datetime.datetime(1970, 1, 1)
+
+def get_microseconds(value):
+ """
+ Function converting time duration expressed in day, second and microseconds
+ into one expressed in microseconds
+ @param value: Time duration to convert
+ @return: Time duration expressed in microsecond
+ """
+ return float(value.days * DAY + \
+ value.seconds * SECOND + \
+ value.microseconds)
+ return
+
+def generate_time(value):
+ """
+ Function converting time duration expressed in day, second and microseconds
+ into a IEC 61131 TIME literal
+ @param value: Time duration to convert
+ @return: IEC 61131 TIME literal
+ """
+ microseconds = get_microseconds(value)
+
+ # Get absolute microseconds value and save if it was negative
+ negative = microseconds < 0
+ microseconds = abs(microseconds)
+
+ # TIME literal prefix
+ data = "T#"
+ if negative:
+ data += "-"
+
+ # In TIME literal format, it isn't mandatory to indicate null values
+ # if no greater non-null values are available. This variable is used to
+ # inhibit formatting until a non-null value is found
+ not_null = False
+
+ for val, format in [
+ (int(microseconds) / DAY, "%dd"), # Days
+ ((int(microseconds) % DAY) / HOUR, "%dh"), # Hours
+ ((int(microseconds) % HOUR) / MINUTE, "%dm"), # Minutes
+ ((int(microseconds) % MINUTE) / SECOND, "%ds")]: # Seconds
+
+ # Add value to TIME literal if value is non-null or another non-null
+ # value have already be found
+ if val > 0 or not_null:
+ data += format % val
+
+ # Update non-null variable
+ not_null = True
+
+ # In any case microseconds have to be added to TIME literal
+ data += "%gms" % (microseconds % SECOND / 1000.)
+
+ return data
+
+def generate_date(value):
+ """
+ Function converting time duration expressed in day, second and microseconds
+ into a IEC 61131 DATE literal
+ @param value: Time duration to convert
+ @return: IEC 61131 DATE literal
+ """
+ return (DATE_ORIGIN + value).strftime("DATE#%Y-%m-%d")
+
+def generate_datetime(value):
+ """
+ Function converting time duration expressed in day, second and microseconds
+ into a IEC 61131 DATE_AND_TIME literal
+ @param value: Time duration to convert
+ @return: IEC 61131 DATE_AND_TIME literal
+ """
+ return (DATE_ORIGIN + value).strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
+
+def generate_timeofday(value):
+ """
+ Function converting time duration expressed in day, second and microseconds
+ into a IEC 61131 TIME_OF_DAY literal
+ @param value: Time duration to convert
+ @return: IEC 61131 TIME_OF_DAY literal
+ """
+ microseconds = get_microseconds(value)
+
+ # TIME_OF_DAY literal prefix
+ data = "TOD#"
+
+ for val, format in [
+ (int(microseconds) / HOUR, "%2.2d:"), # Hours
+ ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"), # Minutes
+ ((int(microseconds) % MINUTE) / SECOND, "%2.2d."), # Seconds
+ (microseconds % SECOND, "%6.6d")]: # Microseconds
+
+ # Add value to TIME_OF_DAY literal
+ data += format % val
+
+ return data
+
+# Dictionary of translation functions from value send by debugger to IEC
+# literal stored by type
+TYPE_TRANSLATOR = {
+ "TIME": generate_time,
+ "DATE": generate_date,
+ "DT": generate_datetime,
+ "TOD": generate_timeofday,
+ "STRING": lambda v: "'%s'" % v,
+ "WSTRING": lambda v: '"%s"' % v,
+ "REAL": lambda v: "%.6g" % v,
+ "LREAL": lambda v: "%.6g" % v}
+
+#-------------------------------------------------------------------------------
+# Debug Data Consumer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that consumes debug values
+Value update can be inhibited during the time the associated Debug Viewer is
+refreshing
+"""
+
+class DebugDataConsumer:
+
+ def __init__(self):
+ """
+ Constructor
+ """
+ # Debug value and forced flag
+ self.Value = None
+ self.Forced = False
+
+ # Store debug value and forced flag when value update is inhibited
+ self.LastValue = None
+ self.LastForced = False
+
+ # Value IEC data type
+ self.DataType = None
+
+ # Flag that value update is inhibited
+ self.Inhibited = False
+
+ def Inhibit(self, inhibit):
+ """
+ Set flag to inhibit or activate value update
+ @param inhibit: Inhibit flag
+ """
+ # Save inhibit flag
+ self.Inhibited = inhibit
+
+ # When reactivated update value and forced flag with stored values
+ if not inhibit and self.LastValue is not None:
+ self.SetForced(self.LastForced)
+ self.SetValue(self.LastValue)
+
+ # Reset stored values
+ self.LastValue = None
+ self.LastForced = False
+
+ def SetDataType(self, data_type):
+ """
+ Set value IEC data type
+ @param data_type: Value IEC data type
+ """
+ self.DataType = data_type
+
+ def NewValue(self, tick, value, forced=False, raw="BOOL"):
+ """
+ Function called by debug thread when a new debug value is available
+ @param tick: PLC tick when value was captured
+ @param value: Value captured
+ @param forced: Forced flag, True if value is forced (default: False)
+ @param raw: Data type of values not translated (default: 'BOOL')
+ """
+ # Translate value to IEC literal
+ if self.DataType != raw:
+ value = TYPE_TRANSLATOR.get(self.DataType, str)(value)
+
+ # Store value and forced flag when value update is inhibited
+ if self.Inhibited:
+ self.LastValue = value
+ self.LastForced = forced
+
+ # Update value and forced flag in any other case
+ else:
+ self.SetForced(forced)
+ self.SetValue(value)
+
+ def SetValue(self, value):
+ """
+ Update value.
+ May be overridden by inherited classes
+ @param value: New value
+ """
+ self.Value = value
+
+ def GetValue(self):
+ """
+ Return current value
+ @return: Current value
+ """
+ return self.Value
+
+ def SetForced(self, forced):
+ """
+ Update Forced flag.
+ May be overridden by inherited classes
+ @param forced: New forced flag
+ """
+ self.Forced = forced
+
+ def IsForced(self):
+ """
+ Indicate if current value is forced
+ @return: Current forced flag
+ """
+ return self.Forced
--- a/graphics/FBD_Objects.py Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/FBD_Objects.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,7 @@
import wx
-from GraphicCommons import *
+from graphics.GraphicCommons import *
from plcopen.structures import *
#-------------------------------------------------------------------------------
@@ -179,17 +179,20 @@
output.MoveConnected(exclude)
# Returns the block connector that starts with the point given if it exists
- def GetConnector(self, position, name = None):
- # if a name is given
- if name is not None:
- # Test each input and output connector
- #for input in self.Inputs:
- # if name == input.GetName():
- # return input
+ def GetConnector(self, position, output_name = None, input_name = None):
+ if input_name is not None:
+ # Test each input connector
+ for input in self.Inputs:
+ if input_name == input.GetName():
+ return input
+ if output_name is not None:
+ # Test each output connector
for output in self.Outputs:
- if name == output.GetName():
+ if output_name == output.GetName():
return output
- return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+ if input_name is None and output_name is None:
+ return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+ return None
def GetInputTypes(self):
return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
@@ -263,43 +266,49 @@
self.Pen = MiterPen(self.Colour)
# Extract the inputs properties and create or modify the corresponding connector
- idx = 0
- for idx, (input_name, input_type, input_modifier) in enumerate(inputs):
- if idx < len(self.Inputs):
- connector = self.Inputs[idx]
- connector.SetName(input_name)
- connector.SetType(input_type)
- else:
- connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
- self.Inputs.append(connector)
+ input_connectors = []
+ for input_name, input_type, input_modifier in inputs:
+ connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
if input_modifier == "negated":
connector.SetNegated(True)
elif input_modifier != "none":
connector.SetEdge(input_modifier)
- for i in xrange(idx + 1, len(self.Inputs)):
- self.Inputs[i].UnConnect(delete = True)
- self.Inputs = self.Inputs[:idx + 1]
+ for input in self.Inputs:
+ if input.GetName() == input_name:
+ wires = input.GetWires()[:]
+ input.UnConnect()
+ for wire in wires:
+ connector.Connect(wire)
+ break
+ input_connectors.append(connector)
+ for input in self.Inputs:
+ input.UnConnect(delete = True)
+ self.Inputs = input_connectors
# Extract the outputs properties and create or modify the corresponding connector
- idx = 0
- for idx, (output_name, output_type, output_modifier) in enumerate(outputs):
- if idx < len(self.Outputs):
- connector = self.Outputs[idx]
- connector.SetName(output_name)
- connector.SetType(output_type)
- else:
- connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
- self.Outputs.append(connector)
+ output_connectors = []
+ for output_name, output_type, output_modifier in outputs:
+ connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
if output_modifier == "negated":
connector.SetNegated(True)
elif output_modifier != "none":
connector.SetEdge(output_modifier)
- for i in xrange(idx + 1, len(self.Outputs)):
- self.Outputs[i].UnConnect(delete = True)
- self.Outputs = self.Outputs[:idx + 1]
+ for output in self.Outputs:
+ if output.GetName() == output_name:
+ wires = output.GetWires()[:]
+ output.UnConnect()
+ for wire in wires:
+ connector.Connect(wire)
+ break
+ output_connectors.append(connector)
+ for output in self.Outputs:
+ output.UnConnect(delete = True)
+ self.Outputs = output_connectors
self.RefreshMinSize()
self.RefreshConnectors()
+ for output in self.Outputs:
+ output.RefreshWires()
self.RefreshBoundingBox()
# Returns the block type
--- a/graphics/GraphicCommons.py Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/GraphicCommons.py Wed Jul 31 10:45:07 2013 +0900
@@ -23,12 +23,14 @@
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import wx
-from time import time as gettime
from math import *
from types import *
import datetime
from threading import Lock,Timer
+from graphics.ToolTipProducer import ToolTipProducer
+from graphics.DebugDataConsumer import DebugDataConsumer
+
#-------------------------------------------------------------------------------
# Common constants
#-------------------------------------------------------------------------------
@@ -102,9 +104,6 @@
# Define highlight refresh inhibition period in second
REFRESH_HIGHLIGHT_PERIOD = 0.1
-# Define tooltip wait for displaying period in second
-TOOLTIP_WAIT_PERIOD = 0.5
-
HANDLE_CURSORS = {
(1, 1) : 2,
(3, 3) : 2,
@@ -187,56 +186,6 @@
return dir_target
return v_base
-SECOND = 1000000
-MINUTE = 60 * SECOND
-HOUR = 60 * MINUTE
-DAY = 24 * HOUR
-
-def generate_time(value):
- microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
- negative = microseconds < 0
- microseconds = abs(microseconds)
- data = "T#"
- not_null = False
- if negative:
- data += "-"
- for val, format in [(int(microseconds) / DAY, "%dd"),
- ((int(microseconds) % DAY) / HOUR, "%dh"),
- ((int(microseconds) % HOUR) / MINUTE, "%dm"),
- ((int(microseconds) % MINUTE) / SECOND, "%ds")]:
- if val > 0 or not_null:
- data += format % val
- not_null = True
- data += "%gms" % (microseconds % SECOND / 1000.)
- return data
-
-def generate_date(value):
- base_date = datetime.datetime(1970, 1, 1)
- date = base_date + value
- return date.strftime("DATE#%Y-%m-%d")
-
-def generate_datetime(value):
- base_date = datetime.datetime(1970, 1, 1)
- date_time = base_date + value
- return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
-
-def generate_timeofday(value):
- microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
- negative = microseconds < 0
- microseconds = abs(microseconds)
- data = "TOD#"
- for val, format in [(int(microseconds) / HOUR, "%2.2d:"),
- ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),
- ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),
- (microseconds % SECOND, "%6.6d")]:
- data += format % val
- return data
-
-TYPE_TRANSLATOR = {"TIME": generate_time,
- "DATE": generate_date,
- "DT": generate_datetime,
- "TOD": generate_timeofday}
-
def MiterPen(colour, width=1, style=wx.SOLID):
pen = wx.Pen(colour, width, style)
pen.SetJoin(wx.JOIN_MITER)
@@ -244,413 +193,6 @@
return pen
#-------------------------------------------------------------------------------
-# Debug Data Consumer Class
-#-------------------------------------------------------------------------------
-
-class DebugDataConsumer:
-
- def __init__(self):
- self.LastValue = None
- self.Value = None
- self.DataType = None
- self.LastForced = False
- self.Forced = False
- self.Inhibited = False
-
- def Inhibit(self, inhibit):
- self.Inhibited = inhibit
- if not inhibit and self.LastValue is not None:
- self.SetForced(self.LastForced)
- self.SetValue(self.LastValue)
- self.LastValue = None
-
- def SetDataType(self, data_type):
- self.DataType = data_type
-
- def NewValue(self, tick, value, forced=False):
- value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value)
- if self.Inhibited:
- self.LastValue = value
- self.LastForced = forced
- else:
- self.SetForced(forced)
- self.SetValue(value)
-
- def SetValue(self, value):
- self.Value = value
-
- def GetValue(self):
- return self.Value
-
- def SetForced(self, forced):
- self.Forced = forced
-
- def IsForced(self):
- return self.Forced
-
-#-------------------------------------------------------------------------------
-# Debug Viewer Class
-#-------------------------------------------------------------------------------
-
-REFRESH_PERIOD = 0.1
-DEBUG_REFRESH_LOCK = Lock()
-
-class DebugViewer:
-
- def __init__(self, producer, debug, register_tick=True):
- self.DataProducer = None
- self.Debug = debug
- self.RegisterTick = register_tick
- self.Inhibited = False
-
- self.DataConsumers = {}
-
- self.LastRefreshTime = gettime()
- self.HasAcquiredLock = False
- self.AccessLock = Lock()
- self.TimerAccessLock = Lock()
-
- self.LastRefreshTimer = None
-
- self.SetDataProducer(producer)
-
- def __del__(self):
- self.DataProducer = None
- self.DeleteDataConsumers()
- if self.LastRefreshTimer is not None:
- self.LastRefreshTimer.Stop()
- if self.HasAcquiredLock:
- DEBUG_REFRESH_LOCK.release()
-
- def SetDataProducer(self, producer):
- if self.RegisterTick and self.Debug:
- if producer is not None:
- producer.SubscribeDebugIECVariable("__tick__", self)
- if self.DataProducer is not None:
- self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
- self.DataProducer = producer
-
- def IsDebugging(self):
- return self.Debug
-
- def Inhibit(self, inhibit):
- for consumer, iec_path in self.DataConsumers.iteritems():
- consumer.Inhibit(inhibit)
- self.Inhibited = inhibit
-
- def AddDataConsumer(self, iec_path, consumer):
- if self.DataProducer is None:
- return None
- result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer)
- if result is not None and consumer != self:
- self.DataConsumers[consumer] = iec_path
- consumer.SetDataType(self.GetDataType(iec_path))
- return result
-
- def RemoveDataConsumer(self, consumer):
- iec_path = self.DataConsumers.pop(consumer, None)
- if iec_path is not None:
- self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
-
- def RegisterVariables(self):
- pass
-
- def GetDataType(self, iec_path):
- if self.DataProducer is not None:
- infos = self.DataProducer.GetInstanceInfos(iec_path)
- if infos is not None:
- return infos["type"]
- return self.DataProducer.GetDebugIECVariableType(iec_path.upper())
- return None
-
- def IsNumType(self, data_type):
- return self.DataProducer.IsNumType(data_type)
-
- def ForceDataValue(self, iec_path, value):
- if self.DataProducer is not None:
- self.DataProducer.ForceDebugIECVariable(iec_path, value)
-
- def ReleaseDataValue(self, iec_path):
- if self.DataProducer is not None:
- self.DataProducer.ReleaseDebugIECVariable(iec_path)
-
- def DeleteDataConsumers(self):
- if self.DataProducer is not None:
- for consumer, iec_path in self.DataConsumers.iteritems():
- self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
- self.DataConsumers = {}
-
- def ShouldRefresh(self):
- if self:
- wx.CallAfter(self._ShouldRefresh)
-
- def _ShouldRefresh(self):
- if self:
- if DEBUG_REFRESH_LOCK.acquire(False):
- self.AccessLock.acquire()
- self.HasAcquiredLock = True
- self.AccessLock.release()
- self.RefreshNewData()
- else:
- self.TimerAccessLock.acquire()
- self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh)
- self.LastRefreshTimer.start()
- self.TimerAccessLock.release()
-
- def NewDataAvailable(self, tick, *args, **kwargs):
- self.TimerAccessLock.acquire()
- if self.LastRefreshTimer is not None:
- self.LastRefreshTimer.cancel()
- self.LastRefreshTimer=None
- self.TimerAccessLock.release()
- if self.IsShown() and not self.Inhibited:
- if gettime() - self.LastRefreshTime > REFRESH_PERIOD and DEBUG_REFRESH_LOCK.acquire(False):
- self.AccessLock.acquire()
- self.HasAcquiredLock = True
- self.AccessLock.release()
- self.LastRefreshTime = gettime()
- self.Inhibit(True)
- wx.CallAfter(self.RefreshViewOnNewData, *args, **kwargs)
- else:
- self.TimerAccessLock.acquire()
- self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh)
- self.LastRefreshTimer.start()
- self.TimerAccessLock.release()
- elif not self.IsShown() and self.HasAcquiredLock:
- DebugViewer.RefreshNewData(self)
-
- def RefreshViewOnNewData(self, *args, **kwargs):
- if self:
- self.RefreshNewData(*args, **kwargs)
-
- def RefreshNewData(self, *args, **kwargs):
- self.Inhibit(False)
- self.AccessLock.acquire()
- if self.HasAcquiredLock:
- DEBUG_REFRESH_LOCK.release()
- self.HasAcquiredLock = False
- if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
- self.LastRefreshTime = gettime()
- self.AccessLock.release()
-
-#-------------------------------------------------------------------------------
-# Viewer Rubberband
-#-------------------------------------------------------------------------------
-
-"""
-Class that implements a rubberband
-"""
-
-class RubberBand:
-
- # Create a rubberband by indicated on which window it must be drawn
- def __init__(self, viewer):
- self.Viewer = viewer
- self.drawingSurface = viewer.Editor
- self.Reset()
-
- # Method that initializes the internal attributes of the rubberband
- def Reset(self):
- self.startPoint = None
- self.currentBox = None
- self.lastBox = None
-
- # Method that return if a box is currently edited
- def IsShown(self):
- return self.currentBox != None
-
- # Method that returns the currently edited box
- def GetCurrentExtent(self):
- if self.currentBox is None:
- return self.lastBox
- return self.currentBox
-
- # Method called when a new box starts to be edited
- def OnLeftDown(self, event, dc, scaling):
- pos = GetScaledEventPosition(event, dc, scaling)
- # Save the point for calculate the box position and size
- self.startPoint = pos
- self.currentBox = wx.Rect(pos.x, pos.y, 0, 0)
- self.drawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
- self.Redraw()
-
- # Method called when dragging with a box edited
- def OnMotion(self, event, dc, scaling):
- pos = GetScaledEventPosition(event, dc, scaling)
- # Save the last position and size of the box for erasing it
- self.lastBox = wx.Rect(self.currentBox.x, self.currentBox.y, self.currentBox.width,
- self.currentBox.height)
- # Calculate new position and size of the box
- if pos.x >= self.startPoint.x:
- self.currentBox.x = self.startPoint.x
- self.currentBox.width = pos.x - self.startPoint.x + 1
- else:
- self.currentBox.x = pos.x
- self.currentBox.width = self.startPoint.x - pos.x + 1
- if pos.y >= self.startPoint.y:
- self.currentBox.y = self.startPoint.y
- self.currentBox.height = pos.y - self.startPoint.y + 1
- else:
- self.currentBox.y = pos.y
- self.currentBox.height = self.startPoint.y - pos.y + 1
- self.Redraw()
-
- # Method called when dragging is stopped
- def OnLeftUp(self, event, dc, scaling):
- self.drawingSurface.SetCursor(wx.NullCursor)
- self.lastBox = self.currentBox
- self.currentBox = None
- self.Redraw()
-
- # Method that erase the last box and draw the new box
- def Redraw(self, dc = None):
- if dc is None:
- dc = self.Viewer.GetLogicalDC()
- scalex, scaley = dc.GetUserScale()
- dc.SetUserScale(1, 1)
- dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
- dc.SetBrush(wx.TRANSPARENT_BRUSH)
- dc.SetLogicalFunction(wx.XOR)
- if self.lastBox:
- # Erase last box
- dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
- self.lastBox.width * scalex, self.lastBox.height * scaley)
- if self.currentBox:
- # Draw current box
- dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
- self.currentBox.width * scalex, self.currentBox.height * scaley)
- dc.SetUserScale(scalex, scaley)
-
- # Erase last box
- def Erase(self, dc = None):
- if dc is None:
- dc = self.Viewer.GetLogicalDC()
- scalex, scaley = dc.GetUserScale()
- dc.SetUserScale(1, 1)
- dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
- dc.SetBrush(wx.TRANSPARENT_BRUSH)
- dc.SetLogicalFunction(wx.XOR)
- if self.lastBox:
- dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
- self.lastBox.width * scalex, self.lastBox.height * scalex)
- dc.SetUserScale(scalex, scaley)
-
- # Draw current box
- def Draw(self, dc = None):
- if dc is None:
- dc = self.Viewer.GetLogicalDC()
- scalex, scaley = dc.GetUserScale()
- dc.SetUserScale(1, 1)
- dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
- dc.SetBrush(wx.TRANSPARENT_BRUSH)
- dc.SetLogicalFunction(wx.XOR)
- if self.currentBox:
- # Draw current box
- dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
- self.currentBox.width * scalex, self.currentBox.height * scaley)
- dc.SetUserScale(scalex, scaley)
-
-#-------------------------------------------------------------------------------
-# Viewer ToolTip
-#-------------------------------------------------------------------------------
-
-"""
-Class that implements a custom tool tip
-"""
-
-if wx.Platform == '__WXMSW__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Courier New',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 10,
- }
-else:
- faces = { 'times': 'Times',
- 'mono' : 'Courier',
- 'helv' : 'Helvetica',
- 'other': 'new century schoolbook',
- 'size' : 12,
- }
-
-TOOLTIP_MAX_CHARACTERS = 30
-TOOLTIP_MAX_LINE = 5
-
-class ToolTip(wx.PopupWindow):
-
- def __init__(self, parent, tip):
- wx.PopupWindow.__init__(self, parent)
-
- self.CurrentPosition = wx.Point(0, 0)
-
- self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
- self.SetTip(tip)
-
- self.Bind(wx.EVT_PAINT, self.OnPaint)
-
- def SetTip(self, tip):
- lines = []
- for line in tip.splitlines():
- if line != "":
- words = line.split()
- new_line = words[0]
- for word in words[1:]:
- if len(new_line + " " + word) <= TOOLTIP_MAX_CHARACTERS:
- new_line += " " + word
- else:
- lines.append(new_line)
- new_line = word
- lines.append(new_line)
- else:
- lines.append(line)
- if len(lines) > TOOLTIP_MAX_LINE:
- self.Tip = lines[:TOOLTIP_MAX_LINE]
- if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
- self.Tip[-1] += "..."
- else:
- self.Tip[-1] = self.Tip[-1][:TOOLTIP_MAX_CHARACTERS - 3] + "..."
- else:
- self.Tip = lines
- wx.CallAfter(self.RefreshTip)
-
- def MoveToolTip(self, pos):
- self.CurrentPosition = pos
- self.SetPosition(pos)
-
- def GetTipExtent(self):
- max_width = 0
- max_height = 0
- for line in self.Tip:
- w, h = self.GetTextExtent(line)
- max_width = max(max_width, w)
- max_height += h
- return max_width, max_height
-
- def RefreshTip(self):
- if self:
- w, h = self.GetTipExtent()
- self.SetSize(wx.Size(w + 4, h + 4))
- self.SetPosition(self.CurrentPosition)
- self.Refresh()
-
- def OnPaint(self, event):
- dc = wx.AutoBufferedPaintDC(self)
- dc.Clear()
- dc.SetPen(MiterPen(wx.BLACK))
- dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
- dc.SetFont(wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"]))
- dc.BeginDrawing()
- w, h = self.GetTipExtent()
- dc.DrawRectangle(0, 0, w + 4, h + 4)
- offset = 0
- for line in self.Tip:
- dc.DrawText(line, 2, offset + 2)
- w, h = dc.GetTextExtent(line)
- offset += h
- dc.EndDrawing()
- event.Skip()
-
-#-------------------------------------------------------------------------------
# Helpers for highlighting text
#-------------------------------------------------------------------------------
@@ -691,10 +233,11 @@
Class that implements a generic graphic element
"""
-class Graphic_Element:
+class Graphic_Element(ToolTipProducer):
# Create a new graphic element
def __init__(self, parent, id = None):
+ ToolTipProducer.__init__(self, parent)
self.Parent = parent
self.Id = id
self.oldPos = None
@@ -708,13 +251,6 @@
self.Size = wx.Size(0, 0)
self.BoundingBox = wx.Rect(0, 0, 0, 0)
self.Visible = False
- self.ToolTip = None
- self.ToolTipPos = None
- self.ToolTipTimer = wx.Timer(self.Parent, -1)
- self.Parent.Bind(wx.EVT_TIMER, self.OnToolTipTimer, self.ToolTipTimer)
-
- def __del__(self):
- self.ToolTipTimer.Stop()
def GetDefinition(self):
return [self.Id], []
@@ -968,7 +504,7 @@
# If the cursor is dragging and the element have been clicked
if event.Dragging() and self.oldPos:
# Calculate the movement of cursor
- pos = event.GetLogicalPosition(dc)
+ pos = GetScaledEventPosition(event, dc, scaling)
movex = pos.x - self.oldPos.x
movey = pos.y - self.oldPos.y
# If movement is greater than MIN_MOVE then a dragging is initiated
@@ -1087,36 +623,6 @@
return movex, movey
return 0, 0
- def OnToolTipTimer(self, event):
- value = self.GetToolTipValue()
- if value is not None and self.ToolTipPos is not None:
- self.ToolTip = ToolTip(self.Parent, value)
- self.ToolTip.MoveToolTip(self.ToolTipPos)
- self.ToolTip.Show()
-
- def GetToolTipValue(self):
- return None
-
- def CreateToolTip(self, pos):
- value = self.GetToolTipValue()
- if value is not None:
- self.ToolTipPos = pos
- self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
-
- def MoveToolTip(self, pos):
- if self.ToolTip is not None:
- self.ToolTip.MoveToolTip(pos)
- elif self.ToolTipPos is not None:
- self.ToolTipPos = pos
- self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
-
- def ClearToolTip(self):
- self.ToolTipTimer.Stop()
- self.ToolTipPos = None
- if self.ToolTip is not None:
- self.ToolTip.Destroy()
- self.ToolTip = None
-
# Override this method for defining the method to call for adding an highlight to this element
def AddHighlight(self, infos, start, end, highlight_type):
pass
@@ -1347,9 +853,12 @@
if movex != 0 or movey != 0:
element.Move(movex, movey)
element.RefreshModel()
- self.RefreshWireExclusion()
self.RefreshBoundingBox()
+ # Add the given element to the group of elements
+ def AddElement(self, element):
+ self.Elements.append(element)
+
# Remove or select the given element if it is or not in the group
def SelectElement(self, element):
if element in self.Elements:
@@ -1480,10 +989,14 @@
self.Parent.PopupGroupMenu()
# Refreshes the model of all the elements of this group
- def RefreshModel(self):
+ def RefreshModel(self, move=True):
for element in self.Elements:
- element.RefreshModel()
-
+ element.RefreshModel(move)
+
+ # Draws the handles of this element if it is selected
+ def Draw(self, dc):
+ for element in self.Elements:
+ element.Draw(dc)
#-------------------------------------------------------------------------------
# Connector for all types of blocks
@@ -1493,10 +1006,12 @@
Class that implements a connector for any type of block
"""
-class Connector:
+class Connector(DebugDataConsumer, ToolTipProducer):
# Create a new connector
def __init__(self, parent, name, type, position, direction, negated = False, edge = "none", onlyone = False):
+ DebugDataConsumer.__init__(self)
+ ToolTipProducer.__init__(self, parent.Parent)
self.ParentBlock = parent
self.Name = name
self.Type = type
@@ -1513,6 +1028,8 @@
self.Valid = True
self.Value = None
self.Forced = False
+ self.ValueSize = None
+ self.ComputedValue = None
self.Selected = False
self.Highlights = []
self.RefreshNameSize()
@@ -1536,7 +1053,18 @@
height = 5
else:
height = CONNECTOR_SIZE
- return wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
+ rect = wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
+ if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+ self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
+ if self.ValueSize is not None:
+ width, height = self.ValueSize
+ rect = rect.Union(wx.Rect(
+ parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] + \
+ width * (self.Direction[0] - 1) / 2,
+ parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] + \
+ height * (self.Direction[1] - 1),
+ width, height))
+ return rect
# Change the connector selection
def SetSelected(self, selected):
@@ -1600,6 +1128,33 @@
self.Name = name
self.RefreshNameSize()
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ def GetComputedValue(self):
+ if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
+ return self.Value
+ return None
+
+ def GetToolTipValue(self):
+ return self.GetComputedValue()
+
+ def SetValue(self, value):
+ if self.Value != value:
+ self.Value = value
+ computed_value = self.GetComputedValue()
+ if computed_value is not None:
+ self.ComputedValue = computed_value
+ self.SetToolTipText(self.ComputedValue)
+ if len(self.ComputedValue) > 4:
+ self.ComputedValue = self.ComputedValue[:4] + "..."
+ self.ValueSize = None
+ if self.ParentBlock.Visible:
+ self.ParentBlock.Parent.ElementNeedRefresh(self)
+
def RefreshForced(self):
self.Forced = False
for wire, handle in self.Wires:
@@ -1682,6 +1237,10 @@
def InsertConnect(self, idx, wire, refresh = True):
if wire not in self.Wires:
self.Wires.insert(idx, wire)
+ if wire[1] == 0:
+ wire[0].ConnectStartPoint(None, self)
+ else:
+ wire[0].ConnectEndPoint(None, self)
if refresh:
self.ParentBlock.RefreshModel(False)
@@ -1935,6 +1494,21 @@
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
+ if self.Value is not None and not isinstance(self.Value, BooleanType) and self.Value != "undefined":
+ dc.SetFont(self.ParentBlock.Parent.GetMiniFont())
+ dc.SetTextForeground(wx.NamedColour("purple"))
+ if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+ self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
+ if self.ValueSize is not None:
+ width, height = self.ValueSize
+ dc.DrawText(self.ComputedValue,
+ parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] + \
+ width * (self.Direction[0] - 1) / 2,
+ parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] + \
+ height * (self.Direction[1] - 1))
+ dc.SetFont(self.ParentBlock.Parent.GetFont())
+ dc.SetTextForeground(wx.BLACK)
+
#-------------------------------------------------------------------------------
# Common Wire Element
#-------------------------------------------------------------------------------
@@ -1980,17 +1554,6 @@
self.StartConnected = None
self.EndConnected = None
- def GetToolTipValue(self):
- if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
- wire_type = self.GetEndConnectedType()
- if wire_type == "STRING":
- return "'%s'"%self.Value
- elif wire_type == "WSTRING":
- return "\"%s\""%self.Value
- else:
- return str(self.Value)
- return None
-
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = Graphic_Element.GetRedrawRect(self, movex, movey)
@@ -2022,7 +1585,6 @@
def Clone(self, parent, connectors = {}, dx = 0, dy = 0):
start_connector = connectors.get(self.StartConnected, None)
end_connector = connectors.get(self.EndConnected, None)
- print self.StartConnected, "=>", start_connector, ",", self.EndConnected, "=>", end_connector
if start_connector is not None and end_connector is not None:
wire = Wire(parent)
wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
@@ -2149,19 +1711,21 @@
if self.Visible:
self.Parent.ElementNeedRefresh(self)
+ def GetComputedValue(self):
+ if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
+ return self.Value
+ return None
+
+ def GetToolTipValue(self):
+ return self.GetComputedValue()
+
def SetValue(self, value):
if self.Value != value:
self.Value = value
- if value is not None and not isinstance(value, BooleanType):
- wire_type = self.GetEndConnectedType()
- if wire_type == "STRING":
- self.ComputedValue = "'%s'"%value
- elif wire_type == "WSTRING":
- self.ComputedValue = "\"%s\""%value
- else:
- self.ComputedValue = str(value)
- if self.ToolTip is not None:
- self.ToolTip.SetTip(self.ComputedValue)
+ computed_value = self.GetComputedValue()
+ if computed_value is not None:
+ self.ComputedValue = computed_value
+ self.SetToolTipText(self.ComputedValue)
if len(self.ComputedValue) > 4:
self.ComputedValue = self.ComputedValue[:4] + "..."
self.ValueSize = None
--- a/graphics/LD_Objects.py Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/LD_Objects.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,8 @@
import wx
-from GraphicCommons import *
+from graphics.GraphicCommons import *
+from graphics.DebugDataConsumer import DebugDataConsumer
from plcopen.structures import *
#-------------------------------------------------------------------------------
@@ -119,8 +120,10 @@
self.RefreshBoundingBox()
# Returns the block minimum size
- def GetMinSize(self):
- return LD_POWERRAIL_WIDTH, self.Extensions[0] + self.Extensions[1]
+ def GetMinSize(self, default=False):
+ height = (LD_LINE_SIZE * (len(self.Connectors) - 1)
+ if default else 0)
+ return LD_POWERRAIL_WIDTH, height + self.Extensions[0] + self.Extensions[1]
# Add a connector or a blank to this power rail at the last place
def AddConnector(self):
@@ -278,7 +281,7 @@
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
handle_type, handle = self.Handle
- if handle_type == HANDLE_CONNECTOR:
+ if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
wires = handle.GetWires()
if len(wires) == 1:
if handle == wires[0][0].StartConnected:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/RubberBand.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics.GraphicCommons import GetScaledEventPosition
+
+#-------------------------------------------------------------------------------
+# Viewer RubberBand
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a rubberband for graphic Viewers
+"""
+
+class RubberBand:
+
+ def __init__(self, viewer):
+ """
+ Constructor
+ @param viewer: Viewer on which rubberband must be drawn
+ """
+ self.Viewer = viewer
+
+ # wx.Panel on which rubberband will be drawn
+ self.DrawingSurface = viewer.Editor
+
+ self.Reset()
+
+ def Reset(self):
+ """
+ Initialize internal attributes of rubberband
+ """
+ self.StartPoint = None
+ self.CurrentBBox = None
+ self.LastBBox = None
+
+ def IsShown(self):
+ """
+ Indicate if rubberband is drawn on viewer
+ @return: True if rubberband is drawn
+ """
+ return self.CurrentBBox != None
+
+ def GetCurrentExtent(self):
+ """
+ Return the rubberband bounding box
+ @return: Rubberband bounding box (wx.Rect object)
+ """
+ # In case of rubberband not shown, return the last rubberband
+ # bounding box
+ if self.IsShown():
+ return self.CurrentBBox
+ return self.LastBBox
+
+ def OnLeftDown(self, event, dc, scaling):
+ """
+ Called when left mouse is pressed on Viewer. Starts to edit a new
+ rubberband bounding box
+ @param event: Mouse event
+ @param dc: Device Context of Viewer
+ @param scaling: PLCOpen scaling applied on Viewer
+ """
+ # Save the point where mouse was pressed in Viewer unit, position may
+ # be modified by scroll and zoom applied on viewer
+ self.StartPoint = GetScaledEventPosition(event, dc, scaling)
+
+ # Initialize rubberband bounding box
+ self.CurrentBBox = wx.Rect(self.StartPoint.x, self.StartPoint.y, 0, 0)
+
+ # Change viewer mouse cursor to reflect a rubberband bounding box is
+ # edited
+ self.DrawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
+
+ self.Redraw()
+
+ def OnMotion(self, event, dc, scaling):
+ """
+ Called when mouse is dragging over Viewer. Update the current edited
+ rubberband bounding box
+ @param event: Mouse event
+ @param dc: Device Context of Viewer
+ @param scaling: PLCOpen scaling applied on Viewer
+ """
+ # Get mouse position in Viewer unit, position may be modified by scroll
+ # and zoom applied on viewer
+ pos = GetScaledEventPosition(event, dc, scaling)
+
+ # Save the last bounding box drawn for erasing it later
+ self.LastBBox = wx.Rect(0, 0, 0, 0)
+ self.LastBBox.Union(self.CurrentBBox)
+
+ # Calculate new position and size of the box
+ self.CurrentBBox.x = min(pos.x, self.StartPoint.x)
+ self.CurrentBBox.y = min(pos.y, self.StartPoint.y)
+ self.CurrentBBox.width = abs(pos.x - self.StartPoint.x) + 1
+ self.CurrentBBox.height = abs(pos.y - self.StartPoint.y) + 1
+
+ self.Redraw()
+
+ def OnLeftUp(self, event, dc, scaling):
+ """
+ Called when mouse is release from Viewer. Erase the current edited
+ rubberband bounding box
+ @param event: Mouse event
+ @param dc: Device Context of Viewer
+ @param scaling: PLCOpen scaling applied on Viewer
+ """
+ # Change viewer mouse cursor to default
+ self.DrawingSurface.SetCursor(wx.NullCursor)
+
+ # Save the last edited bounding box
+ self.LastBBox = self.CurrentBBox
+ self.CurrentBBox = None
+
+ self.Redraw()
+
+ def DrawBoundingBoxes(self, bboxes, dc=None):
+ """
+ Draw a list of bounding box on Viewer in the order given using XOR
+ logical function
+ @param bboxes: List of bounding boxes to draw on viewer
+ @param dc: Device Context of Viewer (default None)
+ """
+ # Get viewer Device Context if not given
+ if dc is None:
+ dc = self.Viewer.GetLogicalDC()
+
+ # Save current viewer scale factors before resetting them in order to
+ # avoid rubberband pen to be scaled
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+
+ # Set DC drawing style
+ dc.SetPen(wx.Pen(wx.WHITE, style=wx.DOT))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.XOR)
+
+ # Draw the bounding boxes using viewer scale factor
+ for bbox in bboxes:
+ if bbox is not None:
+ dc.DrawRectangle(
+ bbox.x * scalex, bbox.y * scaley,
+ bbox.width * scalex, bbox.height * scaley)
+
+ dc.SetLogicalFunction(wx.COPY)
+
+ # Restore Viewer scale factor
+ dc.SetUserScale(scalex, scaley)
+
+ def Redraw(self, dc = None):
+ """
+ Redraw rubberband on Viewer
+ @param dc: Device Context of Viewer (default None)
+ """
+ # Erase last bbox and draw current bbox
+ self.DrawBoundingBoxes([self.LastBBox, self.CurrentBBox], dc)
+
+ def Erase(self, dc = None):
+ """
+ Erase rubberband from Viewer
+ @param dc: Device Context of Viewer (default None)
+ """
+ # Erase last bbox
+ self.DrawBoundingBoxes([self.LastBBox], dc)
+
+ def Draw(self, dc = None):
+ """
+ Draw rubberband on Viewer
+ @param dc: Device Context of Viewer (default None)
+ """
+ # Erase last bbox and draw current bbox
+ self.DrawBoundingBoxes([self.CurrentBBox], dc)
--- a/graphics/SFC_Objects.py Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/SFC_Objects.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,8 @@
import wx
-from GraphicCommons import *
+from graphics.GraphicCommons import *
+from graphics.DebugDataConsumer import DebugDataConsumer
from plcopen.structures import *
def GetWireSize(block):
@@ -1359,11 +1360,25 @@
# Method called when a LeftDown event have been generated
def OnLeftDown(self, event, dc, scaling):
- connector = None
- if event.ControlDown():
- pos = GetScaledEventPosition(event, dc, scaling)
- # Test if a connector have been handled
- connector = self.TestConnector(pos, exclude=False)
+ self.RealConnectors = {"Inputs":[],"Outputs":[]}
+ for input in self.Inputs:
+ position = input.GetRelPosition()
+ self.RealConnectors["Inputs"].append(float(position.x)/float(self.Size[0]))
+ for output in self.Outputs:
+ position = output.GetRelPosition()
+ self.RealConnectors["Outputs"].append(float(position.x)/float(self.Size[0]))
+ Graphic_Element.OnLeftDown(self, event, dc, scaling)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, dc, scaling):
+ Graphic_Element.OnLeftUp(self, event, dc, scaling)
+ self.RealConnectors = None
+
+ # Method called when a RightDown event have been generated
+ def OnRightDown(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Test if a connector have been handled
+ connector = self.TestConnector(pos, exclude=False)
if connector:
self.Handle = (HANDLE_CONNECTOR, connector)
wx.CallAfter(self.Parent.SetCurrentCursor, 1)
@@ -1371,17 +1386,11 @@
# Initializes the last position
self.oldPos = GetScaledEventPosition(event, dc, scaling)
else:
- self.RealConnectors = {"Inputs":[],"Outputs":[]}
- for input in self.Inputs:
- position = input.GetRelPosition()
- self.RealConnectors["Inputs"].append(float(position.x)/float(self.Size[0]))
- for output in self.Outputs:
- position = output.GetRelPosition()
- self.RealConnectors["Outputs"].append(float(position.x)/float(self.Size[0]))
- Graphic_Element.OnLeftDown(self, event, dc, scaling)
-
- # Method called when a LeftUp event have been generated
- def OnLeftUp(self, event, dc, scaling):
+ Graphic_Element.OnRightDown(self, event, dc, scaling)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
handle_type, handle = self.Handle
if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
wires = handle.GetWires()
@@ -1393,21 +1402,17 @@
block.RefreshInputModel()
else:
block.RefreshOutputModel()
- Graphic_Element.OnLeftUp(self, event, dc, scaling)
- self.RealConnectors = None
-
- # Method called when a RightUp event have been generated
- def OnRightUp(self, event, dc, scaling):
- pos = GetScaledEventPosition(event, dc, scaling)
- # Popup the menu with special items for a block and a connector if one is handled
- connector = self.TestConnector(pos, exclude=False)
- if connector:
- self.Handle = (HANDLE_CONNECTOR, connector)
- self.Parent.PopupDivergenceMenu(True)
- else:
- # Popup the divergence menu without delete branch
- self.Parent.PopupDivergenceMenu(False)
-
+ Graphic_Element.OnRightUp(self, event, dc, scaling)
+ else:
+ # Popup the menu with special items for a block and a connector if one is handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ self.Parent.PopupDivergenceMenu(True)
+ else:
+ # Popup the divergence menu without delete branch
+ self.Parent.PopupDivergenceMenu(False)
+
# Refreshes the divergence state according to move defined and handle selected
def ProcessDragging(self, movex, movey, event, scaling):
handle_type, handle = self.Handle
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/ToolTipProducer.py Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
+
+#-------------------------------------------------------------------------------
+# Tool Tip Producer class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that generate Tool Tip
+"""
+
+class ToolTipProducer:
+
+ def __init__(self, parent):
+ """
+ Constructor
+ @param parent: Parent Viewer
+ """
+ self.Parent = parent
+
+ self.ToolTip = None
+ self.ToolTipPos = None
+
+ # Timer for firing Tool tip display
+ self.ToolTipTimer = wx.Timer(self.Parent, -1)
+ self.Parent.Bind(wx.EVT_TIMER,
+ self.OnToolTipTimer,
+ self.ToolTipTimer)
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ self.DestroyToolTip()
+
+ def OnToolTipTimer(self, event):
+ """
+ Callback for Tool Tip firing timer Event
+ @param event: Tool tip text
+ """
+ # Get Tool Tip text
+ value = self.GetToolTipValue()
+
+ if value is not None and self.ToolTipPos is not None:
+ # Create Tool Tip
+ self.ToolTip = CustomToolTip(self.Parent, value)
+ self.ToolTip.SetToolTipPosition(self.ToolTipPos)
+ self.ToolTip.Show()
+
+ def GetToolTipValue(self):
+ """
+ Return tool tip text
+ Have to be overridden by inherited classes
+ @return: Tool tip text (None if not overridden)
+ """
+ return None
+
+ def DisplayToolTip(self, pos):
+ """
+ Display Tool tip
+ @param pos: Tool tip position
+ """
+ # Destroy current displayed Tool tip
+ self.DestroyToolTip()
+
+ # Save Tool Tip position
+ self.ToolTipPos = pos
+ # Start Tool tip firing timer
+ self.ToolTipTimer.Start(
+ int(TOOLTIP_WAIT_PERIOD * 1000),
+ oneShot=True)
+
+ def SetToolTipText(self, text):
+ """
+ Set current Tool tip text
+ @param text: Tool tip Text
+ """
+ if self.ToolTip is not None:
+ self.ToolTip.SetTip(text)
+
+ def DestroyToolTip(self):
+ """
+ Destroy current displayed Tool Tip
+ """
+ # Stop Tool tip firing timer
+ self.ToolTipTimer.Stop()
+ self.ToolTipPos = None
+
+ # Destroy Tool Tip
+ if self.ToolTip is not None:
+ self.ToolTip.Destroy()
+ self.ToolTip = None
--- a/graphics/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -27,4 +27,6 @@
from GraphicCommons import *
from FBD_Objects import *
from LD_Objects import *
-from SFC_Objects import *
\ No newline at end of file
+from SFC_Objects import *
+from RubberBand import RubberBand
+from DebugDataConsumer import DebugDataConsumer
\ No newline at end of file
--- a/i18n/Beremiz_fr_FR.po Wed Mar 13 12:34:55 2013 +0900
+++ b/i18n/Beremiz_fr_FR.po Wed Jul 31 10:45:07 2013 +0900
@@ -7,8 +7,8 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-09-07 01:17+0200\n"
-"PO-Revision-Date: 2012-09-07 01:31+0100\n"
+"POT-Creation-Date: 2013-03-26 22:55+0100\n"
+"PO-Revision-Date: 2013-03-26 23:08+0100\n"
"Last-Translator: Laurent BESSARD <laurent.bessard@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -16,7 +16,7 @@
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ../PLCOpenEditor.py:520
+#: ../PLCOpenEditor.py:405
msgid ""
"\n"
"An error has occurred.\n"
@@ -38,7 +38,7 @@
"\n"
"Erreur:\n"
-#: ../Beremiz.py:1071
+#: ../Beremiz.py:1119
#, python-format
msgid ""
"\n"
@@ -87,7 +87,7 @@
msgid " Temp"
msgstr " Temporaire"
-#: ../PLCOpenEditor.py:530
+#: ../PLCOpenEditor.py:415
msgid " : "
msgstr " : "
@@ -99,7 +99,7 @@
msgid " and %s"
msgstr " et %s"
-#: ../ProjectController.py:890
+#: ../ProjectController.py:917
msgid " generation failed !\n"
msgstr "la construction a échouée !\n"
@@ -123,8 +123,8 @@
msgid "\"%s\" can't use itself!"
msgstr "\"%s\" ne peut pas s'utiliser lui-même !"
-#: ../IDEFrame.py:1706
-#: ../IDEFrame.py:1725
+#: ../IDEFrame.py:1587
+#: ../IDEFrame.py:1606
#, python-format
msgid "\"%s\" config already exists!"
msgstr "La configuration \"%s\" existe déjà !"
@@ -134,46 +134,46 @@
msgid "\"%s\" configuration already exists !!!"
msgstr "La configuration \"%s\" existe déjà !!!"
-#: ../IDEFrame.py:1660
+#: ../IDEFrame.py:1541
#, python-format
msgid "\"%s\" data type already exists!"
msgstr "Le type de données \"%s\" existe déjà !"
-#: ../PLCControler.py:2040
-#: ../PLCControler.py:2044
+#: ../PLCControler.py:2165
+#: ../PLCControler.py:2169
#, python-format
msgid "\"%s\" element can't be pasted here!!!"
msgstr "L'élément \"%s\" ne peut être collé ici !!!"
-#: ../editors/TextViewer.py:305
-#: ../editors/TextViewer.py:325
-#: ../editors/Viewer.py:252
+#: ../editors/TextViewer.py:298
+#: ../editors/TextViewer.py:318
+#: ../editors/Viewer.py:250
#: ../dialogs/PouTransitionDialog.py:105
-#: ../dialogs/ConnectionDialog.py:150
+#: ../dialogs/ConnectionDialog.py:157
#: ../dialogs/PouActionDialog.py:102
#: ../dialogs/FBDBlockDialog.py:162
#, python-format
msgid "\"%s\" element for this pou already exists!"
msgstr "Un élément \"%s\" existe déjà dans ce POU !"
-#: ../Beremiz.py:894
+#: ../Beremiz.py:921
#, python-format
msgid "\"%s\" folder is not a valid Beremiz project\n"
msgstr "Le dossier \"%s\" ne contient pas de projet Beremiz valide\n"
-#: ../plcopen/structures.py:106
+#: ../plcopen/structures.py:105
#, python-format
msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
msgstr "L'appel à la fonction \"%s\" dans le POU \"%s\" a été abandonné : aucune entrée connectée"
-#: ../controls/VariablePanel.py:656
-#: ../IDEFrame.py:1651
-#: ../editors/DataTypeEditor.py:548
-#: ../editors/DataTypeEditor.py:577
+#: ../controls/VariablePanel.py:659
+#: ../IDEFrame.py:1532
+#: ../editors/DataTypeEditor.py:554
+#: ../editors/DataTypeEditor.py:583
#: ../dialogs/PouNameDialog.py:49
#: ../dialogs/PouTransitionDialog.py:101
#: ../dialogs/SFCStepNameDialog.py:51
-#: ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/ConnectionDialog.py:153
#: ../dialogs/FBDVariableDialog.py:199
#: ../dialogs/PouActionDialog.py:98
#: ../dialogs/PouDialog.py:118
@@ -183,29 +183,29 @@
msgid "\"%s\" is a keyword. It can't be used!"
msgstr "\"%s\" est un mot réservé. Il ne peut être utilisé !"
-#: ../editors/Viewer.py:240
+#: ../editors/Viewer.py:238
#, python-format
msgid "\"%s\" is already used by \"%s\"!"
msgstr "\"%s\" est déjà utilisé par \"%s\" !"
-#: ../plcopen/plcopen.py:2786
+#: ../plcopen/plcopen.py:2836
#, python-format
msgid "\"%s\" is an invalid value!"
msgstr "\"%s\" n'est pas une valeur valide !"
-#: ../PLCOpenEditor.py:362
-#: ../PLCOpenEditor.py:399
+#: ../PLCOpenEditor.py:341
+#: ../PLCOpenEditor.py:378
#, python-format
msgid "\"%s\" is not a valid folder!"
msgstr "\"%s\" n'est pas un répertoire valide !"
-#: ../controls/VariablePanel.py:654
-#: ../IDEFrame.py:1649
-#: ../editors/DataTypeEditor.py:572
+#: ../controls/VariablePanel.py:657
+#: ../IDEFrame.py:1530
+#: ../editors/DataTypeEditor.py:578
#: ../dialogs/PouNameDialog.py:47
#: ../dialogs/PouTransitionDialog.py:99
#: ../dialogs/SFCStepNameDialog.py:49
-#: ../dialogs/ConnectionDialog.py:144
+#: ../dialogs/ConnectionDialog.py:151
#: ../dialogs/PouActionDialog.py:96
#: ../dialogs/PouDialog.py:116
#: ../dialogs/SFCStepDialog.py:120
@@ -214,22 +214,22 @@
msgid "\"%s\" is not a valid identifier!"
msgstr "\"%s\" n'est pas un identifiant valide !"
-#: ../IDEFrame.py:214
-#: ../IDEFrame.py:2445
-#: ../IDEFrame.py:2464
+#: ../IDEFrame.py:221
+#: ../IDEFrame.py:2313
+#: ../IDEFrame.py:2332
#, python-format
msgid "\"%s\" is used by one or more POUs. It can't be removed!"
msgstr "Le POU \"%s\" est utilisé par un ou plusieurs POUs. Il ne peut être supprimé !"
-#: ../controls/VariablePanel.py:311
-#: ../IDEFrame.py:1669
-#: ../editors/TextViewer.py:303
-#: ../editors/TextViewer.py:323
-#: ../editors/TextViewer.py:360
-#: ../editors/Viewer.py:250
-#: ../editors/Viewer.py:295
-#: ../editors/Viewer.py:312
-#: ../dialogs/ConnectionDialog.py:148
+#: ../controls/VariablePanel.py:313
+#: ../IDEFrame.py:1550
+#: ../editors/TextViewer.py:296
+#: ../editors/TextViewer.py:316
+#: ../editors/TextViewer.py:353
+#: ../editors/Viewer.py:248
+#: ../editors/Viewer.py:293
+#: ../editors/Viewer.py:311
+#: ../dialogs/ConnectionDialog.py:155
#: ../dialogs/PouDialog.py:120
#: ../dialogs/FBDBlockDialog.py:160
#, python-format
@@ -252,18 +252,18 @@
msgid "\"%s\" step already exists!"
msgstr "L'étape \"%s\" existe déjà !"
-#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:549
#, python-format
msgid "\"%s\" value already defined!"
msgstr "La valeur \"%s\" est déjà définie !"
-#: ../editors/DataTypeEditor.py:719
+#: ../editors/DataTypeEditor.py:744
#: ../dialogs/ArrayTypeDialog.py:97
#, python-format
msgid "\"%s\" value isn't a valid array dimension!"
msgstr "\"%s\" n'est pas une dimension de tableau valide !"
-#: ../editors/DataTypeEditor.py:726
+#: ../editors/DataTypeEditor.py:751
#: ../dialogs/ArrayTypeDialog.py:103
#, python-format
msgid ""
@@ -273,12 +273,12 @@
"\"%s\" n'est pas une dimension de tableau valide !\n"
"La valeur de droite doit être supérieur à celle de gauche."
-#: ../PLCControler.py:793
+#: ../PLCControler.py:847
#, python-format
msgid "%s \"%s\" can't be pasted as a %s."
msgstr "Le %s \"%s\" ne peut être collé en tant que %s."
-#: ../PLCControler.py:1422
+#: ../PLCControler.py:1476
#, python-format
msgid "%s Data Types"
msgstr "Types de données de %s"
@@ -288,90 +288,90 @@
msgid "%s Graphics"
msgstr "Graphique %s"
-#: ../PLCControler.py:1417
+#: ../PLCControler.py:1471
#, python-format
msgid "%s POUs"
msgstr "POUs de %s"
-#: ../canfestival/SlaveEditor.py:42
-#: ../canfestival/NetworkEditor.py:72
+#: ../canfestival/SlaveEditor.py:46
+#: ../canfestival/NetworkEditor.py:67
#, python-format
msgid "%s Profile"
msgstr "Profil %s"
-#: ../plcopen/plcopen.py:1780
#: ../plcopen/plcopen.py:1790
#: ../plcopen/plcopen.py:1800
#: ../plcopen/plcopen.py:1810
-#: ../plcopen/plcopen.py:1819
+#: ../plcopen/plcopen.py:1820
+#: ../plcopen/plcopen.py:1829
#, python-format
msgid "%s body don't have instances!"
msgstr "Le code d'un %s n'a pas d'instances !"
-#: ../plcopen/plcopen.py:1842
-#: ../plcopen/plcopen.py:1849
+#: ../plcopen/plcopen.py:1852
+#: ../plcopen/plcopen.py:1859
#, python-format
msgid "%s body don't have text!"
msgstr "Le code d'un %s n'a pas de texte !"
-#: ../IDEFrame.py:364
+#: ../IDEFrame.py:369
msgid "&Add Element"
msgstr "&Ajouter un élément"
-#: ../IDEFrame.py:334
+#: ../IDEFrame.py:339
msgid "&Configuration"
msgstr "&Configuration"
-#: ../IDEFrame.py:325
+#: ../IDEFrame.py:330
msgid "&Data Type"
msgstr "&Type de donnée"
-#: ../IDEFrame.py:368
+#: ../IDEFrame.py:373
msgid "&Delete"
msgstr "&Supprimer"
-#: ../IDEFrame.py:317
+#: ../IDEFrame.py:322
msgid "&Display"
msgstr "&Affichage"
-#: ../IDEFrame.py:316
+#: ../IDEFrame.py:321
msgid "&Edit"
msgstr "&Editer"
-#: ../IDEFrame.py:315
+#: ../IDEFrame.py:320
msgid "&File"
msgstr "&Fichier"
-#: ../IDEFrame.py:327
+#: ../IDEFrame.py:332
msgid "&Function"
msgstr "&Fonction"
-#: ../IDEFrame.py:318
+#: ../IDEFrame.py:323
msgid "&Help"
msgstr "&Aide"
-#: ../IDEFrame.py:331
+#: ../IDEFrame.py:336
msgid "&Program"
msgstr "&Programme"
-#: ../PLCOpenEditor.py:148
+#: ../PLCOpenEditor.py:129
msgid "&Properties"
msgstr "&Propriétés"
-#: ../Beremiz.py:310
+#: ../Beremiz.py:312
msgid "&Recent Projects"
msgstr "Projets &récent"
-#: ../Beremiz.py:352
+#: ../Beremiz.py:354
msgid "&Resource"
msgstr "&Ressource"
-#: ../controls/SearchResultPanel.py:237
+#: ../controls/SearchResultPanel.py:252
#, python-format
msgid "'%s' - %d match in project"
msgstr "'%s' - %d correspondance dans le projet"
-#: ../controls/SearchResultPanel.py:239
+#: ../controls/SearchResultPanel.py:254
#, python-format
msgid "'%s' - %d matches in project"
msgstr "'%s' - %d correspondances dans le projet"
@@ -381,14 +381,14 @@
msgid "'%s' is located at %s\n"
msgstr "'%s' is disponible à l'adresse %s\n"
-#: ../controls/SearchResultPanel.py:289
+#: ../controls/SearchResultPanel.py:304
#, python-format
msgid "(%d matches)"
msgstr "(%d correspondances)"
-#: ../PLCOpenEditor.py:508
-#: ../PLCOpenEditor.py:510
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:393
+#: ../PLCOpenEditor.py:395
+#: ../PLCOpenEditor.py:396
msgid ", "
msgstr ", "
@@ -400,25 +400,25 @@
msgid ", %s"
msgstr ", %s"
-#: ../PLCOpenEditor.py:506
+#: ../PLCOpenEditor.py:391
msgid ". "
msgstr ". "
-#: ../ProjectController.py:1268
+#: ../ProjectController.py:1294
msgid "... debugger recovered\n"
msgstr "... déboggueur operationel\n"
-#: ../IDEFrame.py:1672
-#: ../IDEFrame.py:1714
-#: ../IDEFrame.py:1733
+#: ../IDEFrame.py:1553
+#: ../IDEFrame.py:1595
+#: ../IDEFrame.py:1614
#: ../dialogs/PouDialog.py:122
#, python-format
msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
msgstr "Un POU a un élément nommé \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
-#: ../controls/VariablePanel.py:658
-#: ../IDEFrame.py:1684
-#: ../IDEFrame.py:1695
+#: ../controls/VariablePanel.py:661
+#: ../IDEFrame.py:1565
+#: ../IDEFrame.py:1576
#: ../dialogs/PouNameDialog.py:51
#: ../dialogs/PouTransitionDialog.py:103
#: ../dialogs/SFCStepNameDialog.py:53
@@ -428,34 +428,34 @@
msgid "A POU named \"%s\" already exists!"
msgstr "Un POU nommé \"%s\" existe déjà !"
-#: ../ConfigTreeNode.py:371
+#: ../ConfigTreeNode.py:388
#, python-format
msgid "A child named \"%s\" already exist -> \"%s\"\n"
msgstr "Un noeud enfant nommé \"%s\" existe déjà -> \"%s\"\n"
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
msgid "A location must be selected!"
msgstr "Une adresse doit être sélectionné !"
-#: ../controls/VariablePanel.py:660
-#: ../IDEFrame.py:1686
-#: ../IDEFrame.py:1697
+#: ../controls/VariablePanel.py:663
+#: ../IDEFrame.py:1567
+#: ../IDEFrame.py:1578
#: ../dialogs/SFCStepNameDialog.py:55
#: ../dialogs/SFCStepDialog.py:126
#, python-format
msgid "A variable with \"%s\" as name already exists in this pou!"
msgstr "Une variable nommée \"%s\" existe déjà dans ce POU !"
-#: ../Beremiz.py:362
-#: ../PLCOpenEditor.py:181
+#: ../Beremiz.py:364
+#: ../PLCOpenEditor.py:162
msgid "About"
msgstr "A propos"
-#: ../Beremiz.py:931
+#: ../Beremiz.py:957
msgid "About Beremiz"
msgstr "A propos de Beremiz"
-#: ../PLCOpenEditor.py:376
+#: ../PLCOpenEditor.py:355
msgid "About PLCOpenEditor"
msgstr "A propos de PLCOpenEditor"
@@ -468,7 +468,7 @@
msgid "Action"
msgstr "Action"
-#: ../editors/Viewer.py:495
+#: ../editors/Viewer.py:494
msgid "Action Block"
msgstr "Ajouter un bloc fonctionnel"
@@ -480,7 +480,7 @@
msgid "Action Name:"
msgstr "Nom de l'action :"
-#: ../plcopen/plcopen.py:1480
+#: ../plcopen/plcopen.py:1490
#, python-format
msgid "Action with name %s doesn't exist!"
msgstr "L'action nommée %s n'existe pas !"
@@ -493,30 +493,35 @@
msgid "Actions:"
msgstr "Actions :"
-#: ../canfestival/SlaveEditor.py:54
-#: ../canfestival/NetworkEditor.py:84
+#: ../editors/Viewer.py:999
+msgid "Active"
+msgstr "Actif"
+
+#: ../canfestival/SlaveEditor.py:57
+#: ../canfestival/NetworkEditor.py:78
+#: ../Beremiz.py:987
#: ../editors/Viewer.py:527
msgid "Add"
msgstr "Ajouter"
-#: ../IDEFrame.py:1925
-#: ../IDEFrame.py:1956
+#: ../IDEFrame.py:1801
+#: ../IDEFrame.py:1832
msgid "Add Action"
msgstr "Ajouter une action"
-#: ../features.py:7
+#: ../features.py:8
msgid "Add C code accessing located variables synchronously"
msgstr "Ajoute un code C ayant accès à des variables localisées de façon synchrone"
-#: ../IDEFrame.py:1908
+#: ../IDEFrame.py:1784
msgid "Add Configuration"
msgstr "Ajouter une configuration"
-#: ../IDEFrame.py:1888
+#: ../IDEFrame.py:1764
msgid "Add DataType"
msgstr "Ajouter un type de donnée"
-#: ../editors/Viewer.py:453
+#: ../editors/Viewer.py:452
msgid "Add Divergence Branch"
msgstr "Ajouter une branche à la divergence"
@@ -524,25 +529,25 @@
msgid "Add IP"
msgstr "Ajouter IP"
-#: ../IDEFrame.py:1896
+#: ../IDEFrame.py:1772
msgid "Add POU"
msgstr "Ajouter un POU"
-#: ../features.py:8
+#: ../features.py:9
msgid "Add Python code executed asynchronously"
msgstr "Ajoute un code Python executé de façon asynchone"
-#: ../IDEFrame.py:1936
-#: ../IDEFrame.py:1982
+#: ../IDEFrame.py:1812
+#: ../IDEFrame.py:1858
msgid "Add Resource"
msgstr "Ajouter une resource"
-#: ../IDEFrame.py:1914
-#: ../IDEFrame.py:1953
+#: ../IDEFrame.py:1790
+#: ../IDEFrame.py:1829
msgid "Add Transition"
msgstr "Ajouter une transition"
-#: ../editors/Viewer.py:442
+#: ../editors/Viewer.py:441
msgid "Add Wire Segment"
msgstr "Ajouter un segment au fil"
@@ -550,7 +555,7 @@
msgid "Add a new initial step"
msgstr "Ajouter une nouvelle étape initiale"
-#: ../editors/Viewer.py:2289
+#: ../editors/Viewer.py:2363
#: ../editors/SFCViewer.py:696
msgid "Add a new jump"
msgstr "Ajouter un nouveau renvoi"
@@ -559,7 +564,7 @@
msgid "Add a new step"
msgstr "Ajouter une nouvelle étape"
-#: ../features.py:9
+#: ../features.py:10
msgid "Add a simple WxGlade based GUI."
msgstr "Ajoute une interface simple utilisant WxGlade"
@@ -567,23 +572,24 @@
msgid "Add action"
msgstr "Ajouter une action"
-#: ../editors/DataTypeEditor.py:345
+#: ../editors/DataTypeEditor.py:351
msgid "Add element"
msgstr "Ajouter un élément"
-#: ../editors/ResourceEditor.py:251
+#: ../editors/ResourceEditor.py:259
msgid "Add instance"
msgstr "Ajouter une instance"
-#: ../canfestival/NetworkEditor.py:86
+#: ../canfestival/NetworkEditor.py:80
msgid "Add slave"
msgstr "Ajouter un esclave"
-#: ../editors/ResourceEditor.py:222
+#: ../editors/ResourceEditor.py:230
msgid "Add task"
msgstr "Ajouter une tâche"
-#: ../controls/VariablePanel.py:378
+#: ../controls/VariablePanel.py:380
+#: ../c_ext/CFileEditor.py:517
msgid "Add variable"
msgstr "Ajouter une variable"
@@ -591,33 +597,43 @@
msgid "Addition"
msgstr "Addition"
-#: ../plcopen/structures.py:250
+#: ../plcopen/structures.py:249
msgid "Additional function blocks"
msgstr "Blocs fonctionnels additionnels"
-#: ../editors/Viewer.py:1395
+#: ../editors/Viewer.py:510
+msgid "Adjust Block Size"
+msgstr "Ajuster la taille des blocs"
+
+#: ../editors/Viewer.py:1458
msgid "Alignment"
msgstr "Alignement"
#: ../controls/VariablePanel.py:75
-#: ../dialogs/BrowseLocationsDialog.py:35
-#: ../dialogs/BrowseLocationsDialog.py:116
+#: ../dialogs/BrowseLocationsDialog.py:34
+#: ../dialogs/BrowseLocationsDialog.py:43
+#: ../dialogs/BrowseLocationsDialog.py:136
+#: ../dialogs/BrowseLocationsDialog.py:139
msgid "All"
-msgstr "Toutes"
+msgstr "Tout"
#: ../editors/FileManagementPanel.py:35
msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
msgstr "Tous les fichiers|*.*|Fichiers CSV (*.csv)|*.csv"
-#: ../ProjectController.py:1335
+#: ../ProjectController.py:1373
msgid "Already connected. Please disconnect\n"
msgstr "Déjà connecté. Veuillez déconnecter\n"
-#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:593
#, python-format
msgid "An element named \"%s\" already exists in this structure!"
msgstr "Un élément nommé \"%s\" existe déjà dans la structure !"
+#: ../dialogs/ConnectionDialog.py:98
+msgid "Apply name modification to all continuations with the same name"
+msgstr "Appliquer la modification de nom à toutes les prolongements portant ce même nom"
+
#: ../plcopen/iec_std.csv:31
msgid "Arc cosine"
msgstr "Arc cosinus"
@@ -634,8 +650,9 @@
msgid "Arithmetic"
msgstr "Arithmétique"
-#: ../controls/VariablePanel.py:729
-#: ../editors/DataTypeEditor.py:52
+#: ../controls/VariablePanel.py:732
+#: ../editors/DataTypeEditor.py:54
+#: ../editors/DataTypeEditor.py:634
msgid "Array"
msgstr "Tableau"
@@ -673,19 +690,19 @@
msgid "Bad location size : %s"
msgstr "Mauvaise taille d'adresse : %s"
-#: ../editors/DataTypeEditor.py:168
-#: ../editors/DataTypeEditor.py:198
-#: ../editors/DataTypeEditor.py:290
+#: ../editors/DataTypeEditor.py:174
+#: ../editors/DataTypeEditor.py:204
+#: ../editors/DataTypeEditor.py:296
#: ../dialogs/ArrayTypeDialog.py:55
msgid "Base Type:"
msgstr "Type de base :"
-#: ../controls/VariablePanel.py:699
-#: ../editors/DataTypeEditor.py:617
+#: ../controls/VariablePanel.py:702
+#: ../editors/DataTypeEditor.py:624
msgid "Base Types"
msgstr "Types de base"
-#: ../Beremiz.py:486
+#: ../Beremiz.py:511
msgid "Beremiz"
msgstr "Beremiz"
@@ -717,7 +734,7 @@
msgid "Bitwise inverting"
msgstr "Inversion bit à bit"
-#: ../editors/Viewer.py:465
+#: ../editors/Viewer.py:464
msgid "Block"
msgstr "Block"
@@ -725,7 +742,7 @@
msgid "Block Properties"
msgstr "Propriétés du bloc"
-#: ../editors/Viewer.py:434
+#: ../editors/Viewer.py:433
msgid "Bottom"
msgstr "Bas"
@@ -734,31 +751,35 @@
msgid "Browse %s values library"
msgstr "Explorer la liste des valeurs du paramètre '%s'"
-#: ../dialogs/BrowseLocationsDialog.py:55
+#: ../dialogs/BrowseLocationsDialog.py:61
msgid "Browse Locations"
msgstr "Naviger dans les adresses"
-#: ../ProjectController.py:1484
+#: ../ProjectController.py:1519
msgid "Build"
msgstr "Compiler"
-#: ../ProjectController.py:1051
+#: ../ProjectController.py:1079
msgid "Build directory already clean\n"
msgstr "Le répertoire de compilation est déjà nettoyé\n"
-#: ../ProjectController.py:1485
+#: ../ProjectController.py:1520
msgid "Build project into build folder"
msgstr "Compiler le projet dans le répertoire ce compilation"
-#: ../ProjectController.py:910
+#: ../ProjectController.py:937
msgid "C Build crashed !\n"
msgstr "La compilation du C a mal fonctionné !\n"
-#: ../ProjectController.py:907
+#: ../ProjectController.py:934
msgid "C Build failed.\n"
msgstr "La compilation du C a échouée !\n"
-#: ../ProjectController.py:895
+#: ../c_ext/CFileEditor.py:731
+msgid "C code"
+msgstr "Code C"
+
+#: ../ProjectController.py:922
msgid "C code generated successfully.\n"
msgstr "Code C généré avec succès.\n"
@@ -767,18 +788,26 @@
msgid "C compilation of %s failed.\n"
msgstr "La compilation C de %s a échouée.\n"
-#: ../features.py:7
+#: ../features.py:8
msgid "C extension"
msgstr "Extension C"
-#: ../features.py:6
+#: ../canfestival/NetworkEditor.py:29
+msgid "CANOpen network"
+msgstr "Réseau CANOpen"
+
+#: ../canfestival/SlaveEditor.py:21
+msgid "CANOpen slave"
+msgstr "Esclave CANOpen"
+
+#: ../features.py:7
msgid "CANopen support"
msgstr "Support CANopen"
-#: ../plcopen/plcopen.py:1722
-#: ../plcopen/plcopen.py:1736
-#: ../plcopen/plcopen.py:1757
-#: ../plcopen/plcopen.py:1773
+#: ../plcopen/plcopen.py:1732
+#: ../plcopen/plcopen.py:1746
+#: ../plcopen/plcopen.py:1767
+#: ../plcopen/plcopen.py:1783
msgid "Can only generate execution order on FBD networks!"
msgstr "L'ordre d'exécution ne peut être généré que dans les FBD !"
@@ -786,7 +815,7 @@
msgid "Can only give a location to local or global variables"
msgstr "Une adresse ne peut être affecté qu'à des variables locales ou globales"
-#: ../PLCOpenEditor.py:357
+#: ../PLCOpenEditor.py:336
#, python-format
msgid "Can't generate program to file %s!"
msgstr "Le programme n'a pu être généré dans le fichier \"%s\" !"
@@ -795,21 +824,21 @@
msgid "Can't give a location to a function block instance"
msgstr "Une adresse ne peut être affectée à une instance de Function Block"
-#: ../PLCOpenEditor.py:397
+#: ../PLCOpenEditor.py:376
#, python-format
msgid "Can't save project to file %s!"
msgstr "Le projet n'a pu être sauvé dans le fichier \"%s\" !"
-#: ../controls/VariablePanel.py:298
+#: ../controls/VariablePanel.py:300
msgid "Can't set an initial value to a function block instance"
msgstr "Une valeur initiale ne peut être affectée une instance de Function Block"
-#: ../ConfigTreeNode.py:470
+#: ../ConfigTreeNode.py:490
#, python-format
msgid "Cannot create child %s of type %s "
msgstr "Impossible d'ajouter un élément \"%s\" de type \"%s\""
-#: ../ConfigTreeNode.py:400
+#: ../ConfigTreeNode.py:417
#, python-format
msgid "Cannot find lower free IEC channel than %d\n"
msgstr "Impossible de trouver un numéro IEC inférieur à %d libre\n"
@@ -818,7 +847,7 @@
msgid "Cannot get PLC status - connection failed.\n"
msgstr "Impossible d'obtenir le statut de l'automate - la connexion a échoué.\n"
-#: ../ProjectController.py:715
+#: ../ProjectController.py:737
msgid "Cannot open/parse VARIABLES.csv!\n"
msgstr "Impossible d'ouvrir ou d'analyser le fichier VARIABLES.csv !\n"
@@ -832,27 +861,27 @@
msgid "Case sensitive"
msgstr "Respecter la casse"
-#: ../editors/Viewer.py:429
+#: ../editors/Viewer.py:428
msgid "Center"
msgstr "Centre"
-#: ../Beremiz_service.py:322
+#: ../Beremiz_service.py:326
msgid "Change IP of interface to bind"
msgstr "Changer l'adresse IP de l'interface à lier"
-#: ../Beremiz_service.py:321
+#: ../Beremiz_service.py:325
msgid "Change Name"
msgstr "Changer le nom"
-#: ../IDEFrame.py:1974
+#: ../IDEFrame.py:1850
msgid "Change POU Type To"
msgstr "Changer le type du POU pour"
-#: ../Beremiz_service.py:325
+#: ../Beremiz_service.py:327
msgid "Change Port Number"
msgstr "Changer le numéro de port"
-#: ../Beremiz_service.py:327
+#: ../Beremiz_service.py:328
msgid "Change working directory"
msgstr "Changer le dossier de travail"
@@ -864,19 +893,19 @@
msgid "Choose a SVG file"
msgstr "Choisissez un fichier SVG"
-#: ../ProjectController.py:353
+#: ../ProjectController.py:364
msgid "Choose a directory to save project"
msgstr "Choisissez un dossier où enregistrer le projet"
-#: ../canfestival/canfestival.py:118
-#: ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:347
-#: ../PLCOpenEditor.py:391
+#: ../canfestival/canfestival.py:136
+#: ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:326
+#: ../PLCOpenEditor.py:370
msgid "Choose a file"
msgstr "Choisissez un fichier"
-#: ../Beremiz.py:831
-#: ../Beremiz.py:866
+#: ../Beremiz.py:858
+#: ../Beremiz.py:893
msgid "Choose a project"
msgstr "Choisissez un projet"
@@ -885,15 +914,15 @@
msgid "Choose a value for %s:"
msgstr "Choisissez une valeur pour le paramètre %s :"
-#: ../Beremiz_service.py:373
+#: ../Beremiz_service.py:378
msgid "Choose a working directory "
msgstr "Choisissez un dossier de travail"
-#: ../ProjectController.py:281
+#: ../ProjectController.py:288
msgid "Chosen folder doesn't contain a program. It's not a valid project!"
msgstr "Le répertoire ne contient pas de programme. Ce n'est pas un projet valide !"
-#: ../ProjectController.py:247
+#: ../ProjectController.py:255
msgid "Chosen folder isn't empty. You can't use it for a new project!"
msgstr "Le répertoire n'est pas vide. Vous ne pouvez pas l'utiliser pour créer un nouveau projet !"
@@ -902,7 +931,7 @@
msgid "Class"
msgstr "Classe"
-#: ../controls/VariablePanel.py:369
+#: ../controls/VariablePanel.py:371
msgid "Class Filter:"
msgstr "Filtre de classe :"
@@ -910,19 +939,19 @@
msgid "Class:"
msgstr "Classe :"
-#: ../ProjectController.py:1488
+#: ../ProjectController.py:1523
msgid "Clean"
msgstr "Nettoyer"
-#: ../ProjectController.py:1490
+#: ../ProjectController.py:1525
msgid "Clean project build folder"
msgstr "Nettoyer le répertoire de compilation"
-#: ../ProjectController.py:1048
+#: ../ProjectController.py:1076
msgid "Cleaning the build directory\n"
msgstr "Répertoire de compilation en cours de nettoyage\n"
-#: ../IDEFrame.py:411
+#: ../IDEFrame.py:416
msgid "Clear Errors"
msgstr "Effacer les erreurs"
@@ -934,29 +963,29 @@
msgid "Clear the graph values"
msgstr "Vider les valeurs du graphique"
-#: ../Beremiz.py:598
-#: ../PLCOpenEditor.py:221
+#: ../Beremiz.py:633
+#: ../PLCOpenEditor.py:202
msgid "Close Application"
msgstr "Fermer l'application"
-#: ../IDEFrame.py:1089
-#: ../Beremiz.py:319
-#: ../Beremiz.py:552
-#: ../PLCOpenEditor.py:131
+#: ../IDEFrame.py:972
+#: ../Beremiz.py:321
+#: ../Beremiz.py:587
+#: ../PLCOpenEditor.py:112
msgid "Close Project"
msgstr "Fermer le projet"
-#: ../Beremiz.py:317
-#: ../PLCOpenEditor.py:129
+#: ../Beremiz.py:319
+#: ../PLCOpenEditor.py:110
msgid "Close Tab"
msgstr "Fermer l'onglet"
-#: ../editors/Viewer.py:481
+#: ../editors/Viewer.py:480
msgid "Coil"
msgstr "Relai"
-#: ../editors/Viewer.py:501
-#: ../editors/LDViewer.py:503
+#: ../editors/Viewer.py:500
+#: ../editors/LDViewer.py:506
msgid "Comment"
msgstr "Commentaire"
@@ -972,7 +1001,7 @@
msgid "Comparison"
msgstr "Comparaison"
-#: ../ProjectController.py:538
+#: ../ProjectController.py:552
msgid "Compiling IEC Program into C code...\n"
msgstr "Compilation du program en IEC vers du code C en cours...\n"
@@ -980,6 +1009,14 @@
msgid "Concatenation"
msgstr "Concaténation"
+#: ../editors/ConfTreeNodeEditor.py:249
+msgid "Config"
+msgstr "Configuration"
+
+#: ../editors/ProjectNodeEditor.py:13
+msgid "Config variables"
+msgstr "Variables de configuration"
+
#: ../dialogs/SearchInProjectDialog.py:47
msgid "Configuration"
msgstr "Configuration"
@@ -988,20 +1025,25 @@
msgid "Configurations"
msgstr "Configurations"
-#: ../ProjectController.py:1503
+#: ../ProjectController.py:1538
msgid "Connect"
msgstr "Connecter"
-#: ../ProjectController.py:1504
+#: ../ProjectController.py:1539
msgid "Connect to the target PLC"
msgstr "Connecter à l'automate cible"
+#: ../ProjectController.py:1125
+#, python-format
+msgid "Connected to URI: %s"
+msgstr "Connecté à l'URI : %s"
+
#: ../connectors/PYRO/__init__.py:40
#, python-format
msgid "Connecting to URI : %s\n"
msgstr "Connection à l'URI %s en cours...\n"
-#: ../editors/Viewer.py:467
+#: ../editors/Viewer.py:466
#: ../dialogs/SFCTransitionDialog.py:76
msgid "Connection"
msgstr "Connexion"
@@ -1010,11 +1052,11 @@
msgid "Connection Properties"
msgstr "Propriétés de la connexion"
-#: ../ProjectController.py:1359
+#: ../ProjectController.py:1397
msgid "Connection canceled!\n"
msgstr "La connection a été abandonnée !\n"
-#: ../ProjectController.py:1384
+#: ../ProjectController.py:1422
#, python-format
msgid "Connection failed to %s!\n"
msgstr "La connection à \"%s\" a échouée !\n"
@@ -1024,6 +1066,7 @@
msgid "Connection to '%s' failed.\n"
msgstr "La connexion à l'adresse '%s' a échouée.\n"
+#: ../editors/Viewer.py:1426
#: ../dialogs/ConnectionDialog.py:56
msgid "Connector"
msgstr "Connecteur"
@@ -1032,11 +1075,15 @@
msgid "Connectors:"
msgstr "Connecteurs :"
+#: ../Beremiz.py:420
+msgid "Console"
+msgstr "Console"
+
#: ../controls/VariablePanel.py:65
msgid "Constant"
msgstr "Constante"
-#: ../editors/Viewer.py:477
+#: ../editors/Viewer.py:476
msgid "Contact"
msgstr "Contact"
@@ -1044,6 +1091,7 @@
msgid "Content Description (optional):"
msgstr "Description du contenu (optionel) :"
+#: ../editors/Viewer.py:1427
#: ../dialogs/ConnectionDialog.py:61
msgid "Continuation"
msgstr "Prolongement"
@@ -1064,21 +1112,21 @@
msgid "Conversion to time-of-day"
msgstr "Conversion en heure de la journée"
-#: ../IDEFrame.py:348
-#: ../IDEFrame.py:401
+#: ../IDEFrame.py:353
+#: ../IDEFrame.py:406
#: ../editors/Viewer.py:536
msgid "Copy"
msgstr "Copier"
-#: ../IDEFrame.py:1961
+#: ../IDEFrame.py:1837
msgid "Copy POU"
msgstr "Copier ce POU"
-#: ../editors/FileManagementPanel.py:283
+#: ../editors/FileManagementPanel.py:65
msgid "Copy file from left folder to right"
msgstr "Copier un fichier du dossier de gauche vers celui de droite"
-#: ../editors/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:64
msgid "Copy file from right folder to left"
msgstr "Copier un fichier du dossier de droite vers celui de gauche"
@@ -1086,7 +1134,7 @@
msgid "Cosine"
msgstr "Cosinus"
-#: ../ConfigTreeNode.py:582
+#: ../ConfigTreeNode.py:602
#, python-format
msgid ""
"Could not add child \"%s\", type %s :\n"
@@ -1095,7 +1143,7 @@
"Impossible d'ajouter le noeud enfant \"%s\", de type %s :\n"
"%s\n"
-#: ../ConfigTreeNode.py:559
+#: ../ConfigTreeNode.py:579
#, python-format
msgid ""
"Couldn't load confnode base parameters %s :\n"
@@ -1104,7 +1152,7 @@
"Impossible de charger les paramètres de base du plugin %s :\n"
" %s"
-#: ../ConfigTreeNode.py:570
+#: ../ConfigTreeNode.py:590
#, python-format
msgid ""
"Couldn't load confnode parameters %s :\n"
@@ -1113,20 +1161,20 @@
"Impossible de charger les paramètres du plugin %s :\n"
" %s"
-#: ../PLCControler.py:765
-#: ../PLCControler.py:802
+#: ../PLCControler.py:819
+#: ../PLCControler.py:856
msgid "Couldn't paste non-POU object."
msgstr "Impossible de coller autre chose qu'un POU."
-#: ../ProjectController.py:1317
+#: ../ProjectController.py:1344
msgid "Couldn't start PLC !\n"
msgstr "Impossible de démarrer l'automate !\n"
-#: ../ProjectController.py:1325
+#: ../ProjectController.py:1352
msgid "Couldn't stop PLC !\n"
msgstr "Impossible d'arrêter l'automate !\n"
-#: ../ProjectController.py:1295
+#: ../ProjectController.py:1321
msgid "Couldn't stop debugger.\n"
msgstr "Impossible d'arrêter le débogage de l'automate !\n"
@@ -1142,42 +1190,42 @@
msgid "Create a new action"
msgstr "Créer une nouvelle action"
-#: ../IDEFrame.py:135
+#: ../IDEFrame.py:142
msgid "Create a new action block"
msgstr "Créer un nouveau bloc d'actions"
-#: ../IDEFrame.py:84
-#: ../IDEFrame.py:114
-#: ../IDEFrame.py:147
+#: ../IDEFrame.py:91
+#: ../IDEFrame.py:121
+#: ../IDEFrame.py:154
msgid "Create a new block"
msgstr "Créer un nouveau bloc"
-#: ../IDEFrame.py:108
+#: ../IDEFrame.py:115
msgid "Create a new branch"
msgstr "Créer une nouvelle branche"
-#: ../IDEFrame.py:102
+#: ../IDEFrame.py:109
msgid "Create a new coil"
msgstr "Créer un nouveau relai"
-#: ../IDEFrame.py:78
-#: ../IDEFrame.py:93
-#: ../IDEFrame.py:123
+#: ../IDEFrame.py:85
+#: ../IDEFrame.py:100
+#: ../IDEFrame.py:130
msgid "Create a new comment"
msgstr "Créer un nouveau copmmentaire"
-#: ../IDEFrame.py:87
-#: ../IDEFrame.py:117
-#: ../IDEFrame.py:150
+#: ../IDEFrame.py:94
+#: ../IDEFrame.py:124
+#: ../IDEFrame.py:157
msgid "Create a new connection"
msgstr "Créer une nouvelle connexion"
-#: ../IDEFrame.py:105
-#: ../IDEFrame.py:156
+#: ../IDEFrame.py:112
+#: ../IDEFrame.py:163
msgid "Create a new contact"
msgstr "Créer un nouveau contact"
-#: ../IDEFrame.py:138
+#: ../IDEFrame.py:145
msgid "Create a new divergence"
msgstr "Créer une nouvelle divergence"
@@ -1185,45 +1233,45 @@
msgid "Create a new divergence or convergence"
msgstr "Créer une nouvelle divergence ou convergence"
-#: ../IDEFrame.py:126
+#: ../IDEFrame.py:133
msgid "Create a new initial step"
msgstr "Créer une nouvelle étape initiale"
-#: ../IDEFrame.py:141
+#: ../IDEFrame.py:148
msgid "Create a new jump"
msgstr "Créer un nouveau renvoi"
-#: ../IDEFrame.py:96
-#: ../IDEFrame.py:153
+#: ../IDEFrame.py:103
+#: ../IDEFrame.py:160
msgid "Create a new power rail"
msgstr "Créer une nouvelle barre d'alimentation"
-#: ../IDEFrame.py:99
+#: ../IDEFrame.py:106
msgid "Create a new rung"
msgstr "Créer un nouvel échelon"
-#: ../IDEFrame.py:129
+#: ../IDEFrame.py:136
msgid "Create a new step"
msgstr "Créer une nouvelle étape"
-#: ../IDEFrame.py:132
+#: ../IDEFrame.py:139
#: ../dialogs/PouTransitionDialog.py:42
msgid "Create a new transition"
msgstr "Créer une nouvelle transition"
-#: ../IDEFrame.py:81
-#: ../IDEFrame.py:111
-#: ../IDEFrame.py:144
+#: ../IDEFrame.py:88
+#: ../IDEFrame.py:118
+#: ../IDEFrame.py:151
msgid "Create a new variable"
msgstr "Créer une nouvelle variable"
-#: ../IDEFrame.py:346
-#: ../IDEFrame.py:400
+#: ../IDEFrame.py:351
+#: ../IDEFrame.py:405
#: ../editors/Viewer.py:535
msgid "Cut"
msgstr "Couper"
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
msgid "Cyclic"
msgstr "Périodique"
@@ -1239,13 +1287,13 @@
msgid "DEPRECATED"
msgstr "OBSOLETE"
-#: ../canfestival/SlaveEditor.py:50
-#: ../canfestival/NetworkEditor.py:80
+#: ../canfestival/SlaveEditor.py:53
+#: ../canfestival/NetworkEditor.py:74
msgid "DS-301 Profile"
msgstr "Profil DS-301"
-#: ../canfestival/SlaveEditor.py:51
-#: ../canfestival/NetworkEditor.py:81
+#: ../canfestival/SlaveEditor.py:54
+#: ../canfestival/NetworkEditor.py:75
msgid "DS-302 Profile"
msgstr "Profil DS-302"
@@ -1282,60 +1330,61 @@
msgid "Days:"
msgstr "Jours :"
-#: ../ProjectController.py:1405
-msgid "Debug connect matching running PLC\n"
-msgstr "L'automate connecté correspond au project ouvert.\n"
-
-#: ../ProjectController.py:1408
-msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
-msgstr "L'automate connecté ne correspond pas au project ouvert - Arrêter/transférez/démarrer pour pouvoir débogguer.\n"
-
-#: ../controls/PouInstanceVariablesPanel.py:52
+#: ../ProjectController.py:1444
+msgid "Debug does not match PLC - stop/transfert/start to re-enable\n"
+msgstr "Les informations de débogage ne correspond pas l'automate connecté - Arrêter/transférez/démarrer pour pouvoir débogguer.\n"
+
+#: ../controls/PouInstanceVariablesPanel.py:59
msgid "Debug instance"
msgstr "Déboguer l'instance"
-#: ../editors/Viewer.py:3222
+#: ../editors/Viewer.py:1016
+#: ../editors/Viewer.py:3326
#, python-format
msgid "Debug: %s"
-msgstr "Déboggage : %s"
-
-#: ../ProjectController.py:1122
+msgstr "Débogage : %s"
+
+#: ../ProjectController.py:1153
#, python-format
msgid "Debug: Unknown variable '%s'\n"
msgstr "Débogage : Variable '%s' inconnue\n"
-#: ../ProjectController.py:1120
+#: ../ProjectController.py:1151
#, python-format
msgid "Debug: Unsupported type to debug '%s'\n"
msgstr "Débogage : Type non supporté dans le débogage '%'\n"
-#: ../IDEFrame.py:608
+#: ../IDEFrame.py:612
msgid "Debugger"
msgstr "Déboggueur"
-#: ../ProjectController.py:1285
+#: ../ProjectController.py:1311
msgid "Debugger disabled\n"
msgstr "Débogueur désactivé\n"
-#: ../ProjectController.py:1297
+#: ../ProjectController.py:1441
+msgid "Debugger ready\n"
+msgstr "Débogueur \n"
+
+#: ../ProjectController.py:1323
msgid "Debugger stopped.\n"
msgstr "Débogueur désactivé\n"
-#: ../IDEFrame.py:1990
-#: ../Beremiz.py:958
+#: ../IDEFrame.py:1866
+#: ../Beremiz.py:991
#: ../editors/Viewer.py:511
msgid "Delete"
msgstr "Supprimer"
-#: ../editors/Viewer.py:454
+#: ../editors/Viewer.py:453
msgid "Delete Divergence Branch"
msgstr "Supprimer une branche de divergence"
-#: ../editors/FileManagementPanel.py:371
+#: ../editors/FileManagementPanel.py:153
msgid "Delete File"
msgstr "Supprimer un fichier"
-#: ../editors/Viewer.py:443
+#: ../editors/Viewer.py:442
msgid "Delete Wire Segment"
msgstr "Supprimer un segment de fil"
@@ -1347,11 +1396,11 @@
msgid "Deletion (within)"
msgstr "Suppression (au milieu)"
-#: ../editors/DataTypeEditor.py:146
+#: ../editors/DataTypeEditor.py:152
msgid "Derivation Type:"
msgstr "Type de dérivation :"
-#: ../plcopen/structures.py:264
+#: ../plcopen/structures.py:263
msgid ""
"Derivative\n"
"The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
@@ -1359,11 +1408,11 @@
"Dérivée\n"
"Le Function Block derivative produit une sortie XOUT proportionnelle au rapport de changement de l'entrée XIN."
-#: ../controls/VariablePanel.py:360
+#: ../controls/VariablePanel.py:362
msgid "Description:"
msgstr "Description :"
-#: ../editors/DataTypeEditor.py:314
+#: ../editors/DataTypeEditor.py:320
#: ../dialogs/ArrayTypeDialog.py:61
msgid "Dimensions:"
msgstr "Dimensions :"
@@ -1372,23 +1421,23 @@
msgid "Direction"
msgstr "Direction"
-#: ../dialogs/BrowseLocationsDialog.py:78
+#: ../dialogs/BrowseLocationsDialog.py:86
msgid "Direction:"
msgstr "Direction :"
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Directly"
msgstr "Direct"
-#: ../ProjectController.py:1512
+#: ../ProjectController.py:1547
msgid "Disconnect"
msgstr "Déconnecter"
-#: ../ProjectController.py:1514
+#: ../ProjectController.py:1549
msgid "Disconnect from PLC"
msgstr "Déconnecter l'automate"
-#: ../editors/Viewer.py:496
+#: ../editors/Viewer.py:495
msgid "Divergence"
msgstr "Divergence"
@@ -1396,7 +1445,7 @@
msgid "Division"
msgstr "Division"
-#: ../editors/FileManagementPanel.py:370
+#: ../editors/FileManagementPanel.py:152
#, python-format
msgid "Do you really want to delete the file '%s'?"
msgstr "Êtes-vous sûr de vouloir supprimer le fichier '%s' ?"
@@ -1406,11 +1455,11 @@
msgid "Documentation"
msgstr "Documentation"
-#: ../PLCOpenEditor.py:351
+#: ../PLCOpenEditor.py:330
msgid "Done"
msgstr "Terminé"
-#: ../plcopen/structures.py:227
+#: ../plcopen/structures.py:226
msgid ""
"Down-counter\n"
"The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
@@ -1422,11 +1471,11 @@
msgid "Duration"
msgstr "Durée"
-#: ../canfestival/canfestival.py:118
+#: ../canfestival/canfestival.py:139
msgid "EDS files (*.eds)|*.eds|All files|*.*"
msgstr "Fichiers EDS (*.eds)|*.eds|Tous les fichiers|*.*"
-#: ../editors/Viewer.py:510
+#: ../editors/Viewer.py:509
msgid "Edit Block"
msgstr "Editer le block"
@@ -1458,14 +1507,14 @@
msgid "Edit array type properties"
msgstr "Editer les propriétés d'un type de données tableau"
-#: ../editors/Viewer.py:2112
-#: ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630
-#: ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186
+#: ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706
+#: ../editors/Viewer.py:2708
msgid "Edit comment"
msgstr "Editer le commentaire"
-#: ../editors/FileManagementPanel.py:284
+#: ../editors/FileManagementPanel.py:66
msgid "Edit file"
msgstr "Editer un fichier"
@@ -1473,11 +1522,11 @@
msgid "Edit item"
msgstr "Editer l'élément"
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2670
msgid "Edit jump target"
msgstr "Editer la cible du renvoi"
-#: ../ProjectController.py:1526
+#: ../ProjectController.py:1561
msgid "Edit raw IEC code added to code generated by PLCGenerator"
msgstr "Editer le code IEC ajouté au code généré par PLCGenerator"
@@ -1489,35 +1538,35 @@
msgid "Edit transition"
msgstr "Editer la transition"
-#: ../IDEFrame.py:580
+#: ../IDEFrame.py:584
msgid "Editor ToolBar"
msgstr "Barre d'outils d'édition"
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
msgid "Editor selection"
msgstr "Selection d'un éditeur"
-#: ../editors/DataTypeEditor.py:341
+#: ../editors/DataTypeEditor.py:347
msgid "Elements :"
msgstr "Eléments :"
-#: ../IDEFrame.py:343
+#: ../IDEFrame.py:348
msgid "Enable Undo/Redo"
msgstr "Activer Défaire/Refaire"
-#: ../Beremiz_service.py:380
+#: ../Beremiz_service.py:385
msgid "Enter a name "
msgstr "Saisissez un nom"
-#: ../Beremiz_service.py:365
+#: ../Beremiz_service.py:370
msgid "Enter a port number "
msgstr "Saisissez un numéro de port"
-#: ../Beremiz_service.py:355
+#: ../Beremiz_service.py:360
msgid "Enter the IP of the interface to bind"
msgstr "Saisissez l'adresse IP de l'interface à lier"
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Enumerated"
msgstr "Enumération"
@@ -1525,43 +1574,41 @@
msgid "Equal to"
msgstr "Egal à "
-#: ../Beremiz_service.py:270
-#: ../Beremiz_service.py:394
-#: ../controls/VariablePanel.py:330
-#: ../controls/VariablePanel.py:678
-#: ../controls/DebugVariablePanel.py:164
-#: ../IDEFrame.py:1083
-#: ../IDEFrame.py:1672
-#: ../IDEFrame.py:1709
-#: ../IDEFrame.py:1714
-#: ../IDEFrame.py:1728
-#: ../IDEFrame.py:1733
-#: ../IDEFrame.py:2422
-#: ../Beremiz.py:1083
-#: ../PLCOpenEditor.py:358
-#: ../PLCOpenEditor.py:363
-#: ../PLCOpenEditor.py:531
-#: ../PLCOpenEditor.py:541
-#: ../editors/TextViewer.py:376
-#: ../editors/DataTypeEditor.py:543
-#: ../editors/DataTypeEditor.py:548
-#: ../editors/DataTypeEditor.py:572
-#: ../editors/DataTypeEditor.py:577
-#: ../editors/DataTypeEditor.py:587
-#: ../editors/DataTypeEditor.py:719
-#: ../editors/DataTypeEditor.py:726
-#: ../editors/Viewer.py:366
-#: ../editors/LDViewer.py:663
-#: ../editors/LDViewer.py:879
-#: ../editors/LDViewer.py:883
-#: ../editors/FileManagementPanel.py:210
-#: ../ProjectController.py:221
+#: ../Beremiz_service.py:271
+#: ../controls/VariablePanel.py:332
+#: ../controls/VariablePanel.py:681
+#: ../controls/DebugVariablePanel.py:379
+#: ../IDEFrame.py:966
+#: ../IDEFrame.py:1553
+#: ../IDEFrame.py:1590
+#: ../IDEFrame.py:1595
+#: ../IDEFrame.py:1609
+#: ../IDEFrame.py:1614
+#: ../IDEFrame.py:2290
+#: ../Beremiz.py:1131
+#: ../PLCOpenEditor.py:337
+#: ../PLCOpenEditor.py:342
+#: ../PLCOpenEditor.py:416
+#: ../PLCOpenEditor.py:426
+#: ../editors/TextViewer.py:369
+#: ../editors/DataTypeEditor.py:549
+#: ../editors/DataTypeEditor.py:554
+#: ../editors/DataTypeEditor.py:578
+#: ../editors/DataTypeEditor.py:583
+#: ../editors/DataTypeEditor.py:593
+#: ../editors/DataTypeEditor.py:744
+#: ../editors/DataTypeEditor.py:751
+#: ../editors/Viewer.py:365
+#: ../editors/LDViewer.py:666
+#: ../editors/LDViewer.py:882
+#: ../editors/LDViewer.py:886
+#: ../ProjectController.py:225
#: ../dialogs/PouNameDialog.py:53
#: ../dialogs/PouTransitionDialog.py:107
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
#: ../dialogs/ProjectDialog.py:71
#: ../dialogs/SFCStepNameDialog.py:59
-#: ../dialogs/ConnectionDialog.py:152
+#: ../dialogs/ConnectionDialog.py:159
#: ../dialogs/FBDVariableDialog.py:201
#: ../dialogs/PouActionDialog.py:104
#: ../dialogs/BrowseValuesLibraryDialog.py:83
@@ -1574,20 +1621,20 @@
#: ../dialogs/ArrayTypeDialog.py:97
#: ../dialogs/ArrayTypeDialog.py:103
#: ../dialogs/FBDBlockDialog.py:164
-#: ../dialogs/ForceVariableDialog.py:169
+#: ../dialogs/ForceVariableDialog.py:179
msgid "Error"
msgstr "Erreur"
-#: ../ProjectController.py:587
+#: ../ProjectController.py:601
msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
msgstr "Erreur : Au moins une configuration ou une ressource doit être déclarée dans l'automate !\n"
-#: ../ProjectController.py:579
+#: ../ProjectController.py:593
#, python-format
msgid "Error : IEC to C compiler returned %d\n"
msgstr "Erreur : Le compilateur d'IEC en C a retourné %d\n"
-#: ../ProjectController.py:520
+#: ../ProjectController.py:534
#, python-format
msgid ""
"Error in ST/IL/SFC code generator :\n"
@@ -1596,24 +1643,24 @@
"Erreur dans le générateur de code ST/IL/SFC :\n"
"%s\n"
-#: ../ConfigTreeNode.py:182
+#: ../ConfigTreeNode.py:183
#, python-format
msgid "Error while saving \"%s\"\n"
msgstr "Erreur lors de l'enregistrement de \"%s\"\n"
-#: ../canfestival/canfestival.py:122
+#: ../canfestival/canfestival.py:144
msgid "Error: Export slave failed\n"
msgstr "Erreur : L'export de l'esclave a échoué\n"
-#: ../canfestival/canfestival.py:270
+#: ../canfestival/canfestival.py:345
msgid "Error: No Master generated\n"
msgstr "Erreur : Aucun maître généré\n"
-#: ../canfestival/canfestival.py:265
+#: ../canfestival/canfestival.py:340
msgid "Error: No PLC built\n"
msgstr "Erreur : Aucun automate compilé\n"
-#: ../ProjectController.py:1378
+#: ../ProjectController.py:1416
#, python-format
msgid "Exception while connecting %s!\n"
msgstr "Une exception est apparu au cours de la connexion %s !\n"
@@ -1627,7 +1674,7 @@
msgid "Execution Order:"
msgstr "Ordre d'exécution :"
-#: ../features.py:10
+#: ../features.py:11
msgid "Experimental web based HMI"
msgstr "IHM expérimentale utilisant les technologies web"
@@ -1639,15 +1686,16 @@
msgid "Exponentiation"
msgstr "Exponentiel"
-#: ../canfestival/canfestival.py:128
+#: ../canfestival/canfestival.py:150
msgid "Export CanOpen slave to EDS file"
msgstr "Exporter un esclave CANopen sous la forme d'un fichier EDS"
+#: ../controls/DebugVariablePanel.py:1472
#: ../editors/GraphicViewer.py:144
msgid "Export graph values to clipboard"
msgstr "Exporter les valeurs du graphique vers le presse-papier"
-#: ../canfestival/canfestival.py:127
+#: ../canfestival/canfestival.py:149
msgid "Export slave"
msgstr "Exporter un esclave"
@@ -1659,7 +1707,7 @@
msgid "External"
msgstr "Externe"
-#: ../ProjectController.py:591
+#: ../ProjectController.py:605
msgid "Extracting Located Variables...\n"
msgstr "Extraction des variables adressées en cours...\n"
@@ -1670,16 +1718,16 @@
msgid "FBD"
msgstr "FBD"
-#: ../ProjectController.py:1445
+#: ../ProjectController.py:1480
msgid "Failed : Must build before transfer.\n"
msgstr "Echec : Le projet doit être compilé avant d'être transféré.\n"
-#: ../editors/Viewer.py:405
+#: ../editors/Viewer.py:404
#: ../dialogs/LDElementDialog.py:84
msgid "Falling Edge"
msgstr "Front descendant"
-#: ../plcopen/structures.py:217
+#: ../plcopen/structures.py:216
msgid ""
"Falling edge detector\n"
"The output produces a single pulse when a falling edge is detected."
@@ -1687,7 +1735,7 @@
"Détecteur de front descendant\n"
"La sortie produit une impulsion unique lorsqu'un front descendant est détecté."
-#: ../ProjectController.py:900
+#: ../ProjectController.py:927
msgid "Fatal : cannot get builder.\n"
msgstr "Erreur fatale : impossible de trouver un compilateur.\n"
@@ -1701,22 +1749,17 @@
msgid "Fields %s haven't a valid value!"
msgstr "Les champs %s n'ont pas une valeur valide !"
-#: ../editors/FileManagementPanel.py:209
-#, python-format
-msgid "File '%s' already exists!"
-msgstr "Le fichier '%s' existe déjà !"
-
-#: ../IDEFrame.py:353
+#: ../IDEFrame.py:358
#: ../dialogs/FindInPouDialog.py:30
#: ../dialogs/FindInPouDialog.py:99
msgid "Find"
msgstr "Rechercher"
-#: ../IDEFrame.py:355
+#: ../IDEFrame.py:360
msgid "Find Next"
msgstr "Recherche suivante"
-#: ../IDEFrame.py:357
+#: ../IDEFrame.py:362
msgid "Find Previous"
msgstr "Recherche précédente"
@@ -1732,12 +1775,12 @@
msgid "Force runtime reload\n"
msgstr "Redémarrage du runtime forcé\n"
-#: ../controls/DebugVariablePanel.py:295
-#: ../editors/Viewer.py:1353
+#: ../controls/DebugVariablePanel.py:1934
+#: ../editors/Viewer.py:1385
msgid "Force value"
msgstr "Forcer la valeur"
-#: ../dialogs/ForceVariableDialog.py:152
+#: ../dialogs/ForceVariableDialog.py:162
msgid "Forcing Variable Value"
msgstr "Forcer la valeur de la variable"
@@ -1750,7 +1793,7 @@
msgid "Form isn't complete. %s must be filled!"
msgstr "Le formulaire est incomplet. %s doit être complété !"
-#: ../dialogs/ConnectionDialog.py:142
+#: ../dialogs/ConnectionDialog.py:149
#: ../dialogs/FBDBlockDialog.py:154
msgid "Form isn't complete. Name must be filled!"
msgstr "Le formulaire est incomplet. Le nom doit être complété !"
@@ -1771,16 +1814,16 @@
msgid "Function"
msgstr "Fonction"
-#: ../IDEFrame.py:329
+#: ../IDEFrame.py:334
msgid "Function &Block"
msgstr "&Bloc Fonctionnel"
-#: ../IDEFrame.py:1969
+#: ../IDEFrame.py:1845
#: ../dialogs/SearchInProjectDialog.py:45
msgid "Function Block"
msgstr "Bloc fonctionnel"
-#: ../controls/VariablePanel.py:741
+#: ../controls/VariablePanel.py:744
msgid "Function Block Types"
msgstr "Types de blocs fonctionnels"
@@ -1792,11 +1835,7 @@
msgid "Function Blocks can't be used in Functions!"
msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des functions !"
-#: ../editors/Viewer.py:238
-msgid "Function Blocks can't be used in Transitions!"
-msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des transitions"
-
-#: ../PLCControler.py:2055
+#: ../PLCControler.py:2180
#, python-format
msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
msgstr "Le bloc fonctionnel \"%s\" ne peuvent être collés dans une function !"
@@ -1805,11 +1844,11 @@
msgid "Functions"
msgstr "Fonctions"
-#: ../PLCOpenEditor.py:138
+#: ../PLCOpenEditor.py:119
msgid "Generate Program"
msgstr "Générer le program"
-#: ../ProjectController.py:510
+#: ../ProjectController.py:524
msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
msgstr "Création du code ST/IL/SFC de l'automate IEC-61131 en cours...\n"
@@ -1817,6 +1856,7 @@
msgid "Global"
msgstr "Globale"
+#: ../controls/DebugVariablePanel.py:1471
#: ../editors/GraphicViewer.py:131
msgid "Go to current value"
msgstr "Aller à la valeur actuelle"
@@ -1841,7 +1881,7 @@
msgid "Height:"
msgstr "Hauteur :"
-#: ../editors/FileManagementPanel.py:303
+#: ../editors/FileManagementPanel.py:85
msgid "Home Directory:"
msgstr "Répertoire utilisateur :"
@@ -1853,7 +1893,7 @@
msgid "Hours:"
msgstr "Heures :"
-#: ../plcopen/structures.py:279
+#: ../plcopen/structures.py:278
msgid ""
"Hysteresis\n"
"The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
@@ -1861,7 +1901,7 @@
"Hystérésis\n"
"Le bloc functionnel hystérésis fourni un booléen en sortie suivant une courbe d'hystérésis entre les deux entrées réelles (REAL) XIN1 et XIN2."
-#: ../ProjectController.py:827
+#: ../ProjectController.py:851
msgid "IEC-61131-3 code generation failed !\n"
msgstr "La création du code IEC-61131-3 a échouée !\n"
@@ -1871,8 +1911,8 @@
msgid "IL"
msgstr "IL"
-#: ../Beremiz_service.py:356
-#: ../Beremiz_service.py:357
+#: ../Beremiz_service.py:361
+#: ../Beremiz_service.py:362
msgid "IP is not valid!"
msgstr "l'IP est invalide !"
@@ -1882,11 +1922,16 @@
msgstr "Importer un SVG"
#: ../controls/VariablePanel.py:76
+#: ../editors/Viewer.py:1412
#: ../dialogs/FBDVariableDialog.py:34
msgid "InOut"
msgstr "Entrée-Sortie"
-#: ../controls/VariablePanel.py:263
+#: ../editors/Viewer.py:999
+msgid "Inactive"
+msgstr "Inactif"
+
+#: ../controls/VariablePanel.py:265
#, python-format
msgid "Incompatible data types between \"%s\" and \"%s\""
msgstr "Types de donnée imcompatible entre \"%s\" et \"%s\""
@@ -1905,20 +1950,20 @@
msgid "Indicator"
msgstr "Indicateur"
-#: ../editors/Viewer.py:492
+#: ../editors/Viewer.py:491
msgid "Initial Step"
msgstr "Étape initiale"
#: ../controls/VariablePanel.py:58
#: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
+#: ../editors/DataTypeEditor.py:50
msgid "Initial Value"
msgstr "Valeur initiale"
-#: ../editors/DataTypeEditor.py:178
-#: ../editors/DataTypeEditor.py:209
-#: ../editors/DataTypeEditor.py:265
-#: ../editors/DataTypeEditor.py:303
+#: ../editors/DataTypeEditor.py:184
+#: ../editors/DataTypeEditor.py:215
+#: ../editors/DataTypeEditor.py:271
+#: ../editors/DataTypeEditor.py:309
msgid "Initial Value:"
msgstr "Valeur initiale :"
@@ -1933,7 +1978,8 @@
msgstr "Inline"
#: ../controls/VariablePanel.py:76
-#: ../dialogs/BrowseLocationsDialog.py:36
+#: ../editors/Viewer.py:1410
+#: ../dialogs/BrowseLocationsDialog.py:35
#: ../dialogs/FBDVariableDialog.py:33
#: ../dialogs/SFCStepDialog.py:61
msgid "Input"
@@ -1947,16 +1993,16 @@
msgid "Insertion (into)"
msgstr "Insertion (au milieu)"
-#: ../plcopen/plcopen.py:1833
+#: ../plcopen/plcopen.py:1843
#, python-format
msgid "Instance with id %d doesn't exist!"
msgstr "L'instance dont l'id est %d n'existe pas !"
-#: ../editors/ResourceEditor.py:247
+#: ../editors/ResourceEditor.py:255
msgid "Instances:"
msgstr "Instances :"
-#: ../plcopen/structures.py:259
+#: ../plcopen/structures.py:258
msgid ""
"Integral\n"
"The integral function block integrates the value of input XIN over time."
@@ -1968,16 +2014,16 @@
msgid "Interface"
msgstr "Interface"
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
msgid "Interrupt"
msgstr "Interruption"
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Interval"
msgstr "Interval"
-#: ../PLCControler.py:2032
-#: ../PLCControler.py:2070
+#: ../PLCControler.py:2157
+#: ../PLCControler.py:2195
msgid "Invalid plcopen element(s)!!!"
msgstr "Les éléments plcopen ne sont pas valides !!! "
@@ -1987,13 +2033,13 @@
msgid "Invalid type \"%s\"-> %d != %d for location\"%s\""
msgstr "Type invalide \"%s\"-> %d != %d pour cette adresse \"%s\""
-#: ../dialogs/ForceVariableDialog.py:167
+#: ../dialogs/ForceVariableDialog.py:177
#, python-format
msgid "Invalid value \"%s\" for \"%s\" variable!"
msgstr "Valeur \"%s\" invalide pour une variable de type \"%s\" !"
-#: ../controls/DebugVariablePanel.py:153
-#: ../controls/DebugVariablePanel.py:156
+#: ../controls/DebugVariablePanel.py:319
+#: ../controls/DebugVariablePanel.py:322
#, python-format
msgid "Invalid value \"%s\" for debug variable"
msgstr "Chemin de variable à déboguer \"%s\" invalide"
@@ -2018,7 +2064,7 @@
"Valeur invalide !\n"
"Vous devez rentrer une valeur numérique."
-#: ../editors/Viewer.py:497
+#: ../editors/Viewer.py:496
msgid "Jump"
msgstr "Renvoi"
@@ -2051,19 +2097,19 @@
msgid "Language:"
msgstr "Langue :"
-#: ../ProjectController.py:1451
+#: ../ProjectController.py:1486
msgid "Latest build already matches current target. Transfering anyway...\n"
msgstr "La dernière compilation correspond à la cible actuelle...\n"
-#: ../Beremiz_service.py:324
+#: ../Beremiz_service.py:331
msgid "Launch WX GUI inspector"
msgstr "Lancer un inspecteur d'IHM WX"
-#: ../Beremiz_service.py:323
+#: ../Beremiz_service.py:330
msgid "Launch a live Python shell"
msgstr "Lancer une console Python"
-#: ../editors/Viewer.py:428
+#: ../editors/Viewer.py:427
msgid "Left"
msgstr "Gauche"
@@ -2083,7 +2129,7 @@
msgid "Less than or equal to"
msgstr "Inférieur ou égal à "
-#: ../IDEFrame.py:600
+#: ../IDEFrame.py:604
msgid "Library"
msgstr "Librairie"
@@ -2100,7 +2146,11 @@
msgid "Local"
msgstr "Locale"
-#: ../ProjectController.py:1353
+#: ../canfestival/canfestival.py:322
+msgid "Local entries"
+msgstr "Entrées locales"
+
+#: ../ProjectController.py:1391
msgid "Local service discovery failed!\n"
msgstr "Echec de la sélection d'un service!\n"
@@ -2108,14 +2158,10 @@
msgid "Location"
msgstr "Adresse"
-#: ../dialogs/BrowseLocationsDialog.py:61
+#: ../dialogs/BrowseLocationsDialog.py:68
msgid "Locations available:"
msgstr "Adresses disponibles :"
-#: ../Beremiz.py:393
-msgid "Log Console"
-msgstr "Console de log"
-
#: ../plcopen/iec_std.csv:25
msgid "Logarithm to base 10"
msgstr "Logarithme de base 10"
@@ -2125,20 +2171,20 @@
msgid "MDNS resolution failure for '%s'\n"
msgstr "Echec de la résolution MDNS pour '%s'\n"
-#: ../canfestival/SlaveEditor.py:37
-#: ../canfestival/NetworkEditor.py:67
+#: ../canfestival/SlaveEditor.py:41
+#: ../canfestival/NetworkEditor.py:62
msgid "Map Variable"
msgstr "Variable mappable"
-#: ../features.py:6
+#: ../features.py:7
msgid "Map located variables over CANopen"
msgstr "Mappe des variables localisées sur un bus CANopen"
-#: ../canfestival/NetworkEditor.py:89
+#: ../canfestival/NetworkEditor.py:83
msgid "Master"
msgstr "Maître"
-#: ../ConfigTreeNode.py:480
+#: ../ConfigTreeNode.py:500
#, python-format
msgid "Max count (%d) reached for this confnode of type %s "
msgstr "Nombre limite(%d) atteint pour les plugin de type %s"
@@ -2147,15 +2193,15 @@
msgid "Maximum"
msgstr "Maximum"
-#: ../editors/DataTypeEditor.py:232
+#: ../editors/DataTypeEditor.py:238
msgid "Maximum:"
msgstr "Maximum :"
-#: ../dialogs/BrowseLocationsDialog.py:38
+#: ../dialogs/BrowseLocationsDialog.py:37
msgid "Memory"
msgstr "Mémoire"
-#: ../IDEFrame.py:568
+#: ../IDEFrame.py:572
msgid "Menu ToolBar"
msgstr "Barre d'outils du menu principal"
@@ -2163,7 +2209,7 @@
msgid "Microseconds:"
msgstr "Microsecondes :"
-#: ../editors/Viewer.py:433
+#: ../editors/Viewer.py:432
msgid "Middle"
msgstr "Milieu"
@@ -2175,7 +2221,7 @@
msgid "Minimum"
msgstr "Minimum"
-#: ../editors/DataTypeEditor.py:219
+#: ../editors/DataTypeEditor.py:225
msgid "Minimum:"
msgstr "Minimum :"
@@ -2191,8 +2237,8 @@
msgid "Modifier:"
msgstr "Modificateur :"
-#: ../PLCGenerator.py:703
-#: ../PLCGenerator.py:936
+#: ../PLCGenerator.py:732
+#: ../PLCGenerator.py:975
#, python-format
msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
msgstr "Plusieurs connecteurs trouvés pour le prolongement \"%s\" dans le POU \"%s\""
@@ -2205,11 +2251,11 @@
msgid "Move action up"
msgstr "Déplacer une action vers le haut"
-#: ../controls/DebugVariablePanel.py:185
+#: ../controls/DebugVariablePanel.py:1532
msgid "Move debug variable down"
msgstr "Déplacer une variable à déboguer vers le bas"
-#: ../controls/DebugVariablePanel.py:184
+#: ../controls/DebugVariablePanel.py:1531
msgid "Move debug variable up"
msgstr "Déplacer une variable à déboguer vers le haut"
@@ -2217,34 +2263,34 @@
msgid "Move down"
msgstr "Déplacer vers le haut"
-#: ../editors/DataTypeEditor.py:348
+#: ../editors/DataTypeEditor.py:354
msgid "Move element down"
msgstr "Déplcer un élément vers le bas"
-#: ../editors/DataTypeEditor.py:347
+#: ../editors/DataTypeEditor.py:353
msgid "Move element up"
msgstr "Déplacer un élément vers le haut"
-#: ../editors/ResourceEditor.py:254
+#: ../editors/ResourceEditor.py:262
msgid "Move instance down"
msgstr "Déplacer une instance vers le bas"
-#: ../editors/ResourceEditor.py:253
+#: ../editors/ResourceEditor.py:261
msgid "Move instance up"
msgstr "Déplacer une instance vers le haut"
-#: ../editors/ResourceEditor.py:225
+#: ../editors/ResourceEditor.py:233
msgid "Move task down"
msgstr "Déplcer une tâche vers le bas"
-#: ../editors/ResourceEditor.py:224
+#: ../editors/ResourceEditor.py:232
msgid "Move task up"
msgstr "Déplacer une tâche vers le haut"
-#: ../IDEFrame.py:75
-#: ../IDEFrame.py:90
-#: ../IDEFrame.py:120
-#: ../IDEFrame.py:161
+#: ../IDEFrame.py:82
+#: ../IDEFrame.py:97
+#: ../IDEFrame.py:127
+#: ../IDEFrame.py:168
msgid "Move the view"
msgstr "Déplacer la vue"
@@ -2252,11 +2298,13 @@
msgid "Move up"
msgstr "Déplacer vers le bas"
-#: ../controls/VariablePanel.py:381
+#: ../controls/VariablePanel.py:383
+#: ../c_ext/CFileEditor.py:520
msgid "Move variable down"
msgstr "Déplacer une variable vers le bas"
-#: ../controls/VariablePanel.py:380
+#: ../controls/VariablePanel.py:382
+#: ../c_ext/CFileEditor.py:519
msgid "Move variable up"
msgstr "Déplacer une variable vers le haut"
@@ -2268,19 +2316,19 @@
msgid "Multiplication"
msgstr "Multiplication"
-#: ../editors/FileManagementPanel.py:301
+#: ../editors/FileManagementPanel.py:83
msgid "My Computer:"
msgstr "Poste de travail :"
#: ../controls/VariablePanel.py:58
#: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
-#: ../editors/ResourceEditor.py:67
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50
+#: ../editors/ResourceEditor.py:68
+#: ../editors/ResourceEditor.py:77
msgid "Name"
msgstr "Nom"
-#: ../Beremiz_service.py:381
+#: ../Beremiz_service.py:386
msgid "Name must not be null!"
msgstr "Le nom ne doit pas être vide !"
@@ -2296,15 +2344,15 @@
msgid "Natural logarithm"
msgstr "Logarithme népérien"
-#: ../editors/Viewer.py:403
+#: ../editors/Viewer.py:402
#: ../dialogs/LDElementDialog.py:67
msgid "Negated"
msgstr "Inversé"
-#: ../Beremiz.py:307
-#: ../Beremiz.py:342
-#: ../PLCOpenEditor.py:125
-#: ../PLCOpenEditor.py:167
+#: ../Beremiz.py:309
+#: ../Beremiz.py:344
+#: ../PLCOpenEditor.py:106
+#: ../PLCOpenEditor.py:148
msgid "New"
msgstr "Nouveau"
@@ -2312,30 +2360,30 @@
msgid "New item"
msgstr "Nouvel élément"
-#: ../editors/Viewer.py:402
+#: ../editors/Viewer.py:401
msgid "No Modifier"
msgstr "Pas de modificateur"
-#: ../PLCControler.py:2929
+#: ../PLCControler.py:3054
msgid "No PLC project found"
msgstr "Pas de projet d'automate trouvé"
-#: ../ProjectController.py:1478
+#: ../ProjectController.py:1513
msgid "No PLC to transfer (did build succeed ?)\n"
msgstr "Aucun automate à transférer (la compilation a-t-elle réussi ?)\n"
-#: ../PLCGenerator.py:1321
+#: ../PLCGenerator.py:1360
#, python-format
msgid "No body defined in \"%s\" POU"
msgstr "Pas de code défini dans le POU \"%s\""
-#: ../PLCGenerator.py:722
-#: ../PLCGenerator.py:945
+#: ../PLCGenerator.py:751
+#: ../PLCGenerator.py:984
#, python-format
msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
msgstr "Pas de connecteur trouvé pour le prolongement \"%s\" dans le POU \"%s\""
-#: ../PLCOpenEditor.py:370
+#: ../PLCOpenEditor.py:349
msgid ""
"No documentation available.\n"
"Coming soon."
@@ -2343,19 +2391,15 @@
"Pas de documentation.\n"
"Bientôt disponible."
-#: ../PLCGenerator.py:744
+#: ../PLCGenerator.py:773
#, python-format
msgid "No informations found for \"%s\" block"
msgstr "Aucune information trouvée pour le block \"%s\""
-#: ../plcopen/structures.py:167
+#: ../plcopen/structures.py:166
msgid "No output variable found"
msgstr "Pas de variable de sortie trouvée."
-#: ../Beremiz_service.py:394
-msgid "No running PLC"
-msgstr "Aucun automate en cours d'exécution"
-
#: ../controls/SearchResultPanel.py:169
msgid "No search results available."
msgstr "Pas de résultat de recherche disponible."
@@ -2379,16 +2423,11 @@
msgid "No valid value selected!"
msgstr "Aucune valeur valide sélectionnée !"
-#: ../PLCGenerator.py:1319
+#: ../PLCGenerator.py:1358
#, python-format
msgid "No variable defined in \"%s\" POU"
msgstr "Pas de varaibles définies dans le POU \"%s\""
-#: ../canfestival/SlaveEditor.py:49
-#: ../canfestival/NetworkEditor.py:79
-msgid "Node infos"
-msgstr "Propriétés du noeud"
-
#: ../canfestival/config_utils.py:354
#, python-format
msgid "Non existing node ID : %d (variable %s)"
@@ -2419,7 +2458,7 @@
msgid "Numerical"
msgstr "Numérique"
-#: ../plcopen/structures.py:247
+#: ../plcopen/structures.py:246
msgid ""
"Off-delay timer\n"
"The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
@@ -2427,7 +2466,7 @@
"Temporisation avec retard à l'extinction\n"
"La temporisation avec retard à l'extinction peut être utilisé pour retarder le passage de la sortie à l'état faux, d'une période fixe après le passage de l'entrée à l'état faux"
-#: ../plcopen/structures.py:242
+#: ../plcopen/structures.py:241
msgid ""
"On-delay timer\n"
"The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
@@ -2439,10 +2478,10 @@
msgid "Only Elements"
msgstr "Uniquement les éléments"
-#: ../Beremiz.py:309
-#: ../Beremiz.py:343
-#: ../PLCOpenEditor.py:127
-#: ../PLCOpenEditor.py:168
+#: ../Beremiz.py:311
+#: ../Beremiz.py:345
+#: ../PLCOpenEditor.py:108
+#: ../PLCOpenEditor.py:149
msgid "Open"
msgstr "Ouvrir"
@@ -2450,7 +2489,7 @@
msgid "Open Inkscape"
msgstr "Ouverture de Inkscape"
-#: ../ProjectController.py:1530
+#: ../ProjectController.py:1565
msgid "Open a file explorer to manage project files"
msgstr "Ouvrir un explorateur de fichier pour gérer les fichiers de projet"
@@ -2471,29 +2510,30 @@
msgid "Organization (optional):"
msgstr "Groupe (optionnel) :"
-#: ../canfestival/SlaveEditor.py:47
-#: ../canfestival/NetworkEditor.py:77
+#: ../canfestival/SlaveEditor.py:51
+#: ../canfestival/NetworkEditor.py:72
msgid "Other Profile"
msgstr "Autre profil"
#: ../controls/VariablePanel.py:76
-#: ../dialogs/BrowseLocationsDialog.py:37
+#: ../editors/Viewer.py:1411
+#: ../dialogs/BrowseLocationsDialog.py:36
#: ../dialogs/FBDVariableDialog.py:35
#: ../dialogs/SFCStepDialog.py:65
msgid "Output"
msgstr "Sortie"
-#: ../canfestival/SlaveEditor.py:36
-#: ../canfestival/NetworkEditor.py:66
+#: ../canfestival/SlaveEditor.py:40
+#: ../canfestival/NetworkEditor.py:61
msgid "PDO Receive"
msgstr "PDO reçu"
-#: ../canfestival/SlaveEditor.py:35
-#: ../canfestival/NetworkEditor.py:65
+#: ../canfestival/SlaveEditor.py:39
+#: ../canfestival/NetworkEditor.py:60
msgid "PDO Transmit"
msgstr "PDO transmis"
-#: ../plcopen/structures.py:269
+#: ../plcopen/structures.py:268
msgid ""
"PID\n"
"The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
@@ -2505,19 +2545,17 @@
msgid "PLC :\n"
msgstr "Automate :\n"
-#: ../ProjectController.py:1096
-#: ../ProjectController.py:1398
-#, python-format
-msgid "PLC is %s\n"
-msgstr "L'automate est dans l'état %s\n"
-
-#: ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:391
+#: ../Beremiz.py:425
+msgid "PLC Log"
+msgstr "Log de l'automate"
+
+#: ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:370
msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
msgstr "Fichiers PLCOpen (*.xml)|*.xml|Tous les fichiers|*.*"
-#: ../PLCOpenEditor.py:175
-#: ../PLCOpenEditor.py:231
+#: ../PLCOpenEditor.py:156
+#: ../PLCOpenEditor.py:212
msgid "PLCOpenEditor"
msgstr "PLCOpenEditor"
@@ -2537,8 +2575,8 @@
msgid "POU Type:"
msgstr "Type du POU :"
-#: ../Beremiz.py:322
-#: ../PLCOpenEditor.py:141
+#: ../Beremiz.py:324
+#: ../PLCOpenEditor.py:122
msgid "Page Setup"
msgstr "Mise en page..."
@@ -2546,22 +2584,22 @@
msgid "Page Size (optional):"
msgstr "Taille de la page (optionnel) :"
-#: ../PLCOpenEditor.py:476
+#: ../IDEFrame.py:2492
#, python-format
msgid "Page: %d"
msgstr "Page: %d"
-#: ../controls/PouInstanceVariablesPanel.py:41
+#: ../controls/PouInstanceVariablesPanel.py:48
msgid "Parent instance"
msgstr "Instance parent"
-#: ../IDEFrame.py:350
-#: ../IDEFrame.py:402
+#: ../IDEFrame.py:355
+#: ../IDEFrame.py:407
#: ../editors/Viewer.py:537
msgid "Paste"
msgstr "Coller"
-#: ../IDEFrame.py:1900
+#: ../IDEFrame.py:1776
msgid "Paste POU"
msgstr "Coller un POU"
@@ -2573,16 +2611,16 @@
msgid "Pin number:"
msgstr "Nombre de pattes :"
-#: ../editors/Viewer.py:2289
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2363
+#: ../editors/Viewer.py:2670
#: ../editors/SFCViewer.py:696
msgid "Please choose a target"
msgstr "Choisissez une cible"
-#: ../editors/Viewer.py:2112
-#: ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630
-#: ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186
+#: ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706
+#: ../editors/Viewer.py:2708
msgid "Please enter comment text"
msgstr "Saisissez le texte du commentaire"
@@ -2592,16 +2630,16 @@
msgid "Please enter step name"
msgstr "Saisissez le nom de l'étape"
-#: ../dialogs/ForceVariableDialog.py:153
+#: ../dialogs/ForceVariableDialog.py:163
#, python-format
msgid "Please enter value for a \"%s\" variable:"
msgstr "Veuillez entrer la valeur pour une variable de type \"%s\" :"
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
msgid "Port number must be 0 <= port <= 65535!"
msgstr "Le numéro de port doit être compris entre 0 et 65535 !"
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
msgid "Port number must be an integer!"
msgstr "Le numéro de port doit être un entier !"
@@ -2609,7 +2647,7 @@
msgid "Position:"
msgstr "Position :"
-#: ../editors/Viewer.py:476
+#: ../editors/Viewer.py:475
msgid "Power Rail"
msgstr "Barre d'alimentation"
@@ -2617,8 +2655,8 @@
msgid "Power Rail Properties"
msgstr "Propriétés de la barre d'alimentation"
-#: ../Beremiz.py:324
-#: ../PLCOpenEditor.py:143
+#: ../Beremiz.py:326
+#: ../PLCOpenEditor.py:124
msgid "Preview"
msgstr "Aperçu avant impression"
@@ -2633,18 +2671,18 @@
msgid "Preview:"
msgstr "Aperçu :"
-#: ../Beremiz.py:326
-#: ../Beremiz.py:346
-#: ../PLCOpenEditor.py:145
-#: ../PLCOpenEditor.py:171
+#: ../Beremiz.py:328
+#: ../Beremiz.py:348
+#: ../PLCOpenEditor.py:126
+#: ../PLCOpenEditor.py:152
msgid "Print"
msgstr "Imprimer"
-#: ../IDEFrame.py:1155
+#: ../IDEFrame.py:1038
msgid "Print preview"
msgstr "Aperçu avant impression"
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Priority"
msgstr "Priorité"
@@ -2652,6 +2690,11 @@
msgid "Priority:"
msgstr "Priorité :"
+#: ../runtime/PLCObject.py:318
+#, python-format
+msgid "Problem starting PLC : error %d"
+msgstr "Problème au démarrage du PLC : erreur %d"
+
#: ../controls/ProjectPropertiesPanel.py:80
msgid "Product Name (required):"
msgstr "Nom du produit (obligatoire) :"
@@ -2664,12 +2707,12 @@
msgid "Product Version (required):"
msgstr "Version du produit (obligatoire) :"
-#: ../IDEFrame.py:1972
+#: ../IDEFrame.py:1848
#: ../dialogs/SearchInProjectDialog.py:46
msgid "Program"
msgstr "Programme"
-#: ../PLCOpenEditor.py:360
+#: ../PLCOpenEditor.py:339
msgid "Program was successfully generated!"
msgstr "Le programme a été généré avec succès !"
@@ -2682,7 +2725,7 @@
msgstr "Les programmes ne peuvent être utilisés par les autres POUs !"
#: ../controls/ProjectPropertiesPanel.py:84
-#: ../IDEFrame.py:553
+#: ../IDEFrame.py:557
msgid "Project"
msgstr "Projet"
@@ -2691,7 +2734,7 @@
msgid "Project '%s':"
msgstr "Projet '%s' :"
-#: ../ProjectController.py:1529
+#: ../ProjectController.py:1564
msgid "Project Files"
msgstr "Fichiers de projet"
@@ -2703,7 +2746,7 @@
msgid "Project Version (optional):"
msgstr "Version du projet (optionnel) :"
-#: ../PLCControler.py:2916
+#: ../PLCControler.py:3041
msgid ""
"Project file syntax error:\n"
"\n"
@@ -2711,20 +2754,25 @@
"Erreur de syntaxe dans le fichier du projet :\n"
"\n"
+#: ../editors/ProjectNodeEditor.py:14
#: ../dialogs/ProjectDialog.py:32
msgid "Project properties"
msgstr "Propriétés du projet"
-#: ../ConfigTreeNode.py:506
+#: ../ConfigTreeNode.py:526
#, python-format
msgid "Project tree layout do not match confnode.xml %s!=%s "
msgstr "L'organisation du projet ne correspond pas à plugin.xml %s!=%s"
+#: ../dialogs/ConnectionDialog.py:96
+msgid "Propagate Name"
+msgstr "Propager le nom"
+
#: ../PLCControler.py:96
msgid "Properties"
msgstr "Propriétés"
-#: ../plcopen/structures.py:237
+#: ../plcopen/structures.py:236
msgid ""
"Pulse timer\n"
"The pulse timer can be used to generate output pulses of a given time duration."
@@ -2732,7 +2780,11 @@
"Temporisation à impulsion\n"
"La temporisation à impulsion peut être utilisée pour générer sur la sortie des impulsions d'une durée déterminée."
-#: ../features.py:8
+#: ../py_ext/PythonEditor.py:61
+msgid "Python code"
+msgstr "Code Python"
+
+#: ../features.py:9
msgid "Python file"
msgstr "Fichier Python"
@@ -2740,13 +2792,13 @@
msgid "Qualifier"
msgstr "Qualificatif"
-#: ../Beremiz_service.py:328
-#: ../Beremiz.py:329
-#: ../PLCOpenEditor.py:151
+#: ../Beremiz_service.py:333
+#: ../Beremiz.py:331
+#: ../PLCOpenEditor.py:132
msgid "Quit"
msgstr "Quitter"
-#: ../plcopen/structures.py:202
+#: ../plcopen/structures.py:201
msgid ""
"RS bistable\n"
"The RS bistable is a latch where the Reset dominates."
@@ -2754,7 +2806,7 @@
"Bascule RS\n"
"La bascule RS est une bascule où le Reset est dominant."
-#: ../plcopen/structures.py:274
+#: ../plcopen/structures.py:273
msgid ""
"Ramp\n"
"The RAMP function block is modelled on example given in the standard."
@@ -2762,15 +2814,16 @@
"Rampe\n"
"Le bloc fonctionnel RAMP est basé sur l'exemple du standard."
+#: ../controls/DebugVariablePanel.py:1462
#: ../editors/GraphicViewer.py:89
msgid "Range:"
msgstr "Echelle :"
-#: ../ProjectController.py:1525
+#: ../ProjectController.py:1560
msgid "Raw IEC code"
msgstr "Ajout code IEC"
-#: ../plcopen/structures.py:254
+#: ../plcopen/structures.py:253
msgid ""
"Real time clock\n"
"The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
@@ -2778,13 +2831,13 @@
"Horloge temps réel\n"
"L'horloge temps réel est utilisée dans de nombreux cas tels que l'horodatage, la définition des dates et heures dans des rapports de commandes, dans des messages d'alarme et bien d'autres."
-#: ../Beremiz.py:1039
+#: ../Beremiz.py:1072
#, python-format
msgid "Really delete node '%s'?"
msgstr "Êtes-vous sûr de vouloir supprimer le noeud '%s' ?"
-#: ../IDEFrame.py:340
-#: ../IDEFrame.py:398
+#: ../IDEFrame.py:345
+#: ../IDEFrame.py:403
msgid "Redo"
msgstr "Refaire"
@@ -2793,7 +2846,7 @@
msgid "Reference"
msgstr "Référence"
-#: ../IDEFrame.py:408
+#: ../IDEFrame.py:413
#: ../dialogs/DiscoveryDialog.py:105
msgid "Refresh"
msgstr "Actualiser"
@@ -2806,8 +2859,8 @@
msgid "Regular expressions"
msgstr "Expressions régulières"
-#: ../controls/DebugVariablePanel.py:299
-#: ../editors/Viewer.py:1356
+#: ../controls/DebugVariablePanel.py:1938
+#: ../editors/Viewer.py:1388
msgid "Release value"
msgstr "Relacher la valeur"
@@ -2815,7 +2868,7 @@
msgid "Remainder (modulo)"
msgstr "Modulo"
-#: ../Beremiz.py:1040
+#: ../Beremiz.py:1073
#, python-format
msgid "Remove %s node"
msgstr "Enlever un noeud %s"
@@ -2824,39 +2877,40 @@
msgid "Remove action"
msgstr "Supprimer une action"
-#: ../controls/DebugVariablePanel.py:183
+#: ../controls/DebugVariablePanel.py:1530
msgid "Remove debug variable"
msgstr "Supprimer une variable à déboguer"
-#: ../editors/DataTypeEditor.py:346
+#: ../editors/DataTypeEditor.py:352
msgid "Remove element"
msgstr "Supprimer un élément"
-#: ../editors/FileManagementPanel.py:281
+#: ../editors/FileManagementPanel.py:63
msgid "Remove file from left folder"
msgstr "Supprimer un fichier du dossier de gauche"
-#: ../editors/ResourceEditor.py:252
+#: ../editors/ResourceEditor.py:260
msgid "Remove instance"
msgstr "Supprimer une instance"
-#: ../canfestival/NetworkEditor.py:87
+#: ../canfestival/NetworkEditor.py:81
msgid "Remove slave"
msgstr "Enlever l'esclave"
-#: ../editors/ResourceEditor.py:223
+#: ../editors/ResourceEditor.py:231
msgid "Remove task"
msgstr "Supprimer la tâche"
-#: ../controls/VariablePanel.py:379
+#: ../controls/VariablePanel.py:381
+#: ../c_ext/CFileEditor.py:518
msgid "Remove variable"
msgstr "Supprimer une variable"
-#: ../IDEFrame.py:1976
+#: ../IDEFrame.py:1852
msgid "Rename"
msgstr "Renommer"
-#: ../editors/FileManagementPanel.py:399
+#: ../editors/FileManagementPanel.py:181
msgid "Replace File"
msgstr "Remplacer un fichier"
@@ -2872,7 +2926,7 @@
msgid "Reset Execution Order"
msgstr "Réinitialiser l'order d'exécution"
-#: ../IDEFrame.py:423
+#: ../IDEFrame.py:428
msgid "Reset Perspective"
msgstr "Réinitialiser l'interface"
@@ -2892,11 +2946,11 @@
msgid "Retain"
msgstr "Persistante"
-#: ../controls/VariablePanel.py:352
+#: ../controls/VariablePanel.py:354
msgid "Return Type:"
msgstr "Type de retour :"
-#: ../editors/Viewer.py:430
+#: ../editors/Viewer.py:429
msgid "Right"
msgstr "Droite"
@@ -2904,12 +2958,12 @@
msgid "Right PowerRail"
msgstr "Barre d'alimentation à droite"
-#: ../editors/Viewer.py:404
+#: ../editors/Viewer.py:403
#: ../dialogs/LDElementDialog.py:80
msgid "Rising Edge"
msgstr "Front montant"
-#: ../plcopen/structures.py:212
+#: ../plcopen/structures.py:211
msgid ""
"Rising edge detector\n"
"The output produces a single pulse when a rising edge is detected."
@@ -2929,22 +2983,22 @@
msgid "Rounding up/down"
msgstr "Arrondi"
-#: ../ProjectController.py:1493
+#: ../ProjectController.py:1528
msgid "Run"
msgstr "Exécuter"
-#: ../ProjectController.py:841
-#: ../ProjectController.py:850
+#: ../ProjectController.py:865
+#: ../ProjectController.py:874
msgid "Runtime extensions C code generation failed !\n"
msgstr "La génération du code des plugins a échoué !\n"
-#: ../canfestival/SlaveEditor.py:34
-#: ../canfestival/NetworkEditor.py:64
+#: ../canfestival/SlaveEditor.py:38
+#: ../canfestival/NetworkEditor.py:59
msgid "SDO Client"
msgstr "Client SDO"
-#: ../canfestival/SlaveEditor.py:33
-#: ../canfestival/NetworkEditor.py:63
+#: ../canfestival/SlaveEditor.py:37
+#: ../canfestival/NetworkEditor.py:58
msgid "SDO Server"
msgstr "Serveur SDO"
@@ -2953,7 +3007,7 @@
msgid "SFC"
msgstr "SFC"
-#: ../plcopen/structures.py:197
+#: ../plcopen/structures.py:196
msgid ""
"SR bistable\n"
"The SR bistable is a latch where the Set dominates."
@@ -2967,7 +3021,7 @@
msgid "ST"
msgstr "ST"
-#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:326
msgid "ST files (*.st)|*.st|All files|*.*"
msgstr "Fichiers ST (*.st)|*.st|Tous les fichiers|*.*"
@@ -2975,24 +3029,24 @@
msgid "SVG files (*.svg)|*.svg|All files|*.*"
msgstr "Fichiers SVG (*.svg)|*.svg|Tous les fichiers|*.*"
-#: ../features.py:10
+#: ../features.py:11
msgid "SVGUI"
msgstr "SVGUI"
-#: ../Beremiz.py:313
-#: ../Beremiz.py:344
-#: ../PLCOpenEditor.py:134
-#: ../PLCOpenEditor.py:169
+#: ../Beremiz.py:315
+#: ../Beremiz.py:346
+#: ../PLCOpenEditor.py:115
+#: ../PLCOpenEditor.py:150
msgid "Save"
msgstr "Enregistrer"
-#: ../Beremiz.py:345
-#: ../PLCOpenEditor.py:136
-#: ../PLCOpenEditor.py:170
+#: ../Beremiz.py:347
+#: ../PLCOpenEditor.py:117
+#: ../PLCOpenEditor.py:151
msgid "Save As..."
msgstr "Enregistrer sous..."
-#: ../Beremiz.py:315
+#: ../Beremiz.py:317
msgid "Save as"
msgstr "Enregistrer sous..."
@@ -3000,13 +3054,13 @@
msgid "Scope"
msgstr "Contexte"
-#: ../IDEFrame.py:592
+#: ../IDEFrame.py:596
#: ../dialogs/SearchInProjectDialog.py:105
msgid "Search"
msgstr "Rechercher"
-#: ../IDEFrame.py:360
-#: ../IDEFrame.py:404
+#: ../IDEFrame.py:365
+#: ../IDEFrame.py:409
#: ../dialogs/SearchInProjectDialog.py:52
msgid "Search in Project"
msgstr "Rechercher dans le projet"
@@ -3015,25 +3069,26 @@
msgid "Seconds:"
msgstr "Secondes :"
-#: ../IDEFrame.py:366
+#: ../IDEFrame.py:371
msgid "Select All"
msgstr "Tout sélectionner"
+#: ../controls/LocationCellEditor.py:97
#: ../controls/VariablePanel.py:277
-#: ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../editors/TextViewer.py:323
+#: ../editors/Viewer.py:275
msgid "Select a variable class:"
msgstr "Sélectionner une direction pour la variable :"
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
msgid "Select an editor:"
msgstr "Sélectionner un éditeur :"
-#: ../controls/PouInstanceVariablesPanel.py:197
+#: ../controls/PouInstanceVariablesPanel.py:209
msgid "Select an instance"
msgstr "Sélectionnez une instance"
-#: ../IDEFrame.py:576
+#: ../IDEFrame.py:580
msgid "Select an object"
msgstr "Sélectionner un objet"
@@ -3049,7 +3104,7 @@
msgid "Selection Divergence"
msgstr "Divergence simple"
-#: ../plcopen/structures.py:207
+#: ../plcopen/structures.py:206
msgid ""
"Semaphore\n"
"The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
@@ -3073,19 +3128,19 @@
msgid "Shift right"
msgstr "Décalage à droite"
-#: ../ProjectController.py:1519
+#: ../ProjectController.py:1554
msgid "Show IEC code generated by PLCGenerator"
msgstr "Afficher le code IEC généré par PLCGenerator"
-#: ../canfestival/canfestival.py:288
+#: ../canfestival/canfestival.py:363
msgid "Show Master"
msgstr "Afficher le maître"
-#: ../canfestival/canfestival.py:289
+#: ../canfestival/canfestival.py:364
msgid "Show Master generated by config_utils"
msgstr "Afficher le maître généré par config_utils"
-#: ../ProjectController.py:1517
+#: ../ProjectController.py:1552
msgid "Show code"
msgstr "Afficher le code"
@@ -3101,7 +3156,7 @@
msgid "Sine"
msgstr "Sinus"
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Single"
msgstr "Evènement"
@@ -3109,53 +3164,53 @@
msgid "Square root (base 2)"
msgstr "Racine carré (base 2)"
-#: ../plcopen/structures.py:193
+#: ../plcopen/structures.py:192
msgid "Standard function blocks"
msgstr "Blocs fonctionnels standards"
-#: ../Beremiz_service.py:319
-#: ../ProjectController.py:1495
+#: ../Beremiz_service.py:321
+#: ../ProjectController.py:1530
msgid "Start PLC"
msgstr "Démarrer l'automate"
-#: ../ProjectController.py:819
+#: ../ProjectController.py:843
#, python-format
msgid "Start build in %s\n"
msgstr "Début de la compilation dans %s\n"
-#: ../ProjectController.py:1314
+#: ../ProjectController.py:1341
msgid "Starting PLC\n"
msgstr "Démarrer l'automate\n"
-#: ../Beremiz.py:403
+#: ../Beremiz.py:435
msgid "Status ToolBar"
msgstr "Barre d'outils de statut"
-#: ../editors/Viewer.py:493
+#: ../editors/Viewer.py:492
msgid "Step"
msgstr "Étape"
-#: ../ProjectController.py:1498
+#: ../ProjectController.py:1533
msgid "Stop"
msgstr "Arrêter"
-#: ../Beremiz_service.py:320
+#: ../Beremiz_service.py:322
msgid "Stop PLC"
msgstr "Arrêter l'automate"
-#: ../ProjectController.py:1500
+#: ../ProjectController.py:1535
msgid "Stop Running PLC"
msgstr "Arrêter l'automate en cours d'exécution"
-#: ../ProjectController.py:1292
+#: ../ProjectController.py:1318
msgid "Stopping debugger...\n"
msgstr "Arrêt du débogage en cours\n"
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Structure"
msgstr "Structure"
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Subrange"
msgstr "Sous-ensemble"
@@ -3163,7 +3218,7 @@
msgid "Subtraction"
msgstr "Soustraction"
-#: ../ProjectController.py:915
+#: ../ProjectController.py:942
msgid "Successfully built.\n"
msgstr "Compilé avec succès.\n"
@@ -3175,11 +3230,11 @@
msgid "Tangent"
msgstr "Tangente"
-#: ../editors/ResourceEditor.py:76
+#: ../editors/ResourceEditor.py:77
msgid "Task"
msgstr "Tâche"
-#: ../editors/ResourceEditor.py:218
+#: ../editors/ResourceEditor.py:226
msgid "Tasks:"
msgstr "Tâches :"
@@ -3187,7 +3242,7 @@
msgid "Temp"
msgstr "Temporaire"
-#: ../editors/FileManagementPanel.py:398
+#: ../editors/FileManagementPanel.py:180
#, python-format
msgid ""
"The file '%s' already exist.\n"
@@ -3196,22 +3251,22 @@
"Le fichier '%s' existe déjà .\n"
"Voulez-vous le remplacer ?"
-#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:882
msgid "The group of block must be coherent!"
msgstr "Le groupe de blocs doit être cohérent !"
-#: ../IDEFrame.py:1091
-#: ../Beremiz.py:555
+#: ../IDEFrame.py:974
+#: ../Beremiz.py:590
msgid "There are changes, do you want to save?"
msgstr "Le projet a été modifié. Voulez-vous l'enregistrer ?"
-#: ../IDEFrame.py:1709
-#: ../IDEFrame.py:1728
+#: ../IDEFrame.py:1590
+#: ../IDEFrame.py:1609
#, python-format
msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
msgstr "Un POU a pour nom \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
-#: ../IDEFrame.py:1178
+#: ../IDEFrame.py:1061
msgid ""
"There was a problem printing.\n"
"Perhaps your current printer is not set correctly?"
@@ -3219,7 +3274,7 @@
"Un problème est apparu lors de l'impression.\n"
"Peut-être que votre imprimante n'est pas correctement configurée ?"
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
msgid "This option isn't available yet!"
msgstr "Cette option n'a pas encore disponible"
@@ -3267,31 +3322,31 @@
msgid "Time-of-day subtraction"
msgstr "Soustraction d'horodatage"
-#: ../editors/Viewer.py:432
+#: ../editors/Viewer.py:431
msgid "Top"
msgstr "Haut"
-#: ../ProjectController.py:1507
+#: ../ProjectController.py:1542
msgid "Transfer"
msgstr "Transférer"
-#: ../ProjectController.py:1509
+#: ../ProjectController.py:1544
msgid "Transfer PLC"
msgstr "Transférer l'automate"
-#: ../ProjectController.py:1474
+#: ../ProjectController.py:1509
msgid "Transfer completed successfully.\n"
msgstr "Transfert effectué avec succès.\n"
-#: ../ProjectController.py:1476
+#: ../ProjectController.py:1511
msgid "Transfer failed\n"
msgstr "Le transfert a échoué\n"
-#: ../editors/Viewer.py:494
+#: ../editors/Viewer.py:493
msgid "Transition"
msgstr "Transition"
-#: ../PLCGenerator.py:1212
+#: ../PLCGenerator.py:1252
#, python-format
msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
msgstr "Le code de la transition \"%s\" doit contenir une variable de sortie ou un relai dont la référence est son nom"
@@ -3304,17 +3359,17 @@
msgid "Transition Name:"
msgstr "Nom de la transition :"
-#: ../PLCGenerator.py:1301
+#: ../PLCGenerator.py:1340
#, python-format
msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en sortie dans le POU \"%s\" !"
-#: ../PLCGenerator.py:1292
+#: ../PLCGenerator.py:1331
#, python-format
msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en entrée dans le POU \"%s\" !"
-#: ../plcopen/plcopen.py:1442
+#: ../plcopen/plcopen.py:1447
#, python-format
msgid "Transition with name %s doesn't exist!"
msgstr "La transition nommée %s n'existe pas !"
@@ -3323,18 +3378,22 @@
msgid "Transitions"
msgstr "Transitions"
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Triggering"
msgstr "Activation"
#: ../controls/VariablePanel.py:58
#: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50
+#: ../editors/ResourceEditor.py:77
#: ../dialogs/ActionBlockDialog.py:37
msgid "Type"
msgstr "Type"
+#: ../dialogs/BrowseLocationsDialog.py:44
+msgid "Type and derivated"
+msgstr "Type et ses dérivés"
+
#: ../canfestival/config_utils.py:335
#: ../canfestival/config_utils.py:617
#, python-format
@@ -3345,12 +3404,17 @@
msgid "Type conversion"
msgstr "Conversion de type"
-#: ../editors/DataTypeEditor.py:155
+#: ../editors/DataTypeEditor.py:161
msgid "Type infos:"
msgstr "Propriétés du type :"
+#: ../dialogs/BrowseLocationsDialog.py:45
+msgid "Type strict"
+msgstr "Type uniquement"
+
#: ../dialogs/SFCDivergenceDialog.py:51
#: ../dialogs/LDPowerRailDialog.py:51
+#: ../dialogs/BrowseLocationsDialog.py:95
#: ../dialogs/ConnectionDialog.py:52
#: ../dialogs/SFCTransitionDialog.py:53
#: ../dialogs/FBDBlockDialog.py:48
@@ -3368,33 +3432,33 @@
msgid "Unable to get Xenomai's %s \n"
msgstr "Unable to get Xenomai's %s \n"
-#: ../PLCGenerator.py:865
-#: ../PLCGenerator.py:924
+#: ../PLCGenerator.py:904
+#: ../PLCGenerator.py:963
#, python-format
msgid "Undefined block type \"%s\" in \"%s\" POU"
msgstr "Type de block \"%s\" indéfini dans le POU \"%s\""
-#: ../PLCGenerator.py:240
+#: ../PLCGenerator.py:252
#, python-format
msgid "Undefined pou type \"%s\""
msgstr "Type de POU \"%s\" indéterminé !"
-#: ../IDEFrame.py:338
-#: ../IDEFrame.py:397
+#: ../IDEFrame.py:343
+#: ../IDEFrame.py:402
msgid "Undo"
msgstr "Défaire"
-#: ../ProjectController.py:254
+#: ../ProjectController.py:262
msgid "Unknown"
msgstr "Inconnu"
-#: ../editors/Viewer.py:336
+#: ../editors/Viewer.py:335
#, python-format
msgid "Unknown variable \"%s\" for this POU!"
msgstr "Variable \"%s\" inconnue dans ce POU !"
-#: ../ProjectController.py:251
-#: ../ProjectController.py:252
+#: ../ProjectController.py:259
+#: ../ProjectController.py:260
msgid "Unnamed"
msgstr "SansNom"
@@ -3408,7 +3472,7 @@
msgid "Unrecognized data size \"%s\""
msgstr "Taille de donnée \"%s\" non identifié !"
-#: ../plcopen/structures.py:222
+#: ../plcopen/structures.py:221
msgid ""
"Up-counter\n"
"The up-counter can be used to signal when a count has reached a maximum value."
@@ -3416,7 +3480,7 @@
"Compteur incrémental\n"
"Le compteur incrémental peut être utilisé pour signaler lorsque le compteur a atteint la valeur maximale."
-#: ../plcopen/structures.py:232
+#: ../plcopen/structures.py:231
msgid ""
"Up-down counter\n"
"The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
@@ -3424,13 +3488,13 @@
"Compteur bidirectionnel\n"
"Le compteur bidirectionnel a deux entrées CU et CD. Il peut être utilisé pour compter de façon incrémentale ou décrémentale sur l'une ou l'autre des entrées."
-#: ../controls/VariablePanel.py:709
-#: ../editors/DataTypeEditor.py:623
+#: ../controls/VariablePanel.py:712
+#: ../editors/DataTypeEditor.py:631
msgid "User Data Types"
msgstr "Types de donnée du projet"
-#: ../canfestival/SlaveEditor.py:38
-#: ../canfestival/NetworkEditor.py:68
+#: ../canfestival/SlaveEditor.py:42
+#: ../canfestival/NetworkEditor.py:63
msgid "User Type"
msgstr "Type utilisateur"
@@ -3438,7 +3502,7 @@
msgid "User-defined POUs"
msgstr "POUs du projet"
-#: ../controls/DebugVariablePanel.py:40
+#: ../controls/DebugVariablePanel.py:58
#: ../dialogs/ActionBlockDialog.py:37
msgid "Value"
msgstr "Valeur"
@@ -3447,12 +3511,12 @@
msgid "Values"
msgstr "Valeurs"
-#: ../editors/DataTypeEditor.py:252
+#: ../editors/DataTypeEditor.py:258
msgid "Values:"
msgstr "Valeurs"
-#: ../controls/DebugVariablePanel.py:40
-#: ../editors/Viewer.py:466
+#: ../controls/DebugVariablePanel.py:58
+#: ../editors/Viewer.py:465
#: ../dialogs/ActionBlockDialog.py:41
msgid "Variable"
msgstr "Variable"
@@ -3461,14 +3525,15 @@
msgid "Variable Properties"
msgstr "Propriétés de la variable"
+#: ../controls/LocationCellEditor.py:97
#: ../controls/VariablePanel.py:277
-#: ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../editors/TextViewer.py:323
+#: ../editors/Viewer.py:275
msgid "Variable class"
msgstr "Direction de la variable"
-#: ../editors/TextViewer.py:374
-#: ../editors/Viewer.py:338
+#: ../editors/TextViewer.py:367
+#: ../editors/Viewer.py:337
msgid "Variable don't belong to this POU!"
msgstr "La variable n'appartient pas à ce POU !"
@@ -3484,16 +3549,16 @@
msgid "WXGLADE GUI"
msgstr "IHM WXGlade"
-#: ../ProjectController.py:1276
+#: ../ProjectController.py:1302
msgid "Waiting debugger to recover...\n"
msgstr "En attente de la mise en route du déboggueur...\n"
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
#: ../dialogs/PouDialog.py:126
msgid "Warning"
msgstr "Attention"
-#: ../ProjectController.py:515
+#: ../ProjectController.py:529
msgid "Warnings in ST/IL/SFC code generator :\n"
msgstr "Mises en garde du generateur de code ST/IL/SFC :\n"
@@ -3509,7 +3574,7 @@
msgid "Wrap search"
msgstr "Boucler"
-#: ../features.py:9
+#: ../features.py:10
msgid "WxGlade GUI"
msgstr "Interface WxGlade"
@@ -3529,7 +3594,7 @@
"Vous n'avez pas les permissions d'écriture.\n"
"Ouvrir wxGlade tout de même ?"
-#: ../ProjectController.py:220
+#: ../ProjectController.py:224
msgid ""
"You must have permission to work on the project\n"
"Work on a project copy ?"
@@ -3537,11 +3602,11 @@
"Vous n'avez pas la permission de travailler sur le projet.\n"
"Travailler sur une copie du projet ?"
-#: ../editors/LDViewer.py:883
+#: ../editors/LDViewer.py:886
msgid "You must select the block or group of blocks around which a branch should be added!"
msgstr "Vous devez sélectionné le bloc ou le group autour duquel un ebranche doit être ajoutée !"
-#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:666
msgid "You must select the wire where a contact should be added!"
msgstr "Vous devez sélectionner le fil sur lequel le contact doit être ajouté !"
@@ -3551,11 +3616,11 @@
msgid "You must type a name!"
msgstr "Vous devez saisir un nom !"
-#: ../dialogs/ForceVariableDialog.py:165
+#: ../dialogs/ForceVariableDialog.py:175
msgid "You must type a value!"
msgstr "Vous devez saisir une valeur !"
-#: ../IDEFrame.py:414
+#: ../IDEFrame.py:419
msgid "Zoom"
msgstr "Zoom"
@@ -3563,7 +3628,7 @@
msgid "Zoom:"
msgstr "Zoom :"
-#: ../PLCOpenEditor.py:356
+#: ../PLCOpenEditor.py:335
#, python-format
msgid "error: %s\n"
msgstr "erreur: %s\n"
@@ -3573,8 +3638,8 @@
msgid "exited with status %s (pid %s)\n"
msgstr "a quitté avec le status %s (pid %s)\n"
-#: ../PLCOpenEditor.py:508
-#: ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:393
+#: ../PLCOpenEditor.py:395
msgid "file : "
msgstr "fichier :"
@@ -3582,7 +3647,7 @@
msgid "function"
msgstr "fonction"
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
msgid "function : "
msgstr "fonction :"
@@ -3590,7 +3655,7 @@
msgid "functionBlock"
msgstr "Bloc fonctionnel"
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
msgid "line : "
msgstr "ligne :"
@@ -3610,7 +3675,7 @@
msgid "string right of"
msgstr "Caractères à droite de"
-#: ../PLCOpenEditor.py:354
+#: ../PLCOpenEditor.py:333
#, python-format
msgid "warning: %s\n"
msgstr "attention: %s\n"
@@ -3646,9 +3711,6 @@
msgid "CAN_Driver"
msgstr "Driver CAN"
-msgid "Debug_mode"
-msgstr "Mode de débogage"
-
msgid "CExtension"
msgstr "Extension C"
@@ -3700,6 +3762,28 @@
msgid "Disable_Extensions"
msgstr "Disable_Extensions"
+#~ msgid "Debug connect matching running PLC\n"
+#~ msgstr "L'automate connecté correspond au project ouvert.\n"
+
+#~ msgid "File '%s' already exists!"
+#~ msgstr "Le fichier '%s' existe déjà !"
+
+#~ msgid "Function Blocks can't be used in Transitions!"
+#~ msgstr ""
+#~ "Les blocs fonctionnels ne peuvent être utilisés dans des transitions"
+
+#~ msgid "No running PLC"
+#~ msgstr "Aucun automate en cours d'exécution"
+
+#~ msgid "Node infos"
+#~ msgstr "Propriétés du noeud"
+
+#~ msgid "PLC is %s\n"
+#~ msgstr "L'automate est dans l'état %s\n"
+
+#~ msgid "Debug_mode"
+#~ msgstr "Mode de débogage"
+
#, fuzzy
#~ msgid "Close Project\tCTRL+SHIFT+W"
#~ msgstr ""
@@ -3814,9 +3898,6 @@
#~ msgid "Block Types"
#~ msgstr "Types de blocs"
-#~ msgid "CSV Log"
-#~ msgstr "Log CVS"
-
#~ msgid "Delete Task"
#~ msgstr "Supprimer une tâche"
@@ -3844,9 +3925,6 @@
#~ msgid "Plugins"
#~ msgstr "Plugins"
-#~ msgid "Types"
-#~ msgstr "Types"
-
#~ msgid "Create a new POU from"
#~ msgstr "Créer un nouveau POU à partir de"
--- a/i18n/messages.pot Wed Mar 13 12:34:55 2013 +0900
+++ b/i18n/messages.pot Wed Jul 31 10:45:07 2013 +0900
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-09-07 01:17+0200\n"
+"POT-Creation-Date: 2013-03-26 22:55+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +17,7 @@
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ../PLCOpenEditor.py:520
+#: ../PLCOpenEditor.py:405
msgid ""
"\n"
"An error has occurred.\n"
@@ -30,7 +30,7 @@
"Error:\n"
msgstr ""
-#: ../Beremiz.py:1071
+#: ../Beremiz.py:1119
#, python-format
msgid ""
"\n"
@@ -69,7 +69,7 @@
msgid " Temp"
msgstr ""
-#: ../PLCOpenEditor.py:530
+#: ../PLCOpenEditor.py:415
msgid " : "
msgstr ""
@@ -79,7 +79,7 @@
msgid " and %s"
msgstr ""
-#: ../ProjectController.py:890
+#: ../ProjectController.py:917
msgid " generation failed !\n"
msgstr ""
@@ -103,7 +103,7 @@
msgid "\"%s\" can't use itself!"
msgstr ""
-#: ../IDEFrame.py:1706 ../IDEFrame.py:1725
+#: ../IDEFrame.py:1587 ../IDEFrame.py:1606
#, python-format
msgid "\"%s\" config already exists!"
msgstr ""
@@ -113,38 +113,38 @@
msgid "\"%s\" configuration already exists !!!"
msgstr ""
-#: ../IDEFrame.py:1660
+#: ../IDEFrame.py:1541
#, python-format
msgid "\"%s\" data type already exists!"
msgstr ""
-#: ../PLCControler.py:2040 ../PLCControler.py:2044
+#: ../PLCControler.py:2165 ../PLCControler.py:2169
#, python-format
msgid "\"%s\" element can't be pasted here!!!"
msgstr ""
-#: ../editors/TextViewer.py:305 ../editors/TextViewer.py:325
-#: ../editors/Viewer.py:252 ../dialogs/PouTransitionDialog.py:105
-#: ../dialogs/ConnectionDialog.py:150 ../dialogs/PouActionDialog.py:102
+#: ../editors/TextViewer.py:298 ../editors/TextViewer.py:318
+#: ../editors/Viewer.py:250 ../dialogs/PouTransitionDialog.py:105
+#: ../dialogs/ConnectionDialog.py:157 ../dialogs/PouActionDialog.py:102
#: ../dialogs/FBDBlockDialog.py:162
#, python-format
msgid "\"%s\" element for this pou already exists!"
msgstr ""
-#: ../Beremiz.py:894
+#: ../Beremiz.py:921
#, python-format
msgid "\"%s\" folder is not a valid Beremiz project\n"
msgstr ""
-#: ../plcopen/structures.py:106
+#: ../plcopen/structures.py:105
#, python-format
msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
msgstr ""
-#: ../controls/VariablePanel.py:656 ../IDEFrame.py:1651
-#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:577
+#: ../controls/VariablePanel.py:659 ../IDEFrame.py:1532
+#: ../editors/DataTypeEditor.py:554 ../editors/DataTypeEditor.py:583
#: ../dialogs/PouNameDialog.py:49 ../dialogs/PouTransitionDialog.py:101
-#: ../dialogs/SFCStepNameDialog.py:51 ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/SFCStepNameDialog.py:51 ../dialogs/ConnectionDialog.py:153
#: ../dialogs/FBDVariableDialog.py:199 ../dialogs/PouActionDialog.py:98
#: ../dialogs/PouDialog.py:118 ../dialogs/SFCStepDialog.py:122
#: ../dialogs/FBDBlockDialog.py:158
@@ -152,41 +152,41 @@
msgid "\"%s\" is a keyword. It can't be used!"
msgstr ""
-#: ../editors/Viewer.py:240
+#: ../editors/Viewer.py:238
#, python-format
msgid "\"%s\" is already used by \"%s\"!"
msgstr ""
-#: ../plcopen/plcopen.py:2786
+#: ../plcopen/plcopen.py:2836
#, python-format
msgid "\"%s\" is an invalid value!"
msgstr ""
-#: ../PLCOpenEditor.py:362 ../PLCOpenEditor.py:399
+#: ../PLCOpenEditor.py:341 ../PLCOpenEditor.py:378
#, python-format
msgid "\"%s\" is not a valid folder!"
msgstr ""
-#: ../controls/VariablePanel.py:654 ../IDEFrame.py:1649
-#: ../editors/DataTypeEditor.py:572 ../dialogs/PouNameDialog.py:47
+#: ../controls/VariablePanel.py:657 ../IDEFrame.py:1530
+#: ../editors/DataTypeEditor.py:578 ../dialogs/PouNameDialog.py:47
#: ../dialogs/PouTransitionDialog.py:99 ../dialogs/SFCStepNameDialog.py:49
-#: ../dialogs/ConnectionDialog.py:144 ../dialogs/PouActionDialog.py:96
+#: ../dialogs/ConnectionDialog.py:151 ../dialogs/PouActionDialog.py:96
#: ../dialogs/PouDialog.py:116 ../dialogs/SFCStepDialog.py:120
#: ../dialogs/FBDBlockDialog.py:156
#, python-format
msgid "\"%s\" is not a valid identifier!"
msgstr ""
-#: ../IDEFrame.py:214 ../IDEFrame.py:2445 ../IDEFrame.py:2464
+#: ../IDEFrame.py:221 ../IDEFrame.py:2313 ../IDEFrame.py:2332
#, python-format
msgid "\"%s\" is used by one or more POUs. It can't be removed!"
msgstr ""
-#: ../controls/VariablePanel.py:311 ../IDEFrame.py:1669
-#: ../editors/TextViewer.py:303 ../editors/TextViewer.py:323
-#: ../editors/TextViewer.py:360 ../editors/Viewer.py:250
-#: ../editors/Viewer.py:295 ../editors/Viewer.py:312
-#: ../dialogs/ConnectionDialog.py:148 ../dialogs/PouDialog.py:120
+#: ../controls/VariablePanel.py:313 ../IDEFrame.py:1550
+#: ../editors/TextViewer.py:296 ../editors/TextViewer.py:316
+#: ../editors/TextViewer.py:353 ../editors/Viewer.py:248
+#: ../editors/Viewer.py:293 ../editors/Viewer.py:311
+#: ../dialogs/ConnectionDialog.py:155 ../dialogs/PouDialog.py:120
#: ../dialogs/FBDBlockDialog.py:160
#, python-format
msgid "\"%s\" pou already exists!"
@@ -207,29 +207,29 @@
msgid "\"%s\" step already exists!"
msgstr ""
-#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:549
#, python-format
msgid "\"%s\" value already defined!"
msgstr ""
-#: ../editors/DataTypeEditor.py:719 ../dialogs/ArrayTypeDialog.py:97
+#: ../editors/DataTypeEditor.py:744 ../dialogs/ArrayTypeDialog.py:97
#, python-format
msgid "\"%s\" value isn't a valid array dimension!"
msgstr ""
-#: ../editors/DataTypeEditor.py:726 ../dialogs/ArrayTypeDialog.py:103
+#: ../editors/DataTypeEditor.py:751 ../dialogs/ArrayTypeDialog.py:103
#, python-format
msgid ""
"\"%s\" value isn't a valid array dimension!\n"
"Right value must be greater than left value."
msgstr ""
-#: ../PLCControler.py:793
+#: ../PLCControler.py:847
#, python-format
msgid "%s \"%s\" can't be pasted as a %s."
msgstr ""
-#: ../PLCControler.py:1422
+#: ../PLCControler.py:1476
#, python-format
msgid "%s Data Types"
msgstr ""
@@ -239,86 +239,86 @@
msgid "%s Graphics"
msgstr ""
-#: ../PLCControler.py:1417
+#: ../PLCControler.py:1471
#, python-format
msgid "%s POUs"
msgstr ""
-#: ../canfestival/SlaveEditor.py:42 ../canfestival/NetworkEditor.py:72
+#: ../canfestival/SlaveEditor.py:46 ../canfestival/NetworkEditor.py:67
#, python-format
msgid "%s Profile"
msgstr ""
-#: ../plcopen/plcopen.py:1780 ../plcopen/plcopen.py:1790
-#: ../plcopen/plcopen.py:1800 ../plcopen/plcopen.py:1810
-#: ../plcopen/plcopen.py:1819
+#: ../plcopen/plcopen.py:1790 ../plcopen/plcopen.py:1800
+#: ../plcopen/plcopen.py:1810 ../plcopen/plcopen.py:1820
+#: ../plcopen/plcopen.py:1829
#, python-format
msgid "%s body don't have instances!"
msgstr ""
-#: ../plcopen/plcopen.py:1842 ../plcopen/plcopen.py:1849
+#: ../plcopen/plcopen.py:1852 ../plcopen/plcopen.py:1859
#, python-format
msgid "%s body don't have text!"
msgstr ""
-#: ../IDEFrame.py:364
+#: ../IDEFrame.py:369
msgid "&Add Element"
msgstr ""
-#: ../IDEFrame.py:334
+#: ../IDEFrame.py:339
msgid "&Configuration"
msgstr ""
-#: ../IDEFrame.py:325
+#: ../IDEFrame.py:330
msgid "&Data Type"
msgstr ""
-#: ../IDEFrame.py:368
+#: ../IDEFrame.py:373
msgid "&Delete"
msgstr ""
-#: ../IDEFrame.py:317
+#: ../IDEFrame.py:322
msgid "&Display"
msgstr ""
-#: ../IDEFrame.py:316
+#: ../IDEFrame.py:321
msgid "&Edit"
msgstr ""
-#: ../IDEFrame.py:315
+#: ../IDEFrame.py:320
msgid "&File"
msgstr ""
-#: ../IDEFrame.py:327
+#: ../IDEFrame.py:332
msgid "&Function"
msgstr ""
-#: ../IDEFrame.py:318
+#: ../IDEFrame.py:323
msgid "&Help"
msgstr ""
-#: ../IDEFrame.py:331
+#: ../IDEFrame.py:336
msgid "&Program"
msgstr ""
-#: ../PLCOpenEditor.py:148
+#: ../PLCOpenEditor.py:129
msgid "&Properties"
msgstr ""
-#: ../Beremiz.py:310
+#: ../Beremiz.py:312
msgid "&Recent Projects"
msgstr ""
-#: ../Beremiz.py:352
+#: ../Beremiz.py:354
msgid "&Resource"
msgstr ""
-#: ../controls/SearchResultPanel.py:237
+#: ../controls/SearchResultPanel.py:252
#, python-format
msgid "'%s' - %d match in project"
msgstr ""
-#: ../controls/SearchResultPanel.py:239
+#: ../controls/SearchResultPanel.py:254
#, python-format
msgid "'%s' - %d matches in project"
msgstr ""
@@ -328,12 +328,12 @@
msgid "'%s' is located at %s\n"
msgstr ""
-#: ../controls/SearchResultPanel.py:289
+#: ../controls/SearchResultPanel.py:304
#, python-format
msgid "(%d matches)"
msgstr ""
-#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510 ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:393 ../PLCOpenEditor.py:395 ../PLCOpenEditor.py:396
msgid ", "
msgstr ""
@@ -343,21 +343,21 @@
msgid ", %s"
msgstr ""
-#: ../PLCOpenEditor.py:506
+#: ../PLCOpenEditor.py:391
msgid ". "
msgstr ""
-#: ../ProjectController.py:1268
+#: ../ProjectController.py:1294
msgid "... debugger recovered\n"
msgstr ""
-#: ../IDEFrame.py:1672 ../IDEFrame.py:1714 ../IDEFrame.py:1733
+#: ../IDEFrame.py:1553 ../IDEFrame.py:1595 ../IDEFrame.py:1614
#: ../dialogs/PouDialog.py:122
#, python-format
msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
msgstr ""
-#: ../controls/VariablePanel.py:658 ../IDEFrame.py:1684 ../IDEFrame.py:1695
+#: ../controls/VariablePanel.py:661 ../IDEFrame.py:1565 ../IDEFrame.py:1576
#: ../dialogs/PouNameDialog.py:51 ../dialogs/PouTransitionDialog.py:103
#: ../dialogs/SFCStepNameDialog.py:53 ../dialogs/PouActionDialog.py:100
#: ../dialogs/SFCStepDialog.py:124
@@ -365,30 +365,30 @@
msgid "A POU named \"%s\" already exists!"
msgstr ""
-#: ../ConfigTreeNode.py:371
+#: ../ConfigTreeNode.py:388
#, python-format
msgid "A child named \"%s\" already exist -> \"%s\"\n"
msgstr ""
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
msgid "A location must be selected!"
msgstr ""
-#: ../controls/VariablePanel.py:660 ../IDEFrame.py:1686 ../IDEFrame.py:1697
+#: ../controls/VariablePanel.py:663 ../IDEFrame.py:1567 ../IDEFrame.py:1578
#: ../dialogs/SFCStepNameDialog.py:55 ../dialogs/SFCStepDialog.py:126
#, python-format
msgid "A variable with \"%s\" as name already exists in this pou!"
msgstr ""
-#: ../Beremiz.py:362 ../PLCOpenEditor.py:181
+#: ../Beremiz.py:364 ../PLCOpenEditor.py:162
msgid "About"
msgstr ""
-#: ../Beremiz.py:931
+#: ../Beremiz.py:957
msgid "About Beremiz"
msgstr ""
-#: ../PLCOpenEditor.py:376
+#: ../PLCOpenEditor.py:355
msgid "About PLCOpenEditor"
msgstr ""
@@ -400,7 +400,7 @@
msgid "Action"
msgstr ""
-#: ../editors/Viewer.py:495
+#: ../editors/Viewer.py:494
msgid "Action Block"
msgstr ""
@@ -412,7 +412,7 @@
msgid "Action Name:"
msgstr ""
-#: ../plcopen/plcopen.py:1480
+#: ../plcopen/plcopen.py:1490
#, python-format
msgid "Action with name %s doesn't exist!"
msgstr ""
@@ -425,28 +425,32 @@
msgid "Actions:"
msgstr ""
-#: ../canfestival/SlaveEditor.py:54 ../canfestival/NetworkEditor.py:84
-#: ../editors/Viewer.py:527
+#: ../editors/Viewer.py:999
+msgid "Active"
+msgstr ""
+
+#: ../canfestival/SlaveEditor.py:57 ../canfestival/NetworkEditor.py:78
+#: ../Beremiz.py:987 ../editors/Viewer.py:527
msgid "Add"
msgstr ""
-#: ../IDEFrame.py:1925 ../IDEFrame.py:1956
+#: ../IDEFrame.py:1801 ../IDEFrame.py:1832
msgid "Add Action"
msgstr ""
-#: ../features.py:7
+#: ../features.py:8
msgid "Add C code accessing located variables synchronously"
msgstr ""
-#: ../IDEFrame.py:1908
+#: ../IDEFrame.py:1784
msgid "Add Configuration"
msgstr ""
-#: ../IDEFrame.py:1888
+#: ../IDEFrame.py:1764
msgid "Add DataType"
msgstr ""
-#: ../editors/Viewer.py:453
+#: ../editors/Viewer.py:452
msgid "Add Divergence Branch"
msgstr ""
@@ -454,23 +458,23 @@
msgid "Add IP"
msgstr ""
-#: ../IDEFrame.py:1896
+#: ../IDEFrame.py:1772
msgid "Add POU"
msgstr ""
-#: ../features.py:8
+#: ../features.py:9
msgid "Add Python code executed asynchronously"
msgstr ""
-#: ../IDEFrame.py:1936 ../IDEFrame.py:1982
+#: ../IDEFrame.py:1812 ../IDEFrame.py:1858
msgid "Add Resource"
msgstr ""
-#: ../IDEFrame.py:1914 ../IDEFrame.py:1953
+#: ../IDEFrame.py:1790 ../IDEFrame.py:1829
msgid "Add Transition"
msgstr ""
-#: ../editors/Viewer.py:442
+#: ../editors/Viewer.py:441
msgid "Add Wire Segment"
msgstr ""
@@ -478,7 +482,7 @@
msgid "Add a new initial step"
msgstr ""
-#: ../editors/Viewer.py:2289 ../editors/SFCViewer.py:696
+#: ../editors/Viewer.py:2363 ../editors/SFCViewer.py:696
msgid "Add a new jump"
msgstr ""
@@ -486,7 +490,7 @@
msgid "Add a new step"
msgstr ""
-#: ../features.py:9
+#: ../features.py:10
msgid "Add a simple WxGlade based GUI."
msgstr ""
@@ -494,23 +498,23 @@
msgid "Add action"
msgstr ""
-#: ../editors/DataTypeEditor.py:345
+#: ../editors/DataTypeEditor.py:351
msgid "Add element"
msgstr ""
-#: ../editors/ResourceEditor.py:251
+#: ../editors/ResourceEditor.py:259
msgid "Add instance"
msgstr ""
-#: ../canfestival/NetworkEditor.py:86
+#: ../canfestival/NetworkEditor.py:80
msgid "Add slave"
msgstr ""
-#: ../editors/ResourceEditor.py:222
+#: ../editors/ResourceEditor.py:230
msgid "Add task"
msgstr ""
-#: ../controls/VariablePanel.py:378
+#: ../controls/VariablePanel.py:380 ../c_ext/CFileEditor.py:517
msgid "Add variable"
msgstr ""
@@ -518,16 +522,22 @@
msgid "Addition"
msgstr ""
-#: ../plcopen/structures.py:250
+#: ../plcopen/structures.py:249
msgid "Additional function blocks"
msgstr ""
-#: ../editors/Viewer.py:1395
+#: ../editors/Viewer.py:510
+msgid "Adjust Block Size"
+msgstr ""
+
+#: ../editors/Viewer.py:1458
msgid "Alignment"
msgstr ""
-#: ../controls/VariablePanel.py:75 ../dialogs/BrowseLocationsDialog.py:35
-#: ../dialogs/BrowseLocationsDialog.py:116
+#: ../controls/VariablePanel.py:75 ../dialogs/BrowseLocationsDialog.py:34
+#: ../dialogs/BrowseLocationsDialog.py:43
+#: ../dialogs/BrowseLocationsDialog.py:136
+#: ../dialogs/BrowseLocationsDialog.py:139
msgid "All"
msgstr ""
@@ -535,15 +545,19 @@
msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
msgstr ""
-#: ../ProjectController.py:1335
+#: ../ProjectController.py:1373
msgid "Already connected. Please disconnect\n"
msgstr ""
-#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:593
#, python-format
msgid "An element named \"%s\" already exists in this structure!"
msgstr ""
+#: ../dialogs/ConnectionDialog.py:98
+msgid "Apply name modification to all continuations with the same name"
+msgstr ""
+
#: ../plcopen/iec_std.csv:31
msgid "Arc cosine"
msgstr ""
@@ -560,7 +574,8 @@
msgid "Arithmetic"
msgstr ""
-#: ../controls/VariablePanel.py:729 ../editors/DataTypeEditor.py:52
+#: ../controls/VariablePanel.py:732 ../editors/DataTypeEditor.py:54
+#: ../editors/DataTypeEditor.py:634
msgid "Array"
msgstr ""
@@ -597,16 +612,16 @@
msgid "Bad location size : %s"
msgstr ""
-#: ../editors/DataTypeEditor.py:168 ../editors/DataTypeEditor.py:198
-#: ../editors/DataTypeEditor.py:290 ../dialogs/ArrayTypeDialog.py:55
+#: ../editors/DataTypeEditor.py:174 ../editors/DataTypeEditor.py:204
+#: ../editors/DataTypeEditor.py:296 ../dialogs/ArrayTypeDialog.py:55
msgid "Base Type:"
msgstr ""
-#: ../controls/VariablePanel.py:699 ../editors/DataTypeEditor.py:617
+#: ../controls/VariablePanel.py:702 ../editors/DataTypeEditor.py:624
msgid "Base Types"
msgstr ""
-#: ../Beremiz.py:486
+#: ../Beremiz.py:511
msgid "Beremiz"
msgstr ""
@@ -638,7 +653,7 @@
msgid "Bitwise inverting"
msgstr ""
-#: ../editors/Viewer.py:465
+#: ../editors/Viewer.py:464
msgid "Block"
msgstr ""
@@ -646,7 +661,7 @@
msgid "Block Properties"
msgstr ""
-#: ../editors/Viewer.py:434
+#: ../editors/Viewer.py:433
msgid "Bottom"
msgstr ""
@@ -655,31 +670,35 @@
msgid "Browse %s values library"
msgstr ""
-#: ../dialogs/BrowseLocationsDialog.py:55
+#: ../dialogs/BrowseLocationsDialog.py:61
msgid "Browse Locations"
msgstr ""
-#: ../ProjectController.py:1484
+#: ../ProjectController.py:1519
msgid "Build"
msgstr ""
-#: ../ProjectController.py:1051
+#: ../ProjectController.py:1079
msgid "Build directory already clean\n"
msgstr ""
-#: ../ProjectController.py:1485
+#: ../ProjectController.py:1520
msgid "Build project into build folder"
msgstr ""
-#: ../ProjectController.py:910
+#: ../ProjectController.py:937
msgid "C Build crashed !\n"
msgstr ""
-#: ../ProjectController.py:907
+#: ../ProjectController.py:934
msgid "C Build failed.\n"
msgstr ""
-#: ../ProjectController.py:895
+#: ../c_ext/CFileEditor.py:731
+msgid "C code"
+msgstr ""
+
+#: ../ProjectController.py:922
msgid "C code generated successfully.\n"
msgstr ""
@@ -688,16 +707,24 @@
msgid "C compilation of %s failed.\n"
msgstr ""
+#: ../features.py:8
+msgid "C extension"
+msgstr ""
+
+#: ../canfestival/NetworkEditor.py:29
+msgid "CANOpen network"
+msgstr ""
+
+#: ../canfestival/SlaveEditor.py:21
+msgid "CANOpen slave"
+msgstr ""
+
#: ../features.py:7
-msgid "C extension"
-msgstr ""
-
-#: ../features.py:6
msgid "CANopen support"
msgstr ""
-#: ../plcopen/plcopen.py:1722 ../plcopen/plcopen.py:1736
-#: ../plcopen/plcopen.py:1757 ../plcopen/plcopen.py:1773
+#: ../plcopen/plcopen.py:1732 ../plcopen/plcopen.py:1746
+#: ../plcopen/plcopen.py:1767 ../plcopen/plcopen.py:1783
msgid "Can only generate execution order on FBD networks!"
msgstr ""
@@ -705,7 +732,7 @@
msgid "Can only give a location to local or global variables"
msgstr ""
-#: ../PLCOpenEditor.py:357
+#: ../PLCOpenEditor.py:336
#, python-format
msgid "Can't generate program to file %s!"
msgstr ""
@@ -714,21 +741,21 @@
msgid "Can't give a location to a function block instance"
msgstr ""
-#: ../PLCOpenEditor.py:397
+#: ../PLCOpenEditor.py:376
#, python-format
msgid "Can't save project to file %s!"
msgstr ""
-#: ../controls/VariablePanel.py:298
+#: ../controls/VariablePanel.py:300
msgid "Can't set an initial value to a function block instance"
msgstr ""
-#: ../ConfigTreeNode.py:470
+#: ../ConfigTreeNode.py:490
#, python-format
msgid "Cannot create child %s of type %s "
msgstr ""
-#: ../ConfigTreeNode.py:400
+#: ../ConfigTreeNode.py:417
#, python-format
msgid "Cannot find lower free IEC channel than %d\n"
msgstr ""
@@ -737,7 +764,7 @@
msgid "Cannot get PLC status - connection failed.\n"
msgstr ""
-#: ../ProjectController.py:715
+#: ../ProjectController.py:737
msgid "Cannot open/parse VARIABLES.csv!\n"
msgstr ""
@@ -750,27 +777,27 @@
msgid "Case sensitive"
msgstr ""
-#: ../editors/Viewer.py:429
+#: ../editors/Viewer.py:428
msgid "Center"
msgstr ""
-#: ../Beremiz_service.py:322
+#: ../Beremiz_service.py:326
msgid "Change IP of interface to bind"
msgstr ""
-#: ../Beremiz_service.py:321
+#: ../Beremiz_service.py:325
msgid "Change Name"
msgstr ""
-#: ../IDEFrame.py:1974
+#: ../IDEFrame.py:1850
msgid "Change POU Type To"
msgstr ""
-#: ../Beremiz_service.py:325
+#: ../Beremiz_service.py:327
msgid "Change Port Number"
msgstr ""
-#: ../Beremiz_service.py:327
+#: ../Beremiz_service.py:328
msgid "Change working directory"
msgstr ""
@@ -782,16 +809,16 @@
msgid "Choose a SVG file"
msgstr ""
-#: ../ProjectController.py:353
+#: ../ProjectController.py:364
msgid "Choose a directory to save project"
msgstr ""
-#: ../canfestival/canfestival.py:118 ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:347 ../PLCOpenEditor.py:391
+#: ../canfestival/canfestival.py:136 ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:326 ../PLCOpenEditor.py:370
msgid "Choose a file"
msgstr ""
-#: ../Beremiz.py:831 ../Beremiz.py:866
+#: ../Beremiz.py:858 ../Beremiz.py:893
msgid "Choose a project"
msgstr ""
@@ -800,15 +827,15 @@
msgid "Choose a value for %s:"
msgstr ""
-#: ../Beremiz_service.py:373
+#: ../Beremiz_service.py:378
msgid "Choose a working directory "
msgstr ""
-#: ../ProjectController.py:281
+#: ../ProjectController.py:288
msgid "Chosen folder doesn't contain a program. It's not a valid project!"
msgstr ""
-#: ../ProjectController.py:247
+#: ../ProjectController.py:255
msgid "Chosen folder isn't empty. You can't use it for a new project!"
msgstr ""
@@ -816,7 +843,7 @@
msgid "Class"
msgstr ""
-#: ../controls/VariablePanel.py:369
+#: ../controls/VariablePanel.py:371
msgid "Class Filter:"
msgstr ""
@@ -824,19 +851,19 @@
msgid "Class:"
msgstr ""
-#: ../ProjectController.py:1488
+#: ../ProjectController.py:1523
msgid "Clean"
msgstr ""
-#: ../ProjectController.py:1490
+#: ../ProjectController.py:1525
msgid "Clean project build folder"
msgstr ""
-#: ../ProjectController.py:1048
+#: ../ProjectController.py:1076
msgid "Cleaning the build directory\n"
msgstr ""
-#: ../IDEFrame.py:411
+#: ../IDEFrame.py:416
msgid "Clear Errors"
msgstr ""
@@ -848,24 +875,24 @@
msgid "Clear the graph values"
msgstr ""
-#: ../Beremiz.py:598 ../PLCOpenEditor.py:221
+#: ../Beremiz.py:633 ../PLCOpenEditor.py:202
msgid "Close Application"
msgstr ""
-#: ../IDEFrame.py:1089 ../Beremiz.py:319 ../Beremiz.py:552
-#: ../PLCOpenEditor.py:131
+#: ../IDEFrame.py:972 ../Beremiz.py:321 ../Beremiz.py:587
+#: ../PLCOpenEditor.py:112
msgid "Close Project"
msgstr ""
-#: ../Beremiz.py:317 ../PLCOpenEditor.py:129
+#: ../Beremiz.py:319 ../PLCOpenEditor.py:110
msgid "Close Tab"
msgstr ""
-#: ../editors/Viewer.py:481
+#: ../editors/Viewer.py:480
msgid "Coil"
msgstr ""
-#: ../editors/Viewer.py:501 ../editors/LDViewer.py:503
+#: ../editors/Viewer.py:500 ../editors/LDViewer.py:506
msgid "Comment"
msgstr ""
@@ -881,7 +908,7 @@
msgid "Comparison"
msgstr ""
-#: ../ProjectController.py:538
+#: ../ProjectController.py:552
msgid "Compiling IEC Program into C code...\n"
msgstr ""
@@ -889,6 +916,14 @@
msgid "Concatenation"
msgstr ""
+#: ../editors/ConfTreeNodeEditor.py:249
+msgid "Config"
+msgstr ""
+
+#: ../editors/ProjectNodeEditor.py:13
+msgid "Config variables"
+msgstr ""
+
#: ../dialogs/SearchInProjectDialog.py:47
msgid "Configuration"
msgstr ""
@@ -897,20 +932,25 @@
msgid "Configurations"
msgstr ""
-#: ../ProjectController.py:1503
+#: ../ProjectController.py:1538
msgid "Connect"
msgstr ""
-#: ../ProjectController.py:1504
+#: ../ProjectController.py:1539
msgid "Connect to the target PLC"
msgstr ""
+#: ../ProjectController.py:1125
+#, python-format
+msgid "Connected to URI: %s"
+msgstr ""
+
#: ../connectors/PYRO/__init__.py:40
#, python-format
msgid "Connecting to URI : %s\n"
msgstr ""
-#: ../editors/Viewer.py:467 ../dialogs/SFCTransitionDialog.py:76
+#: ../editors/Viewer.py:466 ../dialogs/SFCTransitionDialog.py:76
msgid "Connection"
msgstr ""
@@ -918,11 +958,11 @@
msgid "Connection Properties"
msgstr ""
-#: ../ProjectController.py:1359
+#: ../ProjectController.py:1397
msgid "Connection canceled!\n"
msgstr ""
-#: ../ProjectController.py:1384
+#: ../ProjectController.py:1422
#, python-format
msgid "Connection failed to %s!\n"
msgstr ""
@@ -932,7 +972,7 @@
msgid "Connection to '%s' failed.\n"
msgstr ""
-#: ../dialogs/ConnectionDialog.py:56
+#: ../editors/Viewer.py:1426 ../dialogs/ConnectionDialog.py:56
msgid "Connector"
msgstr ""
@@ -940,11 +980,15 @@
msgid "Connectors:"
msgstr ""
+#: ../Beremiz.py:420
+msgid "Console"
+msgstr ""
+
#: ../controls/VariablePanel.py:65
msgid "Constant"
msgstr ""
-#: ../editors/Viewer.py:477
+#: ../editors/Viewer.py:476
msgid "Contact"
msgstr ""
@@ -952,7 +996,7 @@
msgid "Content Description (optional):"
msgstr ""
-#: ../dialogs/ConnectionDialog.py:61
+#: ../editors/Viewer.py:1427 ../dialogs/ConnectionDialog.py:61
msgid "Continuation"
msgstr ""
@@ -972,19 +1016,19 @@
msgid "Conversion to time-of-day"
msgstr ""
-#: ../IDEFrame.py:348 ../IDEFrame.py:401 ../editors/Viewer.py:536
+#: ../IDEFrame.py:353 ../IDEFrame.py:406 ../editors/Viewer.py:536
msgid "Copy"
msgstr ""
-#: ../IDEFrame.py:1961
+#: ../IDEFrame.py:1837
msgid "Copy POU"
msgstr ""
-#: ../editors/FileManagementPanel.py:283
+#: ../editors/FileManagementPanel.py:65
msgid "Copy file from left folder to right"
msgstr ""
-#: ../editors/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:64
msgid "Copy file from right folder to left"
msgstr ""
@@ -992,40 +1036,40 @@
msgid "Cosine"
msgstr ""
-#: ../ConfigTreeNode.py:582
+#: ../ConfigTreeNode.py:602
#, python-format
msgid ""
"Could not add child \"%s\", type %s :\n"
"%s\n"
msgstr ""
-#: ../ConfigTreeNode.py:559
+#: ../ConfigTreeNode.py:579
#, python-format
msgid ""
"Couldn't load confnode base parameters %s :\n"
" %s"
msgstr ""
-#: ../ConfigTreeNode.py:570
+#: ../ConfigTreeNode.py:590
#, python-format
msgid ""
"Couldn't load confnode parameters %s :\n"
" %s"
msgstr ""
-#: ../PLCControler.py:765 ../PLCControler.py:802
+#: ../PLCControler.py:819 ../PLCControler.py:856
msgid "Couldn't paste non-POU object."
msgstr ""
-#: ../ProjectController.py:1317
+#: ../ProjectController.py:1344
msgid "Couldn't start PLC !\n"
msgstr ""
-#: ../ProjectController.py:1325
+#: ../ProjectController.py:1352
msgid "Couldn't stop PLC !\n"
msgstr ""
-#: ../ProjectController.py:1295
+#: ../ProjectController.py:1321
msgid "Couldn't stop debugger.\n"
msgstr ""
@@ -1041,35 +1085,35 @@
msgid "Create a new action"
msgstr ""
-#: ../IDEFrame.py:135
+#: ../IDEFrame.py:142
msgid "Create a new action block"
msgstr ""
-#: ../IDEFrame.py:84 ../IDEFrame.py:114 ../IDEFrame.py:147
+#: ../IDEFrame.py:91 ../IDEFrame.py:121 ../IDEFrame.py:154
msgid "Create a new block"
msgstr ""
-#: ../IDEFrame.py:108
+#: ../IDEFrame.py:115
msgid "Create a new branch"
msgstr ""
-#: ../IDEFrame.py:102
+#: ../IDEFrame.py:109
msgid "Create a new coil"
msgstr ""
-#: ../IDEFrame.py:78 ../IDEFrame.py:93 ../IDEFrame.py:123
+#: ../IDEFrame.py:85 ../IDEFrame.py:100 ../IDEFrame.py:130
msgid "Create a new comment"
msgstr ""
-#: ../IDEFrame.py:87 ../IDEFrame.py:117 ../IDEFrame.py:150
+#: ../IDEFrame.py:94 ../IDEFrame.py:124 ../IDEFrame.py:157
msgid "Create a new connection"
msgstr ""
-#: ../IDEFrame.py:105 ../IDEFrame.py:156
+#: ../IDEFrame.py:112 ../IDEFrame.py:163
msgid "Create a new contact"
msgstr ""
-#: ../IDEFrame.py:138
+#: ../IDEFrame.py:145
msgid "Create a new divergence"
msgstr ""
@@ -1077,39 +1121,39 @@
msgid "Create a new divergence or convergence"
msgstr ""
-#: ../IDEFrame.py:126
+#: ../IDEFrame.py:133
msgid "Create a new initial step"
msgstr ""
-#: ../IDEFrame.py:141
+#: ../IDEFrame.py:148
msgid "Create a new jump"
msgstr ""
-#: ../IDEFrame.py:96 ../IDEFrame.py:153
+#: ../IDEFrame.py:103 ../IDEFrame.py:160
msgid "Create a new power rail"
msgstr ""
-#: ../IDEFrame.py:99
+#: ../IDEFrame.py:106
msgid "Create a new rung"
msgstr ""
-#: ../IDEFrame.py:129
+#: ../IDEFrame.py:136
msgid "Create a new step"
msgstr ""
-#: ../IDEFrame.py:132 ../dialogs/PouTransitionDialog.py:42
+#: ../IDEFrame.py:139 ../dialogs/PouTransitionDialog.py:42
msgid "Create a new transition"
msgstr ""
-#: ../IDEFrame.py:81 ../IDEFrame.py:111 ../IDEFrame.py:144
+#: ../IDEFrame.py:88 ../IDEFrame.py:118 ../IDEFrame.py:151
msgid "Create a new variable"
msgstr ""
-#: ../IDEFrame.py:346 ../IDEFrame.py:400 ../editors/Viewer.py:535
+#: ../IDEFrame.py:351 ../IDEFrame.py:405 ../editors/Viewer.py:535
msgid "Cut"
msgstr ""
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
msgid "Cyclic"
msgstr ""
@@ -1121,11 +1165,11 @@
msgid "DEPRECATED"
msgstr ""
-#: ../canfestival/SlaveEditor.py:50 ../canfestival/NetworkEditor.py:80
+#: ../canfestival/SlaveEditor.py:53 ../canfestival/NetworkEditor.py:74
msgid "DS-301 Profile"
msgstr ""
-#: ../canfestival/SlaveEditor.py:51 ../canfestival/NetworkEditor.py:81
+#: ../canfestival/SlaveEditor.py:54 ../canfestival/NetworkEditor.py:75
msgid "DS-302 Profile"
msgstr ""
@@ -1158,58 +1202,58 @@
msgid "Days:"
msgstr ""
-#: ../ProjectController.py:1405
-msgid "Debug connect matching running PLC\n"
-msgstr ""
-
-#: ../ProjectController.py:1408
-msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
-msgstr ""
-
-#: ../controls/PouInstanceVariablesPanel.py:52
+#: ../ProjectController.py:1444
+msgid "Debug does not match PLC - stop/transfert/start to re-enable\n"
+msgstr ""
+
+#: ../controls/PouInstanceVariablesPanel.py:59
msgid "Debug instance"
msgstr ""
-#: ../editors/Viewer.py:3222
+#: ../editors/Viewer.py:1016 ../editors/Viewer.py:3326
#, python-format
msgid "Debug: %s"
msgstr ""
-#: ../ProjectController.py:1122
+#: ../ProjectController.py:1153
#, python-format
msgid "Debug: Unknown variable '%s'\n"
msgstr ""
-#: ../ProjectController.py:1120
+#: ../ProjectController.py:1151
#, python-format
msgid "Debug: Unsupported type to debug '%s'\n"
msgstr ""
-#: ../IDEFrame.py:608
+#: ../IDEFrame.py:612
msgid "Debugger"
msgstr ""
-#: ../ProjectController.py:1285
+#: ../ProjectController.py:1311
msgid "Debugger disabled\n"
msgstr ""
-#: ../ProjectController.py:1297
+#: ../ProjectController.py:1441
+msgid "Debugger ready\n"
+msgstr ""
+
+#: ../ProjectController.py:1323
msgid "Debugger stopped.\n"
msgstr ""
-#: ../IDEFrame.py:1990 ../Beremiz.py:958 ../editors/Viewer.py:511
+#: ../IDEFrame.py:1866 ../Beremiz.py:991 ../editors/Viewer.py:511
msgid "Delete"
msgstr ""
-#: ../editors/Viewer.py:454
+#: ../editors/Viewer.py:453
msgid "Delete Divergence Branch"
msgstr ""
-#: ../editors/FileManagementPanel.py:371
+#: ../editors/FileManagementPanel.py:153
msgid "Delete File"
msgstr ""
-#: ../editors/Viewer.py:443
+#: ../editors/Viewer.py:442
msgid "Delete Wire Segment"
msgstr ""
@@ -1221,21 +1265,21 @@
msgid "Deletion (within)"
msgstr ""
-#: ../editors/DataTypeEditor.py:146
+#: ../editors/DataTypeEditor.py:152
msgid "Derivation Type:"
msgstr ""
-#: ../plcopen/structures.py:264
+#: ../plcopen/structures.py:263
msgid ""
"Derivative\n"
"The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
msgstr ""
-#: ../controls/VariablePanel.py:360
+#: ../controls/VariablePanel.py:362
msgid "Description:"
msgstr ""
-#: ../editors/DataTypeEditor.py:314 ../dialogs/ArrayTypeDialog.py:61
+#: ../editors/DataTypeEditor.py:320 ../dialogs/ArrayTypeDialog.py:61
msgid "Dimensions:"
msgstr ""
@@ -1243,23 +1287,23 @@
msgid "Direction"
msgstr ""
-#: ../dialogs/BrowseLocationsDialog.py:78
+#: ../dialogs/BrowseLocationsDialog.py:86
msgid "Direction:"
msgstr ""
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Directly"
msgstr ""
-#: ../ProjectController.py:1512
+#: ../ProjectController.py:1547
msgid "Disconnect"
msgstr ""
-#: ../ProjectController.py:1514
+#: ../ProjectController.py:1549
msgid "Disconnect from PLC"
msgstr ""
-#: ../editors/Viewer.py:496
+#: ../editors/Viewer.py:495
msgid "Divergence"
msgstr ""
@@ -1267,7 +1311,7 @@
msgid "Division"
msgstr ""
-#: ../editors/FileManagementPanel.py:370
+#: ../editors/FileManagementPanel.py:152
#, python-format
msgid "Do you really want to delete the file '%s'?"
msgstr ""
@@ -1276,11 +1320,11 @@
msgid "Documentation"
msgstr ""
-#: ../PLCOpenEditor.py:351
+#: ../PLCOpenEditor.py:330
msgid "Done"
msgstr ""
-#: ../plcopen/structures.py:227
+#: ../plcopen/structures.py:226
msgid ""
"Down-counter\n"
"The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
@@ -1290,11 +1334,11 @@
msgid "Duration"
msgstr ""
-#: ../canfestival/canfestival.py:118
+#: ../canfestival/canfestival.py:139
msgid "EDS files (*.eds)|*.eds|All files|*.*"
msgstr ""
-#: ../editors/Viewer.py:510
+#: ../editors/Viewer.py:509
msgid "Edit Block"
msgstr ""
@@ -1326,12 +1370,12 @@
msgid "Edit array type properties"
msgstr ""
-#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186 ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706 ../editors/Viewer.py:2708
msgid "Edit comment"
msgstr ""
-#: ../editors/FileManagementPanel.py:284
+#: ../editors/FileManagementPanel.py:66
msgid "Edit file"
msgstr ""
@@ -1339,11 +1383,11 @@
msgid "Edit item"
msgstr ""
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2670
msgid "Edit jump target"
msgstr ""
-#: ../ProjectController.py:1526
+#: ../ProjectController.py:1561
msgid "Edit raw IEC code added to code generated by PLCGenerator"
msgstr ""
@@ -1355,35 +1399,35 @@
msgid "Edit transition"
msgstr ""
-#: ../IDEFrame.py:580
+#: ../IDEFrame.py:584
msgid "Editor ToolBar"
msgstr ""
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
msgid "Editor selection"
msgstr ""
-#: ../editors/DataTypeEditor.py:341
+#: ../editors/DataTypeEditor.py:347
msgid "Elements :"
msgstr ""
-#: ../IDEFrame.py:343
+#: ../IDEFrame.py:348
msgid "Enable Undo/Redo"
msgstr ""
-#: ../Beremiz_service.py:380
+#: ../Beremiz_service.py:385
msgid "Enter a name "
msgstr ""
-#: ../Beremiz_service.py:365
+#: ../Beremiz_service.py:370
msgid "Enter a port number "
msgstr ""
-#: ../Beremiz_service.py:355
+#: ../Beremiz_service.py:360
msgid "Enter the IP of the interface to bind"
msgstr ""
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Enumerated"
msgstr ""
@@ -1391,23 +1435,22 @@
msgid "Equal to"
msgstr ""
-#: ../Beremiz_service.py:270 ../Beremiz_service.py:394
-#: ../controls/VariablePanel.py:330 ../controls/VariablePanel.py:678
-#: ../controls/DebugVariablePanel.py:164 ../IDEFrame.py:1083
-#: ../IDEFrame.py:1672 ../IDEFrame.py:1709 ../IDEFrame.py:1714
-#: ../IDEFrame.py:1728 ../IDEFrame.py:1733 ../IDEFrame.py:2422
-#: ../Beremiz.py:1083 ../PLCOpenEditor.py:358 ../PLCOpenEditor.py:363
-#: ../PLCOpenEditor.py:531 ../PLCOpenEditor.py:541
-#: ../editors/TextViewer.py:376 ../editors/DataTypeEditor.py:543
-#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:572
-#: ../editors/DataTypeEditor.py:577 ../editors/DataTypeEditor.py:587
-#: ../editors/DataTypeEditor.py:719 ../editors/DataTypeEditor.py:726
-#: ../editors/Viewer.py:366 ../editors/LDViewer.py:663
-#: ../editors/LDViewer.py:879 ../editors/LDViewer.py:883
-#: ../editors/FileManagementPanel.py:210 ../ProjectController.py:221
-#: ../dialogs/PouNameDialog.py:53 ../dialogs/PouTransitionDialog.py:107
-#: ../dialogs/BrowseLocationsDialog.py:175 ../dialogs/ProjectDialog.py:71
-#: ../dialogs/SFCStepNameDialog.py:59 ../dialogs/ConnectionDialog.py:152
+#: ../Beremiz_service.py:271 ../controls/VariablePanel.py:332
+#: ../controls/VariablePanel.py:681 ../controls/DebugVariablePanel.py:379
+#: ../IDEFrame.py:966 ../IDEFrame.py:1553 ../IDEFrame.py:1590
+#: ../IDEFrame.py:1595 ../IDEFrame.py:1609 ../IDEFrame.py:1614
+#: ../IDEFrame.py:2290 ../Beremiz.py:1131 ../PLCOpenEditor.py:337
+#: ../PLCOpenEditor.py:342 ../PLCOpenEditor.py:416 ../PLCOpenEditor.py:426
+#: ../editors/TextViewer.py:369 ../editors/DataTypeEditor.py:549
+#: ../editors/DataTypeEditor.py:554 ../editors/DataTypeEditor.py:578
+#: ../editors/DataTypeEditor.py:583 ../editors/DataTypeEditor.py:593
+#: ../editors/DataTypeEditor.py:744 ../editors/DataTypeEditor.py:751
+#: ../editors/Viewer.py:365 ../editors/LDViewer.py:666
+#: ../editors/LDViewer.py:882 ../editors/LDViewer.py:886
+#: ../ProjectController.py:225 ../dialogs/PouNameDialog.py:53
+#: ../dialogs/PouTransitionDialog.py:107
+#: ../dialogs/BrowseLocationsDialog.py:212 ../dialogs/ProjectDialog.py:71
+#: ../dialogs/SFCStepNameDialog.py:59 ../dialogs/ConnectionDialog.py:159
#: ../dialogs/FBDVariableDialog.py:201 ../dialogs/PouActionDialog.py:104
#: ../dialogs/BrowseValuesLibraryDialog.py:83 ../dialogs/PouDialog.py:132
#: ../dialogs/SFCTransitionDialog.py:147
@@ -1415,44 +1458,44 @@
#: ../dialogs/DurationEditorDialog.py:163
#: ../dialogs/SearchInProjectDialog.py:157 ../dialogs/SFCStepDialog.py:130
#: ../dialogs/ArrayTypeDialog.py:97 ../dialogs/ArrayTypeDialog.py:103
-#: ../dialogs/FBDBlockDialog.py:164 ../dialogs/ForceVariableDialog.py:169
+#: ../dialogs/FBDBlockDialog.py:164 ../dialogs/ForceVariableDialog.py:179
msgid "Error"
msgstr ""
-#: ../ProjectController.py:587
+#: ../ProjectController.py:601
msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
msgstr ""
-#: ../ProjectController.py:579
+#: ../ProjectController.py:593
#, python-format
msgid "Error : IEC to C compiler returned %d\n"
msgstr ""
-#: ../ProjectController.py:520
+#: ../ProjectController.py:534
#, python-format
msgid ""
"Error in ST/IL/SFC code generator :\n"
"%s\n"
msgstr ""
-#: ../ConfigTreeNode.py:182
+#: ../ConfigTreeNode.py:183
#, python-format
msgid "Error while saving \"%s\"\n"
msgstr ""
-#: ../canfestival/canfestival.py:122
+#: ../canfestival/canfestival.py:144
msgid "Error: Export slave failed\n"
msgstr ""
-#: ../canfestival/canfestival.py:270
+#: ../canfestival/canfestival.py:345
msgid "Error: No Master generated\n"
msgstr ""
-#: ../canfestival/canfestival.py:265
+#: ../canfestival/canfestival.py:340
msgid "Error: No PLC built\n"
msgstr ""
-#: ../ProjectController.py:1378
+#: ../ProjectController.py:1416
#, python-format
msgid "Exception while connecting %s!\n"
msgstr ""
@@ -1465,7 +1508,7 @@
msgid "Execution Order:"
msgstr ""
-#: ../features.py:10
+#: ../features.py:11
msgid "Experimental web based HMI"
msgstr ""
@@ -1477,15 +1520,15 @@
msgid "Exponentiation"
msgstr ""
-#: ../canfestival/canfestival.py:128
+#: ../canfestival/canfestival.py:150
msgid "Export CanOpen slave to EDS file"
msgstr ""
-#: ../editors/GraphicViewer.py:144
+#: ../controls/DebugVariablePanel.py:1472 ../editors/GraphicViewer.py:144
msgid "Export graph values to clipboard"
msgstr ""
-#: ../canfestival/canfestival.py:127
+#: ../canfestival/canfestival.py:149
msgid "Export slave"
msgstr ""
@@ -1497,7 +1540,7 @@
msgid "External"
msgstr ""
-#: ../ProjectController.py:591
+#: ../ProjectController.py:605
msgid "Extracting Located Variables...\n"
msgstr ""
@@ -1507,21 +1550,21 @@
msgid "FBD"
msgstr ""
-#: ../ProjectController.py:1445
+#: ../ProjectController.py:1480
msgid "Failed : Must build before transfer.\n"
msgstr ""
-#: ../editors/Viewer.py:405 ../dialogs/LDElementDialog.py:84
+#: ../editors/Viewer.py:404 ../dialogs/LDElementDialog.py:84
msgid "Falling Edge"
msgstr ""
-#: ../plcopen/structures.py:217
+#: ../plcopen/structures.py:216
msgid ""
"Falling edge detector\n"
"The output produces a single pulse when a falling edge is detected."
msgstr ""
-#: ../ProjectController.py:900
+#: ../ProjectController.py:927
msgid "Fatal : cannot get builder.\n"
msgstr ""
@@ -1535,21 +1578,16 @@
msgid "Fields %s haven't a valid value!"
msgstr ""
-#: ../editors/FileManagementPanel.py:209
-#, python-format
-msgid "File '%s' already exists!"
-msgstr ""
-
-#: ../IDEFrame.py:353 ../dialogs/FindInPouDialog.py:30
+#: ../IDEFrame.py:358 ../dialogs/FindInPouDialog.py:30
#: ../dialogs/FindInPouDialog.py:99
msgid "Find"
msgstr ""
-#: ../IDEFrame.py:355
+#: ../IDEFrame.py:360
msgid "Find Next"
msgstr ""
-#: ../IDEFrame.py:357
+#: ../IDEFrame.py:362
msgid "Find Previous"
msgstr ""
@@ -1565,11 +1603,11 @@
msgid "Force runtime reload\n"
msgstr ""
-#: ../controls/DebugVariablePanel.py:295 ../editors/Viewer.py:1353
+#: ../controls/DebugVariablePanel.py:1934 ../editors/Viewer.py:1385
msgid "Force value"
msgstr ""
-#: ../dialogs/ForceVariableDialog.py:152
+#: ../dialogs/ForceVariableDialog.py:162
msgid "Forcing Variable Value"
msgstr ""
@@ -1580,7 +1618,7 @@
msgid "Form isn't complete. %s must be filled!"
msgstr ""
-#: ../dialogs/ConnectionDialog.py:142 ../dialogs/FBDBlockDialog.py:154
+#: ../dialogs/ConnectionDialog.py:149 ../dialogs/FBDBlockDialog.py:154
msgid "Form isn't complete. Name must be filled!"
msgstr ""
@@ -1600,15 +1638,15 @@
msgid "Function"
msgstr ""
-#: ../IDEFrame.py:329
+#: ../IDEFrame.py:334
msgid "Function &Block"
msgstr ""
-#: ../IDEFrame.py:1969 ../dialogs/SearchInProjectDialog.py:45
+#: ../IDEFrame.py:1845 ../dialogs/SearchInProjectDialog.py:45
msgid "Function Block"
msgstr ""
-#: ../controls/VariablePanel.py:741
+#: ../controls/VariablePanel.py:744
msgid "Function Block Types"
msgstr ""
@@ -1620,11 +1658,7 @@
msgid "Function Blocks can't be used in Functions!"
msgstr ""
-#: ../editors/Viewer.py:238
-msgid "Function Blocks can't be used in Transitions!"
-msgstr ""
-
-#: ../PLCControler.py:2055
+#: ../PLCControler.py:2180
#, python-format
msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
msgstr ""
@@ -1633,11 +1667,11 @@
msgid "Functions"
msgstr ""
-#: ../PLCOpenEditor.py:138
+#: ../PLCOpenEditor.py:119
msgid "Generate Program"
msgstr ""
-#: ../ProjectController.py:510
+#: ../ProjectController.py:524
msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
msgstr ""
@@ -1645,7 +1679,7 @@
msgid "Global"
msgstr ""
-#: ../editors/GraphicViewer.py:131
+#: ../controls/DebugVariablePanel.py:1471 ../editors/GraphicViewer.py:131
msgid "Go to current value"
msgstr ""
@@ -1669,7 +1703,7 @@
msgid "Height:"
msgstr ""
-#: ../editors/FileManagementPanel.py:303
+#: ../editors/FileManagementPanel.py:85
msgid "Home Directory:"
msgstr ""
@@ -1681,13 +1715,13 @@
msgid "Hours:"
msgstr ""
-#: ../plcopen/structures.py:279
+#: ../plcopen/structures.py:278
msgid ""
"Hysteresis\n"
"The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
msgstr ""
-#: ../ProjectController.py:827
+#: ../ProjectController.py:851
msgid "IEC-61131-3 code generation failed !\n"
msgstr ""
@@ -1696,7 +1730,7 @@
msgid "IL"
msgstr ""
-#: ../Beremiz_service.py:356 ../Beremiz_service.py:357
+#: ../Beremiz_service.py:361 ../Beremiz_service.py:362
msgid "IP is not valid!"
msgstr ""
@@ -1704,11 +1738,16 @@
msgid "Import SVG"
msgstr ""
-#: ../controls/VariablePanel.py:76 ../dialogs/FBDVariableDialog.py:34
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1412
+#: ../dialogs/FBDVariableDialog.py:34
msgid "InOut"
msgstr ""
-#: ../controls/VariablePanel.py:263
+#: ../editors/Viewer.py:999
+msgid "Inactive"
+msgstr ""
+
+#: ../controls/VariablePanel.py:265
#, python-format
msgid "Incompatible data types between \"%s\" and \"%s\""
msgstr ""
@@ -1727,17 +1766,17 @@
msgid "Indicator"
msgstr ""
-#: ../editors/Viewer.py:492
+#: ../editors/Viewer.py:491
msgid "Initial Step"
msgstr ""
#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
+#: ../editors/DataTypeEditor.py:50
msgid "Initial Value"
msgstr ""
-#: ../editors/DataTypeEditor.py:178 ../editors/DataTypeEditor.py:209
-#: ../editors/DataTypeEditor.py:265 ../editors/DataTypeEditor.py:303
+#: ../editors/DataTypeEditor.py:184 ../editors/DataTypeEditor.py:215
+#: ../editors/DataTypeEditor.py:271 ../editors/DataTypeEditor.py:309
msgid "Initial Value:"
msgstr ""
@@ -1750,8 +1789,9 @@
msgid "Inline"
msgstr ""
-#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:36
-#: ../dialogs/FBDVariableDialog.py:33 ../dialogs/SFCStepDialog.py:61
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1410
+#: ../dialogs/BrowseLocationsDialog.py:35 ../dialogs/FBDVariableDialog.py:33
+#: ../dialogs/SFCStepDialog.py:61
msgid "Input"
msgstr ""
@@ -1763,16 +1803,16 @@
msgid "Insertion (into)"
msgstr ""
-#: ../plcopen/plcopen.py:1833
+#: ../plcopen/plcopen.py:1843
#, python-format
msgid "Instance with id %d doesn't exist!"
msgstr ""
-#: ../editors/ResourceEditor.py:247
+#: ../editors/ResourceEditor.py:255
msgid "Instances:"
msgstr ""
-#: ../plcopen/structures.py:259
+#: ../plcopen/structures.py:258
msgid ""
"Integral\n"
"The integral function block integrates the value of input XIN over time."
@@ -1782,15 +1822,15 @@
msgid "Interface"
msgstr ""
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
msgid "Interrupt"
msgstr ""
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Interval"
msgstr ""
-#: ../PLCControler.py:2032 ../PLCControler.py:2070
+#: ../PLCControler.py:2157 ../PLCControler.py:2195
msgid "Invalid plcopen element(s)!!!"
msgstr ""
@@ -1799,12 +1839,12 @@
msgid "Invalid type \"%s\"-> %d != %d for location\"%s\""
msgstr ""
-#: ../dialogs/ForceVariableDialog.py:167
+#: ../dialogs/ForceVariableDialog.py:177
#, python-format
msgid "Invalid value \"%s\" for \"%s\" variable!"
msgstr ""
-#: ../controls/DebugVariablePanel.py:153 ../controls/DebugVariablePanel.py:156
+#: ../controls/DebugVariablePanel.py:319 ../controls/DebugVariablePanel.py:322
#, python-format
msgid "Invalid value \"%s\" for debug variable"
msgstr ""
@@ -1825,7 +1865,7 @@
"You must fill a numeric value."
msgstr ""
-#: ../editors/Viewer.py:497
+#: ../editors/Viewer.py:496
msgid "Jump"
msgstr ""
@@ -1854,19 +1894,19 @@
msgid "Language:"
msgstr ""
-#: ../ProjectController.py:1451
+#: ../ProjectController.py:1486
msgid "Latest build already matches current target. Transfering anyway...\n"
msgstr ""
-#: ../Beremiz_service.py:324
+#: ../Beremiz_service.py:331
msgid "Launch WX GUI inspector"
msgstr ""
-#: ../Beremiz_service.py:323
+#: ../Beremiz_service.py:330
msgid "Launch a live Python shell"
msgstr ""
-#: ../editors/Viewer.py:428
+#: ../editors/Viewer.py:427
msgid "Left"
msgstr ""
@@ -1886,7 +1926,7 @@
msgid "Less than or equal to"
msgstr ""
-#: ../IDEFrame.py:600
+#: ../IDEFrame.py:604
msgid "Library"
msgstr ""
@@ -1902,7 +1942,11 @@
msgid "Local"
msgstr ""
-#: ../ProjectController.py:1353
+#: ../canfestival/canfestival.py:322
+msgid "Local entries"
+msgstr ""
+
+#: ../ProjectController.py:1391
msgid "Local service discovery failed!\n"
msgstr ""
@@ -1910,14 +1954,10 @@
msgid "Location"
msgstr ""
-#: ../dialogs/BrowseLocationsDialog.py:61
+#: ../dialogs/BrowseLocationsDialog.py:68
msgid "Locations available:"
msgstr ""
-#: ../Beremiz.py:393
-msgid "Log Console"
-msgstr ""
-
#: ../plcopen/iec_std.csv:25
msgid "Logarithm to base 10"
msgstr ""
@@ -1927,19 +1967,19 @@
msgid "MDNS resolution failure for '%s'\n"
msgstr ""
-#: ../canfestival/SlaveEditor.py:37 ../canfestival/NetworkEditor.py:67
+#: ../canfestival/SlaveEditor.py:41 ../canfestival/NetworkEditor.py:62
msgid "Map Variable"
msgstr ""
-#: ../features.py:6
+#: ../features.py:7
msgid "Map located variables over CANopen"
msgstr ""
-#: ../canfestival/NetworkEditor.py:89
+#: ../canfestival/NetworkEditor.py:83
msgid "Master"
msgstr ""
-#: ../ConfigTreeNode.py:480
+#: ../ConfigTreeNode.py:500
#, python-format
msgid "Max count (%d) reached for this confnode of type %s "
msgstr ""
@@ -1948,15 +1988,15 @@
msgid "Maximum"
msgstr ""
-#: ../editors/DataTypeEditor.py:232
+#: ../editors/DataTypeEditor.py:238
msgid "Maximum:"
msgstr ""
-#: ../dialogs/BrowseLocationsDialog.py:38
+#: ../dialogs/BrowseLocationsDialog.py:37
msgid "Memory"
msgstr ""
-#: ../IDEFrame.py:568
+#: ../IDEFrame.py:572
msgid "Menu ToolBar"
msgstr ""
@@ -1964,7 +2004,7 @@
msgid "Microseconds:"
msgstr ""
-#: ../editors/Viewer.py:433
+#: ../editors/Viewer.py:432
msgid "Middle"
msgstr ""
@@ -1976,7 +2016,7 @@
msgid "Minimum"
msgstr ""
-#: ../editors/DataTypeEditor.py:219
+#: ../editors/DataTypeEditor.py:225
msgid "Minimum:"
msgstr ""
@@ -1992,7 +2032,7 @@
msgid "Modifier:"
msgstr ""
-#: ../PLCGenerator.py:703 ../PLCGenerator.py:936
+#: ../PLCGenerator.py:732 ../PLCGenerator.py:975
#, python-format
msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
msgstr ""
@@ -2005,11 +2045,11 @@
msgid "Move action up"
msgstr ""
-#: ../controls/DebugVariablePanel.py:185
+#: ../controls/DebugVariablePanel.py:1532
msgid "Move debug variable down"
msgstr ""
-#: ../controls/DebugVariablePanel.py:184
+#: ../controls/DebugVariablePanel.py:1531
msgid "Move debug variable up"
msgstr ""
@@ -2017,31 +2057,31 @@
msgid "Move down"
msgstr ""
-#: ../editors/DataTypeEditor.py:348
+#: ../editors/DataTypeEditor.py:354
msgid "Move element down"
msgstr ""
-#: ../editors/DataTypeEditor.py:347
+#: ../editors/DataTypeEditor.py:353
msgid "Move element up"
msgstr ""
-#: ../editors/ResourceEditor.py:254
+#: ../editors/ResourceEditor.py:262
msgid "Move instance down"
msgstr ""
-#: ../editors/ResourceEditor.py:253
+#: ../editors/ResourceEditor.py:261
msgid "Move instance up"
msgstr ""
-#: ../editors/ResourceEditor.py:225
+#: ../editors/ResourceEditor.py:233
msgid "Move task down"
msgstr ""
-#: ../editors/ResourceEditor.py:224
+#: ../editors/ResourceEditor.py:232
msgid "Move task up"
msgstr ""
-#: ../IDEFrame.py:75 ../IDEFrame.py:90 ../IDEFrame.py:120 ../IDEFrame.py:161
+#: ../IDEFrame.py:82 ../IDEFrame.py:97 ../IDEFrame.py:127 ../IDEFrame.py:168
msgid "Move the view"
msgstr ""
@@ -2049,11 +2089,11 @@
msgid "Move up"
msgstr ""
-#: ../controls/VariablePanel.py:381
+#: ../controls/VariablePanel.py:383 ../c_ext/CFileEditor.py:520
msgid "Move variable down"
msgstr ""
-#: ../controls/VariablePanel.py:380
+#: ../controls/VariablePanel.py:382 ../c_ext/CFileEditor.py:519
msgid "Move variable up"
msgstr ""
@@ -2065,17 +2105,17 @@
msgid "Multiplication"
msgstr ""
-#: ../editors/FileManagementPanel.py:301
+#: ../editors/FileManagementPanel.py:83
msgid "My Computer:"
msgstr ""
#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:67
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50 ../editors/ResourceEditor.py:68
+#: ../editors/ResourceEditor.py:77
msgid "Name"
msgstr ""
-#: ../Beremiz_service.py:381
+#: ../Beremiz_service.py:386
msgid "Name must not be null!"
msgstr ""
@@ -2089,12 +2129,12 @@
msgid "Natural logarithm"
msgstr ""
-#: ../editors/Viewer.py:403 ../dialogs/LDElementDialog.py:67
+#: ../editors/Viewer.py:402 ../dialogs/LDElementDialog.py:67
msgid "Negated"
msgstr ""
-#: ../Beremiz.py:307 ../Beremiz.py:342 ../PLCOpenEditor.py:125
-#: ../PLCOpenEditor.py:167
+#: ../Beremiz.py:309 ../Beremiz.py:344 ../PLCOpenEditor.py:106
+#: ../PLCOpenEditor.py:148
msgid "New"
msgstr ""
@@ -2102,47 +2142,43 @@
msgid "New item"
msgstr ""
-#: ../editors/Viewer.py:402
+#: ../editors/Viewer.py:401
msgid "No Modifier"
msgstr ""
-#: ../PLCControler.py:2929
+#: ../PLCControler.py:3054
msgid "No PLC project found"
msgstr ""
-#: ../ProjectController.py:1478
+#: ../ProjectController.py:1513
msgid "No PLC to transfer (did build succeed ?)\n"
msgstr ""
-#: ../PLCGenerator.py:1321
+#: ../PLCGenerator.py:1360
#, python-format
msgid "No body defined in \"%s\" POU"
msgstr ""
-#: ../PLCGenerator.py:722 ../PLCGenerator.py:945
+#: ../PLCGenerator.py:751 ../PLCGenerator.py:984
#, python-format
msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
msgstr ""
-#: ../PLCOpenEditor.py:370
+#: ../PLCOpenEditor.py:349
msgid ""
"No documentation available.\n"
"Coming soon."
msgstr ""
-#: ../PLCGenerator.py:744
+#: ../PLCGenerator.py:773
#, python-format
msgid "No informations found for \"%s\" block"
msgstr ""
-#: ../plcopen/structures.py:167
+#: ../plcopen/structures.py:166
msgid "No output variable found"
msgstr ""
-#: ../Beremiz_service.py:394
-msgid "No running PLC"
-msgstr ""
-
#: ../controls/SearchResultPanel.py:169
msgid "No search results available."
msgstr ""
@@ -2166,15 +2202,11 @@
msgid "No valid value selected!"
msgstr ""
-#: ../PLCGenerator.py:1319
+#: ../PLCGenerator.py:1358
#, python-format
msgid "No variable defined in \"%s\" POU"
msgstr ""
-#: ../canfestival/SlaveEditor.py:49 ../canfestival/NetworkEditor.py:79
-msgid "Node infos"
-msgstr ""
-
#: ../canfestival/config_utils.py:354
#, python-format
msgid "Non existing node ID : %d (variable %s)"
@@ -2205,13 +2237,13 @@
msgid "Numerical"
msgstr ""
-#: ../plcopen/structures.py:247
+#: ../plcopen/structures.py:246
msgid ""
"Off-delay timer\n"
"The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
msgstr ""
-#: ../plcopen/structures.py:242
+#: ../plcopen/structures.py:241
msgid ""
"On-delay timer\n"
"The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
@@ -2221,8 +2253,8 @@
msgid "Only Elements"
msgstr ""
-#: ../Beremiz.py:309 ../Beremiz.py:343 ../PLCOpenEditor.py:127
-#: ../PLCOpenEditor.py:168
+#: ../Beremiz.py:311 ../Beremiz.py:345 ../PLCOpenEditor.py:108
+#: ../PLCOpenEditor.py:149
msgid "Open"
msgstr ""
@@ -2230,7 +2262,7 @@
msgid "Open Inkscape"
msgstr ""
-#: ../ProjectController.py:1530
+#: ../ProjectController.py:1565
msgid "Open a file explorer to manage project files"
msgstr ""
@@ -2250,24 +2282,25 @@
msgid "Organization (optional):"
msgstr ""
-#: ../canfestival/SlaveEditor.py:47 ../canfestival/NetworkEditor.py:77
+#: ../canfestival/SlaveEditor.py:51 ../canfestival/NetworkEditor.py:72
msgid "Other Profile"
msgstr ""
-#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:37
-#: ../dialogs/FBDVariableDialog.py:35 ../dialogs/SFCStepDialog.py:65
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1411
+#: ../dialogs/BrowseLocationsDialog.py:36 ../dialogs/FBDVariableDialog.py:35
+#: ../dialogs/SFCStepDialog.py:65
msgid "Output"
msgstr ""
-#: ../canfestival/SlaveEditor.py:36 ../canfestival/NetworkEditor.py:66
+#: ../canfestival/SlaveEditor.py:40 ../canfestival/NetworkEditor.py:61
msgid "PDO Receive"
msgstr ""
-#: ../canfestival/SlaveEditor.py:35 ../canfestival/NetworkEditor.py:65
+#: ../canfestival/SlaveEditor.py:39 ../canfestival/NetworkEditor.py:60
msgid "PDO Transmit"
msgstr ""
-#: ../plcopen/structures.py:269
+#: ../plcopen/structures.py:268
msgid ""
"PID\n"
"The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
@@ -2277,16 +2310,15 @@
msgid "PLC :\n"
msgstr ""
-#: ../ProjectController.py:1096 ../ProjectController.py:1398
-#, python-format
-msgid "PLC is %s\n"
-msgstr ""
-
-#: ../PLCOpenEditor.py:313 ../PLCOpenEditor.py:391
+#: ../Beremiz.py:425
+msgid "PLC Log"
+msgstr ""
+
+#: ../PLCOpenEditor.py:294 ../PLCOpenEditor.py:370
msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
msgstr ""
-#: ../PLCOpenEditor.py:175 ../PLCOpenEditor.py:231
+#: ../PLCOpenEditor.py:156 ../PLCOpenEditor.py:212
msgid "PLCOpenEditor"
msgstr ""
@@ -2306,7 +2338,7 @@
msgid "POU Type:"
msgstr ""
-#: ../Beremiz.py:322 ../PLCOpenEditor.py:141
+#: ../Beremiz.py:324 ../PLCOpenEditor.py:122
msgid "Page Setup"
msgstr ""
@@ -2314,20 +2346,20 @@
msgid "Page Size (optional):"
msgstr ""
-#: ../PLCOpenEditor.py:476
+#: ../IDEFrame.py:2492
#, python-format
msgid "Page: %d"
msgstr ""
-#: ../controls/PouInstanceVariablesPanel.py:41
+#: ../controls/PouInstanceVariablesPanel.py:48
msgid "Parent instance"
msgstr ""
-#: ../IDEFrame.py:350 ../IDEFrame.py:402 ../editors/Viewer.py:537
+#: ../IDEFrame.py:355 ../IDEFrame.py:407 ../editors/Viewer.py:537
msgid "Paste"
msgstr ""
-#: ../IDEFrame.py:1900
+#: ../IDEFrame.py:1776
msgid "Paste POU"
msgstr ""
@@ -2339,13 +2371,13 @@
msgid "Pin number:"
msgstr ""
-#: ../editors/Viewer.py:2289 ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2363 ../editors/Viewer.py:2670
#: ../editors/SFCViewer.py:696
msgid "Please choose a target"
msgstr ""
-#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186 ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706 ../editors/Viewer.py:2708
msgid "Please enter comment text"
msgstr ""
@@ -2354,16 +2386,16 @@
msgid "Please enter step name"
msgstr ""
-#: ../dialogs/ForceVariableDialog.py:153
+#: ../dialogs/ForceVariableDialog.py:163
#, python-format
msgid "Please enter value for a \"%s\" variable:"
msgstr ""
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
msgid "Port number must be 0 <= port <= 65535!"
msgstr ""
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
msgid "Port number must be an integer!"
msgstr ""
@@ -2371,7 +2403,7 @@
msgid "Position:"
msgstr ""
-#: ../editors/Viewer.py:476
+#: ../editors/Viewer.py:475
msgid "Power Rail"
msgstr ""
@@ -2379,7 +2411,7 @@
msgid "Power Rail Properties"
msgstr ""
-#: ../Beremiz.py:324 ../PLCOpenEditor.py:143
+#: ../Beremiz.py:326 ../PLCOpenEditor.py:124
msgid "Preview"
msgstr ""
@@ -2390,16 +2422,16 @@
msgid "Preview:"
msgstr ""
-#: ../Beremiz.py:326 ../Beremiz.py:346 ../PLCOpenEditor.py:145
-#: ../PLCOpenEditor.py:171
+#: ../Beremiz.py:328 ../Beremiz.py:348 ../PLCOpenEditor.py:126
+#: ../PLCOpenEditor.py:152
msgid "Print"
msgstr ""
-#: ../IDEFrame.py:1155
+#: ../IDEFrame.py:1038
msgid "Print preview"
msgstr ""
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Priority"
msgstr ""
@@ -2407,6 +2439,11 @@
msgid "Priority:"
msgstr ""
+#: ../runtime/PLCObject.py:318
+#, python-format
+msgid "Problem starting PLC : error %d"
+msgstr ""
+
#: ../controls/ProjectPropertiesPanel.py:80
msgid "Product Name (required):"
msgstr ""
@@ -2419,11 +2456,11 @@
msgid "Product Version (required):"
msgstr ""
-#: ../IDEFrame.py:1972 ../dialogs/SearchInProjectDialog.py:46
+#: ../IDEFrame.py:1848 ../dialogs/SearchInProjectDialog.py:46
msgid "Program"
msgstr ""
-#: ../PLCOpenEditor.py:360
+#: ../PLCOpenEditor.py:339
msgid "Program was successfully generated!"
msgstr ""
@@ -2435,7 +2472,7 @@
msgid "Programs can't be used by other POUs!"
msgstr ""
-#: ../controls/ProjectPropertiesPanel.py:84 ../IDEFrame.py:553
+#: ../controls/ProjectPropertiesPanel.py:84 ../IDEFrame.py:557
msgid "Project"
msgstr ""
@@ -2444,7 +2481,7 @@
msgid "Project '%s':"
msgstr ""
-#: ../ProjectController.py:1529
+#: ../ProjectController.py:1564
msgid "Project Files"
msgstr ""
@@ -2456,32 +2493,40 @@
msgid "Project Version (optional):"
msgstr ""
-#: ../PLCControler.py:2916
+#: ../PLCControler.py:3041
msgid ""
"Project file syntax error:\n"
"\n"
msgstr ""
-#: ../dialogs/ProjectDialog.py:32
+#: ../editors/ProjectNodeEditor.py:14 ../dialogs/ProjectDialog.py:32
msgid "Project properties"
msgstr ""
-#: ../ConfigTreeNode.py:506
+#: ../ConfigTreeNode.py:526
#, python-format
msgid "Project tree layout do not match confnode.xml %s!=%s "
msgstr ""
+#: ../dialogs/ConnectionDialog.py:96
+msgid "Propagate Name"
+msgstr ""
+
#: ../PLCControler.py:96
msgid "Properties"
msgstr ""
-#: ../plcopen/structures.py:237
+#: ../plcopen/structures.py:236
msgid ""
"Pulse timer\n"
"The pulse timer can be used to generate output pulses of a given time duration."
msgstr ""
-#: ../features.py:8
+#: ../py_ext/PythonEditor.py:61
+msgid "Python code"
+msgstr ""
+
+#: ../features.py:9
msgid "Python file"
msgstr ""
@@ -2489,42 +2534,42 @@
msgid "Qualifier"
msgstr ""
-#: ../Beremiz_service.py:328 ../Beremiz.py:329 ../PLCOpenEditor.py:151
+#: ../Beremiz_service.py:333 ../Beremiz.py:331 ../PLCOpenEditor.py:132
msgid "Quit"
msgstr ""
-#: ../plcopen/structures.py:202
+#: ../plcopen/structures.py:201
msgid ""
"RS bistable\n"
"The RS bistable is a latch where the Reset dominates."
msgstr ""
-#: ../plcopen/structures.py:274
+#: ../plcopen/structures.py:273
msgid ""
"Ramp\n"
"The RAMP function block is modelled on example given in the standard."
msgstr ""
-#: ../editors/GraphicViewer.py:89
+#: ../controls/DebugVariablePanel.py:1462 ../editors/GraphicViewer.py:89
msgid "Range:"
msgstr ""
-#: ../ProjectController.py:1525
+#: ../ProjectController.py:1560
msgid "Raw IEC code"
msgstr ""
-#: ../plcopen/structures.py:254
+#: ../plcopen/structures.py:253
msgid ""
"Real time clock\n"
"The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
msgstr ""
-#: ../Beremiz.py:1039
+#: ../Beremiz.py:1072
#, python-format
msgid "Really delete node '%s'?"
msgstr ""
-#: ../IDEFrame.py:340 ../IDEFrame.py:398
+#: ../IDEFrame.py:345 ../IDEFrame.py:403
msgid "Redo"
msgstr ""
@@ -2532,7 +2577,7 @@
msgid "Reference"
msgstr ""
-#: ../IDEFrame.py:408 ../dialogs/DiscoveryDialog.py:105
+#: ../IDEFrame.py:413 ../dialogs/DiscoveryDialog.py:105
msgid "Refresh"
msgstr ""
@@ -2544,7 +2589,7 @@
msgid "Regular expressions"
msgstr ""
-#: ../controls/DebugVariablePanel.py:299 ../editors/Viewer.py:1356
+#: ../controls/DebugVariablePanel.py:1938 ../editors/Viewer.py:1388
msgid "Release value"
msgstr ""
@@ -2552,7 +2597,7 @@
msgid "Remainder (modulo)"
msgstr ""
-#: ../Beremiz.py:1040
+#: ../Beremiz.py:1073
#, python-format
msgid "Remove %s node"
msgstr ""
@@ -2561,39 +2606,39 @@
msgid "Remove action"
msgstr ""
-#: ../controls/DebugVariablePanel.py:183
+#: ../controls/DebugVariablePanel.py:1530
msgid "Remove debug variable"
msgstr ""
-#: ../editors/DataTypeEditor.py:346
+#: ../editors/DataTypeEditor.py:352
msgid "Remove element"
msgstr ""
-#: ../editors/FileManagementPanel.py:281
+#: ../editors/FileManagementPanel.py:63
msgid "Remove file from left folder"
msgstr ""
-#: ../editors/ResourceEditor.py:252
+#: ../editors/ResourceEditor.py:260
msgid "Remove instance"
msgstr ""
-#: ../canfestival/NetworkEditor.py:87
+#: ../canfestival/NetworkEditor.py:81
msgid "Remove slave"
msgstr ""
-#: ../editors/ResourceEditor.py:223
+#: ../editors/ResourceEditor.py:231
msgid "Remove task"
msgstr ""
-#: ../controls/VariablePanel.py:379
+#: ../controls/VariablePanel.py:381 ../c_ext/CFileEditor.py:518
msgid "Remove variable"
msgstr ""
-#: ../IDEFrame.py:1976
+#: ../IDEFrame.py:1852
msgid "Rename"
msgstr ""
-#: ../editors/FileManagementPanel.py:399
+#: ../editors/FileManagementPanel.py:181
msgid "Replace File"
msgstr ""
@@ -2609,7 +2654,7 @@
msgid "Reset Execution Order"
msgstr ""
-#: ../IDEFrame.py:423
+#: ../IDEFrame.py:428
msgid "Reset Perspective"
msgstr ""
@@ -2629,11 +2674,11 @@
msgid "Retain"
msgstr ""
-#: ../controls/VariablePanel.py:352
+#: ../controls/VariablePanel.py:354
msgid "Return Type:"
msgstr ""
-#: ../editors/Viewer.py:430
+#: ../editors/Viewer.py:429
msgid "Right"
msgstr ""
@@ -2641,11 +2686,11 @@
msgid "Right PowerRail"
msgstr ""
-#: ../editors/Viewer.py:404 ../dialogs/LDElementDialog.py:80
+#: ../editors/Viewer.py:403 ../dialogs/LDElementDialog.py:80
msgid "Rising Edge"
msgstr ""
-#: ../plcopen/structures.py:212
+#: ../plcopen/structures.py:211
msgid ""
"Rising edge detector\n"
"The output produces a single pulse when a rising edge is detected."
@@ -2663,19 +2708,19 @@
msgid "Rounding up/down"
msgstr ""
-#: ../ProjectController.py:1493
+#: ../ProjectController.py:1528
msgid "Run"
msgstr ""
-#: ../ProjectController.py:841 ../ProjectController.py:850
+#: ../ProjectController.py:865 ../ProjectController.py:874
msgid "Runtime extensions C code generation failed !\n"
msgstr ""
-#: ../canfestival/SlaveEditor.py:34 ../canfestival/NetworkEditor.py:64
+#: ../canfestival/SlaveEditor.py:38 ../canfestival/NetworkEditor.py:59
msgid "SDO Client"
msgstr ""
-#: ../canfestival/SlaveEditor.py:33 ../canfestival/NetworkEditor.py:63
+#: ../canfestival/SlaveEditor.py:37 ../canfestival/NetworkEditor.py:58
msgid "SDO Server"
msgstr ""
@@ -2683,7 +2728,7 @@
msgid "SFC"
msgstr ""
-#: ../plcopen/structures.py:197
+#: ../plcopen/structures.py:196
msgid ""
"SR bistable\n"
"The SR bistable is a latch where the Set dominates."
@@ -2694,7 +2739,7 @@
msgid "ST"
msgstr ""
-#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:326
msgid "ST files (*.st)|*.st|All files|*.*"
msgstr ""
@@ -2702,20 +2747,20 @@
msgid "SVG files (*.svg)|*.svg|All files|*.*"
msgstr ""
-#: ../features.py:10
+#: ../features.py:11
msgid "SVGUI"
msgstr ""
-#: ../Beremiz.py:313 ../Beremiz.py:344 ../PLCOpenEditor.py:134
-#: ../PLCOpenEditor.py:169
+#: ../Beremiz.py:315 ../Beremiz.py:346 ../PLCOpenEditor.py:115
+#: ../PLCOpenEditor.py:150
msgid "Save"
msgstr ""
-#: ../Beremiz.py:345 ../PLCOpenEditor.py:136 ../PLCOpenEditor.py:170
+#: ../Beremiz.py:347 ../PLCOpenEditor.py:117 ../PLCOpenEditor.py:151
msgid "Save As..."
msgstr ""
-#: ../Beremiz.py:315
+#: ../Beremiz.py:317
msgid "Save as"
msgstr ""
@@ -2723,11 +2768,11 @@
msgid "Scope"
msgstr ""
-#: ../IDEFrame.py:592 ../dialogs/SearchInProjectDialog.py:105
+#: ../IDEFrame.py:596 ../dialogs/SearchInProjectDialog.py:105
msgid "Search"
msgstr ""
-#: ../IDEFrame.py:360 ../IDEFrame.py:404
+#: ../IDEFrame.py:365 ../IDEFrame.py:409
#: ../dialogs/SearchInProjectDialog.py:52
msgid "Search in Project"
msgstr ""
@@ -2736,24 +2781,24 @@
msgid "Seconds:"
msgstr ""
-#: ../IDEFrame.py:366
+#: ../IDEFrame.py:371
msgid "Select All"
msgstr ""
-#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../controls/LocationCellEditor.py:97 ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:323 ../editors/Viewer.py:275
msgid "Select a variable class:"
msgstr ""
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
msgid "Select an editor:"
msgstr ""
-#: ../controls/PouInstanceVariablesPanel.py:197
+#: ../controls/PouInstanceVariablesPanel.py:209
msgid "Select an instance"
msgstr ""
-#: ../IDEFrame.py:576
+#: ../IDEFrame.py:580
msgid "Select an object"
msgstr ""
@@ -2769,7 +2814,7 @@
msgid "Selection Divergence"
msgstr ""
-#: ../plcopen/structures.py:207
+#: ../plcopen/structures.py:206
msgid ""
"Semaphore\n"
"The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
@@ -2791,19 +2836,19 @@
msgid "Shift right"
msgstr ""
-#: ../ProjectController.py:1519
+#: ../ProjectController.py:1554
msgid "Show IEC code generated by PLCGenerator"
msgstr ""
-#: ../canfestival/canfestival.py:288
+#: ../canfestival/canfestival.py:363
msgid "Show Master"
msgstr ""
-#: ../canfestival/canfestival.py:289
+#: ../canfestival/canfestival.py:364
msgid "Show Master generated by config_utils"
msgstr ""
-#: ../ProjectController.py:1517
+#: ../ProjectController.py:1552
msgid "Show code"
msgstr ""
@@ -2819,7 +2864,7 @@
msgid "Sine"
msgstr ""
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Single"
msgstr ""
@@ -2827,52 +2872,52 @@
msgid "Square root (base 2)"
msgstr ""
-#: ../plcopen/structures.py:193
+#: ../plcopen/structures.py:192
msgid "Standard function blocks"
msgstr ""
-#: ../Beremiz_service.py:319 ../ProjectController.py:1495
+#: ../Beremiz_service.py:321 ../ProjectController.py:1530
msgid "Start PLC"
msgstr ""
-#: ../ProjectController.py:819
+#: ../ProjectController.py:843
#, python-format
msgid "Start build in %s\n"
msgstr ""
-#: ../ProjectController.py:1314
+#: ../ProjectController.py:1341
msgid "Starting PLC\n"
msgstr ""
-#: ../Beremiz.py:403
+#: ../Beremiz.py:435
msgid "Status ToolBar"
msgstr ""
-#: ../editors/Viewer.py:493
+#: ../editors/Viewer.py:492
msgid "Step"
msgstr ""
-#: ../ProjectController.py:1498
+#: ../ProjectController.py:1533
msgid "Stop"
msgstr ""
-#: ../Beremiz_service.py:320
+#: ../Beremiz_service.py:322
msgid "Stop PLC"
msgstr ""
-#: ../ProjectController.py:1500
+#: ../ProjectController.py:1535
msgid "Stop Running PLC"
msgstr ""
-#: ../ProjectController.py:1292
+#: ../ProjectController.py:1318
msgid "Stopping debugger...\n"
msgstr ""
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Structure"
msgstr ""
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
msgid "Subrange"
msgstr ""
@@ -2880,7 +2925,7 @@
msgid "Subtraction"
msgstr ""
-#: ../ProjectController.py:915
+#: ../ProjectController.py:942
msgid "Successfully built.\n"
msgstr ""
@@ -2892,11 +2937,11 @@
msgid "Tangent"
msgstr ""
-#: ../editors/ResourceEditor.py:76
+#: ../editors/ResourceEditor.py:77
msgid "Task"
msgstr ""
-#: ../editors/ResourceEditor.py:218
+#: ../editors/ResourceEditor.py:226
msgid "Tasks:"
msgstr ""
@@ -2904,33 +2949,33 @@
msgid "Temp"
msgstr ""
-#: ../editors/FileManagementPanel.py:398
+#: ../editors/FileManagementPanel.py:180
#, python-format
msgid ""
"The file '%s' already exist.\n"
"Do you want to replace it?"
msgstr ""
-#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:882
msgid "The group of block must be coherent!"
msgstr ""
-#: ../IDEFrame.py:1091 ../Beremiz.py:555
+#: ../IDEFrame.py:974 ../Beremiz.py:590
msgid "There are changes, do you want to save?"
msgstr ""
-#: ../IDEFrame.py:1709 ../IDEFrame.py:1728
+#: ../IDEFrame.py:1590 ../IDEFrame.py:1609
#, python-format
msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
msgstr ""
-#: ../IDEFrame.py:1178
+#: ../IDEFrame.py:1061
msgid ""
"There was a problem printing.\n"
"Perhaps your current printer is not set correctly?"
msgstr ""
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
msgid "This option isn't available yet!"
msgstr ""
@@ -2971,31 +3016,31 @@
msgid "Time-of-day subtraction"
msgstr ""
-#: ../editors/Viewer.py:432
+#: ../editors/Viewer.py:431
msgid "Top"
msgstr ""
-#: ../ProjectController.py:1507
+#: ../ProjectController.py:1542
msgid "Transfer"
msgstr ""
+#: ../ProjectController.py:1544
+msgid "Transfer PLC"
+msgstr ""
+
#: ../ProjectController.py:1509
-msgid "Transfer PLC"
-msgstr ""
-
-#: ../ProjectController.py:1474
msgid "Transfer completed successfully.\n"
msgstr ""
-#: ../ProjectController.py:1476
+#: ../ProjectController.py:1511
msgid "Transfer failed\n"
msgstr ""
-#: ../editors/Viewer.py:494
+#: ../editors/Viewer.py:493
msgid "Transition"
msgstr ""
-#: ../PLCGenerator.py:1212
+#: ../PLCGenerator.py:1252
#, python-format
msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
msgstr ""
@@ -3008,17 +3053,17 @@
msgid "Transition Name:"
msgstr ""
-#: ../PLCGenerator.py:1301
+#: ../PLCGenerator.py:1340
#, python-format
msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
msgstr ""
-#: ../PLCGenerator.py:1292
+#: ../PLCGenerator.py:1331
#, python-format
msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
msgstr ""
-#: ../plcopen/plcopen.py:1442
+#: ../plcopen/plcopen.py:1447
#, python-format
msgid "Transition with name %s doesn't exist!"
msgstr ""
@@ -3027,16 +3072,20 @@
msgid "Transitions"
msgstr ""
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
msgid "Triggering"
msgstr ""
#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50 ../editors/ResourceEditor.py:77
#: ../dialogs/ActionBlockDialog.py:37
msgid "Type"
msgstr ""
+#: ../dialogs/BrowseLocationsDialog.py:44
+msgid "Type and derivated"
+msgstr ""
+
#: ../canfestival/config_utils.py:335 ../canfestival/config_utils.py:617
#, python-format
msgid "Type conflict for location \"%s\""
@@ -3046,13 +3095,17 @@
msgid "Type conversion"
msgstr ""
-#: ../editors/DataTypeEditor.py:155
+#: ../editors/DataTypeEditor.py:161
msgid "Type infos:"
msgstr ""
+#: ../dialogs/BrowseLocationsDialog.py:45
+msgid "Type strict"
+msgstr ""
+
#: ../dialogs/SFCDivergenceDialog.py:51 ../dialogs/LDPowerRailDialog.py:51
-#: ../dialogs/ConnectionDialog.py:52 ../dialogs/SFCTransitionDialog.py:53
-#: ../dialogs/FBDBlockDialog.py:48
+#: ../dialogs/BrowseLocationsDialog.py:95 ../dialogs/ConnectionDialog.py:52
+#: ../dialogs/SFCTransitionDialog.py:53 ../dialogs/FBDBlockDialog.py:48
msgid "Type:"
msgstr ""
@@ -3066,30 +3119,30 @@
msgid "Unable to get Xenomai's %s \n"
msgstr ""
-#: ../PLCGenerator.py:865 ../PLCGenerator.py:924
+#: ../PLCGenerator.py:904 ../PLCGenerator.py:963
#, python-format
msgid "Undefined block type \"%s\" in \"%s\" POU"
msgstr ""
-#: ../PLCGenerator.py:240
+#: ../PLCGenerator.py:252
#, python-format
msgid "Undefined pou type \"%s\""
msgstr ""
-#: ../IDEFrame.py:338 ../IDEFrame.py:397
+#: ../IDEFrame.py:343 ../IDEFrame.py:402
msgid "Undo"
msgstr ""
-#: ../ProjectController.py:254
+#: ../ProjectController.py:262
msgid "Unknown"
msgstr ""
-#: ../editors/Viewer.py:336
+#: ../editors/Viewer.py:335
#, python-format
msgid "Unknown variable \"%s\" for this POU!"
msgstr ""
-#: ../ProjectController.py:251 ../ProjectController.py:252
+#: ../ProjectController.py:259 ../ProjectController.py:260
msgid "Unnamed"
msgstr ""
@@ -3103,23 +3156,23 @@
msgid "Unrecognized data size \"%s\""
msgstr ""
-#: ../plcopen/structures.py:222
+#: ../plcopen/structures.py:221
msgid ""
"Up-counter\n"
"The up-counter can be used to signal when a count has reached a maximum value."
msgstr ""
-#: ../plcopen/structures.py:232
+#: ../plcopen/structures.py:231
msgid ""
"Up-down counter\n"
"The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
msgstr ""
-#: ../controls/VariablePanel.py:709 ../editors/DataTypeEditor.py:623
+#: ../controls/VariablePanel.py:712 ../editors/DataTypeEditor.py:631
msgid "User Data Types"
msgstr ""
-#: ../canfestival/SlaveEditor.py:38 ../canfestival/NetworkEditor.py:68
+#: ../canfestival/SlaveEditor.py:42 ../canfestival/NetworkEditor.py:63
msgid "User Type"
msgstr ""
@@ -3127,7 +3180,7 @@
msgid "User-defined POUs"
msgstr ""
-#: ../controls/DebugVariablePanel.py:40 ../dialogs/ActionBlockDialog.py:37
+#: ../controls/DebugVariablePanel.py:58 ../dialogs/ActionBlockDialog.py:37
msgid "Value"
msgstr ""
@@ -3135,11 +3188,11 @@
msgid "Values"
msgstr ""
-#: ../editors/DataTypeEditor.py:252
+#: ../editors/DataTypeEditor.py:258
msgid "Values:"
msgstr ""
-#: ../controls/DebugVariablePanel.py:40 ../editors/Viewer.py:466
+#: ../controls/DebugVariablePanel.py:58 ../editors/Viewer.py:465
#: ../dialogs/ActionBlockDialog.py:41
msgid "Variable"
msgstr ""
@@ -3148,12 +3201,12 @@
msgid "Variable Properties"
msgstr ""
-#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../controls/LocationCellEditor.py:97 ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:323 ../editors/Viewer.py:275
msgid "Variable class"
msgstr ""
-#: ../editors/TextViewer.py:374 ../editors/Viewer.py:338
+#: ../editors/TextViewer.py:367 ../editors/Viewer.py:337
msgid "Variable don't belong to this POU!"
msgstr ""
@@ -3169,15 +3222,15 @@
msgid "WXGLADE GUI"
msgstr ""
-#: ../ProjectController.py:1276
+#: ../ProjectController.py:1302
msgid "Waiting debugger to recover...\n"
msgstr ""
-#: ../editors/LDViewer.py:888 ../dialogs/PouDialog.py:126
+#: ../editors/LDViewer.py:891 ../dialogs/PouDialog.py:126
msgid "Warning"
msgstr ""
-#: ../ProjectController.py:515
+#: ../ProjectController.py:529
msgid "Warnings in ST/IL/SFC code generator :\n"
msgstr ""
@@ -3193,7 +3246,7 @@
msgid "Wrap search"
msgstr ""
-#: ../features.py:9
+#: ../features.py:10
msgid "WxGlade GUI"
msgstr ""
@@ -3209,17 +3262,17 @@
"Open wxGlade anyway ?"
msgstr ""
-#: ../ProjectController.py:220
+#: ../ProjectController.py:224
msgid ""
"You must have permission to work on the project\n"
"Work on a project copy ?"
msgstr ""
-#: ../editors/LDViewer.py:883
+#: ../editors/LDViewer.py:886
msgid "You must select the block or group of blocks around which a branch should be added!"
msgstr ""
-#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:666
msgid "You must select the wire where a contact should be added!"
msgstr ""
@@ -3228,11 +3281,11 @@
msgid "You must type a name!"
msgstr ""
-#: ../dialogs/ForceVariableDialog.py:165
+#: ../dialogs/ForceVariableDialog.py:175
msgid "You must type a value!"
msgstr ""
-#: ../IDEFrame.py:414
+#: ../IDEFrame.py:419
msgid "Zoom"
msgstr ""
@@ -3240,7 +3293,7 @@
msgid "Zoom:"
msgstr ""
-#: ../PLCOpenEditor.py:356
+#: ../PLCOpenEditor.py:335
#, python-format
msgid "error: %s\n"
msgstr ""
@@ -3250,7 +3303,7 @@
msgid "exited with status %s (pid %s)\n"
msgstr ""
-#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:393 ../PLCOpenEditor.py:395
msgid "file : "
msgstr ""
@@ -3258,7 +3311,7 @@
msgid "function"
msgstr ""
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
msgid "function : "
msgstr ""
@@ -3266,7 +3319,7 @@
msgid "functionBlock"
msgstr ""
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
msgid "line : "
msgstr ""
@@ -3286,7 +3339,7 @@
msgid "string right of"
msgstr ""
-#: ../PLCOpenEditor.py:354
+#: ../PLCOpenEditor.py:333
#, python-format
msgid "warning: %s\n"
msgstr ""
@@ -3323,9 +3376,6 @@
msgid "CAN_Driver"
msgstr ""
-msgid "Debug_mode"
-msgstr ""
-
msgid "CExtension"
msgstr ""
Binary file images/LOG_CRITICAL.png has changed
Binary file images/LOG_DEBUG.png has changed
Binary file images/LOG_INFO.png has changed
Binary file images/LOG_WARNING.png has changed
Binary file images/fit_graph.png has changed
Binary file images/full_graph.png has changed
--- a/images/icons.svg Wed Mar 13 12:34:55 2013 +0900
+++ b/images/icons.svg Wed Jul 31 10:45:07 2013 +0900
@@ -16,7 +16,7 @@
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.48.3.1 r9886"
- sodipodi:docname="icons.svg.2013_02_08_17_20_04.0.svg"
+ sodipodi:docname="icons.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata13810">
@@ -26,7 +26,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
+ <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -34,19 +34,19 @@
inkscape:window-height="1056"
inkscape:window-width="1920"
inkscape:pageshadow="2"
- inkscape:pageopacity="1"
+ inkscape:pageopacity="0"
guidetolerance="10.0"
gridtolerance="10000"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
- pagecolor="#f2f1f0"
+ pagecolor="#ffffff"
id="base"
showgrid="false"
- inkscape:zoom="1"
- inkscape:cx="590.30324"
- inkscape:cy="563.15477"
- inkscape:window-x="1920"
+ inkscape:zoom="4.0000001"
+ inkscape:cx="590.44587"
+ inkscape:cy="681.55725"
+ inkscape:window-x="0"
inkscape:window-y="24"
inkscape:current-layer="svg2"
showguides="true"
@@ -90933,7 +90933,7 @@
sodipodi:nodetypes="cccccccccccccccccccc" />
<rect
inkscape:label="#rect16270"
- style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="Debug"
y="131.36218"
x="670"
@@ -93515,4 +93515,132 @@
id="path18458"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccccccccccccccccc" />
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"
+ id="text18383-2-5"
+ y="379.81824"
+ x="170.02246"><tspan
+ id="tspan18385-7-6"
+ y="379.81824"
+ x="170.02246">Log levels icons</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ style="font-size:12.76000023px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ xml:space="preserve"
+ id="text60407-68-0"
+ y="360.20483"
+ x="365.61026"><tspan
+ y="360.20483"
+ x="365.61026"
+ id="tspan16195-3-3"
+ sodipodi:role="line">%% LOG_CRITICAL LOG_WARNING LOG_INFO LOG_DEBUG %%</tspan></text>
+ <rect
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="LOG_CRITICAL"
+ y="366.36215"
+ x="431"
+ height="16"
+ width="16" />
+ <rect
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="LOG_WARNING"
+ y="365.86215"
+ x="521"
+ height="16"
+ width="16" />
+ <rect
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="LOG_INFO"
+ y="365.36215"
+ x="609.75"
+ height="16"
+ width="16" />
+ <rect
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="LOG_DEBUG"
+ y="365.36215"
+ x="680"
+ height="16"
+ width="16" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path18473"
+ sodipodi:cx="420.25"
+ sodipodi:cy="379.61218"
+ sodipodi:rx="6.25"
+ sodipodi:ry="6.25"
+ d="m 426.5,379.61218 a 6.25,6.25 0 1 1 -12.5,0 6.25,6.25 0 1 1 12.5,0 z"
+ transform="matrix(1.2040688,0,0,1.2040688,-67.009896,-82.717008)" />
+ <path
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+ d="m 436.51042,370.07049 -1.85417,1.91667 2.4375,2.375 -2.4375,2.375 1.85417,1.91667 2.47917,-2.4375 2.5,2.4375 1.85417,-1.91667 -2.43751,-2.375 2.43751,-2.375 -1.85417,-1.91667 -2.5,2.4375 -2.47917,-2.4375 z"
+ id="path18476"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:type="star"
+ style="color:#000000;fill:#ffcc00;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path19290"
+ sodipodi:sides="3"
+ sodipodi:cx="485.4288"
+ sodipodi:cy="371.94867"
+ sodipodi:r1="12.432317"
+ sodipodi:r2="6.3169394"
+ sodipodi:arg1="0.52090978"
+ sodipodi:arg2="1.568032"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 496.21218,378.13585 -10.76592,0.12973 -10.76741,-0.0718 5.27061,-9.38843 5.44591,-9.28893 5.49531,9.25869 z"
+ inkscape:transform-center-x="-0.011147353"
+ inkscape:transform-center-y="-1.9795276"
+ transform="matrix(0.6666672,0,0,0.6666672,205.3694,127.93464)" />
+ <text
+ xml:space="preserve"
+ style="font-size:12.4041214px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="441.56531"
+ y="451.09576"
+ id="text19292"
+ sodipodi:linespacing="125%"
+ transform="scale(1.191303,0.839417)"><tspan
+ sodipodi:role="line"
+ id="tspan19294"
+ x="441.56531"
+ y="451.09576">!</tspan></text>
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path18473-1"
+ sodipodi:cx="420.25"
+ sodipodi:cy="379.61218"
+ sodipodi:rx="6.25"
+ sodipodi:ry="6.25"
+ d="m 426.5,379.61218 a 6.25,6.25 0 1 1 -12.5,0 6.25,6.25 0 1 1 12.5,0 z"
+ transform="matrix(1.2040688,0,0,1.2040688,111.7401,-83.717008)" />
+ <text
+ xml:space="preserve"
+ style="font-size:15.56442642px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+ x="475.99768"
+ y="488.26718"
+ id="text19314"
+ sodipodi:linespacing="125%"
+ transform="scale(1.2919212,0.77404101)"><tspan
+ sodipodi:role="line"
+ id="tspan19316"
+ x="475.99768"
+ y="488.26718">i</tspan></text>
+ <rect
+ style="color:#000000;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:#c8c8c8;stroke-width:0.66666722;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect19318"
+ width="14.259998"
+ height="13.081486"
+ x="680.87"
+ y="366.82141" />
+ <path
+ style="fill:none;stroke:#ffffff;stroke-width:1.33333445;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 681.20882,373.5854 4.27211,0 1.44678,-2.50589 2.63578,4.56531 1.3874,-2.40303 3.84029,0"
+ id="path19324"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
</svg>
--- a/images/plcopen_icons.svg Wed Mar 13 12:34:55 2013 +0900
+++ b/images/plcopen_icons.svg Wed Jul 31 10:45:07 2013 +0900
@@ -11114,6 +11114,318 @@
id="radialGradient5955"
xlink:href="#radialGradient4208-2"
inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5533-1"
+ y2="609.51001"
+ gradientUnits="userSpaceOnUse"
+ x2="302.85999"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.77854,15.669)"
+ y1="366.64999"
+ x1="302.85999">
+ <stop
+ id="stop5050-7-1"
+ style="stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056-19-2"
+ offset=".5" />
+ <stop
+ id="stop5052-4-7"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5535-3"
+ xlink:href="#linearGradient5060-1-4"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.78465,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5060-1-4">
+ <stop
+ id="stop5062-7-0"
+ offset="0" />
+ <stop
+ id="stop5064-9-6"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5537-8"
+ xlink:href="#linearGradient5060-1-4"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(-0.031048,0,0,0.013668,23.215,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5891">
+ <stop
+ id="stop5893"
+ offset="0" />
+ <stop
+ id="stop5895-3"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5565-0"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.63636,0,0,0.62295,-3.9091,-3.1066)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-4"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-2"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-4"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5562-5-4"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.61291,0,0,0.58621,-3.3226,-2.069)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-0"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-8"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5559-2"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.52632,0,0,0.48148,-0.63158,1.7407)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-5"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-67"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6639-4"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,-1,0,25.121,-26.636)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient6388-8">
+ <stop
+ id="stop6390-9"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop6392-8"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6643-1"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,-1,-1,0,25.121,55.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient5912">
+ <stop
+ id="stop5914"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5916"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6647-7"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,1,0,-1.1213,-26.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient5919">
+ <stop
+ id="stop5921"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5923"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="22.839001"
+ x2="33.25"
+ y1="16.121"
+ x1="39.879002"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3099-5-7"
+ xlink:href="#linearGradient6388-8"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5926">
+ <stop
+ id="stop5928"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5930-6"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="22.839001"
+ x2="33.25"
+ y1="16.121"
+ x1="39.879002"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5947"
+ xlink:href="#linearGradient6388-8"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5528-2"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-0"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-1"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-8"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5525-2"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-5"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-7"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5522-2"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-3"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-4"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5528-5"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-8"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-3"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-3"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5525-31"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-48"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-10"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5522-7"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-7"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-7"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
</defs>
<sodipodi:namedview
id="base"
@@ -11122,9 +11434,9 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
- inkscape:zoom="2.828427"
- inkscape:cx="60.957367"
- inkscape:cy="-299.13955"
+ inkscape:zoom="7.9999995"
+ inkscape:cx="103.60893"
+ inkscape:cy="-243.21864"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="16px"
@@ -16155,5 +16467,161 @@
d="M 18.531,8.7812 V 10 A 0.51754,0.51754 0 0 1 18,10.531 H 9.4375 l 0.03125,2.9375 h 8.5312 a 0.51754,0.51754 0 0 1 0.531,0.532 v 1.1562 l 3.469,-3.281 -3.469,-3.0938 z"
transform="translate(0,0.99987)" />
</g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="70.198586"
+ y="255.80313"
+ id="text3638-3-3-2-0-9-8-5-5-2-6"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9-5-4"
+ x="70.198586"
+ y="255.80313">%%fit_graph%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="259.125"
+ x="80.25"
+ height="16"
+ width="16"
+ id="fit_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="108.19859"
+ y="255.80313"
+ id="text3638-3-3-2-0-9-8-5-5-2-6-9"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9-5-4-0"
+ x="108.19859"
+ y="255.80313">%%full_graph%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="259.125"
+ x="119.84098"
+ height="16"
+ width="16"
+ id="full_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <g
+ transform="translate(119.73268,258.52291)"
+ id="g5512">
+ <rect
+ id="rect1887-08"
+ style="fill:url(#linearGradient5528-2);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+ height="14"
+ width="15"
+ y="1.5"
+ x="0.49996999" />
+ <rect
+ id="rect2779-5"
+ style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-2);stroke-width:1.00010002"
+ height="12"
+ width="13"
+ y="2.5000999"
+ x="1.5001" />
+ <rect
+ id="rect6287-2"
+ style="fill:url(#linearGradient5522-2)"
+ height="9"
+ width="14"
+ y="6"
+ x="1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6293-3"
+ style="fill:#ffc24c"
+ d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect5590-5"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 5.5355,13.293 2.7071,10.464 2,14 5.5355,13.293 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6302"
+ style="fill:#ffc24c"
+ d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6304"
+ style="fill:#ffc24c"
+ d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6312"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 5.5355,7.7071 2.7071,10.536 2,7 5.5355,7.7071 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6316"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 10.464,13.293 13.293,10.464 14,14 10.464,13.293 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6318"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 10.464,7.7071 13.293,10.536 14,7 10.464,7.7071 z" />
+ </g>
+ <g
+ transform="translate(80.18127,258.41201)"
+ id="g5498-7">
+ <rect
+ id="rect1887-3"
+ style="fill:url(#linearGradient5528-5);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+ height="14"
+ width="15"
+ y="1.5"
+ x="0.49996999" />
+ <rect
+ id="rect2779-33"
+ style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-31);stroke-width:1.00010002"
+ height="12"
+ width="13"
+ y="2.5000999"
+ x="1.5001" />
+ <rect
+ id="rect6287-0"
+ style="fill:url(#linearGradient5522-7)"
+ height="9"
+ width="14"
+ y="6"
+ x="1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6293-8"
+ style="fill:#ffc24c"
+ d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect5590-8"
+ style="fill:#73a300"
+ d="M 2.4645,11.707 5.2929,14.536 6,11 2.4645,11.707 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6302-0"
+ style="fill:#ffc24c"
+ d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6304-7"
+ style="fill:#ffc24c"
+ d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6312-0"
+ style="fill:#73a300"
+ d="M 2.4645,9.2929 5.2929,6.4645 6,10 2.4645,9.2929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6316-1"
+ style="fill:#73a300"
+ d="M 13.536,11.707 10.707,14.536 10,11 l 3.5355,0.70711 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6318-5"
+ style="fill:#73a300"
+ d="M 13.536,9.2929 10.707,6.4645 10,10 13.536,9.2929 z" />
+ </g>
</g>
</svg>
Binary file locale/fr_FR/LC_MESSAGES/Beremiz.mo has changed
--- a/plcopen/plcopen.py Wed Mar 13 12:34:55 2013 +0900
+++ b/plcopen/plcopen.py Wed Jul 31 10:45:07 2013 +0900
@@ -155,6 +155,17 @@
self.text = text
setattr(cls, "updateElementAddress", updateElementAddress)
+ def hasblock(self, block_type):
+ text = self.text.upper()
+ index = text.find(block_type.upper())
+ while index != -1:
+ if (not (index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_")) and
+ not (index < len(text) - len(block_type) and text[index + len(block_type)] != "(")):
+ return True
+ index = text.find(block_type.upper(), index + len(block_type))
+ return False
+ setattr(cls, "hasblock", hasblock)
+
def Search(self, criteria, parent_infos):
return [(tuple(parent_infos),) + result for result in TestTextElement(self.gettext(), criteria)]
setattr(cls, "Search", Search)
@@ -489,17 +500,19 @@
self.CustomBlockTypes.append(block_infos)
setattr(cls, "AddCustomBlockType", AddCustomBlockType)
+ def AddElementUsingTreeInstance(self, name, type_infos):
+ typename = type_infos.getname()
+ if not self.ElementUsingTree.has_key(typename):
+ self.ElementUsingTree[typename] = [name]
+ elif name not in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+ setattr(cls, "AddElementUsingTreeInstance", AddElementUsingTreeInstance)
+
def RefreshElementUsingTree(self):
# Reset the tree of user-defined element cross-use
self.ElementUsingTree = {}
pous = self.getpous()
datatypes = self.getdataTypes()
- # Reference all the user-defined elementu names and initialize the tree of
- # user-defined elemnt cross-use
- elementnames = [datatype.getname() for datatype in datatypes] + \
- [pou.getname() for pou in pous]
- for name in elementnames:
- self.ElementUsingTree[name] = []
# Analyze each datatype
for datatype in datatypes:
name = datatype.getname()
@@ -511,16 +524,12 @@
elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned", "array"]:
base_type = basetype_content["value"].baseType.getcontent()
if base_type["name"] == "derived":
- typename = base_type["value"].getname()
- if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
- self.ElementUsingTree[typename].append(name)
+ self.AddElementUsingTreeInstance(name, base_type["value"])
elif basetype_content["name"] == "struct":
for element in basetype_content["value"].getvariable():
type_content = element.type.getcontent()
if type_content["name"] == "derived":
- typename = type_content["value"].getname()
- if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
- self.ElementUsingTree[typename].append(name)
+ self.AddElementUsingTreeInstance(name, type_content["value"])
# Analyze each pou
for pou in pous:
name = pou.getname()
@@ -530,9 +539,11 @@
for var in varlist.getvariable():
vartype_content = var.gettype().getcontent()
if vartype_content["name"] == "derived":
- typename = vartype_content["value"].getname()
- if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
- self.ElementUsingTree[typename].append(name)
+ self.AddElementUsingTreeInstance(name, vartype_content["value"])
+ for typename in self.ElementUsingTree.iterkeys():
+ if typename != name and pou.hasblock(block_type=typename) and name not in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+
setattr(cls, "RefreshElementUsingTree", RefreshElementUsingTree)
def GetParentType(self, type):
@@ -840,6 +851,35 @@
cls = PLCOpenClasses.get("configurations_configuration", None)
if cls:
+
+ def addglobalVar(self, type, name, location="", description=""):
+ globalvars = self.getglobalVars()
+ if len(globalvars) == 0:
+ globalvars.append(PLCOpenClasses["varList"]())
+ var = PLCOpenClasses["varListPlain_variable"]()
+ var.setname(name)
+ var_type = PLCOpenClasses["dataType"]()
+ if type in [x for x,y in TypeHierarchy_list if not x.startswith("ANY")]:
+ if type == "STRING":
+ var_type.setcontent({"name" : "string", "value" : PLCOpenClasses["elementaryTypes_string"]()})
+ elif type == "WSTRING":
+ var_type.setcontent({"name" : "wstring", "value" : PLCOpenClasses["elementaryTypes_wstring"]()})
+ else:
+ var_type.setcontent({"name" : type, "value" : None})
+ else:
+ derived_type = PLCOpenClasses["derivedTypes_derived"]()
+ derived_type.setname(type)
+ var_type.setcontent({"name" : "derived", "value" : derived_type})
+ var.settype(var_type)
+ if location != "":
+ var.setaddress(location)
+ if description != "":
+ ft = PLCOpenClasses["formattedText"]()
+ ft.settext(description)
+ var.setdocumentation(ft)
+ globalvars[-1].appendvariable(var)
+ setattr(cls, "addglobalVar", addglobalVar)
+
def updateElementName(self, old_name, new_name):
_updateConfigurationResourceElementName(self, old_name, new_name)
for resource in self.getresource():
@@ -1382,21 +1422,25 @@
break
setattr(cls, "removepouVar", removepouVar)
- def hasblock(self, name):
+ def hasblock(self, name=None, block_type=None):
if self.getbodyType() in ["FBD", "LD", "SFC"]:
for instance in self.getinstances():
- if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
+ if (isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and
+ (name and instance.getinstanceName() == name or
+ block_type and instance.gettypeName() == block_type)):
return True
if self.transitions:
for transition in self.transitions.gettransition():
- result = transition.hasblock(name)
+ result = transition.hasblock(name, block_type)
if result:
return result
if self.actions:
for action in self.actions.getaction():
- result = action.hasblock(name)
+ result = action.hasblock(name, block_type)
if result:
return result
+ elif block_type is not None and len(self.body) > 0:
+ return self.body[0].hasblock(block_type)
return False
setattr(cls, "hasblock", hasblock)
@@ -1626,6 +1670,24 @@
def gettext(self):
return self.body.gettext()
+def hasblock(self, name=None, block_type=None):
+ if self.getbodyType() in ["FBD", "LD", "SFC"]:
+ for instance in self.getinstances():
+ if (isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and
+ (name and instance.getinstanceName() == name or
+ block_type and instance.gettypeName() == block_type)):
+ return True
+ elif block_type is not None:
+ return self.body.hasblock(block_type)
+ return False
+
+def updateElementName(self, old_name, new_name):
+ self.body.updateElementName(old_name, new_name)
+
+def updateElementAddress(self, address_model, new_leading):
+ self.body.updateElementAddress(address_model, new_leading)
+
+
cls = PLCOpenClasses.get("transitions_transition", None)
if cls:
setattr(cls, "setbodyType", setbodyType)
@@ -1641,23 +1703,10 @@
setattr(cls, "removeinstance", removeinstance)
setattr(cls, "settext", settext)
setattr(cls, "gettext", gettext)
-
- def updateElementName(self, old_name, new_name):
- self.body.updateElementName(old_name, new_name)
+ setattr(cls, "hasblock", hasblock)
setattr(cls, "updateElementName", updateElementName)
-
- def updateElementAddress(self, address_model, new_leading):
- self.body.updateElementAddress(address_model, new_leading)
setattr(cls, "updateElementAddress", updateElementAddress)
-
- def hasblock(self, name):
- if self.getbodyType() in ["FBD", "LD", "SFC"]:
- for instance in self.getinstances():
- if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
- return True
- return False
- setattr(cls, "hasblock", hasblock)
-
+
def Search(self, criteria, parent_infos):
search_result = []
parent_infos = parent_infos[:-1] + ["T::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
@@ -1682,23 +1731,10 @@
setattr(cls, "removeinstance", removeinstance)
setattr(cls, "settext", settext)
setattr(cls, "gettext", gettext)
-
- def updateElementName(self, old_name, new_name):
- self.body.updateElementName(old_name, new_name)
+ setattr(cls, "hasblock", hasblock)
setattr(cls, "updateElementName", updateElementName)
-
- def updateElementAddress(self, address_model, new_leading):
- self.body.updateElementAddress(address_model, new_leading)
setattr(cls, "updateElementAddress", updateElementAddress)
-
- def hasblock(self, name):
- if self.getbodyType() in ["FBD", "LD", "SFC"]:
- for instance in self.getinstances():
- if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
- return True
- return False
- setattr(cls, "hasblock", hasblock)
-
+
def Search(self, criteria, parent_infos):
search_result = []
parent_infos = parent_infos[:-1] + ["A::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
@@ -1711,6 +1747,24 @@
cls = PLCOpenClasses.get("body", None)
if cls:
cls.currentExecutionOrderId = 0
+ cls.instances_dict = {}
+
+ setattr(cls, "_init_", getattr(cls, "__init__"))
+
+ def __init__(self, *args, **kwargs):
+ self._init_(*args, **kwargs)
+ self.instances_dict = {}
+ setattr(cls, "__init__", __init__)
+
+ setattr(cls, "_loadXMLTree", getattr(cls, "loadXMLTree"))
+
+ def loadXMLTree(self, *args, **kwargs):
+ self._loadXMLTree(*args, **kwargs)
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ self.instances_dict = dict(
+ [(element["value"].getlocalId(), element)
+ for element in self.content["value"].getcontent()])
+ setattr(cls, "loadXMLTree", loadXMLTree)
def resetcurrentExecutionOrderId(self):
object.__setattr__(self, "currentExecutionOrderId", 0)
@@ -1785,7 +1839,9 @@
def appendcontentInstance(self, name, instance):
if self.content["name"] in ["LD","FBD","SFC"]:
- self.content["value"].appendcontent({"name" : name, "value" : instance})
+ element = {"name" : name, "value" : instance}
+ self.content["value"].appendcontent(element)
+ self.instances_dict[instance.getlocalId()] = element
else:
raise TypeError, _("%s body don't have instances!")%self.content["name"]
setattr(cls, "appendcontentInstance", appendcontentInstance)
@@ -1802,9 +1858,9 @@
def getcontentInstance(self, id):
if self.content["name"] in ["LD","FBD","SFC"]:
- for element in self.content["value"].getcontent():
- if element["value"].getlocalId() == id:
- return element["value"]
+ instance = self.instances_dict.get(id, None)
+ if instance is not None:
+ return instance["value"]
return None
else:
raise TypeError, _("%s body don't have instances!")%self.content["name"]
@@ -1812,9 +1868,9 @@
def getcontentRandomInstance(self, exclude):
if self.content["name"] in ["LD","FBD","SFC"]:
- for element in self.content["value"].getcontent():
- if element["value"].getlocalId() not in exclude:
- return element["value"]
+ ids = self.instances_dict.viewkeys() - exclude
+ if len(ids) > 0:
+ return self.instances_dict[ids.pop()]["value"]
return None
else:
raise TypeError, _("%s body don't have instances!")%self.content["name"]
@@ -1831,15 +1887,10 @@
def removecontentInstance(self, id):
if self.content["name"] in ["LD","FBD","SFC"]:
- i = 0
- removed = False
- elements = self.content["value"].getcontent()
- while i < len(elements) and not removed:
- if elements[i]["value"].getlocalId() == id:
- self.content["value"].removecontent(i)
- removed = True
- i += 1
- if not removed:
+ element = self.instances_dict.pop(id, None)
+ if element is not None:
+ self.content["value"].getcontent().remove(element)
+ else:
raise ValueError, _("Instance with id %d doesn't exist!")%id
else:
raise TypeError, "%s body don't have instances!"%self.content["name"]
@@ -1859,6 +1910,13 @@
raise TypeError, _("%s body don't have text!")%self.content["name"]
setattr(cls, "gettext", gettext)
+ def hasblock(self, block_type):
+ if self.content["name"] in ["IL","ST"]:
+ return self.content["value"].hasblock(block_type)
+ else:
+ raise TypeError, _("%s body don't have text!")%self.content["name"]
+ setattr(cls, "hasblock", hasblock)
+
def updateElementName(self, old_name, new_name):
if self.content["name"] in ["IL", "ST"]:
self.content["value"].updateElementName(old_name, new_name)
--- a/plcopen/structures.py Wed Mar 13 12:34:55 2013 +0900
+++ b/plcopen/structures.py Wed Jul 31 10:45:07 2013 +0900
@@ -46,38 +46,67 @@
name = block.getinstanceName()
type = block.gettypeName()
executionOrderId = block.getexecutionOrderId()
+ input_variables = block.inputVariables.getvariable()
+ output_variables = block.outputVariables.getvariable()
inout_variables = {}
- for input_variable in block.inputVariables.getvariable():
- for output_variable in block.outputVariables.getvariable():
+ for input_variable in input_variables:
+ for output_variable in output_variables:
if input_variable.getformalParameter() == output_variable.getformalParameter():
inout_variables[input_variable.getformalParameter()] = ""
+ input_names = [input[0] for input in block_infos["inputs"]]
+ output_names = [output[0] for output in block_infos["outputs"]]
if block_infos["type"] == "function":
- output_variables = block.outputVariables.getvariable()
if not generator.ComputedBlocks.get(block, False) and not order:
generator.ComputedBlocks[block] = True
- vars = []
+ connected_vars = []
+ if not block_infos["extensible"]:
+ input_connected = dict([("EN", None)] +
+ [(input_name, None) for input_name in input_names])
+ for variable in input_variables:
+ parameter = variable.getformalParameter()
+ if input_connected.has_key(parameter):
+ input_connected[parameter] = variable
+ if input_connected["EN"] is None:
+ input_connected.pop("EN")
+ input_parameters = input_names
+ else:
+ input_parameters = ["EN"] + input_names
+ else:
+ input_connected = dict([(variable.getformalParameter(), variable)
+ for variable in input_variables])
+ input_parameters = [variable.getformalParameter()
+ for variable in input_variables]
one_input_connected = False
- for i, variable in enumerate(block.inputVariables.getvariable()):
- input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
- connections = variable.connectionPointIn.getconnections()
- if connections is not None:
- parameter = variable.getformalParameter()
- if parameter != "EN":
- one_input_connected = True
- if inout_variables.has_key(parameter):
- value = generator.ComputeExpression(body, connections, executionOrderId > 0, True)
- inout_variables[parameter] = value
+ all_input_connected = True
+ for i, parameter in enumerate(input_parameters):
+ variable = input_connected.get(parameter)
+ if variable is not None:
+ input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
+ connections = variable.connectionPointIn.getconnections()
+ if connections is not None:
+ if parameter != "EN":
+ one_input_connected = True
+ if inout_variables.has_key(parameter):
+ expression = generator.ComputeExpression(body, connections, executionOrderId > 0, True)
+ if expression is not None:
+ inout_variables[parameter] = value
+ else:
+ expression = generator.ComputeExpression(body, connections, executionOrderId > 0)
+ if expression is not None:
+ connected_vars.append(([(parameter, input_info), (" := ", ())],
+ generator.ExtractModifier(variable, expression, input_info)))
else:
- value = generator.ComputeExpression(body, connections, executionOrderId > 0)
- if len(output_variables) > 1:
- vars.append([(parameter, input_info),
- (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
- else:
- vars.append(generator.ExtractModifier(variable, value, input_info))
+ all_input_connected = False
+ else:
+ all_input_connected = False
+ if len(output_variables) > 1 or not all_input_connected:
+ vars = [name + value for name, value in connected_vars]
+ else:
+ vars = [value for name, value in connected_vars]
if one_input_connected:
for i, variable in enumerate(output_variables):
parameter = variable.getformalParameter()
- if not inout_variables.has_key(parameter):
+ if not inout_variables.has_key(parameter) and parameter in output_names + ["", "ENO"]:
if variable.getformalParameter() == "":
variable_name = "%s%d"%(type, block.getlocalId())
else:
@@ -103,67 +132,99 @@
generator.Program += [(");\n", ())]
else:
generator.Warnings.append(_("\"%s\" function cancelled in \"%s\" POU: No input connected")%(type, generator.TagName.split("::")[-1]))
- if link:
- connectionPoint = link.getposition()[-1]
- else:
- connectionPoint = None
- for i, variable in enumerate(output_variables):
- blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
- if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
- output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
- parameter = variable.getformalParameter()
- if inout_variables.has_key(parameter):
- output_value = inout_variables[parameter]
- else:
- if parameter == "":
- output_name = "%s%d"%(type, block.getlocalId())
- else:
- output_name = "%s%d_%s"%(type, block.getlocalId(), parameter)
- output_value = [(output_name, output_info)]
- return generator.ExtractModifier(variable, output_value, output_info)
elif block_infos["type"] == "functionBlock":
if not generator.ComputedBlocks.get(block, False) and not order:
generator.ComputedBlocks[block] = True
vars = []
- for i, variable in enumerate(block.inputVariables.getvariable()):
- input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
- connections = variable.connectionPointIn.getconnections()
- if connections is not None:
- parameter = variable.getformalParameter()
- value = generator.ComputeExpression(body, connections, executionOrderId > 0, inout_variables.has_key(parameter))
- vars.append([(parameter, input_info),
- (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
+ offset_idx = 0
+ for variable in input_variables:
+ parameter = variable.getformalParameter()
+ if parameter in input_names or parameter == "EN":
+ if parameter == "EN":
+ input_idx = 0
+ offset_idx = 1
+ else:
+ input_idx = offset_idx + input_names.index(parameter)
+ input_info = (generator.TagName, "block", block.getlocalId(), "input", input_idx)
+ connections = variable.connectionPointIn.getconnections()
+ if connections is not None:
+ expression = generator.ComputeExpression(body, connections, executionOrderId > 0, inout_variables.has_key(parameter))
+ if expression is not None:
+ vars.append([(parameter, input_info),
+ (" := ", ())] + generator.ExtractModifier(variable, expression, input_info))
generator.Program += [(generator.CurrentIndent, ()),
(name, (generator.TagName, "block", block.getlocalId(), "name")),
("(", ())]
generator.Program += JoinList([(", ", ())], vars)
generator.Program += [(");\n", ())]
- if link:
- connectionPoint = link.getposition()[-1]
+
+ if link:
+ connectionPoint = link.getposition()[-1]
+ output_parameter = link.getformalParameter()
+ else:
+ connectionPoint = None
+ output_parameter = None
+
+ output_variable = None
+ output_idx = 0
+ if output_parameter is not None:
+ if output_parameter in output_names or output_parameter == "ENO":
+ for variable in output_variables:
+ if variable.getformalParameter() == output_parameter:
+ output_variable = variable
+ if output_parameter != "ENO":
+ output_idx = output_names.index(output_parameter)
+ else:
+ for i, variable in enumerate(output_variables):
+ blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
+ if (not connectionPoint or
+ block.getx() + blockPointx == connectionPoint.getx() and
+ block.gety() + blockPointy == connectionPoint.gety()):
+ output_variable = variable
+ output_parameter = variable.getformalParameter()
+ output_idx = i
+
+ if output_variable is not None:
+ if block_infos["type"] == "function":
+ output_info = (generator.TagName, "block", block.getlocalId(), "output", output_idx)
+ if inout_variables.has_key(output_parameter):
+ output_value = inout_variables[output_parameter]
+ else:
+ if output_parameter == "":
+ output_name = "%s%d"%(type, block.getlocalId())
+ else:
+ output_name = "%s%d_%s"%(type, block.getlocalId(), output_parameter)
+ output_value = [(output_name, output_info)]
+ return generator.ExtractModifier(output_variable, output_value, output_info)
+
+ if block_infos["type"] == "functionBlock":
+ output_info = (generator.TagName, "block", block.getlocalId(), "output", output_idx)
+ output_name = generator.ExtractModifier(output_variable, [("%s.%s"%(name, output_parameter), output_info)], output_info)
+ if to_inout:
+ variable_name = "%s_%s"%(name, output_parameter)
+ if not generator.IsAlreadyDefined(variable_name):
+ if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
+ generator.Interface.append(("VAR", None, False, []))
+ if variable.connectionPointOut in generator.ConnectionTypes:
+ generator.Interface[-1][3].append(
+ (generator.ConnectionTypes[output_variable.connectionPointOut], variable_name, None, None))
+ else:
+ generator.Interface[-1][3].append(("ANY", variable_name, None, None))
+ generator.Program += [(generator.CurrentIndent, ()),
+ ("%s := "%variable_name, ())]
+ generator.Program += output_name
+ generator.Program += [(";\n", ())]
+ return [(variable_name, ())]
+ return output_name
+ if link is not None:
+ if output_parameter is None:
+ output_parameter = ""
+ if name:
+ blockname = "%s(%s)" % (name, type)
else:
- connectionPoint = None
- for i, variable in enumerate(block.outputVariables.getvariable()):
- blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
- if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
- output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
- output_name = generator.ExtractModifier(variable, [("%s.%s"%(name, variable.getformalParameter()), output_info)], output_info)
- if to_inout:
- variable_name = "%s_%s"%(name, variable.getformalParameter())
- if not generator.IsAlreadyDefined(variable_name):
- if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
- generator.Interface.append(("VAR", None, False, []))
- if variable.connectionPointOut in generator.ConnectionTypes:
- generator.Interface[-1][3].append((generator.ConnectionTypes[variable.connectionPointOut], variable_name, None, None))
- else:
- generator.Interface[-1][3].append(("ANY", variable_name, None, None))
- generator.Program += [(generator.CurrentIndent, ()),
- ("%s := "%variable_name, ())]
- generator.Program += output_name
- generator.Program += [(";\n", ())]
- return [(variable_name, ())]
- return output_name
- if link is not None:
- raise ValueError, _("No output variable found")
+ blockname = type
+ raise ValueError, _("No output %s variable found in block %s in POU %s. Connection must be broken") % \
+ (output_parameter, blockname, generator.Name)
def initialise_block(type, name, block = None):
return [(type, name, None, None)]
--- a/py_ext/PythonEditor.py Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/PythonEditor.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,485 +1,59 @@
-import wx
-import wx.grid
-import wx.stc as stc
import keyword
+import wx.stc as stc
-from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from controls.CustomStyledTextCtrl import faces
+from editors.CodeFileEditor import CodeFileEditor, CodeEditor
-if wx.Platform == '__WXMSW__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Courier New',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 10,
- 'size2': 8,
- }
-elif wx.Platform == '__WXMAC__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Monaco',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 12,
- 'size2': 10,
- }
-else:
- faces = { 'times': 'Times',
- 'mono' : 'Courier',
- 'helv' : 'Helvetica',
- 'other': 'new century schoolbook',
- 'size' : 12,
- 'size2': 10,
- }
+class PythonCodeEditor(CodeEditor):
-[ID_PYTHONEDITOR,
-] = [wx.NewId() for _init_ctrls in range(1)]
-
-def GetCursorPos(old, new):
- old_length = len(old)
- new_length = len(new)
- common_length = min(old_length, new_length)
- i = 0
- for i in xrange(common_length):
- if old[i] != new[i]:
- break
- if old_length < new_length:
- if common_length > 0 and old[i] != new[i]:
- return i + new_length - old_length
- else:
- return i + new_length - old_length + 1
- elif old_length > new_length or i < min(old_length, new_length) - 1:
- if common_length > 0 and old[i] != new[i]:
- return i
- else:
- return i + 1
- else:
- return None
-
-class PythonEditor(ConfTreeNodeEditor):
-
- fold_symbols = 3
- CONFNODEEDITOR_TABS = [
- (_("Python code"), "_create_PythonCodeEditor")]
+ KEYWORDS = keyword.kwlist
+ COMMENT_HEADER = "#"
- def _create_PythonCodeEditor(self, prnt):
- self.PythonCodeEditor = stc.StyledTextCtrl(id=ID_PYTHONEDITOR, parent=prnt,
- name="TextViewer", pos=wx.DefaultPosition,
- size=wx.DefaultSize, style=0)
- self.PythonCodeEditor.ParentWindow = self
+ def SetCodeLexer(self):
+ self.SetLexer(stc.STC_LEX_PYTHON)
- self.PythonCodeEditor.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
- self.PythonCodeEditor.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
-
- self.PythonCodeEditor.SetLexer(stc.STC_LEX_PYTHON)
- self.PythonCodeEditor.SetKeyWords(0, " ".join(keyword.kwlist))
-
- self.PythonCodeEditor.SetProperty("fold", "1")
- self.PythonCodeEditor.SetProperty("tab.timmy.whinge.level", "1")
- self.PythonCodeEditor.SetMargins(0,0)
-
- self.PythonCodeEditor.SetViewWhiteSpace(False)
-
- self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
- self.PythonCodeEditor.SetEdgeColumn(78)
-
- # Set up the numbers in the margin for margin #1
- self.PythonCodeEditor.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
- # Reasonable value for, say, 4-5 digits using a mono font (40 pix)
- self.PythonCodeEditor.SetMarginWidth(1, 40)
-
- # Setup a margin to hold fold markers
- self.PythonCodeEditor.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
- self.PythonCodeEditor.SetMarginMask(2, stc.STC_MASK_FOLDERS)
- self.PythonCodeEditor.SetMarginSensitive(2, True)
- self.PythonCodeEditor.SetMarginWidth(2, 12)
-
- if self.fold_symbols == 0:
- # Arrow pointing right for contracted folders, arrow pointing down for expanded
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
- elif self.fold_symbols == 1:
- # Plus for contracted folders, minus for expanded
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
- elif self.fold_symbols == 2:
- # Like a flattened tree control using circular headers and curved joins
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040")
-
- elif self.fold_symbols == 3:
- # Like a flattened tree control using square headers
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
- self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
-
-
- self.PythonCodeEditor.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
- self.PythonCodeEditor.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
- self.PythonCodeEditor.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
-
- # Global default style
- if wx.Platform == '__WXMSW__':
- self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier New')
- elif wx.Platform == '__WXMAC__':
- # TODO: if this looks fine on Linux too, remove the Mac-specific case
- # and use this whenever OS != MSW.
- self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Monaco')
- else:
- defsize = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT).GetPointSize()
- self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier,size:%d'%defsize)
-
- # Clear styles and revert to default.
- self.PythonCodeEditor.StyleClearAll()
-
- # Following style specs only indicate differences from default.
- # The rest remains unchanged.
-
# Line numbers in margin
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')
+ self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2,size:%(size)d' % faces)
# Highlighted brace
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00')
+ self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00,size:%(size)d' % faces)
# Unmatched brace
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000')
+ self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000,size:%(size)d' % faces)
# Indentation guide
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD")
+ self.StyleSetSpec(stc.STC_STYLE_INDENTGUIDE, 'fore:#CDCDCD,size:%(size)d' % faces)
# Python styles
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
+ self.StyleSetSpec(stc.STC_P_DEFAULT, 'fore:#000000,size:%(size)d' % faces)
# Comments
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, 'fore:#008000,back:#F0FFF0')
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0')
+ self.StyleSetSpec(stc.STC_P_COMMENTLINE, 'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
# Numbers
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080')
+ self.StyleSetSpec(stc.STC_P_NUMBER, 'fore:#008080,size:%(size)d' % faces)
# Strings and characters
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080')
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080')
+ self.StyleSetSpec(stc.STC_P_STRING, 'fore:#800080,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_P_CHARACTER, 'fore:#800080,size:%(size)d' % faces)
# Keywords
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
+ self.StyleSetSpec(stc.STC_P_WORD, 'fore:#000080,bold,size:%(size)d' % faces)
# Triple quotes
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA')
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA')
+ self.StyleSetSpec(stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
+ self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
# Class names
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold')
+ self.StyleSetSpec(stc.STC_P_CLASSNAME, 'fore:#0000FF,bold,size:%(size)d' % faces)
# Function names
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold')
+ self.StyleSetSpec(stc.STC_P_DEFNAME, 'fore:#008080,bold,size:%(size)d' % faces)
# Operators
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold')
+ self.StyleSetSpec(stc.STC_P_OPERATOR, 'fore:#800000,bold,size:%(size)d' % faces)
# Identifiers. I leave this as not bold because everything seems
# to be an identifier if it doesn't match the above criterae
- self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000')
+ self.StyleSetSpec(stc.STC_P_IDENTIFIER, 'fore:#000000,size:%(size)d' % faces)
+
- # Caret color
- self.PythonCodeEditor.SetCaretForeground("BLUE")
- # Selection background
- self.PythonCodeEditor.SetSelBackground(1, '#66CCFF')
+#-------------------------------------------------------------------------------
+# CFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
- self.PythonCodeEditor.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
- self.PythonCodeEditor.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
-
- # register some images for use in the AutoComplete box.
- #self.RegisterImage(1, images.getSmilesBitmap())
- self.PythonCodeEditor.RegisterImage(1,
- wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(16,16)))
- self.PythonCodeEditor.RegisterImage(2,
- wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
- self.PythonCodeEditor.RegisterImage(3,
- wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
+class PythonEditor(CodeFileEditor):
+
+ CONFNODEEDITOR_TABS = [
+ (_("Python code"), "_create_CodePanel")]
+ CODE_EDITOR = PythonCodeEditor
- # Indentation and tab stuff
- self.PythonCodeEditor.SetIndent(4) # Proscribed indent size for wx
- self.PythonCodeEditor.SetIndentationGuides(True) # Show indent guides
- self.PythonCodeEditor.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space
- self.PythonCodeEditor.SetTabIndents(True) # Tab key indents
- self.PythonCodeEditor.SetTabWidth(4) # Proscribed tab size for wx
- self.PythonCodeEditor.SetUseTabs(False) # Use spaces rather than tabs, or
- # TabTimmy will complain!
- # White space
- self.PythonCodeEditor.SetViewWhiteSpace(False) # Don't view white space
-
- # EOL: Since we are loading/saving ourselves, and the
- # strings will always have \n's in them, set the STC to
- # edit them that way.
- self.PythonCodeEditor.SetEOLMode(wx.stc.STC_EOL_LF)
- self.PythonCodeEditor.SetViewEOL(False)
-
- # No right-edge mode indicator
- self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_NONE)
-
- self.PythonCodeEditor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
-
- self.PythonCodeEditor.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR)
- self.PythonCodeEditor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
- self.PythonCodeEditor.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_PYTHONEDITOR)
-
- return self.PythonCodeEditor
-
- def __init__(self, parent, controler, window):
- ConfTreeNodeEditor.__init__(self, parent, controler, window)
-
- self.DisableEvents = False
- self.CurrentAction = None
-
- def GetBufferState(self):
- return self.Controler.GetBufferState()
-
- def Undo(self):
- self.Controler.LoadPrevious()
- self.RefreshView()
-
- def Redo(self):
- self.Controler.LoadNext()
- self.RefreshView()
-
- def OnModification(self, event):
- if not self.DisableEvents:
- mod_type = event.GetModificationType()
- if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
- if mod_type&wx.stc.STC_MOD_BEFOREINSERT:
- if self.CurrentAction is None:
- self.StartBuffering()
- elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
- self.CurrentAction = ("Add", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
- elif mod_type&wx.stc.STC_MOD_BEFOREDELETE:
- if self.CurrentAction == None:
- self.StartBuffering()
- elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
- self.CurrentAction = ("Delete", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
- event.Skip()
-
- def OnDoDrop(self, event):
- self.ResetBuffer()
- wx.CallAfter(self.RefreshModel)
- event.Skip()
-
- # Buffer the last model state
- def RefreshBuffer(self):
- self.Controler.BufferPython()
- if self.ParentWindow is not None:
- self.ParentWindow.RefreshTitle()
- self.ParentWindow.RefreshFileMenu()
- self.ParentWindow.RefreshEditMenu()
- self.ParentWindow.RefreshPageTitles()
-
- def StartBuffering(self):
- self.Controler.StartBuffering()
- if self.ParentWindow is not None:
- self.ParentWindow.RefreshTitle()
- self.ParentWindow.RefreshFileMenu()
- self.ParentWindow.RefreshEditMenu()
- self.ParentWindow.RefreshPageTitles()
-
- def ResetBuffer(self):
- if self.CurrentAction != None:
- self.Controler.EndBuffering()
- self.CurrentAction = None
-
- def RefreshView(self):
- ConfTreeNodeEditor.RefreshView(self)
-
- self.ResetBuffer()
- self.DisableEvents = True
- old_cursor_pos = self.PythonCodeEditor.GetCurrentPos()
- old_text = self.PythonCodeEditor.GetText()
- new_text = self.Controler.GetPythonCode()
- self.PythonCodeEditor.SetText(new_text)
- new_cursor_pos = GetCursorPos(old_text, new_text)
- if new_cursor_pos != None:
- self.PythonCodeEditor.GotoPos(new_cursor_pos)
- else:
- self.PythonCodeEditor.GotoPos(old_cursor_pos)
- self.PythonCodeEditor.ScrollToColumn(0)
- self.PythonCodeEditor.EmptyUndoBuffer()
- self.DisableEvents = False
-
- self.PythonCodeEditor.Colourise(0, -1)
-
- def RefreshModel(self):
- self.Controler.SetPythonCode(self.PythonCodeEditor.GetText())
-
- def OnKeyPressed(self, event):
- if self.PythonCodeEditor.CallTipActive():
- self.PythonCodeEditor.CallTipCancel()
- key = event.GetKeyCode()
-
- if key == 32 and event.ControlDown():
- pos = self.PythonCodeEditor.GetCurrentPos()
-
- # Code completion
- if not event.ShiftDown():
- self.PythonCodeEditor.AutoCompSetIgnoreCase(False) # so this needs to match
-
- # Images are specified with a appended "?type"
- self.PythonCodeEditor.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist]))
- else:
- event.Skip()
-
- def OnKillFocus(self, event):
- self.PythonCodeEditor.AutoCompCancel()
- event.Skip()
-
- def OnUpdateUI(self, evt):
- # check for matching braces
- braceAtCaret = -1
- braceOpposite = -1
- charBefore = None
- caretPos = self.PythonCodeEditor.GetCurrentPos()
-
- if caretPos > 0:
- charBefore = self.PythonCodeEditor.GetCharAt(caretPos - 1)
- styleBefore = self.PythonCodeEditor.GetStyleAt(caretPos - 1)
-
- # check before
- if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
- braceAtCaret = caretPos - 1
-
- # check after
- if braceAtCaret < 0:
- charAfter = self.PythonCodeEditor.GetCharAt(caretPos)
- styleAfter = self.PythonCodeEditor.GetStyleAt(caretPos)
-
- if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
- braceAtCaret = caretPos
-
- if braceAtCaret >= 0:
- braceOpposite = self.PythonCodeEditor.BraceMatch(braceAtCaret)
-
- if braceAtCaret != -1 and braceOpposite == -1:
- self.PythonCodeEditor.BraceBadLight(braceAtCaret)
- else:
- self.PythonCodeEditor.BraceHighlight(braceAtCaret, braceOpposite)
-
- def OnMarginClick(self, evt):
- # fold and unfold as needed
- if evt.GetMargin() == 2:
- if evt.GetShift() and evt.GetControl():
- self.FoldAll()
- else:
- lineClicked = self.PythonCodeEditor.LineFromPosition(evt.GetPosition())
-
- if self.PythonCodeEditor.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
- if evt.GetShift():
- self.PythonCodeEditor.SetFoldExpanded(lineClicked, True)
- self.Expand(lineClicked, True, True, 1)
- elif evt.GetControl():
- if self.PythonCodeEditor.GetFoldExpanded(lineClicked):
- self.PythonCodeEditor.SetFoldExpanded(lineClicked, False)
- self.Expand(lineClicked, False, True, 0)
- else:
- self.PythonCodeEditor.SetFoldExpanded(lineClicked, True)
- self.Expand(lineClicked, True, True, 100)
- else:
- self.PythonCodeEditor.ToggleFold(lineClicked)
-
-
- def FoldAll(self):
- lineCount = self.PythonCodeEditor.GetLineCount()
- expanding = True
-
- # find out if we are folding or unfolding
- for lineNum in range(lineCount):
- if self.PythonCodeEditor.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
- expanding = not self.PythonCodeEditor.GetFoldExpanded(lineNum)
- break
-
- lineNum = 0
-
- while lineNum < lineCount:
- level = self.PythonCodeEditor.GetFoldLevel(lineNum)
- if level & stc.STC_FOLDLEVELHEADERFLAG and \
- (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
-
- if expanding:
- self.PythonCodeEditor.SetFoldExpanded(lineNum, True)
- lineNum = self.Expand(lineNum, True)
- lineNum = lineNum - 1
- else:
- lastChild = self.PythonCodeEditor.GetLastChild(lineNum, -1)
- self.PythonCodeEditor.SetFoldExpanded(lineNum, False)
-
- if lastChild > lineNum:
- self.PythonCodeEditor.HideLines(lineNum+1, lastChild)
-
- lineNum = lineNum + 1
-
-
-
- def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
- lastChild = self.PythonCodeEditor.GetLastChild(line, level)
- line = line + 1
-
- while line <= lastChild:
- if force:
- if visLevels > 0:
- self.PythonCodeEditor.ShowLines(line, line)
- else:
- self.PythonCodeEditor.HideLines(line, line)
- else:
- if doExpand:
- self.PythonCodeEditor.ShowLines(line, line)
-
- if level == -1:
- level = self.PythonCodeEditor.GetFoldLevel(line)
-
- if level & stc.STC_FOLDLEVELHEADERFLAG:
- if force:
- if visLevels > 1:
- self.PythonCodeEditor.SetFoldExpanded(line, True)
- else:
- self.PythonCodeEditor.SetFoldExpanded(line, False)
-
- line = self.Expand(line, doExpand, force, visLevels-1)
-
- else:
- if doExpand and self.PythonCodeEditor.GetFoldExpanded(line):
- line = self.Expand(line, True, force, visLevels-1)
- else:
- line = self.Expand(line, False, force, visLevels-1)
- else:
- line = line + 1
-
- return line
-
- def Cut(self):
- self.ResetBuffer()
- self.DisableEvents = True
- self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_CUT)
- self.DisableEvents = False
- self.RefreshModel()
- self.RefreshBuffer()
-
- def Copy(self):
- self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_COPY)
-
- def Paste(self):
- self.ResetBuffer()
- self.DisableEvents = True
- self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
- self.DisableEvents = False
- self.RefreshModel()
- self.RefreshBuffer()
--- a/py_ext/PythonFileCTNMixin.py Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/PythonFileCTNMixin.py Wed Jul 31 10:45:07 2013 +0900
@@ -3,20 +3,30 @@
from PythonEditor import PythonEditor
from xml.dom import minidom
-from xmlclass import *
+from xmlclass import GenerateClassesFromXSD
import cPickle
+from CodeFileTreeNode import CodeFile
+
PythonClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd"))
-class PythonFileCTNMixin:
-
+class PythonFileCTNMixin(CodeFile):
+
+ CODEFILE_NAME = "PyFile"
+ SECTIONS_NAMES = [
+ "globals",
+ "init",
+ "cleanup",
+ "start",
+ "stop"]
EditorType = PythonEditor
def __init__(self):
+ CodeFile.__init__(self)
filepath = self.PythonFileName()
- self.PythonCode = PythonClasses["Python"]()
+ python_code = PythonClasses["Python"]()
if os.path.isfile(filepath):
xmlfile = open(filepath, 'r')
tree = minidom.parse(xmlfile)
@@ -24,87 +34,179 @@
for child in tree.childNodes:
if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "Python":
- self.PythonCode.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
- self.CreatePythonBuffer(True)
- else:
- self.CreatePythonBuffer(False)
- self.OnCTNSave()
-
+ python_code.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
+ self.CodeFile.globals.settext(python_code.gettext())
+ os.remove(filepath)
+ self.CreateCodeFileBuffer(False)
+ self.OnCTNSave()
+
+ def CodeFileName(self):
+ return os.path.join(self.CTNPath(), "pyfile.xml")
+
def PythonFileName(self):
return os.path.join(self.CTNPath(), "py_ext.xml")
- def GetFilename(self):
- if self.PythonBuffer.IsCurrentSaved():
- return "py_ext"
+ PreSectionsTexts = {}
+ PostSectionsTexts = {}
+ def GetSection(self,section):
+ return self.PreSectionsTexts.get(section,"") + "\n" + \
+ getattr(self.CodeFile, section).gettext() + "\n" + \
+ self.PostSectionsTexts.get(section,"")
+
+
+ def CTNGenerate_C(self, buildpath, locations):
+ # location string for that CTN
+ location_str = "_".join(map(lambda x:str(x),
+ self.GetCurrentLocation()))
+ configname = self.GetCTRoot().GetProjectConfigNames()[0]
+
+
+ # python side PLC global variables access stub
+ globalstubs = "\n".join(["""\
+_%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\
+ TypeTranslator["%(IECtype)s"]
+_PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s
+_PySafeGetPLCGlob_%(name)s.restype = None
+_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
+_PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s
+_PySafeSetPLCGlob_%(name)s.restype = None
+_PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
+""" % { "name": variable.getname(),
+ "configname": configname.upper(),
+ "uppername": variable.getname().upper(),
+ "IECtype": variable.gettype()}
+ for variable in self.CodeFile.variables.variable])
+
+ # Runtime calls (start, stop, init, and cleanup)
+ rtcalls = ""
+ for section in self.SECTIONS_NAMES:
+ if section != "globals":
+ rtcalls += "def _runtime_%s_%s():\n" % (location_str, section)
+ sectiontext = self.GetSection(section).strip()
+ if sectiontext:
+ rtcalls += ' ' + \
+ sectiontext.replace('\n', '\n ')+"\n\n"
+ else:
+ rtcalls += " pass\n\n"
+
+ globalsection = self.GetSection("globals")
+
+ PyFileContent = """\
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+## Code generated by Beremiz python mixin confnode
+##
+
+## Code for PLC global variable access
+from targets.typemapping import TypeTranslator
+import ctypes
+%(globalstubs)s
+
+## User code in "global" scope
+%(globalsection)s
+
+## Beremiz python runtime calls
+%(rtcalls)s
+
+""" % locals()
+
+ # write generated content to python file
+ runtimefile_path = os.path.join(buildpath,
+ "runtime_%s.py"%location_str)
+ runtimefile = open(runtimefile_path, 'w')
+ runtimefile.write(PyFileContent.encode('utf-8'))
+ runtimefile.close()
+
+ # C code for safe global variables access
+
+ vardecfmt = """\
+extern __IEC_%(IECtype)s_t %(configname)s__%(uppername)s;
+IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s;
+IEC_%(IECtype)s __%(name)s_wbuffer;
+long __%(name)s_rlock = 0;
+long __%(name)s_wlock = 0;
+int __%(name)s_wbuffer_written = 0;
+void __SafeGetPLCGlob_%(name)s(IEC_%(IECtype)s *pvalue){
+ while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1));
+ *pvalue = __%(name)s_rbuffer;
+ AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+}
+void __SafeSetPLCGlob_%(name)s(IEC_%(IECtype)s *value){
+ while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1));
+ __%(name)s_wbuffer = *value;
+ __%(name)s_wbuffer_written = 1;
+ AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+}
+
+"""
+ varretfmt = """\
+ if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){
+ if(__%(name)s_wbuffer_written == 1){
+ %(configname)s__%(uppername)s.value = __%(name)s_wbuffer;
+ __%(name)s_wbuffer_written = 0;
+ }
+ AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+ }
+"""
+ varpubfmt = """\
+ if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
+ __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+ AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+ }
+"""
+
+ var_str = map("\n".join, zip(*[
+ map(lambda f : f % varinfo,
+ (vardecfmt, varretfmt, varpubfmt))
+ for varinfo in map(lambda variable : {
+ "name": variable.getname(),
+ "configname": configname.upper(),
+ "uppername": variable.getname().upper(),
+ "IECtype": variable.gettype()},
+ self.CodeFile.variables.variable)]))
+ if len(var_str) > 0:
+ vardec, varret, varpub = var_str
else:
- return "~py_ext~"
-
- def SetPythonCode(self, text):
- self.PythonCode.settext(text)
-
- def GetPythonCode(self):
- return self.PythonCode.gettext()
-
- def CTNTestModified(self):
- return self.ChangesToSave or not self.PythonIsSaved()
-
- def OnCTNSave(self):
- filepath = self.PythonFileName()
-
- text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
- extras = {"xmlns":"http://www.w3.org/2001/XMLSchema",
- "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
- "xsi:schemaLocation" : "py_ext_xsd.xsd"}
- text += self.PythonCode.generateXMLText("Python", 0, extras)
-
- xmlfile = open(filepath,"w")
- xmlfile.write(text.encode("utf-8"))
- xmlfile.close()
-
- self.MarkPythonAsSaved()
- return True
-
-#-------------------------------------------------------------------------------
-# Current Buffering Management Functions
-#-------------------------------------------------------------------------------
-
- """
- Return a copy of the project
- """
- def Copy(self, model):
- return cPickle.loads(cPickle.dumps(model))
-
- def CreatePythonBuffer(self, saved):
- self.Buffering = False
- self.PythonBuffer = UndoBuffer(cPickle.dumps(self.PythonCode), saved)
-
- def BufferPython(self):
- self.PythonBuffer.Buffering(cPickle.dumps(self.PythonCode))
-
- def StartBuffering(self):
- self.Buffering = True
-
- def EndBuffering(self):
- if self.Buffering:
- self.PythonBuffer.Buffering(cPickle.dumps(self.PythonCode))
- self.Buffering = False
-
- def MarkPythonAsSaved(self):
- self.EndBuffering()
- self.PythonBuffer.CurrentSaved()
-
- def PythonIsSaved(self):
- return self.PythonBuffer.IsCurrentSaved() and not self.Buffering
-
- def LoadPrevious(self):
- self.EndBuffering()
- self.PythonCode = cPickle.loads(self.PythonBuffer.Previous())
-
- def LoadNext(self):
- self.PythonCode = cPickle.loads(self.PythonBuffer.Next())
-
- def GetBufferState(self):
- first = self.PythonBuffer.IsFirst() and not self.Buffering
- last = self.PythonBuffer.IsLast()
- return not first, not last
-
+ vardec = varret = varpub = ""
+
+ PyCFileContent = """\
+/*
+ * Code generated by Beremiz py_ext confnode
+ * for safe global variables access
+ */
+#include "iec_types_all.h"
+#include "beremiz.h"
+
+/* User variables reference */
+%(vardec)s
+
+/* Beremiz confnode functions */
+int __init_%(location_str)s(int argc,char **argv){
+ return 0;
+}
+
+void __cleanup_%(location_str)s(void){
+}
+
+void __retrieve_%(location_str)s(void){
+%(varret)s
+}
+
+void __publish_%(location_str)s(void){
+%(varpub)s
+}
+""" % locals()
+
+ Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c"%location_str)
+ pycfile = open(Gen_PyCfile_path,'w')
+ pycfile.write(PyCFileContent)
+ pycfile.close()
+
+ matiec_flags = '"-I%s"'%os.path.abspath(
+ self.GetCTRoot().GetIECLibPath())
+
+ return ([(Gen_PyCfile_path, matiec_flags)],
+ "",
+ True,
+ ("runtime_%s.py"%location_str, file(runtimefile_path,"rb")))
+
--- a/py_ext/py_ext.py Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/py_ext.py Wed Jul 31 10:45:07 2013 +0900
@@ -8,18 +8,21 @@
def Generate_C(self, buildpath, varlist, IECCFLAGS):
- plc_python_filepath = os.path.join(os.path.split(__file__)[0], "plc_python.c")
+ plc_python_filepath = os.path.join(
+ os.path.split(__file__)[0], "plc_python.c")
plc_python_file = open(plc_python_filepath, 'r')
plc_python_code = plc_python_file.read()
plc_python_file.close()
python_eval_fb_list = []
for v in varlist:
- if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL","PYTHON_POLL"]:
+ if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL",
+ "PYTHON_POLL"]:
python_eval_fb_list.append(v)
python_eval_fb_count = max(1, len(python_eval_fb_list))
# prepare python code
- plc_python_code = plc_python_code % { "python_eval_fb_count": python_eval_fb_count }
+ plc_python_code = plc_python_code % {
+ "python_eval_fb_count": python_eval_fb_count }
Gen_Pythonfile_path = os.path.join(buildpath, "py_ext.c")
pythonfile = open(Gen_Pythonfile_path,'w')
@@ -33,15 +36,4 @@
def GetIconName(self):
return "Pyfile"
- def CTNGenerate_C(self, buildpath, locations):
- current_location = self.GetCurrentLocation()
- # define a unique name for the generated C file
- location_str = "_".join(map(lambda x:str(x), current_location))
-
- runtimefile_path = os.path.join(buildpath, "runtime_%s.py"%location_str)
- runtimefile = open(runtimefile_path, 'w')
- runtimefile.write(self.GetPythonCode())
- runtimefile.close()
-
- return [], "", False, ("runtime_%s.py"%location_str, file(runtimefile_path,"rb"))
--- a/runtime/PLCObject.py Wed Mar 13 12:34:55 2013 +0900
+++ b/runtime/PLCObject.py Wed Jul 31 10:45:07 2013 +0900
@@ -25,7 +25,7 @@
import Pyro.core as pyro
from threading import Timer, Thread, Lock, Semaphore
import ctypes, os, commands, types, sys
-from targets.typemapping import LogLevelsDefault, LogLevelsCount, SameEndianessTypeTranslator as TypeTranslator
+from targets.typemapping import LogLevelsDefault, LogLevelsCount, TypeTranslator, UnpackDebugBuffer
if os.name in ("nt", "ce"):
@@ -67,13 +67,14 @@
self.hmi_frame = None
self.website = website
self._loading_error = None
- self.python_threads_vars = None
+ self.python_runtime_vars = None
# Get the last transfered PLC if connector must be restart
try:
self.CurrentPLCFilename=open(
self._GetMD5FileName(),
"r").read().strip() + lib_ext
+ self.LoadPLC()
except Exception, e:
self.PLCStatus = "Empty"
self.CurrentPLCFilename=None
@@ -90,12 +91,15 @@
msg, = args
return self._LogMessage(level, msg, len(msg))
+ def ResetLogCount(self):
+ if self._ResetLogCount is not None:
+ self._ResetLogCount()
def GetLogCount(self, level):
if self._GetLogCount is not None :
return int(self._GetLogCount(level))
elif self._loading_error is not None and level==0:
- return 1;
+ return 1
def GetLogMessage(self, level, msgid):
tick = ctypes.c_uint32()
@@ -122,7 +126,7 @@
return os.path.join(self.workingdir,self.CurrentPLCFilename)
- def _LoadNewPLC(self):
+ def LoadPLC(self):
"""
Load PLC library
Declare all functions, arguments and return values
@@ -182,6 +186,9 @@
self._resumeDebug = self.PLClibraryHandle.resumeDebug
self._resumeDebug.restype = None
+ self._ResetLogCount = self.PLClibraryHandle.ResetLogCount
+ self._ResetLogCount.restype = None
+
self._GetLogCount = self.PLClibraryHandle.GetLogCount
self._GetLogCount.restype = ctypes.c_uint32
self._GetLogCount.argtypes = [ctypes.c_uint8]
@@ -189,19 +196,26 @@
self._LogMessage = self.PLClibraryHandle.LogMessage
self._LogMessage.restype = ctypes.c_int
self._LogMessage.argtypes = [ctypes.c_uint8, ctypes.c_char_p, ctypes.c_uint32]
-
+
self._log_read_buffer = ctypes.create_string_buffer(1<<14) #16K
self._GetLogMessage = self.PLClibraryHandle.GetLogMessage
self._GetLogMessage.restype = ctypes.c_uint32
self._GetLogMessage.argtypes = [ctypes.c_uint8, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32)]
self._loading_error = None
+
+ self.PythonRuntimeInit()
+
return True
except:
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
return False
+ def UnLoadPLC(self):
+ self.PythonRuntimeCleanup()
+ self._FreePLC()
+
def _FreePLC(self):
"""
Unload PLC library.
@@ -209,7 +223,7 @@
"""
self.PLClibraryLock.acquire()
# Forget all refs to library
- self._startPLC = lambda:None
+ self._startPLC = lambda x,y:None
self._stopPLC = lambda:None
self._ResetDebugVariables = lambda:None
self._RegisterDebugVariable = lambda x, y:None
@@ -231,49 +245,75 @@
self.PLClibraryLock.release()
return False
- def PrepareRuntimePy(self):
- self.python_threads_vars = globals().copy()
- self.python_threads_vars["WorkingDir"] = self.workingdir
- self.python_threads_vars["website"] = self.website
- self.python_threads_vars["_runtime_begin"] = []
- self.python_threads_vars["_runtime_cleanup"] = []
- self.python_threads_vars["PLCObject"] = self
- self.python_threads_vars["PLCBinary"] = self.PLClibraryHandle
-
+ def PythonRuntimeCall(self, methodname):
+ """
+ Calls init, start, stop or cleanup method provided by
+ runtime python files, loaded when new PLC uploaded
+ """
+ for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
+ res,exp = self.evaluator(method)
+ if exp is not None:
+ self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
+
+ def PythonRuntimeInit(self):
+ MethodNames = ["init", "start", "stop", "cleanup"]
+ self.python_runtime_vars = globals().copy()
+ self.python_runtime_vars["WorkingDir"] = self.workingdir
+ self.python_runtime_vars["website"] = self.website
+ for methodname in MethodNames :
+ self.python_runtime_vars["_runtime_%s"%methodname] = []
+ self.python_runtime_vars["PLCObject"] = self
+ self.python_runtime_vars["PLCBinary"] = self.PLClibraryHandle
+ class PLCSafeGlobals:
+ def __getattr__(_self, name):
+ try :
+ t = self.python_runtime_vars["_"+name+"_ctype"]
+ except KeyError:
+ raise KeyError("Try to get unknown shared global variable : %s"%name)
+ v = t()
+ r = self.python_runtime_vars["_PySafeGetPLCGlob_"+name](ctypes.byref(v))
+ return self.python_runtime_vars["_"+name+"_unpack"](v)
+ def __setattr__(_self, name, value):
+ try :
+ t = self.python_runtime_vars["_"+name+"_ctype"]
+ except KeyError:
+ raise KeyError("Try to set unknown shared global variable : %s"%name)
+ v = self.python_runtime_vars["_"+name+"_pack"](t,value)
+ self.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
+ self.python_runtime_vars["PLCGlobals"] = PLCSafeGlobals()
try:
for filename in os.listdir(self.workingdir):
name, ext = os.path.splitext(filename)
if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
- execfile(os.path.join(self.workingdir, filename), self.python_threads_vars)
- runtime_begin = self.python_threads_vars.get("_%s_begin" % name, None)
- if runtime_begin is not None:
- self.python_threads_vars["_runtime_begin"].append(runtime_begin)
- runtime_cleanup = self.python_threads_vars.get("_%s_cleanup" % name, None)
- if runtime_cleanup is not None:
- self.python_threads_vars["_runtime_cleanup"].append(runtime_cleanup)
-
- for runtime_begin in self.python_threads_vars.get("_runtime_begin", []):
- runtime_begin()
+ execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
+ for methodname in MethodNames:
+ method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
+ if method is not None:
+ self.python_runtime_vars["_runtime_%s"%methodname].append(method)
except:
self.LogMessage(0,traceback.format_exc())
raise
+ self.PythonRuntimeCall("init")
+
if self.website is not None:
self.website.PLCStarted()
- def FinishRuntimePy(self):
- for runtime_cleanup in self.python_threads_vars.get("_runtime_cleanup", []):
- runtime_cleanup()
+
+ def PythonRuntimeCleanup(self):
+ if self.python_runtime_vars is not None:
+ self.PythonRuntimeCall("cleanup")
+
if self.website is not None:
self.website.PLCStopped()
- self.python_threads_vars = None
+
+ self.python_runtime_vars = None
def PythonThreadProc(self):
self.PLCStatus = "Started"
self.StatusChange()
self.StartSem.release()
- res,exp = self.evaluator(self.PrepareRuntimePy)
- if exp is not None: raise(exp)
+ self.PythonRuntimeCall("start")
res,cmd,blkid = "None","None",ctypes.c_void_p()
compile_cache={}
while True:
@@ -284,24 +324,25 @@
if cmd is None:
break
try :
- self.python_threads_vars["FBID"]=FBID
+ self.python_runtime_vars["FBID"]=FBID
ccmd,AST =compile_cache.get(FBID, (None,None))
if ccmd is None or ccmd!=cmd:
AST = compile(cmd, '<plc>', 'eval')
compile_cache[FBID]=(cmd,AST)
- result,exp = self.evaluator(eval,cmd,self.python_threads_vars)
+ result,exp = self.evaluator(eval,cmd,self.python_runtime_vars)
if exp is not None:
- raise(exp)
+ res = "#EXCEPTION : "+str(exp[1])
+ self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
+ '\n'.join(traceback.format_exception(*exp))))
else:
res=str(result)
- self.python_threads_vars["FBID"]=None
+ self.python_runtime_vars["FBID"]=None
except Exception,e:
res = "#EXCEPTION : "+str(e)
self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
self.PLCStatus = "Stopped"
self.StatusChange()
- exp,res = self.evaluator(self.FinishRuntimePy)
- if exp is not None: raise(exp)
+ self.PythonRuntimeCall("stop")
def StartPLC(self):
if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
@@ -343,12 +384,13 @@
return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
def NewPLC(self, md5sum, data, extrafiles):
- self.LogMessage("NewPLC (%s)"%md5sum)
if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
NewFileName = md5sum + lib_ext
extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
- self._FreePLC()
+ self.UnLoadPLC()
+
+ self.LogMessage("NewPLC (%s)"%md5sum)
self.PLCStatus = "Empty"
try:
@@ -385,9 +427,10 @@
PLCprint(traceback.format_exc())
return False
- if self._LoadNewPLC():
+ if self.LoadPLC():
self.PLCStatus = "Stopped"
else:
+ self.PLCStatus = "Broken"
self._FreePLC()
self.StatusChange()
@@ -431,39 +474,20 @@
Return a list of variables, corresponding to the list of required idx
"""
if self.PLCStatus == "Started":
- res=[]
tick = ctypes.c_uint32()
size = ctypes.c_uint32()
- buffer = ctypes.c_void_p()
- offset = 0
+ buff = ctypes.c_void_p()
+ TraceVariables = None
if self.PLClibraryLock.acquire(False):
if self._GetDebugData(ctypes.byref(tick),
ctypes.byref(size),
- ctypes.byref(buffer)) == 0:
+ ctypes.byref(buff)) == 0:
if size.value:
- for idx, iectype, forced in self._Idxs:
- cursor = ctypes.c_void_p(buffer.value + offset)
- c_type,unpack_func, pack_func = \
- TypeTranslator.get(iectype,
- (None,None,None))
- if c_type is not None and offset < size.value:
- res.append(unpack_func(
- ctypes.cast(cursor,
- ctypes.POINTER(c_type)).contents))
- offset += ctypes.sizeof(c_type)
- else:
- if c_type is None:
- PLCprint("Debug error - " + iectype +
- " not supported !")
- #if offset >= size.value:
- #PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size.value))
- break
+ TraceVariables = UnpackDebugBuffer(buff, size.value, self._Idxs)
self._FreeDebugData()
self.PLClibraryLock.release()
- if offset and offset == size.value:
- return self.PLCStatus, tick.value, res
- #elif size.value:
- #PLCprint("Debug error - wrong buffer unpack ! %d != %d"%(offset, size.value))
+ if TraceVariables is not None:
+ return self.PLCStatus, tick.value, TraceVariables
return self.PLCStatus, None, []
def RemoteExec(self, script, **kwargs):
--- a/svgui/svgui.py Wed Mar 13 12:34:55 2013 +0900
+++ b/svgui/svgui.py Wed Jul 31 10:45:07 2013 +0900
@@ -5,12 +5,13 @@
from POULibrary import POULibrary
from docutil import open_svg
+from py_ext import PythonFileCTNMixin
class SVGUILibrary(POULibrary):
def GetLibraryPath(self):
return os.path.join(os.path.split(__file__)[0], "pous.xml")
-class SVGUI:
+class SVGUI(PythonFileCTNMixin):
ConfNodeMethods = [
{"bitmap" : "ImportSVG",
@@ -26,13 +27,21 @@
def ConfNodePath(self):
return os.path.join(os.path.dirname(__file__))
- def _getSVGpath(self):
- # define name for IEC raw code file
- return os.path.join(self.CTNPath(), "gui.svg")
+ def _getSVGpath(self, project_path=None):
+ if project_path is None:
+ project_path = self.CTNPath()
+ # define name for SVG file containing gui layout
+ return os.path.join(project_path, "gui.svg")
def _getSVGUIserverpath(self):
return os.path.join(os.path.dirname(__file__), "svgui_server.py")
+ def OnCTNSave(self, from_project_path=None):
+ if from_project_path is not None:
+ shutil.copyfile(self._getSVGpath(from_project_path),
+ self._getSVGpath())
+ return PythonFileCTNMixin.OnCTNSave(self, from_project_path)
+
def CTNGenerate_C(self, buildpath, locations):
"""
Return C code generated by iec2c compiler
@@ -72,10 +81,10 @@
runtimefile = open(runtimefile_path, 'w')
runtimefile.write(svguiservercode % {"svgfile" : "gui.svg"})
runtimefile.write("""
-def _runtime_%(location)s_begin():
+def _runtime_%(location)s_start():
website.LoadHMI(%(svgui_class)s, %(jsmodules)s)
-def _runtime_%(location)s_cleanup():
+def _runtime_%(location)s_stop():
website.UnLoadHMI()
""" % {"location": location_str,
--- a/targets/Linux/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Linux/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
from ..toolchain_gcc import toolchain_gcc
class Linux_target(toolchain_gcc):
+ dlopen_prefix = "./"
extension = ".so"
def getBuilderCFLAGS(self):
return toolchain_gcc.getBuilderCFLAGS(self) + ["-fPIC"]
--- a/targets/Linux/plc_Linux_main.c Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Linux/plc_Linux_main.c Wed Jul 31 10:45:07 2013 +0900
@@ -11,7 +11,6 @@
#include <locale.h>
#include <semaphore.h>
-extern unsigned long long common_ticktime__;
static sem_t Run_PLC;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
@@ -97,8 +96,6 @@
{
struct sigevent sigev;
setlocale(LC_NUMERIC, "C");
- /* Define Ttick to 1ms if common_ticktime not defined */
- Ttick = common_ticktime__?common_ticktime__:1000000;
PLC_shutdown = 0;
--- a/targets/Win32/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Win32/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
from ..toolchain_gcc import toolchain_gcc
class Win32_target(toolchain_gcc):
+ dlopen_prefix = ""
extension = ".dll"
def getBuilderLDFLAGS(self):
return toolchain_gcc.getBuilderLDFLAGS(self) + ["-shared", "-lwinmm"]
--- a/targets/Win32/plc_Win32_main.c Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Win32/plc_Win32_main.c Wed Jul 31 10:45:07 2013 +0900
@@ -8,8 +8,6 @@
#include <windows.h>
#include <locale.h>
-/* provided by POUS.C */
-extern unsigned long long common_ticktime__;
long AtomicCompareExchange(long* atomicvar, long compared, long exchange)
{
@@ -77,8 +75,6 @@
unsigned long thread_id = 0;
BOOL tmp;
setlocale(LC_NUMERIC, "C");
- /* Define Ttick to 1ms if common_ticktime not defined */
- Ttick = common_ticktime__?common_ticktime__:1000000;
InitializeCriticalSection(&Atomic64CS);
--- a/targets/Xenomai/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Xenomai/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
from ..toolchain_gcc import toolchain_gcc
class Xenomai_target(toolchain_gcc):
+ dlopen_prefix = "./"
extension = ".so"
def getXenoConfig(self, flagsname):
""" Get xeno-config from target parameters """
--- a/targets/Xenomai/plc_Xenomai_main.c Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Xenomai/plc_Xenomai_main.c Wed Jul 31 10:45:07 2013 +0900
@@ -38,8 +38,6 @@
#define PYTHON_PIPE_MINOR 3
#define PIPE_SIZE 1
-/* provided by POUS.C */
-extern unsigned long common_ticktime__;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
@@ -170,9 +168,6 @@
/* no memory swapping for that process */
mlockall(MCL_CURRENT | MCL_FUTURE);
- /* Define Ttick to 1ms if common_ticktime not defined */
- Ttick = common_ticktime__?common_ticktime__:1000000;
-
PLC_shutdown = 0;
/*** RT Pipes creation and opening ***/
--- a/targets/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -70,6 +70,10 @@
def GetTargetCode(targetname):
return open(targets[targetname]["code"]).read()
+def GetHeader():
+ filename = path.join(path.split(__file__)[0],"beremiz.h")
+ return open(filename).read()
+
def GetCode(name):
filename = path.join(path.split(__file__)[0],name + ".c")
return open(filename).read()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/beremiz.h Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,14 @@
+/* Beremiz' header file for use by extensions */
+
+#include "iec_types.h"
+
+#define LOG_LEVELS 4
+#define LOG_CRITICAL 0
+#define LOG_WARNING 1
+#define LOG_INFO 2
+#define LOG_DEBUG 3
+
+extern unsigned long long common_ticktime__;
+int LogMessage(uint8_t level, char* buf, uint32_t size);
+long AtomicCompareExchange(long* atomicvar,long compared, long exchange);
+
--- a/targets/plc_common_main.c Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/**
- * Code common to all C targets
- **/
-
-#include "iec_types.h"
-/*
- * Prototypes of functions provided by generated C softPLC
- **/
-void config_run__(unsigned long tick);
-void config_init__(void);
-
-/*
- * Prototypes of functions provided by generated target C code
- * */
-void __init_debug(void);
-void __cleanup_debug(void);
-/*void __retrieve_debug(void);*/
-void __publish_debug(void);
-
-/*
- * Variables used by generated C softPLC and plugins
- **/
-IEC_TIME __CURRENT_TIME;
-IEC_BOOL __DEBUG = 0;
-unsigned long __tick = 0;
-
-/*
- * Variable generated by C softPLC and plugins
- **/
-extern unsigned long greatest_tick_count__;
-
-/* Help to quit cleanly when init fail at a certain level */
-static int init_level = 0;
-
-/*
- * Prototypes of functions exported by plugins
- **/
-%(calls_prototypes)s
-
-/*
- * Retrieve input variables, run PLC and publish output variables
- **/
-void __run(void)
-{
- __tick++;
- if (greatest_tick_count__)
- __tick %%= greatest_tick_count__;
-
- %(retrieve_calls)s
-
- /*__retrieve_debug();*/
-
- config_run__(__tick);
-
- __publish_debug();
-
- %(publish_calls)s
-
-}
-
-/*
- * Initialize variables according to PLC's default values,
- * and then init plugins with that values
- **/
-int __init(int argc,char **argv)
-{
- int res = 0;
- init_level = 0;
- config_init__();
- __init_debug();
- %(init_calls)s
- return res;
-}
-/*
- * Calls plugin cleanup proc.
- **/
-void __cleanup(void)
-{
- %(cleanup_calls)s
- __cleanup_debug();
-}
-
-
-void PLC_GetTime(IEC_TIME *CURRENT_TIME);
-void PLC_SetTimer(unsigned long long next, unsigned long long period);
-
-#define CALIBRATED -2
-#define NOT_CALIBRATED -1
-static int calibration_count = NOT_CALIBRATED;
-static IEC_TIME cal_begin;
-static long long Tsync = 0;
-static long long FreqCorr = 0;
-static int Nticks = 0;
-static unsigned long last_tick = 0;
-static long long Ttick = 0;
-#define mod %%
-/*
- * Call this on each external sync,
- * @param sync_align_ratio 0->100 : align ratio, < 0 : no align, calibrate period
- **/
-void align_tick(int sync_align_ratio)
-{
- /*
- printf("align_tick(%%d)\n", calibrate);
- */
- if(sync_align_ratio < 0){ /* Calibration */
- if(calibration_count == CALIBRATED)
- /* Re-calibration*/
- calibration_count = NOT_CALIBRATED;
- if(calibration_count == NOT_CALIBRATED)
- /* Calibration start, get time*/
- PLC_GetTime(&cal_begin);
- calibration_count++;
- }else{ /* do alignment (if possible) */
- if(calibration_count >= 0){
- /* End of calibration */
- /* Get final time */
- IEC_TIME cal_end;
- PLC_GetTime(&cal_end);
- /*adjust calibration_count*/
- calibration_count++;
- /* compute mean of Tsync, over calibration period */
- Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
- (cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
- if( (Nticks = (Tsync / Ttick)) > 0){
- FreqCorr = (Tsync mod Ttick); /* to be divided by Nticks */
- }else{
- FreqCorr = Tsync - (Ttick mod Tsync);
- }
- /*
- printf("Tsync = %%ld\n", Tsync);
- printf("calibration_count = %%d\n", calibration_count);
- printf("Nticks = %%d\n", Nticks);
- */
- calibration_count = CALIBRATED;
- }
- if(calibration_count == CALIBRATED){
- /* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
- IEC_TIME now;
- long long elapsed;
- long long Tcorr;
- long long PhaseCorr;
- long long PeriodicTcorr;
- PLC_GetTime(&now);
- elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
- if(Nticks > 0){
- PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
- Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
- if(Nticks < 2){
- /* When Sync source period is near Tick time */
- /* PhaseCorr may not be applied to Periodic time given to timer */
- PeriodicTcorr = Ttick + FreqCorr / Nticks;
- }else{
- PeriodicTcorr = Tcorr;
- }
- }else if(__tick > last_tick){
- last_tick = __tick;
- PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
- PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
- }else{
- /*PLC did not run meanwhile. Nothing to do*/
- return;
- }
- /* DO ALIGNEMENT */
- PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
- }
- }
-}
-
-/**
- * Prototypes for function provided by arch-specific code (main)
- * is concatained hereafter
- **/
--- a/targets/plc_debug.c Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/plc_debug.c Wed Jul 31 10:45:07 2013 +0900
@@ -152,6 +152,10 @@
/* compute next cursor positon.
No need to check overflow, as BUFFER_SIZE
is computed large enough */
+ if(vartype == STRING_ENUM){
+ /* optimization for strings */
+ size = ((STRING*)visible_value_p)->len + 1;
+ }
char* next_cursor = buffer_cursor + size;
/* copy data to the buffer */
memcpy(buffer_cursor, visible_value_p, size);
@@ -304,128 +308,3 @@
return wait_error;
}
-
-/* LOGGING
-*/
-
-#define LOG_LEVELS 4
-#define LOG_CRITICAL 0
-#define LOG_WARNING 1
-#define LOG_INFO 2
-#define LOG_DEBUG 3
-
-#ifndef LOG_BUFFER_SIZE
-#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
-#endif
-#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
-static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE];
-void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
- if(buffpos + size < LOG_BUFFER_SIZE){
- memcpy(&LogBuff[level][buffpos], buf, size);
- }else{
- uint32_t remaining = LOG_BUFFER_SIZE - buffpos - 1;
- memcpy(&LogBuff[level][buffpos], buf, remaining);
- memcpy(LogBuff[level], buf + remaining, size - remaining);
- }
-}
-void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
- if(buffpos + size < LOG_BUFFER_SIZE){
- memcpy(buf, &LogBuff[level][buffpos], size);
- }else{
- uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
- memcpy(buf, &LogBuff[level][buffpos], remaining);
- memcpy(buf + remaining, LogBuff[level], size - remaining);
- }
-}
-
-/* Log buffer structure
-
- |<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
- | Message1 Body | Tail1 | Message2 Body | Tail2 |
-
-*/
-typedef struct {
- uint32_t msgidx;
- uint32_t msgsize;
- unsigned long tick;
- IEC_TIME time;
-} mTail;
-
-/* Log cursor : 64b
- |63 ... 32|31 ... 0|
- | Message | Buffer |
- | counter | Index | */
-static uint64_t LogCursor[LOG_LEVELS] = {0x0,0x0,0x0,0x0};
-
-/* Store one log message of give size */
-int LogMessage(uint8_t level, uint8_t* buf, uint32_t size){
- if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
- uint32_t buffpos;
- uint64_t new_cursor, old_cursor;
-
- mTail tail;
- tail.msgsize = size;
- tail.tick = __tick;
- PLC_GetTime(&tail.time);
-
- /* We cannot increment both msg index and string pointer
- in a single atomic operation but we can detect having been interrupted.
- So we can try with atomic compare and swap in a loop until operation
- succeeds non interrupted */
- do{
- old_cursor = LogCursor[level];
- buffpos = (uint32_t)old_cursor;
- tail.msgidx = (old_cursor >> 32);
- new_cursor = ((uint64_t)(tail.msgidx + 1)<<32)
- | (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
- }while(AtomicCompareExchange64(
- (long long*)&LogCursor[level],
- (long long)old_cursor,
- (long long)new_cursor)!=old_cursor);
-
- copy_to_log(level, buffpos, buf, size);
- copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
-
- return 1; /* Success */
- }else{
- uint8_t mstr[] = "Logging error : message too big";
- LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
- }
- return 0;
-}
-
-uint32_t GetLogCount(uint8_t level){
- return (uint64_t)LogCursor[level] >> 32;
-}
-
-/* Return message size and content */
-uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
- uint64_t cursor = LogCursor[level];
- if(cursor){
- /* seach cursor */
- uint32_t stailpos = (uint32_t)cursor;
- uint32_t smsgidx;
- mTail tail;
- tail.msgidx = cursor >> 32;
- tail.msgsize = 0;
-
- /* Message search loop */
- do {
- smsgidx = tail.msgidx;
- stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
- copy_from_log(level, stailpos, &tail, sizeof(mTail));
- }while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
-
- if(tail.msgidx == msgidx){
- uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK;
- uint32_t totalsize = tail.msgsize;
- *tick = tail.tick;
- *tv_sec = tail.time.tv_sec;
- *tv_nsec = tail.time.tv_nsec;
- copy_from_log(level, sbuffpos, buf,
- totalsize > max_size ? max_size : totalsize);
- return totalsize;
- }
- }
- return 0;
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/plc_main_head.c Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,94 @@
+/**
+ * Head of code common to all C targets
+ **/
+
+#include "beremiz.h"
+/*
+ * Prototypes of functions provided by generated C softPLC
+ **/
+void config_run__(unsigned long tick);
+void config_init__(void);
+
+/*
+ * Prototypes of functions provided by generated target C code
+ * */
+void __init_debug(void);
+void __cleanup_debug(void);
+/*void __retrieve_debug(void);*/
+void __publish_debug(void);
+
+/*
+ * Variables used by generated C softPLC and plugins
+ **/
+IEC_TIME __CURRENT_TIME;
+IEC_BOOL __DEBUG = 0;
+unsigned long __tick = 0;
+
+/*
+ * Variable generated by C softPLC and plugins
+ **/
+extern unsigned long greatest_tick_count__;
+
+/* Effective tick time with 1ms default value */
+static long long Ttick = 1000000;
+
+/* Help to quit cleanly when init fail at a certain level */
+static int init_level = 0;
+
+/*
+ * Prototypes of functions exported by plugins
+ **/
+%(calls_prototypes)s
+
+/*
+ * Retrieve input variables, run PLC and publish output variables
+ **/
+void __run(void)
+{
+ __tick++;
+ if (greatest_tick_count__)
+ __tick %%= greatest_tick_count__;
+
+ %(retrieve_calls)s
+
+ /*__retrieve_debug();*/
+
+ config_run__(__tick);
+
+ __publish_debug();
+
+ %(publish_calls)s
+
+}
+
+/*
+ * Initialize variables according to PLC's default values,
+ * and then init plugins with that values
+ **/
+int __init(int argc,char **argv)
+{
+ int res = 0;
+ init_level = 0;
+
+ if(common_ticktime__)
+ Ttick = common_ticktime__;
+
+ config_init__();
+ __init_debug();
+ %(init_calls)s
+ return res;
+}
+/*
+ * Calls plugin cleanup proc.
+ **/
+void __cleanup(void)
+{
+ %(cleanup_calls)s
+ __cleanup_debug();
+}
+
+void PLC_GetTime(IEC_TIME *CURRENT_TIME);
+void PLC_SetTimer(unsigned long long next, unsigned long long period);
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/plc_main_tail.c Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,221 @@
+/**
+ * Tail of code common to all C targets
+ **/
+
+/**
+ * LOGGING
+ **/
+
+#ifndef LOG_BUFFER_SIZE
+#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
+#endif
+#ifndef LOG_BUFFER_ATTRS
+#define LOG_BUFFER_ATTRS
+#endif
+
+#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
+
+static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
+void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
+ if(buffpos + size < LOG_BUFFER_SIZE){
+ memcpy(&LogBuff[level][buffpos], buf, size);
+ }else{
+ uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
+ memcpy(&LogBuff[level][buffpos], buf, remaining);
+ memcpy(LogBuff[level], (char*)buf + remaining, size - remaining);
+ }
+}
+void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
+ if(buffpos + size < LOG_BUFFER_SIZE){
+ memcpy(buf, &LogBuff[level][buffpos], size);
+ }else{
+ uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
+ memcpy(buf, &LogBuff[level][buffpos], remaining);
+ memcpy((char*)buf + remaining, LogBuff[level], size - remaining);
+ }
+}
+
+/* Log buffer structure
+
+ |<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
+ | Message1 Body | Tail1 | Message2 Body | Tail2 |
+
+*/
+typedef struct {
+ uint32_t msgidx;
+ uint32_t msgsize;
+ unsigned long tick;
+ IEC_TIME time;
+} mTail;
+
+/* Log cursor : 64b
+ |63 ... 32|31 ... 0|
+ | Message | Buffer |
+ | counter | Index | */
+static uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0,0x0,0x0,0x0};
+
+void ResetLogCount(void) {
+ uint8_t level;
+ for(level=0;level<LOG_LEVELS;level++){
+ LogCursor[level] = 0;
+ }
+}
+
+/* Store one log message of give size */
+int LogMessage(uint8_t level, char* buf, uint32_t size){
+ if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
+ uint32_t buffpos;
+ uint64_t new_cursor, old_cursor;
+
+ mTail tail;
+ tail.msgsize = size;
+ tail.tick = __tick;
+ PLC_GetTime(&tail.time);
+
+ /* We cannot increment both msg index and string pointer
+ in a single atomic operation but we can detect having been interrupted.
+ So we can try with atomic compare and swap in a loop until operation
+ succeeds non interrupted */
+ do{
+ old_cursor = LogCursor[level];
+ buffpos = (uint32_t)old_cursor;
+ tail.msgidx = (old_cursor >> 32);
+ new_cursor = ((uint64_t)(tail.msgidx + 1)<<32)
+ | (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
+ }while(AtomicCompareExchange64(
+ (long long*)&LogCursor[level],
+ (long long)old_cursor,
+ (long long)new_cursor)!=(long long)old_cursor);
+
+ copy_to_log(level, buffpos, buf, size);
+ copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
+
+ return 1; /* Success */
+ }else{
+ char mstr[] = "Logging error : message too big";
+ LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
+ }
+ return 0;
+}
+
+uint32_t GetLogCount(uint8_t level){
+ return (uint64_t)LogCursor[level] >> 32;
+}
+
+/* Return message size and content */
+uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
+ uint64_t cursor = LogCursor[level];
+ if(cursor){
+ /* seach cursor */
+ uint32_t stailpos = (uint32_t)cursor;
+ uint32_t smsgidx;
+ mTail tail;
+ tail.msgidx = cursor >> 32;
+ tail.msgsize = 0;
+
+ /* Message search loop */
+ do {
+ smsgidx = tail.msgidx;
+ stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
+ copy_from_log(level, stailpos, &tail, sizeof(mTail));
+ }while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
+
+ if(tail.msgidx == msgidx){
+ uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK;
+ uint32_t totalsize = tail.msgsize;
+ *tick = tail.tick;
+ *tv_sec = tail.time.tv_sec;
+ *tv_nsec = tail.time.tv_nsec;
+ copy_from_log(level, sbuffpos, buf,
+ totalsize > max_size ? max_size : totalsize);
+ return totalsize;
+ }
+ }
+ return 0;
+}
+
+#define CALIBRATED -2
+#define NOT_CALIBRATED -1
+static int calibration_count = NOT_CALIBRATED;
+static IEC_TIME cal_begin;
+static long long Tsync = 0;
+static long long FreqCorr = 0;
+static int Nticks = 0;
+static unsigned long last_tick = 0;
+
+/*
+ * Called on each external periodic sync event
+ * make PLC tick synchronous with external sync
+ * ratio defines when PLC tick occurs between two external sync
+ * @param sync_align_ratio
+ * 0->100 : align ratio
+ * < 0 : no align, calibrate period
+ **/
+void align_tick(int sync_align_ratio)
+{
+ /*
+ printf("align_tick(%d)\n", calibrate);
+ */
+ if(sync_align_ratio < 0){ /* Calibration */
+ if(calibration_count == CALIBRATED)
+ /* Re-calibration*/
+ calibration_count = NOT_CALIBRATED;
+ if(calibration_count == NOT_CALIBRATED)
+ /* Calibration start, get time*/
+ PLC_GetTime(&cal_begin);
+ calibration_count++;
+ }else{ /* do alignment (if possible) */
+ if(calibration_count >= 0){
+ /* End of calibration */
+ /* Get final time */
+ IEC_TIME cal_end;
+ PLC_GetTime(&cal_end);
+ /*adjust calibration_count*/
+ calibration_count++;
+ /* compute mean of Tsync, over calibration period */
+ Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
+ (cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
+ if( (Nticks = (Tsync / Ttick)) > 0){
+ FreqCorr = (Tsync % Ttick); /* to be divided by Nticks */
+ }else{
+ FreqCorr = Tsync - (Ttick % Tsync);
+ }
+ /*
+ printf("Tsync = %ld\n", Tsync);
+ printf("calibration_count = %d\n", calibration_count);
+ printf("Nticks = %d\n", Nticks);
+ */
+ calibration_count = CALIBRATED;
+ }
+ if(calibration_count == CALIBRATED){
+ /* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
+ IEC_TIME now;
+ long long elapsed;
+ long long Tcorr;
+ long long PhaseCorr;
+ long long PeriodicTcorr;
+ PLC_GetTime(&now);
+ elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
+ if(Nticks > 0){
+ PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
+ Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
+ if(Nticks < 2){
+ /* When Sync source period is near Tick time */
+ /* PhaseCorr may not be applied to Periodic time given to timer */
+ PeriodicTcorr = Ttick + FreqCorr / Nticks;
+ }else{
+ PeriodicTcorr = Tcorr;
+ }
+ }else if(__tick > last_tick){
+ last_tick = __tick;
+ PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
+ PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
+ }else{
+ /*PLC did not run meanwhile. Nothing to do*/
+ return;
+ }
+ /* DO ALIGNEMENT */
+ PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
+ }
+ }
+}
--- a/targets/toolchain_gcc.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/toolchain_gcc.py Wed Jul 31 10:45:07 2013 +0900
@@ -98,7 +98,7 @@
######### GENERATE OBJECT FILES ########################################
obns = []
objs = []
- relink = False
+ relink = self.GetBinaryCode() is None
for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS:
if CFilesAndCFLAGS:
if Location :
--- a/targets/typemapping.py Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/typemapping.py Wed Jul 31 10:45:07 2013 +0900
@@ -73,9 +73,37 @@
#TODO
}
+TypeTranslator=SameEndianessTypeTranslator
+
# Construct debugger natively supported types
DebugTypesSize = dict([(key,sizeof(t)) for key,(t,p,u) in SameEndianessTypeTranslator.iteritems() if t is not None])
+def UnpackDebugBuffer(buff, size, indexes):
+ res = []
+ offset = 0
+ for idx, iectype, forced in indexes:
+ cursor = c_void_p(buff.value + offset)
+ c_type,unpack_func, pack_func = \
+ TypeTranslator.get(iectype,
+ (None,None,None))
+ if c_type is not None and offset < size:
+ res.append(unpack_func(
+ cast(cursor,
+ POINTER(c_type)).contents))
+ offset += sizeof(c_type) if iectype != "STRING" else len(res[-1])+1
+ else:
+ #if c_type is None:
+ # PLCprint("Debug error - " + iectype +
+ # " not supported !")
+ #if offset >= size:
+ # PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size))
+ break
+ if offset and offset == size:
+ return res
+ return None
+
+
+
LogLevels = ["CRITICAL","WARNING","INFO","DEBUG"]
LogLevelsCount = len(LogLevels)
LogLevelsDict = dict(zip(LogLevels,range(LogLevelsCount)))
--- a/tests/logging/plc.xml Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/logging/plc.xml Wed Jul 31 10:45:07 2013 +0900
@@ -8,7 +8,7 @@
productVersion="1"
creationDateTime="2013-01-29T14:01:00"/>
<contentHeader name="Unnamed"
- modificationDateTime="2013-02-26T16:22:11">
+ modificationDateTime="2013-04-04T11:06:06">
<coordinateInfo>
<fbd>
<scaling x="0" y="0"/>
--- a/tests/logging/py_ext_0@py_ext/py_ext.xml Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/logging/py_ext_0@py_ext/py_ext.xml Wed Jul 31 10:45:07 2013 +0900
@@ -1,23 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Python xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="py_ext_xsd.xsd">
-<![CDATA[import threading
+<![CDATA[import threading, time
MyT = None
Stop = False
+def StartLog():
+ global MyT
+ MyT=threading.Thread(target = DoLog)
+ MyT.start()
+
def DoLog():
- global MyT,Stop
- MyT=threading.Timer(0.3, DoLog)
- if not Stop : MyT.start()
- Stop = False
- PLCObject.LogMessage("Python side Logging")
+ global Stop
+ while not Stop:
+ PLCObject.LogMessage("Python side Logging (PLC is %s)"%PLCObject.PLCStatus)
+ time.sleep(0.3)
def StopLog():
global MyT,Stop
Stop=True
- if MyT is not None: MyT.cancel()
-_runtime_begin.append(DoLog)
+_runtime_init.append(StartLog)
_runtime_cleanup.append(StopLog)
]]>
</Python>
--- a/tests/python/c_code@c_ext/cfile.xml Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/python/c_code@c_ext/cfile.xml Wed Jul 31 10:45:07 2013 +0900
@@ -1,16 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<CFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="cext_xsd.xsd">
+<CFile>
<includes>
-<![CDATA[#include "stdio.h"]]>
+<![CDATA[
+]]>
</includes>
<variables>
- <variable name="TestInput" type="SINT" class="input"/>
- <variable name="TestOutput" type="SINT" class="output"/>
+ <variable name="TestInput" type="SINT" initial="0"/>
+ <variable name="TestOutput" type="SINT"/>
</variables>
<globals>
-<![CDATA[volatile long Lock=0;
+<![CDATA[
+volatile long Lock=0;
volatile char PtoC=1,CtoP=2;
+extern long AtomicCompareExchange(long*,long, long);
+
int Simple_C_Call(int val){
return val+1;
}
@@ -23,10 +27,10 @@
beremiz' runtime implementation */
int res = 0;
- if(!AtomicCompareExchange(&Lock, 0, 1)){
+ if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
PtoC=toC;
*fromC=CtoP;
- AtomicCompareExchange(&Lock, 1, 0);
+ AtomicCompareExchange((long*)&Lock, 1, 0);
res=1;
}
printf("C code called by Python: toC %d fromC %d\n",toC,*fromC);
@@ -36,29 +40,34 @@
int PLC_C_Call(char fromPLC, char *toPLC){
/* PLC also have to be realy carefull not to
conflict with asynchronous python access */
- int res;
- if(!AtomicCompareExchange(&Lock, 0, 1)){
+ if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
CtoP = fromPLC;
*toPLC = PtoC;
- AtomicCompareExchange(&Lock, 1, 0);
+ AtomicCompareExchange((long*)&Lock, 1, 0);
return 1;
}
return 0;
-}]]>
+}
+]]>
</globals>
<initFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
</initFunction>
<cleanUpFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
</cleanUpFunction>
<retrieveFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
</retrieveFunction>
<publishFunction>
-<![CDATA[if(!AtomicCompareExchange(&Lock, 0, 1)){
+<![CDATA[
+if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
TestInput = CtoP + PtoC + TestOutput;
- AtomicCompareExchange(&Lock, 1, 0);
-}]]>
+ AtomicCompareExchange((long*)&Lock, 1, 0);
+}
+]]>
</publishFunction>
</CFile>
--- a/tests/python/plc.xml Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/python/plc.xml Wed Jul 31 10:45:07 2013 +0900
@@ -3,12 +3,12 @@
xsi:schemaLocation="http://www.plcopen.org/xml/tc6.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
- <fileHeader companyName="beremiz"
+ <fileHeader companyName=""
productName="Beremiz"
productVersion="0.0"
creationDateTime="2008-12-14T16:21:19"/>
<contentHeader name="Beremiz Python Support Tests"
- modificationDateTime="2013-02-23T23:37:47">
+ modificationDateTime="2013-05-15T18:19:52">
<coordinateInfo>
<pageSize x="1024" y="1024"/>
<fbd>
@@ -102,18 +102,18 @@
</type>
</variable>
</localVars>
- <localVars>
- <variable name="TestInput" address="%IB1.0">
+ <externalVars>
+ <variable name="TestInput">
<type>
<SINT/>
</type>
</variable>
- <variable name="TestOutput" address="%QB1.0">
+ <variable name="TestOutput">
<type>
<SINT/>
</type>
</variable>
- </localVars>
+ </externalVars>
<localVars>
<variable name="FromInput">
<type>
@@ -181,6 +181,26 @@
<derived name="RS"/>
</type>
</variable>
+ <variable name="TUTU">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="TOTO">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="Test_Python_Var">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="Second_Python_Var">
+ <type>
+ <INT/>
+ </type>
+ </variable>
</externalVars>
</interface>
<body>
@@ -615,14 +635,14 @@
</variable>
</outputVariables>
</block>
- <inVariable localId="27" height="30" width="75">
- <position x="305" y="770"/>
- <connectionPointOut>
- <relPosition x="75" y="15"/>
+ <inVariable localId="27" height="30" width="90">
+ <position x="300" y="770"/>
+ <connectionPointOut>
+ <relPosition x="90" y="15"/>
</connectionPointOut>
<expression>TestInput</expression>
</inVariable>
- <outVariable localId="28" height="30" width="90">
+ <outVariable localId="28" height="30" width="105">
<position x="395" y="705"/>
<connectionPointIn>
<relPosition x="0" y="15"/>
@@ -639,7 +659,7 @@
<relPosition x="0" y="15"/>
<connection refLocalId="27">
<position x="415" y="785"/>
- <position x="380" y="785"/>
+ <position x="390" y="785"/>
</connection>
</connectionPointIn>
<expression>FromInput</expression>
@@ -847,15 +867,15 @@
</connectionPointOut>
<expression>'True'</expression>
</inVariable>
- <block localId="46" width="130" height="45" typeName="BYTE_TO_STRING">
+ <block localId="46" width="130" height="45" typeName="INT_TO_STRING">
<position x="900" y="970"/>
<inputVariables>
<variable formalParameter="IN">
<connectionPointIn>
<relPosition x="0" y="30"/>
- <connection refLocalId="47">
+ <connection refLocalId="58">
<position x="900" y="1000"/>
- <position x="850" y="1000"/>
+ <position x="840" y="1000"/>
</connection>
</connectionPointIn>
</variable>
@@ -869,13 +889,6 @@
</variable>
</outputVariables>
</block>
- <inVariable localId="47" height="30" width="80">
- <position x="770" y="985"/>
- <connectionPointOut>
- <relPosition x="80" y="15"/>
- </connectionPointOut>
- <expression>BYTE#145</expression>
- </inVariable>
<inVariable localId="50" height="30" width="105">
<position x="200" y="1085"/>
<connectionPointOut>
@@ -1048,6 +1061,62 @@
</connectionPointOut>
<expression>Test_TOD</expression>
</inOutVariable>
+ <inVariable localId="49" height="30" width="30">
+ <position x="765" y="1200"/>
+ <connectionPointOut>
+ <relPosition x="30" y="15"/>
+ </connectionPointOut>
+ <expression>42</expression>
+ </inVariable>
+ <outVariable localId="57" height="30" width="50">
+ <position x="845" y="1200"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="49">
+ <position x="845" y="1215"/>
+ <position x="795" y="1215"/>
+ </connection>
+ </connectionPointIn>
+ <expression>TOTO</expression>
+ </outVariable>
+ <outVariable localId="56" height="30" width="50">
+ <position x="845" y="1240"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="49">
+ <position x="845" y="1255"/>
+ <position x="820" y="1255"/>
+ <position x="820" y="1215"/>
+ <position x="795" y="1215"/>
+ </connection>
+ </connectionPointIn>
+ <expression>TUTU</expression>
+ </outVariable>
+ <inVariable localId="58" height="30" width="145">
+ <position x="715" y="985"/>
+ <connectionPointOut>
+ <relPosition x="145" y="15"/>
+ </connectionPointOut>
+ <expression>Second_Python_Var</expression>
+ </inVariable>
+ <outVariable localId="47" height="30" width="125">
+ <position x="400" y="975"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="59">
+ <position x="400" y="990"/>
+ <position x="330" y="990"/>
+ </connection>
+ </connectionPointIn>
+ <expression>Test_Python_Var</expression>
+ </outVariable>
+ <inVariable localId="59" height="30" width="30">
+ <position x="300" y="975"/>
+ <connectionPointOut>
+ <relPosition x="30" y="15"/>
+ </connectionPointOut>
+ <expression>23</expression>
+ </inVariable>
</FBD>
</body>
</pou>
@@ -1120,6 +1189,7 @@
{{
char toPLC;
char fromPLC = GetFbVar(IN);
+ extern int PLC_C_Call(char, char *);
if(PLC_C_Call(fromPLC, &toPLC)){
SetFbVar(OUT, toPLC);
}
@@ -1182,6 +1252,11 @@
<derived name="RS"/>
</type>
</variable>
+ <variable name="TUTU">
+ <type>
+ <INT/>
+ </type>
+ </variable>
</globalVars>
</configuration>
</configurations>
--- a/tests/python/python@py_ext/py_ext.xml Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<Python xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="py_ext_xsd.xsd">
-<![CDATA[import time,sys,ctypes
-Python_to_C_Call = PLCBinary.Python_to_C_Call
-Python_to_C_Call.restype = ctypes.c_int
-Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
-
-def MyPythonFunc(arg):
- i = ctypes.c_int()
- if(Python_to_C_Call(arg, i)):
- res = i.value
- print "toC:", arg, "from C:", res, "FBID:", FBID
- else:
- print "Failed Python_to_C_Call failed"
- res = None
- sys.stdout.flush()
- return res
-]]>
-</Python>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/python/python@py_ext/pyfile.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+ <variables>
+ <variable name="Test_Python_Var" type="INT" initial="4"/>
+ <variable name="Second_Python_Var" type="INT" initial="5"/>
+ </variables>
+ <globals>
+<![CDATA[
+import time,sys,ctypes
+Python_to_C_Call = PLCBinary.Python_to_C_Call
+Python_to_C_Call.restype = ctypes.c_int
+Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
+
+def MyPythonFunc(arg):
+ i = ctypes.c_int()
+ if(Python_to_C_Call(arg, i)):
+ res = i.value
+ print "toC:", arg, "from C:", res, "FBID:", FBID
+ else:
+ print "Failed Python_to_C_Call failed"
+ res = None
+ print "Python read PLC global :",PLCGlobals.Test_Python_Var
+ PLCGlobals.Second_Python_Var = 789
+ sys.stdout.flush()
+ return res
+
+async_error_test_code = """
+def badaboom():
+ tuple()[0]
+
+import wx
+def badaboomwx():
+ wx.CallAfter(badaboom)
+
+from threading import Timer
+a = Timer(3, badaboom)
+a.start()
+
+b = Timer(6, badaboomwx)
+b.start()
+"""
+]]>
+ </globals>
+ <init>
+<![CDATA[
+global x, y
+x = 2
+y = 5
+print "py_runtime init:", x, ",", y
+]]>
+ </init>
+ <cleanup>
+<![CDATA[
+print "py_runtime cleanup"
+]]>
+ </cleanup>
+ <start>
+<![CDATA[
+global x, y
+print "py_runtime start", x * x + y * y
+]]>
+ </start>
+ <stop>
+<![CDATA[
+print "py_runtime stop"
+]]>
+ </stop>
+</PyFile>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/beremiz.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BeremizRoot URI_location="LOCAL://">
+ <TargetType/>
+</BeremizRoot>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/plc.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.plcopen.org/xml/tc6.xsd"
+ xsi:schemaLocation="http://www.plcopen.org/xml/tc6.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
+ <fileHeader companyName="Unknown"
+ productName="Unnamed"
+ productVersion="1"
+ creationDateTime="2012-09-12T23:30:19"/>
+ <contentHeader name="Unnamed"
+ modificationDateTime="2013-05-28T18:28:14">
+ <coordinateInfo>
+ <fbd>
+ <scaling x="5" y="5"/>
+ </fbd>
+ <ld>
+ <scaling x="0" y="0"/>
+ </ld>
+ <sfc>
+ <scaling x="0" y="0"/>
+ </sfc>
+ </coordinateInfo>
+ </contentHeader>
+ <types>
+ <dataTypes/>
+ <pous>
+ <pou name="main" pouType="program">
+ <interface>
+ <localVars>
+ <variable name="x">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="y">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="a">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="b">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="c">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="b1">
+ <type>
+ <WORD/>
+ </type>
+ </variable>
+ <variable name="b0">
+ <type>
+ <WORD/>
+ </type>
+ </variable>
+ </localVars>
+ <externalVars>
+ <variable name="WiiNunchuckStickX">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="WiiNunchuckStickY">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="WiiNunchuckButtons">
+ <type>
+ <WORD/>
+ </type>
+ </variable>
+ <variable name="WiiButtons">
+ <type>
+ <WORD/>
+ </type>
+ </variable>
+ <variable name="WiiNunchuckAccX">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="WiiNunchuckAccY">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="WiiNunchuckAccZ">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ </externalVars>
+ </interface>
+ <body>
+ <FBD>
+ <outVariable localId="1" height="30" width="20">
+ <position x="345" y="40"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="3">
+ <position x="345" y="55"/>
+ <position x="295" y="55"/>
+ </connection>
+ </connectionPointIn>
+ <expression>x</expression>
+ </outVariable>
+ <inVariable localId="3" height="30" width="150">
+ <position x="145" y="40"/>
+ <connectionPointOut>
+ <relPosition x="150" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckStickX</expression>
+ </inVariable>
+ <inVariable localId="4" height="30" width="150">
+ <position x="145" y="100"/>
+ <connectionPointOut>
+ <relPosition x="150" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckStickY</expression>
+ </inVariable>
+ <inVariable localId="5" height="30" width="155">
+ <position x="115" y="175"/>
+ <connectionPointOut>
+ <relPosition x="155" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckButtons</expression>
+ </inVariable>
+ <inVariable localId="6" height="30" width="90">
+ <position x="180" y="225"/>
+ <connectionPointOut>
+ <relPosition x="90" y="15"/>
+ </connectionPointOut>
+ <expression>WiiButtons</expression>
+ </inVariable>
+ <outVariable localId="8" height="30" width="20">
+ <position x="345" y="100"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="4">
+ <position x="345" y="115"/>
+ <position x="295" y="115"/>
+ </connection>
+ </connectionPointIn>
+ <expression>y</expression>
+ </outVariable>
+ <outVariable localId="9" height="30" width="30">
+ <position x="345" y="175"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="5">
+ <position x="345" y="190"/>
+ <position x="270" y="190"/>
+ </connection>
+ </connectionPointIn>
+ <expression>b1</expression>
+ </outVariable>
+ <outVariable localId="10" height="30" width="30">
+ <position x="345" y="225"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="6">
+ <position x="345" y="240"/>
+ <position x="270" y="240"/>
+ </connection>
+ </connectionPointIn>
+ <expression>b0</expression>
+ </outVariable>
+ <inVariable localId="11" height="30" width="135">
+ <position x="135" y="295"/>
+ <connectionPointOut>
+ <relPosition x="135" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckAccX</expression>
+ </inVariable>
+ <inVariable localId="12" height="30" width="135">
+ <position x="135" y="340"/>
+ <connectionPointOut>
+ <relPosition x="135" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckAccY</expression>
+ </inVariable>
+ <inVariable localId="13" height="30" width="130">
+ <position x="140" y="385"/>
+ <connectionPointOut>
+ <relPosition x="130" y="15"/>
+ </connectionPointOut>
+ <expression>WiiNunchuckAccZ</expression>
+ </inVariable>
+ <outVariable localId="14" height="30" width="20">
+ <position x="345" y="295"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="11">
+ <position x="345" y="310"/>
+ <position x="270" y="310"/>
+ </connection>
+ </connectionPointIn>
+ <expression>a</expression>
+ </outVariable>
+ <outVariable localId="15" height="30" width="20">
+ <position x="345" y="340"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="12">
+ <position x="345" y="355"/>
+ <position x="270" y="355"/>
+ </connection>
+ </connectionPointIn>
+ <expression>b</expression>
+ </outVariable>
+ <outVariable localId="16" height="30" width="20">
+ <position x="345" y="385"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="13">
+ <position x="345" y="400"/>
+ <position x="270" y="400"/>
+ </connection>
+ </connectionPointIn>
+ <expression>c</expression>
+ </outVariable>
+ </FBD>
+ </body>
+ <documentation>
+<![CDATA[]]>
+ </documentation>
+ </pou>
+ </pous>
+ </types>
+ <instances>
+ <configurations>
+ <configuration name="config">
+ <resource name="resource1">
+ <task name="tsk1" interval="T#1ms" priority="0">
+ <pouInstance name="inst1" typeName="main"/>
+ </task>
+ </resource>
+ </configuration>
+ </configurations>
+ </instances>
+</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/py_ext_0@py_ext/baseconfnode.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="py_ext_0" IEC_Channel="1"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/py_ext_0@py_ext/pyfile.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+ <variables>
+ <variable name="WiiNunchuckStickX" type="INT"/>
+ <variable name="WiiNunchuckStickY" type="INT"/>
+ <variable name="WiiNunchuckAccX" type="INT"/>
+ <variable name="WiiNunchuckAccY" type="INT"/>
+ <variable name="WiiNunchuckAccZ" type="INT"/>
+ <variable name="WiiNunchuckButtons" type="WORD"/>
+ <variable name="WiiButtons" type="WORD"/>
+ </variables>
+ <globals>
+<![CDATA[
+import cwiid,commands,sys,re,os,time
+
+wiimote = None
+WIIMOTE_ADDR_MODEL = re.compile("((?:[0-9A-F]{2})(?::[0-9A-F]{2}){5})\s*Nintendo")
+nunchuckzero = None
+
+def Wiimote_cback(messages, time):
+ global nunchuckzero
+ state = dict(messages)
+ bts = state.get(cwiid.MESG_BTN, None)
+ if bts is not None:
+ PLCGlobals.WiiButtons = bts
+ nunchuck = state.get(cwiid.MESG_NUNCHUK, None)
+ if nunchuck is not None:
+ PLCGlobals.WiiNunchuckButtons = nunchuck['buttons']
+ X,Y = nunchuck['stick']
+ PLCGlobals.WiiNunchuckAccX = nunchuck['acc'][cwiid.X]
+ PLCGlobals.WiiNunchuckAccY = nunchuck['acc'][cwiid.Y]
+ PLCGlobals.WiiNunchuckAccZ = nunchuck['acc'][cwiid.Z]
+ if nunchuckzero is None:
+ nunchuckzero = X,Y
+ (PLCGlobals.WiiNunchuckStickX,
+ PLCGlobals.WiiNunchuckStickY) = X-nunchuckzero[0],Y-nunchuckzero[1]
+
+def Connect_Wiimote(connected_callback):
+ global wiimote,nunchuckzero
+ mac_addr = ''
+ try:
+ mac_addr = file("wiimac.txt","rt").read()
+ except:
+ PLCObject.LogMessage("Wiimote MAC unknown, scanning bluetooth")
+ output = commands.getoutput("hcitool scan")
+ result = WIIMOTE_ADDR_MODEL.search(output)
+ if result is not None:
+ mac_addr = result.group(1)
+ PLCObject.LogMessage("Found Wiimote with MAC %s"%mac_addr)
+ file("wiimac.txt","wt").write(mac_addr)
+
+ # Connect to wiimote
+ if not mac_addr:
+ PLCObject.LogMessage("Connection to unknown Wiimote...")
+ wiimote = cwiid.Wiimote()
+ else:
+ PLCObject.LogMessage("Connection to Wiimote %s..."%mac_addr)
+ wiimote = cwiid.Wiimote(mac_addr)
+
+ if wiimote is not None:
+ nunchuckzero = None
+ wiimote.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_EXT
+ # use the callback interface
+ wiimote.mesg_callback = Wiimote_cback
+ wiimote.enable(cwiid.FLAG_MESG_IFC)
+ connected_callback(mac_addr)
+ PLCObject.LogMessage("Wiimote %s Connected"%mac_addr)
+ else:
+ PLCObject.LogMessage("Wiimote %s not found"%mac_addr)
+ os.remove("wiimac.txt")
+ connected_callback(None)
+
+def Disconnect_Wiimote():
+ global wiimote
+ if wiimote is not None:
+ wiimote.disable(cwiid.FLAG_MESG_IFC)
+ time.sleep(0.1)
+ wiimote.close()
+ wiimote = None
+ PLCObject.LogMessage("Wiimote disconnected")
+
+]]>
+ </globals>
+ <init>
+<![CDATA[
+]]>
+ </init>
+ <cleanup>
+<![CDATA[
+Disconnect_Wiimote()
+
+]]>
+ </cleanup>
+ <start>
+<![CDATA[
+]]>
+ </start>
+ <stop>
+<![CDATA[
+]]>
+ </stop>
+</PyFile>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/baseconfnode.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="wxglade_hmi" IEC_Channel="0"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/hmi.wxg Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- generated by wxGlade 0.6.4 on Thu May 16 13:24:16 2013 -->
+
+<application path="" name="" class="" option="0" language="python" top_window="wxglade_hmi" encoding="UTF-8" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
+ <object class="Class_wxglade_hmi" name="wxglade_hmi" base="EditFrame">
+ <style>wxDEFAULT_FRAME_STYLE</style>
+ <title>frame_1</title>
+ <object class="wxStaticBoxSizer" name="sizer_1" base="EditStaticBoxSizer">
+ <orient>wxVERTICAL</orient>
+ <label>Wiimote Test</label>
+ <object class="sizeritem">
+ <flag>wxEXPAND</flag>
+ <border>0</border>
+ <option>0</option>
+ <object class="wxButton" name="button_1" base="EditButton">
+ <label>Connect Wiimote</label>
+ <events>
+ <handler event="EVT_BUTTON">OnConnectButton</handler>
+ </events>
+ </object>
+ </object>
+ <object class="sizeritem">
+ <flag>wxEXPAND</flag>
+ <border>0</border>
+ <option>0</option>
+ <object class="wxButton" name="button_2" base="EditButton">
+ <label>Disconnect Wiimote</label>
+ <events>
+ <handler event="EVT_BUTTON">OnDisconnectButton</handler>
+ </events>
+ </object>
+ </object>
+ <object class="sizeritem">
+ <flag>wxEXPAND</flag>
+ <border>0</border>
+ <option>0</option>
+ <object class="wxStaticText" name="label_1" base="EditStaticText">
+ <attribute>1</attribute>
+ <label>Status :</label>
+ </object>
+ </object>
+ <object class="sizeritem">
+ <flag>wxEXPAND</flag>
+ <border>0</border>
+ <option>0</option>
+ <object class="wxStaticText" name="label_2" base="EditStaticText">
+ <attribute>1</attribute>
+ <label>no status</label>
+ </object>
+ </object>
+ </object>
+ </object>
+</application>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/pyfile.xml Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+ <variables/>
+ <globals>
+<![CDATA[
+from threading import Thread
+
+def OnConnectButton(self, event):
+ def OnWiiConnected(mac_addr):
+ self.label_2.SetLabel(
+ "Wiimote %s connected"%mac_addr
+ if mac_addr else
+ "Wiimote connection failed !")
+
+ def WiiConnected(mac_addr):
+ wx.CallAfter(OnWiiConnected,mac_addr)
+
+ Thread(target = Connect_Wiimote, args = (WiiConnected,)).start()
+ self.label_2.SetLabel("Press wiimote 1+2")
+ event.Skip()
+
+def OnDisconnectButton(self, event):
+ Disconnect_Wiimote()
+ self.label_2.SetLabel("Wiimote disconnected")
+ event.Skip()
+
+]]>
+ </globals>
+ <init>
+<![CDATA[
+]]>
+ </init>
+ <cleanup>
+<![CDATA[
+]]>
+ </cleanup>
+ <start>
+<![CDATA[
+]]>
+ </start>
+ <stop>
+<![CDATA[
+]]>
+ </stop>
+</PyFile>
--- a/wxglade_hmi/wxglade_hmi.py Wed Mar 13 12:34:55 2013 +0900
+++ b/wxglade_hmi/wxglade_hmi.py Wed Jul 31 10:45:07 2013 +0900
@@ -1,5 +1,5 @@
import wx
-import os, sys
+import os, sys, shutil
from xml.dom import minidom
from py_ext import PythonFileCTNMixin
@@ -13,12 +13,17 @@
"method" : "_editWXGLADE"},
]
+ def GetIconName(self):
+ return "wxGlade"
+
def ConfNodePath(self):
return os.path.join(os.path.dirname(__file__))
- def _getWXGLADEpath(self):
- # define name for IEC raw code file
- return os.path.join(self.CTNPath(), "hmi.wxg")
+ def _getWXGLADEpath(self, project_path=None):
+ if project_path is None:
+ project_path = self.CTNPath()
+ # define name for wxGlade gui file
+ return os.path.join(project_path, "hmi.wxg")
def launch_wxglade(self, options, wait=False):
from wxglade import __file__ as fileName
@@ -29,23 +34,15 @@
mode = {False:os.P_NOWAIT, True:os.P_WAIT}[wait]
os.spawnv(mode, sys.executable, ["\"%s\""%sys.executable] + [glade] + options)
+ def OnCTNSave(self, from_project_path=None):
+ if from_project_path is not None:
+ shutil.copyfile(self._getWXGLADEpath(from_project_path),
+ self._getWXGLADEpath())
+ return PythonFileCTNMixin.OnCTNSave(self, from_project_path)
def CTNGenerate_C(self, buildpath, locations):
- """
- Return C code generated by iec2c compiler
- when _generate_softPLC have been called
- @param locations: ignored
- @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
- """
- current_location = self.GetCurrentLocation()
- # define a unique name for the generated C file
- location_str = "_".join(map(lambda x:str(x), current_location))
-
- runtimefile_path = os.path.join(buildpath, "runtime_%s.py"%location_str)
- runtimefile = open(runtimefile_path, 'w')
-
- hmi_frames = {}
+ hmi_frames = []
wxgfile_path=self._getWXGLADEpath()
if os.path.exists(wxgfile_path):
@@ -55,7 +52,12 @@
for node in wxgtree.childNodes[1].childNodes:
if node.nodeType == wxgtree.ELEMENT_NODE:
- hmi_frames[node._attrs["name"].value] = node._attrs["class"].value
+ hmi_frames.append({
+ "name" : node.getAttribute("name"),
+ "class" : node.getAttribute("class"),
+ "handlers" : [
+ hnode.firstChild.data for hnode in
+ node.getElementsByTagName("handler")]})
hmipyfile_path=os.path.join(self._getBuildPath(), "hmi.py")
if wx.Platform == '__WXMSW__':
@@ -66,39 +68,42 @@
self.launch_wxglade(['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True)
hmipyfile = open(hmipyfile_path, 'r')
- runtimefile.write(hmipyfile.read())
+ define_hmi = hmipyfile.read().decode('utf-8')
hmipyfile.close()
- runtimefile.write(self.GetPythonCode())
- runtimefile.write("""
-%(declare)s
+ else:
+ define_hmi = ""
+
+ declare_hmi = "\n".join(["%(name)s = None\n" % x +
+ "\n".join(["%(class)s.%(h)s = %(h)s"%
+ dict(x,h=h) for h in x['handlers']])
+ for x in hmi_frames])
+ global_hmi = ("global %s\n"%",".join(
+ [x["name"] for x in hmi_frames])
+ if len(hmi_frames) > 0 else "")
+ init_hmi = "\n".join(["""\
+def OnCloseFrame(evt):
+ wx.MessageBox(_("Please stop PLC to close"))
-def _runtime_%(location)s_begin():
- global %(global)s
-
- def OnCloseFrame(evt):
- wx.MessageBox(_("Please stop PLC to close"))
-
- %(init)s
-
-def _runtime_%(location)s_cleanup():
- global %(global)s
-
- %(cleanup)s
+%(name)s = %(class)s(None)
+%(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
+%(name)s.Show()
+""" % x for x in hmi_frames])
+ cleanup_hmi = "\n".join(
+ ["if %(name)s is not None: %(name)s.Destroy()" % x
+ for x in hmi_frames])
+
+ self.PreSectionsTexts = {
+ "globals":define_hmi,
+ "start":global_hmi,
+ "stop":global_hmi + cleanup_hmi
+ }
+ self.PostSectionsTexts = {
+ "globals":declare_hmi,
+ "start":init_hmi,
+ }
-""" % {"location": location_str,
- "declare": "\n".join(map(lambda x:"%s = None" % x, hmi_frames.keys())),
- "global": ",".join(hmi_frames.keys()),
- "init": "\n".join(map(lambda x: """
- %(name)s = %(class)s(None)
- %(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
- %(name)s.Show()
-""" % {"name": x[0], "class": x[1]},
- hmi_frames.items())),
- "cleanup": "\n ".join(map(lambda x:"%s.Destroy()" % x, hmi_frames.keys()))})
- runtimefile.close()
-
- return [], "", False, ("runtime_%s.py"%location_str, file(runtimefile_path,"rb"))
+ return PythonFileCTNMixin.CTNGenerate_C(self, buildpath, locations)
def _editWXGLADE(self):
wxg_filename = self._getWXGLADEpath()
@@ -128,3 +133,4 @@
if wx.Platform == '__WXMSW__':
wxg_filename = "\"%s\""%wxg_filename
self.launch_wxglade([wxg_filename])
+
--- a/xmlclass/__init__.py Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/__init__.py Wed Jul 31 10:45:07 2013 +0900
@@ -24,5 +24,5 @@
# Package initialisation
-from xmlclass import ClassFactory, GenerateClasses, GetAttributeValue, time_model, CreateNode, NodeSetAttr, NodeRenameAttr
+from xmlclass import ClassFactory, GenerateClasses, GetAttributeValue, time_model, CreateNode, NodeSetAttr, NodeRenameAttr, UpdateXMLClassGlobals
from xsdschema import XSDClassFactory, GenerateClassesFromXSD, GenerateClassesFromXSDstring
--- a/xmlclass/xmlclass.py Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/xmlclass.py Wed Jul 31 10:45:07 2013 +0900
@@ -1598,14 +1598,14 @@
if path is not None:
parts = path.split(".", 1)
if attributes.has_key(parts[0]):
- if len(parts) != 0:
+ if len(parts) != 1:
raise ValueError("Wrong path!")
attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"],
attributes[parts[0]]["attr_type"]["facets"])
value = getattr(self, parts[0], "")
elif elements.has_key(parts[0]):
if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
- if len(parts) != 0:
+ if len(parts) != 1:
raise ValueError("Wrong path!")
attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"],
elements[parts[0]]["elmt_type"]["facets"])
@@ -1620,6 +1620,11 @@
return attr.getElementInfos(parts[0])
else:
return attr.getElementInfos(parts[0], parts[1])
+ elif elements.has_key("content"):
+ if len(parts) > 0:
+ return self.content["value"].getElementInfos(name, path)
+ elif classinfos.has_key("base"):
+ classinfos["base"].getElementInfos(name, path)
else:
raise ValueError("Wrong path!")
else:
@@ -1669,6 +1674,13 @@
raise ValueError("Wrong path!")
if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
setattr(self, parts[0], value)
+ elif attributes[parts[0]]["use"] == "optional" and value == "":
+ if attributes[parts[0]].has_key("default"):
+ setattr(self, parts[0],
+ attributes[parts[0]]["attr_type"]["extract"](
+ attributes[parts[0]]["default"], False))
+ else:
+ setattr(self, parts[0], None)
else:
setattr(self, parts[0], attributes[parts[0]]["attr_type"]["extract"](value, False))
elif elements.has_key(parts[0]):
@@ -1677,6 +1689,8 @@
raise ValueError("Wrong path!")
if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
setattr(self, parts[0], value)
+ elif attributes[parts[0]]["minOccurs"] == 0 and value == "":
+ setattr(self, parts[0], None)
else:
setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False))
else:
@@ -1848,9 +1862,11 @@
def GenerateClasses(factory):
ComputedClasses = factory.CreateClasses()
if factory.FileName is not None and len(ComputedClasses) == 1:
- globals().update(ComputedClasses[factory.FileName])
+ UpdateXMLClassGlobals(ComputedClasses[factory.FileName])
return ComputedClasses[factory.FileName]
else:
- globals().update(ComputedClasses)
+ UpdateXMLClassGlobals(ComputedClasses)
return ComputedClasses
+def UpdateXMLClassGlobals(classes):
+ globals().update(classes)
--- a/xmlclass/xsdschema.py Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/xsdschema.py Wed Jul 31 10:45:07 2013 +0900
@@ -2142,7 +2142,7 @@
"extract": GenerateIntegerExtraction(minExclusive=0),
"facets": DECIMAL_FACETS,
"generate": GenerateSimpleTypeXMLText(str),
- "initial": lambda: 0,
+ "initial": lambda: 1,
"check": lambda x: isinstance(x, IntType)
},
@@ -2152,7 +2152,7 @@
"extract": GenerateIntegerExtraction(maxExclusive=0),
"facets": DECIMAL_FACETS,
"generate": GenerateSimpleTypeXMLText(str),
- "initial": lambda: 0,
+ "initial": lambda: -1,
"check": lambda x: isinstance(x, IntType)
},