commit f96452d6692860fff154482a4908940c990a7367 Author: Albert S Date: Wed Jan 3 09:52:05 2018 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d2ddfa --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +qsRunner +======== +qsRunner is a launcher. It contains user defined entries for applications and also searches +system-applications. Using libcalculate, it can also be used as a calculator. It +can also search for files (and their contents) by querying a sqlite database, although it itself does +not index files. + +If you run a desktop environment like KDE it is questionable whether you will +find this useful, since they usually bring applications that are more or less +comparable to qsRunner, although much more +powerful (like KRunner). It can be useful for users running a window manager like +fluxbox etc. + +Dependencies +------------ +It has been only tested for Qt 5.7. + +For the calculation engine, libqalculate is needed. +For file search, easyindex is necessary. + +Currently no conditional compile flags are supported... + +Getting started +---------------- +Currently it may not be considered a classical GUI application because the +configuration must be done outside of it. + +mkdir $HOME/.config/qsRunner +In this folder user-defined entries should be put (See "Entry format"). + +Config format +------------ +Path: $HOME/.config/qsRunner/qsrunner.config + +``` +[Search] +dbpath="/path/to/database.sqlite" + +[General] +systemApplicationsPath="/usr/share/applications/" +``` + +systemApplicationsPath will default to "/usr/share/applications/", +therefore specifying it explicitly is not necessary. + +Entry format +------------ +It rudimentary supports .desktop files, but for user entries, the own format +should be preferred. + +It's a simple format: [key] [value]. + +Example: quasselclient.qsrun: + +``` +command quasselclient +name Quassel +icon /usr/share/icons/hicolor/128x128/apps/quassel.png +row 1 +col 0 +key I +``` + +"key" means a shortcut key, you can launch those by pressing Ctrl + "key", so in +the example above: CTRL + I. + +Simply pressing Ctrl will show you the associated shortcuts on each individual +button. + + +General usage +============= +Starting to type will search user defined entries first, followed by system +entries. Then the PATH variable will be searched, if there is a single match you can also +press TAB for auto completion. + +In general it will launch anything once you press enter, however it won't open a +terminal. + +Calculator +========= +Start by typing "=", followed by your expression, e. g: "=(2+3)^2" + +File searches +============= +It only queries a sqlite database. It does not index files, this is beyond the +scope a launcher. For the file searches functionality, easyindex is necessary. +See: git.quitesimple.org/easyindex. + + diff --git a/calculationengine.cpp b/calculationengine.cpp new file mode 100644 index 0000000..35313e6 --- /dev/null +++ b/calculationengine.cpp @@ -0,0 +1,42 @@ +#include "calculationengine.h" + + +CalculationEngine::CalculationEngine() +{ + if (!CALCULATOR) { + new Calculator(); + CALCULATOR->terminateThreads(); + CALCULATOR->setPrecision(16); + } + + //stolen from krunner's qalculate engine + /*eo.auto_post_conversion = POST_CONVERSION_BEST; + eo.keep_zero_units = false; + + eo.parse_options.angle_unit = ANGLE_UNIT_RADIANS; + eo.structuring = STRUCTURING_SIMPLIFY; + + // suggested in https://github.com/Qalculate/libqalculate/issues/16 + // to avoid memory overflow for seemingly innocent calculations (Bug 277011) + eo.approximation = APPROXIMATION_APPROXIMATE; + + po.number_fraction_format = FRACTION_DECIMAL; + po.indicate_infinite_series = false; + po.use_all_prefixes = false; + po.use_denominator_prefix = true; + po.negative_exponents = false; + po.lower_case_e = true; + po.base_display = BASE_DISPLAY_NORMAL;*/ +} + +QString CalculationEngine::evaluate(const QString &expression) +{ + CALCULATOR->terminateThreads(); + QByteArray ba = expression.toLatin1(); + const char *ctext = ba.data(); + MathStructure result = CALCULATOR->calculate(ctext, this->eo); + result.format(po); + return result.print(po).c_str(); + + +} diff --git a/calculationengine.h b/calculationengine.h new file mode 100644 index 0000000..fa091f7 --- /dev/null +++ b/calculationengine.h @@ -0,0 +1,22 @@ +#ifndef CALCULATIONENGINE_H +#define CALCULATIONENGINE_H +#include +#include +#include +#include +#include +#include +#include + +class CalculationEngine +{ +private: + EvaluationOptions eo; + PrintOptions po; + +public: + CalculationEngine(); + QString evaluate(const QString &expression); +}; + +#endif // CALCULATIONENGINE_H diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..59d27a6 --- /dev/null +++ b/config.cpp @@ -0,0 +1,151 @@ +#include "config.h" +#include +#include +#include + +ConfigReader::ConfigReader(QString directory) +{ + this->configDirectory = directory; +} + + +EntryConfig ConfigReader::readFromDesktopFile(const QString &path) +{ + EntryConfig result; + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + //TODO: better exception class + throw new std::runtime_error("Failed to open file"); + } + QTextStream stream(&file); + QString firstline = stream.readLine(); + while(firstline[0] == '#') + { + firstline = stream.readLine(); + } + if(firstline != "[Desktop Entry]") + { + throw new ConfigFormatException(".desktop file does not start with [Desktop Entry]"); + } + + while(!stream.atEnd()) + { + QString line = stream.readLine(); + QStringList splitted = line.split("="); + if(splitted.length() >= 2) + { + qDebug() << splitted [0] + " " + splitted[1]; + QString key = splitted[0].toLower(); + if(key == "name") + { + if(result.name.length() == 0) + { + result.name = splitted[1]; + } + } + if(key == "icon") + { + result.icon = QIcon::fromTheme(splitted[1]); + } + if(key == "exec") + { + //TODO: the other arguments may also be relevant... except for %f and so + + QStringList arguments = splitted[1].split(" "); + + result.command = arguments[0]; + if(arguments.length() > 1) + { + arguments = arguments.mid(1); + } + } + + } + + } + + return result; +} + +/* qsrun own's config file */ +EntryConfig ConfigReader::readFromFile(const QString &path) +{ + EntryConfig result; + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + //TODO: better exception class + throw new std::runtime_error("Failed to open file"); + } + QTextStream stream(&file); + while(!stream.atEnd()) + { + QString line = stream.readLine(); + QStringList splitted = line.split(" "); + if(splitted.length() < 2) + { + // throw new ConfigFormatException("Format must be [key] [value] for every line"); + + } + qDebug() << splitted [0] + " " + splitted[1]; + QString key = splitted[0]; + if(key == "arguments") + { + result.arguments = splitted.mid(1); + } + if(key == "name") + { + result.name = splitted[1]; + } + if(key == "icon") + { + result.icon = QIcon(splitted[1]); + } + if(key == "row") + { + result.row = splitted[1].toInt(); + } + if(key == "col") + { + result.col = splitted[1].toInt(); + } + if(key == "command") + { + result.command = splitted[1]; + } + if(key == "key") + { + //QKeySequence sequence(splitted[1]); + //result.keySequence = sequence; + result.key = splitted[1].toLower(); + } + } + return result; +} + +QVector ConfigReader::readConfig() +{ + QVector result; + QDirIterator it(this->configDirectory); + while(it.hasNext()) + { + QString path = it.next(); + QFileInfo info(path); + if(info.isFile()) + { + QString suffix = info.completeSuffix(); + if(suffix == "desktop") + { + result.append(readFromDesktopFile(path)); + + } + if(suffix == "qsrun") + { + result.append(readFromFile(path)); + } + } + } + + return result; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..976cf98 --- /dev/null +++ b/config.h @@ -0,0 +1,43 @@ +#ifndef CONFIG_H +#define CONFIG_H +#include +#include +#include +#include +#include +#include +#include +class EntryConfig +{ + public: + QString key; + QString name; + QString command; + QStringList arguments; + QIcon icon; + int row=0; + int col=0; + + + +}; + +class ConfigReader +{ + private: + QString configDirectory; + EntryConfig readFromFile(const QString &path); + EntryConfig readFromDesktopFile(const QString &path); + public: + ConfigReader(QString path); + QVector readConfig(); +}; + +class ConfigFormatException : public std::runtime_error +{ +public: + ConfigFormatException() : std::runtime_error("Error in configuration file, misformated line?") {}; + ConfigFormatException(const std::string &str) : std::runtime_error(str) {}; +}; + +#endif diff --git a/entrypushbutton.cpp b/entrypushbutton.cpp new file mode 100644 index 0000000..ac87842 --- /dev/null +++ b/entrypushbutton.cpp @@ -0,0 +1,48 @@ +#include "entrypushbutton.h" + +EntryPushButton::EntryPushButton(const EntryConfig &config) : QPushButton() +{ + this->setText(config.name); + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + this->setIcon(config.icon); + this->setIconSize(config.icon.availableSizes().first()); + this->config = config; + connect(this, SIGNAL(clicked()), this, SLOT(emitOwnClicked())); +} + + + + +void EntryPushButton::emitOwnClicked() +{ + emit clicked(this->config); +} + +const EntryConfig &EntryPushButton::getEntryConfig() +{ + return this->config; +} + +void EntryPushButton::setEntryConfig(const EntryConfig &config) +{ + this->config = config; +} + +void EntryPushButton::showShortcut() +{ + this->setText(this->config.key); +} + +void EntryPushButton::showName() +{ + this->setText(this->config.name); +} + +int EntryPushButton::getRow() const { return config.row; } +int EntryPushButton::getCol() const { return config.col; } +QString EntryPushButton::getName() const { return config.name; } +QString EntryPushButton::getShortcutKey() const { return config.key; } +void EntryPushButton::setRow(int row) { this->config.row = row; } +void EntryPushButton::setCol(int col) { this->config.col = col; } +QStringList EntryPushButton::getArguments() const { return this->config.arguments; } +QString EntryPushButton::getCommand() const { return this->config.command; } diff --git a/entrypushbutton.h b/entrypushbutton.h new file mode 100644 index 0000000..a64f83b --- /dev/null +++ b/entrypushbutton.h @@ -0,0 +1,35 @@ +#ifndef ENTRYPUSHBUTTON_H +#define ENTRYPUSHBUTTON_H + +#include +#include +#include "config.h" +class EntryPushButton : public QPushButton +{ + Q_OBJECT +private: + EntryConfig config; +private slots: + void emitOwnClicked(); + +signals: + void clicked(const EntryConfig &config); +public: + EntryPushButton(const EntryConfig &config); + const EntryConfig &getEntryConfig(); + void setEntryConfig(const EntryConfig &config); + void showShortcut(); + void showName(); + int getRow() const; + int getCol() const; + QString getName() const; + QString getShortcutKey() const; + QStringList getArguments() const; + QString getCommand() const; + void setRow(int row); + void setCol(int col); + + +}; + +#endif // ENTRYPUSHBUTTON_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..943afd5 --- /dev/null +++ b/main.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include "window.h" + + + + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QString configDirectoryPath; + if(argc >= 2) + { + configDirectoryPath = QCoreApplication::arguments().at(1); + } + else + { + configDirectoryPath = QDir::homePath() + "/.config/qsRun/"; + } + qRegisterMetaType >("QVector"); + + QDir dir(configDirectoryPath); + if(!dir.exists(configDirectoryPath)) + { + QMessageBox::warning(nullptr, "Directory not found", configDirectoryPath + " was not found!"); + return 1; + } + QSettings settings(configDirectoryPath + "qsrun.config", QSettings::NativeFormat); + QString dbpath = settings.value("Search/dbpath").toString(); + QString systemApplicationsPath = settings.value("General/systemApplicationsPath", "/usr/share/applications/").toString(); + + QVector configs; + + try + { + ConfigReader reader(configDirectoryPath); + configs = reader.readConfig(); + } + catch(std::exception &e) + { + std::cerr << e.what() << std::endl; + } + + Window w(configs, dbpath); + + /* + * TODO: Reconsider the need + * QFuture future = QtConcurrent::run([&w] { + ConfigReader systemConfigReader("/usr/share/applications/"); + QList systemconfigs = systemConfigReader.readConfig(); + if(systemconfigs.count() > 0) + { + w.setSystemConfig(systemconfigs); + w.systemConfigReady(); + } + });*/ + + + try + { + ConfigReader systemConfigReader(systemApplicationsPath); + QVector systemconfigs = systemConfigReader.readConfig(); + if(systemconfigs.count() > 0) + { + w.setSystemConfig(systemconfigs); + } + } + catch(std::exception &e) + { + std::cerr << e.what() << std::endl; + } + + + + w.showMaximized(); + return app.exec(); +} diff --git a/qsRun.pro b/qsRun.pro new file mode 100644 index 0000000..a80dee5 --- /dev/null +++ b/qsRun.pro @@ -0,0 +1,23 @@ +###################################################################### +# Automatically generated by qmake (3.0) Mon Dec 25 15:21:45 2017 +###################################################################### + +TEMPLATE = app +TARGET = qsRun +INCLUDEPATH += . + +# Input +HEADERS += config.h window.h \ + entrypushbutton.h \ + calculationengine.h \ + searchworker.h +SOURCES += config.cpp main.cpp window.cpp \ + entrypushbutton.cpp \ + calculationengine.cpp \ + searchworker.cpp +QT += widgets sql +QT_CONFIG -= no-pkg-config +LIBS += -lcln +CONFIG += link_pkgconfig +PKGCONFIG += libqalculate + diff --git a/searchworker.cpp b/searchworker.cpp new file mode 100644 index 0000000..a2d72c5 --- /dev/null +++ b/searchworker.cpp @@ -0,0 +1,73 @@ +#include "searchworker.h" +#include +#include +//TODO: we have code duplication in the search functions currently. +SearchWorker::SearchWorker(const QString &dbpath) +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(dbpath); + if(!db.open()) + { + qDebug() << "failed to open database"; + } + queryContent = new QSqlQuery(db); + queryFile = new QSqlQuery(db); + queryFile->prepare("SELECT path FROM file WHERE path LIKE ? ORDER BY mtime DESC"); + queryContent->prepare("SELECT file.path FROM file INNER JOIN file_fts ON file.id = file_fts.ROWID WHERE file_fts.content MATCH ? ORDER By file.mtime DESC"); +} + +void SearchWorker::searchForFile(const QString &query) +{ + this->isPending = true; + this->cancelCurrent = false; + QVector results; + queryFile->addBindValue("%" + query + "%"); + queryFile->exec(); + while(queryFile->next()) + { + if(cancelCurrent) + { + this->isPending = false; + emit searchCancelled(); + return; + } + QString result = queryFile->value(0).toString(); + qDebug() << "result" << result; + results.append(queryFile->value(0).toString()); + } + this->isPending = false; + emit searchResultsReady(results); +} + +void SearchWorker::requestCancellation() +{ + this->cancelCurrent = true; +} + +void SearchWorker::searchForContent(const QString &query) +{ + this->isPending = true; + this->cancelCurrent = false; + QVector results; + queryContent->addBindValue(query); + queryContent->exec(); + while(queryContent->next()) + { + if(cancelCurrent) + { + this->isPending = false; + emit searchCancelled(); + return; + } + QString result = queryContent->value(0).toString(); + qDebug() << "result" << result; + results.append(queryContent->value(0).toString()); + } + this->isPending = false; + emit searchResultsReady(results); +} + +bool SearchWorker::isOperationPending() +{ + return this->isPending; +} diff --git a/searchworker.h b/searchworker.h new file mode 100644 index 0000000..58ff3d2 --- /dev/null +++ b/searchworker.h @@ -0,0 +1,32 @@ +#ifndef SEARCHWORKER_H +#define SEARCHWORKER_H +#include +#include +#include +#include +#include + +class SearchWorker : public QObject +{ + Q_OBJECT +private: + QSqlQuery *queryFile; + QSqlQuery *queryContent; + bool isPending = false; + bool cancelCurrent = false; +public: + SearchWorker(const QString &dbpath); + bool isOperationPending(); + void requestCancellation(); + + +public slots: + void searchForFile(const QString &query); + void searchForContent(const QString &query); +signals: + void searchResultsReady(const QVector &results); + void searchCancelled(); + +}; + +#endif // SEARCHWORKER_H diff --git a/window.cpp b/window.cpp new file mode 100644 index 0000000..658c932 --- /dev/null +++ b/window.cpp @@ -0,0 +1,423 @@ +#include "window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +Window::Window(const QVector &configs, const QString &dbpath) +{ + this->userEntryButtons = generateEntryButtons(configs); + createGui(); + populateGrid(this->userEntryButtons); + searchWorker = new SearchWorker(dbpath); + searchWorker->moveToThread(&searchThread); + connect(this, &Window::beginFileSearch, searchWorker, &SearchWorker::searchForFile); + connect(this, &Window::beginContentSearch, searchWorker, &SearchWorker::searchForContent); + connect(searchWorker, &SearchWorker::searchResultsReady, this, &Window::handleSearchResults); + searchThread.start(); + initTreeWidgets(); + this->lineEdit->installEventFilter(this); + +} + +Window::~Window() +{ + searchThread.quit(); + searchThread.wait(); + +} + +void Window::initTreeWidgets() +{ + QStringList headers; + headers << "Filename"; + headers << "Path"; + headers << "Modification time"; + treeFileSearchResults.setHeaderLabels(headers); + treeFileSearchResults.header()->setSectionResizeMode(QHeaderView::ResizeToContents); + connect(&treeFileSearchResults, &QTreeWidget::itemActivated, this, &Window::treeSearchItemActivated); +} + +QVector Window::generateEntryButtons(const QVector &configs) +{ + QVector result; + for(const EntryConfig &config : configs) + { + EntryPushButton *button = createEntryButton(config); + result.append(button); + } + return result; +} + +void Window::createGui() +{ + QVBoxLayout *vbox = new QVBoxLayout(this); + grid = new QGridLayout(); + lineEdit = new QLineEdit(); + vbox->setAlignment(Qt::AlignTop); + vbox->addWidget(lineEdit); + vbox->addLayout(grid); + connect(lineEdit, &QLineEdit::textChanged, this, [this](QString newtext) { this->lineEditTextChanged(newtext); }); + connect(lineEdit, &QLineEdit::returnPressed, this, &Window::lineEditReturnPressed); +} + +void Window::populateGrid(const QVector &list) +{ + clearGrid(); + for(EntryPushButton *button : list) + { + button->setVisible(true); + grid->addWidget(button, button->getRow(), button->getCol()); + buttonsInGrid.append(button); + } +} + +void Window::buttonClick(const EntryPushButton &config) +{ + QProcess::startDetached(config.getCommand(), config.getArguments()); + qApp->quit(); +} + +QStringList Window::generatePATHSuggestions(const QString &text) +{ + QStringList results; + QString pathVar = QProcessEnvironment::systemEnvironment().value("PATH", "/usr/bin/:/bin/:"); + QStringList paths = pathVar.split(":"); + for(const QString &path : paths) + { + QDirIterator it(path); + while(it.hasNext()) + { + QFileInfo info(it.next()); + if(info.isFile() && info.isExecutable()) + { + QString entry = info.baseName(); + if(entry.startsWith(text)) + { + + results.append(entry); + } + } + } + } + return results; +} + +void Window::addPATHSuggestion(const QString &text) +{ + QStringList suggestions = generatePATHSuggestions(text); + if(suggestions.length() == 1) + { + EntryConfig e; + e.name = suggestions[0]; + e.col=0; + e.row=0; + e.command = suggestions[0]; + e.icon = QIcon::fromTheme(suggestions[0]); + EntryPushButton *button = createEntryButton(e); + clearGrid(); + grid->addWidget(button, 0, 0); + buttonsInGrid.append(button); + } +} + +void Window::clearGrid() +{ + int count = grid->count(); + for(int i = 0; i < count; i++) + { + auto item = grid->itemAt(0)->widget(); + grid->removeWidget(item); + item->setVisible(false); + + } + buttonsInGrid.clear(); +} + +void Window::addCalcResult(const QString &expression) +{ + clearGrid(); + QString calculationresult = calcEngine.evaluate(expression); + QLabel *lbl = new QLabel(); + lbl->setText(expression + ": " + calculationresult); + lbl->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + lbl->setAlignment(Qt::AlignCenter); + lbl->setWordWrap(true); + QFont font; + font.setPointSize(48); + font.setBold(true); + lbl->setFont(font); + grid->addWidget(lbl, 0, 0); +} + +//main problem here there is no easy event compression (clearing emit queue and only processing the last one) +void Window::lineEditTextChanged(QString text) +{ + if(text.length() >= 2) + { + QString input = text.mid(1); + if(text[0] == '=') + { + + addCalcResult(input); + return; + } + if(text[0] == '/') + { + if(searchWorker->isOperationPending()) + { + this->queuedFileSearch = input; + searchWorker->requestCancellation(); + } + else + { + this->queuedFileSearch = ""; + emit beginFileSearch(input); + } + return; + } + if(text[0] == '|') + { + if(searchWorker->isOperationPending()) + { + this->queuedContentSearch = input; + searchWorker->requestCancellation(); + } + else + { + this->queuedContentSearch = ""; + emit beginContentSearch(input); + } + return; + } + } + + filterGridFor(text); + if(this->grid->count() == 0) + { + addPATHSuggestion(text); + if(this->grid->count() == 0) + { + + QStringList arguments = text.split(" "); + EntryConfig e; + e.name = "Execute: " + text; + if(arguments.length() > 1) + { + e.arguments = arguments.mid(1); + } + e.command = arguments[0]; + e.icon = QIcon::fromTheme("utilities-terminal"); + + EntryPushButton *button = createEntryButton(e); + clearGrid(); + grid->addWidget(button, 0, 0); + buttonsInGrid.append(button); + } + } +} + +void Window::keyReleaseEvent(QKeyEvent *event) +{ + if(event->key() == Qt::Key_Control) + { + for(EntryPushButton *button : buttonsInGrid) + { + button->showName(); + } + + } + QWidget::keyReleaseEvent(event); + +} +void Window::keyPressEvent(QKeyEvent *event) +{ + bool quit = ((event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Q) || event->key() == Qt::Key_Escape); + if(quit) + { + qApp->quit(); + } + + if(event->modifiers() & Qt::ControlModifier && buttonsInGrid.count() > 0) + { + + if(event->key() == Qt::Key_L) + { + this->lineEdit->setFocus(); + this->lineEdit->selectAll(); + } + + + for(EntryPushButton *button : buttonsInGrid) + { + button->showShortcut(); + } + + + QKeySequence seq(event->key()); + QString key = seq.toString().toLower(); + + auto it = std::find_if(userEntryButtons.begin(), userEntryButtons.end(), [&key](const EntryPushButton *y) { return y->getShortcutKey() == key; }); + if(it != userEntryButtons.end()) + { + buttonClick(**it); + } + } + QWidget::keyPressEvent(event); +} + +void Window::filterGridFor(QString filter) +{ + if(filter.length() > 0) + { + clearGrid(); + bool userEntryMatch = false; + for(EntryPushButton *button : this->userEntryButtons) + { + if(button->getName().contains(filter, Qt::CaseInsensitive)) + { + button->setVisible(true); + grid->addWidget(button, button->getRow(), button->getCol()); + this->buttonsInGrid.append(button); + userEntryMatch = true; + } + } + if(!userEntryMatch) + { + int currow = 0; + int curcol = 0; + int i = 1; + for(EntryPushButton *button : this->systemEntryButtons) + { + if(button->getName().contains(filter, Qt::CaseInsensitive)) + { + button->setVisible(true); + button->setShortcut(QString::number(i++)); + grid->addWidget(button, currow, curcol++); + this->buttonsInGrid.append(button); + if(curcol == 3) + { + curcol = 0; + ++currow; + } + } + } + } + + + + } + else + { + populateGrid(this->userEntryButtons); + } + +} + +EntryPushButton * Window::createEntryButton(const EntryConfig &entry) +{ + EntryPushButton *button = new EntryPushButton(entry); + connect(button, &EntryPushButton::clicked, this, &Window::buttonClick); + return button; +} + +void Window::lineEditReturnPressed() +{ + if(buttonsInGrid.length() > 0 && this->lineEdit->text().length() > 0 ) + { + buttonClick(*buttonsInGrid[0]); + return; + } + if(this->isSearchMode()) + { + treeSearchItemActivated(treeFileSearchResults.topLevelItem(0), 0); + } +} + +void Window::setSystemConfig(const QVector &config) +{ + this->systemEntryButtons = generateEntryButtons(config); +} + +void Window::handleSearchResults(const QVector &results) +{ + clearGrid(); + treeFileSearchResults.clear(); + for(const QString &path : results) + { + QFileInfo pathInfo(path); + QString fileName =pathInfo.fileName(); + QString absPath = path; + QString datestr = pathInfo.lastModified().toString(Qt::ISODate); + QTreeWidgetItem *item = new QTreeWidgetItem(&treeFileSearchResults); + item->setText(0, fileName); + item->setText(1, absPath); + item->setText(2, datestr); + + } + treeFileSearchResults.resizeColumnToContents(0); + treeFileSearchResults.resizeColumnToContents(2); + treeFileSearchResults.setVisible(true); + + this->grid->addWidget(&treeFileSearchResults, 0, 0); +} + +void Window::handleCancelledSearch() +{ + if(this->queuedFileSearch != "") + { + QString searchFor = this->queuedFileSearch; + this->queuedFileSearch = ""; + emit beginFileSearch(searchFor); + } + if(this->queuedContentSearch != "") + { + QString searchFor = this->queuedContentSearch; + this->queuedContentSearch = ""; + emit beginContentSearch(searchFor); + } +} + +void Window::treeSearchItemActivated(QTreeWidgetItem *item, int i) +{ + qDebug() << item->text(1); + QDesktopServices::openUrl(QUrl::fromLocalFile(item->text(1))); +} + + +bool Window::eventFilter(QObject *obj, QEvent *event) +{ + + if(obj == this->lineEdit) + { + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if(keyEvent->key() == Qt::Key_Tab) + { + QStringList suggestions = generatePATHSuggestions(this->lineEdit->text()); + if(suggestions.length() == 1) + { + this->lineEdit->setText(suggestions[0] + " "); + this->lineEdit->setCursorPosition(this->lineEdit->text().length()+1); + } + + return true; + } + } + + } + return QObject::eventFilter(obj, event); + +} + +bool Window::isSearchMode() +{ + QChar c = this->lineEdit->text()[0]; + return c == '/' || c == '|'; +} diff --git a/window.h b/window.h new file mode 100644 index 0000000..72814f7 --- /dev/null +++ b/window.h @@ -0,0 +1,68 @@ +#ifndef WINDOW_H +#define WINDOW_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "entrypushbutton.h" +#include "calculationengine.h" +#include "searchworker.h" + +class Window : public QWidget +{ + Q_OBJECT + private: + QThread searchThread; + SearchWorker *searchWorker; + CalculationEngine calcEngine; + QVector userEntryButtons; + QVector systemEntryButtons; + QVector buttonsInGrid; + QTreeWidget treeFileSearchResults; + QString queuedFileSearch; + QString queuedContentSearch; + void createGui(); + void filterGridFor(QString filter); + void populateGrid(const QVector &list); + void keyReleaseEvent(QKeyEvent *event); + QVector generateEntryButtons(const QVector &userEntryButtons); + void keyPressEvent(QKeyEvent *event); + void buttonClick(const EntryPushButton &config); + QLineEdit *lineEdit; + QGridLayout *grid; + EntryPushButton *createEntryButton(const EntryConfig &config); + void lineEditTextChanged(QString text); + void addPATHSuggestion(const QString &text); + void clearGrid(); + void addCalcResult(const QString & expression); + void initTreeWidgets(); + QStringList generatePATHSuggestions(const QString &text); + bool isSearchMode(); + private slots: + void lineEditReturnPressed(); + void handleSearchResults(const QVector &results); + void handleCancelledSearch(); + void treeSearchItemActivated(QTreeWidgetItem *item, int i); + signals: + void beginFileSearch(const QString &query); + void beginContentSearch(const QString &query); + public: + Window(const QVector &userEntryButtons, const QString &dbpath); + void setSystemConfig(const QVector &config); + bool eventFilter(QObject *obj, QEvent *event); + ~Window(); + +}; + +#endif