Initial commit for QML slideshow support.

This commit is contained in:
Teo Mrnjavac 2015-01-23 14:02:40 +01:00
parent 46a5796e29
commit 14ddba70ef
12 changed files with 558 additions and 24 deletions

View File

@ -29,7 +29,7 @@ include( MacroLogFeature )
set( QT_VERSION 5.3.0 ) 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( YamlCpp 0.5.1 REQUIRED )
find_package( PolkitQt5-1 REQUIRED ) find_package( PolkitQt5-1 REQUIRED )

View File

@ -14,5 +14,4 @@ images:
productLogo: "squid.png" productLogo: "squid.png"
productIcon: "squid.png" productIcon: "squid.png"
slideshow: slideshow: "show.qml"
- "squid.png"

View File

@ -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"
}
}

View File

@ -116,20 +116,37 @@ Branding::Branding( const QString& brandingFilePath,
m_images.insert( it.key(), imageFi.absoluteFilePath() ); 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." ); 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 ) catch ( YAML::Exception& e )
{ {
@ -197,10 +214,10 @@ Branding::image( Branding::ImageEntry imageEntry, const QSize& size ) const
} }
QStringList QString
Branding::slideshowPaths() const Branding::slideshowPath() const
{ {
return m_slideshow; return m_slideshowPath;
} }

View File

@ -65,7 +65,7 @@ public:
QString string( Branding::StringEntry stringEntry ) const; QString string( Branding::StringEntry stringEntry ) const;
QString imagePath( Branding::ImageEntry imageEntry ) const; QString imagePath( Branding::ImageEntry imageEntry ) const;
QPixmap image( Branding::ImageEntry imageEntry, const QSize& size ) 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 * Creates a map called "branding" in the global storage, and inserts an
@ -86,7 +86,7 @@ private:
QString m_componentName; QString m_componentName;
QMap< QString, QString > m_strings; QMap< QString, QString > m_strings;
QMap< QString, QString > m_images; QMap< QString, QString > m_images;
QStringList m_slideshow; QString m_slideshowPath;
}; };
} }

View File

@ -40,7 +40,10 @@ calamares_add_library( ${CALAMARESUI_LIBRARY_TARGET}
LINK_LIBRARIES LINK_LIBRARIES
yaml-cpp yaml-cpp
Qt5::Svg Qt5::Svg
Qt5::QuickWidgets
RESOURCES libcalamaresui.qrc RESOURCES libcalamaresui.qrc
EXPORT CalamaresLibraryDepends EXPORT CalamaresLibraryDepends
VERSION ${CALAMARES_VERSION_SHORT} VERSION ${CALAMARES_VERSION_SHORT}
) )
add_subdirectory( slideshow )

View File

@ -18,11 +18,18 @@
#include <InstallationViewStep.h> #include <InstallationViewStep.h>
#include <JobQueue.h> #include "JobQueue.h"
#include "Branding.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
#include "Settings.h"
#include <QDir>
#include <QLabel> #include <QLabel>
#include <QProgressBar> #include <QProgressBar>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtQuickWidgets/QQuickWidget>
#include <QQmlEngine>
namespace Calamares namespace Calamares
{ {
@ -35,10 +42,83 @@ InstallationViewStep::InstallationViewStep( QObject* parent )
m_progressBar->setMaximum( 10000 ); m_progressBar->setMaximum( 10000 );
m_label = new QLabel; m_label = new QLabel;
QVBoxLayout* layout = new QVBoxLayout( m_widget ); QVBoxLayout* layout = new QVBoxLayout( m_widget );
layout->addWidget(m_progressBar); QVBoxLayout* innerLayout = new QVBoxLayout;
layout->addWidget(m_label);
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 QString

View File

@ -23,6 +23,7 @@
class QLabel; class QLabel;
class QProgressBar; class QProgressBar;
class QQuickWidget;
namespace Calamares namespace Calamares
{ {
@ -51,6 +52,7 @@ private:
QWidget* m_widget; QWidget* m_widget;
QProgressBar* m_progressBar; QProgressBar* m_progressBar;
QLabel* m_label; QLabel* m_label;
QQuickWidget* m_slideShow;
void updateFromJobQueue( qreal percent, const QString& message ); void updateFromJobQueue( qreal percent, const QString& message );
}; };

View File

@ -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()

View File

@ -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<root.children.length; ++i) {
var r = root.children[i];
if (r.isSlide) {
slides.push(r);
}
}
root.slides = slides;
root._userNum = 0;
// Make first slide visible...
if (root.slides.length > 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 == "";
}
}
}

View File

@ -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
}
}
}
}
}

View File

@ -0,0 +1,4 @@
module slideshow
Presentation 1.0 Presentation.qml
Slide 1.0 Slide.qml