looqs/gui/mainwindow.cpp

336 rader
9.9 KiB
C++

#include <poppler-qt5.h>
#include <QLabel>
#include <QtDebug>
#include <QFileInfo>
#include <QDesktopServices>
#include <QUrl>
#include <QClipboard>
#include <QtGlobal>
#include <QSettings>
#include <QDateTime>
#include <QProcess>
#include <QComboBox>
#include <QtConcurrent/QtConcurrent>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "clicklabel.h"
#include "../shared/sqlitesearch.h"
#include "../shared/looqsgeneralexception.h"
#include "../shared/common.h"
MainWindow::MainWindow(QWidget *parent, IPCClient &client) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle(QCoreApplication::applicationName());
this->ipcClient = &client;
QSettings settings;
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(Common::databasePath());
if(!db.open())
{
qDebug() << "failed to open database";
throw std::runtime_error("Failed to open database");
}
connectSignals();
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
ui->tabWidget->setCurrentIndex(0);
ui->statusBar->addWidget(ui->lblSearchResults);
ui->statusBar->addWidget(ui->previewProcessBar);
ui->previewProcessBar->hide();
ui->comboScale->setCurrentText(settings.value("currentScale").toString());
previewsPerPage = settings.value("previewsPerPage", 20).toInt();
ui->spinPreviewPage->setMinimum(1);
}
void MainWindow::connectSignals()
{
connect(ui->txtSearch, &QLineEdit::returnPressed, this, &MainWindow::lineEditReturnPressed);
connect(&searchWatcher, &QFutureWatcher<SearchResult>::finished, this,
[&]
{
try
{
this->ui->txtSearch->setEnabled(true);
auto results = searchWatcher.future().result();
handleSearchResults(results);
}
catch(LooqsGeneralException &e)
{
handleSearchError(e.message);
}
});
connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::resultReadyAt, this,
[&](int index) { previewReceived(previewWorkerWatcher.resultAt(index)); });
connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::progressValueChanged,
ui->previewProcessBar, &QProgressBar::setValue);
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
&MainWindow::showSearchResultsContextMenu);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
connect(ui->comboScale, qOverload<int>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged);
connect(ui->spinPreviewPage, qOverload<int>(&QSpinBox::valueChanged), this,
&MainWindow::spinPreviewPageValueChanged);
}
void MainWindow::spinPreviewPageValueChanged(int val)
{
makePreviews(val);
}
void MainWindow::comboScaleChanged(int i)
{
QSettings scaleSetting;
scaleSetting.setValue("currentScale", ui->comboScale->currentText());
makePreviews(ui->spinPreviewPage->value());
}
bool MainWindow::previewTabActive()
{
return ui->tabWidget->currentIndex() == 1;
}
void MainWindow::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)
{
if(event->key() == Qt::Key_L)
{
ui->txtSearch->setFocus();
ui->txtSearch->selectAll();
}
}
QWidget::keyPressEvent(event);
}
void MainWindow::tabChanged()
{
if(previewTabActive())
{
if(previewDirty)
{
makePreviews(ui->spinPreviewPage->value());
}
ui->previewProcessBar->show();
}
else
{
ui->previewProcessBar->hide();
}
}
void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview)
{
if(preview->hasPreview())
{
QString docPath = preview->getDocumentPath();
auto previewPage = preview->getPage();
ClickLabel *label = dynamic_cast<ClickLabel *>(preview->createPreviewWidget());
ui->scrollAreaWidgetContents->layout()->addWidget(label);
connect(label, &ClickLabel::leftClick, [this, docPath, previewPage]() { ipcDocOpen(docPath, previewPage); });
connect(label, &ClickLabel::rightClick,
[this, docPath, previewPage]()
{
QFileInfo fileInfo{docPath};
QMenu menu("labeRightClick", this);
createSearchResutlMenu(menu, fileInfo);
menu.addAction("Copy page number", [previewPage]
{ QGuiApplication::clipboard()->setText(QString::number(previewPage)); });
menu.exec(QCursor::pos());
});
}
}
void MainWindow::lineEditReturnPressed()
{
QString q = ui->txtSearch->text();
if(!LooqsQuery::checkParanthesis(q))
{
ui->lblSearchResults->setText("Invalid paranthesis");
return;
}
// TODO: validate q;
ui->lblSearchResults->setText("Searching...");
this->ui->txtSearch->setEnabled(false);
QFuture<QVector<SearchResult>> searchFuture = QtConcurrent::run(
[&, q]()
{
SqliteSearch searcher(db);
QVector<SearchResult> results;
this->contentSearchQuery = LooqsQuery::build(q, TokenType::FILTER_CONTENT_CONTAINS, true);
/* We can have a path search in contentsearch too (if given explicitly), so no need to do it twice.
Make sure path results are listed first. */
bool addContentSearch = this->contentSearchQuery.hasContentSearch();
bool addPathSearch = !this->contentSearchQuery.hasPathSearch() || !addContentSearch;
if(addPathSearch)
{
LooqsQuery filesQuery = LooqsQuery::build(q, TokenType::FILTER_PATH_CONTAINS, false);
results.append(searcher.search(filesQuery));
}
if(addContentSearch)
{
results.append(searcher.search(this->contentSearchQuery));
}
return results;
});
searchWatcher.setFuture(searchFuture);
}
void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
{
this->previewableSearchResults.clear();
ui->treeResultsList->clear();
bool hasDeleted = false;
for(const SearchResult &result : results)
{
QFileInfo pathInfo(result.fileData.absPath);
QString fileName = pathInfo.fileName();
QTreeWidgetItem *item = new QTreeWidgetItem(ui->treeResultsList);
QDateTime dt = QDateTime::fromSecsSinceEpoch(result.fileData.mtime);
item->setIcon(0, iconProvider.icon(pathInfo));
item->setText(0, fileName);
item->setText(1, result.fileData.absPath);
item->setText(2, dt.toString(Qt::ISODate));
item->setText(3, this->locale().formattedDataSize(result.fileData.size));
bool exists = pathInfo.exists();
if(exists)
{
if(result.fileData.absPath.endsWith(".pdf"))
{
this->previewableSearchResults.append(result);
}
}
else
{
hasDeleted = true;
}
}
ui->treeResultsList->resizeColumnToContents(0);
ui->treeResultsList->resizeColumnToContents(1);
previewDirty = !this->previewableSearchResults.empty();
int numpages = ceil(static_cast<double>(this->previewableSearchResults.size()) / previewsPerPage);
ui->spinPreviewPage->setMinimum(1);
ui->spinPreviewPage->setMaximum(numpages);
ui->spinPreviewPage->setValue(1);
if(previewTabActive() && previewDirty)
{
makePreviews(1);
}
QString statusText = "Results: " + QString::number(results.size()) + " files";
if(hasDeleted)
{
statusText += " WARNING: Some files don't exist anymore. No preview available for those. Index out of sync";
}
ui->lblSearchResults->setText(statusText);
}
void MainWindow::makePreviews(int page)
{
this->previewWorkerWatcher.cancel();
this->previewWorkerWatcher.waitForFinished();
QCoreApplication::processEvents(); // Maybe not necessary anymore, depends on whether it's possible that a slot is
// still to be fired.
qDeleteAll(ui->scrollAreaWidgetContents->children());
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
ui->previewProcessBar->setMaximum(this->previewableSearchResults.size());
processedPdfPreviews = 0;
QString scaleText = ui->comboScale->currentText();
scaleText.chop(1);
QVector<QString> wordsToHighlight;
QRegularExpression extractor(R"#("([^"]*)"|(\w+))#");
for(const Token &token : this->contentSearchQuery.getTokens())
{
if(token.type == FILTER_CONTENT_CONTAINS)
{
QRegularExpressionMatchIterator i = extractor.globalMatch(token.value);
while(i.hasNext())
{
QRegularExpressionMatch m = i.next();
QString value = m.captured(1);
if(value.isEmpty())
{
value = m.captured(2);
}
wordsToHighlight.append(value);
}
}
}
PreviewWorker worker;
int end = previewsPerPage;
int begin = page * previewsPerPage - previewsPerPage;
this->previewWorkerWatcher.setFuture(worker.generatePreviews(this->previewableSearchResults.mid(begin, end),
wordsToHighlight, scaleText.toInt() / 100.));
ui->previewProcessBar->setMaximum(this->previewWorkerWatcher.progressMaximum());
ui->previewProcessBar->setMinimum(this->previewWorkerWatcher.progressMinimum());
}
void MainWindow::handleSearchError(QString error)
{
ui->lblSearchResults->setText("Error:" + error);
}
void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo)
{
menu.addAction("Copy filename to clipboard",
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); });
menu.addAction("Copy full path to clipboard",
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); });
menu.addAction("Open containing folder", [this, &fileInfo] { this->ipcFileOpen(fileInfo.absolutePath()); });
}
void MainWindow::ipcDocOpen(QString path, int num)
{
QStringList args;
args << path;
args << QString::number(num);
this->ipcClient->sendCommand(DocOpen, args);
}
void MainWindow::ipcFileOpen(QString path)
{
QStringList args;
args << path;
this->ipcClient->sendCommand(FileOpen, args);
}
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i)
{
ipcFileOpen(item->text(1));
}
void MainWindow::showSearchResultsContextMenu(const QPoint &point)
{
QTreeWidgetItem *item = ui->treeResultsList->itemAt(point);
if(item == nullptr)
{
return;
}
QFileInfo pathinfo(item->text(1));
QMenu menu("SearchResults", this);
createSearchResutlMenu(menu, pathinfo);
menu.exec(QCursor::pos());
}
MainWindow::~MainWindow()
{
delete ui;
}