mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-04 19:10:09 +01:00
Add html2 (WebView) classes
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@73036 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
67
etg/_html2.py
Normal file
67
etg/_html2.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#---------------------------------------------------------------------------
|
||||
# Name: etg/_html2.py
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 20-Nov-2012
|
||||
# Copyright: (c) 2012 by Total Control Software
|
||||
# License: wxWindows License
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
from etgtools import PyFunctionDef, PyCodeDef, PyPropertyDef
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_html2"
|
||||
NAME = "_html2" # Base name of the file to generate to for this script
|
||||
DOCSTRING = ""
|
||||
|
||||
# The classes and/or the basename of the Doxygen XML files to be processed by
|
||||
# this script.
|
||||
ITEMS = [ ]
|
||||
|
||||
|
||||
# The list of other ETG scripts and back-end generator modules that are
|
||||
# included as part of this module. These should all be items that are put in
|
||||
# the wxWidgets "webview" library in a multi-lib build.
|
||||
INCLUDES = [ 'webview',
|
||||
]
|
||||
|
||||
|
||||
# Separate the list into those that are generated from ETG scripts and the
|
||||
# rest. These lists can be used from the build scripts to get a list of
|
||||
# sources and/or additional dependencies when building this extension module.
|
||||
ETGFILES = ['etg/%s.py' % NAME] + tools.getEtgFiles(INCLUDES)
|
||||
DEPENDS = tools.getNonEtgFiles(INCLUDES)
|
||||
OTHERDEPS = [ ]
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def run():
|
||||
# Parse the XML file(s) building a collection of Extractor objects
|
||||
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
|
||||
etgtools.parseDoxyXML(module, ITEMS)
|
||||
module.check4unittest = False
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
# Tweak the parsed meta objects in the module object as needed for
|
||||
# customizing the generated code and docstrings.
|
||||
|
||||
module.addHeaderCode('#include <wxpy_api.h>')
|
||||
module.addImport('_core')
|
||||
module.addPyCode('import wx', order=10)
|
||||
module.addInclude(INCLUDES)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
#-----------------------------------------------------------------
|
||||
tools.doCommonTweaks(module)
|
||||
tools.runGenerators(module)
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
124
etg/webview.py
Normal file
124
etg/webview.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#---------------------------------------------------------------------------
|
||||
# Name: etg/webview.py
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 20-Nov-2012
|
||||
# Copyright: (c) 2012 by Total Control Software
|
||||
# License: wxWindows License
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_html2"
|
||||
NAME = "webview" # Base name of the file to generate to for this script
|
||||
DOCSTRING = ""
|
||||
|
||||
# The classes and/or the basename of the Doxygen XML files to be processed by
|
||||
# this script.
|
||||
ITEMS = [ 'wxWebViewHistoryItem',
|
||||
'wxWebViewHandler',
|
||||
'wxWebViewArchiveHandler',
|
||||
'wxWebViewFSHandler',
|
||||
'wxWebView',
|
||||
'wxWebViewEvent',
|
||||
]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def run():
|
||||
# Parse the XML file(s) building a collection of Extractor objects
|
||||
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
|
||||
etgtools.parseDoxyXML(module, ITEMS)
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
# Tweak the parsed meta objects in the module object as needed for
|
||||
# customizing the generated code and docstrings.
|
||||
|
||||
module.addHeaderCode('#include <wx/webview.h>')
|
||||
|
||||
c = module.find('wxWebView')
|
||||
assert isinstance(c, etgtools.ClassDef)
|
||||
tools.fixWindowClass(c)
|
||||
c.abstract = True
|
||||
|
||||
module.addGlobalStr('wxWebViewNameStr', c)
|
||||
module.addGlobalStr('wxWebViewDefaultURLStr', c)
|
||||
|
||||
for m in c.find('New').all():
|
||||
m.factory = True
|
||||
c.find('New.id').default = 'wxID_ANY'
|
||||
c.find('New.parent').transferThis = True
|
||||
|
||||
|
||||
c.find('RegisterHandler.handler').type = 'wxWebViewHandler*'
|
||||
c.find('RegisterHandler.handler').transfer = True
|
||||
c.find('RegisterHandler').setCppCode_sip(
|
||||
"sipCpp->RegisterHandler(wxSharedPtr<wxWebViewHandler>(handler));")
|
||||
|
||||
|
||||
# Custom code to deal with the
|
||||
# wxVector<wxSharedPtr<wxWebViewHistoryItem>> return type of these two
|
||||
# methods. We'll just convert them to a Python list of history items.
|
||||
code = """\
|
||||
wxPyThreadBlocker blocker;
|
||||
PyObject* result = PyList_New(0);
|
||||
wxVector<wxSharedPtr<wxWebViewHistoryItem> > vector = self->{method}();
|
||||
for (size_t idx=0; idx < vector.size(); idx++) {{
|
||||
PyObject* obj;
|
||||
wxWebViewHistoryItem* item = new wxWebViewHistoryItem(*vector[idx].get());
|
||||
obj = wxPyConstructObject((void*)item, "wxWebViewHistoryItem", true);
|
||||
PyList_Append(result, obj);
|
||||
Py_DECREF(obj);
|
||||
}}
|
||||
return result;
|
||||
"""
|
||||
c.find('GetBackwardHistory').type = 'PyObject*'
|
||||
c.find('GetBackwardHistory').setCppCode(code.format(method='GetBackwardHistory'))
|
||||
c.find('GetForwardHistory').type = 'PyObject*'
|
||||
c.find('GetForwardHistory').setCppCode(code.format(method='GetForwardHistory'))
|
||||
|
||||
|
||||
# Since LoadHistoryItem expects to get an actual item in the history
|
||||
# list, and since we make copies of the items in the cppCode above, then
|
||||
# this won't be possible to do from the Python wrappers. However, it's
|
||||
# just as easy to use LoadURL to reload a history item so it's not a
|
||||
# great loss.
|
||||
c.find('LoadHistoryItem').ignore()
|
||||
##c.find('LoadHistoryItem.item').type = 'wxWebViewHistoryItem*'
|
||||
##c.find('LoadHistoryItem.item').transfer = True
|
||||
##c.find('LoadHistoryItem').setCppCode_sip(
|
||||
## "sipCpp->LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem>(item));")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
c = module.find('wxWebViewEvent')
|
||||
tools.fixEventClass(c)
|
||||
|
||||
module.addPyCode("""\
|
||||
EVT_WEB_VIEW_NAVIGATING = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_NAVIGATING, 1 )
|
||||
EVT_WEB_VIEW_NAVIGATED = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_NAVIGATED, 1 )
|
||||
EVT_WEB_VIEW_LOADED = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_LOADED, 1 )
|
||||
EVT_WEB_VIEW_ERROR = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_ERROR, 1 )
|
||||
EVT_WEB_VIEW_NEWWINDOW = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_NEWWINDOW, 1 )
|
||||
EVT_WEB_VIEW_TITLE_CHANGED = wx.PyEventBinder( wxEVT_COMMAND_WEB_VIEW_TITLE_CHANGED, 1 )
|
||||
""")
|
||||
|
||||
|
||||
|
||||
c = module.find('wxWebViewHistoryItem')
|
||||
tools.addAutoProperties(c)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
tools.doCommonTweaks(module)
|
||||
tools.runGenerators(module)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
||||
161
samples/html2/HTML2_WebView.py
Normal file
161
samples/html2/HTML2_WebView.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import sys
|
||||
import wx
|
||||
import wx.html2 as webview
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
class TestPanel(wx.Panel):
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent)
|
||||
|
||||
self.current = "http://wxPython.org"
|
||||
self.frame = self.GetTopLevelParent()
|
||||
self.titleBase = self.frame.GetTitle()
|
||||
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.wv = webview.WebView.New(self)
|
||||
self.Bind(webview.EVT_WEB_VIEW_NAVIGATING, self.OnWebViewNavigating, self.wv)
|
||||
self.Bind(webview.EVT_WEB_VIEW_NAVIGATED, self.OnWebViewNavigated, self.wv)
|
||||
self.Bind(webview.EVT_WEB_VIEW_LOADED, self.OnWebViewLoaded, self.wv)
|
||||
self.Bind(webview.EVT_WEB_VIEW_TITLE_CHANGED, self.OnWebViewTitleChanged, self.wv)
|
||||
|
||||
btn = wx.Button(self, -1, "Open", style=wx.BU_EXACTFIT)
|
||||
self.Bind(wx.EVT_BUTTON, self.OnOpenButton, btn)
|
||||
btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
|
||||
|
||||
btn = wx.Button(self, -1, "<--", style=wx.BU_EXACTFIT)
|
||||
self.Bind(wx.EVT_BUTTON, self.OnPrevPageButton, btn)
|
||||
btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
|
||||
self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanGoBack, btn)
|
||||
|
||||
btn = wx.Button(self, -1, "-->", style=wx.BU_EXACTFIT)
|
||||
self.Bind(wx.EVT_BUTTON, self.OnNextPageButton, btn)
|
||||
btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
|
||||
self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanGoForward, btn)
|
||||
|
||||
btn = wx.Button(self, -1, "Stop", style=wx.BU_EXACTFIT)
|
||||
self.Bind(wx.EVT_BUTTON, self.OnStopButton, btn)
|
||||
btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
|
||||
|
||||
btn = wx.Button(self, -1, "Refresh", style=wx.BU_EXACTFIT)
|
||||
self.Bind(wx.EVT_BUTTON, self.OnRefreshPageButton, btn)
|
||||
btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
|
||||
|
||||
txt = wx.StaticText(self, -1, "Location:")
|
||||
btnSizer.Add(txt, 0, wx.CENTER|wx.ALL, 2)
|
||||
|
||||
self.location = wx.ComboBox(
|
||||
self, -1, "", style=wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)
|
||||
self.location.AppendItems(['http://wxPython.org',
|
||||
'http://wxwidgets.org',
|
||||
'http://google.com'])
|
||||
|
||||
#for url in ['http://wxPython.org',
|
||||
# 'http://wxwidgets.org',
|
||||
# 'http://google.com']:
|
||||
# item = webview.WebViewHistoryItem(url, url)
|
||||
# self.wv.LoadHistoryItem(item)
|
||||
|
||||
self.Bind(wx.EVT_COMBOBOX, self.OnLocationSelect, self.location)
|
||||
self.location.Bind(wx.EVT_TEXT_ENTER, self.OnLocationEnter)
|
||||
btnSizer.Add(self.location, 1, wx.EXPAND|wx.ALL, 2)
|
||||
|
||||
|
||||
sizer.Add(btnSizer, 0, wx.EXPAND)
|
||||
sizer.Add(self.wv, 1, wx.EXPAND)
|
||||
self.SetSizer(sizer)
|
||||
|
||||
self.wv.LoadURL(self.current)
|
||||
|
||||
|
||||
|
||||
# WebView events
|
||||
def OnWebViewNavigating(self, evt):
|
||||
# this event happens prior to trying to get a resource
|
||||
if evt.GetURL() == 'http://www.microsoft.com/':
|
||||
if wx.MessageBox("Are you sure you want to visit Microsoft?",
|
||||
style=wx.YES_NO|wx.ICON_QUESTION) == wx.NO:
|
||||
# This is how you can cancel loading a page.
|
||||
evt.Veto()
|
||||
|
||||
|
||||
def OnWebViewNavigated(self, evt):
|
||||
self.frame.SetStatusText("Loading %s..." % evt.GetURL())
|
||||
|
||||
|
||||
def OnWebViewLoaded(self, evt):
|
||||
# The full document has loaded
|
||||
self.current = evt.GetURL()
|
||||
self.location.SetValue(self.current)
|
||||
self.frame.SetStatusText("Loaded")
|
||||
|
||||
|
||||
def OnWebViewTitleChanged(self, evt):
|
||||
# Set the frame's title to include the document's title
|
||||
self.frame.SetTitle("%s -- %s" % (self.titleBase, evt.GetString()))
|
||||
|
||||
|
||||
# Control bar events
|
||||
def OnLocationSelect(self, evt):
|
||||
url = self.location.GetStringSelection()
|
||||
print('OnLocationSelect: %s\n' % url)
|
||||
self.wv.LoadURL(url)
|
||||
|
||||
def OnLocationEnter(self, evt):
|
||||
url = self.location.GetValue()
|
||||
self.location.Append(url)
|
||||
self.wv.LoadURL(url)
|
||||
|
||||
|
||||
def OnOpenButton(self, event):
|
||||
dlg = wx.TextEntryDialog(self, "Open Location",
|
||||
"Enter a full URL or local path",
|
||||
self.current, wx.OK|wx.CANCEL)
|
||||
dlg.CentreOnParent()
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
self.current = dlg.GetValue()
|
||||
self.wv.LoadURL(self.current)
|
||||
|
||||
dlg.Destroy()
|
||||
|
||||
def OnPrevPageButton(self, event):
|
||||
for i in self.wv.GetBackwardHistory():
|
||||
print i.Url, i.Title
|
||||
self.wv.GoBack()
|
||||
|
||||
def OnNextPageButton(self, event):
|
||||
for i in self.wv.GetForwardHistory():
|
||||
print i.URL, i.Title
|
||||
self.wv.GoForward()
|
||||
|
||||
def OnCheckCanGoBack(self, event):
|
||||
event.Enable(self.wv.CanGoBack())
|
||||
|
||||
def OnCheckCanGoForward(self, event):
|
||||
event.Enable(self.wv.CanGoForward())
|
||||
|
||||
def OnStopButton(self, evt):
|
||||
self.wv.Stop()
|
||||
|
||||
def OnRefreshPageButton(self, evt):
|
||||
self.wv.Reload()
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
app = wx.App()
|
||||
frm = wx.Frame(None, title="html2.WebView sample", size=(700,500))
|
||||
frm.CreateStatusBar()
|
||||
pnl = TestPanel(frm)
|
||||
frm.Show()
|
||||
app.MainLoop()
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
16
setup.py
16
setup.py
@@ -229,6 +229,22 @@ extensions.append(ext)
|
||||
cfg.CLEANUP.append(opj(cfg.PKGDIR, 'glcanvas.py'))
|
||||
|
||||
|
||||
etg = loadETG('etg/_html2.py')
|
||||
tgDepends = etg.DEPENDS + etg.OTHERDEPS
|
||||
ext = Extension('_html2', getEtgSipCppFiles(etg),
|
||||
depends = getEtgSipHeaders(etg),
|
||||
include_dirs = cfg.includes,
|
||||
define_macros = cfg.defines,
|
||||
library_dirs = cfg.libdirs,
|
||||
libraries = cfg.libs + cfg.makeLibName('webview', True),
|
||||
extra_compile_args = cfg.cflags,
|
||||
extra_link_args = cfg.lflags,
|
||||
)
|
||||
extensions.append(ext)
|
||||
cfg.CLEANUP.append(opj(cfg.PKGDIR, 'html2.py'))
|
||||
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -43,6 +43,9 @@ class PIImportTest(unittest.TestCase):
|
||||
def test_html_pi(self):
|
||||
self.runPI('html.pi')
|
||||
|
||||
def test_html2_pi(self):
|
||||
self.runPI('html2.pi')
|
||||
|
||||
def test_dataview_pi(self):
|
||||
self.runPI('dataview.pi')
|
||||
|
||||
|
||||
78
unittests/test_webview.py
Normal file
78
unittests/test_webview.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import imp_unittest, unittest
|
||||
import wtc
|
||||
import wx
|
||||
import wx.html2 as webview
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
class webview_Tests(wtc.WidgetTestCase):
|
||||
|
||||
def test_webview1(self):
|
||||
wv = webview.WebView.New()
|
||||
wv.Create(self.frame)
|
||||
|
||||
def test_webview2(self):
|
||||
wv = webview.WebView.New(self.frame)
|
||||
|
||||
def test_webview3(self):
|
||||
wv = webview.WebView.New(self.frame)
|
||||
self.frame.SendSizeEvent()
|
||||
wv.LoadURL('file://' + __file__)
|
||||
while wv.IsBusy():
|
||||
self.myYield()
|
||||
h = wv.GetBackwardHistory()
|
||||
self.assertTrue(isinstance(h, list))
|
||||
if len(h):
|
||||
self.assertTrue(isinstance(h[0], webview.WebViewHistoryItem))
|
||||
|
||||
|
||||
def test_webview5(self):
|
||||
webview.WEB_VIEW_ZOOM_TINY
|
||||
webview.WEB_VIEW_ZOOM_SMALL
|
||||
webview.WEB_VIEW_ZOOM_MEDIUM
|
||||
webview.WEB_VIEW_ZOOM_LARGE
|
||||
webview.WEB_VIEW_ZOOM_LARGEST
|
||||
webview.WEB_VIEW_ZOOM_TYPE_LAYOUT
|
||||
webview.WEB_VIEW_ZOOM_TYPE_TEXT
|
||||
webview.WEB_NAV_ERR_CONNECTION
|
||||
webview.WEB_NAV_ERR_CERTIFICATE
|
||||
webview.WEB_NAV_ERR_AUTH
|
||||
webview.WEB_NAV_ERR_SECURITY
|
||||
webview.WEB_NAV_ERR_NOT_FOUND
|
||||
webview.WEB_NAV_ERR_REQUEST
|
||||
webview.WEB_NAV_ERR_USER_CANCELLED
|
||||
webview.WEB_NAV_ERR_OTHER
|
||||
webview.WEB_VIEW_RELOAD_DEFAULT
|
||||
webview.WEB_VIEW_RELOAD_NO_CACHE
|
||||
webview.WEB_VIEW_FIND_WRAP
|
||||
webview.WEB_VIEW_FIND_ENTIRE_WORD
|
||||
webview.WEB_VIEW_FIND_MATCH_CASE
|
||||
webview.WEB_VIEW_FIND_HIGHLIGHT_RESULT
|
||||
webview.WEB_VIEW_FIND_BACKWARDS
|
||||
webview.WEB_VIEW_FIND_DEFAULT
|
||||
webview.WEB_VIEW_BACKEND_DEFAULT
|
||||
webview.WEB_VIEW_BACKEND_WEBKIT
|
||||
webview.WEB_VIEW_BACKEND_IE
|
||||
|
||||
|
||||
def test_webview6(self):
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_NAVIGATING
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_NAVIGATED
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_LOADED
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_ERROR
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_NEWWINDOW
|
||||
webview.wxEVT_COMMAND_WEB_VIEW_TITLE_CHANGED
|
||||
|
||||
webview.EVT_WEB_VIEW_NAVIGATING
|
||||
webview.EVT_WEB_VIEW_NAVIGATED
|
||||
webview.EVT_WEB_VIEW_LOADED
|
||||
webview.EVT_WEB_VIEW_ERROR
|
||||
webview.EVT_WEB_VIEW_NEWWINDOW
|
||||
webview.EVT_WEB_VIEW_TITLE_CHANGED
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
16
wscript
16
wscript
@@ -90,6 +90,8 @@ def configure(conf):
|
||||
_copyEnvGroup(conf.env, '_WX', '_WXGL')
|
||||
conf.env.LIB_WXGL += cfg.makeLibName('gl')
|
||||
|
||||
_copyEnvGroup(conf.env, '_WX', '_WXWEBVIEW')
|
||||
conf.env.LIB_WXWEBVIEW += cfg.makeLibName('webview')
|
||||
|
||||
|
||||
|
||||
@@ -142,6 +144,10 @@ def configure(conf):
|
||||
args='--cxxflags --libs gl,core,net',
|
||||
uselib_store='WXGL', mandatory=True)
|
||||
|
||||
conf.check_cfg(path=conf.options.wx_config, package='',
|
||||
args='--cxxflags --libs webview,core,net',
|
||||
uselib_store='WXWEBVIEW', mandatory=True)
|
||||
|
||||
|
||||
# NOTE: This assumes that if the platform is not win32 (from
|
||||
# the test above) and not darwin then we must be using the
|
||||
@@ -314,6 +320,16 @@ def build(bld):
|
||||
makeExtCopyRule(bld, '_glcanvas')
|
||||
|
||||
|
||||
etg = loadETG('etg/_html2.py')
|
||||
adv = bld(
|
||||
features = 'c cxx cxxshlib pyext',
|
||||
target = makeTargetName(bld, '_html2'),
|
||||
source = getEtgSipCppFiles(etg) + rc,
|
||||
uselib = 'WXWEBVIEW WXPY',
|
||||
)
|
||||
makeExtCopyRule(bld, '_html2')
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# helpers
|
||||
|
||||
Reference in New Issue
Block a user