QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskDialogButtonBox.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskDialogButtonBox.h"
7#include "QskDialogButton.h"
8#include "QskLinearBox.h"
9#include "QskSkin.h"
10
11#include "QskLinearLayoutEngine.h"
12
13#include <qevent.h>
14#include <qpointer.h>
15#include <qvector.h>
16
17#include <qpa/qplatformdialoghelper.h>
18
19#include <limits>
20
21QSK_SUBCONTROL( QskDialogButtonBox, Panel )
22
23static void qskSendEventTo( QObject* object, QEvent::Type type )
24{
25 QEvent event( type );
26 QCoreApplication::sendEvent( object, &event );
27}
28
29namespace
30{
31 class LayoutEngine : public QskLinearLayoutEngine
32 {
33 public:
34 LayoutEngine( Qt::Orientation orientation )
35 : QskLinearLayoutEngine( orientation, std::numeric_limits< uint >::max() )
36 {
37 }
38
39 void addStretch()
40 {
41 const auto index = insertSpacerAt( count(), 0 );
42 setStretchFactorAt( index, 1 );
43 }
44
45 void addButtons( const QVector< QskPushButton* >& buttons, bool reverse )
46 {
47 if ( reverse )
48 {
49 for ( int i = buttons.count() - 1; i >= 0; i-- )
50 addItem( buttons[ i ] );
51 }
52 else
53 {
54 for ( int i = 0; i < buttons.count(); i++ )
55 addItem( buttons[ i ] );
56 }
57 }
58 };
59}
60
61class QskDialogButtonBox::PrivateData
62{
63 public:
64 PrivateData( Qt::Orientation orientation )
65 : layoutEngine( orientation )
66 {
67 }
68
69 static inline void connectButton( QskDialogButtonBox* box,
70 QskPushButton* button, bool on )
71 {
72 if ( on )
73 {
74 connect( button, &QskPushButton::clicked,
75 box, &QskDialogButtonBox::onButtonClicked,
76 Qt::UniqueConnection );
77
78 connect( button, &QskPushButton::visibleChanged,
79 box, &QskDialogButtonBox::invalidateLayout,
80 Qt::UniqueConnection );
81 }
82 else
83 {
84 disconnect( button, &QskPushButton::clicked,
85 box, &QskDialogButtonBox::onButtonClicked );
86
87 disconnect( button, &QskPushButton::visibleChanged,
88 box, &QskDialogButtonBox::invalidateLayout );
89 }
90 }
91 LayoutEngine layoutEngine;
92
93 QVector< QskPushButton* > buttons[ QskDialog::NActionRoles ];
94 QPointer< QskPushButton > defaultButton;
95
96 QskDialog::Action clickedAction = QskDialog::NoAction;
97 bool centeredButtons = false;
98};
99
100QskDialogButtonBox::QskDialogButtonBox( QQuickItem* parent )
101 : QskDialogButtonBox( Qt::Horizontal, parent )
102{
103}
104
105QskDialogButtonBox::QskDialogButtonBox( Qt::Orientation orientation, QQuickItem* parent )
106 : Inherited( parent )
107 , m_data( new PrivateData( orientation ) )
108{
109 setPolishOnResize( true );
110
111 if ( orientation == Qt::Horizontal )
112 initSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Fixed );
113 else
114 initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred );
115}
116
117QskDialogButtonBox::~QskDialogButtonBox()
118{
119 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
120 {
121 for ( auto button : std::as_const( m_data->buttons[ i ] ) )
122 {
123 /*
124 The destructor of QQuickItem sets the parentItem of
125 all children to nullptr, what leads to visibleChanged
126 signals. So we better disconnect first.
127 */
128 PrivateData::connectButton( this, button, false );
129 }
130 }
131}
132
133void QskDialogButtonBox::setOrientation( Qt::Orientation orientation )
134{
135 if ( m_data->layoutEngine.orientation() == orientation )
136 return;
137
138 m_data->layoutEngine.setOrientation( orientation );
139
140 if ( orientation == Qt::Horizontal )
141 setSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Fixed );
142 else
143 setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred );
144
145 invalidateLayout();
146
147 Q_EMIT orientationChanged();
148}
149
150Qt::Orientation QskDialogButtonBox::orientation() const
151{
152 return m_data->layoutEngine.orientation();
153}
154
155QskAspect::Subcontrol QskDialogButtonBox::substitutedSubcontrol(
156 QskAspect::Subcontrol subControl ) const
157{
158 if ( subControl == QskBox::Panel )
159 return QskDialogButtonBox::Panel;
160
161 return Inherited::substitutedSubcontrol( subControl );
162}
163
164QSizeF QskDialogButtonBox::layoutSizeHint(
165 Qt::SizeHint which, const QSizeF& constraint ) const
166{
167 if ( which == Qt::MaximumSize )
168 return QSizeF(); // unlimited
169
170 if ( ( m_data->layoutEngine.count() == 0 ) && hasChildItems() )
171 {
172 const_cast< QskDialogButtonBox* >( this )->rearrangeButtons();
173 }
174
175 return m_data->layoutEngine.sizeHint( which, constraint );
176}
177
178void QskDialogButtonBox::invalidateLayout()
179{
180 m_data->layoutEngine.clear();
182 polish();
183}
184
185void QskDialogButtonBox::updateLayout()
186{
187 auto& layoutEngine = m_data->layoutEngine;
188
189 if ( ( layoutEngine.count() == 0 ) && hasChildItems() )
190 {
191 rearrangeButtons();
192
193 if ( parentItem() && ( layoutEngine.count() > 0 ) )
194 qskSendEventTo( parentItem(), QEvent::LayoutRequest );
195 }
196
197 if ( !maybeUnresized() )
198 layoutEngine.setGeometries( layoutRect() );
199}
200
201void QskDialogButtonBox::rearrangeButtons()
202{
203 // Result differs from QDialogButtonBox. Needs more
204 // investigation - TODO ...
205
206 auto& layoutEngine = m_data->layoutEngine;
207 layoutEngine.clear();
208
209 const int* currentLayout = effectiveSkin()->dialogButtonLayout( orientation() );
210
211 if ( m_data->centeredButtons )
212 layoutEngine.addStretch();
213
214 while ( *currentLayout != QPlatformDialogHelper::EOL )
215 {
216 const int role = ( *currentLayout & ~QPlatformDialogHelper::Reverse );
217 const bool reverse = ( *currentLayout & QPlatformDialogHelper::Reverse );
218
219 switch ( role )
220 {
221 case QPlatformDialogHelper::Stretch:
222 {
223 if ( !m_data->centeredButtons )
224 layoutEngine.addStretch();
225
226 break;
227 }
228 case QPlatformDialogHelper::AcceptRole:
229 {
230 const auto& buttons = m_data->buttons[ role ];
231
232 if ( !buttons.isEmpty() )
233 layoutEngine.addItem( buttons.first() );
234
235 break;
236 }
237 case QPlatformDialogHelper::AlternateRole:
238 {
239 const auto& buttons = m_data->buttons[ QskDialog::AcceptRole ];
240
241 if ( buttons.size() > 1 )
242 layoutEngine.addButtons( buttons.mid( 1 ), reverse );
243
244 break;
245 }
246 case QPlatformDialogHelper::DestructiveRole:
247 case QPlatformDialogHelper::RejectRole:
248 case QPlatformDialogHelper::ActionRole:
249 case QPlatformDialogHelper::HelpRole:
250 case QPlatformDialogHelper::YesRole:
251 case QPlatformDialogHelper::NoRole:
252 case QPlatformDialogHelper::ApplyRole:
253 case QPlatformDialogHelper::ResetRole:
254 {
255 const auto& buttons = m_data->buttons[ role ];
256
257 if ( !buttons.isEmpty() )
258 layoutEngine.addButtons( buttons, reverse );
259
260 break;
261 }
262 }
263
264 ++currentLayout;
265 }
266
267 if ( m_data->centeredButtons )
268 layoutEngine.addStretch();
269
270 updateTabFocusChain();
271}
272
273void QskDialogButtonBox::updateTabFocusChain()
274{
275 if ( childItems().count() <= 1 )
276 return;
277
278 QQuickItem* lastItem = nullptr;
279
280 const auto& layoutEngine = m_data->layoutEngine;
281 for ( int i = 0; i < layoutEngine.count(); i++ )
282 {
283 if ( auto item = layoutEngine.itemAt( i ) )
284 {
285 if ( lastItem )
286 item->stackAfter( lastItem );
287
288 lastItem = item;
289 }
290 }
291}
292
293void QskDialogButtonBox::setCenteredButtons( bool centered )
294{
295 if ( centered != m_data->centeredButtons )
296 {
297 m_data->centeredButtons = centered;
298 invalidateLayout();
299
300 Q_EMIT centeredButtonsChanged();
301 }
302}
303
304bool QskDialogButtonBox::centeredButtons() const
305{
306 return m_data->centeredButtons;
307}
308
309void QskDialogButtonBox::addButton(
310 QskPushButton* button, QskDialog::ActionRole role )
311{
312 if ( role < 0 || role >= QskDialog::NActionRoles )
313 return;
314
315 if ( button )
316 {
317 if ( button->parent() == nullptr )
318 button->setParent( this );
319
320 /*
321 Order of the children according to the layout rules
322 will be done later in updateTabOrder
323 */
324 button->setParentItem( this );
325
326 PrivateData::connectButton( this, button, true );
327
328 m_data->buttons[ role ].removeOne( button );
329 m_data->buttons[ role ] += button;
330 invalidateLayout();
331 }
332}
333
334void QskDialogButtonBox::addAction( QskDialog::Action action )
335{
336 if ( auto button = createButton( action ) )
337 addButton( button, QskDialog::actionRole( action ) );
338}
339
340void QskDialogButtonBox::removeButton( QskPushButton* button )
341{
342 if ( button == nullptr )
343 return;
344
345 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
346 {
347 if ( m_data->buttons[ i ].removeOne( button ) )
348 {
349 PrivateData::connectButton( this, button, false );
350
351 invalidateLayout();
352
353 return;
354 }
355 }
356}
357
358QskPushButton* QskDialogButtonBox::createButton(
359 QskDialog::Action action ) const
360{
361 return new QskDialogButton( action );
362}
363
364void QskDialogButtonBox::clear()
365{
366 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
367 {
368 qDeleteAll( m_data->buttons[ i ] );
369 m_data->buttons[ i ].clear();
370 }
371
372 invalidateLayout();
373}
374
375void QskDialogButtonBox::setDefaultButton( QskPushButton* button )
376{
377 m_data->defaultButton = button;
378}
379
380QskPushButton* QskDialogButtonBox::defaultButton() const
381{
382 return m_data->defaultButton;
383}
384
385void QskDialogButtonBox::setActions( QskDialog::Actions actions )
386{
387 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
388 {
389 qDeleteAll( m_data->buttons[ i ] );
390 m_data->buttons[ i ].clear();
391 }
392
393 for ( int i = QskDialog::Ok; i <= QskDialog::RestoreDefaults; i <<= 1 )
394 {
395 const auto action = static_cast< QskDialog::Action >( i );
396 if ( action & actions )
397 addAction( action );
398 }
399
400 invalidateLayout();
401}
402
403QVector< QskPushButton* > QskDialogButtonBox::buttons() const
404{
405 QVector< QskPushButton* > buttons;
406
407 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
408 buttons += m_data->buttons[ i ];
409
410 return buttons;
411}
412
413QVector< QskPushButton* > QskDialogButtonBox::buttons(
414 QskDialog::ActionRole role ) const
415{
416 if ( role < 0 || role >= QskDialog::NActionRoles )
417 return QVector< QskPushButton* >();
418
419 return m_data->buttons[ role ];
420}
421
422
423QskDialog::ActionRole QskDialogButtonBox::actionRole(
424 const QskPushButton* button ) const
425{
426 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
427 {
428 const auto& buttons = m_data->buttons[ i ];
429
430 for ( const auto btn : buttons )
431 {
432 if ( button == btn )
433 return static_cast< QskDialog::ActionRole >( i );
434 }
435 }
436
437 return QskDialog::InvalidRole;
438}
439
440void QskDialogButtonBox::onButtonClicked()
441{
442 auto button = qobject_cast< QskPushButton* >( sender() );
443 if ( button == nullptr )
444 return;
445
446 switch ( actionRole( button ) )
447 {
448 case QskDialog::AcceptRole:
449 case QskDialog::YesRole:
450 {
451 m_data->clickedAction = action( button );
452
453 Q_EMIT clicked( button );
454 Q_EMIT accepted();
455 break;
456 }
457 case QskDialog::RejectRole:
458 case QskDialog::NoRole:
459 case QskDialog::DestructiveRole:
460 {
461 m_data->clickedAction = action( button );
462
463 Q_EMIT clicked( button );
464 Q_EMIT rejected();
465 break;
466 }
467 default:
468 {
469 m_data->clickedAction = QskDialog::NoAction;
470 Q_EMIT clicked( button );
471
472 break;
473 }
474 }
475}
476
477QskDialog::Actions QskDialogButtonBox::actions() const
478{
479 QskDialog::Actions actions;
480
481 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
482 {
483 const auto& buttons = m_data->buttons[ i ];
484
485 for ( const auto btn : buttons )
486 actions |= action( btn );
487 }
488
489 return actions;
490}
491
492QskDialog::Action QskDialogButtonBox::action( const QskPushButton* button ) const
493{
494 if ( button )
495 {
496 if ( auto dialogButton = qobject_cast< const QskDialogButton* >( button ) )
497 return dialogButton->action();
498
499 const QVariant value = button->property( "standardButton" );
500 if ( value.canConvert< int >() )
501 return static_cast< QskDialog::Action >( value.toInt() );
502 }
503
504 return QskDialog::NoAction;
505}
506
507QskPushButton* QskDialogButtonBox::button( QskDialog::Action action ) const
508{
509 for ( int i = 0; i < QskDialog::NActionRoles; i++ )
510 {
511 const auto& buttons = m_data->buttons[ i ];
512 for ( auto btn : buttons )
513 {
514 if ( this->action( btn ) == action )
515 return btn;
516 }
517 }
518
519 return nullptr;
520}
521
522QskDialog::Action QskDialogButtonBox::clickedAction() const
523{
524 return m_data->clickedAction;
525}
526
527bool QskDialogButtonBox::event( QEvent* event )
528{
529 switch ( static_cast< int >( event->type() ) )
530 {
531 case QEvent::LayoutRequest:
532 {
533 invalidateLayout();
534 break;
535 }
536
537 case QEvent::LayoutDirectionChange:
538 {
539 m_data->layoutEngine.setVisualDirection(
540 layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight );
541
542 break;
543 }
544 case QEvent::ContentsRectChange:
545 {
546 polish();
547 break;
548 }
549 }
550
551 return Inherited::event( event );
552}
553
554void QskDialogButtonBox::itemChange(
555 QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& value )
556{
557 Inherited::itemChange( change, value );
558
559 if ( change == ItemChildRemovedChange )
560 {
561 if ( auto button = qobject_cast< QskPushButton* >( value.item ) )
562 removeButton( button );
563 }
564}
565
566bool QskDialogButtonBox::isDefaultButtonKeyEvent( const QKeyEvent* event )
567{
568 if ( !event->modifiers() )
569 return ( event->key() == Qt::Key_Enter ) || ( event->key() == Qt::Key_Return );
570
571 return ( event->modifiers() & Qt::KeypadModifier ) && ( event->key() == Qt::Key_Enter );
572}
573
574#include "moc_QskDialogButtonBox.cpp"
Subcontrol
For use within the rendering or lay-outing of a specific QskSkinnable.
Definition QskAspect.h:104
void setSizePolicy(QskSizePolicy)
void itemChange(ItemChange, const ItemChangeData &) override
QRectF layoutRect() const
bool hasChildItems
Definition QskItem.h:39
void resetImplicitSize()
Definition QskItem.cpp:721
bool maybeUnresized() const
Definition QskItem.cpp:583
bool layoutMirroring() const
Definition QskItem.cpp:511
QskSkin * effectiveSkin() const
@ LeftToRight