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
139QT_BEGIN_NAMESPACE
140
141QGraphicsAnchor::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*/
152QGraphicsAnchor::~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
171void QGraphicsAnchor::setSizePolicy(QSizePolicy::Policy policy)
172{
173 Q_D(QGraphicsAnchor);
174 d->setSizePolicy(policy);
175}
176
177QSizePolicy::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*/
192void QGraphicsAnchor::setSpacing(qreal spacing)
193{
194 Q_D(QGraphicsAnchor);
195 d->setSpacing(spacing);
196}
197
198qreal QGraphicsAnchor::spacing() const
199{
200 Q_D(const QGraphicsAnchor);
201 return d->spacing();
202}
203
204void 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 */
214QGraphicsAnchorLayout::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*/
224QGraphicsAnchorLayout::~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, Qt::Horizontal);
238 d->removeCenterConstraints(this, Qt::Vertical);
239 d->deleteLayoutEdges();
240
241 Q_ASSERT(d->itemCenterConstraints[Qt::Horizontal].isEmpty());
242 Q_ASSERT(d->itemCenterConstraints[Qt::Vertical].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 */
269QGraphicsAnchor *
270QGraphicsAnchorLayout::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*/
283QGraphicsAnchor *
284QGraphicsAnchorLayout::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*/
313void 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*/
349void 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*/
371void QGraphicsAnchorLayout::setHorizontalSpacing(qreal spacing)
372{
373 Q_D(QGraphicsAnchorLayout);
374
375 d->spacings[Qt::Horizontal] = 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*/
384void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing)
385{
386 Q_D(QGraphicsAnchorLayout);
387
388 d->spacings[Qt::Vertical] = 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*/
403void QGraphicsAnchorLayout::setSpacing(qreal spacing)
404{
405 Q_D(QGraphicsAnchorLayout);
406
407 d->spacings = {spacing, spacing};
408 invalidate();
409}
410
411/*!
412 Returns the default horizontal spacing for the anchor layout.
413
414 \sa verticalSpacing(), setHorizontalSpacing()
415*/
416qreal 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*/
427qreal QGraphicsAnchorLayout::verticalSpacing() const
428{
429 Q_D(const QGraphicsAnchorLayout);
430 return d->styleInfo().defaultSpacing(Qt::Vertical);
431}
432
433/*!
434 \reimp
435*/
436void QGraphicsAnchorLayout::setGeometry(const QRectF &geom)
437{
438 Q_D(QGraphicsAnchorLayout);
439
440 QGraphicsLayout::setGeometry(geom);
441 d->calculateVertexPositions(Qt::Horizontal);
442 d->calculateVertexPositions(Qt::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*/
454void 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, Qt::Horizontal);
464 d->removeCenterConstraints(item, Qt::Vertical);
465 d->removeAnchors(item);
466 d->items.remove(index);
467
468 item->setParentLayoutItem(nullptr);
469 invalidate();
470}
471
472/*!
473 \reimp
474*/
475int QGraphicsAnchorLayout::count() const
476{
477 Q_D(const QGraphicsAnchorLayout);
478 return d->items.size();
479}
480
481/*!
482 \reimp
483*/
484QGraphicsLayoutItem *QGraphicsAnchorLayout::itemAt(int index) const
485{
486 Q_D(const QGraphicsAnchorLayout);
487 return d->items.value(index);
488}
489
490/*!
491 \reimp
492*/
493void QGraphicsAnchorLayout::invalidate()
494{
495 Q_D(QGraphicsAnchorLayout);
496 QGraphicsLayout::invalidate();
497 d->calculateGraphCacheDirty = true;
498 d->styleInfoDirty = true;
499}
500
501/*!
502 \reimp
503*/
504QSizeF 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{d->sizeHints[Qt::Horizontal][which],
522 d->sizeHints[Qt::Vertical][which]};
523
524 qreal left, top, right, bottom;
525 getContentsMargins(&left, &top, &right, &bottom);
526
527 return engineSizeHint + QSizeF(left + right, top + bottom);
528}
529
530QT_END_NAMESPACE
531
532#include "moc_qgraphicsanchorlayout.cpp"
533