diff --git a/CMakeLists.txt b/CMakeLists.txt index 48488fcae..0c594d65a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ include( MacroLogFeature ) set( QT_VERSION 5.3.0 ) -find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED Core Gui Widgets LinguistTools Svg ) +find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED Core Gui Widgets LinguistTools Svg Quick QuickWidgets ) find_package( YamlCpp 0.5.1 REQUIRED ) find_package( PolkitQt5-1 REQUIRED ) diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index d55e8ce6d..d6913c1e9 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -14,5 +14,4 @@ images: productLogo: "squid.png" productIcon: "squid.png" -slideshow: - - "squid.png" +slideshow: "show.qml" diff --git a/src/branding/default/show.qml b/src/branding/default/show.qml new file mode 100644 index 000000000..70ac54c31 --- /dev/null +++ b/src/branding/default/show.qml @@ -0,0 +1,13 @@ +import QtQuick 2.0; +import slideshow 1.0; + +Presentation +{ + id: presentation + width: 1280 + height: 720 + + Slide { + centeredText: "Calamares is really nice" + } +} diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index f8f649d23..997bb59b4 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -116,20 +116,37 @@ Branding::Branding( const QString& brandingFilePath, m_images.insert( it.key(), imageFi.absoluteFilePath() ); } - if ( !doc[ "slideshow" ].IsSequence() ) + if ( doc[ "slideshow" ].IsSequence() ) + { + QStringList slideShowPictures; + doc[ "slideshow" ] >> slideShowPictures; + for ( int i = 0; i < slideShowPictures.count(); ++i ) + { + QString pathString = slideShowPictures[ i ]; + QFileInfo imageFi( componentDir.absoluteFilePath( pathString ) ); + if ( !imageFi.exists() ) + bail( QString( "Slideshow file %1 does not exist." ) + .arg( imageFi.absoluteFilePath() ) ); + + slideShowPictures[ i ] = imageFi.absoluteFilePath(); + } + + //FIXME: implement a GenericSlideShow.qml that uses these slideShowPictures + } + else if ( doc[ "slideshow" ].IsScalar() ) + { + QString slideshowPath = QString::fromStdString( doc[ "slideshow" ] + .as< std::string >() ); + QFileInfo slideshowFi( componentDir.absoluteFilePath( slideshowPath ) ); + if ( !slideshowFi.exists() || + !slideshowFi.fileName().toLower().endsWith( ".qml" ) ) + bail( QString( "Slideshow file %1 does not exist or is not a valid QML file." ) + .arg( slideshowFi.absoluteFilePath() ) ); + m_slideshowPath = slideshowFi.absoluteFilePath(); + } + else bail( "Syntax error in slideshow sequence." ); - doc[ "slideshow" ] >> m_slideshow; - for ( int i = 0; i < m_slideshow.count(); ++i ) - { - QString pathString = m_slideshow[ i ]; - QFileInfo imageFi( componentDir.absoluteFilePath( pathString ) ); - if ( !imageFi.exists() ) - bail( QString( "Slideshow file %1 does not exist." ) - .arg( imageFi.absoluteFilePath() ) ); - - m_slideshow[ i ] = imageFi.absoluteFilePath(); - } } catch ( YAML::Exception& e ) { @@ -197,10 +214,10 @@ Branding::image( Branding::ImageEntry imageEntry, const QSize& size ) const } -QStringList -Branding::slideshowPaths() const +QString +Branding::slideshowPath() const { - return m_slideshow; + return m_slideshowPath; } diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index 031cdd051..87b836646 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -65,7 +65,7 @@ public: QString string( Branding::StringEntry stringEntry ) const; QString imagePath( Branding::ImageEntry imageEntry ) const; QPixmap image( Branding::ImageEntry imageEntry, const QSize& size ) const; - QStringList slideshowPaths() const; + QString slideshowPath() const; /** * Creates a map called "branding" in the global storage, and inserts an @@ -86,7 +86,7 @@ private: QString m_componentName; QMap< QString, QString > m_strings; QMap< QString, QString > m_images; - QStringList m_slideshow; + QString m_slideshowPath; }; } diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index a78d52bd8..9c8721861 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -40,7 +40,10 @@ calamares_add_library( ${CALAMARESUI_LIBRARY_TARGET} LINK_LIBRARIES yaml-cpp Qt5::Svg + Qt5::QuickWidgets RESOURCES libcalamaresui.qrc EXPORT CalamaresLibraryDepends VERSION ${CALAMARES_VERSION_SHORT} ) + +add_subdirectory( slideshow ) diff --git a/src/libcalamaresui/InstallationViewStep.cpp b/src/libcalamaresui/InstallationViewStep.cpp index f1eecd483..80fa88d20 100644 --- a/src/libcalamaresui/InstallationViewStep.cpp +++ b/src/libcalamaresui/InstallationViewStep.cpp @@ -18,11 +18,18 @@ #include -#include +#include "JobQueue.h" +#include "Branding.h" +#include "utils/CalamaresUtilsGui.h" +#include "utils/Logger.h" +#include "Settings.h" +#include #include #include #include +#include +#include namespace Calamares { @@ -35,10 +42,83 @@ InstallationViewStep::InstallationViewStep( QObject* parent ) m_progressBar->setMaximum( 10000 ); m_label = new QLabel; QVBoxLayout* layout = new QVBoxLayout( m_widget ); - layout->addWidget(m_progressBar); - layout->addWidget(m_label); + QVBoxLayout* innerLayout = new QVBoxLayout; + + m_slideShow = new QQuickWidget; + layout->addWidget( m_slideShow ); + CalamaresUtils::unmarginLayout( layout ); + + layout->addLayout( innerLayout ); + m_slideShow->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + m_slideShow->setResizeMode( QQuickWidget::SizeRootObjectToView ); + + QDir importPath; + { + QString subpath( "slideshow" ); + + if ( CalamaresUtils::isAppDataDirOverridden() ) + { + importPath = QDir( CalamaresUtils::appDataDir() + .absoluteFilePath( subpath ) ); + if ( !importPath.exists() || !importPath.isReadable() ) + { + cLog() << "FATAL ERROR: explicitly configured application data directory" + << CalamaresUtils::appDataDir().absolutePath() + << "does not contain a valid slideshow directory at" + << importPath.absolutePath() + << "\nCowardly refusing to continue startup without slideshow."; + ::exit( EXIT_FAILURE ); + } + } + else + { + QStringList slideshowDirCandidatesByPriority; + if ( Calamares::Settings::instance()->debugMode() ) + { + slideshowDirCandidatesByPriority.append( + QDir::current().absoluteFilePath( + QString( "src/libcalamaresui/%1" ) + .arg( subpath ) ) ); + } + slideshowDirCandidatesByPriority.append( CalamaresUtils::appDataDir() + .absoluteFilePath( subpath ) ); + + foreach ( const QString& path, slideshowDirCandidatesByPriority ) + { + QDir dir( path ); + if ( dir.exists() && dir.isReadable() ) + { + importPath = dir; + break; + } + } + + if ( !importPath.exists() || !importPath.isReadable() ) + { + cLog() << "FATAL ERROR: none of the expected slideshow paths (" + << slideshowDirCandidatesByPriority.join( ", " ) + << ") exist." + << "\nCowardly refusing to continue startup without slideshow."; + ::exit( EXIT_FAILURE ); + } + } + } + cDebug() << "importPath:" << importPath; + importPath.cdUp(); + + m_slideShow->engine()->addImportPath( importPath.absolutePath() ); + m_slideShow->setSource( QUrl::fromLocalFile( Calamares::Branding::instance() + ->slideshowPath() ) ); + + innerLayout->addSpacing( CalamaresUtils::defaultFontHeight() / 2 ); + innerLayout->addWidget( m_progressBar ); + innerLayout->addWidget( m_label ); + + connect( JobQueue::instance(), &JobQueue::progress, + this, &InstallationViewStep::updateFromJobQueue ); + + cDebug() << "importPathList:" << m_slideShow->engine()->importPathList(); - connect( JobQueue::instance(), &JobQueue::progress, this, &InstallationViewStep::updateFromJobQueue ); } QString diff --git a/src/libcalamaresui/InstallationViewStep.h b/src/libcalamaresui/InstallationViewStep.h index c482b5222..2ba38c167 100644 --- a/src/libcalamaresui/InstallationViewStep.h +++ b/src/libcalamaresui/InstallationViewStep.h @@ -23,6 +23,7 @@ class QLabel; class QProgressBar; +class QQuickWidget; namespace Calamares { @@ -51,6 +52,7 @@ private: QWidget* m_widget; QProgressBar* m_progressBar; QLabel* m_label; + QQuickWidget* m_slideShow; void updateFromJobQueue( qreal percent, const QString& message ); }; diff --git a/src/libcalamaresui/slideshow/CMakeLists.txt b/src/libcalamaresui/slideshow/CMakeLists.txt new file mode 100644 index 000000000..babb35824 --- /dev/null +++ b/src/libcalamaresui/slideshow/CMakeLists.txt @@ -0,0 +1,14 @@ +set( QML_FILES + qmldir + Presentation.qml + Slide.qml +) + +set( SLIDESHOW_DIR share/calamares/slideshow ) + +foreach( QML_FILE ${QML_FILES} ) + configure_file( ${QML_FILE} ${QML_FILE} COPYONLY ) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${QML_FILE} + DESTINATION ${SLIDESHOW_DIR} ) +endforeach() + diff --git a/src/libcalamaresui/slideshow/Presentation.qml b/src/libcalamaresui/slideshow/Presentation.qml new file mode 100644 index 000000000..086e9071c --- /dev/null +++ b/src/libcalamaresui/slideshow/Presentation.qml @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QML Presentation System. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.0 +import QtQuick.Window 2.0 + +Item { + id: root + + property variant slides: [] + property int currentSlide; + + property bool showNotes: false; + property bool allowDelay: true; + + property color titleColor: textColor; + property color textColor: "black" + property string fontFamily: "Helvetica" + property string codeFontFamily: "Courier New" + + // Private API + property bool _faded: false + property int _userNum; + + Component.onCompleted: { + var slideCount = 0; + var slides = []; + for (var i=0; i 0) { + root.currentSlide = 0; + root.slides[root.currentSlide].visible = true; + } + } + + function switchSlides(from, to, forward) { + from.visible = false + to.visible = true + return true + } + + function goToNextSlide() { + root._userNum = 0 + if (_faded) + return + if (root.slides[currentSlide].delayPoints) { + if (root.slides[currentSlide]._advance()) + return; + } + if (root.currentSlide + 1 < root.slides.length) { + var from = slides[currentSlide] + var to = slides[currentSlide + 1] + if (switchSlides(from, to, true)) { + currentSlide = currentSlide + 1; + root.focus = true; + } + } + } + + function goToPreviousSlide() { + root._userNum = 0 + if (root._faded) + return + if (root.currentSlide - 1 >= 0) { + var from = slides[currentSlide] + var to = slides[currentSlide - 1] + if (switchSlides(from, to, false)) { + currentSlide = currentSlide - 1; + root.focus = true; + } + } + } + + function goToUserSlide() { + --_userNum; + if (root._faded || _userNum >= root.slides.length) + return + if (_userNum < 0) + goToNextSlide() + else if (root.currentSlide != _userNum) { + var from = slides[currentSlide] + var to = slides[_userNum] + if (switchSlides(from, to, _userNum > currentSlide)) { + currentSlide = _userNum; + root.focus = true; + } + } + } + + focus: true + + Keys.onSpacePressed: goToNextSlide() + Keys.onRightPressed: goToNextSlide() + Keys.onDownPressed: goToNextSlide() + Keys.onLeftPressed: goToPreviousSlide() + Keys.onUpPressed: goToPreviousSlide() + Keys.onEscapePressed: Qt.quit() + Keys.onPressed: { + if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) + _userNum = 10 * _userNum + (event.key - Qt.Key_0) + else { + if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) + goToUserSlide(); + else if (event.key == Qt.Key_Backspace) + goToPreviousSlide(); + else if (event.key == Qt.Key_C) + root._faded = !root._faded; + _userNum = 0; + } + } + + Rectangle { + z: 1000 + color: "black" + anchors.fill: parent + opacity: root._faded ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 250 } } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button == Qt.RightButton) + goToPreviousSlide() + else + goToNextSlide() + } + onPressAndHold: goToPreviousSlide(); //A back mechanism for touch only devices + } + + Window { + id: notesWindow; + width: 400 + height: 300 + + title: "QML Presentation: Notes" + visible: root.showNotes + + Text { + anchors.fill: parent + anchors.margins: parent.height * 0.1; + + font.pixelSize: 16 + wrapMode: Text.WordWrap + + property string notes: root.slides[root.currentSlide].notes; + text: notes == "" ? "Slide has no notes..." : notes; + font.italic: notes == ""; + } + } +} diff --git a/src/libcalamaresui/slideshow/Slide.qml b/src/libcalamaresui/slideshow/Slide.qml new file mode 100644 index 000000000..e46b5687d --- /dev/null +++ b/src/libcalamaresui/slideshow/Slide.qml @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QML Presentation System. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.0 + +Item { + /* + Slides can only be instantiated as a direct child of a Presentation {} as they rely on + several properties there. + */ + + id: slide + + property bool isSlide: true; + + property bool delayPoints: false; + property int _pointCounter: 0; + function _advance() { + if (!parent.allowDelay) + return false; + + _pointCounter = _pointCounter + 1; + if (_pointCounter < content.length) + return true; + _pointCounter = 0; + return false; + } + + property string title; + property variant content: [] + property string centeredText + property string writeInText; + property string notes; + + property real fontSize: parent.height * 0.05 + property real fontScale: 1 + + property real baseFontSize: fontSize * fontScale + property real titleFontSize: fontSize * 1.2 * fontScale + property real bulletSpacing: 1 + + property real contentWidth: width + + // Define the slide to be the "content area" + x: parent.width * 0.05 + y: parent.height * 0.2 + width: parent.width * 0.9 + height: parent.height * 0.7 + + property real masterWidth: parent.width + property real masterHeight: parent.height + + property color titleColor: parent.titleColor; + property color textColor: parent.textColor; + property string fontFamily: parent.fontFamily; + property int textFormat: Text.PlainText + + visible: false + + Text { + id: titleText + font.pixelSize: titleFontSize + text: title; + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.top + anchors.bottomMargin: parent.fontSize * 1.5 + font.bold: true; + font.family: slide.fontFamily + color: slide.titleColor + horizontalAlignment: Text.Center + z: 1 + } + + Text { + id: centeredId + width: parent.width + anchors.centerIn: parent + anchors.verticalCenterOffset: - parent.y / 3 + text: centeredText + horizontalAlignment: Text.Center + font.pixelSize: baseFontSize + font.family: slide.fontFamily + color: slide.textColor + wrapMode: Text.Wrap + } + + Text { + id: writeInTextId + property int length; + font.family: slide.fontFamily + font.pixelSize: baseFontSize + color: slide.textColor + + anchors.fill: parent; + wrapMode: Text.Wrap + + text: slide.writeInText.substring(0, length); + + NumberAnimation on length { + from: 0; + to: slide.writeInText.length; + duration: slide.writeInText.length * 30; + running: slide.visible && parent.visible && slide.writeInText.length > 0 + } + + visible: slide.writeInText != undefined; + } + + + Column { + id: contentId + anchors.fill: parent + + Repeater { + model: content.length + + Row { + id: row + + function decideIndentLevel(s) { return s.charAt(0) == " " ? 1 + decideIndentLevel(s.substring(1)) : 0 } + property int indentLevel: decideIndentLevel(content[index]) + property int nextIndentLevel: index < content.length - 1 ? decideIndentLevel(content[index+1]) : 0 + property real indentFactor: (10 - row.indentLevel * 2) / 10; + + height: text.height + (nextIndentLevel == 0 ? 1 : 0.3) * slide.baseFontSize * slide.bulletSpacing + x: slide.baseFontSize * indentLevel + visible: (!slide.parent.allowDelay || !delayPoints) || index <= _pointCounter + + Rectangle { + id: dot + y: baseFontSize * row.indentFactor / 2 + width: baseFontSize / 4 + height: baseFontSize / 4 + color: slide.textColor + radius: width / 2 + smooth: true + opacity: text.text.length == 0 ? 0 : 1 + } + + Rectangle { + id: space + width: dot.width * 2 + height: 1 + color: "#00ffffff" + } + + Text { + id: text + width: slide.contentWidth - parent.x - dot.width - space.width + font.pixelSize: baseFontSize * row.indentFactor + text: content[index] + textFormat: slide.textFormat + wrapMode: Text.WordWrap + color: slide.textColor + horizontalAlignment: Text.AlignLeft + font.family: slide.fontFamily + } + } + } + } + +} diff --git a/src/libcalamaresui/slideshow/qmldir b/src/libcalamaresui/slideshow/qmldir new file mode 100644 index 000000000..9b3160e66 --- /dev/null +++ b/src/libcalamaresui/slideshow/qmldir @@ -0,0 +1,4 @@ +module slideshow +Presentation 1.0 Presentation.qml +Slide 1.0 Slide.qml +