QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGridBox.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGridBox.h"
7#include "QskGridLayoutEngine.h"
8#include "QskEvent.h"
9#include "QskQuick.h"
10#include <qdebug.h>
11#include <algorithm>
12
13static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on )
14{
15 /*
16 For QQuickItems not being derived from QskControl we manually
17 send QEvent::LayoutRequest events.
18 */
19
20 if ( on )
21 {
22 auto sendLayoutRequest =
23 [receiver]()
24 {
25 QEvent event( QEvent::LayoutRequest );
26 QCoreApplication::sendEvent( receiver, &event );
27 };
28
29 QObject::connect( item, &QQuickItem::implicitWidthChanged,
30 receiver, sendLayoutRequest );
31
32 QObject::connect( item, &QQuickItem::implicitHeightChanged,
33 receiver, sendLayoutRequest );
34 }
35 else
36 {
37 QObject::disconnect( item, &QQuickItem::implicitWidthChanged, receiver, nullptr );
38 QObject::disconnect( item, &QQuickItem::implicitHeightChanged, receiver, nullptr );
39 }
40}
41
42static void qskUpdateFocusChain(
43 QskGridBox* box, const QskGridLayoutEngine* engine,
44 QQuickItem* item, const QRect& grid )
45{
46 /*
47 We are running over all entries each time an item gets inserted.
48 There should be a faster way TODO ...
49 */
50
51 const int cellIndex = grid.y() * engine->columnCount() + grid.x();
52
53 QQuickItem* itemNext = nullptr;
54 int minDelta = -1;
55
56 for ( int i = 0; i < engine->count(); i++ )
57 {
58 const auto itemAt = engine->itemAt( i );
59
60 if ( itemAt && item != itemAt )
61 {
62 const auto gridAt = engine->gridAt( i );
63 const int delta = gridAt.y() * engine->columnCount() + gridAt.x() - cellIndex;
64
65 if ( delta > 0 )
66 {
67 if ( itemNext == nullptr || delta < minDelta )
68 {
69 itemNext = itemAt;
70 minDelta = delta;
71 }
72 }
73 }
74 }
75
76 if ( itemNext )
77 {
78 item->stackBefore( itemNext );
79 }
80 else
81 {
82 const auto itemLast = box->childItems().constLast();
83 if ( itemLast != item )
84 item->stackAfter( itemLast );
85 }
86}
87
88class QskGridBox::PrivateData
89{
90 public:
92 bool blockAutoRemove = false;
93};
94
95QskGridBox::QskGridBox( QQuickItem* parent )
96 : QskBox( false, parent )
97 , m_data( new PrivateData() )
98{
99}
100
101QskGridBox::~QskGridBox()
102{
103 auto& engine = m_data->engine;
104
105 for ( int i = 0; i < engine.count(); i++ )
106 {
107 if ( auto item = engine.itemAt( i ) )
108 setItemActive( item, false );
109 }
110}
111
112int QskGridBox::addItem( QQuickItem* item,
113 int row, int column, Qt::Alignment alignment )
114{
115 if ( auto control = qskControlCast( item ) )
116 control->setLayoutAlignmentHint( alignment );
117
118 return addItem( item, row, column );
119}
120
121int QskGridBox::addItem( QQuickItem* item,
122 int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment )
123{
124 if ( auto control = qskControlCast( item ) )
125 control->setLayoutAlignmentHint( alignment );
126
127 return addItem( item, row, column, rowSpan, columnSpan );
128}
129
130int QskGridBox::addItem( QQuickItem* item,
131 int row, int column, int rowSpan, int columnSpan )
132{
133 if ( item == nullptr || item == this || row < 0 || column < 0 )
134 return -1;
135
136 if ( !qskPlacementPolicy( item ).isEffective() )
137 {
138 qWarning() << "Inserting an item that is to be ignored for layouting:"
139 << item->metaObject()->className();
140
141 qskSetPlacementPolicy( item, QskPlacementPolicy() );
142 }
143
144 rowSpan = qMax( rowSpan, -1 );
145 columnSpan = qMax( columnSpan, -1 );
146
147 auto& engine = m_data->engine;
148
149 const QRect itemGrid( column, row, columnSpan, rowSpan );
150 int index = -1;
151
152 if ( item->parentItem() == this )
153 {
154 index = indexOf( item );
155 if ( index >= 0 )
156 {
157 if ( engine.gridAt( index ) == itemGrid )
158 return index;
159 }
160 }
161
162 if ( index < 0 )
163 {
164 if ( item->parent() == nullptr )
165 item->setParent( this );
166
167 if ( item->parentItem() != this )
168 item->setParentItem( this );
169
170 setItemActive( item, true );
171 index = engine.insertItem( item, itemGrid );
172 }
173
174 if ( engine.count() > 1 )
175 qskUpdateFocusChain( this, &engine, item, itemGrid );
176
178 polish();
179
180 return index;
181}
182
183int QskGridBox::addSpacer( const QSizeF& spacing,
184 int row, int column, int rowSpan, int columnSpan )
185{
186 const int index = m_data->engine.insertSpacer(
187 spacing, QRect( column, row, columnSpan, rowSpan ) );
188
190 polish();
191
192 return index;
193}
194
195int QskGridBox::addColumnSpacer( qreal spacing, int column )
196{
197 return addSpacer( QSizeF( spacing, 0.0 ), 0, column );
198}
199
200int QskGridBox::addRowSpacer( qreal spacing, int row )
201{
202 return addSpacer( QSizeF( 0.0, spacing ), row, 0 );
203}
204
205void QskGridBox::removeAt( int index )
206{
207 auto& engine = m_data->engine;
208
209 if ( auto item = engine.itemAt( index ) )
210 setItemActive( item, false );
211
212 engine.removeAt( index );
213
215 polish();
216}
217
218void QskGridBox::removeItem( const QQuickItem* item )
219{
220 removeAt( indexOf( item ) );
221}
222
223void QskGridBox::clear( bool autoDelete )
224{
225 m_data->blockAutoRemove = true;
226
227 for ( int i = 0; i < elementCount(); i++ )
228 {
229 if ( auto item = itemAtIndex( i ) )
230 {
231 setItemActive( item, false );
232
233 if( autoDelete && ( item->parent() == this ) )
234 delete item;
235 else
236 item->setParentItem( nullptr );
237 }
238 }
239
240 m_data->blockAutoRemove = false;
241
242 m_data->engine.clear();
243}
244
245int QskGridBox::elementCount() const
246{
247 return m_data->engine.count();
248}
249
250int QskGridBox::rowCount() const
251{
252 return m_data->engine.rowCount();
253}
254
255int QskGridBox::columnCount() const
256{
257 return m_data->engine.columnCount();
258}
259
260QQuickItem* QskGridBox::itemAtIndex( int index ) const
261{
262 return m_data->engine.itemAt( index );
263}
264
265int QskGridBox::indexOf( const QQuickItem* item ) const
266{
267 return m_data->engine.indexOf( item );
268}
269
270QQuickItem* QskGridBox::itemAt( int row, int column ) const
271{
272 return m_data->engine.itemAt( row, column );
273}
274
275int QskGridBox::indexAt( int row, int column ) const
276{
277 return m_data->engine.indexAt( row, column );
278}
279
280QRect QskGridBox::gridOfIndex( int index ) const
281{
282 return m_data->engine.gridAt( index );
283}
284
285QRect QskGridBox::effectiveGridOfIndex( int index ) const
286{
287 return m_data->engine.effectiveGridAt( index );
288}
289
290void QskGridBox::setDefaultAlignment( Qt::Alignment alignment )
291{
292 if ( m_data->engine.setDefaultAlignment( alignment ) )
293 Q_EMIT defaultAlignmentChanged();
294}
295
296Qt::Alignment QskGridBox::defaultAlignment() const
297{
298 return m_data->engine.defaultAlignment();
299}
300
301void QskGridBox::setSpacing( Qt::Orientations orientations, qreal spacing )
302{
303 if ( m_data->engine.setSpacing( spacing, orientations ) )
304 {
306 polish();
307 }
308}
309
310void QskGridBox::resetSpacing( Qt::Orientations orientations )
311{
312 for ( const auto o : { Qt::Horizontal, Qt::Vertical } )
313 {
314 if ( orientations & o )
315 setSpacing( o, m_data->engine.defaultSpacing( o ) );
316 }
317}
318
319qreal QskGridBox::spacing( Qt::Orientation orientation ) const
320{
321 return m_data->engine.spacing( orientation );
322}
323
324void QskGridBox::setRowStretchFactor( int row, int stretch )
325{
326 if ( m_data->engine.setStretchFactor( row, stretch, Qt::Vertical ) )
327 polish();
328}
329
330int QskGridBox::rowStretchFactor( int row ) const
331{
332 return m_data->engine.stretchFactor( row, Qt::Vertical );
333}
334
335void QskGridBox::setColumnStretchFactor( int column, int stretch )
336{
337 if ( m_data->engine.setStretchFactor( column, stretch, Qt::Horizontal ) )
338 polish();
339}
340
341int QskGridBox::columnStretchFactor( int column ) const
342{
343 return m_data->engine.stretchFactor( column, Qt::Horizontal );
344}
345
346void QskGridBox::setRowFixedHeight( int row, qreal height )
347{
348 setRowHeightHint( row, Qt::MinimumSize, height );
349 setRowHeightHint( row, Qt::MaximumSize, height );
350}
351
352void QskGridBox::setColumnFixedWidth( int column, qreal width )
353{
354 setColumnWidthHint( column, Qt::MinimumSize, width );
355 setColumnWidthHint( column, Qt::MaximumSize, width );
356}
357
358void QskGridBox::setRowHeightHint( int row, Qt::SizeHint which, qreal height )
359{
360 if ( m_data->engine.setRowSizeHint( row, which, height ) )
361 polish();
362}
363
364qreal QskGridBox::rowHeightHint( int row, Qt::SizeHint which ) const
365{
366 return m_data->engine.rowSizeHint( row, which );
367}
368
369void QskGridBox::setColumnWidthHint( int column, Qt::SizeHint which, qreal width )
370{
371 if ( m_data->engine.setColumnSizeHint( column, which, width ) )
372 polish();
373}
374
375qreal QskGridBox::columnWidthHint( int column, Qt::SizeHint which ) const
376{
377 return m_data->engine.columnSizeHint( column, which );
378}
379
380void QskGridBox::invalidate()
381{
382 m_data->engine.invalidate();
383
385 polish();
386}
387
388void QskGridBox::setItemActive( QQuickItem* item, bool on )
389{
390 if ( on )
391 {
392 QObject::connect( item, &QQuickItem::visibleChanged,
393 this, &QskGridBox::invalidate );
394 }
395 else
396 {
397 QObject::disconnect( item, &QQuickItem::visibleChanged,
398 this, &QskGridBox::invalidate );
399 }
400
401 if ( qskControlCast( item ) == nullptr )
402 qskSetItemActive( this, item, on );
403}
404
405void QskGridBox::updateLayout()
406{
407 if ( !maybeUnresized() )
408 m_data->engine.setGeometries( layoutRect() );
409}
410
411QSizeF QskGridBox::layoutSizeHint(
412 Qt::SizeHint which, const QSizeF& constraint ) const
413{
414 if ( which == Qt::MaximumSize )
415 {
416 // we can extend beyond the maximum size of the children
417 return QSizeF();
418 }
419
420 return m_data->engine.sizeHint( which, constraint );
421}
422
424{
426
427 if ( event->isResized() )
428 polish();
429}
430
431void QskGridBox::itemChange( ItemChange change, const ItemChangeData& value )
432{
433 Inherited::itemChange( change, value );
434
435 switch ( change )
436 {
437 case ItemChildRemovedChange:
438 {
439 if ( !m_data->blockAutoRemove )
440 removeItem( value.item );
441 break;
442 }
443 case QQuickItem::ItemVisibleHasChanged:
444 {
445 if ( value.boolValue )
446 polish();
447 break;
448 }
449 case QQuickItem::ItemSceneChange:
450 {
451 if ( value.window )
452 polish();
453 break;
454 }
455 default:
456 break;
457 }
458}
459
460bool QskGridBox::event( QEvent* event )
461{
462 switch ( static_cast< int >( event->type() ) )
463 {
464 case QEvent::LayoutRequest:
465 {
466 invalidate();
467 break;
468 }
469 case QEvent::LayoutDirectionChange:
470 {
471 m_data->engine.setVisualDirection(
472 layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight );
473
474 polish();
475 break;
476 }
477 case QEvent::ContentsRectChange:
478 {
479 polish();
480 break;
481 }
482 }
483
484 return Inherited::event( event );
485}
486
487void QskGridBox::dump() const
488{
489 const auto& engine = m_data->engine;
490
491 auto debug = qDebug();
492
493 QDebugStateSaver saver( debug );
494 debug.nospace();
495
496 const auto constraint = sizeConstraint();
497
498 debug << "QskGridBox"
499 << "[" << engine.columnCount() << "," << engine.rowCount() << "] w:"
500 << constraint.width() << " h:" << constraint.height() << '\n';
501
502 for ( int i = 0; i < engine.count(); i++ )
503 {
504 const auto grid = engine.gridAt( i );
505
506 debug << " [";
507
508 debug << grid.left();
509 if ( grid.width() > 1 )
510 debug << "-" << grid.right();
511 debug << ",";
512
513 debug << grid.top();
514 if ( grid.height() > 1 )
515 debug << "->" << grid.bottom();
516
517 debug << "]: ";
518
519 if ( auto item = engine.itemAt( i ) )
520 {
521 const auto size = qskSizeConstraint( item, Qt::PreferredSize );
522
523 debug << item->metaObject()->className()
524 << " w:" << size.width() << " h:" << size.height();
525 }
526 else
527 {
528 const auto size = engine.spacerAt( i );
529 debug << "spacer w:" << size.width() << " h:" << size.height();
530 }
531
532 debug << '\n';
533 }
534}
535
536#include "moc_QskGridBox.cpp"
void itemChange(ItemChange, const ItemChangeData &) override
QRectF layoutRect() const
QSizeF sizeConstraint
Definition QskControl.h:50
void geometryChangeEvent(QskGeometryChangeEvent *) 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
void debug(QskAspect) const
@ LeftToRight