begin work on qt gui - basic search & pdf preview

This commit is contained in:
Albert S. 2018-08-12 16:45:39 +02:00
parent e006a95673
commit 68ab917756
15 changed files with 626 additions and 0 deletions

6
gui/clicklabel.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "clicklabel.h"
void ClickLabel::mousePressEvent(QMouseEvent *event)
{
emit clicked();
}

17
gui/clicklabel.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef CLICKLABEL_H
#define CLICKLABEL_H
#include <QLabel>
class ClickLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event);
};
#endif // CLICKLABEL_H

19
gui/main.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "mainwindow.h"
#include <QApplication>
#include <QSettings>
#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<SearchResult>>("QVector<SearchResult>");
qRegisterMetaType<QVector<PdfPreview>>("QVector<PdfPreview>");
qRegisterMetaType<PdfPreview>("PdfPreview");
MainWindow w;
w.showMaximized();
return a.exec();
}

160
gui/mainwindow.cpp Normal file
View File

@ -0,0 +1,160 @@
#include <poppler-qt5.h>
#include <QLabel>
#include <QtDebug>
#include <QFileInfo>
#include <QDesktopServices>
#include <QUrl>
#include <QClipboard>
#include <QSettings>
#include <QDateTime>
#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<SearchResult> &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;
}

53
gui/mainwindow.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
#include <QTreeWidgetItem>
#include <QFileIconProvider>
#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<SearchResult> 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<SearchResult> pdfSearchResults;
void connectSignals();
void makePdfPreview();
bool pdfTabActive();
private slots:
void lineEditReturnPressed();
void lineEditTextChanged();
void handleSearchResults(const QVector<SearchResult> &results);
void handleCancelledSearch();
void treeSearchItemActivated(QTreeWidgetItem *item, int i);
void showSearchResultsContextMenu(const QPoint &point);
void tabChanged();
void pdfPreviewReceived(PdfPreview preview);
};
#endif // MAINWINDOW_H

132
gui/mainwindow.ui Normal file
View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1221</width>
<height>614</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="txtSearch"/>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::South</enum>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="resultsTab">
<attribute name="title">
<string>Search results</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTreeWidget" name="treeResultsList">
<column>
<property name="text">
<string>Filename</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>Last modified</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="pdfPreviewTab">
<attribute name="title">
<string>PDF-Preview</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1179</width>
<height>428</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout"/>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="lblScale">
<property name="text">
<string>Scale</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1221</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

5
gui/pdfpreview.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "pdfpreview.h"
PdfPreview::PdfPreview()
{
}

13
gui/pdfpreview.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef PDFPREVIEW_H
#define PDFPREVIEW_H
#include <QImage>
class PdfPreview
{
public:
PdfPreview();
QImage previewImage;
QString documentPath;
};
#endif // PDFPREVIEW_H

44
gui/pdfworker.cpp Normal file
View File

@ -0,0 +1,44 @@
#include <QApplication>
#include <QScreen>
#include <QDebug>
#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<SearchResult> 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();
}

26
gui/pdfworker.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef PDFWORKER_H
#define PDFWORKER_H
#include <QObject>
#include <QImage>
#include <QHash>
#include <poppler-qt5.h>
#include "pdfpreview.h"
#include "searchresult.h"
class PdfWorker : public QObject
{
Q_OBJECT
private:
QHash<QString, Poppler::Document *> documentcache;
Poppler::Document *document(QString path);
public:
PdfWorker();
public slots:
void generatePreviews(QVector<SearchResult> paths, double scalefactor);
signals:
void previewReady(PdfPreview p);
void previewsFinished();
};
#endif // PDFWORKER_H

49
gui/qss.pro Normal file
View File

@ -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

5
gui/searchresult.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "searchresult.h"
SearchResult::SearchResult()
{
}

14
gui/searchresult.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef SEARCHRESULT_H
#define SEARCHRESULT_H
#include <QString>
class SearchResult
{
public:
unsigned int page;
QString path;
uint64_t mtime;
SearchResult();
};
#endif // SEARCHRESULT_H

55
gui/searchworker.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "searchworker.h"
#include <QDebug>
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<SearchResult> 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<SearchResult> 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);
}

28
gui/searchworker.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef SEARCHWORKER_H
#define SEARCHWORKER_H
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QString>
#include <QVector>
#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<SearchResult> &results);
void searchCancelled();
};
#endif // SEARCHWORKER_H