2018-06-26 14:39:22 +02:00
|
|
|
#include "widgets/splits/Split.hpp"
|
2018-04-27 22:11:19 +02:00
|
|
|
|
2020-10-31 18:17:43 +01:00
|
|
|
#include "Application.hpp"
|
2018-06-26 15:33:51 +02:00
|
|
|
#include "common/Common.hpp"
|
2024-01-15 21:28:44 +01:00
|
|
|
#include "common/network/NetworkRequest.hpp"
|
|
|
|
#include "common/network/NetworkResult.hpp"
|
2020-11-21 16:20:10 +01:00
|
|
|
#include "common/QLogging.hpp"
|
2018-11-14 17:26:08 +01:00
|
|
|
#include "controllers/accounts/AccountController.hpp"
|
2021-11-21 18:46:21 +01:00
|
|
|
#include "controllers/commands/CommandController.hpp"
|
|
|
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
|
|
|
#include "controllers/notifications/NotificationController.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "messages/MessageThread.hpp"
|
2023-03-27 18:26:08 +02:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
2022-12-18 15:36:39 +01:00
|
|
|
#include "providers/twitch/TwitchAccount.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "providers/twitch/TwitchChannel.hpp"
|
2019-09-15 13:02:02 +02:00
|
|
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
2019-09-23 19:36:52 +02:00
|
|
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
2020-10-17 15:00:10 +02:00
|
|
|
#include "singletons/Fonts.hpp"
|
2023-11-19 12:05:30 +01:00
|
|
|
#include "singletons/ImageUploader.hpp"
|
2018-06-28 19:46:45 +02:00
|
|
|
#include "singletons/Settings.hpp"
|
2018-06-28 20:03:04 +02:00
|
|
|
#include "singletons/Theme.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "singletons/WindowManager.hpp"
|
2020-01-24 21:36:51 +01:00
|
|
|
#include "util/Clipboard.hpp"
|
2021-10-24 17:27:18 +02:00
|
|
|
#include "util/Helpers.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "util/StreamLink.hpp"
|
2018-06-26 15:11:45 +02:00
|
|
|
#include "widgets/dialogs/QualityPopup.hpp"
|
|
|
|
#include "widgets/dialogs/SelectChannelDialog.hpp"
|
2020-10-18 15:16:56 +02:00
|
|
|
#include "widgets/dialogs/SelectChannelFiltersDialog.hpp"
|
2018-06-26 15:11:45 +02:00
|
|
|
#include "widgets/dialogs/UserInfoPopup.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "widgets/helper/ChannelView.hpp"
|
2018-06-26 17:20:03 +02:00
|
|
|
#include "widgets/helper/DebugPopup.hpp"
|
2018-10-09 19:43:29 +02:00
|
|
|
#include "widgets/helper/NotebookTab.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "widgets/helper/ResizingTextEdit.hpp"
|
2018-06-26 17:20:03 +02:00
|
|
|
#include "widgets/helper/SearchPopup.hpp"
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include "widgets/Notebook.hpp"
|
|
|
|
#include "widgets/Scrollbar.hpp"
|
2022-12-25 12:09:25 +01:00
|
|
|
#include "widgets/splits/DraggedSplit.hpp"
|
2018-06-26 17:20:03 +02:00
|
|
|
#include "widgets/splits/SplitContainer.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "widgets/splits/SplitHeader.hpp"
|
|
|
|
#include "widgets/splits/SplitInput.hpp"
|
2018-06-26 17:20:03 +02:00
|
|
|
#include "widgets/splits/SplitOverlay.hpp"
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include "widgets/Window.hpp"
|
2017-01-17 00:15:44 +01:00
|
|
|
|
2017-09-12 19:06:16 +02:00
|
|
|
#include <QApplication>
|
|
|
|
#include <QClipboard>
|
2018-01-17 03:18:47 +01:00
|
|
|
#include <QDesktopServices>
|
2017-09-11 22:37:39 +02:00
|
|
|
#include <QDockWidget>
|
2018-01-17 01:19:42 +01:00
|
|
|
#include <QDrag>
|
2018-07-15 14:11:46 +02:00
|
|
|
#include <QJsonArray>
|
2018-10-09 18:28:40 +02:00
|
|
|
#include <QLabel>
|
2017-09-11 22:37:39 +02:00
|
|
|
#include <QListWidget>
|
2018-01-17 01:19:42 +01:00
|
|
|
#include <QMimeData>
|
2018-10-09 18:28:40 +02:00
|
|
|
#include <QMovie>
|
2017-01-17 00:15:44 +01:00
|
|
|
#include <QPainter>
|
2023-03-27 18:26:08 +02:00
|
|
|
#include <QSet>
|
2017-01-17 00:15:44 +01:00
|
|
|
#include <QVBoxLayout>
|
2016-12-29 17:31:07 +01:00
|
|
|
|
2017-07-09 17:49:02 +02:00
|
|
|
#include <functional>
|
2017-09-17 04:36:48 +02:00
|
|
|
#include <random>
|
2017-07-09 17:49:02 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
using namespace chatterino;
|
|
|
|
|
|
|
|
QString formatVIPListError(HelixListVIPsError error, const QString &message)
|
|
|
|
{
|
|
|
|
using Error = HelixListVIPsError;
|
|
|
|
|
|
|
|
QString errorMessage = QString("Failed to list VIPs - ");
|
|
|
|
|
|
|
|
switch (error)
|
|
|
|
{
|
|
|
|
case Error::Forwarded: {
|
|
|
|
errorMessage += message;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::Ratelimited: {
|
|
|
|
errorMessage += "You are being ratelimited by Twitch. Try "
|
|
|
|
"again in a few seconds.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserMissingScope: {
|
|
|
|
// TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE
|
|
|
|
errorMessage += "Missing required scope. "
|
|
|
|
"Re-login with your "
|
|
|
|
"account and try again.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserNotAuthorized: {
|
|
|
|
// TODO(pajlada): Phrase MISSING_PERMISSION
|
|
|
|
errorMessage += "You don't have permission to "
|
|
|
|
"perform that action.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserNotBroadcaster: {
|
|
|
|
errorMessage +=
|
|
|
|
"Due to Twitch restrictions, "
|
|
|
|
"this command can only be used by the broadcaster. "
|
|
|
|
"To see the list of VIPs you must use the Twitch website.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::Unknown: {
|
|
|
|
errorMessage += "An unknown error has occurred.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return errorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString formatModsError(HelixGetModeratorsError error, QString message)
|
|
|
|
{
|
|
|
|
using Error = HelixGetModeratorsError;
|
|
|
|
|
|
|
|
QString errorMessage = QString("Failed to get moderators: ");
|
|
|
|
|
|
|
|
switch (error)
|
|
|
|
{
|
|
|
|
case Error::Forwarded: {
|
|
|
|
errorMessage += message;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserMissingScope: {
|
|
|
|
errorMessage += "Missing required scope. "
|
|
|
|
"Re-login with your "
|
|
|
|
"account and try again.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserNotAuthorized: {
|
|
|
|
errorMessage +=
|
|
|
|
"Due to Twitch restrictions, "
|
|
|
|
"this command can only be used by the broadcaster. "
|
|
|
|
"To see the list of mods you must use the Twitch website.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::Unknown: {
|
|
|
|
errorMessage += "An unknown error has occurred.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return errorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString formatChattersError(HelixGetChattersError error, QString message)
|
|
|
|
{
|
|
|
|
using Error = HelixGetChattersError;
|
|
|
|
|
|
|
|
QString errorMessage = QString("Failed to get chatters: ");
|
|
|
|
|
|
|
|
switch (error)
|
|
|
|
{
|
|
|
|
case Error::Forwarded: {
|
|
|
|
errorMessage += message;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserMissingScope: {
|
|
|
|
errorMessage += "Missing required scope. "
|
|
|
|
"Re-login with your "
|
|
|
|
"account and try again.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::UserNotAuthorized: {
|
|
|
|
errorMessage +=
|
|
|
|
"Due to Twitch restrictions, "
|
|
|
|
"this command can only be used by moderators. "
|
|
|
|
"To see the list of chatters you must use the Twitch website.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Error::Unknown: {
|
|
|
|
errorMessage += "An unknown error has occurred.";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return errorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2017-04-14 17:52:22 +02:00
|
|
|
namespace chatterino {
|
2018-10-09 18:28:40 +02:00
|
|
|
namespace {
|
|
|
|
void showTutorialVideo(QWidget *parent, const QString &source,
|
|
|
|
const QString &title, const QString &description)
|
|
|
|
{
|
2023-12-02 12:56:03 +01:00
|
|
|
auto *window = new BasePopup(
|
|
|
|
{
|
|
|
|
BaseWindow::EnableCustomFrame,
|
|
|
|
BaseWindow::BoundsCheckOnShow,
|
|
|
|
},
|
|
|
|
parent);
|
2018-10-09 18:28:40 +02:00
|
|
|
window->setWindowTitle("Chatterino - " + title);
|
|
|
|
window->setAttribute(Qt::WA_DeleteOnClose);
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *layout = new QVBoxLayout();
|
2018-10-09 18:28:40 +02:00
|
|
|
layout->addWidget(new QLabel(description));
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *label = new QLabel(window);
|
2018-10-09 18:28:40 +02:00
|
|
|
layout->addWidget(label);
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *movie = new QMovie(label);
|
2018-10-09 18:28:40 +02:00
|
|
|
movie->setFileName(source);
|
|
|
|
label->setMovie(movie);
|
|
|
|
movie->start();
|
|
|
|
window->getLayoutContainer()->setLayout(layout);
|
|
|
|
window->show();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2018-05-10 23:58:07 +02:00
|
|
|
pajlada::Signals::Signal<Qt::KeyboardModifiers> Split::modifierStatusChanged;
|
|
|
|
Qt::KeyboardModifiers Split::modifierStatus = Qt::NoModifier;
|
2018-05-08 15:12:04 +02:00
|
|
|
|
2018-04-27 22:11:19 +02:00
|
|
|
Split::Split(QWidget *parent)
|
|
|
|
: BaseWidget(parent)
|
2018-07-06 19:23:47 +02:00
|
|
|
, channel_(Channel::getEmpty())
|
2018-08-11 22:23:06 +02:00
|
|
|
, vbox_(new QVBoxLayout(this))
|
|
|
|
, header_(new SplitHeader(this))
|
2022-11-12 16:53:42 +01:00
|
|
|
, view_(new ChannelView(this, this, ChannelView::Context::None,
|
|
|
|
getSettings()->scrollbackSplitLimit))
|
2018-08-11 22:23:06 +02:00
|
|
|
, input_(new SplitInput(this))
|
2018-07-06 19:23:47 +02:00
|
|
|
, overlay_(new SplitOverlay(this))
|
2016-12-29 17:31:07 +01:00
|
|
|
{
|
2018-01-17 01:19:42 +01:00
|
|
|
this->setMouseTracking(true);
|
2018-12-02 18:37:51 +01:00
|
|
|
this->view_->setPausable(true);
|
2022-06-26 18:53:09 +02:00
|
|
|
this->view_->setFocusProxy(this->input_->ui_.textEdit);
|
|
|
|
this->setFocusProxy(this->input_->ui_.textEdit);
|
2018-01-17 01:19:42 +01:00
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->vbox_->setSpacing(0);
|
2022-11-10 20:11:40 +01:00
|
|
|
this->vbox_->setContentsMargins(1, 1, 1, 1);
|
2017-01-01 02:30:42 +01:00
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->vbox_->addWidget(this->header_);
|
|
|
|
this->vbox_->addWidget(this->view_, 1);
|
|
|
|
this->vbox_->addWidget(this->input_);
|
2017-06-10 23:53:39 +02:00
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->input_->ui_.textEdit->installEventFilter(parent);
|
2017-09-16 00:05:06 +02:00
|
|
|
|
2021-11-20 12:22:30 +01:00
|
|
|
// update placeholder text on Twitch account change and channel change
|
2022-05-07 17:22:39 +02:00
|
|
|
this->bSignals_.emplace_back(
|
|
|
|
getApp()->accounts->twitch.currentUserChanged.connect([this] {
|
2020-12-13 12:16:08 +01:00
|
|
|
this->updateInputPlaceholder();
|
2022-05-07 17:22:39 +02:00
|
|
|
}));
|
2020-12-13 12:16:08 +01:00
|
|
|
this->signalHolder_.managedConnect(channelChanged, [this] {
|
|
|
|
this->updateInputPlaceholder();
|
|
|
|
});
|
|
|
|
this->updateInputPlaceholder();
|
2020-10-31 18:17:43 +01:00
|
|
|
|
2022-12-07 19:21:04 +01:00
|
|
|
// clear SplitInput selection when selecting in ChannelView
|
2023-09-16 13:52:51 +02:00
|
|
|
// this connection can be ignored since the ChannelView is owned by this Split
|
|
|
|
std::ignore = this->view_->selectionChanged.connect([this]() {
|
2022-11-27 20:39:53 +01:00
|
|
|
if (this->input_->hasSelection())
|
2018-08-11 22:23:06 +02:00
|
|
|
{
|
|
|
|
this->input_->clearSelection();
|
2017-12-17 16:19:16 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-12-07 19:21:04 +01:00
|
|
|
// clear ChannelView selection when selecting in SplitInput
|
2023-09-16 13:52:51 +02:00
|
|
|
// this connection can be ignored since the SplitInput is owned by this Split
|
|
|
|
std::ignore = this->input_->selectionChanged.connect([this]() {
|
2022-12-07 19:21:04 +01:00
|
|
|
if (this->view_->hasSelection())
|
|
|
|
{
|
|
|
|
this->view_->clearSelection();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-09-16 13:52:51 +02:00
|
|
|
// this connection can be ignored since the ChannelView is owned by this Split
|
|
|
|
std::ignore = this->view_->openChannelIn.connect(
|
|
|
|
[this](QString twitchChannel, FromTwitchLinkOpenChannelIn openIn) {
|
|
|
|
ChannelPtr channel =
|
|
|
|
getApp()->twitch->getOrAddChannel(twitchChannel);
|
|
|
|
switch (openIn)
|
|
|
|
{
|
|
|
|
case FromTwitchLinkOpenChannelIn::Split:
|
|
|
|
this->openSplitRequested.invoke(channel);
|
|
|
|
break;
|
|
|
|
case FromTwitchLinkOpenChannelIn::Tab:
|
|
|
|
this->joinChannelInNewTab(channel);
|
|
|
|
break;
|
|
|
|
case FromTwitchLinkOpenChannelIn::BrowserPlayer:
|
|
|
|
this->openChannelInBrowserPlayer(channel);
|
|
|
|
break;
|
|
|
|
case FromTwitchLinkOpenChannelIn::Streamlink:
|
|
|
|
this->openChannelInStreamlink(twitchChannel);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
qCWarning(chatterinoWidget)
|
|
|
|
<< "Unhandled \"FromTwitchLinkOpenChannelIn\" enum "
|
|
|
|
"value: "
|
|
|
|
<< static_cast<int>(openIn);
|
|
|
|
}
|
|
|
|
});
|
2018-09-21 20:35:14 +02:00
|
|
|
|
2023-09-16 13:52:51 +02:00
|
|
|
// this connection can be ignored since the SplitInput is owned by this Split
|
|
|
|
std::ignore =
|
|
|
|
this->input_->textChanged.connect([this](const QString &newText) {
|
|
|
|
if (getSettings()->showEmptyInput)
|
|
|
|
{
|
|
|
|
// We always show the input regardless of the text, so we can early out here
|
|
|
|
return;
|
|
|
|
}
|
2017-12-17 16:19:16 +01:00
|
|
|
|
2023-09-16 13:52:51 +02:00
|
|
|
if (newText.isEmpty())
|
|
|
|
{
|
|
|
|
this->input_->hide();
|
|
|
|
}
|
|
|
|
else if (this->input_->isHidden())
|
|
|
|
{
|
|
|
|
// Text updated and the input was previously hidden, show it
|
|
|
|
this->input_->show();
|
|
|
|
}
|
|
|
|
});
|
2017-12-17 16:19:16 +01:00
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
getSettings()->showEmptyInput.connect(
|
2018-07-05 18:17:12 +02:00
|
|
|
[this](const bool &showEmptyInput, auto) {
|
2022-06-26 18:53:09 +02:00
|
|
|
if (showEmptyInput)
|
2018-08-11 22:23:06 +02:00
|
|
|
{
|
2022-06-26 18:53:09 +02:00
|
|
|
this->input_->show();
|
2018-01-02 02:15:11 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-26 18:53:09 +02:00
|
|
|
if (this->input_->getInputText().isEmpty())
|
|
|
|
{
|
|
|
|
this->input_->hide();
|
|
|
|
}
|
2018-01-02 02:15:11 +01:00
|
|
|
}
|
2018-04-27 22:11:19 +02:00
|
|
|
},
|
2021-12-19 15:57:56 +01:00
|
|
|
this->signalHolder_);
|
2018-01-17 16:52:51 +01:00
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->header_->updateModerationModeIcon();
|
2018-07-06 19:23:47 +02:00
|
|
|
this->overlay_->hide();
|
2018-04-18 19:18:14 +02:00
|
|
|
|
|
|
|
this->setSizePolicy(QSizePolicy::MinimumExpanding,
|
|
|
|
QSizePolicy::MinimumExpanding);
|
2018-05-08 15:12:04 +02:00
|
|
|
|
2022-12-31 12:56:47 +01:00
|
|
|
// update moderation button when items changed
|
|
|
|
this->signalHolder_.managedConnect(
|
|
|
|
getSettings()->moderationActions.delayedItemsChanged, [this] {
|
|
|
|
this->refreshModerationMode();
|
|
|
|
});
|
|
|
|
|
2021-12-19 15:57:56 +01:00
|
|
|
this->signalHolder_.managedConnect(
|
|
|
|
modifierStatusChanged, [this](Qt::KeyboardModifiers status) {
|
|
|
|
if ((status ==
|
|
|
|
showSplitOverlayModifiers /*|| status == showAddSplitRegions*/) &&
|
|
|
|
this->isMouseOver_)
|
|
|
|
{
|
|
|
|
this->overlay_->show();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->overlay_->hide();
|
|
|
|
}
|
2019-09-16 10:40:02 +02:00
|
|
|
|
2021-12-19 15:57:56 +01:00
|
|
|
if (getSettings()->pauseChatModifier.getEnum() != Qt::NoModifier &&
|
|
|
|
status == getSettings()->pauseChatModifier.getEnum())
|
|
|
|
{
|
|
|
|
this->view_->pause(PauseReason::KeyboardModifier);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->view_->unpause(PauseReason::KeyboardModifier);
|
|
|
|
}
|
|
|
|
});
|
2018-05-25 14:57:17 +02:00
|
|
|
|
2022-07-23 15:42:41 +02:00
|
|
|
this->signalHolder_.managedConnect(this->input_->ui_.textEdit->focused,
|
|
|
|
[this] {
|
|
|
|
// Forward textEdit's focused event
|
|
|
|
this->focused.invoke();
|
|
|
|
});
|
|
|
|
this->signalHolder_.managedConnect(this->input_->ui_.textEdit->focusLost,
|
|
|
|
[this] {
|
|
|
|
// Forward textEdit's focusLost event
|
|
|
|
this->focusLost.invoke();
|
|
|
|
});
|
2023-09-16 13:52:51 +02:00
|
|
|
|
|
|
|
// this connection can be ignored since the SplitInput is owned by this Split
|
|
|
|
std::ignore = this->input_->ui_.textEdit->imagePasted.connect(
|
2020-07-05 14:32:10 +02:00
|
|
|
[this](const QMimeData *source) {
|
2020-08-22 18:33:37 +02:00
|
|
|
if (!getSettings()->imageUploaderEnabled)
|
2023-12-10 13:28:31 +01:00
|
|
|
{
|
2020-08-22 18:33:37 +02:00
|
|
|
return;
|
2023-12-10 13:28:31 +01:00
|
|
|
}
|
2020-08-22 18:33:37 +02:00
|
|
|
|
2020-07-05 14:32:10 +02:00
|
|
|
if (getSettings()->askOnImageUpload.getValue())
|
2020-05-09 13:14:41 +02:00
|
|
|
{
|
2022-04-16 10:59:20 +02:00
|
|
|
QMessageBox msgBox(this->window());
|
2021-08-14 12:08:55 +02:00
|
|
|
msgBox.setWindowTitle("Chatterino");
|
2020-07-05 14:32:10 +02:00
|
|
|
msgBox.setText("Image upload");
|
|
|
|
msgBox.setInformativeText(
|
2020-08-22 16:49:23 +02:00
|
|
|
"You are uploading an image to a 3rd party service not in "
|
2021-08-14 12:08:55 +02:00
|
|
|
"control of the Chatterino team. You may not be able to "
|
2020-08-22 16:49:23 +02:00
|
|
|
"remove the image from the site. Are you okay with this?");
|
2023-12-10 13:28:31 +01:00
|
|
|
auto *cancel = msgBox.addButton(QMessageBox::Cancel);
|
|
|
|
auto *yes = msgBox.addButton(QMessageBox::Yes);
|
|
|
|
auto *yesDontAskAgain = msgBox.addButton("Yes, don't ask again",
|
|
|
|
QMessageBox::YesRole);
|
2020-07-05 14:32:10 +02:00
|
|
|
|
|
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
|
|
|
2023-12-10 13:28:31 +01:00
|
|
|
msgBox.exec();
|
|
|
|
|
|
|
|
auto *clickedButton = msgBox.clickedButton();
|
|
|
|
if (clickedButton == yesDontAskAgain)
|
|
|
|
{
|
|
|
|
getSettings()->askOnImageUpload.setValue(false);
|
|
|
|
}
|
|
|
|
else if (clickedButton == yes)
|
|
|
|
{
|
|
|
|
// Continue with image upload
|
|
|
|
}
|
|
|
|
else if (clickedButton == cancel)
|
2020-07-05 14:32:10 +02:00
|
|
|
{
|
2023-12-10 13:28:31 +01:00
|
|
|
// Not continuing with image upload
|
2020-07-05 14:32:10 +02:00
|
|
|
return;
|
|
|
|
}
|
2023-12-10 13:28:31 +01:00
|
|
|
else
|
2020-07-05 14:32:10 +02:00
|
|
|
{
|
2023-12-10 13:28:31 +01:00
|
|
|
// An unknown "button" was pressed - handle it as if cancel was pressed
|
|
|
|
// cancel is already handled as the "escape" option, so this should never happen
|
|
|
|
qCWarning(chatterinoImageuploader)
|
|
|
|
<< "Unhandled button pressed:" << clickedButton;
|
|
|
|
return;
|
2020-07-05 14:32:10 +02:00
|
|
|
}
|
2020-05-09 13:14:41 +02:00
|
|
|
}
|
2023-11-19 12:05:30 +01:00
|
|
|
QPointer<ResizingTextEdit> edit = this->input_->ui_.textEdit;
|
|
|
|
getApp()->imageUploader->upload(source, this->getChannel(), edit);
|
2020-07-05 14:32:10 +02:00
|
|
|
});
|
2020-08-22 18:33:37 +02:00
|
|
|
|
|
|
|
getSettings()->imageUploaderEnabled.connect(
|
2020-11-08 12:02:19 +01:00
|
|
|
[this](const bool &val) {
|
|
|
|
this->setAcceptDrops(val);
|
|
|
|
},
|
2021-12-19 15:57:56 +01:00
|
|
|
this->signalHolder_);
|
2021-11-21 18:46:21 +01:00
|
|
|
this->addShortcuts();
|
2021-12-19 15:57:56 +01:00
|
|
|
this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated,
|
|
|
|
[this]() {
|
|
|
|
this->clearShortcuts();
|
|
|
|
this->addShortcuts();
|
|
|
|
});
|
2021-11-21 18:46:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Split::addShortcuts()
|
|
|
|
{
|
|
|
|
HotkeyController::HotkeyMap actions{
|
|
|
|
{"delete",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->deleteFromContainer();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"changeChannel",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->changeChannel();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"showSearch",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
2022-05-23 02:47:16 +02:00
|
|
|
this->showSearch(true);
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"showGlobalSearch",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->showSearch(false);
|
2021-11-21 18:46:21 +01:00
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"reconnect",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->reconnect();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"debug",
|
|
|
|
[](std::vector<QString>) -> QString {
|
|
|
|
auto *popup = new DebugPopup;
|
|
|
|
popup->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
popup->setWindowTitle("Chatterino - Debug popup");
|
|
|
|
popup->show();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"focus",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (arguments.size() == 0)
|
|
|
|
{
|
|
|
|
return "focus action requires only one argument: the "
|
|
|
|
"focus direction Use \"up\", \"above\", \"down\", "
|
|
|
|
"\"below\", \"left\" or \"right\".";
|
|
|
|
}
|
|
|
|
auto direction = arguments.at(0);
|
|
|
|
if (direction == "up" || direction == "above")
|
|
|
|
{
|
|
|
|
this->actionRequested.invoke(Action::SelectSplitAbove);
|
|
|
|
}
|
|
|
|
else if (direction == "down" || direction == "below")
|
|
|
|
{
|
|
|
|
this->actionRequested.invoke(Action::SelectSplitBelow);
|
|
|
|
}
|
|
|
|
else if (direction == "left")
|
|
|
|
{
|
|
|
|
this->actionRequested.invoke(Action::SelectSplitLeft);
|
|
|
|
}
|
|
|
|
else if (direction == "right")
|
|
|
|
{
|
|
|
|
this->actionRequested.invoke(Action::SelectSplitRight);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return "focus in unknown direction. Use \"up\", "
|
|
|
|
"\"above\", \"down\", \"below\", \"left\" or "
|
|
|
|
"\"right\".";
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"scrollToBottom",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->getChannelView().getScrollBar().scrollToBottom(
|
|
|
|
getSettings()->enableSmoothScrollingNewMessages.getValue());
|
|
|
|
return "";
|
|
|
|
}},
|
2022-06-18 13:48:55 +02:00
|
|
|
{"scrollToTop",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->getChannelView().getScrollBar().scrollToTop(
|
|
|
|
getSettings()->enableSmoothScrollingNewMessages.getValue());
|
|
|
|
return "";
|
|
|
|
}},
|
2021-11-21 18:46:21 +01:00
|
|
|
{"scrollPage",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (arguments.size() == 0)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoHotkeys)
|
|
|
|
<< "scrollPage hotkey called without arguments!";
|
|
|
|
return "scrollPage hotkey called without arguments!";
|
|
|
|
}
|
|
|
|
auto direction = arguments.at(0);
|
|
|
|
|
|
|
|
auto &scrollbar = this->getChannelView().getScrollBar();
|
|
|
|
if (direction == "up")
|
|
|
|
{
|
|
|
|
scrollbar.offset(-scrollbar.getLargeChange());
|
|
|
|
}
|
|
|
|
else if (direction == "down")
|
|
|
|
{
|
|
|
|
scrollbar.offset(scrollbar.getLargeChange());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoHotkeys) << "Unknown scroll direction";
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"pickFilters",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->setFiltersDialog();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"openInBrowser",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
if (this->getChannel()->getType() == Channel::Type::TwitchWhispers)
|
|
|
|
{
|
|
|
|
this->openWhispersInBrowser();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->openInBrowser();
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"openInStreamlink",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->openInStreamlink();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"openInCustomPlayer",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->openWithCustomScheme();
|
|
|
|
return "";
|
|
|
|
}},
|
2023-08-07 15:41:32 +02:00
|
|
|
{"openPlayerInBrowser",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->openBrowserPlayer();
|
|
|
|
return "";
|
|
|
|
}},
|
2021-11-21 18:46:21 +01:00
|
|
|
{"openModView",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->openModViewInBrowser();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"createClip",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
// Alt+X: create clip LUL
|
|
|
|
if (const auto type = this->getChannel()->getType();
|
|
|
|
type != Channel::Type::Twitch &&
|
|
|
|
type != Channel::Type::TwitchWatching)
|
|
|
|
{
|
|
|
|
return "Cannot create clip it non-twitch channel.";
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *twitchChannel =
|
|
|
|
dynamic_cast<TwitchChannel *>(this->getChannel().get());
|
|
|
|
|
|
|
|
twitchChannel->createClip();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"reloadEmotes",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
auto reloadChannel = true;
|
|
|
|
auto reloadSubscriber = true;
|
|
|
|
if (arguments.size() != 0)
|
|
|
|
{
|
|
|
|
auto arg = arguments.at(0);
|
|
|
|
if (arg == "channel")
|
|
|
|
{
|
|
|
|
reloadSubscriber = false;
|
|
|
|
}
|
|
|
|
else if (arg == "subscriber")
|
|
|
|
{
|
|
|
|
reloadChannel = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reloadChannel)
|
|
|
|
{
|
|
|
|
this->header_->reloadChannelEmotes();
|
|
|
|
}
|
|
|
|
if (reloadSubscriber)
|
|
|
|
{
|
|
|
|
this->header_->reloadSubscriberEmotes();
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"setModerationMode",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (!this->getChannel()->isTwitchChannel())
|
|
|
|
{
|
|
|
|
return "Cannot set moderation mode in non-twitch channel.";
|
|
|
|
}
|
|
|
|
auto mode = 2;
|
|
|
|
// 0 is off
|
|
|
|
// 1 is on
|
|
|
|
// 2 is toggle
|
|
|
|
if (arguments.size() != 0)
|
|
|
|
{
|
|
|
|
auto arg = arguments.at(0);
|
|
|
|
if (arg == "off")
|
|
|
|
{
|
|
|
|
mode = 0;
|
|
|
|
}
|
|
|
|
else if (arg == "on")
|
|
|
|
{
|
|
|
|
mode = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mode = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == 0)
|
|
|
|
{
|
|
|
|
this->setModerationMode(false);
|
|
|
|
}
|
|
|
|
else if (mode == 1)
|
|
|
|
{
|
|
|
|
this->setModerationMode(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->setModerationMode(!this->getModerationMode());
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"openViewerList",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
2023-11-20 18:59:04 +01:00
|
|
|
this->showChatterList();
|
2021-11-21 18:46:21 +01:00
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"clearMessages",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->clear();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"runCommand",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (arguments.size() == 0)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoHotkeys)
|
|
|
|
<< "runCommand hotkey called without arguments!";
|
|
|
|
return "runCommand hotkey called without arguments!";
|
|
|
|
}
|
|
|
|
QString command = getApp()->commands->execCommand(
|
|
|
|
arguments.at(0).replace('\n', ' '), this->getChannel(), false);
|
|
|
|
this->getChannel()->sendMessage(command);
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"setChannelNotification",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (!this->getChannel()->isTwitchChannel())
|
|
|
|
{
|
|
|
|
return "Cannot set channel notifications for non-twitch "
|
|
|
|
"channel.";
|
|
|
|
}
|
|
|
|
auto mode = 2;
|
|
|
|
// 0 is off
|
|
|
|
// 1 is on
|
|
|
|
// 2 is toggle
|
|
|
|
if (arguments.size() != 0)
|
|
|
|
{
|
|
|
|
auto arg = arguments.at(0);
|
|
|
|
if (arg == "off")
|
|
|
|
{
|
|
|
|
mode = 0;
|
|
|
|
}
|
|
|
|
else if (arg == "on")
|
|
|
|
{
|
|
|
|
mode = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mode = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == 0)
|
|
|
|
{
|
|
|
|
getApp()->notifications->removeChannelNotification(
|
|
|
|
this->getChannel()->getName(), Platform::Twitch);
|
|
|
|
}
|
|
|
|
else if (mode == 1)
|
|
|
|
{
|
|
|
|
getApp()->notifications->addChannelNotification(
|
|
|
|
this->getChannel()->getName(), Platform::Twitch);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
getApp()->notifications->updateChannelNotification(
|
|
|
|
this->getChannel()->getName(), Platform::Twitch);
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
};
|
|
|
|
|
|
|
|
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
|
|
|
|
HotkeyCategory::Split, actions, this);
|
2017-01-01 02:30:42 +01:00
|
|
|
}
|
|
|
|
|
2017-11-12 17:21:50 +01:00
|
|
|
Split::~Split()
|
2017-01-01 02:30:42 +01:00
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
this->usermodeChangedConnection_.disconnect();
|
|
|
|
this->roomModeChangedConnection_.disconnect();
|
|
|
|
this->channelIDChangedConnection_.disconnect();
|
|
|
|
this->indirectChannelChangedConnection_.disconnect();
|
2016-12-29 17:31:07 +01:00
|
|
|
}
|
|
|
|
|
2018-05-10 19:50:31 +02:00
|
|
|
ChannelView &Split::getChannelView()
|
|
|
|
{
|
2018-08-11 22:23:06 +02:00
|
|
|
return *this->view_;
|
2018-05-10 19:50:31 +02:00
|
|
|
}
|
|
|
|
|
2021-07-24 12:01:50 +02:00
|
|
|
SplitInput &Split::getInput()
|
|
|
|
{
|
|
|
|
return *this->input_;
|
|
|
|
}
|
|
|
|
|
2020-12-13 12:16:08 +01:00
|
|
|
void Split::updateInputPlaceholder()
|
2020-10-31 18:17:43 +01:00
|
|
|
{
|
2020-11-05 09:06:34 +01:00
|
|
|
if (!this->getChannel()->isTwitchChannel())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-31 18:17:43 +01:00
|
|
|
auto user = getApp()->accounts->twitch.getCurrent();
|
|
|
|
QString placeholderText;
|
|
|
|
|
|
|
|
if (user->isAnon())
|
|
|
|
{
|
|
|
|
placeholderText = "Log in to send messages...";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
placeholderText =
|
|
|
|
QString("Send message as %1...")
|
|
|
|
.arg(getApp()->accounts->twitch.getCurrent()->getUserName());
|
|
|
|
}
|
|
|
|
|
|
|
|
this->input_->ui_.textEdit->setPlaceholderText(placeholderText);
|
|
|
|
}
|
|
|
|
|
2021-07-11 13:54:19 +02:00
|
|
|
void Split::joinChannelInNewTab(ChannelPtr channel)
|
|
|
|
{
|
|
|
|
auto &nb = getApp()->windows->getMainWindow().getNotebook();
|
|
|
|
SplitContainer *container = nb.addPage(true);
|
|
|
|
|
|
|
|
Split *split = new Split(container);
|
|
|
|
split->setChannel(channel);
|
2022-12-25 12:09:25 +01:00
|
|
|
container->insertSplit(split);
|
2021-07-11 13:54:19 +02:00
|
|
|
}
|
|
|
|
|
2022-12-31 12:56:47 +01:00
|
|
|
void Split::refreshModerationMode()
|
|
|
|
{
|
|
|
|
this->header_->updateModerationModeIcon();
|
|
|
|
this->view_->queueLayout();
|
|
|
|
}
|
|
|
|
|
2021-07-11 13:54:19 +02:00
|
|
|
void Split::openChannelInBrowserPlayer(ChannelPtr channel)
|
|
|
|
{
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
2021-07-11 13:54:19 +02:00
|
|
|
{
|
|
|
|
QDesktopServices::openUrl(
|
|
|
|
"https://player.twitch.tv/?parent=twitch.tv&channel=" +
|
|
|
|
twitchChannel->getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Split::openChannelInStreamlink(QString channelName)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
openStreamlinkForChannel(channelName);
|
|
|
|
}
|
|
|
|
catch (const Exception &ex)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoWidget)
|
|
|
|
<< "Error in doOpenStreamlink:" << ex.what();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-20 19:54:45 +02:00
|
|
|
IndirectChannel Split::getIndirectChannel()
|
2017-04-12 17:46:44 +02:00
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
return this->channel_;
|
2017-04-12 17:46:44 +02:00
|
|
|
}
|
|
|
|
|
2022-06-26 18:53:09 +02:00
|
|
|
ChannelPtr Split::getChannel() const
|
2017-04-12 17:46:44 +02:00
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
return this->channel_.get();
|
2018-04-20 19:54:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Split::setChannel(IndirectChannel newChannel)
|
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
this->channel_ = newChannel;
|
2018-04-20 22:33:28 +02:00
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->setChannel(newChannel.get());
|
2017-02-02 22:15:09 +01:00
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
this->usermodeChangedConnection_.disconnect();
|
|
|
|
this->roomModeChangedConnection_.disconnect();
|
|
|
|
this->indirectChannelChangedConnection_.disconnect();
|
2018-01-17 18:36:12 +01:00
|
|
|
|
2018-04-20 19:54:45 +02:00
|
|
|
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(newChannel.get().get());
|
2018-01-17 18:36:12 +01:00
|
|
|
|
|
|
|
if (tc != nullptr)
|
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
this->usermodeChangedConnection_ = tc->userStateChanged.connect([this] {
|
2018-08-11 22:23:06 +02:00
|
|
|
this->header_->updateModerationModeIcon();
|
|
|
|
this->header_->updateRoomModes();
|
2018-07-04 19:43:41 +02:00
|
|
|
});
|
2018-05-24 08:58:34 +02:00
|
|
|
|
2020-11-08 12:02:19 +01:00
|
|
|
this->roomModeChangedConnection_ = tc->roomModesChanged.connect([this] {
|
|
|
|
this->header_->updateRoomModes();
|
|
|
|
});
|
2018-01-17 18:36:12 +01:00
|
|
|
}
|
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
this->indirectChannelChangedConnection_ =
|
2020-11-08 12:02:19 +01:00
|
|
|
newChannel.getChannelChanged().connect([this] {
|
|
|
|
QTimer::singleShot(0, [this] {
|
|
|
|
this->setChannel(this->channel_);
|
|
|
|
});
|
2018-04-20 19:54:45 +02:00
|
|
|
});
|
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->header_->updateModerationModeIcon();
|
|
|
|
this->header_->updateChannelText();
|
|
|
|
this->header_->updateRoomModes();
|
2018-01-17 18:36:12 +01:00
|
|
|
|
2020-10-10 17:24:53 +02:00
|
|
|
if (newChannel.getType() == Channel::Type::Twitch)
|
|
|
|
{
|
2023-11-20 18:59:04 +01:00
|
|
|
this->header_->setChattersButtonVisible(true);
|
2020-10-10 17:24:53 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-20 18:59:04 +01:00
|
|
|
this->header_->setChattersButtonVisible(false);
|
2020-10-10 17:24:53 +02:00
|
|
|
}
|
|
|
|
|
2023-07-23 12:11:57 +02:00
|
|
|
this->channelSignalHolder_.managedConnect(
|
|
|
|
this->channel_.get()->displayNameChanged, [this] {
|
|
|
|
this->actionRequested.invoke(Action::RefreshTab);
|
|
|
|
});
|
2020-12-06 14:07:33 +01:00
|
|
|
|
2018-04-03 02:55:32 +02:00
|
|
|
this->channelChanged.invoke();
|
2022-07-07 18:52:15 +02:00
|
|
|
this->actionRequested.invoke(Action::RefreshTab);
|
2018-10-07 18:27:40 +02:00
|
|
|
|
|
|
|
// Queue up save because: Split channel changed
|
|
|
|
getApp()->windows->queueSave();
|
2017-04-12 17:46:44 +02:00
|
|
|
}
|
|
|
|
|
2018-01-17 16:52:51 +01:00
|
|
|
void Split::setModerationMode(bool value)
|
|
|
|
{
|
2018-10-21 16:13:26 +02:00
|
|
|
this->moderationMode_ = value;
|
2022-12-31 12:56:47 +01:00
|
|
|
this->refreshModerationMode();
|
2018-01-17 16:52:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Split::getModerationMode() const
|
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
return this->moderationMode_;
|
2018-01-17 16:52:51 +01:00
|
|
|
}
|
|
|
|
|
2018-07-10 18:27:42 +02:00
|
|
|
void Split::insertTextToInput(const QString &text)
|
|
|
|
{
|
2018-08-11 22:23:06 +02:00
|
|
|
this->input_->insertText(text);
|
2018-07-10 18:27:42 +02:00
|
|
|
}
|
|
|
|
|
2018-04-18 09:12:29 +02:00
|
|
|
void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
|
|
|
|
std::function<void(bool)> callback)
|
2017-01-17 00:15:44 +01:00
|
|
|
{
|
2024-01-15 22:30:34 +01:00
|
|
|
if (!this->selectChannelDialog_.isNull())
|
2018-07-06 19:23:47 +02:00
|
|
|
{
|
|
|
|
this->selectChannelDialog_->raise();
|
2018-06-24 11:45:30 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *dialog = new SelectChannelDialog(this);
|
2017-07-31 01:36:42 +02:00
|
|
|
if (!empty)
|
|
|
|
{
|
2018-04-20 22:33:28 +02:00
|
|
|
dialog->setSelectedChannel(this->getIndirectChannel());
|
2017-01-17 00:15:44 +01:00
|
|
|
}
|
2018-04-18 09:12:29 +02:00
|
|
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
2021-10-23 13:22:54 +02:00
|
|
|
dialog->setWindowTitle(dialogTitle);
|
2018-04-18 09:12:29 +02:00
|
|
|
dialog->show();
|
2023-09-16 13:52:51 +02:00
|
|
|
// We can safely ignore this signal connection since the dialog will be closed before
|
|
|
|
// this Split is closed
|
|
|
|
std::ignore = dialog->closed.connect([=, this] {
|
2018-04-18 09:12:29 +02:00
|
|
|
if (dialog->hasSeletedChannel())
|
|
|
|
{
|
|
|
|
this->setChannel(dialog->getSelectedChannel());
|
|
|
|
}
|
2017-07-31 01:36:42 +02:00
|
|
|
|
2018-04-18 09:12:29 +02:00
|
|
|
callback(dialog->hasSeletedChannel());
|
|
|
|
});
|
2018-07-06 19:23:47 +02:00
|
|
|
this->selectChannelDialog_ = dialog;
|
2017-01-17 00:15:44 +01:00
|
|
|
}
|
|
|
|
|
2017-11-12 17:21:50 +01:00
|
|
|
void Split::updateGifEmotes()
|
2017-04-12 17:46:44 +02:00
|
|
|
{
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->queueUpdate();
|
2017-04-12 17:46:44 +02:00
|
|
|
}
|
|
|
|
|
2018-01-23 22:48:33 +01:00
|
|
|
void Split::updateLastReadMessage()
|
|
|
|
{
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->updateLastReadMessage();
|
2018-01-23 22:48:33 +01:00
|
|
|
}
|
|
|
|
|
2017-11-12 17:21:50 +01:00
|
|
|
void Split::paintEvent(QPaintEvent *)
|
2016-12-29 17:31:07 +01:00
|
|
|
{
|
2017-04-12 17:46:44 +02:00
|
|
|
// color the background of the chat
|
2017-01-11 18:52:09 +01:00
|
|
|
QPainter painter(this);
|
2016-12-29 17:31:07 +01:00
|
|
|
|
2018-07-06 17:11:37 +02:00
|
|
|
painter.fillRect(this->rect(), this->theme->splits.background);
|
2016-12-29 17:31:07 +01:00
|
|
|
}
|
2017-01-29 11:38:00 +01:00
|
|
|
|
2018-01-17 01:19:42 +01:00
|
|
|
void Split::mouseMoveEvent(QMouseEvent *event)
|
|
|
|
{
|
2024-01-17 21:05:44 +01:00
|
|
|
(void)event;
|
|
|
|
|
2018-05-31 16:02:20 +02:00
|
|
|
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Split::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
2024-01-17 21:05:44 +01:00
|
|
|
(void)event;
|
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->unsetCursor();
|
2018-05-31 16:02:20 +02:00
|
|
|
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Split::keyReleaseEvent(QKeyEvent *event)
|
|
|
|
{
|
2024-01-17 21:05:44 +01:00
|
|
|
(void)event;
|
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->unsetCursor();
|
2018-05-31 16:02:20 +02:00
|
|
|
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
|
|
|
|
2018-05-08 15:12:04 +02:00
|
|
|
void Split::resizeEvent(QResizeEvent *event)
|
|
|
|
{
|
2018-10-07 18:27:40 +02:00
|
|
|
// Queue up save because: Split resized
|
|
|
|
getApp()->windows->queueSave();
|
|
|
|
|
2018-05-08 15:12:04 +02:00
|
|
|
BaseWidget::resizeEvent(event);
|
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
this->overlay_->setGeometry(this->rect());
|
2018-05-08 15:12:04 +02:00
|
|
|
}
|
|
|
|
|
2023-02-11 18:13:29 +01:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
|
|
void Split::enterEvent(QEnterEvent * /*event*/)
|
|
|
|
#else
|
|
|
|
void Split::enterEvent(QEvent * /*event*/)
|
|
|
|
#endif
|
2018-05-08 15:12:04 +02:00
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
this->isMouseOver_ = true;
|
2018-05-10 23:58:07 +02:00
|
|
|
|
2018-05-31 16:02:20 +02:00
|
|
|
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
2018-05-10 23:58:07 +02:00
|
|
|
|
2018-06-01 14:20:46 +02:00
|
|
|
if (modifierStatus ==
|
|
|
|
showSplitOverlayModifiers /*|| modifierStatus == showAddSplitRegions*/)
|
|
|
|
{
|
2018-07-06 19:23:47 +02:00
|
|
|
this->overlay_->show();
|
2018-05-08 15:12:04 +02:00
|
|
|
}
|
2018-06-01 14:46:41 +02:00
|
|
|
|
2021-04-17 16:15:23 +02:00
|
|
|
this->actionRequested.invoke(Action::ResetMouseStatus);
|
2018-05-08 15:12:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Split::leaveEvent(QEvent *event)
|
|
|
|
{
|
2024-01-17 21:05:44 +01:00
|
|
|
(void)event;
|
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
this->isMouseOver_ = false;
|
2018-05-17 16:39:38 +02:00
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
this->overlay_->hide();
|
2018-05-17 16:39:38 +02:00
|
|
|
|
2018-05-31 16:02:20 +02:00
|
|
|
this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
|
2018-05-08 15:12:04 +02:00
|
|
|
}
|
|
|
|
|
2018-05-31 16:02:20 +02:00
|
|
|
void Split::handleModifiers(Qt::KeyboardModifiers modifiers)
|
2018-01-17 01:19:42 +01:00
|
|
|
{
|
2018-05-10 23:58:07 +02:00
|
|
|
if (modifierStatus != modifiers)
|
|
|
|
{
|
|
|
|
modifierStatus = modifiers;
|
|
|
|
modifierStatusChanged.invoke(modifiers);
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
2018-05-10 19:50:31 +02:00
|
|
|
}
|
|
|
|
|
2018-09-04 21:39:54 +02:00
|
|
|
void Split::setIsTopRightSplit(bool value)
|
|
|
|
{
|
|
|
|
this->isTopRightSplit_ = value;
|
|
|
|
this->header_->setAddButtonVisible(value);
|
|
|
|
}
|
|
|
|
|
2017-06-10 23:53:39 +02:00
|
|
|
/// Slots
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::addSibling()
|
2017-06-10 23:53:39 +02:00
|
|
|
{
|
2021-04-17 16:15:23 +02:00
|
|
|
this->actionRequested.invoke(Action::AppendNewSplit);
|
2017-06-10 23:53:39 +02:00
|
|
|
}
|
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::deleteFromContainer()
|
2017-06-10 23:53:39 +02:00
|
|
|
{
|
2021-04-17 16:15:23 +02:00
|
|
|
this->actionRequested.invoke(Action::Delete);
|
2017-06-10 23:53:39 +02:00
|
|
|
}
|
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::changeChannel()
|
2017-06-10 23:53:39 +02:00
|
|
|
{
|
2018-04-18 09:12:29 +02:00
|
|
|
this->showChangeChannelPopup("Change channel", false, [](bool) {});
|
2018-06-24 11:45:30 +02:00
|
|
|
|
2017-09-15 17:23:49 +02:00
|
|
|
auto popup = this->findChildren<QDockWidget *>();
|
2017-09-23 19:23:10 +02:00
|
|
|
if (popup.size() && popup.at(0)->isVisible() && !popup.at(0)->isFloating())
|
|
|
|
{
|
2017-09-12 22:10:30 +02:00
|
|
|
popup.at(0)->hide();
|
2023-11-20 18:59:04 +01:00
|
|
|
showChatterList();
|
2017-09-12 22:10:30 +02:00
|
|
|
}
|
2017-06-10 23:53:39 +02:00
|
|
|
}
|
|
|
|
|
2018-10-09 18:28:40 +02:00
|
|
|
void Split::explainMoving()
|
|
|
|
{
|
|
|
|
showTutorialVideo(this, ":/examples/moving.gif", "Moving",
|
|
|
|
"Hold <Ctrl+Alt> to move splits.\n\nExample:");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Split::explainSplitting()
|
|
|
|
{
|
|
|
|
showTutorialVideo(this, ":/examples/splitting.gif", "Splitting",
|
|
|
|
"Hold <Ctrl+Alt> to add new splits.\n\nExample:");
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::popup()
|
2017-06-11 09:11:55 +02:00
|
|
|
{
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *app = getApp();
|
2018-08-11 22:23:06 +02:00
|
|
|
Window &window = app->windows->createWindow(WindowType::Popup);
|
2017-11-12 17:21:50 +01:00
|
|
|
|
2018-04-06 23:31:34 +02:00
|
|
|
Split *split = new Split(static_cast<SplitContainer *>(
|
|
|
|
window.getNotebook().getOrAddSelectedPage()));
|
2017-11-12 17:21:50 +01:00
|
|
|
|
2018-04-20 22:54:09 +02:00
|
|
|
split->setChannel(this->getIndirectChannel());
|
2021-08-07 00:04:09 +02:00
|
|
|
split->setModerationMode(this->getModerationMode());
|
|
|
|
split->setFilters(this->getFilters());
|
2017-11-12 17:21:50 +01:00
|
|
|
|
2022-12-25 12:09:25 +01:00
|
|
|
window.getNotebook().getOrAddSelectedPage()->insertSplit(split);
|
2017-11-12 17:21:50 +01:00
|
|
|
window.show();
|
2017-06-11 09:11:55 +02:00
|
|
|
}
|
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::clear()
|
2017-06-11 09:11:55 +02:00
|
|
|
{
|
2018-08-11 22:23:06 +02:00
|
|
|
this->view_->clearMessages();
|
2017-06-11 09:11:55 +02:00
|
|
|
}
|
|
|
|
|
2018-08-02 14:23:27 +02:00
|
|
|
void Split::openInBrowser()
|
2017-06-11 09:11:55 +02:00
|
|
|
{
|
2018-08-02 14:23:27 +02:00
|
|
|
auto channel = this->getChannel();
|
2018-01-17 03:18:47 +01:00
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
2018-08-02 14:23:27 +02:00
|
|
|
{
|
|
|
|
QDesktopServices::openUrl("https://twitch.tv/" +
|
|
|
|
twitchChannel->getName());
|
2018-01-17 03:18:47 +01:00
|
|
|
}
|
2017-06-11 09:11:55 +02:00
|
|
|
}
|
|
|
|
|
2020-08-08 15:17:51 +02:00
|
|
|
void Split::openWhispersInBrowser()
|
|
|
|
{
|
|
|
|
auto userName = getApp()->accounts->twitch.getCurrent()->getUserName();
|
|
|
|
QDesktopServices::openUrl("https://twitch.tv/popout/moderator/" + userName +
|
|
|
|
"/whispers");
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:35:54 +02:00
|
|
|
void Split::openBrowserPlayer()
|
2017-06-11 09:11:55 +02:00
|
|
|
{
|
2021-07-11 13:54:19 +02:00
|
|
|
this->openChannelInBrowserPlayer(this->getChannel());
|
2017-06-11 09:11:55 +02:00
|
|
|
}
|
|
|
|
|
2021-01-02 17:25:27 +01:00
|
|
|
void Split::openModViewInBrowser()
|
|
|
|
{
|
|
|
|
auto channel = this->getChannel();
|
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
2021-01-02 17:25:27 +01:00
|
|
|
{
|
|
|
|
QDesktopServices::openUrl("https://twitch.tv/moderator/" +
|
|
|
|
twitchChannel->getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:23:27 +02:00
|
|
|
void Split::openInStreamlink()
|
2017-08-12 14:44:27 +02:00
|
|
|
{
|
2021-07-11 13:54:19 +02:00
|
|
|
this->openChannelInStreamlink(this->getChannel()->getName());
|
2017-08-12 14:44:27 +02:00
|
|
|
}
|
|
|
|
|
2020-04-11 11:43:35 +02:00
|
|
|
void Split::openWithCustomScheme()
|
|
|
|
{
|
2020-08-22 15:01:16 +02:00
|
|
|
QString scheme = getSettings()->customURIScheme.getValue();
|
2020-04-11 11:43:35 +02:00
|
|
|
if (scheme.isEmpty())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2020-08-22 15:01:16 +02:00
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *const channel = this->getChannel().get();
|
2020-04-11 11:43:35 +02:00
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *const twitchChannel = dynamic_cast<TwitchChannel *>(channel))
|
2020-04-11 11:43:35 +02:00
|
|
|
{
|
|
|
|
QDesktopServices::openUrl(QString("%1https://twitch.tv/%2")
|
|
|
|
.arg(scheme)
|
|
|
|
.arg(twitchChannel->getName()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-20 18:59:04 +01:00
|
|
|
void Split::showChatterList()
|
2017-09-11 22:37:39 +02:00
|
|
|
{
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *chatterDock = new QDockWidget(
|
|
|
|
"Chatter List - " + this->getChannel()->getName(), this);
|
|
|
|
chatterDock->setAllowedAreas(Qt::LeftDockWidgetArea);
|
|
|
|
chatterDock->setFeatures(QDockWidget::DockWidgetVerticalTitleBar |
|
|
|
|
QDockWidget::DockWidgetClosable |
|
|
|
|
QDockWidget::DockWidgetFloatable);
|
|
|
|
chatterDock->resize(
|
2017-09-15 17:23:49 +02:00
|
|
|
0.5 * this->width(),
|
2018-08-11 22:23:06 +02:00
|
|
|
this->height() - this->header_->height() - this->input_->height());
|
2023-11-20 18:59:04 +01:00
|
|
|
chatterDock->move(0, this->header_->height());
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *multiWidget = new QWidget(chatterDock);
|
2023-06-10 12:55:47 +02:00
|
|
|
auto *dockVbox = new QVBoxLayout();
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *searchBar = new QLineEdit(chatterDock);
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *chattersList = new QListWidget();
|
|
|
|
auto *resultList = new QListWidget();
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
auto channel = this->getChannel();
|
|
|
|
if (!channel)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoWidget)
|
2023-11-20 18:59:04 +01:00
|
|
|
<< "Chatter list opened when no channel was defined";
|
2023-03-27 18:26:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
|
|
|
|
|
|
|
if (twitchChannel == nullptr)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoWidget)
|
2023-11-20 18:59:04 +01:00
|
|
|
<< "Chatter list opened in a non-Twitch channel";
|
2023-03-27 18:26:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *loadingLabel = new QLabel("Loading...");
|
|
|
|
searchBar->setPlaceholderText("Search User...");
|
|
|
|
|
2020-10-17 15:00:10 +02:00
|
|
|
auto formatListItemText = [](QString text) {
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *item = new QListWidgetItem();
|
2020-10-17 15:00:10 +02:00
|
|
|
item->setText(text);
|
|
|
|
item->setFont(getApp()->fonts->getFont(FontStyle::ChatMedium, 1.0));
|
|
|
|
return item;
|
|
|
|
};
|
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
auto addLabel = [this, formatListItemText, chattersList](QString label) {
|
2023-11-20 18:59:04 +01:00
|
|
|
auto *formattedLabel = formatListItemText(label);
|
2023-03-27 18:26:08 +02:00
|
|
|
formattedLabel->setForeground(this->theme->accent);
|
|
|
|
chattersList->addItem(formattedLabel);
|
|
|
|
};
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
auto addUserList = [=](QStringList users, QString label) {
|
|
|
|
if (users.isEmpty())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
addLabel(QString("%1 (%2)").arg(label, localizeNumbers(users.size())));
|
|
|
|
|
|
|
|
for (const auto &user : users)
|
|
|
|
{
|
|
|
|
chattersList->addItem(formatListItemText(user));
|
|
|
|
}
|
|
|
|
chattersList->addItem(new QListWidgetItem());
|
|
|
|
};
|
2022-05-29 13:06:01 +02:00
|
|
|
|
|
|
|
auto performListSearch = [=]() {
|
|
|
|
auto query = searchBar->text();
|
|
|
|
if (query.isEmpty())
|
|
|
|
{
|
|
|
|
resultList->hide();
|
|
|
|
chattersList->show();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto results = chattersList->findItems(query, Qt::MatchContains);
|
|
|
|
chattersList->hide();
|
|
|
|
resultList->clear();
|
|
|
|
for (auto &item : results)
|
|
|
|
{
|
|
|
|
if (!item->text().contains("("))
|
|
|
|
{
|
|
|
|
resultList->addItem(formatListItemText(item->text()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resultList->show();
|
|
|
|
};
|
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
auto loadChatters = [=](auto modList, auto vipList, bool isBroadcaster) {
|
|
|
|
getHelix()->getChatters(
|
|
|
|
twitchChannel->roomId(),
|
|
|
|
getApp()->accounts->twitch.getCurrent()->getUserId(), 50000,
|
|
|
|
[=](auto chatters) {
|
|
|
|
auto broadcaster = channel->getName().toLower();
|
|
|
|
QStringList chatterList;
|
|
|
|
QStringList modChatters;
|
|
|
|
QStringList vipChatters;
|
|
|
|
|
|
|
|
bool addedBroadcaster = false;
|
|
|
|
for (auto chatter : chatters.chatters)
|
|
|
|
{
|
|
|
|
chatter = chatter.toLower();
|
|
|
|
|
|
|
|
if (!addedBroadcaster && chatter == broadcaster)
|
|
|
|
{
|
|
|
|
addedBroadcaster = true;
|
|
|
|
addLabel("Broadcaster");
|
|
|
|
chattersList->addItem(broadcaster);
|
|
|
|
chattersList->addItem(new QListWidgetItem());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (modList.contains(chatter))
|
|
|
|
{
|
|
|
|
modChatters.append(chatter);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vipList.contains(chatter))
|
|
|
|
{
|
|
|
|
vipChatters.append(chatter);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
chatterList.append(chatter);
|
|
|
|
}
|
2022-05-29 13:06:01 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
modChatters.sort();
|
|
|
|
vipChatters.sort();
|
|
|
|
chatterList.sort();
|
2019-08-20 21:50:36 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
if (isBroadcaster)
|
|
|
|
{
|
|
|
|
addUserList(modChatters, QString("Moderators"));
|
|
|
|
addUserList(vipChatters, QString("VIPs"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
addLabel("Moderators");
|
|
|
|
chattersList->addItem(
|
|
|
|
"Moderators cannot check who is a moderator");
|
|
|
|
chattersList->addItem(new QListWidgetItem());
|
|
|
|
|
|
|
|
addLabel("VIPs");
|
|
|
|
chattersList->addItem(
|
|
|
|
"Moderators cannot check who is a VIP");
|
|
|
|
chattersList->addItem(new QListWidgetItem());
|
|
|
|
}
|
2021-10-24 17:27:18 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
addUserList(chatterList, QString("Chatters"));
|
|
|
|
|
|
|
|
loadingLabel->hide();
|
|
|
|
performListSearch();
|
|
|
|
},
|
|
|
|
[chattersList, formatListItemText](auto error, auto message) {
|
|
|
|
auto errorMessage = formatChattersError(error, message);
|
|
|
|
chattersList->addItem(formatListItemText(errorMessage));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
QObject::connect(searchBar, &QLineEdit::textEdited, this,
|
|
|
|
performListSearch);
|
|
|
|
|
|
|
|
// Only broadcaster can get vips, mods can get chatters
|
|
|
|
if (channel->isBroadcaster())
|
|
|
|
{
|
|
|
|
// Add moderators
|
|
|
|
getHelix()->getModerators(
|
|
|
|
twitchChannel->roomId(), 1000,
|
|
|
|
[=](auto mods) {
|
|
|
|
QSet<QString> modList;
|
|
|
|
for (const auto &mod : mods)
|
2020-10-17 15:00:10 +02:00
|
|
|
{
|
2023-03-27 18:26:08 +02:00
|
|
|
modList.insert(mod.userName.toLower());
|
2020-10-17 15:00:10 +02:00
|
|
|
}
|
2018-07-07 13:08:57 +02:00
|
|
|
|
2023-03-27 18:26:08 +02:00
|
|
|
// Add vips
|
|
|
|
getHelix()->getChannelVIPs(
|
|
|
|
twitchChannel->roomId(),
|
|
|
|
[=](auto vips) {
|
|
|
|
QSet<QString> vipList;
|
|
|
|
for (const auto &vip : vips)
|
|
|
|
{
|
|
|
|
vipList.insert(vip.userName.toLower());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add chatters
|
|
|
|
loadChatters(modList, vipList, true);
|
|
|
|
},
|
|
|
|
[chattersList, formatListItemText](auto error,
|
|
|
|
auto message) {
|
|
|
|
auto errorMessage = formatVIPListError(error, message);
|
|
|
|
chattersList->addItem(formatListItemText(errorMessage));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
[chattersList, formatListItemText](auto error, auto message) {
|
|
|
|
auto errorMessage = formatModsError(error, message);
|
|
|
|
chattersList->addItem(formatListItemText(errorMessage));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (channel->hasModRights())
|
|
|
|
{
|
|
|
|
QSet<QString> modList;
|
|
|
|
QSet<QString> vipList;
|
|
|
|
loadChatters(modList, vipList, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chattersList->addItem(
|
|
|
|
formatListItemText("Due to Twitch restrictions, this feature is "
|
|
|
|
"only \navailable for moderators."));
|
|
|
|
chattersList->addItem(
|
2023-11-20 18:59:04 +01:00
|
|
|
formatListItemText("If you would like to see the Chatter list, you "
|
2023-03-27 18:26:08 +02:00
|
|
|
"must \nuse the Twitch website."));
|
|
|
|
loadingLabel->hide();
|
|
|
|
}
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2023-11-20 18:59:04 +01:00
|
|
|
QObject::connect(chatterDock, &QDockWidget::topLevelChanged, this, [=]() {
|
|
|
|
chatterDock->setMinimumWidth(300);
|
2020-11-08 12:02:19 +01:00
|
|
|
});
|
2017-09-11 22:37:39 +02:00
|
|
|
|
2022-05-29 13:06:01 +02:00
|
|
|
auto listDoubleClick = [this](const QModelIndex &index) {
|
|
|
|
const auto itemText = index.data().toString();
|
|
|
|
|
2022-04-24 17:54:37 +02:00
|
|
|
// if the list item contains a parentheses it means that
|
|
|
|
// it's a category label so don't show a usercard
|
2022-05-29 13:06:01 +02:00
|
|
|
if (!itemText.contains("(") && !itemText.isEmpty())
|
2018-10-06 12:13:14 +02:00
|
|
|
{
|
2022-05-29 13:06:01 +02:00
|
|
|
this->view_->showUserInfoPopup(itemText);
|
2017-09-12 22:10:30 +02:00
|
|
|
}
|
2018-10-06 12:13:14 +02:00
|
|
|
};
|
|
|
|
|
2022-05-29 13:06:01 +02:00
|
|
|
QObject::connect(chattersList, &QListWidget::doubleClicked, this,
|
|
|
|
listDoubleClick);
|
2017-09-12 22:10:30 +02:00
|
|
|
|
2022-05-29 13:06:01 +02:00
|
|
|
QObject::connect(resultList, &QListWidget::doubleClicked, this,
|
|
|
|
listDoubleClick);
|
2017-09-12 22:10:30 +02:00
|
|
|
|
2022-05-14 13:44:18 +02:00
|
|
|
HotkeyController::HotkeyMap actions{
|
|
|
|
{"delete",
|
2023-11-20 18:59:04 +01:00
|
|
|
[chatterDock](std::vector<QString>) -> QString {
|
|
|
|
chatterDock->close();
|
2022-05-14 13:44:18 +02:00
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"accept", nullptr},
|
|
|
|
{"reject", nullptr},
|
|
|
|
{"scrollPage", nullptr},
|
|
|
|
{"openTab", nullptr},
|
|
|
|
{"search",
|
|
|
|
[searchBar](std::vector<QString>) -> QString {
|
|
|
|
searchBar->setFocus();
|
|
|
|
searchBar->selectAll();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
};
|
|
|
|
|
|
|
|
getApp()->hotkeys->shortcutsForCategory(HotkeyCategory::PopupWindow,
|
2023-11-20 18:59:04 +01:00
|
|
|
actions, chatterDock);
|
2022-05-14 13:44:18 +02:00
|
|
|
|
2017-09-11 22:37:39 +02:00
|
|
|
dockVbox->addWidget(searchBar);
|
|
|
|
dockVbox->addWidget(loadingLabel);
|
|
|
|
dockVbox->addWidget(chattersList);
|
|
|
|
dockVbox->addWidget(resultList);
|
|
|
|
resultList->hide();
|
|
|
|
|
2018-07-06 17:11:37 +02:00
|
|
|
multiWidget->setStyleSheet(this->theme->splits.input.styleSheet);
|
2017-09-11 22:37:39 +02:00
|
|
|
multiWidget->setLayout(dockVbox);
|
2023-11-20 18:59:04 +01:00
|
|
|
chatterDock->setWidget(multiWidget);
|
|
|
|
chatterDock->setFloating(true);
|
2023-12-02 12:56:03 +01:00
|
|
|
widgets::showAndMoveWindowTo(
|
|
|
|
chatterDock, this->mapToGlobal(QPoint{0, this->header_->height()}),
|
|
|
|
widgets::BoundsChecking::CursorPosition);
|
2023-11-20 18:59:04 +01:00
|
|
|
chatterDock->activateWindow();
|
2017-08-12 14:44:27 +02:00
|
|
|
}
|
|
|
|
|
2019-07-16 21:59:04 +02:00
|
|
|
void Split::openSubPage()
|
|
|
|
{
|
|
|
|
ChannelPtr channel = this->getChannel();
|
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
2019-07-16 21:59:04 +02:00
|
|
|
{
|
|
|
|
QDesktopServices::openUrl(twitchChannel->subscriptionUrl());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 15:16:56 +02:00
|
|
|
void Split::setFiltersDialog()
|
|
|
|
{
|
|
|
|
SelectChannelFiltersDialog d(this->getFilters(), this);
|
|
|
|
d.setWindowTitle("Select filters");
|
|
|
|
|
|
|
|
if (d.exec() == QDialog::Accepted)
|
|
|
|
{
|
|
|
|
this->setFilters(d.getSelection());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Split::setFilters(const QList<QUuid> ids)
|
|
|
|
{
|
|
|
|
this->view_->setFilters(ids);
|
|
|
|
this->header_->updateChannelText();
|
|
|
|
}
|
|
|
|
|
|
|
|
const QList<QUuid> Split::getFilters() const
|
|
|
|
{
|
|
|
|
return this->view_->getFilterIds();
|
|
|
|
}
|
|
|
|
|
2022-05-23 02:47:16 +02:00
|
|
|
void Split::showSearch(bool singleChannel)
|
2018-01-05 13:42:23 +01:00
|
|
|
{
|
2022-07-31 12:45:25 +02:00
|
|
|
auto *popup = new SearchPopup(this, this);
|
2020-08-22 22:35:07 +02:00
|
|
|
popup->setAttribute(Qt::WA_DeleteOnClose);
|
2022-05-23 02:47:16 +02:00
|
|
|
|
|
|
|
if (singleChannel)
|
|
|
|
{
|
|
|
|
popup->addChannel(this->getChannelView());
|
|
|
|
popup->show();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass every ChannelView for every Split across the app to the search popup
|
|
|
|
auto ¬ebook = getApp()->windows->getMainWindow().getNotebook();
|
|
|
|
for (int i = 0; i < notebook.getPageCount(); ++i)
|
|
|
|
{
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *container = dynamic_cast<SplitContainer *>(notebook.getPageAt(i));
|
|
|
|
for (auto *split : container->getSplits())
|
2022-05-23 02:47:16 +02:00
|
|
|
{
|
2023-12-03 23:07:30 +01:00
|
|
|
if (split->channel_.getType() != Channel::Type::TwitchAutomod)
|
|
|
|
{
|
|
|
|
popup->addChannel(split->getChannelView());
|
|
|
|
}
|
2022-05-23 02:47:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-05 13:42:23 +01:00
|
|
|
popup->show();
|
|
|
|
}
|
|
|
|
|
2018-10-22 22:46:55 +02:00
|
|
|
void Split::reloadChannelAndSubscriberEmotes()
|
|
|
|
{
|
|
|
|
auto channel = this->getChannel();
|
2021-08-08 12:59:28 +02:00
|
|
|
getApp()->accounts->twitch.getCurrent()->loadEmotes(channel);
|
2018-10-22 22:46:55 +02:00
|
|
|
|
2024-01-14 17:54:52 +01:00
|
|
|
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
2019-08-27 20:45:55 +02:00
|
|
|
{
|
2020-05-16 12:43:44 +02:00
|
|
|
twitchChannel->refreshBTTVChannelEmotes(true);
|
|
|
|
twitchChannel->refreshFFZChannelEmotes(true);
|
2022-10-16 13:22:17 +02:00
|
|
|
twitchChannel->refreshSevenTVChannelEmotes(true);
|
2019-08-27 20:45:55 +02:00
|
|
|
}
|
2018-10-22 22:46:55 +02:00
|
|
|
}
|
|
|
|
|
2020-11-22 14:28:07 +01:00
|
|
|
void Split::reconnect()
|
|
|
|
{
|
|
|
|
this->getChannel()->reconnect();
|
|
|
|
}
|
|
|
|
|
2020-02-08 16:41:01 +01:00
|
|
|
void Split::dragEnterEvent(QDragEnterEvent *event)
|
|
|
|
{
|
2020-08-22 18:33:37 +02:00
|
|
|
if (getSettings()->imageUploaderEnabled &&
|
|
|
|
(event->mimeData()->hasImage() || event->mimeData()->hasUrls()))
|
2020-02-08 16:41:01 +01:00
|
|
|
{
|
|
|
|
event->acceptProposedAction();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
BaseWidget::dragEnterEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Split::dropEvent(QDropEvent *event)
|
|
|
|
{
|
2020-08-22 18:33:37 +02:00
|
|
|
if (getSettings()->imageUploaderEnabled &&
|
|
|
|
(event->mimeData()->hasImage() || event->mimeData()->hasUrls()))
|
2020-02-08 16:41:01 +01:00
|
|
|
{
|
|
|
|
this->input_->ui_.textEdit->imagePasted.invoke(event->mimeData());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
BaseWidget::dropEvent(event);
|
|
|
|
}
|
|
|
|
}
|
2017-09-17 04:36:48 +02:00
|
|
|
template <typename Iter, typename RandomGenerator>
|
|
|
|
static Iter select_randomly(Iter start, Iter end, RandomGenerator &g)
|
|
|
|
{
|
|
|
|
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
|
|
|
|
std::advance(start, dis(g));
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Iter>
|
|
|
|
static Iter select_randomly(Iter start, Iter end)
|
|
|
|
{
|
|
|
|
static std::random_device rd;
|
|
|
|
static std::mt19937 gen(rd());
|
|
|
|
return select_randomly(start, end, gen);
|
|
|
|
}
|
|
|
|
|
2018-01-17 01:19:42 +01:00
|
|
|
void Split::drag()
|
|
|
|
{
|
2022-12-25 12:09:25 +01:00
|
|
|
auto *container = dynamic_cast<SplitContainer *>(this->parentWidget());
|
|
|
|
if (!container)
|
2018-08-02 14:23:27 +02:00
|
|
|
{
|
2022-12-25 12:09:25 +01:00
|
|
|
qCWarning(chatterinoWidget)
|
|
|
|
<< "Attempted to initiate split drag without a container parent";
|
|
|
|
return;
|
|
|
|
}
|
2018-01-17 01:19:42 +01:00
|
|
|
|
2022-12-25 12:09:25 +01:00
|
|
|
startDraggingSplit();
|
2018-01-17 01:19:42 +01:00
|
|
|
|
2022-12-25 12:09:25 +01:00
|
|
|
auto originalLocation = container->releaseSplit(this);
|
2024-01-14 17:54:52 +01:00
|
|
|
auto *drag = new QDrag(this);
|
|
|
|
auto *mimeData = new QMimeData;
|
2018-01-17 01:19:42 +01:00
|
|
|
|
2022-12-25 12:09:25 +01:00
|
|
|
mimeData->setData("chatterino/split", "xD");
|
|
|
|
drag->setMimeData(mimeData);
|
2018-01-17 01:19:42 +01:00
|
|
|
|
2022-12-25 12:09:25 +01:00
|
|
|
// drag->exec is a blocking action
|
|
|
|
if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction)
|
|
|
|
{
|
|
|
|
// The split wasn't dropped in a valid spot, return it to its original position
|
|
|
|
container->insertSplit(this, {.position = originalLocation});
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
2022-12-25 12:09:25 +01:00
|
|
|
|
|
|
|
stopDraggingSplit();
|
2018-01-17 01:19:42 +01:00
|
|
|
}
|
2018-03-31 13:44:15 +02:00
|
|
|
|
2023-11-05 17:25:26 +01:00
|
|
|
void Split::setInputReply(const MessagePtr &reply)
|
2022-07-31 12:45:25 +02:00
|
|
|
{
|
|
|
|
this->input_->setReply(reply);
|
|
|
|
}
|
|
|
|
|
2017-04-14 17:52:22 +02:00
|
|
|
} // namespace chatterino
|
2022-06-26 18:53:09 +02:00
|
|
|
|
|
|
|
QDebug operator<<(QDebug dbg, const chatterino::Split &split)
|
|
|
|
{
|
|
|
|
auto channel = split.getChannel();
|
|
|
|
if (channel)
|
|
|
|
{
|
|
|
|
dbg.nospace() << "Split(" << (void *)&split
|
|
|
|
<< ", channel:" << channel->getName() << ")";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dbg.nospace() << "Split(" << (void *)&split << ", no channel)";
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbg;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDebug operator<<(QDebug dbg, const chatterino::Split *split)
|
|
|
|
{
|
|
|
|
if (split != nullptr)
|
|
|
|
{
|
|
|
|
return operator<<(dbg, *split);
|
|
|
|
}
|
|
|
|
|
|
|
|
dbg.nospace() << "Split(nullptr)";
|
|
|
|
|
|
|
|
return dbg;
|
|
|
|
}
|