From f0c7d97223adfb31b64b36477dff3795d65f2b20 Mon Sep 17 00:00:00 2001 From: hama Date: Mon, 18 Jan 2016 00:42:57 +0100 Subject: [PATCH] reimgconvert works --- appl/reimgconvert/ReImgConvert.html | 88 +++++++++++++++ appl/reimgconvert/converter.cpp | 101 ++++++++---------- appl/reimgconvert/converter.hpp | 2 +- appl/reimgconvert/main.cpp | 1 - appl/reimgconvert/mainwindow.cpp | 159 +++++++++++++--------------- appl/reimgconvert/mainwindow.hpp | 20 ++-- appl/reimgconvert/mainwindow.ui | 9 +- appl/reimgconvert/reimgconvert.pro | 3 + base/ReFileUtils.cpp | 23 +++- base/ReFileUtils.hpp | 1 + base/ReQStringUtils.cpp | 57 ++++++++++ base/ReQStringUtils.hpp | 2 + base/ReTest.cpp | 2 - cunit/allTests.cpp | 4 +- cunit/cuReQStringUtils.cpp | 29 +++++ gui/ReGuiQueue.hpp | 4 +- gui/ReStateStorage.cpp | 122 +++++++++++++++++---- gui/ReStateStorage.hpp | 22 ++++ 18 files changed, 450 insertions(+), 199 deletions(-) create mode 100644 appl/reimgconvert/ReImgConvert.html diff --git a/appl/reimgconvert/ReImgConvert.html b/appl/reimgconvert/ReImgConvert.html new file mode 100644 index 0000000..f32ef37 --- /dev/null +++ b/appl/reimgconvert/ReImgConvert.html @@ -0,0 +1,88 @@ + + + + +
Documentation of ReImgConvert
+

Release Notes

+

+

+

+ +

User Manual

+

Purpose:

+

This program allows to convert image files (pictures) into another format.

+

Conversion is done for the dimensions (width/height) and/or the quality/file +size.

+ +

Simple Mode and Detailed Mode

+

There are two modes of processing: +

+

+ +

Templates

+

For the simple mode there are predefined limits called "templates". +Chosing one template with the combobox the fields "max. width" and "max height" +will be filled with these limits. +

+ +

Conversion Details

+ +

If an image is lower than the given limits it will be copied without +conversion.

+

This program is only the GUI of the image conversion program "convert" +from the package ImageMagick.

+

Installation

+ +

Copy reimgconvert into a directory inserted in the execution +path, e.g. /usr/local/bin and give it the right "executable".

+

Install the packages qt5 and imagemagick.

+ +

Requirements

+

+

+

+ +

Program Documentation

+ +

Programming Features

+ +

This is a example for a complete QT application with the following features: +

+

+ +

(Un)License: Public Domain

+ +

You can use and modify this file without any restriction.
+Do what you want.
+No warranties and disclaimer of any damages.
+More info: http://unlicense.org
+The latest sources: https://github.com/republib. +

+ + + diff --git a/appl/reimgconvert/converter.cpp b/appl/reimgconvert/converter.cpp index 0eb8c5f..50bbde8 100644 --- a/appl/reimgconvert/converter.cpp +++ b/appl/reimgconvert/converter.cpp @@ -34,20 +34,9 @@ * * If an image is lower than the given limits it will be copied without * conversion - * This program is only the GUI of an Perl script which does the conversion. - * The script uses the - * - * @section install_sec Installation - * - * Copy reimgconvert into a directory inserted in the executable - * path, e.g. /usr/local/bin and give them the right "executable". - * - * @subsection req_sec Requirements - * - * - * + * This program is only the GUI of the image conversion program "convert" + * from the package ImageMagick + * * * @section prog_sec Programming Features * * This is a example for a complete QT application with the following features: @@ -57,9 +46,7 @@ *
  • Threads
  • *
  • Using GUI wizzard
  • *
  • Abstract class as interface
  • - *
  • Calling an external script
  • - *
  • Lists the output of the external list line by line at the moment - * the line is created
  • + *
  • Calling an external program
  • * * * @section version_sec Release Notes @@ -74,7 +61,7 @@ * * You can use and modify this file without any restriction. * There is no warranty. - * You also can use the licence from http://www.wtfpl.net/. + * You also can use the licence from http://unlicense.org * The original sources can be found on https://github.com/republib */ @@ -130,10 +117,7 @@ Converter::Converter(const QString& directory, const QString& targetDirectory, int landscapeY, int portraitX, int portraitY, int squareX, int quality, MainWindow* mainWindow) : m_dir(directory), - m_targetDir( - targetDirectory.indexOf(QDir::separator()) >= 0 ? - targetDirectory : - directory + QDir::separator() + targetDirectory), + m_targetDir(targetDirectory), m_sourcePattern(sourcePattern), m_targetType(targetType), m_landscapeWidth(landscapeX), @@ -151,7 +135,7 @@ Converter::Converter(const QString& directory, const QString& targetDirectory, m_simpleMode(simpleConversion), m_maxWidth(maxWidth), m_maxHeight(maxHeight) - { +{ } /** @@ -181,7 +165,7 @@ void addArg(QStringList& args, const char* prefix, int value){ * @param info an info about the change */ void Converter::changeState(Converter::State state, const QString& info){ - m_mainWindows->on_threadStateChanged(state, info); + m_mainWindows->onThreadStateChanged(state, info); } /** @@ -291,12 +275,13 @@ bool Converter::handleUserDefined(int width, int height, int& widthNew, int& hei /** * @brief Converts one file. * - * @param source the file's name with path - * @param target the new filename with path - * @param size the size of the file (in byte) + * @param source the file's name with path + * @param target the new filename with path + * @param size the size of the file (in byte) + * @param sizeTarget OUT: the file size of the target */ void Converter::convertOneFile(const QString& source, const QString& target, - qint64 size){ + qint64 size, qint64& sizeTarget){ int width = 0; int height = 0; QString info; @@ -304,22 +289,23 @@ void Converter::convertOneFile(const QString& source, const QString& target, if (readProperties(source, width, height, info)){ int widthNew = width; int heightNew = height; - bool doConvert; if (m_simpleMode) - doConvert = handleSimple(width, height, widthNew, heightNew); + handleSimple(width, height, widthNew, heightNew); else - doConvert = handleUserDefined(width, height, widthNew, heightNew); + handleUserDefined(width, height, widthNew, heightNew); + QString node = ReFileUtils::nodeOf(source); log( - source + " " + info + " " + sizeToString(size) + node + " " + info + " " + ReQStringUtils::readableSize(size) + QString(" -> %1x%2 ").arg(widthNew).arg(heightNew)); convert(source, target, width, height, widthNew, heightNew, m_quality); struct stat info; - if (stat(I18N::s2b(target).constData(), &info) == 0) - m_mainWindows->logAppendLast(sizeToString(info.st_size) + " "); + if (stat(I18N::s2b(target).constData(), &info) == 0){ + sizeTarget = info.st_size; + m_mainWindows->logAppendLast(ReQStringUtils::readableSize(sizeTarget) + + " " + ReQStringUtils::readableDuration( + clock() - start)); + } } - m_mainWindows->logAppendLast( - QString("").sprintf("%.3f sec", - double(clock() - start) / CLOCKS_PER_SEC)); } /** @@ -421,6 +407,10 @@ bool Converter::readPropertiesByIdentify(const QString& name, int& width, int& h */ void Converter::run(){ QString msg; + clock_t start = clock(); + uint64_t preSize = 0; + uint64_t postSize = 0; + uint64_t lengthTarget = 0; int no = 0; try{ if (!m_dir.exists()) @@ -458,7 +448,16 @@ void Converter::run(){ QString nodeTarget = ReFileUtils::replaceExtension(node, "." + m_targetType); QString target = m_targetDir.absoluteFilePath(nodeTarget); - convertOneFile(path, target, length); + convertOneFile(path, target, length, lengthTarget); + preSize += length; + postSize += lengthTarget; + m_mainWindows->setStatusMessage(LOG_INFO, + tr("%1 file(s) %2 -> %3 (%4 %)") + .arg(no) + .arg(ReQStringUtils::readableSize(preSize)) + .arg(ReQStringUtils::readableSize(postSize)) + .arg(postSize * 100.0 / max(1, preSize), 0, + 'f', 2)); } } changeState(Converter::STATE_SUB_TASK_STOPPED, msg); @@ -467,7 +466,12 @@ void Converter::run(){ QObject::tr("Execution stopped because of error(s): ") + exc.message()); } - msg = QObject::tr("%1 file(s) converted").arg(no); + preSize = max(1, preSize); + msg = QObject::tr("%1 file(s) converted, %2 -> %3 (%4 %), duration: %5").arg(no) + .arg(ReQStringUtils::readableSize(preSize)) + .arg(ReQStringUtils::readableSize(postSize)) + .arg(postSize * 100.0 / preSize, 0, 'f', 2) + .arg(ReQStringUtils::readableDuration(clock() - start)); changeState(Converter::STATE_READY, msg); } @@ -494,22 +498,3 @@ QString findScript(const QString& node){ } return rc; } -/** - * @brief Converts the size into a human readable string. - * - * @param size the size in bytes - * @return the size as human readable string, e.g. "2MiByte" - */ -QString sizeToString(qint64 size){ - QString rc; - if (size < 10 * 1024) - rc.sprintf("%d Bytes", (int) size); - else if (size < qint64(10 * 1024 * 1024)) - rc.sprintf("%d KiBytes", (int) (size / 1024)); - else if (size < qint64(10 * 1024 * 1024) * 1024) - rc.sprintf("%d MiBytes", (int) (size / 1024 / 1024)); - else - rc.sprintf("%d GiBytes", (int) (size / 1024 / 1024 / 1024)); - return rc; -} - diff --git a/appl/reimgconvert/converter.hpp b/appl/reimgconvert/converter.hpp index c0585d8..0ba19a1 100644 --- a/appl/reimgconvert/converter.hpp +++ b/appl/reimgconvert/converter.hpp @@ -65,7 +65,7 @@ protected: void convert(const QString& source, const QString& target, int width, int height, int widthNew, int heightNew, int quality); void convertOneFile(const QString& source, const QString& target, - qint64 size); + qint64 size, qint64& sizeTarget); bool handleSimple(int width, int height, int& widthNew, int& heightNew); bool handleUserDefined(int width, int height, int& widthNew, int& heightNew); bool readProperties(const QString& name, int &width, int &height, diff --git a/appl/reimgconvert/main.cpp b/appl/reimgconvert/main.cpp index 051c685..0d45da3 100644 --- a/appl/reimgconvert/main.cpp +++ b/appl/reimgconvert/main.cpp @@ -19,7 +19,6 @@ int main(int argc, char *argv[]){ QString homeDir = argc > 1 ? argv[1] : ""; QApplication a(argc, argv); MainWindow w(homeDir); - QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(closing())); w.show(); return a.exec(); } diff --git a/appl/reimgconvert/mainwindow.cpp b/appl/reimgconvert/mainwindow.cpp index c258dda..5f8006d 100644 --- a/appl/reimgconvert/mainwindow.cpp +++ b/appl/reimgconvert/mainwindow.cpp @@ -32,24 +32,25 @@ const QString VERSION("2016.01.15"); * @param parent NULL or the parent (who destroys the objects at the end) */ MainWindow::MainWindow(const QString& homeDir, QWidget *parent) : - QMainWindow(parent), - m_homeDir(homeDir), - m_storageFile(), + ReHomeApplication("reimgconvert", homeDir, 2, 100100100, parent), ui(new Ui::MainWindow), m_converter(NULL), m_logger(), m_guiQueue(), m_guiTimer(new QTimer(this)){ ui->setupUi(this); - initializeHome(); - m_logger.buildStandardAppender(I18N::s2b(m_homeDir + OS_SEPARATOR_STR - + "reimgconvert").constData()); startStop(false); m_statusMessage = new QLabel(tr("Welcome at reimgconvert")); statusBar()->addWidget(m_statusMessage); + connect(ui->actionStart, SIGNAL(triggered()), this, + SLOT(onConvert())); + connect(ui->actionStop, SIGNAL(triggered()), this, + SLOT(onStop())); connect(ui->actionSelectTarget, SIGNAL(triggered()), this, SLOT(selectTarget())); connect(ui->actionClear, SIGNAL(triggered()), this, SLOT(clear())); + connect(ui->pushButtonConvert, SIGNAL(clicked()), this, SLOT(onConvert())); + connect(ui->pushButtonStop, SIGNAL(clicked()), this, SLOT(onStop())); connect(ui->pushButtonClear, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->actionSelectTarget, SIGNAL(triggered()), this, SLOT(selectTarget())); @@ -61,8 +62,7 @@ MainWindow::MainWindow(const QString& homeDir, QWidget *parent) : SLOT(selectSource())); connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(about())); connect(ui->comboBoxTemplate, SIGNAL(currentIndexChanged(const QString&)), - this, SLOT(on_templateChangeIndex(const QString&))); - connect(ui->pushButtonActivate, SIGNAL(clicked()), this, SLOT(activate())); + this, SLOT(onTemplateChangeIndex(const QString&))); connect(m_guiTimer, SIGNAL(timeout()), this, SLOT(guiTimerUpdate())); restoreState(); m_guiTimer->start(100); @@ -85,17 +85,10 @@ void MainWindow::about(){ } /** - * Slot when the pushbutton "activate" is clicked. + * Actions while closing the application. */ -void MainWindow::activate(){ - QString theTemplate = comboText(ui->comboBoxTemplate); - QRegularExpression rexpr("(\\d+)x(\\d+)"); - QRegularExpressionMatch matcher = rexpr.match(theTemplate); - if (matcher.hasMatch()){ - int width = atoi(matcher.captured(1).toLatin1()); - int height = atoi(matcher.captured(2).toLatin1()); - setMaxDimensions(width, height); - } +void MainWindow::aboutToQuit(){ + saveState(); } /** @@ -106,13 +99,6 @@ void MainWindow::clear() ui->listWidget->clear(); } -/** - * Actions while closing the application. - */ -void MainWindow::closing(){ - saveState(); -} - /** * Issues an error message. * @@ -146,6 +132,16 @@ void MainWindow::guiTimerUpdate() case ReGuiQueueItem::LogMessage: say(LOG_INFO, item.m_value); break; + case ReGuiQueueItem::StatusLine: + setStatusMessage(false, item.m_value); + break; + case ReGuiQueueItem::ListAppendToCurrent: + { + QListWidgetItem* item2 = reinterpret_cast( + item.m_widget)->currentItem(); + item2->setText(item2->text() + " " + item.m_value); + break; + } default: say(LOG_ERROR, "unknown item type: " + QString::number(item.m_type) + " " + item.m_value); @@ -155,26 +151,6 @@ void MainWindow::guiTimerUpdate() } } } -/** - * initializeHomeializes the program home directory. - */ -void MainWindow::initializeHome(){ - if (m_homeDir.isEmpty()){ - m_homeDir = QDir::home().absoluteFilePath(".reimgconvert"); - } - - QDir home(m_homeDir); - if (!home.exists()){ - if (!home.mkpath(m_homeDir)){ - m_homeDir = home.tempPath() + "/.reimgconvert"; - home.mkpath(m_homeDir); - } - } - if (!m_homeDir.endsWith("/")) - m_homeDir += "/"; - m_storageFile = m_homeDir + "state.conf"; -} - /** * @brief Logs a message @@ -197,17 +173,41 @@ bool MainWindow::log(const QString& message){ * @return true */ bool MainWindow::logAppendLast(const QString& message){ - m_guiQueue.pushBack(ReGuiQueueItem(ReGuiQueueItem::ListEnd, ui->listWidget, message)); - QListWidgetItem* item = ui->listWidget->item(0); - item->setText(item->text() + " " + message); + m_guiQueue.pushBack(ReGuiQueueItem(ReGuiQueueItem::ListAppendToCurrent, + ui->listWidget, message)); return true; } /** - * @brief Handles the click on the button "stop". + * @brief Handles the button click on "convert". */ -void MainWindow::on_pushButtonStop_clicked(){ - m_converter->stop(); +void MainWindow::onConvert(){ + startStop(true); + delete m_converter; + m_errors = 0; + int landscapeX = comboInt(ui->comboBoxLandscapeX, 0, "*", 0); + int landscapeY = comboInt(ui->comboBoxLandscapeY, 0, "*", 0); + int portraitX = comboInt(ui->comboBoxPortraitX, 0, "*", 0); + int portraitY = comboInt(ui->comboBoxPortraitY, 0, "*", 0); + int squareX = comboInt(ui->comboBoxSquareX, 0); + int quality = comboInt(ui->comboBoxQuality, 75); + int maxWidth = comboInt(ui->comboBoxMaxWidth, 0); + int maxHeight = comboInt(ui->comboBoxMaxHeight, 0); + QRegularExpressionMatch match = QRegularExpression("\\dx\\d").match( + ui->comboBoxTemplate->currentText()); + bool simpleConversion = match.hasMatch(); + if (m_errors == 0){ + saveState(); + m_converter = new Converter(ui->comboBoxSourceDir->currentText(), + ui->comboBoxTargetDir->currentText(), + ui->comboBoxSourcePattern->currentText(), + ui->comboBoxDestType->currentText(), + simpleConversion, maxWidth, maxHeight, + landscapeX, landscapeY, portraitX, + portraitY, squareX, quality, this); + // start the thread: + m_converter->start(); + } } /** @@ -216,7 +216,7 @@ void MainWindow::on_pushButtonStop_clicked(){ * Shows a selection dialog for directories and sets the source directory * onto the selected directory. */ -void MainWindow::on_pushButtonFileSelect_clicked(){ +void MainWindow::onSourceFileSelect(){ QFileDialog selection; selection.setFileMode(QFileDialog::DirectoryOnly); QString dir = ui->comboBoxSourceDir->currentText(); @@ -226,12 +226,19 @@ void MainWindow::on_pushButtonFileSelect_clicked(){ ui->comboBoxSourceDir->setCurrentText(selection.selectedFiles().at(0)); } +/** + * @brief Handles the click on the button "stop". + */ +void MainWindow::onStop(){ + m_converter->stop(); +} + /** * Slot when the value of the template combobox has been changed. * * @param text the new selected combobox text */ -void MainWindow::on_templateChangeIndex(const QString & text){ +void MainWindow::onTemplateChangeIndex(const QString & text){ QRegularExpression rexpr("(\\d+)x(\\d+)"); QRegularExpressionMatch match = rexpr.match(text); int width = match.captured(1).toInt(); @@ -239,38 +246,6 @@ void MainWindow::on_templateChangeIndex(const QString & text){ setMaxDimensions(width, height); } -/** - * @brief Handles the button click on "convert". - */ -void MainWindow::on_pushButtonConvert_clicked(){ - startStop(true); - delete m_converter; - m_errors = 0; - int landscapeX = comboInt(ui->comboBoxLandscapeX, 0, "*", 0); - int landscapeY = comboInt(ui->comboBoxLandscapeY, 0, "*", 0); - int portraitX = comboInt(ui->comboBoxPortraitX, 0, "*", 0); - int portraitY = comboInt(ui->comboBoxPortraitY, 0, "*", 0); - int squareX = comboInt(ui->comboBoxSquareX, 0); - int quality = comboInt(ui->comboBoxQuality, 75); - int maxWidth = comboInt(ui->comboBoxMaxWidth, 0); - int maxHeight = comboInt(ui->comboBoxMaxHeight, 0); - QRegularExpressionMatch match = QRegularExpression("\\dx\\d").match( - ui->comboBoxTemplate->currentText()); - bool simpleConversion = match.hasMatch(); - if (m_errors == 0){ - m_converter = new Converter(ui->comboBoxSourceDir->currentText(), - ui->comboBoxLandscapeX->currentText(), - ui->comboBoxSourcePattern->currentText(), - ui->comboBoxDestType->currentText(), - simpleConversion, maxWidth, maxHeight, - landscapeX, landscapeY, portraitX, - portraitY, squareX, quality, this); - // start the thread: - m_converter->start(); - } - -} - /** * @brief Handles the event "thread changed". * @@ -279,7 +254,7 @@ void MainWindow::on_pushButtonConvert_clicked(){ * @param state the new state of the thread * @param info info about the new state. Not used */ -void MainWindow::on_threadStateChanged(Converter::State state, const QString& info){ +void MainWindow::onThreadStateChanged(Converter::State state, const QString& info){ switch (state) { case Converter::STATE_READY: m_guiQueue.pushBack(ReGuiQueueItem(ReGuiQueueItem::ReadyMessage, NULL, info)); @@ -401,6 +376,18 @@ void MainWindow::selectTarget(){ ui->comboBoxTargetDir->setCurrentText(dir); } +/** + * Writes a text to the status line. + * + * Note: this method is called from a non-main thread + * + * @param message the text to set + */ +void MainWindow::setRemoteStatus(const QString& message){ + m_guiQueue.pushBack(ReGuiQueueItem(ReGuiQueueItem::StatusLine, + NULL, message)); +} + /** * Writes a text to the status line. * diff --git a/appl/reimgconvert/mainwindow.hpp b/appl/reimgconvert/mainwindow.hpp index cef44ba..05ececf 100644 --- a/appl/reimgconvert/mainwindow.hpp +++ b/appl/reimgconvert/mainwindow.hpp @@ -23,7 +23,7 @@ namespace Ui { class MainWindow; } -class MainWindow: public QMainWindow, +class MainWindow: public ReHomeApplication, public ReGuiValidator, public ConvertLogger { Q_OBJECT @@ -32,36 +32,32 @@ public: explicit MainWindow(const QString& homeDir, QWidget *parent = 0); ~MainWindow(); -public slots: - void closing(); public: + virtual void aboutToQuit(); bool error(const QString& message); bool log(const QString& message); bool logAppendLast(const QString& message); - void on_threadStateChanged(Converter::State state, const QString& info); + void onThreadStateChanged(Converter::State state, const QString& info); + void setRemoteStatus(const QString& message); void setStatusMessage(bool error, const QString& message); public: virtual bool say(ReLoggerLevel level, const QString& message); private: - void initializeHome(); void restoreState(); void saveState(); void setMaxDimensions(int maxWidth, int maxHeight);public slots: void startStop(bool isStart); private slots: - void activate(); void about(); void clear(); void guiTimerUpdate(); - void on_pushButtonFileSelect_clicked(); - void on_pushButtonStop_clicked(); - void on_pushButtonConvert_clicked(); - void on_templateChangeIndex(const QString& text); + void onConvert(); + void onSourceFileSelect(); + void onStop(); + void onTemplateChangeIndex(const QString& text); void selectSource(); void selectTarget(); private: - QString m_homeDir; - QString m_storageFile; Ui::MainWindow *ui; Converter* m_converter; QLabel* m_statusMessage; diff --git a/appl/reimgconvert/mainwindow.ui b/appl/reimgconvert/mainwindow.ui index 5ed2f0c..21bcb38 100644 --- a/appl/reimgconvert/mainwindow.ui +++ b/appl/reimgconvert/mainwindow.ui @@ -71,7 +71,7 @@ - 315 + 316 0 @@ -213,13 +213,6 @@ - - - - &Activate template - - - diff --git a/appl/reimgconvert/reimgconvert.pro b/appl/reimgconvert/reimgconvert.pro index d2d7863..4aa94bf 100644 --- a/appl/reimgconvert/reimgconvert.pro +++ b/appl/reimgconvert/reimgconvert.pro @@ -45,3 +45,6 @@ TRANSLATIONS = reimgconvert_de.ts CODECFORSRC = UTF-8 OTHER_FILES += + +DISTFILES += \ + ReImgConvert.html diff --git a/base/ReFileUtils.cpp b/base/ReFileUtils.cpp index 2c49d40..8c3c375 100644 --- a/base/ReFileUtils.cpp +++ b/base/ReFileUtils.cpp @@ -100,7 +100,7 @@ bool ReFileUtils::deleteTree(const QString& path, bool withBase, if (info.isDir()) { if (!deleteTree(full, false, logger)) rc = false; - else if (_rmdir(I18N::s2b(full)) != 0) { + else if (_rmdir(I18N::s2b(full)) != 0) { rc = false; if (logger != NULL) logger->logv(LOG_ERROR, LOC_DELETE_TREE_1, @@ -118,7 +118,7 @@ bool ReFileUtils::deleteTree(const QString& path, bool withBase, } } } - if (withBase && (_rmdir(I18N::s2b(path))) != 0) { + if (withBase && (_rmdir(I18N::s2b(path))) != 0) { rc = false; logger->logv(LOG_ERROR, LOC_DELETE_TREE_3, "cannot delete directory (%d): %s", errno, I18N::s2b(path).constData()); @@ -178,7 +178,7 @@ QByteArray ReFileUtils::extensionOf(const char* filename) { if ((cc = filename[ix - 1]) != '/' && cc != '\\') rc.append(filename + ix); break; - } else if (cc == '\\' || cc == '/') + } else if (cc == '\\' || cc == '/') break; ix--; } @@ -222,6 +222,21 @@ bool ReFileUtils::isAbsolutPath(const char* path) { return rc; } +/** + * Checks whether path is a root directory. + * + * @param path the path to inspect + * @return true: path is a root directory, e.g. "/" or "c:\\" + */ +bool ReFileUtils::isRootDir(const char* path) +{ +#if defined __linux__ + return path[0] == OS_SEPARATOR && path[1] == '\0'; +#elif defined WIN32 + return isalpha(path[0]) && path[1] == ':' && path[2] == '\\' && path[3] = '\0'; +#endif +} + /** * Extracts the node of a filename. * @@ -659,7 +674,7 @@ QByteArray ReFileUtils::tempFile(const char* node, const char* parent, rc += node; struct stat info; if (deleteIfExists && stat(rc.constData(), &info) == 0) - _unlink(rc.constData()); + _unlink(rc.constData()); return rc; } diff --git a/base/ReFileUtils.hpp b/base/ReFileUtils.hpp index a378ce8..23b021e 100644 --- a/base/ReFileUtils.hpp +++ b/base/ReFileUtils.hpp @@ -36,6 +36,7 @@ public: static QByteArray extensionOf(const char* filename); static bool isAbsolutPath(const QString& path); static bool isAbsolutPath(const char* path); + static bool isRootDir(const char* path); /** Returns a path with native separators. * QT under windows can operator with 2 separators: '\\' and '/'. * '\\' is the native separator. diff --git a/base/ReQStringUtils.cpp b/base/ReQStringUtils.cpp index 785a072..2458f1f 100644 --- a/base/ReQStringUtils.cpp +++ b/base/ReQStringUtils.cpp @@ -473,6 +473,63 @@ void ReQStringUtils::skipExpected(const ReString& text, QChar expected, } } +/** + * Returns a readable string for a duration given by clock_t. + * + * @param duration a duration measured by clock() + * + * @return a string describing the duration. + */ +QString ReQStringUtils::readableDuration(clock_t duration){ + QString rc; + char buffer[128]; + int cl_per_sec = CLOCKS_PER_SEC; + double duration2 = (double) duration / CLOCKS_PER_SEC; + if (duration2 < 60.0){ + rc = QString::number(duration2, 'f', 3) + " sec"; + } else if (duration2 < 3600.0){ + int duration3 = int(duration2); + snprintf(buffer, sizeof buffer, "%d:%02d", + duration3 / 60, duration3 % 60); + rc = buffer; + } else if (duration2 < 3600.0 * 24){ + int duration3 = int(duration2); + snprintf(buffer, sizeof buffer, "%d:%02d:%02d", + duration3 / 3600, duration3 % 3600 / 60, + duration3 % 60); + rc = buffer; + } else { + int duration3 = int(duration2); + snprintf(buffer, sizeof buffer, "%d:%02d:%02d:%02d", + int(duration3 / (3600*24)), + int(duration3 % (3600*24) / 3600), int(duration3 % 3600 / 60), + int(duration3 % 60)); + rc = buffer; + } + return rc; +} + +/** + * Returns a readable string for a filesize. + * + * @param filesize the filesize to convert + * @return a filesize expression (number and unit) with at least 3 significant digits. + */ +QString ReQStringUtils::readableSize(int64_t filesize){ + QString rc; + if (filesize < 1000){ + rc = QString::number(filesize) + " B"; + } else if (filesize < 1000*1000){ + rc = QString::number(filesize / 1e3, 'f', 3) + " kB"; + } else if (filesize < 1000*1000*1000){ + rc = QString::number(filesize / 1e6, 'f', 3) + " MB"; + } else if (filesize < int64_t(1000)*1000*1000*1000){ + rc = QString::number(filesize / 1e9, 'f', 3) + " GB"; + } else { + rc = QString::number(filesize / 1e12, 'f', 3) + " TB"; + } + return rc; +} /** * @brief Converts a ReString into an utf-8 string * diff --git a/base/ReQStringUtils.hpp b/base/ReQStringUtils.hpp index f90372a..eca88b8 100644 --- a/base/ReQStringUtils.hpp +++ b/base/ReQStringUtils.hpp @@ -71,6 +71,8 @@ public: return path; #endif } + static QString readableDuration(clock_t duration); + static QString readableSize(int64_t filesize); static bool replacePlaceholders(QString& text, const QMap& placeholders, QString* error); static void skipExpected(const ReString& text, QChar expected, int& index, diff --git a/base/ReTest.cpp b/base/ReTest.cpp index 831279c..860914b 100644 --- a/base/ReTest.cpp +++ b/base/ReTest.cpp @@ -168,8 +168,6 @@ bool ReTest::assertEquals(const ReString& expected, const ReString& current, */ bool ReTest::assertEquals(const char* expected, const char* current, const char* file, int lineNo) { - int len1 = strlen(expected); - int len2 = strlen(current); bool equal = strcmp(expected, current) == 0; if (!equal) { if (strchr(expected, '\n') != NULL || strchr(current, '\n')) { diff --git a/cunit/allTests.cpp b/cunit/allTests.cpp index 998ec7b..0bc2b41 100644 --- a/cunit/allTests.cpp +++ b/cunit/allTests.cpp @@ -44,12 +44,12 @@ static void testBase() { void testReWriter(); void testReFile(); void testReMatcher(); + testReQStringUtil(); testReProgArgs(); testReProcess(); testReRandomizer(); testReFileUtils(); testReMatcher(); - testReQStringUtil(); testReFile(); if (s_allTest) { testReProcess(); @@ -101,9 +101,9 @@ static void testOs() { testReCryptFileSystem(); } void allTests() { + testBase(); testOs(); testExpr(); - testBase(); testGui(); if (s_allTest) { testBase(); diff --git a/cunit/cuReQStringUtils.cpp b/cunit/cuReQStringUtils.cpp index f32ae4b..35e523d 100644 --- a/cunit/cuReQStringUtils.cpp +++ b/cunit/cuReQStringUtils.cpp @@ -205,8 +205,37 @@ public: list.clear(); checkEqu("", ReQStringUtils::longestPrefix(list)); } + void testReadableSize(){ + checkEqu("0 B", ReQStringUtils::readableSize(0)); + checkEqu("10 B", ReQStringUtils::readableSize(10)); + checkEqu("999 B", ReQStringUtils::readableSize(999)); + checkEqu("1.000 kB", ReQStringUtils::readableSize(1000)); + checkEqu("1.001 kB", ReQStringUtils::readableSize(1001)); + checkEqu("999.999 kB", ReQStringUtils::readableSize(999999)); + checkEqu("1.000 MB", ReQStringUtils::readableSize(1000000)); + checkEqu("1.001 MB", ReQStringUtils::readableSize(1001000)); + checkEqu("999.999 MB", ReQStringUtils::readableSize(999999000)); + checkEqu("1.000 GB", ReQStringUtils::readableSize(1000000000)); + checkEqu("1.001 GB", ReQStringUtils::readableSize(1001000000)); + checkEqu("999.999 GB", ReQStringUtils::readableSize(999999000000L)); + checkEqu("1.000 TB", ReQStringUtils::readableSize(1000000000000L)); + checkEqu("1.001 TB", ReQStringUtils::readableSize(1001000000000l)); + } + void testReadableDuration(){ +#define clf(sec) clock_t(clock_t((sec) * CLOCKS_PER_SEC)) + checkEqu("0.000 sec", ReQStringUtils::readableDuration(clf(0.0))); + checkEqu("1.234 sec", ReQStringUtils::readableDuration(clf(1.234))); + checkEqu("59.250 sec", ReQStringUtils::readableDuration(clf(59.250))); + checkEqu("1:01", ReQStringUtils::readableDuration(clf(61.251))); + checkEqu("59:59", ReQStringUtils::readableDuration(clf(59*60+59.499))); + checkEqu("1:00:01", ReQStringUtils::readableDuration(clf(1*3600+1.99))); + checkEqu("23:59:59", ReQStringUtils::readableDuration(clf(24*3600-0.1))); + checkEqu("1:02:03:04", ReQStringUtils::readableDuration(clf(24*3600+2*3600+3*60+4.0))); + } virtual void runTests(void) { + testReadableSize(); + testReadableDuration(); testLongestPrefix(); testLengtOfTime(); testLengtOfDate(); diff --git a/gui/ReGuiQueue.hpp b/gui/ReGuiQueue.hpp index 9186a17..23fa19d 100644 --- a/gui/ReGuiQueue.hpp +++ b/gui/ReGuiQueue.hpp @@ -15,8 +15,8 @@ class ReGuiQueueItem { public: enum WidgetType { - Undef, LabelText, NewTableRow, ListEnd, - LogMessage, ReadyMessage, + Undef, LabelText, NewTableRow, ListEnd, ListAppendToCurrent, + LogMessage, ReadyMessage, StatusLine, UserDefined1, UserDefined2 }; diff --git a/gui/ReStateStorage.cpp b/gui/ReStateStorage.cpp index 50a68f3..e21b7e8 100644 --- a/gui/ReStateStorage.cpp +++ b/gui/ReStateStorage.cpp @@ -12,6 +12,7 @@ #include "base/rebase.hpp" #include "gui/regui.hpp" #include +#include enum { LOC_INIT_FOR_WRITE_1 = LOC_FIRST_OF(LOC_STATESTORAGE), // 12001 LOC_INIT_FOR_READ_1, // 12002 @@ -199,22 +200,31 @@ bool ReStateStorage::initForWrite() { void ReStateStorage::restore(QComboBox* combo, const char* name, bool withCurrentText) { if (initForRead()) { - QByteArray keyPrefix = fullname(name) + ".item"; - int ix = 0; - QByteArray key; - while (true) { - key = keyPrefix + QByteArray::number(ix); - if (!m_map.contains(key)) - break; - combo->addItem(m_map.value(key)); - ix++; - } - key = fullname(name) + ".text"; - if (!withCurrentText) - combo->setCurrentText(""); - else { - if (m_map.contains(key)) - combo->setCurrentText(m_map.value(key)); + if (! combo->isEditable()){ + QByteArray key = fullname(name) + ".index"; + if (m_map.contains(key)){ + QString value = m_map[key]; + int index = atoi(value.toLatin1().constData()); + combo->setCurrentIndex(index); + } + } else { + QByteArray keyPrefix = fullname(name) + ".item"; + int ix = 0; + QByteArray key; + while (true) { + key = keyPrefix + QByteArray::number(ix); + if (!m_map.contains(key)) + break; + combo->addItem(m_map.value(key)); + ix++; + } + key = fullname(name) + ".text"; + if (!withCurrentText) + combo->setCurrentText(""); + else { + if (m_map.contains(key)) + combo->setCurrentText(m_map.value(key)); + } } } } @@ -300,15 +310,19 @@ void ReStateStorage::store(const QComboBox* combo, const char* name, bool withCurrentText) { if (initForWrite()) { QByteArray key(fullname(name)); - for (int ii = 0; ii < combo->count(); ii++) { - *m_stream << key << ".item" << ii << "=" << combo->itemText(ii) - << endl; - } - if (withCurrentText) { - *m_stream << key << ".text=" << combo->currentText() << endl; + if (! combo->isEditable()){ + *m_stream << key << ".index=" << combo->currentIndex() << endl; + } else { + for (int ii = 0; ii < combo->count(); ii++) { + *m_stream << key << ".item" << ii << "=" << combo->itemText(ii) + << endl; + } + if (withCurrentText) { + *m_stream << key << ".text=" << combo->currentText() << endl; + } } - m_stream->flush(); } + m_stream->flush(); } /** @@ -340,3 +354,65 @@ void ReStateStorage::store(const QMainWindow* window) { m_stream->flush(); } } + +/** + * Constructor. + * + * @param applicationName the name of the application. Defines the node + * of the home directory + * @param homeDirBase "": the user's home directory will be used
    + * otherwise: the home directory will created in this + * directory + * @param maxLogFiles max. count of (rotating) log files + * @param maxLogSize max. size of the (rotating) log files + */ +ReHomeApplication::ReHomeApplication(const char* applicationName, + const QString& homeDirBase, + int maxLogFiles, int maxLogSize, + QWidget *parent = NULL) : + QMainWindow(parent), + m_applicationName(applicationName), + m_homeDir(buildHomeDir(homeDirBase, "." + m_applicationName)), + m_logger(), + m_storageFile() +{ + m_logger.buildStandardAppender(I18N::s2b(m_homeDir) + "logger.", + maxLogSize, maxLogFiles); + m_storageFile = m_homeDir + OS_SEPARATOR_STR + "status.conf"; + m_storageFile = m_homeDir + "state.conf"; + QCoreApplication* app = QCoreApplication::instance(); + QObject::connect(app, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); +} + +void ReHomeApplication::aboutToQuit() +{ + m_logger.log(LOG_INFO, 999, "exit"); +} + +/** + * Build the home directory path name. + * + * @param homeDirBase "": the user's home directory will be used
    + * otherwise: the home directory will created in this + * directory + * @param node + * @return + */ +QString ReHomeApplication::buildHomeDir(QString homeDirBase, const QString& node){ + QString homeDir; + if (homeDirBase.isEmpty()){ + homeDir = QDir::home().absoluteFilePath(node); + } else if (ReFileUtils::isRootDir(homeDirBase.toLatin1().constData())){ + homeDir = homeDirBase + node; + } + ReQStringUtils::chomp(homeDir, OS_SEPARATOR); + QDir home(homeDir); + if (!home.exists()){ + if (!home.mkpath(homeDir)){ + homeDir = home.tempPath() + node; + home.mkpath(homeDir); + } + } + ReQStringUtils::ensureLastChar(homeDir, OS_SEPARATOR); + return homeDir; +} diff --git a/gui/ReStateStorage.hpp b/gui/ReStateStorage.hpp index b44de8b..075fec2 100644 --- a/gui/ReStateStorage.hpp +++ b/gui/ReStateStorage.hpp @@ -55,4 +55,26 @@ private: ReLogger* m_logger; }; +/** + * An abstract base class for applications which stores their data in a home directory. + */ +class ReHomeApplication : public QMainWindow{ +public: + ReHomeApplication(const char* applicationName, + const QString& homeDir, int maxLogFiles, int maxLogSize, + QWidget *parent); +public slots: + /** + * Callback method called when the application is closed. + */ + void aboutToQuit(); +public: + static QString buildHomeDir(QString homeDirBase, const QString& node); +protected: + QByteArray m_applicationName; + QString m_homeDir; + ReLogger m_logger; + QString m_storageFile; +}; + #endif /* GUI_RESTATESTORAGE_HPP_ */ -- 2.39.5