1 /* === This file is part of Calamares - <https://calamares.io> ===
3 * SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
4 * SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
5 * SPDX-FileCopyrightText: 2018 Raul Rodrigo Segura (raurodse)
6 * SPDX-FileCopyrightText: 2019 Collabora Ltd <arnaud.ferraris@collabora.com>
7 * SPDX-FileCopyrightText: 2020 Anubhav Choudhary <ac.10edu@gmail.com>
8 * SPDX-License-Identifier: GPL-3.0-or-later
10 * Calamares is Free Software: see the License-Identifier above.
14 #include "CalamaresWindow.h"
17 #include "CalamaresConfig.h"
18 #include "DebugWindow.h"
20 #include "ViewManager.h"
21 #include "progresstree/ProgressTreeView.h"
22 #include "utils/CalamaresUtilsGui.h"
23 #include "utils/Logger.h"
24 #include "utils/Qml.h"
25 #include "utils/Retranslator.h"
27 #include <QApplication>
29 #include <QCloseEvent>
30 #include <QDesktopWidget>
36 #include <QQuickWidget>
41 windowDimensionToPixels( const Calamares::Branding::WindowDimension& u )
47 if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Pixies )
49 return static_cast< int >( u.value() );
51 if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Fonties )
53 return static_cast< int >( u.value() * CalamaresUtils::defaultFontHeight() );
60 CalamaresWindow::getWidgetSidebar( QWidget* parent, int desiredWidth )
62 const Calamares::Branding* const branding = Calamares::Branding::instance();
64 QWidget* sideBox = new QWidget( parent );
65 sideBox->setObjectName( "sidebarApp" );
67 QBoxLayout* sideLayout = new QVBoxLayout;
68 sideBox->setLayout( sideLayout );
69 // Set this attribute into qss file
70 sideBox->setFixedWidth( desiredWidth );
71 sideBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
73 QHBoxLayout* logoLayout = new QHBoxLayout;
74 sideLayout->addLayout( logoLayout );
75 logoLayout->addStretch();
76 QLabel* logoLabel = new QLabel( sideBox );
77 logoLabel->setObjectName( "logoApp" );
78 //Define all values into qss file
80 QPalette plt = sideBox->palette();
81 sideBox->setAutoFillBackground( true );
82 plt.setColor( sideBox->backgroundRole(), branding->styleString( Calamares::Branding::SidebarBackground ) );
83 plt.setColor( sideBox->foregroundRole(), branding->styleString( Calamares::Branding::SidebarText ) );
84 sideBox->setPalette( plt );
85 logoLabel->setPalette( plt );
87 logoLabel->setAlignment( Qt::AlignCenter );
88 logoLabel->setFixedSize( 80, 80 );
89 logoLabel->setPixmap( branding->image( Calamares::Branding::ProductLogo, logoLabel->size() ) );
90 logoLayout->addWidget( logoLabel );
91 logoLayout->addStretch();
93 ProgressTreeView* tv = new ProgressTreeView( sideBox );
94 tv->setModel( Calamares::ViewManager::instance() );
95 tv->setFocusPolicy( Qt::NoFocus );
96 sideLayout->addWidget( tv );
98 if ( Calamares::Settings::instance()->debugMode() || ( Logger::logLevel() >= Logger::LOGVERBOSE ) )
100 QPushButton* debugWindowBtn = new QPushButton;
101 debugWindowBtn->setObjectName( "debugButton" );
102 CALAMARES_RETRANSLATE( debugWindowBtn->setText( tr( "Show debug information" ) ); )
103 sideLayout->addWidget( debugWindowBtn );
104 debugWindowBtn->setFlat( true );
105 debugWindowBtn->setCheckable( true );
106 connect( debugWindowBtn, &QPushButton::clicked, this, [=]( bool checked ) {
109 m_debugWindow = new Calamares::DebugWindow();
110 m_debugWindow->show();
111 connect( m_debugWindow.data(), &Calamares::DebugWindow::closed, this, [=]() {
112 m_debugWindow->deleteLater();
113 debugWindowBtn->setChecked( false );
120 m_debugWindow->deleteLater();
126 CalamaresUtils::unmarginLayout( sideLayout );
130 /** @brief Get a button-sized icon. */
131 static inline QPixmap
132 getButtonIcon( const QString& name )
134 return Calamares::Branding::instance()->image( name, QSize( 22, 22 ) );
138 setButtonIcon( QPushButton* button, const QString& name )
140 auto icon = getButtonIcon( name );
141 if ( button && !icon.isNull() )
143 button->setIcon( icon );
148 CalamaresWindow::getWidgetNavigation( QWidget* parent )
150 QWidget* navigation = new QWidget( parent );
151 QBoxLayout* bottomLayout = new QHBoxLayout;
152 bottomLayout->addStretch();
154 // Create buttons and sets an initial icon; the icons may change
156 auto* back = new QPushButton( getButtonIcon( QStringLiteral( "go-previous" ) ), tr( "&Back" ), navigation );
157 back->setObjectName( "view-button-back" );
158 back->setEnabled( m_viewManager->backEnabled() );
159 connect( back, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::back );
160 connect( m_viewManager, &Calamares::ViewManager::backEnabledChanged, back, &QPushButton::setEnabled );
161 connect( m_viewManager, &Calamares::ViewManager::backLabelChanged, back, &QPushButton::setText );
162 connect( m_viewManager, &Calamares::ViewManager::backIconChanged, this, [=]( QString n ) {
163 setButtonIcon( back, n );
165 bottomLayout->addWidget( back );
168 auto* next = new QPushButton( getButtonIcon( QStringLiteral( "go-next" ) ), tr( "&Next" ), navigation );
169 next->setObjectName( "view-button-next" );
170 next->setEnabled( m_viewManager->nextEnabled() );
171 connect( next, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::next );
172 connect( m_viewManager, &Calamares::ViewManager::nextEnabledChanged, next, &QPushButton::setEnabled );
173 connect( m_viewManager, &Calamares::ViewManager::nextLabelChanged, next, &QPushButton::setText );
174 connect( m_viewManager, &Calamares::ViewManager::nextIconChanged, this, [=]( QString n ) {
175 setButtonIcon( next, n );
177 bottomLayout->addWidget( next );
179 bottomLayout->addSpacing( 12 );
181 auto* quit = new QPushButton( getButtonIcon( QStringLiteral( "dialog-cancel" ) ), tr( "&Cancel" ), navigation );
182 quit->setObjectName( "view-button-cancel" );
183 connect( quit, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::quit );
184 connect( m_viewManager, &Calamares::ViewManager::quitEnabledChanged, quit, &QPushButton::setEnabled );
185 connect( m_viewManager, &Calamares::ViewManager::quitLabelChanged, quit, &QPushButton::setText );
186 connect( m_viewManager, &Calamares::ViewManager::quitIconChanged, this, [=]( QString n ) {
187 setButtonIcon( quit, n );
189 connect( m_viewManager, &Calamares::ViewManager::quitTooltipChanged, quit, &QPushButton::setToolTip );
190 connect( m_viewManager, &Calamares::ViewManager::quitVisibleChanged, quit, &QPushButton::setVisible );
191 bottomLayout->addWidget( quit );
194 bottomLayout->setContentsMargins( 0, 0, 6, 6 );
195 navigation->setLayout( bottomLayout );
201 CalamaresWindow::getQmlSidebar( QWidget* parent, int )
203 CalamaresUtils::registerQmlModels();
204 QQuickWidget* w = new QQuickWidget( parent );
205 w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
206 w->setResizeMode( QQuickWidget::SizeRootObjectToView );
208 CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-sidebar" ) ) ) );
213 CalamaresWindow::getQmlNavigation( QWidget* parent )
215 CalamaresUtils::registerQmlModels();
216 QQuickWidget* w = new QQuickWidget( parent );
217 w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
218 w->setResizeMode( QQuickWidget::SizeRootObjectToView );
220 CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-navigation" ) ) ) );
222 // If the QML itself sets a height, use that, otherwise go to 48 pixels
223 // which seems to match what the widget navigation would use for height
224 // (with *my* specific screen, style, etc. so YMMV).
225 qreal minimumHeight = qBound( qreal( 16 ), w->rootObject() ? w->rootObject()->height() : 48, qreal( 64 ) );
226 w->setMinimumHeight( int( minimumHeight ) );
231 // Bogus to keep the linker happy
233 CalamaresWindow::getQmlSidebar( QWidget*, int )
238 CalamaresWindow::getQmlNavigation( QWidget* )
246 /**@brief Picks one of two methods to call
248 * Calls method (member function) @p widget or @p qml with arguments @p a
249 * on the given window, based on the flavor.
251 template < typename widgetMaker, typename... args >
253 flavoredWidget( Calamares::Branding::PanelFlavor flavor,
257 widgetMaker qml, // Only if WITH_QML is on
263 // Member-function calling syntax is (object.*member)(args)
266 case Calamares::Branding::PanelFlavor::Widget:
267 return ( w->*widget )( parent, a... );
269 case Calamares::Branding::PanelFlavor::Qml:
270 return ( w->*qml )( parent, a... );
272 case Calamares::Branding::PanelFlavor::None:
275 __builtin_unreachable();
278 /** @brief Adds widgets to @p layout if they belong on this @p side
281 insertIf( QBoxLayout* layout,
282 Calamares::Branding::PanelSide side,
284 Calamares::Branding::PanelSide firstSide )
286 if ( first && side == firstSide )
288 layout->addWidget( first );
292 CalamaresWindow::CalamaresWindow( QWidget* parent )
294 , m_debugWindow( nullptr )
295 , m_viewManager( nullptr )
297 // If we can never cancel, don't show the window-close button
298 if ( Calamares::Settings::instance()->disableCancel() )
300 setWindowFlag( Qt::WindowCloseButtonHint, false );
303 CALAMARES_RETRANSLATE( const auto* branding = Calamares::Branding::instance();
304 setWindowTitle( Calamares::Settings::instance()->isSetupMode()
305 ? tr( "%1 Setup Program" ).arg( branding->productName() )
306 : tr( "%1 Installer" ).arg( branding->productName() ) ); )
308 const Calamares::Branding* const branding = Calamares::Branding::instance();
309 using ImageEntry = Calamares::Branding::ImageEntry;
311 using CalamaresUtils::windowMinimumHeight;
312 using CalamaresUtils::windowMinimumWidth;
313 using CalamaresUtils::windowPreferredHeight;
314 using CalamaresUtils::windowPreferredWidth;
316 using PanelSide = Calamares::Branding::PanelSide;
318 // Needs to match what's checked in DebugWindow
319 this->setObjectName( "mainApp" );
321 QSize availableSize = qApp->desktop()->availableGeometry( this ).size();
322 QSize minimumSize( qBound( windowMinimumWidth, availableSize.width(), windowPreferredWidth ),
323 qBound( windowMinimumHeight, availableSize.height(), windowPreferredHeight ) );
324 setMinimumSize( minimumSize );
326 cDebug() << "Available desktop" << availableSize << "minimum size" << minimumSize;
328 auto brandingSizes = branding->windowSize();
330 int w = qBound( minimumSize.width(), windowDimensionToPixels( brandingSizes.first ), availableSize.width() );
331 int h = qBound( minimumSize.height(), windowDimensionToPixels( brandingSizes.second ), availableSize.height() );
333 cDebug() << Logger::SubEntry << "Proposed window size:" << w << h;
336 QWidget* baseWidget = this;
337 if ( !( branding->imagePath( ImageEntry::ProductWallpaper ).isEmpty() ) )
339 QWidget* label = new QWidget( this );
340 QVBoxLayout* l = new QVBoxLayout;
341 CalamaresUtils::unmarginLayout( l );
342 l->addWidget( label );
344 label->setObjectName( "backgroundWidget" );
345 label->setStyleSheet(
346 QStringLiteral( "#backgroundWidget { background-image: url(%1); background-repeat: repeat-xy; }" )
347 .arg( branding->imagePath( ImageEntry::ProductWallpaper ) ) );
352 m_viewManager = Calamares::ViewManager::instance( baseWidget );
353 if ( branding->windowExpands() )
355 connect( m_viewManager, &Calamares::ViewManager::ensureSize, this, &CalamaresWindow::ensureSize );
357 // NOTE: Although the ViewManager has a signal cancelEnabled() that
358 // signals when the state of the cancel button changes (in
359 // particular, to disable cancel during the exec phase),
360 // we don't connect to it here. Changing the window flag
361 // for the close button causes uncomfortable window flashing
362 // and requires an extra show() (at least with KWin/X11) which
363 // is too annoying. Instead, leave it up to ignoring-the-quit-
364 // event, which is also the ViewManager's responsibility.
366 QBoxLayout* mainLayout = new QHBoxLayout;
367 QBoxLayout* contentsLayout = new QVBoxLayout;
368 contentsLayout->setSpacing( 0 );
370 QWidget* sideBox = flavoredWidget(
371 branding->sidebarFlavor(),
374 &CalamaresWindow::getWidgetSidebar,
375 &CalamaresWindow::getQmlSidebar,
376 qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) );
377 QWidget* navigation = flavoredWidget( branding->navigationFlavor(),
380 &CalamaresWindow::getWidgetNavigation,
381 &CalamaresWindow::getQmlNavigation );
383 // Build up the contentsLayout (a VBox) top-to-bottom
384 // .. note that the bottom is mirrored wrt. the top
385 insertIf( contentsLayout, PanelSide::Top, sideBox, branding->sidebarSide() );
386 insertIf( contentsLayout, PanelSide::Top, navigation, branding->navigationSide() );
387 contentsLayout->addWidget( m_viewManager->centralWidget() );
388 insertIf( contentsLayout, PanelSide::Bottom, navigation, branding->navigationSide() );
389 insertIf( contentsLayout, PanelSide::Bottom, sideBox, branding->sidebarSide() );
391 // .. and then the mainLayout left-to-right
392 insertIf( mainLayout, PanelSide::Left, sideBox, branding->sidebarSide() );
393 insertIf( mainLayout, PanelSide::Left, navigation, branding->navigationSide() );
394 mainLayout->addLayout( contentsLayout );
395 insertIf( mainLayout, PanelSide::Right, navigation, branding->navigationSide() );
396 insertIf( mainLayout, PanelSide::Right, sideBox, branding->sidebarSide() );
398 // layout->count() returns number of things in it; above we have put
399 // at **least** the central widget, which comes from the view manager,
400 // both vertically and horizontally -- so if there's a panel along
401 // either axis, the count in that axis will be > 1.
402 m_viewManager->setPanelSides(
403 ( contentsLayout->count() > 1 ? Qt::Orientations( Qt::Horizontal ) : Qt::Orientations() )
404 | ( mainLayout->count() > 1 ? Qt::Orientations( Qt::Vertical ) : Qt::Orientations() ) );
406 CalamaresUtils::unmarginLayout( mainLayout );
407 CalamaresUtils::unmarginLayout( contentsLayout );
408 baseWidget->setLayout( mainLayout );
409 setStyleSheet( Calamares::Branding::instance()->stylesheet() );
413 CalamaresWindow::ensureSize( QSize size )
415 auto mainGeometry = this->geometry();
416 QSize availableSize = qApp->desktop()->availableGeometry( this ).size();
418 // We only care about vertical sizes that are big enough
419 int embiggenment = qMax( 0, size.height() - m_viewManager->centralWidget()->size().height() );
420 if ( embiggenment < 6 )
425 auto h = qBound( 0, mainGeometry.height() + embiggenment, availableSize.height() );
426 auto w = this->size().width();
432 CalamaresWindow::closeEvent( QCloseEvent* event )
434 if ( ( !m_viewManager ) || m_viewManager->confirmCancelInstallation() )