Mercurial > clickerconvert
changeset 50:36ee5dd46fd3
Add GUI
New Mainwindow that allows to set output formats and input
files through a GUI.
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Tue, 19 Jul 2016 12:19:45 +0200 |
parents | a849b1de248f |
children | e9cc707838d1 |
files | src/CMakeLists.txt src/constants.h src/filenamerequester.cpp src/filenamerequester.h src/l10n/main_de_DE.ts src/main.cpp src/mainwindow.cpp src/mainwindow.h |
diffstat | 8 files changed, 675 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/src/CMakeLists.txt Tue Jul 19 12:18:57 2016 +0200 +++ b/src/CMakeLists.txt Tue Jul 19 12:19:45 2016 +0200 @@ -17,6 +17,9 @@ converter.cpp strhelp.c cconvert_options.h + icons/icon.rc + filenamerequester.cpp + mainwindow.cpp ) find_package(Qt5LinguistTools) @@ -36,6 +39,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/l10n/main_de_DE.ts) endif() +qt5_add_resources(APPLICATION_SRC icons/icons.qrc) add_executable(${PROJECT_NAME} ${_add_executable_params}
--- a/src/constants.h Tue Jul 19 12:18:57 2016 +0200 +++ b/src/constants.h Tue Jul 19 12:19:45 2016 +0200 @@ -91,4 +91,14 @@ #define HTML_COL2_PERCENT 30 #define HTML_COL3_PERCENT 35 +/** + * @brief name of the default icon. Only used on Linux to load + * the file from the resource. */ +#define ICON_NAME ":/64_icon-pdf.png" + + +#define DEFAULT_DIR QStandardPaths::DownloadLocation + +#define MAX_FILENAME_COUNT 10000 + #endif // CONSTANTS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filenamerequester.cpp Tue Jul 19 12:19:45 2016 +0200 @@ -0,0 +1,183 @@ +/* Copyright (C) 2016 by ETH Zürich + * Software engineering by Intevation GmbH + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +/* This file was originally taken from Libkleo where the license was: + ui/filenamerequester.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2007 Klarälvdalens Datakonsult AB + + Kleopatra 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. + + Kleopatra 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "filenamerequester.h" + +#include <QLineEdit> + +#include <QHBoxLayout> +#include <QToolButton> +#include <QCompleter> +#include <QDirModel> +#include <QString> +#include <QFileDialog> +#include <QApplication> +#include <QStyle> + +class FileNameRequester::Private +{ + friend class FileNameRequester; + FileNameRequester *const q; +public: + explicit Private(FileNameRequester *qq); + ~Private(); + +private: + void slotButtonClicked(); + +private: + QDirModel dirmodel; + QCompleter completer; + + QLineEdit lineedit; + QToolButton button; + QHBoxLayout hlay; + + QString nameFilter; + bool existingOnly; +}; + +FileNameRequester::Private::Private(FileNameRequester *qq) + : q(qq), + dirmodel(), + completer(&dirmodel), + lineedit(q), + button(q), + hlay(q), + nameFilter(), + existingOnly(true) +{ + dirmodel.setObjectName(QStringLiteral("dirmodel")); + completer.setObjectName(QStringLiteral("completer")); + lineedit.setObjectName(QStringLiteral("lineedit")); + button.setObjectName(QStringLiteral("button")); + hlay.setObjectName(QStringLiteral("hlay")); + + button.setIcon(QApplication::style()->standardIcon(QStyle::SP_DirIcon)); + lineedit.setCompleter(&completer); + lineedit.setClearButtonEnabled(true); + hlay.setMargin(0); + hlay.addWidget(&lineedit); + hlay.addWidget(&button); + + connect(&button, SIGNAL(clicked()), q, SLOT(slotButtonClicked())); + connect(&lineedit, &QLineEdit::textChanged, q, &FileNameRequester::fileNameChanged); +} + +FileNameRequester::Private::~Private() {} + +FileNameRequester::FileNameRequester(QWidget *p) + : QWidget(p), d(new Private(this)) +{ + +} + +FileNameRequester::FileNameRequester(QDir::Filters f, QWidget *p) + : QWidget(p), d(new Private(this)) +{ + d->dirmodel.setFilter(f); +} + +FileNameRequester::~FileNameRequester() +{ + delete d; +} + +void FileNameRequester::setFileName(const QString &file) +{ + d->lineedit.setText(file); +} + +QString FileNameRequester::fileName() const +{ + return d->lineedit.text(); +} + +void FileNameRequester::setExistingOnly(bool on) +{ + d->existingOnly = on; +} + +bool FileNameRequester::existingOnly() const +{ + return d->existingOnly; +} + +void FileNameRequester::setFilter(QDir::Filters f) +{ + d->dirmodel.setFilter(f); +} + +QDir::Filters FileNameRequester::filter() const +{ + return d->dirmodel.filter(); +} + +void FileNameRequester::setNameFilter(const QString &nameFilter) +{ + d->nameFilter = nameFilter; +} + +QString FileNameRequester::nameFilter() const +{ + return d->nameFilter; +} + +void FileNameRequester::Private::slotButtonClicked() +{ + const QString fileName = q->requestFileName(); + if (!fileName.isEmpty()) { + q->setFileName(fileName); + } +} + +QString FileNameRequester::requestFileName() +{ + const QDir::Filters filters = filter(); + if ((filters & QDir::Dirs) && !(filters & QDir::Files)) { + return QFileDialog::getExistingDirectory(this); + } else if (d->existingOnly) { + return QFileDialog::getOpenFileName(this, QString(), QString(), d->nameFilter); + } else { + return QFileDialog::getSaveFileName(this, QString(), QString(), d->nameFilter); + } +} + +#include "moc_filenamerequester.cpp"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filenamerequester.h Tue Jul 19 12:19:45 2016 +0200 @@ -0,0 +1,73 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + ui/filenamerequester.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2007 Klarälvdalens Datakonsult AB + + Kleopatra 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. + + Kleopatra 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KLEOPATRA_UI_FILENAMEREQUESTER_H__ +#define __KLEOPATRA_UI_FILENAMEREQUESTER_H__ + +#include <QWidget> + +#include <QDir> + +class FileNameRequester : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(bool existingOnly READ existingOnly WRITE setExistingOnly) +public: + explicit FileNameRequester(QWidget *parent = Q_NULLPTR); + explicit FileNameRequester(QDir::Filters filter, QWidget *parent = Q_NULLPTR); + ~FileNameRequester(); + + void setFileName(const QString &name); + QString fileName() const; + + void setExistingOnly(bool on); + bool existingOnly() const; + + void setFilter(QDir::Filters f); + QDir::Filters filter() const; + + void setNameFilter(const QString &nameFilter); + QString nameFilter() const; + +Q_SIGNALS: + void fileNameChanged(const QString &filename); + +private: + virtual QString requestFileName(); + +private: + class Private; + Private *d; + Q_PRIVATE_SLOT(d, void slotButtonClicked()) +}; +#endif /* __KLEOPATRA_UI_FILENAMEREQUESTER_H__ */
--- a/src/l10n/main_de_DE.ts Tue Jul 19 12:18:57 2016 +0200 +++ b/src/l10n/main_de_DE.ts Tue Jul 19 12:19:45 2016 +0200 @@ -42,6 +42,84 @@ </message> </context> <context> + <name>MainWindow</name> + <message> + <location filename="../mainwindow.cpp" line="49"/> + <source>Input File:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="57"/> + <source>Export Folder:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="68"/> + <source>PDF</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="69"/> + <source>Convert into PDF Format.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="70"/> + <source>HTML</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="71"/> + <source>Convert into HTML Format.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="72"/> + <source>XLSX</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="73"/> + <source>Convert into XLSX Format.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="83"/> + <source>mandatory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="85"/> + <source>Title (e.g. Feedback Questions SS2016)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="92"/> + <source>Convert</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="110"/> + <source>Error!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="159"/> + <source>Failed to create output directory.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="165"/> + <source>Failed to open "%1" for reading.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="199"/> + <source>Failed to find an available free name for "%1" please choose another folder.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>QObject</name> <message> <location filename="../cconvert_options.h" line="22"/>
--- a/src/main.cpp Tue Jul 19 12:18:57 2016 +0200 +++ b/src/main.cpp Tue Jul 19 12:19:45 2016 +0200 @@ -16,6 +16,7 @@ #include "strhelp.h" #include "converter.h" #include "cconvert_options.h" +#include "mainwindow.h" #include <QApplication> #include <QCommandLineParser> @@ -133,19 +134,29 @@ if (args.size()) { infile = args.first(); } - Converter conv(infile, parser.values("output"), - parser.value("title")); - - conv.start(); - conv.wait(); - const QStringList errors = conv.errors(); - if (errors.isEmpty()) { - return 0; + MainWindow *mainWin = Q_NULLPTR; + const QStringList outputs = parser.values("output"); + const QString title = parser.value("title"); + if (outputs.isEmpty() || title.isEmpty()) { + mainWin = new MainWindow(); + mainWin->setTitle(title); + mainWin->show(); + app.exec(); + delete mainWin; } else { - Q_FOREACH (const QString err, errors) { - qCritical() << err; + Converter conv(infile, parser.values("output"), + parser.value("title")); + + conv.start(); + conv.wait(); + const QStringList errors = conv.errors(); + if (!errors.isEmpty()) { + Q_FOREACH (const QString err, errors) { + qCritical() << err; + } + return 1; } } - return 1; + return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainwindow.cpp Tue Jul 19 12:19:45 2016 +0200 @@ -0,0 +1,234 @@ +/* Copyright (C) 2016 by ETH Zürich + * Software engineering by Intevation GmbH + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +/** @file See mainwindow.h */ +#include "mainwindow.h" + +#include "constants.h" +#include "converter.h" +#include "filenamerequester.h" + +#include <QApplication> +#include <QCheckBox> +#include <QDebug> +#include <QDialogButtonBox> +#include <QDesktopServices> +#include <QDir> +#include <QGroupBox> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> +#include <QMessageBox> +#include <QMimeData> +#include <QPushButton> +#include <QUrl> +#include <QStandardPaths> +#include <QStyle> +#include <QVBoxLayout> +#include <QWidget> +#include <QGridLayout> + +MainWindow::MainWindow() { + setupGUI(); + readSettings(); +#ifndef Q_OS_WIN + QIcon windowIcon = QIcon(ICON_NAME); + setWindowIcon(windowIcon); + setWindowTitle(QStringLiteral(APPNAME)); +#endif +} + +void MainWindow::setupGUI() { + auto vLay = new QVBoxLayout; + auto inoutLay = new QGridLayout; + auto inputLabel = new QLabel(tr("Input File:")); + inoutLay->addWidget(inputLabel, 0, 0); + mInputRequester = new FileNameRequester(QDir::Files); + mInputRequester->setExistingOnly(true); + inputLabel->setBuddy(mInputRequester); + + inoutLay->addWidget(mInputRequester, 0, 1); + + auto outLabel = new QLabel(tr("Export Folder:")); + inoutLay->addWidget(outLabel, 1, 0); + mOutRequester = new FileNameRequester(QDir::Dirs); + mOutRequester->setExistingOnly(false); + inoutLay->addWidget(mOutRequester, 1, 1); + outLabel->setBuddy(mOutRequester); + + vLay->addLayout(inoutLay); + + auto optionsGrp = new QGroupBox("Export Options:"); + auto optionsLay = new QVBoxLayout; + mPdfChk = new QCheckBox(tr("PDF")); + mPdfChk->setToolTip(tr("Convert into PDF Format.")); + mHtmlChk = new QCheckBox(tr("HTML")); + mHtmlChk->setToolTip(tr("Convert into HTML Format.")); + mXlsxChk = new QCheckBox(tr("XLSX")); + mXlsxChk->setToolTip(tr("Convert into XLSX Format.")); + + optionsLay->addWidget(mPdfChk); + optionsLay->addWidget(mHtmlChk); + optionsLay->addWidget(mXlsxChk); + optionsGrp->setLayout(optionsLay); + vLay->addWidget(optionsGrp); + + mTitleEdit = new QLineEdit; + auto titleLay = new QHBoxLayout; + auto titleLabel = new QLabel(QStringLiteral("<small>") + tr("mandatory") + + QStringLiteral("</small>")); + mTitleEdit->setPlaceholderText(tr("Title (e.g. Feedback Questions SS2016)")); + titleLay->addWidget(mTitleEdit); + titleLay->addWidget(titleLabel); + vLay->addLayout(titleLay); + vLay->addStretch(1); + auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); + mConvertBtn = btns->button(QDialogButtonBox::Ok); + mConvertBtn->setText(tr("Convert")); + vLay->addWidget(btns); + + auto widget = new QWidget; + widget->setLayout(vLay); + setCentralWidget(widget); + checkCompleted(); + + connect(mConvertBtn, &QPushButton::clicked, this, &MainWindow::doConvert); + connect(mPdfChk, &QCheckBox::stateChanged, this, &MainWindow::checkCompleted); + connect(mHtmlChk, &QCheckBox::stateChanged, this, &MainWindow::checkCompleted); + connect(mXlsxChk, &QCheckBox::stateChanged, this, &MainWindow::checkCompleted); + connect(mTitleEdit, &QLineEdit::textChanged, this, &MainWindow::checkCompleted); + connect(mInputRequester, &FileNameRequester::fileNameChanged, this, &MainWindow::checkCompleted); + connect(mOutRequester, &FileNameRequester::fileNameChanged, this, &MainWindow::checkCompleted); +} + +void MainWindow::showErrorMessage(const QString& errMsg) { + QMessageBox::warning(this, tr("Error!"), errMsg); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + mSettings.setValue("geometry", saveGeometry()); + mSettings.setValue("windowState", saveState()); + QMainWindow::closeEvent(event); +} + +void MainWindow::readSettings() { + if (mSettings.contains("geometry")) { + restoreGeometry(mSettings.value("geometry").toByteArray()); + restoreState(mSettings.value("windowState").toByteArray()); + } + + setInputFile(mSettings.value("lastInput", + QStandardPaths::writableLocation(DEFAULT_DIR)).toString()); + mOutRequester->setFileName(mSettings.value("lastOutput", + QStandardPaths::writableLocation(DEFAULT_DIR)).toString()); + + mPdfChk->setChecked(mSettings.value("pdfChk", true).toBool()); + mHtmlChk->setChecked(mSettings.value("htmlChk", false).toBool()); + mXlsxChk->setChecked(mSettings.value("xlsxChk", false).toBool()); +} + +void MainWindow::setInputFile(const QString& file) +{ + mInputRequester->setFileName(file); +} + +void MainWindow::setTitle(const QString& title) { + mTitleEdit->setText(title); +} + +void MainWindow::checkCompleted() { + if (!mTitleEdit->text().isEmpty() && + (mPdfChk->isChecked() || mXlsxChk->isChecked() || mHtmlChk->isChecked())) { + mConvertBtn->setEnabled(true); + } else { + mConvertBtn->setEnabled(false); + } +} + +void MainWindow::doConvert() { + /* Construct output names */ + QStringList outNames; + + QDir outDir(mOutRequester->fileName()); + if (!outDir.exists() && !outDir.mkpath(mOutRequester->fileName())) { + showErrorMessage(tr("Failed to create output directory.")); + return; + } + + const QFileInfo fi(mInputRequester->fileName()); + if (!fi.exists() || !fi.isReadable()) { + showErrorMessage(tr("Failed to open \"%1\" for reading.").arg(mInputRequester->fileName())); + return; + } + if (mPdfChk->isChecked()) { + outNames << outDir.absoluteFilePath(fi.baseName() + QStringLiteral(".pdf")); + } + if (mHtmlChk->isChecked()) { + outNames << outDir.absoluteFilePath(fi.baseName() + QStringLiteral(".html")); + } + if (mXlsxChk->isChecked()) { + outNames << outDir.absoluteFilePath(fi.baseName() + QStringLiteral(".xslx")); + } + + QStringList cleanedNames; + foreach (const QString &name, outNames) { + const QFileInfo fi2(name); + if (!fi2.exists()) { + cleanedNames << name; + continue; + } + /* File exists. Lets try a number. */ + bool replacementFound = false; + for (int i = 1; i < MAX_FILENAME_COUNT; i++) { + const QString newName = outDir.absoluteFilePath(QStringLiteral("%1_%2.%3").arg( + fi2.baseName()).arg(i).arg(fi2.suffix())); + const QFileInfo fi3(newName); + if (!fi3.exists()) { + qDebug() << "Using " << newName << " as replacement because other files exist."; + cleanedNames << newName; + replacementFound = true; + break; + } + } + if (!replacementFound) { + showErrorMessage(tr("Failed to find an available free name for \"%1\" please choose another folder.").arg(name)); + return; + } + } + + mSettings.setValue("lastInput", mInputRequester->fileName()); + mSettings.setValue("lastOutput", mOutRequester->fileName()); + mSettings.setValue("pdfChk", mPdfChk->isChecked()); + mSettings.setValue("xlsxChk", mXlsxChk->isChecked()); + mSettings.setValue("htmlChk", mHtmlChk->isChecked()); + + /* Convert away */ + Converter conv(mInputRequester->fileName(), cleanedNames, mTitleEdit->text()); + + conv.start(); + hide(); + qDebug() << "Waiting for conversion."; + conv.wait(); + qDebug() << "Conversion done."; + const QStringList errors = conv.errors(); + if (!errors.isEmpty()) { + Q_FOREACH (const QString err, errors) { + showErrorMessage(err); + } + } + + if (cleanedNames.size() == 1) { + qDebug() << "Opening: " << QUrl(QStringLiteral("file:///%1").arg(cleanedNames[0]), QUrl::TolerantMode); + QDesktopServices::openUrl(QUrl(QStringLiteral("file:///%1").arg(cleanedNames[0]), QUrl::TolerantMode)); + } else { + qDebug() << "Opening: " << QUrl(QStringLiteral("file:///%1").arg(outDir.path()), QUrl::TolerantMode); + QDesktopServices::openUrl(QUrl(QStringLiteral("file:///%1").arg(outDir.path()), QUrl::TolerantMode)); + } + + close(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainwindow.h Tue Jul 19 12:19:45 2016 +0200 @@ -0,0 +1,71 @@ +/* Copyright (C) 2016 by ETH Zürich + * Software engineering by Intevation GmbH + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + + +/** @file The Main UI class. + * + * Parent class of all dialogs and graphical user interaction. + */ +#include <QMainWindow> +#include <QSettings> + +class QPushButton; +class QLineEdit; +class QCheckBox; +class FileNameRequester; + +/** + * @class MainWindow + * @brief The mainwindow of the application. + * @details Holds the input fields and export options. + */ +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +protected: + /** @brief UI setup */ + void setupGUI(); + + /** @brief Cleanup and save the current state */ + virtual void closeEvent(QCloseEvent *event); + + /** @brief Restores the last window state */ + void readSettings(); + +public slots: + /** @brief directly set an input file. */ + void setInputFile(const QString& file); + + /** @brief Prefill title with @title. */ + void setTitle(const QString& title); + +protected slots: + /** @brief Show an error dialog to the user. */ + void showErrorMessage(const QString& errMsg); + + /** @brief check if all mandataory fields are set. */ + void checkCompleted(); + + /** @brief Do the actual conversion. */ + void doConvert(); + +private: + QSettings mSettings; + + FileNameRequester* mInputRequester; + FileNameRequester* mOutRequester; + QLineEdit *mTitleEdit; + QPushButton *mConvertBtn; + QCheckBox *mPdfChk, + *mXlsxChk, + *mHtmlChk; +};