From 68ab917756edeaeb6281a3b53952228f1a0112e9 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 12 Aug 2018 16:45:39 +0200 Subject: [PATCH] begin work on qt gui - basic search & pdf preview --- gui/clicklabel.cpp | 6 ++ gui/clicklabel.h | 17 +++++ gui/main.cpp | 19 +++++ gui/mainwindow.cpp | 160 +++++++++++++++++++++++++++++++++++++++++++ gui/mainwindow.h | 53 ++++++++++++++ gui/mainwindow.ui | 132 +++++++++++++++++++++++++++++++++++ gui/pdfpreview.cpp | 5 ++ gui/pdfpreview.h | 13 ++++ gui/pdfworker.cpp | 44 ++++++++++++ gui/pdfworker.h | 26 +++++++ gui/qss.pro | 49 +++++++++++++ gui/searchresult.cpp | 5 ++ gui/searchresult.h | 14 ++++ gui/searchworker.cpp | 55 +++++++++++++++ gui/searchworker.h | 28 ++++++++ 15 files changed, 626 insertions(+) create mode 100644 gui/clicklabel.cpp create mode 100644 gui/clicklabel.h create mode 100644 gui/main.cpp create mode 100644 gui/mainwindow.cpp create mode 100644 gui/mainwindow.h create mode 100644 gui/mainwindow.ui create mode 100644 gui/pdfpreview.cpp create mode 100644 gui/pdfpreview.h create mode 100644 gui/pdfworker.cpp create mode 100644 gui/pdfworker.h create mode 100644 gui/qss.pro create mode 100644 gui/searchresult.cpp create mode 100644 gui/searchresult.h create mode 100644 gui/searchworker.cpp create mode 100644 gui/searchworker.h diff --git a/gui/clicklabel.cpp b/gui/clicklabel.cpp new file mode 100644 index 0000000..692ffc1 --- /dev/null +++ b/gui/clicklabel.cpp @@ -0,0 +1,6 @@ +#include "clicklabel.h" + +void ClickLabel::mousePressEvent(QMouseEvent *event) +{ + emit clicked(); +} diff --git a/gui/clicklabel.h b/gui/clicklabel.h new file mode 100644 index 0000000..28b6c63 --- /dev/null +++ b/gui/clicklabel.h @@ -0,0 +1,17 @@ +#ifndef CLICKLABEL_H +#define CLICKLABEL_H +#include + +class ClickLabel : public QLabel +{ + Q_OBJECT + public: + using QLabel::QLabel; + signals: + void clicked(); + + protected: + void mousePressEvent(QMouseEvent *event); +}; + +#endif // CLICKLABEL_H diff --git a/gui/main.cpp b/gui/main.cpp new file mode 100644 index 0000000..bca1d4a --- /dev/null +++ b/gui/main.cpp @@ -0,0 +1,19 @@ +#include "mainwindow.h" +#include +#include +#include "searchresult.h" +#include "pdfpreview.h" +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("quitesimple.org"); + QCoreApplication::setOrganizationDomain("quitesimple.org"); + QCoreApplication::setApplicationName("qss"); + QApplication a(argc, argv); + qRegisterMetaType>("QVector"); + qRegisterMetaType>("QVector"); + qRegisterMetaType("PdfPreview"); + MainWindow w; + w.showMaximized(); + + return a.exec(); +} diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp new file mode 100644 index 0000000..5b823fd --- /dev/null +++ b/gui/mainwindow.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "clicklabel.h" +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + searchWorker = new SearchWorker(QSettings().value("dbpath").toString()); + pdfWorker = new PdfWorker(); + searchWorker->moveToThread(&searchThread); + pdfWorker->moveToThread(&pdfWorkerThread); + connectSignals(); + searchThread.start(); + ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + + ui->tabWidget->setCurrentIndex(0); +} + +void MainWindow::connectSignals() +{ + connect(ui->txtSearch, &QLineEdit::textChanged, this, &MainWindow::lineEditTextChanged); + connect(ui->txtSearch, &QLineEdit::returnPressed, this, &MainWindow::lineEditReturnPressed); + connect(this, &MainWindow::beginFileSearch, searchWorker, &SearchWorker::searchForFile); + connect(this, &MainWindow::beginContentSearch, searchWorker, &SearchWorker::searchForContent); + connect(searchWorker, &SearchWorker::searchResultsReady, this, &MainWindow::handleSearchResults); + connect(searchWorker, &SearchWorker::searchCancelled, this, &MainWindow::handleCancelledSearch); + 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(this, &MainWindow::startPdfPreviewGeneration, pdfWorker, &PdfWorker::generatePreviews); + connect(pdfWorker, &PdfWorker::previewReady, this, &MainWindow::pdfPreviewReceived); + connect(pdfWorker, &PdfWorker::previewsFinished, [&] { this->pdfDirty = false; }); +} + +bool MainWindow::pdfTabActive() +{ + return ui->tabWidget->currentIndex() == 1; +} + +void MainWindow::tabChanged() +{ + if(pdfTabActive() && pdfDirty) + { + makePdfPreview(); + } +} + +void MainWindow::pdfPreviewReceived(PdfPreview preview) +{ + ClickLabel *label = new ClickLabel(); + label->setPixmap(QPixmap::fromImage(preview.previewImage)); + ui->scrollAreaWidgetContents->layout()->addWidget(label); + connect(label, &ClickLabel::clicked, + [=]() { QDesktopServices::openUrl(QUrl::fromLocalFile(preview.documentPath)); }); +} + +void MainWindow::lineEditReturnPressed() +{ + if(pdfTabActive() && pdfDirty) + { + makePdfPreview(); + } +} + +void MainWindow::lineEditTextChanged() +{ + QString q = ui->txtSearch->text(); + if(q.startsWith("|")) + { + q = q.mid(1); + emit beginContentSearch(q); + } + else + { + emit beginFileSearch(q); + } +} + +void MainWindow::handleSearchResults(const QVector &results) +{ + this->pdfSearchResults.clear(); + ui->treeResultsList->clear(); + + for(const SearchResult &result : results) + { + QFileInfo pathInfo(result.path); + QString fileName = pathInfo.fileName(); + QTreeWidgetItem *item = new QTreeWidgetItem(ui->treeResultsList); + QDateTime dt = QDateTime::fromSecsSinceEpoch(result.mtime); + item->setIcon(0, iconProvider.icon(pathInfo)); + item->setText(0, fileName); + item->setText(1, result.path); + item->setText(2, dt.toString(Qt::ISODate)); + // TODO: this must be user definied or done more intelligently + if(this->pdfSearchResults.size() < 300) + { + if(result.path.endsWith(".pdf")) + { + this->pdfSearchResults.append(result); + } + } + } + ui->treeResultsList->resizeColumnToContents(0); + ui->treeResultsList->resizeColumnToContents(1); + pdfDirty = !this->pdfSearchResults.empty(); +} + +void MainWindow::makePdfPreview() +{ + if(!pdfWorkerThread.isRunning()) + pdfWorkerThread.start(); + qDeleteAll(ui->scrollAreaWidgetContents->children()); + + ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout()); + emit startPdfPreviewGeneration(this->pdfSearchResults, 0.75); +} + +void MainWindow::handleCancelledSearch() +{ +} + +void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i) +{ + QDesktopServices::openUrl(QUrl::fromLocalFile(item->text(1))); +} + +void MainWindow::showSearchResultsContextMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeResultsList->itemAt(point); + if(item == nullptr) + { + return; + } + QMenu menu("SearchResult", this); + menu.addAction("Copy filename to clipboard", [&] { QGuiApplication::clipboard()->setText(item->text(0)); }); + menu.addAction("Copy full path to clipboard", [&] { QGuiApplication::clipboard()->setText(item->text(1)); }); + menu.addAction("Open containing folder", + [&] + { + QFileInfo pathinfo(item->text(1)); + QString dir = pathinfo.absolutePath(); + QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + }); + menu.exec(QCursor::pos()); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h new file mode 100644 index 0000000..2749dc7 --- /dev/null +++ b/gui/mainwindow.h @@ -0,0 +1,53 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include + +#include "searchworker.h" +#include "pdfworker.h" +namespace Ui +{ +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + signals: + void beginFileSearch(const QString &query); + void beginContentSearch(const QString &query); + void startPdfPreviewGeneration(QVector paths, double scalefactor); + + private: + Ui::MainWindow *ui; + QFileIconProvider iconProvider; + bool pdfDirty; + SearchWorker *searchWorker; + PdfWorker *pdfWorker; + void add(QString path, unsigned int page); + QThread searchThread; + QThread pdfWorkerThread; + QVector pdfSearchResults; + void connectSignals(); + void makePdfPreview(); + bool pdfTabActive(); + private slots: + void lineEditReturnPressed(); + void lineEditTextChanged(); + void handleSearchResults(const QVector &results); + void handleCancelledSearch(); + void treeSearchItemActivated(QTreeWidgetItem *item, int i); + + void showSearchResultsContextMenu(const QPoint &point); + void tabChanged(); + void pdfPreviewReceived(PdfPreview preview); +}; + +#endif // MAINWINDOW_H diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui new file mode 100644 index 0000000..44ad6d6 --- /dev/null +++ b/gui/mainwindow.ui @@ -0,0 +1,132 @@ + + + MainWindow + + + + 0 + 0 + 1221 + 614 + + + + MainWindow + + + + + + + + + + QTabWidget::South + + + 1 + + + + Search results + + + + + + + Filename + + + + + Path + + + + + Last modified + + + + + Size + + + + + + + + + PDF-Preview + + + + + + Qt::LeftToRight + + + true + + + Qt::AlignCenter + + + + + 0 + 0 + 1179 + 428 + + + + + + + + + + + + Scale + + + + + + + + + + + + + + + + + + 0 + 0 + 1221 + 20 + + + + + + TopToolBarArea + + + false + + + + + + + + diff --git a/gui/pdfpreview.cpp b/gui/pdfpreview.cpp new file mode 100644 index 0000000..034fbde --- /dev/null +++ b/gui/pdfpreview.cpp @@ -0,0 +1,5 @@ +#include "pdfpreview.h" + +PdfPreview::PdfPreview() +{ +} diff --git a/gui/pdfpreview.h b/gui/pdfpreview.h new file mode 100644 index 0000000..71ac505 --- /dev/null +++ b/gui/pdfpreview.h @@ -0,0 +1,13 @@ +#ifndef PDFPREVIEW_H +#define PDFPREVIEW_H +#include + +class PdfPreview +{ + public: + PdfPreview(); + QImage previewImage; + QString documentPath; +}; + +#endif // PDFPREVIEW_H diff --git a/gui/pdfworker.cpp b/gui/pdfworker.cpp new file mode 100644 index 0000000..da8ccd4 --- /dev/null +++ b/gui/pdfworker.cpp @@ -0,0 +1,44 @@ + +#include +#include +#include +#include "pdfworker.h" + +PdfWorker::PdfWorker() +{ +} + +Poppler::Document *PdfWorker::document(QString path) +{ + if(this->documentcache.contains(path)) + return this->documentcache.value(path); + + Poppler::Document *result = Poppler::Document::load(path); + result->setRenderHint(Poppler::Document::TextAntialiasing); + this->documentcache.insert(path, result); + return result; +} +void PdfWorker::generatePreviews(QVector paths, double scalefactor) +{ + for(SearchResult &sr : paths) + { + Poppler::Document *doc = document(sr.path); + qDebug() << sr.path; + if(doc->isLocked()) + { + continue; + } + int p = (int)sr.page - 1; + if(p < 0) + p = 0; + Poppler::Page *pdfpage = doc->page(p); + QImage image = pdfpage->renderToImage(QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor, + QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor); + + PdfPreview preview; + preview.previewImage = image; + preview.documentPath = sr.path; + emit previewReady(preview); + } + emit previewsFinished(); +} diff --git a/gui/pdfworker.h b/gui/pdfworker.h new file mode 100644 index 0000000..7b243b2 --- /dev/null +++ b/gui/pdfworker.h @@ -0,0 +1,26 @@ +#ifndef PDFWORKER_H +#define PDFWORKER_H +#include +#include +#include +#include +#include "pdfpreview.h" +#include "searchresult.h" +class PdfWorker : public QObject +{ + Q_OBJECT + + private: + QHash documentcache; + Poppler::Document *document(QString path); + + public: + PdfWorker(); + public slots: + void generatePreviews(QVector paths, double scalefactor); + signals: + void previewReady(PdfPreview p); + void previewsFinished(); +}; + +#endif // PDFWORKER_H diff --git a/gui/qss.pro b/gui/qss.pro new file mode 100644 index 0000000..ae65d8c --- /dev/null +++ b/gui/qss.pro @@ -0,0 +1,49 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2018-08-09T12:23:48 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = qss +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + searchresult.cpp \ + searchworker.cpp \ + pdfworker.cpp \ + pdfpreview.cpp \ + clicklabel.cpp + +HEADERS += \ + mainwindow.h \ + searchresult.h \ + searchworker.h \ + pdfworker.h \ + pdfpreview.h \ + clicklabel.h + +FORMS += \ + mainwindow.ui + +INCLUDEPATH += /usr/include/poppler/qt5/ +LIBS += -lpoppler-qt5 +QT += widgets sql + diff --git a/gui/searchresult.cpp b/gui/searchresult.cpp new file mode 100644 index 0000000..a148b18 --- /dev/null +++ b/gui/searchresult.cpp @@ -0,0 +1,5 @@ +#include "searchresult.h" + +SearchResult::SearchResult() +{ +} diff --git a/gui/searchresult.h b/gui/searchresult.h new file mode 100644 index 0000000..f124197 --- /dev/null +++ b/gui/searchresult.h @@ -0,0 +1,14 @@ +#ifndef SEARCHRESULT_H +#define SEARCHRESULT_H +#include + +class SearchResult +{ + public: + unsigned int page; + QString path; + uint64_t mtime; + SearchResult(); +}; + +#endif // SEARCHRESULT_H diff --git a/gui/searchworker.cpp b/gui/searchworker.cpp new file mode 100644 index 0000000..db44057 --- /dev/null +++ b/gui/searchworker.cpp @@ -0,0 +1,55 @@ +#include "searchworker.h" + +#include +SearchWorker::SearchWorker() +{ +} +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, mtime FROM file WHERE path LIKE ? ORDER BY mtime DESC"); + + queryContent->prepare("SELECT file.path, content.page, file.mtime FROM file INNER JOIN content ON file.id = " + "content.fileid INNER JOIN content_fts ON content.id = content_fts.ROWID WHERE " + "content_fts.content MATCH ? ORDER By file.mtime DESC, content.page ASC"); +} + +void SearchWorker::searchForFile(const QString &query) +{ + QVector results; + queryFile->addBindValue("%" + query + "%"); + queryFile->exec(); + while(queryFile->next()) + { + SearchResult result; + result.page = 0; + result.path = queryFile->value(0).toString(); + result.mtime = queryFile->value(1).toUInt(); + + results.append(result); + } + emit searchResultsReady(results); +} +void SearchWorker::searchForContent(const QString &query) +{ + QVector results; + queryContent->addBindValue(query); + queryContent->exec(); + while(queryContent->next()) + { + SearchResult result; + + result.path = queryContent->value(0).toString(); + result.page = queryContent->value(1).toUInt(); + result.mtime = queryContent->value(2).toUInt(); + results.append(result); + } + emit searchResultsReady(results); +} diff --git a/gui/searchworker.h b/gui/searchworker.h new file mode 100644 index 0000000..7252a84 --- /dev/null +++ b/gui/searchworker.h @@ -0,0 +1,28 @@ +#ifndef SEARCHWORKER_H +#define SEARCHWORKER_H +#include +#include +#include +#include +#include +#include "searchresult.h" + +class SearchWorker : public QObject +{ + Q_OBJECT + private: + QSqlQuery *queryFile; + QSqlQuery *queryContent; + + public: + SearchWorker(); + SearchWorker(const QString &dbpath); + public slots: + void searchForFile(const QString &query); + void searchForContent(const QString &query); + signals: + void searchResultsReady(const QVector &results); + void searchCancelled(); +}; + +#endif // SEARCHWORKER_H