Improve code for Shortcuts handling.

This commit is contained in:
John Preston 2018-11-16 16:15:14 +04:00
parent 8a3615281c
commit b3ffbeb63e
24 changed files with 683 additions and 560 deletions

View File

@ -0,0 +1,14 @@
// This is a list of your own shortcuts for Telegram Desktop
// You can see full list of commands in the 'shortcuts-default.json' file
// Place a null value instead of a command string to switch the shortcut off
[
// {
// "command": "close_telegram",
// "keys": "ctrl+f4"
// },
// {
// "command": "quit_telegram",
// "keys": "ctrl+q"
// }
]

View File

@ -65,6 +65,9 @@
<qresource prefix="/qt-project.org">
<file>qmime/freedesktop.org.xml</file>
</qresource>
<qresource prefix="/misc">
<file alias="default_shortcuts-custom.json">../default_shortcuts-custom.json</file>
</qresource>
<qresource prefix="/langs">
<file alias="lang_it.strings">../langs/lang_it.strings</file>
<file alias="lang_es.strings">../langs/lang_es.strings</file>

View File

@ -68,8 +68,6 @@ enum {
MaxZoomLevel = 7, // x8
ZoomToScreenLevel = 1024, // just constant
ShortcutsCountLimit = 256, // how many shortcuts can be in json file
PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request
SearchPeopleLimit = 5,

View File

@ -0,0 +1,451 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/shortcuts.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "messenger.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_specific.h"
#include "base/parse_helper.h"
namespace Shortcuts {
namespace {
constexpr auto kCountLimit = 256; // How many shortcuts can be in json file.
rpl::event_stream<not_null<Request*>> RequestsStream;
const auto AutoRepeatCommands = base::flat_set<Command>{
Command::MediaPrevious,
Command::MediaNext,
Command::ChatPrevious,
Command::ChatNext,
};
const auto MediaCommands = base::flat_set<Command>{
Command::MediaPlay,
Command::MediaPause,
Command::MediaPlayPause,
Command::MediaStop,
Command::MediaPrevious,
Command::MediaNext,
};
const auto CommandByName = base::flat_map<QString, Command>{
{ qsl("close_telegram") , Command::Close },
{ qsl("lock_telegram") , Command::Lock },
{ qsl("minimize_telegram"), Command::Minimize },
{ qsl("quit_telegram") , Command::Quit },
{ qsl("media_play") , Command::MediaPlay },
{ qsl("media_pause") , Command::MediaPause },
{ qsl("media_playpause") , Command::MediaPlayPause },
{ qsl("media_stop") , Command::MediaStop },
{ qsl("media_previous") , Command::MediaPrevious },
{ qsl("media_next") , Command::MediaNext },
{ qsl("search") , Command::Search },
{ qsl("previous_chat") , Command::ChatPrevious },
{ qsl("next_chat") , Command::ChatNext },
};
const auto CommandNames = base::flat_map<Command, QString>{
{ Command::Close , qsl("close_telegram") },
{ Command::Lock , qsl("lock_telegram") },
{ Command::Minimize , qsl("minimize_telegram") },
{ Command::Quit , qsl("quit_telegram") },
{ Command::MediaPlay , qsl("media_play") },
{ Command::MediaPause , qsl("media_pause") },
{ Command::MediaPlayPause, qsl("media_playpause") },
{ Command::MediaStop , qsl("media_stop") },
{ Command::MediaPrevious , qsl("media_previous") },
{ Command::MediaNext , qsl("media_next") },
{ Command::Search , qsl("search") },
{ Command::ChatPrevious , qsl("previous_chat") },
{ Command::ChatNext , qsl("next_chat") },
};
class Manager {
public:
void fill();
void clear();
std::optional<Command> lookup(int shortcutId) const;
void toggleMedia(bool toggled);
const QStringList &errors() const;
private:
void fillDefaults();
void writeDefaultFile();
bool readCustomFile();
void set(const QString &keys, Command command);
void remove(const QString &keys);
void unregister(base::unique_qptr<QShortcut> shortcut);
QStringList _errors;
base::flat_map<QKeySequence, base::unique_qptr<QShortcut>> _shortcuts;
base::flat_map<int, Command> _commandByShortcutId;
base::flat_set<QShortcut*> _mediaShortcuts;
};
QString DefaultFilePath() {
return cWorkingDir() + qsl("tdata/shortcuts-default.json");
}
QString CustomFilePath() {
return cWorkingDir() + qsl("tdata/shortcuts-custom.json");
}
bool DefaultFileIsValid() {
QFile file(DefaultFilePath());
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
base::parse::stripComments(file.readAll()),
&error);
file.close();
if (error.error != QJsonParseError::NoError || !document.isArray()) {
return false;
}
const auto shortcuts = document.array();
if (shortcuts.isEmpty() || !(*shortcuts.constBegin()).isObject()) {
return false;
}
const auto versionObject = (*shortcuts.constBegin()).toObject();
const auto version = versionObject.constFind(qsl("version"));
if (version == versionObject.constEnd()
|| !(*version).isString()
|| (*version).toString() != QString::number(AppVersion)) {
return false;
}
return true;
}
void WriteDefaultCustomFile() {
const auto path = CustomFilePath();
auto input = QFile(":/misc/default_shortcuts-custom.json");
auto output = QFile(path);
if (input.open(QIODevice::ReadOnly) && output.open(QIODevice::WriteOnly)) {
output.write(input.readAll());
}
}
void Manager::fill() {
fillDefaults();
if (!DefaultFileIsValid()) {
writeDefaultFile();
}
if (!readCustomFile()) {
WriteDefaultCustomFile();
}
}
void Manager::clear() {
_errors.clear();
_shortcuts.clear();
_commandByShortcutId.clear();
_mediaShortcuts.clear();
}
const QStringList &Manager::errors() const {
return _errors;
}
std::optional<Command> Manager::lookup(int shortcutId) const {
const auto i = _commandByShortcutId.find(shortcutId);
return (i != end(_commandByShortcutId))
? base::make_optional(i->second)
: std::nullopt;
}
void Manager::toggleMedia(bool toggled) {
for (const auto shortcut : _mediaShortcuts) {
shortcut->setEnabled(toggled);
}
}
bool Manager::readCustomFile() {
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
QFile file(CustomFilePath());
if (!file.exists()) {
return false;
}
const auto guard = gsl::finally([&] {
if (!_errors.isEmpty()) {
_errors.push_front(qsl("While reading file '%1'..."
).arg(file.fileName()));
}
});
if (!file.open(QIODevice::ReadOnly)) {
_errors.push_back(qsl("Could not read the file!"));
return true;
}
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
base::parse::stripComments(file.readAll()),
&error);
file.close();
if (error.error != QJsonParseError::NoError) {
_errors.push_back(qsl("Failed to parse! Error: %2"
).arg(error.errorString()));
return true;
} else if (!document.isArray()) {
_errors.push_back(qsl("Failed to parse! Error: array expected"));
return true;
}
const auto shortcuts = document.array();
auto limit = kCountLimit;
for (auto i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) {
if (!(*i).isObject()) {
_errors.push_back(qsl("Bad entry! Error: object expected"));
continue;
}
const auto entry = (*i).toObject();
const auto keys = entry.constFind(qsl("keys"));
const auto command = entry.constFind(qsl("command"));
if (keys == entry.constEnd()
|| command == entry.constEnd()
|| !(*keys).isString()
|| (!(*command).isString() && !(*command).isNull())) {
_errors.push_back(qsl("Bad entry! "
"{\"keys\": \"...\", \"command\": [ \"...\" | null ]} "
"expected."));
} else if ((*command).isNull()) {
remove((*keys).toString());
} else {
const auto name = (*command).toString();
const auto i = CommandByName.find(name);
if (i != end(CommandByName)) {
set((*keys).toString(), i->second);
} else {
LOG(("Shortcut Warning: "
"could not find shortcut command handler '%1'"
).arg(name));
}
}
if (!--limit) {
_errors.push_back(qsl("Too many entries! Limit is %1"
).arg(kCountLimit));
break;
}
}
return true;
}
void Manager::fillDefaults() {
set(qsl("ctrl+w"), Command::Close);
set(qsl("ctrl+f4"), Command::Close);
set(qsl("ctrl+l"), Command::Lock);
set(qsl("ctrl+m"), Command::Minimize);
set(qsl("ctrl+q"), Command::Quit);
set(qsl("media play"), Command::MediaPlay);
set(qsl("media pause"), Command::MediaPause);
set(qsl("toggle media play/pause"), Command::MediaPlayPause);
set(qsl("media stop"), Command::MediaStop);
set(qsl("media previous"), Command::MediaPrevious);
set(qsl("media next"), Command::MediaNext);
set(qsl("ctrl+f"), Command::Search);
set(qsl("search"), Command::Search);
set(qsl("find"), Command::Search);
set(qsl("ctrl+pgdown"), Command::ChatNext);
set(qsl("alt+down"), Command::ChatNext);
set(qsl("ctrl+pgup"), Command::ChatPrevious);
set(qsl("alt+up"), Command::ChatPrevious);
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
set(qsl("meta+tab"), Command::ChatNext);
set(qsl("meta+shift+tab"), Command::ChatPrevious);
set(qsl("meta+backtab"), Command::ChatPrevious);
} else {
set(qsl("ctrl+tab"), Command::ChatNext);
set(qsl("ctrl+shift+tab"), Command::ChatPrevious);
set(qsl("ctrl+backtab"), Command::ChatPrevious);
}
}
void Manager::writeDefaultFile() {
auto file = QFile(DefaultFilePath());
if (!file.open(QIODevice::WriteOnly)) {
return;
}
const char *defaultHeader = R"HEADER(
// This is a list of default shortcuts for Telegram Desktop
// Please don't modify it, its content is not used in any way
// You can place your own shortcuts in the 'shortcuts-custom.json' file
)HEADER";
file.write(defaultHeader);
auto shortcuts = QJsonArray();
auto version = QJsonObject();
version.insert(qsl("version"), QString::number(AppVersion));
shortcuts.push_back(version);
for (const auto &[sequence, shortcut] : _shortcuts) {
const auto i = _commandByShortcutId.find(shortcut->id());
if (i != end(_commandByShortcutId)) {
const auto j = CommandNames.find(i->second);
if (j != end(CommandNames)) {
QJsonObject entry;
entry.insert(qsl("keys"), sequence.toString().toLower());
entry.insert(qsl("command"), j->second);
shortcuts.append(entry);
}
}
}
auto document = QJsonDocument();
document.setArray(shortcuts);
file.write(document.toJson(QJsonDocument::Indented));
}
void Manager::set(const QString &keys, Command command) {
if (keys.isEmpty()) {
return;
}
const auto result = QKeySequence(keys, QKeySequence::PortableText);
if (result.isEmpty()) {
_errors.push_back(qsl("Could not derive key sequence '%1'!"
).arg(keys));
return;
}
auto shortcut = base::make_unique_q<QShortcut>(
result,
Messenger::Instance().getActiveWindow(),
nullptr,
nullptr,
Qt::ApplicationShortcut);
if (AutoRepeatCommands.contains(command)) {
shortcut->setAutoRepeat(false);
}
const auto isMediaShortcut = MediaCommands.contains(command);
if (isMediaShortcut) {
shortcut->setEnabled(false);
}
const auto id = shortcut->id();
if (!id) {
_errors.push_back(qsl("Could not create shortcut '%1'!").arg(keys));
return;
}
auto i = _shortcuts.find(result);
if (i == end(_shortcuts)) {
i = _shortcuts.emplace(result, std::move(shortcut)).first;
} else {
unregister(std::exchange(i->second, std::move(shortcut)));
}
_commandByShortcutId.emplace(id, command);
if (isMediaShortcut) {
_mediaShortcuts.emplace(i->second.get());
}
}
void Manager::remove(const QString &keys) {
if (keys.isEmpty()) {
return;
}
const auto result = QKeySequence(keys, QKeySequence::PortableText);
if (result.isEmpty()) {
_errors.push_back(qsl("Could not derive key sequence '%1'!"
).arg(keys));
return;
}
const auto i = _shortcuts.find(result);
if (i != end(_shortcuts)) {
unregister(std::move(i->second));
_shortcuts.erase(i);
}
}
void Manager::unregister(base::unique_qptr<QShortcut> shortcut) {
if (shortcut) {
_commandByShortcutId.erase(shortcut->id());
_mediaShortcuts.erase(shortcut.get());
}
}
Manager Data;
} // namespace
Request::Request(Command command) : _command(command) {
}
bool Request::check(Command command, int priority) {
if (_command == command && priority > _handlerPriority) {
_handlerPriority = priority;
return true;
}
return false;
}
bool Request::handle(FnMut<bool()> handler) {
_handler = std::move(handler);
return true;
}
bool Launch(Command command) {
auto request = Request(command);
RequestsStream.fire(&request);
return request._handler ? request._handler() : false;
}
rpl::producer<not_null<Request*>> Requests() {
return RequestsStream.events();
}
void Start() {
Assert(Global::started());
Data.fill();
}
const QStringList &Errors() {
return Data.errors();
}
bool HandleEvent(not_null<QShortcutEvent*> event) {
if (const auto command = Data.lookup(event->shortcutId())) {
return Launch(*command);
}
return false;
}
void EnableMediaShortcuts() {
Data.toggleMedia(true);
Platform::SetWatchingMediaKeys(true);
}
void DisableMediaShortcuts() {
Data.toggleMedia(false);
Platform::SetWatchingMediaKeys(false);
}
void Finish() {
Data.clear();
}
} // namespace Shortcuts

View File

@ -0,0 +1,64 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Shortcuts {
enum class Command {
Close,
Lock,
Minimize,
Quit,
MediaPlay,
MediaPause,
MediaPlayPause,
MediaStop,
MediaPrevious,
MediaNext,
Search,
ChatPrevious,
ChatNext,
};
bool Launch(Command command);
class Request {
public:
bool check(Command command, int priority = 0);
bool handle(FnMut<bool()> handler);
private:
explicit Request(Command command);
Command _command;
int _handlerPriority = -1;
FnMut<bool()> _handler;
friend bool Launch(Command command);
};
rpl::producer<not_null<Request*>> Requests();
void Start();
void Finish();
bool HandleEvent(not_null<QShortcutEvent*> event);
const QStringList &Errors();
// Media shortcuts are not enabled by default, because other
// applications also use them. They are enabled only when
// the in-app player is active and disabled back after.
void EnableMediaShortcuts();
void DisableMediaShortcuts();
} // namespace Shortcuts

View File

@ -10,9 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_inner.h"
#include "history/admin_log/history_admin_log_filter.h"
#include "profile/profile_back_button.h"
#include "styles/style_history.h"
#include "styles/style_window.h"
#include "styles/style_info.h"
#include "core/shortcuts.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
@ -25,6 +23,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "base/timer.h"
#include "lang/lang_keys.h"
#include "styles/style_history.h"
#include "styles/style_window.h"
#include "styles/style_info.h"
namespace AdminLog {
@ -262,6 +263,8 @@ Widget::Widget(QWidget *parent, not_null<Window::Controller*> controller, not_nu
connect(_scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); });
_whatIsThis->setClickedCallback([=] { Ui::show(Box<InformBox>(lang(lng_admin_log_about_text))); });
setupShortcuts();
}
void Widget::showFilter() {
@ -317,12 +320,17 @@ void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> m
restoreState(memento);
}
bool Widget::cmd_search() {
if (!inFocusChain()) {
return false;
}
_fixedBar->showSearch();
return true;
void Widget::setupShortcuts() {
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
if (isActiveWindow() && !Ui::isLayerShown() && inFocusChain()) {
request->check(Command::Search, 1) && request->handle([=] {
_fixedBar->showSearch();
return true;
});
}
}, lifetime());
}
std::unique_ptr<Window::SectionMemento> Widget::createMemento() {

View File

@ -87,8 +87,6 @@ public:
void applyFilter(FilterValue &&value);
bool cmd_search() override;
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -104,6 +102,7 @@ private:
void updateAdaptiveLayout();
void saveState(not_null<SectionMemento*> memento);
void restoreState(not_null<SectionMemento*> memento);
void setupShortcuts();
object_ptr<Ui::ScrollArea> _scroll;
QPointer<InnerWidget> _inner;

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_service.h"
#include "history/history_inner_widget.h"
#include "core/event_filter.h"
#include "core/shortcuts.h"
#include "lang/lang_keys.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
@ -134,6 +135,7 @@ Widget::Widget(
}, lifetime());
setupScrollDownButton();
setupShortcuts();
}
void Widget::setupScrollDownButton() {
@ -295,13 +297,17 @@ void Widget::setInternalState(
restoreState(memento);
}
bool Widget::cmd_search() {
if (!inFocusChain()) {
return false;
}
App::main()->searchInChat(_feed);
return true;
void Widget::setupShortcuts() {
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
if (isActiveWindow() && !Ui::isLayerShown() && inFocusChain()) {
request->check(Command::Search, 1) && request->handle([=] {
App::main()->searchInChat(_feed);
return true;
});
}
}, lifetime());
}
HistoryView::Context Widget::listContext() {

View File

@ -68,8 +68,6 @@ public:
bool wheelEventFromFloatPlayer(QEvent *e) override;
QRect rectForFloatPlayer() const override;
bool cmd_search() override;
// HistoryView::ListDelegate interface.
HistoryView::Context listContext() override;
void listScrollTo(int top) override;
@ -122,6 +120,8 @@ private:
void confirmDeleteSelected();
void clearSelected();
void setupShortcuts();
not_null<Data::Feed*> _feed;
object_ptr<Ui::ScrollArea> _scroll;
QPointer<HistoryView::ListWidget> _inner;

View File

@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "inline_bots/inline_results_widget.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h"
#include "core/shortcuts.h"
#include "support/support_common.h"
#include "support/support_autocomplete.h"
#include "dialogs/dialogs_key.h"
@ -778,6 +779,7 @@ HistoryWidget::HistoryWidget(
}, lifetime());
orderWidgets();
setupShortcuts();
}
void HistoryWidget::supportInitAutocomplete() {
@ -1582,17 +1584,30 @@ void HistoryWidget::notify_migrateUpdated(PeerData *peer) {
}
}
bool HistoryWidget::cmd_search() {
if (!inFocusChain() || !_history) return false;
App::main()->searchInChat(_history);
return true;
void HistoryWidget::setupShortcuts() {
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
if (isActiveWindow() && !Ui::isLayerShown() && _history) {
if (inFocusChain()) {
request->check(Command::Search) && request->handle([=] {
App::main()->searchInChat(_history);
return true;
});
}
request->check(Command::ChatPrevious) && request->handle([=] {
return showPreviousChat();
});
request->check(Command::ChatNext) && request->handle([=] {
return showNextChat();
});
}
}, lifetime());
}
bool HistoryWidget::cmd_next_chat() {
if (!_history) {
return false;
}
bool HistoryWidget::showNextChat() {
Expects(_history != nullptr);
const auto next = App::main()->chatListEntryAfter(
Dialogs::RowDescriptor(
_history,
@ -1611,10 +1626,9 @@ bool HistoryWidget::cmd_next_chat() {
return jumpToDialogRow(to);
}
bool HistoryWidget::cmd_previous_chat() {
if (!_history) {
return false;
}
bool HistoryWidget::showPreviousChat() {
Expects(_history != nullptr);
const auto previous = App::main()->chatListEntryBefore(
Dialogs::RowDescriptor(
_history,

View File

@ -354,10 +354,6 @@ public:
void notify_userIsBotChanged(UserData *user);
void notify_migrateUpdated(PeerData *peer);
bool cmd_search();
bool cmd_next_chat();
bool cmd_previous_chat();
~HistoryWidget();
protected:
@ -570,6 +566,10 @@ private:
}
bool jumpToDialogRow(const Dialogs::RowDescriptor &to);
void setupShortcuts();
bool showNextChat();
bool showPreviousChat();
MsgId _replyToId = 0;
Text _replyToName;
int _replyToNameVersion = 0;

View File

@ -17,9 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_shared_media.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "shortcuts.h"
#include "auth_session.h"
#include "lang/lang_keys.h"
#include "core/shortcuts.h"
#include "ui/special_buttons.h"
#include "ui/unread_badge.h"
#include "ui/widgets/buttons.h"

View File

@ -61,7 +61,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/download_path_box.h"
#include "boxes/connection_box.h"
#include "storage/localstorage.h"
#include "shortcuts.h"
#include "media/media_audio.h"
#include "media/player/media_player_panel.h"
#include "media/player/media_player_widget.h"
@ -79,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/dc_options.h"
#include "core/file_utilities.h"
#include "core/update_checker.h"
#include "core/shortcuts.h"
#include "calls/calls_instance.h"
#include "calls/calls_top_bar.h"
#include "export/export_settings.h"
@ -621,24 +621,6 @@ void MainWidget::notify_historyMuteUpdated(History *history) {
_dialogs->notify_historyMuteUpdated(history);
}
bool MainWidget::cmd_search() {
if (Ui::isLayerShown() || !isActiveWindow()) return false;
if (_mainSection) {
return _mainSection->cmd_search();
}
return _history->cmd_search();
}
bool MainWidget::cmd_next_chat() {
if (Ui::isLayerShown() || !isActiveWindow()) return false;
return _history->cmd_next_chat();
}
bool MainWidget::cmd_previous_chat() {
if (Ui::isLayerShown() || !isActiveWindow()) return false;
return _history->cmd_previous_chat();
}
void MainWidget::noHider(HistoryHider *destroyed) {
if (_hider == destroyed) {
_hider = nullptr;
@ -1208,7 +1190,7 @@ void MainWidget::closeBothPlayers() {
Media::Player::instance()->stop(AudioMsgId::Type::Voice);
Media::Player::instance()->stop(AudioMsgId::Type::Song);
Shortcuts::disableMediaShortcuts();
Shortcuts::DisableMediaShortcuts();
}
void MainWidget::createPlayer() {
@ -1230,7 +1212,7 @@ void MainWidget::createPlayer() {
if (_a_show.animating()) {
_player->show(anim::type::instant);
_player->setVisible(false);
Shortcuts::enableMediaShortcuts();
Shortcuts::EnableMediaShortcuts();
} else {
_player->hide(anim::type::instant);
}
@ -1240,7 +1222,7 @@ void MainWidget::createPlayer() {
_player->show(anim::type::normal);
_playerHeight = _contentScrollAddToY = _player->contentHeight();
updateControlsGeometry();
Shortcuts::enableMediaShortcuts();
Shortcuts::EnableMediaShortcuts();
}
}
}

View File

@ -314,10 +314,6 @@ public:
void notify_migrateUpdated(PeerData *peer);
void notify_historyMuteUpdated(History *history);
bool cmd_search();
bool cmd_next_chat();
bool cmd_previous_chat();
~MainWidget();
signals:

View File

@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_cloud_manager.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "shortcuts.h"
#include "core/shortcuts.h"
#include "messenger.h"
#include "auth_session.h"
#include "application.h"

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "data/data_media_types.h"
#include "window/window_controller.h"
#include "core/shortcuts.h"
#include "messenger.h"
#include "mainwindow.h"
#include "auth_session.h"
@ -75,6 +76,8 @@ Instance::Instance()
Messenger::Instance().authSessionChanged(),
handleAuthSessionChange);
handleAuthSessionChange();
setupShortcuts();
}
AudioMsgId::Type Instance::getActiveType() const {
@ -494,5 +497,36 @@ void Instance::handleLogout() {
_usePanelPlayer.notify(false, true);
}
void Instance::setupShortcuts() {
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
request->check(Command::MediaPlay) && request->handle([=] {
play();
return true;
});
request->check(Command::MediaPause) && request->handle([=] {
pause();
return true;
});
request->check(Command::MediaPlayPause) && request->handle([=] {
playPause();
return true;
});
request->check(Command::MediaStop) && request->handle([=] {
stop();
return true;
});
request->check(Command::MediaPrevious) && request->handle([=] {
previous();
return true;
});
request->check(Command::MediaNext) && request->handle([=] {
next();
return true;
});
}, _lifetime);
}
} // namespace Player
} // namespace Media

View File

@ -127,6 +127,8 @@ private:
Instance();
friend void start();
void setupShortcuts();
using SharedMediaType = Storage::SharedMediaType;
using SliceKey = SparseIdsMergedSlice::Key;
struct Data {
@ -202,6 +204,8 @@ private:
base::Observable<AudioMsgId::Type> _trackChangedNotifier;
base::Observable<AudioMsgId::Type> _repeatChangedNotifier;
rpl::lifetime _lifetime;
};
} // namespace Clip

View File

@ -12,13 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "base/timer.h"
#include "core/update_checker.h"
#include "core/shortcuts.h"
#include "storage/localstorage.h"
#include "platform/platform_specific.h"
#include "mainwindow.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "application.h"
#include "shortcuts.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "calls/calls_instance.h"
@ -143,8 +143,7 @@ Messenger::Messenger(not_null<Core::Launcher*> launcher)
DEBUG_LOG(("Application Info: window created..."));
Shortcuts::start();
startShortcuts();
App::initMedia();
Local::ReadMapState state = Local::readMap(QByteArray());
@ -172,11 +171,8 @@ Messenger::Messenger(not_null<Core::Launcher*> launcher)
_window->updateIsActive(Global::OnlineFocusTimeout());
if (!Shortcuts::errors().isEmpty()) {
const QStringList &errors(Shortcuts::errors());
for (QStringList::const_iterator i = errors.cbegin(), e = errors.cend(); i != e; ++i) {
LOG(("Shortcuts Error: %1").arg(*i));
}
for (const auto &error : Shortcuts::Errors()) {
LOG(("Shortcuts Error: %1").arg(error));
}
}
@ -252,8 +248,10 @@ bool Messenger::eventFilter(QObject *object, QEvent *e) {
} break;
case QEvent::Shortcut: {
DEBUG_LOG(("Shortcut event caught: %1").arg(static_cast<QShortcutEvent*>(e)->key().toString()));
if (Shortcuts::launch(static_cast<QShortcutEvent*>(e)->shortcutId())) {
const auto event = static_cast<QShortcutEvent*>(e);
DEBUG_LOG(("Shortcut event caught: %1"
).arg(event->key().toString()));
if (Shortcuts::HandleEvent(event)) {
return true;
}
} break;
@ -1039,7 +1037,7 @@ Messenger::~Messenger() {
_mtproto.reset();
_mtprotoForKeysDestroy.reset();
Shortcuts::finish();
Shortcuts::Finish();
Ui::Emoji::Clear();
@ -1217,3 +1215,29 @@ void Messenger::quitDelayed() {
_private->quitTimer.callOnce(kQuitPreventTimeoutMs);
}
}
void Messenger::startShortcuts() {
Shortcuts::Start();
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
request->check(Command::Quit) && request->handle([] {
App::quit();
return true;
});
request->check(Command::Lock) && request->handle([=] {
if (!passcodeLocked() && Global::LocalPasscode()) {
lockByPasscode();
return true;
}
return false;
});
request->check(Command::Minimize) && request->handle([=] {
return minimizeActiveWindow();
});
request->check(Command::Close) && request->handle([=] {
return closeActiveWindow();
});
}, _lifetime);
}

View File

@ -220,6 +220,7 @@ public slots:
private:
void destroyMtpKeys(MTP::AuthKeysList &&keys);
void startLocalStorage();
void startShortcuts();
friend void App::quit();
static void QuitAttempt();
@ -277,4 +278,6 @@ private:
};
std::vector<LeaveSubscription> _leaveSubscriptions;
rpl::lifetime _lifetime;
};

View File

@ -1,446 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "shortcuts.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "messenger.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_specific.h"
#include "base/parse_helper.h"
namespace ShortcutCommands {
using Handler = bool(*)();
bool lock_telegram() {
if (!Messenger::Instance().passcodeLocked()
&& Global::LocalPasscode()) {
Messenger::Instance().lockByPasscode();
return true;
}
return false;
}
bool minimize_telegram() {
return Messenger::Instance().minimizeActiveWindow();
}
bool close_telegram() {
return Messenger::Instance().closeActiveWindow();
}
bool quit_telegram() {
App::quit();
return true;
}
//void start_stop_recording() {
//}
//void cancel_recording() {
//}
bool media_play() {
Media::Player::instance()->play();
return true;
}
bool media_pause() {
Media::Player::instance()->pause(AudioMsgId::Type::Song);
return true;
}
bool media_playpause() {
Media::Player::instance()->playPause();
return true;
}
bool media_stop() {
Media::Player::instance()->stop();
return true;
}
bool media_previous() {
Media::Player::instance()->previous();
return true;
}
bool media_next() {
Media::Player::instance()->next();
return true;
}
bool search() {
if (auto m = App::main()) {
return m->cmd_search();
}
return false;
}
bool previous_chat() {
if (auto m = App::main()) {
return m->cmd_previous_chat();
}
return false;
}
bool next_chat() {
if (auto m = App::main()) {
return m->cmd_next_chat();
}
return false;
}
// other commands here
} // namespace ShortcutCommands
inline bool qMapLessThanKey(const ShortcutCommands::Handler &a, const ShortcutCommands::Handler &b) {
return a < b;
}
namespace Shortcuts {
struct DataStruct;
DataStruct *DataPtr = nullptr;
namespace {
void createCommand(const QString &command, ShortcutCommands::Handler handler);
QKeySequence setShortcut(const QString &keys, const QString &command);
void destroyShortcut(QShortcut *shortcut);
} // namespace
struct DataStruct {
DataStruct() {
Assert(DataPtr == nullptr);
DataPtr = this;
if (autoRepeatCommands.isEmpty()) {
autoRepeatCommands.insert(qsl("media_previous"));
autoRepeatCommands.insert(qsl("media_next"));
autoRepeatCommands.insert(qsl("next_chat"));
autoRepeatCommands.insert(qsl("previous_chat"));
}
if (mediaCommands.isEmpty()) {
mediaCommands.insert(qsl("media_play"));
mediaCommands.insert(qsl("media_playpause"));
mediaCommands.insert(qsl("media_play"));
mediaCommands.insert(qsl("media_stop"));
mediaCommands.insert(qsl("media_previous"));
mediaCommands.insert(qsl("media_next"));
}
#define DeclareAlias(keys, command) setShortcut(qsl(keys), qsl(#command))
#define DeclareCommand(keys, command) createCommand(qsl(#command), ShortcutCommands::command); DeclareAlias(keys, command)
DeclareCommand("ctrl+w", close_telegram);
DeclareAlias("ctrl+f4", close_telegram);
DeclareCommand("ctrl+l", lock_telegram);
DeclareCommand("ctrl+m", minimize_telegram);
DeclareCommand("ctrl+q", quit_telegram);
//DeclareCommand("ctrl+r", start_stop_recording);
//DeclareCommand("ctrl+shift+r", cancel_recording);
//DeclareCommand("media record", start_stop_recording);
DeclareCommand("media play", media_play);
DeclareCommand("media pause", media_pause);
DeclareCommand("toggle media play/pause", media_playpause);
DeclareCommand("media stop", media_stop);
DeclareCommand("media previous", media_previous);
DeclareCommand("media next", media_next);
DeclareCommand("ctrl+f", search);
DeclareAlias("search", search);
DeclareAlias("find", search);
DeclareCommand("ctrl+pgdown", next_chat);
DeclareAlias("alt+down", next_chat);
DeclareCommand("ctrl+pgup", previous_chat);
DeclareAlias("alt+up", previous_chat);
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
DeclareAlias("meta+tab", next_chat);
DeclareAlias("meta+shift+tab", previous_chat);
DeclareAlias("meta+backtab", previous_chat);
} else {
DeclareAlias("ctrl+tab", next_chat);
DeclareAlias("ctrl+shift+tab", previous_chat);
DeclareAlias("ctrl+backtab", previous_chat);
}
// other commands here
#undef DeclareCommand
#undef DeclareAlias
}
QStringList errors;
QMap<QString, ShortcutCommands::Handler> commands;
QMap<ShortcutCommands::Handler, QString> commandnames;
QMap<QKeySequence, QShortcut*> sequences;
QMap<int, ShortcutCommands::Handler> handlers;
QSet<QShortcut*> mediaShortcuts;
QSet<QString> autoRepeatCommands;
QSet<QString> mediaCommands;
};
namespace {
void createCommand(const QString &command, ShortcutCommands::Handler handler) {
Assert(DataPtr != nullptr);
Assert(!command.isEmpty());
DataPtr->commands.insert(command, handler);
DataPtr->commandnames.insert(handler, command);
}
QKeySequence setShortcut(const QString &keys, const QString &command) {
Assert(DataPtr != nullptr);
Assert(!command.isEmpty());
if (keys.isEmpty()) return QKeySequence();
QKeySequence seq(keys, QKeySequence::PortableText);
if (seq.isEmpty()) {
DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys));
} else {
auto it = DataPtr->commands.constFind(command);
if (it == DataPtr->commands.cend()) {
LOG(("Warning: could not find shortcut command handler '%1'").arg(command));
} else {
auto shortcut = std::make_unique<QShortcut>(seq, Messenger::Instance().getActiveWindow(), nullptr, nullptr, Qt::ApplicationShortcut);
if (!DataPtr->autoRepeatCommands.contains(command)) {
shortcut->setAutoRepeat(false);
}
auto isMediaShortcut = DataPtr->mediaCommands.contains(command);
if (isMediaShortcut) {
shortcut->setEnabled(false);
}
int shortcutId = shortcut->id();
if (!shortcutId) {
DataPtr->errors.push_back(qsl("Could not create shortcut '%1'!").arg(keys));
} else {
auto seqIt = DataPtr->sequences.find(seq);
if (seqIt == DataPtr->sequences.cend()) {
seqIt = DataPtr->sequences.insert(seq, shortcut.release());
} else {
auto oldShortcut = seqIt.value();
seqIt.value() = shortcut.release();
destroyShortcut(oldShortcut);
}
DataPtr->handlers.insert(shortcutId, it.value());
if (isMediaShortcut) {
DataPtr->mediaShortcuts.insert(seqIt.value());
}
}
}
}
return seq;
}
QKeySequence removeShortcut(const QString &keys) {
Assert(DataPtr != nullptr);
if (keys.isEmpty()) return QKeySequence();
QKeySequence seq(keys, QKeySequence::PortableText);
if (seq.isEmpty()) {
DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys));
} else {
auto seqIt = DataPtr->sequences.find(seq);
if (seqIt != DataPtr->sequences.cend()) {
auto shortcut = seqIt.value();
DataPtr->sequences.erase(seqIt);
destroyShortcut(shortcut);
}
}
return seq;
}
void destroyShortcut(QShortcut *shortcut) {
Assert(DataPtr != nullptr);
DataPtr->handlers.remove(shortcut->id());
DataPtr->mediaShortcuts.remove(shortcut);
delete shortcut;
}
} // namespace
void start() {
Assert(Global::started());
new DataStruct();
// write default shortcuts to a file if they are not there already
bool defaultValid = false;
QFile defaultFile(cWorkingDir() + qsl("tdata/shortcuts-default.json"));
if (defaultFile.open(QIODevice::ReadOnly)) {
QJsonParseError error = { 0, QJsonParseError::NoError };
QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(defaultFile.readAll()), &error);
defaultFile.close();
if (error.error == QJsonParseError::NoError && doc.isArray()) {
QJsonArray shortcuts(doc.array());
if (!shortcuts.isEmpty() && (*shortcuts.constBegin()).isObject()) {
QJsonObject versionObject((*shortcuts.constBegin()).toObject());
QJsonObject::const_iterator version = versionObject.constFind(qsl("version"));
if (version != versionObject.constEnd() && (*version).isString() && (*version).toString() == QString::number(AppVersion)) {
defaultValid = true;
}
}
}
}
if (!defaultValid && defaultFile.open(QIODevice::WriteOnly)) {
const char *defaultHeader = "\
// This is a list of default shortcuts for Telegram Desktop\n\
// Please don't modify it, its content is not used in any way\n\
// You can place your own shortcuts in the 'shortcuts-custom.json' file\n\n";
defaultFile.write(defaultHeader);
QJsonArray shortcuts;
QJsonObject version;
version.insert(qsl("version"), QString::number(AppVersion));
shortcuts.push_back(version);
for (auto i = DataPtr->sequences.cbegin(), e = DataPtr->sequences.cend(); i != e; ++i) {
auto h = DataPtr->handlers.constFind(i.value()->id());
if (h != DataPtr->handlers.cend()) {
auto n = DataPtr->commandnames.constFind(h.value());
if (n != DataPtr->commandnames.cend()) {
QJsonObject entry;
entry.insert(qsl("keys"), i.key().toString().toLower());
entry.insert(qsl("command"), n.value());
shortcuts.append(entry);
}
}
}
QJsonDocument doc;
doc.setArray(shortcuts);
defaultFile.write(doc.toJson(QJsonDocument::Indented));
defaultFile.close();
}
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
QFile customFile(cWorkingDir() + qsl("tdata/shortcuts-custom.json"));
if (customFile.exists()) {
if (customFile.open(QIODevice::ReadOnly)) {
QJsonParseError error = { 0, QJsonParseError::NoError };
QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(customFile.readAll()), &error);
customFile.close();
if (error.error != QJsonParseError::NoError) {
DataPtr->errors.push_back(qsl("Failed to parse! Error: %2").arg(error.errorString()));
} else if (!doc.isArray()) {
DataPtr->errors.push_back(qsl("Failed to parse! Error: array expected"));
} else {
QJsonArray shortcuts = doc.array();
int limit = ShortcutsCountLimit;
for (QJsonArray::const_iterator i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) {
if (!(*i).isObject()) {
DataPtr->errors.push_back(qsl("Bad entry! Error: object expected"));
} else {
QKeySequence seq;
QJsonObject entry((*i).toObject());
QJsonObject::const_iterator keys = entry.constFind(qsl("keys")), command = entry.constFind(qsl("command"));
if (keys == entry.constEnd() || command == entry.constEnd() || !(*keys).isString() || (!(*command).isString() && !(*command).isNull())) {
DataPtr->errors.push_back(qsl("Bad entry! {\"keys\": \"...\", \"command\": [ \"...\" | null ]} expected"));
} else if ((*command).isNull()) {
seq = removeShortcut((*keys).toString());
} else {
seq = setShortcut((*keys).toString(), (*command).toString());
}
if (!--limit) {
DataPtr->errors.push_back(qsl("Too many entries! Limit is %1").arg(ShortcutsCountLimit));
break;
}
}
}
}
} else {
DataPtr->errors.push_back(qsl("Could not read the file!"));
}
if (!DataPtr->errors.isEmpty()) {
DataPtr->errors.push_front(qsl("While reading file '%1'...").arg(customFile.fileName()));
}
} else if (customFile.open(QIODevice::WriteOnly)) {
const char *customContent = "\
// This is a list of your own shortcuts for Telegram Desktop\n\
// You can see full list of commands in the 'shortcuts-default.json' file\n\
// Place a null value instead of a command string to switch the shortcut off\n\n\
[\n\
// {\n\
// \"command\": \"close_telegram\",\n\
// \"keys\": \"ctrl+f4\"\n\
// },\n\
// {\n\
// \"command\": \"quit_telegram\",\n\
// \"keys\": \"ctrl+q\"\n\
// }\n\
]\n";
customFile.write(customContent);
customFile.close();
}
}
const QStringList &errors() {
Assert(DataPtr != nullptr);
return DataPtr->errors;
}
bool launch(int shortcutId) {
Assert(DataPtr != nullptr);
auto it = DataPtr->handlers.constFind(shortcutId);
if (it == DataPtr->handlers.cend()) {
return false;
}
return (*it.value())();
}
bool launch(const QString &command) {
Assert(DataPtr != nullptr);
auto it = DataPtr->commands.constFind(command);
if (it == DataPtr->commands.cend()) {
return false;
}
return (*it.value())();
}
void enableMediaShortcuts() {
if (!DataPtr) return;
for_const (auto shortcut, DataPtr->mediaShortcuts) {
shortcut->setEnabled(true);
}
Platform::SetWatchingMediaKeys(true);
}
void disableMediaShortcuts() {
if (!DataPtr) return;
for_const (auto shortcut, DataPtr->mediaShortcuts) {
shortcut->setEnabled(false);
}
Platform::SetWatchingMediaKeys(false);
}
void finish() {
delete DataPtr;
DataPtr = nullptr;
}
} // namespace Shortcuts

View File

@ -1,26 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Shortcuts {
void start();
const QStringList &errors();
bool launch(int shortcutId);
bool launch(const QString &command);
// Media shortcuts are not enabled by default, because other
// applications also use them. They are enabled only when
// the in-app player is active and disabled back after.
void enableMediaShortcuts();
void disableMediaShortcuts();
void finish();
} // namespace Shortcuts

View File

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "support/support_common.h"
#include "shortcuts.h"
#include "core/shortcuts.h"
namespace Support {
@ -36,10 +36,10 @@ Qt::KeyboardModifiers SkipSwitchModifiers() {
void PerformSwitch(SwitchSettings value) {
switch (value) {
case SwitchSettings::Next:
Shortcuts::launch("next_chat");
Shortcuts::Launch(Shortcuts::Command::ChatNext);
break;
case SwitchSettings::Previous:
Shortcuts::launch("previous_chat");
Shortcuts::Launch(Shortcuts::Command::ChatPrevious);
break;
default:
break;

View File

@ -122,11 +122,6 @@ public:
return nullptr;
}
// Global shortcut handler. For now that ugly :(
virtual bool cmd_search() {
return false;
}
static void PaintBackground(QWidget *widget, QPaintEvent *event);
protected:

View File

@ -122,6 +122,8 @@
<(src_loc)/core/media_active_cache.h
<(src_loc)/core/mime_type.cpp
<(src_loc)/core/mime_type.h
<(src_loc)/core/shortcuts.cpp
<(src_loc)/core/shortcuts.h
<(src_loc)/core/single_timer.cpp
<(src_loc)/core/single_timer.h
<(src_loc)/core/tl_help.h
@ -766,8 +768,6 @@
<(src_loc)/qt_static_plugins.cpp
<(src_loc)/settings.cpp
<(src_loc)/settings.h
<(src_loc)/shortcuts.cpp
<(src_loc)/shortcuts.h
<(emoji_suggestions_loc)/emoji_suggestions.cpp
<(emoji_suggestions_loc)/emoji_suggestions.h