OSDN Git Service

Name added in copyright section
[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-FileCopyrightText: 2020 Anubhav Choudhary <ac.10edu@gmail.com>
8  *   SPDX-License-Identifier: GPL-3.0-or-later
9  *
10  *   Calamares is Free Software: see the License-Identifier above.
11  *
12  */
13
14 #include "CalamaresWindow.h"
15
16 #include "Branding.h"
17 #include "CalamaresConfig.h"
18 #include "DebugWindow.h"
19 #include "Settings.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"
26
27 #include <QApplication>
28 #include <QBoxLayout>
29 #include <QCloseEvent>
30 #include <QDesktopWidget>
31 #include <QFile>
32 #include <QFileInfo>
33 #include <QLabel>
34 #ifdef WITH_QML
35 #include <QQuickItem>
36 #include <QQuickWidget>
37 #endif
38 #include <QTreeView>
39
40 static inline int
41 windowDimensionToPixels( const Calamares::Branding::WindowDimension& u )
42 {
43     if ( !u.isValid() )
44     {
45         return 0;
46     }
47     if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Pixies )
48     {
49         return static_cast< int >( u.value() );
50     }
51     if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Fonties )
52     {
53         return static_cast< int >( u.value() * CalamaresUtils::defaultFontHeight() );
54     }
55     return 0;
56 }
57
58
59 QWidget*
60 CalamaresWindow::getWidgetSidebar( QWidget* parent, int desiredWidth )
61 {
62     const Calamares::Branding* const branding = Calamares::Branding::instance();
63
64     QWidget* sideBox = new QWidget( parent );
65     sideBox->setObjectName( "sidebarApp" );
66
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 );
72
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
79     {
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 );
86     }
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();
92
93     ProgressTreeView* tv = new ProgressTreeView( sideBox );
94     tv->setModel( Calamares::ViewManager::instance() );
95     tv->setFocusPolicy( Qt::NoFocus );
96     sideLayout->addWidget( tv );
97
98     if ( Calamares::Settings::instance()->debugMode() || ( Logger::logLevel() >= Logger::LOGVERBOSE ) )
99     {
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 ) {
107             if ( checked )
108             {
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 );
114                 } );
115             }
116             else
117             {
118                 if ( m_debugWindow )
119                 {
120                     m_debugWindow->deleteLater();
121                 }
122             }
123         } );
124     }
125
126     CalamaresUtils::unmarginLayout( sideLayout );
127     return sideBox;
128 }
129
130 /** @brief Get a button-sized icon. */
131 static inline QPixmap
132 getButtonIcon( const QString& name )
133 {
134     return Calamares::Branding::instance()->image( name, QSize( 22, 22 ) );
135 }
136
137 static inline void
138 setButtonIcon( QPushButton* button, const QString& name )
139 {
140     auto icon = getButtonIcon( name );
141     if ( button && !icon.isNull() )
142     {
143         button->setIcon( icon );
144     }
145 }
146
147 QWidget*
148 CalamaresWindow::getWidgetNavigation( QWidget* parent )
149 {
150     QWidget* navigation = new QWidget( parent );
151     QBoxLayout* bottomLayout = new QHBoxLayout;
152     bottomLayout->addStretch();
153
154     // Create buttons and sets an initial icon; the icons may change
155     {
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 );
164         } );
165         bottomLayout->addWidget( back );
166     }
167     {
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 );
176         } );
177         bottomLayout->addWidget( next );
178     }
179     bottomLayout->addSpacing( 12 );
180     {
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 );
188         } );
189         connect( m_viewManager, &Calamares::ViewManager::quitTooltipChanged, quit, &QPushButton::setToolTip );
190         connect( m_viewManager, &Calamares::ViewManager::quitVisibleChanged, quit, &QPushButton::setVisible );
191         bottomLayout->addWidget( quit );
192     }
193
194     bottomLayout->setContentsMargins( 0, 0, 6, 6 );
195     navigation->setLayout( bottomLayout );
196     return navigation;
197 }
198
199 #ifdef WITH_QML
200 QWidget*
201 CalamaresWindow::getQmlSidebar( QWidget* parent, int )
202 {
203     CalamaresUtils::registerQmlModels();
204     QQuickWidget* w = new QQuickWidget( parent );
205     w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
206     w->setResizeMode( QQuickWidget::SizeRootObjectToView );
207     w->setSource( QUrl(
208         CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-sidebar" ) ) ) );
209     return w;
210 }
211
212 QWidget*
213 CalamaresWindow::getQmlNavigation( QWidget* parent )
214 {
215     CalamaresUtils::registerQmlModels();
216     QQuickWidget* w = new QQuickWidget( parent );
217     w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
218     w->setResizeMode( QQuickWidget::SizeRootObjectToView );
219     w->setSource( QUrl(
220         CalamaresUtils::searchQmlFile( CalamaresUtils::QmlSearch::Both, QStringLiteral( "calamares-navigation" ) ) ) );
221
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 ) );
227
228     return w;
229 }
230 #else
231 // Bogus to keep the linker happy
232 QWidget*
233 CalamaresWindow::getQmlSidebar( QWidget*, int )
234 {
235     return nullptr;
236 }
237 QWidget*
238 CalamaresWindow::getQmlNavigation( QWidget* )
239 {
240     return nullptr;
241 }
242
243
244 #endif
245
246 /**@brief Picks one of two methods to call
247  *
248  * Calls method (member function) @p widget or @p qml with arguments @p a
249  * on the given window, based on the flavor.
250  */
251 template < typename widgetMaker, typename... args >
252 QWidget*
253 flavoredWidget( Calamares::Branding::PanelFlavor flavor,
254                 CalamaresWindow* w,
255                 QWidget* parent,
256                 widgetMaker widget,
257                 widgetMaker qml,  // Only if WITH_QML is on
258                 args... a )
259 {
260 #ifndef WITH_QML
261     Q_UNUSED( qml )
262 #endif
263     // Member-function calling syntax is (object.*member)(args)
264     switch ( flavor )
265     {
266     case Calamares::Branding::PanelFlavor::Widget:
267         return ( w->*widget )( parent, a... );
268 #ifdef WITH_QML
269     case Calamares::Branding::PanelFlavor::Qml:
270         return ( w->*qml )( parent, a... );
271 #endif
272     case Calamares::Branding::PanelFlavor::None:
273         return nullptr;
274     }
275     __builtin_unreachable();
276 }
277
278 /** @brief Adds widgets to @p layout if they belong on this @p side
279  */
280 static inline void
281 insertIf( QBoxLayout* layout,
282           Calamares::Branding::PanelSide side,
283           QWidget* first,
284           Calamares::Branding::PanelSide firstSide )
285 {
286     if ( first && side == firstSide )
287     {
288         layout->addWidget( first );
289     }
290 }
291
292 CalamaresWindow::CalamaresWindow( QWidget* parent )
293     : QWidget( parent )
294     , m_debugWindow( nullptr )
295     , m_viewManager( nullptr )
296 {
297     // If we can never cancel, don't show the window-close button
298     if ( Calamares::Settings::instance()->disableCancel() )
299     {
300         setWindowFlag( Qt::WindowCloseButtonHint, false );
301     }
302
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() ) ); )
307
308     const Calamares::Branding* const branding = Calamares::Branding::instance();
309     using ImageEntry = Calamares::Branding::ImageEntry;
310
311     using CalamaresUtils::windowMinimumHeight;
312     using CalamaresUtils::windowMinimumWidth;
313     using CalamaresUtils::windowPreferredHeight;
314     using CalamaresUtils::windowPreferredWidth;
315
316     using PanelSide = Calamares::Branding::PanelSide;
317
318     // Needs to match what's checked in DebugWindow
319     this->setObjectName( "mainApp" );
320
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 );
325
326     cDebug() << "Available desktop" << availableSize << "minimum size" << minimumSize;
327
328     auto brandingSizes = branding->windowSize();
329
330     int w = qBound( minimumSize.width(), windowDimensionToPixels( brandingSizes.first ), availableSize.width() );
331     int h = qBound( minimumSize.height(), windowDimensionToPixels( brandingSizes.second ), availableSize.height() );
332
333     cDebug() << Logger::SubEntry << "Proposed window size:" << w << h;
334     resize( w, h );
335
336     QWidget* baseWidget = this;
337     if ( !( branding->imagePath( ImageEntry::ProductWallpaper ).isEmpty() ) )
338     {
339         QWidget* label = new QWidget( this );
340         QVBoxLayout* l = new QVBoxLayout;
341         CalamaresUtils::unmarginLayout( l );
342         l->addWidget( label );
343         setLayout( l );
344         label->setObjectName( "backgroundWidget" );
345         label->setStyleSheet(
346             QStringLiteral( "#backgroundWidget { background-image: url(%1); background-repeat: repeat-xy; }" )
347                 .arg( branding->imagePath( ImageEntry::ProductWallpaper ) ) );
348
349         baseWidget = label;
350     }
351
352     m_viewManager = Calamares::ViewManager::instance( baseWidget );
353     if ( branding->windowExpands() )
354     {
355         connect( m_viewManager, &Calamares::ViewManager::ensureSize, this, &CalamaresWindow::ensureSize );
356     }
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.
365
366     QBoxLayout* mainLayout = new QHBoxLayout;
367     QBoxLayout* contentsLayout = new QVBoxLayout;
368     contentsLayout->setSpacing( 0 );
369
370     QWidget* sideBox = flavoredWidget(
371         branding->sidebarFlavor(),
372         this,
373         baseWidget,
374         &CalamaresWindow::getWidgetSidebar,
375         &CalamaresWindow::getQmlSidebar,
376         qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) );
377     QWidget* navigation = flavoredWidget( branding->navigationFlavor(),
378                                           this,
379                                           baseWidget,
380                                           &CalamaresWindow::getWidgetNavigation,
381                                           &CalamaresWindow::getQmlNavigation );
382
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() );
390
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() );
397
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() ) );
405
406     CalamaresUtils::unmarginLayout( mainLayout );
407     CalamaresUtils::unmarginLayout( contentsLayout );
408     baseWidget->setLayout( mainLayout );
409     setStyleSheet( Calamares::Branding::instance()->stylesheet() );
410 }
411
412 void
413 CalamaresWindow::ensureSize( QSize size )
414 {
415     auto mainGeometry = this->geometry();
416     QSize availableSize = qApp->desktop()->availableGeometry( this ).size();
417
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 )
421     {
422         return;
423     }
424
425     auto h = qBound( 0, mainGeometry.height() + embiggenment, availableSize.height() );
426     auto w = this->size().width();
427
428     resize( w, h );
429 }
430
431 void
432 CalamaresWindow::closeEvent( QCloseEvent* event )
433 {
434     if ( ( !m_viewManager ) || m_viewManager->confirmCancelInstallation() )
435     {
436         event->accept();
437         qApp->quit();
438     }
439     else
440     {
441         event->ignore();
442     }
443 }