created directory for dialogs

This commit is contained in:
fourtf
2018-06-26 15:11:45 +02:00
parent f09c7e8b4e
commit 0bc08a364c
50 changed files with 68 additions and 175 deletions
+166
View File
@@ -0,0 +1,166 @@
#include "EmotePopup.hpp"
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "widgets/Notebook.hpp"
#include <QHBoxLayout>
#include <QShortcut>
#include <QTabWidget>
using namespace chatterino::providers::twitch;
using namespace chatterino::messages;
namespace chatterino {
namespace widgets {
EmotePopup::EmotePopup()
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
{
this->viewEmotes = new ChannelView();
this->viewEmojis = new ChannelView();
this->viewEmotes->setOverrideFlags(MessageElement::Flags(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmojis->setOverrideFlags(MessageElement::Flags(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmotes->setEnableScrollingToBottom(false);
this->viewEmojis->setEnableScrollingToBottom(false);
auto *layout = new QVBoxLayout(this);
this->getLayoutContainer()->setLayout(layout);
Notebook *notebook = new Notebook(this);
layout->addWidget(notebook);
layout->setMargin(0);
notebook->addPage(this->viewEmotes, "Emotes");
notebook->addPage(this->viewEmojis, "Emojis");
this->loadEmojis();
this->viewEmotes->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
this->viewEmojis->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
}
void EmotePopup::loadChannel(ChannelPtr _channel)
{
this->setWindowTitle("Emotes from " + _channel->name);
TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get());
if (channel == nullptr) {
return;
}
ChannelPtr emoteChannel(new Channel("", Channel::None));
auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) {
// TITLE
messages::MessageBuilder builder1;
builder1.append(new TextElement(title, MessageElement::Text));
builder1.getMessage()->flags |= Message::Centered;
emoteChannel->addMessage(builder1.getMessage());
// EMOTES
messages::MessageBuilder builder2;
builder2.getMessage()->flags |= Message::Centered;
builder2.getMessage()->flags |= Message::DisableCompactEmotes;
map.each([&](const QString &key, const util::EmoteData &value) {
builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::InsertText, key)));
});
emoteChannel->addMessage(builder2.getMessage());
};
auto app = getApp();
QString userID = app->accounts->twitch.getCurrent()->getUserId();
// fourtf: the entire emote manager needs to be refactored so there's no point in trying to
// fix this pile of garbage
for (const auto &set : app->emotes->twitch.emotes[userID].emoteSets) {
// TITLE
messages::MessageBuilder builder1;
QString setText;
if (set->text.isEmpty()) {
if (set->channelName.isEmpty()) {
setText = "Twitch Account Emotes";
} else {
setText = "Twitch Account Emotes (" + set->channelName + ")";
}
} else {
setText = set->text;
}
builder1.append(new TextElement(setText, MessageElement::Text));
builder1.getMessage()->flags |= Message::Centered;
emoteChannel->addMessage(builder1.getMessage());
// EMOTES
messages::MessageBuilder builder2;
builder2.getMessage()->flags |= Message::Centered;
builder2.getMessage()->flags |= Message::DisableCompactEmotes;
for (const auto &emote : set->emotes) {
[&](const QString &key, const util::EmoteData &value) {
builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::InsertText, key)));
}(emote.code, app->emotes->twitch.getEmoteById(emote.id, emote.code));
}
emoteChannel->addMessage(builder2.getMessage());
}
addEmotes(app->emotes->bttv.globalEmotes, "BetterTTV Global Emotes", "BetterTTV Global Emote");
addEmotes(*channel->bttvChannelEmotes.get(), "BetterTTV Channel Emotes",
"BetterTTV Channel Emote");
addEmotes(app->emotes->ffz.globalEmotes, "FrankerFaceZ Global Emotes",
"FrankerFaceZ Global Emote");
addEmotes(*channel->ffzChannelEmotes.get(), "FrankerFaceZ Channel Emotes",
"FrankerFaceZ Channel Emote");
this->viewEmotes->setChannel(emoteChannel);
}
void EmotePopup::loadEmojis()
{
auto &emojis = getApp()->emotes->emojis.emojis;
ChannelPtr emojiChannel(new Channel("", Channel::None));
// title
messages::MessageBuilder builder1;
builder1.append(new TextElement("emojis", MessageElement::Text));
builder1.getMessage()->flags |= Message::Centered;
emojiChannel->addMessage(builder1.getMessage());
// emojis
messages::MessageBuilder builder;
builder.getMessage()->flags |= Message::Centered;
builder.getMessage()->flags |= Message::DisableCompactEmotes;
emojis.each([&builder](const QString &key, const auto &value) {
builder.append(
(new EmoteElement(value->emoteData, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::Type::InsertText, ":" + value->shortCodes[0] + ":")));
});
emojiChannel->addMessage(builder.getMessage());
this->viewEmojis->setChannel(emojiChannel);
}
} // namespace widgets
} // namespace chatterino
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "Channel.hpp"
#include "widgets/BaseWindow.hpp"
#include "widgets/helper/ChannelView.hpp"
#include <pajlada/signals/signal.hpp>
namespace chatterino {
namespace widgets {
class EmotePopup : public BaseWindow
{
public:
EmotePopup();
void loadChannel(ChannelPtr channel);
void loadEmojis();
pajlada::Signals::Signal<messages::Link> linkClicked;
private:
ChannelView *viewEmotes;
ChannelView *viewEmojis;
};
} // namespace widgets
} // namespace chatterino
@@ -0,0 +1,89 @@
#include "LastRunCrashDialog.hpp"
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include "singletons/UpdateManager.hpp"
#include "util/LayoutCreator.hpp"
#include "util/PostToThread.hpp"
namespace chatterino {
namespace widgets {
LastRunCrashDialog::LastRunCrashDialog()
{
this->setWindowFlag(Qt::WindowContextHelpButtonHint, false);
this->setWindowTitle("Chatterino");
auto &updateManager = singletons::UpdateManager::getInstance();
auto layout = util::LayoutCreator<LastRunCrashDialog>(this).setLayoutType<QVBoxLayout>();
layout.emplace<QLabel>(
"The application wasn't terminated properly the last time it was executed.");
layout->addSpacing(16);
// auto update = layout.emplace<QLabel>();
auto buttons = layout.emplace<QDialogButtonBox>();
// auto *installUpdateButton = buttons->addButton("Install Update",
// QDialogButtonBox::NoRole); installUpdateButton->setEnabled(false);
// QObject::connect(installUpdateButton, &QPushButton::clicked, [this, update]() mutable {
// auto &updateManager = singletons::UpdateManager::getInstance();
// updateManager.installUpdates();
// this->setEnabled(false);
// update->setText("Downloading updates...");
// });
auto *okButton = buttons->addButton("Ignore", QDialogButtonBox::ButtonRole::NoRole);
QObject::connect(okButton, &QPushButton::clicked, [this] { this->accept(); });
// Updates
// auto updateUpdateLabel = [update]() mutable {
// auto &updateManager = singletons::UpdateManager::getInstance();
// switch (updateManager.getStatus()) {
// case singletons::UpdateManager::None: {
// update->setText("Not checking for updates.");
// } break;
// case singletons::UpdateManager::Searching: {
// update->setText("Checking for updates...");
// } break;
// case singletons::UpdateManager::UpdateAvailable: {
// update->setText("Update available.");
// } break;
// case singletons::UpdateManager::NoUpdateAvailable: {
// update->setText("No update abailable.");
// } break;
// case singletons::UpdateManager::SearchFailed: {
// update->setText("Error while searching for update.\nEither the update service
// is "
// "temporarily down or there is an issue with your
// installation.");
// } break;
// case singletons::UpdateManager::Downloading: {
// update->setText(
// "Downloading the update. Chatterino will close once the download is
// done.");
// } break;
// case singletons::UpdateManager::DownloadFailed: {
// update->setText("Download failed.");
// } break;
// case singletons::UpdateManager::WriteFileFailed: {
// update->setText("Writing the update file to the hard drive failed.");
// } break;
// }
// };
// updateUpdateLabel();
// this->managedConnect(updateManager.statusUpdated, [updateUpdateLabel](auto) mutable {
// util::postToThread([updateUpdateLabel]() mutable { updateUpdateLabel(); });
// });
}
} // namespace widgets
} // namespace chatterino
@@ -0,0 +1,16 @@
#pragma once
#include <QDialog>
#include <pajlada/signals/signalholder.hpp>
namespace chatterino {
namespace widgets {
class LastRunCrashDialog : public QDialog, pajlada::Signals::SignalHolder
{
public:
LastRunCrashDialog();
};
} // namespace widgets
} // namespace chatterino
+219
View File
@@ -0,0 +1,219 @@
#include "widgets/dialogs/LoginDialog.hpp"
#include "Common.hpp"
#include "util/UrlFetch.hpp"
#ifdef USEWINSDK
#include <Windows.h>
#endif
#include <QClipboard>
#include <QDebug>
#include <QDesktopServices>
#include <QMessageBox>
#include <QUrl>
#include <pajlada/settings/setting.hpp>
namespace chatterino {
namespace widgets {
namespace {
void LogInWithCredentials(const std::string &userID, const std::string &username,
const std::string &clientID, const std::string &oauthToken)
{
QStringList errors;
if (userID.empty()) {
errors.append("Missing user ID");
}
if (username.empty()) {
errors.append("Missing username");
}
if (clientID.empty()) {
errors.append("Missing Client ID");
}
if (oauthToken.empty()) {
errors.append("Missing OAuth Token");
}
if (errors.length() > 0) {
QMessageBox messageBox;
messageBox.setIcon(QMessageBox::Critical);
messageBox.setText(errors.join("<br />"));
messageBox.setStandardButtons(QMessageBox::Ok);
messageBox.exec();
return;
}
// QMessageBox messageBox;
// messageBox.setIcon(QMessageBox::Information);
// messageBox.setText("Successfully logged in with user <b>" + qS(username) + "</b>!");
pajlada::Settings::Setting<std::string>::set("/accounts/uid" + userID + "/username", username);
pajlada::Settings::Setting<std::string>::set("/accounts/uid" + userID + "/userID", userID);
pajlada::Settings::Setting<std::string>::set("/accounts/uid" + userID + "/clientID", clientID);
pajlada::Settings::Setting<std::string>::set("/accounts/uid" + userID + "/oauthToken",
oauthToken);
getApp()->accounts->twitch.reloadUsers();
// messageBox.exec();
getApp()->accounts->twitch.currentUsername = username;
}
} // namespace
BasicLoginWidget::BasicLoginWidget()
{
this->setLayout(&this->ui.layout);
this->ui.loginButton.setText("Log in (Opens in browser)");
this->ui.pasteCodeButton.setText("Paste code");
this->ui.horizontalLayout.addWidget(&this->ui.loginButton);
this->ui.horizontalLayout.addWidget(&this->ui.pasteCodeButton);
this->ui.layout.addLayout(&this->ui.horizontalLayout);
connect(&this->ui.loginButton, &QPushButton::clicked, []() {
printf("open login in browser\n");
QDesktopServices::openUrl(QUrl("https://chatterino.com/client_login"));
});
connect(&this->ui.pasteCodeButton, &QPushButton::clicked, [this]() {
QClipboard *clipboard = QGuiApplication::clipboard();
QString clipboardString = clipboard->text();
QStringList parameters = clipboardString.split(';');
std::string oauthToken, clientID, username, userID;
for (const auto &param : parameters) {
QStringList kvParameters = param.split('=');
if (kvParameters.size() != 2) {
continue;
}
QString key = kvParameters[0];
QString value = kvParameters[1];
if (key == "oauth_token") {
oauthToken = value.toStdString();
} else if (key == "client_id") {
clientID = value.toStdString();
} else if (key == "username") {
username = value.toStdString();
} else if (key == "user_id") {
userID = value.toStdString();
} else {
qDebug() << "Unknown key in code: " << key;
}
}
LogInWithCredentials(userID, username, clientID, oauthToken);
clipboard->clear();
this->window()->close();
});
}
AdvancedLoginWidget::AdvancedLoginWidget()
{
this->setLayout(&this->ui.layout);
this->ui.instructionsLabel.setText("1. Fill in your username\n2. Fill in your user ID or press "
"the 'Get user ID from username' button\n3. Fill in your "
"Client ID\n4. Fill in your OAuth Token\n5. Press Add User");
this->ui.instructionsLabel.setWordWrap(true);
this->ui.layout.addWidget(&this->ui.instructionsLabel);
this->ui.layout.addLayout(&this->ui.formLayout);
this->ui.layout.addLayout(&this->ui.buttonUpperRow.layout);
this->ui.layout.addLayout(&this->ui.buttonLowerRow.layout);
this->refreshButtons();
/// Form
this->ui.formLayout.addRow("Username", &this->ui.usernameInput);
this->ui.formLayout.addRow("User ID", &this->ui.userIDInput);
this->ui.formLayout.addRow("Client ID", &this->ui.clientIDInput);
this->ui.formLayout.addRow("Oauth token", &this->ui.oauthTokenInput);
this->ui.oauthTokenInput.setEchoMode(QLineEdit::Password);
connect(&this->ui.userIDInput, &QLineEdit::textChanged, [=]() { this->refreshButtons(); });
connect(&this->ui.usernameInput, &QLineEdit::textChanged, [=]() { this->refreshButtons(); });
connect(&this->ui.clientIDInput, &QLineEdit::textChanged, [=]() { this->refreshButtons(); });
connect(&this->ui.oauthTokenInput, &QLineEdit::textChanged, [=]() { this->refreshButtons(); });
/// Upper button row
this->ui.buttonUpperRow.addUserButton.setText("Add user");
this->ui.buttonUpperRow.clearFieldsButton.setText("Clear fields");
this->ui.buttonUpperRow.layout.addWidget(&this->ui.buttonUpperRow.addUserButton);
this->ui.buttonUpperRow.layout.addWidget(&this->ui.buttonUpperRow.clearFieldsButton);
connect(&this->ui.buttonUpperRow.clearFieldsButton, &QPushButton::clicked, [=]() {
this->ui.userIDInput.clear();
this->ui.usernameInput.clear();
this->ui.clientIDInput.clear();
this->ui.oauthTokenInput.clear();
});
connect(&this->ui.buttonUpperRow.addUserButton, &QPushButton::clicked, [=]() {
std::string userID = this->ui.userIDInput.text().toStdString();
std::string username = this->ui.usernameInput.text().toStdString();
std::string clientID = this->ui.clientIDInput.text().toStdString();
std::string oauthToken = this->ui.oauthTokenInput.text().toStdString();
LogInWithCredentials(userID, username, clientID, oauthToken);
});
/// Lower button row
this->ui.buttonLowerRow.fillInUserIDButton.setText("Get user ID from username");
this->ui.buttonLowerRow.layout.addWidget(&this->ui.buttonLowerRow.fillInUserIDButton);
connect(&this->ui.buttonLowerRow.fillInUserIDButton, &QPushButton::clicked, [=]() {
util::twitch::getUserID(this->ui.usernameInput.text(), this, [=](const QString &userID) {
this->ui.userIDInput.setText(userID); //
});
});
}
void AdvancedLoginWidget::refreshButtons()
{
this->ui.buttonLowerRow.fillInUserIDButton.setEnabled(!this->ui.usernameInput.text().isEmpty());
if (this->ui.userIDInput.text().isEmpty() || this->ui.usernameInput.text().isEmpty() ||
this->ui.clientIDInput.text().isEmpty() || this->ui.oauthTokenInput.text().isEmpty()) {
this->ui.buttonUpperRow.addUserButton.setEnabled(false);
} else {
this->ui.buttonUpperRow.addUserButton.setEnabled(true);
}
}
LoginWidget::LoginWidget()
{
#ifdef USEWINSDK
::SetWindowPos(HWND(this->winId()), HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
#endif
this->setLayout(&this->ui.mainLayout);
this->ui.mainLayout.addWidget(&this->ui.tabWidget);
this->ui.tabWidget.addTab(&this->ui.basic, "Basic");
this->ui.tabWidget.addTab(&this->ui.advanced, "Advanced");
this->ui.buttonBox.setStandardButtons(QDialogButtonBox::Close);
QObject::connect(&this->ui.buttonBox, &QDialogButtonBox::rejected, [this]() {
this->close(); //
});
this->ui.mainLayout.addWidget(&this->ui.buttonBox);
}
} // namespace widgets
} // namespace chatterino
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include "widgets/BaseWidget.hpp"
#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTabWidget>
#include <QtWidgets/QVBoxLayout>
namespace chatterino {
namespace widgets {
class BasicLoginWidget : public QWidget
{
public:
BasicLoginWidget();
struct {
QVBoxLayout layout;
QHBoxLayout horizontalLayout;
QPushButton loginButton;
QPushButton pasteCodeButton;
} ui;
};
class AdvancedLoginWidget : public QWidget
{
public:
AdvancedLoginWidget();
void refreshButtons();
struct {
QVBoxLayout layout;
QLabel instructionsLabel;
QFormLayout formLayout;
QLineEdit userIDInput;
QLineEdit usernameInput;
QLineEdit clientIDInput;
QLineEdit oauthTokenInput;
struct {
QHBoxLayout layout;
QPushButton addUserButton;
QPushButton clearFieldsButton;
} buttonUpperRow;
struct {
QHBoxLayout layout;
QPushButton fillInUserIDButton;
} buttonLowerRow;
} ui;
};
class LoginWidget : public QDialog
{
public:
LoginWidget();
private:
struct {
QVBoxLayout mainLayout;
QTabWidget tabWidget;
QDialogButtonBox buttonBox;
BasicLoginWidget basic;
AdvancedLoginWidget advanced;
} ui;
};
} // namespace widgets
} // namespace chatterino
+50
View File
@@ -0,0 +1,50 @@
#include "NotificationPopup.hpp"
#include "widgets/helper/ChannelView.hpp"
#include <QApplication>
#include <QDesktopWidget>
#include <QScreen>
namespace chatterino {
namespace widgets {
NotificationPopup::NotificationPopup()
: BaseWindow((QWidget *)nullptr, BaseWindow::Frameless)
, channel(std::make_shared<Channel>("notifications", Channel::None))
{
this->channelView = new ChannelView(this);
auto *layout = new QVBoxLayout(this);
this->setLayout(layout);
layout->addWidget(this->channelView);
this->channelView->setChannel(this->channel);
this->setScaleIndependantSize(300, 150);
}
void NotificationPopup::updatePosition()
{
Location location = BottomRight;
QDesktopWidget *desktop = QApplication::desktop();
const QRect rect = desktop->availableGeometry();
switch (location) {
case BottomRight: {
this->move(rect.right() - this->width(), rect.bottom() - this->height());
} break;
}
}
void NotificationPopup::addMessage(messages::MessagePtr msg)
{
this->channel->addMessage(msg);
// QTimer::singleShot(5000, this, [this, msg] { this->channel->remove });
}
} // namespace widgets
} // namespace chatterino
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "Channel.hpp"
#include "messages/Message.hpp"
#include "widgets/BaseWindow.hpp"
namespace chatterino {
namespace widgets {
class ChannelView;
class NotificationPopup : public BaseWindow
{
public:
enum Location { TopLeft, TopRight, BottomLeft, BottomRight };
NotificationPopup();
void addMessage(messages::MessagePtr msg);
void updatePosition();
private:
ChannelView *channelView;
ChannelPtr channel;
};
} // namespace widgets
} // namespace chatterino
+61
View File
@@ -0,0 +1,61 @@
#include "QualityPopup.hpp"
#include "debug/Log.hpp"
#include "util/StreamLink.hpp"
namespace chatterino {
namespace widgets {
QualityPopup::QualityPopup(const QString &_channelName, QStringList options)
: channelName(_channelName)
{
this->ui_.okButton.setText("OK");
this->ui_.cancelButton.setText("Cancel");
QObject::connect(&this->ui_.okButton, &QPushButton::clicked, this,
&QualityPopup::okButtonClicked);
QObject::connect(&this->ui_.cancelButton, &QPushButton::clicked, this,
&QualityPopup::cancelButtonClicked);
this->ui_.buttonBox.addButton(&this->ui_.okButton, QDialogButtonBox::ButtonRole::AcceptRole);
this->ui_.buttonBox.addButton(&this->ui_.cancelButton, QDialogButtonBox::ButtonRole::RejectRole);
this->ui_.selector.addItems(options);
this->ui_.vbox.addWidget(&this->ui_.selector);
this->ui_.vbox.addWidget(&this->ui_.buttonBox);
this->setLayout(&this->ui_.vbox);
}
void QualityPopup::showDialog(const QString &channelName, QStringList options)
{
QualityPopup *instance = new QualityPopup(channelName, options);
instance->setAttribute(Qt::WA_DeleteOnClose, true);
instance->show();
instance->activateWindow();
instance->raise();
instance->setFocus();
}
void QualityPopup::okButtonClicked()
{
QString channelURL = "twitch.tv/" + this->channelName;
try {
streamlink::OpenStreamlink(channelURL, this->ui_.selector.currentText());
} catch (const streamlink::Exception &ex) {
debug::Log("Exception caught trying to open streamlink: {}", ex.what());
}
this->close();
}
void QualityPopup::cancelButtonClicked()
{
this->close();
}
} // namespace widgets
} // namespace chatterino
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "widgets/BaseWindow.hpp"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
namespace chatterino {
namespace widgets {
class QualityPopup : public BaseWindow
{
public:
QualityPopup(const QString &_channelName, QStringList options);
static void showDialog(const QString &_channelName, QStringList options);
private:
struct {
QVBoxLayout vbox;
QComboBox selector;
QDialogButtonBox buttonBox;
QPushButton okButton;
QPushButton cancelButton;
} ui_;
QString channelName;
void okButtonClicked();
void cancelButtonClicked();
};
} // namespace widgets
} // namespace chatterino
+283
View File
@@ -0,0 +1,283 @@
#include "SelectChannelDialog.hpp"
#include "Application.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "util/LayoutCreator.hpp"
#include "widgets/Notebook.hpp"
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
#define TAB_TWITCH 0
namespace chatterino {
namespace widgets {
SelectChannelDialog::SelectChannelDialog(QWidget *parent)
: BaseWindow(parent, BaseWindow::EnableCustomFrame)
, selectedChannel(Channel::getEmpty())
{
this->setWindowTitle("Select a channel to join");
this->tabFilter.dialog = this;
util::LayoutCreator<QWidget> layoutWidget(this->getLayoutContainer());
auto layout = layoutWidget.setLayoutType<QVBoxLayout>().withoutMargin();
auto notebook = layout.emplace<Notebook>(this).assign(&this->ui_.notebook);
// twitch
{
util::LayoutCreator<QWidget> obj(new QWidget());
auto vbox = obj.setLayoutType<QVBoxLayout>();
// channel_btn
auto channel_btn = vbox.emplace<QRadioButton>("Channel").assign(&this->ui_.twitch.channel);
auto channel_lbl = vbox.emplace<QLabel>("Join a twitch channel by its name.").hidden();
channel_lbl->setWordWrap(true);
auto channel_edit =
vbox.emplace<QLineEdit>().hidden().assign(&this->ui_.twitch.channelName);
QObject::connect(channel_btn.getElement(), &QRadioButton::toggled,
[=](bool enabled) mutable {
if (enabled) {
channel_edit->setFocus();
channel_edit->setSelection(0, channel_edit->text().length());
}
channel_edit->setVisible(enabled);
channel_lbl->setVisible(enabled);
});
channel_btn->installEventFilter(&this->tabFilter);
channel_edit->installEventFilter(&this->tabFilter);
// whispers_btn
auto whispers_btn =
vbox.emplace<QRadioButton>("Whispers").assign(&this->ui_.twitch.whispers);
auto whispers_lbl =
vbox.emplace<QLabel>("Shows the whispers that you receive while chatterino is running.")
.hidden();
whispers_lbl->setWordWrap(true);
whispers_btn->installEventFilter(&this->tabFilter);
QObject::connect(whispers_btn.getElement(), &QRadioButton::toggled,
[=](bool enabled) mutable { whispers_lbl->setVisible(enabled); });
// mentions_btn
auto mentions_btn =
vbox.emplace<QRadioButton>("Mentions").assign(&this->ui_.twitch.mentions);
auto mentions_lbl =
vbox.emplace<QLabel>("Shows all the messages that highlight you from any channel.")
.hidden();
mentions_lbl->setWordWrap(true);
mentions_btn->installEventFilter(&this->tabFilter);
QObject::connect(mentions_btn.getElement(), &QRadioButton::toggled,
[=](bool enabled) mutable { mentions_lbl->setVisible(enabled); });
// watching_btn
auto watching_btn =
vbox.emplace<QRadioButton>("Watching").assign(&this->ui_.twitch.watching);
auto watching_lbl =
vbox.emplace<QLabel>("Requires the chatterino browser extension.").hidden();
watching_lbl->setWordWrap(true);
watching_btn->installEventFilter(&this->tabFilter);
QObject::connect(watching_btn.getElement(), &QRadioButton::toggled,
[=](bool enabled) mutable { watching_lbl->setVisible(enabled); });
vbox->addStretch(1);
// tabbing order
QWidget::setTabOrder(watching_btn.getElement(), channel_btn.getElement());
QWidget::setTabOrder(channel_btn.getElement(), whispers_btn.getElement());
QWidget::setTabOrder(whispers_btn.getElement(), mentions_btn.getElement());
QWidget::setTabOrder(mentions_btn.getElement(), watching_btn.getElement());
// tab
NotebookTab *tab = notebook->addPage(obj.getElement());
tab->setCustomTitle("Twitch");
}
// irc
/*{
util::LayoutCreator<QWidget> obj(new QWidget());
auto vbox = obj.setLayoutType<QVBoxLayout>();
auto edit = vbox.emplace<QLabel>("not implemented");
NotebookTab2 *tab = notebook->addPage(obj.getElement());
tab->setTitle("Irc");
}*/
layout->setStretchFactor(notebook.getElement(), 1);
auto buttons = layout.emplace<QHBoxLayout>().emplace<QDialogButtonBox>(this);
{
auto *button_ok = buttons->addButton(QDialogButtonBox::Ok);
QObject::connect(button_ok, &QPushButton::clicked, [=](bool) { this->ok(); });
auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel);
QObject::connect(button_cancel, &QAbstractButton::clicked, [=](bool) { this->close(); });
}
this->setScaleIndependantSize(300, 210);
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.channel->setFocus();
// Shortcuts
auto *shortcut_ok = new QShortcut(QKeySequence("Return"), this);
QObject::connect(shortcut_ok, &QShortcut::activated, [=] { this->ok(); });
auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this);
QObject::connect(shortcut_cancel, &QShortcut::activated, [=] { this->close(); });
}
void SelectChannelDialog::ok()
{
this->_hasSelectedChannel = true;
this->close();
}
void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel)
{
auto channel = _channel.get();
assert(channel);
this->selectedChannel = channel;
switch (_channel.getType()) {
case Channel::Twitch: {
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.channel->setFocus();
this->ui_.twitch.channelName->setText(channel->name);
} break;
case Channel::TwitchWatching: {
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.watching->setFocus();
} break;
case Channel::TwitchMentions: {
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.mentions->setFocus();
} break;
case Channel::TwitchWhispers: {
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.whispers->setFocus();
} break;
default: {
this->ui_.notebook->selectIndex(TAB_TWITCH);
this->ui_.twitch.channel->setFocus();
}
}
this->_hasSelectedChannel = false;
}
IndirectChannel SelectChannelDialog::getSelectedChannel() const
{
if (!this->_hasSelectedChannel) {
return this->selectedChannel;
}
auto app = getApp();
switch (this->ui_.notebook->getSelectedIndex()) {
case TAB_TWITCH: {
if (this->ui_.twitch.channel->isChecked()) {
return app->twitch.server->getOrAddChannel(this->ui_.twitch.channelName->text());
} else if (this->ui_.twitch.watching->isChecked()) {
return app->twitch.server->watchingChannel;
} else if (this->ui_.twitch.mentions->isChecked()) {
return app->twitch.server->mentionsChannel;
} else if (this->ui_.twitch.whispers->isChecked()) {
return app->twitch.server->whispersChannel;
}
}
}
return this->selectedChannel;
}
bool SelectChannelDialog::hasSeletedChannel() const
{
return this->_hasSelectedChannel;
}
bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, QEvent *event)
{
auto *widget = (QWidget *)watched;
if (event->type() == QEvent::FocusIn) {
widget->grabKeyboard();
auto *radio = dynamic_cast<QRadioButton *>(watched);
if (radio) {
radio->setChecked(true);
}
return true;
} else if (event->type() == QEvent::FocusOut) {
widget->releaseKeyboard();
return false;
} else if (event->type() == QEvent::KeyPress) {
QKeyEvent *event_key = static_cast<QKeyEvent *>(event);
if ((event_key->key() == Qt::Key_Tab || event_key->key() == Qt::Key_Down) &&
event_key->modifiers() == Qt::NoModifier) {
if (widget == this->dialog->ui_.twitch.channelName) {
this->dialog->ui_.twitch.whispers->setFocus();
return true;
} else {
widget->nextInFocusChain()->setFocus();
}
return true;
} else if (((event_key->key() == Qt::Key_Tab || event_key->key() == Qt::Key_Backtab) &&
event_key->modifiers() == Qt::ShiftModifier) ||
((event_key->key() == Qt::Key_Up) && event_key->modifiers() == Qt::NoModifier)) {
if (widget == this->dialog->ui_.twitch.channelName) {
this->dialog->ui_.twitch.watching->setFocus();
return true;
} else if (widget == this->dialog->ui_.twitch.whispers) {
this->dialog->ui_.twitch.channel->setFocus();
return true;
}
widget->previousInFocusChain()->setFocus();
return true;
} else {
return false;
}
return true;
} else if (event->type() == QEvent::KeyRelease) {
QKeyEvent *event_key = static_cast<QKeyEvent *>(event);
if ((event_key->key() == Qt::Key_Backtab || event_key->key() == Qt::Key_Down) &&
event_key->modifiers() == Qt::NoModifier) {
return true;
}
}
return false;
}
void SelectChannelDialog::closeEvent(QCloseEvent *)
{
this->closed.invoke();
}
void SelectChannelDialog::themeRefreshEvent()
{
BaseWindow::themeRefreshEvent();
if (this->themeManager->isLightTheme()) {
this->setStyleSheet("QRadioButton { color: #000 } QLabel { color: #000 }");
} else {
this->setStyleSheet("QRadioButton { color: #fff } QLabel { color: #fff }");
}
}
} // namespace widgets
} // namespace chatterino
@@ -0,0 +1,61 @@
#pragma once
#include "Channel.hpp"
#include "widgets/BaseWindow.hpp"
#include "widgets/Notebook.hpp"
#include <pajlada/signals/signal.hpp>
#include <QLabel>
#include <QRadioButton>
namespace chatterino {
namespace widgets {
class SelectChannelDialog : public BaseWindow
{
public:
SelectChannelDialog(QWidget *parent = nullptr);
void setSelectedChannel(IndirectChannel selectedChannel);
IndirectChannel getSelectedChannel() const;
bool hasSeletedChannel() const;
pajlada::Signals::NoArgSignal closed;
protected:
virtual void closeEvent(QCloseEvent *) override;
virtual void themeRefreshEvent() override;
private:
class EventFilter : public QObject
{
public:
SelectChannelDialog *dialog;
protected:
virtual bool eventFilter(QObject *watched, QEvent *event) override;
};
struct {
Notebook *notebook;
struct {
QRadioButton *channel;
QLineEdit *channelName;
QRadioButton *whispers;
QRadioButton *mentions;
QRadioButton *watching;
} twitch;
} ui_;
EventFilter tabFilter;
ChannelPtr selectedChannel;
bool _hasSelectedChannel = false;
void ok();
friend class EventFilter;
};
} // namespace widgets
} // namespace chatterino
+228
View File
@@ -0,0 +1,228 @@
#include "widgets/dialogs/SettingsDialog.hpp"
#include "Application.hpp"
#include "util/LayoutCreator.hpp"
#include "widgets/helper/SettingsDialogTab.hpp"
#include "widgets/settingspages/AboutPage.hpp"
#include "widgets/settingspages/AccountsPage.hpp"
#include "widgets/settingspages/AppearancePage.hpp"
#include "widgets/settingspages/BehaviourPage.hpp"
#include "widgets/settingspages/BrowserextensionPage.hpp"
#include "widgets/settingspages/CommandPage.hpp"
#include "widgets/settingspages/EmotesPage.hpp"
#include "widgets/settingspages/ExternaltoolsPage.hpp"
#include "widgets/settingspages/HighlightingPage.hpp"
#include "widgets/settingspages/IgnoreusersPage.hpp"
#include "widgets/settingspages/KeyboardsettingsPage.hpp"
#include "widgets/settingspages/LogsPage.hpp"
#include "widgets/settingspages/ModerationPage.hpp"
#include "widgets/settingspages/SpecialChannelsPage.hpp"
#include <QDialogButtonBox>
namespace chatterino {
namespace widgets {
SettingsDialog *SettingsDialog::handle = nullptr;
SettingsDialog::SettingsDialog()
: BaseWindow(nullptr, BaseWindow::DisableCustomScaling)
{
this->initUi();
this->addTabs();
this->scaleChangedEvent(this->getScale());
}
void SettingsDialog::initUi()
{
util::LayoutCreator<SettingsDialog> layoutCreator(this);
// tab pages
layoutCreator.emplace<QWidget>()
.assign(&this->ui_.tabContainerContainer)
.emplace<QVBoxLayout>()
.withoutMargin()
.assign(&this->ui_.tabContainer);
// right side layout
auto right = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
{
right.emplace<QStackedLayout>().assign(&this->ui_.pageStack).withoutMargin();
auto buttons = right.emplace<QDialogButtonBox>(Qt::Horizontal);
{
this->ui_.okButton = buttons->addButton("Ok", QDialogButtonBox::YesRole);
this->ui_.cancelButton = buttons->addButton("Cancel", QDialogButtonBox::NoRole);
}
}
// ---- misc
this->ui_.tabContainerContainer->setObjectName("tabWidget");
this->ui_.pageStack->setObjectName("pages");
QObject::connect(this->ui_.okButton, &QPushButton::clicked, this,
&SettingsDialog::okButtonClicked);
QObject::connect(this->ui_.cancelButton, &QPushButton::clicked, this,
&SettingsDialog::cancelButtonClicked);
}
SettingsDialog *SettingsDialog::getHandle()
{
return SettingsDialog::handle;
}
void SettingsDialog::addTabs()
{
this->ui_.tabContainer->setSpacing(0);
this->addTab(new settingspages::AccountsPage);
this->ui_.tabContainer->addSpacing(16);
this->addTab(new settingspages::AppearancePage);
this->addTab(new settingspages::BehaviourPage);
this->ui_.tabContainer->addSpacing(16);
this->addTab(new settingspages::CommandPage);
// this->addTab(new settingspages::EmotesPage);
this->addTab(new settingspages::HighlightingPage);
this->addTab(new settingspages::IgnoreUsersPage);
this->ui_.tabContainer->addSpacing(16);
this->addTab(new settingspages::KeyboardSettingsPage);
// this->addTab(new settingspages::LogsPage);
this->addTab(new settingspages::ModerationPage);
// this->addTab(new settingspages::SpecialChannelsPage);
this->addTab(new settingspages::BrowserExtensionPage);
this->addTab(new settingspages::ExternalToolsPage);
this->ui_.tabContainer->addStretch(1);
this->addTab(new settingspages::AboutPage, Qt::AlignBottom);
}
void SettingsDialog::addTab(settingspages::SettingsPage *page, Qt::Alignment alignment)
{
auto tab = new SettingsDialogTab(this, page, page->getIconResource());
this->ui_.pageStack->addWidget(page);
this->ui_.tabContainer->addWidget(tab, 0, alignment);
this->tabs.push_back(tab);
if (this->tabs.size() == 1) {
this->select(tab);
}
}
void SettingsDialog::select(SettingsDialogTab *tab)
{
this->ui_.pageStack->setCurrentWidget(tab->getSettingsPage());
if (this->selectedTab != nullptr) {
this->selectedTab->setSelected(false);
this->selectedTab->setStyleSheet("color: #FFF");
}
tab->setSelected(true);
tab->setStyleSheet("background: #555; color: #FFF");
this->selectedTab = tab;
}
void SettingsDialog::showDialog(PreferredTab preferredTab)
{
static SettingsDialog *instance = new SettingsDialog();
instance->refresh();
switch (preferredTab) {
case SettingsDialog::PreferredTab::Accounts: {
instance->select(instance->tabs.at(0));
} break;
}
instance->show();
instance->activateWindow();
instance->raise();
instance->setFocus();
}
void SettingsDialog::refresh()
{
// this->ui.accountSwitchWidget->refresh();
getApp()->settings->saveSnapshot();
for (auto *tab : this->tabs) {
tab->getSettingsPage()->onShow();
}
}
void SettingsDialog::scaleChangedEvent(float newDpi)
{
QFile file(":/qss/settings.qss");
file.open(QFile::ReadOnly);
QString styleSheet = QLatin1String(file.readAll());
styleSheet.replace("<font-size>", QString::number((int)(14 * newDpi)));
styleSheet.replace("<checkbox-size>", QString::number((int)(14 * newDpi)));
for (SettingsDialogTab *tab : this->tabs) {
tab->setFixedHeight((int)(30 * newDpi));
}
this->setStyleSheet(styleSheet);
this->ui_.tabContainerContainer->setFixedWidth((int)(200 * newDpi));
}
void SettingsDialog::themeRefreshEvent()
{
BaseWindow::themeRefreshEvent();
QPalette palette;
palette.setColor(QPalette::Background, QColor("#444"));
this->setPalette(palette);
}
// void SettingsDialog::setChildrensFont(QLayout *object, QFont &font, int indent)
//{
// // for (QWidget *widget : this->widgets) {
// // widget->setFont(font);
// // }
// // for (int i = 0; i < object->count(); i++) {
// // if (object->itemAt(i)->layout()) {
// // setChildrensFont(object->layout()->itemAt(i)->layout(), font, indent + 2);
// // }
// // if (object->itemAt(i)->widget()) {
// // object->itemAt(i)->widget()->setFont(font);
// // if (object->itemAt(i)->widget()->layout() &&
// // !object->itemAt(i)->widget()->layout()->isEmpty()) {
// // setChildrensFont(object->itemAt(i)->widget()->layout(), font, indent +
// 2);
// // }
// // }
// // }
//}
///// Widget creation helpers
void SettingsDialog::okButtonClicked()
{
this->close();
}
void SettingsDialog::cancelButtonClicked()
{
for (auto &tab : this->tabs) {
tab->getSettingsPage()->cancel();
}
getApp()->settings->recallSnapshot();
this->close();
}
} // namespace widgets
} // namespace chatterino
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include "widgets/BaseWindow.hpp"
#include <QPushButton>
#include <QStackedLayout>
#include <QVBoxLayout>
#include <QWidget>
#include <pajlada/settings/setting.hpp>
namespace chatterino {
namespace widgets {
namespace settingspages {
class SettingsPage;
} // namespace settingspages
class SettingsDialogTab;
class SettingsDialog : public BaseWindow
{
public:
SettingsDialog();
static SettingsDialog *getHandle(); // may be NULL
enum class PreferredTab {
NoPreference,
Accounts,
};
static void showDialog(PreferredTab preferredTab = PreferredTab::NoPreference);
protected:
virtual void scaleChangedEvent(float newDpi) override;
virtual void themeRefreshEvent() override;
private:
void refresh();
static SettingsDialog *handle;
struct {
QWidget *tabContainerContainer;
QVBoxLayout *tabContainer;
QStackedLayout *pageStack;
QPushButton *okButton;
QPushButton *cancelButton;
} ui_;
std::vector<SettingsDialogTab *> tabs;
void initUi();
void addTabs();
void addTab(settingspages::SettingsPage *page, Qt::Alignment alignment = Qt::AlignTop);
void select(SettingsDialogTab *tab);
SettingsDialogTab *selectedTab = nullptr;
void okButtonClicked();
void cancelButtonClicked();
friend class SettingsDialogTab;
// static void setChildrensFont(QLayout *object, QFont &font, int indent = 0);
};
} // namespace widgets
} // namespace chatterino
+46
View File
@@ -0,0 +1,46 @@
#include "widgets/dialogs/TextInputDialog.hpp"
#include <QSizePolicy>
namespace chatterino {
namespace widgets {
TextInputDialog::TextInputDialog(QWidget *parent)
: QDialog(parent)
, _vbox(this)
, _okButton("OK")
, _cancelButton("Cancel")
{
_vbox.addWidget(&_lineEdit);
_vbox.addLayout(&_buttonBox);
_buttonBox.addStretch(1);
_buttonBox.addWidget(&_okButton);
_buttonBox.addWidget(&_cancelButton);
QObject::connect(&_okButton, SIGNAL(clicked()), this, SLOT(okButtonClicked()));
QObject::connect(&_cancelButton, SIGNAL(clicked()), this, SLOT(cancelButtonClicked()));
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setWindowFlags((windowFlags() & ~(Qt::WindowContextHelpButtonHint)) | Qt::Dialog |
Qt::MSWindowsFixedSizeDialogHint);
}
void TextInputDialog::okButtonClicked()
{
accept();
close();
}
void TextInputDialog::cancelButtonClicked()
{
reject();
close();
}
void TextInputDialog::highlightText()
{
this->_lineEdit.selectAll();
}
} // namespace widgets
} // namespace chatterino
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <QDialog>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QString>
#include <QVBoxLayout>
namespace chatterino {
namespace widgets {
class TextInputDialog : public QDialog
{
Q_OBJECT
public:
TextInputDialog(QWidget *parent = nullptr);
QString getText() const
{
return _lineEdit.text();
}
void setText(const QString &text)
{
_lineEdit.setText(text);
}
void highlightText();
private:
QVBoxLayout _vbox;
QLineEdit _lineEdit;
QHBoxLayout _buttonBox;
QPushButton _okButton;
QPushButton _cancelButton;
private slots:
void okButtonClicked();
void cancelButtonClicked();
};
} // namespace widgets
} // namespace chatterino
+408
View File
@@ -0,0 +1,408 @@
#include "UserInfoPopup.hpp"
#include "Application.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "singletons/ResourceManager.hpp"
#include "util/LayoutCreator.hpp"
#include "util/PostToThread.hpp"
#include "util/UrlFetch.hpp"
#include "widgets/helper/Line.hpp"
#include "widgets/helper/RippleEffectLabel.hpp"
#include "widgets/Label.hpp"
#include <QCheckBox>
#include <QDesktopServices>
#include <QLabel>
#define TEXT_FOLLOWERS "Followers: "
#define TEXT_VIEWS "Views: "
#define TEXT_CREATED "Created: "
namespace chatterino {
namespace widgets {
UserInfoPopup::UserInfoPopup()
: BaseWindow(nullptr, BaseWindow::Flags(BaseWindow::Frameless | BaseWindow::DeleteOnFocusOut |
BaseWindow::FramelessDraggable))
, hack_(new bool)
{
this->setStayInScreenRect(true);
#ifdef Q_OS_LINUX
this->setWindowFlag(Qt::Popup);
#endif
auto app = getApp();
auto layout = util::LayoutCreator<UserInfoPopup>(this).setLayoutType<QVBoxLayout>();
// first line
auto head = layout.emplace<QHBoxLayout>().withoutMargin();
{
// avatar
auto avatar = head.emplace<RippleEffectButton>(nullptr).assign(&this->ui_.avatarButton);
avatar->setScaleIndependantSize(100, 100);
QObject::connect(avatar.getElement(), &RippleEffectButton::clicked, [this] {
QDesktopServices::openUrl(QUrl("https://twitch.tv/" + this->userName_));
});
// items on the right
auto vbox = head.emplace<QVBoxLayout>();
{
auto name = vbox.emplace<Label>().assign(&this->ui_.nameLabel);
auto font = name->font();
font.setBold(true);
name->setFont(font);
vbox.emplace<Label>(TEXT_VIEWS).assign(&this->ui_.viewCountLabel);
vbox.emplace<Label>(TEXT_FOLLOWERS).assign(&this->ui_.followerCountLabel);
vbox.emplace<Label>(TEXT_CREATED).assign(&this->ui_.createdDateLabel);
}
}
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 mod = user.emplace<RippleEffectButton>(this);
mod->setPixmap(app->resources->buttons.mod);
mod->setScaleIndependantSize(30, 30);
auto unmod = user.emplace<RippleEffectButton>(this);
unmod->setPixmap(app->resources->buttons.unmod);
unmod->setScaleIndependantSize(30, 30);
user->addStretch(1);
QObject::connect(mod.getElement(), &RippleEffectButton::clicked,
[this] { this->channel_->sendMessage("/mod " + this->userName_); });
QObject::connect(unmod.getElement(), &RippleEffectButton::clicked,
[this] { this->channel_->sendMessage("/unmod " + this->userName_); });
// userstate
this->userStateChanged.connect([this, mod, unmod]() mutable {
providers::twitch::TwitchChannel *twitchChannel =
dynamic_cast<providers::twitch::TwitchChannel *>(this->channel_.get());
if (twitchChannel) {
qDebug() << this->userName_;
bool isMyself =
QString::compare(getApp()->accounts->twitch.getCurrent()->getUserName(),
this->userName_, Qt::CaseInsensitive) == 0;
mod->setVisible(twitchChannel->isBroadcaster() && !isMyself);
unmod->setVisible((twitchChannel->isBroadcaster() && !isMyself) ||
(twitchChannel->isMod() && isMyself));
}
});
}
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 {
providers::twitch::TwitchChannel *twitchChannel =
dynamic_cast<providers::twitch::TwitchChannel *>(this->channel_.get());
if (twitchChannel) {
lineMod->setVisible(twitchChannel->hasModRights());
timeout->setVisible(twitchChannel->hasModRights());
}
});
timeout->buttonClicked.connect([this](auto item) {
TimeoutWidget::Action action;
int arg;
std::tie(action, arg) = item;
switch (action) {
case TimeoutWidget::Ban: {
if (this->channel_) {
this->channel_->sendMessage("/ban " + this->userName_);
}
} break;
case TimeoutWidget::Unban: {
if (this->channel_) {
this->channel_->sendMessage("/unban " + this->userName_);
}
} break;
case TimeoutWidget::Timeout: {
if (this->channel_) {
this->channel_->sendMessage("/timeout " + this->userName_ + " " +
QString::number(arg));
}
} break;
}
});
}
this->setStyleSheet("font-size: 11pt;");
this->installEvents();
}
void UserInfoPopup::themeRefreshEvent()
{
BaseWindow::themeRefreshEvent();
this->setStyleSheet("background: #333");
}
void UserInfoPopup::installEvents()
{
std::weak_ptr<bool> hack = this->hack_;
// follow
QObject::connect(this->ui_.follow, &QCheckBox::stateChanged, [this](int) mutable {
auto currentUser = getApp()->accounts->twitch.getCurrent();
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + currentUser->getUserId() +
"/follows/channels/" + this->userId_);
this->ui_.follow->setEnabled(false);
if (this->ui_.follow->isChecked()) {
util::twitch::put(requestUrl,
[this](QJsonObject) { this->ui_.follow->setEnabled(true); });
} else {
util::twitch::sendDelete(requestUrl, [this] { this->ui_.follow->setEnabled(true); });
}
});
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);
}
});
}
});
}
void UserInfoPopup::setData(const QString &name, const ChannelPtr &channel)
{
this->userName_ = name;
this->channel_ = channel;
this->ui_.nameLabel->setText(name);
this->updateUserData();
this->userStateChanged.invoke();
}
void UserInfoPopup::updateUserData()
{
std::weak_ptr<bool> hack = this->hack_;
// get user info
util::twitch::getUserID(this->userName_, this, [this, hack](QString id) {
auto currentUser = getApp()->accounts->twitch.getCurrent();
this->userId_ = id;
// get channel info
util::twitch::get(
"https://api.twitch.tv/kraken/channels/" + id, this, [this](const QJsonObject &obj) {
this->ui_.followerCountLabel->setText(
TEXT_FOLLOWERS + QString::number(obj.value("followers").toInt()));
this->ui_.viewCountLabel->setText(TEXT_VIEWS +
QString::number(obj.value("views").toInt()));
this->ui_.createdDateLabel->setText(
TEXT_CREATED + obj.value("created_at").toString().section("T", 0, 0));
this->loadAvatar(QUrl(obj.value("logo").toString()));
});
// get follow state
currentUser->checkFollow(id, [this, hack](auto result) {
if (hack.lock()) {
if (result != FollowResult_Failed) {
this->ui_.follow->setEnabled(true);
this->ui_.follow->setChecked(result == FollowResult_Following);
}
}
});
// get ignore state
bool isIgnoring = false;
for (const auto &ignoredUser : currentUser->getIgnores()) {
if (id == ignoredUser.id) {
isIgnoring = true;
break;
}
}
this->ui_.ignore->setEnabled(true);
this->ui_.ignore->setChecked(isIgnoring);
});
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 =
util::LayoutCreator<TimeoutWidget>(this).setLayoutType<QHBoxLayout>().withoutMargin();
QColor color1(255, 255, 255, 80);
QColor color2(255, 255, 255, 0);
int buttonWidth = 24;
int buttonWidth2 = 32;
int buttonHeight = 32;
layout->setSpacing(16);
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<RippleEffectButton>(nullptr);
button->setPixmap(pixmap);
button->setScaleIndependantSize(buttonHeight, buttonHeight);
button->setBorderColor(QColor(255, 255, 255, 127));
QObject::connect(button.getElement(), &RippleEffectButton::clicked, [this, action] {
this->buttonClicked.invoke(std::make_pair(action, -1));
});
}
}
};
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<RippleEffectLabel2>();
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(), &RippleEffectLabel2::clicked, [
this, timeout = std::get<1>(item)
] { this->buttonClicked.invoke(std::make_pair(Action::Timeout, timeout)); });
}
}
};
addButton(Unban, "unban", getApp()->resources->buttons.unban);
addTimeouts("sec", {{"1", 1}});
addTimeouts("min", {
{"1", 1 * 60}, {"5", 5 * 60}, {"10", 10 * 60},
});
addTimeouts("hour", {
{"1", 1 * 60 * 60}, {"4", 4 * 60 * 60},
});
addTimeouts("days", {
{"1", 1 * 60 * 60 * 24}, {"3", 3 * 60 * 60 * 24},
});
addTimeouts("weeks", {
{"1", 1 * 60 * 60 * 24 * 7}, {"2", 2 * 60 * 60 * 24 * 7},
});
addButton(Ban, "ban", getApp()->resources->buttons.ban);
}
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 widgets
} // namespace chatterino
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include "Channel.hpp"
#include "widgets/BaseWindow.hpp"
#include <pajlada/signals/signal.hpp>
class QCheckBox;
namespace chatterino {
namespace widgets {
class Label;
class UserInfoPopup final : public BaseWindow
{
Q_OBJECT
public:
UserInfoPopup();
void setData(const QString &name, const ChannelPtr &channel);
protected:
virtual void themeRefreshEvent() override;
private:
bool isMod_;
bool isBroadcaster_;
QString userName_;
QString userId_;
ChannelPtr channel_;
pajlada::Signals::NoArgSignal userStateChanged;
void installEvents();
void updateUserData();
void loadAvatar(const QUrl &url);
std::shared_ptr<bool> hack_;
struct {
RippleEffectButton *avatarButton = nullptr;
Label *nameLabel = nullptr;
Label *viewCountLabel = nullptr;
Label *followerCountLabel = nullptr;
Label *createdDateLabel = nullptr;
QCheckBox *follow = nullptr;
QCheckBox *ignore = nullptr;
QCheckBox *ignoreHighlights = nullptr;
} ui_;
class TimeoutWidget : public BaseWidget
{
public:
enum Action { Ban, Unban, Timeout };
TimeoutWidget();
pajlada::Signals::Signal<std::pair<Action, int>> buttonClicked;
protected:
void paintEvent(QPaintEvent *event) override;
};
};
} // namespace widgets
} // namespace chatterino
+13
View File
@@ -0,0 +1,13 @@
#include "WelcomeDialog.hpp"
namespace chatterino {
namespace widgets {
WelcomeDialog::WelcomeDialog()
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
{
this->setWindowTitle("Chatterino quick setup");
}
} // namespace widgets
} // namespace chatterino
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "widgets/BaseWindow.hpp"
namespace chatterino {
namespace widgets {
class WelcomeDialog : public BaseWindow
{
public:
WelcomeDialog();
};
} // namespace widgets
} // namespace chatterino