OSDN Git Service

a141317e011209a25fb2454baf89ae5c93a6e077
[alterlinux/alterlinux-calamares.git] / src / calamares / CalamaresWindow.cpp
1 /* === This file is part of Calamares - <https://calamares.io> ===
2  *
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-License-Identifier: GPL-3.0-or-later
8  *
9  *   Calamares is Free Software: see the License-Identifier above.
10  *
11  */
12
13 #include "CalamaresWindow.h"
14
15 #include "Branding.h"
16 #include "CalamaresConfig.h"
17 #include "DebugWindow.h"
18 #include "Settings.h"
19 #include "ViewManager.h"
20 #include "progresstree/ProgressTreeView.h"
21 #include "utils/CalamaresUtilsGui.h"
22 #include "utils/Logger.h"
23 #include "utils/Qml.h"
24 #include "utils/Retranslator.h"
25
26 #include <QApplication>
27 #include <QBoxLayout>
28 #include <QCloseEvent>
29 #include <QDesktopWidget>
30 #include <QFile>
31 #include <QFileInfo>
32 #include <QLabel>
33 #ifdef WITH_QML
34 #include <QQuickItem>
35 #include <QQuickWidget>
36 #endif
37 #include <QTreeView>
38
39 static inline int
40 windowDimensionToPixels( const Calamares::Branding::WindowDimension& u )
41 {
42     if ( !u.isValid() )
43     {
44         return 0;
45     }
46     if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Pixies )
47     {
48         return static_cast< int >( u.value() );
49     }
50     if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Fonties )
51     {
52         return static_cast< int >( u.value() * CalamaresUtils::defaultFontHeight() );
53     }
54     return 0;
55 }
56
57
58 QWidget*
59 CalamaresWindow::getWidgetSidebar( QWidget* parent, int desiredWidth )
60 {
61     const Calamares::Branding* const branding = Calamares::Branding::instance();
62
63     QWidget* sideBox = new QWidget( parent );
64     sideBox->setObjectName( "sidebarApp" );
65
66     QBoxLayout* sideLayout = new QVBoxLayout;
67     sideBox->setLayout( sideLayout );
68     // Set this attribute into qss file
69     sideBox->setFixedWidth( desiredWidth );
70     sideBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
71
72     QHBoxLayout* logoLayout = new QHBoxLayout;
73     sideLayout->addLayout( logoLayout );
74     logoLayout->addStretch();
75     QLabel* logoLabel = new QLabel( sideBox );
76     logoLabel->setObjectName( "logoApp" );
77     //Define all values into qss file
78     {
79         QPalette plt = sideBox->palette();
80         sideBox->setAutoFillBackground( true );
81         plt.setColor( sideBox->backgroundRole(), branding->styleString( Calamares::Branding::SidebarBackground ) );
82         plt.setColor( sideBox->foregroundRole(), branding->styleString( Calamares::Branding::SidebarText ) );
83         sideBox->setPalette( plt );
84         logoLabel->setPalette( plt );
85     }
86     logoLabel->setAlignment( Qt::AlignCenter );
87     logoLabel->setFixedSize( 80, 80 );
88     logoLabel->setPixmap( branding->image( Calamares::Branding::ProductLogo, logoLabel->size() ) );
89     logoLayout->addWidget( logoLabel );
90     logoLayout->addStretch();
91
92     ProgressTreeView* tv = new ProgressTreeView( sideBox );
93     tv->setModel( Calamares::ViewManager::instance() );
94     tv->setFocusPolicy( Qt::NoFocus );
95     sideLayout->addWidget( tv );
96
97     if ( Calamares::Settings::instance()->debugMode() || ( Logger::logLevel() >= Logger::LOGVERBOSE ) )
98     {
99         QPushButton* debugWindowBtn = new QPushButton;
100         debugWindowBtn->setObjectName( "debugButton" );
101         CALAMARES_RETRANSLATE( debugWindowBtn->setText( tr( "Show debug information" ) ); )
102         sideLayout->addWidget( debugWindowBtn );
103         debugWindowBtn->setFlat( true );
104         debugWindowBtn->setCheckable( true );
105         connect( debugWindowBtn, &QPushButton::clicked, this, [=]( bool checked ) {
106             if ( checked )
107             {
108                 m_debugWindow = new Calamares::DebugWindow();
109                 m_debugWindow->show();
110                 connect( m_debugWindow.data(), &Calamares::DebugWindow::closed, this, [=]() {
111                     m_debugWindow->deleteLater();
112                     debugWindowBtn->setChecked( false );
113                 } );
114             }
115             else
116             {
117                 if ( m_debugWindow )
118                 {
119                     m_debugWindow->deleteLater();
120                 }
121             }
122         } );
123     }
124
125     CalamaresUtils::unmarginLayout( sideLayout );
126     return sideBox;
127 }
128
129 /** @brief Get a button-sized icon. */
130 static inline QPixmap
131 getButtonIcon( const QString& name )
132 {
133     return Calamares::Branding::instance()->image( name, QSize( 22, 22 ) );
134 }
135
136 static inline void
137 setButtonIcon( QPushButton* button, const QString& name )
138 {
139     auto icon = getButtonIcon( name );
140     if ( button && !icon.isNull() )
141     {
142         button->setIcon( icon );
143     }
144 }
145
146 QWidget*
147 CalamaresWindow::getWidgetNavigation( QWidget* parent )
148 {
149     QWidget* navigation = new QWidget( parent );
150     QBoxLayout* bottomLayout = new QHBoxLayout;
151     bottomLayout->addStretch();
152
153     // Create buttons and sets an initial icon; the icons may change
154     {
155         auto* back = new QPushButton( getButtonIcon( QStringLiteral( "go-previous" ) ), tr( "&Back" ), navigation );
156         back->setObjectName( "view-button-back" );
157         back->setEnabled( m_viewManager->backEnabled() );
158         connect( back, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::back );
159         connect( m_viewManager, &Calamares::ViewManager::backEnabledChanged, back, &QPushButton::setEnabled );
160         connect( m_viewManager, &Calamares::ViewManager::backLabelChanged, back, &QPushButton::setText );
161         connect( m_viewManager, &Calamares::ViewManager::backIconChanged, this, [=]( QString n ) {
162             setButtonIcon( back, n );
163         } );
164         bottomLayout->addWidget( back );
165     }
166     {
167         auto* next = new QPushButton( getButtonIcon( QStringLiteral( "go-next" ) ), tr( "&Next" ), navigation );
168         next->setObjectName( "view-button-next" );
169         next->setEnabled( m_viewManager->nextEnabled() );
170         connect( next, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::next );
171         connect( m_viewManager, &Calamares::ViewManager::nextEnabledChanged, next, &QPushButton::setEnabled );
172         connect( m_viewManager, &Calamares::ViewManager::nextLabelChanged, next, &QPushButton::setText );
173         connect( m_viewManager, &Calamares::ViewManager::nextIconChanged, this, [=]( QString n ) {
174             setButtonIcon( next, n );
175         } );
176         bottomLayout->addWidget( next );
177     }
178     bottomLayout->addSpacing( 12 );
179     {
180         auto* quit = new QPushButton( getButtonIcon( QStringLiteral( "dialog-cancel" ) ), tr( "&Cancel" ), navigation );
181         quit->setObjectName( "view-button-cancel" );
182         connect( quit, &QPushButton::clicked, m_viewManager, &Calamares::ViewManager::quit );
183         connect( m_viewManager, &Calamares::ViewManager::quitEnabledChanged, quit, &QPushButton::setEnabled );
184         connect( m_viewManager, &Calamares::ViewManager::quitLabelChanged, quit, &QPushButton::setText );
185         connect( m_viewManager, &Calamares::ViewManager::quitIconChanged, this, [=]( QString n ) {
186             setButtonIcon( quit, n );
187         } );
188         connect( m_viewManager, &Calamares::ViewManager::quitTooltipChanged, quit, &QPushButton::setToolTip );
189         connect( m_viewManager, &Calamares::ViewManager::quitVisibleChanged, quit, &QPushButton::setVisible );
190         bottomLayout->addWidget( quit );
191     }
192
193     bottomLayout->setContentsMargins( 0, 0, 6, 6 );
194     navigation->setLayout( bottomLayout );
195     return navigation;
196 }
197
198 #ifdef WITH_QML
199 QWidget*
200 CalamaresWindow::getQmlSidebar( QWidget* parent, int )
201 {
202     CalamaresUtils::registerQmlModels();
203     QQuickWidget* w = new QQuickWidget( parent );
204     w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
205     w->setResizeMode( QQuickWidget::SizeRootObjectToView );
206     w->setSource( QUrl(
207         CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-sidebar" ) ) ) );
208     return w;
209 }
210
211 QWidget*
212 CalamaresWindow::getQmlNavigation( QWidget* parent )
213 {
214     CalamaresUtils::registerQmlModels();
215     QQuickWidget* w = new QQuickWidget( parent );
216     w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
217     w->setResizeMode( QQuickWidget::SizeRootObjectToView );
218     w->setSource( QUrl(
219         CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-navigation" ) ) ) );
220
221     // If the QML itself sets a height, use that, otherwise go to 48 pixels
222     // which seems to match what the widget navigation would use for height
223     // (with *my* specific screen, style, etc. so YMMV).
224     qreal minimumHeight = qBound( qreal( 16 ), w->rootObject() ? w->rootObject()->height() : 48, qreal( 64 ) );
225     w->setMinimumHeight( int( minimumHeight ) );
226
227     return w;
228 }
229 #else
230 // Bogus to keep the linker happy
231 QWidget*
232 CalamaresWindow::getQmlSidebar( QWidget*, int )
233 {
234     return nullptr;
235 }
236 QWidget*
237 CalamaresWindow::getQmlNavigation( QWidget* )
238 {
239     return nullptr;
240 }
241
242
243 #endif
244
245 /**@brief Picks one of two methods to call
246  *
247  * Calls method (member function) @p widget or @p qml with arguments @p a
248  * on the given window, based on the flavor.
249  */
250 template < typename widgetMaker, typename... args >
251 QWidget*
252 flavoredWidget( Calamares::Branding::PanelFlavor flavor,
253                 CalamaresWindow* w,
254                 QWidget* parent,
255                 widgetMaker widget,
256                 widgetMaker qml,  // Only if WITH_QML is on
257                 args... a )
258 {
259 #ifndef WITH_QML
260     Q_UNUSED( qml )
261 #endif
262     // Member-function calling syntax is (object.*member)(args)
263     switch ( flavor )
264     {
265     case Calamares::Branding::PanelFlavor::Widget:
266         return ( w->*widget )( parent, a... );
267 #ifdef WITH_QML
268     case Calamares::Branding::PanelFlavor::Qml:
269         return ( w->*qml )( parent, a... );
270 #endif
271     case Calamares::Branding::PanelFlavor::None:
272         return nullptr;
273     }
274     __builtin_unreachable();
275 }
276
277 /** @brief Adds widgets to @p layout if they belong on this @p side
278  */
279 static inline void
280 insertIf( QBoxLayout* layout,
281           Calamares::Branding::PanelSide side,
282           QWidget* first,
283           Calamares::Branding::PanelSide firstSide )
284 {
285     if ( first && side == firstSide )
286     {
287         layout->addWidget( first );
288     }
289 }
290
291 CalamaresWindow::CalamaresWindow( QWidget* parent )
292     : QWidget( parent )
293     , m_debugWindow( nullptr )
294     , m_viewManager( nullptr )
295 {
296     // If we can never cancel, don't show the window-close button
297     if ( Calamares::Settings::instance()->disableCancel() )
298     {
299         setWindowFlag( Qt::WindowCloseButtonHint, false );
300     }
301
302     CALAMARES_RETRANSLATE( const auto* branding = Calamares::Branding::instance();
303                            setWindowTitle( Calamares::Settings::instance()->isSetupMode()
304                                                ? tr( "%1 Setup Program" ).arg( branding->productName() )
305                                                : tr( "%1 Installer" ).arg( branding->productName() ) ); )
306
307     const Calamares::Branding* const branding = Calamares::Branding::instance();
308     using ImageEntry = Calamares::Branding::ImageEntry;
309
310     using CalamaresUtils::windowMinimumHeight;
311     using CalamaresUtils::windowMinimumWidth;
312     using CalamaresUtils::windowPreferredHeight;
313     using CalamaresUtils::windowPreferredWidth;
314
315     using PanelSide = Calamares::Branding::PanelSide;
316
317     // Needs to match what's checked in DebugWindow
318     this->setObjectName( "mainApp" );
319
320     QSize availableSize = qApp->desktop()->availableGeometry( this ).size();
321     QSize minimumSize( qBound( windowMinimumWidth, availableSize.width(), windowPreferredWidth ),
322                        qBound( windowMinimumHeight, availableSize.height(), windowPreferredHeight ) );
323     setMinimumSize( minimumSize );
324
325     cDebug() << "Available desktop" << availableSize << "minimum size" << minimumSize;
326
327     auto brandingSizes = branding->windowSize();
328
329     int w = qBound( minimumSize.width(), windowDimensionToPixels( brandingSizes.first ), availableSize.width() );
330     int h = qBound( minimumSize.height(), windowDimensionToPixels( brandingSizes.second ), availableSize.height() );
331
332     cDebug() << Logger::SubEntry << "Proposed window size:" << w << h;
333     resize( w, h );
334
335     QWidget* baseWidget = this;
336     if ( !( branding->imagePath( ImageEntry::ProductWallpaper ).isEmpty() ) )
337     {
338         QWidget* label = new QWidget( this );
339         QVBoxLayout* l = new QVBoxLayout;
340         CalamaresUtils::unmarginLayout( l );
341         l->addWidget( label );
342         setLayout( l );
343         label->setObjectName( "backgroundWidget" );
344         label->setStyleSheet(
345             QStringLiteral( "#backgroundWidget { background-image: url(%1); background-repeat: repeat-xy; }" )
346                 .arg( branding->imagePath( ImageEntry::ProductWallpaper ) ) );
347
348         baseWidget = label;
349     }
350
351     m_viewManager = Calamares::ViewManager::instance( baseWidget );
352     if ( branding->windowExpands() )
353     {
354         connect( m_viewManager, &Calamares::ViewManager::ensureSize, this, &CalamaresWindow::ensureSize );
355     }
356     // NOTE: Although the ViewManager has a signal cancelEnabled() that
357     //       signals when the state of the cancel button changes (in
358     //       particular, to disable cancel during the exec phase),
359     //       we don't connect to it here. Changing the window flag
360     //       for the close button causes uncomfortable window flashing
361     //       and requires an extra show() (at least with KWin/X11) which
362     //       is too annoying. Instead, leave it up to ignoring-the-quit-
363     //       event, which is also the ViewManager's responsibility.
364
365     QBoxLayout* mainLayout = new QHBoxLayout;
366     QBoxLayout* contentsLayout = new QVBoxLayout;
367     contentsLayout->setSpacing( 0 );
368
369     QWidget* sideBox = flavoredWidget(
370         branding->sidebarFlavor(),
371         this,
372         baseWidget,
373         &CalamaresWindow::getWidgetSidebar,
374         &CalamaresWindow::getQmlSidebar,
375         qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) );
376     QWidget* navigation = flavoredWidget( branding->navigationFlavor(),
377                                           this,
378                                           baseWidget,
379                                           &CalamaresWindow::getWidgetNavigation,
380                                           &CalamaresWindow::getQmlNavigation );
381
382     // Build up the contentsLayout (a VBox) top-to-bottom
383     // .. note that the bottom is mirrored wrt. the top
384     insertIf( contentsLayout, PanelSide::Top, sideBox, branding->sidebarSide() );
385     insertIf( contentsLayout, PanelSide::Top, navigation, branding->navigationSide() );
386     contentsLayout->addWidget( m_viewManager->centralWidget() );
387     insertIf( contentsLayout, PanelSide::Bottom, navigation, branding->navigationSide() );
388     insertIf( contentsLayout, PanelSide::Bottom, sideBox, branding->sidebarSide() );
389
390     // .. and then the mainLayout left-to-right
391     insertIf( mainLayout, PanelSide::Left, sideBox, branding->sidebarSide() );
392     insertIf( mainLayout, PanelSide::Left, navigation, branding->navigationSide() );
393     mainLayout->addLayout( contentsLayout );
394     insertIf( mainLayout, PanelSide::Right, navigation, branding->navigationSide() );
395     insertIf( mainLayout, PanelSide::Right, sideBox, branding->sidebarSide() );
396
397     // layout->count() returns number of things in it; above we have put
398     // at **least** the central widget, which comes from the view manager,
399     // both vertically and horizontally -- so if there's a panel along
400     // either axis, the count in that axis will be > 1.
401     m_viewManager->setPanelSides(
402         ( contentsLayout->count() > 1 ? Qt::Orientations( Qt::Horizontal ) : Qt::Orientations() )
403         | ( mainLayout->count() > 1 ? Qt::Orientations( Qt::Vertical ) : Qt::Orientations() ) );
404
405     CalamaresUtils::unmarginLayout( mainLayout );
406     CalamaresUtils::unmarginLayout( contentsLayout );
407     baseWidget->setLayout( mainLayout );
408     setStyleSheet( Calamares::Branding::instance()->stylesheet() );
409 }
410
411 void
412 CalamaresWindow::ensureSize( QSize size )
413 {
414     auto mainGeometry = this->geometry();
415     QSize availableSize = qApp->desktop()->availableGeometry( this ).size();
416
417     // We only care about vertical sizes that are big enough
418     int embiggenment = qMax( 0, size.height() - m_viewManager->centralWidget()->size().height() );
419     if ( embiggenment < 6 )
420     {
421         return;
422     }
423
424     auto h = qBound( 0, mainGeometry.height() + embiggenment, availableSize.height() );
425     auto w = this->size().width();
426
427     resize( w, h );
428 }
429
430 void
431 CalamaresWindow::closeEvent( QCloseEvent* event )
432 {
433     if ( ( !m_viewManager ) || m_viewManager->confirmCancelInstallation() )
434     {
435         event->accept();
436         qApp->quit();
437     }
438     else
439     {
440         event->ignore();
441     }
442 }