diff --git a/.vs/chatterino/v14/.suo b/.vs/chatterino/v14/.suo new file mode 100644 index 000000000..8379e15cb Binary files /dev/null and b/.vs/chatterino/v14/.suo differ diff --git a/appsettings.h b/appsettings.h index 96220bde7..d32a2858c 100644 --- a/appsettings.h +++ b/appsettings.h @@ -11,6 +11,17 @@ public: } static bool isIgnoredEmote(const QString& emote); + static qreal emoteScale() { + return 1; + } + + static qreal badgeScale() { + return 1; + } + + static bool scaleEmotesByLineHeight() { + return false; + } private: AppSettings(); diff --git a/channel.cpp b/channel.cpp index c96afe588..c0f422a4a 100644 --- a/channel.cpp +++ b/channel.cpp @@ -1,8 +1,10 @@ #include "channel.h" #include "message.h" -Channel Channel::whispers = Channel(QString("/whispers")); -Channel Channel::mentions = Channel(QString("/mentions")); +#include + +Channel Channel::whispers(QString("/whispers")); +Channel Channel::mentions(QString("/mentions")); QMap Channel::channels = QMap(); @@ -30,7 +32,7 @@ Channel* Channel::addChannel(const QString &channel) return c; } - c->referenceCount++; + c->m_referenceCount++; return c; } @@ -60,25 +62,26 @@ void Channel::removeChannel(const QString &channel) if (c == NULL) return; - c->referenceCount--; + c->m_referenceCount--; - if (c->referenceCount == 0) { + if (c->m_referenceCount == 0) { channels.remove(channel); delete c; } } -QVector Channel::getMessagesClone() +QVector> Channel::getMessagesClone() { m_messageMutex.lock(); - QVector M = QVector(*m_messages); + QVector> M(m_messages); + M.detach(); m_messageMutex.unlock(); return M; } -void Channel::addMessage(Message *message) +void Channel::addMessage(std::shared_ptr message) { m_messageMutex.lock(); -// messages + m_messages.append(message); m_messageMutex.unlock(); } diff --git a/channel.h b/channel.h index 9f7bdab8a..c9ecf958a 100644 --- a/channel.h +++ b/channel.h @@ -8,6 +8,7 @@ #include #include #include +#include class Message; @@ -48,9 +49,9 @@ public: const QString& streamGame() const { return m_streamGame; } // methods - void addMessage(Message* message); + void addMessage(std::shared_ptr message); - QVector getMessagesClone(); + QVector> getMessagesClone(); private: Channel(QString channel); @@ -61,7 +62,7 @@ private: int m_referenceCount = 0; - QVector m_messages; + QVector> m_messages; QString m_name; int m_roomID; diff --git a/chatterino.pro b/chatterino.pro index cd67d8b39..de419f370 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -56,7 +56,8 @@ SOURCES += main.cpp\ link.cpp \ fonts.cpp \ appsettings.cpp \ - emojis.cpp + emojis.cpp \ + wordpart.cpp HEADERS += mainwindow.h \ chatwidget.h \ @@ -87,11 +88,12 @@ HEADERS += mainwindow.h \ word.h \ link.h \ fonts.h \ - common.h \ appsettings.h \ - emojis.h + emojis.h \ + wordpart.h \ + common.h -PRECOMPILED_HEADER = common.h +PRECOMPILED_HEADER = FORMS += \ dialog.ui diff --git a/chatwidgetview.cpp b/chatwidgetview.cpp index ef34cab80..9c711fe06 100644 --- a/chatwidgetview.cpp +++ b/chatwidgetview.cpp @@ -1,6 +1,10 @@ #include "chatwidgetview.h" -#include "QScroller" -#include "QPainter" +#include "word.h" +#include "wordpart.h" +#include "message.h" + +#include +#include ChatWidgetView::ChatWidgetView() : QWidget(), @@ -18,6 +22,17 @@ void ChatWidgetView::resizeEvent(QResizeEvent *) { scrollbar.resize(scrollbar.width(), height()); scrollbar.move(width() - scrollbar.width(), 0); + + auto c = channel(); + + if (c == NULL) return; + + auto messages = c->getMessagesClone(); + + for (std::shared_ptr& message : messages) + { + message.get()->layout(width(), true); + } } void ChatWidgetView::paintEvent(QPaintEvent *) @@ -28,7 +43,34 @@ void ChatWidgetView::paintEvent(QPaintEvent *) if (c == NULL) return; - auto M = c->getMessagesClone(); + auto messages = c->getMessagesClone(); + int y = 0; + for (std::shared_ptr const& message : messages) + { + for (WordPart const& wordPart : message.get()->wordParts()) + { + // image + if (wordPart.word().isImage()) + { + LazyLoadedImage& lli = wordPart.word().getImage(); + + const QImage* image = lli.image(); + + if (image != NULL) + { + painter.drawImage(QRect(wordPart.x(), wordPart.y() + y, wordPart.width(), wordPart.height()), *image); + } + } + + // text + else + { + painter.drawText(wordPart.x(), wordPart.y() + y, wordPart.getText()); + } + } + + y += message.get()->height(); + } } diff --git a/common.h.cpp b/common.h.cpp new file mode 100644 index 000000000..702ad8b37 --- /dev/null +++ b/common.h.cpp @@ -0,0 +1,16 @@ +/*-------------------------------------------------------------------- +* Precompiled header source file used by Visual Studio.NET to generate +* the .pch file. +* +* Due to issues with the dependencies checker within the IDE, it +* sometimes fails to recompile the PCH file, if we force the IDE to +* create the PCH file directly from the header file. +* +* This file is auto-generated by qmake since no PRECOMPILED_SOURCE was +* specified, and is used as the common stdafx.cpp. The file is only +* generated when creating .vcxproj project files, and is not used for +* command line compilations by nmake. +* +* WARNING: All changes made in this file will be lost. +--------------------------------------------------------------------*/ +#include "common.h" diff --git a/concurrentmap.h b/concurrentmap.h index 57d2cd60f..16c531fd0 100644 --- a/concurrentmap.h +++ b/concurrentmap.h @@ -14,7 +14,7 @@ public: map = new QMap(); } - bool tryGet(const TKey &name, TValue& value) { + bool tryGet(const TKey &name, TValue& value) const { mutex->lock(); auto a = map->find(name); if (a == map->end()) { diff --git a/emojis.cpp b/emojis.cpp index 637f9c5fd..03b2b4a79 100644 --- a/emojis.cpp +++ b/emojis.cpp @@ -15,7 +15,7 @@ ConcurrentMap* Emojis::imageCache = new ConcurrentMap QString Emojis::replaceShortCodes(const QString &text) { -#warning "xD" +#pragma message WARN("xD") return text; } diff --git a/emotes.cpp b/emotes.cpp index 9b9a31a2a..e5987e6d6 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -23,14 +23,14 @@ Emotes::Emotes() LazyLoadedImage* Emotes::getTwitchEmoteById(const QString &name, long id) { -#warning "xD" +#pragma message WARN("xD") return new LazyLoadedImage(NULL); // return m_twitchEmoteFromCache->getOrAdd() } LazyLoadedImage* Emotes::getCheerImage(long long amount, bool animated) { -#warning "xD" +#pragma message WARN("xD") return getCheerBadge(amount); } diff --git a/fonts.cpp b/fonts.cpp index a82fd54ad..82401159d 100644 --- a/fonts.cpp +++ b/fonts.cpp @@ -9,6 +9,13 @@ QFont* Fonts::small = new QFont(DEFAULT_FONT); QFont* Fonts::large = new QFont(DEFAULT_FONT); QFont* Fonts::veryLarge = new QFont(DEFAULT_FONT); +QFontMetrics* Fonts::metricsMedium = new QFontMetrics(*medium ); +QFontMetrics* Fonts::metricsMediumBold = new QFontMetrics(*mediumBold ); +QFontMetrics* Fonts::metricsMediumItalic = new QFontMetrics(*mediumItalic); +QFontMetrics* Fonts::metricsSmall = new QFontMetrics(*small ); +QFontMetrics* Fonts::metricsLarge = new QFontMetrics(*large ); +QFontMetrics* Fonts::metricsVeryLarge = new QFontMetrics(*veryLarge ); + Fonts::Fonts() { @@ -25,3 +32,15 @@ QFont& Fonts::getFont(Type type) return *medium; } + +QFontMetrics& Fonts::getFontMetrics(Type type) +{ + if (type == Medium ) return *metricsMedium ; + if (type == MediumBold ) return *metricsMediumBold ; + if (type == MediumItalic) return *metricsMediumItalic; + if (type == Small ) return *metricsSmall ; + if (type == Large ) return *metricsLarge ; + if (type == VeryLarge ) return *metricsVeryLarge ; + + return *metricsMedium; +} diff --git a/fonts.h b/fonts.h index a1937d1ce..b8b109608 100644 --- a/fonts.h +++ b/fonts.h @@ -1,7 +1,8 @@ #ifndef FONTS_H #define FONTS_H -#include "QFont" +#include +#include class Fonts { @@ -16,6 +17,7 @@ public: }; static QFont& getFont(Type type); + static QFontMetrics& getFontMetrics(Type type); private: Fonts(); @@ -26,6 +28,13 @@ private: static QFont* small; static QFont* large; static QFont* veryLarge; + + static QFontMetrics* metricsMedium; + static QFontMetrics* metricsMediumBold; + static QFontMetrics* metricsMediumItalic; + static QFontMetrics* metricsSmall; + static QFontMetrics* metricsLarge; + static QFontMetrics* metricsVeryLarge; }; #endif // FONTS_H diff --git a/ircmanager.cpp b/ircmanager.cpp index 75fb960e4..22e0a9d88 100644 --- a/ircmanager.cpp +++ b/ircmanager.cpp @@ -22,7 +22,7 @@ QMutex* IrcManager::twitchBlockedUsersMutex = new QMutex(); IrcManager::IrcManager() { -// account = Account::anon(); + } void IrcManager::connect() @@ -160,7 +160,7 @@ void IrcManager::privateMessageReceived(IrcPrivateMessage *message) auto c = Channel::getChannel(message->target().mid(1)); if (c != NULL) { - c->addMessage(new Message(*message, *c)); + c->addMessage(std::shared_ptr(new Message(*message, *c))); } } diff --git a/lazyloadedimage.cpp b/lazyloadedimage.cpp index b85039342..d0ddbeb22 100644 --- a/lazyloadedimage.cpp +++ b/lazyloadedimage.cpp @@ -1,7 +1,8 @@ #include "lazyloadedimage.h" LazyLoadedImage::LazyLoadedImage(const QString& url, qreal scale, const QString& name, const QString& tooltip, const QMargins& margin, bool isHat) - : m_url(url) + : m_image(NULL) + , m_url(url) , m_name(name) , m_tooltip(tooltip) , m_animated(false) diff --git a/lazyloadedimage.h b/lazyloadedimage.h index 31e90dcb0..0c5815ed3 100644 --- a/lazyloadedimage.h +++ b/lazyloadedimage.h @@ -25,8 +25,22 @@ public: bool animated() const { return m_animated; } bool isHat() const { return m_ishat; } + const long width() const { + if (m_image == NULL) { + return 16; + } + return m_image->width(); + } + + const long height() const { + if (m_image == NULL) { + return 16; + } + return m_image->height(); + } + private: - QImage* m_image = NULL; + QImage* m_image; qreal m_scale; QString m_url; diff --git a/main.cpp b/main.cpp index 83a1e734b..c1106f5b9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,9 @@ -#include #include "mainwindow.h" #include "colorscheme.h" #include "ircmanager.h" #include "emojis.h" + +#include #include int main(int argc, char *argv[]) diff --git a/message.cpp b/message.cpp index dafbfcc20..e2b795617 100644 --- a/message.cpp +++ b/message.cpp @@ -6,11 +6,18 @@ #include "link.h" #include "appsettings.h" #include "ircmanager.h" +#include "fonts.h" #include #include +#include #include +#define MARGIN_LEFT 8 +#define MARGIN_RIGHT 8 +#define MARGIN_TOP 8 +#define MARGIN_BOTTOM 8 + LazyLoadedImage* Message::badgeStaff = new LazyLoadedImage(new QImage(":/images/staff_bg.png")); LazyLoadedImage* Message::badgeAdmin = new LazyLoadedImage(new QImage(":/images/admin_bg.png")); LazyLoadedImage* Message::badgeModerator = new LazyLoadedImage(new QImage(":/images/moderator_bg.png")); @@ -22,29 +29,33 @@ LazyLoadedImage* Message::badgePremium = new LazyLoadedImage(new QImage(":/i QRegularExpression* Message::cheerRegex = new QRegularExpression("cheer[1-9][0-9]*"); Message::Message(const QString &text) + : m_wordParts(new std::list()) { } Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bool enablePingSound, bool isReceivedWhisper, bool isSentWhisper, bool includeChannel ) + : m_wordParts(new std::list()) { m_parseTime = std::chrono::system_clock::now(); - auto words = new QList(); + auto words = std::vector(); - auto iterator = ircMessage.tags().find("id"); + auto tags = ircMessage.tags(); - if (iterator != ircMessage.tags().end()) + auto iterator = tags.find("id"); + + if (iterator != tags.end()) { m_id = iterator.value().toString(); } // timestamps - iterator = ircMessage.tags().find("tmi-sent-ts"); + iterator = tags.find("tmi-sent-ts"); std::time_t time = std::time(NULL); - if (iterator != ircMessage.tags().end()) + if (iterator != tags.end()) { time = strtoll(iterator.value().toString().toStdString().c_str(), NULL, 10); } @@ -57,13 +68,13 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&time)); QString timestampWithSeconds = QString(timeStampBuffer); - words->append(Word(timestamp, Word::TimestampNoSeconds, ColorScheme::instance().SystemMessageColor, QString(), QString())); - words->append(Word(timestampWithSeconds, Word::TimestampWithSeconds, ColorScheme::instance().SystemMessageColor, QString(), QString())); + words.push_back(Word(timestamp, Word::TimestampNoSeconds, ColorScheme::instance().SystemMessageColor, QString(), QString())); + words.push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds, ColorScheme::instance().SystemMessageColor, QString(), QString())); // badges - iterator = ircMessage.tags().find("badges"); + iterator = tags.find("badges"); - if (iterator != ircMessage.tags().end()) + if (iterator != tags.end()) { auto badges = iterator.value().toString().split(','); @@ -72,36 +83,36 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo if (badge.startsWith("bits/")) { long long int cheer = strtoll(badge.mid(5).toStdString().c_str(), NULL, 10); - words->append(Word(Emotes::getCheerBadge(cheer), Word::BadgeCheer, QString(), QString("Twitch Cheer" + QString::number(cheer)))); + words.push_back(Word(Emotes::getCheerBadge(cheer), Word::BadgeCheer, QString(), QString("Twitch Cheer" + QString::number(cheer)))); } else if (badge == "staff/1") { - words->append(Word(badgeStaff, Word::BadgeStaff, QString(), QString("Twitch Staff"))); + words.push_back(Word(badgeStaff, Word::BadgeStaff, QString(), QString("Twitch Staff"))); } else if (badge == "admin/1") { - words->append(Word(badgeAdmin, Word::BadgeAdmin, QString(), QString("Twitch Admin"))); + words.push_back(Word(badgeAdmin, Word::BadgeAdmin, QString(), QString("Twitch Admin"))); } else if (badge == "global_mod/1") { - words->append(Word(badgeGlobalmod, Word::BadgeGlobalMod, QString(), QString("Global Moderator"))); + words.push_back(Word(badgeGlobalmod, Word::BadgeGlobalMod, QString(), QString("Global Moderator"))); } else if (badge == "moderator/1") { -#warning "xD" - words->append(Word(badgeTurbo, Word::BadgeModerator, QString(), QString("Channel Moderator"))); // custom badge +#pragma message WARN("xD") + words.push_back(Word(badgeTurbo, Word::BadgeModerator, QString(), QString("Channel Moderator"))); // custom badge } else if (badge == "turbo/1") { - words->append(Word(badgeStaff, Word::BadgeTurbo, QString(), QString("Turbo Subscriber"))); + words.push_back(Word(badgeStaff, Word::BadgeTurbo, QString(), QString("Turbo Subscriber"))); } else if (badge == "broadcaster/1") { - words->append(Word(badgeBroadcaster, Word::BadgeBroadcaster, QString(), QString("Channel Broadcaster"))); + words.push_back(Word(badgeBroadcaster, Word::BadgeBroadcaster, QString(), QString("Channel Broadcaster"))); } else if (badge == "premium/1") { - words->append(Word(badgePremium, Word::BadgePremium, QString(), QString("Twitch Prime"))); + words.push_back(Word(badgePremium, Word::BadgePremium, QString(), QString("Twitch Prime"))); } } } @@ -109,8 +120,8 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo // color QColor usernameColor = ColorScheme::instance().SystemMessageColor; - iterator = ircMessage.tags().find("color"); - if (iterator != ircMessage.tags().end()) + iterator = tags.find("color"); + if (iterator != tags.end()) { usernameColor = QColor(iterator.value().toString()); } @@ -119,7 +130,7 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo if (includeChannel) { QString channelName("#" + channel.name()); - words->append(Word(channelName, Word::Misc, ColorScheme::instance().SystemMessageColor, QString(channelName), QString(), Link(Link::Url, channel.name() + "\n" + m_id))); + words.push_back(Word(channelName, Word::Misc, ColorScheme::instance().SystemMessageColor, QString(channelName), QString(), Link(Link::Url, channel.name() + "\n" + m_id))); } // username @@ -127,9 +138,9 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo if (m_userName.isEmpty()) { - auto iterator = ircMessage.tags().find("login"); + auto iterator = tags.find("login"); - if (iterator != ircMessage.tags().end()) + if (iterator != tags.end()) { m_userName = iterator.value().toString(); } @@ -137,8 +148,8 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo QString displayName; - iterator = ircMessage.tags().find("display-name"); - if (iterator == ircMessage.tags().end()) { + iterator = tags.find("display-name"); + if (iterator == tags.end()) { displayName = ircMessage.account(); } else { @@ -163,7 +174,7 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo userDisplayString += ": "; } - words->append(Word(userDisplayString, Word::Username, usernameColor, userDisplayString, QString())); + words.push_back(Word(userDisplayString, Word::Username, usernameColor, userDisplayString, QString())); // highlights #pragma message WARN("xD") @@ -171,18 +182,18 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo // bits QString bits = ""; - iterator = ircMessage.tags().find("bits"); - if (iterator != ircMessage.tags().end()) + iterator = tags.find("bits"); + if (iterator != tags.end()) { bits = iterator.value().toString(); } // twitch emotes - QVector> twitchEmotes; + std::vector> twitchEmotes; - iterator = ircMessage.tags().find("emotes"); + iterator = tags.find("emotes"); - if (iterator != ircMessage.tags().end()) + if (iterator != tags.end()) { auto emotes = iterator.value().toString().split('/'); @@ -211,7 +222,7 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo QString name = ircMessage.content().mid(start, end - start); - twitchEmotes.append(std::pair(start, Emotes::getTwitchEmoteById(name, id))); + twitchEmotes.push_back(std::pair(start, Emotes::getTwitchEmoteById(name, id))); } } @@ -234,8 +245,8 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo if (currentTwitchEmote->first == i) { - words->append(Word(currentTwitchEmote->second, Word::TwitchEmoteImage, currentTwitchEmote->second->name(), currentTwitchEmote->second->name() + QString("\nTwitch Emote"))); - words->append(Word(currentTwitchEmote->second->name(), Word::TwitchEmoteText, textColor, currentTwitchEmote->second->name(), currentTwitchEmote->second->name() + QString("\nTwitch Emote"))); + words.push_back(Word(currentTwitchEmote->second, Word::TwitchEmoteImage, currentTwitchEmote->second->name(), currentTwitchEmote->second->name() + QString("\nTwitch Emote"))); + words.push_back(Word(currentTwitchEmote->second->name(), Word::TwitchEmoteText, textColor, currentTwitchEmote->second->name(), currentTwitchEmote->second->name() + QString("\nTwitch Emote"))); i += split.length() + 1; currentTwitchEmote = std::next(currentTwitchEmote); @@ -296,26 +307,26 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo LazyLoadedImage* imageAnimated = Emotes::miscImageFromCache().getOrAdd(bitsLinkAnimated, [&bitsLinkAnimated]{ return new LazyLoadedImage(bitsLinkAnimated); }); LazyLoadedImage* image = Emotes::miscImageFromCache().getOrAdd(bitsLink, [&bitsLink]{ return new LazyLoadedImage(bitsLink); }); - words->append(Word(imageAnimated, Word::BitsAnimated, QString("cheer"), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); - words->append(Word(image, Word::Bits, QString("cheer"), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); + words.push_back(Word(imageAnimated, Word::BitsAnimated, QString("cheer"), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); + words.push_back(Word(image, Word::Bits, QString("cheer"), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); - words->append(Word(QString("x" + string.mid(5)), Word::BitsAmount, bitsColor, QString(string.mid(5)), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); + words.push_back(Word(QString("x" + string.mid(5)), Word::BitsAmount, bitsColor, QString(string.mid(5)), QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/introducing-cheering-celebrate-together-da62af41fac6")))); + + continue; } - continue; - // bttv / ffz emotes LazyLoadedImage* bttvEmote; +#pragma message WARN( "xD ignored emotes") if ( - #warning "xD ignored emotes" Emotes::bttvEmotes().tryGet(string, bttvEmote) || channel.bttvChannelEmotes().tryGet(string, bttvEmote) || Emotes::ffzEmotes().tryGet(string, bttvEmote) || channel.ffzChannelEmotes().tryGet(string, bttvEmote) || Emotes::chatterinoEmotes().tryGet(string, bttvEmote)) { - words->append(Word(bttvEmote, Word::BttvEmoteImage, bttvEmote->name(), bttvEmote->tooltip(), Link(Link::Url, bttvEmote->url()))); + words.push_back(Word(bttvEmote, Word::BttvEmoteImage, bttvEmote->name(), bttvEmote->tooltip(), Link(Link::Url, bttvEmote->url()))); continue; } @@ -323,22 +334,131 @@ Message::Message(const IrcPrivateMessage& ircMessage, const Channel& channel, bo // actually just a word QString link = matchLink(string); - words->append(Word(string, Word::Text, textColor, string, QString(), link.isEmpty() ? Link() : Link(Link::Url, link))); + words.push_back(Word(string, Word::Text, textColor, string, QString(), link.isEmpty() ? Link() : Link(Link::Url, link))); } } i += split.length() + 1; } - this->words() = words; + this->m_words = words; -#warning "xD" +#pragma message WARN("xD") // if (!isReceivedWhisper && AppSettings.HighlightIgnoredUsers.ContainsKey(Username)) // { // HighlightTab = false; // } } +//static void normalize + +bool Message::layout(int width, bool enableEmoteMargins) +{ + width = width - (width % 2); + + int mediumTextLineHeight = Fonts::getFontMetrics(Fonts::Medium).height(); + + bool redraw = width != m_currentLayoutWidth || m_relayoutRequested; + + if (m_recalculateImages || m_recalculateText) + { + redraw = true; + + for (auto& word : m_words) + { + if (word.isImage()) + { + if (m_recalculateImages) + { + auto& image = word.getImage(); + + qreal w = image.width(); + qreal h = image.height(); + + if (AppSettings::scaleEmotesByLineHeight()) + { + word.setSize(w * mediumTextLineHeight / h * AppSettings::emoteScale(), mediumTextLineHeight * AppSettings::emoteScale()); + } + else + { + word.setSize(w * image.scale() * AppSettings::emoteScale(), h * image.scale() * AppSettings::emoteScale()); + } + } + } + else + { + if (m_recalculateText) + { + QFontMetrics& metrics = word.getFontMetrics(); + word.setSize(metrics.width(word.getText()), metrics.height()); + } + } + } + + m_recalculateImages = false; + m_recalculateText = false; + } + + if (redraw) + { + int x = MARGIN_LEFT; + int y = MARGIN_TOP; + + int right = width - MARGIN_RIGHT; + + std::list* parts; + + auto lineStart = m_wordParts->begin(); + int lineHeight = 0; + + for (auto it = m_words.begin(); it != m_words.end(); ++it) + { + Word& word = *it; + + int xOffset = 0, yOffset = 0; + + if (enableEmoteMargins) + { + if (word.isImage() && word.getImage().isHat()) + { + xOffset = -word.width() + 2; + } + else + { + xOffset = word.xOffset(); + yOffset = word.yOffset(); + } + + lineHeight = std::max(word.height(), lineHeight); + } + + if (x + word.width() + xOffset <= right) + { + parts->push_back(WordPart(word, x, y, QStringRef(&word.copyText()))); + x += word.width() + xOffset; + } +// else if (word.isText() && word.getText().length() > 2) +// { + +// } + else + { + parts->push_back(WordPart(word, x, y, QStringRef(&word.copyText()))); + + lineHeight = std::max(word.height(), lineHeight); + } + } + + auto tmp = m_wordParts; + m_wordParts = parts; + delete tmp; + + m_height = y + lineHeight; + } + + return redraw; +} + bool Message::sortTwitchEmotes(const std::pair& a, const std::pair& b) { return a.first < b.first; @@ -346,6 +466,6 @@ bool Message::sortTwitchEmotes(const std::pair& a, c QString Message::matchLink(const QString &string) { -#warning "xD" +#pragma message WARN("xD") return QString(); } diff --git a/message.h b/message.h index a0a18b117..3c50b6d3c 100644 --- a/message.h +++ b/message.h @@ -1,29 +1,27 @@ #ifndef MESSAGE_H #define MESSAGE_H -#include "IrcMessage" #include "word.h" -#include "chrono" +#include "wordpart.h" #include "channel.h" +#include +#include +#include + class Message { public: -// enum Badges : char { -// None = 0, -// Mod = 1, -// Turbo = 2, -// Sub = 4, -// Staff = 8, -// GlobalMod = 16, -// Admin = 32, -// Broadcaster = 64, -// }; - Message(const QString& text); Message(const IrcPrivateMessage& ircMessage, const Channel& Channel, bool enablePingSound = true, bool isReceivedWhisper = false, bool isSentWhisper = false, bool includeChannel = false); + ~Message() { + if (m_wordParts != NULL) { + delete m_wordParts; + } + } + bool canHighlightTab() const { return m_highlightTab; } @@ -44,10 +42,14 @@ public: return m_displayName; } - QList words() const { + const std::vector words() const { return m_words; } + const std::list wordParts() const { + return *m_wordParts; + } + bool disabled() const { return m_disabled; } @@ -56,6 +58,16 @@ public: return m_id; } + int height() const { + return m_height; + } + + bool layout(int width, bool enableEmoteMargins = true); + + void requestRelayout() { m_relayoutRequested = true; } + void requestTextRecalculation() { m_recalculateText = true; } + void requestImageRecalculation() { m_recalculateImages = true; } + private: static LazyLoadedImage* badgeStaff; static LazyLoadedImage* badgeAdmin; @@ -77,7 +89,15 @@ private: QString m_displayName = ""; QString m_id = ""; - QList m_words; + int m_height = 0; + + std::vector m_words; + std::list* m_wordParts; + + long m_currentLayoutWidth = -1; + bool m_relayoutRequested = true; + bool m_recalculateText = true; + bool m_recalculateImages = true; static QString matchLink(const QString& string); diff --git a/word.cpp b/word.cpp index 58df10657..5e405a0ed 100644 --- a/word.cpp +++ b/word.cpp @@ -11,13 +11,14 @@ Word::Word(LazyLoadedImage* image, Type type, const QString& copytext, const QSt , m_color() , m_link(link) { + image->width(); // professional segfault test } // Text word Word::Word(const QString& text, Type type, const QColor& color, const QString& copytext, const QString& tooltip, const Link& link) - : m_image(nullptr) + : m_image(NULL) , m_text(text) - , m_isImage(true) + , m_isImage(false) , m_type(type) , m_copyText(copytext) , m_tooltip(tooltip) diff --git a/word.h b/word.h index 920d8c6a2..3062506cc 100644 --- a/word.h +++ b/word.h @@ -27,6 +27,7 @@ public: BttvGifEmoteText = 0x200, FfzEmoteImage = 0x400, FfzEmoteText = 0x800, + EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage, Bits = 0x1000, BitsAnimated = 0x2000, @@ -75,30 +76,19 @@ public: return m_height; } - int x() const { - return m_x; - } - - int y() const { - return m_y; - } - - int right() const { - return m_x + m_width; - } - - int bottom() const { - return m_y + m_height; - } - - QRect rect() const { - return QRect(m_x, m_y, m_width, m_height); + void setSize(int width, int height) { + m_width = width; + m_height = height; } bool isImage() const { return m_isImage; } + bool isText() const { + return !m_isImage; + } + const QString& copyText() const { return m_copyText; } @@ -111,6 +101,10 @@ public: return Fonts::getFont(m_font); } + QFontMetrics& getFontMetrics() const { + return Fonts::getFontMetrics(m_font); + } + Type type() const { return m_type; } @@ -127,6 +121,19 @@ public: return m_link; } + int xOffset() const { + return m_xOffset; + } + + int yOffset() const { + return m_yOffset; + } + + void setOffset(int xOffset, int yOffset) { + m_xOffset = std::max(0, xOffset); + m_yOffset = std::max(0, yOffset); + } + private: LazyLoadedImage* m_image; QString m_text; @@ -136,10 +143,12 @@ private: Type m_type; QString m_copyText; QString m_tooltip; - int m_x; - int m_y; + int m_width; int m_height; + int m_xOffset; + int m_yOffset; + bool m_hasTrailingSpace; Fonts::Type m_font = Fonts::Medium; Link m_link; diff --git a/wordpart.cpp b/wordpart.cpp new file mode 100644 index 000000000..6d923778e --- /dev/null +++ b/wordpart.cpp @@ -0,0 +1,18 @@ +#include "wordpart.h" +#include "word.h" + +WordPart::WordPart(Word& word, int x, int y, const QStringRef& copyText, bool allowTrailingSpace) + : m_word(word) + , m_copyText(copyText) + , m_x(x) + , m_y(y) + , m_width(word.width()) + , m_height(word.height()) + , m_trailingSpace(word.hasTrailingSpace() & allowTrailingSpace) +{ +} + +const QString& WordPart::getText() const +{ + return m_word.getText(); +} diff --git a/wordpart.h b/wordpart.h new file mode 100644 index 000000000..810ba6450 --- /dev/null +++ b/wordpart.h @@ -0,0 +1,74 @@ +#ifndef WORDPART_H +#define WORDPART_H + +#include +#include + +class Word; + +class WordPart +{ +public: + WordPart(Word& word, int x, int y, const QStringRef& copyText, bool allowTrailingSpace = true); + + const Word& word() const { + return m_word; + } + + int width() const { + return m_width; + } + + int height() const { + return m_height; + } + + int x() const { + return m_x; + } + + int y() const { + return m_y; + } + + void setPosition(int x, int y) { + m_x = x; + m_y = y; + } + + int right() const { + return m_x + m_width; + } + + int bottom() const { + return m_y + m_height; + } + + QRect rect() const { + return QRect(m_x, m_y, m_width, m_height); + } + + const QStringRef copyText() const { + return m_copyText; + } + + int hasTrailingSpace() const { + return m_trailingSpace; + } + + const QString& getText() const; + +private: + Word& m_word; + + QStringRef m_copyText; + + int m_x; + int m_y; + int m_width; + int m_height; + + bool m_trailingSpace; +}; + +#endif // WORDPART_H