Commit 706b4c7f authored by Urs Fleisch's avatar Urs Fleisch
Browse files

QML: Support QML scripts as user actions.

parent c1c76218
......@@ -286,6 +286,10 @@ if (NOT Qt4_FOUND AND NOT QT4_FOUND AND NOT Qt5Core_FOUND)
message(FATAL_ERROR "Neither Qt4 nor Qt5 found.")
endif (NOT Qt4_FOUND AND NOT QT4_FOUND AND NOT Qt5Core_FOUND)
if (BUILD_QML_APP AND (Qt5Qml_FOUND OR QT_QTDECLARATIVE_FOUND))
set(HAVE_QML 1)
endif ()
# Check that QT_NO_DEBUG is defined for release configurations
foreach (_buildType RELWITHDEBINFO RELEASE MINSIZEREL)
......
......@@ -14,6 +14,9 @@
/* Define if mntent.h is available */
#cmakedefine HAVE_MNTENT_H 1
/* Define if build with QML support */
#cmakedefine HAVE_QML 1
#cmakedefine CFG_DATAROOTDIR "@CFG_DATAROOTDIR@"
#cmakedefine CFG_DOCDIR "@CFG_DOCDIR@"
#cmakedefine CFG_TRANSLATIONSDIR "@CFG_TRANSLATIONSDIR@"
......
......@@ -192,6 +192,10 @@ MainView {
app.openDirectory(configs.fileConfig().lastOpenedFile)
}
Component.onDestruction: {
app.frameEditor = null
}
Connections {
target: app
......
......@@ -59,3 +59,8 @@ if (HAVE_QTDBUS)
set(model_SRCS ${model_SRCS} model/scriptinterface.cpp)
set(model_MOC_HDRS ${model_MOC_HDRS} model/scriptinterface.h)
endif (HAVE_QTDBUS)
if (HAVE_QML)
set(model_SRCS ${model_SRCS} model/qmlprocess.cpp)
set(model_MOC_HDRS ${model_MOC_HDRS} model/qmlprocess.h)
endif (HAVE_QML)
......@@ -33,6 +33,10 @@
#include <QStringList>
#include <QTextCursor>
#include "taggedfile.h"
#include "config.h"
#ifdef HAVE_QML
#include "qmlprocess.h"
#endif
/**
* Constructor.
......@@ -124,10 +128,12 @@ void ExternalProcess::OutputViewer::scrollToBottom()
/**
* Constructor.
*
* @param app application context
* @param parent parent object
*/
ExternalProcess::ExternalProcess(QWidget* parent) :
QObject(parent), m_parent(parent), m_process(0), m_outputViewer(0)
ExternalProcess::ExternalProcess(Kid3Application* app, QWidget* parent) :
QObject(parent), m_app(app), m_parent(parent), m_process(0),
m_outputViewer(0), m_qmlProcess(0)
{
setObjectName(QLatin1String("ExternalProcess"));
}
......@@ -185,6 +191,16 @@ void ExternalProcess::launchCommand(const QString& name, const QStringList& args
QStringList arguments = args;
QString program = arguments.takeFirst();
#ifdef HAVE_QML
if (!m_qmlProcess) {
m_qmlProcess = new QmlProcess(m_app, this);
connect(m_qmlProcess, SIGNAL(qmlOutput(QString)),
this, SLOT(showOutputLine(QString)));
}
if (m_qmlProcess->startQml(program, arguments, showOutput)) {
return;
}
#endif
m_process->start(program, arguments);
if (!m_process->waitForStarted(10000)) {
QMessageBox::warning(
......@@ -201,3 +217,14 @@ void ExternalProcess::readFromStdout()
{
m_outputViewer->append(QString::fromLocal8Bit(m_process->readAllStandardOutput()));
}
/**
* Show a line in the output viewer.
* @param msg message to be displayed
*/
void ExternalProcess::showOutputLine(const QString& msg)
{
if (m_outputViewer) {
m_outputViewer->append(msg + QLatin1Char('\n'));
}
}
......@@ -34,14 +34,15 @@
class QProcess;
class QString;
class QStringList;
class Kid3Application;
class TaggedFile;
class QmlProcess;
/**
* Handler for external process.
*/
class KID3_CORE_EXPORT ExternalProcess : public QObject {
Q_OBJECT
Q_OBJECT
public:
/**
* Dialog to show output from external process.
......@@ -78,9 +79,10 @@ public:
/**
* Constructor.
*
* @param app application context
* @param parent parent object
*/
explicit ExternalProcess(QWidget* parent = 0);
explicit ExternalProcess(Kid3Application* app, QWidget* parent = 0);
/**
* Destructor.
......@@ -104,10 +106,18 @@ private slots:
*/
void readFromStdout();
/**
* Show a line in the output viewer.
* @param msg message to be displayed
*/
void showOutputLine(const QString& msg);
private:
Kid3Application* m_app;
QWidget* m_parent;
QProcess* m_process;
OutputViewer* m_outputViewer;
QmlProcess* m_qmlProcess;
};
#endif // EXTERNALPROCESS_H
......@@ -2913,6 +2913,22 @@ void Kid3Application::setFrameEditor(FrameEditorObject* frameEditor)
}
}
/**
* Remove frame editor.
* Has to be called in the destructor of the frame editor to avoid a dangling
* pointer to a deleted object.
* @param frameEditor frame editor
*/
void Kid3Application::removeFrameEditor(IFrameEditor* frameEditor)
{
if (m_storedFrameEditor == frameEditor) {
m_storedFrameEditor = 0;
}
if (m_frameEditor == frameEditor) {
setFrameEditor(0);
}
}
/**
* Get the numbers of the selected rows in a list suitable for scripting.
* @return list with row numbers.
......
......@@ -639,6 +639,14 @@ public:
*/
void setFrameEditor(FrameEditorObject* frameEditor);
/**
* Remove frame editor.
* Has to be called in the destructor of the frame editor to avoid a dangling
* pointer to a deleted object.
* @param frameEditor frame editor
*/
void removeFrameEditor(IFrameEditor* frameEditor);
/**
* Get ID to get cover art image.
* @return ID for cover art image.
......
/**
* \file qmlprocess.cpp
* Starter for QML scripts.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 15 Feb 2015
*
* Copyright (C) 2015 Urs Fleisch
*
* This file is part of Kid3.
*
* Kid3 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kid3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qmlprocess.h"
#include <QDir>
#if QT_VERSION >= 0x050000
#include <QQuickView>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlComponent>
#include <QTimer>
#else
#include <QDeclarativeView>
#include <QDeclarativeEngine>
#include <QDeclarativeContext>
#include <QDeclarativeComponent>
#include <QDeclarativeItem>
#include <QCloseEvent>
#endif
#include "kid3application.h"
#if QT_VERSION < 0x050000
//! @cond
#define QQmlEngine QDeclarativeEngine
#define QQuickView QmlView
#define QQmlComponent QDeclarativeComponent
#define QQmlError QDeclarativeError
//! @endcond
#endif
namespace {
/**
* Set import path and app property in QML engine.
* @param engine QML engine
* @param app application context
*/
void setupQmlEngine(QQmlEngine* engine, Kid3Application* app)
{
QDir pluginsDir;
if (Kid3Application::findPluginsDirectory(pluginsDir) &&
pluginsDir.cd(QLatin1String("imports"))) {
engine->addImportPath(pluginsDir.absolutePath());
}
engine->rootContext()->setContextProperty(QLatin1String("app"), app);
}
}
/**
* Constructor.
*
* @param app application context
* @param parent parent object
*/
QmlProcess::QmlProcess(Kid3Application* app, QObject* parent) : QObject(parent),
m_app(app), m_qmlView(0), m_qmlEngine(0), m_showOutput(false)
{
}
/**
* Destructor.
*/
QmlProcess::~QmlProcess()
{
if (m_qmlView) {
m_qmlView->close();
}
delete m_qmlView;
if (s_messageHandlerInstance == this) {
s_messageHandlerInstance = 0;
}
}
/**
* Start a QML script.
*
* @param program virtual program, e.g. "qmlview"
* @param arguments arguments to pass to script
* @param showOutput true to enable output in output viewer
* @return true if program and arguments are suitable for QML script.
*/
bool QmlProcess::startQml(const QString& program,
const QStringList& arguments, bool showOutput)
{
if (!arguments.isEmpty()) {
if (program == QLatin1String("qmlview")) {
m_showOutput = showOutput;
if (!m_qmlView) {
m_qmlView = new QQuickView;
m_qmlView->setResizeMode(QQuickView::SizeRootObjectToView);
setupQmlEngine(m_qmlView->engine(), m_app);
connect(m_qmlView, SIGNAL(closing(QQuickCloseEvent*)),
this, SLOT(onQmlViewClosing()));
connect(m_qmlView->engine(), SIGNAL(quit()),
this, SLOT(onQmlViewFinished()), Qt::QueuedConnection);
}
m_qmlView->engine()->rootContext()->setContextProperty(
QLatin1String("args"), arguments);
onEngineReady();
m_qmlView->setSource(QUrl::fromLocalFile(arguments.first()));
if (m_qmlView->status() == QQuickView::Ready) {
m_qmlView->show();
} else {
// Probably an error.
if (m_showOutput && m_qmlView->status() == QQuickView::Error) {
foreach (const QQmlError& err, m_qmlView->errors()) {
emit qmlOutput(err.toString());
}
}
m_qmlView->engine()->clearComponentCache();
onEngineFinished();
}
return true;
} else if (program == QLatin1String("qml")) {
m_showOutput = showOutput;
if (!m_qmlEngine) {
m_qmlEngine = new QQmlEngine(this);
connect(m_qmlEngine, SIGNAL(quit()), this, SLOT(onQmlEngineQuit()));
setupQmlEngine(m_qmlEngine, m_app);
}
m_qmlEngine->rootContext()->setContextProperty(QLatin1String("args"),
arguments);
QQmlComponent component(m_qmlEngine, arguments.first());
if (component.status() == QQmlComponent::Ready) {
onEngineReady();
component.create();
} else {
// Probably an error.
if (m_showOutput && component.isError()) {
foreach (const QQmlError& err, component.errors()) {
emit qmlOutput(err.toString());
}
}
m_qmlEngine->clearComponentCache();
}
return true;
}
}
return false;
}
/**
* Called when the QML view is closing.
*/
void QmlProcess::onQmlViewClosing()
{
if (QQuickView* view = qobject_cast<QQuickView*>(sender())) {
// This will invoke destruction of the currently loaded QML code.
view->setSource(QUrl());
view->engine()->clearComponentCache();
onEngineFinished();
}
}
/**
* Called when Qt.quit() is called from the QML code in the QQuickView.
*/
void QmlProcess::onQmlViewFinished()
{
if (m_qmlView) {
#if QT_VERSION >= 0x050000
// Unfortunately, calling close() on the QQuickView will not give a
// QEvent::Close in an installed event filter, there is no closeEvent(),
// closing() is not signalled. What remains is the hard way.
m_qmlView->deleteLater();
m_qmlView = 0;
QTimer::singleShot(0, this, SLOT(onEngineFinished()));
#else
m_qmlView->close();
#endif
}
}
/**
* Called when Qt.quit() is called from the QML code in the core engine.
*/
void QmlProcess::onQmlEngineQuit()
{
if (m_qmlEngine) {
m_qmlEngine->clearComponentCache();
}
onEngineFinished();
}
/**
* Restore default message handler after QML code is terminated.
*/
void QmlProcess::onEngineFinished()
{
if (m_showOutput) {
#if QT_VERSION >= 0x050000
qInstallMessageHandler(0);
#else
qInstallMsgHandler(0);
#endif
s_messageHandlerInstance = 0;
}
}
/**
* Forward console output to output viewer while QML code is executed.
*/
void QmlProcess::onEngineReady()
{
if (m_showOutput) {
s_messageHandlerInstance = this;
#if QT_VERSION >= 0x050000
qInstallMessageHandler(messageHandler);
#else
qInstallMsgHandler(messageHandler);
#endif
}
}
/** Instance of QmlProcess running and generating messages. */
QmlProcess* QmlProcess::s_messageHandlerInstance = 0;
/**
* Message handler emitting qmlOutput().
*/
#if QT_VERSION >= 0x050000
void QmlProcess::messageHandler(QtMsgType, const QMessageLogContext&, const QString& msg)
{
if (s_messageHandlerInstance) {
emit s_messageHandlerInstance->qmlOutput(msg);
}
}
#else
void QmlProcess::messageHandler(QtMsgType, const char* msg)
{
if (s_messageHandlerInstance) {
emit s_messageHandlerInstance->qmlOutput(QString::fromUtf8(msg));
}
}
#endif
#if QT_VERSION < 0x050000
/**
* Constructor.
* @param parent parent widget
*/
QmlView::QmlView(QWidget* parent) : QDeclarativeView(parent)
{
}
/**
* Destructor.
*/
QmlView::~QmlView()
{
}
/**
* Handle close event.
* @param ev close event
*/
void QmlView::closeEvent(QCloseEvent* ev)
{
ev->accept();
emit closing(0);
}
#endif
/**
* \file qmlprocess.h
* Starter for QML scripts.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 15 Feb 2015
*
* Copyright (C) 2015 Urs Fleisch
*
* This file is part of Kid3.
*
* Kid3 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kid3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QMLPROCESS_H
#define QMLPROCESS_H
#include <QObject>
#include "kid3api.h"
#if QT_VERSION < 0x050000
#include <QDeclarativeView>
#endif
class Kid3Application;
#if QT_VERSION >= 0x050000
class QQuickView;
class QQmlEngine;
#else
class QDeclarativeEngine;
class QQuickCloseEvent;
#endif
/**
* Starter for QML scripts.
*/
class KID3_CORE_EXPORT QmlProcess : public QObject {
Q_OBJECT
public:
/**
* Constructor.
*
* @param app application context
* @param parent parent object
*/
explicit QmlProcess(Kid3Application* app, QObject* parent = 0);
/**
* Destructor.
*/
virtual ~QmlProcess();
/**
* Start a QML script.
*
* @param program virtual program, e.g. "qmlview"
* @param arguments arguments to pass to script
* @param showOutput true to enable output in output viewer
* @return true if program and arguments are suitable for QML script.
*/
bool startQml(const QString& program, const QStringList& arguments,
bool showOutput);
signals:
/**
* Emitted when output is enabled and a QML message is generated.
* @param msg message from QML, error or console output
*/
void qmlOutput(const QString& msg);
private slots:
void onQmlViewClosing();
void onQmlViewFinished();
void onQmlEngineQuit();
void onEngineFinished();
private:
void onEngineReady();
#if QT_VERSION >= 0x050000
static void messageHandler(QtMsgType type, const QMessageLogContext& context,
const QString& msg);
#else
static void messageHandler(QtMsgType type, const char* msg);
#endif
Kid3Application* m_app;
#if QT_VERSION >= 0x050000
QQuickView* m_qmlView;
QQmlEngine* m_qmlEngine;
#else
QDeclarativeView* m_qmlView;
QDeclarativeEngine* m_qmlEngine;
#endif
bool m_showOutput;
static QmlProcess* s_messageHandlerInstance;
};
#if QT_VERSION < 0x050000
/**
* QDeclarativeView with a closing signal.
*/
class QmlView : public QDeclarativeView {
Q_OBJECT
public:
/**
* Constructor.
* @param parent parent widget
*/
explicit QmlView(QWidget* parent = 0);
/**
* Destructor.
*/
virtual ~QmlView();
signals:
/**
* Emitted when window is closed.
* @param ev close event, always 0, just for compatibility with Qt 5
*/
void closing(QQuickCloseEvent* ev);
protected:
/**
* Handle close event.
* @param ev close event
*/
virtual void closeEvent(QCloseEvent* ev);
};
#endif
#endif // QMLPROCESS_H
......@@ -66,10 +66,12 @@ static QString getToolTip()
/**
* Constructor.
*
* @param app application context
* @param parent parent widget