|
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 |