divided shortcuts file to default and custom

This commit is contained in:
John Preston 2016-02-28 12:48:09 +03:00
parent bfa8075acf
commit 26ffbbc34f
3 changed files with 222 additions and 77 deletions

View File

@ -133,6 +133,76 @@ inline bool qMapLessThanKey(const ShortcutCommands::Handler &a, const ShortcutCo
namespace Shortcuts { namespace Shortcuts {
// inspired by https://github.com/sindresorhus/strip-json-comments
QByteArray _stripJsonComments(const QByteArray &json) {
enum InsideComment {
InsideCommentNone,
InsideCommentSingleLine,
InsideCommentMultiLine,
};
InsideComment insideComment = InsideCommentNone;
bool insideString = false;
QByteArray result;
const char *b = json.cbegin(), *e = json.cend(), *offset = b;
for (const char *ch = offset; ch != e; ++ch) {
char currentChar = *ch;
char nextChar = (ch + 1 == e) ? 0 : *(ch + 1);
if (insideComment == InsideCommentNone && currentChar == '"') {
bool escaped = ((ch > b) && *(ch - 1) == '\\') && ((ch - 1 < b) || *(ch - 2) != '\\');
if (!escaped) {
insideString = !insideString;
}
}
if (insideString) {
continue;
}
if (insideComment == InsideCommentNone && currentChar == '/' && nextChar == '/') {
if (ch > offset) {
if (result.isEmpty()) result.reserve(json.size() - 2);
result.append(offset, ch - offset);
offset = ch;
}
insideComment = InsideCommentSingleLine;
++ch;
} else if (insideComment == InsideCommentSingleLine && currentChar == '\r' && nextChar == '\n') {
if (ch > offset) {
offset = ch;
}
++ch;
insideComment = InsideCommentNone;
} else if (insideComment == InsideCommentSingleLine && currentChar == '\n') {
if (ch > offset) {
offset = ch;
}
insideComment = InsideCommentNone;
} else if (insideComment == InsideCommentNone && currentChar == '/' && nextChar == '*') {
if (ch > offset) {
if (result.isEmpty()) result.reserve(json.size() - 2);
result.append(offset, ch - offset);
offset = ch;
}
insideComment = InsideCommentMultiLine;
++ch;
} else if (insideComment == InsideCommentMultiLine && currentChar == '*' && nextChar == '/') {
if (ch > offset) {
offset = ch;
}
++ch;
insideComment = InsideCommentNone;
}
}
if (insideComment == InsideCommentNone && e > offset && !result.isEmpty()) {
result.append(offset, e - offset);
}
return result.isEmpty() ? json : result;
}
struct DataStruct; struct DataStruct;
DataStruct *DataPtr = nullptr; DataStruct *DataPtr = nullptr;
@ -253,73 +323,116 @@ namespace Shortcuts {
new DataStruct(); new DataStruct();
QJsonArray shortcuts; // write default shortcuts to a file if they are not there already
OrderedSet<QKeySequence> notfound; bool defaultValid = false;
QFile f(cWorkingDir() + qsl("tdata/shortcuts.json")); QFile defaultFile(cWorkingDir() + qsl("tdata/shortcuts-default.json"));
if (f.exists()) { if (defaultFile.open(QIODevice::ReadOnly)) {
if (f.open(QIODevice::ReadOnly)) { QJsonParseError error = { 0, QJsonParseError::NoError };
QJsonParseError error = { 0, QJsonParseError::NoError }; QJsonDocument doc = QJsonDocument::fromJson(_stripJsonComments(defaultFile.readAll()), &error);
QJsonDocument doc = QJsonDocument::fromJson(f.readAll(), &error); defaultFile.close();
if (error.error != QJsonParseError::NoError) {
DataPtr->errors.push_back(qsl("Failed to parse '%1'! Error: %2").arg(f.fileName()).arg(error.errorString()));
} else if (!doc.isArray()) {
DataPtr->errors.push_back(qsl("Failed to parse '%1'! Error: array expected").arg(f.fileName()));
} else {
for (QMap<QKeySequence, QShortcut*>::const_iterator i = DataPtr->sequences.cbegin(), e = DataPtr->sequences.cend(); i != e; ++i) {
notfound.insert(i.key());
}
shortcuts = doc.array(); if (error.error == QJsonParseError::NoError && doc.isArray()) {
int limit = ShortcutsCountLimit; QJsonArray shortcuts(doc.array());
for (QJsonArray::const_iterator i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) { if (!shortcuts.isEmpty() && shortcuts.constBegin()->isObject()) {
if (!i->isObject()) { QJsonObject versionObject(shortcuts.constBegin()->toObject());
DataPtr->errors.push_back(qsl("Bad entry in '%1'! Error: object expected").arg(f.fileName())); QJsonObject::const_iterator version = versionObject.constFind(qsl("version"));
} else { if (version != versionObject.constEnd() && version->isString() && version->toString() == QString::number(AppVersion)) {
QKeySequence seq; defaultValid = true;
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 in '%1'! Error: {\"keys\": \"..\", \"command\": [ \"..\" | null ]} expected").arg(f.fileName()));
} 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 in '%1'!").arg(f.fileName()));
break;
} else if (DataPtr->errors.isEmpty()) {
notfound.remove(seq);
}
}
} }
} }
f.close();
} else {
DataPtr->errors.push_back(qsl("Could not read '") + f.fileName() + qsl("'!"));
} }
} }
if (DataPtr->errors.isEmpty() && (shortcuts.isEmpty() || !notfound.isEmpty()) && f.open(QIODevice::WriteOnly)) { if (!defaultValid && defaultFile.open(QIODevice::WriteOnly)) {
for (OrderedSet<QKeySequence>::const_iterator i = notfound.cbegin(), e = notfound.cend(); i != e; ++i) { const char *defaultHeader = "\
QMap<QKeySequence, QShortcut*>::const_iterator s = DataPtr->sequences.constFind(i.key()); // This is a list of default shortcuts for Telegram Desktop\n\
if (s != DataPtr->sequences.cend()) { // Please don't modify it, its content is not used in any way\n\
QMap<int, ShortcutCommands::Handler>::const_iterator h = DataPtr->handlers.constFind(s.value()->id()); // You can place your own shortcuts in the 'shortcuts-custom.json' file\n\n";
if (h != DataPtr->handlers.cend()) { defaultFile.write(defaultHeader);
QMap<ShortcutCommands::Handler, QString>::const_iterator n = DataPtr->commandnames.constFind(h.value());
if (n != DataPtr->commandnames.cend()) { QJsonArray shortcuts;
QJsonObject entry;
entry.insert(qsl("keys"), i.key().toString().toLower()); QJsonObject version;
entry.insert(qsl("command"), n.value()); version.insert(qsl("version"), QString::number(AppVersion));
shortcuts.append(entry); shortcuts.push_back(version);
}
for (QMap<QKeySequence, QShortcut*>::const_iterator i = DataPtr->sequences.cbegin(), e = DataPtr->sequences.cend(); i != e; ++i) {
QMap<int, ShortcutCommands::Handler>::const_iterator h = DataPtr->handlers.constFind(i.value()->id());
if (h != DataPtr->handlers.cend()) {
QMap<ShortcutCommands::Handler, QString>::const_iterator 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; QJsonDocument doc;
doc.setArray(shortcuts); doc.setArray(shortcuts);
f.write(doc.toJson(QJsonDocument::Indented)); defaultFile.write(doc.toJson(QJsonDocument::Indented));
f.close(); 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(_stripJsonComments(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();
} }
} }
@ -328,22 +441,26 @@ namespace Shortcuts {
return DataPtr->errors; return DataPtr->errors;
} }
void launch(int shortcutId) { bool launch(int shortcutId) {
t_assert(DataPtr != nullptr); t_assert(DataPtr != nullptr);
QMap<int, ShortcutCommands::Handler>::const_iterator it = DataPtr->handlers.constFind(shortcutId); QMap<int, ShortcutCommands::Handler>::const_iterator it = DataPtr->handlers.constFind(shortcutId);
if (it != DataPtr->handlers.cend()) { if (it == DataPtr->handlers.cend()) {
(*it.value())(); return false;
} }
(*it.value())();
return true;
} }
void launch(const QString &command) { bool launch(const QString &command) {
t_assert(DataPtr != nullptr); t_assert(DataPtr != nullptr);
QMap<QString, ShortcutCommands::Handler>::const_iterator it = DataPtr->commands.constFind(command); QMap<QString, ShortcutCommands::Handler>::const_iterator it = DataPtr->commands.constFind(command);
if (it != DataPtr->commands.cend()) { if (it == DataPtr->commands.cend()) {
(*it.value())(); return false;
} }
(*it.value())();
return true;
} }
void finish() { void finish() {

View File

@ -25,8 +25,8 @@ namespace Shortcuts {
void start(); void start();
const QStringList &errors(); const QStringList &errors();
void launch(int shortcutId); bool launch(int shortcutId);
void launch(const QString &command); bool launch(const QString &command);
void finish(); void finish();

View File

@ -1011,24 +1011,43 @@ QRect Window::iconRect() const {
} }
bool Window::eventFilter(QObject *obj, QEvent *e) { bool Window::eventFilter(QObject *obj, QEvent *e) {
QEvent::Type t = e->type(); switch (e->type()) {
if (t == QEvent::MouseButtonPress || t == QEvent::KeyPress || t == QEvent::TouchBegin || t == QEvent::Wheel) { case QEvent::MouseButtonPress:
case QEvent::KeyPress:
case QEvent::TouchBegin:
case QEvent::Wheel:
psUserActionDone(); psUserActionDone();
} else if (t == QEvent::MouseMove) { break;
case QEvent::MouseMove:
if (main && main->isIdle()) { if (main && main->isIdle()) {
psUserActionDone(); psUserActionDone();
main->checkIdleFinish(); main->checkIdleFinish();
} }
} else if (t == QEvent::MouseButtonRelease) { break;
case QEvent::MouseButtonRelease:
Ui::hideStickerPreview(); Ui::hideStickerPreview();
} else if (t == QEvent::Shortcut) { break;
Shortcuts::launch(static_cast<QShortcutEvent*>(e)->shortcutId());
} case QEvent::ShortcutOverride: // handle shortcuts ourselves
if (obj == Application::instance()) { return true;
if (t == QEvent::ApplicationActivate) {
case QEvent::Shortcut:
if (Shortcuts::launch(static_cast<QShortcutEvent*>(e)->shortcutId())) {
return true;
}
break;
case QEvent::ApplicationActivate:
if (obj == Application::instance()) {
psUserActionDone(); psUserActionDone();
QTimer::singleShot(1, this, SLOT(checkHistoryActivation())); QTimer::singleShot(1, this, SLOT(checkHistoryActivation()));
} else if (t == QEvent::FileOpen) { }
break;
case QEvent::FileOpen:
if (obj == Application::instance()) {
QString url = static_cast<QFileOpenEvent*>(e)->url().toEncoded(); QString url = static_cast<QFileOpenEvent*>(e)->url().toEncoded();
if (!url.trimmed().midRef(0, 5).compare(qsl("tg://"), Qt::CaseInsensitive)) { if (!url.trimmed().midRef(0, 5).compare(qsl("tg://"), Qt::CaseInsensitive)) {
cSetStartUrl(url); cSetStartUrl(url);
@ -1039,14 +1058,23 @@ bool Window::eventFilter(QObject *obj, QEvent *e) {
} }
activate(); activate();
} }
} else if (obj == this) { break;
if (t == QEvent::WindowStateChange) {
case QEvent::WindowStateChange:
if (obj == this) {
Qt::WindowState state = (windowState() & Qt::WindowMinimized) ? Qt::WindowMinimized : ((windowState() & Qt::WindowMaximized) ? Qt::WindowMaximized : ((windowState() & Qt::WindowFullScreen) ? Qt::WindowFullScreen : Qt::WindowNoState)); Qt::WindowState state = (windowState() & Qt::WindowMinimized) ? Qt::WindowMinimized : ((windowState() & Qt::WindowMaximized) ? Qt::WindowMaximized : ((windowState() & Qt::WindowFullScreen) ? Qt::WindowFullScreen : Qt::WindowNoState));
stateChanged(state); stateChanged(state);
} else if (t == QEvent::Move || t == QEvent::Resize) { }
break;
case QEvent::Move:
case QEvent::Resize:
if (obj == this) {
psUpdatedPosition(); psUpdatedPosition();
} }
break;
} }
return PsMainWindow::eventFilter(obj, e); return PsMainWindow::eventFilter(obj, e);
} }