From e73fe47c7bba8066feeb68b0f1568f3ac493f50c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Enar=20V=C3=A4ikene?= Date: Fri, 25 Nov 2011 13:06:52 +0200 Subject: [PATCH] Added the FileFinder application. The FileFinder application is used to search for files that match file and/or content patterns. --- src/apps/CMakeLists.txt | 3 + src/apps/FileFinder/CMakeLists.txt | 2 + src/apps/FileFinder/Engine/CMakeLists.txt | 39 +++ src/apps/FileFinder/Engine/engine.cpp | 397 ++++++++++++++++++++++ src/apps/FileFinder/Engine/engine.h | 286 ++++++++++++++++ src/apps/FileFinder/Engine/iFileFinder | 1 + src/apps/FileFinder/Engine/ifilefinder.h | 137 ++++++++ src/apps/FileFinder/Engine/version.h | 60 ++++ src/apps/FileFinder/Engine/version.rc | 54 +++ src/apps/FileFinder/GUI/CMakeLists.txt | 41 +++ src/apps/FileFinder/GUI/gui.cpp | 376 ++++++++++++++++++++ src/apps/FileFinder/GUI/gui.h | 154 +++++++++ src/apps/FileFinder/GUI/gui.qrc | 4 + src/apps/FileFinder/GUI/version.h | 60 ++++ src/apps/FileFinder/GUI/version.rc | 54 +++ 15 files changed, 1668 insertions(+) create mode 100644 src/apps/FileFinder/CMakeLists.txt create mode 100644 src/apps/FileFinder/Engine/CMakeLists.txt create mode 100644 src/apps/FileFinder/Engine/engine.cpp create mode 100644 src/apps/FileFinder/Engine/engine.h create mode 100644 src/apps/FileFinder/Engine/iFileFinder create mode 100644 src/apps/FileFinder/Engine/ifilefinder.h create mode 100644 src/apps/FileFinder/Engine/version.h create mode 100644 src/apps/FileFinder/Engine/version.rc create mode 100644 src/apps/FileFinder/GUI/CMakeLists.txt create mode 100644 src/apps/FileFinder/GUI/gui.cpp create mode 100644 src/apps/FileFinder/GUI/gui.h create mode 100644 src/apps/FileFinder/GUI/gui.qrc create mode 100644 src/apps/FileFinder/GUI/version.h create mode 100644 src/apps/FileFinder/GUI/version.rc diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index e6cf7f0..3aa82ad 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -1 +1,4 @@ +set(eVaf_INCLUDE ${eVaf_INCLUDE} ${CMAKE_SOURCE_DIR}/src/apps/FileFinder) + add_subdirectory(PswGen) +add_subdirectory(FileFinder) diff --git a/src/apps/FileFinder/CMakeLists.txt b/src/apps/FileFinder/CMakeLists.txt new file mode 100644 index 0000000..cea6206 --- /dev/null +++ b/src/apps/FileFinder/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(Engine) +add_subdirectory(GUI) diff --git a/src/apps/FileFinder/Engine/CMakeLists.txt b/src/apps/FileFinder/Engine/CMakeLists.txt new file mode 100644 index 0000000..4e9e9ba --- /dev/null +++ b/src/apps/FileFinder/Engine/CMakeLists.txt @@ -0,0 +1,39 @@ +# Name of the target +set(TARGET FileFinderEngine) + +# Qt modules +set(QT_DONT_USE_QTGUI TRUE) +include(${QT_USE_FILE}) + +# Needed for exporting/importing symbols +add_definitions(-DFILEFINDER_ENGINE_LIBRARY) + +# Include files +include_directories(${eVaf_INCLUDE}) + +# Required eVaf libraries +set(eVaf_LIBRARIES CommonLib PluginsLib) + +# Source files +set(SRCS + engine.cpp +) + +# Header files for the meta-object compiler +set(MOC_HDRS + ifilefinder.h + engine.h +) + +# Version info resource file for Windows builds +if(WIN32) + set(SRCS ${SRCS} version.rc) +endif(WIN32) + +qt4_wrap_cpp(MOC_SRCS ${MOC_HDRS}) + +add_library(${TARGET} SHARED ${SRCS} ${MOC_SRCS}) + +target_link_libraries(${TARGET} ${QT_LIBRARIES} ${eVaf_LIBRARIES}) + +install(TARGETS ${TARGET} DESTINATION bin) diff --git a/src/apps/FileFinder/Engine/engine.cpp b/src/apps/FileFinder/Engine/engine.cpp new file mode 100644 index 0000000..b9fae0e --- /dev/null +++ b/src/apps/FileFinder/Engine/engine.cpp @@ -0,0 +1,397 @@ +/** + * @file FileFinder/Engine/engine.h + * @brief Module for the FileFinder application that searches for files + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + + +#include "engine.h" +#include "version.h" + +#include +#include + +#include + +VER_EXPORT_VERSION_INFO() +Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::FileFinder::Engine::Module) + +using namespace eVaf; +using namespace eVaf::FileFinder; +using namespace eVaf::FileFinder::Engine; + +//------------------------------------------------------------------- + +Module::Module() + : Plugins::iPlugin() + , mReady(false) +{ + setObjectName(QString("%1.Module").arg(VER_MODULE_NAME_STR)); + + mEngine = new Internal::Engine; + + EVAF_INFO("%s created", qPrintable(objectName())); +} + +Module::~Module() +{ + delete mEngine; + + EVAF_INFO("%s destroyed", qPrintable(objectName())); +} + +bool Module::init(QString const & args) +{ + Q_UNUSED(args) + + if (!mEngine->init()) + return false; + + mReady = true; + + EVAF_INFO("%s initialized", qPrintable(objectName())); + + return true; +} + +void Module::done() +{ + mReady = false; + + mEngine->done(); + + EVAF_INFO("%s finalized", qPrintable(objectName())); +} + + +//------------------------------------------------------------------- + +Internal::Engine::Engine() + : iFileFinder() + , mWorker(0) +{ + setObjectName(QString("%1.Engine").arg(VER_MODULE_NAME_STR)); + + EVAF_INFO("%s created", qPrintable(objectName())); +} + +Internal::Engine::~Engine() +{ + EVAF_INFO("%s destroyed", qPrintable(objectName())); +} + +bool Internal::Engine::init() +{ + // Register the iFileFinder interface + Common::iRegistry::instance()->registerInterface("iFileFinder", this); + + mWorker = new Internal::Worker(this); + connect(mWorker, SIGNAL(found(QString, QString)), this, SIGNAL(found(QString,QString))); + connect(mWorker, SIGNAL(finished(bool)), this, SIGNAL(finished(bool))); + mWorker->start(); + + EVAF_INFO("%s initialized", qPrintable(objectName())); + + return true; +} + +void Internal::Engine::done() +{ + if (mWorker) { + mWorker->stop(); + delete mWorker; + mWorker = 0; + } + + EVAF_INFO("%s finalized", qPrintable(objectName())); +} + +void Internal::Engine::search(QString const & dir, bool recursive, Filter const & filter) +{ + if (mWorker) + mWorker->search(dir, recursive, filter); +} + +bool Internal::Engine::busy() const +{ + if (mWorker) + return mWorker->busy(); + return false; +} + +void Internal::Engine::cancel() +{ + if (mWorker) + mWorker->cancel(); +} + + +//------------------------------------------------------------------- + +Internal::RegExpChain::~RegExpChain() +{ + clear(); +} + +void Internal::RegExpChain::clear() +{ + foreach (QRegExp * pattern, mPatterns) { + delete pattern; + } + mPatterns.clear(); + mValid = false; +} + +void Internal::RegExpChain::setPattern(QString const & pattern) +{ + clear(); + + QStringList patterns = split(pattern); + + foreach (QString s, patterns) { + if (s.isEmpty()) + continue; + QRegExp * rx = new QRegExp(s, Qt::CaseSensitive, QRegExp::WildcardUnix); + if (!rx->isValid()) { + delete rx; + continue; + } + mPatterns.append(rx); + } + + mValid = !mPatterns.isEmpty(); +} + +bool Internal::RegExpChain::exactMatch(const QString& str) const +{ + if (mPatterns.isEmpty()) + return false; + + foreach (QRegExp * pattern, mPatterns) { + if (pattern->exactMatch(str)) + return true; + } + return false; +} + +QStringList Internal::RegExpChain::split(const QString & pattern) +{ + QStringList rval; + int offset = 0; + int sz = pattern.size(); + QString s; + bool e = false; + + while (offset < sz) { + QChar ch = pattern.at(offset++); + if (e) { + e = false; + if (ch == '*' || ch == '?' || ch == '[' || ch == ']') + s.append('\\'); + s.append(ch); + } + else { + if (ch == '\\') + e = true; + else if (ch == ',') { + rval.append(s); + s.clear(); + } + else + s.append(ch); + } + } + if (!s.isEmpty()) + rval.append(s); + + return rval; +} + + +//------------------------------------------------------------------- + +int const Internal::Worker::ReadBufferSize = 4096; + +Internal::Worker::Worker(QObject * parent) + : QThread(parent) + , mNewRecursive(false) + , mRecursive(false) + , mDoSearch(false) + , mDoTerminate(false) + , mDoCancel(false) + , mBusy(false) +{ + setObjectName(QString("%1.Worker").arg(VER_MODULE_NAME_STR)); +} + +void Internal::Worker::cancel() +{ + mLock.lock(); + mDoCancel = true; + mSomethingToDo.wakeAll(); + mLock.unlock(); +} + +void Internal::Worker::stop() +{ + mLock.lock(); + mDoTerminate = true; + mDoCancel = true; + mSomethingToDo.wakeAll(); + mLock.unlock(); + wait(); +} + +void Internal::Worker::search(QString const & dir, bool recursive, Filter const & filter) +{ + mLock.lock(); + mDoCancel = true; + mNewDir = dir; + mNewRecursive = recursive; + mNewFilter = filter; + mDoSearch = true; + mSomethingToDo.wakeAll(); + mLock.unlock(); +} + +bool Internal::Worker::busy() const +{ + QMutexLocker l(&mLock); + return mBusy; +} + +void Internal::Worker::run() +{ + forever { + QMutexLocker lock(&mLock); + + mSomethingToDo.wait(&mLock); + + if (mDoTerminate) + break; + + mDoCancel = false; + + if (mDoSearch) { + mDoSearch = false; + mBusy = true; + + // Copy search arguments + mDir.setPath(mNewDir); + mRecursive = mNewRecursive; + mRxIncludeNames.setPattern(mNewFilter.includeNames); + mRxExcludeNames.setPattern(mNewFilter.excludeNames); + mRxIncludeContent.setPattern(mNewFilter.includeContent); + mRxExcludeContent.setPattern(mNewFilter.excludeContent); + + lock.unlock(); + + // Perform the actual search + recursiveSearch(mDir.path()); + + lock.relock(); + mBusy = false; + + emit finished(mDoCancel); + } + } +} + +void Internal::Worker::recursiveSearch(QString const & path) +{ + QString l = path; + if (!l.endsWith(QChar('/'))) + l.append(QChar('/')); + QDir dir(l); + + // Get the list of files in this directory + QStringList files = dir.entryList(QDir::Files | QDir::NoSymLinks); + foreach (QString const & file, files) { + + // Check for the cancel flag + { + QMutexLocker l(&mLock); + if (mDoCancel) + break; + } + + // Check for the file name to match the include filter and not the exclude filter + if (mRxIncludeNames.isValid() && !mRxIncludeNames.isEmpty()) { + if (!mRxIncludeNames.exactMatch(file)) + continue; + } + if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty()) { + if (mRxExcludeNames.exactMatch(file)) + continue; + } + + // Check for the file content to match the include filter and not the exclude filter + if ((mRxIncludeContent.isValid() && !mRxIncludeContent.isEmpty()) || + (mRxExcludeContent.isValid() && !mRxExcludeContent.isEmpty())) { + + QFile f(l + file); + if (!f.open(QFile::ReadOnly)) + continue; // Ignore silently if opening fails + + int includeFilterMatched = 0; + if (!mRxIncludeContent.isValid() || mRxIncludeContent.isEmpty()) + includeFilterMatched = 1; + int excludeFilterMatched = 0; + if (!mRxExcludeContent.isValid() || mRxExcludeContent.isEmpty()) + excludeFilterMatched = -1; + QByteArray buf; + while (!f.atEnd() && (includeFilterMatched <= 0 || excludeFilterMatched <= 0)) { + + /* We read ReadBufferSize bytes from the file and append to the buffer. + * We keep max 2 x ReadBufferSize bytes in the buffer and throw away the oldest + * ReadBufferSize bytes of data. Every block is checked twice, but we make sure that + * also strings that stretch from one block to another are checked. + */ + QByteArray b = f.read(ReadBufferSize); + buf.append(b); + if (buf.size() > (2 * ReadBufferSize)) + buf.remove(0, ReadBufferSize); + if (includeFilterMatched == 0 && mRxIncludeContent.indexIn(buf) >= 0) + ++includeFilterMatched; + if (excludeFilterMatched == 0 && mRxExcludeContent.indexIn(buf) >= 0) + ++excludeFilterMatched; + + } + + if (includeFilterMatched == 0 || excludeFilterMatched > 0) + continue; + } + + // Found a file + emit found(mDir.relativeFilePath(l + file), mDir.path()); + } + + // Process sub-directories + if (mRecursive) { + QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + foreach (QString const & directory, dirs) { + + // Check for the cancel flag + { + QMutexLocker l(&mLock); + if (mDoCancel) + break; + } + + recursiveSearch(l + directory); + } + } +} diff --git a/src/apps/FileFinder/Engine/engine.h b/src/apps/FileFinder/Engine/engine.h new file mode 100644 index 0000000..384fa40 --- /dev/null +++ b/src/apps/FileFinder/Engine/engine.h @@ -0,0 +1,286 @@ +/** + * @file FileFinder/Engine/engine.h + * @brief Module for the FileFinder application that searches for files + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#ifndef __FILEFINDER_ENGINE_ENGINE_H +# define __FILEFINDER_ENGINE_ENGINE_H + +#include "ifilefinder.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace eVaf { + +/** + * Application for searching files. + */ +namespace FileFinder { + +/** + * Module that implements the engine for searching files. + */ +namespace Engine { + +/** + * Internal implementation of the FileFinder engine. + */ +namespace Internal { + class Engine; +} // namespace eVaf::FileFinder::Engine::Internal + +/** + * Module for the FileFinder application that searches for files. + * + * The Module class implements the Plugins::iPlugin interface and + * creates the Internal::Engine object for file searching. + */ +class Module : public Plugins::iPlugin +{ + Q_OBJECT + Q_INTERFACES(eVaf::Plugins::iPlugin) + +public: + + Module(); + + virtual ~Module(); + + virtual bool init(QString const & args); + + virtual void done(); + + virtual bool isReady() const { return mReady; } + + +private: // Members + + /// Flag indicating that the module is ready + bool mReady; + + /// iFileFinder interface implementation + Internal::Engine * mEngine; + +}; + + +namespace Internal { + +class Worker; + +/** + * Implements the iFileFinder interface. + * + * The Internal::Engine class implements the iFileFinder interface. + * Searching for files is done in a separate worker thread Internal::Worker. + */ +class Engine : public iFileFinder +{ + Q_OBJECT + Q_INTERFACES(eVaf::FileFinder::iFileFinder) + +public: + + Engine(); + + virtual ~Engine(); + + bool init(); + + void done(); + + virtual void search(QString const & dir, bool recursive, Filter const & filter); + + virtual void cancel(); + + virtual bool busy() const; + + +private: // Members + + /// Worker thread + Worker * mWorker; + +}; + +/** + * A chain of QRegExp patterns. + */ +class RegExpChain +{ +public: + + RegExpChain() + : mValid(false) + {} + + ~RegExpChain(); + + void setPattern(QString const & pattern); + + void clear(); + + bool isEmpty() const { return mPatterns.isEmpty(); } + + bool isValid() const { return mValid; } + + bool exactMatch(QString const & str) const; + + +private: + + QVector mPatterns; + + bool mValid; + + /** + * Splits the string str into individual patterns separated by commas + * @param str Input string + * @return List of individual patterns + * + * The split() function splits the string str into individual patterns. Patterns are + * separated with commas. To use the comma as part of the pattern, escape it with '\'. + */ + QStringList split(QString const & str); +}; + +/** + * Worker thread searching for files. + * + * The Worker class is a separate thread that searches for files and then emits + * the found() signal on every file found. + */ +class Worker : public QThread +{ + Q_OBJECT + +public: + + Worker(QObject * parent = 0); + + /** + * Starts a new search. + * @param dir Directory where to search + * @param recursive If true, searches recurively in sub-directories + * @param filter Search filter + * + * The search() function starts a new search. If a search is already ongoing, + * cancels it and starts a new one. + * + * Search results are reported back with the found() signal and the finished() + * signal indicates that the search is done. + */ + void search(QString const & dir, bool recursive, Filter const & filter); + + /** + * Indicates that a search is ongoing and the worker thread busy. + */ + bool busy() const; + + /** + * Cancels an ongoing search. + */ + void cancel(); + + /** + * Stops and terminates the worker thread. + */ + void stop(); + + +signals: + + /** + * A file was found during the search. + * @param fileName Name of the file with a path relative to the search directory + * @param dir Search directory + */ + void found(QString const & fileName, QString const & dir); + + /** + * Signal indicating that a search is finished. + * @param canceled True if the search was canceled instead of finished + */ + void finished(bool canceled); + + +protected: + + virtual void run(); + + +private: // Members + + /// Size of the read buffer + static int const ReadBufferSize; + + /// RW access lock + mutable QMutex mLock; + + /// Wait for something to do + QWaitCondition mSomethingToDo; + + QString mNewDir; + QDir mDir; + + bool mNewRecursive; + bool mRecursive; + + Filter mNewFilter; + RegExpChain mRxIncludeNames; + RegExpChain mRxExcludeNames; + QRegExp mRxIncludeContent; + QRegExp mRxExcludeContent; + + bool mDoSearch; + + bool mDoTerminate; + + bool mDoCancel; + + bool mBusy; + + +private: // Methods + + /** + * Searches for files in the given directory + * @param path Path to the directory + * + * The recursiveSearch() function searches for files in the given directory and emits found() + * signals for every file found. If the mRecursive flag is set to true, searches recurively + * in all the sub-directories. + */ + void recursiveSearch(QString const & path); + +}; + +} // namespace eVaf::FileFinder::Engine::Internal +} // namespace eVaf::FileFinder::Engine +} // namespace eVaf::FileFinder +} // namespace eVaf + +#endif // engine.h diff --git a/src/apps/FileFinder/Engine/iFileFinder b/src/apps/FileFinder/Engine/iFileFinder new file mode 100644 index 0000000..07999fa --- /dev/null +++ b/src/apps/FileFinder/Engine/iFileFinder @@ -0,0 +1 @@ +#include "ifilefinder.h" diff --git a/src/apps/FileFinder/Engine/ifilefinder.h b/src/apps/FileFinder/Engine/ifilefinder.h new file mode 100644 index 0000000..7c0380e --- /dev/null +++ b/src/apps/FileFinder/Engine/ifilefinder.h @@ -0,0 +1,137 @@ +/** + * @file FileFinder/Engine/ifilefinder.h + * @brief Interface for the file finder engine + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#ifndef __FILEFINDER_ENGINE_IFILEFINDER_H +# define __FILEFINDER_ENGINE_IFILEFINDER_H + +#include +#include + +namespace eVaf { +namespace FileFinder { + +/** + * File filter defining patterns for file names and contents. + * + * File name patterns are wildcards (QRegExp::WildcardUnix). Multiple patterns can be separated by comma like, + * for example, "*.cpp,*.h". To include a comma in the pattern, escape it with '\'. + * + * Content patterns are regular expressions (QRegExp::RegExp). + * + * A file matches the filter if: + * @li file name matches the includeNames pattern or the includeNames pattern is empty; + * @li and; file name does not match the excludeNames pattern or the excludeNames pattern is empty; + * @li and; file content matches the includeContent pattern or the includeContent pattern is empty; + * @li and; file content does not match the excludeContent pattern or the excludeContent patytern is empty. + */ +struct Filter +{ + Filter() + {} + + Filter(QString const & in, QString const & en, QString const & ic, QString const & ec) + : includeNames(in) + , excludeNames(en) + , includeContent(ic) + , excludeContent(ec) + {} + + /// QRegExp expression specifying file names that are included in the search result + QString includeNames; + + /// QRegExp expression specifying file names that are excluded from the search result + QString excludeNames; + + /// QRegExp expression specifying file content that is included in the search result + QString includeContent; + + /// QRegExp expression specifying file content that is excluded from the search result + QString excludeContent; +}; + +/** + * File finder interface. + * + * The iFileFinder interface is used to search for files using name and content filters. + */ +class iFileFinder : public QObject +{ + Q_OBJECT + +public: + + /// Interface constructor + iFileFinder() : QObject() {} + + /// Empty virtual destructor + virtual ~iFileFinder() {} + + /** + * Starts a file search in the directory dir using filter. + * @param dir Directory where to search + * @param recursive If True, searches also in sub-directories + * @param filter File name and content filter + * + * The search() function searches for files matching the given filter. The search is started + * in the directory dir and continues recursively into sub-directories if the recursive flag + * is set to true. + * + * The function is non-blocking and returns immediately. Results of the search are reported + * with found() signals and the finished() signal indicates that the search is either finished + * or canceled. + * + * If a new search is started before the current search is finished, then the current search is canceled. + */ + virtual void search(QString const & dir, bool recursive, Filter const & filter) = 0; + + /** + * Cancels an ongoing search. + */ + virtual void cancel() = 0; + + /** + * Indicates that a search is ongoing + */ + virtual bool busy() const = 0; + + +signals: + + /** + * Signal emitted for every file found during the search. + * @param fileName name of the file with a path relative to the search directory + * @param dir Search directory + */ + void found(QString const & fileName, QString const & dir); + + /** + * Signal emitted when the search is finished + * @param canceled True if the search was canceled instead of finished + */ + void finished(bool canceled); + +}; + + +} // namespace eVaf::FileFinder +} // namespace eVaf + +Q_DECLARE_INTERFACE(eVaf::FileFinder::iFileFinder, "eVaf.FileFinder.iFileFinder/1.0") + +#endif // ifilefinder.h diff --git a/src/apps/FileFinder/Engine/version.h b/src/apps/FileFinder/Engine/version.h new file mode 100644 index 0000000..5e2f8ae --- /dev/null +++ b/src/apps/FileFinder/Engine/version.h @@ -0,0 +1,60 @@ +/** + * @file FileFinder/Engine/version.h + * @brief Version information for eVaf modules + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#ifndef __FILEFINDER_ENGINE_VERSION_H +# define __FILEFINDER_ENGINE_VERSION_H + +#include + +/** + * Module/library version number in the form major,minor,release,build + */ +#define VER_FILE_VERSION 0,1,1,1 + +/** + * Module/library version number in the string format (shall end with \0) + */ +#define VER_FILE_VERSION_STR "0.1.1.1\0" + +/** + * Module/library name (shall end with \0) + */ +#define VER_MODULE_NAME_STR "FileFinderEngine\0" + +/** + * Module type (see version_rc.h for all the types) + */ +#define VER_MODULE_TYPE MT_GENERIC + +/** + * Module type in the string format (see version_rc for all the types) + */ +#define VER_MODULE_TYPE_STR MT_GENERIC + +/** + * Original file name for windows (shall end with \0) + */ +#define VER_ORIGINAL_FILE_NAME_STR "FileFinderEngine.dll\0" + +/** + * Description of the module/library (shall end with \0) + */ +#define VER_FILE_DESCRIPTION_STR "Search engine for the FileFinder application.\0" + +#endif // version.h diff --git a/src/apps/FileFinder/Engine/version.rc b/src/apps/FileFinder/Engine/version.rc new file mode 100644 index 0000000..33672fb --- /dev/null +++ b/src/apps/FileFinder/Engine/version.rc @@ -0,0 +1,54 @@ +/** + * @file PswGen/Generator/version.rc + * @brief Windows resource file with module/library version information. + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#include "version.h" +#include +#include + + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILE_VERSION + PRODUCTVERSION VER_PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", VER_COMPANY_NAME_STR + VALUE "FileDescription", VER_FILE_DESCRIPTION_STR + VALUE "FileVersion", VER_FILE_VERSION_STR + VALUE "LegalCopyright", VER_LEGAL_COPYRIGHT_STR + VALUE "OriginalFilename", VER_ORIGINAL_FILE_NAME_STR + VALUE "ProductName", VER_PRODUCT_NAME_STR + VALUE "ProductVersion", VER_PRODUCT_VERSION_STR + VALUE "Build Date", VER_PRODUCT_DATE_STR + VALUE "Module Name", VER_MODULE_NAME_STR + VALUE "Module Type", VER_MODULE_TYPE_STR + END + END + END diff --git a/src/apps/FileFinder/GUI/CMakeLists.txt b/src/apps/FileFinder/GUI/CMakeLists.txt new file mode 100644 index 0000000..a29d09f --- /dev/null +++ b/src/apps/FileFinder/GUI/CMakeLists.txt @@ -0,0 +1,41 @@ +# Name of the target +set(TARGET FileFinderGui) + +# Qt modules +include(${QT_USE_FILE}) + +# Include files +include_directories(${eVaf_INCLUDE}) + +# Required eVaf libraries +set(eVaf_LIBRARIES CommonLib PluginsLib) + +# Source files +set(SRCS + gui.cpp +) + +# Header files for the meta-object compiler +set(MOC_HDRS + gui.h +) + +# Resources +#set(RCCS +# gui.qrc +#) + +# Version info resource file for Windows builds +if(WIN32) + set(SRCS ${SRCS} version.rc) +endif(WIN32) + +qt4_add_resources(RCC_SRCS ${RCCS}) + +qt4_wrap_cpp(MOC_SRCS ${MOC_HDRS}) + +add_library(${TARGET} SHARED ${SRCS} ${MOC_SRCS} ${RCC_SRCS}) + +target_link_libraries(${TARGET} ${QT_LIBRARIES} ${eVaf_LIBRARIES}) + +install(TARGETS ${TARGET} DESTINATION bin) diff --git a/src/apps/FileFinder/GUI/gui.cpp b/src/apps/FileFinder/GUI/gui.cpp new file mode 100644 index 0000000..a849da9 --- /dev/null +++ b/src/apps/FileFinder/GUI/gui.cpp @@ -0,0 +1,376 @@ +/** + * @file FileFinder/GUI/gui.cpp + * @brief GUI for the FileFinder application + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#include "gui.h" +#include "version.h" + +#include "Engine/iFileFinder" + +#include +#include +#include +#include +#include + +#include + +VER_EXPORT_VERSION_INFO() +Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::FileFinder::GUI::Module) + +using namespace eVaf; + + +//------------------------------------------------------------------- + +void FileFinder::GUI::Internal::MainWidget::keyPressEvent(QKeyEvent * e) +{ + if (!e->modifiers() || ((e->modifiers() & Qt::KeypadModifier) && e->key() == Qt::Key_Enter)) { + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: { + QList buttons = qFindChildren(this); + foreach (QPushButton * btn, buttons) { + if (btn->isDefault() && btn->isVisible()) { + if (btn->isEnabled()) + btn->click(); + return; + } + } + break; + } + case Qt::Key_Escape: + emit quit(); + break; + case Qt::Key_Up: + focusPreviousChild(); + break; + case Qt::Key_Down: + focusNextChild(); + break; + default: + e->ignore(); + } + } + else { + e->ignore(); + } +} + + +//------------------------------------------------------------------- + +FileFinder::GUI::Module::Module() + : Plugins::iPlugin() + , mReady(false) + , mFinder(0) + , mOpenFileAction(0) + , mOpenDirectoryAction(0) + , wMain(0) + , wDirectory(0) + , wRecursive(0) + , wIncludeNames(0) + , wExcludeNames(0) + , wIncludeContent(0) + , wExcludeContent(0) + , wFind(0) +{ + setObjectName(QString("%1.Module").arg(VER_MODULE_NAME_STR)); + + EVAF_INFO("%s created", qPrintable(objectName())); +} + +FileFinder::GUI::Module::~Module() +{ + EVAF_INFO("%s destroyed", qPrintable(objectName())); +} + +bool FileFinder::GUI::Module::init(QString const & args) +{ + Q_UNUSED(args) + + // Get the iFileFinder interface + EVAF_TEST_X((mFinder = evafQueryInterface("iFileFinder")), "No iFileFinder interface"); + connect(mFinder, SIGNAL(found(QString,QString)), this, SLOT(found(QString,QString))); + connect(mFinder, SIGNAL(finished(bool)), this, SLOT(finished(bool))); + + // Get the main window interface and fill it with widgets + SdiWindow::iSdiWindow * win = evafQueryInterface("iSdiWindow"); + EVAF_TEST_X(win, "No iSdiWindow interface"); + + // Create the main widget for this window + wMain = new Internal::MainWidget; + connect(wMain, SIGNAL(quit()), qApp, SLOT(quit())); + win->addWidget(wMain); + + // Create actions for the window and widgets on it + createActions(); + + // Create all the other widgets + createWidgets(); + + // Load stored searches + loadHistory(); + + mReady = true; + + EVAF_INFO("%s initialized", qPrintable(objectName())); + + return true; +} + +void FileFinder::GUI::Module::done() +{ + mReady = false; + + /* + * Widgets are deleted by the SdiWindow module. We use wMain to track calls to done() without + * proper init(). + */ + if (wMain) { + wMain = 0; + saveHistory(); + } + + EVAF_INFO("%s finalized", qPrintable(objectName())); +} + +void FileFinder::GUI::Module::saveHistory() +{ + QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name()); + + QStringList t; + for (int i = 0; i < wDirectory->count(); ++i) + t.append(wDirectory->itemText(i)); + settings.setValue("FileFinder/Directories", t); + + t.clear(); + for (int i = 0; i < wIncludeNames->count(); ++i) + t.append(wIncludeNames->itemText(i)); + settings.setValue("FileFinder/IncludeNames", t); + + t.clear(); + for (int i = 0; i < wExcludeNames->count(); ++i) + t.append(wExcludeNames->itemText(i)); + settings.setValue("FileFinder/ExcludeNames", t); + + t.clear(); + for (int i = 0; i < wIncludeContent->count(); ++i) + t.append(wIncludeContent->itemText(i)); + settings.setValue("FileFinder/IncludeContent", t); + + t.clear(); + for (int i = 0; i < wExcludeContent->count(); ++i) + t.append(wExcludeContent->itemText(i)); + settings.setValue("FileFinder/ExcludeContent", t); + +} + +void FileFinder::GUI::Module::loadHistory() +{ + QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name()); + + wDirectory->addItems(settings.value("FileFinder/Directories").toStringList()); + wDirectory->setEditText(QDir::currentPath()); + + wIncludeNames->addItems(settings.value("FileFinder/IncludeNames").toStringList()); + wIncludeNames->setEditText(""); + + wExcludeNames->addItems(settings.value("FileFinder/ExcludeNames").toStringList()); + wExcludeNames->setEditText(""); + + wIncludeContent->addItems(settings.value("FileFinder/IncludeContent").toStringList()); + wIncludeContent->setEditText(""); + + wExcludeContent->addItems(settings.value("FileFinder/ExcludeContent").toStringList()); + wExcludeContent->setEditText(""); +} + +void FileFinder::GUI::Module::createWidgets() +{ + QVBoxLayout * vbox = new QVBoxLayout; + wMain->setLayout(vbox); + + QHBoxLayout * hbox = new QHBoxLayout; + vbox->addLayout(hbox); + + QLabel * l = new QLabel(tr("&Directory:")); + hbox->addWidget(l); + + wDirectory = new QComboBox; + wDirectory->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + wDirectory->setEditable(true); + wDirectory->setInsertPolicy(QComboBox::InsertAtTop); + l->setBuddy(wDirectory); + hbox->addWidget(wDirectory); + + QPushButton * btn = new QPushButton(tr("&Browse")); + connect(btn, SIGNAL(clicked()), this, SLOT(browseDirectory())); + hbox->addWidget(btn); + + wRecursive = new QCheckBox(tr("&Recursive")); + wRecursive->setChecked(true); + hbox->addWidget(wRecursive); + + QGridLayout * gr = new QGridLayout; + vbox->addLayout(gr); + + gr->setColumnStretch(1, 1); + gr->setColumnStretch(3, 1); + + l = new QLabel(tr("&File filter:")); + gr->addWidget(l, 0, 0); + + wIncludeNames = new QComboBox; + wIncludeNames->setEditable(true); + wIncludeNames->setInsertPolicy(QComboBox::InsertAtTop); + l->setBuddy(wIncludeNames); + gr->addWidget(wIncludeNames, 0, 1); + + l = new QLabel(tr("¬:")); + gr->addWidget(l, 0, 2); + + wExcludeNames = new QComboBox; + wExcludeNames->setEditable(true); + wExcludeNames->setInsertPolicy(QComboBox::InsertAtTop); + l->setBuddy(wExcludeNames); + gr->addWidget(wExcludeNames); + + l = new QLabel(tr("C&ontains:")); + gr->addWidget(l, 1, 0); + + wIncludeContent = new QComboBox; + wIncludeContent->setEditable(true); + wIncludeContent->setInsertPolicy(QComboBox::InsertAtTop); + l->setBuddy(wIncludeContent); + gr->addWidget(wIncludeContent, 1, 1); + + l = new QLabel(tr("no&t:")); + gr->addWidget(l, 1, 2); + + wExcludeContent = new QComboBox; + wExcludeContent->setEditable(true); + wExcludeContent->setInsertPolicy(QComboBox::InsertAtTop); + l->setBuddy(wExcludeContent); + gr->addWidget(wExcludeContent, 1, 3); + + wResults = new QListWidget; + wResults->setContextMenuPolicy(Qt::ActionsContextMenu); + wResults->addAction(mOpenFileAction); + wResults->addAction(mOpenDirectoryAction); + connect(wResults, SIGNAL(currentRowChanged(int)), this, SLOT(currentRowChanged(int))); + connect(wResults, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(openFile(QModelIndex))); + vbox->addWidget(wResults); + + hbox = new QHBoxLayout; + vbox->addLayout(hbox); + hbox->addStretch(); + + wFind = new QPushButton(tr("&Search")); + wFind->setDefault(true); + connect(wFind, SIGNAL(clicked()), this, SLOT(find())); + hbox->addWidget(wFind); + + btn = new QPushButton(tr("&Close")); + connect(btn, SIGNAL(clicked()), qApp, SLOT(quit())); + hbox->addWidget(btn); +} + +void FileFinder::GUI::Module::createActions() +{ + QAction * a = new QAction(wMain); + a->setShortcuts(QKeySequence::Quit); + connect(a, SIGNAL(triggered()), qApp, SLOT(quit())); + wMain->addAction(a); + + mOpenFileAction = new QAction(tr("&Open"), wMain); + mOpenFileAction->setEnabled(false); + connect(mOpenFileAction, SIGNAL(triggered()), this, SLOT(openFile())); + + mOpenDirectoryAction = new QAction(tr("Open &location"), wMain); + mOpenDirectoryAction->setEnabled(false); + connect(mOpenDirectoryAction, SIGNAL(triggered()), this, SLOT(openDirectory())); +} + +void FileFinder::GUI::Module::browseDirectory() +{ + QString s = QFileDialog::getExistingDirectory(wMain, tr("Select the directory"), wDirectory->currentText()); + if (!s.isEmpty()) + wDirectory->setEditText(s); +} + +void FileFinder::GUI::Module::find() +{ + if (!mFinder) + return; + if (mFinder->busy()) { + mFinder->cancel(); + } + else { + wResults->clear(); + + mFinder->search(wDirectory->currentText(), + wRecursive->isChecked(), + FileFinder::Filter( + wIncludeNames->currentText(), + wExcludeNames->currentText(), + wIncludeContent->currentText(), + wExcludeContent->currentText() + ) + ); + + wFind->setText(tr("&Stop")); + } +} + +void FileFinder::GUI::Module::found(QString const & file, QString const & dir) +{ + QString result = dir; + if (!result.endsWith(QChar('/'))) + result.append(QChar('/')); + result.append(file); + wResults->addItem(result); +} + +void FileFinder::GUI::Module::finished(bool canceled) +{ + Q_UNUSED(canceled) + + wFind->setText(tr("&Search")); +} + +void FileFinder::GUI::Module::currentRowChanged(int currentRow) +{ + mOpenFileAction->setEnabled(currentRow >= 0); + mOpenDirectoryAction->setEnabled(currentRow >= 0); +} + +void FileFinder::GUI::Module::openFile(QModelIndex const & index) +{ + Q_UNUSED(index) + if (wResults->currentItem()) + QDesktopServices::openUrl(QUrl(QString("file:///%1").arg(wResults->currentItem()->text()))); +} + +void FileFinder::GUI::Module::openDirectory() +{ + if (wResults->currentItem()) { + QFileInfo fi(wResults->currentItem()->text()); + QDesktopServices::openUrl(QUrl(QString("file:///%1").arg(fi.path()))); + } +} diff --git a/src/apps/FileFinder/GUI/gui.h b/src/apps/FileFinder/GUI/gui.h new file mode 100644 index 0000000..809dab1 --- /dev/null +++ b/src/apps/FileFinder/GUI/gui.h @@ -0,0 +1,154 @@ +/** + * @file FileFinder/GUI/gui.h + * @brief GUI for the FileFinder application + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#ifndef __FILEFINDER_GUI_GUI_H +# define __FILEFINDER_GUI_GUI_H + +#include + +#include +#include +#include +#include + +class QWidget; +class QCheckBox; +class QComboBox; +class QPushButton; +class QListWidget; +class QAction; + +namespace eVaf { +namespace SdiWindow { + struct iSdiWindow; +} // namespace eVaf::SdiWindow +namespace FileFinder { + struct iFileFinder; +namespace GUI { + +namespace Internal { + +/** + * Main widget for the FileFinder window + */ +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + + MainWidget(QWidget * parent = 0) + : QWidget(parent) + {} + + virtual void keyPressEvent(QKeyEvent * e); + + +signals: + + /** + * Request to close the windows/application + */ + void quit(); + +}; + +} // namespace eVaf::FileFinder::GUI::Internal + +/** + * Graphical User Interface for the FileFinder application. + * + * This module adds a GUI window to the FileFinder application using the SdiWindow module. + */ +class Module : public Plugins::iPlugin +{ + Q_OBJECT + Q_INTERFACES(eVaf::Plugins::iPlugin) + +public: + + Module(); + + virtual ~Module(); + + virtual bool init(QString const & args); + + virtual void done(); + + virtual bool isReady() const { return mReady; } + + +private slots: + + void browseDirectory(); + + void find(); + + void found(QString const & file, QString const & dir); + + void finished(bool); + + void currentRowChanged(int currentRow); + + void openFile(QModelIndex const & index = QModelIndex()); + + void openDirectory(); + + +private: // Members + + /// Flag indicating that the module is ready + bool mReady; + + /// The iFileFinder interface + eVaf::FileFinder::iFileFinder * mFinder; + + /// Actions + QAction * mOpenFileAction; + QAction * mOpenDirectoryAction; + + /// Widgets on the screen + Internal::MainWidget * wMain; + QComboBox * wDirectory; + QCheckBox * wRecursive; + QComboBox * wIncludeNames; + QComboBox * wExcludeNames; + QComboBox * wIncludeContent; + QComboBox * wExcludeContent; + QListWidget * wResults; + QPushButton * wFind; + + +private: // Methods + + void createWidgets(); + + void createActions(); + + void saveHistory(); + + void loadHistory(); + +}; + +} // namespace eVaf::FileFinder::GUI +} // namespace eVaf::FileFinder +} // namespace eVaf + +#endif // gui.h diff --git a/src/apps/FileFinder/GUI/gui.qrc b/src/apps/FileFinder/GUI/gui.qrc new file mode 100644 index 0000000..ef1de7a --- /dev/null +++ b/src/apps/FileFinder/GUI/gui.qrc @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/FileFinder/GUI/version.h b/src/apps/FileFinder/GUI/version.h new file mode 100644 index 0000000..745d9c7 --- /dev/null +++ b/src/apps/FileFinder/GUI/version.h @@ -0,0 +1,60 @@ +/** + * @file FileFinder/GUI/version.h + * @brief Version information for eVaf modules + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#ifndef __FILEFINDER_GUI_VERSION_H +# define __FILEFINDER_GUI_VERSION_H + +#include + +/** + * Module/library version number in the form major,minor,release,build + */ +#define VER_FILE_VERSION 0,1,1,1 + +/** + * Module/library version number in the string format (shall end with \0) + */ +#define VER_FILE_VERSION_STR "0.1.1.1\0" + +/** + * Module/library name (shall end with \0) + */ +#define VER_MODULE_NAME_STR "FileFinderGui\0" + +/** + * Module type (see version_rc.h for all the types) + */ +#define VER_MODULE_TYPE MT_GENERIC + +/** + * Module type in the string format (see version_rc for all the types) + */ +#define VER_MODULE_TYPE_STR MT_GENERIC + +/** + * Original file name for windows (shall end with \0) + */ +#define VER_ORIGINAL_FILE_NAME_STR "FileFinderGui.dll\0" + +/** + * Description of the module/library (shall end with \0) + */ +#define VER_FILE_DESCRIPTION_STR "GUI for the FileFinder application.\0" + +#endif // version.h diff --git a/src/apps/FileFinder/GUI/version.rc b/src/apps/FileFinder/GUI/version.rc new file mode 100644 index 0000000..33672fb --- /dev/null +++ b/src/apps/FileFinder/GUI/version.rc @@ -0,0 +1,54 @@ +/** + * @file PswGen/Generator/version.rc + * @brief Windows resource file with module/library version information. + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can 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 included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#include "version.h" +#include +#include + + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILE_VERSION + PRODUCTVERSION VER_PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", VER_COMPANY_NAME_STR + VALUE "FileDescription", VER_FILE_DESCRIPTION_STR + VALUE "FileVersion", VER_FILE_VERSION_STR + VALUE "LegalCopyright", VER_LEGAL_COPYRIGHT_STR + VALUE "OriginalFilename", VER_ORIGINAL_FILE_NAME_STR + VALUE "ProductName", VER_PRODUCT_NAME_STR + VALUE "ProductVersion", VER_PRODUCT_VERSION_STR + VALUE "Build Date", VER_PRODUCT_DATE_STR + VALUE "Module Name", VER_MODULE_NAME_STR + VALUE "Module Type", VER_MODULE_TYPE_STR + END + END + END -- 2.47.0