Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
++
++ /*!
++ \example painting/painterpaths
++ \title Painter Paths Example
++ \examplecategory {Graphics}
++ \ingroup examples-painting
++ \brief The Painter Paths example shows how to use painter paths
++ to build complex shapes for rendering.
++
++ \image painterpaths-example.png
++ {Application showing various shapes and graphical options}
++
++ The QPainterPath class provides a container for painting
++ operations, enabling graphical shapes to be constructed and
++ reused.
++
++ A painter path is an object composed of a number of graphical
++ building blocks (such as rectangles, ellipses, lines, and curves),
++ and can be used for filling, outlining, and clipping. The main
++ advantage of painter paths over normal drawing operations is that
++ complex shapes only need to be created once, but they can be drawn
++ many times using only calls to QPainter::drawPath().
++
++ The example consists of two classes:
++
++ \list
++ \li The \c RenderArea class which is a custom widget displaying
++ a single painter path.
++ \li The \c Window class which is the applications main window
++ displaying several \c RenderArea widgets, and allowing the user
++ to manipulate the painter paths' filling, pen, color
++ and rotation angle.
++ \endlist
++
++ First we will review the \c Window class, then we will take a look
++ at the \c RenderArea class.
++
++ \section1 Window Class Definition
++
++ The \c Window class inherits QWidget, and is the applications main
++ window displaying several \c RenderArea widgets, and allowing the
++ user to manipulate the painter paths' filling, pen, color and
++ rotation angle.
++
++ \snippet painting/painterpaths/window.h 0
++
++ We declare three private slots to respond to user input regarding
++ filling and color: \c fillRuleChanged(), \c fillGradientChanged()
++ and \c penColorChanged().
++
++ When the user changes the pen width and the rotation angle, the
++ new value is passed directly on to the \c RenderArea widgets using
++ the QSpinBox::valueChanged() signal. The reason why we must
++ implement slots to update the filling and color, is that QComboBox
++ doesn't provide a similar signal passing the new value as
++ argument; so we need to retrieve the new value, or values, before
++ we can update the \c RenderArea widgets.
++
++ \snippet painting/painterpaths/window.h 1
++
++ We also declare a couple of private convenience functions: \c
++ populateWithColors() populates a given QComboBox with items
++ corresponding to the color names Qt knows about, and \c
++ currentItemData() returns the current item for a given QComboBox.
++
++ \snippet painting/painterpaths/window.h 2
++
++ Then we declare the various components of the main window
++ widget. We also declare a convenience constant specifying the
++ number of \c RenderArea widgets.
++
++ \section1 Window Class Implementation
++
++ In the \c Window constructor, we define the various painter paths
++ and create corresponding \c RenderArea widgets which will render
++ the graphical shapes:
++
++ \snippet painting/painterpaths/window.cpp 1
++
++ We construct a rectangle with sharp corners using the
++ QPainterPath::moveTo() and QPainterPath::lineTo()
++ functions.
++
++ QPainterPath::moveTo() moves the current point to the point passed
++ as argument. A painter path is an object composed of a number of
++ graphical building blocks, i.e. subpaths. Moving the current point
++ will also start a new subpath (implicitly closing the previously
++ current path when the new one is started). The
++ QPainterPath::lineTo() function adds a straight line from the
++ current point to the given end point. After the line is drawn, the
++ current point is updated to be at the end point of the line.
++
++ We first move the current point starting a new subpath, and we
++ draw three of the rectangle's sides. Then we call the
++ QPainterPath::closeSubpath() function which draws a line to the
++ beginning of the current subpath. A new subpath is automatically
++ begun when the current subpath is closed. The current point of the
++ new path is (0, 0). We could also have called
++ QPainterPath::lineTo() to draw the last line as well, and then
++ explicitly start a new subpath using the QPainterPath::moveTo()
++ function.
++
++ QPainterPath also provides the QPainterPath::addRect() convenience
++ function, which adds a given rectangle to the path as a closed
++ subpath. The rectangle is added as a clockwise set of lines. The
++ painter path's current position after the rect has been added is
++ at the top-left corner of the rectangle.
++
++ \snippet painting/painterpaths/window.cpp 2
++
++ Then we construct a rectangle with rounded corners. As before, we
++ use the QPainterPath::moveTo() and QPainterPath::lineTo()
++ functions to draw the rectangle's sides. To create the rounded
++ corners we use the QPainterPath::arcTo() function.
++
++ QPainterPath::arcTo() creates an arc that occupies the given
++ rectangle (specified by a QRect or the rectangle's coordinates),
++ beginning at the given start angle and extending the given degrees
++ counter-clockwise. Angles are specified in degrees. Clockwise arcs
++ can be specified using negative angles. The function connects the
++ current point to the starting point of the arc if they are not
++ already connected.
++
++ \snippet painting/painterpaths/window.cpp 3
++
++ We also use the QPainterPath::arcTo() function to construct the
++ ellipse path. First we move the current point starting a new
++ path. Then we call QPainterPath::arcTo() with starting angle 0.0
++ and 360.0 degrees as the last argument, creating an ellipse.
++
++ Again, QPainterPath provides a convenience function (
++ QPainterPath::addEllipse()) which creates an ellipse within a
++ given bounding rectangle and adds it to the painter path. If the
++ current subpath is closed, a new subpath is started. The ellipse
++ is composed of a clockwise curve, starting and finishing at zero
++ degrees (the 3 o'clock position).
++
++ \snippet painting/painterpaths/window.cpp 4
++
++ When constructing the pie chart path we continue to use a
++ combination of the mentioned functions: First we move the current
++ point, starting a new subpath. Then we create a line from the
++ center of the chart to the arc, and the arc itself. When we close
++ the subpath, we implicitly construct the last line back to the
++ center of the chart.
++
++ \snippet painting/painterpaths/window.cpp 5
++
++ Constructing a polygon is equivalent to constructing a rectangle.
++
++ QPainterPath also provides the QPainterPath::addPolygon()
++ convenience function which adds the given polygon to the path as a
++ new subpath. Current position after the polygon has been added is
++ the last point in polygon.
++
++ \snippet painting/painterpaths/window.cpp 6
++
++ Then we create a path consisting of a group of subpaths: First we
++ move the current point, and create a circle using the
++ QPainterPath::arcTo() function with starting angle 0.0, and 360
++ degrees as the last argument, as we did when we created the
++ ellipse path. Then we move the current point again, starting a
++ new subpath, and construct three sides of a square using the
++ QPainterPath::lineTo() function.
++
++ Now, when we call the QPainterPath::closeSubpath() function the
++ last side is created. Remember that the
++ QPainterPath::closeSubpath() function draws a line to the
++ beginning of the \e current subpath, i.e the square.
++
++ QPainterPath provide a convenience function,
++ QPainterPath::addPath() which adds a given path to the path that
++ calls the function.
++
++ \snippet painting/painterpaths/window.cpp 7
++
++ When creating the text path, we first create the font. Then we set
++ the font's style strategy which tells the font matching algorithm
++ what type of fonts should be used to find an appropriate default
++ family. QFont::ForceOutline forces the use of outline fonts.
++
++ To construct the text, we use the QPainterPath::addText() function
++ which adds the given text to the path as a set of closed subpaths
++ created from the supplied font. The subpaths are positioned so
++ that the left end of the text's baseline lies at the specified
++ point.
++
++ \snippet painting/painterpaths/window.cpp 8
++
++ To create the Bezier path, we use the QPainterPath::cubicTo()
++ function which adds a Bezier curve between the current point and
++ the given end point with the given control point. After the curve
++ is added, the current point is updated to be at the end point of
++ the curve.
++
++ In this case we omit to close the subpath so that we only have a
++ simple curve. But there is still a logical line from the curve's
++ endpoint back to the beginning of the subpath; it becomes visible
++ when filling the path as can be seen in the applications main
++ window.
++
++ \snippet painting/painterpaths/window.cpp 9
++
++ The final path that we construct shows that you can use
++ QPainterPath to construct rather complex shapes using only the
++ previous mentioned QPainterPath::moveTo(), QPainterPath::lineTo()
++ and QPainterPath::closeSubpath() functions.
++
++ \snippet painting/painterpaths/window.cpp 10
++
++ Now that we have created all the painter paths that we need, we
++ create a corresponding \c RenderArea widget for each. In the end,
++ we make sure that the number of render areas is correct using the
++ Q_ASSERT() macro.
++
++ \snippet painting/painterpaths/window.cpp 11
++
++ Then we create the widgets associated with the painter paths' fill
++ rule.
++
++ There are two available fill rules in Qt: The Qt::OddEvenFill rule
++ determine whether a point is inside the shape by drawing a
++ horizontal line from the point to a location outside the shape,
++ and count the number of intersections. If the number of
++ intersections is an odd number, the point is inside the
++ shape. This rule is the default.
++
++ The Qt::WindingFill rule determine whether a point is inside the
++ shape by drawing a horizontal line from the point to a location
++ outside the shape. Then it determines whether the direction of the
++ line at each intersection point is up or down. The winding number
++ is determined by summing the direction of each intersection. If
++ the number is non zero, the point is inside the shape.
++
++ The Qt::WindingFill rule can in most cases be considered as the
++ intersection of closed shapes.
++
++ \snippet painting/painterpaths/window.cpp 12
++
++ We also create the other widgets associated with the filling, the
++ pen and the rotation angle.
++
++ \snippet painting/painterpaths/window.cpp 16
++
++ We connect the comboboxes \l {QComboBox::activated()}{activated()}
++ signals to the associated slots in the \c Window class, while we
++ connect the spin boxes \l
++ {QSpinBox::valueChanged()}{valueChanged()} signal directly to the
++ \c RenderArea widget's respective slots.
++
++ \snippet painting/painterpaths/window.cpp 17
++
++ We add the \c RenderArea widgets to a separate layout which we
++ then add to the main layout along with the rest of the widgets.
++
++ \snippet painting/painterpaths/window.cpp 18
++
++ Finally, we initialize the \c RenderArea widgets by calling the \c
++ fillRuleChanged(), \c fillGradientChanged() and \c
++ penColorChanged() slots, and we set the initial pen width and
++ window title.
++
++ \snippet painting/painterpaths/window.cpp 19
++ \codeline
++ \snippet painting/painterpaths/window.cpp 20
++ \codeline
++ \snippet painting/painterpaths/window.cpp 21
++
++ The private slots are implemented to retrieve the new value, or
++ values, from the associated comboboxes and update the RenderArea
++ widgets.
++
++ First we determine the new value, or values, using the private \c
++ currentItemData() function and the qvariant_cast() template
++ function. Then we call the associated slot for each of the \c
++ RenderArea widgets to update the painter paths.
++
++ \snippet painting/painterpaths/window.cpp 22
++
++ The \c populateWithColors() function populates the given combobox
++ with items corresponding to the color names Qt knows about
++ provided by the static QColor::colorNames() function.
++
++ \snippet painting/painterpaths/window.cpp 23
++
++ The \c currentItemData() function simply return the current item
++ of the given combobox.
++
++ \section1 RenderArea Class Definition
++
++ The \c RenderArea class inherits QWidget, and is a custom widget
++ displaying a single painter path.
++
++ \snippet painting/painterpaths/renderarea.h 0
++
++ We declare several public slots updating the \c RenderArea
++ widget's associated painter path. In addition we reimplement the
++ QWidget::minimumSizeHint() and QWidget::sizeHint() functions to
++ give the \c RenderArea widget a reasonable size within our
++ application, and we reimplement the QWidget::paintEvent() event
++ handler to draw its painter path.
++
++ \snippet painting/painterpaths/renderarea.h 1
++
++ Each instance of the \c RenderArea class has a QPainterPath, a
++ couple of fill colors, a pen width, a pen color and a rotation
++ angle.
++
++ \section1 RenderArea Class Implementation
++
++ The constructor takes a QPainterPath as argument (in addition to
++ the optional QWidget parent):
++
++ \snippet painting/painterpaths/renderarea.cpp 0
++
++ In the constructor we initialize the \c RenderArea widget with the
++ QPainterPath parameter as well as initializing the pen width and
++ rotation angle. We also set the widgets \l
++ {QWidget::backgroundRole()}{background role}; QPalette::Base is
++ typically white.
++
++ \snippet painting/painterpaths/renderarea.cpp 1
++ \codeline
++ \snippet painting/painterpaths/renderarea.cpp 2
++
++ Then we reimplement the QWidget::minimumSizeHint() and
++ QWidget::sizeHint() functions to give the \c RenderArea widget a
++ reasonable size within our application.
++
++ \snippet painting/painterpaths/renderarea.cpp 3
++ \codeline
++ \snippet painting/painterpaths/renderarea.cpp 4
++ \codeline
++ \snippet painting/painterpaths/renderarea.cpp 5
++ \codeline
++ \snippet painting/painterpaths/renderarea.cpp 6
++ \codeline
++ \snippet painting/painterpaths/renderarea.cpp 7
++
++ The various public slots updates the \c RenderArea widget's
++ painter path by setting the associated property and make a call to
++ the QWidget::update() function, forcing a repaint of the widget
++ with the new rendering preferences.
++
++ The QWidget::update() slot does not cause an immediate repaint;
++ instead it schedules a paint event for processing when Qt returns
++ to the main event loop.
++
++ \snippet painting/painterpaths/renderarea.cpp 8
++
++ A paint event is a request to repaint all or parts of the
++ widget. The paintEvent() function is an event handler that can be
++ reimplemented to receive the widget's paint events. We reimplement
++ the event handler to render the \c RenderArea widget's painter
++ path.
++
++ First, we create a QPainter for the \c RenderArea instance, and
++ set the painter's render hints. The QPainter::RenderHints are used
++ to specify flags to QPainter that may, or may not, be respected by
++ any given engine. QPainter::Antialiasing indicates that the engine
++ should anti-alias the edges of primitives if possible, i.e. put
++ additional pixels around the original ones to smooth the edges.
++
++ \snippet painting/painterpaths/renderarea.cpp 9
++
++ Then we scale the QPainter's coordinate system to ensure that the
++ painter path is rendered in the right size, i.e that it grows with
++ the \c RenderArea widget when the application is resized. When we
++ constructed the various painter paths, they were all rnedered
++ within a square with a 100 pixel width which is equivalent to \c
++ RenderArea::sizeHint(). The QPainter::scale() function scales the
++ coordinate system by the \c RenderArea widget's \e current width
++ and height divided by 100.
++
++ Now, when we are sure that the painter path has the right size, we
++ can translate the coordinate system to make the painter path
++ rotate around the \c RenderArea widget's center. After we have
++ performed the rotation, we must remember to translate the
++ coordinate system back again.
++
++ \snippet painting/painterpaths/renderarea.cpp 10
++
++ Then we set the QPainter's pen with the instance's rendering
++ preferences. We create a QLinearGradient and set its colors
++ corresponding to the \c RenderArea widget's fill colors. Finally,
++ we set the QPainter's brush (the gradient is automatically
++ converted into a QBrush), and draw the \c RenderArea widget's
++ painter path using the QPainter::drawPath() function.
++ */