PLCOpenEditor.py
branch1.1 Korean release
changeset 968 eee7625de1f7
parent 967 8a339cd61cb4
child 980 c7ba67d01d65
equal deleted inserted replaced
808:6e205c1f05a0 968:eee7625de1f7
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 import wx
       
    26 import os, sys, platform, time, traceback, getopt
       
    27 
       
    28 CWD = os.path.split(os.path.realpath(__file__))[0]
       
    29 
       
    30 __version__ = "$Revision: 1.130 $"
       
    31 
       
    32 if __name__ == '__main__':
       
    33     # Usage message displayed when help request or when error detected in 
       
    34     # command line
       
    35     def usage():
       
    36         print "\nUsage of PLCOpenEditor.py :"
       
    37         print "\n   %s [Filepath]\n"%sys.argv[0]
       
    38 
       
    39     # Parse options given to PLCOpenEditor in command line
       
    40     try:
       
    41         opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
       
    42     except getopt.GetoptError:
       
    43         # print help information and exit:
       
    44         usage()
       
    45         sys.exit(2)
       
    46     
       
    47     # Extract if help has been requested
       
    48     for o, a in opts:
       
    49         if o in ("-h", "--help"):
       
    50             usage()
       
    51             sys.exit()
       
    52     
       
    53     # Extract the optional filename to open
       
    54     fileOpen = None
       
    55     if len(args) > 1:
       
    56         usage()
       
    57         sys.exit()
       
    58     elif len(args) == 1:
       
    59         fileOpen = args[0]
       
    60     
       
    61     # Create wxApp (Need to create App before internationalization because of
       
    62     # Windows) 
       
    63     app = wx.PySimpleApp()
       
    64 
       
    65 from docutil import *
       
    66 
       
    67 from util.TranslationCatalogs import AddCatalog
       
    68 from util.BitmapLibrary import AddBitmapFolder, GetBitmap
       
    69 
       
    70 AddCatalog(os.path.join(CWD, "locale"))
       
    71 AddBitmapFolder(os.path.join(CWD, "images"))
       
    72 
       
    73 if __name__ == '__main__':
       
    74     # Import module for internationalization
       
    75     import gettext
       
    76     import __builtin__
       
    77     
       
    78     __builtin__.__dict__['_'] = wx.GetTranslation
       
    79 
       
    80 from IDEFrame import IDEFrame, AppendMenu
       
    81 from IDEFrame import TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE, PAGETITLES
       
    82 from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
       
    83 from editors.Viewer import Viewer
       
    84 from PLCControler import PLCControler
       
    85 
       
    86 #-------------------------------------------------------------------------------
       
    87 #                            PLCOpenEditor Main Class
       
    88 #-------------------------------------------------------------------------------
       
    89 
       
    90 # Define PLCOpenEditor FileMenu extra items id
       
    91 [ID_PLCOPENEDITORFILEMENUGENERATE, 
       
    92 ] = [wx.NewId() for _init_coll_FileMenu_Items in range(1)]
       
    93 
       
    94 class PLCOpenEditor(IDEFrame):
       
    95 
       
    96     # Compatibility function for wx versions < 2.6
       
    97     if wx.VERSION < (2, 6, 0):
       
    98         def Bind(self, event, function, id = None):
       
    99             if id is not None:
       
   100                 event(self, id, function)
       
   101             else:
       
   102                 event(self, function)
       
   103 
       
   104     def _init_coll_FileMenu_Items(self, parent):
       
   105         AppendMenu(parent, help='', id=wx.ID_NEW,
       
   106               kind=wx.ITEM_NORMAL, text=_(u'New') +'\tCTRL+N')
       
   107         AppendMenu(parent, help='', id=wx.ID_OPEN,
       
   108               kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
       
   109         AppendMenu(parent, help='', id=wx.ID_CLOSE,
       
   110               kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
       
   111         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
       
   112               kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
       
   113         parent.AppendSeparator()
       
   114         AppendMenu(parent, help='', id=wx.ID_SAVE,
       
   115               kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
       
   116         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
       
   117               kind=wx.ITEM_NORMAL, text=_(u'Save As...') + '\tCTRL+SHIFT+S')
       
   118         AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATE,
       
   119               kind=wx.ITEM_NORMAL, text=_(u'Generate Program') + '\tCTRL+G')
       
   120         parent.AppendSeparator()
       
   121         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
       
   122               kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
       
   123         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
       
   124               kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
       
   125         AppendMenu(parent, help='', id=wx.ID_PRINT,
       
   126               kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
       
   127         parent.AppendSeparator()
       
   128         AppendMenu(parent, help='', id=wx.ID_PROPERTIES,
       
   129               kind=wx.ITEM_NORMAL, text=_(u'&Properties'))
       
   130         parent.AppendSeparator()
       
   131         AppendMenu(parent, help='', id=wx.ID_EXIT,
       
   132               kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
       
   133         
       
   134         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
       
   135         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
       
   136         self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
       
   137         self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
       
   138         self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
       
   139         self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
       
   140         self.Bind(wx.EVT_MENU, self.OnGenerateProgramMenu,
       
   141               id=ID_PLCOPENEDITORFILEMENUGENERATE)
       
   142         self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
       
   143         self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
       
   144         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
       
   145         self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES)
       
   146         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
       
   147         
       
   148         self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
       
   149                                (wx.ID_OPEN, "open", _(u'Open'), None),
       
   150                                (wx.ID_SAVE, "save", _(u'Save'), None),
       
   151                                (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
       
   152                                (wx.ID_PRINT, "print", _(u'Print'), None)])
       
   153             
       
   154     def _init_coll_HelpMenu_Items(self, parent):
       
   155         AppendMenu(parent, help='', id=wx.ID_HELP, 
       
   156             kind=wx.ITEM_NORMAL, text=_(u'PLCOpenEditor') + '\tF1')
       
   157         #AppendMenu(parent, help='', id=wx.ID_HELP_CONTENTS,
       
   158         #      kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
       
   159         #AppendMenu(parent, help='', id=wx.ID_HELP_CONTEXT,
       
   160         #      kind=wx.ITEM_NORMAL, text=u'IEC 61131-3\tF3')
       
   161         AppendMenu(parent, help='', id=wx.ID_ABOUT,
       
   162             kind=wx.ITEM_NORMAL, text=_(u'About'))
       
   163         self.Bind(wx.EVT_MENU, self.OnPLCOpenEditorMenu, id=wx.ID_HELP)
       
   164         #self.Bind(wx.EVT_MENU, self.OnPLCOpenMenu, id=wx.ID_HELP_CONTENTS)
       
   165         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
       
   166 
       
   167     ## Constructor of the PLCOpenEditor class.
       
   168     #  @param parent The parent window.
       
   169     #  @param controler The controler been used by PLCOpenEditor (default: None).
       
   170     #  @param fileOpen The filepath to open if no controler defined (default: None).
       
   171     #  @param debug The filepath to open if no controler defined (default: False).
       
   172     def __init__(self, parent, fileOpen = None):
       
   173         IDEFrame.__init__(self, parent)
       
   174         
       
   175         result = None
       
   176         
       
   177         # Open the filepath if defined
       
   178         if fileOpen is not None:
       
   179             fileOpen = DecodeFileSystemPath(fileOpen, False)
       
   180             if os.path.isfile(fileOpen):
       
   181                 # Create a new controller
       
   182                 controler = PLCControler()
       
   183                 result = controler.OpenXMLFile(fileOpen)
       
   184                 if result is None:
       
   185                     self.Controler = controler
       
   186                     self.LibraryPanel.SetController(controler)
       
   187                     self.ProjectTree.Enable(True)
       
   188                     self.PouInstanceVariablesPanel.SetController(controler)
       
   189                     self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   190         
       
   191         # Define PLCOpenEditor icon
       
   192         self.SetIcon(wx.Icon(os.path.join(CWD, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
       
   193 
       
   194         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
       
   195         
       
   196         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
       
   197         
       
   198         if result is not None:
       
   199             self.ShowErrorMessage(result)
       
   200 
       
   201     def OnCloseFrame(self, event):
       
   202         if self.Controler is None or self.CheckSaveBeforeClosing(_("Close Application")):
       
   203             self.AUIManager.UnInit()
       
   204             
       
   205             self.SaveLastState()
       
   206             
       
   207             event.Skip()
       
   208         else:
       
   209             event.Veto()
       
   210 
       
   211     def RefreshTitle(self):
       
   212         name = _("PLCOpenEditor")
       
   213         if self.Controler is not None:
       
   214             self.SetTitle("%s - %s"%(name, self.Controler.GetFilename()))
       
   215         else:
       
   216             self.SetTitle(name)
       
   217 
       
   218 #-------------------------------------------------------------------------------
       
   219 #                            File Menu Functions
       
   220 #-------------------------------------------------------------------------------
       
   221 
       
   222     def RefreshFileMenu(self):
       
   223         MenuToolBar = self.Panes["MenuToolBar"]
       
   224         if self.Controler is not None:
       
   225             selected = self.TabsOpened.GetSelection()
       
   226             if selected >= 0:
       
   227                 graphic_viewer = isinstance(self.TabsOpened.GetPage(selected), Viewer)
       
   228             else:
       
   229                 graphic_viewer = False
       
   230             if self.TabsOpened.GetPageCount() > 0:
       
   231                 self.FileMenu.Enable(wx.ID_CLOSE, True)
       
   232                 if graphic_viewer:
       
   233                     self.FileMenu.Enable(wx.ID_PREVIEW, True)
       
   234                     self.FileMenu.Enable(wx.ID_PRINT, True)
       
   235                     MenuToolBar.EnableTool(wx.ID_PRINT, True)
       
   236                 else:
       
   237                     self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   238                     self.FileMenu.Enable(wx.ID_PRINT, False)
       
   239                     MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   240             else:
       
   241                 self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   242                 self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   243                 self.FileMenu.Enable(wx.ID_PRINT, False)
       
   244                 MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   245             self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
       
   246             project_modified = not self.Controler.ProjectIsSaved()
       
   247             self.FileMenu.Enable(wx.ID_SAVE, project_modified)
       
   248             MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
       
   249             self.FileMenu.Enable(wx.ID_PROPERTIES, True)
       
   250             self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
       
   251             self.FileMenu.Enable(wx.ID_SAVEAS, True)
       
   252             MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
       
   253             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, True)
       
   254         else:
       
   255             self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   256             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
       
   257             self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   258             self.FileMenu.Enable(wx.ID_PRINT, False)
       
   259             MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   260             self.FileMenu.Enable(wx.ID_SAVE, False)
       
   261             MenuToolBar.EnableTool(wx.ID_SAVE, False)
       
   262             self.FileMenu.Enable(wx.ID_PROPERTIES, False)
       
   263             self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
       
   264             self.FileMenu.Enable(wx.ID_SAVEAS, False)
       
   265             MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
       
   266             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, False)
       
   267 
       
   268     def OnNewProjectMenu(self, event):
       
   269         if self.Controler is not None and not self.CheckSaveBeforeClosing():
       
   270             return
       
   271         dialog = ProjectDialog(self)
       
   272         if dialog.ShowModal() == wx.ID_OK:
       
   273             properties = dialog.GetValues()
       
   274             self.ResetView()
       
   275             self.Controler = PLCControler()
       
   276             self.Controler.CreateNewProject(properties)
       
   277             self.LibraryPanel.SetController(self.Controler)
       
   278             self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, 
       
   279                           LIBRARYTREE)
       
   280 
       
   281     def OnOpenProjectMenu(self, event):
       
   282         if self.Controler is not None and not self.CheckSaveBeforeClosing():
       
   283             return
       
   284         filepath = ""
       
   285         if self.Controler is not None:
       
   286             filepath = self.Controler.GetFilePath()
       
   287         if filepath != "":
       
   288             directory = os.path.dirname(filepath)
       
   289         else:
       
   290             directory = os.getcwd()
       
   291         
       
   292         result = None
       
   293         
       
   294         dialog = wx.FileDialog(self, _("Choose a file"), directory, "",  _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
       
   295         if dialog.ShowModal() == wx.ID_OK:
       
   296             filepath = dialog.GetPath()
       
   297             if os.path.isfile(filepath):
       
   298                 self.ResetView()
       
   299                 controler = PLCControler()
       
   300                 result = controler.OpenXMLFile(filepath)
       
   301                 if result is None:
       
   302                     self.Controler = controler
       
   303                     self.LibraryPanel.SetController(controler)
       
   304                     self.ProjectTree.Enable(True)
       
   305                     self.PouInstanceVariablesPanel.SetController(controler)
       
   306                     self.LoadProjectLayout()
       
   307                     self._Refresh(PROJECTTREE, LIBRARYTREE)
       
   308             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   309         dialog.Destroy()
       
   310         
       
   311         if result is not None:
       
   312             self.ShowErrorMessage(result)
       
   313     
       
   314     def OnCloseProjectMenu(self, event):
       
   315         if not self.CheckSaveBeforeClosing():
       
   316             return
       
   317         self.SaveProjectLayout()
       
   318         self.ResetView()
       
   319         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   320 
       
   321     def OnSaveProjectMenu(self, event):
       
   322         self.SaveProject()
       
   323 
       
   324     def OnSaveProjectAsMenu(self, event):
       
   325         self.SaveProjectAs()
       
   326 
       
   327     def OnGenerateProgramMenu(self, event):
       
   328         dialog = wx.FileDialog(self, _("Choose a file"), os.getcwd(), self.Controler.GetProgramFilePath(),  _("ST files (*.st)|*.st|All files|*.*"), wx.SAVE|wx.CHANGE_DIR)
       
   329         if dialog.ShowModal() == wx.ID_OK:
       
   330             filepath = dialog.GetPath()
       
   331             message_text = ""
       
   332             header, icon = _("Done"), wx.ICON_INFORMATION
       
   333             if os.path.isdir(os.path.dirname(filepath)):
       
   334                 program, errors, warnings = self.Controler.GenerateProgram(filepath)
       
   335                 message_text += "".join([_("warning: %s\n") for warning in warnings])
       
   336                 if len(errors) > 0:
       
   337                     message_text += "".join([_("error: %s\n") for error in errors])
       
   338                     message_text += _("Can't generate program to file %s!")%filepath
       
   339                     header, icon = _("Error"), wx.ICON_ERROR
       
   340                 else:
       
   341                     message_text += _("Program was successfully generated!")
       
   342             else:
       
   343                 message_text += _("\"%s\" is not a valid folder!")%os.path.dirname(filepath)
       
   344                 header, icon = _("Error"), wx.ICON_ERROR
       
   345             message = wx.MessageDialog(self, message_text, header, wx.OK|icon)
       
   346             message.ShowModal()
       
   347             message.Destroy()
       
   348         dialog.Destroy()
       
   349 
       
   350     def OnPLCOpenEditorMenu(self, event):
       
   351         wx.MessageBox(_("No documentation available.\nComing soon."))
       
   352         
       
   353     def OnPLCOpenMenu(self, event):
       
   354         open_pdf(os.path.join(CWD, "plcopen", "TC6_XML_V101.pdf"))
       
   355     
       
   356     def OnAboutMenu(self, event):
       
   357         OpenHtmlFrame(self,_("About PLCOpenEditor"), os.path.join(CWD, "doc", "plcopen_about.html"), wx.Size(350, 350))
       
   358 
       
   359     def SaveProject(self):
       
   360         result = self.Controler.SaveXMLFile()
       
   361         if not result:
       
   362             self.SaveProjectAs()
       
   363         else:
       
   364             self._Refresh(TITLE, FILEMENU, PAGETITLES)
       
   365         
       
   366     def SaveProjectAs(self):
       
   367         filepath = self.Controler.GetFilePath()
       
   368         if filepath != "":
       
   369             directory, filename = os.path.split(filepath)
       
   370         else:
       
   371             directory, filename = os.getcwd(), "%(projectName)s.xml"%self.Controler.GetProjectProperties()
       
   372         dialog = wx.FileDialog(self, _("Choose a file"), directory, filename,  _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.SAVE|wx.OVERWRITE_PROMPT)
       
   373         if dialog.ShowModal() == wx.ID_OK:
       
   374             filepath = dialog.GetPath()
       
   375             if os.path.isdir(os.path.dirname(filepath)):
       
   376                 result = self.Controler.SaveXMLFile(filepath)
       
   377                 if not result:
       
   378                     self.ShowErrorMessage(_("Can't save project to file %s!")%filepath)
       
   379             else:
       
   380                 self.ShowErrorMessage(_("\"%s\" is not a valid folder!")%os.path.dirname(filepath))
       
   381             self._Refresh(TITLE, FILEMENU, PAGETITLES)
       
   382         dialog.Destroy()
       
   383 
       
   384 #-------------------------------------------------------------------------------
       
   385 #                               Exception Handler
       
   386 #-------------------------------------------------------------------------------
       
   387 
       
   388 Max_Traceback_List_Size = 20
       
   389 
       
   390 def Display_Exception_Dialog(e_type,e_value,e_tb):
       
   391     trcbck_lst = []
       
   392     for i,line in enumerate(traceback.extract_tb(e_tb)):
       
   393         trcbck = " " + str(i+1) + _(". ")
       
   394         if line[0].find(os.getcwd()) == -1:
       
   395             trcbck += _("file : ") + str(line[0]) + _(",   ")
       
   396         else:
       
   397             trcbck += _("file : ") + str(line[0][len(os.getcwd()):]) + _(",   ")
       
   398         trcbck += _("line : ") + str(line[1]) + _(",   ") + _("function : ") + str(line[2])
       
   399         trcbck_lst.append(trcbck)
       
   400         
       
   401     # Allow clicking....
       
   402     cap = wx.Window_GetCapture()
       
   403     if cap:
       
   404         cap.ReleaseMouse()
       
   405 
       
   406     dlg = wx.SingleChoiceDialog(None, 
       
   407         _("""
       
   408 An error has occurred.
       
   409 
       
   410 Click OK to save an error report.
       
   411 
       
   412 Please be kind enough to send this file to:
       
   413 edouard.tisserant@gmail.com
       
   414 
       
   415 Error:
       
   416 """) +
       
   417         str(e_type) + _(" : ") + str(e_value), 
       
   418         _("Error"),
       
   419         trcbck_lst)
       
   420     try:
       
   421         res = (dlg.ShowModal() == wx.ID_OK)
       
   422     finally:
       
   423         dlg.Destroy()
       
   424 
       
   425     return res
       
   426 
       
   427 def Display_Error_Dialog(e_value):
       
   428     message = wx.MessageDialog(None, str(e_value), _("Error"), wx.OK|wx.ICON_ERROR)
       
   429     message.ShowModal()
       
   430     message.Destroy()
       
   431 
       
   432 def get_last_traceback(tb):
       
   433     while tb.tb_next:
       
   434         tb = tb.tb_next
       
   435     return tb
       
   436 
       
   437 
       
   438 def format_namespace(d, indent='    '):
       
   439     return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])
       
   440 
       
   441 
       
   442 ignored_exceptions = [] # a problem with a line in a module is only reported once per session
       
   443 
       
   444 def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):
       
   445     
       
   446     def handle_exception(e_type, e_value, e_traceback):
       
   447         traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
       
   448         last_tb = get_last_traceback(e_traceback)
       
   449         ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
       
   450         if str(e_value).startswith("!!!"):
       
   451             Display_Error_Dialog(e_value)
       
   452         elif ex not in ignored_exceptions:
       
   453             result = Display_Exception_Dialog(e_type,e_value,e_traceback)
       
   454             if result:
       
   455                 ignored_exceptions.append(ex)
       
   456                 info = {
       
   457                     'app-title' : wx.GetApp().GetAppName(), # app_title
       
   458                     'app-version' : app_version,
       
   459                     'wx-version' : wx.VERSION_STRING,
       
   460                     'wx-platform' : wx.Platform,
       
   461                     'python-version' : platform.python_version(), #sys.version.split()[0],
       
   462                     'platform' : platform.platform(),
       
   463                     'e-type' : e_type,
       
   464                     'e-value' : e_value,
       
   465                     'date' : time.ctime(),
       
   466                     'cwd' : os.getcwd(),
       
   467                     }
       
   468                 if e_traceback:
       
   469                     info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
       
   470                     last_tb = get_last_traceback(e_traceback)
       
   471                     exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred
       
   472                     info['locals'] = format_namespace(exception_locals)
       
   473                     if 'self' in exception_locals:
       
   474                         info['self'] = format_namespace(exception_locals['self'].__dict__)
       
   475                 
       
   476                 output = open(path+os.sep+"bug_report_"+info['date'].replace(':','-').replace(' ','_')+".txt",'w')
       
   477                 lst = info.keys()
       
   478                 lst.sort()
       
   479                 for a in lst:
       
   480                     output.write(a+":\n"+str(info[a])+"\n\n")
       
   481 
       
   482     #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
       
   483     sys.excepthook = handle_exception
       
   484 
       
   485 if __name__ == '__main__':
       
   486     wx.InitAllImageHandlers()
       
   487     
       
   488     # Install a exception handle for bug reports
       
   489     AddExceptHook(os.getcwd(),__version__)
       
   490     
       
   491     frame = PLCOpenEditor(None, fileOpen=fileOpen)
       
   492 
       
   493     frame.Show()
       
   494     app.MainLoop()
       
   495