make dictionary mouseover and make extra window larger to start

This commit is contained in:
Akash Mozumdar 2020-02-22 20:58:47 -07:00
parent b61272a5e6
commit 2d2a3dedb9
3 changed files with 67 additions and 14 deletions

View File

@ -10,6 +10,7 @@
#include <QMenu> #include <QMenu>
#include <QPainter> #include <QPainter>
#include <QGraphicsEffect> #include <QGraphicsEffect>
#include <QFontMetrics>
#include <QMouseEvent> #include <QMouseEvent>
#include <QWheelEvent> #include <QWheelEvent>
@ -155,7 +156,7 @@ public:
PrettyWindow("Extra Window") PrettyWindow("Extra Window")
{ {
ui.display->setTextFormat(Qt::PlainText); ui.display->setTextFormat(Qt::PlainText);
setGeometry(settings.value(WINDOW, geometry()).toRect()); if (settings.contains(WINDOW) && QGuiApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect());
for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{ for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{
{ TOPMOST, false, &ExtraWindow::setTopmost }, { TOPMOST, false, &ExtraWindow::setTopmost },
@ -171,6 +172,7 @@ public:
action->setChecked(default); action->setChecked(default);
} }
ui.display->installEventFilter(this); ui.display->installEventFilter(this);
ui.display->setMouseTracking(true);
QMetaObject::invokeMethod(this, [this] QMetaObject::invokeMethod(this, [this]
{ {
@ -188,6 +190,7 @@ public:
{ {
if (!showOriginal) sentence = sentence.section('\n', sentence.count('\n') / 2 + 1); if (!showOriginal) sentence = sentence.section('\n', sentence.count('\n') / 2 + 1);
sanitize(sentence); sanitize(sentence);
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
sentenceHistory.push_back(sentence); sentenceHistory.push_back(sentence);
historyIndex = sentenceHistory.size() - 1; historyIndex = sentenceHistory.size() - 1;
ui.display->setText(sentence); ui.display->setText(sentence);
@ -226,14 +229,42 @@ private:
settings.setValue(DICTIONARY, this->useDictionary = useDictionary); settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
} }
void computeDictionaryPosition(QPoint mouse)
{
if (cachedDisplayInfo.compareExchange(ui.display))
{
QString sentence = ui.display->text();
QFontMetrics fontMetrics(ui.display->font(), ui.display);
textPositionMap.clear();
for (int i = 0, height = 0, lineBreak = 0; i < sentence.size(); ++i)
{
int block = 1;
for (int charHeight = fontMetrics.boundingRect(0, 0, 1, INT_MAX, Qt::TextWordWrap, sentence.mid(i, 1)).height();
i + block < sentence.size() && fontMetrics.boundingRect(0, 0, 1, INT_MAX, Qt::TextWordWrap, sentence.mid(i, block + 1)).height() < charHeight * 1.5; ++block);
auto boundingRect = fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.left(i + block));
if (boundingRect.height() > height)
{
height = boundingRect.height();
lineBreak = i;
}
textPositionMap.push_back({
fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.mid(lineBreak, i - lineBreak + 1)).width(),
height
});
}
}
int i;
for (i = 0; i < textPositionMap.size(); ++i) if (textPositionMap[i].y() > mouse.y() && textPositionMap[i].x() > mouse.x()) break;
if (i == textPositionMap.size() || (mouse - textPositionMap[i]).manhattanLength() > ui.display->font().pointSize() * 2) return;
if (ui.display->text().mid(i) == dictionaryWindow.term) return dictionaryWindow.ShowDefinition();
dictionaryWindow.ui.display->setFixedWidth(ui.display->width() / 2);
dictionaryWindow.setTerm(ui.display->text().mid(i));
dictionaryWindow.move(ui.display->mapToGlobal(mouse + QPoint(2, 2)));
}
bool eventFilter(QObject*, QEvent* event) override bool eventFilter(QObject*, QEvent* event) override
{ {
if (useDictionary && event->type() == QEvent::MouseButtonRelease && ui.display->hasSelectedText()) if (useDictionary && event->type() == QEvent::MouseMove) computeDictionaryPosition(((QMouseEvent*)event)->localPos().toPoint());
{
dictionaryWindow.ui.display->setFixedWidth(ui.display->width());
dictionaryWindow.setTerm(ui.display->text().mid(ui.display->selectionStart()));
dictionaryWindow.move({ x(), y() - dictionaryWindow.height() });
}
if (event->type() == QEvent::MouseButtonPress) dictionaryWindow.hide(); if (event->type() == QEvent::MouseButtonPress) dictionaryWindow.hide();
return false; return false;
} }
@ -259,6 +290,26 @@ private:
bool locked, showOriginal, useDictionary; bool locked, showOriginal, useDictionary;
QPoint oldPos; QPoint oldPos;
class
{
public:
bool compareExchange(QLabel* display)
{
if (display->text() == text && display->font() == font && display->width() == width) return false;
text = display->text();
font = display->font();
width = display->width();
return true;
}
private:
QString text;
QFont font;
int width;
} cachedDisplayInfo;
std::vector<QPoint> textPositionMap;
std::vector<QString> sentenceHistory; std::vector<QString> sentenceHistory;
int historyIndex = 0; int historyIndex = 0;
@ -312,11 +363,12 @@ private:
void setTerm(QString term) void setTerm(QString term)
{ {
this->term = term;
UpdateDictionary(); UpdateDictionary();
definitions.clear(); definitions.clear();
definitionIndex = 0; definitionIndex = 0;
std::unordered_set<std::string_view> definitionSet; std::unordered_set<std::string_view> definitionSet;
for (QByteArray utf8term = term.left(200).toUtf8(); !utf8term.isEmpty(); utf8term.chop(1)) for (QByteArray utf8term = term.left(500).toUtf8(); !utf8term.isEmpty(); utf8term.chop(1))
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ utf8term }); it != end; ++it) for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ utf8term }); it != end; ++it)
if (definitionSet.emplace(it->definition).second) if (definitionSet.emplace(it->definition).second)
definitions.push_back(QStringLiteral("<h3>%1 (%3/%4)</h3>%2").arg(utf8term, it->definition)); definitions.push_back(QStringLiteral("<h3>%1 (%3/%4)</h3>%2").arg(utf8term, it->definition));
@ -326,7 +378,7 @@ private:
void ShowDefinition() void ShowDefinition()
{ {
if (definitions.empty()) return; if (definitions.empty()) return hide();
ui.display->setText(definitions[definitionIndex]); ui.display->setText(definitions[definitionIndex]);
adjustSize(); adjustSize();
resize(width(), 1); resize(width(), 1);
@ -340,6 +392,7 @@ private:
bool operator<(DictionaryEntry other) const { return strcmp(term, other.term) < 0; } bool operator<(DictionaryEntry other) const { return strcmp(term, other.term) < 0; }
}; };
std::vector<DictionaryEntry> dictionary; std::vector<DictionaryEntry> dictionary;
QString term;
private: private:
void wheelEvent(QWheelEvent* event) override void wheelEvent(QWheelEvent* event) override
@ -347,9 +400,7 @@ private:
int scroll = event->angleDelta().y(); int scroll = event->angleDelta().y();
if (scroll > 0 && definitionIndex > 0) definitionIndex -= 1; if (scroll > 0 && definitionIndex > 0) definitionIndex -= 1;
if (scroll < 0 && definitionIndex + 1 < definitions.size()) definitionIndex += 1; if (scroll < 0 && definitionIndex + 1 < definitions.size()) definitionIndex += 1;
int oldHeight = height();
ShowDefinition(); ShowDefinition();
move(x(), y() + oldHeight - height());
} }
std::filesystem::file_time_type dictionaryFileLastWrite; std::filesystem::file_time_type dictionaryFileLastWrite;

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>800</width>
<height>300</height> <height>300</height>
</rect> </rect>
</property> </property>
@ -27,6 +27,9 @@
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum> <enum>Qt::CustomContextMenu</enum>
</property> </property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignTop</set> <set>Qt::AlignTop</set>
</property> </property>

View File

@ -127,11 +127,10 @@ const char* DICTIONARY_INSTRUCTIONS = u8R"(This file is used only for the "Dicti
It uses a custom format specific to Textractor and is not meant to be written manually. It uses a custom format specific to Textractor and is not meant to be written manually.
You should look for a dictionary in this format online (https://github.com/Artikash/Textractor-Dictionaries/releases is a good place to start). You should look for a dictionary in this format online (https://github.com/Artikash/Textractor-Dictionaries/releases is a good place to start).
Alternatively, if you're a programmer, you can write a script to convert a dictionary from another format with the info below. Alternatively, if you're a programmer, you can write a script to convert a dictionary from another format with the info below.
Once you have a dictionary, to look up some text in Extra Window, select it. All matching definitions will be shown. Once you have a dictionary, to look up some text in Extra Window, hover over it. All matching definitions will be shown. Scroll to change definitions.
Definitions are formatted like this:|TERM|Hola|TERM|hola|TERM|Bonjour|TERM|bonjour|DEFINITION|hello|END| Definitions are formatted like this:|TERM|Hola|TERM|hola|TERM|Bonjour|TERM|bonjour|DEFINITION|hello|END|
The definition can include rich text (https://doc.qt.io/qt-5/richtext-html-subset.html) which will be formatted properly. The definition can include rich text (https://doc.qt.io/qt-5/richtext-html-subset.html) which will be formatted properly.
All text in this file outside of a definition is ignored. All text in this file outside of a definition is ignored.
Terms longer than 50 characters may not be shown (for performance reasons that should be fixed soon).
This file must be encoded in UTF-8.)"; This file must be encoded in UTF-8.)";
const char* SHOW_ORIGINAL = u8"Original text"; const char* SHOW_ORIGINAL = u8"Original text";
const char* SHOW_ORIGINAL_INFO = u8R"(Original text will not be shown const char* SHOW_ORIGINAL_INFO = u8R"(Original text will not be shown