Saving FlatTextarea tags to drafts, applying them in setText.

Now instead of plain text a TextWithTags struct is used almost
everywhere. Started writing and reading serialized tags to drafts
from 9048, switched version to 0.9.48 for testing.
This commit is contained in:
John Preston 2016-05-05 19:04:17 +03:00
parent 5a47d8e29b
commit 463450e607
18 changed files with 324 additions and 205 deletions

View file

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,47,0
PRODUCTVERSION 0,9,47,0
FILEVERSION 0,9,48,0
PRODUCTVERSION 0,9,48,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "0.9.47.0"
VALUE "FileVersion", "0.9.48.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.47.0"
VALUE "ProductVersion", "0.9.48.0"
END
END
BLOCK "VarFileInfo"

View file

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,47,0
PRODUCTVERSION 0,9,47,0
FILEVERSION 0,9,48,0
PRODUCTVERSION 0,9,48,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater"
VALUE "FileVersion", "0.9.47.0"
VALUE "FileVersion", "0.9.48.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.47.0"
VALUE "ProductVersion", "0.9.48.0"
END
END
BLOCK "VarFileInfo"

View file

@ -166,6 +166,16 @@ template <typename T, size_t N> char(&ArraySizeHelper(T(&array)[N]))[N];
#define qsl(s) QStringLiteral(s)
#define qstr(s) QLatin1String(s, sizeof(s) - 1)
// For QFlags<> declared in private section of a class we need to declare
// operators from Q_DECLARE_OPERATORS_FOR_FLAGS as friend functions.
#define Q_DECLARE_FRIEND_INCOMPATIBLE_FLAGS(Flags) \
friend QIncompatibleFlag operator|(Flags::enum_type f1, int f2) Q_DECL_NOTHROW;
#define Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(Flags) \
friend QFlags<Flags::enum_type> operator|(Flags::enum_type f1, Flags::enum_type f2) Q_DECL_NOTHROW; \
friend QFlags<Flags::enum_type> operator|(Flags::enum_type f1, QFlags<Flags::enum_type> f2) Q_DECL_NOTHROW; \
Q_DECLARE_FRIEND_INCOMPATIBLE_FLAGS(Flags)
// using for_const instead of plain range-based for loop to ensure usage of const_iterator
// it is important for the copy-on-write Qt containers
// if you have "QVector<T*> v" then "for (T * const p : v)" will still call QVector::detach(),

View file

@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 9047;
constexpr str_const AppVersionStr = "0.9.47";
constexpr int AppVersion = 9048;
constexpr str_const AppVersionStr = "0.9.48";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View file

@ -151,22 +151,23 @@ struct SendAction {
int32 progress;
};
using TextWithTags = FlatTextarea::TextWithTags;
struct HistoryDraft {
HistoryDraft() : msgId(0), previewCancelled(false) {
}
HistoryDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled)
: text(text)
HistoryDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled)
: textWithTags(textWithTags)
, msgId(msgId)
, cursor(cursor)
, previewCancelled(previewCancelled) {
}
HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled)
: text(field.getLastText())
: textWithTags(field.getTextWithTags())
, msgId(msgId)
, cursor(field)
, previewCancelled(previewCancelled) {
}
QString text;
TextWithTags textWithTags;
MsgId msgId; // replyToId for message draft, editMsgId for edit draft
MessageCursor cursor;
bool previewCancelled;
@ -176,8 +177,8 @@ struct HistoryEditDraft : public HistoryDraft {
: HistoryDraft()
, saveRequest(0) {
}
HistoryEditDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(text, msgId, cursor, previewCancelled)
HistoryEditDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(textWithTags, msgId, cursor, previewCancelled)
, saveRequest(saveRequest) {
}
HistoryEditDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequest = 0)
@ -373,7 +374,7 @@ public:
}
void takeMsgDraft(History *from) {
if (auto &draft = from->_msgDraft) {
if (!draft->text.isEmpty() && !_msgDraft) {
if (!draft->textWithTags.text.isEmpty() && !_msgDraft) {
_msgDraft = std_::move(draft);
_msgDraft->msgId = 0; // edit and reply to drafts can't migrate
}

View file

@ -2053,8 +2053,8 @@ MessageField::MessageField(HistoryWidget *history, const style::flatTextarea &st
}
bool MessageField::hasSendText() const {
const QString &text(getLastText());
for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
auto &text(getTextWithTags().text);
for (auto *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
ushort code = ch->unicode();
if (code != ' ' && code != '\n' && code != '\r' && !chReplacedBySpace(code)) {
return true;
@ -2735,7 +2735,7 @@ QPoint SilentToggle::tooltipPos() const {
return QCursor::pos();
}
EntitiesInText entitiesFromFieldTags(const FlatTextarea::TagList &tags) {
EntitiesInText entitiesFromTextTags(const FlatTextarea::TagList &tags) {
EntitiesInText result;
if (tags.isEmpty()) {
return result;
@ -2754,6 +2754,24 @@ EntitiesInText entitiesFromFieldTags(const FlatTextarea::TagList &tags) {
return result;
}
TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities) {
TextWithTags::Tags result;
if (entities.isEmpty()) {
return result;
}
result.reserve(entities.size());
for_const (auto &entity, entities) {
if (entity.type() == EntityInTextMentionName) {
auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
if (match.hasMatch()) {
result.push_back({ entity.offset(), entity.length(), qstr("mention://user.") + entity.data() });
}
}
}
return result;
}
namespace {
// For mention tags save and validate userId, ignore tags for different userId.
@ -2974,7 +2992,7 @@ void HistoryWidget::onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::
// Send bot command at once, if it was not inserted by pressing Tab.
if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
App::sendBotCommand(_peer, nullptr, str);
setFieldText(_field.getLastText().mid(_field.textCursor().position()));
setFieldText(_field.getTextWithTagsPart(_field.textCursor().position()));
} else {
_field.insertTag(str);
}
@ -3022,15 +3040,16 @@ void HistoryWidget::updateInlineBotQuery() {
void HistoryWidget::updateStickersByEmoji() {
int32 len = 0;
if (EmojiPtr emoji = emojiFromText(_field.getLastText(), &len)) {
if (_field.getLastText().size() <= len) {
_fieldAutocomplete->showStickers(emoji);
} else {
auto &text = _field.getTextWithTags().text;
if (EmojiPtr emoji = emojiFromText(text, &len)) {
if (text.size() > len) {
len = 0;
} else {
_fieldAutocomplete->showStickers(emoji);
}
}
if (!len) {
_fieldAutocomplete->showStickers(EmojiPtr(0));
_fieldAutocomplete->showStickers(nullptr);
}
}
@ -3039,7 +3058,7 @@ void HistoryWidget::onTextChange() {
updateStickersByEmoji();
if (_peer && (!_peer->isChannel() || _peer->isMegagroup() || !_peer->asChannel()->canPublish() || (!_peer->asChannel()->isBroadcast() && !_broadcast.checked()))) {
if (!_inlineBot && !_editMsgId && (_textUpdateEventsFlags & TextUpdateEventsSendTyping)) {
if (!_inlineBot && !_editMsgId && (_textUpdateEvents.testFlag(TextUpdateEvent::SendTyping))) {
updateSendAction(_history, SendActionTyping);
}
}
@ -3065,13 +3084,13 @@ void HistoryWidget::onTextChange() {
update();
}
if (!_peer || !(_textUpdateEventsFlags & TextUpdateEventsSaveDraft)) return;
if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return;
_saveDraftText = true;
onDraftSave(true);
}
void HistoryWidget::onDraftSaveDelayed() {
if (!_peer || !(_textUpdateEventsFlags & TextUpdateEventsSaveDraft)) return;
if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return;
if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) {
if (!Local::hasDraftCursors(_peer->id)) {
return;
@ -3106,17 +3125,17 @@ void HistoryWidget::writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **edit
Local::MessageDraft localMsgDraft, localEditDraft;
if (msgDraft) {
if (*msgDraft) {
localMsgDraft = Local::MessageDraft((*msgDraft)->msgId, (*msgDraft)->text, (*msgDraft)->previewCancelled);
localMsgDraft = Local::MessageDraft((*msgDraft)->msgId, (*msgDraft)->textWithTags, (*msgDraft)->previewCancelled);
}
} else {
localMsgDraft = Local::MessageDraft(_replyToId, _field.getLastText(), _previewCancelled);
localMsgDraft = Local::MessageDraft(_replyToId, _field.getTextWithTags(), _previewCancelled);
}
if (editDraft) {
if (*editDraft) {
localEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->text, (*editDraft)->previewCancelled);
localEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled);
}
} else if (_editMsgId) {
localEditDraft = Local::MessageDraft(_editMsgId, _field.getLastText(), _previewCancelled);
localEditDraft = Local::MessageDraft(_editMsgId, _field.getTextWithTags(), _previewCancelled);
}
Local::writeDrafts(_peer->id, localMsgDraft, localEditDraft);
if (_migrated) {
@ -3152,11 +3171,11 @@ void HistoryWidget::writeDrafts(History *history) {
Local::MessageDraft localMsgDraft, localEditDraft;
MessageCursor msgCursor, editCursor;
if (auto msgDraft = history->msgDraft()) {
localMsgDraft = Local::MessageDraft(msgDraft->msgId, msgDraft->text, msgDraft->previewCancelled);
localMsgDraft = Local::MessageDraft(msgDraft->msgId, msgDraft->textWithTags, msgDraft->previewCancelled);
msgCursor = msgDraft->cursor;
}
if (auto editDraft = history->editDraft()) {
localEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->text, editDraft->previewCancelled);
localEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled);
editCursor = editDraft->cursor;
}
Local::writeDrafts(history->peer->id, localMsgDraft, localEditDraft);
@ -3331,8 +3350,9 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query) {
}
bot->botInfo->inlineReturnPeerId = 0;
History *h = App::history(toPeerId);
auto text = '@' + bot->username + ' ' + query;
h->setMsgDraft(std_::make_unique<HistoryDraft>(text, 0, MessageCursor(text.size(), text.size(), QFIXED_MAX), false));
TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
if (h == _history) {
applyDraft();
} else {
@ -3633,11 +3653,11 @@ void HistoryWidget::applyDraft(bool parseLinks) {
return;
}
_textUpdateEventsFlags = 0;
setFieldText(draft->text);
_textUpdateEvents = 0;
setFieldText(draft->textWithTags);
_field.setFocus();
draft->cursor.applyTo(_field);
_textUpdateEventsFlags = TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping;
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
_previewCancelled = draft->previewCancelled;
if (auto editDraft = _history->editDraft()) {
_editMsgId = editDraft->msgId;
@ -3730,7 +3750,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
if (_editMsgId) {
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
} else {
if (_replyToId || !_field.getLastText().isEmpty()) {
if (_replyToId || !_field.isEmpty()) {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearMsgDraft();
@ -4738,11 +4758,11 @@ void HistoryWidget::preloadHistoryIfNeeded() {
}
void HistoryWidget::onInlineBotCancel() {
QString text = _field.getLastText();
if (text.size() > _inlineBotUsername.size() + 2) {
setFieldText('@' + _inlineBotUsername + ' ', TextUpdateEventsSaveDraft, false);
auto &textWithTags = _field.getTextWithTags();
if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
} else {
clearFieldText(TextUpdateEventsSaveDraft, false);
clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
}
}
@ -4783,11 +4803,10 @@ void HistoryWidget::saveEditMsg() {
WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
auto fieldText = _field.getLastText();
auto fieldTags = _field.getLastTags();
auto &textWithTags = _field.getTextWithTags();
auto prepareFlags = itemTextOptions(_history, App::self()).flags;
EntitiesInText sendingEntities, leftEntities = entitiesFromFieldTags(fieldTags);
QString sendingText, leftText = prepareTextWithEntities(fieldText, prepareFlags, &leftEntities);
EntitiesInText sendingEntities, leftEntities = entitiesFromTextTags(textWithTags.tags);
QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
if (!textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
_field.selectAll();
@ -4865,8 +4884,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
MainWidget::MessageToSend message;
message.history = _history;
message.text = _field.getLastText();
message.entities = _field.getLastTags();
message.textWithTags = _field.getTextWithTags();
message.replyTo = replyTo;
message.broadcast = _broadcast.checked();
message.silent = _silent.checked();
@ -5379,7 +5397,7 @@ void HistoryWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString
MainWidget::MessageToSend message;
message.history = _history;
message.text = toSend;
message.textWithTags = { toSend, TextWithTags::Tags() };
message.replyTo = replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? replyTo : -1) : 0;
message.broadcast = false;
message.silent = false;
@ -5460,8 +5478,9 @@ bool HistoryWidget::botCallbackFail(BotCallbackInfo info, const RPCError &error,
bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) {
if (!_history) return false;
bool insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@');
QString toInsert = cmd;
if (!toInsert.isEmpty() && toInsert.at(0) != '@') {
if (!toInsert.isEmpty() && !insertingInlineBot) {
PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? App::hoveredLinkItem()->fromOriginal() : 0);
if (!bot->isUser() || !bot->asUser()->botInfo) bot = 0;
QString username = bot ? bot->asUser()->username : QString();
@ -5472,28 +5491,33 @@ bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) {
}
toInsert += ' ';
if (toInsert.at(0) != '@') {
QString text = _field.getLastText();
if (!insertingInlineBot) {
auto &textWithTags = _field.getTextWithTags();
if (specialGif) {
if (text.trimmed() == '@' + cInlineGifBotUsername() && text.at(0) == '@') {
clearFieldText(TextUpdateEventsSaveDraft, false);
if (textWithTags.text.trimmed() == '@' + cInlineGifBotUsername() && textWithTags.text.at(0) == '@') {
clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
}
} else {
QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(text);
TextWithTags textWithTagsToSet;
QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text);
if (m.hasMatch()) {
text = toInsert + text.mid(m.capturedLength());
textWithTagsToSet = _field.getTextWithTagsPart(m.capturedLength());
} else {
text = toInsert + text;
textWithTagsToSet = textWithTags;
}
_field.setTextFast(text);
textWithTagsToSet.text = toInsert + textWithTagsToSet.text;
for (auto &tag : textWithTagsToSet.tags) {
tag.offset += toInsert.size();
}
_field.setTextWithTags(textWithTagsToSet);
QTextCursor cur(_field.textCursor());
cur.movePosition(QTextCursor::End);
_field.setTextCursor(cur);
}
} else {
if (!specialGif || _field.getLastText().isEmpty()) {
setFieldText(toInsert, TextUpdateEventsSaveDraft, false);
if (!specialGif || _field.isEmpty()) {
setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
_field.setFocus();
return true;
}
@ -5787,7 +5811,7 @@ void HistoryWidget::onKbToggle(bool manual) {
}
void HistoryWidget::onCmdStart() {
setFieldText(qsl("/"));
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, FlatTextarea::AddToUndoHistory);
}
void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) {
@ -6998,7 +7022,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
} else if (e->key() == Qt::Key_Up) {
if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
if (_history && _history->lastSentMsg && _history->lastSentMsg->canEdit(::date(unixtime()))) {
if (_field.getLastText().isEmpty() && !_editMsgId && !_replyToId) {
if (_field.isEmpty() && !_editMsgId && !_replyToId) {
App::contextItem(_history->lastSentMsg);
onEditMessage();
}
@ -7308,11 +7332,11 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption)
_field.setFocus();
}
void HistoryWidget::setFieldText(const QString &text, int32 textUpdateEventsFlags, bool clearUndoHistory) {
_textUpdateEventsFlags = textUpdateEventsFlags;
_field.setTextFast(text, clearUndoHistory);
void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, FlatTextarea::UndoHistoryAction undoHistoryAction) {
_textUpdateEvents = events;
_field.setTextWithTags(textWithTags, undoHistoryAction);
_field.moveCursor(QTextCursor::End);
_textUpdateEventsFlags = TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping;
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
_previewCancelled = false;
_previewData = nullptr;
@ -7351,7 +7375,7 @@ void HistoryWidget::onReplyToMessage() {
if (auto msgDraft = _history->msgDraft()) {
msgDraft->msgId = to->id;
} else {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(QString(), to->id, MessageCursor(), false));
_history->setMsgDraft(std_::make_unique<HistoryDraft>(TextWithTags(), to->id, MessageCursor(), false));
}
} else {
_replyEditMsg = to;
@ -7384,14 +7408,17 @@ void HistoryWidget::onEditMessage() {
} else {
delete box;
if (_replyToId || !_field.getLastText().isEmpty()) {
if (_replyToId || !_field.isEmpty()) {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearMsgDraft();
}
QString text(textApplyEntities(to->originalText(), to->originalEntities()));
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(text, to->id, MessageCursor(text.size(), text.size(), QFIXED_MAX), false));
auto originalText = to->originalText();
auto originalEntities = to->originalEntities();
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) };
MessageCursor cursor = { original.text.size(), original.text.size(), QFIXED_MAX };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(original, to->id, cursor, false));
applyDraft(false);
_previewData = 0;
@ -7510,7 +7537,7 @@ void HistoryWidget::cancelReply(bool lastKeyboardUsed) {
update();
} else if (auto msgDraft = (_history ? _history->msgDraft() : nullptr)) {
if (msgDraft->msgId) {
if (msgDraft->text.isEmpty()) {
if (msgDraft->textWithTags.text.isEmpty()) {
_history->clearMsgDraft();
} else {
msgDraft->msgId = 0;
@ -7533,7 +7560,7 @@ void HistoryWidget::cancelEdit() {
if (!_editMsgId) return;
_editMsgId = 0;
_replyEditMsg = 0;
_replyEditMsg = nullptr;
_history->clearEditDraft();
applyDraft();
@ -7546,21 +7573,21 @@ void HistoryWidget::cancelEdit() {
_saveDraftStart = getms();
onDraftSave();
mouseMoveEvent(0);
mouseMoveEvent(nullptr);
if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) {
_fieldBarCancel.hide();
updateMouseTracking();
}
int32 old = _textUpdateEventsFlags;
_textUpdateEventsFlags = 0;
auto old = _textUpdateEvents;
_textUpdateEvents = 0;
onTextChange();
_textUpdateEventsFlags = old;
_textUpdateEvents = old;
updateBotKeyboard();
updateFieldPlaceholder();
resizeEvent(0);
resizeEvent(nullptr);
update();
}
@ -7728,7 +7755,10 @@ void HistoryWidget::onCancel() {
if (_inlineBotCancel) {
onInlineBotCancel();
} else if (_editMsgId) {
if (_replyEditMsg && textApplyEntities(_replyEditMsg->originalText(), _replyEditMsg->originalEntities()) != _field.getLastText()) {
auto originalText = _replyEditMsg ? _replyEditMsg->originalText() : QString();
auto originalEntities = _replyEditMsg ? _replyEditMsg->originalEntities() : EntitiesInText();
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) };
if (_replyEditMsg && original != _field.getTextWithTags()) {
auto box = new ConfirmBox(lang(lng_cancel_edit_post_sure), lang(lng_cancel_edit_post_yes), st::defaultBoxButton, lang(lng_cancel_edit_post_no));
connect(box, SIGNAL(confirmed()), this, SLOT(onFieldBarCancel()));
Ui::showLayer(box);

View file

@ -486,12 +486,8 @@ public:
};
EntitiesInText entitiesFromFieldTags(const FlatTextarea::TagList &tags);
enum TextUpdateEventsFlags {
TextUpdateEventsSaveDraft = 0x01,
TextUpdateEventsSendTyping = 0x02,
};
EntitiesInText entitiesFromTextTags(const TextWithTags::Tags &tags);
TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities);
class HistoryWidget : public TWidget, public RPCSender {
Q_OBJECT
@ -954,11 +950,18 @@ private:
void savedGifsGot(const MTPmessages_SavedGifs &gifs);
bool savedGifsFailed(const RPCError &error);
enum class TextUpdateEvent {
SaveDraft = 0x01,
SendTyping = 0x02,
};
Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent);
Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents);
void writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **editDraft);
void writeDrafts(History *history);
void setFieldText(const QString &text, int32 textUpdateEventsFlags = 0, bool clearUndoHistory = true);
void clearFieldText(int32 textUpdateEventsFlags = 0, bool clearUndoHistory = true) {
setFieldText(QString());
void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory);
void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) {
setFieldText(TextWithTags(), events, undoHistoryAction);
}
QStringList getMediasFromMime(const QMimeData *d);
@ -1062,7 +1065,7 @@ private:
int32 _selCount; // < 0 - text selected, focus list, not _field
TaskQueue _fileLoader;
int32 _textUpdateEventsFlags = (TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping);
TextUpdateEvents _textUpdateEvents = (TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
int64 _serviceImageCacheSize = 0;
QString _confirmSource;
@ -1095,3 +1098,4 @@ private:
};
Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryWidget::TextUpdateEvents)

View file

@ -122,14 +122,6 @@ namespace {
return true;
}
uint32 _dateTimeSize() {
return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
}
uint32 _bytearraySize(const QByteArray &arr) {
return sizeof(quint32) + arr.size();
}
QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted;
MTP::AuthKey _oldKey, _settingsKey, _passKey, _localKey;
@ -628,18 +620,18 @@ namespace {
size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name());
if (AppVersion > 9013) {
// bookmark
size += _bytearraySize(i.value().bookmark());
size += Serialize::bytearraySize(i.value().bookmark());
}
// date + size
size += _dateTimeSize() + sizeof(quint32);
size += Serialize::dateTimeSize() + sizeof(quint32);
}
//end mark
size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString());
if (AppVersion > 9013) {
size += _bytearraySize(QByteArray());
size += Serialize::bytearraySize(QByteArray());
}
size += _dateTimeSize() + sizeof(quint32);
size += Serialize::dateTimeSize() + sizeof(quint32);
size += sizeof(quint32); // aliases count
for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) {
@ -1530,7 +1522,7 @@ namespace {
}
uint32 size = 16 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + _bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark());
size += sizeof(quint32) + Serialize::stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + Serialize::bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark());
size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64));
size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
@ -2281,8 +2273,8 @@ namespace Local {
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft) {
if (!_working()) return;
if (msgDraft.msgId <= 0 && msgDraft.text.isEmpty() && editDraft.msgId <= 0) {
DraftsMap::iterator i = _draftsMap.find(peer);
if (msgDraft.msgId <= 0 && msgDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) {
auto i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
_draftsMap.erase(i);
@ -2292,17 +2284,26 @@ namespace Local {
_draftsNotReadMap.remove(peer);
} else {
DraftsMap::const_iterator i = _draftsMap.constFind(peer);
auto i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
EncryptedDescriptor data(sizeof(quint64) + Serialize::stringSize(msgDraft.text) + 2 * sizeof(qint32) + Serialize::stringSize(editDraft.text) + 2 * sizeof(qint32));
auto msgTags = FlatTextarea::serializeTagsList(msgDraft.textWithTags.tags);
auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags);
int size = sizeof(quint64);
size += Serialize::stringSize(msgDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32);
size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32);
EncryptedDescriptor data(size);
data.stream << quint64(peer);
data.stream << msgDraft.text << qint32(msgDraft.msgId) << qint32(msgDraft.previewCancelled ? 1 : 0);
data.stream << editDraft.text << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
data.stream << msgDraft.textWithTags.text << msgTags;
data.stream << qint32(msgDraft.msgId) << qint32(msgDraft.previewCancelled ? 1 : 0);
data.stream << editDraft.textWithTags.text << editTags;
data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
@ -2370,15 +2371,23 @@ namespace Local {
}
quint64 draftPeer = 0;
QString msgText, editText;
TextWithTags msgData, editData;
QByteArray msgTagsSerialized, editTagsSerialized;
qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0;
draft.stream >> draftPeer >> msgText;
draft.stream >> draftPeer >> msgData.text;
if (draft.version >= 9048) {
draft.stream >> msgTagsSerialized;
}
if (draft.version >= 7021) {
draft.stream >> msgReplyTo;
if (draft.version >= 8001) {
draft.stream >> msgPreviewCancelled;
if (!draft.stream.atEnd()) {
draft.stream >> editText >> editMsgId >> editPreviewCancelled;
draft.stream >> editData.text;
if (draft.version >= 9048) {
draft.stream >> editTagsSerialized;
}
draft.stream >> editMsgId >> editPreviewCancelled;
}
}
}
@ -2389,18 +2398,21 @@ namespace Local {
return;
}
msgData.tags = FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size());
editData.tags = FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size());
MessageCursor msgCursor, editCursor;
_readDraftCursors(peer, msgCursor, editCursor);
if (msgText.isEmpty() && !msgReplyTo) {
if (msgData.text.isEmpty() && !msgReplyTo) {
h->clearMsgDraft();
} else {
h->setMsgDraft(std_::make_unique<HistoryDraft>(msgText, msgReplyTo, msgCursor, msgPreviewCancelled));
h->setMsgDraft(std_::make_unique<HistoryDraft>(msgData, msgReplyTo, msgCursor, msgPreviewCancelled));
}
if (!editMsgId) {
h->clearEditDraft();
} else {
h->setEditDraft(std_::make_unique<HistoryEditDraft>(editText, editMsgId, editCursor, editPreviewCancelled));
h->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, editMsgId, editCursor, editPreviewCancelled));
}
}
@ -3019,7 +3031,7 @@ namespace Local {
} else {
int32 setsCount = 0;
QByteArray hashToWrite;
quint32 size = sizeof(quint32) + _bytearraySize(hashToWrite);
quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite);
for (auto i = sets.cbegin(); i != sets.cend(); ++i) {
bool notLoaded = (i->flags & MTPDstickerSet_ClientFlag::f_not_loaded);
if (notLoaded) {
@ -3682,7 +3694,7 @@ namespace Local {
}
quint32 size = sizeof(quint32);
for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) {
size += _peerSize(i.key()) + _dateTimeSize();
size += _peerSize(i.key()) + Serialize::dateTimeSize();
}
EncryptedDescriptor data(size);

View file

@ -105,11 +105,15 @@ namespace Local {
int32 oldSettingsVersion();
using TextWithTags = FlatTextarea::TextWithTags;
struct MessageDraft {
MessageDraft(MsgId msgId = 0, QString text = QString(), bool previewCancelled = false) : msgId(msgId), text(text), previewCancelled(previewCancelled) {
MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false)
: msgId(msgId)
, textWithTags(textWithTags)
, previewCancelled(previewCancelled) {
}
MsgId msgId;
QString text;
TextWithTags textWithTags;
bool previewCancelled;
};
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft);

View file

@ -159,7 +159,9 @@ bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QStrin
return false;
}
History *h = App::history(peer);
h->setMsgDraft(std_::make_unique<HistoryDraft>(url + '\n' + text, 0, MessageCursor(url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX), false));
TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() };
MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) {
@ -177,7 +179,9 @@ bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQ
return false;
}
History *h = App::history(peer);
h->setMsgDraft(std_::make_unique<HistoryDraft>(botAndQuery, 0, MessageCursor(botAndQuery.size(), botAndQuery.size(), QFIXED_MAX), false));
TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() };
MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) {
@ -1086,7 +1090,7 @@ void executeParsedCommand(const QString &command) {
void MainWidget::sendMessage(const MessageToSend &message) {
auto history = message.history;
const auto &text = message.text;
const auto &textWithTags = message.textWithTags;
readServerHistory(history, false);
_history->fastShowAtEnd(history);
@ -1095,13 +1099,13 @@ void MainWidget::sendMessage(const MessageToSend &message) {
return;
}
saveRecentHashtags(text);
saveRecentHashtags(textWithTags.text);
EntitiesInText sendingEntities, leftEntities = entitiesFromFieldTags(message.entities);
EntitiesInText sendingEntities, leftEntities = entitiesFromTextTags(textWithTags.tags);
auto prepareFlags = itemTextOptions(history, App::self()).flags;
QString sendingText, leftText = prepareTextWithEntities(text, prepareFlags, &leftEntities);
QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
QString command = parseCommandFromMessage(history, text);
QString command = parseCommandFromMessage(history, textWithTags.text);
HistoryItem *lastMessage = nullptr;
MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : 0;

View file

@ -283,8 +283,7 @@ public:
struct MessageToSend {
History *history = nullptr;
QString text;
FlatTextarea::TagList entities;
TextWithTags textWithTags;
MsgId replyTo = 0;
bool broadcast = false;
bool silent = false;

View file

@ -86,7 +86,7 @@ void MacPrivate::notifyReplied(unsigned long long peer, int msgid, const char *s
MainWidget::MessageToSend message;
message.history = history;
message.text = QString::fromUtf8(str);
message.textWithTags = { QString::fromUtf8(str), TextWithTags::Tags() };
message.replyTo = (msgid > 0 && !history->peer->isUser()) ? msgid : 0;
message.broadcast = false;
message.silent = false;

View file

@ -23,10 +23,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Serialize {
int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) {
stream << qint32(loc.width()) << qint32(loc.height());
stream << qint32(loc.dc()) << quint64(loc.volume()) << qint32(loc.local()) << quint64(loc.secret());

View file

@ -24,7 +24,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Serialize {
int stringSize(const QString &str);
inline int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}
inline int bytearraySize(const QByteArray &arr) {
return sizeof(quint32) + arr.size();
}
inline int dateTimeSize() {
return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
}
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc);
StorageImageLocation readStorageImageLocation(QDataStream &stream);

View file

@ -23,9 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
namespace {
QByteArray serializeTagsList(const FlatTextarea::TagList &tags) {
QByteArray FlatTextarea::serializeTagsList(const TagList &tags) {
if (tags.isEmpty()) {
return QByteArray();
}
@ -44,8 +42,11 @@ QByteArray serializeTagsList(const FlatTextarea::TagList &tags) {
return tagsSerialized;
}
FlatTextarea::TagList deserializeTagsList(QByteArray data, int textSize) {
FlatTextarea::TagList result;
FlatTextarea::TagList FlatTextarea::deserializeTagsList(QByteArray data, int textLength) {
TagList result;
if (data.isEmpty()) {
return result;
}
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
@ -58,7 +59,7 @@ FlatTextarea::TagList deserializeTagsList(QByteArray data, int textSize) {
if (stream.status() != QDataStream::Ok) {
return result;
}
if (tagCount <= 0 || tagCount > textSize) {
if (tagCount <= 0 || tagCount > textLength) {
return result;
}
@ -69,7 +70,7 @@ FlatTextarea::TagList deserializeTagsList(QByteArray data, int textSize) {
if (stream.status() != QDataStream::Ok) {
return result;
}
if (offset < 0 || length <= 0 || offset + length > textSize) {
if (offset < 0 || length <= 0 || offset + length > textLength) {
return result;
}
result.push_back({ offset, length, id });
@ -77,12 +78,12 @@ FlatTextarea::TagList deserializeTagsList(QByteArray data, int textSize) {
return result;
}
constexpr str_const TagsMimeType = "application/x-td-field-tags";
QString FlatTextarea::tagsMimeType() {
return qsl("application/x-td-field-tags");
}
} // namespace
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent)
, _oldtext(v)
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v, const TagList &tags) : QTextEdit(parent)
, _lastTextWithTags { v, tags }
, _phVisible(!v.length())
, a_phLeft(_phVisible ? 0 : st.phShift)
, a_phAlpha(_phVisible ? 1 : 0)
@ -126,22 +127,41 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const
connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
if (!v.isEmpty()) {
setTextFast(v);
if (!_lastTextWithTags.text.isEmpty()) {
setTextWithTags(_lastTextWithTags, ClearUndoHistory);
}
}
void FlatTextarea::setTextFast(const QString &text, bool clearUndoHistory) {
if (clearUndoHistory) {
setPlainText(text);
FlatTextarea::TextWithTags FlatTextarea::getTextWithTagsPart(int start, int end) {
TextWithTags result;
result.text = getTextPart(start, end, &result.tags);
return result;
}
void FlatTextarea::setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction) {
_insertedTags = textWithTags.tags;
_insertedTagsAreFromMime = false;
_realInsertPosition = 0;
_realCharsAdded = textWithTags.text.size();
auto doc = document();
auto cursor = QTextCursor(doc->docHandle(), 0);
if (undoHistoryAction == ClearUndoHistory) {
doc->setUndoRedoEnabled(false);
cursor.beginEditBlock();
} else if (undoHistoryAction == MergeWithUndoHistory) {
cursor.joinPreviousEditBlock();
} else {
QTextCursor c(document()->docHandle(), 0);
c.joinPreviousEditBlock();
c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
c.insertText(text);
c.movePosition(QTextCursor::End);
c.endEditBlock();
cursor.beginEditBlock();
}
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.insertText(textWithTags.text);
cursor.movePosition(QTextCursor::End);
cursor.endEditBlock();
if (undoHistoryAction == ClearUndoHistory) {
doc->setUndoRedoEnabled(true);
}
_insertedTags.clear();
_realInsertPosition = -1;
finishPlaceholder();
}
@ -266,7 +286,8 @@ void FlatTextarea::paintEvent(QPaintEvent *e) {
p.setFont(_st.font);
p.setPen(a_phColor.current());
if (_st.phAlign == style::al_topleft && _phAfter > 0) {
p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + _st.font->width(getLastText().mid(0, _phAfter)), _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph);
int skipWidth = _st.font->width(getTextWithTags().text.mid(0, _phAfter));
p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph);
} else {
QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom());
p.drawText(phRect, _ph, QTextOption(_st.phAlign));
@ -317,7 +338,7 @@ QString FlatTextarea::getInlineBotQuery(UserData **outInlineBot, QString *outInl
t_assert(outInlineBot != nullptr);
t_assert(outInlineBotUsername != nullptr);
const QString &text(getLastText());
auto &text = getTextWithTags().text;
int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size();
if (size > 2 && text.at(0) == '@' && text.at(1).isLetter()) {
@ -474,10 +495,8 @@ void FlatTextarea::insertTag(const QString &text, QString tagId) {
cursor.insertText(text + ' ', format);
} else {
_insertedTags.clear();
if (_tagMimeProcessor) {
tagId = _tagMimeProcessor->mimeTagFromTag(tagId);
}
_insertedTags.push_back({ 0, text.size(), tagId });
_insertedTagsAreFromMime = false;
cursor.insertText(text + ' ');
_insertedTags.clear();
}
@ -601,7 +620,7 @@ private:
} // namespace
QString FlatTextarea::getText(int start, int end, TagList *outTagsList, bool *outTagsChanged) const {
QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged) const {
if (end >= 0 && end <= start) return QString();
if (start < 0) start = 0;
@ -632,7 +651,7 @@ QString FlatTextarea::getText(int start, int end, TagList *outTagsList, bool *ou
int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length());
if (!full) {
tillFragmentEnd = (e <= end);
if (p == end && outTagsList) {
if (p == end) {
tagAccumulator.feed(fragment.charFormat().anchorName(), result.size());
}
if (p >= end) {
@ -642,7 +661,7 @@ QString FlatTextarea::getText(int start, int end, TagList *outTagsList, bool *ou
continue;
}
}
if (outTagsList && (full || p >= start)) {
if (full || p >= start) {
tagAccumulator.feed(fragment.charFormat().anchorName(), result.size());
}
@ -689,11 +708,9 @@ QString FlatTextarea::getText(int start, int end, TagList *outTagsList, bool *ou
}
result.chop(1);
if (outTagsList) {
if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size());
if (outTagsChanged) {
*outTagsChanged = tagAccumulator.changed();
}
if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size());
if (outTagsChanged) {
*outTagsChanged = tagAccumulator.changed();
}
return result;
}
@ -816,11 +833,12 @@ QStringList FlatTextarea::linksList() const {
}
void FlatTextarea::insertFromMimeData(const QMimeData *source) {
auto mime = str_const_toString(TagsMimeType);
auto mime = tagsMimeType();
auto text = source->text();
if (source->hasFormat(mime)) {
auto tagsData = source->data(mime);
_insertedTags = deserializeTagsList(tagsData, text.size());
_insertedTagsAreFromMime = true;
} else {
_insertedTags.clear();
}
@ -982,7 +1000,9 @@ void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
auto doc = document();
// Apply inserted tags.
int breakTagOnNotLetterTill = processInsertedTags(doc, insertPosition, insertEnd, _insertedTags, _tagMimeProcessor.get());
auto insertedTagsProcessor = _insertedTagsAreFromMime ? _tagMimeProcessor.get() : nullptr;
int breakTagOnNotLetterTill = processInsertedTags(doc, insertPosition, insertEnd,
_insertedTags, insertedTagsProcessor);
using ActionType = FormattingAction::Type;
while (true) {
FormattingAction action;
@ -1181,15 +1201,15 @@ void FlatTextarea::onDocumentContentsChanged() {
if (_correcting) return;
auto tagsChanged = false;
auto curText = getText(0, -1, &_oldtags, &tagsChanged);
auto curText = getTextPart(0, -1, &_lastTextWithTags.tags, &tagsChanged);
_correcting = true;
correctValue(_oldtext, curText, _oldtags);
correctValue(_lastTextWithTags.text, curText, _lastTextWithTags.tags);
_correcting = false;
bool textOrTagsChanged = tagsChanged || (_oldtext != curText);
bool textOrTagsChanged = tagsChanged || (_lastTextWithTags.text != curText);
if (textOrTagsChanged) {
_oldtext = curText;
_lastTextWithTags.text = curText;
emit changed();
checkContentHeight();
}
@ -1231,12 +1251,16 @@ void FlatTextarea::setPlaceholder(const QString &ph, int32 afterSymbols) {
_phAfter = afterSymbols;
updatePlaceholder();
}
_phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - (_phAfter ? _st.font->width(getLastText().mid(0, _phAfter)) : 0));
int skipWidth = 0;
if (_phAfter) {
skipWidth = _st.font->width(getTextWithTags().text.mid(0, _phAfter));
}
_phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - skipWidth);
if (_phVisible) update();
}
void FlatTextarea::updatePlaceholder() {
bool vis = (getLastText().size() <= _phAfter);
bool vis = (getTextWithTags().text.size() <= _phAfter);
if (vis == _phVisible) return;
a_phLeft.start(vis ? 0 : _st.phShift);
@ -1252,14 +1276,14 @@ QMimeData *FlatTextarea::createMimeDataFromSelection() const {
int32 start = c.selectionStart(), end = c.selectionEnd();
if (end > start) {
TagList tags;
result->setText(getText(start, end, &tags, nullptr));
result->setText(getTextPart(start, end, &tags));
if (!tags.isEmpty()) {
if (_tagMimeProcessor) {
for (auto &tag : tags) {
tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
}
}
result->setData(str_const_toString(TagsMimeType), serializeTagsList(tags));
result->setData(tagsMimeType(), serializeTagsList(tags));
}
}
return result;

View file

@ -31,16 +31,27 @@ class FlatTextarea : public QTextEdit {
public:
FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString());
struct Tag {
int offset, length;
QString id;
};
using TagList = QVector<Tag>;
struct TextWithTags {
using Tags = FlatTextarea::TagList;
QString text;
Tags tags;
};
static QByteArray serializeTagsList(const TagList &tags);
static TagList deserializeTagsList(QByteArray data, int textLength);
static QString tagsMimeType();
FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString(), const TagList &tags = TagList());
void setMaxLength(int32 maxLength);
void setMinHeight(int32 minHeight);
void setMaxHeight(int32 maxHeight);
const QString &getLastText() const {
return _oldtext;
}
void setPlaceholder(const QString &ph, int32 afterSymbols = 0);
void updatePlaceholder();
void finishPlaceholder();
@ -82,18 +93,23 @@ public:
};
void setSubmitSettings(SubmitSettings settings);
void setTextFast(const QString &text, bool clearUndoHistory = true);
struct Tag {
int offset, length;
QString id;
};
using TagList = QVector<Tag>;
const TagList &getLastTags() const {
return _oldtags;
const TextWithTags &getTextWithTags() const {
return _lastTextWithTags;
}
TextWithTags getTextWithTagsPart(int start, int end = -1);
void insertTag(const QString &text, QString tagId = QString());
bool isEmpty() const {
return _lastTextWithTags.text.isEmpty();
}
enum UndoHistoryAction {
AddToUndoHistory,
MergeWithUndoHistory,
ClearUndoHistory
};
void setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction = AddToUndoHistory);
// If you need to make some preparations of tags before putting them to QMimeData
// (and then to clipboard or to drag-n-drop object), here is a strategy for that.
class TagMimeProcessor {
@ -147,9 +163,9 @@ protected:
private:
// "start" and "end" are in coordinates of text where emoji are replaced by ObjectReplacementCharacter.
// If "end" = -1 means get text till the end. "outTagsList" and "outTagsChanged" may be nullptr.
QString getText(int start, int end, TagList *outTagsList, bool *outTagsChanged) const;
// "start" and "end" are in coordinates of text where emoji are replaced
// by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
QString getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged = nullptr) const;
void getSingleEmojiFragment(QString &text, QTextFragment &fragment) const;
@ -169,8 +185,7 @@ private:
int _maxLength = -1;
SubmitSettings _submitSettings = SubmitSettings::Enter;
QString _ph, _phelided, _oldtext;
TagList _oldtags;
QString _ph, _phelided;
int _phAfter = 0;
bool _phVisible;
anim::ivalue a_phLeft;
@ -178,8 +193,11 @@ private:
anim::cvalue a_phColor;
Animation _a_appearance;
TextWithTags _lastTextWithTags;
// Tags list which we should apply while setText() call or insert from mime data.
TagList _insertedTags;
bool _insertedTagsAreFromMime;
// Override insert position and charsAdded from complex text editing
// (like drag-n-drop in the same text edit field).
@ -222,6 +240,13 @@ inline bool operator!=(const FlatTextarea::Tag &a, const FlatTextarea::Tag &b) {
return !(a == b);
}
inline bool operator==(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) {
return (a.text == b.text) && (a.tags == b.tags);
}
inline bool operator!=(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) {
return !(a == b);
}
inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) {
return (a.start == b.start) && (a.length == b.length);
}

View file

@ -2023,7 +2023,7 @@
SDKROOT = macosx;
SYMROOT = ./../Mac;
TDESKTOP_MAJOR_VERSION = 0.9;
TDESKTOP_VERSION = 0.9.47;
TDESKTOP_VERSION = 0.9.48;
};
name = Release;
};
@ -2164,7 +2164,7 @@
SDKROOT = macosx;
SYMROOT = ./../Mac;
TDESKTOP_MAJOR_VERSION = 0.9;
TDESKTOP_VERSION = 0.9.47;
TDESKTOP_VERSION = 0.9.48;
};
name = Debug;
};

View file

@ -1,6 +1,6 @@
AppVersion 9047
AppVersion 9048
AppVersionStrMajor 0.9
AppVersionStrSmall 0.9.47
AppVersionStr 0.9.47
AppVersionStrSmall 0.9.48
AppVersionStr 0.9.48
AlphaChannel 1
BetaVersion 0