From 3446a623f51b0dc0d96209af9a47c82300d68f4b Mon Sep 17 00:00:00 2001 From: fourtf Date: Wed, 18 Apr 2018 09:12:29 +0200 Subject: [PATCH] added select channel dialog --- chatterino.pro | 6 +- src/channel.cpp | 17 +- src/channel.hpp | 17 +- src/messages/layouts/messagelayout.cpp | 16 +- src/messages/layouts/messagelayout.hpp | 3 +- .../layouts/messagelayoutcontainer.cpp | 11 + .../layouts/messagelayoutcontainer.hpp | 1 + src/providers/irc/abstractircserver.cpp | 2 +- src/providers/twitch/twitchchannel.cpp | 2 +- src/providers/twitch/twitchmessagebuilder.cpp | 12 +- src/providers/twitch/twitchserver.cpp | 5 +- src/providers/twitch/twitchserver.hpp | 1 + src/singletons/settingsmanager.cpp | 1 + src/singletons/thememanager.cpp | 2 + src/singletons/thememanager.hpp | 1 + src/util/layoutcreator.hpp | 14 + src/widgets/emotepopup.cpp | 16 +- src/widgets/helper/channelview.cpp | 30 +- src/widgets/helper/channelview.hpp | 2 + src/widgets/helper/notebooktab.cpp | 371 ++++++++++++++++++ src/widgets/helper/notebooktab.hpp | 66 ++++ src/widgets/helper/searchpopup.cpp | 2 +- src/widgets/notebook.cpp | 329 ++++++++++++++++ src/widgets/notebook.hpp | 55 +++ src/widgets/selectchanneldialog.cpp | 269 +++++++++++++ src/widgets/selectchanneldialog.hpp | 61 +++ src/widgets/split.cpp | 34 +- src/widgets/split.hpp | 3 +- src/widgets/splitcontainer.cpp | 15 +- 29 files changed, 1295 insertions(+), 69 deletions(-) create mode 100644 src/widgets/selectchanneldialog.cpp create mode 100644 src/widgets/selectchanneldialog.hpp diff --git a/chatterino.pro b/chatterino.pro index 91385e2bd..c2f93815e 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -178,7 +178,8 @@ SOURCES += \ src/singletons/pubsubmanager.cpp \ src/util/rapidjson-helpers.cpp \ src/singletons/helper/pubsubhelpers.cpp \ - src/singletons/helper/pubsubactions.cpp + src/singletons/helper/pubsubactions.cpp \ + src/widgets/selectchanneldialog.cpp HEADERS += \ src/precompiled_header.hpp \ @@ -299,7 +300,8 @@ HEADERS += \ src/singletons/pubsubmanager.hpp \ src/util/rapidjson-helpers.hpp \ src/singletons/helper/pubsubhelpers.hpp \ - src/singletons/helper/pubsubactions.hpp + src/singletons/helper/pubsubactions.hpp \ + src/widgets/selectchanneldialog.hpp RESOURCES += \ resources/resources.qrc diff --git a/src/channel.cpp b/src/channel.cpp index 1834979f0..18054d2a2 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -18,8 +18,9 @@ using namespace chatterino::messages; namespace chatterino { -Channel::Channel(const QString &_name) - : name(_name) +Channel::Channel(const QString &_name, Type _type) + : type(_type) + , name(_name) , completionModel(this->name) { this->clearCompletionModelTimer = new QTimer; @@ -37,6 +38,11 @@ Channel::~Channel() this->clearCompletionModelTimer->deleteLater(); } +Channel::Type Channel::getType() const +{ + return this->type; +} + bool Channel::isEmpty() const { return this->name.isEmpty(); @@ -99,9 +105,14 @@ void Channel::sendMessage(const QString &message) { } +bool Channel::isMod() const +{ + return false; +} + std::shared_ptr Channel::getEmpty() { - static std::shared_ptr channel(new Channel("")); + static std::shared_ptr channel(new Channel("", None)); return channel; } diff --git a/src/channel.hpp b/src/channel.hpp index e42b5b7c9..d349eb4c2 100644 --- a/src/channel.hpp +++ b/src/channel.hpp @@ -22,7 +22,15 @@ class Channel : public std::enable_shared_from_this QTimer *clearCompletionModelTimer; public: - explicit Channel(const QString &_name); + enum Type { + None, + Twitch, + TwitchWhispers, + TwitchWatching, + TwitchMentions, + }; + + explicit Channel(const QString &_name, Type type); virtual ~Channel(); pajlada::Signals::Signal sendMessageSignal; @@ -33,6 +41,7 @@ public: pajlada::Signals::Signal messageReplaced; pajlada::Signals::NoArgSignal destroyed; + Type getType() const; virtual bool isEmpty() const; messages::LimitedQueueSnapshot getMessageSnapshot(); @@ -46,10 +55,7 @@ public: virtual bool canSendMessage() const; virtual void sendMessage(const QString &message); - virtual bool isMod() const - { - return false; - } + virtual bool isMod() const; static std::shared_ptr getEmpty(); @@ -60,6 +66,7 @@ protected: private: messages::LimitedQueue messages; + Type type; }; using ChannelPtr = std::shared_ptr; diff --git a/src/messages/layouts/messagelayout.cpp b/src/messages/layouts/messagelayout.cpp index 658f6ad7c..db9efa6a4 100644 --- a/src/messages/layouts/messagelayout.cpp +++ b/src/messages/layouts/messagelayout.cpp @@ -22,9 +22,6 @@ MessageLayout::MessageLayout(MessagePtr _message) : message(_message) , buffer(nullptr) { - if (_message->flags & Message::Collapsed) { - this->flags &= MessageLayout::Collapsed; - } util::DebugCount::increase("message layout"); } @@ -90,11 +87,11 @@ bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags) // update word sizes if needed if (imagesChanged) { // this->container.updateImages(); - this->flags &= MessageLayout::RequiresBufferUpdate; + this->flags |= MessageLayout::RequiresBufferUpdate; } if (textChanged) { // this->container.updateText(); - this->flags &= MessageLayout::RequiresBufferUpdate; + this->flags |= MessageLayout::RequiresBufferUpdate; } if (widthChanged || wordMaskChanged) { this->deleteBuffer(); @@ -229,6 +226,15 @@ void MessageLayout::deleteBuffer() } } +void MessageLayout::deleteCache() +{ + this->deleteBuffer(); + +#ifdef XD + this->container.clear(); +#endif +} + // Elements // assert(QThread::currentThread() == QApplication::instance()->thread()); diff --git a/src/messages/layouts/messagelayout.hpp b/src/messages/layouts/messagelayout.hpp index 82bfaea50..1d1fcbd48 100644 --- a/src/messages/layouts/messagelayout.hpp +++ b/src/messages/layouts/messagelayout.hpp @@ -19,7 +19,7 @@ namespace layouts { class MessageLayout : boost::noncopyable { public: - enum Flags : uint8_t { Collapsed, RequiresBufferUpdate, RequiresLayout }; + enum Flags : uint8_t { RequiresBufferUpdate = 1 << 1, RequiresLayout = 1 << 2 }; MessageLayout(MessagePtr message); ~MessageLayout(); @@ -40,6 +40,7 @@ public: bool isLastReadMessage, bool isWindowFocused); void invalidateBuffer(); void deleteBuffer(); + void deleteCache(); // Elements const MessageLayoutElement *getElementAt(QPoint point); diff --git a/src/messages/layouts/messagelayoutcontainer.cpp b/src/messages/layouts/messagelayoutcontainer.cpp index 1acbe8cbb..c6e6f9db4 100644 --- a/src/messages/layouts/messagelayoutcontainer.cpp +++ b/src/messages/layouts/messagelayoutcontainer.cpp @@ -65,8 +65,18 @@ void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element this->_addElement(element); } +bool MessageLayoutContainer::canAddElements() +{ + return !(this->flags & Message::MessageFlags::Collapsed && line >= 3); +} + void MessageLayoutContainer::_addElement(MessageLayoutElement *element) { + if (!this->canAddElements()) { + delete element; + return; + } + // top margin if (this->elements.size() == 0) { this->currentY = this->margin.top * this->scale; @@ -139,6 +149,7 @@ void MessageLayoutContainer::breakLine() this->currentY += this->lineHeight; this->height = this->currentY + (this->margin.bottom * this->scale); this->lineHeight = 0; + this->line++; } bool MessageLayoutContainer::atStartOfLine() diff --git a/src/messages/layouts/messagelayoutcontainer.hpp b/src/messages/layouts/messagelayoutcontainer.hpp index 69cf16607..6400b789d 100644 --- a/src/messages/layouts/messagelayoutcontainer.hpp +++ b/src/messages/layouts/messagelayoutcontainer.hpp @@ -58,6 +58,7 @@ struct MessageLayoutContainer { void end(); void clear(); + bool canAddElements(); void addElement(MessageLayoutElement *element); void addElementNoLineBreak(MessageLayoutElement *element); void breakLine(); diff --git a/src/providers/irc/abstractircserver.cpp b/src/providers/irc/abstractircserver.cpp index c2150f5be..30372f4b7 100644 --- a/src/providers/irc/abstractircserver.cpp +++ b/src/providers/irc/abstractircserver.cpp @@ -197,7 +197,7 @@ void AbstractIrcServer::onDisconnected() std::lock_guard lock(this->channelMutex); MessagePtr msg = Message::createSystemMessage("disconnected from chat"); - msg->flags &= Message::DisconnectedMessage; + msg->flags |= Message::DisconnectedMessage; for (std::weak_ptr &weak : this->channels.values()) { std::shared_ptr chan = weak.lock(); diff --git a/src/providers/twitch/twitchchannel.cpp b/src/providers/twitch/twitchchannel.cpp index f0abcca53..8e0d94dec 100644 --- a/src/providers/twitch/twitchchannel.cpp +++ b/src/providers/twitch/twitchchannel.cpp @@ -20,7 +20,7 @@ namespace providers { namespace twitch { TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection *_readConnection) - : Channel(channelName) + : Channel(channelName, Channel::Twitch) , bttvChannelEmotes(new util::EmoteMap) , ffzChannelEmotes(new util::EmoteMap) , subscriptionURL("https://www.twitch.tv/subs/" + name) diff --git a/src/providers/twitch/twitchmessagebuilder.cpp b/src/providers/twitch/twitchmessagebuilder.cpp index 9089156cb..c05f1b582 100644 --- a/src/providers/twitch/twitchmessagebuilder.cpp +++ b/src/providers/twitch/twitchmessagebuilder.cpp @@ -68,9 +68,13 @@ MessagePtr TwitchMessageBuilder::build() // PARSING this->parseUsername(); - // this->message->setCollapsedDefault(true); - // this->appendWord(Word(Resources::getInstance().badgeCollapsed, Word::Collapsed, QString(), - // QString())); +#ifdef XD + if (this->originalMessage.length() > 100) { + this->message->flags |= Message::Collapsed; + this->emplace(singletons::ResourceManager::getInstance().badgeCollapsed, + MessageElement::Collapsed); + } +#endif // PARSING this->parseMessageID(); @@ -440,7 +444,7 @@ void TwitchMessageBuilder::parseHighlights() } if (doHighlight) { - this->message->flags &= Message::Highlighted; + this->message->flags |= Message::Highlighted; } } } diff --git a/src/providers/twitch/twitchserver.cpp b/src/providers/twitch/twitchserver.cpp index aec19a32e..581a7bbb3 100644 --- a/src/providers/twitch/twitchserver.cpp +++ b/src/providers/twitch/twitchserver.cpp @@ -17,8 +17,9 @@ namespace providers { namespace twitch { TwitchServer::TwitchServer() - : whispersChannel(new Channel("/whispers")) - , mentionsChannel(new Channel("/mentions")) + : whispersChannel(new Channel("/whispers", Channel::TwitchWhispers)) + , mentionsChannel(new Channel("/mentions", Channel::TwitchMentions)) + , watchingChannel(new Channel("/watching", Channel::TwitchWatching)) { AccountManager::getInstance().Twitch.userChanged.connect([this]() { // util::postToThread([this] { this->connect(); }); diff --git a/src/providers/twitch/twitchserver.hpp b/src/providers/twitch/twitchserver.hpp index a3bb8fe0e..052c56ea0 100644 --- a/src/providers/twitch/twitchserver.hpp +++ b/src/providers/twitch/twitchserver.hpp @@ -22,6 +22,7 @@ public: const ChannelPtr whispersChannel; const ChannelPtr mentionsChannel; + const ChannelPtr watchingChannel; protected: void initializeConnection(Communi::IrcConnection *connection, bool isRead, diff --git a/src/singletons/settingsmanager.cpp b/src/singletons/settingsmanager.cpp index 647b84d81..5f2eed2d3 100644 --- a/src/singletons/settingsmanager.cpp +++ b/src/singletons/settingsmanager.cpp @@ -79,6 +79,7 @@ void SettingManager::updateWordTypeMask() newMaskUint |= MessageElement::Username; newMaskUint |= MessageElement::AlwaysShow; + newMaskUint |= MessageElement::Collapsed; MessageElement::Flags newMask = static_cast(newMaskUint); diff --git a/src/singletons/thememanager.cpp b/src/singletons/thememanager.cpp index c15575ed0..e7d3a6be2 100644 --- a/src/singletons/thememanager.cpp +++ b/src/singletons/thememanager.cpp @@ -113,6 +113,8 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier) this->tabs.selected = {QColor("#000"), {QColor("#999"), QColor("#999"), QColor("#888")}}; } + + this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color(); } // Split diff --git a/src/singletons/thememanager.hpp b/src/singletons/thememanager.hpp index 664f97135..6a384e0aa 100644 --- a/src/singletons/thememanager.hpp +++ b/src/singletons/thememanager.hpp @@ -47,6 +47,7 @@ public: TabColors highlighted; TabColors newMessage; QColor border; + QColor bottomLine; } tabs; /// SPLITS diff --git a/src/util/layoutcreator.hpp b/src/util/layoutcreator.hpp index 48f5c4354..1573087bd 100644 --- a/src/util/layoutcreator.hpp +++ b/src/util/layoutcreator.hpp @@ -24,6 +24,11 @@ public: return this->item; } + T *operator*() + { + return this->item; + } + T *getElement() { return this->item; @@ -75,6 +80,15 @@ public: return *this; } + template ::value, int>::type = 0> + LayoutCreator hidden() + { + this->item->setVisible(false); + + return *this; + } + template ::value, int>::type = 0> LayoutCreator appendTab(T2 *item, const QString &title) diff --git a/src/widgets/emotepopup.cpp b/src/widgets/emotepopup.cpp index 411e289eb..7b6fe215b 100644 --- a/src/widgets/emotepopup.cpp +++ b/src/widgets/emotepopup.cpp @@ -51,7 +51,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel) return; } - ChannelPtr emoteChannel(new Channel("")); + ChannelPtr emoteChannel(new Channel("", Channel::None)); auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) { // TITLE @@ -59,13 +59,13 @@ void EmotePopup::loadChannel(ChannelPtr _channel) builder1.append(new TextElement(title, MessageElement::Text)); - builder1.getMessage()->flags &= Message::Centered; + builder1.getMessage()->flags |= Message::Centered; emoteChannel->addMessage(builder1.getMessage()); // EMOTES messages::MessageBuilder builder2; - builder2.getMessage()->flags &= Message::Centered; - builder2.getMessage()->flags &= Message::DisableCompactEmotes; + builder2.getMessage()->flags |= Message::Centered; + builder2.getMessage()->flags |= Message::DisableCompactEmotes; map.each([&](const QString &key, const util::EmoteData &value) { builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) @@ -96,19 +96,19 @@ void EmotePopup::loadEmojis() { auto &emojis = singletons::EmoteManager::getInstance().getEmojis(); - ChannelPtr emojiChannel(new Channel("")); + ChannelPtr emojiChannel(new Channel("", Channel::None)); // title messages::MessageBuilder builder1; builder1.append(new TextElement("emojis", MessageElement::Text)); - builder1.getMessage()->flags &= Message::Centered; + builder1.getMessage()->flags |= Message::Centered; emojiChannel->addMessage(builder1.getMessage()); // emojis messages::MessageBuilder builder; - builder.getMessage()->flags &= Message::Centered; - builder.getMessage()->flags &= Message::DisableCompactEmotes; + builder.getMessage()->flags |= Message::Centered; + builder.getMessage()->flags |= Message::DisableCompactEmotes; emojis.each([&builder](const QString &key, const auto &value) { builder.append((new EmoteElement(value.emoteData, MessageElement::Flags::AlwaysShow)) diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index ab5294a2d..8f80e7a00 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -446,10 +446,14 @@ void ChannelView::setChannel(ChannelPtr newChannel) void ChannelView::detachChannel() { // on message added - this->messageAppendedConnection.disconnect(); + if (this->messageAppendedConnection.isConnected()) { + this->messageAppendedConnection.disconnect(); + } // on message removed - this->messageRemovedConnection.disconnect(); + if (this->messageRemovedConnection.isConnected()) { + this->messageRemovedConnection.disconnect(); + } } void ChannelView::pause(int msecTimeout) @@ -704,7 +708,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } // message under cursor is collapsed - if (layout->flags & MessageLayout::Collapsed) { + if (layout->getMessage()->flags & Message::Collapsed) { this->setCursor(Qt::PointingHandCursor); tooltipWidget->hide(); return; @@ -780,7 +784,7 @@ void ChannelView::mousePressEvent(QMouseEvent *event) } // check if message is collapsed - if (layout->flags & MessageLayout::Collapsed) { + if (layout->getMessage()->flags & Message::Collapsed) { return; } @@ -838,9 +842,10 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) } // message under cursor is collapsed - if (layout->flags & MessageLayout::Collapsed) { - layout->flags &= MessageLayout::Collapsed; - // this->layoutMessages(); + if (layout->getMessage()->flags & Message::MessageFlags::Collapsed) { + layout->getMessage()->flags &= ~Message::MessageFlags::Collapsed; + + this->layoutMessages(); return; } @@ -871,7 +876,7 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) } // message under cursor is collapsed - if (layout->flags & MessageLayout::Collapsed) { + if (layout->getMessage()->flags & Message::Collapsed) { return; } @@ -887,6 +892,15 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) } } +void ChannelView::hideEvent(QHideEvent *) +{ + for (auto &layout : this->messagesOnScreen) { + layout->deleteBuffer(); + } + + this->messagesOnScreen.clear(); +} + void ChannelView::handleLinkClick(QMouseEvent *event, const messages::Link &link, messages::MessageLayout *layout) { diff --git a/src/widgets/helper/channelview.hpp b/src/widgets/helper/channelview.hpp index b231c406c..a86a64659 100644 --- a/src/widgets/helper/channelview.hpp +++ b/src/widgets/helper/channelview.hpp @@ -70,6 +70,8 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; + void hideEvent(QHideEvent *) override; + void handleLinkClick(QMouseEvent *event, const messages::Link &link, messages::MessageLayout *layout); diff --git a/src/widgets/helper/notebooktab.cpp b/src/widgets/helper/notebooktab.cpp index b5a3ed992..1674dfa82 100644 --- a/src/widgets/helper/notebooktab.cpp +++ b/src/widgets/helper/notebooktab.cpp @@ -17,6 +17,377 @@ namespace chatterino { namespace widgets { +NotebookTab2::NotebookTab2(Notebook2 *_notebook) + : BaseWidget(_notebook) + , positionChangedAnimation(this, "pos") + , notebook(_notebook) + , menu(this) +{ + this->setAcceptDrops(true); + + this->positionChangedAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic)); + + singletons::SettingManager::getInstance().hideTabX.connect( + boost::bind(&NotebookTab2::hideTabXChanged, this, _1), this->managedConnections); + + this->setMouseTracking(true); + + this->menu.addAction("Rename", [this]() { + TextInputDialog d(this); + + d.setWindowTitle("Change tab title (Leave empty for default behaviour)"); + if (this->useDefaultTitle) { + d.setText(""); + } else { + d.setText(this->getTitle()); + d.highlightText(); + } + + if (d.exec() == QDialog::Accepted) { + QString newTitle = d.getText(); + if (newTitle.isEmpty()) { + this->useDefaultTitle = true; + + // fourtf: xD + // this->page->refreshTitle(); + } else { + this->useDefaultTitle = false; + this->setTitle(newTitle); + } + } + }); + + QAction *enableHighlightsOnNewMessageAction = + new QAction("Enable highlights on new message", &this->menu); + enableHighlightsOnNewMessageAction->setCheckable(true); + + this->menu.addAction("Close", [=]() { this->notebook->removePage(this->page); }); + + this->menu.addAction(enableHighlightsOnNewMessageAction); + + QObject::connect(enableHighlightsOnNewMessageAction, &QAction::toggled, [this](bool newValue) { + debug::Log("New value is {}", newValue); // + }); +} + +void NotebookTab2::themeRefreshEvent() +{ + this->update(); +} + +void NotebookTab2::updateSize() +{ + float scale = getScale(); + + int width; + + if (singletons::SettingManager::getInstance().hideTabX) { + width = (int)((fontMetrics().width(this->title) + 16 /*+ 16*/) * scale); + } else { + width = (int)((fontMetrics().width(this->title) + 8 + 24 /*+ 16*/) * scale); + } + + this->resize(std::min((int)(150 * scale), width), (int)(24 * scale)); + + // if (this->parent() != nullptr) { + // (static_cast(this->parent()))->performLayout(true); + // } +} + +const QString &NotebookTab2::getTitle() const +{ + return this->title; +} + +void NotebookTab2::setTitle(const QString &newTitle) +{ + if (this->title != newTitle) { + this->title = newTitle; + this->updateSize(); + this->update(); + } +} + +bool NotebookTab2::isSelected() const +{ + return this->selected; +} + +void NotebookTab2::setSelected(bool value) +{ + this->selected = value; + + this->highlightState = HighlightState::None; + + this->update(); +} + +void NotebookTab2::setHighlightState(HighlightState newHighlightStyle) +{ + if (this->isSelected()) { + return; + } + + if (this->highlightState != HighlightState::Highlighted) { + this->highlightState = newHighlightStyle; + + this->update(); + } +} + +QRect NotebookTab2::getDesiredRect() const +{ + return QRect(positionAnimationDesiredPoint, size()); +} + +void NotebookTab2::hideTabXChanged(bool) +{ + this->updateSize(); + this->update(); +} + +void NotebookTab2::moveAnimated(QPoint pos, bool animated) +{ + this->positionAnimationDesiredPoint = pos; + + QWidget *w = this->window(); + + if ((w != nullptr && !w->isVisible()) || !animated || !positionChangedAnimationRunning) { + this->move(pos); + + this->positionChangedAnimationRunning = true; + return; + } + + if (this->positionChangedAnimation.endValue() == pos) { + return; + } + + this->positionChangedAnimation.stop(); + this->positionChangedAnimation.setDuration(75); + this->positionChangedAnimation.setStartValue(this->pos()); + this->positionChangedAnimation.setEndValue(pos); + this->positionChangedAnimation.start(); +} + +void NotebookTab2::paintEvent(QPaintEvent *) +{ + singletons::SettingManager &settingManager = singletons::SettingManager::getInstance(); + QPainter painter(this); + float scale = this->getScale(); + + int height = (int)(scale * 24); + // int fullHeight = (int)(scale * 48); + + // select the right tab colors + singletons::ThemeManager::TabColors colors; + singletons::ThemeManager::TabColors regular = this->themeManager.tabs.regular; + + if (this->selected) { + colors = this->themeManager.tabs.selected; + } else if (this->highlightState == HighlightState::Highlighted) { + colors = this->themeManager.tabs.highlighted; + } else if (this->highlightState == HighlightState::NewMessage) { + colors = this->themeManager.tabs.newMessage; + } else { + colors = this->themeManager.tabs.regular; + } + + bool windowFocused = this->window() == QApplication::activeWindow(); + // || SettingsDialog::getHandle() == QApplication::activeWindow(); + + QBrush tabBackground = this->mouseOver ? colors.backgrounds.hover + : (windowFocused ? colors.backgrounds.regular + : colors.backgrounds.unfocused); + + if (true) { + painter.fillRect(rect(), this->mouseOver ? regular.backgrounds.hover + : (windowFocused ? regular.backgrounds.regular + : regular.backgrounds.unfocused)); + + // fill the tab background + painter.fillRect(rect(), tabBackground); + + // draw border + // painter.setPen(QPen("#ccc")); + // QPainterPath path(QPointF(0, height)); + // path.lineTo(0, 0); + // path.lineTo(this->width() - 1, 0); + // path.lineTo(this->width() - 1, this->height() - 1); + // path.lineTo(0, this->height() - 1); + // painter.drawPath(path); + } else { + // QPainterPath path(QPointF(0, height)); + // path.lineTo(8 * scale, 0); + // path.lineTo(this->width() - 8 * scale, 0); + // path.lineTo(this->width(), height); + // painter.fillPath(path, this->mouseOver ? regular.backgrounds.hover + // : (windowFocused ? + // regular.backgrounds.regular + // : + // regular.backgrounds.unfocused)); + + // // fill the tab background + // painter.fillPath(path, tabBackground); + // painter.setPen(QColor("#FFF")); + // painter.setRenderHint(QPainter::Antialiasing); + // painter.drawPath(path); + // // painter.setBrush(QColor("#000")); + + // QLinearGradient gradient(0, height, 0, fullHeight); + // gradient.setColorAt(0, tabBackground.color()); + // gradient.setColorAt(1, "#fff"); + + // QBrush brush(gradient); + // painter.fillRect(0, height, this->width(), fullHeight - height, + // brush); + } + + // set the pen color + painter.setPen(colors.text); + + // set area for text + int rectW = (settingManager.hideTabX ? 0 : static_cast(16) * scale); + QRect rect(0, 0, this->width() - rectW, height); + + // draw text + if (true) { // legacy + // painter.drawText(rect, this->getTitle(), QTextOption(Qt::AlignCenter)); + int offset = (int)(scale * 8); + QRect textRect(offset, 0, this->width() - offset - offset, height); + + QTextOption option(Qt::AlignLeft | Qt::AlignVCenter); + option.setWrapMode(QTextOption::NoWrap); + painter.drawText(textRect, this->getTitle(), option); + } else { + // QTextOption option(Qt::AlignLeft | Qt::AlignVCenter); + // option.setWrapMode(QTextOption::NoWrap); + // int offset = (int)(scale * 16); + // QRect textRect(offset, 0, this->width() - offset - offset, height); + // painter.drawText(textRect, this->getTitle(), option); + } + + // draw close x + if (!settingManager.hideTabX && (mouseOver || selected)) { + QRect xRect = this->getXRect(); + if (!xRect.isNull()) { + if (mouseOverX) { + painter.fillRect(xRect, QColor(0, 0, 0, 64)); + + if (mouseDownX) { + painter.fillRect(xRect, QColor(0, 0, 0, 64)); + } + } + + int a = static_cast(scale * 4); + + painter.drawLine(xRect.topLeft() + QPoint(a, a), xRect.bottomRight() + QPoint(-a, -a)); + painter.drawLine(xRect.topRight() + QPoint(-a, a), xRect.bottomLeft() + QPoint(a, -a)); + } + } +} + +void NotebookTab2::mousePressEvent(QMouseEvent *event) +{ + this->mouseDown = true; + this->mouseDownX = this->getXRect().contains(event->pos()); + + this->update(); + + this->notebook->select(page); + + if (this->notebook->getAllowUserTabManagement()) { + switch (event->button()) { + case Qt::RightButton: { + this->menu.popup(event->globalPos()); + } break; + } + } +} + +void NotebookTab2::mouseReleaseEvent(QMouseEvent *event) +{ + this->mouseDown = false; + + if (event->button() == Qt::MiddleButton) { + if (this->rect().contains(event->pos())) { + this->notebook->removePage(this->page); + } + } else { + if (!singletons::SettingManager::getInstance().hideTabX && this->mouseDownX && + this->getXRect().contains(event->pos())) { + this->mouseDownX = false; + + this->notebook->removePage(this->page); + } else { + this->update(); + } + } +} + +void NotebookTab2::enterEvent(QEvent *) +{ + this->mouseOver = true; + + this->update(); +} + +void NotebookTab2::leaveEvent(QEvent *) +{ + this->mouseOverX = false; + this->mouseOver = false; + + this->update(); +} + +void NotebookTab2::dragEnterEvent(QDragEnterEvent *) +{ + this->notebook->select(this->page); +} + +void NotebookTab2::mouseMoveEvent(QMouseEvent *event) +{ + if (!singletons::SettingManager::getInstance().hideTabX && + this->notebook->getAllowUserTabManagement()) // + { + bool overX = this->getXRect().contains(event->pos()); + + if (overX != this->mouseOverX) { + // Over X state has been changed (we either left or entered it; + this->mouseOverX = overX; + + this->update(); + } + } + + QPoint relPoint = this->mapToParent(event->pos()); + + if (this->mouseDown && !this->getDesiredRect().contains(relPoint) && + this->notebook->getAllowUserTabManagement()) // + { + int index; + QWidget *clickedPage = notebook->tabAt(relPoint, index, this->width()); + + assert(clickedPage); + + if (clickedPage != nullptr && clickedPage != this->page) { + this->notebook->rearrangePage(this->page, index); + } + } +} + +QRect NotebookTab2::getXRect() +{ + if (this->notebook->getAllowUserTabManagement()) { + return QRect(); + } + + float s = this->getScale(); + return QRect(this->width() - static_cast(20 * s), static_cast(4 * s), + static_cast(16 * s), static_cast(16 * s)); +} + +// 2 NotebookTab::NotebookTab(Notebook *_notebook) : BaseWidget(_notebook) , positionChangedAnimation(this, "pos") diff --git a/src/widgets/helper/notebooktab.hpp b/src/widgets/helper/notebooktab.hpp index 2f871dd22..628b835b4 100644 --- a/src/widgets/helper/notebooktab.hpp +++ b/src/widgets/helper/notebooktab.hpp @@ -12,8 +12,74 @@ namespace chatterino { namespace widgets { class Notebook; +class Notebook2; class SplitContainer; +class NotebookTab2 : public BaseWidget +{ + Q_OBJECT + +public: + explicit NotebookTab2(Notebook2 *_notebook); + + void updateSize(); + + QWidget *page; + + const QString &getTitle() const; + void setTitle(const QString &newTitle); + bool isSelected() const; + void setSelected(bool value); + + void setHighlightState(HighlightState style); + + void moveAnimated(QPoint pos, bool animated = true); + + QRect getDesiredRect() const; + void hideTabXChanged(bool); + +protected: + virtual void themeRefreshEvent() override; + + virtual void paintEvent(QPaintEvent *) override; + + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void enterEvent(QEvent *) override; + virtual void leaveEvent(QEvent *) override; + + virtual void dragEnterEvent(QDragEnterEvent *event) override; + + virtual void mouseMoveEvent(QMouseEvent *event) override; + +private: + std::vector managedConnections; + + QPropertyAnimation positionChangedAnimation; + bool positionChangedAnimationRunning = false; + QPoint positionAnimationDesiredPoint; + + Notebook2 *notebook; + + QString title; + +public: + bool useDefaultTitle = true; + +private: + bool selected = false; + bool mouseOver = false; + bool mouseDown = false; + bool mouseOverX = false; + bool mouseDownX = false; + + HighlightState highlightState = HighlightState::None; + + QMenu menu; + + QRect getXRect(); +}; + class NotebookTab : public BaseWidget { Q_OBJECT diff --git a/src/widgets/helper/searchpopup.cpp b/src/widgets/helper/searchpopup.cpp index 8de997357..136647aea 100644 --- a/src/widgets/helper/searchpopup.cpp +++ b/src/widgets/helper/searchpopup.cpp @@ -70,7 +70,7 @@ void SearchPopup::performSearch() { QString text = searchInput->text(); - ChannelPtr channel(new Channel("search")); + ChannelPtr channel(new Channel("search", Channel::None)); for (size_t i = 0; i < this->snapshot.getLength(); i++) { messages::MessagePtr message = this->snapshot[i]; diff --git a/src/widgets/notebook.cpp b/src/widgets/notebook.cpp index 086eb4a9d..7a548ce50 100644 --- a/src/widgets/notebook.cpp +++ b/src/widgets/notebook.cpp @@ -23,6 +23,335 @@ namespace chatterino { namespace widgets { +Notebook2::Notebook2(QWidget *parent) + : BaseWidget(singletons::ThemeManager::getInstance(), parent) + , addButton(this) +{ + this->addButton.setHidden(true); + + auto *shortcut_next = new QShortcut(QKeySequence("Ctrl+Tab"), this); + QObject::connect(shortcut_next, &QShortcut::activated, [this] { this->selectNextTab(); }); + + auto *shortcut_prev = new QShortcut(QKeySequence("Ctrl+Shift+Tab"), this); + QObject::connect(shortcut_prev, &QShortcut::activated, [this] { this->selectPreviousTab(); }); +} + +NotebookTab2 *Notebook2::addPage(QWidget *page, bool select) +{ + auto *tab = new NotebookTab2(this); + tab->page = page; + + Item item; + item.page = page; + item.tab = tab; + + this->items.append(item); + + page->hide(); + page->setParent(this); + + if (select || this->items.count() == 1) { + this->select(page); + } + + this->performLayout(); + + return tab; +} + +void Notebook2::removePage(QWidget *page) +{ + for (int i = 0; i < this->items.count(); i++) { + if (this->items[i].page == page) { + if (this->items.count() == 1) { + this->select(nullptr); + } else if (i == this->items.count() - 1) { + this->select(this->items[i - 1].page); + } else { + this->select(this->items[i + 1].page); + } + + this->items[i].page->deleteLater(); + this->items[i].tab->deleteLater(); + + // if (this->items.empty()) { + // this->addNewPage(); + // } + + this->items.removeAt(i); + break; + } + } +} + +void Notebook2::removeCurrentPage() +{ + if (this->selectedPage != nullptr) { + this->removePage(this->selectedPage); + } +} + +int Notebook2::indexOf(QWidget *page) const +{ + for (int i = 0; i < this->items.count(); i++) { + if (this->items[i].page == page) { + return i; + } + } + + return -1; +} + +void Notebook2::select(QWidget *page) +{ + if (page == this->selectedPage) { + return; + } + + if (page != nullptr) { + page->setHidden(false); + + NotebookTab2 *tab = this->getTabFromPage(page); + tab->setSelected(true); + tab->raise(); + } + + if (this->selectedPage != nullptr) { + this->selectedPage->setHidden(true); + + NotebookTab2 *tab = this->getTabFromPage(selectedPage); + tab->setSelected(false); + + // for (auto split : this->selectedPage->getSplits()) { + // split->updateLastReadMessage(); + // } + } + + this->selectedPage = page; + + this->performLayout(); +} + +void Notebook2::selectIndex(int index) +{ + if (index < 0 || this->items.count() <= index) { + return; + } + + this->select(this->items[index].page); +} + +void Notebook2::selectNextTab() +{ + if (this->items.size() <= 1) { + return; + } + + int index = (this->indexOf(this->selectedPage) + 1) % this->items.count(); + + this->select(this->items[index].page); +} + +void Notebook2::selectPreviousTab() +{ + if (this->items.size() <= 1) { + return; + } + + int index = this->indexOf(this->selectedPage) - 1; + + if (index < 0) { + index += this->items.count(); + } + + this->select(this->items[index].page); +} + +int Notebook2::getPageCount() const +{ + return this->items.count(); +} + +int Notebook2::getSelectedIndex() const +{ + return this->indexOf(this->selectedPage); +} + +QWidget *Notebook2::getSelectedPage() const +{ + return this->selectedPage; +} + +QWidget *Notebook2::tabAt(QPoint point, int &index, int maxWidth) +{ + int i = 0; + + for (auto &item : this->items) { + QRect rect = item.tab->getDesiredRect(); + rect.setHeight((int)(this->getScale() * 24)); + + rect.setWidth(std::min(maxWidth, rect.width())); + + if (rect.contains(point)) { + index = i; + return item.page; + } + + i++; + } + + index = -1; + return nullptr; +} + +void Notebook2::rearrangePage(QWidget *page, int index) +{ + this->items.move(this->indexOf(page), index); + + this->performLayout(); +} + +bool Notebook2::getAllowUserTabManagement() const +{ + return this->allowUserTabManagement; +} + +void Notebook2::setAllowUserTabManagement(bool value) +{ + this->allowUserTabManagement = value; +} + +bool Notebook2::getShowAddButton() const +{ + return this->showAddButton; +} + +void Notebook2::setShowAddButton(bool value) +{ + this->showAddButton = value; + + this->addButton.setHidden(!value); +} + +void Notebook2::scaleChangedEvent(float scale) +{ + // float h = 24 * this->getScale(); + + // this->settingsButton.setFixedSize(h, h); + // this->userButton.setFixedSize(h, h); + // this->addButton.setFixedSize(h, h); + + for (auto &i : this->items) { + i.tab->updateSize(); + } +} + +void Notebook2::resizeEvent(QResizeEvent *) +{ + this->performLayout(); +} + +void Notebook2::performLayout(bool animated) +{ + singletons::SettingManager &settings = singletons::SettingManager::getInstance(); + + int xStart = (int)(2 * this->getScale()); + + int x = xStart, y = 0; + float scale = this->getScale(); + + // bool customFrame = this->parentWindow->hasCustomWindowFrame(); + + // bool customFrame = false; + + // if (!this->showButtons || settings.hidePreferencesButton || customFrame) { + // this->settingsButton.hide(); + // } else { + // this->settingsButton.show(); + // x += settingsButton.width(); + // } + // if (!this->showButtons || settings.hideUserButton || customFrame) { + // this->userButton.hide(); + // } else { + // this->userButton.move(x, 0); + // this->userButton.show(); + // x += userButton.width(); + // } + + // if (customFrame || !this->showButtons || + // (settings.hideUserButton && settings.hidePreferencesButton)) { + // x += (int)(scale * 2); + // } + + int tabHeight = static_cast(24 * scale); + bool first = true; + + for (auto i = this->items.begin(); i != this->items.end(); i++) { + if (!first && + (i == this->items.end() && this->showAddButton ? tabHeight : 0) + x + i->tab->width() > + width()) // + { + y += i->tab->height(); + // y += 20; + i->tab->moveAnimated(QPoint(xStart, y), animated); + x = i->tab->width() + xStart; + } else { + i->tab->moveAnimated(QPoint(x, y), animated); + x += i->tab->width(); + } + + x += 1; + + first = false; + } + + if (this->showAddButton) { + this->addButton.move(x, y); + } + + if (this->lineY != y + tabHeight) { + this->lineY = y + tabHeight; + this->update(); + } + + y += (int)(1 * scale); + + for (auto &i : this->items) { + i.tab->raise(); + } + + if (this->showAddButton) { + this->addButton.raise(); + } + + if (this->selectedPage != nullptr) { + this->selectedPage->move(0, y + tabHeight); + this->selectedPage->resize(width(), height() - y - tabHeight); + this->selectedPage->raise(); + } +} + +void Notebook2::paintEvent(QPaintEvent *event) +{ + BaseWidget::paintEvent(event); + + QPainter painter(this); + painter.fillRect(0, this->lineY, this->width(), (int)(1 * this->getScale()), + this->themeManager.tabs.bottomLine); +} + +NotebookTab2 *Notebook2::getTabFromPage(QWidget *page) +{ + for (auto &it : this->items) { + if (it.page == page) { + return it.tab; + } + } + + return nullptr; +} + +// Notebook2::OLD NOTEBOOK Notebook::Notebook(Window *parent, bool _showButtons) : BaseWidget(parent) , parentWindow(parent) diff --git a/src/widgets/notebook.hpp b/src/widgets/notebook.hpp index f200cbcbb..220b2ed22 100644 --- a/src/widgets/notebook.hpp +++ b/src/widgets/notebook.hpp @@ -14,6 +14,61 @@ namespace widgets { class Window; +class Notebook2 : public BaseWidget +{ + Q_OBJECT + +public: + explicit Notebook2(QWidget *parent); + + NotebookTab2 *addPage(QWidget *page, bool select = false); + void removePage(QWidget *page); + void removeCurrentPage(); + + int indexOf(QWidget *page) const; + void select(QWidget *page); + void selectIndex(int index); + void selectNextTab(); + void selectPreviousTab(); + + int getPageCount() const; + int getSelectedIndex() const; + QWidget *getSelectedPage() const; + + QWidget *tabAt(QPoint point, int &index, int maxWidth = 2000000000); + void rearrangePage(QWidget *page, int index); + + bool getAllowUserTabManagement() const; + void setAllowUserTabManagement(bool value); + + bool getShowAddButton() const; + void setShowAddButton(bool value); + +protected: + virtual void scaleChangedEvent(float scale) override; + virtual void resizeEvent(QResizeEvent *) override; + virtual void paintEvent(QPaintEvent *) override; + +private: + struct Item { + NotebookTab2 *tab; + QWidget *page; + }; + + QList items; + QWidget *selectedPage = nullptr; + + NotebookButton addButton; + + bool allowUserTabManagement = false; + bool showAddButton = false; + int lineY = 20; + + void performLayout(bool animate = true); + + NotebookTab2 *getTabFromPage(QWidget *page); +}; + class Notebook : public BaseWidget { Q_OBJECT diff --git a/src/widgets/selectchanneldialog.cpp b/src/widgets/selectchanneldialog.cpp new file mode 100644 index 000000000..f118c3041 --- /dev/null +++ b/src/widgets/selectchanneldialog.cpp @@ -0,0 +1,269 @@ +#include "selectchanneldialog.hpp" + +#include "providers/twitch/twitchserver.hpp" +#include "util/layoutcreator.hpp" + +#include +#include +#include +#include +#include +#include + +#define TAB_TWITCH 0 + +namespace chatterino { +namespace widgets { + +SelectChannelDialog::SelectChannelDialog() + : BaseWindow((QWidget *)nullptr, true) + , selectedChannel(Channel::getEmpty()) +{ + this->tabFilter.dialog = this; + + util::LayoutCreator layoutWidget(this->getLayoutContainer()); + auto layout = layoutWidget.setLayoutType().withoutMargin(); + auto notebook = layout.emplace(this).assign(&this->ui.notebook); + + // twitch + { + util::LayoutCreator obj(new QWidget()); + auto vbox = obj.setLayoutType(); + + // channel_btn + auto channel_btn = vbox.emplace("Channel").assign(&this->ui.twitch.channel); + auto channel_lbl = vbox.emplace("Join a twitch channel by it's name.").hidden(); + channel_lbl->setWordWrap(true); + auto channel_edit = vbox.emplace().hidden().assign(&this->ui.twitch.channelName); + + QObject::connect(*channel_btn, &QRadioButton::toggled, [=](bool enabled) mutable { + if (enabled) { + channel_edit->setFocus(); + channel_edit->setSelection(0, channel_edit->text().length()); + } + + channel_edit->setVisible(enabled); + channel_lbl->setVisible(enabled); + }); + + channel_btn->installEventFilter(&this->tabFilter); + channel_edit->installEventFilter(&this->tabFilter); + + // whispers_btn + auto whispers_btn = + vbox.emplace("Whispers").assign(&this->ui.twitch.whispers); + auto whispers_lbl = + vbox.emplace("Shows the whispers that you receive while chatterino is running.") + .hidden(); + + whispers_lbl->setWordWrap(true); + whispers_btn->installEventFilter(&this->tabFilter); + + QObject::connect(*whispers_btn, &QRadioButton::toggled, + [=](bool enabled) mutable { whispers_lbl->setVisible(enabled); }); + + // mentions_btn + auto mentions_btn = + vbox.emplace("Mentions").assign(&this->ui.twitch.mentions); + auto mentions_lbl = + vbox.emplace("Shows all the messages that highlight you from any channel.") + .hidden(); + + mentions_lbl->setWordWrap(true); + mentions_btn->installEventFilter(&this->tabFilter); + + QObject::connect(*mentions_btn, &QRadioButton::toggled, + [=](bool enabled) mutable { mentions_lbl->setVisible(enabled); }); + + // watching_btn + auto watching_btn = + vbox.emplace("Watching").assign(&this->ui.twitch.watching); + auto watching_lbl = + vbox.emplace("Requires the chatterino browser extension.").hidden(); + + watching_lbl->setWordWrap(true); + watching_btn->installEventFilter(&this->tabFilter); + + QObject::connect(*watching_btn, &QRadioButton::toggled, + [=](bool enabled) mutable { watching_lbl->setVisible(enabled); }); + + vbox->addStretch(1); + + // tabbing order + QWidget::setTabOrder(*channel_btn, *whispers_btn); + QWidget::setTabOrder(*whispers_btn, *mentions_btn); + QWidget::setTabOrder(*mentions_btn, *watching_btn); + QWidget::setTabOrder(*watching_btn, *channel_btn); + + // tab + NotebookTab2 *tab = notebook->addPage(obj.getElement()); + tab->setTitle("Twitch"); + } + + // irc + /*{ + util::LayoutCreator obj(new QWidget()); + auto vbox = obj.setLayoutType(); + + auto edit = vbox.emplace("not implemented"); + + NotebookTab2 *tab = notebook->addPage(obj.getElement()); + tab->setTitle("Irc"); + }*/ + + layout->setStretchFactor(*notebook, 1); + + auto buttons = layout.emplace().emplace(this); + { + auto *button_ok = buttons->addButton(QDialogButtonBox::Ok); + QObject::connect(button_ok, &QPushButton::clicked, [=](bool) { this->ok(); }); + auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel); + QObject::connect(button_cancel, &QAbstractButton::clicked, [=](bool) { this->close(); }); + } + + this->setScaleIndependantSize(300, 210); + + this->setStyleSheet("QRadioButton { color: #fff } QLabel { color: #fff }"); + + // Shortcuts + auto *shortcut_ok = new QShortcut(QKeySequence("Return"), this); + QObject::connect(shortcut_ok, &QShortcut::activated, [=] { this->ok(); }); + auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this); + QObject::connect(shortcut_cancel, &QShortcut::activated, [=] { this->close(); }); +} + +void SelectChannelDialog::ok() +{ + this->_hasSelectedChannel = true; + this->close(); +} + +void SelectChannelDialog::setSelectedChannel(ChannelPtr channel) +{ + assert(channel); + + this->selectedChannel = channel; + + switch (channel->getType()) { + case Channel::Twitch: { + this->ui.notebook->selectIndex(TAB_TWITCH); + this->ui.twitch.channel->setFocus(); + this->ui.twitch.channelName->setText(channel->name); + } break; + case Channel::TwitchWatching: { + this->ui.notebook->selectIndex(TAB_TWITCH); + this->ui.twitch.watching->setFocus(); + } break; + case Channel::TwitchMentions: { + this->ui.notebook->selectIndex(TAB_TWITCH); + this->ui.twitch.mentions->setFocus(); + } break; + case Channel::TwitchWhispers: { + this->ui.notebook->selectIndex(TAB_TWITCH); + this->ui.twitch.whispers->setFocus(); + } break; + default: { + this->ui.notebook->selectIndex(TAB_TWITCH); + this->ui.twitch.channel->setFocus(); + } + } + + this->_hasSelectedChannel = false; +} + +ChannelPtr SelectChannelDialog::getSelectedChannel() const +{ + if (!this->_hasSelectedChannel) { + return this->selectedChannel; + } + + switch (this->ui.notebook->getSelectedIndex()) { + case TAB_TWITCH: { + if (this->ui.twitch.channel->isChecked()) { + return providers::twitch::TwitchServer::getInstance().addChannel( + this->ui.twitch.channelName->text()); + } else if (this->ui.twitch.watching->isChecked()) { + return providers::twitch::TwitchServer::getInstance().watchingChannel; + } else if (this->ui.twitch.mentions->isChecked()) { + return providers::twitch::TwitchServer::getInstance().mentionsChannel; + } else if (this->ui.twitch.whispers->isChecked()) { + return providers::twitch::TwitchServer::getInstance().whispersChannel; + } + } + } + + return this->selectedChannel; +} + +bool SelectChannelDialog::hasSeletedChannel() const +{ + return this->_hasSelectedChannel; +} + +bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, QEvent *event) +{ + auto *widget = (QWidget *)watched; + + if (event->type() == QEvent::FocusIn) { + widget->grabKeyboard(); + + auto *radio = dynamic_cast(watched); + if (radio) { + radio->setChecked(true); + } + + return true; + } else if (event->type() == QEvent::FocusOut) { + widget->releaseKeyboard(); + return false; + } else if (event->type() == QEvent::KeyPress) { + QKeyEvent *event_key = static_cast(event); + if ((event_key->key() == Qt::Key_Tab || event_key->key() == Qt::Key_Down) && + event_key->modifiers() == Qt::NoModifier) { + if (widget == this->dialog->ui.twitch.channelName) { + this->dialog->ui.twitch.whispers->setFocus(); + return true; + } else { + widget->nextInFocusChain()->setFocus(); + } + return true; + } else if (((event_key->key() == Qt::Key_Tab || event_key->key() == Qt::Key_Backtab) && + event_key->modifiers() == Qt::ShiftModifier) || + (event_key->key() == Qt::Key_Up) && event_key->modifiers() == Qt::NoModifier) { + if (widget == this->dialog->ui.twitch.channelName) { + this->dialog->ui.twitch.watching->setFocus(); + return true; + } else if (widget == this->dialog->ui.twitch.whispers) { + this->dialog->ui.twitch.channel->setFocus(); + return true; + } + + widget->previousInFocusChain()->setFocus(); + return true; + } else { + return false; + } + return true; + } else if (event->type() == QEvent::KeyRelease) { + QKeyEvent *event_key = static_cast(event); + if ((event_key->key() == Qt::Key_Backtab || event_key->key() == Qt::Key_Down) && + event_key->modifiers() == Qt::NoModifier) { + return true; + } + } + + return false; +} + +void SelectChannelDialog::SelectChannelDialog::showEvent(QShowEvent *) +{ + // QTimer::singleShot(100, [=] { this->setSelectedChannel(this->selectedChannel); }); +} + +void SelectChannelDialog::closeEvent(QCloseEvent *) +{ + this->closed.invoke(); +} + +} // namespace widgets +} // namespace chatterino diff --git a/src/widgets/selectchanneldialog.hpp b/src/widgets/selectchanneldialog.hpp new file mode 100644 index 000000000..3517b5810 --- /dev/null +++ b/src/widgets/selectchanneldialog.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "channel.hpp" +#include "widgets/basewindow.hpp" +#include "widgets/notebook.hpp" + +#include + +#include +#include + +namespace chatterino { +namespace widgets { + +class SelectChannelDialog : public BaseWindow +{ +public: + SelectChannelDialog(); + + void setSelectedChannel(ChannelPtr selectedChannel); + ChannelPtr getSelectedChannel() const; + bool hasSeletedChannel() const; + + pajlada::Signals::NoArgSignal closed; + +protected: + virtual void closeEvent(QCloseEvent *) override; + virtual void showEvent(QShowEvent *) override; + +private: + class EventFilter : public QObject + { + public: + SelectChannelDialog *dialog; + + protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + }; + + struct { + Notebook2 *notebook; + struct { + QRadioButton *channel; + QLineEdit *channelName; + QRadioButton *whispers; + QRadioButton *mentions; + QRadioButton *watching; + } twitch; + } ui; + + EventFilter tabFilter; + + ChannelPtr selectedChannel; + bool _hasSelectedChannel = false; + + void ok(); + friend class EventFilter; +}; + +} // namespace widgets +} // namespace chatterino diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp index 371221cee..db443fa6c 100644 --- a/src/widgets/split.cpp +++ b/src/widgets/split.cpp @@ -12,6 +12,7 @@ #include "widgets/helper/searchpopup.hpp" #include "widgets/helper/shortcut.hpp" #include "widgets/qualitypopup.hpp" +#include "widgets/selectchanneldialog.hpp" #include "widgets/splitcontainer.hpp" #include "widgets/textinputdialog.hpp" #include "widgets/window.hpp" @@ -180,28 +181,23 @@ bool Split::getModerationMode() const return this->moderationMode; } -bool Split::showChangeChannelPopup(const char *dialogTitle, bool empty) +void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, + std::function callback) { - // create new input dialog and execute it - TextInputDialog dialog(this); - - dialog.setWindowTitle(dialogTitle); - + SelectChannelDialog *dialog = new SelectChannelDialog(); if (!empty) { - dialog.setText(this->channel->name); - dialog.highlightText(); + dialog->setSelectedChannel(this->getChannel()); } + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + dialog->closed.connect([=] { + if (dialog->hasSeletedChannel()) { + this->setChannel(dialog->getSelectedChannel()); + this->parentPage.refreshTitle(); + } - if (dialog.exec() == QDialog::Accepted) { - QString newChannelName = dialog.getText().trimmed(); - - this->setChannel(providers::twitch::TwitchServer::getInstance().addChannel(newChannelName)); - this->parentPage.refreshTitle(); - - return true; - } - - return false; + callback(dialog->hasSeletedChannel()); + }); } void Split::layoutMessages() @@ -290,7 +286,7 @@ void Split::doCloseSplit() void Split::doChangeChannel() { - this->showChangeChannelPopup("Change channel"); + this->showChangeChannelPopup("Change channel", false, [](bool) {}); auto popup = this->findChildren(); if (popup.size() && popup.at(0)->isVisible() && !popup.at(0)->isFloating()) { popup.at(0)->hide(); diff --git a/src/widgets/split.hpp b/src/widgets/split.hpp index e4a51be03..f5394117f 100644 --- a/src/widgets/split.hpp +++ b/src/widgets/split.hpp @@ -64,7 +64,8 @@ public: void setModerationMode(bool value); bool getModerationMode() const; - bool showChangeChannelPopup(const char *dialogTitle, bool empty = false); + void showChangeChannelPopup(const char *dialogTitle, bool empty, + std::function callback); void giveFocus(Qt::FocusReason reason); bool hasFocus() const; void layoutMessages(); diff --git a/src/widgets/splitcontainer.cpp b/src/widgets/splitcontainer.cpp index 8cefb5b57..f398a1345 100644 --- a/src/widgets/splitcontainer.cpp +++ b/src/widgets/splitcontainer.cpp @@ -219,17 +219,16 @@ NotebookTab *SplitContainer::getTab() const void SplitContainer::addChat(bool openChannelNameDialog) { Split *w = this->createChatWidget(); + this->addToLayout(w, std::pair(-1, -1)); if (openChannelNameDialog) { - bool ret = w->showChangeChannelPopup("Open channel name", true); - - if (!ret) { - delete w; - return; - } + w->showChangeChannelPopup("Open channel name", true, [=](bool ok) { + if (!ok) { + this->removeFromLayout(w); + delete w; + } + }); } - - this->addToLayout(w, std::pair(-1, -1)); } void SplitContainer::refreshCurrentFocusCoordinates(bool alsoSetLastRequested)