2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-09-02 23:55:12 +00:00

Apply formatting from context menu or shortcuts.

This commit is contained in:
John Preston
2018-05-25 23:31:18 +03:00
parent c23ec41622
commit 4870558827
5 changed files with 221 additions and 5 deletions

View File

@@ -39,11 +39,36 @@ const auto kNewlineChars = QString("\r\n")
+ QChar(0xfdd1) // QTextEndOfFrame
+ QChar(QChar::ParagraphSeparator)
+ QChar(QChar::LineSeparator);
const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
bool IsNewline(QChar ch) {
return (kNewlineChars.indexOf(ch) >= 0);
}
QString GetFullSimpleTextTag(const TextWithTags &textWithTags) {
const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags;
const auto tag = (tags.size() == 1) ? tags[0] : TextWithTags::Tag();
auto from = 0;
auto till = int(text.size());
for (; from != till; ++from) {
if (!IsNewline(text[from]) && !chIsSpace(text[from])) {
break;
}
}
while (till != from) {
if (!IsNewline(text[till - 1]) && !chIsSpace(text[till - 1])) {
break;
}
--till;
}
return ((tag.offset <= from)
&& (tag.offset + tag.length >= till))
? (tag.id == kTagPre ? kTagCode : tag.id)
: QString();
}
class TagAccumulator {
public:
TagAccumulator(TextWithTags::Tags &tags) : _tags(tags) {
@@ -2227,6 +2252,8 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
}
} else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) {
e->ignore();
} else if (handleMarkdownKey(e)) {
e->accept();
} else if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) {
e->ignore();
#ifdef Q_OS_MAC
@@ -2273,13 +2300,36 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
}
}
bool InputField::handleMarkdownKey(QKeyEvent *e) {
if (!_markdownEnabled) {
return false;
}
const auto matches = [&](const QKeySequence &sequence) {
const auto searchKey = (e->modifiers() | e->key())
& ~(Qt::KeypadModifier | Qt::GroupSwitchModifier);
const auto events = QKeySequence(searchKey);
return sequence.matches(events) == QKeySequence::ExactMatch;
};
if (e == QKeySequence::Bold) {
toggleSelectionMarkdown(kTagBold);
} else if (e == QKeySequence::Italic) {
toggleSelectionMarkdown(kTagItalic);
} else if (matches(kMonospaceSequence)) {
toggleSelectionMarkdown(kTagCode);
} else if (matches(kClearFormatSequence)) {
clearSelectionMarkdown();
} else {
return false;
}
return true;
}
const InstantReplaces &InputField::instantReplaces() const {
return _mutableInstantReplaces;
}
bool InputField::processMarkdownReplaces(const QString &appended) {
if (appended.size() != 1
|| !_markdownEnabled) {
if (appended.size() != 1 || !_markdownEnabled) {
return false;
}
const auto ch = appended[0];
@@ -2374,6 +2424,10 @@ void InputField::commitInstantReplacement(
}
auto cursor = textCursor();
const auto currentTag = cursor.charFormat().property(kTagProperty);
if (currentTag == kTagPre || currentTag == kTagCode) {
return;
}
cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor);
@@ -2513,6 +2567,53 @@ bool InputField::commitMarkdownReplacement(
return true;
}
void InputField::toggleSelectionMarkdown(const QString &tag) {
_reverseMarkdownReplacement = false;
const auto cursor = textCursor();
const auto anchor = cursor.anchor();
const auto position = cursor.position();
const auto from = std::min(anchor, position);
const auto till = std::max(anchor, position);
if (from == till) {
return;
}
if (tag.isEmpty()
|| GetFullSimpleTextTag(getTextWithTagsPart(from, till)) == tag) {
RemoveDocumentTags(_st, document(), from, till);
return;
}
const auto commitTag = [&] {
if (tag != kTagCode) {
return tag;
}
const auto leftForBlock = [&] {
if (!from) {
return true;
}
const auto text = getTextWithTagsPart(from - 1, from + 1).text;
return text.isEmpty()
|| IsNewline(text[0])
|| IsNewline(text[text.size() - 1]);
}();
const auto rightForBlock = [&] {
const auto text = getTextWithTagsPart(till - 1, till + 1).text;
return text.isEmpty()
|| IsNewline(text[0])
|| IsNewline(text[text.size() - 1]);
}();
return (leftForBlock && rightForBlock) ? kTagPre : kTagCode;
}();
commitMarkdownReplacement(from, till, commitTag);
auto restorePosition = textCursor();
restorePosition.setPosition(anchor);
restorePosition.setPosition(position, QTextCursor::KeepAnchor);
setTextCursor(restorePosition);
}
void InputField::clearSelectionMarkdown() {
toggleSelectionMarkdown(QString());
}
bool InputField::revertFormatReplace() {
const auto cursor = textCursor();
const auto position = cursor.position();
@@ -2604,10 +2705,98 @@ bool InputField::revertFormatReplace() {
void InputField::contextMenuEventInner(QContextMenuEvent *e) {
if (const auto menu = _inner->createStandardContextMenu()) {
(new Ui::PopupMenu(nullptr, menu))->popup(e->globalPos());
addMarkdownActions(menu);
_contextMenu = base::make_unique_q<Ui::PopupMenu>(nullptr, menu);
_contextMenu->popup(e->globalPos());
}
}
void InputField::addMarkdownActions(not_null<QMenu*> menu) {
if (!_markdownEnabled) {
return;
}
const auto formatting = new QAction(lang(lng_menu_formatting), menu);
addMarkdownMenuAction(menu, formatting);
const auto submenu = new QMenu(menu);
formatting->setMenu(submenu);
const auto cursor = textCursor();
const auto from = std::min(cursor.anchor(), cursor.position());
const auto till = std::max(cursor.anchor(), cursor.position());
const auto textWithTags = getTextWithTagsPart(from, till);
const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags;
formatting->setDisabled(text.isEmpty());
if (text.isEmpty()) {
return;
}
const auto hasTags = !textWithTags.tags.isEmpty();
const auto fullTag = GetFullSimpleTextTag(textWithTags);
const auto add = [&](
LangKey key,
QKeySequence sequence,
bool disabled,
auto callback) {
const auto add = sequence.isEmpty()
? QString()
: QChar('\t') + sequence.toString(QKeySequence::NativeText);
const auto action = new QAction(lang(key) + add, submenu);
connect(action, &QAction::triggered, this, callback);
action->setDisabled(disabled);
submenu->addAction(action);
};
const auto addtag = [&](
LangKey key,
QKeySequence sequence,
const QString &tag) {
const auto disabled = (fullTag == tag)
|| (fullTag == kTagPre && tag == kTagCode);
add(key, sequence, (fullTag == tag), [=] {
toggleSelectionMarkdown(tag);
});
};
//const auto addlink = [&] {
// add(lng_menu_formatting_link, QKeySequence("ctrl+k"), false, [=] {
// createMarkdownLink();
// });
//};
const auto addclear = [&] {
add(lng_menu_formatting_clear, kClearFormatSequence, !hasTags, [=] {
clearSelectionMarkdown();
});
};
addtag(lng_menu_formatting_bold, QKeySequence::Bold, kTagBold);
addtag(lng_menu_formatting_italic, QKeySequence::Italic, kTagItalic);
addtag(lng_menu_formatting_monospace, kMonospaceSequence, kTagCode);
//submenu->addSeparator();
//addlink();
submenu->addSeparator();
addclear();
}
void InputField::addMarkdownMenuAction(
not_null<QMenu*> menu,
not_null<QAction*> action) {
const auto actions = menu->actions();
const auto before = [&] {
auto seenAfter = false;
for (const auto action : actions) {
if (seenAfter) {
return action;
} else if (action->objectName() == qstr("edit-delete")) {
seenAfter = true;
}
}
return (QAction*)nullptr;
}();
menu->insertSeparator(before);
menu->insertAction(before, action);
}
void InputField::dropEventInner(QDropEvent *e) {
_inDrop = true;
_inner->QTextEdit::dropEvent(e);