andrej@1662: #!/usr/bin/env python
andrej@1662: # -*- coding: utf-8 -*-
andrej@1662: 
andrej@1662: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1662: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1662: #
andrej@1662: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
andrej@1662: # Copyright (C) 2016: Andrey Skvortsov <andrej.skvortzov@gmail.com>
andrej@1662: #
andrej@1662: # See COPYING file for copyrights details.
andrej@1662: #
andrej@1662: # This program is free software; you can redistribute it and/or
andrej@1662: # modify it under the terms of the GNU General Public License
andrej@1662: # as published by the Free Software Foundation; either version 2
andrej@1662: # of the License, or (at your option) any later version.
andrej@1662: #
andrej@1662: # This program is distributed in the hope that it will be useful,
andrej@1662: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1662: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1662: # GNU General Public License for more details.
andrej@1662: #
andrej@1662: # You should have received a copy of the GNU General Public License
andrej@1662: # along with this program; if not, write to the Free Software
andrej@1662: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
andrej@1662: 
andrej@1662: 
andrej@1732: import os
kinsamanka@3758: import pickle
andrej@1732: import sys
andrej@1662: import shutil
andrej@1834: import time
edouard@3712: import signal
andrej@1832: from time import time as gettime
edouard@3833: from threading import Lock, Timer, current_thread
andrej@1832: 
andrej@1732: import wx.lib.buttons
andrej@1732: import wx.lib.statbmp
andrej@1732: import wx.stc
edouard@3570: import wx.adv
andrej@1832: 
andrej@1832: 
andrej@1832: import version
andrej@1662: from editors.EditorPanel import EditorPanel
andrej@1662: from editors.Viewer import Viewer
andrej@1662: from editors.TextViewer import TextViewer
andrej@1662: from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
andrej@1662: from editors.DataTypeEditor import DataTypeEditor
edouard@3906: from util.paths import Bpath, ThirdPartyPath
andrej@1662: from util.MiniTextControler import MiniTextControler
andrej@1834: from util.BitmapLibrary import GetBitmap
andrej@1662: from controls.LogViewer import LogViewer
andrej@1662: from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
andrej@1662: from controls import EnhancedStatusBar as esb
andrej@1662: from dialogs.AboutDialog import ShowAboutDialog
andrej@1662: 
Edouard@1948: from plcopen.types_enums import \
Edouard@1948:     ComputeConfigurationName, \
andrej@1783:     LOCATION_CONFNODE, \
andrej@1783:     LOCATION_MODULE, \
andrej@1783:     LOCATION_GROUP, \
andrej@1783:     LOCATION_VAR_INPUT, \
andrej@1783:     LOCATION_VAR_OUTPUT, \
andrej@1783:     LOCATION_VAR_MEMORY, \
andrej@1783:     ITEM_PROJECT, \
Edouard@1948:     ITEM_RESOURCE, \
Edouard@1948:     ITEM_CONFNODE
Edouard@1948: 
Edouard@1948: from ProjectController import ProjectController, GetAddMenuItems, MATIEC_ERROR_MODEL
andrej@1662: 
andrej@1783: from IDEFrame import \
andrej@1783:     TITLE,\
andrej@1783:     EDITORTOOLBAR,\
andrej@1783:     FILEMENU,\
andrej@1783:     EDITMENU,\
andrej@1783:     DISPLAYMENU,\
andrej@1783:     PROJECTTREE,\
andrej@1783:     POUINSTANCEVARIABLESPANEL,\
andrej@1783:     LIBRARYTREE,\
andrej@1783:     PAGETITLES,\
andrej@1783:     IDEFrame, \
andrej@1783:     EncodeFileSystemPath, \
andrej@1783:     DecodeFileSystemPath
andrej@1783: 
edouard@3501: from LocalRuntimeMixin import LocalRuntimeMixin
edouard@3501: 
andrej@1783: 
Edouard@2737: def AppendMenu(parent, help, id, kind, text):
edouard@3303:     return parent.Append(wx.MenuItem(helpString=help, id=id, kind=kind, text=text))
andrej@1662: 
andrej@1662: MAX_RECENT_PROJECTS = 9
andrej@1662: 
andrej@1783: 
andrej@1662: if wx.Platform == '__WXMSW__':
andrej@1662:     faces = {
andrej@1739:         'mono': 'Courier New',
andrej@1739:         'size': 8,
andrej@1662:     }
andrej@1662: else:
andrej@1662:     faces = {
edouard@2704:         'mono': 'FreeMono',
andrej@1739:         'size': 10,
andrej@1662:     }
andrej@1662: 
andrej@1783: 
edouard@3833: MainThread = current_thread().ident
andrej@1662: REFRESH_PERIOD = 0.1
andrej@1736: 
andrej@1736: 
andrej@1831: class LogPseudoFile(object):
andrej@1662:     """ Base class for file like objects to facilitate StdOut for the Shell."""
edouard@3423:     def __init__(self, output, risecall, logf):
andrej@1662:         self.red_white = 1
andrej@1662:         self.red_yellow = 2
andrej@1662:         self.black_white = wx.stc.STC_STYLE_DEFAULT
andrej@1662:         self.output = output
andrej@1662:         self.risecall = risecall
andrej@1662:         # to prevent rapid fire on rising log panel
andrej@1662:         self.rising_timer = 0
edouard@2728:         self.StackLock = Lock()
andrej@1662:         self.YieldLock = Lock()
andrej@1662:         self.RefreshLock = Lock()
andrej@1662:         self.TimerAccessLock = Lock()
andrej@1662:         self.stack = []
andrej@1662:         self.LastRefreshTime = gettime()
andrej@1662:         self.LastRefreshTimer = None
edouard@2730:         self.refreshPending = False
edouard@3423:         self.logf = logf
andrej@1662: 
andrej@1744:     def write(self, s, style=None):
edouard@3423:         if self.logf is not None:
edouard@3423:             self.logf.write(s)
edouard@3437:             self.logf.flush()
edouard@2728:         self.StackLock.acquire()
edouard@2728:         self.stack.append((s, style))
edouard@2728:         self.StackLock.release()
edouard@2728:         current_time = gettime()
edouard@2728:         with self.TimerAccessLock:
edouard@2728:             if self.LastRefreshTimer is not None:
edouard@2728:                 self.LastRefreshTimer.cancel()
edouard@2728:                 self.LastRefreshTimer = None
edouard@2728:         elapsed = current_time - self.LastRefreshTime
edouard@2728:         if elapsed > REFRESH_PERIOD:
edouard@2728:             self._should_write()
edouard@2728:         else:
edouard@2726:             with self.TimerAccessLock:
edouard@2728:                 if self.LastRefreshTimer is None:
edouard@2728:                     self.LastRefreshTimer = Timer(REFRESH_PERIOD - elapsed, self._timer_expired)
edouard@2728:                     self.LastRefreshTimer.start()
andrej@1662: 
andrej@1662:     def _timer_expired(self):
edouard@2726:         self._should_write()
edouard@2726:         with self.TimerAccessLock:
edouard@2726:             self.LastRefreshTimer = None
andrej@1662: 
andrej@1662:     def _should_write(self):
edouard@3833:         if MainThread == current_thread().ident:
edouard@2726:             app = wx.GetApp()
andrej@1662:             if app is not None:
edouard@2726:                 self._write()
andrej@1662:                 if self.YieldLock.acquire(0):
andrej@1662:                     app.Yield()
andrej@1662:                     self.YieldLock.release()
edouard@2726:         else:
edouard@2726:             with self.RefreshLock:
edouard@2726:                 if not self.refreshPending:
edouard@2726:                     self.refreshPending = True
edouard@2726:                     wx.CallAfter(self._write)
andrej@1662: 
andrej@1662:     def _write(self):
andrej@1739:         if self.output:
edouard@2726:             with self.RefreshLock:
edouard@2726:                 self.output.Freeze()
edouard@2728:                 self.output.AnnotationClearAll()
edouard@2728:                 self.StackLock.acquire()
edouard@2726:                 for s, style in self.stack:
edouard@2726:                     if style is None:
edouard@2726:                         style = self.black_white
edouard@2726:                     if style != self.black_white:
edouard@3303:                         self.output.StartStyling(self.output.GetLength())
edouard@2726: 
edouard@2726:                     # Temporary deactivate read only mode on StyledTextCtrl for
edouard@2726:                     # adding text. It seems that text modifications, even
edouard@2726:                     # programmatically, are disabled in StyledTextCtrl when read
edouard@2726:                     # only is active
edouard@2726:                     start_pos = self.output.GetLength()
edouard@2726:                     self.output.SetReadOnly(False)
edouard@2726:                     self.output.AppendText(s)
edouard@2726:                     self.output.SetReadOnly(True)
edouard@2726:                     text_len = self.output.GetLength() - start_pos
edouard@2726: 
edouard@2726:                     if style != self.black_white:
edouard@2726:                         self.output.SetStyling(text_len, style)
edouard@2726:                 self.stack = []
edouard@2728:                 self.StackLock.release()
Edouard@2731:                 self.output.ScrollToEnd()
edouard@2726:                 self.output.Thaw()
edouard@2726:                 self.LastRefreshTime = gettime()
edouard@2726:                 newtime = time.time()
edouard@2726:                 if newtime - self.rising_timer > 1:
edouard@2726:                     self.risecall(self.output)
edouard@2726:                 self.rising_timer = newtime
edouard@2726:                 self.refreshPending = False
edouard@2726: 
andrej@1662: 
andrej@1662:     def write_warning(self, s):
andrej@1740:         self.write(s, self.red_white)
andrej@1662: 
andrej@1662:     def write_error(self, s):
andrej@1740:         self.write(s, self.red_yellow)
andrej@1662: 
andrej@1662:     def flush(self):
andrej@1662:         # Temporary deactivate read only mode on StyledTextCtrl for clearing
andrej@1662:         # text. It seems that text modifications, even programmatically, are
andrej@1662:         # disabled in StyledTextCtrl when read only is active
andrej@1662:         self.output.SetReadOnly(False)
andrej@1662:         self.output.SetText("")
andrej@1662:         self.output.SetReadOnly(True)
andrej@1662: 
andrej@1662:     def isatty(self):
andrej@1662:         return False
andrej@1662: 
edouard@2730:     def progress(self, text):
Edouard@2740:         l = max(self.output.GetLineCount()-2, 0)
edouard@2730:         self.output.AnnotationSetText(l, text)
edouard@2730:         self.output.AnnotationSetVisible(wx.stc.STC_ANNOTATION_BOXED)
edouard@2730:         self.output.AnnotationSetStyle(l, self.black_white)
edouard@2730:         if self.YieldLock.acquire(0):
edouard@2730:             app = wx.GetApp()
edouard@2730:             app.Yield()
edouard@2730:             self.YieldLock.release()
edouard@2730: 
andrej@1749: 
kinsamanka@3766: ID_FILEMENURECENTPROJECTS = wx.NewIdRef()
andrej@1662: 
andrej@1736: 
edouard@3501: class Beremiz(IDEFrame, LocalRuntimeMixin):
andrej@1662: 
andrej@1662:     def _init_utils(self):
andrej@1662:         self.ConfNodeMenu = wx.Menu(title='')
andrej@1662:         self.RecentProjectsMenu = wx.Menu(title='')
edouard@3351:         self.TutorialsProjectsMenu = wx.Menu(title='')
andrej@1662: 
andrej@1662:         IDEFrame._init_utils(self)
andrej@1662: 
andrej@1662:     def _init_coll_FileMenu_Items(self, parent):
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_NEW,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('New') + '\tCTRL+N')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_OPEN,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Open') + '\tCTRL+O')
edouard@3833:         self.RecentProjectsMenuItem = parent.AppendSubMenu(self.RecentProjectsMenu, _("&Recent Projects"))
andrej@1662:         parent.AppendSeparator()
edouard@3833:         parent.AppendSubMenu(self.TutorialsProjectsMenu, _("&Tutorials and Examples"))
edouard@3351: 
edouard@3351:         exemples_dir = Bpath("exemples")
edouard@3351:         project_list = sorted(os.listdir(exemples_dir))
edouard@3351: 
edouard@3351:         for idx, dirname  in enumerate(project_list):
kinsamanka@3750:             text = '&%d: %s' % (idx + 1, dirname)
edouard@3351: 
edouard@3351:             item = self.TutorialsProjectsMenu.Append(wx.ID_ANY, text, '')
edouard@3351: 
edouard@3351:             projectpath = os.path.join(exemples_dir, dirname)
edouard@3351: 
edouard@3390:             def OpenExemple(event, projectpath=projectpath):
edouard@3351:                 if self.CTR is not None and not self.CheckSaveBeforeClosing():
edouard@3351:                     return
edouard@3351: 
edouard@3351:                 self.OpenProject(projectpath)
edouard@3351:                 if not self.CTR.CheckProjectPathPerm():
edouard@3351:                     self.ResetView()
edouard@3351: 
edouard@3351:             self.Bind(wx.EVT_MENU, OpenExemple, item)
edouard@3351:         parent.AppendSeparator()
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_SAVE,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Save') + '\tCTRL+S')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Save as') + '\tCTRL+SHIFT+S')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_CLOSE,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Close Tab') + '\tCTRL+W')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Close Project') + '\tCTRL+SHIFT+W')
andrej@1662:         parent.AppendSeparator()
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Page Setup') + '\tCTRL+ALT+P')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Preview') + '\tCTRL+SHIFT+P')
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_PRINT,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Print') + '\tCTRL+P')
andrej@1662:         parent.AppendSeparator()
andrej@1662:         AppendMenu(parent, help='', id=wx.ID_EXIT,
kinsamanka@3750:                    kind=wx.ITEM_NORMAL, text=_('Quit') + '\tCTRL+Q')
andrej@1662: 
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
andrej@1662: 
kinsamanka@3750:         self.AddToMenuToolBar([(wx.ID_NEW, "new", _('New'), None),
kinsamanka@3750:                                (wx.ID_OPEN, "open", _('Open'), None),
kinsamanka@3750:                                (wx.ID_SAVE, "save", _('Save'), None),
kinsamanka@3750:                                (wx.ID_SAVEAS, "saveas", _('Save As...'), None),
kinsamanka@3750:                                (wx.ID_PRINT, "print", _('Print'), None)])
andrej@1662: 
andrej@1662:     def _RecursiveAddMenuItems(self, menu, items):
Edouard@2569:         for name, text, helpstr, children in items:
andrej@1662:             if len(children) > 0:
andrej@1662:                 new_menu = wx.Menu(title='')
edouard@3303:                 menu.AppendSubMenu(new_menu, text)
andrej@1662:                 self._RecursiveAddMenuItems(new_menu, children)
andrej@1662:             else:
edouard@3336:                 item = menu.Append(wx.MenuItem(text=text, helpString=helpstr, kind=wx.ITEM_NORMAL, id=wx.ID_ANY))
Edouard@2569:                 self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name), item)
andrej@1662: 
andrej@1662:     def _init_coll_AddMenu_Items(self, parent):
andrej@1662:         IDEFrame._init_coll_AddMenu_Items(self, parent, False)
andrej@1662:         self._RecursiveAddMenuItems(parent, GetAddMenuItems())
andrej@1662: 
andrej@1662:     def _init_coll_HelpMenu_Items(self, parent):
andrej@1762:         def handler(event):
andrej@1762:             return wx.MessageBox(
andrej@1762:                 version.GetCommunityHelpMsg(),
kinsamanka@3750:                 _('Community support'),
andrej@1762:                 wx.OK | wx.ICON_INFORMATION)
andrej@1762: 
kinsamanka@3750:         item = parent.Append(wx.ID_ANY, _('Community support'), '')
Edouard@2569:         self.Bind(wx.EVT_MENU, handler, item)
andrej@1730: 
edouard@3303:         parent.Append(wx.MenuItem(helpString='', id=wx.ID_ABOUT,
kinsamanka@3750:                       kind=wx.ITEM_NORMAL, text=_('About')))
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
andrej@1662: 
andrej@1662:     def _init_coll_ConnectionStatusBar_Fields(self, parent):
andrej@1662:         parent.SetFieldsCount(3)
andrej@1662: 
edouard@3303:         parent.SetStatusText(i=0, text='')
edouard@3303:         parent.SetStatusText(i=1, text='')
edouard@3303:         parent.SetStatusText(i=2, text='')
andrej@1662: 
andrej@1662:         parent.SetStatusWidths([-1, 300, 200])
andrej@1662: 
andrej@1662:     def _init_ctrls(self, prnt):
andrej@1662:         IDEFrame._init_ctrls(self, prnt)
andrej@1662: 
andrej@1662:         self.EditMenuSize = self.EditMenu.GetMenuItemCount()
andrej@1662: 
kinsamanka@3766:         inspectorID = wx.NewIdRef()
andrej@1662:         self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID)
andrej@1745:         accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord('I'), inspectorID)]
andrej@1662: 
edouard@3445:         self.methodLock = Lock()
edouard@3445: 
andrej@1740:         for method, shortcut in [("Stop",     wx.WXK_F4),
andrej@1740:                                  ("Run",      wx.WXK_F5),
andrej@1740:                                  ("Transfer", wx.WXK_F6),
andrej@1740:                                  ("Connect",  wx.WXK_F7),
Edouard@2735:                                  ("Clean",    wx.WXK_F9),
andrej@1740:                                  ("Build",    wx.WXK_F11)]:
andrej@1740:             def OnMethodGen(obj, meth):
andrej@1662:                 def OnMethod(evt):
andrej@1662:                     if obj.CTR is not None:
edouard@3445:                         if obj.methodLock.acquire(False):
edouard@3445:                             obj.CTR.CallMethod('_'+meth)
edouard@3445:                             obj.methodLock.release()
edouard@3445:                             wx.CallAfter(obj.RefreshStatusToolBar)
edouard@3445:                         else:
edouard@3445:                             # Postpone call if one of method already running
edouard@3445:                             # can happen because of long method using log, 
edouard@3445:                             # itself calling wx.Yield
edouard@3445:                             wx.CallLater(50, OnMethod, evt)
andrej@1662:                 return OnMethod
kinsamanka@3766:             newid = wx.NewIdRef()
andrej@1740:             self.Bind(wx.EVT_MENU, OnMethodGen(self, method), id=newid)
andrej@1740:             accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut, newid)]
andrej@1662: 
andrej@1662:         self.SetAcceleratorTable(wx.AcceleratorTable(accels))
andrej@1662: 
andrej@1662:         self.LogConsole = CustomStyledTextCtrl(
andrej@1878:             name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
andrej@1878:             size=wx.Size(0, 0))
andrej@1662:         self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged)
andrej@1662:         self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged)
andrej@1662:         self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI)
andrej@1662:         self.LogConsole.SetReadOnly(True)
andrej@1662:         self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR)
andrej@1662: 
andrej@1662:         # Define Log Console styles
andrej@1662:         self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
andrej@1662:         self.LogConsole.StyleClearAll()
andrej@1662:         self.LogConsole.StyleSetSpec(1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces)
andrej@1662:         self.LogConsole.StyleSetSpec(2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)
andrej@1662: 
andrej@1662:         # Define Log Console markers
andrej@1662:         self.LogConsole.SetMarginSensitive(1, True)
andrej@1662:         self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
andrej@1662:         self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
andrej@1662: 
andrej@1662:         self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
edouard@3424:         self.LogConsole.SetCaretPeriod(0)
andrej@1662: 
andrej@1662:         self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
andrej@1662:         self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
andrej@1662: 
andrej@1662:         self.MainTabs["LogConsole"] = (self.LogConsole, _("Console"))
andrej@1662:         self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"])
andrej@1782:         # self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT)
andrej@1662: 
andrej@1662:         self.LogViewer = LogViewer(self.BottomNoteBook, self)
andrej@1662:         self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log"))
andrej@1662:         self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"])
andrej@1782:         # self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)
andrej@1662: 
andrej@1662:         StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
edouard@3588:                                    wx.TB_FLAT | wx.TB_HORIZONTAL | wx.NO_BORDER)
andrej@1662:         StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
andrej@1662:         StatusToolBar.Realize()
andrej@1662:         self.Panes["StatusToolBar"] = StatusToolBar
andrej@1662:         self.AUIManager.AddPane(StatusToolBar, wx.aui.AuiPaneInfo().
andrej@1768:                                 Name("StatusToolBar").Caption(_("Status ToolBar")).
andrej@1768:                                 ToolbarPane().Top().Position(1).
andrej@1768:                                 LeftDockable(False).RightDockable(False))
andrej@1662: 
andrej@1662:         self.AUIManager.Update()
andrej@1662: 
edouard@3303:         self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.STB_SIZEGRIP)
andrej@1662:         self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
andrej@1744:         self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range=100)
andrej@1730:         self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2)
andrej@1662:         self.ProgressStatusBar.Hide()
andrej@1662:         self.SetStatusBar(self.ConnectionStatusBar)
andrej@1662: 
andrej@1662:     def __init_execute_path(self):
andrej@1662:         if os.name == 'nt':
andrej@1662:             # on windows, desktop shortcut launches Beremiz.py
andrej@1662:             # with working dir set to mingw/bin.
andrej@1662:             # then we prefix CWD to PATH in order to ensure that
andrej@1662:             # commands invoked by build process by default are
andrej@1662:             # found here.
andrej@1662:             os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"]
andrej@1730: 
edouard@3423:     def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True, logf=None):
edouard@3501: 
andrej@1662:         # Add beremiz's icon in top left corner of the frame
andrej@1662:         self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO)
andrej@1662:         self.__init_execute_path()
andrej@1730: 
andrej@1662:         IDEFrame.__init__(self, parent, debug)
edouard@3423:         self.Log = LogPseudoFile(self.LogConsole, self.SelectTab, logf)
andrej@1662: 
edouard@3502:         LocalRuntimeMixin.__init__(self, self.Log)
edouard@3502: 
edouard@4068:         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
edouard@4068: 
andrej@1662:         self.LastPanelSelected = None
andrej@1662: 
andrej@1662:         # Define Tree item icon list
andrej@1662:         self.LocationImageList = wx.ImageList(16, 16)
andrej@1662:         self.LocationImageDict = {}
andrej@1662: 
andrej@1662:         # Icons for location items
andrej@1662:         for imgname, itemtype in [
andrej@1766:                 ("CONFIGURATION", LOCATION_CONFNODE),
andrej@1766:                 ("RESOURCE",      LOCATION_MODULE),
andrej@1766:                 ("PROGRAM",       LOCATION_GROUP),
andrej@1766:                 ("VAR_INPUT",     LOCATION_VAR_INPUT),
andrej@1766:                 ("VAR_OUTPUT",    LOCATION_VAR_OUTPUT),
andrej@1766:                 ("VAR_LOCAL",     LOCATION_VAR_MEMORY)]:
andrej@1662:             self.LocationImageDict[itemtype] = self.LocationImageList.Add(GetBitmap(imgname))
andrej@1662: 
andrej@1662:         # Icons for other items
andrej@1662:         for imgname, itemtype in [
andrej@1769:                 ("Extension", ITEM_CONFNODE)]:
andrej@1662:             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
andrej@1662: 
andrej@1662:         if projectOpen is not None:
andrej@1662:             projectOpen = DecodeFileSystemPath(projectOpen, False)
andrej@1662: 
andrej@1662:         if projectOpen is not None and os.path.isdir(projectOpen):
andrej@1662:             self.CTR = ProjectController(self, self.Log)
andrej@1662:             self.Controler = self.CTR
andrej@1847:             result, _err = self.CTR.LoadProject(projectOpen, buildpath)
andrej@1662:             if not result:
andrej@1662:                 self.LibraryPanel.SetController(self.Controler)
andrej@1662:                 self.ProjectTree.Enable(True)
andrej@1662:                 self.PouInstanceVariablesPanel.SetController(self.Controler)
andrej@1662:                 self.RefreshConfigRecentProjects(os.path.abspath(projectOpen))
Edouard@1933:                 self.RefreshAfterLoad()
andrej@1662:             else:
andrej@1662:                 self.ResetView()
andrej@1662:                 self.ShowErrorMessage(result)
andrej@1662:         else:
andrej@1662:             self.CTR = ctr
andrej@1662:             self.Controler = ctr
andrej@1662:             if ctr is not None:
Edouard@1933:                 ctr.SetAppFrame(self, self.Log)
andrej@1662:                 self.LibraryPanel.SetController(self.Controler)
andrej@1662:                 self.ProjectTree.Enable(True)
andrej@1662:                 self.PouInstanceVariablesPanel.SetController(self.Controler)
Edouard@1933:                 self.RefreshAfterLoad()
andrej@1662:         if self.EnableDebug:
andrej@1662:             self.DebugVariablePanel.SetDataProducer(self.CTR)
andrej@1662: 
andrej@1662:         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
andrej@1662:         self.RefreshAll()
andrej@1662:         self.LogConsole.SetFocus()
andrej@1662: 
edouard@3712:         signal.signal(signal.SIGTERM,self.signalTERM_handler)
edouard@3712: 
andrej@1662:     def RefreshTitle(self):
andrej@1662:         name = _("Beremiz")
andrej@1662:         if self.CTR is not None:
andrej@1662:             projectname = self.CTR.GetProjectName()
andrej@1662:             if self.CTR.ProjectTestModified():
andrej@1662:                 projectname = "~%s~" % projectname
andrej@1662:             self.SetTitle("%s - %s" % (name, projectname))
andrej@1662:         else:
andrej@1662:             self.SetTitle(name)
andrej@1662: 
andrej@1662:     def OnOpenWidgetInspector(self, evt):
andrej@1662:         # Activate the widget inspection tool
andrej@1662:         from wx.lib.inspection import InspectionTool
andrej@1662:         if not InspectionTool().initialized:
andrej@1662:             InspectionTool().Init()
andrej@1662: 
andrej@1662:         # Find a widget to be selected in the tree.  Use either the
andrej@1662:         # one under the cursor, if any, or this frame.
andrej@1662:         wnd = wx.FindWindowAtPointer()
andrej@1662:         if not wnd:
andrej@1662:             wnd = self
andrej@1662:         InspectionTool().Show(wnd, True)
andrej@1662: 
andrej@1662:     def OnLogConsoleFocusChanged(self, event):
andrej@1662:         self.RefreshEditMenu()
andrej@1662:         event.Skip()
andrej@1662: 
andrej@1662:     def OnLogConsoleUpdateUI(self, event):
edouard@3564:         if event.GetUpdated()==wx.stc.STC_UPDATE_SELECTION:
edouard@3564:             self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
andrej@1662:         event.Skip()
andrej@1662: 
andrej@1662:     def OnLogConsoleMarginClick(self, event):
andrej@1662:         line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
andrej@1662:         wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx))
andrej@1662:         event.Skip()
andrej@1662: 
andrej@1662:     def OnLogConsoleModified(self, event):
andrej@1662:         line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
andrej@1662:         line = self.LogConsole.GetLine(line_idx)
andrej@1662:         if line:
andrej@1662:             result = MATIEC_ERROR_MODEL.match(line)
andrej@1662:             if result is not None:
andrej@1662:                 self.LogConsole.MarkerAdd(line_idx, 0)
andrej@1662:         event.Skip()
andrej@1662: 
andrej@1662:     def SearchLineForError(self, line):
andrej@1662:         if self.CTR is not None:
andrej@1662:             result = MATIEC_ERROR_MODEL.match(line)
andrej@1662:             if result is not None:
andrej@1847:                 first_line, first_column, last_line, last_column, _error = result.groups()
andrej@1846:                 self.CTR.ShowError(self.Log,
andrej@1846:                                    (int(first_line), int(first_column)),
andrej@1846:                                    (int(last_line),  int(last_column)))
andrej@1662: 
andrej@1662:     def CheckSaveBeforeClosing(self, title=_("Close Project")):
andrej@1781:         """Function displaying an Error dialog in PLCOpenEditor.
andrej@1781: 
andrej@1781:         :returns: False if closing cancelled.
andrej@1781:         """
andrej@1662:         if self.CTR.ProjectTestModified():
andrej@1662:             dialog = wx.MessageDialog(self,
andrej@1662:                                       _("There are changes, do you want to save?"),
andrej@1662:                                       title,
andrej@1745:                                       wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION)
andrej@1662:             answer = dialog.ShowModal()
andrej@1662:             dialog.Destroy()
andrej@1662:             if answer == wx.ID_YES:
andrej@1662:                 self.CTR.SaveProject()
andrej@1662:             elif answer == wx.ID_CANCEL:
andrej@1662:                 return False
andrej@1662: 
kinsamanka@3750:         for idx in range(self.TabsOpened.GetPageCount()):
andrej@1662:             window = self.TabsOpened.GetPage(idx)
andrej@1662:             if not window.CheckSaveBeforeClosing():
andrej@1662:                 return False
andrej@1662: 
andrej@1662:         return True
andrej@1662: 
andrej@1662:     def GetTabInfos(self, tab):
andrej@1878:         if isinstance(tab, EditorPanel) and \
andrej@1878:            not isinstance(tab, (Viewer,
andrej@1878:                                 TextViewer,
andrej@1878:                                 ResourceEditor,
andrej@1878:                                 ConfigurationEditor,
andrej@1878:                                 DataTypeEditor)):
andrej@1662:             return ("confnode", tab.Controler.CTNFullName(), tab.GetTagName())
andrej@1662:         elif (isinstance(tab, TextViewer) and
andrej@1662:               (tab.Controler is None or isinstance(tab.Controler, MiniTextControler))):
andrej@1662:             return ("confnode", None, tab.GetInstancePath())
andrej@1662:         else:
andrej@1662:             return IDEFrame.GetTabInfos(self, tab)
andrej@1662: 
andrej@1662:     def LoadTab(self, notebook, page_infos):
andrej@1662:         if page_infos[0] == "confnode":
andrej@1662:             if page_infos[1] is None:
andrej@1662:                 confnode = self.CTR
andrej@1662:             else:
andrej@1662:                 confnode = self.CTR.GetChildByName(page_infos[1])
andrej@1662:             return notebook.GetPageIndex(confnode._OpenView(*page_infos[2:]))
andrej@1662:         else:
andrej@1662:             return IDEFrame.LoadTab(self, notebook, page_infos)
andrej@1662: 
andrej@1662:     # Strange hack required by WAMP connector, using twisted.
andrej@1662:     # Twisted reactor needs to be stopped only before quit,
andrej@1662:     # since it cannot be restarted
andrej@1662:     ToDoBeforeQuit = []
andrej@1751: 
andrej@1662:     def AddToDoBeforeQuit(self, Thing):
andrej@1662:         self.ToDoBeforeQuit.append(Thing)
andrej@1662: 
Edouard@2483:     def TryCloseFrame(self):
andrej@1662:         if self.CTR is None or self.CheckSaveBeforeClosing(_("Close Application")):
andrej@1662:             if self.CTR is not None:
andrej@1662:                 self.CTR.KillDebugThread()
edouard@3348:                 self.CTR._Disconnect()
andrej@1662:             self.KillLocalRuntime()
andrej@1662: 
andrej@1662:             self.SaveLastState()
andrej@1662: 
andrej@1739:             for Thing in self.ToDoBeforeQuit:
andrej@1662:                 Thing()
andrej@1662:             self.ToDoBeforeQuit = []
andrej@1662: 
Edouard@2483:             return True
Edouard@2483:         return False
Edouard@2483: 
Edouard@2483:     def OnCloseFrame(self, event):
Edouard@2483:         if self.TryCloseFrame():
edouard@2624:             self.LogConsole.Disconnect(-1, -1, wx.wxEVT_KILL_FOCUS)
andrej@1662:             event.Skip()
andrej@1662:         else:
Edouard@2483:             # prevent event to continue, i.e. cancel closing
andrej@1662:             event.Veto()
andrej@1662: 
edouard@3712:     def signalTERM_handler(self, sig, frame):
edouard@3712:         print ("Signal TERM caught: kill local runtime and quit, no save")
edouard@3712:         self.KillLocalRuntime()
edouard@3712:         sys.exit()
edouard@3712: 
andrej@1662:     def RefreshFileMenu(self):
andrej@1662:         self.RefreshRecentProjectsMenu()
andrej@1662: 
andrej@1662:         MenuToolBar = self.Panes["MenuToolBar"]
andrej@1662:         if self.CTR is not None:
andrej@1662:             selected = self.TabsOpened.GetSelection()
andrej@1662:             if selected >= 0:
andrej@1662:                 window = self.TabsOpened.GetPage(selected)
andrej@1662:                 viewer_is_modified = window.IsModified()
andrej@1662:                 is_viewer = isinstance(window, Viewer)
andrej@1662:             else:
andrej@1662:                 viewer_is_modified = is_viewer = False
andrej@1662:             if self.TabsOpened.GetPageCount() > 0:
andrej@1662:                 self.FileMenu.Enable(wx.ID_CLOSE, True)
andrej@1662:                 if is_viewer:
andrej@1662:                     self.FileMenu.Enable(wx.ID_PREVIEW, True)
andrej@1662:                     self.FileMenu.Enable(wx.ID_PRINT, True)
andrej@1662:                     MenuToolBar.EnableTool(wx.ID_PRINT, True)
andrej@1662:                 else:
andrej@1662:                     self.FileMenu.Enable(wx.ID_PREVIEW, False)
andrej@1662:                     self.FileMenu.Enable(wx.ID_PRINT, False)
andrej@1662:                     MenuToolBar.EnableTool(wx.ID_PRINT, False)
andrej@1662:             else:
andrej@1662:                 self.FileMenu.Enable(wx.ID_CLOSE, False)
andrej@1662:                 self.FileMenu.Enable(wx.ID_PREVIEW, False)
andrej@1662:                 self.FileMenu.Enable(wx.ID_PRINT, False)
andrej@1662:                 MenuToolBar.EnableTool(wx.ID_PRINT, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
andrej@1662:             project_modified = self.CTR.ProjectTestModified() or viewer_is_modified
andrej@1662:             self.FileMenu.Enable(wx.ID_SAVE, project_modified)
andrej@1662:             MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
andrej@1662:             self.FileMenu.Enable(wx.ID_SAVEAS, True)
andrej@1662:             MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
andrej@1662:             self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
andrej@1662:         else:
andrej@1662:             self.FileMenu.Enable(wx.ID_CLOSE, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_PREVIEW, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_PRINT, False)
andrej@1662:             MenuToolBar.EnableTool(wx.ID_PRINT, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_SAVE, False)
andrej@1662:             MenuToolBar.EnableTool(wx.ID_SAVE, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_SAVEAS, False)
andrej@1662:             MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
andrej@1662:             self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
andrej@1662: 
andrej@1662:     def RefreshRecentProjectsMenu(self):
andrej@1662:         try:
kinsamanka@3750:             recent_projects = list(map(DecodeFileSystemPath,
kinsamanka@3750:                                   self.GetConfigEntry("RecentProjects", [])))
andrej@1780:         except Exception:
andrej@1662:             recent_projects = []
andrej@1662: 
Edouard@2571:         while self.RecentProjectsMenu.GetMenuItemCount() > 0:
andrej@1662:             item = self.RecentProjectsMenu.FindItemByPosition(0)
edouard@3303:             self.RecentProjectsMenu.Remove(item)
andrej@1662: 
edouard@3833:         self.RecentProjectsMenuItem.Enable(len(recent_projects) > 0)
andrej@1662:         for idx, projectpath in enumerate(recent_projects):
kinsamanka@3750:             text = '&%d: %s' % (idx + 1, projectpath)
andrej@1662: 
Edouard@2571:             item = self.RecentProjectsMenu.Append(wx.ID_ANY, text, '')
Edouard@2569:             self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), item)
andrej@1662: 
andrej@1662:     def GenerateOpenRecentProjectFunction(self, projectpath):
andrej@1662:         def OpenRecentProject(event):
andrej@1662:             if self.CTR is not None and not self.CheckSaveBeforeClosing():
andrej@1662:                 return
andrej@1662: 
andrej@1662:             self.OpenProject(projectpath)
andrej@1662:         return OpenRecentProject
andrej@1662: 
andrej@1662:     def GenerateMenuRecursive(self, items, menu):
andrej@1662:         for kind, infos in items:
andrej@2450:             if isinstance(kind, list):
andrej@1662:                 text, id = infos
andrej@1662:                 submenu = wx.Menu('')
andrej@1662:                 self.GenerateMenuRecursive(kind, submenu)
andrej@1662:                 menu.AppendMenu(id, text, submenu)
andrej@1662:             elif kind == wx.ITEM_SEPARATOR:
andrej@1662:                 menu.AppendSeparator()
andrej@1662:             else:
andrej@1847:                 text, id, _help, callback = infos
andrej@1662:                 AppendMenu(menu, help='', id=id, kind=kind, text=text)
andrej@1662:                 if callback is not None:
andrej@1662:                     self.Bind(wx.EVT_MENU, callback, id=id)
andrej@1662: 
andrej@1662:     def RefreshEditorToolBar(self):
andrej@1662:         IDEFrame.RefreshEditorToolBar(self)
andrej@1662:         self.AUIManager.GetPane("EditorToolBar").Position(2)
andrej@1662:         self.AUIManager.GetPane("StatusToolBar").Position(1)
andrej@1662:         self.AUIManager.Update()
andrej@1662: 
andrej@1662:     def RefreshStatusToolBar(self):
andrej@1662:         StatusToolBar = self.Panes["StatusToolBar"]
andrej@1662:         StatusToolBar.ClearTools()
andrej@2255:         StatusToolBar.SetMinSize(StatusToolBar.GetToolBitmapSize())
andrej@1662: 
andrej@1662:         if self.CTR is not None:
andrej@1662: 
andrej@1662:             for confnode_method in self.CTR.StatusMethods:
andrej@1740:                 if "method" in confnode_method and confnode_method.get("shown", True):
edouard@3303:                     tool = StatusToolBar.AddTool(
edouard@3588:                         wx.ID_ANY, confnode_method["name"],
edouard@3588:                         GetBitmap(confnode_method.get("bitmap", "Unknown")),
edouard@3588:                         confnode_method["tooltip"])
Edouard@2566:                     self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), tool)
andrej@1662: 
andrej@1662:             StatusToolBar.Realize()
andrej@1662:             self.AUIManager.GetPane("StatusToolBar").BestSize(StatusToolBar.GetBestSize()).Show()
andrej@1662:         else:
andrej@1662:             self.AUIManager.GetPane("StatusToolBar").Hide()
andrej@1662:         self.AUIManager.GetPane("EditorToolBar").Position(2)
andrej@1662:         self.AUIManager.GetPane("StatusToolBar").Position(1)
andrej@1662:         self.AUIManager.Update()
andrej@1662: 
andrej@1662:     def RefreshEditMenu(self):
andrej@1662:         IDEFrame.RefreshEditMenu(self)
andrej@1662:         if self.FindFocus() == self.LogConsole:
andrej@1662:             self.EditMenu.Enable(wx.ID_COPY, True)
andrej@1662:             self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True)
andrej@1662: 
andrej@1662:         if self.CTR is not None:
andrej@1662:             selected = self.TabsOpened.GetSelection()
andrej@1662:             if selected >= 0:
andrej@1662:                 panel = self.TabsOpened.GetPage(selected)
andrej@1662:             else:
andrej@1662:                 panel = None
andrej@1662:             if panel != self.LastPanelSelected:
kinsamanka@3750:                 for i in range(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
andrej@1662:                     item = self.EditMenu.FindItemByPosition(self.EditMenuSize)
andrej@1662:                     if item is not None:
andrej@1662:                         if item.IsSeparator():
andrej@1662:                             self.EditMenu.RemoveItem(item)
andrej@1662:                         else:
andrej@1662:                             self.EditMenu.Delete(item.GetId())
andrej@1662:                 self.LastPanelSelected = panel
andrej@1662:                 if panel is not None:
andrej@1662:                     items = panel.GetConfNodeMenuItems()
andrej@1662:                 else:
andrej@1662:                     items = []
andrej@1662:                 if len(items) > 0:
andrej@1662:                     self.EditMenu.AppendSeparator()
andrej@1662:                     self.GenerateMenuRecursive(items, self.EditMenu)
andrej@1662:             if panel is not None:
andrej@1662:                 panel.RefreshConfNodeMenu(self.EditMenu)
andrej@1662:         else:
kinsamanka@3750:             for i in range(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
andrej@1662:                 item = self.EditMenu.FindItemByPosition(i)
andrej@1662:                 if item is not None:
andrej@1662:                     if item.IsSeparator():
andrej@1662:                         self.EditMenu.RemoveItem(item)
andrej@1662:                     else:
andrej@1662:                         self.EditMenu.Delete(item.GetId())
andrej@1662:             self.LastPanelSelected = None
edouard@3303:         self.MenuBar.Refresh()
andrej@1662: 
andrej@1662:     def RefreshAll(self):
andrej@1662:         self.RefreshStatusToolBar()
andrej@1662: 
andrej@1662:     def GetMenuCallBackFunction(self, method):
andrej@1662:         """ Generate the callbackfunc for a given CTR method"""
andrej@1662:         def OnMenu(event):
andrej@1662:             # Disable button to prevent re-entrant call
andrej@1662:             event.GetEventObject().Disable()
andrej@1662:             # Call
andrej@1662:             getattr(self.CTR, method)()
andrej@1662:             # Re-enable button
andrej@1662:             event.GetEventObject().Enable()
andrej@1662:         return OnMenu
andrej@1662: 
andrej@1662:     def GetConfigEntry(self, entry_name, default):
kinsamanka@3758:         return pickle.loads(self.Config.Read(entry_name,
kinsamanka@3758:                                              pickle.dumps(default,
kinsamanka@3758:                                                           0).decode()).encode())
andrej@1662: 
andrej@1662:     def ResetConnectionStatusBar(self):
kinsamanka@3750:         for field in range(self.ConnectionStatusBar.GetFieldsCount()):
andrej@1662:             self.ConnectionStatusBar.SetStatusText('', field)
andrej@1662: 
andrej@1662:     def ResetView(self):
andrej@1662:         IDEFrame.ResetView(self)
andrej@1662:         if self.CTR is not None:
andrej@1662:             self.CTR.CloseProject()
andrej@1662:         self.CTR = None
andrej@1662:         self.Log.flush()
andrej@1662:         if self.EnableDebug:
andrej@1662:             self.DebugVariablePanel.SetDataProducer(None)
andrej@1662:             self.ResetConnectionStatusBar()
andrej@1662: 
andrej@1662:     def RefreshConfigRecentProjects(self, projectpath, err=False):
andrej@1662:         try:
kinsamanka@3750:             recent_projects = list(map(DecodeFileSystemPath,
kinsamanka@3750:                                   self.GetConfigEntry("RecentProjects", [])))
andrej@1780:         except Exception:
andrej@1662:             recent_projects = []
andrej@1662:         if projectpath in recent_projects:
andrej@1662:             recent_projects.remove(projectpath)
andrej@1662:         if not err:
andrej@1662:             recent_projects.insert(0, projectpath)
kinsamanka@3758:         self.Config.Write("RecentProjects",
kinsamanka@3758:                           pickle.dumps(
kinsamanka@3758:                               list(map(EncodeFileSystemPath,
kinsamanka@3758:                                        recent_projects[:MAX_RECENT_PROJECTS])),
kinsamanka@3758:                               0))
andrej@1662:         self.Config.Flush()
andrej@1662: 
andrej@1662:     def ResetPerspective(self):
andrej@1662:         IDEFrame.ResetPerspective(self)
andrej@1662:         self.RefreshStatusToolBar()
andrej@1662: 
andrej@1662:     def OnNewProjectMenu(self, event):
andrej@1662:         if self.CTR is not None and not self.CheckSaveBeforeClosing():
andrej@1662:             return
andrej@1662: 
andrej@1662:         try:
kinsamanka@3755:             defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder").encode())
andrej@1780:         except Exception:
andrej@1662:             defaultpath = os.path.expanduser("~")
andrej@1662: 
andrej@1739:         dialog = wx.DirDialog(self, _("Choose an empty directory for new project"), defaultpath)
andrej@1662:         if dialog.ShowModal() == wx.ID_OK:
andrej@1662:             projectpath = dialog.GetPath()
andrej@1662:             self.Config.Write("lastopenedfolder",
andrej@1662:                               EncodeFileSystemPath(os.path.dirname(projectpath)))
andrej@1662:             self.Config.Flush()
andrej@1662:             self.ResetView()
andrej@1662:             ctr = ProjectController(self, self.Log)
andrej@1662:             result = ctr.NewProject(projectpath)
andrej@1662:             if not result:
andrej@1662:                 self.CTR = ctr
andrej@1662:                 self.Controler = self.CTR
andrej@1662:                 self.LibraryPanel.SetController(self.Controler)
andrej@1662:                 self.ProjectTree.Enable(True)
andrej@1662:                 self.PouInstanceVariablesPanel.SetController(self.Controler)
andrej@1662:                 self.RefreshConfigRecentProjects(projectpath)
andrej@1662:                 if self.EnableDebug:
andrej@1662:                     self.DebugVariablePanel.SetDataProducer(self.CTR)
Edouard@1933:                 self.RefreshAfterLoad()
surkovsv93@1708:                 IDEFrame.OnAddNewProject(self, event)
andrej@1662:             else:
andrej@1662:                 self.ResetView()
andrej@1662:                 self.ShowErrorMessage(result)
andrej@1662:             self.RefreshAll()
andrej@1662:             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
andrej@1662:         dialog.Destroy()
andrej@1662: 
andrej@1662:     def OnOpenProjectMenu(self, event):
andrej@1662:         if self.CTR is not None and not self.CheckSaveBeforeClosing():
andrej@1662:             return
andrej@1662: 
andrej@1662:         try:
andrej@1662:             defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
andrej@1780:         except Exception:
andrej@1662:             defaultpath = os.path.expanduser("~")
andrej@1662: 
andrej@1767:         dialog = wx.DirDialog(self, _("Choose a project"), defaultpath,
andrej@1767:                               style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
andrej@1662:         if dialog.ShowModal() == wx.ID_OK:
andrej@1662:             self.OpenProject(dialog.GetPath())
andrej@1662:         dialog.Destroy()
andrej@1662: 
andrej@1662:     def OpenProject(self, projectpath):
andrej@1662:         if os.path.isdir(projectpath):
andrej@1662:             self.Config.Write("lastopenedfolder",
andrej@1662:                               EncodeFileSystemPath(os.path.dirname(projectpath)))
andrej@1662:             self.Config.Flush()
andrej@1662:             self.ResetView()
andrej@1662:             self.CTR = ProjectController(self, self.Log)
andrej@1662:             self.Controler = self.CTR
andrej@1662:             result, err = self.CTR.LoadProject(projectpath)
andrej@1662:             if not result:
andrej@1662:                 self.LibraryPanel.SetController(self.Controler)
andrej@1662:                 self.ProjectTree.Enable(True)
andrej@1662:                 self.PouInstanceVariablesPanel.SetController(self.Controler)
andrej@1662:                 if self.EnableDebug:
andrej@1662:                     self.DebugVariablePanel.SetDataProducer(self.CTR)
Edouard@1933:                 self.RefreshAfterLoad()
andrej@1662:             else:
andrej@1662:                 self.ResetView()
andrej@1662:                 self.ShowErrorMessage(result)
andrej@1662:             self.RefreshAll()
andrej@1662:             self.SearchResultPanel.ResetSearchResults()
andrej@1662:         else:
andrej@1662:             self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath)
andrej@1662:             err = True
andrej@1662:         self.RefreshConfigRecentProjects(projectpath, err)
andrej@1662:         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
andrej@1662: 
andrej@1662:     def OnCloseProjectMenu(self, event):
andrej@1662:         if self.CTR is not None and not self.CheckSaveBeforeClosing():
andrej@1662:             return
andrej@1662: 
andrej@1662:         self.ResetView()
andrej@1662:         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
andrej@1662:         self.RefreshAll()
andrej@1662: 
Edouard@1933:     def RefreshAfterLoad(self):
Edouard@1933:         self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
Edouard@1933: 
Edouard@1927:     def RefreshAfterSave(self):
Edouard@1927:         self.RefreshAll()
Edouard@1927:         self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
Edouard@1927: 
andrej@1662:     def OnSaveProjectMenu(self, event):
andrej@1662:         selected = self.TabsOpened.GetSelection()
andrej@1662:         if selected != -1:
andrej@1662:             window = self.TabsOpened.GetPage(selected)
andrej@1662:             window.Save()
andrej@1662:         if self.CTR is not None:
andrej@1662:             self.CTR.SaveProject()
Edouard@1927:             self.RefreshAfterSave()
andrej@1662: 
andrej@1662:     def OnSaveProjectAsMenu(self, event):
andrej@1662:         selected = self.TabsOpened.GetSelection()
andrej@1662:         if selected != -1:
andrej@1662:             window = self.TabsOpened.GetPage(selected)
andrej@1662:             window.SaveAs()
andrej@1662:         if self.CTR is not None:
andrej@1662:             self.CTR.SaveProjectAs()
Edouard@1927:             self.RefreshAfterSave()
andrej@1662:             self.RefreshConfigRecentProjects(self.CTR.ProjectPath)
andrej@1662: 
andrej@1662:     def OnQuitMenu(self, event):
andrej@1662:         self.Close()
andrej@1662: 
andrej@1662:     def OnAboutMenu(self, event):
edouard@3570:         info = wx.adv.AboutDialogInfo()
edouard@3570:         info = version.GetAboutDialogInfo(info)
edouard@3570:         info.Name = "Beremiz"
edouard@3909:         try:
edouard@3909:             with open(ThirdPartyPath("revisions.txt")) as f:
edouard@3909:                 revisions=f.read()
edouard@3909:         except Exception as e:
edouard@3909:             revisions="Can't load revisions.txt: "+str(e)
edouard@3909:         finally:
edouard@3906:             info.SetVersion(info.GetVersion(), longVersion=revisions)
edouard@3906: 
edouard@3570:         info.Description = _("Open Source framework for automation, "
edouard@3586:             "implementing IEC 61131 IDE with constantly growing set of extensions "
edouard@3570:             "and flexible PLC runtime.")
edouard@3570: 
edouard@3570:         info.Icon = wx.Icon(Bpath("images", "about_brz_logo.png"), wx.BITMAP_TYPE_PNG)
edouard@3570: 
andrej@1662:         ShowAboutDialog(self, info)
andrej@1662: 
andrej@1662:     def OnProjectTreeItemBeginEdit(self, event):
andrej@1662:         selected = event.GetItem()
kinsamanka@3789:         if self.ProjectTree.GetItemData(selected)["type"] == ITEM_CONFNODE:
andrej@1662:             event.Veto()
andrej@1662:         else:
andrej@1662:             IDEFrame.OnProjectTreeItemBeginEdit(self, event)
andrej@1662: 
andrej@1662:     def OnProjectTreeRightUp(self, event):
andrej@1662:         item = event.GetItem()
kinsamanka@3789:         item_infos = self.ProjectTree.GetItemData(item)
andrej@1662: 
andrej@1662:         if item_infos["type"] == ITEM_CONFNODE:
andrej@1662:             confnode_menu = wx.Menu(title='')
andrej@1662: 
andrej@1662:             confnode = item_infos["confnode"]
andrej@1662:             if confnode is not None:
andrej@1662:                 menu_items = confnode.GetContextualMenuItems()
andrej@1662:                 if menu_items is not None:
Edouard@2569:                     for text, helpstr, callback in menu_items:
Edouard@2569:                         item = confnode_menu.Append(wx.ID_ANY, text, helpstr)
Edouard@2569:                         self.Bind(wx.EVT_MENU, callback, item)
andrej@1662:                 else:
Edouard@2569:                     for name, XSDClass, helpstr in confnode.CTNChildrenTypes:
surkovsv93@1684:                         if not hasattr(XSDClass, 'CTNMaxCount') or not confnode.Children.get(name) \
surkovsv93@1684:                                 or len(confnode.Children[name]) < XSDClass.CTNMaxCount:
Edouard@2569:                             item = confnode_menu.Append(wx.ID_ANY, _("Add") + " " + name, helpstr)
Edouard@2569:                             self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name, confnode), item)
Edouard@2569:             item = confnode_menu.Append(wx.ID_ANY, _("Delete"))
Edouard@2569:             self.Bind(wx.EVT_MENU, self.GetDeleteMenuFunction(confnode), item)
andrej@1662: 
andrej@1662:             self.PopupMenu(confnode_menu)
andrej@1662:             confnode_menu.Destroy()
andrej@1662: 
andrej@1662:             event.Skip()
andrej@1662:         elif item_infos["type"] == ITEM_RESOURCE:
andrej@1662:             # prevent last resource to be delted
andrej@1662:             parent = self.ProjectTree.GetItemParent(item)
andrej@1662:             parent_name = self.ProjectTree.GetItemText(parent)
andrej@1662:             if parent_name == _("Resources"):
andrej@1662:                 IDEFrame.OnProjectTreeRightUp(self, event)
andrej@1662:         else:
andrej@1662:             IDEFrame.OnProjectTreeRightUp(self, event)
andrej@1662: 
andrej@1662:     def OnProjectTreeItemActivated(self, event):
andrej@1662:         selected = event.GetItem()
kinsamanka@3789:         item_infos = self.ProjectTree.GetItemData(selected)
andrej@1662:         if item_infos["type"] == ITEM_CONFNODE:
andrej@1662:             item_infos["confnode"]._OpenView()
andrej@1662:             event.Skip()
andrej@1662:         elif item_infos["type"] == ITEM_PROJECT:
andrej@1662:             self.CTR._OpenView()
andrej@1662:         else:
andrej@1662:             IDEFrame.OnProjectTreeItemActivated(self, event)
andrej@1662: 
andrej@1662:     def ProjectTreeItemSelect(self, select_item):
andrej@1662:         if select_item is not None and select_item.IsOk():
kinsamanka@3789:             item_infos = self.ProjectTree.GetItemData(select_item)
andrej@1662:             if item_infos["type"] == ITEM_CONFNODE:
andrej@1662:                 item_infos["confnode"]._OpenView(onlyopened=True)
andrej@1662:             elif item_infos["type"] == ITEM_PROJECT:
andrej@1662:                 self.CTR._OpenView(onlyopened=True)
andrej@1662:             else:
andrej@1662:                 IDEFrame.ProjectTreeItemSelect(self, select_item)
andrej@1662: 
Edouard@2524:     def GetProjectElementWindow(self, element, tagname):
Edouard@2551:         is_a_CTN_tagname = len(tagname.split("::")) == 1
Edouard@2524:         if is_a_CTN_tagname:
Edouard@2524:             confnode = self.CTR.GetChildByName(tagname)
Edouard@2551:             return confnode.GetView()
Edouard@2551:         else:
Edouard@2524:             return IDEFrame.GetProjectElementWindow(self, element, tagname)
Edouard@2524: 
andrej@1662:     def SelectProjectTreeItem(self, tagname):
andrej@1662:         if self.ProjectTree is not None:
andrej@1662:             root = self.ProjectTree.GetRootItem()
andrej@1662:             if root.IsOk():
andrej@1662:                 words = tagname.split("::")
andrej@1662:                 if len(words) == 1:
andrej@1662:                     if tagname == "Project":
andrej@1662:                         self.SelectedItem = root
andrej@1662:                         self.ProjectTree.SelectItem(root)
andrej@1662:                         self.ResetSelectedItem()
andrej@1662:                     else:
andrej@1768:                         return self.RecursiveProjectTreeItemSelection(
andrej@1768:                             root,
andrej@1768:                             [(word, ITEM_CONFNODE) for word in tagname.split(".")])
andrej@1662:                 elif words[0] == "R":
andrej@1662:                     return self.RecursiveProjectTreeItemSelection(root, [(words[2], ITEM_RESOURCE)])
andrej@1662:                 elif not os.path.exists(words[0]):
andrej@1662:                     IDEFrame.SelectProjectTreeItem(self, tagname)
andrej@1662: 
andrej@1662:     def GetAddConfNodeFunction(self, name, confnode=None):
andrej@1662:         def AddConfNodeMenuFunction(event):
andrej@1662:             wx.CallAfter(self.AddConfNode, name, confnode)
andrej@1662:         return AddConfNodeMenuFunction
andrej@1662: 
andrej@1662:     def GetDeleteMenuFunction(self, confnode):
andrej@1662:         def DeleteMenuFunction(event):
andrej@1662:             wx.CallAfter(self.DeleteConfNode, confnode)
andrej@1662:         return DeleteMenuFunction
andrej@1662: 
andrej@1662:     def AddConfNode(self, ConfNodeType, confnode=None):
andrej@1662:         if self.CTR.CheckProjectPathPerm():
andrej@1662:             ConfNodeName = "%s_0" % ConfNodeType
andrej@1662:             if confnode is not None:
andrej@1662:                 confnode.CTNAddChild(ConfNodeName, ConfNodeType)
andrej@1662:             else:
andrej@1662:                 self.CTR.CTNAddChild(ConfNodeName, ConfNodeType)
andrej@1662:             self._Refresh(TITLE, FILEMENU, PROJECTTREE)
andrej@1662: 
andrej@1662:     def DeleteConfNode(self, confnode):
andrej@1662:         if self.CTR.CheckProjectPathPerm():
andrej@1768:             dialog = wx.MessageDialog(
andrej@1768:                 self,
andrej@1662:                 _("Really delete node '%s'?") % confnode.CTNName(),
andrej@1662:                 _("Remove %s node") % confnode.CTNType,
andrej@1745:                 wx.YES_NO | wx.NO_DEFAULT)
andrej@1662:             if dialog.ShowModal() == wx.ID_YES:
andrej@1662:                 confnode.CTNRemove()
andrej@1662:                 del confnode
andrej@1662:                 self._Refresh(TITLE, FILEMENU, PROJECTTREE)
andrej@1662:             dialog.Destroy()
andrej@1662: 
andrej@1782: # -------------------------------------------------------------------------------
andrej@1662: #                        Highlights showing functions
andrej@1782: # -------------------------------------------------------------------------------
andrej@1662: 
andrej@1662:     def ShowHighlight(self, infos, start, end, highlight_type):
andrej@1662:         config_name = self.Controler.GetProjectMainConfigurationName()
Edouard@1948:         if config_name is not None and infos[0] == ComputeConfigurationName(config_name):
andrej@1662:             self.CTR._OpenView()
andrej@1662:             selected = self.TabsOpened.GetSelection()
andrej@1662:             if selected != -1:
andrej@1662:                 viewer = self.TabsOpened.GetPage(selected)
andrej@1662:                 viewer.AddHighlight(infos[1:], start, end, highlight_type)
andrej@1662:         else:
andrej@1662:             IDEFrame.ShowHighlight(self, infos, start, end, highlight_type)