mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
added text selection
This commit is contained in:
parent
8b40393023
commit
81b1a8774b
18 changed files with 585 additions and 239 deletions
|
@ -100,6 +100,7 @@ void ColorScheme::setColors(double hue, double multiplier)
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
ChatBackground = getColor(0, 0.1, 1);
|
ChatBackground = getColor(0, 0.1, 1);
|
||||||
|
ChatBackgroundHighlighted = blendColors(TabSelectedBackground, ChatBackground, 0.8);
|
||||||
ChatHeaderBackground = getColor(0, 0.1, 0.9);
|
ChatHeaderBackground = getColor(0, 0.1, 0.9);
|
||||||
ChatHeaderBorder = getColor(0, 0.1, 0.85);
|
ChatHeaderBorder = getColor(0, 0.1, 0.85);
|
||||||
ChatInputBackground = getColor(0, 0.1, 0.95);
|
ChatInputBackground = getColor(0, 0.1, 0.95);
|
||||||
|
@ -120,6 +121,15 @@ void ColorScheme::setColors(double hue, double multiplier)
|
||||||
this->updated();
|
this->updated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor ColorScheme::blendColors(const QColor &color1, const QColor &color2, qreal ratio)
|
||||||
|
{
|
||||||
|
int r = color1.red() * (1 - ratio) + color2.red() * ratio;
|
||||||
|
int g = color1.green() * (1 - ratio) + color2.green() * ratio;
|
||||||
|
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio;
|
||||||
|
|
||||||
|
return QColor(r, g, b, 255);
|
||||||
|
}
|
||||||
|
|
||||||
void ColorScheme::normalizeColor(QColor &color)
|
void ColorScheme::normalizeColor(QColor &color)
|
||||||
{
|
{
|
||||||
if (this->lightTheme) {
|
if (this->lightTheme) {
|
||||||
|
|
|
@ -87,6 +87,7 @@ private:
|
||||||
pajlada::Settings::Setting<double> themeHue;
|
pajlada::Settings::Setting<double> themeHue;
|
||||||
|
|
||||||
void setColors(double hue, double multiplier);
|
void setColors(double hue, double multiplier);
|
||||||
|
QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio);
|
||||||
|
|
||||||
double middleLookupTable[360] = {};
|
double middleLookupTable[360] = {};
|
||||||
double minLookupTable[360] = {};
|
double minLookupTable[360] = {};
|
||||||
|
|
|
@ -22,14 +22,14 @@ LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_wi
|
||||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||||
: emoteManager(_emoteManager)
|
: emoteManager(_emoteManager)
|
||||||
, windowManager(_windowManager)
|
, windowManager(_windowManager)
|
||||||
, _currentPixmap(nullptr)
|
, currentPixmap(nullptr)
|
||||||
, _url(url)
|
, url(url)
|
||||||
, _name(name)
|
, name(name)
|
||||||
, _tooltip(tooltip)
|
, tooltip(tooltip)
|
||||||
, _margin(margin)
|
, margin(margin)
|
||||||
, _ishat(isHat)
|
, ishat(isHat)
|
||||||
, _scale(scale)
|
, scale(scale)
|
||||||
, _isLoading(false)
|
, isLoading(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,19 +38,19 @@ LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_wi
|
||||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||||
: emoteManager(_emoteManager)
|
: emoteManager(_emoteManager)
|
||||||
, windowManager(_windowManager)
|
, windowManager(_windowManager)
|
||||||
, _currentPixmap(image)
|
, currentPixmap(image)
|
||||||
, _name(name)
|
, name(name)
|
||||||
, _tooltip(tooltip)
|
, tooltip(tooltip)
|
||||||
, _margin(margin)
|
, margin(margin)
|
||||||
, _ishat(isHat)
|
, ishat(isHat)
|
||||||
, _scale(scale)
|
, scale(scale)
|
||||||
, _isLoading(true)
|
, isLoading(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyLoadedImage::loadImage()
|
void LazyLoadedImage::loadImage()
|
||||||
{
|
{
|
||||||
util::urlFetch(_url, [=](QNetworkReply &reply) {
|
util::urlFetch(this->url, [=](QNetworkReply &reply) {
|
||||||
QByteArray array = reply.readAll();
|
QByteArray array = reply.readAll();
|
||||||
QBuffer buffer(&array);
|
QBuffer buffer(&array);
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
@ -66,19 +66,19 @@ void LazyLoadedImage::loadImage()
|
||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
_currentPixmap = pixmap;
|
this->currentPixmap = pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameData data;
|
FrameData data;
|
||||||
data.duration = std::max(20, reader.nextImageDelay());
|
data.duration = std::max(20, reader.nextImageDelay());
|
||||||
data.image = pixmap;
|
data.image = pixmap;
|
||||||
|
|
||||||
_allFrames.push_back(data);
|
this->allFrames.push_back(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allFrames.size() > 1) {
|
if (this->allFrames.size() > 1) {
|
||||||
_animated = true;
|
this->animated = true;
|
||||||
|
|
||||||
this->emoteManager.getGifUpdateSignal().connect([this] {
|
this->emoteManager.getGifUpdateSignal().connect([this] {
|
||||||
gifUpdateTimout(); //
|
gifUpdateTimout(); //
|
||||||
|
@ -92,18 +92,90 @@ void LazyLoadedImage::loadImage()
|
||||||
|
|
||||||
void LazyLoadedImage::gifUpdateTimout()
|
void LazyLoadedImage::gifUpdateTimout()
|
||||||
{
|
{
|
||||||
_currentFrameOffset += GIF_FRAME_LENGTH;
|
this->currentFrameOffset += GIF_FRAME_LENGTH;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (_currentFrameOffset > _allFrames.at(_currentFrame).duration) {
|
if (this->currentFrameOffset > this->allFrames.at(this->currentFrame).duration) {
|
||||||
_currentFrameOffset -= _allFrames.at(_currentFrame).duration;
|
this->currentFrameOffset -= this->allFrames.at(this->currentFrame).duration;
|
||||||
_currentFrame = (_currentFrame + 1) % _allFrames.size();
|
this->currentFrame = (this->currentFrame + 1) % this->allFrames.size();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentPixmap = _allFrames[_currentFrame].image;
|
this->currentPixmap = this->allFrames[this->currentFrame].image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QPixmap *LazyLoadedImage::getPixmap()
|
||||||
|
{
|
||||||
|
if (!this->isLoading) {
|
||||||
|
this->isLoading = true;
|
||||||
|
|
||||||
|
loadImage();
|
||||||
|
}
|
||||||
|
return this->currentPixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal LazyLoadedImage::getScale() const
|
||||||
|
{
|
||||||
|
return this->scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &LazyLoadedImage::getUrl() const
|
||||||
|
{
|
||||||
|
return this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &LazyLoadedImage::getName() const
|
||||||
|
{
|
||||||
|
return this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &LazyLoadedImage::getTooltip() const
|
||||||
|
{
|
||||||
|
return this->tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMargins &LazyLoadedImage::getMargin() const
|
||||||
|
{
|
||||||
|
return this->margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LazyLoadedImage::getAnimated() const
|
||||||
|
{
|
||||||
|
return this->animated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LazyLoadedImage::isHat() const
|
||||||
|
{
|
||||||
|
return this->ishat;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LazyLoadedImage::getWidth() const
|
||||||
|
{
|
||||||
|
if (this->currentPixmap == nullptr) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
return this->currentPixmap->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int LazyLoadedImage::getScaledWidth() const
|
||||||
|
{
|
||||||
|
return static_cast<int>(getWidth() * this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LazyLoadedImage::getHeight() const
|
||||||
|
{
|
||||||
|
if (this->currentPixmap == nullptr) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
return this->currentPixmap->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
int LazyLoadedImage::getScaledHeight() const
|
||||||
|
{
|
||||||
|
return static_cast<int>(getHeight() * this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -25,66 +25,18 @@ public:
|
||||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||||
bool isHat = false);
|
bool isHat = false);
|
||||||
|
|
||||||
const QPixmap *getPixmap()
|
const QPixmap *getPixmap();
|
||||||
{
|
qreal getScale() const;
|
||||||
if (!_isLoading) {
|
const QString &getUrl() const;
|
||||||
_isLoading = true;
|
const QString &getName() const;
|
||||||
|
const QString &getTooltip() const;
|
||||||
loadImage();
|
const QMargins &getMargin() const;
|
||||||
}
|
bool getAnimated() const;
|
||||||
return _currentPixmap;
|
bool isHat() const;
|
||||||
}
|
int getWidth() const;
|
||||||
|
int getScaledWidth() const;
|
||||||
qreal getScale() const
|
int getHeight() const;
|
||||||
{
|
int getScaledHeight() const;
|
||||||
return _scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &getUrl() const
|
|
||||||
{
|
|
||||||
return _url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &getName() const
|
|
||||||
{
|
|
||||||
return _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &getTooltip() const
|
|
||||||
{
|
|
||||||
return _tooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QMargins &getMargin() const
|
|
||||||
{
|
|
||||||
return _margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getAnimated() const
|
|
||||||
{
|
|
||||||
return _animated;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isHat() const
|
|
||||||
{
|
|
||||||
return _ishat;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getWidth() const
|
|
||||||
{
|
|
||||||
if (_currentPixmap == nullptr) {
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
return _currentPixmap->width();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getHeight() const
|
|
||||||
{
|
|
||||||
if (_currentPixmap == nullptr) {
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
return _currentPixmap->height();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmoteManager &emoteManager;
|
EmoteManager &emoteManager;
|
||||||
|
@ -95,20 +47,20 @@ private:
|
||||||
int duration;
|
int duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
QPixmap *_currentPixmap;
|
QPixmap *currentPixmap;
|
||||||
std::vector<FrameData> _allFrames;
|
std::vector<FrameData> allFrames;
|
||||||
int _currentFrame = 0;
|
int currentFrame = 0;
|
||||||
int _currentFrameOffset = 0;
|
int currentFrameOffset = 0;
|
||||||
|
|
||||||
QString _url;
|
QString url;
|
||||||
QString _name;
|
QString name;
|
||||||
QString _tooltip;
|
QString tooltip;
|
||||||
bool _animated = false;
|
bool animated = false;
|
||||||
QMargins _margin;
|
QMargins margin;
|
||||||
bool _ishat;
|
bool ishat;
|
||||||
qreal _scale;
|
qreal scale;
|
||||||
|
|
||||||
bool _isLoading;
|
bool isLoading;
|
||||||
|
|
||||||
void loadImage();
|
void loadImage();
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,12 @@ Message::Message(const QString &text)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Message::Message(const QString &text, const std::vector<Word> &words, const bool &highlight)
|
//Message::Message(const QString &text, const std::vector<Word> &words, const bool &highlight)
|
||||||
: text(text)
|
// : text(text)
|
||||||
, highlightTab(highlight)
|
// , highlightTab(highlight)
|
||||||
, words(words)
|
// , words(words)
|
||||||
{
|
//{
|
||||||
}
|
//}
|
||||||
|
|
||||||
bool Message::getCanHighlightTab() const
|
bool Message::getCanHighlightTab() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,8 +24,8 @@ class Message
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// explicit Message(const QString &text);
|
// explicit Message(const QString &text);
|
||||||
explicit Message(const QString &text, const std::vector<messages::Word> &words,
|
//explicit Message(const QString &text, const std::vector<messages::Word> &words,
|
||||||
const bool &highlight);
|
// const bool &highlight);
|
||||||
|
|
||||||
bool getCanHighlightTab() const;
|
bool getCanHighlightTab() const;
|
||||||
const QString &getTimeoutUser() const;
|
const QString &getTimeoutUser() const;
|
||||||
|
|
|
@ -7,21 +7,19 @@ namespace chatterino {
|
||||||
namespace messages {
|
namespace messages {
|
||||||
|
|
||||||
MessageBuilder::MessageBuilder()
|
MessageBuilder::MessageBuilder()
|
||||||
: _words()
|
: message(new Message)
|
||||||
{
|
{
|
||||||
_parseTime = std::chrono::system_clock::now();
|
_parseTime = std::chrono::system_clock::now();
|
||||||
|
|
||||||
linkRegex.setPattern("[[:ascii:]]*\\.[a-zA-Z]+\\/?[[:ascii:]]*");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedMessage MessageBuilder::build()
|
SharedMessage MessageBuilder::build()
|
||||||
{
|
{
|
||||||
return SharedMessage(new Message(this->originalMessage, _words, highlight));
|
return this->message;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageBuilder::appendWord(const Word &word)
|
void MessageBuilder::appendWord(const Word &&word)
|
||||||
{
|
{
|
||||||
_words.push_back(word);
|
this->message->getWords().push_back(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageBuilder::appendTimestamp()
|
void MessageBuilder::appendTimestamp()
|
||||||
|
@ -59,6 +57,9 @@ void MessageBuilder::appendTimestamp(time_t time)
|
||||||
|
|
||||||
QString MessageBuilder::matchLink(const QString &string)
|
QString MessageBuilder::matchLink(const QString &string)
|
||||||
{
|
{
|
||||||
|
static QRegularExpression linkRegex("[[:ascii:]]*\\.[a-zA-Z]+\\/?[[:ascii:]]*");
|
||||||
|
static QRegularExpression httpRegex("\\bhttps?://");
|
||||||
|
|
||||||
auto match = linkRegex.match(string);
|
auto match = linkRegex.match(string);
|
||||||
|
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
|
@ -67,7 +68,7 @@ QString MessageBuilder::matchLink(const QString &string)
|
||||||
|
|
||||||
QString captured = match.captured();
|
QString captured = match.captured();
|
||||||
|
|
||||||
if (!captured.contains(QRegularExpression("\\bhttps?://"))) {
|
if (!captured.contains(httpRegex)) {
|
||||||
captured.insert(0, "http://");
|
captured.insert(0, "http://");
|
||||||
}
|
}
|
||||||
return captured;
|
return captured;
|
||||||
|
|
|
@ -16,7 +16,7 @@ public:
|
||||||
|
|
||||||
SharedMessage build();
|
SharedMessage build();
|
||||||
|
|
||||||
void appendWord(const Word &word);
|
void appendWord(const Word &&word);
|
||||||
void appendTimestamp();
|
void appendTimestamp();
|
||||||
void appendTimestamp(std::time_t time);
|
void appendTimestamp(std::time_t time);
|
||||||
void setHighlight(const bool &value);
|
void setHighlight(const bool &value);
|
||||||
|
@ -27,6 +27,7 @@ public:
|
||||||
QString originalMessage;
|
QString originalMessage;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::shared_ptr<messages::Message> message;
|
||||||
std::vector<Word> _words;
|
std::vector<Word> _words;
|
||||||
bool highlight = false;
|
bool highlight = false;
|
||||||
std::chrono::time_point<std::chrono::system_clock> _parseTime;
|
std::chrono::time_point<std::chrono::system_clock> _parseTime;
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
#define MARGIN_LEFT 8
|
#define MARGIN_LEFT 8
|
||||||
#define MARGIN_RIGHT 8
|
#define MARGIN_RIGHT 8
|
||||||
#define MARGIN_TOP 8
|
#define MARGIN_TOP 4
|
||||||
#define MARGIN_BOTTOM 8
|
#define MARGIN_BOTTOM 4
|
||||||
|
|
||||||
using namespace chatterino::messages;
|
using namespace chatterino::messages;
|
||||||
|
|
||||||
|
@ -142,18 +142,12 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
||||||
|
|
||||||
std::vector<short> &charWidths = word.getCharacterWidthCache();
|
std::vector<short> &charWidths = word.getCharacterWidthCache();
|
||||||
|
|
||||||
if (charWidths.size() == 0) {
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
|
||||||
charWidths.push_back(metrics.charWidth(text, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 2; i <= text.length(); i++) {
|
for (int i = 2; i <= text.length(); i++) {
|
||||||
if ((width = width + charWidths[i - 1]) + MARGIN_LEFT > right) {
|
if ((width = width + charWidths[i - 1]) + MARGIN_LEFT > right) {
|
||||||
QString mid = text.mid(start, i - start - 1);
|
QString mid = text.mid(start, i - start - 1);
|
||||||
|
|
||||||
_wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width, word.getHeight(),
|
_wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width, word.getHeight(),
|
||||||
lineNumber, mid, mid));
|
lineNumber, mid, mid, false));
|
||||||
|
|
||||||
y += metrics.height();
|
y += metrics.height();
|
||||||
|
|
||||||
|
@ -193,6 +187,8 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
||||||
|
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
|
|
||||||
|
lineNumber++;
|
||||||
|
|
||||||
_wordParts.push_back(
|
_wordParts.push_back(
|
||||||
WordPart(word, MARGIN_LEFT, y - word.getHeight(), lineNumber, word.getCopyText()));
|
WordPart(word, MARGIN_LEFT, y - word.getHeight(), lineNumber, word.getCopyText()));
|
||||||
|
|
||||||
|
@ -202,8 +198,6 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
||||||
|
|
||||||
x = word.getWidth() + MARGIN_LEFT;
|
x = word.getWidth() + MARGIN_LEFT;
|
||||||
x += spaceWidth;
|
x += spaceWidth;
|
||||||
|
|
||||||
lineNumber++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,6 +208,8 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
||||||
_height = y + lineHeight;
|
_height = y + lineHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_height += MARGIN_BOTTOM;
|
||||||
|
|
||||||
if (sizeChanged) {
|
if (sizeChanged) {
|
||||||
buffer = nullptr;
|
buffer = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -237,17 +233,16 @@ void MessageRef::alignWordParts(int lineStart, int lineHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageRef::tryGetWordPart(QPoint point, Word &word)
|
const Word *MessageRef::tryGetWordPart(QPoint point)
|
||||||
{
|
{
|
||||||
// go through all words and return the first one that contains the point.
|
// go through all words and return the first one that contains the point.
|
||||||
for (WordPart &wordPart : _wordParts) {
|
for (WordPart &wordPart : _wordParts) {
|
||||||
if (wordPart.getRect().contains(point)) {
|
if (wordPart.getRect().contains(point)) {
|
||||||
word = wordPart.getWord();
|
return &wordPart.getWord();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageRef::getSelectionIndex(QPoint position)
|
int MessageRef::getSelectionIndex(QPoint position)
|
||||||
|
@ -259,7 +254,7 @@ int MessageRef::getSelectionIndex(QPoint position)
|
||||||
// find out in which line the cursor is
|
// find out in which line the cursor is
|
||||||
int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
||||||
|
|
||||||
for (int i = 0; i < _wordParts.size(); i++) {
|
for (size_t i = 0; i < _wordParts.size(); i++) {
|
||||||
WordPart &part = _wordParts[i];
|
WordPart &part = _wordParts[i];
|
||||||
|
|
||||||
if (part.getLineNumber() != 0 && position.y() < part.getY()) {
|
if (part.getLineNumber() != 0 && position.y() < part.getY()) {
|
||||||
|
@ -267,11 +262,11 @@ int MessageRef::getSelectionIndex(QPoint position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.getLineNumber() != lineNumber) {
|
if (part.getLineNumber() != lineNumber) {
|
||||||
lineStart = i - 1;
|
lineStart = i;
|
||||||
lineNumber = part.getLineNumber();
|
lineNumber = part.getLineNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
lineEnd = part.getLineNumber() == 0 ? i : i + 1;
|
lineEnd = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// count up to the cursor
|
// count up to the cursor
|
||||||
|
@ -293,15 +288,19 @@ int MessageRef::getSelectionIndex(QPoint position)
|
||||||
|
|
||||||
// cursor is right of the word part
|
// cursor is right of the word part
|
||||||
if (position.x() > part.getX() + part.getWidth()) {
|
if (position.x() > part.getX() + part.getWidth()) {
|
||||||
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
index += part.getCharacterLength();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cursor is over the word part
|
// cursor is over the word part
|
||||||
if (part.getWord().isImage()) {
|
if (part.getWord().isImage()) {
|
||||||
index++;
|
if (position.x() - part.getX() > part.getWidth() / 2) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
auto text = part.getWord().getText();
|
// TODO: use word.getCharacterWidthCache();
|
||||||
|
|
||||||
|
auto text = part.getText();
|
||||||
|
|
||||||
int x = part.getX();
|
int x = part.getX();
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ public:
|
||||||
std::shared_ptr<QPixmap> buffer = nullptr;
|
std::shared_ptr<QPixmap> buffer = nullptr;
|
||||||
bool updateBuffer = false;
|
bool updateBuffer = false;
|
||||||
|
|
||||||
bool tryGetWordPart(QPoint point, messages::Word &word);
|
const messages::Word *tryGetWordPart(QPoint point);
|
||||||
|
|
||||||
int getSelectionIndex(QPoint position);
|
int getSelectionIndex(QPoint position);
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,12 @@ namespace messages {
|
||||||
// Image word
|
// Image word
|
||||||
Word::Word(LazyLoadedImage *image, Type type, const QString ©text, const QString &tooltip,
|
Word::Word(LazyLoadedImage *image, Type type, const QString ©text, const QString &tooltip,
|
||||||
const Link &link)
|
const Link &link)
|
||||||
: _image(image)
|
: image(image)
|
||||||
, _text()
|
|
||||||
, _color()
|
|
||||||
, _isImage(true)
|
, _isImage(true)
|
||||||
, _type(type)
|
, type(type)
|
||||||
, _copyText(copytext)
|
, copyText(copytext)
|
||||||
, _tooltip(tooltip)
|
, tooltip(tooltip)
|
||||||
, _link(link)
|
, link(link)
|
||||||
, _characterWidthCache()
|
|
||||||
{
|
{
|
||||||
image->getWidth(); // professional segfault test
|
image->getWidth(); // professional segfault test
|
||||||
}
|
}
|
||||||
|
@ -22,113 +19,127 @@ Word::Word(LazyLoadedImage *image, Type type, const QString ©text, const QSt
|
||||||
// Text word
|
// Text word
|
||||||
Word::Word(const QString &text, Type type, const QColor &color, const QString ©text,
|
Word::Word(const QString &text, Type type, const QColor &color, const QString ©text,
|
||||||
const QString &tooltip, const Link &link)
|
const QString &tooltip, const Link &link)
|
||||||
: _image(nullptr)
|
: image(nullptr)
|
||||||
, _text(text)
|
, text(text)
|
||||||
, _color(color)
|
, color(color)
|
||||||
, _isImage(false)
|
, _isImage(false)
|
||||||
, _type(type)
|
, type(type)
|
||||||
, _copyText(copytext)
|
, copyText(copytext)
|
||||||
, _tooltip(tooltip)
|
, tooltip(tooltip)
|
||||||
, _link(link)
|
, link(link)
|
||||||
, _characterWidthCache()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoadedImage &Word::getImage() const
|
LazyLoadedImage &Word::getImage() const
|
||||||
{
|
{
|
||||||
return *_image;
|
return *this->image;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Word::getText() const
|
const QString &Word::getText() const
|
||||||
{
|
{
|
||||||
return _text;
|
return this->text;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Word::getWidth() const
|
int Word::getWidth() const
|
||||||
{
|
{
|
||||||
return _width;
|
return this->width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Word::getHeight() const
|
int Word::getHeight() const
|
||||||
{
|
{
|
||||||
return _height;
|
return this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Word::setSize(int width, int height)
|
void Word::setSize(int width, int height)
|
||||||
{
|
{
|
||||||
_width = width;
|
this->width = width;
|
||||||
_height = height;
|
this->height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Word::isImage() const
|
bool Word::isImage() const
|
||||||
{
|
{
|
||||||
return _isImage;
|
return this->_isImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Word::isText() const
|
bool Word::isText() const
|
||||||
{
|
{
|
||||||
return !_isImage;
|
return !this->_isImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Word::getCopyText() const
|
const QString &Word::getCopyText() const
|
||||||
{
|
{
|
||||||
return _copyText;
|
return this->copyText;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Word::hasTrailingSpace() const
|
bool Word::hasTrailingSpace() const
|
||||||
{
|
{
|
||||||
return _hasTrailingSpace;
|
return this->_hasTrailingSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFont &Word::getFont() const
|
QFont &Word::getFont() const
|
||||||
{
|
{
|
||||||
return FontManager::getInstance().getFont(_font);
|
return FontManager::getInstance().getFont(this->font);
|
||||||
}
|
}
|
||||||
|
|
||||||
QFontMetrics &Word::getFontMetrics() const
|
QFontMetrics &Word::getFontMetrics() const
|
||||||
{
|
{
|
||||||
return FontManager::getInstance().getFontMetrics(_font);
|
return FontManager::getInstance().getFontMetrics(this->font);
|
||||||
}
|
}
|
||||||
|
|
||||||
Word::Type Word::getType() const
|
Word::Type Word::getType() const
|
||||||
{
|
{
|
||||||
return _type;
|
return this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Word::getTooltip() const
|
const QString &Word::getTooltip() const
|
||||||
{
|
{
|
||||||
return _tooltip;
|
return this->tooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QColor &Word::getColor() const
|
const QColor &Word::getColor() const
|
||||||
{
|
{
|
||||||
return _color;
|
return this->color;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Link &Word::getLink() const
|
const Link &Word::getLink() const
|
||||||
{
|
{
|
||||||
return _link;
|
return this->link;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Word::getXOffset() const
|
int Word::getXOffset() const
|
||||||
{
|
{
|
||||||
return _xOffset;
|
return this->xOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Word::getYOffset() const
|
int Word::getYOffset() const
|
||||||
{
|
{
|
||||||
return _yOffset;
|
return this->yOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Word::setOffset(int xOffset, int yOffset)
|
void Word::setOffset(int xOffset, int yOffset)
|
||||||
{
|
{
|
||||||
_xOffset = std::max(0, xOffset);
|
this->xOffset = std::max(0, xOffset);
|
||||||
_yOffset = std::max(0, yOffset);
|
this->yOffset = std::max(0, yOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Word::getCharacterLength() const
|
||||||
|
{
|
||||||
|
return this->isImage() ? 2 : this->getText().length() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<short> &Word::getCharacterWidthCache() const
|
std::vector<short> &Word::getCharacterWidthCache() const
|
||||||
{
|
{
|
||||||
return _characterWidthCache;
|
// lock not required because there is only one gui thread
|
||||||
|
// std::lock_guard<std::mutex> lock(this->charWidthCacheMutex);
|
||||||
|
|
||||||
|
if (this->charWidthCache.size() == 0 && this->isText()) {
|
||||||
|
for (int i = 0; i < this->getText().length(); i++) {
|
||||||
|
this->charWidthCache.push_back(this->getFontMetrics().charWidth(this->getText(), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: on font change
|
||||||
|
return this->charWidthCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
|
|
|
@ -110,29 +110,30 @@ public:
|
||||||
int getXOffset() const;
|
int getXOffset() const;
|
||||||
int getYOffset() const;
|
int getYOffset() const;
|
||||||
void setOffset(int _xOffset, int _yOffset);
|
void setOffset(int _xOffset, int _yOffset);
|
||||||
|
int getCharacterLength() const;
|
||||||
|
|
||||||
std::vector<short> &getCharacterWidthCache() const;
|
std::vector<short> &getCharacterWidthCache() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LazyLoadedImage *_image;
|
LazyLoadedImage *image;
|
||||||
QString _text;
|
QString text;
|
||||||
QColor _color;
|
QColor color;
|
||||||
bool _isImage;
|
bool _isImage;
|
||||||
|
|
||||||
Type _type;
|
Type type;
|
||||||
QString _copyText;
|
QString copyText;
|
||||||
QString _tooltip;
|
QString tooltip;
|
||||||
|
|
||||||
int _width = 16;
|
int width = 16;
|
||||||
int _height = 16;
|
int height = 16;
|
||||||
int _xOffset = 0;
|
int xOffset = 0;
|
||||||
int _yOffset = 0;
|
int yOffset = 0;
|
||||||
|
|
||||||
bool _hasTrailingSpace;
|
bool _hasTrailingSpace = true;
|
||||||
FontManager::Type _font = FontManager::Medium;
|
FontManager::Type font = FontManager::Medium;
|
||||||
Link _link;
|
Link link;
|
||||||
|
|
||||||
mutable std::vector<short> _characterWidthCache;
|
mutable std::vector<short> charWidthCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
|
|
|
@ -14,7 +14,7 @@ WordPart::WordPart(Word &word, int x, int y, int lineNumber, const QString ©
|
||||||
, _width(word.getWidth())
|
, _width(word.getWidth())
|
||||||
, _height(word.getHeight())
|
, _height(word.getHeight())
|
||||||
, _lineNumber(lineNumber)
|
, _lineNumber(lineNumber)
|
||||||
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
|
, _trailingSpace(!word.getCopyText().isEmpty() && word.hasTrailingSpace() & allowTrailingSpace)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ WordPart::WordPart(Word &word, int x, int y, int width, int height, int lineNumb
|
||||||
, _width(width)
|
, _width(width)
|
||||||
, _height(height)
|
, _height(height)
|
||||||
, _lineNumber(lineNumber)
|
, _lineNumber(lineNumber)
|
||||||
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
|
, _trailingSpace(!word.getCopyText().isEmpty() && word.hasTrailingSpace() & allowTrailingSpace)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ int WordPart::getBottom() const
|
||||||
|
|
||||||
QRect WordPart::getRect() const
|
QRect WordPart::getRect() const
|
||||||
{
|
{
|
||||||
return QRect(_x, _y, _width, _height);
|
return QRect(_x, _y, _width, _height - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString WordPart::getCopyText() const
|
const QString WordPart::getCopyText() const
|
||||||
|
@ -98,10 +98,17 @@ const QString &WordPart::getText() const
|
||||||
return _text;
|
return _text;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WordPart::getLineNumber()
|
int WordPart::getLineNumber() const
|
||||||
{
|
{
|
||||||
return _lineNumber;
|
return _lineNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int WordPart::getCharacterLength() const
|
||||||
|
{
|
||||||
|
// return (this->getWord().isImage() ? 1 : this->getText().length()) + (_trailingSpace ? 1 :
|
||||||
|
// 0);
|
||||||
|
return this->getWord().isImage() ? 2 : this->getText().length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -30,7 +30,8 @@ public:
|
||||||
const QString getCopyText() const;
|
const QString getCopyText() const;
|
||||||
int hasTrailingSpace() const;
|
int hasTrailingSpace() const;
|
||||||
const QString &getText() const;
|
const QString &getText() const;
|
||||||
int getLineNumber();
|
int getLineNumber() const;
|
||||||
|
int getCharacterLength() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Word &_word;
|
Word &_word;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "settingsmanager.hpp"
|
#include "settingsmanager.hpp"
|
||||||
#include "widgets/textinputdialog.hpp"
|
#include "widgets/textinputdialog.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
|
@ -65,6 +67,9 @@ ChatWidget::ChatWidget(ChannelManager &_channelManager, NotebookPage *parent)
|
||||||
// CTRL+R: Change Channel
|
// CTRL+R: Change Channel
|
||||||
ezShortcut(this, "CTRL+R", &ChatWidget::doChangeChannel);
|
ezShortcut(this, "CTRL+R", &ChatWidget::doChangeChannel);
|
||||||
|
|
||||||
|
// CTRL+C: Copy
|
||||||
|
ezShortcut(this, "CTRL+B", &ChatWidget::doCopy);
|
||||||
|
|
||||||
this->channelName.getValueChangedSignal().connect(
|
this->channelName.getValueChangedSignal().connect(
|
||||||
std::bind(&ChatWidget::channelNameUpdated, this, std::placeholders::_1));
|
std::bind(&ChatWidget::channelNameUpdated, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
@ -108,8 +113,11 @@ void ChatWidget::setChannel(std::shared_ptr<Channel> _newChannel)
|
||||||
|
|
||||||
// on message removed
|
// on message removed
|
||||||
this->messageRemovedConnection =
|
this->messageRemovedConnection =
|
||||||
this->channel->messageRemovedFromStart.connect([](SharedMessage &) {
|
this->channel->messageRemovedFromStart.connect([this](SharedMessage &) {
|
||||||
//
|
this->view.selection.min.messageIndex--;
|
||||||
|
this->view.selection.max.messageIndex--;
|
||||||
|
this->view.selection.start.messageIndex--;
|
||||||
|
this->view.selection.end.messageIndex--;
|
||||||
});
|
});
|
||||||
|
|
||||||
auto snapshot = this->channel->getMessageSnapshot();
|
auto snapshot = this->channel->getMessageSnapshot();
|
||||||
|
@ -296,5 +304,10 @@ void ChatWidget::doOpenStreamlink()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatWidget::doCopy()
|
||||||
|
{
|
||||||
|
QApplication::clipboard()->setText(this->view.getSelectedText());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace widgets
|
} // namespace widgets
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -115,6 +115,9 @@ public slots:
|
||||||
|
|
||||||
// Open twitch channel stream through streamlink
|
// Open twitch channel stream through streamlink
|
||||||
void doOpenStreamlink();
|
void doOpenStreamlink();
|
||||||
|
|
||||||
|
// Copy text from chat
|
||||||
|
void doCopy();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace widgets
|
} // namespace widgets
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include "widgets/chatwidgetview.hpp"
|
#include "widgets/chatwidgetview.hpp"
|
||||||
#include "channelmanager.hpp"
|
#include "channelmanager.hpp"
|
||||||
#include "colorscheme.hpp"
|
#include "colorscheme.hpp"
|
||||||
|
#include "messages/limitedqueuesnapshot.hpp"
|
||||||
#include "messages/message.hpp"
|
#include "messages/message.hpp"
|
||||||
#include "messages/wordpart.hpp"
|
#include "messages/messageref.hpp"
|
||||||
#include "settingsmanager.hpp"
|
#include "settingsmanager.hpp"
|
||||||
#include "ui_accountpopupform.h"
|
#include "ui_accountpopupform.h"
|
||||||
#include "util/distancebetweenpoints.hpp"
|
#include "util/distancebetweenpoints.hpp"
|
||||||
|
@ -14,8 +15,10 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace widgets {
|
namespace widgets {
|
||||||
|
@ -25,10 +28,6 @@ ChatWidgetView::ChatWidgetView(ChatWidget *_chatWidget)
|
||||||
, chatWidget(_chatWidget)
|
, chatWidget(_chatWidget)
|
||||||
, scrollBar(this)
|
, scrollBar(this)
|
||||||
, userPopupWidget(_chatWidget->getChannelRef())
|
, userPopupWidget(_chatWidget->getChannelRef())
|
||||||
, selectionStart(0, 0)
|
|
||||||
, selectionEnd(0, 0)
|
|
||||||
, selectionMin(0, 0)
|
|
||||||
, selectionMax(0, 0)
|
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_MAC
|
#ifndef Q_OS_MAC
|
||||||
this->setAttribute(Qt::WA_OpaquePaintEvent);
|
this->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
@ -70,14 +69,14 @@ bool ChatWidgetView::layoutMessages()
|
||||||
// The scrollbar was visible and at the bottom
|
// The scrollbar was visible and at the bottom
|
||||||
this->showingLatestMessages = this->scrollBar.isAtBottom() || !this->scrollBar.isVisible();
|
this->showingLatestMessages = this->scrollBar.isAtBottom() || !this->scrollBar.isVisible();
|
||||||
|
|
||||||
int start = this->scrollBar.getCurrentValue();
|
size_t start = this->scrollBar.getCurrentValue();
|
||||||
int layoutWidth = this->scrollBar.isVisible() ? width() - this->scrollBar.width() : width();
|
int layoutWidth = this->scrollBar.isVisible() ? width() - this->scrollBar.width() : width();
|
||||||
|
|
||||||
// layout the visible messages in the view
|
// layout the visible messages in the view
|
||||||
if (messages.getLength() > start) {
|
if (messages.getLength() > start) {
|
||||||
int y = -(messages[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1)));
|
int y = -(messages[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1)));
|
||||||
|
|
||||||
for (int i = start; i < messages.getLength(); ++i) {
|
for (size_t i = start; i < messages.getLength(); ++i) {
|
||||||
auto message = messages[i];
|
auto message = messages[i];
|
||||||
|
|
||||||
redraw |= message->layout(layoutWidth, true);
|
redraw |= message->layout(layoutWidth, true);
|
||||||
|
@ -141,6 +140,97 @@ ScrollBar &ChatWidgetView::getScrollBar()
|
||||||
return this->scrollBar;
|
return this->scrollBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChatWidgetView::getSelectedText() const
|
||||||
|
{
|
||||||
|
messages::LimitedQueueSnapshot<messages::SharedMessageRef> messages =
|
||||||
|
this->chatWidget->getMessagesSnapshot();
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
bool isSingleMessage = this->selection.isSingleMessage();
|
||||||
|
|
||||||
|
size_t i = std::max(0, this->selection.min.messageIndex);
|
||||||
|
|
||||||
|
int charIndex = 0;
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (const messages::WordPart &part : messages[i]->getWordParts()) {
|
||||||
|
int charLength = part.getCharacterLength();
|
||||||
|
|
||||||
|
if (charIndex + charLength < this->selection.min.charIndex) {
|
||||||
|
charIndex += charLength;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (part.getWord().isText()) {
|
||||||
|
text += part.getText().mid(this->selection.min.charIndex - charIndex);
|
||||||
|
} else {
|
||||||
|
text += part.getCopyText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingleMessage && charIndex + charLength >= selection.max.charIndex) {
|
||||||
|
if (part.getWord().isText()) {
|
||||||
|
text += part.getText().mid(0, this->selection.max.charIndex - charIndex);
|
||||||
|
} else {
|
||||||
|
text += part.getCopyText();
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += part.getCopyText();
|
||||||
|
|
||||||
|
if (part.hasTrailingSpace()) {
|
||||||
|
text += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
charIndex += charLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += "\n";
|
||||||
|
|
||||||
|
for (i++; i < this->selection.max.messageIndex; i++) {
|
||||||
|
for (const messages::WordPart &part : messages[i]->getWordParts()) {
|
||||||
|
text += part.getCopyText();
|
||||||
|
|
||||||
|
if (part.hasTrailingSpace()) {
|
||||||
|
text += " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
charIndex = 0;
|
||||||
|
|
||||||
|
for (const messages::WordPart &part :
|
||||||
|
messages[this->selection.max.messageIndex]->getWordParts()) {
|
||||||
|
int charLength = part.getCharacterLength();
|
||||||
|
|
||||||
|
if (charIndex + charLength >= this->selection.max.charIndex) {
|
||||||
|
if (part.getWord().isText()) {
|
||||||
|
text += part.getText().mid(0, this->selection.max.charIndex - charIndex);
|
||||||
|
} else {
|
||||||
|
text += part.getCopyText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += part.getCopyText();
|
||||||
|
|
||||||
|
if (part.hasTrailingSpace()) {
|
||||||
|
text += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
charIndex += charLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatWidgetView::resizeEvent(QResizeEvent *)
|
void ChatWidgetView::resizeEvent(QResizeEvent *)
|
||||||
{
|
{
|
||||||
this->scrollBar.resize(this->scrollBar.width(), height());
|
this->scrollBar.resize(this->scrollBar.width(), height());
|
||||||
|
@ -151,24 +241,13 @@ void ChatWidgetView::resizeEvent(QResizeEvent *)
|
||||||
this->update();
|
this->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatWidgetView::setSelection(SelectionItem start, SelectionItem end)
|
void ChatWidgetView::setSelection(const SelectionItem &start, const SelectionItem &end)
|
||||||
{
|
{
|
||||||
// selections
|
// selections
|
||||||
SelectionItem min = selectionStart;
|
this->selection = Selection(start, end);
|
||||||
SelectionItem max = selectionEnd;
|
|
||||||
|
|
||||||
if (max.isSmallerThan(min)) {
|
// qDebug() << min.messageIndex << ":" << min.charIndex << " " << max.messageIndex << ":"
|
||||||
std::swap(min, max);
|
// << max.charIndex;
|
||||||
}
|
|
||||||
|
|
||||||
this->selectionStart = start;
|
|
||||||
this->selectionEnd = end;
|
|
||||||
|
|
||||||
this->selectionMin = min;
|
|
||||||
this->selectionMax = max;
|
|
||||||
|
|
||||||
qDebug() << min.messageIndex << ":" << min.charIndex << " " << max.messageIndex << ":"
|
|
||||||
<< max.charIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatWidgetView::paintEvent(QPaintEvent * /*event*/)
|
void ChatWidgetView::paintEvent(QPaintEvent * /*event*/)
|
||||||
|
@ -212,7 +291,7 @@ void ChatWidgetView::drawMessages(QPainter &painter)
|
||||||
{
|
{
|
||||||
auto messages = this->chatWidget->getMessagesSnapshot();
|
auto messages = this->chatWidget->getMessagesSnapshot();
|
||||||
|
|
||||||
int start = this->scrollBar.getCurrentValue();
|
size_t start = this->scrollBar.getCurrentValue();
|
||||||
|
|
||||||
if (start >= messages.getLength()) {
|
if (start >= messages.getLength()) {
|
||||||
return;
|
return;
|
||||||
|
@ -234,6 +313,8 @@ void ChatWidgetView::drawMessages(QPainter &painter)
|
||||||
updateBuffer = true;
|
updateBuffer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateBuffer |= this->selecting;
|
||||||
|
|
||||||
// update messages that have been changed
|
// update messages that have been changed
|
||||||
if (updateBuffer) {
|
if (updateBuffer) {
|
||||||
this->updateMessageBuffer(messageRef, buffer, i);
|
this->updateMessageBuffer(messageRef, buffer, i);
|
||||||
|
@ -275,17 +356,17 @@ void ChatWidgetView::updateMessageBuffer(messages::MessageRef *messageRef, QPixm
|
||||||
QPainter painter(buffer);
|
QPainter painter(buffer);
|
||||||
|
|
||||||
// draw background
|
// draw background
|
||||||
// if (this->selectionMin.messageIndex <= messageIndex &&
|
|
||||||
// this->selectionMax.messageIndex >= messageIndex) {
|
|
||||||
// painter.fillRect(buffer->rect(), QColor(24, 55, 25));
|
|
||||||
//} else {
|
|
||||||
painter.fillRect(buffer->rect(),
|
painter.fillRect(buffer->rect(),
|
||||||
(messageRef->getMessage()->getCanHighlightTab())
|
(messageRef->getMessage()->getCanHighlightTab())
|
||||||
? this->colorScheme.ChatBackgroundHighlighted
|
? this->colorScheme.ChatBackgroundHighlighted
|
||||||
: this->colorScheme.ChatBackground);
|
: this->colorScheme.ChatBackground);
|
||||||
//}
|
|
||||||
|
|
||||||
// draw messages
|
// draw selection
|
||||||
|
if (!selection.isEmpty()) {
|
||||||
|
drawMessageSelection(painter, messageRef, messageIndex, buffer->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw message
|
||||||
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
|
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
|
||||||
// image
|
// image
|
||||||
if (wordPart.getWord().isImage()) {
|
if (wordPart.getWord().isImage()) {
|
||||||
|
@ -316,6 +397,155 @@ void ChatWidgetView::updateMessageBuffer(messages::MessageRef *messageRef, QPixm
|
||||||
messageRef->updateBuffer = false;
|
messageRef->updateBuffer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatWidgetView::drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef,
|
||||||
|
int messageIndex, int bufferHeight)
|
||||||
|
{
|
||||||
|
if (this->selection.min.messageIndex > messageIndex ||
|
||||||
|
this->selection.max.messageIndex < messageIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor selectionColor(255, 255, 255, 63);
|
||||||
|
|
||||||
|
int charIndex = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
auto &parts = messageRef->getWordParts();
|
||||||
|
|
||||||
|
int currentLineNumber = 0;
|
||||||
|
QRect rect;
|
||||||
|
|
||||||
|
if (parts.size() > 0) {
|
||||||
|
if (selection.min.messageIndex == messageIndex) {
|
||||||
|
rect.setTop(parts.at(0).getY());
|
||||||
|
}
|
||||||
|
rect.setLeft(parts.at(0).getX());
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip until selection start
|
||||||
|
if (this->selection.min.messageIndex == messageIndex && this->selection.min.charIndex != 0) {
|
||||||
|
for (; i < parts.size(); i++) {
|
||||||
|
const messages::WordPart &part = parts.at(i);
|
||||||
|
auto characterLength = part.getCharacterLength();
|
||||||
|
|
||||||
|
if (characterLength + charIndex > selection.min.charIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
charIndex += characterLength;
|
||||||
|
currentLineNumber = part.getLineNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= parts.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle word that has a cut of selection
|
||||||
|
const messages::WordPart &part = parts.at(i);
|
||||||
|
|
||||||
|
// check if selection if single word
|
||||||
|
int characterLength = part.getCharacterLength();
|
||||||
|
bool isSingleWord = charIndex + characterLength > this->selection.max.charIndex &&
|
||||||
|
this->selection.max.messageIndex == messageIndex;
|
||||||
|
|
||||||
|
rect = part.getRect();
|
||||||
|
currentLineNumber = part.getLineNumber();
|
||||||
|
|
||||||
|
if (part.getWord().isText()) {
|
||||||
|
int offset = this->selection.min.charIndex - charIndex;
|
||||||
|
|
||||||
|
std::vector<short> &characterWidth = part.getWord().getCharacterWidthCache();
|
||||||
|
|
||||||
|
for (int j = 0; j < offset; j++) {
|
||||||
|
rect.setLeft(rect.left() + characterWidth[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingleWord) {
|
||||||
|
int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||||
|
|
||||||
|
rect.setRight(part.getX());
|
||||||
|
|
||||||
|
for (int j = 0; j < offset + length; j++) {
|
||||||
|
rect.setRight(rect.right() + characterWidth[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
painter.fillRect(rect, selectionColor);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isSingleWord) {
|
||||||
|
if (charIndex + 1 != this->selection.max.charIndex) {
|
||||||
|
rect.setRight(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||||
|
}
|
||||||
|
painter.fillRect(rect, selectionColor);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charIndex != this->selection.min.charIndex) {
|
||||||
|
rect.setLeft(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
charIndex += characterLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go through lines and draw selection
|
||||||
|
for (; i < parts.size(); i++) {
|
||||||
|
const messages::WordPart &part = parts.at(i);
|
||||||
|
|
||||||
|
int charLength = part.getCharacterLength();
|
||||||
|
|
||||||
|
bool isLastSelectedWord = this->selection.max.messageIndex == messageIndex &&
|
||||||
|
charIndex + charLength > this->selection.max.charIndex;
|
||||||
|
|
||||||
|
if (part.getLineNumber() == currentLineNumber) {
|
||||||
|
rect.setLeft(std::min(rect.left(), part.getX()));
|
||||||
|
rect.setTop(std::min(rect.top(), part.getY()));
|
||||||
|
rect.setRight(std::max(rect.right(), part.getRight()));
|
||||||
|
rect.setBottom(std::max(rect.bottom(), part.getBottom() - 1));
|
||||||
|
} else {
|
||||||
|
painter.fillRect(rect, selectionColor);
|
||||||
|
|
||||||
|
currentLineNumber = part.getLineNumber();
|
||||||
|
|
||||||
|
rect = part.getRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLastSelectedWord) {
|
||||||
|
if (part.getWord().isText()) {
|
||||||
|
int offset = this->selection.min.charIndex - charIndex;
|
||||||
|
|
||||||
|
std::vector<short> &characterWidth = part.getWord().getCharacterWidthCache();
|
||||||
|
|
||||||
|
int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||||
|
|
||||||
|
rect.setRight(part.getX());
|
||||||
|
|
||||||
|
for (int j = 0; j < offset + length; j++) {
|
||||||
|
rect.setRight(rect.right() + characterWidth[j]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this->selection.max.charIndex == charIndex) {
|
||||||
|
rect.setRight(part.getX());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
painter.fillRect(rect, selectionColor);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
charIndex += charLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->selection.max.messageIndex != messageIndex) {
|
||||||
|
rect.setBottom(bufferHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
painter.fillRect(rect, selectionColor);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatWidgetView::wheelEvent(QWheelEvent *event)
|
void ChatWidgetView::wheelEvent(QWheelEvent *event)
|
||||||
{
|
{
|
||||||
if (this->scrollBar.isVisible()) {
|
if (this->scrollBar.isVisible()) {
|
||||||
|
@ -340,18 +570,18 @@ void ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
|
||||||
if (this->selecting) {
|
if (this->selecting) {
|
||||||
int index = message->getSelectionIndex(relativePos);
|
int index = message->getSelectionIndex(relativePos);
|
||||||
|
|
||||||
this->setSelection(this->selectionStart, SelectionItem(messageIndex, index));
|
this->setSelection(this->selection.start, SelectionItem(messageIndex, index));
|
||||||
|
|
||||||
this->repaint();
|
this->repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
messages::Word hoverWord;
|
const messages::Word *hoverWord;
|
||||||
if (!message->tryGetWordPart(relativePos, hoverWord)) {
|
if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) {
|
||||||
setCursor(Qt::ArrowCursor);
|
setCursor(Qt::ArrowCursor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoverWord.getLink().isValid()) {
|
if (hoverWord->getLink().isValid()) {
|
||||||
setCursor(Qt::PointingHandCursor);
|
setCursor(Qt::PointingHandCursor);
|
||||||
} else {
|
} else {
|
||||||
setCursor(Qt::ArrowCursor);
|
setCursor(Qt::ArrowCursor);
|
||||||
|
@ -420,13 +650,13 @@ void ChatWidgetView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
messages::Word hoverWord;
|
const messages::Word *hoverWord;
|
||||||
|
|
||||||
if (!message->tryGetWordPart(relativePos, hoverWord)) {
|
if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &link = hoverWord.getLink();
|
auto &link = hoverWord->getLink();
|
||||||
|
|
||||||
switch (link.getType()) {
|
switch (link.getType()) {
|
||||||
case messages::Link::UserInfo: {
|
case messages::Link::UserInfo: {
|
||||||
|
@ -451,7 +681,7 @@ bool ChatWidgetView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::Message
|
||||||
{
|
{
|
||||||
auto messages = this->chatWidget->getMessagesSnapshot();
|
auto messages = this->chatWidget->getMessagesSnapshot();
|
||||||
|
|
||||||
int start = this->scrollBar.getCurrentValue();
|
size_t start = this->scrollBar.getCurrentValue();
|
||||||
|
|
||||||
if (start >= messages.getLength()) {
|
if (start >= messages.getLength()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -459,7 +689,7 @@ bool ChatWidgetView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::Message
|
||||||
|
|
||||||
int y = -(messages[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1)));
|
int y = -(messages[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1)));
|
||||||
|
|
||||||
for (int i = start; i < messages.getLength(); ++i) {
|
for (size_t i = start; i < messages.getLength(); ++i) {
|
||||||
auto message = messages[i];
|
auto message = messages[i];
|
||||||
|
|
||||||
if (p.y() < y + message->getHeight()) {
|
if (p.y() < y + message->getHeight()) {
|
||||||
|
|
|
@ -20,16 +20,58 @@ struct SelectionItem {
|
||||||
int messageIndex;
|
int messageIndex;
|
||||||
int charIndex;
|
int charIndex;
|
||||||
|
|
||||||
|
SelectionItem()
|
||||||
|
{
|
||||||
|
messageIndex = charIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
SelectionItem(int _messageIndex, int _charIndex)
|
SelectionItem(int _messageIndex, int _charIndex)
|
||||||
{
|
{
|
||||||
this->messageIndex = _messageIndex;
|
this->messageIndex = _messageIndex;
|
||||||
this->charIndex = _charIndex;
|
this->charIndex = _charIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSmallerThan(SelectionItem &other)
|
bool isSmallerThan(const SelectionItem &other) const
|
||||||
{
|
{
|
||||||
return messageIndex < other.messageIndex ||
|
return this->messageIndex < other.messageIndex ||
|
||||||
(messageIndex == other.messageIndex && charIndex < other.charIndex);
|
(this->messageIndex == other.messageIndex && this->charIndex < other.charIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equals(const SelectionItem &other) const
|
||||||
|
{
|
||||||
|
return this->messageIndex == other.messageIndex && this->charIndex == other.charIndex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Selection {
|
||||||
|
SelectionItem start;
|
||||||
|
SelectionItem end;
|
||||||
|
SelectionItem min;
|
||||||
|
SelectionItem max;
|
||||||
|
|
||||||
|
Selection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Selection(const SelectionItem &start, const SelectionItem &end)
|
||||||
|
: start(start)
|
||||||
|
, end(end)
|
||||||
|
, min(start)
|
||||||
|
, max(end)
|
||||||
|
{
|
||||||
|
if (max.isSmallerThan(min)) {
|
||||||
|
std::swap(this->min, this->max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty() const
|
||||||
|
{
|
||||||
|
return this->start.equals(this->end);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSingleMessage() const
|
||||||
|
{
|
||||||
|
return this->min.messageIndex == this->max.messageIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +79,8 @@ class ChatWidget;
|
||||||
|
|
||||||
class ChatWidgetView : public BaseWidget
|
class ChatWidgetView : public BaseWidget
|
||||||
{
|
{
|
||||||
|
friend class ChatWidget;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatWidgetView(ChatWidget *_chatWidget);
|
explicit ChatWidgetView(ChatWidget *_chatWidget);
|
||||||
~ChatWidgetView();
|
~ChatWidgetView();
|
||||||
|
@ -45,6 +89,7 @@ public:
|
||||||
|
|
||||||
void updateGifEmotes();
|
void updateGifEmotes();
|
||||||
ScrollBar &getScrollBar();
|
ScrollBar &getScrollBar();
|
||||||
|
QString getSelectedText() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void resizeEvent(QResizeEvent *) override;
|
virtual void resizeEvent(QResizeEvent *) override;
|
||||||
|
@ -67,7 +112,9 @@ private:
|
||||||
|
|
||||||
void drawMessages(QPainter &painter);
|
void drawMessages(QPainter &painter);
|
||||||
void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex);
|
void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex);
|
||||||
void setSelection(SelectionItem start, SelectionItem end);
|
void drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef, int messageIndex,
|
||||||
|
int bufferHeight);
|
||||||
|
void setSelection(const SelectionItem &start, const SelectionItem &end);
|
||||||
|
|
||||||
std::vector<GifEmoteData> gifEmotes;
|
std::vector<GifEmoteData> gifEmotes;
|
||||||
|
|
||||||
|
@ -86,10 +133,7 @@ private:
|
||||||
bool isMouseDown = false;
|
bool isMouseDown = false;
|
||||||
QPointF lastPressPosition;
|
QPointF lastPressPosition;
|
||||||
|
|
||||||
SelectionItem selectionStart;
|
Selection selection;
|
||||||
SelectionItem selectionEnd;
|
|
||||||
SelectionItem selectionMin;
|
|
||||||
SelectionItem selectionMax;
|
|
||||||
bool selecting = false;
|
bool selecting = false;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
Loading…
Reference in a new issue