QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskLinearBox.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskLinearBox.h"
7#include "QskLinearLayoutEngine.h"
8#include "QskEvent.h"
9#include "QskQuick.h"
10
11static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on )
12{
13 /*
14 For QQuickItems not being derived from QskControl we manually
15 send QEvent::LayoutRequest events.
16 */
17
18 if ( on )
19 {
20 auto sendLayoutRequest =
21 [receiver]()
22 {
23 QEvent event( QEvent::LayoutRequest );
24 QCoreApplication::sendEvent( receiver, &event );
25 };
26
27 QObject::connect( item, &QQuickItem::implicitWidthChanged,
28 receiver, sendLayoutRequest );
29
30 QObject::connect( item, &QQuickItem::implicitHeightChanged,
31 receiver, sendLayoutRequest );
32 }
33 else
34 {
35 QObject::disconnect( item, &QQuickItem::implicitWidthChanged, receiver, nullptr );
36 QObject::disconnect( item, &QQuickItem::implicitHeightChanged, receiver, nullptr );
37 }
38}
39
40class QskLinearBox::PrivateData
41{
42 public:
43 PrivateData( Qt::Orientation orientation, uint dimension )
44 : engine( orientation, dimension )
45 {
46 }
47
49};
50
51QskLinearBox::QskLinearBox( QQuickItem* parent )
52 : QskLinearBox( Qt::Horizontal, std::numeric_limits< uint >::max(), parent )
53{
54}
55
56QskLinearBox::QskLinearBox( Qt::Orientation orientation, QQuickItem* parent )
57 : QskLinearBox( orientation, std::numeric_limits< uint >::max(), parent )
58{
59}
60
61QskLinearBox::QskLinearBox( Qt::Orientation orientation, uint dimension, QQuickItem* parent )
62 : QskIndexedLayoutBox( parent )
63 , m_data( new PrivateData( orientation, dimension ) )
64{
65}
66
68{
69 auto& engine = m_data->engine;
70
71 for ( int i = 0; i < engine.count(); i++ )
72 {
73 if ( auto item = engine.itemAt( i ) )
74 {
75 /*
76 The destructor of QQuickItem sets the parentItem of
77 all children to nullptr, what leads to visibleChanged
78 signals. So we better disconnect first.
79 */
80 setItemActive( item, false );
81 }
82 }
83}
84
85int QskLinearBox::elementCount() const
86{
87 return m_data->engine.count();
88}
89
90qreal QskLinearBox::spacingAtIndex( int index ) const
91{
92 return m_data->engine.spacerAt( index );
93}
94
95QQuickItem* QskLinearBox::itemAtIndex( int index ) const
96{
97 return m_data->engine.itemAt( index );
98}
99
100int QskLinearBox::indexOf( const QQuickItem* item ) const
101{
102 return m_data->engine.indexOf( item );
103}
104
105void QskLinearBox::removeAt( int index )
106{
107 removeItemInternal( index, true );
108}
109
110void QskLinearBox::removeItemInternal( int index, bool unparent )
111{
112 auto& engine = m_data->engine;
113
114 if ( index < 0 || index >= engine.count() )
115 return;
116
117 auto item = engine.itemAt( index );
118 engine.removeAt( index );
119
120 if ( item )
121 {
122 setItemActive( item, false );
123
124 if ( unparent )
125 unparentItem( item );
126 }
127
129 polish();
130}
131
132void QskLinearBox::removeItem( const QQuickItem* item )
133{
134 removeAt( indexOf( item ) );
135}
136
137void QskLinearBox::clear( bool autoDelete )
138{
139 auto& engine = m_data->engine;
140
141 // do we have visible elements
142 const bool hasVisibleElements = engine.rowCount() > 0;
143
144 for ( int i = engine.count() - 1; i >= 0; i-- )
145 {
146 auto item = engine.itemAt( i );
147 engine.removeAt( i );
148
149 if( item )
150 {
151 setItemActive( item, false );
152
153 if( autoDelete && ( item->parent() == this ) )
154 delete item;
155 else
156 unparentItem( item );
157 }
158 }
159
160 if ( hasVisibleElements )
162}
163
164void QskLinearBox::autoAddItem( QQuickItem* item )
165{
166 insertItem( -1, item );
167}
168
169void QskLinearBox::autoRemoveItem( QQuickItem* item )
170{
171 removeItemInternal( indexOf( item ), false );
172}
173
174void QskLinearBox::activate()
175{
176 polish();
177}
178
179void QskLinearBox::invalidate()
180{
181 m_data->engine.invalidate();
182
184 polish();
185}
186
187void QskLinearBox::setItemActive( QQuickItem* item, bool on )
188{
189 if ( on )
190 {
191 QObject::connect( item, &QQuickItem::visibleChanged,
192 this, &QskLinearBox::invalidate );
193 }
194 else
195 {
196 QObject::disconnect( item, &QQuickItem::visibleChanged,
197 this, &QskLinearBox::invalidate );
198 }
199
200 if ( qskControlCast( item ) == nullptr )
201 qskSetItemActive( this, item, on );
202}
203
204void QskLinearBox::updateLayout()
205{
206 if ( !maybeUnresized() )
207 m_data->engine.setGeometries( layoutRect() );
208}
209
210QSizeF QskLinearBox::layoutSizeHint(
211 Qt::SizeHint which, const QSizeF& constraint ) const
212{
213 if ( which == Qt::MaximumSize )
214 {
215 // we can extend beyond the maximum size of the children
216 return QSizeF();
217 }
218
219 return m_data->engine.sizeHint( which, constraint );
220}
221
223{
225
226 if ( event->isResized() )
227 polish();
228}
229
230
231void QskLinearBox::itemChange( ItemChange change, const ItemChangeData& value )
232{
233 Inherited::itemChange( change, value );
234
235#if 1
236 if ( change == QQuickItem::ItemVisibleHasChanged )
237 {
238 // when becoming visible we should run into polish anyway
239 if ( value.boolValue )
240 polish();
241 }
242#endif
243}
244
245bool QskLinearBox::event( QEvent* event )
246{
247 switch ( static_cast< int >( event->type() ) )
248 {
249 case QEvent::LayoutRequest:
250 {
251 invalidate();
252 break;
253 }
254 case QEvent::LayoutDirectionChange:
255 {
256 m_data->engine.setVisualDirection(
257 layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight );
258
259 polish();
260 break;
261 }
262 case QEvent::ContentsRectChange:
263 {
264 polish();
265 break;
266 }
267 }
268
269 return Inherited::event( event );
270}
271
272void QskLinearBox::setDimension( uint dimension )
273{
274 if ( m_data->engine.setDimension( dimension ) )
275 {
276 polish();
278
279 Q_EMIT dimensionChanged();
280 }
281}
282
283uint QskLinearBox::dimension() const
284{
285 return m_data->engine.dimension();
286}
287
288void QskLinearBox::setOrientation( Qt::Orientation orientation )
289{
290 if ( m_data->engine.setOrientation( orientation ) )
291 {
292 polish();
294
295 Q_EMIT orientationChanged();
296 }
297}
298
299Qt::Orientation QskLinearBox::orientation() const
300{
301 return m_data->engine.orientation();
302}
303
305{
306 auto& engine = m_data->engine;
307
308#if 0
309 #include <qendian.h>
310
311 for ( int i = 0; i < engine.itemCount(); i++ )
312 {
313 auto alignment = engine.alignmentAt( i );
314 qbswap( static_cast< quint16 >( alignment ) );
315 engine.setAlignmentAt( i, alignment );
316 }
317
318 // extraSpacingAt ???
319#endif
320
321 if ( engine.orientation() == Qt::Horizontal )
322 setOrientation( Qt::Vertical );
323 else
324 setOrientation( Qt::Horizontal );
325}
326
327void QskLinearBox::setDefaultAlignment( Qt::Alignment alignment )
328{
329 if ( m_data->engine.setDefaultAlignment( alignment ) )
330 Q_EMIT defaultAlignmentChanged();
331}
332
333Qt::Alignment QskLinearBox::defaultAlignment() const
334{
335 return m_data->engine.defaultAlignment();
336}
337
338void QskLinearBox::setSpacing( qreal spacing )
339{
340 /*
341 we should have setSpacing( qreal, Qt::Orientations ),
342 but need to create an API for Qml in QskQml
343 using qmlAttachedPropertiesObject then. TODO ...
344 */
345
346 if ( m_data->engine.setSpacing(
347 spacing, Qt::Horizontal | Qt::Vertical ) )
348 {
349 polish();
350 Q_EMIT spacingChanged();
351 }
352}
353
355{
356 const qreal spacing = m_data->engine.defaultSpacing( Qt::Horizontal );
358}
359
360qreal QskLinearBox::spacing() const
361{
362 // do we always want to have the same spacing for both orientations
363 return m_data->engine.spacing( Qt::Horizontal );
364}
365
366void QskLinearBox::setExtraSpacingAt( Qt::Edges edges )
367{
368 if ( edges != m_data->engine.extraSpacingAt() )
369 {
370 m_data->engine.setExtraSpacingAt( edges );
371 polish();
372
373 Q_EMIT extraSpacingAtChanged();
374 }
375}
376
377Qt::Edges QskLinearBox::extraSpacingAt() const
378{
379 return m_data->engine.extraSpacingAt();
380}
381
382int QskLinearBox::addItem( QQuickItem* item, Qt::Alignment alignment )
383{
384 return insertItem( -1, item, alignment );
385}
386
387int QskLinearBox::addItem( QQuickItem* item )
388{
389 return insertItem( -1, item );
390}
391
392int QskLinearBox::insertItem(
393 int index, QQuickItem* item, Qt::Alignment alignment )
394{
395 if ( auto control = qskControlCast( item ) )
396 control->setLayoutAlignmentHint( alignment );
397
398 return insertItem( index, item );
399}
400
401int QskLinearBox::insertItem( int index, QQuickItem* item )
402{
403 if ( item == nullptr || item == this )
404 return -1;
405
406 if ( !qskPlacementPolicy( item ).isEffective() )
407 {
408 qWarning() << "Inserting an item that is to be ignored for layouting:"
409 << item->metaObject()->className();
410
411 qskSetPlacementPolicy( item, QskPlacementPolicy() );
412 }
413
414 auto& engine = m_data->engine;
415
416 if ( item->parentItem() == this )
417 {
418 const int oldIndex = indexOf( item );
419 if ( oldIndex >= 0 )
420 {
421 // the item has been inserted before
422
423 const bool doAppend = index < 0 || index >= engine.count();
424
425 if ( ( index == oldIndex ) ||
426 ( doAppend && oldIndex == engine.count() - 1 ) )
427 {
428 // already at its position, nothing to do
429 return oldIndex;
430 }
431
432 removeAt( oldIndex );
433 }
434 }
435
436 reparentItem( item );
437
438 index = engine.insertItem( item, index );
439
440 // Re-ordering the child items to have a a proper focus tab chain
441
442 bool reordered = false;
443
444 if ( index < engine.count() - 1 )
445 {
446 for ( int i = index + 1; i < engine.count(); i++ )
447 {
448 if ( auto nextItem = engine.itemAt( i ) )
449 {
450 item->stackBefore( nextItem );
451 reordered = true;
452
453 break;
454 }
455 }
456 }
457
458 if ( !reordered )
459 {
460 const auto children = childItems();
461 if ( item != children.last() )
462 item->stackAfter( children.last() );
463 }
464
465 setItemActive( item, true );
466
467#if 1
468 // Is there a way to block consecutive calls ???
470 polish();
471#endif
472
473 return index;
474}
475
476int QskLinearBox::addSpacer( qreal spacing, int stretchFactor )
477{
478 return insertSpacer( -1, spacing, stretchFactor );
479}
480
481int QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor )
482{
483 auto& engine = m_data->engine;
484
485 const int numItems = engine.count();
486 if ( index < 0 || index > numItems )
487 index = numItems;
488
489 index = engine.insertSpacerAt( index, spacing );
490
491 stretchFactor = qMax( stretchFactor, 0 );
492 engine.setStretchFactorAt( index, stretchFactor );
493
494#if 1
495 // Is there a way to block consecutive calls ???
497 polish();
498#endif
499
500 return index;
501}
502
503int QskLinearBox::addStretch( int stretchFactor )
504{
505 return insertSpacer( -1, 0, stretchFactor );
506}
507
508int QskLinearBox::insertStretch( int index, int stretchFactor )
509{
510 return insertSpacer( index, 0, stretchFactor );
511}
512
513void QskLinearBox::setStretchFactor( int index, int stretchFactor )
514{
515 auto& engine = m_data->engine;
516
517 if ( engine.stretchFactorAt( index ) != stretchFactor )
518 {
519 engine.setStretchFactorAt( index, stretchFactor );
520 polish();
521 }
522}
523
524int QskLinearBox::stretchFactor( int index ) const
525{
526 return m_data->engine.stretchFactorAt( index );
527}
528
529void QskLinearBox::setStretchFactor( const QQuickItem* item, int stretch )
530{
531 setStretchFactor( indexOf( item ), stretch );
532}
533
534int QskLinearBox::stretchFactor( const QQuickItem* item ) const
535{
536 return stretchFactor( indexOf( item ) );
537}
538
539void QskLinearBox::dump() const
540{
541 const auto& engine = m_data->engine;
542
543 auto debug = qDebug();
544
545 QDebugStateSaver saver( debug );
546 debug.nospace();
547
548 const auto constraint = sizeConstraint();
549
550 debug << "QskLinearBox" << engine.orientation()
551 << " w:" << constraint.width() << " h:" << constraint.height() << '\n';
552
553 for ( int i = 0; i < engine.count(); i++ )
554 {
555 debug << " " << i << ": ";
556
557 if ( auto item = engine.itemAt( i ) )
558 {
559 const auto size = qskSizeConstraint( item, Qt::PreferredSize );
560 debug << item->metaObject()->className()
561 << " w:" << size.width() << " h:" << size.height();
562 }
563 else
564 {
565 debug << "spacer: " << engine.spacerAt( i );
566 }
567
568 debug << '\n';
569 }
570}
571
572#include "moc_QskLinearBox.cpp"
QRectF layoutRect() const
QSizeF sizeConstraint
Definition QskControl.h:50
Base class of layouts with index ordered elements.
void itemChange(ItemChange, const ItemChangeData &) override
void resetImplicitSize()
Definition QskItem.cpp:721
virtual void geometryChangeEvent(QskGeometryChangeEvent *)
Definition QskItem.cpp:855
bool maybeUnresized() const
Definition QskItem.cpp:583
bool layoutMirroring() const
Definition QskItem.cpp:511
Layout stringing items in rows and columns.
Q_INVOKABLE int stretchFactor(int index) const
void resetSpacing()
Reset the global spacing to its initial value.
Q_INVOKABLE int insertSpacer(int index, qreal spacing, int stretchFactor=0)
Insert a spacer at a specific position.
void orientationChanged()
uint dimension
Upper limit for the number of elements in a row or column.
QskLinearBox(QQuickItem *parent=nullptr)
Create a row layout.
void spacingChanged()
void setSpacing(qreal spacing)
Set the global spacing of the layout.
void setDimension(uint)
Set the dimension of the layout.
~QskLinearBox() override
qreal spacing
Global layout spacing.
void setOrientation(Qt::Orientation)
Set the orientation of the layout.
Q_INVOKABLE void setStretchFactor(int index, int stretchFactor)
Modify the stretch factor of a layout element.
void transpose()
Invert the orientation of the layout.
Q_INVOKABLE int addStretch(int stretchFactor=0)
Append a stretch to the layout.
void dimensionChanged()
Qt::Orientation orientation
Direction of flow for laying out the items.
Q_INVOKABLE int insertStretch(int index, int stretchFactor=0)
Insert a stretch at a specific position.
void geometryChangeEvent(QskGeometryChangeEvent *) override
Q_INVOKABLE int addSpacer(qreal spacing, int stretchFactor=0)
Append a spacer to the layout.
void debug(QskAspect) const
@ LeftToRight