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

source code of qtbase/src/widgets/graphicsview/qgraphicsanchorlayout.cpp