Merge remote-tracking branch 'main_repo/master' into git_is_pepega
This commit is contained in:
@@ -29,8 +29,6 @@ BOOL CALLBACK enumWindows(HWND hwnd, LPARAM)
|
||||
auto className = std::make_unique<WCHAR[]>(length);
|
||||
GetClassName(hwnd, className.get(), length);
|
||||
|
||||
// qDebug() << QString::fromWCharArray(className.get(), length);
|
||||
|
||||
if (lstrcmp(className.get(), L"Shell_TrayWnd") == 0 ||
|
||||
lstrcmp(className.get(), L"Shell_Secondary") == 0)
|
||||
{
|
||||
|
||||
@@ -384,7 +384,6 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
|
||||
|
||||
if (!recursiveCheckMouseTracking(widget))
|
||||
{
|
||||
qDebug() << "Start moving";
|
||||
this->moving = true;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +400,6 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->moving)
|
||||
{
|
||||
qDebug() << "Stop moving";
|
||||
this->moving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ void Notebook::select(QWidget *page)
|
||||
{
|
||||
if (containsChild(page, item.selectedWidget))
|
||||
{
|
||||
qDebug() << item.selectedWidget;
|
||||
item.selectedWidget->setFocus(Qt::MouseFocusReason);
|
||||
}
|
||||
else
|
||||
|
||||
+22
-14
@@ -3,6 +3,7 @@
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
@@ -242,6 +243,8 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), this->theme->scrollbars.background);
|
||||
|
||||
bool enableRedeemedHighlights = getSettings()->enableRedeemedHighlight;
|
||||
|
||||
// painter.fillRect(QRect(xOffset, 0, width(), this->buttonHeight),
|
||||
// this->themeManager->ScrollbarArrow);
|
||||
// painter.fillRect(QRect(xOffset, height() - this->buttonHeight,
|
||||
@@ -274,7 +277,8 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
||||
int w = this->width();
|
||||
float y = 0;
|
||||
float dY = float(this->height()) / float(snapshotLength);
|
||||
int highlightHeight = int(std::ceil(dY));
|
||||
int highlightHeight =
|
||||
int(std::ceil(std::max<float>(this->scale() * 2, dY)));
|
||||
|
||||
for (size_t i = 0; i < snapshotLength; i++)
|
||||
{
|
||||
@@ -282,22 +286,26 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
||||
|
||||
if (!highlight.isNull())
|
||||
{
|
||||
QColor color = highlight.getColor();
|
||||
|
||||
switch (highlight.getStyle())
|
||||
if (!highlight.isRedeemedHighlight() || enableRedeemedHighlights)
|
||||
{
|
||||
case ScrollbarHighlight::Default: {
|
||||
painter.fillRect(w / 8 * 3, int(y), w / 4, highlightHeight,
|
||||
color);
|
||||
}
|
||||
break;
|
||||
QColor color = highlight.getColor();
|
||||
color.setAlpha(255);
|
||||
|
||||
case ScrollbarHighlight::Line: {
|
||||
painter.fillRect(0, int(y), w, 1, color);
|
||||
}
|
||||
break;
|
||||
switch (highlight.getStyle())
|
||||
{
|
||||
case ScrollbarHighlight::Default: {
|
||||
painter.fillRect(w / 8 * 3, int(y), w / 4,
|
||||
highlightHeight, color);
|
||||
}
|
||||
break;
|
||||
|
||||
case ScrollbarHighlight::None:;
|
||||
case ScrollbarHighlight::Line: {
|
||||
painter.fillRect(0, int(y), w, 1, color);
|
||||
}
|
||||
break;
|
||||
|
||||
case ScrollbarHighlight::None:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include "EmotePopup.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/CompletionModel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/Shortcut.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
@@ -37,7 +39,8 @@ namespace {
|
||||
std::sort(vec.begin(), vec.end(),
|
||||
[](const std::pair<EmoteName, EmotePtr> &l,
|
||||
const std::pair<EmoteName, EmotePtr> &r) {
|
||||
return l.first.string < r.first.string;
|
||||
return CompletionModel::compareStrings(
|
||||
l.first.string, r.first.string);
|
||||
});
|
||||
for (const auto &emote : vec)
|
||||
{
|
||||
@@ -111,6 +114,8 @@ namespace {
|
||||
EmotePopup::EmotePopup(QWidget *parent)
|
||||
: BasePopup(BaseWindow::EnableCustomFrame, parent)
|
||||
{
|
||||
this->moveTo(this, getApp()->windows->emotePopupPos(), false);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
this->getLayoutContainer()->setLayout(layout);
|
||||
|
||||
@@ -218,4 +223,9 @@ void EmotePopup::loadEmojis()
|
||||
this->viewEmojis_->setChannel(emojiChannel);
|
||||
}
|
||||
|
||||
void EmotePopup::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
getApp()->windows->setEmotePopupPos(this->pos());
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
void loadChannel(ChannelPtr channel);
|
||||
void loadEmojis();
|
||||
|
||||
virtual void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
pajlada::Signals::Signal<Link> linkClicked;
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
#ifdef USEWINSDK
|
||||
@@ -159,7 +158,6 @@ AdvancedLoginWidget::AdvancedLoginWidget()
|
||||
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();
|
||||
|
||||
@@ -207,29 +205,10 @@ AdvancedLoginWidget::AdvancedLoginWidget()
|
||||
|
||||
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,
|
||||
[=]() {
|
||||
const auto onIdFetched = [=](const QString &userID) {
|
||||
this->ui_.userIDInput.setText(userID); //
|
||||
};
|
||||
PartialTwitchUser::byName(this->ui_.usernameInput.text())
|
||||
.getId(onIdFetched, this);
|
||||
});
|
||||
}
|
||||
|
||||
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() ||
|
||||
|
||||
@@ -57,12 +57,6 @@ public:
|
||||
QPushButton addUserButton;
|
||||
QPushButton clearFieldsButton;
|
||||
} buttonUpperRow;
|
||||
|
||||
struct {
|
||||
QHBoxLayout layout;
|
||||
|
||||
QPushButton fillInUserIDButton;
|
||||
} buttonLowerRow;
|
||||
} ui_;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
@@ -152,7 +152,7 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||
auto editor = new IrcConnectionEditor(unique);
|
||||
if (editor->exec() == QDialog::Accepted)
|
||||
{
|
||||
Irc::instance().connections.appendItem(editor->data());
|
||||
Irc::instance().connections.append(editor->data());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,21 +160,20 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||
view->getTableView(), &QTableView::doubleClicked,
|
||||
[](const QModelIndex &index) {
|
||||
auto editor = new IrcConnectionEditor(
|
||||
Irc::instance()
|
||||
.connections.getVector()[size_t(index.row())]);
|
||||
Irc::instance().connections.raw()[size_t(index.row())]);
|
||||
|
||||
if (editor->exec() == QDialog::Accepted)
|
||||
{
|
||||
auto data = editor->data();
|
||||
auto &&conns = Irc::instance().connections.getVector();
|
||||
auto &&conns = Irc::instance().connections.raw();
|
||||
int i = 0;
|
||||
for (auto &&conn : conns)
|
||||
{
|
||||
if (conn.id == data.id)
|
||||
{
|
||||
Irc::instance().connections.removeItem(
|
||||
Irc::instance().connections.removeAt(
|
||||
i, Irc::noEraseCredentialCaller);
|
||||
Irc::instance().connections.insertItem(data, i);
|
||||
Irc::instance().connections.insert(data, i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -348,7 +347,7 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const
|
||||
->currentIndex()
|
||||
.row();
|
||||
|
||||
auto &&vector = Irc::instance().connections.getVector();
|
||||
auto &&vector = Irc::instance().connections.raw();
|
||||
|
||||
if (row >= 0 && row < int(vector.size()))
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/helper/Button.hpp"
|
||||
#include "widgets/helper/SettingsDialogTab.hpp"
|
||||
#include "widgets/settingspages/AboutPage.hpp"
|
||||
#include "widgets/settingspages/AccountsPage.hpp"
|
||||
#include "widgets/settingspages/CommandPage.hpp"
|
||||
@@ -21,22 +20,18 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
SettingsDialog *SettingsDialog::handle = nullptr;
|
||||
|
||||
SettingsDialog::SettingsDialog()
|
||||
: BaseWindow(BaseWindow::DisableCustomScaling)
|
||||
{
|
||||
this->setWindowTitle("Chatterino Settings");
|
||||
this->resize(815, 600);
|
||||
this->themeChangedEvent();
|
||||
this->scaleChangedEvent(this->scale());
|
||||
|
||||
this->initUi();
|
||||
this->addTabs();
|
||||
|
||||
this->scaleChangedEvent(this->scale());
|
||||
|
||||
this->overrideBackgroundColor_ = QColor("#111111");
|
||||
this->themeChangedEvent();
|
||||
|
||||
this->resize(815, 600);
|
||||
this->scaleChangedEvent(this->scale()); // execute twice to width of item
|
||||
}
|
||||
|
||||
void SettingsDialog::initUi()
|
||||
@@ -69,13 +64,9 @@ void SettingsDialog::initUi()
|
||||
.assign(&this->ui_.tabContainer);
|
||||
|
||||
// right side (pages)
|
||||
auto right =
|
||||
centerBox.emplace<QVBoxLayout>().withoutMargin().withoutSpacing();
|
||||
{
|
||||
right.emplace<QStackedLayout>()
|
||||
.assign(&this->ui_.pageStack)
|
||||
.withoutMargin();
|
||||
}
|
||||
centerBox.emplace<QStackedLayout>()
|
||||
.assign(&this->ui_.pageStack)
|
||||
.withoutMargin();
|
||||
|
||||
this->ui_.pageStack->setMargin(0);
|
||||
|
||||
@@ -103,10 +94,11 @@ void SettingsDialog::initUi()
|
||||
void SettingsDialog::filterElements(const QString &text)
|
||||
{
|
||||
// filter elements and hide pages
|
||||
for (auto &&page : this->pages_)
|
||||
for (auto &&tab : this->tabs_)
|
||||
{
|
||||
// filterElements returns true if anything on the page matches the search query
|
||||
page->tab()->setVisible(page->filterElements(text));
|
||||
tab->setVisible(tab->page()->filterElements(text) ||
|
||||
tab->name().contains(text, Qt::CaseInsensitive));
|
||||
}
|
||||
|
||||
// find next visible page
|
||||
@@ -144,11 +136,6 @@ void SettingsDialog::filterElements(const QString &text)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDialog *SettingsDialog::getHandle()
|
||||
{
|
||||
return SettingsDialog::handle;
|
||||
}
|
||||
|
||||
void SettingsDialog::addTabs()
|
||||
{
|
||||
this->ui_.tabContainer->setMargin(0);
|
||||
@@ -156,38 +143,34 @@ void SettingsDialog::addTabs()
|
||||
|
||||
this->ui_.tabContainer->setContentsMargins(0, 20, 0, 20);
|
||||
|
||||
this->addTab(new GeneralPage);
|
||||
// Constructors are wrapped in std::function to remove some strain from first time loading.
|
||||
|
||||
// clang-format off
|
||||
this->addTab([]{return new GeneralPage;}, "General", ":/settings/about.svg");
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
this->addTab(new AccountsPage);
|
||||
|
||||
this->addTab([]{return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts);
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
this->addTab(new CommandPage);
|
||||
this->addTab(new HighlightingPage);
|
||||
this->addTab(new IgnoresPage);
|
||||
|
||||
this->addTab([]{return new CommandPage;}, "Commands", ":/settings/commands.svg");
|
||||
this->addTab([]{return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg");
|
||||
this->addTab([]{return new IgnoresPage;}, "Ignores", ":/settings/ignore.svg");
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
this->addTab(new KeyboardSettingsPage);
|
||||
this->addTab(this->ui_.moderationPage = new ModerationPage);
|
||||
this->addTab(new NotificationPage);
|
||||
this->addTab(new ExternalToolsPage);
|
||||
|
||||
this->addTab([]{return new KeyboardSettingsPage;}, "Keybindings", ":/settings/keybinds.svg");
|
||||
this->addTab([]{return new ModerationPage;}, "Moderation", ":/settings/moderation.svg", SettingsTabId::Moderation);
|
||||
this->addTab([]{return new NotificationPage;}, "Notifications", ":/settings/notification2.svg");
|
||||
this->addTab([]{return new ExternalToolsPage;}, "External tools", ":/settings/externaltools.svg");
|
||||
this->ui_.tabContainer->addStretch(1);
|
||||
this->addTab(new AboutPage, Qt::AlignBottom);
|
||||
this->addTab([]{return new AboutPage;}, "About", ":/settings/about.svg", SettingsTabId(), Qt::AlignBottom);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void SettingsDialog::addTab(SettingsPage *page, Qt::Alignment alignment)
|
||||
void SettingsDialog::addTab(std::function<SettingsPage *()> page,
|
||||
const QString &name, const QString &iconPath,
|
||||
SettingsTabId id, Qt::Alignment alignment)
|
||||
{
|
||||
auto tab = new SettingsDialogTab(this, page, page->getIconResource());
|
||||
page->setTab(tab);
|
||||
auto tab = new SettingsDialogTab(this, std::move(page), name, iconPath, id);
|
||||
|
||||
this->ui_.pageStack->addWidget(page);
|
||||
this->ui_.tabContainer->addWidget(tab, 0, alignment);
|
||||
this->tabs_.push_back(tab);
|
||||
this->pages_.push_back(page);
|
||||
|
||||
if (this->tabs_.size() == 1)
|
||||
{
|
||||
@@ -197,7 +180,16 @@ void SettingsDialog::addTab(SettingsPage *page, Qt::Alignment alignment)
|
||||
|
||||
void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser)
|
||||
{
|
||||
this->ui_.pageStack->setCurrentWidget(tab->getSettingsPage());
|
||||
// add page if it's not been added yet
|
||||
[&] {
|
||||
for (int i = 0; i < this->ui_.pageStack->count(); i++)
|
||||
if (this->ui_.pageStack->itemAt(i)->widget() == tab->page())
|
||||
return;
|
||||
|
||||
this->ui_.pageStack->addWidget(tab->page());
|
||||
}();
|
||||
|
||||
this->ui_.pageStack->setCurrentWidget(tab->page());
|
||||
|
||||
if (this->selectedTab_ != nullptr)
|
||||
{
|
||||
@@ -215,28 +207,49 @@ void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser)
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::selectPage(SettingsPage *page)
|
||||
void SettingsDialog::selectTab(SettingsTabId id)
|
||||
{
|
||||
assert(page);
|
||||
assert(page->tab());
|
||||
auto t = this->tab(id);
|
||||
assert(t);
|
||||
if (!t)
|
||||
return;
|
||||
|
||||
this->selectTab(page->tab());
|
||||
this->selectTab(t);
|
||||
}
|
||||
|
||||
SettingsDialogTab *SettingsDialog::tab(SettingsTabId id)
|
||||
{
|
||||
for (auto &&tab : this->tabs_)
|
||||
if (tab->id() == id)
|
||||
return tab;
|
||||
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SettingsDialog::showDialog(SettingsDialogPreference preferredTab)
|
||||
{
|
||||
static SettingsDialog *instance = new SettingsDialog();
|
||||
instance->refresh();
|
||||
static bool hasShownBefore = false;
|
||||
if (hasShownBefore)
|
||||
instance->refresh();
|
||||
hasShownBefore = true;
|
||||
|
||||
switch (preferredTab)
|
||||
{
|
||||
case SettingsDialogPreference::Accounts:
|
||||
instance->selectTab(instance->tabs_.at(1));
|
||||
instance->selectTab(SettingsTabId::Accounts);
|
||||
break;
|
||||
|
||||
case SettingsDialogPreference::ModerationActions:
|
||||
instance->selectPage(instance->ui_.moderationPage);
|
||||
instance->ui_.moderationPage->selectModerationActions();
|
||||
if (auto tab = instance->tab(SettingsTabId::Moderation))
|
||||
{
|
||||
instance->selectTab(tab);
|
||||
if (auto page = dynamic_cast<ModerationPage *>(tab->page()))
|
||||
{
|
||||
page->selectModerationActions();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
@@ -250,11 +263,13 @@ void SettingsDialog::showDialog(SettingsDialogPreference preferredTab)
|
||||
|
||||
void SettingsDialog::refresh()
|
||||
{
|
||||
// Resets the cancel button.
|
||||
getSettings()->saveSnapshot();
|
||||
|
||||
// Updates tabs.
|
||||
for (auto *tab : this->tabs_)
|
||||
{
|
||||
tab->getSettingsPage()->onShow();
|
||||
tab->page()->onShow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +288,8 @@ void SettingsDialog::scaleChangedEvent(float newDpi)
|
||||
|
||||
this->setStyleSheet(styleSheet);
|
||||
|
||||
this->ui_.tabContainerContainer->setFixedWidth(int(150 * newDpi));
|
||||
if (this->ui_.tabContainerContainer)
|
||||
this->ui_.tabContainerContainer->setFixedWidth(int(150 * newDpi));
|
||||
}
|
||||
|
||||
void SettingsDialog::themeChangedEvent()
|
||||
@@ -281,7 +297,7 @@ void SettingsDialog::themeChangedEvent()
|
||||
BaseWindow::themeChangedEvent();
|
||||
|
||||
QPalette palette;
|
||||
palette.setColor(QPalette::Background, QColor("#111"));
|
||||
palette.setColor(QPalette::Window, QColor("#111"));
|
||||
this->setPalette(palette);
|
||||
}
|
||||
|
||||
@@ -301,7 +317,7 @@ void SettingsDialog::onCancelClicked()
|
||||
{
|
||||
for (auto &tab : this->tabs_)
|
||||
{
|
||||
tab->getSettingsPage()->cancel();
|
||||
tab->page()->cancel();
|
||||
}
|
||||
|
||||
getSettings()->restoreSnapshot();
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#include <QStackedLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <functional>
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include "widgets/helper/SettingsDialogTab.hpp"
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
@@ -32,7 +34,6 @@ class SettingsDialog : public BaseWindow
|
||||
public:
|
||||
SettingsDialog();
|
||||
|
||||
static SettingsDialog *getHandle(); // may be NULL
|
||||
static void showDialog(SettingsDialogPreference preferredTab =
|
||||
SettingsDialogPreference::NoPreference);
|
||||
|
||||
@@ -42,15 +43,16 @@ protected:
|
||||
virtual void showEvent(QShowEvent *) override;
|
||||
|
||||
private:
|
||||
static SettingsDialog *handle;
|
||||
|
||||
void refresh();
|
||||
|
||||
void initUi();
|
||||
SettingsDialogTab *tab(SettingsTabId id);
|
||||
void addTabs();
|
||||
void addTab(SettingsPage *page, Qt::Alignment alignment = Qt::AlignTop);
|
||||
void selectTab(SettingsDialogTab *tab, bool byUser = true);
|
||||
void selectPage(SettingsPage *page);
|
||||
void addTab(std::function<SettingsPage *()> page, const QString &name,
|
||||
const QString &iconPath, SettingsTabId id = {},
|
||||
Qt::Alignment alignment = Qt::AlignTop);
|
||||
void selectTab(SettingsDialogTab *tab, const bool byUser = true);
|
||||
void selectTab(SettingsTabId id);
|
||||
void filterElements(const QString &query);
|
||||
|
||||
void onOkClicked();
|
||||
@@ -62,11 +64,9 @@ private:
|
||||
QStackedLayout *pageStack{};
|
||||
QPushButton *okButton{};
|
||||
QPushButton *cancelButton{};
|
||||
ModerationPage *moderationPage{};
|
||||
QLineEdit *search{};
|
||||
} ui_;
|
||||
std::vector<SettingsDialogTab *> tabs_;
|
||||
std::vector<SettingsPage *> pages_;
|
||||
SettingsDialogTab *selectedTab_{};
|
||||
SettingsDialogTab *lastSelectedByUser_{};
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/api/Kraken.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "widgets/Label.hpp"
|
||||
@@ -21,9 +23,9 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#define TEXT_FOLLOWERS "Followers: "
|
||||
#define TEXT_VIEWS "Views: "
|
||||
#define TEXT_CREATED "Created: "
|
||||
const QString TEXT_VIEWS("Views: %1");
|
||||
const QString TEXT_FOLLOWERS("Followers: %1");
|
||||
const QString TEXT_CREATED("Created: %1");
|
||||
#define TEXT_USER_ID "ID: "
|
||||
#define TEXT_UNAVAILABLE "(not available)"
|
||||
|
||||
@@ -91,10 +93,11 @@ UserInfoPopup::UserInfoPopup()
|
||||
this->ui_.userIDLabel->setPalette(palette);
|
||||
}
|
||||
|
||||
vbox.emplace<Label>(TEXT_VIEWS).assign(&this->ui_.viewCountLabel);
|
||||
vbox.emplace<Label>(TEXT_FOLLOWERS)
|
||||
vbox.emplace<Label>(TEXT_VIEWS.arg(""))
|
||||
.assign(&this->ui_.viewCountLabel);
|
||||
vbox.emplace<Label>(TEXT_FOLLOWERS.arg(""))
|
||||
.assign(&this->ui_.followerCountLabel);
|
||||
vbox.emplace<Label>(TEXT_CREATED)
|
||||
vbox.emplace<Label>(TEXT_CREATED.arg(""))
|
||||
.assign(&this->ui_.createdDateLabel);
|
||||
}
|
||||
}
|
||||
@@ -155,8 +158,6 @@ UserInfoPopup::UserInfoPopup()
|
||||
|
||||
if (twitchChannel)
|
||||
{
|
||||
qDebug() << this->userName_;
|
||||
|
||||
bool isMyself =
|
||||
QString::compare(
|
||||
getApp()->accounts->twitch.getCurrent()->getUserName(),
|
||||
@@ -259,10 +260,6 @@ void UserInfoPopup::installEvents()
|
||||
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_);
|
||||
|
||||
const auto reenableFollowCheckbox = [this] {
|
||||
this->ui_.follow->setEnabled(true); //
|
||||
};
|
||||
@@ -336,24 +333,23 @@ void UserInfoPopup::installEvents()
|
||||
|
||||
if (checked)
|
||||
{
|
||||
getApp()->highlights->blacklistedUsers.insertItem(
|
||||
getSettings()->blacklistedUsers.insert(
|
||||
HighlightBlacklistUser{this->userName_, false});
|
||||
this->ui_.ignoreHighlights->setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &vector =
|
||||
getApp()->highlights->blacklistedUsers.getVector();
|
||||
const auto &vector = getSettings()->blacklistedUsers.raw();
|
||||
|
||||
for (int i = 0; i < vector.size(); i++)
|
||||
{
|
||||
if (this->userName_ == vector[i].getPattern())
|
||||
{
|
||||
getApp()->highlights->blacklistedUsers.removeItem(i);
|
||||
getSettings()->blacklistedUsers.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (getApp()->highlights->blacklistContains(this->userName_))
|
||||
if (getSettings()->isBlacklistedUser(this->userName_))
|
||||
{
|
||||
this->ui_.ignoreHighlights->setToolTip(
|
||||
"Name matched by regex");
|
||||
@@ -383,14 +379,17 @@ void UserInfoPopup::updateUserData()
|
||||
{
|
||||
std::weak_ptr<bool> hack = this->hack_;
|
||||
|
||||
const auto onIdFetchFailed = [this]() {
|
||||
const auto onUserFetchFailed = [this, hack] {
|
||||
if (!hack.lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// this can occur when the account doesn't exist.
|
||||
this->ui_.followerCountLabel->setText(TEXT_FOLLOWERS +
|
||||
QString(TEXT_UNAVAILABLE));
|
||||
this->ui_.viewCountLabel->setText(TEXT_VIEWS +
|
||||
QString(TEXT_UNAVAILABLE));
|
||||
this->ui_.createdDateLabel->setText(TEXT_CREATED +
|
||||
QString(TEXT_UNAVAILABLE));
|
||||
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));
|
||||
|
||||
this->ui_.nameLabel->setText(this->userName_);
|
||||
|
||||
@@ -399,47 +398,59 @@ void UserInfoPopup::updateUserData()
|
||||
this->ui_.userIDLabel->setProperty("copy-text",
|
||||
QString(TEXT_UNAVAILABLE));
|
||||
};
|
||||
const auto onIdFetched = [this, hack](QString id) {
|
||||
const auto onUserFetched = [this, hack](const auto &user) {
|
||||
if (!hack.lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
this->userId_ = id;
|
||||
this->userId_ = user.id;
|
||||
|
||||
this->ui_.userIDLabel->setText(TEXT_USER_ID + id);
|
||||
this->ui_.userIDLabel->setProperty("copy-text", id);
|
||||
// don't wait for the request to complete, just put the user id in the card
|
||||
// right away
|
||||
this->ui_.userIDLabel->setText(TEXT_USER_ID + user.id);
|
||||
this->ui_.userIDLabel->setProperty("copy-text", user.id);
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/channels/" + id);
|
||||
|
||||
NetworkRequest::twitchRequest(url)
|
||||
.caller(this)
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
auto obj = result.parseJson();
|
||||
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_.viewCountLabel->setText(TEXT_VIEWS.arg(user.viewCount));
|
||||
getKraken()->getUser(
|
||||
user.id,
|
||||
[this, hack](const auto &user) {
|
||||
if (!hack.lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->ui_.createdDateLabel->setText(
|
||||
TEXT_CREATED +
|
||||
obj.value("created_at").toString().section("T", 0, 0));
|
||||
TEXT_CREATED.arg(user.createdAt.section("T", 0, 0)));
|
||||
},
|
||||
[] {
|
||||
// failure
|
||||
});
|
||||
this->loadAvatar(user.profileImageUrl);
|
||||
|
||||
this->loadAvatar(QUrl(obj.value("logo").toString()));
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
getHelix()->getUserFollowers(
|
||||
user.id,
|
||||
[this, hack](const auto &followers) {
|
||||
if (!hack.lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->ui_.followerCountLabel->setText(
|
||||
TEXT_FOLLOWERS.arg(followers.total));
|
||||
},
|
||||
[] {
|
||||
// on failure
|
||||
});
|
||||
|
||||
// get follow state
|
||||
currentUser->checkFollow(id, [this, hack](auto result) {
|
||||
if (hack.lock())
|
||||
currentUser->checkFollow(user.id, [this, hack](auto result) {
|
||||
if (!hack.lock())
|
||||
{
|
||||
if (result != FollowResult_Failed)
|
||||
{
|
||||
this->ui_.follow->setEnabled(true);
|
||||
this->ui_.follow->setChecked(result ==
|
||||
FollowResult_Following);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (result != FollowResult_Failed)
|
||||
{
|
||||
this->ui_.follow->setEnabled(true);
|
||||
this->ui_.follow->setChecked(result == FollowResult_Following);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -447,7 +458,7 @@ void UserInfoPopup::updateUserData()
|
||||
bool isIgnoring = false;
|
||||
for (const auto &ignoredUser : currentUser->getIgnores())
|
||||
{
|
||||
if (id == ignoredUser.id)
|
||||
if (user.id == ignoredUser.id)
|
||||
{
|
||||
isIgnoring = true;
|
||||
break;
|
||||
@@ -456,7 +467,7 @@ void UserInfoPopup::updateUserData()
|
||||
|
||||
// get ignoreHighlights state
|
||||
bool isIgnoringHighlights = false;
|
||||
const auto &vector = getApp()->highlights->blacklistedUsers.getVector();
|
||||
const auto &vector = getSettings()->blacklistedUsers.raw();
|
||||
for (int i = 0; i < vector.size(); i++)
|
||||
{
|
||||
if (this->userName_ == vector[i].getPattern())
|
||||
@@ -465,7 +476,7 @@ void UserInfoPopup::updateUserData()
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (getApp()->highlights->blacklistContains(this->userName_) &&
|
||||
if (getSettings()->isBlacklistedUser(this->userName_) &&
|
||||
!isIgnoringHighlights)
|
||||
{
|
||||
this->ui_.ignoreHighlights->setToolTip("Name matched by regex");
|
||||
@@ -479,8 +490,8 @@ void UserInfoPopup::updateUserData()
|
||||
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
|
||||
};
|
||||
|
||||
PartialTwitchUser::byName(this->userName_)
|
||||
.getId(onIdFetched, onIdFetchFailed, this);
|
||||
getHelix()->getUserByName(this->userName_, onUserFetched,
|
||||
onUserFetchFailed);
|
||||
|
||||
this->ui_.follow->setEnabled(false);
|
||||
this->ui_.ignore->setEnabled(false);
|
||||
|
||||
@@ -154,7 +154,6 @@ void Button::fancyPaint(QPainter &painter)
|
||||
return;
|
||||
}
|
||||
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
QColor c;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QGraphicsBlurEffect>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
@@ -24,6 +26,7 @@
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/TooltipPreviewImage.hpp"
|
||||
@@ -113,6 +116,10 @@ ChannelView::ChannelView(BaseWidget *parent)
|
||||
this->initializeScrollbar();
|
||||
this->initializeSignals();
|
||||
|
||||
this->cursors_.neutral = QCursor(getResources().scrolling.neutralScroll);
|
||||
this->cursors_.up = QCursor(getResources().scrolling.upScroll);
|
||||
this->cursors_.down = QCursor(getResources().scrolling.downScroll);
|
||||
|
||||
this->pauseTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->pauseTimer_, &QTimer::timeout, this, [this] {
|
||||
/// remove elements that are finite
|
||||
@@ -130,6 +137,10 @@ ChannelView::ChannelView(BaseWidget *parent)
|
||||
this->clickTimer_->setSingleShot(true);
|
||||
this->clickTimer_->setInterval(500);
|
||||
|
||||
this->scrollTimer_.setInterval(20);
|
||||
QObject::connect(&this->scrollTimer_, &QTimer::timeout, this,
|
||||
&ChannelView::scrollUpdateRequested);
|
||||
|
||||
this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
|
||||
}
|
||||
|
||||
@@ -1059,8 +1070,13 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->isScrolling_)
|
||||
{
|
||||
this->currentMousePosition_ = event->screenPos();
|
||||
}
|
||||
|
||||
// is selecting
|
||||
if (this->isMouseDown_)
|
||||
if (this->isLeftMouseDown_)
|
||||
{
|
||||
// this->pause(PauseReason::Selecting, 300);
|
||||
int index = layout->getSelectionIndex(relativePos);
|
||||
@@ -1325,8 +1341,11 @@ void ChannelView::mousePressEvent(QMouseEvent *event)
|
||||
switch (event->button())
|
||||
{
|
||||
case Qt::LeftButton: {
|
||||
this->lastPressPosition_ = event->screenPos();
|
||||
this->isMouseDown_ = true;
|
||||
if (this->isScrolling_)
|
||||
this->disableScrolling();
|
||||
|
||||
this->lastLeftPressPosition_ = event->screenPos();
|
||||
this->isLeftMouseDown_ = true;
|
||||
|
||||
if (layout->flags.has(MessageLayoutFlag::Collapsed))
|
||||
return;
|
||||
@@ -1343,11 +1362,22 @@ void ChannelView::mousePressEvent(QMouseEvent *event)
|
||||
break;
|
||||
|
||||
case Qt::RightButton: {
|
||||
if (this->isScrolling_)
|
||||
this->disableScrolling();
|
||||
|
||||
this->lastRightPressPosition_ = event->screenPos();
|
||||
this->isRightMouseDown_ = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::MiddleButton: {
|
||||
if (this->isScrolling_)
|
||||
this->disableScrolling();
|
||||
else
|
||||
this->enableScrolling(event->screenPos());
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
@@ -1372,11 +1402,11 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (this->isMouseDown_)
|
||||
else if (this->isLeftMouseDown_)
|
||||
{
|
||||
this->isMouseDown_ = false;
|
||||
this->isLeftMouseDown_ = false;
|
||||
|
||||
if (fabsf(distanceBetweenPoints(this->lastPressPosition_,
|
||||
if (fabsf(distanceBetweenPoints(this->lastLeftPressPosition_,
|
||||
event->screenPos())) > 15.f)
|
||||
{
|
||||
return;
|
||||
@@ -1404,6 +1434,13 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (event->button() == Qt::MiddleButton)
|
||||
{
|
||||
if (event->screenPos() == this->lastMiddlePressPosition_)
|
||||
this->enableScrolling(event->screenPos());
|
||||
else
|
||||
this->disableScrolling();
|
||||
}
|
||||
else
|
||||
{
|
||||
// not left or right button
|
||||
@@ -1637,7 +1674,7 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->isMouseDown_)
|
||||
if (!this->isLeftMouseDown_)
|
||||
{
|
||||
this->isDoubleClick_ = true;
|
||||
|
||||
@@ -1701,7 +1738,6 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||
case Link::UserInfo: {
|
||||
auto user = link.value;
|
||||
this->showUserInfoPopup(user);
|
||||
qDebug() << "Clicked " << user << "s message";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1721,6 +1757,8 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||
.replace("{msg-id}", layout->getMessage()->id)
|
||||
.replace("{message}", layout->getMessage()->messageText);
|
||||
|
||||
value =
|
||||
getApp()->commands->execCommand(value, this->channel_, false);
|
||||
this->channel_->sendMessage(value);
|
||||
}
|
||||
break;
|
||||
@@ -1801,4 +1839,59 @@ void ChannelView::getWordBounds(MessageLayout *layout,
|
||||
wordEnd = wordStart + length;
|
||||
}
|
||||
|
||||
void ChannelView::enableScrolling(const QPointF &scrollStart)
|
||||
{
|
||||
this->isScrolling_ = true;
|
||||
this->lastMiddlePressPosition_ = scrollStart;
|
||||
// The line below prevents a sudden jerk at the beginning
|
||||
this->currentMousePosition_ = scrollStart;
|
||||
|
||||
this->scrollTimer_.start();
|
||||
|
||||
if (!QGuiApplication::overrideCursor())
|
||||
QGuiApplication::setOverrideCursor(this->cursors_.neutral);
|
||||
}
|
||||
|
||||
void ChannelView::disableScrolling()
|
||||
{
|
||||
this->isScrolling_ = false;
|
||||
this->scrollTimer_.stop();
|
||||
QGuiApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void ChannelView::scrollUpdateRequested()
|
||||
{
|
||||
const qreal dpi =
|
||||
QGuiApplication::screenAt(this->pos())->devicePixelRatio();
|
||||
const qreal delta = dpi * (this->currentMousePosition_.y() -
|
||||
this->lastMiddlePressPosition_.y());
|
||||
const int cursorHeight = this->cursors_.neutral.pixmap().height();
|
||||
|
||||
if (fabs(delta) <= cursorHeight * dpi)
|
||||
{
|
||||
/*
|
||||
* If within an area close to the initial position, don't do any
|
||||
* scrolling at all.
|
||||
*/
|
||||
QGuiApplication::changeOverrideCursor(this->cursors_.neutral);
|
||||
return;
|
||||
}
|
||||
|
||||
qreal offset;
|
||||
if (delta > 0)
|
||||
{
|
||||
QGuiApplication::changeOverrideCursor(this->cursors_.down);
|
||||
offset = delta - cursorHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
QGuiApplication::changeOverrideCursor(this->cursors_.up);
|
||||
offset = delta + cursorHeight;
|
||||
}
|
||||
|
||||
// "Good" feeling multiplier found by trial-and-error
|
||||
const qreal multiplier = qreal(0.02);
|
||||
this->scrollBar_->offset(multiplier * offset);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -148,6 +148,9 @@ private:
|
||||
void updatePauses();
|
||||
void unpaused();
|
||||
|
||||
void enableScrolling(const QPointF &scrollStart);
|
||||
void disableScrolling();
|
||||
|
||||
QTimer *layoutCooldown_;
|
||||
bool layoutQueued_;
|
||||
|
||||
@@ -183,27 +186,31 @@ private:
|
||||
bool onlyUpdateEmotes_ = false;
|
||||
|
||||
// Mouse event variables
|
||||
bool isMouseDown_ = false;
|
||||
bool isLeftMouseDown_ = false;
|
||||
bool isRightMouseDown_ = false;
|
||||
bool isDoubleClick_ = false;
|
||||
DoubleClickSelection doubleClickSelection_;
|
||||
QPointF lastPressPosition_;
|
||||
QPointF lastLeftPressPosition_;
|
||||
QPointF lastRightPressPosition_;
|
||||
QPointF lastDClickPosition_;
|
||||
QTimer *clickTimer_;
|
||||
|
||||
bool isScrolling_ = false;
|
||||
QPointF lastMiddlePressPosition_;
|
||||
QPointF currentMousePosition_;
|
||||
QTimer scrollTimer_;
|
||||
|
||||
struct {
|
||||
QCursor neutral;
|
||||
QCursor up;
|
||||
QCursor down;
|
||||
} cursors_;
|
||||
|
||||
Selection selection_;
|
||||
bool selecting_ = false;
|
||||
|
||||
LimitedQueue<MessageLayoutPtr> messages_;
|
||||
|
||||
pajlada::Signals::Connection messageAppendedConnection_;
|
||||
pajlada::Signals::Connection messageAddedAtStartConnection_;
|
||||
pajlada::Signals::Connection messageRemovedConnection_;
|
||||
pajlada::Signals::Connection messageReplacedConnection_;
|
||||
pajlada::Signals::Connection repaintGifsConnection_;
|
||||
pajlada::Signals::Connection layoutConnection_;
|
||||
|
||||
std::vector<pajlada::Signals::ScopedConnection> connections_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> channelConnections_;
|
||||
|
||||
@@ -218,6 +225,8 @@ private slots:
|
||||
queueLayout();
|
||||
update();
|
||||
}
|
||||
|
||||
void scrollUpdateRequested();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define OPEN_IN_BROWSER "Open in browser"
|
||||
#define OPEN_PLAYER_IN_BROWSER "Open in player in browser"
|
||||
#define OPEN_PLAYER_IN_BROWSER "Open player in browser"
|
||||
#define OPEN_IN_STREAMLINK "Open in streamlink"
|
||||
#define DONT_OPEN "Don't open"
|
||||
|
||||
@@ -15,9 +15,12 @@ EditableModelView::EditableModelView(QAbstractTableModel *model)
|
||||
{
|
||||
this->model_->setParent(this);
|
||||
this->tableView_->setModel(model);
|
||||
this->tableView_->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
this->tableView_->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
this->tableView_->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
this->tableView_->verticalHeader()->hide();
|
||||
this->tableView_->setDragDropMode(QTableView::DragDropMode::InternalMove);
|
||||
this->tableView_->setDragDropOverwriteMode(false);
|
||||
this->tableView_->setDefaultDropAction(Qt::DropAction::MoveAction);
|
||||
this->tableView_->verticalHeader()->setVisible(false);
|
||||
|
||||
// create layout
|
||||
QVBoxLayout *vbox = new QVBoxLayout(this);
|
||||
@@ -38,12 +41,17 @@ EditableModelView::EditableModelView(QAbstractTableModel *model)
|
||||
QPushButton *remove = new QPushButton("Remove");
|
||||
buttons->addWidget(remove);
|
||||
QObject::connect(remove, &QPushButton::clicked, [this] {
|
||||
QModelIndexList list;
|
||||
while ((list = this->getTableView()->selectionModel()->selectedRows(0))
|
||||
.length() > 0)
|
||||
{
|
||||
model_->removeRow(list[0].row());
|
||||
}
|
||||
auto selected = this->getTableView()->selectionModel()->selectedRows(0);
|
||||
|
||||
// Remove rows backwards so indices don't shift.
|
||||
std::vector<int> rows;
|
||||
for (auto &&index : selected)
|
||||
rows.push_back(index.row());
|
||||
|
||||
std::sort(rows.begin(), rows.end(), std::greater{});
|
||||
|
||||
for (auto &&row : rows)
|
||||
model_->removeRow(row);
|
||||
});
|
||||
|
||||
buttons->addStretch();
|
||||
|
||||
@@ -91,7 +91,6 @@ void NotebookButton::paintEvent(QPaintEvent *event)
|
||||
|
||||
case User: {
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
auto a = w / 8;
|
||||
QPainterPath path;
|
||||
@@ -99,19 +98,18 @@ void NotebookButton::paintEvent(QPaintEvent *event)
|
||||
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
|
||||
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
|
||||
|
||||
QPainterPath remove;
|
||||
remove.addEllipse(2 * a, 1 * a, 4 * a, 4 * a);
|
||||
path = path.subtracted(remove);
|
||||
|
||||
path.addEllipse(2.5 * a, 1.5 * a, 3 * a, 3 * a);
|
||||
|
||||
painter.fillPath(path, foreground);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
|
||||
|
||||
painter.setBrush(foreground);
|
||||
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
|
||||
}
|
||||
break;
|
||||
|
||||
case Settings: {
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
auto a = w / 8;
|
||||
QPainterPath path;
|
||||
@@ -126,10 +124,10 @@ void NotebookButton::paintEvent(QPaintEvent *event)
|
||||
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
|
||||
}
|
||||
|
||||
painter.fillPath(path, foreground);
|
||||
QPainterPath remove;
|
||||
remove.addEllipse(3 * a, 3 * a, 2 * a, 2 * a);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
|
||||
painter.fillPath(path.subtracted(remove), foreground);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ ScrollbarHighlight::ScrollbarHighlight()
|
||||
}
|
||||
|
||||
ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style)
|
||||
Style style, bool isRedeemedHighlight)
|
||||
: color_(color)
|
||||
, style_(style)
|
||||
, isRedeemedHighlight_(isRedeemedHighlight)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,6 +30,11 @@ ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
|
||||
return this->style_;
|
||||
}
|
||||
|
||||
bool ScrollbarHighlight::isRedeemedHighlight() const
|
||||
{
|
||||
return this->isRedeemedHighlight_;
|
||||
}
|
||||
|
||||
bool ScrollbarHighlight::isNull() const
|
||||
{
|
||||
return this->style_ == None;
|
||||
|
||||
@@ -17,15 +17,17 @@ public:
|
||||
ScrollbarHighlight();
|
||||
|
||||
ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style = Default);
|
||||
Style style = Default, bool isRedeemedHighlight = false);
|
||||
|
||||
QColor getColor() const;
|
||||
Style getStyle() const;
|
||||
bool isRedeemedHighlight() const;
|
||||
bool isNull() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<QColor> color_;
|
||||
Style style_;
|
||||
bool isRedeemedHighlight_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -8,12 +8,16 @@
|
||||
namespace chatterino {
|
||||
|
||||
SettingsDialogTab::SettingsDialogTab(SettingsDialog *_dialog,
|
||||
SettingsPage *_page, QString imageFileName)
|
||||
std::function<SettingsPage *()> _lazyPage,
|
||||
const QString &name, QString imageFileName,
|
||||
SettingsTabId id)
|
||||
: BaseWidget(_dialog)
|
||||
, dialog_(_dialog)
|
||||
, page_(_page)
|
||||
, lazyPage_(std::move(_lazyPage))
|
||||
, id_(id)
|
||||
, name_(name)
|
||||
{
|
||||
this->ui_.labelText = page_->getName();
|
||||
this->ui_.labelText = name;
|
||||
this->ui_.icon.addFile(imageFileName);
|
||||
|
||||
this->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
@@ -34,8 +38,13 @@ void SettingsDialogTab::setSelected(bool _selected)
|
||||
emit selectedChanged(selected_);
|
||||
}
|
||||
|
||||
SettingsPage *SettingsDialogTab::getSettingsPage()
|
||||
SettingsPage *SettingsDialogTab::page()
|
||||
{
|
||||
if (this->page_)
|
||||
return this->page_;
|
||||
|
||||
this->page_ = this->lazyPage_();
|
||||
this->page_->setTab(this);
|
||||
return this->page_;
|
||||
}
|
||||
|
||||
@@ -72,4 +81,14 @@ void SettingsDialogTab::mousePressEvent(QMouseEvent *event)
|
||||
this->setFocus();
|
||||
}
|
||||
|
||||
const QString &SettingsDialogTab::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
SettingsTabId SettingsDialogTab::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -5,22 +5,34 @@
|
||||
#include <QIcon>
|
||||
#include <QPaintEvent>
|
||||
#include <QWidget>
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class SettingsPage;
|
||||
class SettingsDialog;
|
||||
|
||||
enum SettingsTabId {
|
||||
None,
|
||||
Accounts,
|
||||
Moderation,
|
||||
};
|
||||
|
||||
class SettingsDialogTab : public BaseWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SettingsDialogTab(SettingsDialog *dialog_, SettingsPage *page_,
|
||||
QString imageFileName);
|
||||
SettingsDialogTab(SettingsDialog *dialog_,
|
||||
std::function<SettingsPage *()> page_,
|
||||
const QString &name, QString imageFileName,
|
||||
SettingsTabId id);
|
||||
|
||||
void setSelected(bool selected_);
|
||||
SettingsPage *getSettingsPage();
|
||||
SettingsPage *page();
|
||||
SettingsTabId id() const;
|
||||
|
||||
const QString &name() const;
|
||||
|
||||
signals:
|
||||
void selectedChanged(bool);
|
||||
@@ -35,8 +47,11 @@ private:
|
||||
} ui_;
|
||||
|
||||
// Parent settings dialog
|
||||
SettingsDialog *dialog_;
|
||||
SettingsPage *page_;
|
||||
SettingsDialog *dialog_{};
|
||||
SettingsPage *page_{};
|
||||
std::function<SettingsPage *()> lazyPage_;
|
||||
SettingsTabId id_;
|
||||
QString name_;
|
||||
|
||||
bool selected_ = false;
|
||||
};
|
||||
|
||||
@@ -67,7 +67,6 @@ void TitleBarButton::paintEvent(QPaintEvent *event)
|
||||
color = "#999";
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
auto a = xD / 3;
|
||||
QPainterPath path;
|
||||
@@ -92,7 +91,6 @@ void TitleBarButton::paintEvent(QPaintEvent *event)
|
||||
case TitleBarButtonStyle::Settings: {
|
||||
color = "#999";
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
painter.save();
|
||||
painter.translate(3, 3);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
namespace chatterino {
|
||||
|
||||
AboutPage::AboutPage()
|
||||
: SettingsPage("About", ":/settings/about.svg")
|
||||
{
|
||||
LayoutCreator<AboutPage> layoutCreator(this);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
namespace chatterino {
|
||||
|
||||
AccountsPage::AccountsPage()
|
||||
: SettingsPage("Accounts", ":/settings/accounts.svg")
|
||||
{
|
||||
auto *app = getApp();
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
CommandPage::CommandPage()
|
||||
: SettingsPage("Commands", ":/settings/commands.svg")
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
@@ -47,10 +46,11 @@ CommandPage::CommandPage()
|
||||
view->setTitles({"Trigger", "Command"});
|
||||
view->getTableView()->horizontalHeader()->setStretchLastSection(true);
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->commands->items_.appendItem(
|
||||
getApp()->commands->items_.append(
|
||||
Command{"/command", "I made a new command HeyGuys"});
|
||||
});
|
||||
|
||||
// TODO: asyncronously check path
|
||||
if (QFile(c1settingsPath()).exists())
|
||||
{
|
||||
auto button = new QPushButton("Import commands from Chatterino 1");
|
||||
@@ -65,7 +65,7 @@ CommandPage::CommandPage()
|
||||
{
|
||||
if (int index = line.indexOf(' '); index != -1)
|
||||
{
|
||||
getApp()->commands->items_.insertItem(
|
||||
getApp()->commands->items_.insert(
|
||||
Command(line.mid(0, index), line.mid(index + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
namespace chatterino {
|
||||
|
||||
ExternalToolsPage::ExternalToolsPage()
|
||||
: SettingsPage("External tools", ":/settings/externaltools.svg")
|
||||
{
|
||||
LayoutCreator<ExternalToolsPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
@@ -69,6 +68,23 @@ ExternalToolsPage::ExternalToolsPage()
|
||||
this->managedConnections_);
|
||||
}
|
||||
|
||||
{
|
||||
auto group = layout.emplace<QGroupBox>("Custom URI Scheme");
|
||||
auto groupLayout = group.setLayoutType<QFormLayout>();
|
||||
|
||||
const auto description = new QLabel(
|
||||
"You can open video streams directly in any video player that "
|
||||
"has built-in Twitch support and has own URI Scheme.\nE.g.: "
|
||||
"IINA for macOS and Potplayer (with extension) for Windows.");
|
||||
description->setWordWrap(true);
|
||||
description->setStyleSheet("color: #bbb");
|
||||
|
||||
groupLayout->setWidget(0, QFormLayout::SpanningRole, description);
|
||||
|
||||
groupLayout->addRow("URI Scheme:", this->createLineEdit(
|
||||
getSettings()->customURIScheme));
|
||||
}
|
||||
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,14 +74,16 @@ namespace {
|
||||
|
||||
TitleLabel *SettingsLayout::addTitle(const QString &title)
|
||||
{
|
||||
auto label = new TitleLabel(title + ":");
|
||||
// space
|
||||
if (!this->groups_.empty())
|
||||
this->addWidget(this->groups_.back().space = new Space);
|
||||
|
||||
if (this->count() == 0)
|
||||
label->setStyleSheet("margin-top: 0");
|
||||
// title
|
||||
auto label = new TitleLabel(title + ":");
|
||||
this->addWidget(label);
|
||||
|
||||
// groups
|
||||
this->groups_.push_back(Group{title, label, {}});
|
||||
this->groups_.push_back(Group{title, label, nullptr, {}});
|
||||
|
||||
return label;
|
||||
}
|
||||
@@ -228,6 +230,8 @@ bool SettingsLayout::filterElements(const QString &query)
|
||||
}
|
||||
}
|
||||
|
||||
if (group.space)
|
||||
group.space->setVisible(groupAny);
|
||||
group.title->setVisible(groupAny);
|
||||
any |= groupAny;
|
||||
}
|
||||
@@ -237,7 +241,6 @@ bool SettingsLayout::filterElements(const QString &query)
|
||||
}
|
||||
|
||||
GeneralPage::GeneralPage()
|
||||
: SettingsPage("General", ":/settings/about.svg")
|
||||
{
|
||||
auto y = new QVBoxLayout;
|
||||
auto scroll = new QScrollArea;
|
||||
@@ -263,9 +266,7 @@ GeneralPage::GeneralPage()
|
||||
bool GeneralPage::filterElements(const QString &query)
|
||||
{
|
||||
if (this->settingsLayout_)
|
||||
return this->settingsLayout_->filterElements(query) ||
|
||||
this->name_.contains(query, Qt::CaseInsensitive) ||
|
||||
query.isEmpty();
|
||||
return this->settingsLayout_->filterElements(query) || query.isEmpty();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@@ -304,6 +305,12 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
layout.addCheckbox("Start with Windows", s.autorun);
|
||||
#endif
|
||||
layout.addCheckbox("Restart on crash", s.restartOnCrash);
|
||||
if (!BaseWindow::supportsCustomWindowFrame())
|
||||
{
|
||||
layout.addCheckbox("Show preferences button (ctrl+p to show)",
|
||||
s.hidePreferencesButton, true);
|
||||
layout.addCheckbox("Show user button", s.hideUserButton, true);
|
||||
}
|
||||
|
||||
layout.addTitle("Chat");
|
||||
|
||||
@@ -321,7 +328,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
layout.addCheckbox("Smooth scrolling on new messages",
|
||||
s.enableSmoothScrollingNewMessages);
|
||||
layout.addDropdown<float>(
|
||||
"Pause on hover", {"Disabled", "0.5s", "1s", "2s", "5s", "Indefinite"},
|
||||
"Pause after hover",
|
||||
{"Disabled", "0.5s", "1s", "2s", "5s", "Indefinite"},
|
||||
s.pauseOnHoverDuration,
|
||||
[](auto val) {
|
||||
if (val < -0.5f)
|
||||
@@ -344,12 +352,6 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
s.pauseChatModifier);
|
||||
layout.addCheckbox("Show input when it's empty", s.showEmptyInput);
|
||||
layout.addCheckbox("Show message length while typing", s.showMessageLength);
|
||||
if (!BaseWindow::supportsCustomWindowFrame())
|
||||
{
|
||||
layout.addCheckbox("Show preferences button (ctrl+p to show)",
|
||||
s.hidePreferencesButton, true);
|
||||
layout.addCheckbox("Show user button", s.hideUserButton, true);
|
||||
}
|
||||
|
||||
layout.addTitle("Messages");
|
||||
layout.addCheckbox("Seperate with lines", s.separateMessages);
|
||||
@@ -358,7 +360,9 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
// layout.addDropdown("Last read message style", {"Default"});
|
||||
layout.addCheckbox("Show deleted messages", s.hideModerated, true);
|
||||
layout.addDropdown<QString>(
|
||||
"Timestamps", {"Disable", "h:mm", "hh:mm", "h:mm a", "hh:mm a"},
|
||||
"Timestamps",
|
||||
{"Disable", "h:mm", "hh:mm", "h:mm a", "hh:mm a", "h:mm:ss", "hh:mm:ss",
|
||||
"h:mm:ss a", "hh:mm:ss a"},
|
||||
s.timestampFormat,
|
||||
[](auto val) {
|
||||
return getSettings()->showTimestamps.getValue()
|
||||
@@ -412,6 +416,32 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
"Apple", "Google", "Messenger"},
|
||||
s.emojiSet);
|
||||
|
||||
layout.addTitle("R9K");
|
||||
layout.addDescription(
|
||||
"Hide similar messages by the same user. Temporarily show hidden "
|
||||
"messages by pressing Ctrl+H.");
|
||||
layout.addCheckbox("Hide similar messages", s.similarityEnabled);
|
||||
//layout.addCheckbox("Gray out matches", s.colorSimilarDisabled);
|
||||
layout.addCheckbox("Hide my own messages", s.hideSimilarMyself);
|
||||
layout.addCheckbox("Receive notification sounds from hidden messages",
|
||||
s.shownSimilarTriggerHighlights);
|
||||
s.hideSimilar.connect(
|
||||
[]() { getApp()->windows->forceLayoutChannelViews(); }, false);
|
||||
layout.addDropdown<float>(
|
||||
"Similarity threshold", {"0.5", "0.75", "0.9"}, s.similarityPercentage,
|
||||
[](auto val) { return QString::number(val); },
|
||||
[](auto args) { return fuzzyToFloat(args.value, 0.9f); });
|
||||
layout.addDropdown<int>(
|
||||
"Maximum delay between messages",
|
||||
{"5s", "10s", "15s", "30s", "60s", "120s"}, s.hideSimilarMaxDelay,
|
||||
[](auto val) { return QString::number(val) + "s"; },
|
||||
[](auto args) { return fuzzyToInt(args.value, 5); });
|
||||
layout.addDropdown<int>(
|
||||
"Amount of previous messages to check", {"1", "2", "3", "4", "5"},
|
||||
s.hideSimilarMaxMessagesToCheck,
|
||||
[](auto val) { return QString::number(val); },
|
||||
[](auto args) { return fuzzyToInt(args.value, 3); });
|
||||
|
||||
layout.addTitle("Visible badges");
|
||||
layout.addCheckbox("Authority (staff, admin)",
|
||||
getSettings()->showBadgesGlobalAuthority);
|
||||
@@ -497,6 +527,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
layout.addCheckbox(
|
||||
"Only search for emote autocompletion at the start of emote names",
|
||||
s.prefixOnlyEmoteCompletion);
|
||||
layout.addCheckbox("Only search for username autocompletion with an @",
|
||||
s.userCompletionOnlyWithAt);
|
||||
|
||||
layout.addCheckbox("Show twitch whispers inline", s.inlineWhispers);
|
||||
layout.addCheckbox("Highlight received inline whispers",
|
||||
@@ -508,6 +540,9 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
s.enableExperimentalIrc);
|
||||
layout.addCheckbox("Show unhandled IRC messages",
|
||||
s.showUnhandledIrcMessages);
|
||||
layout.addCheckbox(
|
||||
"Hide viewercount and stream length while hovering the split",
|
||||
s.hideViewerCountAndDuration);
|
||||
|
||||
layout.addTitle("Cache");
|
||||
layout.addDescription(
|
||||
@@ -547,33 +582,6 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
|
||||
});
|
||||
|
||||
layout.addTitle("Similarity");
|
||||
layout.addDescription(
|
||||
"Hides or grays out similar messages from the same user");
|
||||
layout.addCheckbox("Similarity enabled", s.similarityEnabled);
|
||||
layout.addCheckbox("Gray out similar messages", s.colorSimilarDisabled);
|
||||
layout.addCheckbox("Hide similar messages (Ctrl + H)", s.hideSimilar);
|
||||
layout.addCheckbox("Hide or gray out my own similar messages",
|
||||
s.hideSimilarMyself);
|
||||
layout.addCheckbox("Shown similar messages trigger highlights",
|
||||
s.shownSimilarTriggerHighlights);
|
||||
layout.addDropdown<float>(
|
||||
"Similarity percentage", {"0.5", "0.75", "0.9"}, s.similarityPercentage,
|
||||
[](auto val) { return QString::number(val); },
|
||||
[](auto args) { return fuzzyToFloat(args.value, 0.9f); });
|
||||
s.hideSimilar.connect(
|
||||
[]() { getApp()->windows->forceLayoutChannelViews(); }, false);
|
||||
layout.addDropdown<int>(
|
||||
"Similar messages max delay in seconds",
|
||||
{"5", "10", "15", "30", "60", "120"}, s.hideSimilarMaxDelay,
|
||||
[](auto val) { return QString::number(val); },
|
||||
[](auto args) { return fuzzyToInt(args.value, 5); });
|
||||
layout.addDropdown<int>(
|
||||
"Similar messages max previous messages to check",
|
||||
{"1", "2", "3", "4", "5"}, s.hideSimilarMaxMessagesToCheck,
|
||||
[](auto val) { return QString::number(val); },
|
||||
[](auto args) { return fuzzyToInt(args.value, 3); });
|
||||
|
||||
// invisible element for width
|
||||
auto inv = new BaseWidget(this);
|
||||
inv->setScaleIndependantWidth(500);
|
||||
|
||||
@@ -17,6 +17,11 @@ class QComboBox;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Space : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
class TitleLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -162,6 +167,7 @@ private:
|
||||
struct Group {
|
||||
QString name;
|
||||
QWidget *title{};
|
||||
Space *space{};
|
||||
std::vector<Widget> widgets;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistModel.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
@@ -29,9 +28,7 @@
|
||||
namespace chatterino {
|
||||
|
||||
HighlightingPage::HighlightingPage()
|
||||
: SettingsPage("Highlights", ":/settings/notifications.svg")
|
||||
{
|
||||
auto app = getApp();
|
||||
LayoutCreator<HighlightingPage> layoutCreator(this);
|
||||
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
@@ -47,16 +44,16 @@ HighlightingPage::HighlightingPage()
|
||||
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
|
||||
{
|
||||
highlights.emplace<QLabel>(
|
||||
"Messages can be highlighted if they match a certain "
|
||||
"pattern.\n"
|
||||
"NOTE: User highlights will override phrase highlights.");
|
||||
"Play notification sounds and highlight messages based on "
|
||||
"certain patterns.");
|
||||
|
||||
EditableModelView *view =
|
||||
auto view =
|
||||
highlights
|
||||
.emplace<EditableModelView>(
|
||||
app->highlights->createModel(nullptr))
|
||||
(new HighlightModel(nullptr))
|
||||
->initialized(
|
||||
&getSettings()->highlightedMessages))
|
||||
.getElement();
|
||||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex", "Case-\nsensitive",
|
||||
@@ -74,7 +71,7 @@ HighlightingPage::HighlightingPage()
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
||||
getSettings()->highlightedMessages.append(HighlightPhrase{
|
||||
"my phrase", true, false, false, false, "",
|
||||
*ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
@@ -89,12 +86,15 @@ HighlightingPage::HighlightingPage()
|
||||
auto pingUsers = tabs.appendTab(new QVBoxLayout, "Users");
|
||||
{
|
||||
pingUsers.emplace<QLabel>(
|
||||
"Messages from a certain user can be highlighted.\n"
|
||||
"NOTE: User highlights will override phrase highlights.");
|
||||
"Play notification sounds and highlight messages from "
|
||||
"certain users.\n"
|
||||
"User highlights are prioritized over message "
|
||||
"highlights.");
|
||||
EditableModelView *view =
|
||||
pingUsers
|
||||
.emplace<EditableModelView>(
|
||||
app->highlights->createUserModel(nullptr))
|
||||
(new UserHighlightModel(nullptr))
|
||||
->initialized(&getSettings()->highlightedUsers))
|
||||
.getElement();
|
||||
|
||||
view->addRegexHelpLink();
|
||||
@@ -118,11 +118,10 @@ HighlightingPage::HighlightingPage()
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->highlightedUsers.appendItem(
|
||||
HighlightPhrase{"highlighted user", true, false, false,
|
||||
false, "",
|
||||
*ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
getSettings()->highlightedUsers.append(HighlightPhrase{
|
||||
"highlighted user", true, false, false, false, "",
|
||||
*ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
});
|
||||
|
||||
QObject::connect(view->getTableView(), &QTableView::clicked,
|
||||
@@ -132,19 +131,20 @@ HighlightingPage::HighlightingPage()
|
||||
}
|
||||
|
||||
auto disabledUsers =
|
||||
tabs.appendTab(new QVBoxLayout, "Excluded Users");
|
||||
tabs.appendTab(new QVBoxLayout, "Blacklisted Users");
|
||||
{
|
||||
disabledUsers.emplace<QLabel>(
|
||||
"This is a list of users (e.g. bots) whose messages should "
|
||||
"<u>not</u> be highlighted.");
|
||||
"Disable notification sounds and highlights from certain "
|
||||
"users (e.g. bots).");
|
||||
EditableModelView *view =
|
||||
disabledUsers
|
||||
.emplace<EditableModelView>(
|
||||
app->highlights->createBlacklistModel(nullptr))
|
||||
(new HighlightBlacklistModel(nullptr))
|
||||
->initialized(&getSettings()->blacklistedUsers))
|
||||
.getElement();
|
||||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Enable\nregex"});
|
||||
view->setTitles({"Username", "Enable\nregex"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
@@ -158,7 +158,7 @@ HighlightingPage::HighlightingPage()
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->blacklistedUsers.appendItem(
|
||||
getSettings()->blacklistedUsers.append(
|
||||
HighlightBlacklistUser{"blacklisted user", false});
|
||||
});
|
||||
}
|
||||
@@ -205,7 +205,7 @@ HighlightingPage::HighlightingPage()
|
||||
|
||||
// ---- misc
|
||||
this->disabledUsersChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
||||
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
||||
EditableModelView *view)
|
||||
@@ -227,7 +227,8 @@ void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
||||
Qt::CheckStateRole);
|
||||
}
|
||||
}
|
||||
else if (clicked.column() == Column::Color)
|
||||
else if (clicked.column() == Column::Color &&
|
||||
clicked.row() != HighlightModel::WHISPER_ROW)
|
||||
{
|
||||
auto initial =
|
||||
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
|
||||
|
||||
@@ -29,7 +29,6 @@ static void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> box,
|
||||
QStringListModel &model);
|
||||
|
||||
IgnoresPage::IgnoresPage()
|
||||
: SettingsPage("Ignores", ":/settings/ignore.svg")
|
||||
{
|
||||
LayoutCreator<IgnoresPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
@@ -42,11 +41,12 @@ IgnoresPage::IgnoresPage()
|
||||
|
||||
void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
layout.emplace<QLabel>("Messages can be ignored if "
|
||||
"they match a certain pattern.");
|
||||
layout.emplace<QLabel>("Ignore messages based certain patterns.");
|
||||
EditableModelView *view =
|
||||
layout
|
||||
.emplace<EditableModelView>(getApp()->ignores->createModel(nullptr))
|
||||
.emplace<EditableModelView>(
|
||||
(new IgnoreModel(nullptr))
|
||||
->initialized(&getSettings()->ignoredMessages))
|
||||
.getElement();
|
||||
view->setTitles(
|
||||
{"Pattern", "Regex", "Case Sensitive", "Block", "Replacement"});
|
||||
@@ -62,7 +62,7 @@ void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->ignores->phrases.appendItem(
|
||||
getSettings()->ignoredMessages.append(
|
||||
IgnorePhrase{"my pattern", false, false,
|
||||
getSettings()->ignoredPhraseReplace.getValue(), true});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
namespace chatterino {
|
||||
|
||||
KeyboardSettingsPage::KeyboardSettingsPage()
|
||||
: SettingsPage("Keybindings", ":/settings/keybinds.svg")
|
||||
{
|
||||
auto layout =
|
||||
LayoutCreator<KeyboardSettingsPage>(this).setLayoutType<QVBoxLayout>();
|
||||
@@ -26,10 +25,8 @@ KeyboardSettingsPage::KeyboardSettingsPage()
|
||||
form->addRow(new QLabel("Ctrl + Shift + T"), new QLabel("Create new tab"));
|
||||
form->addRow(new QLabel("Ctrl + Shift + W"),
|
||||
new QLabel("Close current tab"));
|
||||
form->addRow(
|
||||
new QLabel("Ctrl + H"),
|
||||
new QLabel(
|
||||
"Hide/Show similar messages (Enable in General under Similarity)"));
|
||||
form->addRow(new QLabel("Ctrl + H"),
|
||||
new QLabel("Hide/Show similar messages (See General->R9K)"));
|
||||
|
||||
form->addItem(new QSpacerItem(16, 16));
|
||||
form->addRow(new QLabel("Ctrl + 1/2/3/..."),
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/moderationactions/ModerationActionModel.hpp"
|
||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersModel.hpp"
|
||||
#include "singletons/Logging.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
@@ -73,7 +71,6 @@ QString fetchLogDirectorySize()
|
||||
}
|
||||
|
||||
ModerationPage::ModerationPage()
|
||||
: SettingsPage("Moderation", ":/settings/moderation.svg")
|
||||
{
|
||||
auto app = getApp();
|
||||
LayoutCreator<ModerationPage> layoutCreator(this);
|
||||
@@ -175,7 +172,8 @@ ModerationPage::ModerationPage()
|
||||
EditableModelView *view =
|
||||
modMode
|
||||
.emplace<EditableModelView>(
|
||||
app->moderationActions->createModel(nullptr))
|
||||
(new ModerationActionModel(nullptr))
|
||||
->initialized(&getSettings()->moderationActions))
|
||||
.getElement();
|
||||
|
||||
view->setTitles({"Actions"});
|
||||
@@ -185,7 +183,7 @@ ModerationPage::ModerationPage()
|
||||
0, QHeaderView::Stretch);
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->moderationActions->items.appendItem(
|
||||
getSettings()->moderationActions.append(
|
||||
ModerationAction("/timeout {user} 300"));
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
namespace chatterino {
|
||||
|
||||
NotificationPage::NotificationPage()
|
||||
: SettingsPage("Notifications", ":/settings/notification2.svg")
|
||||
{
|
||||
LayoutCreator<NotificationPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
@@ -101,7 +100,7 @@ NotificationPage::NotificationPage()
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()
|
||||
->notifications->channelMap[Platform::Twitch]
|
||||
.appendItem("channel");
|
||||
.append("channel");
|
||||
});
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -64,26 +64,13 @@ bool filterItemsRec(QObject *object, const QString &query)
|
||||
return any;
|
||||
}
|
||||
|
||||
SettingsPage::SettingsPage(const QString &name, const QString &iconResource)
|
||||
: name_(name)
|
||||
, iconResource_(iconResource)
|
||||
SettingsPage::SettingsPage()
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingsPage::filterElements(const QString &query)
|
||||
{
|
||||
return filterItemsRec(this, query) || query.isEmpty() ||
|
||||
this->name_.contains(query, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
const QString &SettingsPage::getName()
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
const QString &SettingsPage::getIconResource()
|
||||
{
|
||||
return this->iconResource_;
|
||||
return filterItemsRec(this, query) || query.isEmpty();
|
||||
}
|
||||
|
||||
SettingsDialogTab *SettingsPage::tab() const
|
||||
|
||||
@@ -47,10 +47,7 @@ class SettingsPage : public QFrame
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SettingsPage(const QString &name, const QString &iconResource);
|
||||
|
||||
const QString &getName();
|
||||
const QString &getIconResource();
|
||||
SettingsPage();
|
||||
|
||||
virtual bool filterElements(const QString &query);
|
||||
|
||||
@@ -72,11 +69,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
QString name_;
|
||||
QString iconResource_;
|
||||
|
||||
SettingsDialogTab *tab_;
|
||||
|
||||
pajlada::Signals::NoArgSignal onCancel_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
};
|
||||
|
||||
@@ -541,6 +541,23 @@ void Split::openInStreamlink()
|
||||
}
|
||||
}
|
||||
|
||||
void Split::openWithCustomScheme()
|
||||
{
|
||||
const auto scheme = getSettings()->customURIScheme.getValue();
|
||||
if (scheme.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto channel = this->getChannel().get();
|
||||
|
||||
if (const auto twitchChannel = dynamic_cast<TwitchChannel *>(channel))
|
||||
{
|
||||
QDesktopServices::openUrl(QString("%1https://twitch.tv/%2")
|
||||
.arg(scheme)
|
||||
.arg(twitchChannel->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
void Split::showViewerList()
|
||||
{
|
||||
auto viewerDock = new QDockWidget("Viewer List", this);
|
||||
|
||||
@@ -129,6 +129,7 @@ public slots:
|
||||
void openInBrowser();
|
||||
void openBrowserPlayer();
|
||||
void openInStreamlink();
|
||||
void openWithCustomScheme();
|
||||
void copyToClipboard();
|
||||
void showSearch();
|
||||
void showViewerList();
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/pings/PingController.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
@@ -96,8 +94,11 @@ namespace {
|
||||
.arg(s.game.toHtmlEscaped())
|
||||
.arg(s.game.isEmpty() ? QString() : "<br>")
|
||||
.arg(s.rerun ? "Vod-casting" : "Live")
|
||||
.arg(s.uptime)
|
||||
.arg(QString::number(s.viewerCount));
|
||||
.arg(getSettings()->hideViewerCountAndDuration ? "<Hidden>"
|
||||
: s.uptime)
|
||||
.arg(getSettings()->hideViewerCountAndDuration
|
||||
? "<Hidden>"
|
||||
: QString::number(s.viewerCount));
|
||||
}
|
||||
auto formatOfflineTooltip(const TwitchChannel::StreamStatus &s)
|
||||
{
|
||||
@@ -189,7 +190,7 @@ void SplitHeader::initializeLayout()
|
||||
switch (button)
|
||||
{
|
||||
case Qt::LeftButton:
|
||||
if (getApp()->moderationActions->items.empty())
|
||||
if (getSettings()->moderationActions.empty())
|
||||
{
|
||||
getApp()->windows->showSettingsDialog(
|
||||
SettingsDialogPreference::
|
||||
@@ -233,9 +234,9 @@ void SplitHeader::initializeLayout()
|
||||
});
|
||||
|
||||
// update moderation button when items changed
|
||||
this->managedConnect(getApp()->moderationActions->items.delayedItemsChanged,
|
||||
this->managedConnect(getSettings()->moderationActions.delayedItemsChanged,
|
||||
[this] {
|
||||
if (getApp()->moderationActions->items.empty())
|
||||
if (getSettings()->moderationActions.empty())
|
||||
{
|
||||
if (this->split_->getModerationMode())
|
||||
this->split_->setModerationMode(true);
|
||||
@@ -247,6 +248,13 @@ void SplitHeader::initializeLayout()
|
||||
}
|
||||
});
|
||||
|
||||
getSettings()->customURIScheme.connect([this] {
|
||||
if (const auto drop = this->dropdownButton_)
|
||||
{
|
||||
drop->setMenu(this->createMainMenu());
|
||||
}
|
||||
});
|
||||
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
this->setLayout(layout);
|
||||
@@ -291,6 +299,12 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
#endif
|
||||
menu->addAction(OPEN_IN_STREAMLINK, this->split_,
|
||||
&Split::openInStreamlink);
|
||||
|
||||
if (!getSettings()->customURIScheme.getValue().isEmpty())
|
||||
{
|
||||
menu->addAction("Open with URI Scheme", this->split_,
|
||||
&Split::openWithCustomScheme);
|
||||
}
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
@@ -339,11 +353,11 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
action->setCheckable(true);
|
||||
|
||||
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
|
||||
action->setChecked(getApp()->pings->isMuted(
|
||||
action->setChecked(getSettings()->isMutedChannel(
|
||||
this->split_->getChannel()->getName()));
|
||||
});
|
||||
action->connect(action, &QAction::triggered, this, [this]() {
|
||||
getApp()->pings->toggleMuteChannel(
|
||||
getSettings()->toggleMutedChannel(
|
||||
this->split_->getChannel()->getName());
|
||||
});
|
||||
|
||||
@@ -568,7 +582,7 @@ void SplitHeader::updateChannelText()
|
||||
void SplitHeader::updateModerationModeIcon()
|
||||
{
|
||||
auto moderationMode = this->split_->getModerationMode() &&
|
||||
!getApp()->moderationActions->items.empty();
|
||||
!getSettings()->moderationActions.empty();
|
||||
|
||||
this->moderationButton_->setPixmap(
|
||||
moderationMode ? getResources().buttons.modModeEnabled
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
#include "widgets/dialogs/EmotePopup.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
@@ -267,14 +268,21 @@ void SplitInput::installKeyPressedEvent()
|
||||
}
|
||||
else if (event->key() == Qt::Key_End)
|
||||
{
|
||||
QTextCursor cursor = this->ui_.textEdit->textCursor();
|
||||
cursor.movePosition(
|
||||
QTextCursor::End,
|
||||
event->modifiers() & Qt::KeyboardModifier::ShiftModifier
|
||||
? QTextCursor::MoveMode::KeepAnchor
|
||||
: QTextCursor::MoveMode::MoveAnchor);
|
||||
this->ui_.textEdit->setTextCursor(cursor);
|
||||
|
||||
if (event->modifiers() == Qt::ControlModifier)
|
||||
{
|
||||
this->split_->getChannelView().getScrollBar().scrollToBottom(
|
||||
getSettings()->enableSmoothScrollingNewMessages.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextCursor cursor = this->ui_.textEdit->textCursor();
|
||||
cursor.movePosition(
|
||||
QTextCursor::End,
|
||||
event->modifiers() & Qt::KeyboardModifier::ShiftModifier
|
||||
? QTextCursor::MoveMode::KeepAnchor
|
||||
: QTextCursor::MoveMode::MoveAnchor);
|
||||
this->ui_.textEdit->setTextCursor(cursor);
|
||||
}
|
||||
event->accept();
|
||||
}
|
||||
else if (event->key() == Qt::Key_H &&
|
||||
|
||||
@@ -158,8 +158,6 @@ void SplitOverlay::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
BaseWidget::mouseMoveEvent(event);
|
||||
|
||||
// qDebug() << QGuiApplication::queryKeyboardModifiers();
|
||||
|
||||
// if ((QGuiApplication::queryKeyboardModifiers() & Qt::AltModifier) ==
|
||||
// Qt::AltModifier) {
|
||||
// this->hide();
|
||||
|
||||
Reference in New Issue
Block a user