Development/Tutorials/KCmdLineArgs/KF5: Difference between revisions
(change prev link to represent the KF5 version) |
m (Remove use of ECM_KDE_MODULE_DIR, part of ECM_MODULE_PATH) |
||
(9 intermediate revisions by one other user not shown) | |||
Line 17: | Line 17: | ||
==Abstract== | ==Abstract== | ||
Now that we have a text editor which can open and save files. We will now make the editor act more like a desktop application by enabling it to open files from command line arguments or even using ''Open with'' from within Dolphin. | |||
[[image:tutorial5-kf5.png|frame|center]] | [[image:tutorial5-kf5.png|frame|center]] | ||
Line 31: | Line 29: | ||
#include <QApplication> | #include <QApplication> | ||
#include <QCommandLineParser> | #include <QCommandLineParser> | ||
#include <QUrl> | |||
#include <QDir> | |||
#include <KAboutData> | #include <KAboutData> | ||
Line 73: | Line 73: | ||
parser.addHelpOption(); | parser.addHelpOption(); | ||
parser.addVersionOption(); | parser.addVersionOption(); | ||
parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open")); | parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open")); | ||
Line 86: | Line 85: | ||
if (parser.positionalArguments().count() > 0) | if (parser.positionalArguments().count() > 0) | ||
{ | { | ||
window->openFile(parser.positionalArguments().at(0)); | window->openFile(QUrl::fromUserInput(parser.positionalArguments().at(0), QDir::currentPath())); | ||
} | } | ||
Line 92: | Line 91: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===mainwindow.h=== | ===mainwindow.h=== | ||
Line 113: | Line 108: | ||
public: | public: | ||
MainWindow(QWidget *parent=0); | MainWindow(QWidget *parent=0); | ||
void openFile(const QUrl &inputFileName); | |||
void openFile(const | |||
private: | private: | ||
Line 134: | Line 128: | ||
#endif | #endif | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===mainwindow.cpp=== | ===mainwindow.cpp=== | ||
Line 143: | Line 133: | ||
#include <QApplication> | #include <QApplication> | ||
#include <QAction> | #include <QAction> | ||
#include <QSaveFile> | #include <QSaveFile> | ||
#include <QFileDialog> | #include <QFileDialog> | ||
#include <QTextStream> | #include <QTextStream> | ||
Line 155: | Line 143: | ||
#include <KStandardAction> | #include <KStandardAction> | ||
#include <KMessageBox> | #include <KMessageBox> | ||
#include <KIO/Job> | #include <KIO/Job> | ||
#include "mainwindow.h" | #include "mainwindow.h" | ||
MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) | MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent), fileName(QString()) | ||
{ | { | ||
textArea = new KTextEdit(); | textArea = new KTextEdit(); | ||
Line 172: | Line 159: | ||
QAction* clearAction = new QAction(this); | QAction* clearAction = new QAction(this); | ||
clearAction->setText(i18n("&Clear")); | clearAction->setText(i18n("&Clear")); | ||
clearAction->setIcon(QIcon::fromTheme("document-new")); | clearAction->setIcon(QIcon::fromTheme("document-new")); | ||
actionCollection()->setDefaultShortcut(clearAction, Qt::CTRL + Qt::Key_W); | actionCollection()->setDefaultShortcut(clearAction, Qt::CTRL + Qt::Key_W); | ||
actionCollection()->addAction("clear", clearAction); | actionCollection()->addAction("clear", clearAction); | ||
connect(clearAction, SIGNAL(triggered(bool)), textArea, SLOT(clear())); | connect(clearAction, SIGNAL(triggered(bool)), textArea, SLOT(clear())); | ||
KStandardAction::quit(qApp, SLOT(quit()), actionCollection()); | KStandardAction::quit(qApp, SLOT(quit()), actionCollection()); | ||
Line 235: | Line 219: | ||
void MainWindow::openFile() | void MainWindow::openFile() | ||
{ | { | ||
openFile(QFileDialog:: | openFile(QFileDialog::getOpenFileUrl(this, i18n("Open File"))); | ||
} | } | ||
void MainWindow::openFile(const | void MainWindow::openFile(const QUrl &inputFileName) | ||
{ | { | ||
if (!inputFileName.isEmpty()) | |||
if (!inputFileName. | |||
{ | { | ||
KIO::Job* job = KIO::storedGet | KIO::Job* job = KIO::storedGet(inputFileName); | ||
fileName = inputFileName; | fileName = inputFileName.toLocalFile(); | ||
connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadFinished(KJob*))); | connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadFinished(KJob*))); | ||
Line 288: | Line 271: | ||
</gui> | </gui> | ||
</syntaxhighlight> | |||
==Explanation== | |||
===mainwindow.h=== | |||
Here we have done nothing but add a new <tt>openFile</tt> function which takes a <tt>QUrl</tt>. Again, we use a QUrl instead of a QString so that we can also work with remote files as if they were local. | |||
<syntaxhighlight lang="cpp-qt"> | |||
void openFile(const QUrl &inputFileName); | |||
</syntaxhighlight> | |||
===mainwindow.cpp=== | |||
There's no new code here, only rearranging. Everything from <tt>void openFile()</tt> has been moved into <tt>void openFile(const QUrl &inputFileName)</tt> except the call to <tt>QFileDialog::getOpenFileUrl()</tt>. | |||
This way, we can call <tt>openFile()</tt> if we want to display a dialog, or we can call <tt>openFile(QString)</tt> if we know the name of the file already. Which will be the case when we feed the file name through the command line. | |||
===main.cpp=== | |||
This is where all the [http://doc.qt.io/qt-5/qcommandlineparser.html QCommandLineParser] magic happens. In previous examples, we only used the class to feed QApplication the necessary data for using flags like <tt>--version</tt> or <tt>--author</tt>. Now we actually get to use it to process command line arguments. | |||
First, we tell QCommandLineParser that we want to add a new positional arguments. In a nutshell, these are arguments that are not options. <tt>-h</tt> or <tt>--version</tt> are options, <tt>file</tt> is an argument. | |||
<syntaxhighlight lang="cpp-qt"> | |||
parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open")); | |||
</syntaxhighlight> | |||
Later on, we start processing positional arguments, but only if there is one. Otherwise, we proceed as usual. In our case we can only open one file at a time, so only the first file is of interest to us. We call the <tt>openFile()</tt> function and feed it the URL of the file we want to open, whether it is a local file like {{path|$HOME/foo}} or a remote one like {{path|ftp.mydomain.com/bar}}. We use the overloaded form of <tt>[http://doc.qt.io/qt-5/qurl.html#fromUserInput-1 QUrl::fromUserInput()]</tt> in order to set the current path. This is needed in order to work with relative paths like <tt>"../baz"</tt>. | |||
<syntaxhighlight lang="cpp-qt"> | |||
if (parser.positionalArguments().count() > 0) | |||
{ | |||
window->openFile(QUrl::fromUserInput(parser.positionalArguments().at(0), QDir::currentPath())); | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 301: | Line 317: | ||
find_package(ECM 1.0.0 REQUIRED NO_MODULE) | find_package(ECM 1.0.0 REQUIRED NO_MODULE) | ||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) | ||
include(KDEInstallDirs) | include(KDEInstallDirs) | ||
Line 308: | Line 324: | ||
include(FeatureSummary) | include(FeatureSummary) | ||
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS | find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS | ||
Core # QCommandLineParser, QStringLiteral, QSaveFile, QTextStream, QByteArray | Core # QCommandLineParser, QStringLiteral, QSaveFile, QTextStream, QByteArray | ||
Line 314: | Line 329: | ||
) | ) | ||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS | find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS | ||
CoreAddons # KAboutData | CoreAddons # KAboutData | ||
Line 325: | Line 339: | ||
) | ) | ||
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) | feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) | ||
set(tutorial5_SRCS main.cpp mainwindow.cpp) | set(tutorial5_SRCS main.cpp mainwindow.cpp) | ||
add_executable(tutorial5 ${tutorial5_SRCS}) | add_executable(tutorial5 ${tutorial5_SRCS}) | ||
target_link_libraries(tutorial5 | target_link_libraries(tutorial5 | ||
Qt5::Widgets | Qt5::Widgets | ||
Line 349: | Line 360: | ||
install(FILES tutorial5ui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/tutorial5) | install(FILES tutorial5ui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/tutorial5) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
With this file, the tutorial can built and run in the same way as tutorial 3 and 4. For more information, see tutorial 3. | |||
<syntaxhighlight lang="bash"> | |||
mkdir build && cd build | |||
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME | |||
make install | |||
XDG_DATA_DIRS=$HOME/share:$XDG_DATA_DIRS $HOME/bin/tutorial5 | |||
</syntaxhighlight> | |||
[[Category:C++]] |
Latest revision as of 18:05, 18 April 2020
Tutorial Series | Beginner Tutorial |
Previous | Tutorial 4 - Loading and saving |
What's Next | Tutorial 6 - ### (TODO User:milliams) |
Further Reading | KCmdLineArgs KCmdLineOptions |
Abstract
Now that we have a text editor which can open and save files. We will now make the editor act more like a desktop application by enabling it to open files from command line arguments or even using Open with from within Dolphin.
The Code
main.cpp
#include <cstdlib>
#include <QApplication>
#include <QCommandLineParser>
#include <QUrl>
#include <QDir>
#include <KAboutData>
#include <KLocalizedString>
#include "mainwindow.h"
int main (int argc, char *argv[])
{
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain("tutorial5");
KAboutData aboutData(
// The program name used internally. (componentName)
QStringLiteral("tutorial5"),
// A displayable program name string. (displayName)
i18n("Tutorial 5"),
// The program version string. (version)
QStringLiteral("1.0"),
// Short description of what the app does. (shortDescription)
i18n("A simple text area which can load and save."),
// The license this code is released under
KAboutLicense::GPL,
// Copyright Statement (copyrightStatement = QString())
i18n("(c) 2015"),
// Optional text shown in the About box.
// Can contain any information desired. (otherText)
i18n("Some text..."),
// The program homepage string. (homePageAddress = QString())
QStringLiteral("http://example.com/"),
// The bug report email address
// (bugsEmailAddress = QLatin1String("[email protected]")
QStringLiteral("[email protected]"));
aboutData.addAuthor(i18n("Name"), i18n("Task"), QStringLiteral("[email protected]"),
QStringLiteral("http://your.website.com"), QStringLiteral("OSC Username"));
KAboutData::setApplicationData(aboutData);
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open"));
aboutData.setupCommandLine(&parser);
parser.process(app);
aboutData.processCommandLine(&parser);
MainWindow* window = new MainWindow();
window->show();
if (parser.positionalArguments().count() > 0)
{
window->openFile(QUrl::fromUserInput(parser.positionalArguments().at(0), QDir::currentPath()));
}
return app.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <KXmlGuiWindow>
class KTextEdit;
class KJob;
class MainWindow : public KXmlGuiWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent=0);
void openFile(const QUrl &inputFileName);
private:
KTextEdit* textArea;
void setupActions();
QString fileName;
private slots:
void newFile();
void openFile();
void saveFile();
void saveFileAs();
void saveFileAs(const QString &outputFileName);
void downloadFinished(KJob* job);
};
#endif
mainwindow.cpp
#include <QApplication>
#include <QAction>
#include <QSaveFile>
#include <QFileDialog>
#include <QTextStream>
#include <QByteArray>
#include <KTextEdit>
#include <KLocalizedString>
#include <KActionCollection>
#include <KStandardAction>
#include <KMessageBox>
#include <KIO/Job>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent), fileName(QString())
{
textArea = new KTextEdit();
setCentralWidget(textArea);
setupActions();
}
void MainWindow::setupActions()
{
QAction* clearAction = new QAction(this);
clearAction->setText(i18n("&Clear"));
clearAction->setIcon(QIcon::fromTheme("document-new"));
actionCollection()->setDefaultShortcut(clearAction, Qt::CTRL + Qt::Key_W);
actionCollection()->addAction("clear", clearAction);
connect(clearAction, SIGNAL(triggered(bool)), textArea, SLOT(clear()));
KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
KStandardAction::open(this, SLOT(openFile()), actionCollection());
KStandardAction::save(this, SLOT(saveFile()), actionCollection());
KStandardAction::saveAs(this, SLOT(saveFileAs()), actionCollection());
KStandardAction::openNew(this, SLOT(newFile()), actionCollection());
setupGUI(Default, "tutorial5ui.rc");
}
void MainWindow::newFile()
{
fileName.clear();
textArea->clear();
}
void MainWindow::saveFileAs(const QString &outputFileName)
{
if (!outputFileName.isNull())
{
QSaveFile file(outputFileName);
file.open(QIODevice::WriteOnly);
QByteArray outputByteArray;
outputByteArray.append(textArea->toPlainText().toUtf8());
file.write(outputByteArray);
file.commit();
fileName = outputFileName;
}
}
void MainWindow::saveFileAs()
{
saveFileAs(QFileDialog::getSaveFileName(this, i18n("Save File As")));
}
void MainWindow::saveFile()
{
if (!fileName.isEmpty())
{
saveFileAs(fileName);
}
else
{
saveFileAs();
}
}
void MainWindow::openFile()
{
openFile(QFileDialog::getOpenFileUrl(this, i18n("Open File")));
}
void MainWindow::openFile(const QUrl &inputFileName)
{
if (!inputFileName.isEmpty())
{
KIO::Job* job = KIO::storedGet(inputFileName);
fileName = inputFileName.toLocalFile();
connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadFinished(KJob*)));
job->exec();
}
}
void MainWindow::downloadFinished(KJob* job)
{
if (job->error())
{
KMessageBox::error(this, job->errorString());
fileName.clear();
return;
}
KIO::StoredTransferJob* storedJob = (KIO::StoredTransferJob*)job;
textArea->setPlainText(QTextStream(storedJob->data(), QIODevice::ReadOnly).readAll());
}
tutorial5ui.rc
<?xml version="1.0" encoding="UTF-8"?>
<gui name="tutorial5"
version="1"
xmlns="http://www.kde.org/standards/kxmlgui/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0
http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >
<MenuBar>
<Menu name="file" >
<Action name="clear" />
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" >
<text>Main Toolbar</text>
<Action name="clear" />
</ToolBar>
</gui>
Explanation
mainwindow.h
Here we have done nothing but add a new openFile function which takes a QUrl. Again, we use a QUrl instead of a QString so that we can also work with remote files as if they were local.
void openFile(const QUrl &inputFileName);
mainwindow.cpp
There's no new code here, only rearranging. Everything from void openFile() has been moved into void openFile(const QUrl &inputFileName) except the call to QFileDialog::getOpenFileUrl().
This way, we can call openFile() if we want to display a dialog, or we can call openFile(QString) if we know the name of the file already. Which will be the case when we feed the file name through the command line.
main.cpp
This is where all the QCommandLineParser magic happens. In previous examples, we only used the class to feed QApplication the necessary data for using flags like --version or --author. Now we actually get to use it to process command line arguments.
First, we tell QCommandLineParser that we want to add a new positional arguments. In a nutshell, these are arguments that are not options. -h or --version are options, file is an argument.
parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open"));
Later on, we start processing positional arguments, but only if there is one. Otherwise, we proceed as usual. In our case we can only open one file at a time, so only the first file is of interest to us. We call the openFile() function and feed it the URL of the file we want to open, whether it is a local file like $HOME/foo or a remote one like ftp.mydomain.com/bar. We use the overloaded form of QUrl::fromUserInput() in order to set the current path. This is needed in order to work with relative paths like "../baz".
if (parser.positionalArguments().count() > 0)
{
window->openFile(QUrl::fromUserInput(parser.positionalArguments().at(0), QDir::currentPath()));
}
Make, Install and Run
CMakeLists.txt
project (tutorial5)
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
set(QT_MIN_VERSION "5.3.0")
set(KF5_MIN_VERSION "5.2.0")
find_package(ECM 1.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings)
include(FeatureSummary)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
Core # QCommandLineParser, QStringLiteral, QSaveFile, QTextStream, QByteArray
Widgets # QApplication, QAction, QFileDialog
)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
CoreAddons # KAboutData
I18n # KLocalizedString
XmlGui # KXmlGuiWindow, KActionCollection
TextWidgets # KTextEdit
ConfigWidgets # KStandardActions
WidgetsAddons # KMessageBox
KIO # KIO
)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
set(tutorial5_SRCS main.cpp mainwindow.cpp)
add_executable(tutorial5 ${tutorial5_SRCS})
target_link_libraries(tutorial5
Qt5::Widgets
KF5::CoreAddons
KF5::I18n
KF5::XmlGui
KF5::TextWidgets
KF5::ConfigWidgets
KF5::WidgetsAddons
KF5::KIOCore
)
install(TARGETS tutorial5 ${INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES tutorial5ui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/tutorial5)
With this file, the tutorial can built and run in the same way as tutorial 3 and 4. For more information, see tutorial 3.
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
make install
XDG_DATA_DIRS=$HOME/share:$XDG_DATA_DIRS $HOME/bin/tutorial5