Merge branch 'master' into apa-notification-on-live
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/EmoteValue.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
@@ -11,15 +11,20 @@
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/StreamLink.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
#include "widgets/dialogs/QualityPopup.hpp"
|
||||
#include "widgets/dialogs/SelectChannelDialog.hpp"
|
||||
#include "widgets/dialogs/TextInputDialog.hpp"
|
||||
#include "widgets/dialogs/UserInfoPopup.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/DebugPopup.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/helper/SearchPopup.hpp"
|
||||
#include "widgets/helper/Shortcut.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
#include "widgets/splits/SplitHeader.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
#include "widgets/splits/SplitOverlay.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
@@ -37,7 +42,6 @@
|
||||
#include <random>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
pajlada::Signals::Signal<Qt::KeyboardModifiers> Split::modifierStatusChanged;
|
||||
Qt::KeyboardModifiers Split::modifierStatus = Qt::NoModifier;
|
||||
|
||||
@@ -51,32 +55,30 @@ Split::Split(QWidget *parent)
|
||||
: BaseWidget(parent)
|
||||
, container_(nullptr)
|
||||
, channel_(Channel::getEmpty())
|
||||
, vbox_(this)
|
||||
, header_(this)
|
||||
, view_(this)
|
||||
, input_(this)
|
||||
, vbox_(new QVBoxLayout(this))
|
||||
, header_(new SplitHeader(this))
|
||||
, view_(new ChannelView(this))
|
||||
, input_(new SplitInput(this))
|
||||
, overlay_(new SplitOverlay(this))
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
this->setMouseTracking(true);
|
||||
|
||||
this->vbox_.setSpacing(0);
|
||||
this->vbox_.setMargin(1);
|
||||
this->vbox_->setSpacing(0);
|
||||
this->vbox_->setMargin(1);
|
||||
|
||||
this->vbox_.addWidget(&this->header_);
|
||||
this->vbox_.addWidget(&this->view_, 1);
|
||||
this->vbox_.addWidget(&this->input_);
|
||||
this->vbox_->addWidget(this->header_);
|
||||
this->vbox_->addWidget(this->view_, 1);
|
||||
this->vbox_->addWidget(this->input_);
|
||||
|
||||
// Initialize chat widget-wide hotkeys
|
||||
// CTRL+W: Close Split
|
||||
createShortcut(this, "CTRL+W", &Split::doCloseSplit);
|
||||
createShortcut(this, "CTRL+W", &Split::deleteFromContainer);
|
||||
|
||||
// CTRL+R: Change Channel
|
||||
createShortcut(this, "CTRL+R", &Split::doChangeChannel);
|
||||
createShortcut(this, "CTRL+R", &Split::changeChannel);
|
||||
|
||||
// CTRL+F: Search
|
||||
createShortcut(this, "CTRL+F", &Split::showSearchPopup);
|
||||
createShortcut(this, "CTRL+F", &Split::showSearch);
|
||||
|
||||
// F12
|
||||
createShortcut(this, "F10", [] {
|
||||
@@ -91,41 +93,41 @@ Split::Split(QWidget *parent)
|
||||
// CreateShortcut(this, "ALT+SHIFT+UP", &Split::doIncFlexY);
|
||||
// CreateShortcut(this, "ALT+SHIFT+DOWN", &Split::doDecFlexY);
|
||||
|
||||
this->input_.ui_.textEdit->installEventFilter(parent);
|
||||
this->input_->ui_.textEdit->installEventFilter(parent);
|
||||
|
||||
this->view_.mouseDown.connect([this](QMouseEvent *) {
|
||||
this->view_->mouseDown.connect([this](QMouseEvent *) {
|
||||
//
|
||||
this->giveFocus(Qt::MouseFocusReason);
|
||||
});
|
||||
this->view_.selectionChanged.connect([this]() {
|
||||
if (view_.hasSelection()) {
|
||||
this->input_.clearSelection();
|
||||
this->view_->selectionChanged.connect([this]() {
|
||||
if (view_->hasSelection()) {
|
||||
this->input_->clearSelection();
|
||||
}
|
||||
});
|
||||
|
||||
this->input_.textChanged.connect([=](const QString &newText) {
|
||||
if (app->settings->showEmptyInput) {
|
||||
this->input_->textChanged.connect([=](const QString &newText) {
|
||||
if (getSettings()->showEmptyInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newText.length() == 0) {
|
||||
this->input_.hide();
|
||||
} else if (this->input_.isHidden()) {
|
||||
this->input_.show();
|
||||
this->input_->hide();
|
||||
} else if (this->input_->isHidden()) {
|
||||
this->input_->show();
|
||||
}
|
||||
});
|
||||
|
||||
app->settings->showEmptyInput.connect(
|
||||
getSettings()->showEmptyInput.connect(
|
||||
[this](const bool &showEmptyInput, auto) {
|
||||
if (!showEmptyInput && this->input_.getInputText().length() == 0) {
|
||||
this->input_.hide();
|
||||
if (!showEmptyInput && this->input_->getInputText().length() == 0) {
|
||||
this->input_->hide();
|
||||
} else {
|
||||
this->input_.show();
|
||||
this->input_->show();
|
||||
}
|
||||
},
|
||||
this->managedConnections_);
|
||||
|
||||
this->header_.updateModerationModeIcon();
|
||||
this->header_->updateModerationModeIcon();
|
||||
this->overlay_->hide();
|
||||
|
||||
this->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
@@ -141,9 +143,9 @@ Split::Split(QWidget *parent)
|
||||
}
|
||||
});
|
||||
|
||||
this->input_.ui_.textEdit->focused.connect(
|
||||
this->input_->ui_.textEdit->focused.connect(
|
||||
[this] { this->focused.invoke(); });
|
||||
this->input_.ui_.textEdit->focusLost.connect(
|
||||
this->input_->ui_.textEdit->focusLost.connect(
|
||||
[this] { this->focusLost.invoke(); });
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ Split::~Split()
|
||||
|
||||
ChannelView &Split::getChannelView()
|
||||
{
|
||||
return this->view_;
|
||||
return *this->view_;
|
||||
}
|
||||
|
||||
SplitContainer *Split::getContainer()
|
||||
@@ -189,7 +191,7 @@ void Split::setChannel(IndirectChannel newChannel)
|
||||
{
|
||||
this->channel_ = newChannel;
|
||||
|
||||
this->view_.setChannel(newChannel.get());
|
||||
this->view_->setChannel(newChannel.get());
|
||||
|
||||
this->usermodeChangedConnection_.disconnect();
|
||||
this->roomModeChangedConnection_.disconnect();
|
||||
@@ -199,12 +201,12 @@ void Split::setChannel(IndirectChannel newChannel)
|
||||
|
||||
if (tc != nullptr) {
|
||||
this->usermodeChangedConnection_ = tc->userStateChanged.connect([this] {
|
||||
this->header_.updateModerationModeIcon();
|
||||
this->header_.updateRoomModes();
|
||||
this->header_->updateModerationModeIcon();
|
||||
this->header_->updateRoomModes();
|
||||
});
|
||||
|
||||
this->roomModeChangedConnection_ = tc->roomModesChanged.connect(
|
||||
[this] { this->header_.updateRoomModes(); });
|
||||
[this] { this->header_->updateRoomModes(); });
|
||||
}
|
||||
|
||||
this->indirectChannelChangedConnection_ =
|
||||
@@ -212,9 +214,9 @@ void Split::setChannel(IndirectChannel newChannel)
|
||||
QTimer::singleShot(0, [this] { this->setChannel(this->channel_); });
|
||||
});
|
||||
|
||||
this->header_.updateModerationModeIcon();
|
||||
this->header_.updateChannelText();
|
||||
this->header_.updateRoomModes();
|
||||
this->header_->updateModerationModeIcon();
|
||||
this->header_->updateChannelText();
|
||||
this->header_->updateRoomModes();
|
||||
|
||||
this->channelChanged.invoke();
|
||||
}
|
||||
@@ -223,8 +225,8 @@ void Split::setModerationMode(bool value)
|
||||
{
|
||||
if (value != this->moderationMode_) {
|
||||
this->moderationMode_ = value;
|
||||
this->header_.updateModerationModeIcon();
|
||||
this->view_.layoutMessages();
|
||||
this->header_->updateModerationModeIcon();
|
||||
this->view_->layoutMessages();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +237,7 @@ bool Split::getModerationMode() const
|
||||
|
||||
void Split::insertTextToInput(const QString &text)
|
||||
{
|
||||
this->input_.insertText(text);
|
||||
this->input_->insertText(text);
|
||||
}
|
||||
|
||||
void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
|
||||
@@ -269,27 +271,27 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
|
||||
|
||||
void Split::layoutMessages()
|
||||
{
|
||||
this->view_.layoutMessages();
|
||||
this->view_->layoutMessages();
|
||||
}
|
||||
|
||||
void Split::updateGifEmotes()
|
||||
{
|
||||
this->view_.queueUpdate();
|
||||
this->view_->queueUpdate();
|
||||
}
|
||||
|
||||
void Split::updateLastReadMessage()
|
||||
{
|
||||
this->view_.updateLastReadMessage();
|
||||
this->view_->updateLastReadMessage();
|
||||
}
|
||||
|
||||
void Split::giveFocus(Qt::FocusReason reason)
|
||||
{
|
||||
this->input_.ui_.textEdit->setFocus(reason);
|
||||
this->input_->ui_.textEdit->setFocus(reason);
|
||||
}
|
||||
|
||||
bool Split::hasFocus() const
|
||||
{
|
||||
return this->input_.ui_.textEdit->hasFocus();
|
||||
return this->input_->ui_.textEdit->hasFocus();
|
||||
}
|
||||
|
||||
void Split::paintEvent(QPaintEvent *)
|
||||
@@ -307,13 +309,13 @@ void Split::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void Split::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
this->view_.unsetCursor();
|
||||
this->view_->unsetCursor();
|
||||
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
||||
}
|
||||
|
||||
void Split::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
this->view_.unsetCursor();
|
||||
this->view_->unsetCursor();
|
||||
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
||||
}
|
||||
|
||||
@@ -362,21 +364,21 @@ void Split::handleModifiers(Qt::KeyboardModifiers modifiers)
|
||||
}
|
||||
|
||||
/// Slots
|
||||
void Split::doAddSplit()
|
||||
void Split::addSibling()
|
||||
{
|
||||
if (this->container_) {
|
||||
this->container_->appendNewSplit(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Split::doCloseSplit()
|
||||
void Split::deleteFromContainer()
|
||||
{
|
||||
if (this->container_) {
|
||||
this->container_->deleteSplit(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Split::doChangeChannel()
|
||||
void Split::changeChannel()
|
||||
{
|
||||
this->showChangeChannelPopup("Change channel", false, [](bool) {});
|
||||
|
||||
@@ -388,10 +390,10 @@ void Split::doChangeChannel()
|
||||
}
|
||||
}
|
||||
|
||||
void Split::doPopup()
|
||||
void Split::popup()
|
||||
{
|
||||
auto app = getApp();
|
||||
Window &window = app->windows->createWindow(Window::Type::Popup);
|
||||
Window &window = app->windows->createWindow(WindowType::Popup);
|
||||
|
||||
Split *split = new Split(static_cast<SplitContainer *>(
|
||||
window.getNotebook().getOrAddSelectedPage()));
|
||||
@@ -402,9 +404,9 @@ void Split::doPopup()
|
||||
window.show();
|
||||
}
|
||||
|
||||
void Split::doClearChat()
|
||||
void Split::clear()
|
||||
{
|
||||
this->view_.clearMessages();
|
||||
this->view_->clearMessages();
|
||||
}
|
||||
|
||||
void Split::openInBrowser()
|
||||
@@ -417,7 +419,7 @@ void Split::openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
void Split::openInPopupPlayer()
|
||||
void Split::openBrowserPlayer()
|
||||
{
|
||||
ChannelPtr channel = this->getChannel();
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
@@ -431,7 +433,7 @@ void Split::openInStreamlink()
|
||||
try {
|
||||
openStreamlinkForChannel(this->getChannel()->getName());
|
||||
} catch (const Exception &ex) {
|
||||
Log("Error in doOpenStreamlink: {}", ex.what());
|
||||
log("Error in doOpenStreamlink: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,8 +446,8 @@ void Split::showViewerList()
|
||||
QDockWidget::DockWidgetFloatable);
|
||||
viewerDock->resize(
|
||||
0.5 * this->width(),
|
||||
this->height() - this->header_.height() - this->input_.height());
|
||||
viewerDock->move(0, this->header_.height());
|
||||
this->height() - this->header_->height() - this->input_->height());
|
||||
viewerDock->move(0, this->header_->height());
|
||||
|
||||
auto multiWidget = new QWidget(viewerDock);
|
||||
auto dockVbox = new QVBoxLayout(viewerDock);
|
||||
@@ -545,10 +547,10 @@ void Split::showUserInfoPopup(const UserName &user)
|
||||
|
||||
void Split::copyToClipboard()
|
||||
{
|
||||
QApplication::clipboard()->setText(this->view_.getSelectedText());
|
||||
QApplication::clipboard()->setText(this->view_->getSelectedText());
|
||||
}
|
||||
|
||||
void Split::showSearchPopup()
|
||||
void Split::showSearch()
|
||||
{
|
||||
SearchPopup *popup = new SearchPopup();
|
||||
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NullablePtr.hpp"
|
||||
#include "common/SerializeCustom.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "messages/layouts/MessageLayout.hpp"
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/RippleEffectLabel.hpp"
|
||||
#include "widgets/splits/SplitHeader.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
|
||||
#include <QFont>
|
||||
#include <QShortcut>
|
||||
@@ -20,6 +12,9 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ChannelView;
|
||||
class SplitHeader;
|
||||
class SplitInput;
|
||||
class SplitContainer;
|
||||
class SplitOverlay;
|
||||
class SelectChannelDialog;
|
||||
@@ -103,10 +98,10 @@ private:
|
||||
SplitContainer *container_;
|
||||
IndirectChannel channel_;
|
||||
|
||||
QVBoxLayout vbox_;
|
||||
SplitHeader header_;
|
||||
ChannelView view_;
|
||||
SplitInput input_;
|
||||
QVBoxLayout *vbox_;
|
||||
SplitHeader *header_;
|
||||
ChannelView *view_;
|
||||
SplitInput *input_;
|
||||
SplitOverlay *overlay_;
|
||||
|
||||
NullablePtr<SelectChannelDialog> selectChannelDialog_;
|
||||
@@ -125,39 +120,16 @@ private:
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
|
||||
public slots:
|
||||
// Add new split to the notebook page that this chat widget is in
|
||||
// This is only activated from the menu now. Hotkey is handled in Notebook
|
||||
void doAddSplit();
|
||||
|
||||
// Close current split (chat widget)
|
||||
void doCloseSplit();
|
||||
|
||||
// Show a dialog for changing the current splits/chat widgets channel
|
||||
void doChangeChannel();
|
||||
|
||||
// Open popup copy of this chat widget
|
||||
// XXX: maybe make current chatwidget a popup instead?
|
||||
void doPopup();
|
||||
|
||||
// Clear chat from all messages
|
||||
void doClearChat();
|
||||
|
||||
// Open link to twitch channel in default browser
|
||||
void addSibling();
|
||||
void deleteFromContainer();
|
||||
void changeChannel();
|
||||
void popup();
|
||||
void clear();
|
||||
void openInBrowser();
|
||||
|
||||
// Open popup player of twitch channel in default browser
|
||||
void openInPopupPlayer();
|
||||
|
||||
// Open twitch channel stream through streamlink
|
||||
void openBrowserPlayer();
|
||||
void openInStreamlink();
|
||||
|
||||
// Copy text from chat
|
||||
void copyToClipboard();
|
||||
|
||||
// Open a search popup
|
||||
void showSearchPopup();
|
||||
|
||||
// Open viewer list of the channel
|
||||
void showSearch();
|
||||
void showViewerList();
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
@@ -40,12 +41,12 @@ SplitContainer::SplitContainer(Notebook *parent)
|
||||
this->layout();
|
||||
|
||||
if (modifiers == showResizeHandlesModifiers) {
|
||||
for (std::unique_ptr<ResizeHandle> &handle : this->resizeHandles_) {
|
||||
for (auto &handle : this->resizeHandles_) {
|
||||
handle->show();
|
||||
handle->raise();
|
||||
}
|
||||
} else {
|
||||
for (std::unique_ptr<ResizeHandle> &handle : this->resizeHandles_) {
|
||||
for (auto &handle : this->resizeHandles_) {
|
||||
handle->hide();
|
||||
}
|
||||
}
|
||||
@@ -89,7 +90,7 @@ void SplitContainer::hideResizeHandles()
|
||||
{
|
||||
this->overlay_.hide();
|
||||
|
||||
for (std::unique_ptr<ResizeHandle> &handle : this->resizeHandles_) {
|
||||
for (auto &handle : this->resizeHandles_) {
|
||||
handle->hide();
|
||||
}
|
||||
}
|
||||
@@ -311,7 +312,7 @@ void SplitContainer::focusSplitRecursive(Node *node, Direction direction)
|
||||
|
||||
void SplitContainer::layout()
|
||||
{
|
||||
this->baseNode_.geometry_ = this->rect();
|
||||
this->baseNode_.geometry_ = this->rect().adjusted(-1, -1, 0, 0);
|
||||
|
||||
std::vector<DropRect> _dropRects;
|
||||
std::vector<ResizeRect> _resizeRects;
|
||||
@@ -326,25 +327,30 @@ void SplitContainer::layout()
|
||||
|
||||
Node *node = this->baseNode_.findNodeContainingSplit(split);
|
||||
|
||||
// left
|
||||
_dropRects.push_back(
|
||||
DropRect(QRect(g.left(), g.top(), g.width() / 3, g.height()),
|
||||
Position(node, Direction::Left)));
|
||||
_dropRects.push_back(DropRect(QRect(g.left() + g.width() / 3 * 2,
|
||||
g.top(), g.width() / 3, g.height()),
|
||||
// right
|
||||
_dropRects.push_back(DropRect(QRect(g.right() - g.width() / 3, g.top(),
|
||||
g.width() / 3, g.height()),
|
||||
Position(node, Direction::Right)));
|
||||
|
||||
// top
|
||||
_dropRects.push_back(
|
||||
DropRect(QRect(g.left(), g.top(), g.width(), g.height() / 2),
|
||||
Position(node, Direction::Above)));
|
||||
_dropRects.push_back(DropRect(QRect(g.left(), g.top() + g.height() / 2,
|
||||
g.width(), g.height() / 2),
|
||||
Position(node, Direction::Below)));
|
||||
// bottom
|
||||
_dropRects.push_back(
|
||||
DropRect(QRect(g.left(), g.bottom() - g.height() / 2, g.width(),
|
||||
g.height() / 2),
|
||||
Position(node, Direction::Below)));
|
||||
}
|
||||
|
||||
if (this->splits_.empty()) {
|
||||
QRect g = this->rect();
|
||||
_dropRects.push_back(
|
||||
DropRect(QRect(g.left(), g.top(), g.width(), g.height()),
|
||||
DropRect(QRect(g.left(), g.top(), g.width() - 1, g.height() - 1),
|
||||
Position(nullptr, Direction::Below)));
|
||||
}
|
||||
|
||||
@@ -899,7 +905,7 @@ void SplitContainer::Node::layout(bool addSpacing, float _scale,
|
||||
case Node::_Split: {
|
||||
QRect rect = this->geometry_.toRect();
|
||||
this->split_->setGeometry(
|
||||
rect.marginsRemoved(QMargins(1, 1, 1, 1)));
|
||||
rect.marginsRemoved(QMargins(1, 1, 0, 0)));
|
||||
} break;
|
||||
case Node::VerticalContainer:
|
||||
case Node::HorizontalContainer: {
|
||||
@@ -963,10 +969,10 @@ void SplitContainer::Node::layout(bool addSpacing, float _scale,
|
||||
}
|
||||
|
||||
// iterate children
|
||||
qreal pos = isVertical ? childRect.top() : childRect.left();
|
||||
auto pos = int(isVertical ? childRect.top() : childRect.left());
|
||||
for (std::unique_ptr<Node> &child : this->children_) {
|
||||
// set rect
|
||||
QRectF rect = childRect;
|
||||
QRect rect = childRect.toRect();
|
||||
if (isVertical) {
|
||||
rect.setTop(pos);
|
||||
rect.setHeight(
|
||||
@@ -982,6 +988,11 @@ void SplitContainer::Node::layout(bool addSpacing, float _scale,
|
||||
sizeMultiplier);
|
||||
}
|
||||
|
||||
if (child == this->children_.back()) {
|
||||
rect.setRight(childRect.right() - 1);
|
||||
rect.setBottom(childRect.bottom() - 1);
|
||||
}
|
||||
|
||||
child->geometry_ = rect;
|
||||
child->layout(addSpacing, _scale, dropRects, resizeRects);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
#include <QDragEnterEvent>
|
||||
#include <QHBoxLayout>
|
||||
@@ -13,21 +11,24 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
#include <vector>
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Split;
|
||||
class NotebookTab;
|
||||
class Notebook;
|
||||
|
||||
//
|
||||
// Note: This class is a spaghetti container. There is a lot of spaghetti code
|
||||
// inside but it doesn't expose any of it publicly.
|
||||
//
|
||||
|
||||
class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder
|
||||
class SplitContainer final : public BaseWidget, pajlada::Signals::SignalHolder
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
+213
-265
@@ -6,136 +6,186 @@
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/LayoutHelper.hpp"
|
||||
#include "widgets/Label.hpp"
|
||||
#include "widgets/TooltipWidget.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDesktopWidget>
|
||||
#include <QDrag>
|
||||
#include <QHBoxLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef USEWEBENGINE
|
||||
#include "widgets/StreamView.hpp"
|
||||
# include "widgets/StreamView.hpp"
|
||||
#endif
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
auto formatRoomMode(TwitchChannel &channel) -> QString
|
||||
{
|
||||
QString text;
|
||||
|
||||
{
|
||||
auto modes = channel.accessRoomModes();
|
||||
|
||||
if (modes->r9k) text += "r9k, ";
|
||||
if (modes->slowMode)
|
||||
text +=
|
||||
QString("slow(%1), ").arg(QString::number(modes->slowMode));
|
||||
if (modes->emoteOnly) text += "emote, ";
|
||||
if (modes->submode) text += "sub, ";
|
||||
}
|
||||
|
||||
if (text.length() > 2) {
|
||||
text = text.mid(0, text.size() - 2);
|
||||
}
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$");
|
||||
|
||||
auto match = commaReplacement.match(text);
|
||||
if (match.hasMatch())
|
||||
text = match.captured(1) + '\n' + match.captured(2);
|
||||
}
|
||||
|
||||
if (text.isEmpty() && channel.hasModRights()) return "none";
|
||||
|
||||
return text;
|
||||
}
|
||||
auto formatTooltip(const TwitchChannel::StreamStatus &s)
|
||||
{
|
||||
return QStringList{"<style>.center { text-align: center; }</style>",
|
||||
"<p class=\"center\">",
|
||||
s.title,
|
||||
"<br><br>",
|
||||
s.game,
|
||||
"<br>",
|
||||
s.rerun ? "Vod-casting" : "Live",
|
||||
" for ",
|
||||
s.uptime,
|
||||
" with ",
|
||||
QString::number(s.viewerCount),
|
||||
" viewers",
|
||||
"</p>"}
|
||||
.join("");
|
||||
}
|
||||
auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings)
|
||||
{
|
||||
auto title = QString();
|
||||
|
||||
// live
|
||||
if (s.rerun)
|
||||
title += " (rerun)";
|
||||
else if (s.streamType.isEmpty())
|
||||
title += " (" + s.streamType + ")";
|
||||
else
|
||||
title += " (live)";
|
||||
|
||||
// description
|
||||
if (settings.showViewerCount)
|
||||
title += " - " + QString::number(s.viewerCount);
|
||||
if (settings.showTitle) title += " - " + s.title;
|
||||
if (settings.showGame) title += " - " + s.game;
|
||||
if (settings.showUptime) title += " - " + s.uptime;
|
||||
|
||||
return title;
|
||||
}
|
||||
auto distance(QPoint a, QPoint b)
|
||||
{
|
||||
auto x = std::abs(a.x() - b.x());
|
||||
auto y = std::abs(a.y() - b.y());
|
||||
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SplitHeader::SplitHeader(Split *_split)
|
||||
: BaseWidget(_split)
|
||||
, split_(_split)
|
||||
{
|
||||
this->split_->focused.connect([this]() { this->themeChangedEvent(); });
|
||||
this->split_->focusLost.connect([this]() { this->themeChangedEvent(); });
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
LayoutCreator<SplitHeader> layoutCreator(this);
|
||||
auto layout = layoutCreator.emplace<QHBoxLayout>().withoutMargin();
|
||||
layout->setSpacing(0);
|
||||
{
|
||||
// channel name label
|
||||
auto title = layout.emplace<Label>().assign(&this->titleLabel);
|
||||
title->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
title->setCentered(true);
|
||||
title->setHasOffset(false);
|
||||
|
||||
// mode button
|
||||
auto mode = layout.emplace<RippleEffectLabel>(nullptr).assign(
|
||||
&this->modeButton_);
|
||||
|
||||
mode->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
mode->hide();
|
||||
|
||||
this->setupModeLabel(*mode);
|
||||
|
||||
mode->setMenu(this->createChatModeMenu());
|
||||
|
||||
// moderation mode
|
||||
auto moderator = layout.emplace<RippleEffectButton>(this).assign(
|
||||
&this->moderationButton_);
|
||||
|
||||
QObject::connect(
|
||||
moderator.getElement(), &RippleEffectButton::clicked, this,
|
||||
[this, moderator]() mutable {
|
||||
this->split_->setModerationMode(
|
||||
!this->split_->getModerationMode());
|
||||
|
||||
moderator->setDim(!this->split_->getModerationMode());
|
||||
});
|
||||
|
||||
this->updateModerationModeIcon();
|
||||
|
||||
// dropdown label
|
||||
auto dropdown = layout.emplace<RippleEffectButton>(this).assign(
|
||||
&this->dropdownButton_);
|
||||
dropdown->setMouseTracking(true);
|
||||
// dropdown->setPixmap(*app->resources->splitHeaderContext->getPixmap());
|
||||
// dropdown->setScaleIndependantSize(23, 23);
|
||||
dropdown->setMenu(this->createMainMenu());
|
||||
QObject::connect(dropdown.getElement(),
|
||||
&RippleEffectButton::leftMousePress, this, [this] {});
|
||||
}
|
||||
|
||||
// ---- misc
|
||||
this->layout()->setMargin(0);
|
||||
this->scaleChangedEvent(this->getScale());
|
||||
|
||||
this->updateChannelText();
|
||||
|
||||
this->initializeChannelSignals();
|
||||
|
||||
this->split_->channelChanged.connect([this]() {
|
||||
this->initializeChannelSignals(); //
|
||||
});
|
||||
|
||||
this->managedConnect(app->accounts->twitch.currentUserChanged,
|
||||
[this] { this->updateModerationModeIcon(); });
|
||||
this->initializeLayout();
|
||||
|
||||
this->setMouseTracking(true);
|
||||
this->updateChannelText();
|
||||
this->handleChannelChanged();
|
||||
this->updateModerationModeIcon();
|
||||
|
||||
// Update title on title-settings-change
|
||||
getSettings()->showViewerCount.connect(
|
||||
[this](const auto &, const auto &) { this->updateChannelText(); },
|
||||
this->managedConnections_);
|
||||
getSettings()->showTitle.connect(
|
||||
[this](const auto &, const auto &) { this->updateChannelText(); },
|
||||
this->managedConnections_);
|
||||
getSettings()->showGame.connect(
|
||||
[this](const auto &, const auto &) { this->updateChannelText(); },
|
||||
this->managedConnections_);
|
||||
getSettings()->showUptime.connect(
|
||||
[this](const auto &, const auto &) { this->updateChannelText(); },
|
||||
this->managedConnections_);
|
||||
this->split_->focused.connect([this]() { this->themeChangedEvent(); });
|
||||
this->split_->focusLost.connect([this]() { this->themeChangedEvent(); });
|
||||
this->split_->channelChanged.connect(
|
||||
[this]() { this->handleChannelChanged(); });
|
||||
|
||||
this->managedConnect(getApp()->accounts->twitch.currentUserChanged,
|
||||
[this] { this->updateModerationModeIcon(); });
|
||||
|
||||
auto _ = [this](const auto &, const auto &) { this->updateChannelText(); };
|
||||
getSettings()->showViewerCount.connect(_, this->managedConnections_);
|
||||
getSettings()->showTitle.connect(_, this->managedConnections_);
|
||||
getSettings()->showGame.connect(_, this->managedConnections_);
|
||||
getSettings()->showUptime.connect(_, this->managedConnections_);
|
||||
}
|
||||
|
||||
SplitHeader::~SplitHeader()
|
||||
void SplitHeader::initializeLayout()
|
||||
{
|
||||
this->onlineStatusChangedConnection_.disconnect();
|
||||
auto layout = makeLayout<QHBoxLayout>(
|
||||
{// title
|
||||
this->titleLabel = makeWidget<Label>([](auto w) {
|
||||
w->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
w->setCentered(true);
|
||||
w->setHasOffset(false);
|
||||
}),
|
||||
// mode
|
||||
this->modeButton_ = makeWidget<EffectLabel>([&](auto w) {
|
||||
w->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
w->hide();
|
||||
this->initializeModeSignals(*w);
|
||||
w->setMenu(this->createChatModeMenu());
|
||||
}),
|
||||
// moderator
|
||||
this->moderationButton_ = makeWidget<Button>([&](auto w) {
|
||||
QObject::connect(w, &Button::clicked, this, [this, w]() mutable {
|
||||
this->split_->setModerationMode(
|
||||
!this->split_->getModerationMode());
|
||||
|
||||
w->setDim(!this->split_->getModerationMode());
|
||||
});
|
||||
}),
|
||||
// dropdown
|
||||
this->dropdownButton_ = makeWidget<Button>(
|
||||
[&](auto w) { w->setMenu(this->createMainMenu()); })});
|
||||
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
this->setLayout(layout);
|
||||
}
|
||||
|
||||
std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
{
|
||||
auto menu = std::make_unique<QMenu>();
|
||||
menu->addAction("New split", this->split_, &Split::doAddSplit,
|
||||
QKeySequence(tr("Ctrl+T")));
|
||||
menu->addAction("Close split", this->split_, &Split::doCloseSplit,
|
||||
QKeySequence(tr("Ctrl+W")));
|
||||
menu->addAction("Change channel", this->split_, &Split::doChangeChannel,
|
||||
QKeySequence(tr("Ctrl+R")));
|
||||
menu->addAction("New split", this->split_, &Split::addSibling,
|
||||
QKeySequence("Ctrl+T"));
|
||||
menu->addAction("Close split", this->split_, &Split::deleteFromContainer,
|
||||
QKeySequence("Ctrl+W"));
|
||||
menu->addAction("Change channel", this->split_, &Split::changeChannel,
|
||||
QKeySequence("Ctrl+R"));
|
||||
menu->addSeparator();
|
||||
menu->addAction("Viewer list", this->split_, &Split::showViewerList);
|
||||
menu->addAction("Search", this->split_, &Split::showSearchPopup,
|
||||
QKeySequence(tr("Ctrl+F")));
|
||||
menu->addAction("Search", this->split_, &Split::showSearch,
|
||||
QKeySequence("Ctrl+F"));
|
||||
menu->addSeparator();
|
||||
menu->addAction("Popup", this->split_, &Split::doPopup);
|
||||
menu->addAction("Popup", this->split_, &Split::popup);
|
||||
#ifdef USEWEBENGINE
|
||||
this->dropdownMenu.addAction("Start watching", this, [this] {
|
||||
ChannelPtr _channel = this->split->getChannel();
|
||||
@@ -152,7 +202,7 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
menu->addAction("Open in browser", this->split_, &Split::openInBrowser);
|
||||
#ifndef USEWEBENGINE
|
||||
menu->addAction("Open player in browser", this->split_,
|
||||
&Split::openInPopupPlayer);
|
||||
&Split::openBrowserPlayer);
|
||||
#endif
|
||||
menu->addAction("Open streamlink", this->split_, &Split::openInStreamlink);
|
||||
|
||||
@@ -172,9 +222,8 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
menu->addAction(action);
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addAction("Reload channel emotes", this,
|
||||
SLOT(menuReloadChannelEmotes()));
|
||||
menu->addAction("Reconnect", this, SLOT(menuManualReconnect()));
|
||||
menu->addAction("Reload channel emotes", this, SLOT(reloadChannelEmotes()));
|
||||
menu->addAction("Reconnect", this, SLOT(reconnect()));
|
||||
// menu->addAction("Clear messages", this->split_, &Split::doClearChat);
|
||||
// menu->addSeparator();
|
||||
// menu->addAction("Show changelog", this, SLOT(menuShowChangelog()));
|
||||
@@ -218,16 +267,10 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
|
||||
setSub->setChecked(roomModes->submode);
|
||||
}));
|
||||
|
||||
auto toggle = [this](const QString &_command, QAction *action) mutable {
|
||||
QString command = _command;
|
||||
|
||||
if (!action->isChecked()) {
|
||||
command += "off";
|
||||
};
|
||||
auto toggle = [this](const QString &command, QAction *action) mutable {
|
||||
this->split_->getChannel().get()->sendMessage(
|
||||
command + (action->isChecked() ? "" : "off"));
|
||||
action->setChecked(!action->isChecked());
|
||||
|
||||
qDebug() << command;
|
||||
this->split_->getChannel().get()->sendMessage(command);
|
||||
};
|
||||
|
||||
QObject::connect(
|
||||
@@ -244,12 +287,12 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
|
||||
setSlow->setChecked(false);
|
||||
return;
|
||||
};
|
||||
bool ok;
|
||||
int slowSec = QInputDialog::getInt(this, "", "Seconds:", 10, 0, 500, 1,
|
||||
&ok, Qt::FramelessWindowHint);
|
||||
auto ok = bool();
|
||||
auto seconds = QInputDialog::getInt(this, "", "Seconds:", 10, 0, 500, 1,
|
||||
&ok, Qt::FramelessWindowHint);
|
||||
if (ok) {
|
||||
this->split_->getChannel().get()->sendMessage(
|
||||
QString("/slow %1").arg(slowSec));
|
||||
QString("/slow %1").arg(seconds));
|
||||
} else {
|
||||
setSlow->setChecked(false);
|
||||
}
|
||||
@@ -267,74 +310,37 @@ void SplitHeader::updateRoomModes()
|
||||
this->modeUpdateRequested_.invoke();
|
||||
}
|
||||
|
||||
void SplitHeader::setupModeLabel(RippleEffectLabel &label)
|
||||
void SplitHeader::initializeModeSignals(EffectLabel &label)
|
||||
{
|
||||
this->managedConnections_.push_back(
|
||||
this->modeUpdateRequested_.connect([this, &label] {
|
||||
auto twitchChannel =
|
||||
dynamic_cast<TwitchChannel *>(this->split_->getChannel().get());
|
||||
|
||||
// return if the channel is not a twitch channel
|
||||
if (twitchChannel == nullptr) {
|
||||
label.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// set lable enabled
|
||||
this->modeUpdateRequested_.connect([this, &label] {
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(
|
||||
this->split_->getChannel().get())) //
|
||||
{
|
||||
label.setEnable(twitchChannel->hasModRights());
|
||||
|
||||
// set the label text
|
||||
QString text;
|
||||
|
||||
{
|
||||
auto roomModes = twitchChannel->accessRoomModes();
|
||||
|
||||
if (roomModes->r9k) text += "r9k, ";
|
||||
if (roomModes->slowMode)
|
||||
text += QString("slow(%1), ")
|
||||
.arg(QString::number(roomModes->slowMode));
|
||||
if (roomModes->emoteOnly) text += "emote, ";
|
||||
if (roomModes->submode) text += "sub, ";
|
||||
}
|
||||
|
||||
if (text.length() > 2) {
|
||||
text = text.mid(0, text.size() - 2);
|
||||
}
|
||||
|
||||
if (text.isEmpty()) {
|
||||
if (twitchChannel->hasModRights()) {
|
||||
label.getLabel().setText("none");
|
||||
label.show();
|
||||
} else {
|
||||
label.hide();
|
||||
}
|
||||
} else {
|
||||
static QRegularExpression commaReplacement("^.+?, .+?,( ).+$");
|
||||
QRegularExpressionMatch match = commaReplacement.match(text);
|
||||
if (match.hasMatch()) {
|
||||
text = text.mid(0, match.capturedStart(1)) + '\n' +
|
||||
text.mid(match.capturedEnd(1));
|
||||
}
|
||||
auto text = formatRoomMode(*twitchChannel);
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
label.getLabel().setText(text);
|
||||
label.show();
|
||||
return;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
label.hide();
|
||||
});
|
||||
}
|
||||
|
||||
void SplitHeader::initializeChannelSignals()
|
||||
void SplitHeader::handleChannelChanged()
|
||||
{
|
||||
// Disconnect any previous signal first
|
||||
this->onlineStatusChangedConnection_.disconnect();
|
||||
this->channelConnections_.clear();
|
||||
|
||||
auto channel = this->split_->getChannel();
|
||||
TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
|
||||
if (twitchChannel) {
|
||||
this->managedConnections_.emplace_back(
|
||||
twitchChannel->liveStatusChanged.connect([this]() {
|
||||
this->updateChannelText(); //
|
||||
}));
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
this->channelConnections_.emplace_back(
|
||||
twitchChannel->liveStatusChanged.connect(
|
||||
[this]() { this->updateChannelText(); }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,89 +351,47 @@ void SplitHeader::scaleChangedEvent(float scale)
|
||||
this->setFixedHeight(w);
|
||||
this->dropdownButton_->setFixedWidth(w);
|
||||
this->moderationButton_->setFixedWidth(w);
|
||||
// this->titleLabel->setFont(
|
||||
// FontManager::getInstance().getFont(FontStyle::Medium, scale));
|
||||
}
|
||||
|
||||
void SplitHeader::updateChannelText()
|
||||
{
|
||||
auto indirectChannel = this->split_->getIndirectChannel();
|
||||
auto channel = this->split_->getChannel();
|
||||
this->isLive_ = false;
|
||||
this->tooltipText_ = QString();
|
||||
|
||||
QString title = channel->getName();
|
||||
auto title = channel->getName();
|
||||
|
||||
if (indirectChannel.getType() == Channel::Type::TwitchWatching) {
|
||||
if (indirectChannel.getType() == Channel::Type::TwitchWatching)
|
||||
title = "watching: " + (title.isEmpty() ? "none" : title);
|
||||
}
|
||||
|
||||
TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
|
||||
if (twitchChannel != nullptr) {
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
const auto streamStatus = twitchChannel->accessStreamStatus();
|
||||
|
||||
if (streamStatus->live) {
|
||||
this->isLive_ = true;
|
||||
this->tooltip_ = "<style>.center { text-align: center; }</style>"
|
||||
"<p class = \"center\">" +
|
||||
streamStatus->title + "<br><br>" +
|
||||
streamStatus->game + "<br>" +
|
||||
(streamStatus->rerun ? "Vod-casting" : "Live") +
|
||||
" for " + streamStatus->uptime + " with " +
|
||||
QString::number(streamStatus->viewerCount) +
|
||||
" viewers"
|
||||
"</p>";
|
||||
if (streamStatus->rerun) {
|
||||
title += " (rerun)";
|
||||
} else if (streamStatus->streamType.isEmpty()) {
|
||||
title += " (" + streamStatus->streamType + ")";
|
||||
} else {
|
||||
title += " (live)";
|
||||
}
|
||||
if (getSettings()->showViewerCount) {
|
||||
title += " - " + QString::number(streamStatus->viewerCount) +
|
||||
" viewers";
|
||||
}
|
||||
if (getSettings()->showTitle) {
|
||||
title += " - " + streamStatus->title;
|
||||
}
|
||||
if (getSettings()->showGame) {
|
||||
title += " - " + streamStatus->game;
|
||||
}
|
||||
if (getSettings()->showUptime) {
|
||||
title += " - uptime: " + streamStatus->uptime;
|
||||
}
|
||||
} else {
|
||||
this->tooltip_ = QString();
|
||||
this->tooltipText_ = formatTooltip(*streamStatus);
|
||||
title += formatTitle(*streamStatus, *getSettings());
|
||||
}
|
||||
}
|
||||
|
||||
if (title.isEmpty()) {
|
||||
title = "<empty>";
|
||||
}
|
||||
|
||||
this->isLive_ = false;
|
||||
this->titleLabel->setText(title);
|
||||
this->titleLabel->setText(title.isEmpty() ? "<empty>" : title);
|
||||
}
|
||||
|
||||
void SplitHeader::updateModerationModeIcon()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
this->moderationButton_->setPixmap(
|
||||
this->split_->getModerationMode()
|
||||
? app->resources->buttons.modModeEnabled
|
||||
: app->resources->buttons.modModeDisabled);
|
||||
|
||||
bool modButtonVisible = false;
|
||||
ChannelPtr channel = this->split_->getChannel();
|
||||
? getApp()->resources->buttons.modModeEnabled
|
||||
: getApp()->resources->buttons.modModeDisabled);
|
||||
|
||||
auto channel = this->split_->getChannel();
|
||||
auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
|
||||
if (twitchChannel != nullptr && twitchChannel->hasModRights()) {
|
||||
modButtonVisible = true;
|
||||
}
|
||||
|
||||
this->moderationButton_->setVisible(modButtonVisible);
|
||||
if (twitchChannel != nullptr && twitchChannel->hasModRights())
|
||||
this->moderationButton_->show();
|
||||
else
|
||||
this->moderationButton_->hide();
|
||||
}
|
||||
|
||||
void SplitHeader::paintEvent(QPaintEvent *)
|
||||
@@ -453,7 +417,7 @@ void SplitHeader::mousePressEvent(QMouseEvent *event)
|
||||
void SplitHeader::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->dragging_ && event->button() == Qt::LeftButton) {
|
||||
QPoint pos = event->globalPos();
|
||||
auto pos = event->globalPos();
|
||||
|
||||
if (!showingHelpTooltip_) {
|
||||
this->showingHelpTooltip_ = true;
|
||||
@@ -465,18 +429,18 @@ void SplitHeader::mouseReleaseEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
TooltipWidget *widget = new TooltipWidget();
|
||||
auto tooltip = new TooltipWidget();
|
||||
|
||||
widget->setText("Double click or press <Ctrl+R> to change the "
|
||||
"channel.\nClick and "
|
||||
"drag to move the split.");
|
||||
widget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
widget->move(pos);
|
||||
widget->show();
|
||||
widget->raise();
|
||||
tooltip->setText("Double click or press <Ctrl+R> to change the "
|
||||
"channel.\nClick and "
|
||||
"drag to move the split.");
|
||||
tooltip->setAttribute(Qt::WA_DeleteOnClose);
|
||||
tooltip->move(pos);
|
||||
tooltip->show();
|
||||
tooltip->raise();
|
||||
|
||||
QTimer::singleShot(3000, widget, [this, widget] {
|
||||
widget->close();
|
||||
QTimer::singleShot(3000, tooltip, [this, tooltip] {
|
||||
tooltip->close();
|
||||
this->showingHelpTooltip_ = false;
|
||||
});
|
||||
});
|
||||
@@ -489,10 +453,7 @@ void SplitHeader::mouseReleaseEvent(QMouseEvent *event)
|
||||
void SplitHeader::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->dragging_) {
|
||||
if (std::abs(this->dragStart_.x() - event->pos().x()) >
|
||||
int(12 * this->getScale()) ||
|
||||
std::abs(this->dragStart_.y() - event->pos().y()) >
|
||||
int(12 * this->getScale())) {
|
||||
if (distance(this->dragStart_, event->pos()) > 15 * this->getScale()) {
|
||||
this->split_->drag();
|
||||
this->dragging_ = false;
|
||||
}
|
||||
@@ -502,20 +463,20 @@ void SplitHeader::mouseMoveEvent(QMouseEvent *event)
|
||||
void SplitHeader::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
this->split_->doChangeChannel();
|
||||
this->split_->changeChannel();
|
||||
}
|
||||
this->doubleClicked_ = true;
|
||||
}
|
||||
|
||||
void SplitHeader::enterEvent(QEvent *event)
|
||||
{
|
||||
if (!this->tooltip_.isEmpty()) {
|
||||
auto tooltipWidget = TooltipWidget::getInstance();
|
||||
tooltipWidget->moveTo(
|
||||
this, this->mapToGlobal(this->rect().bottomLeft()), false);
|
||||
tooltipWidget->setText(this->tooltip_);
|
||||
tooltipWidget->show();
|
||||
tooltipWidget->raise();
|
||||
if (!this->tooltipText_.isEmpty()) {
|
||||
auto tooltip = TooltipWidget::getInstance();
|
||||
tooltip->moveTo(this, this->mapToGlobal(this->rect().bottomLeft()),
|
||||
false);
|
||||
tooltip->setText(this->tooltipText_);
|
||||
tooltip->show();
|
||||
tooltip->raise();
|
||||
}
|
||||
|
||||
BaseWidget::enterEvent(event);
|
||||
@@ -528,13 +489,9 @@ void SplitHeader::leaveEvent(QEvent *event)
|
||||
BaseWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void SplitHeader::rightButtonClicked()
|
||||
{
|
||||
}
|
||||
|
||||
void SplitHeader::themeChangedEvent()
|
||||
{
|
||||
QPalette palette;
|
||||
auto palette = QPalette();
|
||||
|
||||
if (this->split_->hasFocus()) {
|
||||
palette.setColor(QPalette::Foreground,
|
||||
@@ -542,41 +499,32 @@ void SplitHeader::themeChangedEvent()
|
||||
} else {
|
||||
palette.setColor(QPalette::Foreground, this->theme->splits.header.text);
|
||||
}
|
||||
this->titleLabel->setPalette(palette);
|
||||
|
||||
// --
|
||||
if (this->theme->isLightTheme()) {
|
||||
this->dropdownButton_->setPixmap(getApp()->resources->buttons.menuDark);
|
||||
} else {
|
||||
this->dropdownButton_->setPixmap(
|
||||
getApp()->resources->buttons.menuLight);
|
||||
}
|
||||
|
||||
this->titleLabel->setPalette(palette);
|
||||
}
|
||||
|
||||
void SplitHeader::menuMoveSplit()
|
||||
void SplitHeader::moveSplit()
|
||||
{
|
||||
}
|
||||
|
||||
void SplitHeader::menuReloadChannelEmotes()
|
||||
void SplitHeader::reloadChannelEmotes()
|
||||
{
|
||||
auto channel = this->split_->getChannel();
|
||||
TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
|
||||
if (twitchChannel) {
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
twitchChannel->refreshChannelEmotes();
|
||||
}
|
||||
}
|
||||
|
||||
void SplitHeader::menuManualReconnect()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
// fourtf: connection
|
||||
app->twitch.server->connect();
|
||||
}
|
||||
|
||||
void SplitHeader::menuShowChangelog()
|
||||
void SplitHeader::reconnect()
|
||||
{
|
||||
getApp()->twitch.server->connect();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
#include "widgets/helper/RippleEffectLabel.hpp"
|
||||
#include "widgets/helper/SignalLabel.hpp"
|
||||
|
||||
#include <QAction>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPoint>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include <pajlada/signals/connection.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Split;
|
||||
class Button;
|
||||
class EffectLabel;
|
||||
class Label;
|
||||
class Split;
|
||||
|
||||
class SplitHeader : public BaseWidget, pajlada::Signals::SignalHolder
|
||||
class SplitHeader final : public BaseWidget, pajlada::Signals::SignalHolder
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SplitHeader(Split *_chatWidget);
|
||||
virtual ~SplitHeader() override;
|
||||
|
||||
// Update channel text from chat widget
|
||||
void updateChannelText();
|
||||
void updateModerationModeIcon();
|
||||
void updateRoomModes();
|
||||
@@ -49,41 +41,38 @@ protected:
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void rightButtonClicked();
|
||||
void initializeChannelSignals();
|
||||
void setupModeLabel(RippleEffectLabel &label);
|
||||
void initializeLayout();
|
||||
void initializeModeSignals(EffectLabel &label);
|
||||
std::unique_ptr<QMenu> createMainMenu();
|
||||
std::unique_ptr<QMenu> createChatModeMenu();
|
||||
void handleChannelChanged();
|
||||
|
||||
Split *split_;
|
||||
Split *const split_{};
|
||||
QString tooltipText_{};
|
||||
bool isLive_{false};
|
||||
|
||||
QPoint dragStart_;
|
||||
bool dragging_ = false;
|
||||
bool doubleClicked_ = false;
|
||||
bool showingHelpTooltip_ = false;
|
||||
|
||||
pajlada::Signals::Connection onlineStatusChangedConnection_;
|
||||
|
||||
RippleEffectButton *dropdownButton_{};
|
||||
// Label *titleLabel{};
|
||||
// ui
|
||||
Button *dropdownButton_{};
|
||||
Label *titleLabel{};
|
||||
RippleEffectLabel *modeButton_{};
|
||||
RippleEffectButton *moderationButton_{};
|
||||
EffectLabel *modeButton_{};
|
||||
Button *moderationButton_{};
|
||||
|
||||
bool menuVisible_{};
|
||||
// states
|
||||
QPoint dragStart_{};
|
||||
bool dragging_{false};
|
||||
bool doubleClicked_{false};
|
||||
bool showingHelpTooltip_{false};
|
||||
bool menuVisible_{false};
|
||||
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal modeUpdateRequested_;
|
||||
|
||||
QString tooltip_;
|
||||
bool isLive_;
|
||||
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> channelConnections_;
|
||||
|
||||
public slots:
|
||||
void menuMoveSplit();
|
||||
void menuReloadChannelEmotes();
|
||||
void menuManualReconnect();
|
||||
void menuShowChangelog();
|
||||
void moveSplit();
|
||||
void reloadChannelEmotes();
|
||||
void reconnect();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "messages/Link.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/dialogs/EmotePopup.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QPainter>
|
||||
@@ -61,7 +67,7 @@ void SplitInput::initLayout()
|
||||
textEditLength->setAlignment(Qt::AlignRight);
|
||||
|
||||
box->addStretch(1);
|
||||
box.emplace<RippleEffectLabel>().assign(&this->ui_.emoteButton);
|
||||
box.emplace<EffectLabel>().assign(&this->ui_.emoteButton);
|
||||
}
|
||||
|
||||
this->ui_.emoteButton->getLabel().setTextFormat(Qt::RichText);
|
||||
@@ -70,42 +76,40 @@ void SplitInput::initLayout()
|
||||
|
||||
// set edit font
|
||||
this->ui_.textEdit->setFont(
|
||||
app->fonts->getFont(Fonts::Type::ChatMedium, this->getScale()));
|
||||
app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
|
||||
|
||||
this->managedConnections_.push_back(app->fonts->fontChanged.connect([=]() {
|
||||
this->ui_.textEdit->setFont(
|
||||
app->fonts->getFont(Fonts::Type::ChatMedium, this->getScale()));
|
||||
app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
|
||||
}));
|
||||
|
||||
// open emote popup
|
||||
QObject::connect(
|
||||
this->ui_.emoteButton, &RippleEffectLabel::clicked, [this] {
|
||||
if (!this->emotePopup_) {
|
||||
this->emotePopup_ = std::make_unique<EmotePopup>();
|
||||
this->emotePopup_->linkClicked.connect(
|
||||
[this](const Link &link) {
|
||||
if (link.type == Link::InsertText) {
|
||||
this->insertText(link.value + " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
QObject::connect(this->ui_.emoteButton, &EffectLabel::clicked, [this] {
|
||||
if (!this->emotePopup_) {
|
||||
this->emotePopup_ = std::make_unique<EmotePopup>();
|
||||
this->emotePopup_->linkClicked.connect([this](const Link &link) {
|
||||
if (link.type == Link::InsertText) {
|
||||
this->insertText(link.value + " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this->emotePopup_->resize(int(300 * this->emotePopup_->getScale()),
|
||||
int(500 * this->emotePopup_->getScale()));
|
||||
this->emotePopup_->loadChannel(this->split_->getChannel());
|
||||
this->emotePopup_->show();
|
||||
});
|
||||
this->emotePopup_->resize(int(300 * this->emotePopup_->getScale()),
|
||||
int(500 * this->emotePopup_->getScale()));
|
||||
this->emotePopup_->loadChannel(this->split_->getChannel());
|
||||
this->emotePopup_->show();
|
||||
});
|
||||
|
||||
// clear channelview selection when selecting in the input
|
||||
QObject::connect(this->ui_.textEdit, &QTextEdit::copyAvailable,
|
||||
[this](bool available) {
|
||||
if (available) {
|
||||
this->split_->view_.clearSelection();
|
||||
this->split_->view_->clearSelection();
|
||||
}
|
||||
});
|
||||
|
||||
// textEditLength visibility
|
||||
app->settings->showMessageLength.connect(
|
||||
getSettings()->showMessageLength.connect(
|
||||
[this](const bool &value, auto) {
|
||||
this->ui_.textEditLength->setHidden(!value);
|
||||
},
|
||||
@@ -172,8 +176,9 @@ void SplitInput::installKeyPressedEvent()
|
||||
sendMessage = sendMessage.replace('\n', ' ');
|
||||
|
||||
c->sendMessage(sendMessage);
|
||||
// don't add duplicate messages to message history
|
||||
if (this->prevMsg_.isEmpty() || !this->prevMsg_.endsWith(message))
|
||||
// don't add duplicate messages and empty message to message history
|
||||
if ((this->prevMsg_.isEmpty() || !this->prevMsg_.endsWith(message)) &&
|
||||
!message.trimmed().isEmpty())
|
||||
this->prevMsg_.append(message);
|
||||
|
||||
event->accept();
|
||||
@@ -275,7 +280,7 @@ void SplitInput::installKeyPressedEvent()
|
||||
}
|
||||
} else if (event->key() == Qt::Key_C &&
|
||||
event->modifiers() == Qt::ControlModifier) {
|
||||
if (this->split_->view_.hasSelection()) {
|
||||
if (this->split_->view_->hasSelection()) {
|
||||
this->split_->copyToClipboard();
|
||||
event->accept();
|
||||
}
|
||||
@@ -346,7 +351,7 @@ void SplitInput::paintEvent(QPaintEvent *)
|
||||
|
||||
if (this->theme->isLightTheme()) {
|
||||
int s = int(3 * this->getScale());
|
||||
QRect rect = this->rect().marginsRemoved(QMargins(s, s, s, s));
|
||||
QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));
|
||||
|
||||
painter.fillRect(rect, this->theme->splits.input.background);
|
||||
|
||||
@@ -354,7 +359,7 @@ void SplitInput::paintEvent(QPaintEvent *)
|
||||
painter.drawRect(rect);
|
||||
} else {
|
||||
int s = int(1 * this->getScale());
|
||||
QRect rect = this->rect().marginsRemoved(QMargins(s, s, s, s));
|
||||
QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));
|
||||
|
||||
painter.fillRect(rect, this->theme->splits.input.background);
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
#include "widgets/dialogs/EmotePopup.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/helper/RippleEffectLabel.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -16,6 +13,9 @@
|
||||
namespace chatterino {
|
||||
|
||||
class Split;
|
||||
class EmotePopup;
|
||||
class EffectLabel;
|
||||
class ResizingTextEdit;
|
||||
|
||||
class SplitInput : public BaseWidget
|
||||
{
|
||||
@@ -45,12 +45,12 @@ private:
|
||||
void updateEmoteButton();
|
||||
|
||||
Split *const split_;
|
||||
std::unique_ptr<EmotePopup> emotePopup_;
|
||||
std::shared_ptr<EmotePopup> emotePopup_;
|
||||
|
||||
struct {
|
||||
ResizingTextEdit *textEdit;
|
||||
QLabel *textEditLength;
|
||||
RippleEffectLabel *emoteButton;
|
||||
EffectLabel *emoteButton;
|
||||
|
||||
QHBoxLayout *hbox;
|
||||
} ui_;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user