Basic highlighting support

This commit is contained in:
Nicolas Mailloux 2022-08-08 19:57:45 -04:00
parent d27f1d31bc
commit a7d1969fb5
10 changed files with 380 additions and 70 deletions

View file

@ -30,6 +30,7 @@ SOURCES += \
src/widgets/dialogs/library/bookoptionsdialog.cpp \
src/widgets/dialogs/brightnessdialog.cpp \
src/apps/calendarapp.cpp \
src/widgets/dialogs/reader/textdialog.cpp \
src/widgets/reader/dictionarywidget.cpp \
src/encfs/encryptionmanager.cpp \
src/widgets/dialogs/generaldialog.cpp \
@ -68,6 +69,7 @@ HEADERS += \
src/widgets/dialogs/library/bookoptionsdialog.h \
src/widgets/dialogs/brightnessdialog.h \
src/apps/calendarapp.h \
src/widgets/dialogs/reader/textdialog.h \
src/widgets/reader/dictionarywidget.h \
src/encfs/encryptionmanager.h \
src/functions.h \
@ -106,6 +108,7 @@ FORMS += \
src/widgets/dialogs/library/bookoptionsdialog.ui \
src/widgets/dialogs/brightnessdialog.ui \
src/apps/calendarapp.ui \
src/widgets/dialogs/reader/textdialog.ui \
src/widgets/reader/dictionarywidget.ui \
src/encfs/encryptionmanager.ui \
src/widgets/dialogs/generaldialog.ui \
@ -141,4 +144,4 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
RESOURCES += \
src/eink.qrc
INCLUDEPATH += $$system(find $path -type d)
INCLUDEPATH += $$system(find ./ -type d -print -path ./.git -prune | grep -v "./.git")

View file

@ -81,5 +81,7 @@
<file>resources/arrow-right.png</file>
<file>resources/file-text.png</file>
<file>resources/pin.png</file>
<file>resources/highlight.png</file>
<file>resources/unhighlight.png</file>
</qresource>
</RCC>

View file

@ -43,6 +43,7 @@ namespace global {
inline bool bookIsEpub;
inline bool globalReadingSettings;
inline int pdfOrientation;
inline bool highlightAlreadyDone;
}
namespace kobox {
inline bool showKoboxSplash;
@ -135,6 +136,7 @@ namespace global {
static inline QString databasePath = databaseDirectoryPath + "LocalLibrary.db";
static inline QString recentBooksDatabasePath = databaseDirectoryPath + "RecentBooks.db";
static inline QString pinnedBooksDatabasePath = databaseDirectoryPath + "PinnedBooks.db";
static inline QString highlightsDatabasePath = databaseDirectoryPath + "Highlights.db";
inline bool headless;
namespace bookOptionsDialog {
inline int bookID;
@ -1014,6 +1016,112 @@ namespace {
QJsonArray jsonArrayList = jsonObject["database"].toArray();
return jsonArrayList.at(bookID - 1).toObject();
}
QJsonObject readHighlightsDatabase() {
// Read highlights database from file
QFile database(global::localLibrary::highlightsDatabasePath);
QByteArray data;
if(database.open(QIODevice::ReadOnly)) {
data = database.readAll();
database.close();
}
else {
QString function = __func__; log(function + ": Failed to open highlights database file for reading at '" + database.fileName() + "'", "functions");
}
// Parse JSON data
return QJsonDocument::fromJson(qUncompress(QByteArray::fromBase64(data))).object();
}
void writeHighlightsDatabase(QJsonObject jsonObject) {
QFile::remove(global::localLibrary::highlightsDatabasePath);
writeFile(global::localLibrary::highlightsDatabasePath, qCompress(QJsonDocument(jsonObject).toJson()).toBase64());
}
void highlightBookText(QString text, QString bookPath, bool remove) {
if(remove == false) {
if(!QFile::exists(global::localLibrary::highlightsDatabasePath)) {
QJsonObject mainJsonObject;
QJsonObject firstJsonObject;
firstJsonObject.insert("BookPath", QJsonValue(bookPath));
firstJsonObject.insert("Text1", QJsonValue(text));
mainJsonObject["Book1"] = firstJsonObject;
writeHighlightsDatabase(mainJsonObject);
}
else {
QJsonObject jsonObject = readHighlightsDatabase();
bool highlightWrote = false;
int length = jsonObject.length();
for(int i = 1; i <= length; i++) {
if(jsonObject["Book" + QString::number(i)].toObject().value("BookPath").toString() == bookPath) {
log("highlightBookText: Found existing book with path '" + bookPath + "'", "functions");
int keyCount = 0;
// Counting number of highlights for book
foreach(const QString& key, jsonObject["Book" + QString::number(i)].toObject().keys()) {
keyCount++;
}
// First key is 'BookPath'
int highlightsCount = keyCount - 1;
int currentHighlightPosition = highlightsCount + 1;
// Insert highlight
QJsonObject highlightJsonObject = jsonObject["Book" + QString::number(i)].toObject();
// Finding available slot for highlight in case the one we are looking for is already occupied
if(highlightJsonObject.contains("Text" + QString::number(currentHighlightPosition))) {
while(true) {
if(highlightJsonObject.contains("Text" + QString::number(currentHighlightPosition))) {
currentHighlightPosition++;
}
else {
break;
}
}
}
highlightJsonObject.insert("Text" + QString::number(currentHighlightPosition), text);
jsonObject["Book" + QString::number(i)] = highlightJsonObject;
writeHighlightsDatabase(jsonObject);
highlightWrote = true;
}
}
if(highlightWrote == false) {
QJsonObject bookJsonObject;
bookJsonObject.insert("BookPath", QJsonValue(bookPath));
bookJsonObject.insert("Text1", QJsonValue(text));
jsonObject["Book" + QString::number(length + 1)] = bookJsonObject;
writeHighlightsDatabase(jsonObject);
highlightWrote = true;
}
}
}
else {
QJsonObject jsonObject = readHighlightsDatabase();
int length = jsonObject.length();
for(int i = 1; i <= length; i++) {
if(jsonObject["Book" + QString::number(i)].toObject().value("BookPath").toString() == bookPath) {
QJsonObject bookJsonObject = jsonObject["Book" + QString::number(i)].toObject();
foreach(const QString& key, bookJsonObject.keys()) {
if(bookJsonObject.value(key).toString() == text) {
log("Found matching highlight to remove with text '" + text + "'", "functions.h");
bookJsonObject.remove(key);
}
}
jsonObject["Book" + QString::number(i)] = bookJsonObject;
writeHighlightsDatabase(jsonObject);
}
}
}
}
QJsonObject getHighlightsForBook(QString bookPath) {
QJsonObject jsonObject = readHighlightsDatabase();
int length = jsonObject.length();
for(int i = 1; i <= length; i++) {
if(jsonObject["Book" + QString::number(i)].toObject().value("BookPath").toString() == bookPath) {
return jsonObject["Book" + QString::number(i)].toObject();
break;
}
}
return QJsonObject();
}
float determineYIncrease() {
if(global::deviceID == "n705\n" or global::deviceID == "n905\n") {
return 2;

View file

@ -30,11 +30,13 @@ reader::reader(QWidget *parent) :
// Variables
global::battery::showLowBatteryDialog = true;
global::battery::showCriticalBatteryAlert = true;
global::reader::highlightAlreadyDone = false;
if(global::reader::bookIsEpub == true) {
is_epub = true;
}
mupdf::convertRelativeValues = false;
wordwidgetLock = false;
textDialogLock = false;
goToSavedPageDone = false;
initialPdfRotationDone = false;
@ -555,19 +557,19 @@ reader::reader(QWidget *parent) :
else {
if(checkconfig_str_val == "Left") {
textAlignment = 0;
alignText(0);
alignAndHighlightText(0);
}
if(checkconfig_str_val == "Center") {
textAlignment = 1;
alignText(1);
alignAndHighlightText(1);
}
if(checkconfig_str_val == "Right") {
textAlignment = 2;
alignText(2);
alignAndHighlightText(2);
}
if(checkconfig_str_val == "Justify") {
textAlignment = 3;
alignText(3);
alignAndHighlightText(3);
}
log("Setting text alignment to '" + checkconfig_str_val + "'", className);
}
@ -1041,7 +1043,7 @@ void reader::on_nextBtn_clicked()
pagesTurned = pagesTurned + 1;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
else if(is_epub == true) {
@ -1057,7 +1059,7 @@ void reader::on_nextBtn_clicked()
pagesTurned = pagesTurned + 1;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
else if(is_pdf == true) {
@ -1101,7 +1103,7 @@ void reader::on_previousBtn_clicked()
// We always increment pagesTurned regardless whether we press the Previous or Next button or not
pagesTurned = pagesTurned + 1;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
else if (is_pdf == true) {
@ -1136,7 +1138,7 @@ void reader::on_previousBtn_clicked()
// We always increment pagesTurned regardless whether we press the Previous or Next button not
pagesTurned = pagesTurned + 1;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
setupPageWidget();
@ -1324,6 +1326,7 @@ void reader::on_fontChooser_currentIndexChanged(const QString &arg1)
ui->text->setFont(QFont("u001"));
string_writeconfig(".config/04-book/font", "u001");
}
alignAndHighlightText(textAlignment);
}
void reader::on_alignLeftBtn_clicked()
@ -1333,7 +1336,7 @@ void reader::on_alignLeftBtn_clicked()
ui->text->setAlignment(Qt::AlignLeft);
}
else {
alignText(0);
alignAndHighlightText(0);
}
string_writeconfig(".config/04-book/alignment", "Left");
}
@ -1345,7 +1348,7 @@ void reader::on_alignCenterBtn_clicked()
ui->text->setAlignment(Qt::AlignHCenter);
}
else {
alignText(1);
alignAndHighlightText(1);
}
string_writeconfig(".config/04-book/alignment", "Center");
}
@ -1357,7 +1360,7 @@ void reader::on_alignRightBtn_clicked()
ui->text->setAlignment(Qt::AlignRight);
}
else {
alignText(2);
alignAndHighlightText(2);
}
string_writeconfig(".config/04-book/alignment", "Right");
}
@ -1369,12 +1372,13 @@ void reader::on_alignJustifyBtn_clicked()
ui->text->setAlignment(Qt::AlignJustify);
}
else {
alignText(3);
alignAndHighlightText(3);
}
string_writeconfig(".config/04-book/alignment", "Justify");
}
void reader::alignText(int alignment) {
void reader::alignAndHighlightText(int alignment) {
// Alignment
/*
* 0 - Left
* 1 - Center
@ -1382,58 +1386,74 @@ void reader::alignText(int alignment) {
* 3 - Justify
*/
textAlignment = alignment;
QString modifiedPageContent;
if(is_epub == true) {
if(alignment == 0) {
QString epubPageContent_alignChange = epubPageContent;
epubPageContent_alignChange.prepend("<div align='left'>");
epubPageContent_alignChange.append("</div>");
ui->text->setText(epubPageContent_alignChange);
modifiedPageContent = epubPageContent;
modifiedPageContent.prepend("<div align='left'>");
modifiedPageContent.append("</div>");
}
if(alignment == 1) {
QString epubPageContent_alignChange = epubPageContent;
epubPageContent_alignChange.prepend("<div align='center'>");
epubPageContent_alignChange.append("</div>");
ui->text->setText(epubPageContent_alignChange);
modifiedPageContent = epubPageContent;
modifiedPageContent.prepend("<div align='center'>");
modifiedPageContent.append("</div>");
}
if(alignment == 2) {
QString epubPageContent_alignChange = epubPageContent;
epubPageContent_alignChange.prepend("<div align='right'>");
epubPageContent_alignChange.append("</div>");
ui->text->setText(epubPageContent_alignChange);
modifiedPageContent = epubPageContent;
modifiedPageContent.prepend("<div align='right'>");
modifiedPageContent.append("</div>");
}
if(alignment == 3) {
QString epubPageContent_alignChange = epubPageContent;
epubPageContent_alignChange.prepend("<div align='justify'>");
epubPageContent_alignChange.append("</div>");
ui->text->setText(epubPageContent_alignChange);
modifiedPageContent = epubPageContent;
modifiedPageContent.prepend("<div align='justify'>");
modifiedPageContent.append("</div>");
}
}
else {
if(alignment == 0) {
QString ittext_alignChange = ittext;
ittext_alignChange.prepend("<div align='left'>");
ittext_alignChange.append("</div>");
ui->text->setText(ittext_alignChange);
modifiedPageContent = ittext;
modifiedPageContent.prepend("<div align='left'>");
modifiedPageContent.append("</div>");
}
if(alignment == 1) {
QString ittext_alignChange = ittext;
ittext_alignChange.prepend("<div align='center'>");
ittext_alignChange.append("</div>");
ui->text->setText(ittext_alignChange);
modifiedPageContent = ittext;
modifiedPageContent.prepend("<div align='center'>");
modifiedPageContent.append("</div>");
}
if(alignment == 2) {
QString ittext_alignChange = ittext;
ittext_alignChange.prepend("<div align='right'>");
ittext_alignChange.append("</div>");
ui->text->setText(ittext_alignChange);
modifiedPageContent = ittext;
modifiedPageContent.prepend("<div align='right'>");
modifiedPageContent.append("</div>");
}
if(alignment == 3) {
QString ittext_alignChange = ittext;
ittext_alignChange.prepend("<div align='justify'>");
ittext_alignChange.append("</div>");
ui->text->setText(ittext_alignChange);
modifiedPageContent = ittext;
modifiedPageContent.prepend("<div align='justify'>");
modifiedPageContent.append("</div>");
}
}
ui->text->setText(modifiedPageContent);
// Highlight
QString htmlText = ui->text->toHtml();
QJsonObject jsonObject = getHighlightsForBook(book_file);
int keyCount = 1;
foreach(const QString& key, jsonObject.keys()) {
if(keyCount <= 1) {
keyCount++;
continue;
}
else {
QString highlight = jsonObject.value(key).toString();
textDialogLock = true;
if(htmlText.contains(highlight)) {
htmlText.replace(highlight, "<span style=\" background-color:#bbbbbb;\">" + highlight + "</span>");
}
textDialogLock = false;
}
keyCount++;
}
ui->text->setText(htmlText);
}
void reader::menubar_show() {
@ -1852,34 +1872,57 @@ void reader::delay(int mseconds) {
void reader::on_text_selectionChanged() {
delay(100);
if(wordwidgetLock != true) {
if(wordwidgetLock == false and textDialogLock == false) {
QTextCursor cursor = ui->text->textCursor();
selected_text = cursor.selectedText();
if(selected_text != "") {
if(!selected_text.isEmpty()) {
log("Text selection changed; selected text: '" + selected_text + "'", className);
QString dictionary_position_str = QString::number(dictionary_position);
ui->definitionStatusLabel->setText(dictionary_position_str);
if(!selected_text.contains(" ")) {
// Word selection
QString dictionary_position_str = QString::number(dictionary_position);
ui->definitionStatusLabel->setText(dictionary_position_str);
selected_text = selected_text.toLower();
QStringList parts = selected_text.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < parts.size(); ++i)
parts[i].replace(0, 1, parts[i][0].toUpper());
word = parts.join(" ");
letter = word.left(1);
selected_text_str = word.toStdString();
dictionary_lookup(selected_text_str, letter, dictionary_position);
ui->wordLabel->setText(word);
ui->definitionLabel->setText(definition);
if(checkconfig_match(".config/06-words/config", selected_text_str) == true) {
ui->saveWordBtn->setText("");
ui->saveWordBtn->setIcon(QIcon(":/resources/starred_star.png"));
selected_text = selected_text.toLower();
QStringList parts = selected_text.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < parts.size(); ++i)
parts[i].replace(0, 1, parts[i][0].toUpper());
word = parts.join(" ");
letter = word.left(1);
selected_text_str = word.toStdString();
dictionary_lookup(selected_text_str, letter, dictionary_position);
ui->wordLabel->setText(word);
ui->definitionLabel->setText(definition);
if(checkconfig_match(".config/06-words/config", selected_text_str) == true) {
ui->saveWordBtn->setText("");
ui->saveWordBtn->setIcon(QIcon(":/resources/starred_star.png"));
}
else {
ui->saveWordBtn->setText("");
ui->saveWordBtn->setIcon(QIcon(":/resources/star.png"));
}
wordwidgetLock = true;
wordwidget_show();
}
else {
ui->saveWordBtn->setText("");
ui->saveWordBtn->setIcon(QIcon(":/resources/star.png"));
// Highlight
textDialogLock = true;
global::reader::highlightAlreadyDone = false;
QJsonObject jsonObject = getHighlightsForBook(book_file);
QString htmlText = ui->text->toHtml();
if(htmlText.contains("<span style=\" background-color:#bbbbbb;\">" + selected_text + "</span>")) {
log("Highlight already done", className);
global::reader::highlightAlreadyDone = true;
}
textDialog * textDialogWindow = new textDialog(this);
QObject::connect(textDialogWindow, &textDialog::destroyed, this, &reader::unsetTextDialogLock);
QObject::connect(textDialogWindow, &textDialog::highlightText, this, &reader::highlightText);
QObject::connect(textDialogWindow, &textDialog::unhighlightText, this, &reader::unhighlightText);
textDialogWindow->setAttribute(Qt::WA_DeleteOnClose);
textDialogWindow->setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
textDialogWindow->move(mapFromGlobal(QCursor::pos()));
textDialogWindow->show();
}
wordwidgetLock = true;
wordwidget_show();
}
else {
;
@ -2000,7 +2043,7 @@ void reader::gotoPage(int pageNumber) {
pagesTurned = 0;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
else if(is_pdf == true) {
@ -2029,7 +2072,7 @@ void reader::gotoPage(int pageNumber) {
pagesTurned = 0;
writeconfig_pagenumber(false);
alignText(textAlignment);
alignAndHighlightText(textAlignment);
}
}
setupPageWidget();
@ -2344,3 +2387,23 @@ void reader::getPdfOrientation(QString file) {
}
}
}
void reader::unsetTextDialogLock() {
QTextCursor cursor = ui->text->textCursor();
cursor.clearSelection();
ui->text->setTextCursor(cursor);
textDialogLock = false;
}
void reader::highlightText() {
log("Highlighting text '" + selected_text + "'", className);
highlightBookText(selected_text, book_file, false);
alignAndHighlightText(textAlignment);
}
void reader::unhighlightText() {
log("Removing highlighted text '" + selected_text + "'", className);
highlightBookText(selected_text, book_file, true);
alignAndHighlightText(textAlignment);
}

View file

@ -6,6 +6,7 @@
#include "generaldialog.h"
#include "toast.h"
#include "quit.h"
#include "textdialog.h"
#include <QWidget>
#include <QGraphicsScene>
@ -73,6 +74,7 @@ public:
bool remount = true;
bool showTopbarWidget;
bool wordwidgetLock;
bool textDialogLock;
bool isNightModeActive;
bool goToSavedPageDone;
QString ittext;
@ -110,7 +112,7 @@ public:
void convertMuPdfVars(int fileType, bool convertAll);
void refreshScreen();
void setPageStyle(int fileType);
void alignText(int alignment);
void alignAndHighlightText(int alignment);
void delay(int seconds);
void openUsbmsDialog();
QString setPageNumberLabelContent();
@ -161,6 +163,9 @@ private slots:
void on_quitBtn_clicked();
void closeIndefiniteToast();
void getPdfOrientation(QString file);
void unsetTextDialogLock();
void highlightText();
void unhighlightText();
signals:
void openBookFile(QString book, bool relativePath);
@ -171,6 +176,7 @@ private:
generalDialog * generalDialogWindow;
toast * toastWindow;
quit * quitWindow;
textDialog * textDialogWindow;
QGraphicsScene * graphicsScene;
};

BIN
src/resources/highlight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,36 @@
#include "textdialog.h"
#include "ui_textdialog.h"
textDialog::textDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::textDialog)
{
ui->setupUi(this);
ui->highlightBtn->setProperty("type", "borderless");
if(global::reader::highlightAlreadyDone == true) {
global::reader::highlightAlreadyDone = false;
highlightAlreadyDone = true;
ui->highlightBtn->setIcon(QIcon(":/resources/unhighlight.png"));
}
else {
highlightAlreadyDone = false;
ui->highlightBtn->setIcon(QIcon(":/resources/highlight.png"));
}
this->adjustSize();
}
textDialog::~textDialog()
{
delete ui;
}
void textDialog::on_highlightBtn_clicked()
{
if(highlightAlreadyDone == true) {
emit unhighlightText();
}
else {
emit highlightText();
}
textDialog::close();
}

View file

@ -0,0 +1,33 @@
#ifndef TEXTDIALOG_H
#define TEXTDIALOG_H
#include <QDialog>
#include "functions.h"
namespace Ui {
class textDialog;
}
class textDialog : public QDialog
{
Q_OBJECT
public:
QString className = this->metaObject()->className();
explicit textDialog(QWidget *parent = nullptr);
~textDialog();
bool highlightAlreadyDone = false;
signals:
void highlightText();
void unhighlightText();
private slots:
void on_highlightBtn_clicked();
private:
Ui::textDialog *ui;
};
#endif // TEXTDIALOG_H

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>textDialog</class>
<widget class="QDialog" name="textDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>83</width>
<height>61</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="highlightBtn">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">padding: 10px</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>