PLCOpenEditor.py
changeset 818 1d1bdf6e75bf
parent 815 e4f24593a758
child 838 06db7d4edbe6
equal deleted inserted replaced
812:d7251818be37 818:1d1bdf6e75bf
       
     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, locale
       
    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__['loc'] = locale
       
    79     __builtin__.__dict__['_'] = wx.GetTranslation
       
    80 
       
    81 from IDEFrame import IDEFrame, AppendMenu
       
    82 from IDEFrame import TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE, PAGETITLES
       
    83 from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
       
    84 from editors.Viewer import Viewer
       
    85 from PLCControler import PLCControler
       
    86 
       
    87 #-------------------------------------------------------------------------------
       
    88 #                            PLCOpenEditor Main Class
       
    89 #-------------------------------------------------------------------------------
       
    90 
       
    91 # Define PLCOpenEditor FileMenu extra items id
       
    92 [ID_PLCOPENEDITORFILEMENUGENERATE, 
       
    93 ] = [wx.NewId() for _init_coll_FileMenu_Items in range(1)]
       
    94 
       
    95 class PLCOpenEditor(IDEFrame):
       
    96 
       
    97     # Compatibility function for wx versions < 2.6
       
    98     if wx.VERSION < (2, 6, 0):
       
    99         def Bind(self, event, function, id = None):
       
   100             if id is not None:
       
   101                 event(self, id, function)
       
   102             else:
       
   103                 event(self, function)
       
   104 
       
   105     def _init_coll_FileMenu_Items(self, parent):
       
   106         AppendMenu(parent, help='', id=wx.ID_NEW,
       
   107               kind=wx.ITEM_NORMAL, text=_(u'New') +'\tCTRL+N')
       
   108         AppendMenu(parent, help='', id=wx.ID_OPEN,
       
   109               kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
       
   110         AppendMenu(parent, help='', id=wx.ID_CLOSE,
       
   111               kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
       
   112         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
       
   113               kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
       
   114         parent.AppendSeparator()
       
   115         AppendMenu(parent, help='', id=wx.ID_SAVE,
       
   116               kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
       
   117         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
       
   118               kind=wx.ITEM_NORMAL, text=_(u'Save As...') + '\tCTRL+SHIFT+S')
       
   119         AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATE,
       
   120               kind=wx.ITEM_NORMAL, text=_(u'Generate Program') + '\tCTRL+G')
       
   121         parent.AppendSeparator()
       
   122         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
       
   123               kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
       
   124         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
       
   125               kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
       
   126         AppendMenu(parent, help='', id=wx.ID_PRINT,
       
   127               kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
       
   128         parent.AppendSeparator()
       
   129         AppendMenu(parent, help='', id=wx.ID_PROPERTIES,
       
   130               kind=wx.ITEM_NORMAL, text=_(u'&Properties'))
       
   131         parent.AppendSeparator()
       
   132         AppendMenu(parent, help='', id=wx.ID_EXIT,
       
   133               kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
       
   134         
       
   135         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
       
   136         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
       
   137         self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
       
   138         self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
       
   139         self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
       
   140         self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
       
   141         self.Bind(wx.EVT_MENU, self.OnGenerateProgramMenu,
       
   142               id=ID_PLCOPENEDITORFILEMENUGENERATE)
       
   143         self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
       
   144         self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
       
   145         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
       
   146         self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES)
       
   147         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
       
   148         
       
   149         self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
       
   150                                (wx.ID_OPEN, "open", _(u'Open'), None),
       
   151                                (wx.ID_SAVE, "save", _(u'Save'), None),
       
   152                                (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
       
   153                                (wx.ID_PRINT, "print", _(u'Print'), None)])
       
   154             
       
   155     def _init_coll_HelpMenu_Items(self, parent):
       
   156         AppendMenu(parent, help='', id=wx.ID_HELP, 
       
   157             kind=wx.ITEM_NORMAL, text=_(u'PLCOpenEditor') + '\tF1')
       
   158         #AppendMenu(parent, help='', id=wx.ID_HELP_CONTENTS,
       
   159         #      kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
       
   160         #AppendMenu(parent, help='', id=wx.ID_HELP_CONTEXT,
       
   161         #      kind=wx.ITEM_NORMAL, text=u'IEC 61131-3\tF3')
       
   162         AppendMenu(parent, help='', id=wx.ID_ABOUT,
       
   163             kind=wx.ITEM_NORMAL, text=_(u'About'))
       
   164         self.Bind(wx.EVT_MENU, self.OnPLCOpenEditorMenu, id=wx.ID_HELP)
       
   165         #self.Bind(wx.EVT_MENU, self.OnPLCOpenMenu, id=wx.ID_HELP_CONTENTS)
       
   166         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
       
   167 
       
   168     ## Constructor of the PLCOpenEditor class.
       
   169     #  @param parent The parent window.
       
   170     #  @param controler The controler been used by PLCOpenEditor (default: None).
       
   171     #  @param fileOpen The filepath to open if no controler defined (default: None).
       
   172     #  @param debug The filepath to open if no controler defined (default: False).
       
   173     def __init__(self, parent, fileOpen = None):
       
   174         IDEFrame.__init__(self, parent)
       
   175         
       
   176         result = None
       
   177         
       
   178         # Open the filepath if defined
       
   179         if fileOpen is not None:
       
   180             fileOpen = DecodeFileSystemPath(fileOpen, False)
       
   181             if os.path.isfile(fileOpen):
       
   182                 # Create a new controller
       
   183                 controler = PLCControler()
       
   184                 result = controler.OpenXMLFile(fileOpen)
       
   185                 if result is None:
       
   186                     self.Controler = controler
       
   187                     self.LibraryPanel.SetController(controler)
       
   188                     self.ProjectTree.Enable(True)
       
   189                     self.PouInstanceVariablesPanel.SetController(controler)
       
   190                     self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   191         
       
   192         # Define PLCOpenEditor icon
       
   193         self.SetIcon(wx.Icon(os.path.join(CWD, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
       
   194 
       
   195         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
       
   196         
       
   197         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
       
   198         
       
   199         if result is not None:
       
   200             self.ShowErrorMessage(result)
       
   201 
       
   202     def OnCloseFrame(self, event):
       
   203         if self.Controler is None or self.CheckSaveBeforeClosing(_("Close Application")):
       
   204             self.AUIManager.UnInit()
       
   205             
       
   206             self.SaveLastState()
       
   207             
       
   208             event.Skip()
       
   209         else:
       
   210             event.Veto()
       
   211 
       
   212     def RefreshTitle(self):
       
   213         name = _("PLCOpenEditor")
       
   214         if self.Controler is not None:
       
   215             self.SetTitle("%s - %s"%(name, self.Controler.GetFilename()))
       
   216         else:
       
   217             self.SetTitle(name)
       
   218 
       
   219 #-------------------------------------------------------------------------------
       
   220 #                            File Menu Functions
       
   221 #-------------------------------------------------------------------------------
       
   222 
       
   223     def RefreshFileMenu(self):
       
   224         MenuToolBar = self.Panes["MenuToolBar"]
       
   225         if self.Controler is not None:
       
   226             selected = self.TabsOpened.GetSelection()
       
   227             if selected >= 0:
       
   228                 graphic_viewer = isinstance(self.TabsOpened.GetPage(selected), Viewer)
       
   229             else:
       
   230                 graphic_viewer = False
       
   231             if self.TabsOpened.GetPageCount() > 0:
       
   232                 self.FileMenu.Enable(wx.ID_CLOSE, True)
       
   233                 if graphic_viewer:
       
   234                     self.FileMenu.Enable(wx.ID_PREVIEW, True)
       
   235                     self.FileMenu.Enable(wx.ID_PRINT, True)
       
   236                     MenuToolBar.EnableTool(wx.ID_PRINT, True)
       
   237                 else:
       
   238                     self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   239                     self.FileMenu.Enable(wx.ID_PRINT, False)
       
   240                     MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   241             else:
       
   242                 self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   243                 self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   244                 self.FileMenu.Enable(wx.ID_PRINT, False)
       
   245                 MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   246             self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
       
   247             project_modified = not self.Controler.ProjectIsSaved()
       
   248             self.FileMenu.Enable(wx.ID_SAVE, project_modified)
       
   249             MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
       
   250             self.FileMenu.Enable(wx.ID_PROPERTIES, True)
       
   251             self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
       
   252             self.FileMenu.Enable(wx.ID_SAVEAS, True)
       
   253             MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
       
   254             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, True)
       
   255         else:
       
   256             self.FileMenu.Enable(wx.ID_CLOSE, False)
       
   257             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
       
   258             self.FileMenu.Enable(wx.ID_PREVIEW, False)
       
   259             self.FileMenu.Enable(wx.ID_PRINT, False)
       
   260             MenuToolBar.EnableTool(wx.ID_PRINT, False)
       
   261             self.FileMenu.Enable(wx.ID_SAVE, False)
       
   262             MenuToolBar.EnableTool(wx.ID_SAVE, False)
       
   263             self.FileMenu.Enable(wx.ID_PROPERTIES, False)
       
   264             self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
       
   265             self.FileMenu.Enable(wx.ID_SAVEAS, False)
       
   266             MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
       
   267             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, False)
       
   268 
       
   269     def OnNewProjectMenu(self, event):
       
   270         if self.Controler is not None and not self.CheckSaveBeforeClosing():
       
   271             return
       
   272         dialog = ProjectDialog(self)
       
   273         if dialog.ShowModal() == wx.ID_OK:
       
   274             properties = dialog.GetValues()
       
   275             self.ResetView()
       
   276             self.Controler = PLCControler()
       
   277             self.Controler.CreateNewProject(properties)
       
   278             self.LibraryPanel.SetController(self.Controler)
       
   279             self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, 
       
   280                           LIBRARYTREE)
       
   281 
       
   282     def OnOpenProjectMenu(self, event):
       
   283         if self.Controler is not None and not self.CheckSaveBeforeClosing():
       
   284             return
       
   285         filepath = ""
       
   286         if self.Controler is not None:
       
   287             filepath = self.Controler.GetFilePath()
       
   288         if filepath != "":
       
   289             directory = os.path.dirname(filepath)
       
   290         else:
       
   291             directory = os.getcwd()
       
   292         
       
   293         result = None
       
   294         
       
   295         dialog = wx.FileDialog(self, _("Choose a file"), directory, "",  _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
       
   296         if dialog.ShowModal() == wx.ID_OK:
       
   297             filepath = dialog.GetPath()
       
   298             if os.path.isfile(filepath):
       
   299                 self.ResetView()
       
   300                 controler = PLCControler()
       
   301                 result = controler.OpenXMLFile(filepath)
       
   302                 if result is None:
       
   303                     self.Controler = controler
       
   304                     self.LibraryPanel.SetController(controler)
       
   305                     self.ProjectTree.Enable(True)
       
   306                     self.PouInstanceVariablesPanel.SetController(controler)
       
   307                     self.LoadProjectLayout()
       
   308                     self._Refresh(PROJECTTREE, LIBRARYTREE)
       
   309             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   310         dialog.Destroy()
       
   311         
       
   312         if result is not None:
       
   313             self.ShowErrorMessage(result)
       
   314     
       
   315     def OnCloseProjectMenu(self, event):
       
   316         if not self.CheckSaveBeforeClosing():
       
   317             return
       
   318         self.SaveProjectLayout()
       
   319         self.ResetView()
       
   320         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
       
   321 
       
   322     def OnSaveProjectMenu(self, event):
       
   323         self.SaveProject()
       
   324 
       
   325     def OnSaveProjectAsMenu(self, event):
       
   326         self.SaveProjectAs()
       
   327 
       
   328     def OnGenerateProgramMenu(self, event):
       
   329         dialog = wx.FileDialog(self, _("Choose a file"), os.getcwd(), self.Controler.GetProgramFilePath(),  _("ST files (*.st)|*.st|All files|*.*"), wx.SAVE|wx.CHANGE_DIR)
       
   330         if dialog.ShowModal() == wx.ID_OK:
       
   331             filepath = dialog.GetPath()
       
   332             message_text = ""
       
   333             header, icon = _("Done"), wx.ICON_INFORMATION
       
   334             if os.path.isdir(os.path.dirname(filepath)):
       
   335                 program, errors, warnings = self.Controler.GenerateProgram(filepath)
       
   336                 message_text += "".join([_("warning: %s\n") for warning in warnings])
       
   337                 if len(errors) > 0:
       
   338                     message_text += "".join([_("error: %s\n") for error in errors])
       
   339                     message_text += _("Can't generate program to file %s!")%filepath
       
   340                     header, icon = _("Error"), wx.ICON_ERROR
       
   341                 else:
       
   342                     message_text += _("Program was successfully generated!")
       
   343             else:
       
   344                 message_text += _("\"%s\" is not a valid folder!")%os.path.dirname(filepath)
       
   345                 header, icon = _("Error"), wx.ICON_ERROR
       
   346             message = wx.MessageDialog(self, message_text, header, wx.OK|icon)
       
   347             message.ShowModal()
       
   348             message.Destroy()
       
   349         dialog.Destroy()
       
   350 
       
   351     def OnPLCOpenEditorMenu(self, event):
       
   352         wx.MessageBox(_("No documentation available.\nComing soon."))
       
   353         
       
   354     def OnPLCOpenMenu(self, event):
       
   355         open_pdf(os.path.join(CWD, "plcopen", "TC6_XML_V101.pdf"))
       
   356     
       
   357     def OnAboutMenu(self, event):
       
   358         OpenHtmlFrame(self,_("About PLCOpenEditor"), os.path.join(CWD, "doc", "plcopen_about.html"), wx.Size(350, 350))
       
   359 
       
   360     def SaveProject(self):
       
   361         result = self.Controler.SaveXMLFile()
       
   362         if not result:
       
   363             self.SaveProjectAs()
       
   364         else:
       
   365             self._Refresh(TITLE, FILEMENU, PAGETITLES)
       
   366         
       
   367     def SaveProjectAs(self):
       
   368         filepath = self.Controler.GetFilePath()
       
   369         if filepath != "":
       
   370             directory, filename = os.path.split(filepath)
       
   371         else:
       
   372             directory, filename = os.getcwd(), "%(projectName)s.xml"%self.Controler.GetProjectProperties()
       
   373         dialog = wx.FileDialog(self, _("Choose a file"), directory, filename,  _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.SAVE|wx.OVERWRITE_PROMPT)
       
   374         if dialog.ShowModal() == wx.ID_OK:
       
   375             filepath = dialog.GetPath()
       
   376             if os.path.isdir(os.path.dirname(filepath)):
       
   377                 result = self.Controler.SaveXMLFile(filepath)
       
   378                 if not result:
       
   379                     self.ShowErrorMessage(_("Can't save project to file %s!")%filepath)
       
   380             else:
       
   381                 self.ShowErrorMessage(_("\"%s\" is not a valid folder!")%os.path.dirname(filepath))
       
   382             self._Refresh(TITLE, FILEMENU, PAGETITLES)
       
   383         dialog.Destroy()
       
   384 
       
   385 #-------------------------------------------------------------------------------
       
   386 #                            Debug Variables Panel
       
   387 #-------------------------------------------------------------------------------
       
   388 
       
   389 #-------------------------------------------------------------------------------
       
   390 #                               Viewer Printout
       
   391 #-------------------------------------------------------------------------------
       
   392 
       
   393 UPPER_DIV = lambda x, y: (x / y) + {True : 0, False : 1}[(x % y) == 0]
       
   394 
       
   395 class GraphicPrintout(wx.Printout):
       
   396     def __init__(self, viewer, page_size, margins, preview = False):
       
   397         wx.Printout.__init__(self)
       
   398         self.Viewer = viewer
       
   399         self.PageSize = page_size
       
   400         if self.PageSize[0] == 0 or self.PageSize[1] == 0:
       
   401             self.PageSize = (1050, 1485)
       
   402         self.Preview = preview
       
   403         self.Margins = margins
       
   404         self.FontSize = 5
       
   405         self.TextMargin = 3
       
   406         
       
   407         maxx, maxy = viewer.GetMaxSize()
       
   408         self.PageGrid = (UPPER_DIV(maxx, self.PageSize[0]), 
       
   409                          UPPER_DIV(maxy, self.PageSize[1]))
       
   410         
       
   411     def GetPageNumber(self):
       
   412         return self.PageGrid[0] * self.PageGrid[1]
       
   413     
       
   414     def HasPage(self, page):
       
   415         return page <= self.GetPageNumber()
       
   416         
       
   417     def GetPageInfo(self):
       
   418         page_number = self.GetPageNumber()
       
   419         return (1, page_number, 1, page_number)
       
   420 
       
   421     def OnBeginDocument(self, startPage, endPage):
       
   422         dc = self.GetDC()
       
   423         if not self.Preview and isinstance(dc, wx.PostScriptDC):
       
   424             dc.SetResolution(720)
       
   425         super(GraphicPrintout, self).OnBeginDocument(startPage, endPage)
       
   426 
       
   427     def OnPrintPage(self, page):
       
   428         dc = self.GetDC()
       
   429         dc.SetUserScale(1.0, 1.0)
       
   430         dc.SetDeviceOrigin(0, 0)
       
   431         dc.printing = not self.Preview
       
   432         
       
   433         # Get the size of the DC in pixels
       
   434         ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
       
   435         ppiScreenX, ppiScreenY = self.GetPPIScreen()
       
   436         pw, ph = self.GetPageSizePixels()
       
   437         dw, dh = dc.GetSizeTuple()
       
   438         Xscale = (float(dw) * float(ppiPrinterX)) / (float(pw) * 25.4)
       
   439         Yscale = (float(dh) * float(ppiPrinterY)) / (float(ph) * 25.4)
       
   440         
       
   441         fontsize = self.FontSize * Yscale
       
   442         text_margin = self.TextMargin * Yscale
       
   443         
       
   444         margin_left = self.Margins[0].x * Xscale
       
   445         margin_top = self.Margins[0].y * Yscale
       
   446         area_width = dw - self.Margins[1].x * Xscale - margin_left
       
   447         area_height = dh - self.Margins[1].y * Yscale - margin_top
       
   448         
       
   449         dc.SetPen(MiterPen(wx.BLACK))
       
   450         dc.SetBrush(wx.TRANSPARENT_BRUSH)    
       
   451         dc.DrawRectangle(margin_left, margin_top, area_width, area_height)
       
   452         
       
   453         dc.SetFont(wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
       
   454         dc.SetTextForeground(wx.BLACK)
       
   455         block_name = " - ".join(self.Viewer.GetTagName().split("::")[1:])
       
   456         text_width, text_height = dc.GetTextExtent(block_name)
       
   457         dc.DrawText(block_name, margin_left, margin_top - text_height - self.TextMargin)
       
   458         dc.DrawText(_("Page: %d") % page, margin_left, margin_top + area_height + self.TextMargin)
       
   459         
       
   460         # Calculate the position on the DC for centering the graphic
       
   461         posX = area_width * ((page - 1) % self.PageGrid[0])
       
   462         posY = area_height * ((page - 1) / self.PageGrid[0])
       
   463 
       
   464         scaleX = float(area_width) / float(self.PageSize[0])
       
   465         scaleY = float(area_height) / float(self.PageSize[1])
       
   466         scale = min(scaleX, scaleY)
       
   467 
       
   468         # Set the scale and origin
       
   469         dc.SetDeviceOrigin(-posX + margin_left, -posY + margin_top)
       
   470         dc.SetClippingRegion(posX, posY, self.PageSize[0] * scale, self.PageSize[1] * scale)
       
   471         dc.SetUserScale(scale, scale)
       
   472         
       
   473         #-------------------------------------------
       
   474         
       
   475         self.Viewer.DoDrawing(dc, True)
       
   476         
       
   477         return True
       
   478 
       
   479 #-------------------------------------------------------------------------------
       
   480 #                               Exception Handler
       
   481 #-------------------------------------------------------------------------------
       
   482 
       
   483 Max_Traceback_List_Size = 20
       
   484 
       
   485 def Display_Exception_Dialog(e_type,e_value,e_tb):
       
   486     trcbck_lst = []
       
   487     for i,line in enumerate(traceback.extract_tb(e_tb)):
       
   488         trcbck = " " + str(i+1) + _(". ")
       
   489         if line[0].find(os.getcwd()) == -1:
       
   490             trcbck += _("file : ") + str(line[0]) + _(",   ")
       
   491         else:
       
   492             trcbck += _("file : ") + str(line[0][len(os.getcwd()):]) + _(",   ")
       
   493         trcbck += _("line : ") + str(line[1]) + _(",   ") + _("function : ") + str(line[2])
       
   494         trcbck_lst.append(trcbck)
       
   495         
       
   496     # Allow clicking....
       
   497     cap = wx.Window_GetCapture()
       
   498     if cap:
       
   499         cap.ReleaseMouse()
       
   500 
       
   501     dlg = wx.SingleChoiceDialog(None, 
       
   502         _("""
       
   503 An error has occurred.
       
   504 
       
   505 Click OK to save an error report.
       
   506 
       
   507 Please be kind enough to send this file to:
       
   508 edouard.tisserant@gmail.com
       
   509 
       
   510 Error:
       
   511 """) +
       
   512         str(e_type) + _(" : ") + str(e_value), 
       
   513         _("Error"),
       
   514         trcbck_lst)
       
   515     try:
       
   516         res = (dlg.ShowModal() == wx.ID_OK)
       
   517     finally:
       
   518         dlg.Destroy()
       
   519 
       
   520     return res
       
   521 
       
   522 def Display_Error_Dialog(e_value):
       
   523     message = wx.MessageDialog(None, str(e_value), _("Error"), wx.OK|wx.ICON_ERROR)
       
   524     message.ShowModal()
       
   525     message.Destroy()
       
   526 
       
   527 def get_last_traceback(tb):
       
   528     while tb.tb_next:
       
   529         tb = tb.tb_next
       
   530     return tb
       
   531 
       
   532 
       
   533 def format_namespace(d, indent='    '):
       
   534     return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])
       
   535 
       
   536 
       
   537 ignored_exceptions = [] # a problem with a line in a module is only reported once per session
       
   538 
       
   539 def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):
       
   540     
       
   541     def handle_exception(e_type, e_value, e_traceback):
       
   542         traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
       
   543         last_tb = get_last_traceback(e_traceback)
       
   544         ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
       
   545         if str(e_value).startswith("!!!"):
       
   546             Display_Error_Dialog(e_value)
       
   547         elif ex not in ignored_exceptions:
       
   548             result = Display_Exception_Dialog(e_type,e_value,e_traceback)
       
   549             if result:
       
   550                 ignored_exceptions.append(ex)
       
   551                 info = {
       
   552                     'app-title' : wx.GetApp().GetAppName(), # app_title
       
   553                     'app-version' : app_version,
       
   554                     'wx-version' : wx.VERSION_STRING,
       
   555                     'wx-platform' : wx.Platform,
       
   556                     'python-version' : platform.python_version(), #sys.version.split()[0],
       
   557                     'platform' : platform.platform(),
       
   558                     'e-type' : e_type,
       
   559                     'e-value' : e_value,
       
   560                     'date' : time.ctime(),
       
   561                     'cwd' : os.getcwd(),
       
   562                     }
       
   563                 if e_traceback:
       
   564                     info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
       
   565                     last_tb = get_last_traceback(e_traceback)
       
   566                     exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred
       
   567                     info['locals'] = format_namespace(exception_locals)
       
   568                     if 'self' in exception_locals:
       
   569                         info['self'] = format_namespace(exception_locals['self'].__dict__)
       
   570                 
       
   571                 output = open(path+os.sep+"bug_report_"+info['date'].replace(':','-').replace(' ','_')+".txt",'w')
       
   572                 lst = info.keys()
       
   573                 lst.sort()
       
   574                 for a in lst:
       
   575                     output.write(a+":\n"+str(info[a])+"\n\n")
       
   576 
       
   577     #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
       
   578     sys.excepthook = handle_exception
       
   579 
       
   580 if __name__ == '__main__':
       
   581     wx.InitAllImageHandlers()
       
   582     
       
   583     # Install a exception handle for bug reports
       
   584     AddExceptHook(os.getcwd(),__version__)
       
   585     
       
   586     frame = PLCOpenEditor(None, fileOpen=fileOpen)
       
   587 
       
   588     frame.Show()
       
   589     app.MainLoop()
       
   590