svghmi/svghmi.py
branchsvghmi
changeset 3193 8006bb60a4dd
parent 3180 c059026d8626
child 3197 0f41c1e2c121
equal deleted inserted replaced
3192:8df40690efb0 3193:8006bb60a4dd
    19 import wx
    19 import wx
    20 
    20 
    21 from lxml import etree
    21 from lxml import etree
    22 from lxml.etree import XSLTApplyError
    22 from lxml.etree import XSLTApplyError
    23 
    23 
       
    24 from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
    24 import util.paths as paths
    25 import util.paths as paths
    25 from POULibrary import POULibrary
    26 from POULibrary import POULibrary
    26 from docutil import open_svg, get_inkscape_path
    27 from docutil import open_svg, get_inkscape_path
    27 
    28 
    28 from util.ProcessLogger import ProcessLogger
    29 from util.ProcessLogger import ProcessLogger
   371                 #         ^
   372                 #         ^
   372                 # note the double zero after "runtime_", 
   373                 # note the double zero after "runtime_", 
   373                 # to ensure placement before other CTN generated code in execution order
   374                 # to ensure placement before other CTN generated code in execution order
   374 
   375 
   375 
   376 
       
   377 def SVGHMIEditorUpdater(ref):
       
   378     def SVGHMIEditorUpdate():
       
   379         o = ref()
       
   380         if o is not None:
       
   381             wx.CallAfter(o.MakeTree)
       
   382     return SVGHMIEditorUpdate
       
   383 
   376 class HMITreeSelector(wx.TreeCtrl):
   384 class HMITreeSelector(wx.TreeCtrl):
   377     def __init__(self, parent):
   385     def __init__(self, parent):
   378         global on_hmitree_update
   386         global on_hmitree_update
   379         wx.TreeCtrl.__init__(self, parent, style=(
   387         wx.TreeCtrl.__init__(self, parent, style=(
   380             wx.TR_MULTIPLE |
   388             wx.TR_MULTIPLE |
   381             wx.TR_HAS_BUTTONS |
   389             wx.TR_HAS_BUTTONS |
   382             wx.SUNKEN_BORDER |
   390             wx.SUNKEN_BORDER |
   383             wx.TR_LINES_AT_ROOT))
   391             wx.TR_LINES_AT_ROOT))
   384 
   392 
   385         on_hmitree_update = self.SVGHMIEditorUpdater()
   393         on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
   386         self.MakeTree()
   394         self.MakeTree()
   387 
   395 
   388     def _recurseTree(self, current_hmitree_root, current_tc_root):
   396     def _recurseTree(self, current_hmitree_root, current_tc_root):
   389         for c in current_hmitree_root.children:
   397         for c in current_hmitree_root.children:
   390             if hasattr(c, "children"):
   398             if hasattr(c, "children"):
   391                 display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
   399                 display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
   392                                if c.hmiclass is not None else c.name
   400                                if c.hmiclass is not None else c.name
   393                 tc_child = self.AppendItem(current_tc_root, display_name)
   401                 tc_child = self.AppendItem(current_tc_root, display_name)
   394                 self.SetPyData(tc_child, None)
   402                 self.SetPyData(tc_child, None) # TODO
   395 
   403 
   396                 self._recurseTree(c,tc_child)
   404                 self._recurseTree(c,tc_child)
   397             else:
   405             else:
   398                 display_name = '{} {}'.format(c.nodetype[4:], c.name)
   406                 display_name = '{} {}'.format(c.nodetype[4:], c.name)
   399                 tc_child = self.AppendItem(current_tc_root, display_name)
   407                 tc_child = self.AppendItem(current_tc_root, display_name)
   400                 self.SetPyData(tc_child, None)
   408                 self.SetPyData(tc_child, None) # TODO
   401 
   409 
   402     def MakeTree(self):
   410     def MakeTree(self):
   403         global hmi_tree_root
   411         global hmi_tree_root
   404 
   412 
   405         self.Freeze()
   413         self.Freeze()
   406 
   414 
   407         self.root = None
   415         self.root = None
   408         self.DeleteAllItems()
   416         self.DeleteAllItems()
   409 
   417 
   410         root_display_name = _("Please build to see HMI Tree") if hmi_tree_root is None else "HMI"
   418         root_display_name = _("Please build to see HMI Tree") \
       
   419             if hmi_tree_root is None else "HMI"
   411         self.root = self.AddRoot(root_display_name)
   420         self.root = self.AddRoot(root_display_name)
   412         self.SetPyData(self.root, None)
   421         self.SetPyData(self.root, None)
   413 
   422 
   414         if hmi_tree_root is not None:
   423         if hmi_tree_root is not None:
   415             self._recurseTree(hmi_tree_root, self.root)
   424             self._recurseTree(hmi_tree_root, self.root)
   416             self.Expand(self.root)
   425             self.Expand(self.root)
   417 
   426 
   418         self.Thaw()
   427         self.Thaw()
   419 
   428 
   420     def SVGHMIEditorUpdater(self):
   429 class WidgetPicker(wx.TreeCtrl):
   421         selfref = weakref.ref(self)
   430     def __init__(self, parent, initialdir=None):
   422         def SVGHMIEditorUpdate():
   431         wx.TreeCtrl.__init__(self, parent, style=(
   423             o = selfref()
   432             wx.TR_MULTIPLE |
   424             if o is not None:
   433             wx.TR_HAS_BUTTONS |
   425                 wx.CallAfter(o.MakeTree)
   434             wx.SUNKEN_BORDER |
   426         return SVGHMIEditorUpdate
   435             wx.TR_LINES_AT_ROOT))
       
   436 
       
   437         self.MakeTree(initialdir)
       
   438 
       
   439     def _recurseTree(self, current_dir, current_tc_root, dirlist):
       
   440         """
       
   441         recurse through subdirectories, but creates tree nodes 
       
   442         only when (sub)directory conbtains .svg file
       
   443         """
       
   444         res = []
       
   445         for f in sorted(os.listdir(current_dir)):
       
   446             p = os.path.join(current_dir,f)
       
   447             if os.path.isdir(p):
       
   448 
       
   449                 r = self._recurseTree(p, current_tc_root, dirlist + [f])
       
   450                 if len(r) > 0 :
       
   451                     res = r
       
   452                     dirlist = []
       
   453                     current_tc_root = res.pop()
       
   454 
       
   455             elif os.path.splitext(f)[1].upper() == ".SVG":
       
   456                 if len(dirlist) > 0 :
       
   457                     res = []
       
   458                     for d in dirlist:
       
   459                         current_tc_root = self.AppendItem(current_tc_root, d)
       
   460                         res.append(current_tc_root)
       
   461                         self.SetPyData(current_tc_root, None)
       
   462                     dirlist = []
       
   463                     res.pop()
       
   464                 tc_child = self.AppendItem(current_tc_root, f)
       
   465                 self.SetPyData(tc_child, p)
       
   466         return res
       
   467 
       
   468     def MakeTree(self, lib_dir = None):
       
   469         global hmi_tree_root
       
   470 
       
   471         self.Freeze()
       
   472 
       
   473         self.root = None
       
   474         self.DeleteAllItems()
       
   475 
       
   476         root_display_name = _("Please select widget library directory") \
       
   477             if lib_dir is None else os.path.basename(lib_dir)
       
   478         self.root = self.AddRoot(root_display_name)
       
   479         self.SetPyData(self.root, None)
       
   480 
       
   481         if lib_dir is not None:
       
   482             self._recurseTree(lib_dir, self.root, [])
       
   483             self.Expand(self.root)
       
   484 
       
   485         self.Thaw()
       
   486 
       
   487 _conf_key = "SVGHMIWidgetLib"
       
   488 _preview_height = 200
       
   489 class WidgetLibBrowser(wx.Panel):
       
   490     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
       
   491                  size=wx.DefaultSize):
       
   492 
       
   493         wx.Panel.__init__(self, parent, id, pos, size)     
       
   494 
       
   495         self.bmp = None
       
   496         self.msg = None
       
   497         self.hmitree_node = None
       
   498         self.selected_SVG = None
       
   499 
       
   500         self.Config = wx.ConfigBase.Get()
       
   501         self.libdir = self.RecallLibDir()
       
   502 
       
   503         sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
       
   504         sizer.AddGrowableCol(0)
       
   505         sizer.AddGrowableRow(1)
       
   506         self.libbutton = wx.Button(self, -1, _("Select SVG widget library"))
       
   507         self.widgetpicker = WidgetPicker(self, self.libdir)
       
   508         self.preview = wx.Panel(self, size=(-1, _preview_height + 10))  #, style=wx.SIMPLE_BORDER)
       
   509         #self.preview.SetBackgroundColour(wx.WHITE)
       
   510         sizer.AddWindow(self.libbutton, flag=wx.GROW)
       
   511         sizer.AddWindow(self.widgetpicker, flag=wx.GROW)
       
   512         sizer.AddWindow(self.preview, flag=wx.GROW)
       
   513         sizer.Layout()
       
   514         self.SetAutoLayout(True)
       
   515         self.SetSizer(sizer)
       
   516         sizer.Fit(self)
       
   517         self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton)
       
   518         self.preview.Bind(wx.EVT_PAINT, self.OnPaint)
       
   519 
       
   520         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker)
       
   521 
       
   522         self.msg = _("Drag selected Widget from here to Inkscape")
       
   523 
       
   524     def RecallLibDir(self):
       
   525         conf = self.Config.Read(_conf_key)
       
   526         if len(conf) == 0:
       
   527             return None
       
   528         else:
       
   529             return DecodeFileSystemPath(conf)
       
   530 
       
   531     def RememberLibDir(self, path):
       
   532         self.Config.Write(_conf_key,
       
   533                           EncodeFileSystemPath(path))
       
   534         self.Config.Flush()
       
   535 
       
   536     def DrawPreview(self):
       
   537         """
       
   538         Refresh preview panel 
       
   539         """
       
   540         # Init preview panel paint device context
       
   541         dc = wx.PaintDC(self.preview)
       
   542         dc.Clear()
       
   543 
       
   544         if self.bmp:
       
   545             # Get Preview panel size
       
   546             sz = self.preview.GetClientSize()
       
   547             w = self.bmp.GetWidth()
       
   548             dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
       
   549 
       
   550         if self.msg:
       
   551             dc.SetFont(self.GetFont())
       
   552             dc.DrawText(self.msg, 25,25)
       
   553 
       
   554 
       
   555     def OnSelectLibDir(self, event):
       
   556         defaultpath = self.RecallLibDir()
       
   557         if defaultpath == None:
       
   558             defaultpath = os.path.expanduser("~")
       
   559 
       
   560         dialog = wx.DirDialog(self, _("Choose a widget library"), defaultpath,
       
   561                               style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
       
   562 
       
   563         if dialog.ShowModal() == wx.ID_OK:
       
   564             self.libdir = dialog.GetPath()
       
   565             self.RememberLibDir(self.libdir)
       
   566             self.widgetpicker.MakeTree(self.libdir)
       
   567 
       
   568         dialog.Destroy()
       
   569 
       
   570     def OnPaint(self, event):
       
   571         """
       
   572         Called when Preview panel needs to be redrawn
       
   573         @param event: wx.PaintEvent
       
   574         """
       
   575         self.DrawPreview()
       
   576         event.Skip()
       
   577 
       
   578     def GenThumbnail(self, svgpath, thumbpath):
       
   579         inkpath = get_inkscape_path()
       
   580         if inkpath is None:
       
   581             self.msg = _("Inkscape is not installed.")
       
   582             return False
       
   583         # TODO: spawn a thread, to decouple thumbnail gen
       
   584         status, result, _err_result = ProcessLogger(
       
   585             None,
       
   586             '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
       
   587             '" -D -h ' + str(_preview_height)).spin()
       
   588         if status != 0:
       
   589             self.msg = _("Inkscape couldn't generate thumbnail.")
       
   590             return False
       
   591         return True
       
   592 
       
   593     def OnWidgetSelection(self, event):
       
   594         """
       
   595         Called when tree item is selected
       
   596         @param event: wx.TreeEvent
       
   597         """
       
   598         item_pydata = self.widgetpicker.GetPyData(event.GetItem())
       
   599         if item_pydata is not None:
       
   600             svgpath = item_pydata
       
   601             dname = os.path.dirname(svgpath)
       
   602             fname = os.path.basename(svgpath)
       
   603             hasher = hashlib.new('md5')
       
   604             with open(svgpath, 'rb') as afile:
       
   605                 while True:
       
   606                     buf = afile.read(65536)
       
   607                     if len(buf) > 0:
       
   608                         hasher.update(buf)
       
   609                     else:
       
   610                         break
       
   611             digest = hasher.hexdigest()
       
   612             thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
       
   613             thumbdir = os.path.join(dname, ".svghmithumbs") 
       
   614             thumbpath = os.path.join(thumbdir, thumbfname) 
       
   615 
       
   616             self.msg = None
       
   617             have_thumb = os.path.exists(thumbpath)
       
   618 
       
   619             if not have_thumb:
       
   620                 try:
       
   621                     if not os.path.exists(thumbdir):
       
   622                         os.mkdir(thumbdir)
       
   623                 except IOError:
       
   624                     self.msg = _("Widget library must be writable")
       
   625                 else:
       
   626                     have_thumb = self.GenThumbnail(svgpath, thumbpath)
       
   627 
       
   628             self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
       
   629 
       
   630             self.selected_SVG = svgpath if have_thumb else None
       
   631             self.ValidateWidget()
       
   632 
       
   633             self.Refresh()
       
   634         event.Skip()
       
   635 
       
   636     def OnHMITreeNodeSelection(self, hmitree_node):
       
   637         self.hmitree_node = hmitree_node
       
   638         self.ValidateWidget()
       
   639         self.Refresh()
       
   640 
       
   641     def ValidateWidget(self):
       
   642         if self.selected_SVG is not None:
       
   643             if self.hmitree_node is not None:
       
   644                 pass
       
   645         # XXX TODO: 
       
   646         #      - check SVG is valid for selected HMI tree item
       
   647         #      - prepare for D'n'D
       
   648 
   427 
   649 
   428 class HMITreeView(wx.SplitterWindow):
   650 class HMITreeView(wx.SplitterWindow):
   429 
   651 
   430     def __init__(self, parent):
   652     def __init__(self, parent):
   431         wx.SplitterWindow.__init__(self, parent,
   653         wx.SplitterWindow.__init__(self, parent,
   432                                    style=wx.SUNKEN_BORDER | wx.SP_3D)
   654                                    style=wx.SUNKEN_BORDER | wx.SP_3D)
   433 
   655 
   434         self.SelectionTree = HMITreeSelector(self)
   656         self.SelectionTree = HMITreeSelector(self)
   435         #self.Staging = wx.Panel(self)
   657         self.Staging = WidgetLibBrowser(self)
   436         #self.SplitHorizontally(self.SelectionTree, self.Staging, 200)
   658         self.SplitVertically(self.SelectionTree, self.Staging, 300)
   437         self.Initialize(self.SelectionTree)
       
   438 
   659 
   439 
   660 
   440 class SVGHMIEditor(ConfTreeNodeEditor):
   661 class SVGHMIEditor(ConfTreeNodeEditor):
   441     CONFNODEEDITOR_TABS = [
   662     CONFNODEEDITOR_TABS = [
   442         (_("HMI Tree"), "CreateHMITreeView")]
   663         (_("HMI Tree"), "CreateHMITreeView")]
   443 
   664 
   444     def CreateHMITreeView(self, parent):
   665     def CreateHMITreeView(self, parent):
   445         #self.HMITreeView = HMITreeView(self)
       
   446         global hmi_tree_root
   666         global hmi_tree_root
   447 
   667 
   448         if hmi_tree_root is None:
   668         if hmi_tree_root is None:
   449             buildpath = self.Controler.GetCTRoot()._getBuildPath()
   669             buildpath = self.Controler.GetCTRoot()._getBuildPath()
   450             hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
   670             hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
   451             if os.path.exists(hmitree_backup_path):
   671             if os.path.exists(hmitree_backup_path):
   452                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
   672                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
   453                 hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
   673                 hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
   454 
   674 
   455         return HMITreeSelector(parent)
   675 
       
   676         #self.HMITreeView = HMITreeView(self)
       
   677         #return HMITreeSelector(parent)
       
   678         return HMITreeView(parent)
   456 
   679 
   457 class SVGHMI(object):
   680 class SVGHMI(object):
   458     XSD = """<?xml version="1.0" encoding="utf-8" ?>
   681     XSD = """<?xml version="1.0" encoding="utf-8" ?>
   459     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   682     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   460       <xsd:element name="SVGHMI">
   683       <xsd:element name="SVGHMI">