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 QGraphicsLinearLayout |
6 | \brief The QGraphicsLinearLayout class provides a horizontal or vertical |
7 | layout for managing widgets in Graphics View. |
8 | \since 4.4 |
9 | \ingroup graphicsview-api |
10 | \inmodule QtWidgets |
11 | |
12 | The default orientation for a linear layout is Qt::Horizontal. You can |
13 | choose a vertical orientation either by calling setOrientation(), or by |
14 | passing Qt::Vertical to QGraphicsLinearLayout's constructor. |
15 | |
16 | The most common way to use QGraphicsLinearLayout is to construct an object |
17 | on the heap, passing a parent widget to the constructor, then add widgets |
18 | and layouts by calling addItem(). |
19 | |
20 | \snippet code/src_gui_graphicsview_qgraphicslinearlayout.cpp 0 |
21 | |
22 | Alternatively, if you do not pass a parent widget to the layout's constructor, |
23 | you will need to call QGraphicsWidget::setLayout() to set this layout as the |
24 | top-level layout for that widget, the widget will take ownership of |
25 | the layout. |
26 | |
27 | You can add widgets, layouts, stretches (addStretch(), insertStretch() or |
28 | setStretchFactor()), and spacings (setItemSpacing()) to a linear |
29 | layout. The layout takes ownership of the items. In some cases when the layout |
30 | item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a |
31 | ambiguity in ownership because the layout item belongs to two ownership hierarchies. |
32 | See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle |
33 | this. |
34 | You can access each item in the layout by calling count() and itemAt(). Calling |
35 | removeAt() or removeItem() will remove an item from the layout, without |
36 | destroying it. |
37 | |
38 | \section1 Size Hints and Size Policies in QGraphicsLinearLayout |
39 | |
40 | QGraphicsLinearLayout respects each item's size hints and size policies, |
41 | and when the layout contains more space than the items can fill, each item |
42 | is arranged according to the layout's alignment for that item. You can set |
43 | an alignment for each item by calling setAlignment(), and check the |
44 | alignment for any item by calling alignment(). By default, items are |
45 | aligned to the top left. |
46 | |
47 | \section1 Spacing within QGraphicsLinearLayout |
48 | |
49 | Between the items, the layout distributes some space. The actual amount of |
50 | space depends on the managed widget's current style, but the common |
51 | spacing is 4. You can also set your own spacing by calling setSpacing(), |
52 | and get the current spacing value by calling spacing(). If you want to |
53 | configure individual spacing for your items, you can call setItemSpacing(). |
54 | |
55 | \section1 Stretch Factor in QGraphicsLinearLayout |
56 | |
57 | You can assign a stretch factor to each item to control how much space it |
58 | will get compared to the other items. By default, two identical widgets |
59 | arranged in a linear layout will have the same size, but if the first |
60 | widget has a stretch factor of 1 and the second widget has a stretch |
61 | factor of 2, the first widget will get 1/3 of the available space, and the |
62 | second will get 2/3. |
63 | |
64 | QGraphicsLinearLayout calculates the distribution of sizes by adding up |
65 | the stretch factors of all items, and then dividing the available space |
66 | accordingly. The default stretch factor is 0 for all items; a factor of 0 |
67 | means the item does not have any defined stretch factor; effectively this |
68 | is the same as setting the stretch factor to 1. The stretch factor only |
69 | applies to the available space in the lengthwise direction of the layout |
70 | (following its orientation). If you want to control both the item's |
71 | horizontal and vertical stretch, you can use QGraphicsGridLayout instead. |
72 | |
73 | \section1 QGraphicsLinearLayout Compared to Other Layouts |
74 | |
75 | QGraphicsLinearLayout is very similar to QVBoxLayout and QHBoxLayout, but |
76 | in contrast to these classes, it is used to manage QGraphicsWidget and |
77 | QGraphicsLayout instead of QWidget and QLayout. |
78 | |
79 | \sa QGraphicsGridLayout, QGraphicsWidget |
80 | */ |
81 | |
82 | #include "qapplication.h" |
83 | |
84 | #include "qwidget.h" |
85 | #include "qgraphicslayout_p.h" |
86 | #include "qgraphicslayoutitem.h" |
87 | #include "qgraphicslinearlayout.h" |
88 | #include "qgraphicswidget.h" |
89 | #include "qgraphicsgridlayoutengine_p.h" |
90 | #include "qgraphicslayoutstyleinfo_p.h" |
91 | #include "qscopedpointer.h" |
92 | #ifdef QT_DEBUG |
93 | #include <QtCore/qdebug.h> |
94 | #endif |
95 | |
96 | QT_BEGIN_NAMESPACE |
97 | |
98 | class QGraphicsLinearLayoutPrivate : public QGraphicsLayoutPrivate |
99 | { |
100 | public: |
101 | QGraphicsLinearLayoutPrivate(Qt::Orientation orientation) |
102 | : orientation(orientation) |
103 | { } |
104 | |
105 | void removeGridItem(QGridLayoutItem *gridItem); |
106 | QGraphicsLayoutStyleInfo *styleInfo() const; |
107 | void fixIndex(int *index) const; |
108 | int gridRow(int index) const; |
109 | int gridColumn(int index) const; |
110 | |
111 | Qt::Orientation orientation; |
112 | mutable QScopedPointer<QGraphicsLayoutStyleInfo> m_styleInfo; |
113 | QGraphicsGridLayoutEngine engine; |
114 | }; |
115 | |
116 | void QGraphicsLinearLayoutPrivate::removeGridItem(QGridLayoutItem *gridItem) |
117 | { |
118 | int index = gridItem->firstRow(orientation); |
119 | engine.removeItem(item: gridItem); |
120 | engine.removeRows(row: index, count: 1, orientation); |
121 | } |
122 | |
123 | void QGraphicsLinearLayoutPrivate::fixIndex(int *index) const |
124 | { |
125 | int count = engine.rowCount(orientation); |
126 | if (uint(*index) > uint(count)) |
127 | *index = count; |
128 | } |
129 | |
130 | int QGraphicsLinearLayoutPrivate::gridRow(int index) const |
131 | { |
132 | if (orientation == Qt::Horizontal) |
133 | return 0; |
134 | return int(qMin(a: uint(index), b: uint(engine.rowCount()))); |
135 | } |
136 | |
137 | int QGraphicsLinearLayoutPrivate::gridColumn(int index) const |
138 | { |
139 | if (orientation == Qt::Vertical) |
140 | return 0; |
141 | return int(qMin(a: uint(index), b: uint(engine.columnCount()))); |
142 | } |
143 | |
144 | QGraphicsLayoutStyleInfo *QGraphicsLinearLayoutPrivate::styleInfo() const |
145 | { |
146 | if (!m_styleInfo) |
147 | m_styleInfo.reset(other: new QGraphicsLayoutStyleInfo(this)); |
148 | return m_styleInfo.data(); |
149 | } |
150 | |
151 | /*! |
152 | Constructs a QGraphicsLinearLayout instance. You can pass the |
153 | \a orientation for the layout, either horizontal or vertical, and |
154 | \a parent is passed to QGraphicsLayout's constructor. |
155 | */ |
156 | QGraphicsLinearLayout::QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent) |
157 | : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(orientation), parent) |
158 | { |
159 | } |
160 | |
161 | /*! |
162 | Constructs a QGraphicsLinearLayout instance using Qt::Horizontal |
163 | orientation. \a parent is passed to QGraphicsLayout's constructor. |
164 | */ |
165 | QGraphicsLinearLayout::QGraphicsLinearLayout(QGraphicsLayoutItem *parent) |
166 | : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(Qt::Horizontal), parent) |
167 | { |
168 | } |
169 | |
170 | /*! |
171 | Destroys the QGraphicsLinearLayout object. |
172 | */ |
173 | QGraphicsLinearLayout::~QGraphicsLinearLayout() |
174 | { |
175 | for (int i = count() - 1; i >= 0; --i) { |
176 | QGraphicsLayoutItem *item = itemAt(index: i); |
177 | // The following lines can be removed, but this removes the item |
178 | // from the layout more efficiently than the implementation of |
179 | // ~QGraphicsLayoutItem. |
180 | removeAt(index: i); |
181 | if (item) { |
182 | item->setParentLayoutItem(nullptr); |
183 | if (item->ownedByLayout()) |
184 | delete item; |
185 | } |
186 | } |
187 | } |
188 | |
189 | /*! |
190 | Change the layout orientation to \a orientation. Changing the layout |
191 | orientation will automatically invalidate the layout. |
192 | |
193 | \sa orientation() |
194 | */ |
195 | void QGraphicsLinearLayout::setOrientation(Qt::Orientation orientation) |
196 | { |
197 | Q_D(QGraphicsLinearLayout); |
198 | if (orientation != d->orientation) { |
199 | d->engine.transpose(); |
200 | d->orientation = orientation; |
201 | invalidate(); |
202 | } |
203 | } |
204 | |
205 | /*! |
206 | Returns the layout orientation. |
207 | \sa setOrientation() |
208 | */ |
209 | Qt::Orientation QGraphicsLinearLayout::orientation() const |
210 | { |
211 | Q_D(const QGraphicsLinearLayout); |
212 | return d->orientation; |
213 | } |
214 | |
215 | /*! |
216 | \fn void QGraphicsLinearLayout::addItem(QGraphicsLayoutItem *item) |
217 | |
218 | This convenience function is equivalent to calling |
219 | insertItem(-1, \a item). |
220 | */ |
221 | |
222 | /*! |
223 | \fn void QGraphicsLinearLayout::addStretch(int stretch) |
224 | |
225 | This convenience function is equivalent to calling |
226 | insertStretch(-1, \a stretch). |
227 | */ |
228 | |
229 | /*! |
230 | Inserts \a item into the layout at \a index, or before any item that is |
231 | currently at \a index. |
232 | |
233 | \sa addItem(), itemAt(), insertStretch(), setItemSpacing() |
234 | */ |
235 | void QGraphicsLinearLayout::insertItem(int index, QGraphicsLayoutItem *item) |
236 | { |
237 | Q_D(QGraphicsLinearLayout); |
238 | if (!item) { |
239 | qWarning(msg: "QGraphicsLinearLayout::insertItem: cannot insert null item" ); |
240 | return; |
241 | } |
242 | if (item == this) { |
243 | qWarning(msg: "QGraphicsLinearLayout::insertItem: cannot insert itself" ); |
244 | return; |
245 | } |
246 | d->addChildLayoutItem(item); |
247 | |
248 | Q_ASSERT(item); |
249 | d->fixIndex(index: &index); |
250 | d->engine.insertRow(row: index, orientation: d->orientation); |
251 | QGraphicsGridLayoutEngineItem *gridEngineItem = new QGraphicsGridLayoutEngineItem(item, d->gridRow(index), d->gridColumn(index), 1, 1, { }); |
252 | d->engine.insertItem(item: gridEngineItem, index); |
253 | invalidate(); |
254 | } |
255 | |
256 | /*! |
257 | Inserts a stretch of \a stretch at \a index, or before any item that is |
258 | currently at \a index. |
259 | |
260 | \sa addStretch(), setStretchFactor(), setItemSpacing(), insertItem() |
261 | */ |
262 | void QGraphicsLinearLayout::insertStretch(int index, int stretch) |
263 | { |
264 | Q_D(QGraphicsLinearLayout); |
265 | d->fixIndex(index: &index); |
266 | d->engine.insertRow(row: index, orientation: d->orientation); |
267 | d->engine.setRowStretchFactor(row: index, stretch, orientation: d->orientation); |
268 | invalidate(); |
269 | } |
270 | |
271 | /*! |
272 | Removes \a item from the layout without destroying it. Ownership of |
273 | \a item is transferred to the caller. |
274 | |
275 | \sa removeAt(), insertItem() |
276 | */ |
277 | void QGraphicsLinearLayout::removeItem(QGraphicsLayoutItem *item) |
278 | { |
279 | Q_D(QGraphicsLinearLayout); |
280 | if (QGraphicsGridLayoutEngineItem *gridItem = d->engine.findLayoutItem(layoutItem: item)) { |
281 | item->setParentLayoutItem(nullptr); |
282 | d->removeGridItem(gridItem); |
283 | delete gridItem; |
284 | invalidate(); |
285 | } |
286 | } |
287 | |
288 | /*! |
289 | Removes the item at \a index without destroying it. Ownership of the item |
290 | is transferred to the caller. |
291 | |
292 | \sa removeItem(), insertItem() |
293 | */ |
294 | void QGraphicsLinearLayout::removeAt(int index) |
295 | { |
296 | Q_D(QGraphicsLinearLayout); |
297 | if (index < 0 || index >= d->engine.itemCount()) { |
298 | qWarning(msg: "QGraphicsLinearLayout::removeAt: invalid index %d" , index); |
299 | return; |
300 | } |
301 | |
302 | if (QGraphicsGridLayoutEngineItem *gridItem = static_cast<QGraphicsGridLayoutEngineItem*>(d->engine.itemAt(index))) { |
303 | if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem()) |
304 | layoutItem->setParentLayoutItem(nullptr); |
305 | d->removeGridItem(gridItem); |
306 | delete gridItem; |
307 | invalidate(); |
308 | } |
309 | } |
310 | |
311 | /*! |
312 | Sets the layout's spacing to \a spacing. Spacing refers to the |
313 | vertical and horizontal distances between items. |
314 | |
315 | \sa setItemSpacing(), setStretchFactor(), QGraphicsGridLayout::setSpacing() |
316 | */ |
317 | void QGraphicsLinearLayout::setSpacing(qreal spacing) |
318 | { |
319 | Q_D(QGraphicsLinearLayout); |
320 | if (spacing < 0) { |
321 | qWarning(msg: "QGraphicsLinearLayout::setSpacing: invalid spacing %g" , spacing); |
322 | return; |
323 | } |
324 | d->engine.setSpacing(spacing, orientations: Qt::Horizontal | Qt::Vertical); |
325 | invalidate(); |
326 | } |
327 | |
328 | /*! |
329 | Returns the layout's spacing. Spacing refers to the |
330 | vertical and horizontal distances between items. |
331 | |
332 | \sa setSpacing() |
333 | */ |
334 | qreal QGraphicsLinearLayout::spacing() const |
335 | { |
336 | Q_D(const QGraphicsLinearLayout); |
337 | return d->engine.spacing(orientation: d->orientation, styleInfo: d->styleInfo()); |
338 | } |
339 | |
340 | /*! |
341 | Sets the spacing after item at \a index to \a spacing. |
342 | */ |
343 | void QGraphicsLinearLayout::setItemSpacing(int index, qreal spacing) |
344 | { |
345 | Q_D(QGraphicsLinearLayout); |
346 | d->engine.setRowSpacing(row: index, spacing, orientation: d->orientation); |
347 | invalidate(); |
348 | } |
349 | /*! |
350 | Returns the spacing after item at \a index. |
351 | */ |
352 | qreal QGraphicsLinearLayout::itemSpacing(int index) const |
353 | { |
354 | Q_D(const QGraphicsLinearLayout); |
355 | return d->engine.rowSpacing(row: index, orientation: d->orientation); |
356 | } |
357 | |
358 | /*! |
359 | Sets the stretch factor for \a item to \a stretch. If an item's stretch |
360 | factor changes, this function will invalidate the layout. |
361 | |
362 | Setting \a stretch to 0 removes the stretch factor from the item, and is |
363 | effectively equivalent to setting \a stretch to 1. |
364 | |
365 | \sa stretchFactor() |
366 | */ |
367 | void QGraphicsLinearLayout::setStretchFactor(QGraphicsLayoutItem *item, int stretch) |
368 | { |
369 | Q_D(QGraphicsLinearLayout); |
370 | if (!item) { |
371 | qWarning(msg: "QGraphicsLinearLayout::setStretchFactor: cannot assign" |
372 | " a stretch factor to a null item" ); |
373 | return; |
374 | } |
375 | if (stretchFactor(item) == stretch) |
376 | return; |
377 | d->engine.setStretchFactor(layoutItem: item, stretch, orientation: d->orientation); |
378 | invalidate(); |
379 | } |
380 | |
381 | /*! |
382 | Returns the stretch factor for \a item. The default stretch factor is 0, |
383 | meaning that the item has no assigned stretch factor. |
384 | |
385 | \sa setStretchFactor() |
386 | */ |
387 | int QGraphicsLinearLayout::stretchFactor(QGraphicsLayoutItem *item) const |
388 | { |
389 | Q_D(const QGraphicsLinearLayout); |
390 | if (!item) { |
391 | qWarning(msg: "QGraphicsLinearLayout::setStretchFactor: cannot return" |
392 | " a stretch factor for a null item" ); |
393 | return 0; |
394 | } |
395 | return d->engine.stretchFactor(layoutItem: item, orientation: d->orientation); |
396 | } |
397 | |
398 | /*! |
399 | Sets the alignment of \a item to \a alignment. If \a item's alignment |
400 | changes, the layout is automatically invalidated. |
401 | |
402 | \sa alignment(), invalidate() |
403 | */ |
404 | void QGraphicsLinearLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment) |
405 | { |
406 | Q_D(QGraphicsLinearLayout); |
407 | if (this->alignment(item) == alignment) |
408 | return; |
409 | d->engine.setAlignment(graphicsLayoutItem: item, alignment); |
410 | invalidate(); |
411 | } |
412 | |
413 | /*! |
414 | Returns the alignment for \a item. The default alignment is |
415 | Qt::AlignTop | Qt::AlignLeft. |
416 | |
417 | The alignment decides how the item is positioned within its assigned space |
418 | in the case where there's more space available in the layout than the |
419 | widgets can occupy. |
420 | |
421 | \sa setAlignment() |
422 | */ |
423 | Qt::Alignment QGraphicsLinearLayout::alignment(QGraphicsLayoutItem *item) const |
424 | { |
425 | Q_D(const QGraphicsLinearLayout); |
426 | return d->engine.alignment(graphicsLayoutItem: item); |
427 | } |
428 | |
429 | #if 0 // ### |
430 | QSizePolicy::ControlTypes QGraphicsLinearLayout::controlTypes(LayoutSide side) const |
431 | { |
432 | return d->engine.controlTypes(side); |
433 | } |
434 | #endif |
435 | |
436 | /*! |
437 | \reimp |
438 | */ |
439 | int QGraphicsLinearLayout::count() const |
440 | { |
441 | Q_D(const QGraphicsLinearLayout); |
442 | return d->engine.itemCount(); |
443 | } |
444 | |
445 | /*! |
446 | \reimp |
447 | When iterating from 0 and up, it will return the items in the visual arranged order. |
448 | */ |
449 | QGraphicsLayoutItem *QGraphicsLinearLayout::itemAt(int index) const |
450 | { |
451 | Q_D(const QGraphicsLinearLayout); |
452 | if (index < 0 || index >= d->engine.itemCount()) { |
453 | qWarning(msg: "QGraphicsLinearLayout::itemAt: invalid index %d" , index); |
454 | return nullptr; |
455 | } |
456 | QGraphicsLayoutItem *item = nullptr; |
457 | if (QGraphicsGridLayoutEngineItem *gridItem = static_cast<QGraphicsGridLayoutEngineItem *>(d->engine.itemAt(index))) |
458 | item = gridItem->layoutItem(); |
459 | return item; |
460 | } |
461 | |
462 | /*! |
463 | \reimp |
464 | */ |
465 | void QGraphicsLinearLayout::setGeometry(const QRectF &rect) |
466 | { |
467 | Q_D(QGraphicsLinearLayout); |
468 | QGraphicsLayout::setGeometry(rect); |
469 | QRectF effectiveRect = geometry(); |
470 | qreal left, top, right, bottom; |
471 | getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom); |
472 | Qt::LayoutDirection visualDir = d->visualDirection(); |
473 | d->engine.setVisualDirection(visualDir); |
474 | if (visualDir == Qt::RightToLeft) |
475 | qSwap(value1&: left, value2&: right); |
476 | effectiveRect.adjust(xp1: +left, yp1: +top, xp2: -right, yp2: -bottom); |
477 | #ifdef QGRIDLAYOUTENGINE_DEBUG |
478 | if (qt_graphicsLayoutDebug()) { |
479 | static int counter = 0; |
480 | qDebug() << counter++ << "QGraphicsLinearLayout::setGeometry - " << rect; |
481 | dump(1); |
482 | } |
483 | #endif |
484 | d->engine.setGeometries(contentsGeometry: effectiveRect, styleInfo: d->styleInfo()); |
485 | #ifdef QGRIDLAYOUTENGINE_DEBUG |
486 | if (qt_graphicsLayoutDebug()) { |
487 | qDebug("post dump" ); |
488 | dump(1); |
489 | } |
490 | #endif |
491 | } |
492 | |
493 | /*! |
494 | \reimp |
495 | */ |
496 | QSizeF QGraphicsLinearLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const |
497 | { |
498 | Q_D(const QGraphicsLinearLayout); |
499 | qreal left, top, right, bottom; |
500 | getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom); |
501 | const QSizeF (left + right, top + bottom); |
502 | return d->engine.sizeHint(which , constraint: constraint - extraMargins, styleInfo: d->styleInfo()) + extraMargins; |
503 | } |
504 | |
505 | /*! |
506 | \reimp |
507 | */ |
508 | void QGraphicsLinearLayout::invalidate() |
509 | { |
510 | Q_D(QGraphicsLinearLayout); |
511 | d->engine.invalidate(); |
512 | if (d->m_styleInfo) |
513 | d->m_styleInfo->invalidate(); |
514 | QGraphicsLayout::invalidate(); |
515 | } |
516 | |
517 | /*! |
518 | \internal |
519 | */ |
520 | void QGraphicsLinearLayout::dump(int indent) const |
521 | { |
522 | #ifdef QGRIDLAYOUTENGINE_DEBUG |
523 | if (qt_graphicsLayoutDebug()) { |
524 | Q_D(const QGraphicsLinearLayout); |
525 | qDebug("%*s%s layout" , indent, "" , |
526 | d->orientation == Qt::Horizontal ? "Horizontal" : "Vertical" ); |
527 | d->engine.dump(indent + 1); |
528 | } |
529 | #else |
530 | Q_UNUSED(indent); |
531 | #endif |
532 | } |
533 | |
534 | QT_END_NAMESPACE |
535 | |