BeremizIDE.py
changeset 1662 8816f7316d9c
child 1680 6db967480b7d
equal deleted inserted replaced
1661:78f28f40bc10 1662:8816f7316d9c
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # This file is part of Beremiz, a Integrated Development Environment for
       
     5 # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
       
     6 #
       
     7 # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 # Copyright (C) 2016: Andrey Skvortsov <andrej.skvortzov@gmail.com>
       
     9 #
       
    10 # See COPYING file for copyrights details.
       
    11 #
       
    12 # This program is free software; you can redistribute it and/or
       
    13 # modify it under the terms of the GNU General Public License
       
    14 # as published by the Free Software Foundation; either version 2
       
    15 # of the License, or (at your option) any later version.
       
    16 #
       
    17 # This program is distributed in the hope that it will be useful,
       
    18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    20 # GNU General Public License for more details.
       
    21 #
       
    22 # You should have received a copy of the GNU General Public License
       
    23 # along with this program; if not, write to the Free Software
       
    24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
       
    25 
       
    26 
       
    27 import os, sys
       
    28 import tempfile
       
    29 import shutil
       
    30 import random
       
    31 import time
       
    32 import version
       
    33 from types import ListType
       
    34 
       
    35 beremiz_dir = os.path.dirname(os.path.realpath(__file__))
       
    36 
       
    37 def Bpath(*args):
       
    38     return os.path.join(beremiz_dir,*args)
       
    39 
       
    40 
       
    41 
       
    42 import wx.lib.buttons, wx.lib.statbmp, wx.stc
       
    43 import cPickle
       
    44 import types, time, re, platform, time, traceback, commands
       
    45 
       
    46 from docutil import OpenHtmlFrame
       
    47 from editors.EditorPanel import EditorPanel
       
    48 from editors.Viewer import Viewer
       
    49 from editors.TextViewer import TextViewer
       
    50 from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
       
    51 from editors.DataTypeEditor import DataTypeEditor
       
    52 from util.MiniTextControler import MiniTextControler
       
    53 from util.ProcessLogger import ProcessLogger
       
    54 from controls.LogViewer import LogViewer
       
    55 from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
       
    56 from controls import EnhancedStatusBar as esb
       
    57 from dialogs.AboutDialog import ShowAboutDialog
       
    58 
       
    59 from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
       
    60 from ProjectController import ProjectController, GetAddMenuItems, MATIEC_ERROR_MODEL, ITEM_CONFNODE
       
    61 
       
    62 
       
    63 MAX_RECENT_PROJECTS = 9
       
    64 
       
    65 if wx.Platform == '__WXMSW__':
       
    66     faces = {
       
    67         'mono' : 'Courier New',
       
    68         'size' : 8,
       
    69     }
       
    70 else:
       
    71     faces = {
       
    72         'mono' : 'Courier',
       
    73         'size' : 10,
       
    74     }
       
    75 
       
    76 from threading import Lock,Timer,currentThread
       
    77 MainThread = currentThread().ident
       
    78 REFRESH_PERIOD = 0.1
       
    79 from time import time as gettime
       
    80 class LogPseudoFile:
       
    81     """ Base class for file like objects to facilitate StdOut for the Shell."""
       
    82     def __init__(self, output, risecall):
       
    83         self.red_white = 1
       
    84         self.red_yellow = 2
       
    85         self.black_white = wx.stc.STC_STYLE_DEFAULT
       
    86         self.output = output
       
    87         self.risecall = risecall
       
    88         # to prevent rapid fire on rising log panel
       
    89         self.rising_timer = 0
       
    90         self.lock = Lock()
       
    91         self.YieldLock = Lock()
       
    92         self.RefreshLock = Lock()
       
    93         self.TimerAccessLock = Lock()
       
    94         self.stack = []
       
    95         self.LastRefreshTime = gettime()
       
    96         self.LastRefreshTimer = None
       
    97 
       
    98     def write(self, s, style = None):
       
    99         if self.lock.acquire():
       
   100             self.stack.append((s,style))
       
   101             self.lock.release()
       
   102             current_time = gettime()
       
   103             self.TimerAccessLock.acquire()
       
   104             if self.LastRefreshTimer:
       
   105                 self.LastRefreshTimer.cancel()
       
   106                 self.LastRefreshTimer=None
       
   107             self.TimerAccessLock.release()
       
   108             if current_time - self.LastRefreshTime > REFRESH_PERIOD and self.RefreshLock.acquire(False):
       
   109                 self._should_write()
       
   110             else:
       
   111                 self.TimerAccessLock.acquire()
       
   112                 self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired)
       
   113                 self.LastRefreshTimer.start()
       
   114                 self.TimerAccessLock.release()
       
   115 
       
   116     def _timer_expired(self):
       
   117         if self.RefreshLock.acquire(False):
       
   118             self._should_write()
       
   119         else:
       
   120             self.TimerAccessLock.acquire()
       
   121             self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired)
       
   122             self.LastRefreshTimer.start()
       
   123             self.TimerAccessLock.release()
       
   124 
       
   125     def _should_write(self):
       
   126         wx.CallAfter(self._write)
       
   127         if MainThread == currentThread().ident:
       
   128             app = wx.GetApp()
       
   129             if app is not None:
       
   130                 if self.YieldLock.acquire(0):
       
   131                     app.Yield()
       
   132                     self.YieldLock.release()
       
   133 
       
   134     def _write(self):
       
   135         if self.output :
       
   136             self.output.Freeze()
       
   137             self.lock.acquire()
       
   138             for s, style in self.stack:
       
   139                 if style is None : style=self.black_white
       
   140                 if style != self.black_white:
       
   141                     self.output.StartStyling(self.output.GetLength(), 0xff)
       
   142 
       
   143                 # Temporary deactivate read only mode on StyledTextCtrl for
       
   144                 # adding text. It seems that text modifications, even
       
   145                 # programmatically, are disabled in StyledTextCtrl when read
       
   146                 # only is active
       
   147                 start_pos = self.output.GetLength()
       
   148                 self.output.SetReadOnly(False)
       
   149                 self.output.AppendText(s)
       
   150                 self.output.SetReadOnly(True)
       
   151                 text_len = self.output.GetLength() - start_pos
       
   152 
       
   153                 if style != self.black_white:
       
   154                     self.output.SetStyling(text_len, style)
       
   155             self.stack = []
       
   156             self.lock.release()
       
   157             self.output.Thaw()
       
   158             self.LastRefreshTime = gettime()
       
   159             try:
       
   160                 self.RefreshLock.release()
       
   161             except:
       
   162                 pass
       
   163             newtime = time.time()
       
   164             if newtime - self.rising_timer > 1:
       
   165                 self.risecall(self.output)
       
   166             self.rising_timer = newtime
       
   167 
       
   168     def write_warning(self, s):
       
   169         self.write(s,self.red_white)
       
   170 
       
   171     def write_error(self, s):
       
   172         self.write(s,self.red_yellow)
       
   173 
       
   174     def writeyield(self, s):
       
   175         self.write(s)
       
   176         wx.GetApp().Yield()
       
   177 
       
   178     def flush(self):
       
   179         # Temporary deactivate read only mode on StyledTextCtrl for clearing
       
   180         # text. It seems that text modifications, even programmatically, are
       
   181         # disabled in StyledTextCtrl when read only is active
       
   182         self.output.SetReadOnly(False)
       
   183         self.output.SetText("")
       
   184         self.output.SetReadOnly(True)
       
   185 
       
   186     def isatty(self):
       
   187         return False
       
   188 
       
   189 ID_FILEMENURECENTPROJECTS = wx.NewId()
       
   190 
       
   191 from IDEFrame import TITLE,\
       
   192                      EDITORTOOLBAR,\
       
   193                      FILEMENU,\
       
   194                      EDITMENU,\
       
   195                      DISPLAYMENU,\
       
   196                      PROJECTTREE,\
       
   197                      POUINSTANCEVARIABLESPANEL,\
       
   198                      LIBRARYTREE,\
       
   199                      SCALING,\
       
   200                      PAGETITLES,\
       
   201                      IDEFrame, AppendMenu,\
       
   202                      EncodeFileSystemPath, DecodeFileSystemPath
       
   203 from util.BitmapLibrary import GetBitmap
       
   204 
       
   205 class Beremiz(IDEFrame):
       
   206 
       
   207     def _init_utils(self):
       
   208         self.ConfNodeMenu = wx.Menu(title='')
       
   209         self.RecentProjectsMenu = wx.Menu(title='')
       
   210 
       
   211         IDEFrame._init_utils(self)
       
   212 
       
   213     def _init_coll_FileMenu_Items(self, parent):
       
   214         AppendMenu(parent, help='', id=wx.ID_NEW,
       
   215               kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
       
   216         AppendMenu(parent, help='', id=wx.ID_OPEN,
       
   217               kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
       
   218         parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
       
   219         parent.AppendSeparator()
       
   220         AppendMenu(parent, help='', id=wx.ID_SAVE,
       
   221               kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
       
   222         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
       
   223               kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S')
       
   224         AppendMenu(parent, help='', id=wx.ID_CLOSE,
       
   225               kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
       
   226         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
       
   227               kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
       
   228         parent.AppendSeparator()
       
   229         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
       
   230               kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
       
   231         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
       
   232               kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
       
   233         AppendMenu(parent, help='', id=wx.ID_PRINT,
       
   234               kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
       
   235         parent.AppendSeparator()
       
   236         AppendMenu(parent, help='', id=wx.ID_EXIT,
       
   237               kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
       
   238 
       
   239         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
       
   240         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
       
   241         self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
       
   242         self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
       
   243         self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
       
   244         self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
       
   245         self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
       
   246         self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
       
   247         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
       
   248         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
       
   249 
       
   250         self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
       
   251                                (wx.ID_OPEN, "open", _(u'Open'), None),
       
   252                                (wx.ID_SAVE, "save", _(u'Save'), None),
       
   253                                (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
       
   254                                (wx.ID_PRINT, "print", _(u'Print'), None)])
       
   255 
       
   256     def _RecursiveAddMenuItems(self, menu, items):
       
   257         for name, text, help, children in items:
       
   258             new_id = wx.NewId()
       
   259             if len(children) > 0:
       
   260                 new_menu = wx.Menu(title='')
       
   261                 menu.AppendMenu(new_id, text, new_menu)
       
   262                 self._RecursiveAddMenuItems(new_menu, children)
       
   263             else:
       
   264                 AppendMenu(menu, help=help, id=new_id,
       
   265                        kind=wx.ITEM_NORMAL, text=text)
       
   266                 self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name),
       
   267                           id=new_id)
       
   268 
       
   269     def _init_coll_AddMenu_Items(self, parent):
       
   270         IDEFrame._init_coll_AddMenu_Items(self, parent, False)
       
   271         self._RecursiveAddMenuItems(parent, GetAddMenuItems())
       
   272 
       
   273     def _init_coll_HelpMenu_Items(self, parent):
       
   274         parent.Append(help='', id=wx.ID_ABOUT,
       
   275               kind=wx.ITEM_NORMAL, text=_(u'About'))
       
   276         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
       
   277 
       
   278     def _init_coll_ConnectionStatusBar_Fields(self, parent):
       
   279         parent.SetFieldsCount(3)
       
   280 
       
   281         parent.SetStatusText(number=0, text='')
       
   282         parent.SetStatusText(number=1, text='')
       
   283         parent.SetStatusText(number=2, text='')
       
   284 
       
   285         parent.SetStatusWidths([-1, 300, 200])
       
   286 
       
   287     def _init_ctrls(self, prnt):
       
   288         IDEFrame._init_ctrls(self, prnt)
       
   289 
       
   290         self.EditMenuSize = self.EditMenu.GetMenuItemCount()
       
   291 
       
   292         inspectorID = wx.NewId()
       
   293         self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID)
       
   294         accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL|wx.ACCEL_ALT, ord('I'), inspectorID)]
       
   295 
       
   296         keyID = wx.NewId()
       
   297         self.Bind(wx.EVT_MENU, self.SwitchFullScrMode, id=keyID)
       
   298         accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, wx.WXK_F12, keyID)]
       
   299 
       
   300         for method,shortcut in [("Stop",     wx.WXK_F4),
       
   301                                 ("Run",      wx.WXK_F5),
       
   302                                 ("Transfer", wx.WXK_F6),
       
   303                                 ("Connect",  wx.WXK_F7),
       
   304                                 ("Build",    wx.WXK_F11)]:
       
   305             def OnMethodGen(obj,meth):
       
   306                 def OnMethod(evt):
       
   307                     if obj.CTR is not None:
       
   308                        obj.CTR.CallMethod('_'+meth)
       
   309                     wx.CallAfter(self.RefreshStatusToolBar)
       
   310                 return OnMethod
       
   311             newid = wx.NewId()
       
   312             self.Bind(wx.EVT_MENU, OnMethodGen(self,method), id=newid)
       
   313             accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut,newid)]
       
   314 
       
   315         self.SetAcceleratorTable(wx.AcceleratorTable(accels))
       
   316 
       
   317         self.LogConsole = CustomStyledTextCtrl(
       
   318                   name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
       
   319                   size=wx.Size(0, 0))
       
   320         self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged)
       
   321         self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged)
       
   322         self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI)
       
   323         self.LogConsole.SetReadOnly(True)
       
   324         self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR)
       
   325 
       
   326         # Define Log Console styles
       
   327         self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
       
   328         self.LogConsole.StyleClearAll()
       
   329         self.LogConsole.StyleSetSpec(1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces)
       
   330         self.LogConsole.StyleSetSpec(2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)
       
   331 
       
   332         # Define Log Console markers
       
   333         self.LogConsole.SetMarginSensitive(1, True)
       
   334         self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
       
   335         self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
       
   336 
       
   337         self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
       
   338 
       
   339         self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
       
   340         self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
       
   341 
       
   342         self.MainTabs["LogConsole"] = (self.LogConsole, _("Console"))
       
   343         self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"])
       
   344         #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT)
       
   345 
       
   346         self.LogViewer = LogViewer(self.BottomNoteBook, self)
       
   347         self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log"))
       
   348         self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"])
       
   349         #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)
       
   350 
       
   351         StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
       
   352                 wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
       
   353         StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
       
   354         StatusToolBar.Realize()
       
   355         self.Panes["StatusToolBar"] = StatusToolBar
       
   356         self.AUIManager.AddPane(StatusToolBar, wx.aui.AuiPaneInfo().
       
   357                   Name("StatusToolBar").Caption(_("Status ToolBar")).
       
   358                   ToolbarPane().Top().Position(1).
       
   359                   LeftDockable(False).RightDockable(False))
       
   360 
       
   361         self.AUIManager.Update()
       
   362 
       
   363         self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP)
       
   364         self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
       
   365         self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range = 100)
       
   366         self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2)        
       
   367         self.ProgressStatusBar.Hide()
       
   368         self.SetStatusBar(self.ConnectionStatusBar)
       
   369 
       
   370     def __init_execute_path(self):
       
   371         if os.name == 'nt':
       
   372             # on windows, desktop shortcut launches Beremiz.py
       
   373             # with working dir set to mingw/bin.
       
   374             # then we prefix CWD to PATH in order to ensure that
       
   375             # commands invoked by build process by default are
       
   376             # found here.
       
   377             os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"]
       
   378         
       
   379         
       
   380     def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
       
   381         # Add beremiz's icon in top left corner of the frame
       
   382         self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO)
       
   383         self.__init_execute_path()
       
   384         
       
   385         IDEFrame.__init__(self, parent, debug)
       
   386         self.Log = LogPseudoFile(self.LogConsole,self.SelectTab)
       
   387 
       
   388         self.local_runtime = None
       
   389         self.runtime_port = None
       
   390         self.local_runtime_tmpdir = None
       
   391 
       
   392         self.LastPanelSelected = None
       
   393 
       
   394         # Define Tree item icon list
       
   395         self.LocationImageList = wx.ImageList(16, 16)
       
   396         self.LocationImageDict = {}
       
   397 
       
   398         # Icons for location items
       
   399         for imgname, itemtype in [
       
   400             ("CONFIGURATION", LOCATION_CONFNODE),
       
   401             ("RESOURCE",      LOCATION_MODULE),
       
   402             ("PROGRAM",       LOCATION_GROUP),
       
   403             ("VAR_INPUT",     LOCATION_VAR_INPUT),
       
   404             ("VAR_OUTPUT",    LOCATION_VAR_OUTPUT),
       
   405             ("VAR_LOCAL",     LOCATION_VAR_MEMORY)]:
       
   406             self.LocationImageDict[itemtype] = self.LocationImageList.Add(GetBitmap(imgname))
       
   407 
       
   408         # Icons for other items
       
   409         for imgname, itemtype in [
       
   410             ("Extension", ITEM_CONFNODE)]:
       
   411             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
       
   412 
       
   413         if projectOpen is not None:
       
   414             projectOpen = DecodeFileSystemPath(projectOpen, False)
       
   415 
       
   416         if projectOpen is not None and os.path.isdir(projectOpen):
       
   417             self.CTR = ProjectController(self, self.Log)
       
   418             self.Controler = self.CTR
       
   419             result, err = self.CTR.LoadProject(projectOpen, buildpath)
       
   420             if not result:
       
   421                 self.LibraryPanel.SetController(self.Controler)
       
   422                 self.ProjectTree.Enable(True)
       
   423                 self.PouInstanceVariablesPanel.SetController(self.Controler)
       
   424                 self.RefreshConfigRecentProjects(os.path.abspath(projectOpen))
       
   425                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   426             else:
       
   427                 self.ResetView()
       
   428                 self.ShowErrorMessage(result)
       
   429         else:
       
   430             self.CTR = ctr
       
   431             self.Controler = ctr
       
   432             if ctr is not None:
       
   433                 self.LibraryPanel.SetController(self.Controler)
       
   434                 self.ProjectTree.Enable(True)
       
   435                 self.PouInstanceVariablesPanel.SetController(self.Controler)
       
   436                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   437         if self.EnableDebug:
       
   438             self.DebugVariablePanel.SetDataProducer(self.CTR)
       
   439 
       
   440         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
       
   441 
       
   442         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
       
   443         self.RefreshAll()
       
   444         self.LogConsole.SetFocus()
       
   445 
       
   446     def RefreshTitle(self):
       
   447         name = _("Beremiz")
       
   448         if self.CTR is not None:
       
   449             projectname = self.CTR.GetProjectName()
       
   450             if self.CTR.ProjectTestModified():
       
   451                 projectname = "~%s~" % projectname
       
   452             self.SetTitle("%s - %s" % (name, projectname))
       
   453         else:
       
   454             self.SetTitle(name)
       
   455 
       
   456     def StartLocalRuntime(self, taskbaricon = True):
       
   457         if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
       
   458             # create temporary directory for runtime working directory
       
   459             self.local_runtime_tmpdir = tempfile.mkdtemp()
       
   460             # choose an arbitrary random port for runtime
       
   461             self.runtime_port = int(random.random() * 1000) + 61131
       
   462             # launch local runtime
       
   463             self.local_runtime = ProcessLogger(self.Log,
       
   464                 "\"%s\" \"%s\" -p %s -i localhost %s %s"%(
       
   465                     sys.executable,
       
   466                     Bpath("Beremiz_service.py"),
       
   467                     self.runtime_port,
       
   468                     {False : "-x 0", True :"-x 1"}[taskbaricon],
       
   469                     self.local_runtime_tmpdir),
       
   470                 no_gui=False,
       
   471                 timeout=500, keyword = self.local_runtime_tmpdir,
       
   472                 cwd = self.local_runtime_tmpdir)
       
   473             self.local_runtime.spin()
       
   474         return self.runtime_port
       
   475 
       
   476     def KillLocalRuntime(self):
       
   477         if self.local_runtime is not None:
       
   478             # shutdown local runtime
       
   479             self.local_runtime.kill(gently=False)
       
   480             # clear temp dir
       
   481             shutil.rmtree(self.local_runtime_tmpdir)
       
   482 
       
   483             self.local_runtime = None
       
   484 
       
   485     def OnOpenWidgetInspector(self, evt):
       
   486         # Activate the widget inspection tool
       
   487         from wx.lib.inspection import InspectionTool
       
   488         if not InspectionTool().initialized:
       
   489             InspectionTool().Init()
       
   490 
       
   491         # Find a widget to be selected in the tree.  Use either the
       
   492         # one under the cursor, if any, or this frame.
       
   493         wnd = wx.FindWindowAtPointer()
       
   494         if not wnd:
       
   495             wnd = self
       
   496         InspectionTool().Show(wnd, True)
       
   497 
       
   498     def OnLogConsoleFocusChanged(self, event):
       
   499         self.RefreshEditMenu()
       
   500         event.Skip()
       
   501 
       
   502     def OnLogConsoleUpdateUI(self, event):
       
   503         self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
       
   504         event.Skip()
       
   505 
       
   506     def OnLogConsoleMarginClick(self, event):
       
   507         line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
       
   508         wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx))
       
   509         event.Skip()
       
   510 
       
   511     def OnLogConsoleModified(self, event):
       
   512         line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
       
   513         line = self.LogConsole.GetLine(line_idx)
       
   514         if line:
       
   515             result = MATIEC_ERROR_MODEL.match(line)
       
   516             if result is not None:
       
   517                 self.LogConsole.MarkerAdd(line_idx, 0)
       
   518         event.Skip()
       
   519 
       
   520     def SearchLineForError(self, line):
       
   521         if self.CTR is not None:
       
   522             result = MATIEC_ERROR_MODEL.match(line)
       
   523             if result is not None:
       
   524                 first_line, first_column, last_line, last_column, error = result.groups()
       
   525                 infos = self.CTR.ShowError(self.Log,
       
   526                                                   (int(first_line), int(first_column)),
       
   527                                                   (int(last_line), int(last_column)))
       
   528 
       
   529     ## Function displaying an Error dialog in PLCOpenEditor.
       
   530     #  @return False if closing cancelled.
       
   531     def CheckSaveBeforeClosing(self, title=_("Close Project")):
       
   532         if self.CTR.ProjectTestModified():
       
   533             dialog = wx.MessageDialog(self,
       
   534                                       _("There are changes, do you want to save?"),
       
   535                                       title,
       
   536                                       wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
       
   537             answer = dialog.ShowModal()
       
   538             dialog.Destroy()
       
   539             if answer == wx.ID_YES:
       
   540                 self.CTR.SaveProject()
       
   541             elif answer == wx.ID_CANCEL:
       
   542                 return False
       
   543 
       
   544         for idx in xrange(self.TabsOpened.GetPageCount()):
       
   545             window = self.TabsOpened.GetPage(idx)
       
   546             if not window.CheckSaveBeforeClosing():
       
   547                 return False
       
   548 
       
   549         return True
       
   550 
       
   551     def GetTabInfos(self, tab):
       
   552         if (isinstance(tab, EditorPanel) and
       
   553             not isinstance(tab, (Viewer,
       
   554                                  TextViewer,
       
   555                                  ResourceEditor,
       
   556                                  ConfigurationEditor,
       
   557                                  DataTypeEditor))):
       
   558             return ("confnode", tab.Controler.CTNFullName(), tab.GetTagName())
       
   559         elif (isinstance(tab, TextViewer) and
       
   560               (tab.Controler is None or isinstance(tab.Controler, MiniTextControler))):
       
   561             return ("confnode", None, tab.GetInstancePath())
       
   562         else:
       
   563             return IDEFrame.GetTabInfos(self, tab)
       
   564 
       
   565     def LoadTab(self, notebook, page_infos):
       
   566         if page_infos[0] == "confnode":
       
   567             if page_infos[1] is None:
       
   568                 confnode = self.CTR
       
   569             else:
       
   570                 confnode = self.CTR.GetChildByName(page_infos[1])
       
   571             return notebook.GetPageIndex(confnode._OpenView(*page_infos[2:]))
       
   572         else:
       
   573             return IDEFrame.LoadTab(self, notebook, page_infos)
       
   574 
       
   575     # Strange hack required by WAMP connector, using twisted.
       
   576     # Twisted reactor needs to be stopped only before quit,
       
   577     # since it cannot be restarted
       
   578     ToDoBeforeQuit = []
       
   579     def AddToDoBeforeQuit(self, Thing):
       
   580         self.ToDoBeforeQuit.append(Thing)
       
   581 
       
   582     def OnCloseFrame(self, event):
       
   583         for evt_type in [wx.EVT_SET_FOCUS,
       
   584                          wx.EVT_KILL_FOCUS,
       
   585                          wx.stc.EVT_STC_UPDATEUI]:
       
   586             self.LogConsole.Unbind(evt_type)
       
   587         if self.CTR is None or self.CheckSaveBeforeClosing(_("Close Application")):
       
   588             if self.CTR is not None:
       
   589                 self.CTR.KillDebugThread()
       
   590             self.KillLocalRuntime()
       
   591 
       
   592             self.SaveLastState()
       
   593 
       
   594             for Thing in self.ToDoBeforeQuit :
       
   595                 Thing()
       
   596             self.ToDoBeforeQuit = []
       
   597 
       
   598             event.Skip()
       
   599         else:
       
   600             event.Veto()
       
   601 
       
   602     def RefreshFileMenu(self):
       
   603         self.RefreshRecentProjectsMenu()
       
   604 
       
   605         MenuToolBar = self.Panes["MenuToolBar"]
       
   606         if self.CTR is not None:
       
   607             selected = self.TabsOpened.GetSelection()
       
   608             if selected >= 0:
       
   609                 window = self.TabsOpened.GetPage(selected)
       
   610                 viewer_is_modified = window.IsModified()
       
   611                 is_viewer = isinstance(window, Viewer)
       
   612             else:
       
   613                 viewer_is_modified = is_viewer = False
       
   614             if self.TabsOpened.GetPageCount() > 0:
       
   615                 self.FileMenu.Enable(wx.ID_CLOSE, True)
       
   616                 if is_viewer:
       
   617                     self.FileMenu.Enable(wx.ID_PREVIEW, True)
       
   618                     self.FileMenu.Enable(wx.ID_PRINT, True)
       
   619                     MenuToolBar.EnableTool(wx.ID_PRINT, True)
       
   620                 else:
       
   621                     self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   622                     self.FileMenu.Enable(wx.ID_PRINT, False)
       
   623                     MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   624             else:
       
   625                 self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   626                 self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   627                 self.FileMenu.Enable(wx.ID_PRINT, False)
       
   628                 MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   629             self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
       
   630             project_modified = self.CTR.ProjectTestModified() or viewer_is_modified
       
   631             self.FileMenu.Enable(wx.ID_SAVE, project_modified)
       
   632             MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
       
   633             self.FileMenu.Enable(wx.ID_SAVEAS, True)
       
   634             MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
       
   635             self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
       
   636         else:
       
   637             self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   638             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
       
   639             self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   640             self.FileMenu.Enable(wx.ID_PRINT, False)
       
   641             MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   642             self.FileMenu.Enable(wx.ID_SAVE, False)
       
   643             MenuToolBar.EnableTool(wx.ID_SAVE, False)
       
   644             self.FileMenu.Enable(wx.ID_SAVEAS, False)
       
   645             MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
       
   646             self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
       
   647 
       
   648     def RefreshRecentProjectsMenu(self):
       
   649         try:
       
   650             recent_projects = map(DecodeFileSystemPath,
       
   651                                   self.GetConfigEntry("RecentProjects", []))
       
   652         except:
       
   653             recent_projects = []
       
   654 
       
   655         while self.RecentProjectsMenu.GetMenuItemCount() > len(recent_projects):
       
   656             item = self.RecentProjectsMenu.FindItemByPosition(0)
       
   657             self.RecentProjectsMenu.RemoveItem(item)
       
   658 
       
   659         self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0)
       
   660         for idx, projectpath in enumerate(recent_projects):
       
   661             text = u'&%d: %s' % (idx + 1, projectpath)
       
   662 
       
   663             if idx < self.RecentProjectsMenu.GetMenuItemCount():
       
   664                 item = self.RecentProjectsMenu.FindItemByPosition(idx)
       
   665                 id = item.GetId()
       
   666                 item.SetItemLabel(text)
       
   667                 self.Disconnect(id, id, wx.EVT_BUTTON._getEvtType())
       
   668             else:
       
   669                 id = wx.NewId()
       
   670                 AppendMenu(self.RecentProjectsMenu, help='', id=id,
       
   671                            kind=wx.ITEM_NORMAL, text=text)
       
   672             self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), id=id)
       
   673 
       
   674     def GenerateOpenRecentProjectFunction(self, projectpath):
       
   675         def OpenRecentProject(event):
       
   676             if self.CTR is not None and not self.CheckSaveBeforeClosing():
       
   677                 return
       
   678 
       
   679             self.OpenProject(projectpath)
       
   680         return OpenRecentProject
       
   681 
       
   682     def GenerateMenuRecursive(self, items, menu):
       
   683         for kind, infos in items:
       
   684             if isinstance(kind, ListType):
       
   685                 text, id = infos
       
   686                 submenu = wx.Menu('')
       
   687                 self.GenerateMenuRecursive(kind, submenu)
       
   688                 menu.AppendMenu(id, text, submenu)
       
   689             elif kind == wx.ITEM_SEPARATOR:
       
   690                 menu.AppendSeparator()
       
   691             else:
       
   692                 text, id, help, callback = infos
       
   693                 AppendMenu(menu, help='', id=id, kind=kind, text=text)
       
   694                 if callback is not None:
       
   695                     self.Bind(wx.EVT_MENU, callback, id=id)
       
   696 
       
   697     def RefreshEditorToolBar(self):
       
   698         IDEFrame.RefreshEditorToolBar(self)
       
   699         self.AUIManager.GetPane("EditorToolBar").Position(2)
       
   700         self.AUIManager.GetPane("StatusToolBar").Position(1)
       
   701         self.AUIManager.Update()
       
   702 
       
   703     def RefreshStatusToolBar(self):
       
   704         StatusToolBar = self.Panes["StatusToolBar"]
       
   705         StatusToolBar.ClearTools()
       
   706 
       
   707         if self.CTR is not None:
       
   708 
       
   709             for confnode_method in self.CTR.StatusMethods:
       
   710                 if "method" in confnode_method and confnode_method.get("shown",True):
       
   711                     id = wx.NewId()
       
   712                     StatusToolBar.AddSimpleTool(id,
       
   713                         GetBitmap(confnode_method.get("bitmap", "Unknown")),
       
   714                         confnode_method["tooltip"])
       
   715                     self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), id=id)
       
   716 
       
   717             StatusToolBar.Realize()
       
   718             self.AUIManager.GetPane("StatusToolBar").BestSize(StatusToolBar.GetBestSize()).Show()
       
   719         else:
       
   720             self.AUIManager.GetPane("StatusToolBar").Hide()
       
   721         self.AUIManager.GetPane("EditorToolBar").Position(2)
       
   722         self.AUIManager.GetPane("StatusToolBar").Position(1)
       
   723         self.AUIManager.Update()
       
   724 
       
   725     def RefreshEditMenu(self):
       
   726         IDEFrame.RefreshEditMenu(self)
       
   727         if self.FindFocus() == self.LogConsole:
       
   728             self.EditMenu.Enable(wx.ID_COPY, True)
       
   729             self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True)
       
   730 
       
   731         if self.CTR is not None:
       
   732             selected = self.TabsOpened.GetSelection()
       
   733             if selected >= 0:
       
   734                 panel = self.TabsOpened.GetPage(selected)
       
   735             else:
       
   736                 panel = None
       
   737             if panel != self.LastPanelSelected:
       
   738                 for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
       
   739                     item = self.EditMenu.FindItemByPosition(self.EditMenuSize)
       
   740                     if item is not None:
       
   741                         if item.IsSeparator():
       
   742                             self.EditMenu.RemoveItem(item)
       
   743                         else:
       
   744                             self.EditMenu.Delete(item.GetId())
       
   745                 self.LastPanelSelected = panel
       
   746                 if panel is not None:
       
   747                     items = panel.GetConfNodeMenuItems()
       
   748                 else:
       
   749                     items = []
       
   750                 if len(items) > 0:
       
   751                     self.EditMenu.AppendSeparator()
       
   752                     self.GenerateMenuRecursive(items, self.EditMenu)
       
   753             if panel is not None:
       
   754                 panel.RefreshConfNodeMenu(self.EditMenu)
       
   755         else:
       
   756             for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
       
   757                 item = self.EditMenu.FindItemByPosition(i)
       
   758                 if item is not None:
       
   759                     if item.IsSeparator():
       
   760                         self.EditMenu.RemoveItem(item)
       
   761                     else:
       
   762                         self.EditMenu.Delete(item.GetId())
       
   763             self.LastPanelSelected = None
       
   764         self.MenuBar.UpdateMenus()
       
   765 
       
   766     def RefreshAll(self):
       
   767         self.RefreshStatusToolBar()
       
   768 
       
   769     def GetMenuCallBackFunction(self, method):
       
   770         """ Generate the callbackfunc for a given CTR method"""
       
   771         def OnMenu(event):
       
   772             # Disable button to prevent re-entrant call
       
   773             event.GetEventObject().Disable()
       
   774             # Call
       
   775             getattr(self.CTR, method)()
       
   776             # Re-enable button
       
   777             event.GetEventObject().Enable()
       
   778         return OnMenu
       
   779 
       
   780     def GetConfigEntry(self, entry_name, default):
       
   781         return cPickle.loads(str(self.Config.Read(entry_name, cPickle.dumps(default))))
       
   782 
       
   783     def ResetConnectionStatusBar(self):
       
   784         for field in xrange(self.ConnectionStatusBar.GetFieldsCount()):
       
   785             self.ConnectionStatusBar.SetStatusText('', field)
       
   786 
       
   787     def ResetView(self):
       
   788         IDEFrame.ResetView(self)
       
   789         self.ConfNodeInfos = {}
       
   790         if self.CTR is not None:
       
   791             self.CTR.CloseProject()
       
   792         self.CTR = None
       
   793         self.Log.flush()
       
   794         if self.EnableDebug:
       
   795             self.DebugVariablePanel.SetDataProducer(None)
       
   796             self.ResetConnectionStatusBar()
       
   797 
       
   798     def RefreshConfigRecentProjects(self, projectpath, err=False):
       
   799         try:
       
   800             recent_projects = map(DecodeFileSystemPath,
       
   801                                   self.GetConfigEntry("RecentProjects", []))
       
   802         except:
       
   803             recent_projects = []
       
   804         if projectpath in recent_projects:
       
   805             recent_projects.remove(projectpath)
       
   806         if not err:
       
   807             recent_projects.insert(0, projectpath)
       
   808         self.Config.Write("RecentProjects", cPickle.dumps(
       
   809             map(EncodeFileSystemPath, recent_projects[:MAX_RECENT_PROJECTS])))
       
   810         self.Config.Flush()
       
   811 
       
   812     def ResetPerspective(self):
       
   813         IDEFrame.ResetPerspective(self)
       
   814         self.RefreshStatusToolBar()
       
   815 
       
   816     def OnNewProjectMenu(self, event):
       
   817         if self.CTR is not None and not self.CheckSaveBeforeClosing():
       
   818             return
       
   819 
       
   820         try:
       
   821             defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
       
   822         except:
       
   823             defaultpath = os.path.expanduser("~")
       
   824 
       
   825         dialog = wx.DirDialog(self , _("Choose a project"), defaultpath)
       
   826         if dialog.ShowModal() == wx.ID_OK:
       
   827             projectpath = dialog.GetPath()
       
   828             self.Config.Write("lastopenedfolder",
       
   829                               EncodeFileSystemPath(os.path.dirname(projectpath)))
       
   830             self.Config.Flush()
       
   831             self.ResetView()
       
   832             ctr = ProjectController(self, self.Log)
       
   833             result = ctr.NewProject(projectpath)
       
   834             if not result:
       
   835                 self.CTR = ctr
       
   836                 self.Controler = self.CTR
       
   837                 self.LibraryPanel.SetController(self.Controler)
       
   838                 self.ProjectTree.Enable(True)
       
   839                 self.PouInstanceVariablesPanel.SetController(self.Controler)
       
   840                 self.RefreshConfigRecentProjects(projectpath)
       
   841                 if self.EnableDebug:
       
   842                     self.DebugVariablePanel.SetDataProducer(self.CTR)
       
   843                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   844             else:
       
   845                 self.ResetView()
       
   846                 self.ShowErrorMessage(result)
       
   847             self.RefreshAll()
       
   848             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   849         dialog.Destroy()
       
   850 
       
   851     def OnOpenProjectMenu(self, event):
       
   852         if self.CTR is not None and not self.CheckSaveBeforeClosing():
       
   853             return
       
   854 
       
   855         try:
       
   856             defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
       
   857         except:
       
   858             defaultpath = os.path.expanduser("~")
       
   859 
       
   860         dialog = wx.DirDialog(self , _("Choose a project"), defaultpath, style=wx.DEFAULT_DIALOG_STYLE|
       
   861                                                                                wx.RESIZE_BORDER)
       
   862         if dialog.ShowModal() == wx.ID_OK:
       
   863             self.OpenProject(dialog.GetPath())
       
   864         dialog.Destroy()
       
   865 
       
   866     def OpenProject(self, projectpath):
       
   867         if os.path.isdir(projectpath):
       
   868             self.Config.Write("lastopenedfolder",
       
   869                               EncodeFileSystemPath(os.path.dirname(projectpath)))
       
   870             self.Config.Flush()
       
   871             self.ResetView()
       
   872             self.CTR = ProjectController(self, self.Log)
       
   873             self.Controler = self.CTR
       
   874             result, err = self.CTR.LoadProject(projectpath)
       
   875             if not result:
       
   876                 self.LibraryPanel.SetController(self.Controler)
       
   877                 self.ProjectTree.Enable(True)
       
   878                 self.PouInstanceVariablesPanel.SetController(self.Controler)
       
   879                 if self.EnableDebug:
       
   880                     self.DebugVariablePanel.SetDataProducer(self.CTR)
       
   881                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   882             else:
       
   883                 self.ResetView()
       
   884                 self.ShowErrorMessage(result)
       
   885             self.RefreshAll()
       
   886             self.SearchResultPanel.ResetSearchResults()
       
   887         else:
       
   888             self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath)
       
   889             err = True
       
   890         self.RefreshConfigRecentProjects(projectpath, err)
       
   891         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   892 
       
   893     def OnCloseProjectMenu(self, event):
       
   894         if self.CTR is not None and not self.CheckSaveBeforeClosing():
       
   895             return
       
   896 
       
   897         self.ResetView()
       
   898         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   899         self.RefreshAll()
       
   900 
       
   901     def OnSaveProjectMenu(self, event):
       
   902         selected = self.TabsOpened.GetSelection()
       
   903         if selected != -1:
       
   904             window = self.TabsOpened.GetPage(selected)
       
   905             window.Save()
       
   906         if self.CTR is not None:
       
   907             self.CTR.SaveProject()
       
   908             self.RefreshAll()
       
   909             self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
       
   910 
       
   911     def OnSaveProjectAsMenu(self, event):
       
   912         selected = self.TabsOpened.GetSelection()
       
   913         if selected != -1:
       
   914             window = self.TabsOpened.GetPage(selected)
       
   915             window.SaveAs()
       
   916         if self.CTR is not None:
       
   917             self.CTR.SaveProjectAs()
       
   918             self.RefreshAll()
       
   919             self.RefreshConfigRecentProjects(self.CTR.ProjectPath)
       
   920             self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
       
   921 
       
   922     def OnQuitMenu(self, event):
       
   923         self.Close()
       
   924 
       
   925     def OnAboutMenu(self, event):
       
   926         info = version.GetAboutDialogInfo()        
       
   927         ShowAboutDialog(self, info)
       
   928 
       
   929     def OnProjectTreeItemBeginEdit(self, event):
       
   930         selected = event.GetItem()
       
   931         if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFNODE:
       
   932             event.Veto()
       
   933         else:
       
   934             IDEFrame.OnProjectTreeItemBeginEdit(self, event)
       
   935 
       
   936     def OnProjectTreeRightUp(self, event):
       
   937         item = event.GetItem()
       
   938         item_infos = self.ProjectTree.GetPyData(item)
       
   939 
       
   940         if item_infos["type"] == ITEM_CONFNODE:
       
   941             confnode_menu = wx.Menu(title='')
       
   942 
       
   943             confnode = item_infos["confnode"]
       
   944             if confnode is not None:
       
   945                 menu_items = confnode.GetContextualMenuItems()
       
   946                 if menu_items is not None:
       
   947                     for text, help, callback in menu_items:
       
   948                         new_id = wx.NewId()
       
   949                         confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=text)
       
   950                         self.Bind(wx.EVT_MENU, callback, id=new_id)
       
   951                 else:
       
   952                     for name, XSDClass, help in confnode.CTNChildrenTypes:
       
   953                         new_id = wx.NewId()
       
   954                         confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=_("Add") + " " + name)
       
   955                         self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name, confnode), id=new_id)
       
   956 
       
   957             new_id = wx.NewId()
       
   958             AppendMenu(confnode_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Delete"))
       
   959             self.Bind(wx.EVT_MENU, self.GetDeleteMenuFunction(confnode), id=new_id)
       
   960 
       
   961             self.PopupMenu(confnode_menu)
       
   962             confnode_menu.Destroy()
       
   963 
       
   964             event.Skip()
       
   965         elif item_infos["type"] == ITEM_RESOURCE:
       
   966             # prevent last resource to be delted
       
   967             parent = self.ProjectTree.GetItemParent(item)
       
   968             parent_name = self.ProjectTree.GetItemText(parent)
       
   969             if parent_name == _("Resources"):
       
   970                 IDEFrame.OnProjectTreeRightUp(self, event)
       
   971         else:
       
   972             IDEFrame.OnProjectTreeRightUp(self, event)
       
   973 
       
   974     def OnProjectTreeItemActivated(self, event):
       
   975         selected = event.GetItem()
       
   976         name = self.ProjectTree.GetItemText(selected)
       
   977         item_infos = self.ProjectTree.GetPyData(selected)
       
   978         if item_infos["type"] == ITEM_CONFNODE:
       
   979             item_infos["confnode"]._OpenView()
       
   980             event.Skip()
       
   981         elif item_infos["type"] == ITEM_PROJECT:
       
   982             self.CTR._OpenView()
       
   983         else:
       
   984             IDEFrame.OnProjectTreeItemActivated(self, event)
       
   985 
       
   986     def ProjectTreeItemSelect(self, select_item):
       
   987         if select_item is not None and select_item.IsOk():
       
   988             name = self.ProjectTree.GetItemText(select_item)
       
   989             item_infos = self.ProjectTree.GetPyData(select_item)
       
   990             if item_infos["type"] == ITEM_CONFNODE:
       
   991                 item_infos["confnode"]._OpenView(onlyopened=True)
       
   992             elif item_infos["type"] == ITEM_PROJECT:
       
   993                 self.CTR._OpenView(onlyopened=True)
       
   994             else:
       
   995                 IDEFrame.ProjectTreeItemSelect(self, select_item)
       
   996 
       
   997     def SelectProjectTreeItem(self, tagname):
       
   998         if self.ProjectTree is not None:
       
   999             root = self.ProjectTree.GetRootItem()
       
  1000             if root.IsOk():
       
  1001                 words = tagname.split("::")
       
  1002                 if len(words) == 1:
       
  1003                     if tagname == "Project":
       
  1004                         self.SelectedItem = root
       
  1005                         self.ProjectTree.SelectItem(root)
       
  1006                         self.ResetSelectedItem()
       
  1007                     else:
       
  1008                         return self.RecursiveProjectTreeItemSelection(root,
       
  1009                               [(word, ITEM_CONFNODE) for word in tagname.split(".")])
       
  1010                 elif words[0] == "R":
       
  1011                     return self.RecursiveProjectTreeItemSelection(root, [(words[2], ITEM_RESOURCE)])
       
  1012                 elif not os.path.exists(words[0]):
       
  1013                     IDEFrame.SelectProjectTreeItem(self, tagname)
       
  1014 
       
  1015     def GetAddConfNodeFunction(self, name, confnode=None):
       
  1016         def AddConfNodeMenuFunction(event):
       
  1017             wx.CallAfter(self.AddConfNode, name, confnode)
       
  1018         return AddConfNodeMenuFunction
       
  1019 
       
  1020     def GetDeleteMenuFunction(self, confnode):
       
  1021         def DeleteMenuFunction(event):
       
  1022             wx.CallAfter(self.DeleteConfNode, confnode)
       
  1023         return DeleteMenuFunction
       
  1024 
       
  1025     def AddConfNode(self, ConfNodeType, confnode=None):
       
  1026         if self.CTR.CheckProjectPathPerm():
       
  1027             ConfNodeName = "%s_0" % ConfNodeType
       
  1028             if confnode is not None:
       
  1029                 confnode.CTNAddChild(ConfNodeName, ConfNodeType)
       
  1030             else:
       
  1031                 self.CTR.CTNAddChild(ConfNodeName, ConfNodeType)
       
  1032             self._Refresh(TITLE, FILEMENU, PROJECTTREE)
       
  1033 
       
  1034     def DeleteConfNode(self, confnode):
       
  1035         if self.CTR.CheckProjectPathPerm():
       
  1036             dialog = wx.MessageDialog(self,
       
  1037                 _("Really delete node '%s'?") % confnode.CTNName(),
       
  1038                 _("Remove %s node") % confnode.CTNType,
       
  1039                 wx.YES_NO|wx.NO_DEFAULT)
       
  1040             if dialog.ShowModal() == wx.ID_YES:
       
  1041                 confnode.CTNRemove()
       
  1042                 del confnode
       
  1043                 self._Refresh(TITLE, FILEMENU, PROJECTTREE)
       
  1044             dialog.Destroy()
       
  1045 
       
  1046 #-------------------------------------------------------------------------------
       
  1047 #                        Highlights showing functions
       
  1048 #-------------------------------------------------------------------------------
       
  1049 
       
  1050     def ShowHighlight(self, infos, start, end, highlight_type):
       
  1051         config_name = self.Controler.GetProjectMainConfigurationName()
       
  1052         if config_name is not None and infos[0] == self.Controler.ComputeConfigurationName(config_name):
       
  1053             self.CTR._OpenView()
       
  1054             selected = self.TabsOpened.GetSelection()
       
  1055             if selected != -1:
       
  1056                 viewer = self.TabsOpened.GetPage(selected)
       
  1057                 viewer.AddHighlight(infos[1:], start, end, highlight_type)
       
  1058         else:
       
  1059             IDEFrame.ShowHighlight(self, infos, start, end, highlight_type)
       
  1060 
       
  1061 #-------------------------------------------------------------------------------
       
  1062 #                               Exception Handler
       
  1063 #-------------------------------------------------------------------------------
       
  1064 import threading, traceback
       
  1065 
       
  1066 Max_Traceback_List_Size = 20
       
  1067 
       
  1068 def Display_Exception_Dialog(e_type, e_value, e_tb, bug_report_path):
       
  1069     trcbck_lst = []
       
  1070     for i,line in enumerate(traceback.extract_tb(e_tb)):
       
  1071         trcbck = " " + str(i+1) + ". "
       
  1072         if line[0].find(os.getcwd()) == -1:
       
  1073             trcbck += "file : " + str(line[0]) + ",   "
       
  1074         else:
       
  1075             trcbck += "file : " + str(line[0][len(os.getcwd()):]) + ",   "
       
  1076         trcbck += "line : " + str(line[1]) + ",   " + "function : " + str(line[2])
       
  1077         trcbck_lst.append(trcbck)
       
  1078 
       
  1079     # Allow clicking....
       
  1080     cap = wx.Window_GetCapture()
       
  1081     if cap:
       
  1082         cap.ReleaseMouse()
       
  1083 
       
  1084     dlg = wx.SingleChoiceDialog(None,
       
  1085         _("""
       
  1086 An unhandled exception (bug) occured. Bug report saved at :
       
  1087 (%s)
       
  1088 
       
  1089 Please be kind enough to send this file to:
       
  1090 beremiz-devel@lists.sourceforge.net
       
  1091 
       
  1092 You should now restart program.
       
  1093 
       
  1094 Traceback:
       
  1095 """) % bug_report_path +
       
  1096         repr(e_type) + " : " + repr(e_value),
       
  1097         _("Error"),
       
  1098         trcbck_lst)
       
  1099     try:
       
  1100         res = (dlg.ShowModal() == wx.ID_OK)
       
  1101     finally:
       
  1102         dlg.Destroy()
       
  1103 
       
  1104     return res
       
  1105 
       
  1106 def get_last_traceback(tb):
       
  1107     while tb.tb_next:
       
  1108         tb = tb.tb_next
       
  1109     return tb
       
  1110 
       
  1111 
       
  1112 def format_namespace(d, indent='    '):
       
  1113     return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])
       
  1114 
       
  1115 
       
  1116 ignored_exceptions = [] # a problem with a line in a module is only reported once per session
       
  1117 
       
  1118 def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):
       
  1119 
       
  1120     def save_bug_report(e_type, e_value, e_traceback, bug_report_path,date):
       
  1121         info = {
       
  1122             'app-title': wx.GetApp().GetAppName(),  # app_title
       
  1123             'app-version': app_version,
       
  1124             'wx-version': wx.VERSION_STRING,
       
  1125             'wx-platform': wx.Platform,
       
  1126             'python-version': platform.python_version(),  # sys.version.split()[0],
       
  1127             'platform': platform.platform(),
       
  1128             'e-type': e_type,
       
  1129             'e-value': e_value,
       
  1130             'date': date,
       
  1131             'cwd': os.getcwd(),
       
  1132         }
       
  1133         if e_traceback:
       
  1134             info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
       
  1135             last_tb = get_last_traceback(e_traceback)
       
  1136             exception_locals = last_tb.tb_frame.f_locals  # the locals at the level of the stack trace where the exception actually occurred
       
  1137             info['locals'] = format_namespace(exception_locals)
       
  1138             if 'self' in exception_locals:
       
  1139                 try:
       
  1140                     info['self'] = format_namespace(exception_locals['self'].__dict__)
       
  1141                 except:
       
  1142                     pass
       
  1143         if not os.path.exists(path):
       
  1144             os.mkdir(path)
       
  1145         output = open(bug_report_path, 'w')
       
  1146         lst = info.keys()
       
  1147         lst.sort()
       
  1148         for a in lst:
       
  1149             output.write(a + ":\n" + str(info[a]) + "\n\n")
       
  1150         output.close()
       
  1151 
       
  1152     def handle_exception(e_type, e_value, e_traceback):
       
  1153         traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
       
  1154         last_tb = get_last_traceback(e_traceback)
       
  1155         ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
       
  1156         if ex not in ignored_exceptions:
       
  1157             ignored_exceptions.append(ex)
       
  1158             date = time.ctime()
       
  1159             bug_report_path = path + os.sep + "bug_report_" + date.replace(':', '-').replace(' ', '_') + ".txt"
       
  1160             save_bug_report(e_type, e_value, e_traceback, bug_report_path, date)
       
  1161             Display_Exception_Dialog(e_type, e_value, e_traceback, bug_report_path)
       
  1162     #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
       
  1163     sys.excepthook = handle_exception
       
  1164 
       
  1165     init_old = threading.Thread.__init__
       
  1166     def init(self, *args, **kwargs):
       
  1167         init_old(self, *args, **kwargs)
       
  1168         run_old = self.run
       
  1169         def run_with_except_hook(*args, **kw):
       
  1170             try:
       
  1171                 run_old(*args, **kw)
       
  1172             except (KeyboardInterrupt, SystemExit):
       
  1173                 raise
       
  1174             except:
       
  1175                 sys.excepthook(*sys.exc_info())
       
  1176         self.run = run_with_except_hook
       
  1177     threading.Thread.__init__ = init