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