diff -r 78f28f40bc10 -r 8816f7316d9c BeremizIDE.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BeremizIDE.py Fri Mar 10 17:36:18 2017 +0300 @@ -0,0 +1,1177 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# Copyright (C) 2016: Andrey Skvortsov +# +# See COPYING file for copyrights details. +# +# This program 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 +# of the License, or (at your option) any later version. +# +# This program 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 program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import os, sys +import tempfile +import shutil +import random +import time +import version +from types import ListType + +beremiz_dir = os.path.dirname(os.path.realpath(__file__)) + +def Bpath(*args): + return os.path.join(beremiz_dir,*args) + + + +import wx.lib.buttons, wx.lib.statbmp, wx.stc +import cPickle +import types, time, re, platform, time, traceback, commands + +from docutil import OpenHtmlFrame +from editors.EditorPanel import EditorPanel +from editors.Viewer import Viewer +from editors.TextViewer import TextViewer +from editors.ResourceEditor import ConfigurationEditor, ResourceEditor +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 controls import EnhancedStatusBar as esb +from dialogs.AboutDialog import ShowAboutDialog + +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, GetAddMenuItems, MATIEC_ERROR_MODEL, ITEM_CONFNODE + + +MAX_RECENT_PROJECTS = 9 + +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 +from time import time as gettime +class LogPseudoFile: + """ Base class for file like objects to facilitate StdOut for the Shell.""" + def __init__(self, output, risecall): + 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 + self.rising_timer = 0 + self.lock = Lock() + self.YieldLock = Lock() + self.RefreshLock = Lock() + self.TimerAccessLock = Lock() + self.stack = [] + self.LastRefreshTime = gettime() + self.LastRefreshTimer = None + + def write(self, s, style = None): + if self.lock.acquire(): + self.stack.append((s,style)) + self.lock.release() + current_time = gettime() + self.TimerAccessLock.acquire() + if self.LastRefreshTimer: + self.LastRefreshTimer.cancel() + self.LastRefreshTimer=None + self.TimerAccessLock.release() + if current_time - self.LastRefreshTime > REFRESH_PERIOD and self.RefreshLock.acquire(False): + self._should_write() + else: + self.TimerAccessLock.acquire() + self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired) + self.LastRefreshTimer.start() + self.TimerAccessLock.release() + + def _timer_expired(self): + if self.RefreshLock.acquire(False): + self._should_write() + else: + self.TimerAccessLock.acquire() + self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired) + self.LastRefreshTimer.start() + self.TimerAccessLock.release() + + def _should_write(self): + wx.CallAfter(self._write) + if MainThread == currentThread().ident: + app = wx.GetApp() + if app is not None: + if self.YieldLock.acquire(0): + app.Yield() + self.YieldLock.release() + + def _write(self): + if self.output : + self.output.Freeze() + self.lock.acquire() + for s, style in self.stack: + if style is None : style=self.black_white + 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 + start_pos = self.output.GetLength() + self.output.SetReadOnly(False) + self.output.AppendText(s) + self.output.SetReadOnly(True) + text_len = self.output.GetLength() - start_pos + + if style != self.black_white: + self.output.SetStyling(text_len, style) + self.stack = [] + self.lock.release() + self.output.Thaw() + self.LastRefreshTime = gettime() + try: + self.RefreshLock.release() + except: + pass + newtime = time.time() + if newtime - self.rising_timer > 1: + self.risecall(self.output) + self.rising_timer = newtime + + def write_warning(self, s): + self.write(s,self.red_white) + + def write_error(self, s): + self.write(s,self.red_yellow) + + def writeyield(self, s): + self.write(s) + wx.GetApp().Yield() + + def flush(self): + # 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 + +ID_FILEMENURECENTPROJECTS = wx.NewId() + +from IDEFrame import TITLE,\ + EDITORTOOLBAR,\ + FILEMENU,\ + EDITMENU,\ + DISPLAYMENU,\ + PROJECTTREE,\ + POUINSTANCEVARIABLESPANEL,\ + LIBRARYTREE,\ + SCALING,\ + PAGETITLES,\ + IDEFrame, AppendMenu,\ + EncodeFileSystemPath, DecodeFileSystemPath +from util.BitmapLibrary import GetBitmap + +class Beremiz(IDEFrame): + + def _init_utils(self): + self.ConfNodeMenu = wx.Menu(title='') + self.RecentProjectsMenu = wx.Menu(title='') + + IDEFrame._init_utils(self) + + def _init_coll_FileMenu_Items(self, parent): + AppendMenu(parent, help='', id=wx.ID_NEW, + kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N') + AppendMenu(parent, help='', id=wx.ID_OPEN, + kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O') + parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu) + parent.AppendSeparator() + AppendMenu(parent, help='', id=wx.ID_SAVE, + kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S') + AppendMenu(parent, help='', id=wx.ID_SAVEAS, + kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S') + AppendMenu(parent, help='', id=wx.ID_CLOSE, + kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W') + AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL, + kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W') + parent.AppendSeparator() + AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP, + kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P') + AppendMenu(parent, help='', id=wx.ID_PREVIEW, + kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P') + AppendMenu(parent, help='', id=wx.ID_PRINT, + kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P') + parent.AppendSeparator() + AppendMenu(parent, help='', id=wx.ID_EXIT, + kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q') + + self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW) + self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN) + self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE) + self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS) + self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE) + self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL) + self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP) + self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW) + self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT) + self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT) + + self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None), + (wx.ID_OPEN, "open", _(u'Open'), None), + (wx.ID_SAVE, "save", _(u'Save'), None), + (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None), + (wx.ID_PRINT, "print", _(u'Print'), None)]) + + def _RecursiveAddMenuItems(self, menu, items): + for name, text, help, children in items: + new_id = wx.NewId() + if len(children) > 0: + new_menu = wx.Menu(title='') + menu.AppendMenu(new_id, text, new_menu) + self._RecursiveAddMenuItems(new_menu, children) + else: + AppendMenu(menu, help=help, id=new_id, + kind=wx.ITEM_NORMAL, text=text) + self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name), + id=new_id) + + def _init_coll_AddMenu_Items(self, parent): + IDEFrame._init_coll_AddMenu_Items(self, parent, False) + self._RecursiveAddMenuItems(parent, GetAddMenuItems()) + + def _init_coll_HelpMenu_Items(self, parent): + parent.Append(help='', id=wx.ID_ABOUT, + 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) + + self.EditMenuSize = self.EditMenu.GetMenuItemCount() + + inspectorID = wx.NewId() + self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID) + accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL|wx.ACCEL_ALT, ord('I'), inspectorID)] + + keyID = wx.NewId() + self.Bind(wx.EVT_MENU, self.SwitchFullScrMode, id=keyID) + accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, wx.WXK_F12, keyID)] + + for method,shortcut in [("Stop", wx.WXK_F4), + ("Run", wx.WXK_F5), + ("Transfer", wx.WXK_F6), + ("Connect", wx.WXK_F7), + ("Build", wx.WXK_F11)]: + def OnMethodGen(obj,meth): + def OnMethod(evt): + if obj.CTR is not None: + obj.CTR.CallMethod('_'+meth) + wx.CallAfter(self.RefreshStatusToolBar) + return OnMethod + newid = wx.NewId() + self.Bind(wx.EVT_MENU, OnMethodGen(self,method), id=newid) + accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut,newid)] + + self.SetAcceleratorTable(wx.AcceleratorTable(accels)) + + self.LogConsole = CustomStyledTextCtrl( + name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0), + 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)) + StatusToolBar.Realize() + self.Panes["StatusToolBar"] = StatusToolBar + self.AUIManager.AddPane(StatusToolBar, wx.aui.AuiPaneInfo(). + Name("StatusToolBar").Caption(_("Status ToolBar")). + ToolbarPane().Top().Position(1). + LeftDockable(False).RightDockable(False)) + + self.AUIManager.Update() + + self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP) + self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar) + self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range = 100) + self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2) + self.ProgressStatusBar.Hide() + self.SetStatusBar(self.ConnectionStatusBar) + + def __init_execute_path(self): + if os.name == 'nt': + # on windows, desktop shortcut launches Beremiz.py + # with working dir set to mingw/bin. + # then we prefix CWD to PATH in order to ensure that + # commands invoked by build process by default are + # found here. + os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"] + + + def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True): + # Add beremiz's icon in top left corner of the frame + self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO) + self.__init_execute_path() + + IDEFrame.__init__(self, parent, debug) + self.Log = LogPseudoFile(self.LogConsole,self.SelectTab) + + self.local_runtime = None + self.runtime_port = None + self.local_runtime_tmpdir = None + + self.LastPanelSelected = None + + # Define Tree item icon list + self.LocationImageList = wx.ImageList(16, 16) + self.LocationImageDict = {} + + # Icons for location items + for imgname, itemtype in [ + ("CONFIGURATION", LOCATION_CONFNODE), + ("RESOURCE", LOCATION_MODULE), + ("PROGRAM", LOCATION_GROUP), + ("VAR_INPUT", LOCATION_VAR_INPUT), + ("VAR_OUTPUT", LOCATION_VAR_OUTPUT), + ("VAR_LOCAL", LOCATION_VAR_MEMORY)]: + self.LocationImageDict[itemtype] = self.LocationImageList.Add(GetBitmap(imgname)) + + # Icons for other items + for imgname, itemtype in [ + ("Extension", ITEM_CONFNODE)]: + self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname)) + + if projectOpen is not None: + projectOpen = DecodeFileSystemPath(projectOpen, False) + + if projectOpen is not None and os.path.isdir(projectOpen): + self.CTR = ProjectController(self, self.Log) + self.Controler = self.CTR + result, err = self.CTR.LoadProject(projectOpen, buildpath) + if not result: + self.LibraryPanel.SetController(self.Controler) + self.ProjectTree.Enable(True) + self.PouInstanceVariablesPanel.SetController(self.Controler) + self.RefreshConfigRecentProjects(os.path.abspath(projectOpen)) + self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + else: + self.ResetView() + self.ShowErrorMessage(result) + else: + self.CTR = ctr + self.Controler = ctr + if ctr is not None: + self.LibraryPanel.SetController(self.Controler) + self.ProjectTree.Enable(True) + self.PouInstanceVariablesPanel.SetController(self.Controler) + self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + if self.EnableDebug: + self.DebugVariablePanel.SetDataProducer(self.CTR) + + self.Bind(wx.EVT_CLOSE, self.OnCloseFrame) + + self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU) + self.RefreshAll() + self.LogConsole.SetFocus() + + def RefreshTitle(self): + name = _("Beremiz") + if self.CTR is not None: + projectname = self.CTR.GetProjectName() + if self.CTR.ProjectTestModified(): + projectname = "~%s~" % projectname + self.SetTitle("%s - %s" % (name, projectname)) + else: + self.SetTitle(name) + + def StartLocalRuntime(self, taskbaricon = True): + if (self.local_runtime is None) or (self.local_runtime.exitcode is not None): + # create temporary directory for runtime working directory + self.local_runtime_tmpdir = tempfile.mkdtemp() + # choose an arbitrary random port for runtime + self.runtime_port = int(random.random() * 1000) + 61131 + # launch local runtime + self.local_runtime = ProcessLogger(self.Log, + "\"%s\" \"%s\" -p %s -i localhost %s %s"%( + sys.executable, + Bpath("Beremiz_service.py"), + self.runtime_port, + {False : "-x 0", True :"-x 1"}[taskbaricon], + self.local_runtime_tmpdir), + no_gui=False, + timeout=500, keyword = self.local_runtime_tmpdir, + cwd = self.local_runtime_tmpdir) + self.local_runtime.spin() + return self.runtime_port + + def KillLocalRuntime(self): + if self.local_runtime is not None: + # shutdown local runtime + self.local_runtime.kill(gently=False) + # clear temp dir + shutil.rmtree(self.local_runtime_tmpdir) + + self.local_runtime = None + + def OnOpenWidgetInspector(self, evt): + # Activate the widget inspection tool + from wx.lib.inspection import InspectionTool + if not InspectionTool().initialized: + InspectionTool().Init() + + # Find a widget to be selected in the tree. Use either the + # one under the cursor, if any, or this frame. + wnd = wx.FindWindowAtPointer() + if not wnd: + wnd = self + InspectionTool().Show(wnd, True) + + def OnLogConsoleFocusChanged(self, event): + self.RefreshEditMenu() + event.Skip() + + 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: + 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")): + if self.CTR.ProjectTestModified(): + dialog = wx.MessageDialog(self, + _("There are changes, do you want to save?"), + title, + wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION) + answer = dialog.ShowModal() + dialog.Destroy() + if answer == wx.ID_YES: + self.CTR.SaveProject() + elif answer == wx.ID_CANCEL: + return False + + for idx in xrange(self.TabsOpened.GetPageCount()): + window = self.TabsOpened.GetPage(idx) + if not window.CheckSaveBeforeClosing(): + return False + + return True + + def GetTabInfos(self, tab): + if (isinstance(tab, EditorPanel) and + not isinstance(tab, (Viewer, + TextViewer, + ResourceEditor, + ConfigurationEditor, + DataTypeEditor))): + return ("confnode", tab.Controler.CTNFullName(), tab.GetTagName()) + elif (isinstance(tab, TextViewer) and + (tab.Controler is None or isinstance(tab.Controler, MiniTextControler))): + return ("confnode", None, tab.GetInstancePath()) + else: + return IDEFrame.GetTabInfos(self, tab) + + def LoadTab(self, notebook, page_infos): + if page_infos[0] == "confnode": + if page_infos[1] is None: + confnode = self.CTR + else: + confnode = self.CTR.GetChildByName(page_infos[1]) + return notebook.GetPageIndex(confnode._OpenView(*page_infos[2:])) + else: + return IDEFrame.LoadTab(self, notebook, page_infos) + + # Strange hack required by WAMP connector, using twisted. + # Twisted reactor needs to be stopped only before quit, + # since it cannot be restarted + ToDoBeforeQuit = [] + def AddToDoBeforeQuit(self, Thing): + self.ToDoBeforeQuit.append(Thing) + + 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() + self.KillLocalRuntime() + + self.SaveLastState() + + for Thing in self.ToDoBeforeQuit : + Thing() + self.ToDoBeforeQuit = [] + + event.Skip() + else: + event.Veto() + + def RefreshFileMenu(self): + self.RefreshRecentProjectsMenu() + + MenuToolBar = self.Panes["MenuToolBar"] + if self.CTR is not None: + selected = self.TabsOpened.GetSelection() + if selected >= 0: + window = self.TabsOpened.GetPage(selected) + viewer_is_modified = window.IsModified() + is_viewer = isinstance(window, Viewer) + else: + viewer_is_modified = is_viewer = False + if self.TabsOpened.GetPageCount() > 0: + self.FileMenu.Enable(wx.ID_CLOSE, True) + if is_viewer: + self.FileMenu.Enable(wx.ID_PREVIEW, True) + self.FileMenu.Enable(wx.ID_PRINT, True) + MenuToolBar.EnableTool(wx.ID_PRINT, True) + else: + self.FileMenu.Enable(wx.ID_PREVIEW, False) + self.FileMenu.Enable(wx.ID_PRINT, False) + MenuToolBar.EnableTool(wx.ID_PRINT, False) + else: + self.FileMenu.Enable(wx.ID_CLOSE, False) + self.FileMenu.Enable(wx.ID_PREVIEW, False) + self.FileMenu.Enable(wx.ID_PRINT, False) + MenuToolBar.EnableTool(wx.ID_PRINT, False) + self.FileMenu.Enable(wx.ID_PAGE_SETUP, True) + project_modified = self.CTR.ProjectTestModified() or viewer_is_modified + self.FileMenu.Enable(wx.ID_SAVE, project_modified) + MenuToolBar.EnableTool(wx.ID_SAVE, project_modified) + self.FileMenu.Enable(wx.ID_SAVEAS, True) + MenuToolBar.EnableTool(wx.ID_SAVEAS, True) + self.FileMenu.Enable(wx.ID_CLOSE_ALL, True) + else: + self.FileMenu.Enable(wx.ID_CLOSE, False) + self.FileMenu.Enable(wx.ID_PAGE_SETUP, False) + self.FileMenu.Enable(wx.ID_PREVIEW, False) + self.FileMenu.Enable(wx.ID_PRINT, False) + MenuToolBar.EnableTool(wx.ID_PRINT, False) + self.FileMenu.Enable(wx.ID_SAVE, False) + MenuToolBar.EnableTool(wx.ID_SAVE, False) + self.FileMenu.Enable(wx.ID_SAVEAS, False) + MenuToolBar.EnableTool(wx.ID_SAVEAS, False) + self.FileMenu.Enable(wx.ID_CLOSE_ALL, False) + + def RefreshRecentProjectsMenu(self): + try: + recent_projects = map(DecodeFileSystemPath, + self.GetConfigEntry("RecentProjects", [])) + except: + recent_projects = [] + + while self.RecentProjectsMenu.GetMenuItemCount() > len(recent_projects): + item = self.RecentProjectsMenu.FindItemByPosition(0) + self.RecentProjectsMenu.RemoveItem(item) + + self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0) + for idx, projectpath in enumerate(recent_projects): + text = u'&%d: %s' % (idx + 1, projectpath) + + if idx < self.RecentProjectsMenu.GetMenuItemCount(): + item = self.RecentProjectsMenu.FindItemByPosition(idx) + id = item.GetId() + item.SetItemLabel(text) + self.Disconnect(id, id, wx.EVT_BUTTON._getEvtType()) + else: + id = wx.NewId() + AppendMenu(self.RecentProjectsMenu, help='', id=id, + kind=wx.ITEM_NORMAL, text=text) + self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), id=id) + + def GenerateOpenRecentProjectFunction(self, projectpath): + def OpenRecentProject(event): + if self.CTR is not None and not self.CheckSaveBeforeClosing(): + return + + self.OpenProject(projectpath) + return OpenRecentProject + + def GenerateMenuRecursive(self, items, menu): + for kind, infos in items: + if isinstance(kind, ListType): + text, id = infos + submenu = wx.Menu('') + self.GenerateMenuRecursive(kind, submenu) + menu.AppendMenu(id, text, submenu) + elif kind == wx.ITEM_SEPARATOR: + menu.AppendSeparator() + else: + text, id, help, callback = infos + AppendMenu(menu, help='', id=id, kind=kind, text=text) + if callback is not None: + self.Bind(wx.EVT_MENU, callback, id=id) + + def RefreshEditorToolBar(self): + IDEFrame.RefreshEditorToolBar(self) + self.AUIManager.GetPane("EditorToolBar").Position(2) + self.AUIManager.GetPane("StatusToolBar").Position(1) + self.AUIManager.Update() + + def RefreshStatusToolBar(self): + StatusToolBar = self.Panes["StatusToolBar"] + StatusToolBar.ClearTools() + + if self.CTR is not None: + + for confnode_method in self.CTR.StatusMethods: + if "method" in confnode_method and confnode_method.get("shown",True): + id = wx.NewId() + StatusToolBar.AddSimpleTool(id, + GetBitmap(confnode_method.get("bitmap", "Unknown")), + confnode_method["tooltip"]) + self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), id=id) + + StatusToolBar.Realize() + self.AUIManager.GetPane("StatusToolBar").BestSize(StatusToolBar.GetBestSize()).Show() + else: + self.AUIManager.GetPane("StatusToolBar").Hide() + self.AUIManager.GetPane("EditorToolBar").Position(2) + self.AUIManager.GetPane("StatusToolBar").Position(1) + self.AUIManager.Update() + + 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() + if selected >= 0: + panel = self.TabsOpened.GetPage(selected) + else: + panel = None + if panel != self.LastPanelSelected: + for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()): + item = self.EditMenu.FindItemByPosition(self.EditMenuSize) + if item is not None: + if item.IsSeparator(): + self.EditMenu.RemoveItem(item) + else: + self.EditMenu.Delete(item.GetId()) + self.LastPanelSelected = panel + if panel is not None: + items = panel.GetConfNodeMenuItems() + else: + items = [] + if len(items) > 0: + self.EditMenu.AppendSeparator() + self.GenerateMenuRecursive(items, self.EditMenu) + if panel is not None: + panel.RefreshConfNodeMenu(self.EditMenu) + else: + for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()): + item = self.EditMenu.FindItemByPosition(i) + if item is not None: + if item.IsSeparator(): + self.EditMenu.RemoveItem(item) + else: + self.EditMenu.Delete(item.GetId()) + self.LastPanelSelected = None + self.MenuBar.UpdateMenus() + + def RefreshAll(self): + self.RefreshStatusToolBar() + + def GetMenuCallBackFunction(self, method): + """ Generate the callbackfunc for a given CTR method""" + def OnMenu(event): + # Disable button to prevent re-entrant call + event.GetEventObject().Disable() + # Call + getattr(self.CTR, method)() + # Re-enable button + event.GetEventObject().Enable() + return OnMenu + + 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 = {} + if self.CTR is not None: + self.CTR.CloseProject() + self.CTR = None + self.Log.flush() + if self.EnableDebug: + self.DebugVariablePanel.SetDataProducer(None) + self.ResetConnectionStatusBar() + + def RefreshConfigRecentProjects(self, projectpath, err=False): + try: + recent_projects = map(DecodeFileSystemPath, + self.GetConfigEntry("RecentProjects", [])) + except: + recent_projects = [] + if projectpath in recent_projects: + recent_projects.remove(projectpath) + if not err: + recent_projects.insert(0, projectpath) + self.Config.Write("RecentProjects", cPickle.dumps( + map(EncodeFileSystemPath, recent_projects[:MAX_RECENT_PROJECTS]))) + self.Config.Flush() + + def ResetPerspective(self): + IDEFrame.ResetPerspective(self) + self.RefreshStatusToolBar() + + def OnNewProjectMenu(self, event): + if self.CTR is not None and not self.CheckSaveBeforeClosing(): + return + + try: + defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder")) + except: + defaultpath = os.path.expanduser("~") + + dialog = wx.DirDialog(self , _("Choose a project"), defaultpath) + if dialog.ShowModal() == wx.ID_OK: + projectpath = dialog.GetPath() + self.Config.Write("lastopenedfolder", + EncodeFileSystemPath(os.path.dirname(projectpath))) + self.Config.Flush() + self.ResetView() + ctr = ProjectController(self, self.Log) + result = ctr.NewProject(projectpath) + if not result: + self.CTR = ctr + self.Controler = self.CTR + self.LibraryPanel.SetController(self.Controler) + self.ProjectTree.Enable(True) + self.PouInstanceVariablesPanel.SetController(self.Controler) + self.RefreshConfigRecentProjects(projectpath) + if self.EnableDebug: + self.DebugVariablePanel.SetDataProducer(self.CTR) + self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + else: + self.ResetView() + self.ShowErrorMessage(result) + self.RefreshAll() + self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) + dialog.Destroy() + + def OnOpenProjectMenu(self, event): + if self.CTR is not None and not self.CheckSaveBeforeClosing(): + return + + try: + defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder")) + except: + defaultpath = os.path.expanduser("~") + + dialog = wx.DirDialog(self , _("Choose a project"), defaultpath, style=wx.DEFAULT_DIALOG_STYLE| + wx.RESIZE_BORDER) + if dialog.ShowModal() == wx.ID_OK: + self.OpenProject(dialog.GetPath()) + dialog.Destroy() + + def OpenProject(self, projectpath): + if os.path.isdir(projectpath): + self.Config.Write("lastopenedfolder", + EncodeFileSystemPath(os.path.dirname(projectpath))) + self.Config.Flush() + self.ResetView() + self.CTR = ProjectController(self, self.Log) + self.Controler = self.CTR + result, err = self.CTR.LoadProject(projectpath) + if not result: + self.LibraryPanel.SetController(self.Controler) + self.ProjectTree.Enable(True) + self.PouInstanceVariablesPanel.SetController(self.Controler) + if self.EnableDebug: + self.DebugVariablePanel.SetDataProducer(self.CTR) + self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + else: + self.ResetView() + self.ShowErrorMessage(result) + self.RefreshAll() + self.SearchResultPanel.ResetSearchResults() + else: + self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath) + err = True + self.RefreshConfigRecentProjects(projectpath, err) + self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) + + def OnCloseProjectMenu(self, event): + if self.CTR is not None and not self.CheckSaveBeforeClosing(): + return + + self.ResetView() + self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) + self.RefreshAll() + + def OnSaveProjectMenu(self, event): + selected = self.TabsOpened.GetSelection() + if selected != -1: + window = self.TabsOpened.GetPage(selected) + window.Save() + if self.CTR is not None: + self.CTR.SaveProject() + self.RefreshAll() + self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) + + def OnSaveProjectAsMenu(self, event): + selected = self.TabsOpened.GetSelection() + if selected != -1: + window = self.TabsOpened.GetPage(selected) + window.SaveAs() + if self.CTR is not None: + self.CTR.SaveProjectAs() + self.RefreshAll() + self.RefreshConfigRecentProjects(self.CTR.ProjectPath) + self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) + + def OnQuitMenu(self, event): + self.Close() + + def OnAboutMenu(self, event): + info = version.GetAboutDialogInfo() + ShowAboutDialog(self, info) + + def OnProjectTreeItemBeginEdit(self, event): + selected = event.GetItem() + if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFNODE: + event.Veto() + else: + IDEFrame.OnProjectTreeItemBeginEdit(self, event) + + def OnProjectTreeRightUp(self, event): + item = event.GetItem() + item_infos = self.ProjectTree.GetPyData(item) + + if item_infos["type"] == ITEM_CONFNODE: + confnode_menu = wx.Menu(title='') + + confnode = item_infos["confnode"] + if confnode is not None: + menu_items = confnode.GetContextualMenuItems() + if menu_items is not None: + for text, help, callback in menu_items: + new_id = wx.NewId() + confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=text) + self.Bind(wx.EVT_MENU, callback, id=new_id) + else: + for name, XSDClass, help in confnode.CTNChildrenTypes: + new_id = wx.NewId() + confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=_("Add") + " " + name) + self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name, confnode), id=new_id) + + new_id = wx.NewId() + AppendMenu(confnode_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Delete")) + self.Bind(wx.EVT_MENU, self.GetDeleteMenuFunction(confnode), id=new_id) + + self.PopupMenu(confnode_menu) + confnode_menu.Destroy() + + event.Skip() + elif item_infos["type"] == ITEM_RESOURCE: + # prevent last resource to be delted + parent = self.ProjectTree.GetItemParent(item) + parent_name = self.ProjectTree.GetItemText(parent) + if parent_name == _("Resources"): + IDEFrame.OnProjectTreeRightUp(self, event) + else: + IDEFrame.OnProjectTreeRightUp(self, event) + + def OnProjectTreeItemActivated(self, event): + selected = event.GetItem() + name = self.ProjectTree.GetItemText(selected) + item_infos = self.ProjectTree.GetPyData(selected) + if item_infos["type"] == ITEM_CONFNODE: + item_infos["confnode"]._OpenView() + event.Skip() + elif item_infos["type"] == ITEM_PROJECT: + self.CTR._OpenView() + else: + IDEFrame.OnProjectTreeItemActivated(self, event) + + def 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: + root = self.ProjectTree.GetRootItem() + if root.IsOk(): + words = tagname.split("::") + if len(words) == 1: + if tagname == "Project": + self.SelectedItem = root + self.ProjectTree.SelectItem(root) + 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)]) + elif not os.path.exists(words[0]): + IDEFrame.SelectProjectTreeItem(self, tagname) + + def GetAddConfNodeFunction(self, name, confnode=None): + def AddConfNodeMenuFunction(event): + wx.CallAfter(self.AddConfNode, name, confnode) + return AddConfNodeMenuFunction + + def GetDeleteMenuFunction(self, confnode): + def DeleteMenuFunction(event): + wx.CallAfter(self.DeleteConfNode, confnode) + return DeleteMenuFunction + + def AddConfNode(self, ConfNodeType, confnode=None): + if self.CTR.CheckProjectPathPerm(): + ConfNodeName = "%s_0" % ConfNodeType + if confnode is not None: + confnode.CTNAddChild(ConfNodeName, ConfNodeType) + else: + self.CTR.CTNAddChild(ConfNodeName, ConfNodeType) + self._Refresh(TITLE, FILEMENU, PROJECTTREE) + + def DeleteConfNode(self, confnode): + if self.CTR.CheckProjectPathPerm(): + dialog = wx.MessageDialog(self, + _("Really delete node '%s'?") % confnode.CTNName(), + _("Remove %s node") % confnode.CTNType, + wx.YES_NO|wx.NO_DEFAULT) + if dialog.ShowModal() == wx.ID_YES: + confnode.CTNRemove() + del confnode + self._Refresh(TITLE, FILEMENU, PROJECTTREE) + dialog.Destroy() + +#------------------------------------------------------------------------------- +# Highlights showing functions +#------------------------------------------------------------------------------- + + def ShowHighlight(self, infos, start, end, highlight_type): + config_name = self.Controler.GetProjectMainConfigurationName() + if config_name is not None and infos[0] == self.Controler.ComputeConfigurationName(config_name): + self.CTR._OpenView() + selected = self.TabsOpened.GetSelection() + if selected != -1: + viewer = self.TabsOpened.GetPage(selected) + viewer.AddHighlight(infos[1:], start, end, highlight_type) + else: + IDEFrame.ShowHighlight(self, infos, start, end, highlight_type) + +#------------------------------------------------------------------------------- +# Exception Handler +#------------------------------------------------------------------------------- +import threading, traceback + +Max_Traceback_List_Size = 20 + +def Display_Exception_Dialog(e_type, e_value, e_tb, bug_report_path): + trcbck_lst = [] + for i,line in enumerate(traceback.extract_tb(e_tb)): + trcbck = " " + str(i+1) + ". " + if line[0].find(os.getcwd()) == -1: + trcbck += "file : " + str(line[0]) + ", " + else: + trcbck += "file : " + str(line[0][len(os.getcwd()):]) + ", " + trcbck += "line : " + str(line[1]) + ", " + "function : " + str(line[2]) + trcbck_lst.append(trcbck) + + # Allow clicking.... + cap = wx.Window_GetCapture() + if cap: + cap.ReleaseMouse() + + dlg = wx.SingleChoiceDialog(None, + _(""" +An unhandled exception (bug) occured. Bug report saved at : +(%s) + +Please be kind enough to send this file to: +beremiz-devel@lists.sourceforge.net + +You should now restart program. + +Traceback: +""") % bug_report_path + + repr(e_type) + " : " + repr(e_value), + _("Error"), + trcbck_lst) + try: + res = (dlg.ShowModal() == wx.ID_OK) + finally: + dlg.Destroy() + + return res + +def get_last_traceback(tb): + while tb.tb_next: + tb = tb.tb_next + return tb + + +def format_namespace(d, indent=' '): + return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()]) + + +ignored_exceptions = [] # a problem with a line in a module is only reported once per session + +def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]): + + def save_bug_report(e_type, e_value, e_traceback, bug_report_path,date): + info = { + 'app-title': wx.GetApp().GetAppName(), # app_title + 'app-version': app_version, + 'wx-version': wx.VERSION_STRING, + 'wx-platform': wx.Platform, + 'python-version': platform.python_version(), # sys.version.split()[0], + 'platform': platform.platform(), + 'e-type': e_type, + 'e-value': e_value, + 'date': date, + 'cwd': os.getcwd(), + } + if e_traceback: + info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value) + last_tb = get_last_traceback(e_traceback) + exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred + info['locals'] = format_namespace(exception_locals) + if 'self' in exception_locals: + try: + info['self'] = format_namespace(exception_locals['self'].__dict__) + except: + pass + if not os.path.exists(path): + os.mkdir(path) + output = open(bug_report_path, 'w') + lst = info.keys() + lst.sort() + for a in lst: + output.write(a + ":\n" + str(info[a]) + "\n\n") + output.close() + + def handle_exception(e_type, e_value, e_traceback): + traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func + last_tb = get_last_traceback(e_traceback) + ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno) + if ex not in ignored_exceptions: + ignored_exceptions.append(ex) + date = time.ctime() + bug_report_path = path + os.sep + "bug_report_" + date.replace(':', '-').replace(' ', '_') + ".txt" + save_bug_report(e_type, e_value, e_traceback, bug_report_path, date) + Display_Exception_Dialog(e_type, e_value, e_traceback, bug_report_path) + #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