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

@ -588,7 +588,7 @@ buildCustomQt() {
sudo rm -rf "$EXTERNAL/qt${QT_VERSION}"
fi
cd $QT_PATH
rm -rf *
sudo rm -rf *
cd "$EXTERNAL"
git clone git://code.qt.io/qt/qt5.git qt${QT_VERSION}

View File

@ -1255,6 +1255,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
"lng_menu_insert_unicode" = "Insert Unicode control character";
"lng_menu_formatting" = "Formatting";
"lng_menu_formatting_bold" = "Bold";
"lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_monospace" = "Monospace";
//"lng_menu_formatting_link" = "Create link";
"lng_menu_formatting_clear" = "Plain text";
"lng_full_name" = "{first_name} {last_name}";

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);

View File

@ -14,6 +14,8 @@ class UserData;
namespace Ui {
class PopupMenu;
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
struct InstantReplaces {
@ -199,6 +201,8 @@ public:
int till,
const QString &tag,
const QString &edge = QString());
void toggleSelectionMarkdown(const QString &tag);
void clearSelectionMarkdown();
const QString &getLastText() const {
return _lastTextWithTags.text;
@ -340,6 +344,11 @@ private:
bool processMarkdownReplaces(const QString &appended);
bool processMarkdownReplace(const QString &tag);
void addMarkdownActions(not_null<QMenu*> menu);
void addMarkdownMenuAction(
not_null<QMenu*> menu,
not_null<QAction*> action);
bool handleMarkdownKey(QKeyEvent *e);
// We don't want accidentally detach InstantReplaces map.
// So we access it only by const reference from this method.
@ -411,6 +420,7 @@ private:
bool _correcting = false;
MimeDataHook _mimeDataHook;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
QTextCharFormat _defaultCharFormat;

View File

@ -216,7 +216,18 @@ void Menu::paintEvent(QPaintEvent *e) {
p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
p.drawTextLeft(_st.itemPadding.left(), _st.itemPadding.top(), width(), data.text);
if (data.hasSubmenu) {
_st.arrow.paint(p, width() - _st.itemPadding.right() - _st.arrow.width(), (_itemHeight - _st.arrow.height()) / 2, width());
const auto left = width() - _st.itemPadding.right() - _st.arrow.width();
const auto top = (_itemHeight - _st.arrow.height()) / 2;
if (enabled) {
_st.arrow.paint(p, left, top, width());
} else {
_st.arrow.paint(
p,
left,
top,
width(),
_st.itemFgDisabled->c);
}
} else if (!data.shortcut.isEmpty()) {
p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut);