439 lines
10 KiB
C++
439 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2018 Albert S. <mail at quitesimple dot org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
#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 == '|';
|
|
}
|