first commit
This commit is contained in:
當前提交
f96452d669
90
README.md
Normal file
90
README.md
Normal file
@ -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.
|
||||
|
||||
|
42
calculationengine.cpp
Normal file
42
calculationengine.cpp
Normal file
@ -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();
|
||||
|
||||
|
||||
}
|
22
calculationengine.h
Normal file
22
calculationengine.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef CALCULATIONENGINE_H
|
||||
#define CALCULATIONENGINE_H
|
||||
#include <QString>
|
||||
#include <libqalculate/Calculator.h>
|
||||
#include <libqalculate/ExpressionItem.h>
|
||||
#include <libqalculate/Unit.h>
|
||||
#include <libqalculate/Prefix.h>
|
||||
#include <libqalculate/Variable.h>
|
||||
#include <libqalculate/Function.h>
|
||||
|
||||
class CalculationEngine
|
||||
{
|
||||
private:
|
||||
EvaluationOptions eo;
|
||||
PrintOptions po;
|
||||
|
||||
public:
|
||||
CalculationEngine();
|
||||
QString evaluate(const QString &expression);
|
||||
};
|
||||
|
||||
#endif // CALCULATIONENGINE_H
|
151
config.cpp
Normal file
151
config.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "config.h"
|
||||
#include <QDirIterator>
|
||||
#include <QDebug>
|
||||
#include <QTextStream>
|
||||
|
||||
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<EntryConfig> ConfigReader::readConfig()
|
||||
{
|
||||
QVector<EntryConfig> 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;
|
||||
}
|
43
config.h
Normal file
43
config.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
#include <exception>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QIcon>
|
||||
#include <QKeySequence>
|
||||
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<EntryConfig> 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
|
48
entrypushbutton.cpp
Normal file
48
entrypushbutton.cpp
Normal file
@ -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; }
|
35
entrypushbutton.h
Normal file
35
entrypushbutton.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef ENTRYPUSHBUTTON_H
|
||||
#define ENTRYPUSHBUTTON_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#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
|
80
main.cpp
Normal file
80
main.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include <QApplication>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QSettings>
|
||||
#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<QString> >("QVector<QString>");
|
||||
|
||||
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<EntryConfig> 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<void> future = QtConcurrent::run([&w] {
|
||||
ConfigReader systemConfigReader("/usr/share/applications/");
|
||||
QList<EntryConfig> systemconfigs = systemConfigReader.readConfig();
|
||||
if(systemconfigs.count() > 0)
|
||||
{
|
||||
w.setSystemConfig(systemconfigs);
|
||||
w.systemConfigReady();
|
||||
}
|
||||
});*/
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
ConfigReader systemConfigReader(systemApplicationsPath);
|
||||
QVector<EntryConfig> 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();
|
||||
}
|
23
qsRun.pro
Normal file
23
qsRun.pro
Normal file
@ -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
|
||||
|
73
searchworker.cpp
Normal file
73
searchworker.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "searchworker.h"
|
||||
#include <QDebug>
|
||||
#include <QSqlError>
|
||||
//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<QString> 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<QString> 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;
|
||||
}
|
32
searchworker.h
Normal file
32
searchworker.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef SEARCHWORKER_H
|
||||
#define SEARCHWORKER_H
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
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<QString> &results);
|
||||
void searchCancelled();
|
||||
|
||||
};
|
||||
|
||||
#endif // SEARCHWORKER_H
|
423
window.cpp
Normal file
423
window.cpp
Normal file
@ -0,0 +1,423 @@
|
||||
#include "window.h"
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QDirIterator>
|
||||
#include <QIcon>
|
||||
#include <QKeySequence>
|
||||
#include <QLabel>
|
||||
#include <QDate>
|
||||
#include <QHeaderView>
|
||||
#include <QDesktopServices>
|
||||
Window::Window(const QVector<EntryConfig> &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<EntryPushButton*> Window::generateEntryButtons(const QVector<EntryConfig> &configs)
|
||||
{
|
||||
QVector<EntryPushButton*> 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<EntryPushButton *> &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<EntryConfig> &config)
|
||||
{
|
||||
this->systemEntryButtons = generateEntryButtons(config);
|
||||
}
|
||||
|
||||
void Window::handleSearchResults(const QVector<QString> &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<QKeyEvent *>(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 == '|';
|
||||
}
|
68
window.h
Normal file
68
window.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
#include <QWidget>
|
||||
#include <QList>
|
||||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QStyle>
|
||||
#include <QKeyEvent>
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
#include <QThread>
|
||||
#include <QTreeWidget>
|
||||
#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<EntryPushButton*> userEntryButtons;
|
||||
QVector<EntryPushButton*> systemEntryButtons;
|
||||
QVector<EntryPushButton *> buttonsInGrid;
|
||||
QTreeWidget treeFileSearchResults;
|
||||
QString queuedFileSearch;
|
||||
QString queuedContentSearch;
|
||||
void createGui();
|
||||
void filterGridFor(QString filter);
|
||||
void populateGrid(const QVector<EntryPushButton *> &list);
|
||||
void keyReleaseEvent(QKeyEvent *event);
|
||||
QVector<EntryPushButton *> generateEntryButtons(const QVector<EntryConfig> &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<QString> &results);
|
||||
void handleCancelledSearch();
|
||||
void treeSearchItemActivated(QTreeWidgetItem *item, int i);
|
||||
signals:
|
||||
void beginFileSearch(const QString &query);
|
||||
void beginContentSearch(const QString &query);
|
||||
public:
|
||||
Window(const QVector<EntryConfig> &userEntryButtons, const QString &dbpath);
|
||||
void setSystemConfig(const QVector<EntryConfig> &config);
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
~Window();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
載入中…
新增問題並參考
Block a user