2019-09-08 22:27:57 +02:00
|
|
|
#include "UserInfoPopup.hpp"
|
|
|
|
|
|
|
|
#include "Application.hpp"
|
|
|
|
#include "common/Channel.hpp"
|
|
|
|
#include "common/NetworkRequest.hpp"
|
|
|
|
#include "controllers/accounts/AccountController.hpp"
|
2020-02-23 21:18:40 +01:00
|
|
|
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
2020-06-21 14:15:14 +02:00
|
|
|
#include "messages/Message.hpp"
|
2020-10-04 18:32:52 +02:00
|
|
|
#include "providers/IvrApi.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "providers/twitch/TwitchChannel.hpp"
|
2020-03-14 12:13:57 +01:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
|
|
|
#include "providers/twitch/api/Kraken.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "singletons/Resources.hpp"
|
2020-02-23 22:15:13 +01:00
|
|
|
#include "singletons/Settings.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "util/LayoutCreator.hpp"
|
|
|
|
#include "util/PostToThread.hpp"
|
2020-06-14 12:26:50 +02:00
|
|
|
#include "util/Shortcut.hpp"
|
2020-07-12 22:44:33 +02:00
|
|
|
#include "util/StreamerMode.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "widgets/Label.hpp"
|
2020-06-21 14:15:14 +02:00
|
|
|
#include "widgets/helper/ChannelView.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "widgets/helper/EffectLabel.hpp"
|
|
|
|
#include "widgets/helper/Line.hpp"
|
|
|
|
|
|
|
|
#include <QCheckBox>
|
|
|
|
#include <QDesktopServices>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
const QString TEXT_VIEWS("Views: %1");
|
|
|
|
const QString TEXT_FOLLOWERS("Followers: %1");
|
|
|
|
const QString TEXT_CREATED("Created: %1");
|
2020-06-21 14:15:14 +02:00
|
|
|
const QString TEXT_TITLE("%1's Usercard");
|
2019-09-08 22:27:57 +02:00
|
|
|
#define TEXT_USER_ID "ID: "
|
2020-01-25 12:59:31 +01:00
|
|
|
#define TEXT_UNAVAILABLE "(not available)"
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
namespace chatterino {
|
|
|
|
namespace {
|
2019-10-01 22:38:21 +02:00
|
|
|
Label *addCopyableLabel(LayoutCreator<QHBoxLayout> box)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2019-10-01 22:38:21 +02:00
|
|
|
auto label = box.emplace<Label>();
|
2019-09-08 22:27:57 +02:00
|
|
|
auto button = box.emplace<Button>();
|
2019-09-22 10:53:39 +02:00
|
|
|
button->setPixmap(getResources().buttons.copyDark);
|
2019-09-08 22:27:57 +02:00
|
|
|
button->setScaleIndependantSize(18, 18);
|
|
|
|
button->setDim(Button::Dim::Lots);
|
2019-10-01 22:38:21 +02:00
|
|
|
QObject::connect(
|
|
|
|
button.getElement(), &Button::leftClicked,
|
|
|
|
[label = label.getElement()] {
|
|
|
|
auto copyText = label->property("copy-text").toString();
|
|
|
|
|
|
|
|
qApp->clipboard()->setText(copyText.isEmpty() ? label->getText()
|
|
|
|
: copyText);
|
|
|
|
});
|
|
|
|
|
|
|
|
return label.getElement();
|
2019-09-08 22:27:57 +02:00
|
|
|
};
|
2020-09-26 16:59:40 +02:00
|
|
|
|
|
|
|
bool checkMessageUserName(const QString &userName, MessagePtr message)
|
|
|
|
{
|
|
|
|
if (message->flags.has(MessageFlag::Whisper))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool isSubscription = message->flags.has(MessageFlag::Subscription) &&
|
|
|
|
message->loginName.isEmpty() &&
|
|
|
|
message->messageText.split(" ").at(0).compare(
|
|
|
|
userName, Qt::CaseInsensitive) == 0;
|
|
|
|
|
|
|
|
bool isModAction =
|
|
|
|
message->timeoutUser.compare(userName, Qt::CaseInsensitive) == 0;
|
|
|
|
bool isSelectedUser =
|
|
|
|
message->loginName.compare(userName, Qt::CaseInsensitive) == 0;
|
|
|
|
|
|
|
|
return (isSubscription || isModAction || isSelectedUser);
|
|
|
|
}
|
|
|
|
|
2020-06-21 14:15:14 +02:00
|
|
|
ChannelPtr filterMessages(const QString &userName, ChannelPtr channel)
|
|
|
|
{
|
|
|
|
LimitedQueueSnapshot<MessagePtr> snapshot =
|
|
|
|
channel->getMessageSnapshot();
|
|
|
|
|
|
|
|
ChannelPtr channelPtr(
|
|
|
|
new Channel(channel->getName(), Channel::Type::None));
|
|
|
|
|
|
|
|
for (size_t i = 0; i < snapshot.size(); i++)
|
|
|
|
{
|
|
|
|
MessagePtr message = snapshot[i];
|
2020-09-26 16:59:40 +02:00
|
|
|
if (checkMessageUserName(userName, message))
|
2020-06-21 14:15:14 +02:00
|
|
|
{
|
|
|
|
channelPtr->addMessage(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return channelPtr;
|
|
|
|
};
|
2020-09-26 14:54:44 +02:00
|
|
|
|
|
|
|
const auto borderColor = QColor(255, 255, 255, 80);
|
|
|
|
|
|
|
|
int calculateTimeoutDuration(TimeoutButton timeout)
|
|
|
|
{
|
|
|
|
static const QMap<QString, int> durations{
|
|
|
|
{"s", 1}, {"m", 60}, {"h", 3600}, {"d", 86400}, {"w", 604800},
|
|
|
|
};
|
|
|
|
return timeout.second * durations[timeout.first];
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
} // namespace
|
|
|
|
|
2020-08-13 17:17:53 +02:00
|
|
|
UserInfoPopup::UserInfoPopup(bool closeAutomatically)
|
|
|
|
: BaseWindow(
|
|
|
|
closeAutomatically
|
|
|
|
? FlagsEnum<BaseWindow::Flags>{BaseWindow::EnableCustomFrame,
|
|
|
|
BaseWindow::Frameless,
|
|
|
|
BaseWindow::FramelessDraggable}
|
|
|
|
: BaseWindow::EnableCustomFrame)
|
2019-09-08 22:27:57 +02:00
|
|
|
, hack_(new bool)
|
|
|
|
{
|
2020-06-21 14:15:14 +02:00
|
|
|
this->setWindowTitle("Usercard");
|
2019-09-08 22:27:57 +02:00
|
|
|
this->setStayInScreenRect(true);
|
|
|
|
|
2020-08-13 17:17:53 +02:00
|
|
|
if (closeAutomatically)
|
|
|
|
this->setActionOnFocusLoss(BaseWindow::Delete);
|
2020-09-26 18:07:13 +02:00
|
|
|
else
|
|
|
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
2020-08-13 17:17:53 +02:00
|
|
|
|
2020-06-14 12:26:50 +02:00
|
|
|
// Close the popup when Escape is pressed
|
|
|
|
createWindowShortcut(this, "Escape", [this] { this->deleteLater(); });
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
|
|
|
|
.setLayoutType<QVBoxLayout>();
|
|
|
|
|
|
|
|
// first line
|
|
|
|
auto head = layout.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
{
|
|
|
|
// avatar
|
|
|
|
auto avatar =
|
|
|
|
head.emplace<Button>(nullptr).assign(&this->ui_.avatarButton);
|
|
|
|
avatar->setScaleIndependantSize(100, 100);
|
|
|
|
avatar->setDim(Button::Dim::None);
|
|
|
|
QObject::connect(avatar.getElement(), &Button::leftClicked, [this] {
|
|
|
|
QDesktopServices::openUrl(
|
|
|
|
QUrl("https://twitch.tv/" + this->userName_.toLower()));
|
|
|
|
});
|
|
|
|
|
|
|
|
auto vbox = head.emplace<QVBoxLayout>();
|
|
|
|
{
|
2020-10-04 18:32:52 +02:00
|
|
|
// items on the right
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
|
|
|
auto box = vbox.emplace<QHBoxLayout>()
|
|
|
|
.withoutMargin()
|
|
|
|
.withoutSpacing();
|
2019-10-01 22:38:21 +02:00
|
|
|
this->ui_.nameLabel = addCopyableLabel(box);
|
2019-09-08 22:27:57 +02:00
|
|
|
this->ui_.nameLabel->setFontStyle(FontStyle::UiMediumBold);
|
|
|
|
box->addStretch(1);
|
|
|
|
auto palette = QPalette();
|
|
|
|
palette.setColor(QPalette::WindowText, QColor("#aaa"));
|
2020-10-04 18:32:52 +02:00
|
|
|
this->ui_.userIDLabel = addCopyableLabel(box);
|
2019-09-08 22:27:57 +02:00
|
|
|
this->ui_.userIDLabel->setPalette(palette);
|
|
|
|
}
|
|
|
|
|
2020-10-04 18:32:52 +02:00
|
|
|
// items on the left
|
2020-03-14 12:13:57 +01:00
|
|
|
vbox.emplace<Label>(TEXT_VIEWS.arg(""))
|
|
|
|
.assign(&this->ui_.viewCountLabel);
|
|
|
|
vbox.emplace<Label>(TEXT_FOLLOWERS.arg(""))
|
2019-09-08 22:27:57 +02:00
|
|
|
.assign(&this->ui_.followerCountLabel);
|
2020-03-14 12:13:57 +01:00
|
|
|
vbox.emplace<Label>(TEXT_CREATED.arg(""))
|
2019-09-08 22:27:57 +02:00
|
|
|
.assign(&this->ui_.createdDateLabel);
|
2020-10-04 18:32:52 +02:00
|
|
|
vbox.emplace<Line>(true);
|
|
|
|
vbox.emplace<Label>("")
|
|
|
|
.assign(&this->ui_.followageSubageLabel)
|
|
|
|
->setMinimumSize(this->minimumSizeHint());
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
layout.emplace<Line>(false);
|
|
|
|
|
|
|
|
// second line
|
|
|
|
auto user = layout.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
{
|
|
|
|
user->addStretch(1);
|
|
|
|
|
|
|
|
user.emplace<QCheckBox>("Follow").assign(&this->ui_.follow);
|
|
|
|
user.emplace<QCheckBox>("Ignore").assign(&this->ui_.ignore);
|
|
|
|
user.emplace<QCheckBox>("Ignore highlights")
|
|
|
|
.assign(&this->ui_.ignoreHighlights);
|
|
|
|
auto usercard = user.emplace<EffectLabel2>(this);
|
|
|
|
usercard->getLabel().setText("Usercard");
|
2020-06-21 14:15:14 +02:00
|
|
|
auto refresh = user.emplace<EffectLabel2>(this);
|
|
|
|
refresh->getLabel().setText("Refresh");
|
2019-09-08 22:27:57 +02:00
|
|
|
auto mod = user.emplace<Button>(this);
|
2019-09-22 10:53:39 +02:00
|
|
|
mod->setPixmap(getResources().buttons.mod);
|
2019-09-08 22:27:57 +02:00
|
|
|
mod->setScaleIndependantSize(30, 30);
|
|
|
|
auto unmod = user.emplace<Button>(this);
|
2019-09-22 10:53:39 +02:00
|
|
|
unmod->setPixmap(getResources().buttons.unmod);
|
2019-09-08 22:27:57 +02:00
|
|
|
unmod->setScaleIndependantSize(30, 30);
|
2020-09-27 00:20:15 +02:00
|
|
|
auto vip = user.emplace<Button>(this);
|
|
|
|
vip->setPixmap(getResources().buttons.vip);
|
|
|
|
vip->setScaleIndependantSize(30, 30);
|
|
|
|
auto unvip = user.emplace<Button>(this);
|
|
|
|
unvip->setPixmap(getResources().buttons.unvip);
|
|
|
|
unvip->setScaleIndependantSize(30, 30);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
user->addStretch(1);
|
|
|
|
|
|
|
|
QObject::connect(usercard.getElement(), &Button::leftClicked, [this] {
|
|
|
|
QDesktopServices::openUrl("https://www.twitch.tv/popout/" +
|
|
|
|
this->channel_->getName() +
|
|
|
|
"/viewercard/" + this->userName_);
|
|
|
|
});
|
|
|
|
|
2020-06-21 14:15:14 +02:00
|
|
|
QObject::connect(refresh.getElement(), &Button::leftClicked,
|
|
|
|
[this] { this->updateLatestMessages(); });
|
2019-09-08 22:27:57 +02:00
|
|
|
QObject::connect(mod.getElement(), &Button::leftClicked, [this] {
|
|
|
|
this->channel_->sendMessage("/mod " + this->userName_);
|
|
|
|
});
|
|
|
|
QObject::connect(unmod.getElement(), &Button::leftClicked, [this] {
|
|
|
|
this->channel_->sendMessage("/unmod " + this->userName_);
|
2020-09-27 00:20:15 +02:00
|
|
|
});
|
|
|
|
QObject::connect(vip.getElement(), &Button::leftClicked, [this] {
|
|
|
|
this->channel_->sendMessage("/vip " + this->userName_);
|
|
|
|
});
|
|
|
|
QObject::connect(unvip.getElement(), &Button::leftClicked, [this] {
|
|
|
|
this->channel_->sendMessage("/unvip " + this->userName_);
|
2019-09-08 22:27:57 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
// userstate
|
2020-09-27 10:34:20 +02:00
|
|
|
this->userStateChanged_.connect([this, mod, unmod, vip,
|
|
|
|
unvip]() mutable {
|
2019-09-08 22:27:57 +02:00
|
|
|
TwitchChannel *twitchChannel =
|
|
|
|
dynamic_cast<TwitchChannel *>(this->channel_.get());
|
|
|
|
|
2019-12-14 13:22:49 +01:00
|
|
|
bool visibilityModButtons = false;
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
if (twitchChannel)
|
|
|
|
{
|
|
|
|
bool isMyself =
|
|
|
|
QString::compare(
|
|
|
|
getApp()->accounts->twitch.getCurrent()->getUserName(),
|
|
|
|
this->userName_, Qt::CaseInsensitive) == 0;
|
|
|
|
|
2019-12-14 13:22:49 +01:00
|
|
|
visibilityModButtons =
|
|
|
|
twitchChannel->isBroadcaster() && !isMyself;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
2019-12-14 13:22:49 +01:00
|
|
|
mod->setVisible(visibilityModButtons);
|
|
|
|
unmod->setVisible(visibilityModButtons);
|
2020-09-27 10:34:20 +02:00
|
|
|
vip->setVisible(visibilityModButtons);
|
|
|
|
unvip->setVisible(visibilityModButtons);
|
2019-09-08 22:27:57 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
auto lineMod = layout.emplace<Line>(false);
|
|
|
|
|
|
|
|
// third line
|
|
|
|
auto moderation = layout.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
{
|
|
|
|
auto timeout = moderation.emplace<TimeoutWidget>();
|
|
|
|
|
|
|
|
this->userStateChanged_.connect([this, lineMod, timeout]() mutable {
|
|
|
|
TwitchChannel *twitchChannel =
|
|
|
|
dynamic_cast<TwitchChannel *>(this->channel_.get());
|
|
|
|
|
|
|
|
bool hasModRights =
|
|
|
|
twitchChannel ? twitchChannel->hasModRights() : false;
|
|
|
|
lineMod->setVisible(hasModRights);
|
|
|
|
timeout->setVisible(hasModRights);
|
|
|
|
});
|
|
|
|
|
|
|
|
timeout->buttonClicked.connect([this](auto item) {
|
|
|
|
TimeoutWidget::Action action;
|
|
|
|
int arg;
|
|
|
|
std::tie(action, arg) = item;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
2019-09-26 00:51:05 +02:00
|
|
|
case TimeoutWidget::Ban: {
|
2019-09-08 22:27:57 +02:00
|
|
|
if (this->channel_)
|
|
|
|
{
|
|
|
|
this->channel_->sendMessage("/ban " + this->userName_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2019-09-26 00:51:05 +02:00
|
|
|
case TimeoutWidget::Unban: {
|
2019-09-08 22:27:57 +02:00
|
|
|
if (this->channel_)
|
|
|
|
{
|
|
|
|
this->channel_->sendMessage("/unban " +
|
|
|
|
this->userName_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2019-09-26 00:51:05 +02:00
|
|
|
case TimeoutWidget::Timeout: {
|
2019-09-08 22:27:57 +02:00
|
|
|
if (this->channel_)
|
|
|
|
{
|
|
|
|
this->channel_->sendMessage("/timeout " +
|
|
|
|
this->userName_ + " " +
|
|
|
|
QString::number(arg));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-21 14:15:14 +02:00
|
|
|
layout.emplace<Line>(false);
|
|
|
|
|
|
|
|
// fourth line (last messages)
|
|
|
|
auto logs = layout.emplace<QVBoxLayout>().withoutMargin();
|
|
|
|
{
|
|
|
|
this->ui_.noMessagesLabel = new Label("No recent messages");
|
|
|
|
this->ui_.noMessagesLabel->setVisible(false);
|
|
|
|
|
|
|
|
this->ui_.latestMessages = new ChannelView(this);
|
|
|
|
this->ui_.latestMessages->setMinimumSize(400, 275);
|
|
|
|
this->ui_.latestMessages->setSizePolicy(QSizePolicy::Expanding,
|
|
|
|
QSizePolicy::Expanding);
|
|
|
|
|
|
|
|
logs->addWidget(this->ui_.noMessagesLabel);
|
|
|
|
logs->addWidget(this->ui_.latestMessages);
|
|
|
|
logs->setAlignment(this->ui_.noMessagesLabel, Qt::AlignHCenter);
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-06-21 14:15:14 +02:00
|
|
|
this->installEvents();
|
2019-09-08 22:27:57 +02:00
|
|
|
this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Policy::Ignored);
|
|
|
|
}
|
|
|
|
|
2020-09-26 16:59:40 +02:00
|
|
|
// remove once https://github.com/pajlada/signals/pull/10 gets merged
|
|
|
|
UserInfoPopup::~UserInfoPopup()
|
|
|
|
{
|
|
|
|
this->refreshConnection_.disconnect();
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
void UserInfoPopup::themeChangedEvent()
|
|
|
|
{
|
|
|
|
BaseWindow::themeChangedEvent();
|
|
|
|
|
|
|
|
for (auto &&child : this->findChildren<QCheckBox *>())
|
|
|
|
{
|
|
|
|
child->setFont(getFonts()->getFont(FontStyle::UiMedium, this->scale()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::scaleChangedEvent(float /*scale*/)
|
|
|
|
{
|
|
|
|
themeChangedEvent();
|
|
|
|
|
|
|
|
QTimer::singleShot(20, this, [this] {
|
|
|
|
auto geo = this->geometry();
|
|
|
|
geo.setWidth(10);
|
|
|
|
geo.setHeight(10);
|
|
|
|
|
|
|
|
this->setGeometry(geo);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::installEvents()
|
|
|
|
{
|
|
|
|
std::weak_ptr<bool> hack = this->hack_;
|
|
|
|
|
|
|
|
// follow
|
|
|
|
QObject::connect(
|
2020-09-06 12:46:35 +02:00
|
|
|
this->ui_.follow, &QCheckBox::stateChanged,
|
|
|
|
[this](int newState) mutable {
|
2019-09-08 22:27:57 +02:00
|
|
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
|
|
|
|
|
|
|
const auto reenableFollowCheckbox = [this] {
|
|
|
|
this->ui_.follow->setEnabled(true); //
|
|
|
|
};
|
|
|
|
|
2020-09-06 12:46:35 +02:00
|
|
|
if (!this->ui_.follow->isEnabled())
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2020-09-06 12:46:35 +02:00
|
|
|
// We received a state update while the checkbox was disabled
|
|
|
|
// This can only happen from the "check current follow state" call
|
|
|
|
// The state has been updated to properly reflect the users current follow state
|
|
|
|
reenableFollowCheckbox();
|
|
|
|
return;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
2020-09-06 12:46:35 +02:00
|
|
|
|
|
|
|
switch (newState)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2020-09-06 12:46:35 +02:00
|
|
|
case Qt::CheckState::Unchecked: {
|
|
|
|
this->ui_.follow->setEnabled(false);
|
|
|
|
currentUser->unfollowUser(this->userId_,
|
|
|
|
reenableFollowCheckbox);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::CheckState::PartiallyChecked: {
|
|
|
|
// We deliberately ignore this state
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::CheckState::Checked: {
|
|
|
|
this->ui_.follow->setEnabled(false);
|
|
|
|
currentUser->followUser(this->userId_,
|
|
|
|
reenableFollowCheckbox);
|
|
|
|
}
|
|
|
|
break;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
std::shared_ptr<bool> ignoreNext = std::make_shared<bool>(false);
|
|
|
|
|
|
|
|
// ignore
|
|
|
|
QObject::connect(
|
|
|
|
this->ui_.ignore, &QCheckBox::stateChanged,
|
|
|
|
[this, ignoreNext, hack](int) mutable {
|
|
|
|
if (*ignoreNext)
|
|
|
|
{
|
|
|
|
*ignoreNext = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->ui_.ignore->setEnabled(false);
|
|
|
|
|
|
|
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
|
|
|
if (this->ui_.ignore->isChecked())
|
|
|
|
{
|
|
|
|
currentUser->ignoreByID(
|
|
|
|
this->userId_, this->userName_,
|
|
|
|
[=](auto result, const auto &message) mutable {
|
|
|
|
if (hack.lock())
|
|
|
|
{
|
|
|
|
if (result == IgnoreResult_Failed)
|
|
|
|
{
|
|
|
|
*ignoreNext = true;
|
|
|
|
this->ui_.ignore->setChecked(false);
|
|
|
|
}
|
|
|
|
this->ui_.ignore->setEnabled(true);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentUser->unignoreByID(
|
|
|
|
this->userId_, this->userName_,
|
|
|
|
[=](auto result, const auto &message) mutable {
|
|
|
|
if (hack.lock())
|
|
|
|
{
|
|
|
|
if (result == UnignoreResult_Failed)
|
|
|
|
{
|
|
|
|
*ignoreNext = true;
|
|
|
|
this->ui_.ignore->setChecked(true);
|
|
|
|
}
|
|
|
|
this->ui_.ignore->setEnabled(true);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// ignore highlights
|
|
|
|
QObject::connect(
|
|
|
|
this->ui_.ignoreHighlights, &QCheckBox::clicked,
|
|
|
|
[this](bool checked) mutable {
|
|
|
|
this->ui_.ignoreHighlights->setEnabled(false);
|
|
|
|
|
|
|
|
if (checked)
|
|
|
|
{
|
2020-02-23 22:15:13 +01:00
|
|
|
getSettings()->blacklistedUsers.insert(
|
2019-09-08 22:27:57 +02:00
|
|
|
HighlightBlacklistUser{this->userName_, false});
|
|
|
|
this->ui_.ignoreHighlights->setEnabled(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-02-23 22:15:13 +01:00
|
|
|
const auto &vector = getSettings()->blacklistedUsers.raw();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < vector.size(); i++)
|
|
|
|
{
|
|
|
|
if (this->userName_ == vector[i].getPattern())
|
|
|
|
{
|
2020-02-23 22:15:13 +01:00
|
|
|
getSettings()->blacklistedUsers.removeAt(i);
|
2019-09-08 22:27:57 +02:00
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2020-02-23 22:15:13 +01:00
|
|
|
if (getSettings()->isBlacklistedUser(this->userName_))
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
|
|
|
this->ui_.ignoreHighlights->setToolTip(
|
|
|
|
"Name matched by regex");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->ui_.ignoreHighlights->setEnabled(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::setData(const QString &name, const ChannelPtr &channel)
|
|
|
|
{
|
|
|
|
this->userName_ = name;
|
|
|
|
this->channel_ = channel;
|
2020-06-21 14:15:14 +02:00
|
|
|
this->setWindowTitle(TEXT_TITLE.arg(name));
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
this->ui_.nameLabel->setText(name);
|
2019-10-01 22:38:21 +02:00
|
|
|
this->ui_.nameLabel->setProperty("copy-text", name);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
this->updateUserData();
|
|
|
|
|
|
|
|
this->userStateChanged_.invoke();
|
2020-06-21 14:15:14 +02:00
|
|
|
|
|
|
|
this->updateLatestMessages();
|
|
|
|
QTimer::singleShot(1, this, [this] { this->setStayInScreenRect(true); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::updateLatestMessages()
|
|
|
|
{
|
|
|
|
auto filteredChannel = filterMessages(this->userName_, this->channel_);
|
|
|
|
this->ui_.latestMessages->setChannel(filteredChannel);
|
|
|
|
this->ui_.latestMessages->setSourceChannel(this->channel_);
|
|
|
|
|
|
|
|
const bool hasMessages = filteredChannel->hasMessages();
|
|
|
|
this->ui_.latestMessages->setVisible(hasMessages);
|
|
|
|
this->ui_.noMessagesLabel->setVisible(!hasMessages);
|
2020-09-26 16:59:40 +02:00
|
|
|
|
|
|
|
// shrink dialog in case ChannelView goes from visible to hidden
|
|
|
|
this->adjustSize();
|
|
|
|
|
|
|
|
this->refreshConnection_
|
|
|
|
.disconnect(); // remove once https://github.com/pajlada/signals/pull/10 gets merged
|
|
|
|
|
|
|
|
this->refreshConnection_ = this->channel_->messageAppended.connect(
|
|
|
|
[this, hasMessages](auto message, auto) {
|
|
|
|
if (!checkMessageUserName(this->userName_, message))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (hasMessages)
|
|
|
|
{
|
|
|
|
// display message in ChannelView
|
|
|
|
this->ui_.latestMessages->channel()->addMessage(message);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// The ChannelView is currently hidden, so manually refresh
|
|
|
|
// and display the latest messages
|
|
|
|
this->updateLatestMessages();
|
|
|
|
}
|
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::updateUserData()
|
|
|
|
{
|
2020-09-06 12:46:35 +02:00
|
|
|
this->ui_.follow->setEnabled(false);
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
std::weak_ptr<bool> hack = this->hack_;
|
2020-10-04 18:32:52 +02:00
|
|
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-04-05 12:59:46 +02:00
|
|
|
const auto onUserFetchFailed = [this, hack] {
|
|
|
|
if (!hack.lock())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-25 12:59:31 +01:00
|
|
|
// this can occur when the account doesn't exist.
|
2020-03-14 12:13:57 +01:00
|
|
|
this->ui_.followerCountLabel->setText(
|
|
|
|
TEXT_FOLLOWERS.arg(TEXT_UNAVAILABLE));
|
|
|
|
this->ui_.viewCountLabel->setText(TEXT_VIEWS.arg(TEXT_UNAVAILABLE));
|
|
|
|
this->ui_.createdDateLabel->setText(TEXT_CREATED.arg(TEXT_UNAVAILABLE));
|
2020-01-25 12:59:31 +01:00
|
|
|
|
|
|
|
this->ui_.nameLabel->setText(this->userName_);
|
|
|
|
|
2020-10-04 18:32:52 +02:00
|
|
|
this->ui_.userIDLabel->setText(QString("ID ") +
|
2020-01-25 12:59:31 +01:00
|
|
|
QString(TEXT_UNAVAILABLE));
|
|
|
|
this->ui_.userIDLabel->setProperty("copy-text",
|
|
|
|
QString(TEXT_UNAVAILABLE));
|
|
|
|
};
|
2020-10-04 18:32:52 +02:00
|
|
|
const auto onUserFetched = [this, hack,
|
|
|
|
currentUser](const HelixUser &user) {
|
2020-04-05 12:59:46 +02:00
|
|
|
if (!hack.lock())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
this->userId_ = user.id;
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
this->ui_.userIDLabel->setText(TEXT_USER_ID + user.id);
|
|
|
|
this->ui_.userIDLabel->setProperty("copy-text", user.id);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
this->ui_.viewCountLabel->setText(TEXT_VIEWS.arg(user.viewCount));
|
|
|
|
getKraken()->getUser(
|
|
|
|
user.id,
|
2020-04-05 12:59:46 +02:00
|
|
|
[this, hack](const auto &user) {
|
|
|
|
if (!hack.lock())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
this->ui_.createdDateLabel->setText(
|
2020-03-14 12:13:57 +01:00
|
|
|
TEXT_CREATED.arg(user.createdAt.section("T", 0, 0)));
|
|
|
|
},
|
|
|
|
[] {
|
|
|
|
// failure
|
|
|
|
});
|
2020-09-27 00:22:06 +02:00
|
|
|
if (isInStreamerMode())
|
|
|
|
{
|
|
|
|
this->ui_.avatarButton->setPixmap(getResources().streamerMode);
|
|
|
|
}
|
|
|
|
else
|
2020-07-12 22:44:33 +02:00
|
|
|
{
|
|
|
|
this->loadAvatar(user.profileImageUrl);
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
getHelix()->getUserFollowers(
|
|
|
|
user.id,
|
2020-04-05 12:59:46 +02:00
|
|
|
[this, hack](const auto &followers) {
|
|
|
|
if (!hack.lock())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2020-03-14 12:13:57 +01:00
|
|
|
this->ui_.followerCountLabel->setText(
|
|
|
|
TEXT_FOLLOWERS.arg(followers.total));
|
|
|
|
},
|
|
|
|
[] {
|
|
|
|
// on failure
|
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
// get follow state
|
2020-03-14 12:13:57 +01:00
|
|
|
currentUser->checkFollow(user.id, [this, hack](auto result) {
|
2020-04-05 12:59:46 +02:00
|
|
|
if (!hack.lock())
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2020-04-05 12:59:46 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (result != FollowResult_Failed)
|
|
|
|
{
|
|
|
|
this->ui_.follow->setChecked(result == FollowResult_Following);
|
2020-09-06 12:46:35 +02:00
|
|
|
this->ui_.follow->setEnabled(true);
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// get ignore state
|
|
|
|
bool isIgnoring = false;
|
|
|
|
for (const auto &ignoredUser : currentUser->getIgnores())
|
|
|
|
{
|
2020-03-14 12:13:57 +01:00
|
|
|
if (user.id == ignoredUser.id)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
|
|
|
isIgnoring = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get ignoreHighlights state
|
|
|
|
bool isIgnoringHighlights = false;
|
2020-02-23 22:15:13 +01:00
|
|
|
const auto &vector = getSettings()->blacklistedUsers.raw();
|
2019-09-08 22:27:57 +02:00
|
|
|
for (int i = 0; i < vector.size(); i++)
|
|
|
|
{
|
|
|
|
if (this->userName_ == vector[i].getPattern())
|
|
|
|
{
|
|
|
|
isIgnoringHighlights = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-02-23 22:15:13 +01:00
|
|
|
if (getSettings()->isBlacklistedUser(this->userName_) &&
|
2019-09-08 22:27:57 +02:00
|
|
|
!isIgnoringHighlights)
|
|
|
|
{
|
|
|
|
this->ui_.ignoreHighlights->setToolTip("Name matched by regex");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->ui_.ignoreHighlights->setEnabled(true);
|
|
|
|
}
|
|
|
|
this->ui_.ignore->setEnabled(true);
|
|
|
|
this->ui_.ignore->setChecked(isIgnoring);
|
|
|
|
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
|
2020-10-04 18:32:52 +02:00
|
|
|
|
|
|
|
// get followage and subage
|
|
|
|
getIvr()->getSubage(
|
|
|
|
this->userName_, this->channel_->getName(),
|
|
|
|
[this, hack](const IvrSubage &subageInfo) {
|
|
|
|
if (!hack.lock())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString labelText;
|
|
|
|
|
|
|
|
if (!subageInfo.followingSince.isEmpty())
|
|
|
|
{
|
|
|
|
QDateTime followedAt = QDateTime::fromString(
|
|
|
|
subageInfo.followingSince, Qt::ISODate);
|
|
|
|
QString followingSince = followedAt.toString("yyyy-MM-dd");
|
|
|
|
labelText = "Following since " + followingSince;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subageInfo.isSubHidden)
|
|
|
|
{
|
|
|
|
labelText += "\nSubscribtion status hidden";
|
|
|
|
}
|
|
|
|
if (subageInfo.isSubbed)
|
|
|
|
{
|
|
|
|
labelText += QString("\nTier %1 - Subscribed for %2 months")
|
|
|
|
.arg(subageInfo.subTier)
|
|
|
|
.arg(subageInfo.totalSubMonths);
|
|
|
|
}
|
|
|
|
else if (subageInfo.totalSubMonths)
|
|
|
|
{
|
|
|
|
labelText +=
|
|
|
|
QString("\nPreviously subscribed for %1 months")
|
|
|
|
.arg(subageInfo.totalSubMonths);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->ui_.followageSubageLabel->setText(labelText);
|
|
|
|
},
|
|
|
|
[] {});
|
2019-09-08 22:27:57 +02:00
|
|
|
};
|
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
getHelix()->getUserByName(this->userName_, onUserFetched,
|
|
|
|
onUserFetchFailed);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
this->ui_.follow->setEnabled(false);
|
|
|
|
this->ui_.ignore->setEnabled(false);
|
|
|
|
this->ui_.ignoreHighlights->setEnabled(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::loadAvatar(const QUrl &url)
|
|
|
|
{
|
|
|
|
QNetworkRequest req(url);
|
|
|
|
static auto manager = new QNetworkAccessManager();
|
|
|
|
auto *reply = manager->get(req);
|
|
|
|
|
|
|
|
QObject::connect(reply, &QNetworkReply::finished, this, [=] {
|
|
|
|
if (reply->error() == QNetworkReply::NoError)
|
|
|
|
{
|
|
|
|
const auto data = reply->readAll();
|
|
|
|
|
|
|
|
// might want to cache the avatar image
|
|
|
|
QPixmap avatar;
|
|
|
|
avatar.loadFromData(data);
|
|
|
|
this->ui_.avatarButton->setPixmap(avatar);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->ui_.avatarButton->setPixmap(QPixmap());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// TimeoutWidget
|
|
|
|
//
|
|
|
|
UserInfoPopup::TimeoutWidget::TimeoutWidget()
|
|
|
|
: BaseWidget(nullptr)
|
|
|
|
{
|
|
|
|
auto layout = LayoutCreator<TimeoutWidget>(this)
|
|
|
|
.setLayoutType<QHBoxLayout>()
|
|
|
|
.withoutMargin();
|
|
|
|
|
|
|
|
QColor color1(255, 255, 255, 80);
|
|
|
|
QColor color2(255, 255, 255, 0);
|
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
int buttonWidth = 40;
|
|
|
|
// int buttonWidth = 24;
|
2019-09-08 22:27:57 +02:00
|
|
|
int buttonWidth2 = 32;
|
|
|
|
int buttonHeight = 32;
|
|
|
|
|
|
|
|
layout->setSpacing(16);
|
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
//auto addButton = [&](Action action, const QString &text,
|
|
|
|
// const QPixmap &pixmap) {
|
|
|
|
// auto vbox = layout.emplace<QVBoxLayout>().withoutMargin();
|
|
|
|
// {
|
|
|
|
// auto title = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
// title->addStretch(1);
|
|
|
|
// auto label = title.emplace<Label>(text);
|
|
|
|
// label->setHasOffset(false);
|
|
|
|
// label->setStyleSheet("color: #BBB");
|
|
|
|
// title->addStretch(1);
|
|
|
|
|
|
|
|
// auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
// hbox->setSpacing(0);
|
|
|
|
// {
|
|
|
|
// auto button = hbox.emplace<Button>(nullptr);
|
|
|
|
// button->setPixmap(pixmap);
|
|
|
|
// button->setScaleIndependantSize(buttonHeight, buttonHeight);
|
|
|
|
// button->setBorderColor(QColor(255, 255, 255, 127));
|
|
|
|
|
|
|
|
// QObject::connect(
|
|
|
|
// button.getElement(), &Button::leftClicked, [this, action] {
|
|
|
|
// this->buttonClicked.invoke(std::make_pair(action, -1));
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//};
|
|
|
|
|
|
|
|
const auto addLayout = [&](const QString &text) {
|
2019-09-08 22:27:57 +02:00
|
|
|
auto vbox = layout.emplace<QVBoxLayout>().withoutMargin();
|
2020-09-26 14:54:44 +02:00
|
|
|
auto title = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
title->addStretch(1);
|
|
|
|
auto label = title.emplace<Label>(text);
|
|
|
|
label->setStyleSheet("color: #BBB");
|
|
|
|
label->setHasOffset(false);
|
|
|
|
title->addStretch(1);
|
|
|
|
|
|
|
|
auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
hbox->setSpacing(0);
|
|
|
|
return hbox;
|
2019-09-08 22:27:57 +02:00
|
|
|
};
|
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
const auto addButton = [&](Action action, const QString &title,
|
|
|
|
const QPixmap &pixmap) {
|
|
|
|
auto button = addLayout(title).emplace<Button>(nullptr);
|
|
|
|
button->setPixmap(pixmap);
|
|
|
|
button->setScaleIndependantSize(buttonHeight, buttonHeight);
|
|
|
|
button->setBorderColor(QColor(255, 255, 255, 127));
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
QObject::connect(
|
|
|
|
button.getElement(), &Button::leftClicked, [this, action] {
|
|
|
|
this->buttonClicked.invoke(std::make_pair(action, -1));
|
|
|
|
});
|
|
|
|
};
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
auto addTimeouts = [&](const QString &title) {
|
|
|
|
auto hbox = addLayout(title);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
for (const auto &item : getSettings()->timeoutButtons.getValue())
|
|
|
|
{
|
|
|
|
auto a = hbox.emplace<EffectLabel2>();
|
|
|
|
a->getLabel().setText(QString::number(item.second) + item.first);
|
|
|
|
|
|
|
|
a->setScaleIndependantSize(buttonWidth, buttonHeight);
|
|
|
|
a->setBorderColor(borderColor);
|
|
|
|
|
|
|
|
const auto pair =
|
|
|
|
std::make_pair(Action::Timeout, calculateTimeoutDuration(item));
|
|
|
|
|
|
|
|
QObject::connect(
|
|
|
|
a.getElement(), &EffectLabel2::leftClicked,
|
|
|
|
[this, pair] { this->buttonClicked.invoke(pair); });
|
|
|
|
|
|
|
|
//auto addTimeouts = [&](const QString &title_,
|
|
|
|
// const std::vector<std::pair<QString, int>> &items) {
|
|
|
|
// auto vbox = layout.emplace<QVBoxLayout>().withoutMargin();
|
|
|
|
// {
|
|
|
|
// auto title = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
// title->addStretch(1);
|
|
|
|
// auto label = title.emplace<Label>(title_);
|
|
|
|
// label->setStyleSheet("color: #BBB");
|
|
|
|
// label->setHasOffset(false);
|
|
|
|
// title->addStretch(1);
|
|
|
|
|
|
|
|
// auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
|
|
|
|
// hbox->setSpacing(0);
|
|
|
|
|
|
|
|
// for (const auto &item : items)
|
|
|
|
// {
|
|
|
|
// auto a = hbox.emplace<EffectLabel2>();
|
|
|
|
// a->getLabel().setText(std::get<0>(item));
|
|
|
|
|
|
|
|
// if (std::get<0>(item).length() > 1)
|
|
|
|
// {
|
|
|
|
// a->setScaleIndependantSize(buttonWidth2, buttonHeight);
|
|
|
|
// }
|
|
|
|
// else
|
|
|
|
// {
|
|
|
|
// a->setScaleIndependantSize(buttonWidth, buttonHeight);
|
|
|
|
// }
|
|
|
|
// a->setBorderColor(color1);
|
|
|
|
|
|
|
|
// QObject::connect(a.getElement(), &EffectLabel2::leftClicked,
|
|
|
|
// [this, timeout = std::get<1>(item)] {
|
|
|
|
// this->buttonClicked.invoke(std::make_pair(
|
|
|
|
// Action::Timeout, timeout));
|
|
|
|
// });
|
|
|
|
// }
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-26 14:54:44 +02:00
|
|
|
addButton(Unban, "Unban", getResources().buttons.unban);
|
|
|
|
addTimeouts("Timeouts");
|
|
|
|
addButton(Ban, "Ban", getResources().buttons.ban);
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void UserInfoPopup::TimeoutWidget::paintEvent(QPaintEvent *)
|
|
|
|
{
|
|
|
|
// QPainter painter(this);
|
|
|
|
|
|
|
|
// painter.setPen(QColor(255, 255, 255, 63));
|
|
|
|
|
|
|
|
// painter.drawLine(0, this->height() / 2, this->width(), this->height()
|
|
|
|
// / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace chatterino
|