Files
Phoenix/etg/graphics.py
2024-02-12 23:20:17 -05:00

359 lines
13 KiB
Python

#---------------------------------------------------------------------------
# Name: etg/graphics.py
# Author: Kevin Ollivier
# Robin Dunn
#
# Created: 10-Sept-2011
# Copyright: (c) 2011 by Kevin Ollivier
# Copyright: (c) 2011-2020 by Total Control Software
# License: wxWindows License
#---------------------------------------------------------------------------
import etgtools
import etgtools.tweaker_tools as tools
PACKAGE = "wx"
MODULE = "_core"
NAME = "graphics" # 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 = [
'wxGraphicsObject',
'wxGraphicsBitmap',
'wxGraphicsBrush',
'wxGraphicsFont',
'wxGraphicsPenInfo',
'wxGraphicsPen',
'wxGraphicsContext',
'wxGraphicsGradientStop',
'wxGraphicsGradientStops',
'wxGraphicsMatrix',
'wxGraphicsPath',
'wxGraphicsRenderer',
]
#---------------------------------------------------------------------------
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/gdicmn.h>')
def markCreateFactories(klass):
"""Mark all Create methods as factories"""
for func in klass.allItems():
if isinstance(func, etgtools.FunctionDef) \
and func.name.startswith('Create') \
and '*' in func.type:
func.factory = True
#---------------------------------------------
c = module.find('wxGraphicsObject')
assert isinstance(c, etgtools.ClassDef)
c.mustHaveApp()
c.addCppMethod('bool', 'IsOk', '()', 'return !self->IsNull();')
c.addCppMethod('int', '__nonzero__', '()', "return !self->IsNull();")
c.addCppMethod('int', '__bool__', '()', "return !self->IsNull();")
#---------------------------------------------
c = module.find('wxGraphicsContext')
assert isinstance(c, etgtools.ClassDef)
tools.removeVirtuals(c)
c.abstract = True
c.mustHaveApp()
c.addCppMethod('wxGraphicsContext*', 'Create', '(wxAutoBufferedPaintDC* autoPaintDC /KeepReference/)',
pyArgsString='(autoPaintDC) -> GraphicsContext',
isStatic=True,
body="""\
return wxGraphicsContext::Create(*autoPaintDC);
""")
m = c.find('Create').findOverload('wxEnhMetaFileDC')
m.find('metaFileDC').type = 'const wxMetafileDC&'
m.argsString = '(const wxMetafileDC& metaFileDC)'
m.setCppCode("""\
#ifdef __WXMSW__
#if wxUSE_ENH_METAFILE
return wxGraphicsContext::Create(*metaFileDC);
#endif
#endif
wxPyRaiseNotImplemented();
return NULL;
""")
markCreateFactories(c)
# Ensure that the target DC or image passed to Create lives as long as the
# GC does. NOTE: Since the Creates are static methods there is no self to
# associate the extra reference with, but since they are factories then
# that extra reference will be held by the return value of the factory
# instead.
for m in c.find('Create').all():
for p in m.items:
if 'DC' in p.name or p.name == 'image':
p.keepReference = True
c.find('GetSize.width').out = True
c.find('GetSize.height').out = True
c.find('GetDPI.dpiX').out = True
c.find('GetDPI.dpiY').out = True
m = c.find('GetPartialTextExtents')
m.find('widths').ignore()
m.type = 'wxArrayDouble*'
m.factory = True # a new instance is being created
m.setCppCode("""\
wxArrayDouble rval;
self->GetPartialTextExtents(*text, rval);
return new wxArrayDouble(rval);
""")
m = c.find('GetTextExtent')
m.pyName = 'GetFullTextExtent'
m.find('width').out = True
m.find('height').out = True
m.find('descent').out = True
m.find('externalLeading').out = True
c.addCppMethod('PyObject*', 'GetTextExtent', '(const wxString& text)',
pyArgsString="(text) -> (width, height)",
doc="Gets the dimensions of the string using the currently selected font.",
body="""\
wxDouble width = 0.0, height = 0.0;
self->GetTextExtent(*text, &width, &height, NULL, NULL);
wxPyThreadBlocker blocker;
return sipBuildResult(0, "(dd)", width, height);
""")
c.addPyCode("GraphicsContext.DrawRotatedText = wx.deprecated(GraphicsContext.DrawText, 'Use DrawText instead.')")
c.addCppCode(tools.ObjArrayHelperTemplate('wxPoint2D', 'sipType_wxPoint2DDouble',
"Expected a sequence of length-2 sequences or wx.Point2D objects."))
# we'll reimplement this overload as StrokeLineSegments
c.find('StrokeLines').findOverload('beginPoints').ignore()
c.addCppMethod('void', 'StrokeLineSegments', '(PyObject* beginPoints, PyObject* endPoints)',
pyArgsString="(beginPoint2Ds, endPoint2Ds)",
doc="Stroke disconnected lines from begin to end points.",
body="""\
size_t c1, c2, count;
wxPoint2D* beginP = wxPoint2D_array_helper(beginPoints, &c1);
wxPoint2D* endP = wxPoint2D_array_helper(endPoints, &c2);
if ( beginP != NULL && endP != NULL ) {
count = wxMin(c1, c2);
self->StrokeLines(count, beginP, endP);
}
delete [] beginP;
delete [] endP;
""")
# Also reimplement the main StrokeLines method to reuse the same helper
# function as StrokeLineSegments
m = c.find('StrokeLines').findOverload('points').ignore()
c.addCppMethod('void', 'StrokeLines', '(PyObject* points)',
pyArgsString="(point2Ds)",
doc="Stroke lines connecting all the points.",
body="""\
size_t count;
wxPoint2D* ptsArray = wxPoint2D_array_helper(points, &count);
if ( ptsArray != NULL ) {
self->StrokeLines(count, ptsArray);
delete [] ptsArray;
}
""")
# and once more for DrawLines
m = c.find('DrawLines').ignore()
c.addCppMethod('void', 'DrawLines', '(PyObject* points, wxPolygonFillMode fillStyle = wxODDEVEN_RULE)',
pyArgsString="(point2Ds, fillStyle=ODDEVEN_RULE)",
doc="Draws a polygon.",
body="""\
size_t count;
wxPoint2D* ptsArray = wxPoint2D_array_helper(points, &count);
if ( ptsArray != NULL ) {
self->DrawLines(count, ptsArray, fillStyle);
delete [] ptsArray;
}
""")
# TODO: support this?
c.find('CreateFromNativeHDC').ignore()
#---------------------------------------------
c = module.find('wxGraphicsPath')
tools.removeVirtuals(c)
c.find('GetBox').findOverload('wxDouble *x, wxDouble *y').ignore()
c.find('GetCurrentPoint').findOverload('wxDouble *x, wxDouble *y').ignore()
c.mustHaveApp()
#---------------------------------------------
c = module.find('wxGraphicsRenderer')
tools.removeVirtuals(c)
markCreateFactories(c)
c.abstract = True
# The KeepReference annotation doesn't work for us in this case, as it will
# hold the reference in the renderer object, but it is better to hold the
# reference in the returned context object instead. Otherwise there is still
# some possibility that the held DC will be destroyed before the context.
c.addPyCode("""\
def _ctx_hold_ref(f):
from functools import wraps
@wraps(f)
def wrapper(self, obj):
ctx = f(self, obj)
if ctx is not None:
ctx._obj = obj
return ctx
return wrapper
GraphicsRenderer.CreateContext = _ctx_hold_ref(GraphicsRenderer.CreateContext)
GraphicsRenderer.CreateContextFromImage = _ctx_hold_ref(GraphicsRenderer.CreateContextFromImage)
GraphicsRenderer.CreateContextFromUnknownDC = _ctx_hold_ref(GraphicsRenderer.CreateContextFromUnknownDC)
""")
# TODO: support this?
c.find('CreateContext').findOverload('wxEnhMetaFileDC').ignore()
# TODO: support this?
c.find('CreateContextFromNativeHDC').ignore()
c.addPyMethod('GetType', '(self)',
doc="Returns the name of the GraphicsRenderer class.",
body="return self.GetClassInfo().GetClassName()")
c.find('GetGDIPlusRenderer').ignore()
c.addCppMethod('wxGraphicsRenderer*', 'GetGDIPlusRenderer', '()', isStatic=True,
doc="Returns GDI+ renderer (MSW only).",
body="""\
#ifdef __WXMSW__
return wxGraphicsRenderer::GetGDIPlusRenderer();
#else
return NULL;
#endif
""")
c.find('GetDirect2DRenderer').ignore()
c.addCppMethod('wxGraphicsRenderer*', 'GetDirect2DRenderer', '()', isStatic=True,
doc="Returns Direct2D renderer (MSW and Python3 only).",
body="""\
#if wxUSE_GRAPHICS_DIRECT2D
return wxGraphicsRenderer::GetDirect2DRenderer();
#else
return NULL;
#endif
""")
#---------------------------------------------
c = module.find('wxGraphicsMatrix')
tools.removeVirtuals(c)
c.mustHaveApp()
c.find('Concat').overloads = []
c.find('IsEqual').overloads = []
c.find('Get.a').out = True
c.find('Get.b').out = True
c.find('Get.c').out = True
c.find('Get.d').out = True
c.find('Get.tx').out = True
c.find('Get.ty').out = True
c.find('TransformDistance.dx').inOut = True
c.find('TransformDistance.dy').inOut = True
c.find('TransformPoint.x').inOut = True
c.find('TransformPoint.y').inOut = True
#---------------------------------------------
c = module.find('wxGraphicsGradientStops')
c.addCppMethod('Py_ssize_t', '__len__', '()', body="return (Py_ssize_t)self->GetCount();")
c.addCppMethod('wxGraphicsGradientStop*', '__getitem__', '(ulong n)',
pyArgsString='(n)',
body="return new wxGraphicsGradientStop(self->Item(n));",
factory=True)
#---------------------------------------------
c = module.find('wxGraphicsBitmap')
#---------------------------------------------
c = module.find('wxGraphicsPenInfo')
# Ignore Dashes for now
# TODO: we need to do something like wx.Pen.SetDashes, but since
# GraphicsPenInfo is transitory we can't save the reference in it to the
# holder, and the pen will not have been created yet...
c.find('Dashes').ignore()
c.find('GetDashes').ignore()
c.find('GetDashCount').ignore()
c.find('GetDash').ignore()
#---------------------------------------------
# Use the pyNames we set for these classes in geometry.py so the old
# names do not show up in the docstrings, etc.
tools.changeTypeNames(module, 'wxPoint2DDouble', 'wxPoint2D')
tools.changeTypeNames(module, 'wxRect2DDouble', 'wxRect2D')
#-----------------------------------------------------------------
tools.doCommonTweaks(module)
# Add some code to check obj.IsNull() to all methods that are used as getters for a
# PropertyDef. This is needed because it seems that most methods in GraphicsOpbects
# assume that the dev has already checked that the object is valid and so don't check
# it themselves. But when turned into a Python property they will automatically be called
# when introspecting the property values in things like wxNullGraphicsFOO. This can
# easily result in a fatal crash. The tweak below will raise a ValueError exception in
# these cases before it gets to the crashy parts.
checkIsNull = """\
if (sipCpp->IsNull()) {{
wxPyErr_SetString(PyExc_ValueError, "The {} is not valid (likely an uninitialized or null instance)");
return NULL;
}}
"""
for module_item in module.items:
if isinstance(module_item, etgtools.ClassDef):
klass = module_item
if 'wxGraphicsObject' in [klass.name] + klass.bases:
for item in klass.items:
if isinstance(item, etgtools.PropertyDef):
method = klass.find(item.getter)
method.preMethodCode = checkIsNull.format(klass.pyName)
if item.setter:
method = klass.find(item.setter)
method.preMethodCode = checkIsNull.format(klass.pyName)
#-----------------------------------------------------------------
tools.runGenerators(module)
#---------------------------------------------------------------------------
if __name__ == '__main__':
run()