1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtWidgets module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 /*!
41     \class QGraphicsAnchorLayout
42     \brief The QGraphicsAnchorLayout class provides a layout where one can anchor widgets
43     together in Graphics View.
44     \since 4.6
45     \ingroup appearance
46     \ingroup geomanagement
47     \ingroup graphicsview-api
48     \inmodule QtWidgets
49 
50     The anchor layout allows developers to specify how widgets should be placed relative to
51     each other, and to the layout itself. The specification is made by adding anchors to the
52     layout by calling addAnchor(), addAnchors() or addCornerAnchors().
53 
54     Existing anchors in the layout can be accessed with the anchor() function.
55     Items that are anchored are automatically added to the layout, and if items
56     are removed, all their anchors will be automatically removed.
57 
58     \div {class="float-left"}
59     \inlineimage simpleanchorlayout-example.png Using an anchor layout to align simple colored widgets.
60     \enddiv
61 
62     Anchors are always set up between edges of an item, where the "center" is also considered to
63     be an edge. Consider the following example:
64 
65     \snippet graphicsview/simpleanchorlayout/main.cpp adding anchors
66 
67     Here, the right edge of item \c a is anchored to the left edge of item \c b and the bottom
68     edge of item \c a is anchored to the top edge of item \c b, with the result that
69     item \c b will be placed diagonally to the right and below item \c b.
70 
71     The addCornerAnchors() function provides a simpler way of anchoring the corners
72     of two widgets than the two individual calls to addAnchor() shown in the code
73     above. Here, we see how a widget can be anchored to the top-left corner of the enclosing
74     layout:
75 
76     \snippet graphicsview/simpleanchorlayout/main.cpp adding a corner anchor
77 
78     In cases where anchors are used to match the widths or heights of widgets, it is
79     convenient to use the addAnchors() function. As with the other functions for specifying
80     anchors, it can also be used to anchor a widget to a layout.
81 
82     \section1 Size Hints and Size Policies in an Anchor Layout
83 
84     QGraphicsAnchorLayout respects each item's size hints and size policies.
85     Note that there are some properties of QSizePolicy that are \l{Known issues}{not respected}.
86 
87     \section1 Spacing within an Anchor Layout
88 
89     The layout may distribute some space between the items. If the spacing has not been
90     explicitly specified, the actual amount of space will usually be 0.
91 
92     However, if the first edge is the \e opposite of the second edge (e.g., the right edge
93     of the first widget is anchored to the left edge of the second widget), the size of the
94     anchor will be queried from the style through a pixel metric:
95     \l{QStyle::}{PM_LayoutHorizontalSpacing} for horizontal anchors and
96     \l{QStyle::}{PM_LayoutVerticalSpacing} for vertical anchors.
97 
98     If the spacing is negative, the items will overlap to some extent.
99 
100 
101     \section1 Known Issues
102     There are some features that QGraphicsAnchorLayout currently does not support.
103     This might change in the future, so avoid using these features if you want to
104     avoid any future regressions in behaviour:
105     \list
106 
107     \li Stretch factors are not respected.
108 
109     \li QSizePolicy::ExpandFlag is not respected.
110 
111     \li Height for width is not respected.
112 
113     \endlist
114 
115     \sa QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayout
116 */
117 
118 /*!
119     \class QGraphicsAnchor
120     \brief The QGraphicsAnchor class represents an anchor between two items in a
121            QGraphicsAnchorLayout.
122     \since 4.6
123     \ingroup appearance
124     \ingroup geomanagement
125     \ingroup graphicsview-api
126     \inmodule QtWidgets
127 
128     The graphics anchor provides an API that enables you to query and manipulate the
129     properties an anchor has. When an anchor is added to the layout with
130     QGraphicsAnchorLayout::addAnchor(), a QGraphicsAnchor instance is returned where the properties
131     are initialized to their default values. The properties can then be further changed, and they
132     will be picked up the next time the layout is activated.
133 
134     \sa QGraphicsAnchorLayout::anchor()
135 
136 */
137 #include "qgraphicsanchorlayout_p.h"
138 
139 QT_BEGIN_NAMESPACE
140 
QGraphicsAnchor(QGraphicsAnchorLayout * parentLayout)141 QGraphicsAnchor::QGraphicsAnchor(QGraphicsAnchorLayout *parentLayout)
142     : QObject(*(new QGraphicsAnchorPrivate))
143 {
144     Q_D(QGraphicsAnchor);
145     Q_ASSERT(parentLayout);
146     d->layoutPrivate = parentLayout->d_func();
147 }
148 
149 /*!
150     Removes the QGraphicsAnchor object from the layout and destroys it.
151 */
~QGraphicsAnchor()152 QGraphicsAnchor::~QGraphicsAnchor()
153 {
154 }
155 
156 /*!
157     \property QGraphicsAnchor::sizePolicy
158     \brief the size policy for the QGraphicsAnchor.
159 
160     By setting the size policy on an anchor you can configure how the anchor can resize itself
161     from its preferred spacing. For instance, if the anchor has the size policy
162     QSizePolicy::Minimum, the spacing is the minimum size of the anchor. However, its size
163     can grow up to the anchors maximum size. If the default size policy is QSizePolicy::Fixed,
164     the anchor can neither grow or shrink, which means that the only size the anchor can have
165     is the spacing. QSizePolicy::Fixed is the default size policy.
166     QGraphicsAnchor always has a minimum spacing of 0 and a very large maximum spacing.
167 
168     \sa QGraphicsAnchor::spacing
169 */
170 
setSizePolicy(QSizePolicy::Policy policy)171 void QGraphicsAnchor::setSizePolicy(QSizePolicy::Policy policy)
172 {
173     Q_D(QGraphicsAnchor);
174     d->setSizePolicy(policy);
175 }
176 
sizePolicy() const177 QSizePolicy::Policy QGraphicsAnchor::sizePolicy() const
178 {
179     Q_D(const QGraphicsAnchor);
180     return d->sizePolicy;
181 }
182 
183 /*!
184     \property QGraphicsAnchor::spacing
185     \brief the preferred space between items in the QGraphicsAnchorLayout.
186 
187     Depending on the anchor type, the default spacing is either
188     0 or a value returned from the style.
189 
190     \sa QGraphicsAnchorLayout::addAnchor()
191 */
setSpacing(qreal spacing)192 void QGraphicsAnchor::setSpacing(qreal spacing)
193 {
194     Q_D(QGraphicsAnchor);
195     d->setSpacing(spacing);
196 }
197 
spacing() const198 qreal QGraphicsAnchor::spacing() const
199 {
200     Q_D(const QGraphicsAnchor);
201     return d->spacing();
202 }
203 
unsetSpacing()204 void QGraphicsAnchor::unsetSpacing()
205 {
206     Q_D(QGraphicsAnchor);
207     d->unsetSpacing();
208 }
209 
210 /*!
211     Constructs a QGraphicsAnchorLayout instance.  \a parent is passed to
212     QGraphicsLayout's constructor.
213   */
QGraphicsAnchorLayout(QGraphicsLayoutItem * parent)214 QGraphicsAnchorLayout::QGraphicsAnchorLayout(QGraphicsLayoutItem *parent)
215     : QGraphicsLayout(*new QGraphicsAnchorLayoutPrivate(), parent)
216 {
217     Q_D(QGraphicsAnchorLayout);
218     d->createLayoutEdges();
219 }
220 
221 /*!
222     Destroys the QGraphicsAnchorLayout object.
223 */
~QGraphicsAnchorLayout()224 QGraphicsAnchorLayout::~QGraphicsAnchorLayout()
225 {
226     Q_D(QGraphicsAnchorLayout);
227 
228     for (int i = count() - 1; i >= 0; --i) {
229         QGraphicsLayoutItem *item = d->items.at(i);
230         removeAt(i);
231         if (item) {
232             if (item->ownedByLayout())
233                 delete item;
234         }
235     }
236 
237     d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Horizontal);
238     d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Vertical);
239     d->deleteLayoutEdges();
240 
241     Q_ASSERT(d->itemCenterConstraints[0].isEmpty());
242     Q_ASSERT(d->itemCenterConstraints[1].isEmpty());
243     Q_ASSERT(d->items.isEmpty());
244     Q_ASSERT(d->m_vertexList.isEmpty());
245 }
246 
247 /*!
248     Creates an anchor between the edge \a firstEdge of item \a firstItem and the edge \a secondEdge
249     of item \a secondItem. The spacing of the anchor is picked up from the style. Anchors
250     between a layout edge and an item edge will have a size of 0.
251     If there is already an anchor between the edges, the new anchor will replace the old one.
252 
253     \a firstItem and \a secondItem are automatically added to the layout if they are not part
254     of the layout. This means that count() can increase by up to 2.
255 
256     The spacing an anchor will get depends on the type of anchor. For instance, anchors from the
257     Right edge of one item to the Left edge of another (or vice versa) will use the default
258     horizontal spacing. The same behaviour applies to Bottom to Top anchors, (but they will use
259     the default vertical spacing). For all other anchor combinations, the spacing will be 0.
260     All anchoring functions will follow this rule.
261 
262     The spacing can also be set manually by using QGraphicsAnchor::setSpacing() method.
263 
264     Calling this function where \a firstItem or \a secondItem are ancestors of the layout have
265     undefined behaviour.
266 
267     \sa addAnchors(), addCornerAnchors()
268  */
269 QGraphicsAnchor *
addAnchor(QGraphicsLayoutItem * firstItem,Qt::AnchorPoint firstEdge,QGraphicsLayoutItem * secondItem,Qt::AnchorPoint secondEdge)270 QGraphicsAnchorLayout::addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge,
271                                  QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge)
272 {
273     Q_D(QGraphicsAnchorLayout);
274     QGraphicsAnchor *a = d->addAnchor(firstItem, firstEdge, secondItem, secondEdge);
275     invalidate();
276     return a;
277 }
278 
279 /*!
280     Returns the anchor between the anchor points defined by \a firstItem and \a firstEdge and
281     \a secondItem and \a secondEdge. If there is no such anchor, the function will return 0.
282 */
283 QGraphicsAnchor *
anchor(QGraphicsLayoutItem * firstItem,Qt::AnchorPoint firstEdge,QGraphicsLayoutItem * secondItem,Qt::AnchorPoint secondEdge)284 QGraphicsAnchorLayout::anchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge,
285                               QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge)
286 {
287     Q_D(QGraphicsAnchorLayout);
288     return d->getAnchor(firstItem, firstEdge, secondItem, secondEdge);
289 }
290 
291 /*!
292     Creates two anchors between \a firstItem and \a secondItem specified by the corners,
293     \a firstCorner and \a secondCorner, where one is for the horizontal edge and another
294     one for the vertical edge.
295 
296     This is a convenience function, since anchoring corners can be expressed as anchoring
297     two edges. For instance:
298 
299     \snippet graphicsview/simpleanchorlayout/main.cpp adding a corner anchor in two steps
300 
301     This can also be achieved with the following line of code:
302 
303     \snippet graphicsview/simpleanchorlayout/main.cpp adding a corner anchor
304 
305     If there is already an anchor between the edge pairs, it will be replaced by the anchors that
306     this function specifies.
307 
308     \a firstItem and \a secondItem are automatically added to the layout if they are not part of the
309     layout. This means that count() can increase by up to 2.
310 
311     \sa addAnchor(), addAnchors()
312 */
addCornerAnchors(QGraphicsLayoutItem * firstItem,Qt::Corner firstCorner,QGraphicsLayoutItem * secondItem,Qt::Corner secondCorner)313 void QGraphicsAnchorLayout::addCornerAnchors(QGraphicsLayoutItem *firstItem,
314                                              Qt::Corner firstCorner,
315                                              QGraphicsLayoutItem *secondItem,
316                                              Qt::Corner secondCorner)
317 {
318     Q_D(QGraphicsAnchorLayout);
319 
320     // Horizontal anchor
321     Qt::AnchorPoint firstEdge = (firstCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft);
322     Qt::AnchorPoint secondEdge = (secondCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft);
323     if (d->addAnchor(firstItem, firstEdge, secondItem, secondEdge)) {
324         // Vertical anchor
325         firstEdge = (firstCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop);
326         secondEdge = (secondCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop);
327         d->addAnchor(firstItem, firstEdge, secondItem, secondEdge);
328 
329         invalidate();
330     }
331 }
332 
333 /*!
334     Anchors two or four edges of \a firstItem with the corresponding
335     edges of \a secondItem, so that \a firstItem has the same size as
336     \a secondItem in the dimensions specified by \a orientations.
337 
338     For example, the following example anchors the left and right edges of two items
339     to match their widths:
340 
341     \snippet graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes in two steps
342 
343     This can also be achieved using the following line of code:
344 
345     \snippet graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes
346 
347     \sa addAnchor(), addCornerAnchors()
348 */
addAnchors(QGraphicsLayoutItem * firstItem,QGraphicsLayoutItem * secondItem,Qt::Orientations orientations)349 void QGraphicsAnchorLayout::addAnchors(QGraphicsLayoutItem *firstItem,
350                                        QGraphicsLayoutItem *secondItem,
351                                        Qt::Orientations orientations)
352 {
353     bool ok = true;
354     if (orientations & Qt::Horizontal) {
355         // Currently, if the first is ok, then the rest of the calls should be ok
356         ok = addAnchor(secondItem, Qt::AnchorLeft, firstItem, Qt::AnchorLeft) != nullptr;
357         if (ok)
358             addAnchor(firstItem, Qt::AnchorRight, secondItem, Qt::AnchorRight);
359     }
360     if (orientations & Qt::Vertical && ok) {
361         addAnchor(secondItem, Qt::AnchorTop, firstItem, Qt::AnchorTop);
362         addAnchor(firstItem, Qt::AnchorBottom, secondItem, Qt::AnchorBottom);
363     }
364 }
365 
366 /*!
367     Sets the default horizontal spacing for the anchor layout to \a spacing.
368 
369     \sa horizontalSpacing(), setVerticalSpacing(), setSpacing()
370 */
setHorizontalSpacing(qreal spacing)371 void QGraphicsAnchorLayout::setHorizontalSpacing(qreal spacing)
372 {
373     Q_D(QGraphicsAnchorLayout);
374 
375     d->spacings[0] = spacing;
376     invalidate();
377 }
378 
379 /*!
380     Sets the default vertical spacing for the anchor layout to \a spacing.
381 
382     \sa verticalSpacing(), setHorizontalSpacing(), setSpacing()
383 */
setVerticalSpacing(qreal spacing)384 void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing)
385 {
386     Q_D(QGraphicsAnchorLayout);
387 
388     d->spacings[1] = spacing;
389     invalidate();
390 }
391 
392 /*!
393     Sets the default horizontal and the default vertical spacing for the anchor layout to \a spacing.
394 
395     If an item is anchored with no spacing associated with the anchor, it will use the default
396     spacing.
397 
398     QGraphicsAnchorLayout does not support negative spacings. Setting a negative value will unset the
399     previous spacing and make the layout use the spacing provided by the current widget style.
400 
401     \sa setHorizontalSpacing(), setVerticalSpacing()
402 */
setSpacing(qreal spacing)403 void QGraphicsAnchorLayout::setSpacing(qreal spacing)
404 {
405     Q_D(QGraphicsAnchorLayout);
406 
407     d->spacings[0] = d->spacings[1] = spacing;
408     invalidate();
409 }
410 
411 /*!
412     Returns the default horizontal spacing for the anchor layout.
413 
414     \sa verticalSpacing(), setHorizontalSpacing()
415 */
horizontalSpacing() const416 qreal QGraphicsAnchorLayout::horizontalSpacing() const
417 {
418     Q_D(const QGraphicsAnchorLayout);
419     return d->styleInfo().defaultSpacing(Qt::Horizontal);
420 }
421 
422 /*!
423     Returns the default vertical spacing for the anchor layout.
424 
425     \sa horizontalSpacing(), setVerticalSpacing()
426 */
verticalSpacing() const427 qreal QGraphicsAnchorLayout::verticalSpacing() const
428 {
429     Q_D(const QGraphicsAnchorLayout);
430     return d->styleInfo().defaultSpacing(Qt::Vertical);
431 }
432 
433 /*!
434     \reimp
435 */
setGeometry(const QRectF & geom)436 void QGraphicsAnchorLayout::setGeometry(const QRectF &geom)
437 {
438     Q_D(QGraphicsAnchorLayout);
439 
440     QGraphicsLayout::setGeometry(geom);
441     d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Horizontal);
442     d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Vertical);
443     d->setItemsGeometries(geom);
444 }
445 
446 /*!
447     Removes the layout item at \a index without destroying it. Ownership of
448     the item is transferred to the caller.
449 
450     Removing an item will also remove any of the anchors associated with it.
451 
452     \sa itemAt(), count()
453 */
removeAt(int index)454 void QGraphicsAnchorLayout::removeAt(int index)
455 {
456     Q_D(QGraphicsAnchorLayout);
457     QGraphicsLayoutItem *item = d->items.value(index);
458 
459     if (!item)
460         return;
461 
462     // Removing an item affects both horizontal and vertical graphs
463     d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Horizontal);
464     d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Vertical);
465     d->removeAnchors(item);
466     d->items.remove(index);
467 
468     item->setParentLayoutItem(nullptr);
469     invalidate();
470 }
471 
472 /*!
473     \reimp
474 */
count() const475 int QGraphicsAnchorLayout::count() const
476 {
477     Q_D(const QGraphicsAnchorLayout);
478     return d->items.size();
479 }
480 
481 /*!
482     \reimp
483 */
itemAt(int index) const484 QGraphicsLayoutItem *QGraphicsAnchorLayout::itemAt(int index) const
485 {
486     Q_D(const QGraphicsAnchorLayout);
487     return d->items.value(index);
488 }
489 
490 /*!
491     \reimp
492 */
invalidate()493 void QGraphicsAnchorLayout::invalidate()
494 {
495     Q_D(QGraphicsAnchorLayout);
496     QGraphicsLayout::invalidate();
497     d->calculateGraphCacheDirty = true;
498     d->styleInfoDirty = true;
499 }
500 
501 /*!
502     \reimp
503 */
sizeHint(Qt::SizeHint which,const QSizeF & constraint) const504 QSizeF QGraphicsAnchorLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
505 {
506     Q_UNUSED(constraint);
507     Q_D(const QGraphicsAnchorLayout);
508 
509     // Some setup calculations are delayed until the information is
510     // actually needed, avoiding unnecessary recalculations when
511     // adding multiple anchors.
512 
513     // sizeHint() / effectiveSizeHint() already have a cache
514     // mechanism, using invalidate() to force recalculation. However
515     // sizeHint() is called three times after invalidation (for max,
516     // min and pref), but we just need do our setup once.
517 
518     const_cast<QGraphicsAnchorLayoutPrivate *>(d)->calculateGraphs();
519 
520     // ### apply constraint!
521     QSizeF engineSizeHint(
522         d->sizeHints[QGraphicsAnchorLayoutPrivate::Horizontal][which],
523         d->sizeHints[QGraphicsAnchorLayoutPrivate::Vertical][which]);
524 
525     qreal left, top, right, bottom;
526     getContentsMargins(&left, &top, &right, &bottom);
527 
528     return engineSizeHint + QSizeF(left + right, top + bottom);
529 }
530 
531 QT_END_NAMESPACE
532 
533 #include "moc_qgraphicsanchorlayout.cpp"
534