#---------------------------------------------------------------------------
# Name:        etg/stattext.py
# Author:      Kevin Ollivier
#              Robin Dunn
#
# Created:     26-Aug-2011
# Copyright:   (c) 2011 by Wide Open Technologies
# Copyright:   (c) 2011-2018 by Total Control Software
# License:     wxWindows License
#---------------------------------------------------------------------------

import etgtools
import etgtools.tweaker_tools as tools

from textwrap import dedent

PACKAGE   = "wx"
MODULE    = "_core"
NAME      = "dc"   # 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  = [ 'wxFontMetrics',
           'wxDC',
           'wxDCClipper',
           'wxDCBrushChanger',
           'wxDCPenChanger',
           'wxDCTextColourChanger',
           'wxDCFontChanger',
           ]

OTHERDEPS = [ 'src/dc_ex.cpp', ]

#---------------------------------------------------------------------------

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.

    c = module.find('wxDC')
    assert isinstance(c, etgtools.ClassDef)
    c.mustHaveApp()

    c.addPrivateCopyCtor()
    c.addPublic()
    tools.removeVirtuals(c)

    c.addDtor('public', True)

    # Keep only the wxSize overloads of these
    c.find('GetSize').findOverload('wxCoord').ignore()
    c.find('GetSizeMM').findOverload('wxCoord').ignore()

    # TODO: needs wxAffineMatrix2D support.
    #c.find('GetTransformMatrix').ignore()
    #c.find('SetTransformMatrix').ignore()

    # remove wxPoint* overloads, we use the wxPointList ones
    c.find('DrawLines').findOverload('wxPoint points').ignore()
    c.find('DrawPolygon').findOverload('wxPoint points').ignore()
    c.find('DrawSpline').findOverload('wxPoint points').ignore()

    # TODO: we'll need a custom method implementation for this since there
    # are multiple array parameters involved...
    c.find('DrawPolyPolygon').ignore()


    # Add output param annotations so the generated docstrings will be correct
    c.find('GetUserScale.x').out = True
    c.find('GetUserScale.y').out = True

    c.find('GetLogicalScale.x').out = True
    c.find('GetLogicalScale.y').out = True

    c.find('GetLogicalOrigin').overloads = []
    c.find('GetLogicalOrigin.x').out = True
    c.find('GetLogicalOrigin.y').out = True

    c.find('GetClippingBox.x').out = True
    c.find('GetClippingBox.y').out = True
    c.find('GetClippingBox.width').out = True
    c.find('GetClippingBox.height').out = True
    c.addPyMethod('GetClippingRect', '(self)',
        doc="Gets the rectangle surrounding the current clipping region",
        body="return wx.Rect(*self.GetClippingBox())")


    # Deal with the text-extent methods. In Classic we renamed one overloaded
    # method with a "Full" name, and the simpler one was left with the
    # original name.  I think a simpler approach here will be to remove the
    # simple version, rename the Full version as before, and then add a
    # CppMethod to reimplement the simple version.

    for name in ['GetTextExtent', 'GetMultiLineTextExtent']:
        m = c.find(name)
        # if these fail then double-check order of parsed methods, etc.
        assert len(m.overloads) == 1
        assert 'wxCoord' not in m.overloads[0].argsString
        m.overloads = []

    c.find('GetTextExtent').pyName = 'GetFullTextExtent'
    c.find('GetMultiLineTextExtent').pyName = 'GetFullMultiLineTextExtent'

    # Set output parameters
    c.find('GetTextExtent.w').out = True
    c.find('GetTextExtent.h').out = True
    c.find('GetTextExtent.descent').out = True
    c.find('GetTextExtent.externalLeading').out = True

    c.find('GetMultiLineTextExtent.w').out = True
    c.find('GetMultiLineTextExtent.h').out = True
    c.find('GetMultiLineTextExtent.heightLine').out = True


    # Update the docs to remove references to overloading, pointer values, etc.
    m = c.find('GetTextExtent')
    m.briefDoc = "Gets the dimensions of the string as it would be drawn."
    m.detailedDoc = [dedent("""\
        The ``string`` parameter is the string to measure.  The return value
        is a tuple of integer values consisting of ``widget``, ``height``,
        ``decent`` and ``externalLeading``. The ``descent`` is the dimension
        from the baseline of the font to the bottom of the descender, and
        ``externalLeading`` is any extra vertical space added to the font by the
        font designer (usually is zero).

        If the optional parameter ``font`` is specified and valid, then it is
        used for the text extent calculation. Otherwise the currently selected
        font is.

        .. seealso:: :class:`wx.Font`, :meth:`~wx.DC.SetFont`,
           :meth:`~wx.DC.GetPartialTextExtents, :meth:`~wx.DC.GetMultiLineTextExtent`
        """)]

    m = c.find('GetMultiLineTextExtent')
    m.briefDoc = "Gets the dimensions of the string as it would be drawn."
    m.detailedDoc = [dedent("""\
        The ``string`` parameter is the string to measure.  The return value
        is a tuple of integer values consisting of ``widget``, ``height`` and
        ``heightLine``.  The ``heightLine`` is the the height of a single line.

        If the optional parameter ``font`` is specified and valid, then it is
        used for the text extent calculation. Otherwise the currently selected
        font is.

        .. note:: This function works with both single-line and multi-line strings.

        .. seealso:: :class:`wx.Font`, :meth:`~wx.DC.SetFont`,
           :meth:`~wx.DC.GetPartialTextExtents, :meth:`~wx.DC.GetTextExtent`
        """)]

    # Now add the simpler versions of the extent methods
    c.addCppMethod('wxSize*', 'GetTextExtent', '(const wxString& st)', isConst=True,
        doc=dedent("""\
            Return the dimensions of the given string's text extent using the
            currently selected font.

            :param st: The string to be measured

            .. seealso:: :meth:`~wx.DC.GetFullTextExtent`
            """),
        body="""\
            return new wxSize(self->GetTextExtent(*st));
            """,
        overloadOkay=False, factory=True)

    c.addCppMethod('wxSize*', 'GetMultiLineTextExtent', '(const wxString& st)', isConst=True,
        doc=dedent("""\
            Return the dimensions of the given string's text extent using the
            currently selected font, taking into account multiple lines if
            present in the string.

            :param st: The string to be measured

            .. seealso:: :meth:`~wx.DC.GetFullMultiLineTextExtent`
            """),
        body="""\
            return new wxSize(self->GetMultiLineTextExtent(*st));
            """,
        overloadOkay=False, factory=True)




    # Add some alternate implementations for other DC methods, in order to
    # avoid using parameters as return values, etc. as well as Classic
    # compatibility.
    c.find('GetPixel').ignore()
    c.addCppMethod('wxColour*', 'GetPixel', '(wxCoord x, wxCoord y)',
        doc="Gets the colour at the specified location on the DC.",
        body="""\
            wxColour* col = new wxColour;
            self->GetPixel(x, y, col);
            return col;
            """,
        factory=True)

    # Return the rect instead of using an output parameter
    m = c.find('DrawLabel').findOverload('rectBounding')
    m.type = 'wxRect*'
    m.find('rectBounding').ignore()
    m.factory = True  # a new instance of wxRect is being created
    m.setCppCode("""\
        wxRect rv;
        self->DrawLabel(*text, *bitmap, *rect, alignment, indexAccel, &rv);
        return new wxRect(rv);
        """)
    c.addPyCode('DC.DrawImageLabel = wx.deprecated(DC.DrawLabel, "Use DrawLabel instead.")')


    # Return the array instead of using an output parameter
    m = c.find('GetPartialTextExtents')
    m.type = 'wxArrayInt*'
    m.find('widths').ignore()
    m.factory = True  # a new instance is being created
    m.setCppCode("""\
        wxArrayInt rval;
        self->GetPartialTextExtents(*text, rval);
        return new wxArrayInt(rval);
        """)


    c.addCppMethod('int', '__nonzero__', '()', "return self->IsOk();")
    c.addCppMethod('int', '__bool__', '()', "return self->IsOk();")

    c.addPyMethod('GetBoundingBox', '(self)', doc="""\
        GetBoundingBox() -> (x1,y1, x2,y2)\n
        Returns the min and max points used in drawing commands so far.""",
        body="return (self.MinX(), self.MinY(), self.MaxX(), self.MaxY())")


    m = c.find('GetHandle')
    m.type = 'wxUIntPtr*'
    m.setCppCode("return new wxUIntPtr((wxUIntPtr)self->GetHandle());")


    c.addCppMethod('long', 'GetHDC', '()', """\
        #ifdef __WXMSW__
            return (long)self->GetHandle();
        #else
            wxPyRaiseNotImplemented();
            return 0;
        #endif""")
    c.addCppMethod('wxUIntPtr*', 'GetCGContext', '()', """\
        #ifdef __WXMAC__
            return new wxUIntPtr((wxUIntPtr)self->GetHandle());
        #else
            wxPyRaiseNotImplemented();
            return NULL;
        #endif""")
    c.addCppMethod('wxUIntPtr*', 'GetGdkDrawable', '()', """\
        #ifdef __WXGTK__
            return new wxUIntPtr((wxUIntPtr)self->GetHandle());
        #else
            wxPyRaiseNotImplemented();
            return NULL;
        #endif""")

    c.addPyCode('DC.GetHDC = wx.deprecated(DC.GetHDC, "Use GetHandle instead.")')
    c.addPyCode('DC.GetCGContext = wx.deprecated(DC.GetCGContext, "Use GetHandle instead.")')
    c.addPyCode('DC.GetGdkDrawable = wx.deprecated(DC.GetGdkDrawable, "Use GetHandle instead.")')

    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'self.Destroy()')


    # This file contains implementations of functions for quickly drawing
    # lists of items on the DC. They are called from the CppMethods defined
    # below, which in turn are called from the PyMethods below that.
    c.includeCppCode('src/dc_ex.cpp')

    c.addCppMethod('PyObject*', '_DrawPointList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)',
        body="return wxPyDrawXXXList(*self, wxPyDrawXXXPoint, pyCoords, pyPens, pyBrushes);")

    c.addCppMethod('PyObject*', '_DrawLineList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)',
        body="return wxPyDrawXXXList(*self, wxPyDrawXXXLine, pyCoords, pyPens, pyBrushes);")

    c.addCppMethod('PyObject*', '_DrawRectangleList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)',
        body="return wxPyDrawXXXList(*self, wxPyDrawXXXRectangle, pyCoords, pyPens, pyBrushes);")

    c.addCppMethod('PyObject*', '_DrawEllipseList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)',
        body="return wxPyDrawXXXList(*self, wxPyDrawXXXEllipse, pyCoords, pyPens, pyBrushes);")

    c.addCppMethod('PyObject*', '_DrawPolygonList', '(PyObject* pyCoords, PyObject* pyPens, PyObject* pyBrushes)',
        body="return wxPyDrawXXXList(*self, wxPyDrawXXXPolygon, pyCoords, pyPens, pyBrushes);")

    c.addCppMethod('PyObject*', '_DrawTextList',
        '(PyObject* textList, PyObject* pyPoints, PyObject* foregroundList, PyObject* backgroundList)',
        body="return wxPyDrawTextList(*self, textList, pyPoints, foregroundList, backgroundList);")


    c.addPyMethod('DrawPointList', '(self, points, pens=None)',
        doc="""\
            Draw a list of points as quickly as possible.

            :param points: A sequence of 2-element sequences representing
                           each point to draw, (x,y).
            :param pens:   If None, then the current pen is used.  If a single
                           pen then it will be used for all points.  If a list of
                           pens then there should be one for each point in points.
            """,
        body="""\
            if pens is None:
                pens = []
            elif isinstance(pens, wx.Pen):
                pens = [pens]
            elif len(pens) != len(points):
                raise ValueError('points and pens must have same length')
            return self._DrawPointList(points, pens, [])
            """)

    c.addPyMethod('DrawLineList', '(self, lines, pens=None)',
        doc="""\
            Draw a list of lines as quickly as possible.

            :param lines: A sequence of 4-element sequences representing
                          each line to draw, (x1,y1, x2,y2).
            :param pens:  If None, then the current pen is used.  If a
                          single pen then it will be used for all lines.  If
                          a list of pens then there should be one for each line
                          in lines.
            """,
        body="""\
            if pens is None:
                pens = []
            elif isinstance(pens, wx.Pen):
                pens = [pens]
            elif len(pens) != len(lines):
                raise ValueError('lines and pens must have same length')
            return self._DrawLineList(lines, pens, [])
            """)

    c.addPyMethod('DrawRectangleList', '(self, rectangles, pens=None, brushes=None)',
        doc="""\
            Draw a list of rectangles as quickly as possible.

            :param rectangles: A sequence of 4-element sequences representing
                               each rectangle to draw, (x,y, w,h).
            :param pens:       If None, then the current pen is used.  If a
                               single pen then it will be used for all rectangles.
                               If a list of pens then there should be one for each
                               rectangle in rectangles.
            :param brushes:    A brush or brushes to be used to fill the rectagles,
                               with similar semantics as the pens parameter.
            """,
        body="""\
            if pens is None:
                pens = []
            elif isinstance(pens, wx.Pen):
                pens = [pens]
            elif len(pens) != len(rectangles):
                raise ValueError('rectangles and pens must have same length')
            if brushes is None:
                brushes = []
            elif isinstance(brushes, wx.Brush):
                brushes = [brushes]
            elif len(brushes) != len(rectangles):
                raise ValueError('rectangles and brushes must have same length')
            return self._DrawRectangleList(rectangles, pens, brushes)
            """)

    c.addPyMethod('DrawEllipseList', '(self, ellipses, pens=None, brushes=None)',
        doc="""\
            Draw a list of ellipses as quickly as possible.

            :param ellipses: A sequence of 4-element sequences representing
                             each ellipse to draw, (x,y, w,h).
            :param pens:     If None, then the current pen is used.  If a
                             single pen then it will be used for all ellipses.
                             If a list of pens then there should be one for each
                             ellipse in ellipses.
            :param brushes:  A brush or brushes to be used to fill the ellipses,
                             with similar semantics as the pens parameter.
            """,
        body="""\
            if pens is None:
                pens = []
            elif isinstance(pens, wx.Pen):
                pens = [pens]
            elif len(pens) != len(ellipses):
                raise ValueError('ellipses and pens must have same length')
            if brushes is None:
                brushes = []
            elif isinstance(brushes, wx.Brush):
                brushes = [brushes]
            elif len(brushes) != len(ellipses):
                raise ValueError('ellipses and brushes must have same length')
            return self._DrawEllipseList(ellipses, pens, brushes)
            """)

    c.addPyMethod('DrawPolygonList', '(self, polygons, pens=None, brushes=None)',
        doc="""\
            Draw a list of polygons, each of which is a list of points.

            :param polygons: A sequence of sequences of sequences.
                             [[(x1,y1),(x2,y2),(x3,y3)...], [(x1,y1),(x2,y2),(x3,y3)...]]

            :param pens:     If None, then the current pen is used.  If a
                             single pen then it will be used for all polygons.
                             If a list of pens then there should be one for each
                             polygon.
            :param brushes:  A brush or brushes to be used to fill the polygons,
                             with similar semantics as the pens parameter.
            """,
        body="""\
            if pens is None:
                pens = []
            elif isinstance(pens, wx.Pen):
                pens = [pens]
            elif len(pens) != len(polygons):
                raise ValueError('polygons and pens must have same length')
            if brushes is None:
                brushes = []
            elif isinstance(brushes, wx.Brush):
                brushes = [brushes]
            elif len(brushes) != len(polygons):
                raise ValueError('polygons and brushes must have same length')
            return self._DrawPolygonList(polygons, pens, brushes)
            """)

    c.addPyMethod('DrawTextList', '(self, textList, coords, foregrounds=None, backgrounds=None)',
        doc="""\
            Draw a list of strings using a list of coordinants for positioning each string.

            :param textList:    A list of strings
            :param coords:      A list of (x,y) positions
            :param foregrounds: A list of `wx.Colour` objects to use for the
                                foregrounds of the strings.
            :param backgrounds: A list of `wx.Colour` objects to use for the
                                backgrounds of the strings.

            NOTE: Make sure you set background mode to wx.Solid (DC.SetBackgroundMode)
                  If you want backgrounds to do anything.
            """,
        body="""\
            if type(textList) == type(''):
                textList = [textList]
            elif len(textList) != len(coords):
                raise ValueError('textlist and coords must have same length')
            if foregrounds is None:
                foregrounds = []
            elif isinstance(foregrounds, wx.Colour):
                foregrounds = [foregrounds]
            elif len(foregrounds) != len(coords):
                raise ValueError('foregrounds and coords must have same length')
            if backgrounds is None:
                backgrounds = []
            elif isinstance(backgrounds, wx.Colour):
                backgrounds = [backgrounds]
            elif len(backgrounds) != len(coords):
                raise ValueError('backgrounds and coords must have same length')
            return  self._DrawTextList(textList, coords, foregrounds, backgrounds)
            """)




    #-----------------------------------------------------------------
    c = module.find('wxDCClipper')
    assert isinstance(c, etgtools.ClassDef)
    c.addPrivateCopyCtor()
    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False')


    #-----------------------------------------------------------------
    c = module.find('wxDCBrushChanger')
    assert isinstance(c, etgtools.ClassDef)
    c.addPrivateCopyCtor()
    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False')


    #-----------------------------------------------------------------
    c = module.find('wxDCPenChanger')
    assert isinstance(c, etgtools.ClassDef)
    c.addPrivateCopyCtor()
    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False')


    #-----------------------------------------------------------------
    c = module.find('wxDCTextColourChanger')
    assert isinstance(c, etgtools.ClassDef)
    c.addPrivateCopyCtor()
    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False')


    #-----------------------------------------------------------------
    c = module.find('wxDCFontChanger')
    assert isinstance(c, etgtools.ClassDef)
    c.addPrivateCopyCtor()
    # context manager methods
    c.addPyMethod('__enter__', '(self)', 'return self')
    c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'return False')


    #-----------------------------------------------------------------
    tools.doCommonTweaks(module)
    tools.runGenerators(module)


#---------------------------------------------------------------------------
if __name__ == '__main__':
    run()

