svghmi/svghmi.py
branchsvghmi
changeset 3201 6dadc1690284
parent 3197 0f41c1e2c121
child 3208 b5330d76e225
equal deleted inserted replaced
3197:0f41c1e2c121 3201:6dadc1690284
     8 
     8 
     9 from __future__ import absolute_import
     9 from __future__ import absolute_import
    10 import os
    10 import os
    11 import shutil
    11 import shutil
    12 import hashlib
    12 import hashlib
    13 import weakref
       
    14 import shlex
    13 import shlex
    15 import time
    14 import time
    16 
    15 
    17 import wx
    16 import wx
    18 
    17 
    19 from lxml import etree
    18 from lxml import etree
    20 from lxml.etree import XSLTApplyError
    19 from lxml.etree import XSLTApplyError
    21 
    20 
    22 from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
       
    23 import util.paths as paths
    21 import util.paths as paths
    24 from POULibrary import POULibrary
    22 from POULibrary import POULibrary
    25 from docutil import open_svg, get_inkscape_path
    23 from docutil import open_svg, get_inkscape_path
    26 
    24 
    27 from util.ProcessLogger import ProcessLogger
    25 from util.ProcessLogger import ProcessLogger
    28 from runtime.typemapping import DebugTypesSize
    26 from runtime.typemapping import DebugTypesSize
    29 import targets
    27 import targets
    30 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
    28 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
    31 from XSLTransform import XSLTransform
    29 from XSLTransform import XSLTransform
    32 from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations, MatchTranslations, TranslationToEtree, open_pofile
    30 from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations,\
       
    31                         MatchTranslations, TranslationToEtree, open_pofile
    33 from svghmi.hmi_tree import HMI_TYPES, HMITreeNode, SPECIAL_NODES 
    32 from svghmi.hmi_tree import HMI_TYPES, HMITreeNode, SPECIAL_NODES 
       
    33 from svghmi.ui import SVGHMI_UI
    34 
    34 
    35 
    35 
    36 ScriptDirectory = paths.AbsDir(__file__)
    36 ScriptDirectory = paths.AbsDir(__file__)
    37 
    37 
    38 
    38 
   144                         ".".join(new_node.path))
   144                         ".".join(new_node.path))
   145 
   145 
   146                 self.FatalError("SVGHMI : " + message)
   146                 self.FatalError("SVGHMI : " + message)
   147 
   147 
   148         if on_hmitree_update is not None:
   148         if on_hmitree_update is not None:
   149             on_hmitree_update()
   149             on_hmitree_update(hmi_tree_root)
   150 
   150 
   151         variable_decl_array = []
   151         variable_decl_array = []
   152         extern_variables_declarations = []
   152         extern_variables_declarations = []
   153         buf_index = 0
   153         buf_index = 0
   154         item_count = 0
   154         item_count = 0
   235                 #         ^
   235                 #         ^
   236                 # note the double zero after "runtime_", 
   236                 # note the double zero after "runtime_", 
   237                 # to ensure placement before other CTN generated code in execution order
   237                 # to ensure placement before other CTN generated code in execution order
   238 
   238 
   239 
   239 
   240 def SVGHMIEditorUpdater(ref):
   240 def Register_SVGHMI_UI_for_HMI_tree_updates(ref):
   241     def SVGHMIEditorUpdate():
   241     global on_hmitree_update
   242         o = ref()
   242     def HMITreeUpdate(_hmi_tree_root):
   243         if o is not None:
   243         obj = ref()
   244             wx.CallAfter(o.MakeTree)
   244         if obj is not None:
   245     return SVGHMIEditorUpdate
   245             obj.HMITreeUpdate(_hmi_tree_root)
   246 
   246 
   247 class HMITreeSelector(wx.TreeCtrl):
   247     on_hmitree_update = HMITreeUpdate
   248     def __init__(self, parent):
       
   249         global on_hmitree_update
       
   250         wx.TreeCtrl.__init__(self, parent, style=(
       
   251             wx.TR_MULTIPLE |
       
   252             wx.TR_HAS_BUTTONS |
       
   253             wx.SUNKEN_BORDER |
       
   254             wx.TR_LINES_AT_ROOT))
       
   255 
       
   256         on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
       
   257         self.MakeTree()
       
   258 
       
   259     def _recurseTree(self, current_hmitree_root, current_tc_root):
       
   260         for c in current_hmitree_root.children:
       
   261             if hasattr(c, "children"):
       
   262                 display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
       
   263                                if c.hmiclass is not None else c.name
       
   264                 tc_child = self.AppendItem(current_tc_root, display_name)
       
   265                 self.SetPyData(tc_child, None) # TODO
       
   266 
       
   267                 self._recurseTree(c,tc_child)
       
   268             else:
       
   269                 display_name = '{} {}'.format(c.nodetype[4:], c.name)
       
   270                 tc_child = self.AppendItem(current_tc_root, display_name)
       
   271                 self.SetPyData(tc_child, None) # TODO
       
   272 
       
   273     def MakeTree(self):
       
   274         global hmi_tree_root
       
   275 
       
   276         self.Freeze()
       
   277 
       
   278         self.root = None
       
   279         self.DeleteAllItems()
       
   280 
       
   281         root_display_name = _("Please build to see HMI Tree") \
       
   282             if hmi_tree_root is None else "HMI"
       
   283         self.root = self.AddRoot(root_display_name)
       
   284         self.SetPyData(self.root, None)
       
   285 
       
   286         if hmi_tree_root is not None:
       
   287             self._recurseTree(hmi_tree_root, self.root)
       
   288             self.Expand(self.root)
       
   289 
       
   290         self.Thaw()
       
   291 
       
   292 class WidgetPicker(wx.TreeCtrl):
       
   293     def __init__(self, parent, initialdir=None):
       
   294         wx.TreeCtrl.__init__(self, parent, style=(
       
   295             wx.TR_MULTIPLE |
       
   296             wx.TR_HAS_BUTTONS |
       
   297             wx.SUNKEN_BORDER |
       
   298             wx.TR_LINES_AT_ROOT))
       
   299 
       
   300         self.MakeTree(initialdir)
       
   301 
       
   302     def _recurseTree(self, current_dir, current_tc_root, dirlist):
       
   303         """
       
   304         recurse through subdirectories, but creates tree nodes 
       
   305         only when (sub)directory conbtains .svg file
       
   306         """
       
   307         res = []
       
   308         for f in sorted(os.listdir(current_dir)):
       
   309             p = os.path.join(current_dir,f)
       
   310             if os.path.isdir(p):
       
   311 
       
   312                 r = self._recurseTree(p, current_tc_root, dirlist + [f])
       
   313                 if len(r) > 0 :
       
   314                     res = r
       
   315                     dirlist = []
       
   316                     current_tc_root = res.pop()
       
   317 
       
   318             elif os.path.splitext(f)[1].upper() == ".SVG":
       
   319                 if len(dirlist) > 0 :
       
   320                     res = []
       
   321                     for d in dirlist:
       
   322                         current_tc_root = self.AppendItem(current_tc_root, d)
       
   323                         res.append(current_tc_root)
       
   324                         self.SetPyData(current_tc_root, None)
       
   325                     dirlist = []
       
   326                     res.pop()
       
   327                 tc_child = self.AppendItem(current_tc_root, f)
       
   328                 self.SetPyData(tc_child, p)
       
   329         return res
       
   330 
       
   331     def MakeTree(self, lib_dir = None):
       
   332         global hmi_tree_root
       
   333 
       
   334         self.Freeze()
       
   335 
       
   336         self.root = None
       
   337         self.DeleteAllItems()
       
   338 
       
   339         root_display_name = _("Please select widget library directory") \
       
   340             if lib_dir is None else os.path.basename(lib_dir)
       
   341         self.root = self.AddRoot(root_display_name)
       
   342         self.SetPyData(self.root, None)
       
   343 
       
   344         if lib_dir is not None:
       
   345             self._recurseTree(lib_dir, self.root, [])
       
   346             self.Expand(self.root)
       
   347 
       
   348         self.Thaw()
       
   349 
       
   350 _conf_key = "SVGHMIWidgetLib"
       
   351 _preview_height = 200
       
   352 class WidgetLibBrowser(wx.Panel):
       
   353     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
       
   354                  size=wx.DefaultSize):
       
   355 
       
   356         wx.Panel.__init__(self, parent, id, pos, size)     
       
   357 
       
   358         self.bmp = None
       
   359         self.msg = None
       
   360         self.hmitree_node = None
       
   361         self.selected_SVG = None
       
   362 
       
   363         self.Config = wx.ConfigBase.Get()
       
   364         self.libdir = self.RecallLibDir()
       
   365 
       
   366         sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
       
   367         sizer.AddGrowableCol(0)
       
   368         sizer.AddGrowableRow(1)
       
   369         self.libbutton = wx.Button(self, -1, _("Select SVG widget library"))
       
   370         self.widgetpicker = WidgetPicker(self, self.libdir)
       
   371         self.preview = wx.Panel(self, size=(-1, _preview_height + 10))  #, style=wx.SIMPLE_BORDER)
       
   372         #self.preview.SetBackgroundColour(wx.WHITE)
       
   373         sizer.AddWindow(self.libbutton, flag=wx.GROW)
       
   374         sizer.AddWindow(self.widgetpicker, flag=wx.GROW)
       
   375         sizer.AddWindow(self.preview, flag=wx.GROW)
       
   376         sizer.Layout()
       
   377         self.SetAutoLayout(True)
       
   378         self.SetSizer(sizer)
       
   379         sizer.Fit(self)
       
   380         self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton)
       
   381         self.preview.Bind(wx.EVT_PAINT, self.OnPaint)
       
   382 
       
   383         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker)
       
   384 
       
   385         self.msg = _("Drag selected Widget from here to Inkscape")
       
   386 
       
   387     def RecallLibDir(self):
       
   388         conf = self.Config.Read(_conf_key)
       
   389         if len(conf) == 0:
       
   390             return None
       
   391         else:
       
   392             return DecodeFileSystemPath(conf)
       
   393 
       
   394     def RememberLibDir(self, path):
       
   395         self.Config.Write(_conf_key,
       
   396                           EncodeFileSystemPath(path))
       
   397         self.Config.Flush()
       
   398 
       
   399     def DrawPreview(self):
       
   400         """
       
   401         Refresh preview panel 
       
   402         """
       
   403         # Init preview panel paint device context
       
   404         dc = wx.PaintDC(self.preview)
       
   405         dc.Clear()
       
   406 
       
   407         if self.bmp:
       
   408             # Get Preview panel size
       
   409             sz = self.preview.GetClientSize()
       
   410             w = self.bmp.GetWidth()
       
   411             dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
       
   412 
       
   413         if self.msg:
       
   414             dc.SetFont(self.GetFont())
       
   415             dc.DrawText(self.msg, 25,25)
       
   416 
       
   417 
       
   418     def OnSelectLibDir(self, event):
       
   419         defaultpath = self.RecallLibDir()
       
   420         if defaultpath == None:
       
   421             defaultpath = os.path.expanduser("~")
       
   422 
       
   423         dialog = wx.DirDialog(self, _("Choose a widget library"), defaultpath,
       
   424                               style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
       
   425 
       
   426         if dialog.ShowModal() == wx.ID_OK:
       
   427             self.libdir = dialog.GetPath()
       
   428             self.RememberLibDir(self.libdir)
       
   429             self.widgetpicker.MakeTree(self.libdir)
       
   430 
       
   431         dialog.Destroy()
       
   432 
       
   433     def OnPaint(self, event):
       
   434         """
       
   435         Called when Preview panel needs to be redrawn
       
   436         @param event: wx.PaintEvent
       
   437         """
       
   438         self.DrawPreview()
       
   439         event.Skip()
       
   440 
       
   441     def GenThumbnail(self, svgpath, thumbpath):
       
   442         inkpath = get_inkscape_path()
       
   443         if inkpath is None:
       
   444             self.msg = _("Inkscape is not installed.")
       
   445             return False
       
   446         # TODO: spawn a thread, to decouple thumbnail gen
       
   447         status, result, _err_result = ProcessLogger(
       
   448             None,
       
   449             '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
       
   450             '" -D -h ' + str(_preview_height)).spin()
       
   451         if status != 0:
       
   452             self.msg = _("Inkscape couldn't generate thumbnail.")
       
   453             return False
       
   454         return True
       
   455 
       
   456     def OnWidgetSelection(self, event):
       
   457         """
       
   458         Called when tree item is selected
       
   459         @param event: wx.TreeEvent
       
   460         """
       
   461         item_pydata = self.widgetpicker.GetPyData(event.GetItem())
       
   462         if item_pydata is not None:
       
   463             svgpath = item_pydata
       
   464             dname = os.path.dirname(svgpath)
       
   465             fname = os.path.basename(svgpath)
       
   466             hasher = hashlib.new('md5')
       
   467             with open(svgpath, 'rb') as afile:
       
   468                 while True:
       
   469                     buf = afile.read(65536)
       
   470                     if len(buf) > 0:
       
   471                         hasher.update(buf)
       
   472                     else:
       
   473                         break
       
   474             digest = hasher.hexdigest()
       
   475             thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
       
   476             thumbdir = os.path.join(dname, ".svghmithumbs") 
       
   477             thumbpath = os.path.join(thumbdir, thumbfname) 
       
   478 
       
   479             self.msg = None
       
   480             have_thumb = os.path.exists(thumbpath)
       
   481 
       
   482             if not have_thumb:
       
   483                 try:
       
   484                     if not os.path.exists(thumbdir):
       
   485                         os.mkdir(thumbdir)
       
   486                 except IOError:
       
   487                     self.msg = _("Widget library must be writable")
       
   488                 else:
       
   489                     have_thumb = self.GenThumbnail(svgpath, thumbpath)
       
   490 
       
   491             self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
       
   492 
       
   493             self.selected_SVG = svgpath if have_thumb else None
       
   494             self.ValidateWidget()
       
   495 
       
   496             self.Refresh()
       
   497         event.Skip()
       
   498 
       
   499     def OnHMITreeNodeSelection(self, hmitree_node):
       
   500         self.hmitree_node = hmitree_node
       
   501         self.ValidateWidget()
       
   502         self.Refresh()
       
   503 
       
   504     def ValidateWidget(self):
       
   505         if self.selected_SVG is not None:
       
   506             if self.hmitree_node is not None:
       
   507                 pass
       
   508         # XXX TODO: 
       
   509         #      - check SVG is valid for selected HMI tree item
       
   510         #      - prepare for D'n'D
       
   511 
       
   512 
       
   513 class HMITreeView(wx.SplitterWindow):
       
   514 
       
   515     def __init__(self, parent):
       
   516         wx.SplitterWindow.__init__(self, parent,
       
   517                                    style=wx.SUNKEN_BORDER | wx.SP_3D)
       
   518 
       
   519         self.SelectionTree = HMITreeSelector(self)
       
   520         self.Staging = WidgetLibBrowser(self)
       
   521         self.SplitVertically(self.SelectionTree, self.Staging, 300)
       
   522 
   248 
   523 
   249 
   524 class SVGHMIEditor(ConfTreeNodeEditor):
   250 class SVGHMIEditor(ConfTreeNodeEditor):
   525     CONFNODEEDITOR_TABS = [
   251     CONFNODEEDITOR_TABS = [
   526         (_("HMI Tree"), "CreateHMITreeView")]
   252         (_("HMI Tree"), "CreateSVGHMI_UI")]
   527 
   253 
   528     def CreateHMITreeView(self, parent):
   254     def CreateSVGHMI_UI(self, parent):
   529         global hmi_tree_root
   255         global hmi_tree_root
   530 
   256 
   531         if hmi_tree_root is None:
   257         if hmi_tree_root is None:
   532             buildpath = self.Controler.GetCTRoot()._getBuildPath()
   258             buildpath = self.Controler.GetCTRoot()._getBuildPath()
   533             hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
   259             hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
   534             if os.path.exists(hmitree_backup_path):
   260             if os.path.exists(hmitree_backup_path):
   535                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
   261                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
   536                 hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
   262                 hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
   537 
   263 
   538 
   264         return SVGHMI_UI(parent, Register_SVGHMI_UI_for_HMI_tree_updates)
   539         #self.HMITreeView = HMITreeView(self)
       
   540         #return HMITreeSelector(parent)
       
   541         return HMITreeView(parent)
       
   542 
   265 
   543 class SVGHMI(object):
   266 class SVGHMI(object):
   544     XSD = """<?xml version="1.0" encoding="utf-8" ?>
   267     XSD = """<?xml version="1.0" encoding="utf-8" ?>
   545     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   268     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   546       <xsd:element name="SVGHMI">
   269       <xsd:element name="SVGHMI">