QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskWindow.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskWindow.h"
7#include "QskControl.h"
8#include "QskEvent.h"
9#include "QskQuick.h"
10#include "QskSetup.h"
11#include "QskSkin.h"
12#include "QskSkinManager.h"
13
14#include <qmath.h>
15#include <qpointer.h>
16
17QSK_QT_PRIVATE_BEGIN
18#include <private/qquickitem_p.h>
19#include <private/qquickitemchangelistener_p.h>
20#include <private/qquickwindow_p.h>
21#include <private/qsgrenderer_p.h>
22QSK_QT_PRIVATE_END
23
24#include <qpa/qwindowsysteminterface.h>
25#include <QGuiApplication>
26
27// #define QSK_DEBUG_RENDER_TIMING
28
29#ifdef QSK_DEBUG_RENDER_TIMING
30
31#include <qelapsedtimer.h>
32#include <qloggingcategory.h>
33Q_LOGGING_CATEGORY( logTiming, "qsk.window.timing", QtCriticalMsg )
34
35#endif
36
37extern QLocale qskInheritedLocale( const QObject* );
38extern void qskInheritLocale( QObject*, const QLocale& );
39
40static void qskResolveLocale( QskWindow* );
41static bool qskEnforcedSkin = false;
42
43static inline void qskSendEventTo( QObject* object, QEvent::Type type )
44{
45 QEvent event( type );
46 QCoreApplication::sendEvent( object, &event );
47}
48
49static QQuickItem* qskDefaultFocusItem( QQuickWindow* window )
50{
51 const auto children = qskPaintOrderChildItems( window->contentItem() );
52 for ( auto it = children.crbegin(); it != children.crend(); ++it )
53 {
54 auto child = *it;
55
56 if ( child->isFocusScope() && child->isVisible()
57 && child->isEnabled() && child->activeFocusOnTab() )
58 {
59 return child;
60 }
61 }
62
63 return window->contentItem()->nextItemInFocusChain( true );
64}
65
66namespace
67{
68 class ChildListener final : public QQuickItemChangeListener
69 {
70 public:
71 void setEnabled( QQuickItem* contentItem, bool on )
72 {
73 m_contentItem = contentItem;
74
75 const QQuickItemPrivate::ChangeTypes types = QQuickItemPrivate::Children;
76
77 QQuickItemPrivate* p = QQuickItemPrivate::get( contentItem );
78 if ( on )
79 p->addItemChangeListener( this, types );
80 else
81 p->removeItemChangeListener( this, types );
82 }
83
84 void itemChildAdded( QQuickItem*, QQuickItem* ) override
85 {
86 QskWindow* window = static_cast< QskWindow* >( m_contentItem->window() );
87 if ( window->isExposed() )
88 {
89 // the child is not fully constructed
90 QCoreApplication::postEvent( window, new QEvent( QEvent::LayoutRequest ) );
91 }
92 }
93
94 private:
95 QQuickItem* m_contentItem = nullptr;
96 };
97}
98
99static inline int qskToIntegerConstraint( qreal valueF )
100{
101 int value = -1;
102
103 if ( valueF >= 0.0 )
104 {
105 if ( valueF >= std::numeric_limits< int >::max() )
106 value = std::numeric_limits< int >::max();
107 else
108 value = ( int ) qCeil( valueF );
109 }
110
111 return value;
112}
113
114static inline void qskSetVisualizationMode(
115 QQuickWindow* window, const QByteArray& mode )
116{
117 auto d = QQuickWindowPrivate::get( window );
118#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
119 d->visualizationMode = mode;
120#else
121 d->customRenderMode = mode;
122#endif
123}
124
125static inline const QByteArray& qskVisualizationMode( const QQuickWindow* window )
126{
127 auto d = QQuickWindowPrivate::get( const_cast< QQuickWindow* >( window ) );
128#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
129 return d->visualizationMode;
130#else
131 return d->customRenderMode;
132#endif
133}
134
135class QskWindowPrivate : public QQuickWindowPrivate
136{
137 Q_DECLARE_PUBLIC( QskWindow )
138
139 public:
140 QskWindowPrivate()
141 : preferredSize( -1, -1 )
142 , eventAcceptance( QskWindow::EventProcessed )
143 , explicitLocale( false )
144 , deleteOnClose( false )
145 , autoLayoutChildren( true )
146 , showedOnce( false )
147 {
148 }
149
150#ifdef QSK_DEBUG_RENDER_TIMING
151 QElapsedTimer renderInterval;
152#endif
153
154 QPointer< QskSkin > skin;
155
156 ChildListener contentItemListener;
157 QLocale locale;
158
159 // minimum/maximum constraints are offered by QWindow
160 QSize preferredSize;
161
162 QskWindow::EventAcceptance eventAcceptance;
163
164 bool explicitLocale : 1;
165 bool deleteOnClose : 1;
166 bool autoLayoutChildren : 1;
167 bool showedOnce : 1;
168};
169
170QskWindow::QskWindow( QWindow* parent )
171 : Inherited( *( new QskWindowPrivate() ), parent )
172{
173 QSurfaceFormat fmt = format();
174 fmt.setSamples( 4 );
175#if 0
176 // for QOpenGLDebugLogger
177 fmt.setOption( QSurfaceFormat::DebugContext );
178#endif
179 setFormat( fmt );
180
181 /*
182 So that inheriting/resolving of the locale works
183 over all windows and items.
184 */
185 contentItem()->setProperty( "locale", locale() );
186
187 if ( parent )
188 {
189 // also when the parent changes TODO ...
190 qskResolveLocale( this );
191 }
192
193 d_func()->contentItemListener.setEnabled( contentItem(), true );
194
195 if ( !qskEnforcedSkin )
196 connect( this, &QQuickWindow::afterAnimating, this, &QskWindow::enforceSkin );
197}
198
199QskWindow::QskWindow( QQuickRenderControl* renderControl, QWindow* parent )
200 : QskWindow( parent )
201{
202 Q_D( QskWindow );
203
204 d->renderControl = renderControl;
205 d->init( this, renderControl );
206}
207
208QskWindow::~QskWindow()
209{
210}
211
212void QskWindow::setScreen( const QString& name )
213{
214 if ( !name.isEmpty() )
215 {
216 const auto screens = QGuiApplication::screens();
217 for ( auto screen : screens )
218 {
219 if ( screen->name() == name )
220 {
221 setScreen( screen );
222 return;
223 }
224 }
225 }
226
227 setScreen( QGuiApplication::primaryScreen() );
228}
229
230void QskWindow::resizeF( const QSizeF& size )
231{
232 const int w = static_cast< int >( qCeil( size.width() ) );
233 const int h = static_cast< int >( qCeil( size.height() ) );
234
235 resize( w, h );
236}
237
238bool QskWindow::deleteOnClose() const
239{
240 Q_D( const QskWindow );
241 return d->deleteOnClose;
242}
243
244void QskWindow::setDeleteOnClose( bool on )
245{
246 Q_D( QskWindow );
247
248 if ( on != d->deleteOnClose )
249 {
250 d->deleteOnClose = on;
251 Q_EMIT deleteOnCloseChanged();
252 }
253}
254
255void QskWindow::setAutoLayoutChildren( bool on )
256{
257 Q_D( QskWindow );
258
259 if ( on != d->autoLayoutChildren )
260 {
261 d->autoLayoutChildren = on;
262 if ( on )
263 qskSendEventTo( this, QEvent::LayoutRequest );
264
265 Q_EMIT autoLayoutChildrenChanged();
266 }
267}
268
269bool QskWindow::autoLayoutChildren() const
270{
271 Q_D( const QskWindow );
272 return d->autoLayoutChildren;
273}
274
275void QskWindow::addItem( QQuickItem* item )
276{
277 if ( item == nullptr )
278 return;
279
280 item->setParent( contentItem() );
281 item->setParentItem( contentItem() );
282}
283
284void QskWindow::polishItems()
285{
286 Q_D( QskWindow );
287 d->polishItems();
288}
289
290bool QskWindow::event( QEvent* event )
291{
292 /*
293 Qt/Quick is lacking a flag like Qt::WA_NoMousePropagation, so
294 the only way to stop propagating of input events is to accept
295 the event. Then the window can't decide anymore if someone was
296 interested and when to do some fallback handling.
297 To improve the situation we add an extra flag in QskWindow,
298 while the right class to solve this shortcoming would be QQuickWindow.
299 */
300
301 Q_D( QskWindow );
302 d->eventAcceptance = EventProcessed;
303
304 switch ( event->type() )
305 {
306 case QEvent::Show:
307 {
308 if ( !d->showedOnce )
309 {
310 d->showedOnce = true;
311
312 /*
313 When a window has a platform window its size is taken
314 from the platform window - otherwise from d->geometry.
315 A top level window that has not been shown does not have
316 a platform window yet and therefore any size set before calling
317 QWindow::show() is stored there.
318
319 Starting with Qt 6.5 an initial default size is set to the platform
320 window ( at least QXcbWindow ) before the Show event is sent
321 and we can't rely on QWindow::size() anymore. But even if d->geometry
322 is not used anymore we can initialize depending on it.
323 */
324
325 if ( d->geometry.size().isEmpty() )
326 {
327 QSize sz = sizeConstraint();
328 if ( !sz.isEmpty() )
329 {
330 sz = sz.expandedTo( minimumSize() );
331 sz = sz.boundedTo( maximumSize() );
332
333 resize( sz );
334 }
335 }
336 }
337
338 break;
339 }
340 case QEvent::LayoutRequest:
341 {
342 if ( isExposed() )
343 layoutItems();
344 break;
345 }
346 case QEvent::LocaleChange:
347 {
348 Q_EMIT localeChanged( locale() );
349 break;
350 }
351 case QEvent::Close:
352 {
353 if ( event->isAccepted() )
354 {
355 if ( d->deleteOnClose )
356 deleteLater();
357 }
358 break;
359 }
360 case QEvent::UpdateRequest:
361 {
362#ifdef QSK_DEBUG_RENDER_TIMING
363 if ( logTiming().isDebugEnabled() )
364 {
365 if ( !d->renderInterval.isValid() )
366 d->renderInterval.start();
367
368 qCDebug( logTiming() ) << "update timer - elapsed"
369 << d->renderInterval.restart() << objectName();
370 }
371#endif
372 break;
373 }
374
375 default:
376 break;
377 }
378
379 return Inherited::event( event );
380}
381
382void QskWindow::keyPressEvent( QKeyEvent* event )
383{
384 if ( qskFocusChainIncrement( event ) != 0 )
385 {
386 auto focusItem = activeFocusItem();
387 if ( focusItem == nullptr || focusItem == contentItem() )
388 {
389 /*
390 The Qt/Quick implementation for navigating along the
391 focus tab chain gives unsufficient results, when the
392 starting point is the root item. In this specific
393 situation we also have to include all items being
394 tab fences into consideration.
395
396 In certain situations Qt/Quick gets even stuck in a non
397 terminating loop: see Qt-Bug 65943
398
399 So we better block the focus navigation and find the
400 next focus item on our own.
401 */
402 ensureFocus( Qt::TabFocusReason );
403 event->accept();
404
405 return;
406 }
407 }
408
409 Inherited::keyPressEvent( event );
410}
411
412void QskWindow::keyReleaseEvent( QKeyEvent* event )
413{
414 Inherited::keyReleaseEvent( event );
415}
416
417void QskWindow::exposeEvent( QExposeEvent* event )
418{
419#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
420 if ( qskRenderingHardwareInterface( this ) )
421 {
422 /*
423 Actually our code supports Qt5 RHI ( f9c08c34fb2cc64546bbe6ce9d359f416e934961 ),
424 but Qt5 does not come with the qsb tool out of the box. Then we run into
425 problems with compiling the shader code from the makefiles.
426 But why should anyone use the experimental Qt5 RHI implementations
427 instead of using Qt6 ...
428 */
429 qFatal( "the experimental Qt5 RHI implementation is not supported:\n"
430 "\tuse Qt6 or run the default OpenGL backend." );
431 }
432#endif
433 ensureFocus( Qt::OtherFocusReason );
434 layoutItems();
435
436 Inherited::exposeEvent( event );
437}
438
439void QskWindow::resizeEvent( QResizeEvent* event )
440{
441 auto rootItem = contentItem();
442
443 const auto oldRect = qskItemGeometry( rootItem );
444 Inherited::resizeEvent( event );
445
446 const auto newRect = qskItemGeometry( rootItem );
447 if ( newRect != oldRect )
448 {
449 QskGeometryChangeEvent event( newRect, oldRect );
450 QCoreApplication::sendEvent( rootItem, &event );
451 }
452
453 if ( isExposed() )
454 layoutItems();
455}
456
457QLocale QskWindow::locale() const
458{
459 Q_D( const QskWindow );
460 return d->locale;
461}
462
463void QskWindow::setLocale( const QLocale& locale )
464{
465 Q_D( QskWindow );
466
467 d->explicitLocale = true;
468
469 if ( d->locale != locale )
470 {
471 d->locale = locale;
472 qskSendEventTo( this, QEvent::LocaleChange );
473 qskInheritLocale( this, locale );
474 }
475}
476
477void QskWindow::resetLocale()
478{
479 Q_D( QskWindow );
480
481 d->explicitLocale = false;
482 qskResolveLocale( this );
483}
484
485bool qskInheritLocale( QskWindow* window, const QLocale& locale )
486{
487 auto d = static_cast< QskWindowPrivate* >( QQuickWindowPrivate::get( window ) );
488
489 if ( d->explicitLocale || d->locale == locale )
490 return false;
491
492 d->locale = locale;
493 qskSendEventTo( window, QEvent::LocaleChange );
494
495 return true;
496}
497
498static void qskResolveLocale( QskWindow* window )
499{
500 auto d = static_cast< QskWindowPrivate* >( QQuickWindowPrivate::get( window ) );
501
502 const auto locale = qskInheritedLocale( window );
503
504 if ( d->locale != locale )
505 {
506 d->locale = locale;
507 qskSendEventTo( window, QEvent::LocaleChange );
508
509 qskInheritLocale( window, locale );
510 }
511}
512
513void QskWindow::setPreferredSize( const QSize& size )
514{
515 Q_D( QskWindow );
516 d->preferredSize = size;
517}
518
519QSize QskWindow::preferredSize() const
520{
521 Q_D( const QskWindow );
522 return d->preferredSize;
523}
524
525QSize QskWindow::sizeConstraint() const
526{
527 Q_D( const QskWindow );
528
529 QSizeF constraint = d->preferredSize;
530
531 if ( !constraint.isValid() )
532 {
533 const bool doWidth = constraint.width() < 0;
534 const bool doHeight = constraint.height() < 0;
535
536 const auto children = contentItem()->childItems();
537 for ( auto child : children )
538 {
539 if ( qskIsVisibleToLayout( child ) )
540 {
541 const auto size = qskSizeConstraint( child, Qt::PreferredSize );
542
543 if ( doWidth )
544 constraint.setWidth( qMax( constraint.width(), size.width() ) );
545
546 if ( doHeight )
547 constraint.setHeight( qMax( constraint.height(), size.height() ) );
548 }
549 }
550 }
551
552 // QWindow geometries are in integers
553
554 return QSize( qskToIntegerConstraint( constraint.width() ),
555 qskToIntegerConstraint( constraint.height() ) );
556}
557
558void QskWindow::setFixedSize( const QSize& size )
559{
560 // ????
561 setMinimumSize( size );
562 setMaximumSize( size );
563}
564
565void QskWindow::layoutItems()
566{
567 Q_D( QskWindow );
568
569 if ( !d->autoLayoutChildren )
570 return;
571
572 const QRectF rect = qskItemGeometry( contentItem() );
573
574 const auto children = contentItem()->childItems();
575 for ( auto child : children )
576 {
577 if ( qskIsAdjustableByLayout( child ) )
578 {
579 const auto r = qskConstrainedItemRect( child, rect );
580 qskSetItemGeometry( child, r );
581 }
582 }
583}
584
585void QskWindow::ensureFocus( Qt::FocusReason reason )
586{
587 auto focusItem = contentItem()->scopedFocusItem();
588
589 if ( focusItem == nullptr )
590 {
591 focusItem = qskDefaultFocusItem( this );
592 if ( focusItem )
593 focusItem->setFocus( true, reason );
594 }
595}
596
597void QskWindow::setCustomRenderMode( const char* mode )
598{
599 class RenderJob final : public QRunnable
600 {
601 public:
602 RenderJob( QQuickWindow* window, const QByteArray& mode )
603 : m_window( window )
604 , m_mode( mode )
605 {
606 }
607
608 void run() override
609 {
610 qskSetVisualizationMode( m_window, m_mode );
611
612 auto d = QQuickWindowPrivate::get( m_window );
613 if ( d->renderer )
614 {
615 delete d->renderer->rootNode();
616 delete d->renderer;
617 d->renderer = nullptr;
618
619 QMetaObject::invokeMethod( m_window, "update" );
620 }
621 }
622
623 private:
624 QQuickWindow* m_window;
625 const QByteArray m_mode;
626 };
627
628 const QByteArray newMode( mode );
629
630 const auto oldMode = qskVisualizationMode( this );
631
632 if ( newMode != oldMode )
633 {
634 /*
635 batch renderer uses an optimized memory allocation strategy,
636 that is disabled, when a customRenderMode is enabled.
637 This seems to be the reason for crashes, when changing the mode
638 at runtime. The code above tries to get rid of all memory
639 from the previous allocation strategy by deleting the renderer
640 after swapping.
641 */
642
643 if ( newMode.isEmpty() != oldMode.isEmpty() )
644 scheduleRenderJob( new RenderJob( this, newMode ), AfterSwapStage );
645 else
646 qskSetVisualizationMode( this, newMode );
647
648 update();
649 }
650}
651
652const char* QskWindow::customRenderMode() const
653{
654 return qskVisualizationMode( this );
655}
656
657void QskWindow::enforceSkin()
658{
659 if ( !qskEnforcedSkin )
660 {
661 // usually the skin is set in the application startup code, but if not
662 // let's create a default skin on the GUI thread - whatever it is
663 // good for.
664
665 ( void ) qskSkinManager->skin();
666 qskEnforcedSkin = true;
667 }
668
669 disconnect( this, &QQuickWindow::afterAnimating, this, &QskWindow::enforceSkin );
670}
671
672void QskWindow::setEventAcceptance( EventAcceptance acceptance )
673{
674 d_func()->eventAcceptance = acceptance;
675}
676
677QskWindow::EventAcceptance QskWindow::eventAcceptance() const
678{
679 return d_func()->eventAcceptance;
680}
681
682void QskWindow::setSkin( const QString& skinName )
683{
684 // we should compare the skinName with the previous one
685 auto skin = QskSkinManager::instance()->createSkin( skinName );
686 setSkin( skin );
687}
688
689void QskWindow::setSkin( QskSkin* skin )
690{
691 Q_D( QskWindow );
692
693 if ( d->skin == skin )
694 return;
695
696 if ( d->skin )
697 {
698 if ( d->skin->parent() == this )
699 delete d->skin;
700 }
701
702 if ( skin && skin->parent() == nullptr )
703 skin->setParent( this );
704
705 d->skin = skin;
706}
707
708QskSkin* QskWindow::skin() const
709{
710 return d_func()->skin;
711}
712
713QskSkin* qskEffectiveSkin( const QQuickWindow* window )
714{
715 if ( auto w = qobject_cast< const QskWindow* >( window ) )
716 {
717 if ( auto skin = w->skin() )
718 return skin;
719 }
720
721 return qskSkinManager->skin();
722}
723
724#include "moc_QskWindow.cpp"