Make menus and placeholders display appropriate custom key combos. (#4045)

* Add initial support for finding hotkey display key sequences

* Make neededArguments work

* Implement displaying key combos in SplitHeader main menu

* Make Settings search text dynamic

* Make tab hide notice use a custom hotkeys key sequence

* Make Notebook menus use custom hotkeys key combo lookup for hiding tabs

* shut up changelog ci

* Make NotebookTab menus show custom hotkeys. SCUFFED:
this does not update dynamically!

* Scuffed: Make the show prefs button setting show the key bind

* Scuffed: Make the R9K description refer to hotkeys

* @pajlada, is something like this ok?

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Mm2PL 2022-10-09 17:20:44 +02:00 committed by GitHub
parent 4e2da540d2
commit e604a36777
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 248 additions and 41 deletions

View file

@ -61,6 +61,7 @@
- Minor: Migrated /ban to Helix API. (#4049)
- Minor: Migrated /timeout to Helix API. (#4049)
- Minor: Migrated /w to Helix API. Chat command will continue to be used until February 11th 2023. (#4052)
- Minor: Make menus and placeholders display appropriate custom key combos. (#4045)
- Bugfix: Connection to Twitch PubSub now recovers more reliably. (#3643, #3716)
- Bugfix: Fixed `Smooth scrolling on new messages` setting sometimes hiding messages. (#4028)
- Bugfix: Fixed a crash that can occur when closing and quickly reopening a split, then running a command. (#3852)

View file

@ -1,6 +1,7 @@
#include "controllers/hotkeys/HotkeyController.hpp"
#include "common/QLogging.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyModel.hpp"
#include "singletons/Settings.hpp"
@ -547,4 +548,40 @@ void HotkeyController::showHotkeyError(const std::shared_ptr<Hotkey> &hotkey,
msgBox->exec();
}
QKeySequence HotkeyController::getDisplaySequence(
HotkeyCategory category, const QString &action,
const std::optional<std::vector<QString>> &arguments) const
{
const auto &found = this->findLike(category, action, arguments);
if (found != nullptr)
{
return found->keySequence();
}
return {};
}
std::shared_ptr<Hotkey> HotkeyController::findLike(
HotkeyCategory category, const QString &action,
const std::optional<std::vector<QString>> &arguments) const
{
for (auto other : this->hotkeys_)
{
if (other->category() == category && other->action() == action)
{
if (arguments)
{
if (other->arguments() == *arguments)
{
return other;
}
}
else
{
return other;
}
}
}
return nullptr;
}
} // namespace chatterino

View file

@ -8,6 +8,7 @@
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
#include <optional>
#include <set>
class QShortcut;
@ -33,6 +34,17 @@ public:
void save() override;
std::shared_ptr<Hotkey> getHotkeyByName(QString name);
/**
* @brief returns a QKeySequence that perfoms the actions requested.
* Accepted if and only if the category matches, the action matches and arguments match.
* When arguments is present, contents of arguments must match the checked hotkey, otherwise arguments are ignored.
* For example:
* - std::nullopt (or {}) will match any hotkey satisfying category, action values,
* - {{"foo", "bar"}} will only match a hotkey that has these arguments and these arguments only
*/
QKeySequence getDisplaySequence(
HotkeyCategory category, const QString &action,
const std::optional<std::vector<QString>> &arguments = {}) const;
/**
* @brief removes the hotkey with the oldName and inserts newHotkey at the end
@ -114,6 +126,17 @@ private:
**/
static void showHotkeyError(const std::shared_ptr<Hotkey> &hotkey,
QString warning);
/**
* @brief finds a Hotkey matching category, action and arguments.
* Accepted if and only if the category matches, the action matches and arguments match.
* When arguments is present, contents of arguments must match the checked hotkey, otherwise arguments are ignored.
* For example:
* - std::nullopt (or {}) will match any hotkey satisfying category, action values,
* - {{"foo", "bar"}} will only match a hotkey that has these arguments and these arguments only
*/
std::shared_ptr<Hotkey> findLike(
HotkeyCategory category, const QString &action,
const std::optional<std::vector<QString>> &arguments = {}) const;
friend class KeyboardSettingsPage;

View file

@ -2,6 +2,8 @@
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyController.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
@ -48,6 +50,11 @@ Notebook::Notebook(QWidget *parent)
[this](bool value) {
this->setLockNotebookLayout(value);
});
this->showTabsAction_ = new QAction("Toggle visibility of tabs");
QObject::connect(this->showTabsAction_, &QAction::triggered, [this]() {
this->setShowTabs(!this->getShowTabs());
});
this->updateTabVisibilityMenuAction();
this->addNotebookActionsToMenu(&this->menu_);
@ -374,12 +381,32 @@ void Notebook::setShowTabs(bool value)
// show a popup upon hiding tabs
if (!value && getSettings()->informOnTabVisibilityToggle.getValue())
{
auto unhideSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{}});
if (unhideSeq.isEmpty())
{
unhideSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{"toggle"}});
}
if (unhideSeq.isEmpty())
{
unhideSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{"on"}});
}
QString hotkeyInfo = "(currently unbound)";
if (!unhideSeq.isEmpty())
{
hotkeyInfo =
"(" +
unhideSeq.toString(QKeySequence::SequenceFormat::NativeText) +
")";
}
QMessageBox msgBox(this->window());
msgBox.window()->setWindowTitle("Chatterino - hidden tabs");
msgBox.setText("You've just hidden your tabs.");
msgBox.setInformativeText(
"You can toggle tabs by using the keyboard shortcut (Ctrl+U by "
"default) or right-clicking the tab area and selecting \"Toggle "
"You can toggle tabs by using the keyboard shortcut " + hotkeyInfo +
" or right-clicking the tab area and selecting \"Toggle "
"visibility of tabs\".");
msgBox.addButton(QMessageBox::Ok);
auto *dsaButton =
@ -394,6 +421,34 @@ void Notebook::setShowTabs(bool value)
getSettings()->informOnTabVisibilityToggle.setValue(false);
}
}
updateTabVisibilityMenuAction();
}
void Notebook::updateTabVisibilityMenuAction()
{
auto toggleSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{}});
if (toggleSeq.isEmpty())
{
toggleSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{"toggle"}});
}
if (toggleSeq.isEmpty())
{
// show contextual shortcuts
if (this->getShowTabs())
{
toggleSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{"off"}});
}
else if (!this->getShowTabs())
{
toggleSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility", {{"on"}});
}
}
this->showTabsAction_->setShortcut(toggleSeq);
}
bool Notebook::getShowAddButton() const
@ -919,12 +974,7 @@ void Notebook::setLockNotebookLayout(bool value)
void Notebook::addNotebookActionsToMenu(QMenu *menu)
{
menu->addAction(
"Toggle visibility of tabs",
[this]() {
this->setShowTabs(!this->getShowTabs());
},
QKeySequence("Ctrl+U"));
menu->addAction(this->showTabsAction_);
menu->addAction(this->lockNotebookLayoutAction_);
}

View file

@ -86,6 +86,7 @@ protected:
}
private:
void updateTabVisibilityMenuAction();
void resizeAddButton();
bool containsPage(QWidget *page);
@ -111,6 +112,7 @@ private:
bool lockNotebookLayout_ = false;
NotebookTabLocation tabLocation_ = NotebookTabLocation::Top;
QAction *lockNotebookLayoutAction_;
QAction *showTabsAction_;
};
class SplitNotebook : public Notebook

View file

@ -54,6 +54,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
void SettingsDialog::addShortcuts()
{
this->setSearchPlaceholderText();
HotkeyController::HotkeyMap actions{
{"search",
[this](std::vector<QString>) -> QString {
@ -71,6 +72,19 @@ void SettingsDialog::addShortcuts()
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
HotkeyCategory::PopupWindow, actions, this);
}
void SettingsDialog::setSearchPlaceholderText()
{
QString searchHotkey;
auto searchSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::PopupWindow, "search");
if (!searchSeq.isEmpty())
{
searchHotkey =
"(" + searchSeq.toString(QKeySequence::SequenceFormat::NativeText) +
")";
}
this->ui_.search->setPlaceholderText("Find in settings... " + searchHotkey);
}
void SettingsDialog::initUi()
{
@ -85,7 +99,7 @@ void SettingsDialog::initUi()
.withoutMargin()
.emplace<QLineEdit>()
.assign(&this->ui_.search);
edit->setPlaceholderText("Find in settings... (Ctrl+F by default)");
this->setSearchPlaceholderText();
edit->setClearButtonEnabled(true);
edit->findChild<QAbstractButton *>()->setIcon(
QPixmap(":/buttons/clearSearch.png"));

View file

@ -61,6 +61,7 @@ private:
void onOkClicked();
void onCancelClicked();
void addShortcuts() override;
void setSearchPlaceholderText();
struct {
QWidget *tabContainerContainer{};

View file

@ -2,6 +2,8 @@
#include "Application.hpp"
#include "common/Common.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyController.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
@ -60,12 +62,15 @@ NotebookTab::NotebookTab(Notebook *notebook)
this->showRenameDialog();
});
// XXX: this doesn't update after changing hotkeys
this->menu_.addAction(
"Close Tab",
[=]() {
this->notebook_->removePage(this->page);
},
QKeySequence("Ctrl+Shift+W"));
getApp()->hotkeys->getDisplaySequence(HotkeyCategory::Window,
"removeTab"));
this->menu_.addAction(
"Popup Tab",
@ -75,7 +80,8 @@ NotebookTab::NotebookTab(Notebook *notebook)
container->popup();
}
},
QKeySequence("Ctrl+Shift+N"));
getApp()->hotkeys->getDisplaySequence(HotkeyCategory::Window, "popup",
{{"window"}}));
highlightNewMessagesAction_ =
new QAction("Mark Tab as Unread on New Messages", &this->menu_);

View file

@ -3,6 +3,8 @@
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "common/Version.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyController.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/NativeMessaging.hpp"
#include "singletons/Paths.hpp"
@ -193,7 +195,17 @@ void GeneralPage::initLayout(GeneralPageView &layout)
#endif
if (!BaseWindow::supportsCustomWindowFrame())
{
layout.addCheckbox("Show preferences button (Ctrl+P to show)",
auto settingsSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "openSettings");
QString shortcut = " (no key bound to open them otherwise)";
// TODO: maybe prevent the user from locking themselves out of the settings?
if (!settingsSeq.isEmpty())
{
shortcut = QStringLiteral(" (%1 to show)")
.arg(settingsSeq.toString(
QKeySequence::SequenceFormat::NativeText));
}
layout.addCheckbox("Show preferences button" + shortcut,
s.hidePreferencesButton, true);
layout.addCheckbox("Show user button", s.hideUserButton, true);
}
@ -559,8 +571,18 @@ void GeneralPage::initLayout(GeneralPageView &layout)
layout.addCheckbox("Title", s.headerStreamTitle);
layout.addSubtitle("R9K");
auto toggleLocalr9kSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "toggleLocalR9K");
QString toggleLocalr9kShortcut =
"an assigned hotkey (Window -> Toggle local R9K)";
if (!toggleLocalr9kSeq.isEmpty())
{
toggleLocalr9kShortcut = toggleLocalr9kSeq.toString(
QKeySequence::SequenceFormat::NativeText);
}
layout.addDescription("Hide similar messages. Toggle hidden "
"messages by pressing Ctrl+H.");
"messages by pressing " +
toggleLocalr9kShortcut + ".");
layout.addCheckbox("Hide similar messages", s.similarityEnabled);
//layout.addCheckbox("Gray out matches", s.colorSimilarDisabled);
layout.addCheckbox("By the same user", s.hideSimilarBySameUser);

View file

@ -3,6 +3,9 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "controllers/hotkeys/Hotkey.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyController.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
@ -337,21 +340,27 @@ void SplitHeader::initializeLayout()
std::unique_ptr<QMenu> SplitHeader::createMainMenu()
{
// top level menu
const auto &h = getApp()->hotkeys;
auto menu = std::make_unique<QMenu>();
menu->addAction("Change channel", this->split_, &Split::changeChannel,
QKeySequence("Ctrl+R"));
menu->addAction(
"Change channel", this->split_, &Split::changeChannel,
h->getDisplaySequence(HotkeyCategory::Split, "changeChannel"));
menu->addAction("Close", this->split_, &Split::deleteFromContainer,
QKeySequence("Ctrl+W"));
h->getDisplaySequence(HotkeyCategory::Split, "delete"));
menu->addSeparator();
menu->addAction("Popup", this->split_, &Split::popup,
QKeySequence("Ctrl+N"));
menu->addAction(
"Popup", this->split_, &Split::popup,
h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}}));
menu->addAction("Search", this->split_, &Split::showSearch,
QKeySequence("Ctrl+F"));
menu->addAction("Set filters", this->split_, &Split::setFiltersDialog);
h->getDisplaySequence(HotkeyCategory::Split, "showSearch"));
menu->addAction(
"Set filters", this->split_, &Split::setFiltersDialog,
h->getDisplaySequence(HotkeyCategory::Split, "pickFilters"));
menu->addSeparator();
#ifdef USEWEBENGINE
this->dropdownMenu.addAction("Start watching", this->split_,
&Split::startWatching);
this->dropdownMenu.addAction(
"Start watching", this->split_, &Split::startWatching;
h->getDisplaySequence(HotkeyCategory::Split, "startWatching"));
#endif
auto *twitchChannel =
@ -359,24 +368,31 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
if (twitchChannel)
{
menu->addAction(OPEN_IN_BROWSER, this->split_, &Split::openInBrowser);
menu->addAction(
OPEN_IN_BROWSER, this->split_, &Split::openInBrowser,
h->getDisplaySequence(HotkeyCategory::Split, "openInBrowser"));
#ifndef USEWEBENGINE
menu->addAction(OPEN_PLAYER_IN_BROWSER, this->split_,
&Split::openBrowserPlayer);
#endif
menu->addAction(OPEN_IN_STREAMLINK, this->split_,
&Split::openInStreamlink);
menu->addAction(
OPEN_IN_STREAMLINK, this->split_, &Split::openInStreamlink,
h->getDisplaySequence(HotkeyCategory::Split, "openInStreamlink"));
if (!getSettings()->customURIScheme.getValue().isEmpty())
{
menu->addAction("Open in custom player", this->split_,
&Split::openWithCustomScheme);
&Split::openWithCustomScheme,
h->getDisplaySequence(HotkeyCategory::Split,
"openInCustomPlayer"));
}
if (this->split_->getChannel()->hasModRights())
{
menu->addAction(OPEN_MOD_VIEW_IN_BROWSER, this->split_,
&Split::openModViewInBrowser);
menu->addAction(
OPEN_MOD_VIEW_IN_BROWSER, this->split_,
&Split::openModViewInBrowser,
h->getDisplaySequence(HotkeyCategory::Split, "openModView"));
}
menu->addAction(
@ -384,7 +400,7 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
[twitchChannel] {
twitchChannel->createClip();
},
QKeySequence("Alt+X"))
h->getDisplaySequence(HotkeyCategory::Split, "createClip"))
->setVisible(twitchChannel->isLive());
menu->addSeparator();
@ -392,24 +408,35 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
if (this->split_->getChannel()->getType() == Channel::Type::TwitchWhispers)
{
menu->addAction(OPEN_WHISPERS_IN_BROWSER, this->split_,
&Split::openWhispersInBrowser);
menu->addAction(
OPEN_WHISPERS_IN_BROWSER, this->split_,
&Split::openWhispersInBrowser,
h->getDisplaySequence(HotkeyCategory::Split, "openInBrowser"));
menu->addSeparator();
}
// reload / reconnect
if (this->split_->getChannel()->canReconnect())
{
menu->addAction("Reconnect", this, SLOT(reconnect()),
QKeySequence("Ctrl+F5"));
menu->addAction(
"Reconnect", this, SLOT(reconnect()),
h->getDisplaySequence(HotkeyCategory::Split, "reconnect"));
}
if (twitchChannel)
{
auto bothSeq =
h->getDisplaySequence(HotkeyCategory::Split, "reloadEmotes", {{}});
auto channelSeq = h->getDisplaySequence(HotkeyCategory::Split,
"reloadEmotes", {{"channel"}});
auto subSeq = h->getDisplaySequence(HotkeyCategory::Split,
"reloadEmotes", {{"subscriber"}});
menu->addAction("Reload channel emotes", this,
SLOT(reloadChannelEmotes()), QKeySequence("F5"));
SLOT(reloadChannelEmotes()),
channelSeq.isEmpty() ? bothSeq : channelSeq);
menu->addAction("Reload subscriber emotes", this,
SLOT(reloadSubscriberEmotes()), QKeySequence("F5"));
SLOT(reloadSubscriberEmotes()),
subSeq.isEmpty() ? bothSeq : subSeq);
}
menu->addSeparator();
@ -427,9 +454,20 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
// sub menu
auto moreMenu = new QMenu("More", this);
moreMenu->addAction("Toggle moderation mode", this->split_, [this]() {
this->split_->setModerationMode(!this->split_->getModerationMode());
});
auto modModeSeq = h->getDisplaySequence(HotkeyCategory::Split,
"setModerationMode", {{"toggle"}});
if (modModeSeq.isEmpty())
{
modModeSeq = h->getDisplaySequence(HotkeyCategory::Split,
"setModerationMode", {{}});
// this makes a full std::optional<> with an empty vector inside
}
moreMenu->addAction(
"Toggle moderation mode", this->split_,
[this]() {
this->split_->setModerationMode(!this->split_->getModerationMode());
},
modModeSeq);
if (this->split_->getChannel()->getType() == Channel::Type::TwitchMentions)
{
@ -450,8 +488,9 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
if (twitchChannel)
{
moreMenu->addAction("Show viewer list", this->split_,
&Split::showViewerList);
moreMenu->addAction(
"Show viewer list", this->split_, &Split::showViewerList,
h->getDisplaySequence(HotkeyCategory::Split, "openViewerList"));
moreMenu->addAction("Subscribe", this->split_, &Split::openSubPage);
@ -459,6 +498,16 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
action->setText("Notify when live");
action->setCheckable(true);
auto notifySeq = h->getDisplaySequence(
HotkeyCategory::Split, "setChannelNotification", {{"toggle"}});
if (notifySeq.isEmpty())
{
notifySeq = h->getDisplaySequence(HotkeyCategory::Split,
"setChannelNotification", {{}});
// this makes a full std::optional<> with an empty vector inside
}
action->setShortcut(notifySeq);
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
action->setChecked(getApp()->notifications->isChannelNotified(
this->split_->getChannel()->getName(), Platform::Twitch));
@ -490,7 +539,9 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
}
moreMenu->addSeparator();
moreMenu->addAction("Clear messages", this->split_, &Split::clear);
moreMenu->addAction(
"Clear messages", this->split_, &Split::clear,
h->getDisplaySequence(HotkeyCategory::Split, "clearMessages"));
// moreMenu->addSeparator();
// moreMenu->addAction("Show changelog", this,
// SLOT(moreMenuShowChangelog()));