mirror of https://github.com/procxx/kepka.git
Renamed MentionsDropdown to FieldAutocomplete, moved to separate file.
Support for message field mentions without usernames was started.
This commit is contained in:
parent
af248a6714
commit
b4bc515079
|
@ -3870,880 +3870,3 @@ void EmojiPan::recountContentMaxHeight() {
|
||||||
}
|
}
|
||||||
updateContentHeight();
|
updateContentHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows)
|
|
||||||
: _parent(parent)
|
|
||||||
, _mrows(mrows)
|
|
||||||
, _hrows(hrows)
|
|
||||||
, _brows(brows)
|
|
||||||
, _srows(srows)
|
|
||||||
, _stickersPerRow(1)
|
|
||||||
, _recentInlineBotsInRows(0)
|
|
||||||
, _sel(-1)
|
|
||||||
, _down(-1)
|
|
||||||
, _mouseSel(false)
|
|
||||||
, _overDelete(false)
|
|
||||||
, _previewShown(false) {
|
|
||||||
_previewTimer.setSingleShot(true);
|
|
||||||
connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
QRect r(e->rect());
|
|
||||||
if (r != rect()) p.setClipRect(r);
|
|
||||||
|
|
||||||
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
|
|
||||||
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
|
|
||||||
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
|
|
||||||
int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
|
|
||||||
|
|
||||||
if (!_srows->isEmpty()) {
|
|
||||||
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
|
||||||
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
|
||||||
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
|
||||||
int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
|
|
||||||
int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
|
|
||||||
for (int32 row = fromrow; row < torow; ++row) {
|
|
||||||
for (int32 col = fromcol; col < tocol; ++col) {
|
|
||||||
int32 index = row * _stickersPerRow + col;
|
|
||||||
if (index >= _srows->size()) break;
|
|
||||||
|
|
||||||
DocumentData *sticker = _srows->at(index);
|
|
||||||
if (!sticker->sticker()) continue;
|
|
||||||
|
|
||||||
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
|
|
||||||
if (_sel == index) {
|
|
||||||
QPoint tl(pos);
|
|
||||||
if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width());
|
|
||||||
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
|
|
||||||
if (goodThumb) {
|
|
||||||
sticker->thumb->load();
|
|
||||||
} else {
|
|
||||||
sticker->checkSticker();
|
|
||||||
}
|
|
||||||
|
|
||||||
float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height()));
|
|
||||||
if (coef > 1) coef = 1;
|
|
||||||
int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height());
|
|
||||||
if (w < 1) w = 1;
|
|
||||||
if (h < 1) h = 1;
|
|
||||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
|
||||||
if (goodThumb) {
|
|
||||||
p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h));
|
|
||||||
} else if (!sticker->sticker()->img->isNull()) {
|
|
||||||
p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
|
|
||||||
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
|
||||||
bool hasUsername = _parent->filter().indexOf('@') > 1;
|
|
||||||
int filterSize = qMax(_parent->filter().size() - 1, 0);
|
|
||||||
bool filterIsEmpty = (filterSize == 0);
|
|
||||||
for (int32 i = from; i < to; ++i) {
|
|
||||||
if (i >= last) break;
|
|
||||||
|
|
||||||
bool selected = (i == _sel);
|
|
||||||
if (selected) {
|
|
||||||
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b);
|
|
||||||
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
|
|
||||||
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
|
|
||||||
p.drawSprite(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), st::notifyClose.icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.setPen(st::black->p);
|
|
||||||
if (!_mrows->isEmpty()) {
|
|
||||||
UserData *user = _mrows->at(i);
|
|
||||||
QString first = filterIsEmpty ? QString() : ('@' + user->username.mid(0, filterSize));
|
|
||||||
QString second = (filterIsEmpty && !user->username.isEmpty()) ? ('@' + user->username) : user->username.mid(filterSize);
|
|
||||||
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth();
|
|
||||||
if (mentionwidth < unamewidth + namewidth) {
|
|
||||||
namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth);
|
|
||||||
unamewidth = mentionwidth - namewidth;
|
|
||||||
if (firstwidth < unamewidth + st::mentionFont->elidew) {
|
|
||||||
if (firstwidth < unamewidth) {
|
|
||||||
first = st::mentionFont->elided(first, unamewidth);
|
|
||||||
} else if (!second.isEmpty()) {
|
|
||||||
first = st::mentionFont->elided(first + second, unamewidth);
|
|
||||||
second = QString();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
second = st::mentionFont->elided(second, unamewidth - firstwidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user->loadUserpic();
|
|
||||||
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
|
||||||
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
|
|
||||||
|
|
||||||
p.setFont(st::mentionFont->f);
|
|
||||||
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
|
||||||
p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
|
||||||
if (!second.isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
|
||||||
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
|
||||||
}
|
|
||||||
} else if (!_hrows->isEmpty()) {
|
|
||||||
QString hrow = _hrows->at(i);
|
|
||||||
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
|
|
||||||
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
|
|
||||||
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
|
|
||||||
if (htagwidth < firstwidth + secondwidth) {
|
|
||||||
if (htagwidth < firstwidth + st::mentionFont->elidew) {
|
|
||||||
first = st::mentionFont->elided(first + second, htagwidth);
|
|
||||||
second = QString();
|
|
||||||
} else {
|
|
||||||
second = st::mentionFont->elided(second, htagwidth - firstwidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.setFont(st::mentionFont->f);
|
|
||||||
if (!first.isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
|
||||||
p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
|
||||||
}
|
|
||||||
if (!second.isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
|
||||||
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UserData *user = _brows->at(i).first;
|
|
||||||
|
|
||||||
const BotCommand *command = _brows->at(i).second;
|
|
||||||
QString toHighlight = command->command;
|
|
||||||
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
|
||||||
if (hasUsername || botStatus == 0 || botStatus == 2) {
|
|
||||||
toHighlight += '@' + user->username;
|
|
||||||
}
|
|
||||||
user->loadUserpic();
|
|
||||||
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
|
||||||
|
|
||||||
int32 addleft = 0, widthleft = mentionwidth;
|
|
||||||
QString first = filterIsEmpty ? QString() : ('/' + toHighlight.mid(0, filterSize));
|
|
||||||
QString second = filterIsEmpty ? ('/' + toHighlight) : toHighlight.mid(filterSize);
|
|
||||||
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
|
|
||||||
if (widthleft < firstwidth + secondwidth) {
|
|
||||||
if (widthleft < firstwidth + st::mentionFont->elidew) {
|
|
||||||
first = st::mentionFont->elided(first + second, widthleft);
|
|
||||||
second = QString();
|
|
||||||
} else {
|
|
||||||
second = st::mentionFont->elided(second, widthleft - firstwidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.setFont(st::mentionFont->f);
|
|
||||||
if (!first.isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
|
||||||
p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
|
||||||
}
|
|
||||||
if (!second.isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
|
||||||
p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
|
||||||
}
|
|
||||||
addleft += firstwidth + secondwidth + st::mentionPadding.left();
|
|
||||||
widthleft -= firstwidth + secondwidth + st::mentionPadding.left();
|
|
||||||
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
|
|
||||||
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
|
||||||
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
|
|
||||||
}
|
|
||||||
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::resizeEvent(QResizeEvent *e) {
|
|
||||||
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::mouseMoveEvent(QMouseEvent *e) {
|
|
||||||
_mousePos = mapToGlobal(e->pos());
|
|
||||||
_mouseSel = true;
|
|
||||||
onUpdateSelected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::clearSel(bool hidden) {
|
|
||||||
_mouseSel = _overDelete = false;
|
|
||||||
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
|
|
||||||
if (hidden) {
|
|
||||||
_down = -1;
|
|
||||||
_previewShown = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MentionsInner::moveSel(int key) {
|
|
||||||
_mouseSel = false;
|
|
||||||
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
|
|
||||||
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
|
|
||||||
if (!_srows->isEmpty()) {
|
|
||||||
if (key == Qt::Key_Left) {
|
|
||||||
direction = -1;
|
|
||||||
} else if (key == Qt::Key_Right) {
|
|
||||||
direction = 1;
|
|
||||||
} else {
|
|
||||||
direction *= _stickersPerRow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_sel >= maxSel || _sel < 0) {
|
|
||||||
if (direction < -1) {
|
|
||||||
setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true);
|
|
||||||
} else if (direction < 0) {
|
|
||||||
setSel(maxSel - 1, true);
|
|
||||||
} else {
|
|
||||||
setSel(0, true);
|
|
||||||
}
|
|
||||||
return (_sel >= 0 && _sel < maxSel);
|
|
||||||
}
|
|
||||||
setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MentionsInner::select() {
|
|
||||||
if (!_srows->isEmpty()) {
|
|
||||||
if (_sel >= 0 && _sel < _srows->size()) {
|
|
||||||
emit selected(_srows->at(_sel));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QString sel = getSelected();
|
|
||||||
if (!sel.isEmpty()) {
|
|
||||||
emit chosen(sel);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::setRecentInlineBotsInRows(int32 bots) {
|
|
||||||
_recentInlineBotsInRows = bots;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MentionsInner::getSelected() const {
|
|
||||||
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size());
|
|
||||||
if (_sel >= 0 && _sel < maxSel) {
|
|
||||||
QString result;
|
|
||||||
if (!_mrows->isEmpty()) {
|
|
||||||
result = '@' + _mrows->at(_sel)->username;
|
|
||||||
} else if (!_hrows->isEmpty()) {
|
|
||||||
result = '#' + _hrows->at(_sel);
|
|
||||||
} else {
|
|
||||||
UserData *user = _brows->at(_sel).first;
|
|
||||||
const BotCommand *command(_brows->at(_sel).second);
|
|
||||||
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
|
||||||
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 1) {
|
|
||||||
result = '/' + command->command + '@' + user->username;
|
|
||||||
} else {
|
|
||||||
result = '/' + command->command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::mousePressEvent(QMouseEvent *e) {
|
|
||||||
_mousePos = mapToGlobal(e->pos());
|
|
||||||
_mouseSel = true;
|
|
||||||
onUpdateSelected(true);
|
|
||||||
if (e->button() == Qt::LeftButton) {
|
|
||||||
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
|
|
||||||
_mousePos = mapToGlobal(e->pos());
|
|
||||||
bool removed = false;
|
|
||||||
if (_mrows->isEmpty()) {
|
|
||||||
QString toRemove = _hrows->at(_sel);
|
|
||||||
RecentHashtagPack &recent(cRefRecentWriteHashtags());
|
|
||||||
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
|
|
||||||
if (i->first == toRemove) {
|
|
||||||
i = recent.erase(i);
|
|
||||||
removed = true;
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UserData *toRemove = _mrows->at(_sel);
|
|
||||||
RecentInlineBots &recent(cRefRecentInlineBots());
|
|
||||||
int32 index = recent.indexOf(toRemove);
|
|
||||||
if (index >= 0) {
|
|
||||||
recent.remove(index);
|
|
||||||
removed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (removed) {
|
|
||||||
Local::writeRecentHashtagsAndBots();
|
|
||||||
}
|
|
||||||
_parent->updateFiltered();
|
|
||||||
|
|
||||||
_mouseSel = true;
|
|
||||||
onUpdateSelected(true);
|
|
||||||
} else if (_srows->isEmpty()) {
|
|
||||||
select();
|
|
||||||
} else {
|
|
||||||
_down = _sel;
|
|
||||||
_previewTimer.start(QApplication::startDragTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::mouseReleaseEvent(QMouseEvent *e) {
|
|
||||||
_previewTimer.stop();
|
|
||||||
|
|
||||||
int32 pressed = _down;
|
|
||||||
_down = -1;
|
|
||||||
|
|
||||||
_mousePos = mapToGlobal(e->pos());
|
|
||||||
_mouseSel = true;
|
|
||||||
onUpdateSelected(true);
|
|
||||||
|
|
||||||
if (_previewShown) {
|
|
||||||
_previewShown = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return;
|
|
||||||
|
|
||||||
select();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::enterEvent(QEvent *e) {
|
|
||||||
setMouseTracking(true);
|
|
||||||
_mousePos = QCursor::pos();
|
|
||||||
onUpdateSelected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::leaveEvent(QEvent *e) {
|
|
||||||
setMouseTracking(false);
|
|
||||||
if (_sel >= 0) {
|
|
||||||
setSel(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::updateSelectedRow() {
|
|
||||||
if (_sel >= 0) {
|
|
||||||
if (_srows->isEmpty()) {
|
|
||||||
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
|
|
||||||
} else {
|
|
||||||
int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
|
|
||||||
update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::setSel(int sel, bool scroll) {
|
|
||||||
updateSelectedRow();
|
|
||||||
_sel = sel;
|
|
||||||
updateSelectedRow();
|
|
||||||
|
|
||||||
if (scroll && _sel >= 0) {
|
|
||||||
if (_srows->isEmpty()) {
|
|
||||||
emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
|
|
||||||
} else {
|
|
||||||
int32 row = _sel / _stickersPerRow;
|
|
||||||
emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::onUpdateSelected(bool force) {
|
|
||||||
QPoint mouse(mapFromGlobal(_mousePos));
|
|
||||||
if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
|
|
||||||
|
|
||||||
if (_down >= 0 && !_previewShown) return;
|
|
||||||
|
|
||||||
int32 sel = -1, maxSel = 0;
|
|
||||||
if (!_srows->isEmpty()) {
|
|
||||||
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
|
||||||
int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
|
|
||||||
int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;
|
|
||||||
if (row >= 0 && col >= 0) {
|
|
||||||
sel = row * _stickersPerRow + col;
|
|
||||||
}
|
|
||||||
maxSel = _srows->size();
|
|
||||||
_overDelete = false;
|
|
||||||
} else {
|
|
||||||
sel = mouse.y() / int32(st::mentionHeight);
|
|
||||||
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
|
||||||
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
|
|
||||||
}
|
|
||||||
if (sel < 0 || sel >= maxSel) {
|
|
||||||
sel = -1;
|
|
||||||
}
|
|
||||||
if (sel != _sel) {
|
|
||||||
setSel(sel);
|
|
||||||
if (_down >= 0 && _sel >= 0 && _down != _sel) {
|
|
||||||
_down = _sel;
|
|
||||||
if (_down >= 0 && _down < _srows->size()) {
|
|
||||||
Ui::showMediaPreview(_srows->at(_down));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::onParentGeometryChanged() {
|
|
||||||
_mousePos = QCursor::pos();
|
|
||||||
if (rect().contains(mapFromGlobal(_mousePos))) {
|
|
||||||
setMouseTracking(true);
|
|
||||||
onUpdateSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsInner::onPreview() {
|
|
||||||
if (_down >= 0 && _down < _srows->size()) {
|
|
||||||
Ui::showMediaPreview(_srows->at(_down));
|
|
||||||
_previewShown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent)
|
|
||||||
, _scroll(this, st::mentionScroll)
|
|
||||||
, _inner(this, &_mrows, &_hrows, &_brows, &_srows)
|
|
||||||
, _chat(0)
|
|
||||||
, _user(0)
|
|
||||||
, _channel(0)
|
|
||||||
, _hiding(false)
|
|
||||||
, a_opacity(0)
|
|
||||||
, _a_appearance(animation(this, &MentionsDropdown::step_appearance))
|
|
||||||
, _shadow(st::dropdownDef.shadow) {
|
|
||||||
_hideTimer.setSingleShot(true);
|
|
||||||
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
|
|
||||||
connect(&_inner, SIGNAL(chosen(QString)), this, SIGNAL(chosen(QString)));
|
|
||||||
connect(&_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
|
||||||
connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int)));
|
|
||||||
|
|
||||||
connect(App::wnd(), SIGNAL(imageLoaded()), &_inner, SLOT(update()));
|
|
||||||
|
|
||||||
setFocusPolicy(Qt::NoFocus);
|
|
||||||
_scroll.setFocusPolicy(Qt::NoFocus);
|
|
||||||
_scroll.viewport()->setFocusPolicy(Qt::NoFocus);
|
|
||||||
|
|
||||||
_inner.setGeometry(rect());
|
|
||||||
_scroll.setGeometry(rect());
|
|
||||||
|
|
||||||
_scroll.setWidget(&_inner);
|
|
||||||
_scroll.show();
|
|
||||||
_inner.show();
|
|
||||||
|
|
||||||
connect(&_scroll, SIGNAL(geometryChanged()), &_inner, SLOT(onParentGeometryChanged()));
|
|
||||||
connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(onUpdateSelected()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
if (_a_appearance.animating()) {
|
|
||||||
p.setOpacity(a_opacity.current());
|
|
||||||
p.drawPixmap(0, 0, _cache);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.fillRect(rect(), st::white);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) {
|
|
||||||
_chat = peer->asChat();
|
|
||||||
_user = peer->asUser();
|
|
||||||
_channel = peer->asChannel();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
rowsUpdated(MentionRows(), HashtagRows(), BotCommandRows(), _srows, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_emoji = EmojiPtr();
|
|
||||||
|
|
||||||
query = query.toLower();
|
|
||||||
bool resetScroll = (_filter != query);
|
|
||||||
if (resetScroll) {
|
|
||||||
_filter = query;
|
|
||||||
}
|
|
||||||
_addInlineBots = start;
|
|
||||||
|
|
||||||
updateFiltered(resetScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::showStickers(EmojiPtr emoji) {
|
|
||||||
bool resetScroll = (_emoji != emoji);
|
|
||||||
_emoji = emoji;
|
|
||||||
if (!emoji) {
|
|
||||||
rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_chat = 0;
|
|
||||||
_user = 0;
|
|
||||||
_channel = 0;
|
|
||||||
|
|
||||||
updateFiltered(resetScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MentionsDropdown::clearFilteredBotCommands() {
|
|
||||||
if (_brows.isEmpty()) return false;
|
|
||||||
_brows.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
template <typename T, typename U>
|
|
||||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
|
||||||
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
|
|
||||||
if (*i == elem) {
|
|
||||||
return (i - b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|
||||||
int32 now = unixtime(), recentInlineBots = 0;
|
|
||||||
MentionRows mrows;
|
|
||||||
HashtagRows hrows;
|
|
||||||
BotCommandRows brows;
|
|
||||||
StickerPack srows;
|
|
||||||
if (_emoji) {
|
|
||||||
QMap<uint64, uint64> setsToRequest;
|
|
||||||
Stickers::Sets &sets(Global::RefStickerSets());
|
|
||||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
|
||||||
for (int i = 0, l = order.size(); i < l; ++i) {
|
|
||||||
auto it = sets.find(order.at(i));
|
|
||||||
if (it != sets.cend()) {
|
|
||||||
if (it->emoji.isEmpty()) {
|
|
||||||
setsToRequest.insert(it->id, it->access);
|
|
||||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
|
||||||
} else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
|
|
||||||
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
|
|
||||||
if (i != it->emoji.cend()) {
|
|
||||||
srows += *i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!setsToRequest.isEmpty() && App::api()) {
|
|
||||||
for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
|
|
||||||
App::api()->scheduleStickerSetRequest(i.key(), i.value());
|
|
||||||
}
|
|
||||||
App::api()->requestStickerSets();
|
|
||||||
}
|
|
||||||
} else if (_filter.at(0) == '@') {
|
|
||||||
bool listAllSuggestions = (_filter.size() < 2);
|
|
||||||
if (_chat) {
|
|
||||||
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
|
||||||
} else if (_channel && _channel->isMegagroup()) {
|
|
||||||
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
|
|
||||||
} else {
|
|
||||||
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size());
|
|
||||||
}
|
|
||||||
} else if (_addInlineBots) {
|
|
||||||
mrows.reserve(cRecentInlineBots().size());
|
|
||||||
}
|
|
||||||
if (_addInlineBots) {
|
|
||||||
for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) {
|
|
||||||
UserData *user = *i;
|
|
||||||
if (user->username.isEmpty()) continue;
|
|
||||||
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
|
||||||
mrows.push_back(user);
|
|
||||||
++recentInlineBots;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_chat) {
|
|
||||||
QMultiMap<int32, UserData*> ordered;
|
|
||||||
mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
|
||||||
if (_chat->noParticipantInfo()) {
|
|
||||||
if (App::api()) App::api()->requestFullPeer(_chat);
|
|
||||||
} else if (!_chat->participants.isEmpty()) {
|
|
||||||
for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
|
|
||||||
UserData *user = i.key();
|
|
||||||
if (user->username.isEmpty()) continue;
|
|
||||||
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
|
||||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
|
||||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
|
|
||||||
UserData *user = *i;
|
|
||||||
if (user->username.isEmpty()) continue;
|
|
||||||
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
|
||||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
|
||||||
mrows.push_back(user);
|
|
||||||
if (!ordered.isEmpty()) {
|
|
||||||
ordered.remove(App::onlineForSort(user, now), user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!ordered.isEmpty()) {
|
|
||||||
for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
|
|
||||||
--i;
|
|
||||||
mrows.push_back(i.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (_channel && _channel->isMegagroup()) {
|
|
||||||
QMultiMap<int32, UserData*> ordered;
|
|
||||||
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
|
|
||||||
if (App::api()) App::api()->requestLastParticipants(_channel);
|
|
||||||
} else {
|
|
||||||
mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
|
|
||||||
for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
|
|
||||||
UserData *user = *i;
|
|
||||||
if (user->username.isEmpty()) continue;
|
|
||||||
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
|
||||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
|
||||||
mrows.push_back(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (_filter.at(0) == '#') {
|
|
||||||
const RecentHashtagPack &recent(cRecentWriteHashtags());
|
|
||||||
hrows.reserve(recent.size());
|
|
||||||
for (RecentHashtagPack::const_iterator i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
|
|
||||||
if (_filter.size() > 1 && (!i->first.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || i->first.size() + 1 == _filter.size())) continue;
|
|
||||||
hrows.push_back(i->first);
|
|
||||||
}
|
|
||||||
} else if (_filter.at(0) == '/') {
|
|
||||||
bool hasUsername = _filter.indexOf('@') > 1;
|
|
||||||
QMap<UserData*, bool> bots;
|
|
||||||
int32 cnt = 0;
|
|
||||||
if (_chat) {
|
|
||||||
if (_chat->noParticipantInfo()) {
|
|
||||||
if (App::api()) App::api()->requestFullPeer(_chat);
|
|
||||||
} else if (!_chat->participants.isEmpty()) {
|
|
||||||
for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
|
|
||||||
UserData *user = i.key();
|
|
||||||
if (!user->botInfo) continue;
|
|
||||||
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
|
||||||
if (user->botInfo->commands.isEmpty()) continue;
|
|
||||||
bots.insert(user, true);
|
|
||||||
cnt += user->botInfo->commands.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (_user && _user->botInfo) {
|
|
||||||
if (!_user->botInfo->inited && App::api()) App::api()->requestFullPeer(_user);
|
|
||||||
cnt = _user->botInfo->commands.size();
|
|
||||||
bots.insert(_user, true);
|
|
||||||
} else if (_channel && _channel->isMegagroup()) {
|
|
||||||
if (_channel->mgInfo->bots.isEmpty()) {
|
|
||||||
if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel);
|
|
||||||
} else {
|
|
||||||
for_const (auto user, _channel->mgInfo->bots) {
|
|
||||||
if (!user->botInfo) continue;
|
|
||||||
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
|
||||||
if (user->botInfo->commands.isEmpty()) continue;
|
|
||||||
bots.insert(user, true);
|
|
||||||
cnt += user->botInfo->commands.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cnt) {
|
|
||||||
brows.reserve(cnt);
|
|
||||||
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
|
|
||||||
if (_chat) {
|
|
||||||
for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
|
|
||||||
UserData *user = *i;
|
|
||||||
if (!user->botInfo) continue;
|
|
||||||
if (!bots.contains(user)) continue;
|
|
||||||
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
|
||||||
if (user->botInfo->commands.isEmpty()) continue;
|
|
||||||
bots.remove(user);
|
|
||||||
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
|
||||||
if (_filter.size() > 1) {
|
|
||||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
|
||||||
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
|
|
||||||
}
|
|
||||||
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bots.isEmpty()) {
|
|
||||||
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
|
||||||
UserData *user = i.key();
|
|
||||||
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
|
||||||
if (_filter.size() > 1) {
|
|
||||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
|
||||||
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
|
|
||||||
}
|
|
||||||
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rowsUpdated(mrows, hrows, brows, srows, resetScroll);
|
|
||||||
_inner.setRecentInlineBotsInRows(recentInlineBots);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll) {
|
|
||||||
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
|
|
||||||
if (!isHidden()) {
|
|
||||||
hideStart();
|
|
||||||
}
|
|
||||||
_mrows.clear();
|
|
||||||
_hrows.clear();
|
|
||||||
_brows.clear();
|
|
||||||
_srows.clear();
|
|
||||||
} else {
|
|
||||||
_mrows = mrows;
|
|
||||||
_hrows = hrows;
|
|
||||||
_brows = brows;
|
|
||||||
_srows = srows;
|
|
||||||
|
|
||||||
bool hidden = _hiding || isHidden();
|
|
||||||
if (hidden) {
|
|
||||||
show();
|
|
||||||
_scroll.show();
|
|
||||||
}
|
|
||||||
recount(resetScroll);
|
|
||||||
update();
|
|
||||||
if (hidden) {
|
|
||||||
hide();
|
|
||||||
showStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::setBoundings(QRect boundings) {
|
|
||||||
_boundings = boundings;
|
|
||||||
recount();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::recount(bool resetScroll) {
|
|
||||||
int32 h = 0, oldst = _scroll.scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
|
|
||||||
if (!_srows.isEmpty()) {
|
|
||||||
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
|
||||||
int32 rows = rowscount(_srows.size(), stickersPerRow);
|
|
||||||
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
|
|
||||||
} else if (!_mrows.isEmpty()) {
|
|
||||||
h = _mrows.size() * st::mentionHeight;
|
|
||||||
} else if (!_hrows.isEmpty()) {
|
|
||||||
h = _hrows.size() * st::mentionHeight;
|
|
||||||
} else if (!_brows.isEmpty()) {
|
|
||||||
h = _brows.size() * st::mentionHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_inner.width() != _boundings.width() || _inner.height() != h) {
|
|
||||||
_inner.resize(_boundings.width(), h);
|
|
||||||
}
|
|
||||||
if (h > _boundings.height()) h = _boundings.height();
|
|
||||||
if (h > maxh) h = maxh;
|
|
||||||
if (width() != _boundings.width() || height() != h) {
|
|
||||||
setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h);
|
|
||||||
_scroll.resize(_boundings.width(), h);
|
|
||||||
} else if (y() != _boundings.y() + _boundings.height() - h) {
|
|
||||||
move(_boundings.x(), _boundings.y() + _boundings.height() - h);
|
|
||||||
}
|
|
||||||
if (resetScroll) st = 0;
|
|
||||||
if (st != oldst) _scroll.scrollToY(st);
|
|
||||||
if (resetScroll) _inner.clearSel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::fastHide() {
|
|
||||||
if (_a_appearance.animating()) {
|
|
||||||
_a_appearance.stop();
|
|
||||||
}
|
|
||||||
a_opacity = anim::fvalue(0, 0);
|
|
||||||
_hideTimer.stop();
|
|
||||||
hideFinish();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::hideStart() {
|
|
||||||
if (!_hiding) {
|
|
||||||
if (_cache.isNull()) {
|
|
||||||
_scroll.show();
|
|
||||||
_cache = myGrab(this);
|
|
||||||
}
|
|
||||||
_scroll.hide();
|
|
||||||
_hiding = true;
|
|
||||||
a_opacity.start(0);
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
||||||
_a_appearance.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::hideFinish() {
|
|
||||||
hide();
|
|
||||||
_hiding = false;
|
|
||||||
_filter = qsl("-");
|
|
||||||
_inner.clearSel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::showStart() {
|
|
||||||
if (!isHidden() && a_opacity.current() == 1 && !_hiding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_cache.isNull()) {
|
|
||||||
_scroll.show();
|
|
||||||
_cache = myGrab(this);
|
|
||||||
}
|
|
||||||
_scroll.hide();
|
|
||||||
_hiding = false;
|
|
||||||
show();
|
|
||||||
a_opacity.start(1);
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
||||||
_a_appearance.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MentionsDropdown::step_appearance(float64 ms, bool timer) {
|
|
||||||
float64 dt = ms / st::dropdownDef.duration;
|
|
||||||
if (dt >= 1) {
|
|
||||||
_a_appearance.stop();
|
|
||||||
a_opacity.finish();
|
|
||||||
_cache = QPixmap();
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
|
||||||
if (_hiding) {
|
|
||||||
hideFinish();
|
|
||||||
} else {
|
|
||||||
_scroll.show();
|
|
||||||
_inner.clearSel();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a_opacity.update(dt, anim::linear);
|
|
||||||
}
|
|
||||||
if (timer) update();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &MentionsDropdown::filter() const {
|
|
||||||
return _filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatData *MentionsDropdown::chat() const {
|
|
||||||
return _chat;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelData *MentionsDropdown::channel() const {
|
|
||||||
return _channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserData *MentionsDropdown::user() const {
|
|
||||||
return _user;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 MentionsDropdown::innerTop() {
|
|
||||||
return _scroll.scrollTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 MentionsDropdown::innerBottom() {
|
|
||||||
return _scroll.scrollTop() + _scroll.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MentionsDropdown::getSelected() const {
|
|
||||||
return _inner.getSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) {
|
|
||||||
if (isHidden()) return QWidget::eventFilter(obj, e);
|
|
||||||
if (e->type() == QEvent::KeyPress) {
|
|
||||||
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
|
|
||||||
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
|
|
||||||
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
|
|
||||||
return _inner.moveSel(ev->key());
|
|
||||||
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
|
|
||||||
return _inner.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QWidget::eventFilter(obj, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
MentionsDropdown::~MentionsDropdown() {
|
|
||||||
}
|
|
||||||
|
|
|
@ -740,156 +740,3 @@ private:
|
||||||
bool inlineResultsFail(const RPCError &error);
|
bool inlineResultsFail(const RPCError &error);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef QList<UserData*> MentionRows;
|
|
||||||
typedef QList<QString> HashtagRows;
|
|
||||||
typedef QList<QPair<UserData*, const BotCommand*> > BotCommandRows;
|
|
||||||
|
|
||||||
class MentionsDropdown;
|
|
||||||
class MentionsInner : public TWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows);
|
|
||||||
|
|
||||||
void paintEvent(QPaintEvent *e);
|
|
||||||
void resizeEvent(QResizeEvent *e);
|
|
||||||
|
|
||||||
void enterEvent(QEvent *e);
|
|
||||||
void leaveEvent(QEvent *e);
|
|
||||||
|
|
||||||
void mousePressEvent(QMouseEvent *e);
|
|
||||||
void mouseMoveEvent(QMouseEvent *e);
|
|
||||||
void mouseReleaseEvent(QMouseEvent *e);
|
|
||||||
|
|
||||||
void clearSel(bool hidden = false);
|
|
||||||
bool moveSel(int key);
|
|
||||||
bool select();
|
|
||||||
|
|
||||||
void setRecentInlineBotsInRows(int32 bots);
|
|
||||||
|
|
||||||
QString getSelected() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
void chosen(QString mentionOrHashtag);
|
|
||||||
void selected(DocumentData *sticker);
|
|
||||||
void mustScrollTo(int scrollToTop, int scrollToBottom);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
|
|
||||||
void onParentGeometryChanged();
|
|
||||||
void onUpdateSelected(bool force = false);
|
|
||||||
void onPreview();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void updateSelectedRow();
|
|
||||||
void setSel(int sel, bool scroll = false);
|
|
||||||
|
|
||||||
MentionsDropdown *_parent;
|
|
||||||
MentionRows *_mrows;
|
|
||||||
HashtagRows *_hrows;
|
|
||||||
BotCommandRows *_brows;
|
|
||||||
StickerPack *_srows;
|
|
||||||
int32 _stickersPerRow, _recentInlineBotsInRows;
|
|
||||||
int32 _sel, _down;
|
|
||||||
bool _mouseSel;
|
|
||||||
QPoint _mousePos;
|
|
||||||
|
|
||||||
bool _overDelete;
|
|
||||||
|
|
||||||
bool _previewShown;
|
|
||||||
|
|
||||||
QTimer _previewTimer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MentionsDropdown : public TWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
MentionsDropdown(QWidget *parent);
|
|
||||||
|
|
||||||
void paintEvent(QPaintEvent *e);
|
|
||||||
|
|
||||||
void fastHide();
|
|
||||||
|
|
||||||
bool clearFilteredBotCommands();
|
|
||||||
void showFiltered(PeerData *peer, QString query, bool start);
|
|
||||||
void showStickers(EmojiPtr emoji);
|
|
||||||
void updateFiltered(bool resetScroll = false);
|
|
||||||
void setBoundings(QRect boundings);
|
|
||||||
|
|
||||||
void step_appearance(float64 ms, bool timer);
|
|
||||||
|
|
||||||
const QString &filter() const;
|
|
||||||
ChatData *chat() const;
|
|
||||||
ChannelData *channel() const;
|
|
||||||
UserData *user() const;
|
|
||||||
|
|
||||||
int32 innerTop();
|
|
||||||
int32 innerBottom();
|
|
||||||
|
|
||||||
bool eventFilter(QObject *obj, QEvent *e);
|
|
||||||
QString getSelected() const;
|
|
||||||
|
|
||||||
bool stickersShown() const {
|
|
||||||
return !_srows.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool overlaps(const QRect &globalRect) {
|
|
||||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
|
|
||||||
|
|
||||||
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
~MentionsDropdown();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
void chosen(QString mentionOrHashtag);
|
|
||||||
void stickerSelected(DocumentData *sticker);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
|
|
||||||
void hideStart();
|
|
||||||
void hideFinish();
|
|
||||||
|
|
||||||
void showStart();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void recount(bool resetScroll = false);
|
|
||||||
|
|
||||||
QPixmap _cache;
|
|
||||||
MentionRows _mrows;
|
|
||||||
HashtagRows _hrows;
|
|
||||||
BotCommandRows _brows;
|
|
||||||
StickerPack _srows;
|
|
||||||
|
|
||||||
void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll);
|
|
||||||
|
|
||||||
ScrollArea _scroll;
|
|
||||||
MentionsInner _inner;
|
|
||||||
|
|
||||||
ChatData *_chat;
|
|
||||||
UserData *_user;
|
|
||||||
ChannelData *_channel;
|
|
||||||
EmojiPtr _emoji;
|
|
||||||
QString _filter;
|
|
||||||
QRect _boundings;
|
|
||||||
bool _addInlineBots;
|
|
||||||
|
|
||||||
int32 _width, _height;
|
|
||||||
bool _hiding;
|
|
||||||
|
|
||||||
anim::fvalue a_opacity;
|
|
||||||
Animation _a_appearance;
|
|
||||||
|
|
||||||
QTimer _hideTimer;
|
|
||||||
|
|
||||||
BoxShadow _shadow;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,904 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "history/field_autocomplete.h"
|
||||||
|
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "localstorage.h"
|
||||||
|
|
||||||
|
FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent)
|
||||||
|
, _scroll(this, st::mentionScroll)
|
||||||
|
, _inner(this, &_mrows, &_hrows, &_brows, &_srows)
|
||||||
|
, _chat(0)
|
||||||
|
, _user(0)
|
||||||
|
, _channel(0)
|
||||||
|
, _hiding(false)
|
||||||
|
, a_opacity(0)
|
||||||
|
, _a_appearance(animation(this, &FieldAutocomplete::step_appearance))
|
||||||
|
, _shadow(st::dropdownDef.shadow) {
|
||||||
|
_hideTimer.setSingleShot(true);
|
||||||
|
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
|
||||||
|
|
||||||
|
connect(_inner, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)));
|
||||||
|
connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)));
|
||||||
|
connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)));
|
||||||
|
connect(_inner, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)));
|
||||||
|
connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int)));
|
||||||
|
|
||||||
|
connect(App::wnd(), SIGNAL(imageLoaded()), _inner, SLOT(update()));
|
||||||
|
|
||||||
|
setFocusPolicy(Qt::NoFocus);
|
||||||
|
_scroll->setFocusPolicy(Qt::NoFocus);
|
||||||
|
_scroll->viewport()->setFocusPolicy(Qt::NoFocus);
|
||||||
|
|
||||||
|
_inner->setGeometry(rect());
|
||||||
|
_scroll->setGeometry(rect());
|
||||||
|
|
||||||
|
_scroll->setWidget(_inner);
|
||||||
|
_scroll->show();
|
||||||
|
_inner->show();
|
||||||
|
|
||||||
|
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
|
||||||
|
connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(onUpdateSelected()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
if (_a_appearance.animating()) {
|
||||||
|
p.setOpacity(a_opacity.current());
|
||||||
|
p.drawPixmap(0, 0, _cache);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.fillRect(rect(), st::white);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool start) {
|
||||||
|
// _inner->showFiltered(peer, query, start);
|
||||||
|
_chat = peer->asChat();
|
||||||
|
_user = peer->asUser();
|
||||||
|
_channel = peer->asChannel();
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
rowsUpdated(internal::MentionRows(), internal::HashtagRows(), internal::BotCommandRows(), _srows, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_emoji = EmojiPtr();
|
||||||
|
|
||||||
|
query = query.toLower();
|
||||||
|
bool resetScroll = (_filter != query);
|
||||||
|
if (resetScroll) {
|
||||||
|
_filter = query;
|
||||||
|
}
|
||||||
|
_addInlineBots = start;
|
||||||
|
|
||||||
|
updateFiltered(resetScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
||||||
|
bool resetScroll = (_emoji != emoji);
|
||||||
|
_emoji = emoji;
|
||||||
|
if (!emoji) {
|
||||||
|
rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_chat = 0;
|
||||||
|
_user = 0;
|
||||||
|
_channel = 0;
|
||||||
|
|
||||||
|
updateFiltered(resetScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||||
|
if (_brows.isEmpty()) return false;
|
||||||
|
_brows.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||||
|
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
|
||||||
|
if (*i == elem) {
|
||||||
|
return (i - b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||||
|
int32 now = unixtime(), recentInlineBots = 0;
|
||||||
|
internal::MentionRows mrows;
|
||||||
|
internal::HashtagRows hrows;
|
||||||
|
internal::BotCommandRows brows;
|
||||||
|
StickerPack srows;
|
||||||
|
if (_emoji) {
|
||||||
|
QMap<uint64, uint64> setsToRequest;
|
||||||
|
Stickers::Sets &sets(Global::RefStickerSets());
|
||||||
|
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||||
|
for (int i = 0, l = order.size(); i < l; ++i) {
|
||||||
|
auto it = sets.find(order.at(i));
|
||||||
|
if (it != sets.cend()) {
|
||||||
|
if (it->emoji.isEmpty()) {
|
||||||
|
setsToRequest.insert(it->id, it->access);
|
||||||
|
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||||
|
} else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
|
||||||
|
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
|
||||||
|
if (i != it->emoji.cend()) {
|
||||||
|
srows += *i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!setsToRequest.isEmpty() && App::api()) {
|
||||||
|
for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
|
||||||
|
App::api()->scheduleStickerSetRequest(i.key(), i.value());
|
||||||
|
}
|
||||||
|
App::api()->requestStickerSets();
|
||||||
|
}
|
||||||
|
} else if (_filter.at(0) == '@') {
|
||||||
|
bool listAllSuggestions = (_filter.size() < 2);
|
||||||
|
if (_chat) {
|
||||||
|
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
||||||
|
} else if (_channel && _channel->isMegagroup()) {
|
||||||
|
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
|
||||||
|
} else {
|
||||||
|
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size());
|
||||||
|
}
|
||||||
|
} else if (_addInlineBots) {
|
||||||
|
mrows.reserve(cRecentInlineBots().size());
|
||||||
|
}
|
||||||
|
if (_addInlineBots) {
|
||||||
|
for (auto i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) {
|
||||||
|
UserData *user = *i;
|
||||||
|
if (user->username.isEmpty()) continue;
|
||||||
|
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||||
|
mrows.push_back(user);
|
||||||
|
++recentInlineBots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_chat) {
|
||||||
|
QMultiMap<int32, UserData*> ordered;
|
||||||
|
mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
||||||
|
if (_chat->noParticipantInfo()) {
|
||||||
|
if (App::api()) App::api()->requestFullPeer(_chat);
|
||||||
|
} else if (!_chat->participants.isEmpty()) {
|
||||||
|
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
|
||||||
|
UserData *user = i.key();
|
||||||
|
if (!listAllSuggestions && user->username.isEmpty()) continue;
|
||||||
|
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||||
|
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||||
|
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
|
||||||
|
UserData *user = *i;
|
||||||
|
if (!listAllSuggestions && user->username.isEmpty()) continue;
|
||||||
|
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||||
|
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||||
|
mrows.push_back(user);
|
||||||
|
if (!ordered.isEmpty()) {
|
||||||
|
ordered.remove(App::onlineForSort(user, now), user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ordered.isEmpty()) {
|
||||||
|
for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
|
||||||
|
--i;
|
||||||
|
mrows.push_back(i.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_channel && _channel->isMegagroup()) {
|
||||||
|
QMultiMap<int32, UserData*> ordered;
|
||||||
|
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
|
||||||
|
if (App::api()) App::api()->requestLastParticipants(_channel);
|
||||||
|
} else {
|
||||||
|
mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
|
||||||
|
for (auto i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
|
||||||
|
UserData *user = *i;
|
||||||
|
if (!listAllSuggestions && user->username.isEmpty()) continue;
|
||||||
|
if (!listAllSuggestions && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||||
|
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||||
|
mrows.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_filter.at(0) == '#') {
|
||||||
|
auto &recent(cRecentWriteHashtags());
|
||||||
|
hrows.reserve(recent.size());
|
||||||
|
for (auto i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
|
||||||
|
if (_filter.size() > 1 && (!i->first.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || i->first.size() + 1 == _filter.size())) continue;
|
||||||
|
hrows.push_back(i->first);
|
||||||
|
}
|
||||||
|
} else if (_filter.at(0) == '/') {
|
||||||
|
bool hasUsername = _filter.indexOf('@') > 1;
|
||||||
|
QMap<UserData*, bool> bots;
|
||||||
|
int32 cnt = 0;
|
||||||
|
if (_chat) {
|
||||||
|
if (_chat->noParticipantInfo()) {
|
||||||
|
if (App::api()) App::api()->requestFullPeer(_chat);
|
||||||
|
} else if (!_chat->participants.isEmpty()) {
|
||||||
|
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
|
||||||
|
UserData *user = i.key();
|
||||||
|
if (!user->botInfo) continue;
|
||||||
|
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
||||||
|
if (user->botInfo->commands.isEmpty()) continue;
|
||||||
|
bots.insert(user, true);
|
||||||
|
cnt += user->botInfo->commands.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_user && _user->botInfo) {
|
||||||
|
if (!_user->botInfo->inited && App::api()) App::api()->requestFullPeer(_user);
|
||||||
|
cnt = _user->botInfo->commands.size();
|
||||||
|
bots.insert(_user, true);
|
||||||
|
} else if (_channel && _channel->isMegagroup()) {
|
||||||
|
if (_channel->mgInfo->bots.isEmpty()) {
|
||||||
|
if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel);
|
||||||
|
} else {
|
||||||
|
for_const (auto user, _channel->mgInfo->bots) {
|
||||||
|
if (!user->botInfo) continue;
|
||||||
|
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
||||||
|
if (user->botInfo->commands.isEmpty()) continue;
|
||||||
|
bots.insert(user, true);
|
||||||
|
cnt += user->botInfo->commands.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cnt) {
|
||||||
|
brows.reserve(cnt);
|
||||||
|
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
|
||||||
|
if (_chat) {
|
||||||
|
for (auto i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
|
||||||
|
UserData *user = *i;
|
||||||
|
if (!user->botInfo) continue;
|
||||||
|
if (!bots.contains(user)) continue;
|
||||||
|
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
||||||
|
if (user->botInfo->commands.isEmpty()) continue;
|
||||||
|
bots.remove(user);
|
||||||
|
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
||||||
|
if (_filter.size() > 1) {
|
||||||
|
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
||||||
|
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
|
||||||
|
}
|
||||||
|
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!bots.isEmpty()) {
|
||||||
|
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
||||||
|
UserData *user = i.key();
|
||||||
|
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
||||||
|
if (_filter.size() > 1) {
|
||||||
|
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
||||||
|
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
|
||||||
|
}
|
||||||
|
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rowsUpdated(mrows, hrows, brows, srows, resetScroll);
|
||||||
|
_inner->setRecentInlineBotsInRows(recentInlineBots);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll) {
|
||||||
|
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
|
||||||
|
if (!isHidden()) {
|
||||||
|
hideStart();
|
||||||
|
}
|
||||||
|
_mrows.clear();
|
||||||
|
_hrows.clear();
|
||||||
|
_brows.clear();
|
||||||
|
_srows.clear();
|
||||||
|
} else {
|
||||||
|
_mrows = mrows;
|
||||||
|
_hrows = hrows;
|
||||||
|
_brows = brows;
|
||||||
|
_srows = srows;
|
||||||
|
|
||||||
|
bool hidden = _hiding || isHidden();
|
||||||
|
if (hidden) {
|
||||||
|
show();
|
||||||
|
_scroll->show();
|
||||||
|
}
|
||||||
|
recount(resetScroll);
|
||||||
|
update();
|
||||||
|
if (hidden) {
|
||||||
|
hide();
|
||||||
|
showStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::setBoundings(QRect boundings) {
|
||||||
|
_boundings = boundings;
|
||||||
|
recount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::recount(bool resetScroll) {
|
||||||
|
int32 h = 0, oldst = _scroll->scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
|
||||||
|
if (!_srows.isEmpty()) {
|
||||||
|
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
||||||
|
int32 rows = rowscount(_srows.size(), stickersPerRow);
|
||||||
|
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
|
||||||
|
} else if (!_mrows.isEmpty()) {
|
||||||
|
h = _mrows.size() * st::mentionHeight;
|
||||||
|
} else if (!_hrows.isEmpty()) {
|
||||||
|
h = _hrows.size() * st::mentionHeight;
|
||||||
|
} else if (!_brows.isEmpty()) {
|
||||||
|
h = _brows.size() * st::mentionHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_inner->width() != _boundings.width() || _inner->height() != h) {
|
||||||
|
_inner->resize(_boundings.width(), h);
|
||||||
|
}
|
||||||
|
if (h > _boundings.height()) h = _boundings.height();
|
||||||
|
if (h > maxh) h = maxh;
|
||||||
|
if (width() != _boundings.width() || height() != h) {
|
||||||
|
setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h);
|
||||||
|
_scroll->resize(_boundings.width(), h);
|
||||||
|
} else if (y() != _boundings.y() + _boundings.height() - h) {
|
||||||
|
move(_boundings.x(), _boundings.y() + _boundings.height() - h);
|
||||||
|
}
|
||||||
|
if (resetScroll) st = 0;
|
||||||
|
if (st != oldst) _scroll->scrollToY(st);
|
||||||
|
if (resetScroll) _inner->clearSel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::fastHide() {
|
||||||
|
if (_a_appearance.animating()) {
|
||||||
|
_a_appearance.stop();
|
||||||
|
}
|
||||||
|
a_opacity = anim::fvalue(0, 0);
|
||||||
|
_hideTimer.stop();
|
||||||
|
hideFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::hideStart() {
|
||||||
|
if (!_hiding) {
|
||||||
|
if (_cache.isNull()) {
|
||||||
|
_scroll->show();
|
||||||
|
_cache = myGrab(this);
|
||||||
|
}
|
||||||
|
_scroll->hide();
|
||||||
|
_hiding = true;
|
||||||
|
a_opacity.start(0);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||||
|
_a_appearance.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::hideFinish() {
|
||||||
|
hide();
|
||||||
|
_hiding = false;
|
||||||
|
_filter = qsl("-");
|
||||||
|
_inner->clearSel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::showStart() {
|
||||||
|
if (!isHidden() && a_opacity.current() == 1 && !_hiding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_cache.isNull()) {
|
||||||
|
_scroll->show();
|
||||||
|
_cache = myGrab(this);
|
||||||
|
}
|
||||||
|
_scroll->hide();
|
||||||
|
_hiding = false;
|
||||||
|
show();
|
||||||
|
a_opacity.start(1);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||||
|
_a_appearance.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::step_appearance(float64 ms, bool timer) {
|
||||||
|
float64 dt = ms / st::dropdownDef.duration;
|
||||||
|
if (dt >= 1) {
|
||||||
|
_a_appearance.stop();
|
||||||
|
a_opacity.finish();
|
||||||
|
_cache = QPixmap();
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
if (_hiding) {
|
||||||
|
hideFinish();
|
||||||
|
} else {
|
||||||
|
_scroll->show();
|
||||||
|
_inner->clearSel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a_opacity.update(dt, anim::linear);
|
||||||
|
}
|
||||||
|
if (timer) update();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &FieldAutocomplete::filter() const {
|
||||||
|
return _filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatData *FieldAutocomplete::chat() const {
|
||||||
|
return _chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelData *FieldAutocomplete::channel() const {
|
||||||
|
return _channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserData *FieldAutocomplete::user() const {
|
||||||
|
return _user;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FieldAutocomplete::innerTop() {
|
||||||
|
return _scroll->scrollTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FieldAutocomplete::innerBottom() {
|
||||||
|
return _scroll->scrollTop() + _scroll->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldAutocomplete::chooseSelected(ChooseMethod method) const {
|
||||||
|
return _inner->chooseSelected(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
||||||
|
if (isHidden()) return QWidget::eventFilter(obj, e);
|
||||||
|
if (e->type() == QEvent::KeyPress) {
|
||||||
|
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
|
||||||
|
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
|
||||||
|
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
|
||||||
|
return _inner->moveSel(ev->key());
|
||||||
|
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
|
||||||
|
return _inner->chooseSelected(ChooseMethod::ByEnter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QWidget::eventFilter(obj, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldAutocomplete::~FieldAutocomplete() {
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows)
|
||||||
|
: _parent(parent)
|
||||||
|
, _mrows(mrows)
|
||||||
|
, _hrows(hrows)
|
||||||
|
, _brows(brows)
|
||||||
|
, _srows(srows)
|
||||||
|
, _stickersPerRow(1)
|
||||||
|
, _recentInlineBotsInRows(0)
|
||||||
|
, _sel(-1)
|
||||||
|
, _down(-1)
|
||||||
|
, _mouseSel(false)
|
||||||
|
, _overDelete(false)
|
||||||
|
, _previewShown(false) {
|
||||||
|
_previewTimer.setSingleShot(true);
|
||||||
|
connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
QRect r(e->rect());
|
||||||
|
if (r != rect()) p.setClipRect(r);
|
||||||
|
|
||||||
|
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
|
||||||
|
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
|
||||||
|
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
|
||||||
|
int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
|
||||||
|
|
||||||
|
if (!_srows->isEmpty()) {
|
||||||
|
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
||||||
|
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||||
|
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||||
|
int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
|
||||||
|
int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
|
||||||
|
for (int32 row = fromrow; row < torow; ++row) {
|
||||||
|
for (int32 col = fromcol; col < tocol; ++col) {
|
||||||
|
int32 index = row * _stickersPerRow + col;
|
||||||
|
if (index >= _srows->size()) break;
|
||||||
|
|
||||||
|
DocumentData *sticker = _srows->at(index);
|
||||||
|
if (!sticker->sticker()) continue;
|
||||||
|
|
||||||
|
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
|
||||||
|
if (_sel == index) {
|
||||||
|
QPoint tl(pos);
|
||||||
|
if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width());
|
||||||
|
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
|
||||||
|
if (goodThumb) {
|
||||||
|
sticker->thumb->load();
|
||||||
|
} else {
|
||||||
|
sticker->checkSticker();
|
||||||
|
}
|
||||||
|
|
||||||
|
float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height()));
|
||||||
|
if (coef > 1) coef = 1;
|
||||||
|
int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height());
|
||||||
|
if (w < 1) w = 1;
|
||||||
|
if (h < 1) h = 1;
|
||||||
|
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||||
|
if (goodThumb) {
|
||||||
|
p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h));
|
||||||
|
} else if (!sticker->sticker()->img->isNull()) {
|
||||||
|
p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
|
||||||
|
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
||||||
|
bool hasUsername = _parent->filter().indexOf('@') > 1;
|
||||||
|
int filterSize = qMax(_parent->filter().size() - 1, 0);
|
||||||
|
bool filterIsEmpty = (filterSize == 0);
|
||||||
|
for (int32 i = from; i < to; ++i) {
|
||||||
|
if (i >= last) break;
|
||||||
|
|
||||||
|
bool selected = (i == _sel);
|
||||||
|
if (selected) {
|
||||||
|
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b);
|
||||||
|
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
|
||||||
|
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
|
||||||
|
p.drawSprite(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), st::notifyClose.icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.setPen(st::black->p);
|
||||||
|
if (!_mrows->isEmpty()) {
|
||||||
|
UserData *user = _mrows->at(i);
|
||||||
|
QString first = filterIsEmpty ? QString() : ('@' + user->username.mid(0, filterSize));
|
||||||
|
QString second = (filterIsEmpty && !user->username.isEmpty()) ? ('@' + user->username) : user->username.mid(filterSize);
|
||||||
|
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth();
|
||||||
|
if (mentionwidth < unamewidth + namewidth) {
|
||||||
|
namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth);
|
||||||
|
unamewidth = mentionwidth - namewidth;
|
||||||
|
if (firstwidth < unamewidth + st::mentionFont->elidew) {
|
||||||
|
if (firstwidth < unamewidth) {
|
||||||
|
first = st::mentionFont->elided(first, unamewidth);
|
||||||
|
} else if (!second.isEmpty()) {
|
||||||
|
first = st::mentionFont->elided(first + second, unamewidth);
|
||||||
|
second = QString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
second = st::mentionFont->elided(second, unamewidth - firstwidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user->loadUserpic();
|
||||||
|
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
||||||
|
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
|
||||||
|
|
||||||
|
p.setFont(st::mentionFont->f);
|
||||||
|
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
||||||
|
p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
||||||
|
if (!second.isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
||||||
|
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
||||||
|
}
|
||||||
|
} else if (!_hrows->isEmpty()) {
|
||||||
|
QString hrow = _hrows->at(i);
|
||||||
|
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
|
||||||
|
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
|
||||||
|
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
|
||||||
|
if (htagwidth < firstwidth + secondwidth) {
|
||||||
|
if (htagwidth < firstwidth + st::mentionFont->elidew) {
|
||||||
|
first = st::mentionFont->elided(first + second, htagwidth);
|
||||||
|
second = QString();
|
||||||
|
} else {
|
||||||
|
second = st::mentionFont->elided(second, htagwidth - firstwidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setFont(st::mentionFont->f);
|
||||||
|
if (!first.isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
||||||
|
p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
||||||
|
}
|
||||||
|
if (!second.isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
||||||
|
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UserData *user = _brows->at(i).first;
|
||||||
|
|
||||||
|
const BotCommand *command = _brows->at(i).second;
|
||||||
|
QString toHighlight = command->command;
|
||||||
|
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
||||||
|
if (hasUsername || botStatus == 0 || botStatus == 2) {
|
||||||
|
toHighlight += '@' + user->username;
|
||||||
|
}
|
||||||
|
user->loadUserpic();
|
||||||
|
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
||||||
|
|
||||||
|
int32 addleft = 0, widthleft = mentionwidth;
|
||||||
|
QString first = filterIsEmpty ? QString() : ('/' + toHighlight.mid(0, filterSize));
|
||||||
|
QString second = filterIsEmpty ? ('/' + toHighlight) : toHighlight.mid(filterSize);
|
||||||
|
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
|
||||||
|
if (widthleft < firstwidth + secondwidth) {
|
||||||
|
if (widthleft < firstwidth + st::mentionFont->elidew) {
|
||||||
|
first = st::mentionFont->elided(first + second, widthleft);
|
||||||
|
second = QString();
|
||||||
|
} else {
|
||||||
|
second = st::mentionFont->elided(second, widthleft - firstwidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.setFont(st::mentionFont->f);
|
||||||
|
if (!first.isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
|
||||||
|
p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
|
||||||
|
}
|
||||||
|
if (!second.isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
||||||
|
p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
||||||
|
}
|
||||||
|
addleft += firstwidth + secondwidth + st::mentionPadding.left();
|
||||||
|
widthleft -= firstwidth + secondwidth + st::mentionPadding.left();
|
||||||
|
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
|
||||||
|
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
||||||
|
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
|
||||||
|
}
|
||||||
|
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
|
||||||
|
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
_mousePos = mapToGlobal(e->pos());
|
||||||
|
_mouseSel = true;
|
||||||
|
onUpdateSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::clearSel(bool hidden) {
|
||||||
|
_mouseSel = _overDelete = false;
|
||||||
|
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
|
||||||
|
if (hidden) {
|
||||||
|
_down = -1;
|
||||||
|
_previewShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldAutocompleteInner::moveSel(int key) {
|
||||||
|
_mouseSel = false;
|
||||||
|
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
|
||||||
|
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
|
||||||
|
if (!_srows->isEmpty()) {
|
||||||
|
if (key == Qt::Key_Left) {
|
||||||
|
direction = -1;
|
||||||
|
} else if (key == Qt::Key_Right) {
|
||||||
|
direction = 1;
|
||||||
|
} else {
|
||||||
|
direction *= _stickersPerRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_sel >= maxSel || _sel < 0) {
|
||||||
|
if (direction < -1) {
|
||||||
|
setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true);
|
||||||
|
} else if (direction < 0) {
|
||||||
|
setSel(maxSel - 1, true);
|
||||||
|
} else {
|
||||||
|
setSel(0, true);
|
||||||
|
}
|
||||||
|
return (_sel >= 0 && _sel < maxSel);
|
||||||
|
}
|
||||||
|
setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod method) const {
|
||||||
|
if (!_srows->isEmpty()) {
|
||||||
|
if (_sel >= 0 && _sel < _srows->size()) {
|
||||||
|
emit stickerChosen(_srows->at(_sel), method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (!_mrows->isEmpty()) {
|
||||||
|
if (_sel >= 0 && _sel < _mrows->size()) {
|
||||||
|
emit mentionChosen(_mrows->at(_sel), method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (!_hrows->isEmpty()) {
|
||||||
|
if (_sel >= 0 && _sel < _hrows->size()) {
|
||||||
|
emit hashtagChosen('#' + _hrows->at(_sel), method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (!_brows->isEmpty()) {
|
||||||
|
if (_sel >= 0 && _sel < _brows->size()) {
|
||||||
|
UserData *user = _brows->at(_sel).first;
|
||||||
|
const BotCommand *command(_brows->at(_sel).second);
|
||||||
|
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
||||||
|
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 1) {
|
||||||
|
emit botCommandChosen('/' + command->command + '@' + user->username, method);
|
||||||
|
} else {
|
||||||
|
emit botCommandChosen('/' + command->command, method);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
|
||||||
|
_recentInlineBotsInRows = bots;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
|
||||||
|
_mousePos = mapToGlobal(e->pos());
|
||||||
|
_mouseSel = true;
|
||||||
|
onUpdateSelected(true);
|
||||||
|
if (e->button() == Qt::LeftButton) {
|
||||||
|
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
|
||||||
|
_mousePos = mapToGlobal(e->pos());
|
||||||
|
bool removed = false;
|
||||||
|
if (_mrows->isEmpty()) {
|
||||||
|
QString toRemove = _hrows->at(_sel);
|
||||||
|
RecentHashtagPack &recent(cRefRecentWriteHashtags());
|
||||||
|
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
|
||||||
|
if (i->first == toRemove) {
|
||||||
|
i = recent.erase(i);
|
||||||
|
removed = true;
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UserData *toRemove = _mrows->at(_sel);
|
||||||
|
RecentInlineBots &recent(cRefRecentInlineBots());
|
||||||
|
int32 index = recent.indexOf(toRemove);
|
||||||
|
if (index >= 0) {
|
||||||
|
recent.remove(index);
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed) {
|
||||||
|
Local::writeRecentHashtagsAndBots();
|
||||||
|
}
|
||||||
|
_parent->updateFiltered();
|
||||||
|
|
||||||
|
_mouseSel = true;
|
||||||
|
onUpdateSelected(true);
|
||||||
|
} else if (_srows->isEmpty()) {
|
||||||
|
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
|
||||||
|
} else {
|
||||||
|
_down = _sel;
|
||||||
|
_previewTimer.start(QApplication::startDragTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
_previewTimer.stop();
|
||||||
|
|
||||||
|
int32 pressed = _down;
|
||||||
|
_down = -1;
|
||||||
|
|
||||||
|
_mousePos = mapToGlobal(e->pos());
|
||||||
|
_mouseSel = true;
|
||||||
|
onUpdateSelected(true);
|
||||||
|
|
||||||
|
if (_previewShown) {
|
||||||
|
_previewShown = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return;
|
||||||
|
|
||||||
|
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::enterEvent(QEvent *e) {
|
||||||
|
setMouseTracking(true);
|
||||||
|
_mousePos = QCursor::pos();
|
||||||
|
onUpdateSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::leaveEvent(QEvent *e) {
|
||||||
|
setMouseTracking(false);
|
||||||
|
if (_sel >= 0) {
|
||||||
|
setSel(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::updateSelectedRow() {
|
||||||
|
if (_sel >= 0) {
|
||||||
|
if (_srows->isEmpty()) {
|
||||||
|
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
|
||||||
|
} else {
|
||||||
|
int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
|
||||||
|
update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::setSel(int sel, bool scroll) {
|
||||||
|
updateSelectedRow();
|
||||||
|
_sel = sel;
|
||||||
|
updateSelectedRow();
|
||||||
|
|
||||||
|
if (scroll && _sel >= 0) {
|
||||||
|
if (_srows->isEmpty()) {
|
||||||
|
emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
|
||||||
|
} else {
|
||||||
|
int32 row = _sel / _stickersPerRow;
|
||||||
|
emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::onUpdateSelected(bool force) {
|
||||||
|
QPoint mouse(mapFromGlobal(_mousePos));
|
||||||
|
if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
|
||||||
|
|
||||||
|
if (_down >= 0 && !_previewShown) return;
|
||||||
|
|
||||||
|
int32 sel = -1, maxSel = 0;
|
||||||
|
if (!_srows->isEmpty()) {
|
||||||
|
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
||||||
|
int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
|
||||||
|
int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;
|
||||||
|
if (row >= 0 && col >= 0) {
|
||||||
|
sel = row * _stickersPerRow + col;
|
||||||
|
}
|
||||||
|
maxSel = _srows->size();
|
||||||
|
_overDelete = false;
|
||||||
|
} else {
|
||||||
|
sel = mouse.y() / int32(st::mentionHeight);
|
||||||
|
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
||||||
|
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
|
||||||
|
}
|
||||||
|
if (sel < 0 || sel >= maxSel) {
|
||||||
|
sel = -1;
|
||||||
|
}
|
||||||
|
if (sel != _sel) {
|
||||||
|
setSel(sel);
|
||||||
|
if (_down >= 0 && _sel >= 0 && _down != _sel) {
|
||||||
|
_down = _sel;
|
||||||
|
if (_down >= 0 && _down < _srows->size()) {
|
||||||
|
Ui::showMediaPreview(_srows->at(_down));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::onParentGeometryChanged() {
|
||||||
|
_mousePos = QCursor::pos();
|
||||||
|
if (rect().contains(mapFromGlobal(_mousePos))) {
|
||||||
|
setMouseTracking(true);
|
||||||
|
onUpdateSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocompleteInner::onPreview() {
|
||||||
|
if (_down >= 0 && _down < _srows->size()) {
|
||||||
|
Ui::showMediaPreview(_srows->at(_down));
|
||||||
|
_previewShown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/twidget.h"
|
||||||
|
#include "ui/boxshadow.h"
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
using MentionRows = QList<UserData*>;
|
||||||
|
using HashtagRows = QList<QString>;
|
||||||
|
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
|
||||||
|
|
||||||
|
class FieldAutocompleteInner;
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
class FieldAutocomplete final : public TWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
FieldAutocomplete(QWidget *parent);
|
||||||
|
|
||||||
|
void fastHide();
|
||||||
|
|
||||||
|
bool clearFilteredBotCommands();
|
||||||
|
void showFiltered(PeerData *peer, QString query, bool start);
|
||||||
|
void showStickers(EmojiPtr emoji);
|
||||||
|
void setBoundings(QRect boundings);
|
||||||
|
|
||||||
|
void step_appearance(float64 ms, bool timer);
|
||||||
|
|
||||||
|
const QString &filter() const;
|
||||||
|
ChatData *chat() const;
|
||||||
|
ChannelData *channel() const;
|
||||||
|
UserData *user() const;
|
||||||
|
|
||||||
|
int32 innerTop();
|
||||||
|
int32 innerBottom();
|
||||||
|
|
||||||
|
bool eventFilter(QObject *obj, QEvent *e);
|
||||||
|
|
||||||
|
enum class ChooseMethod {
|
||||||
|
ByEnter,
|
||||||
|
ByTab,
|
||||||
|
ByClick,
|
||||||
|
};
|
||||||
|
bool chooseSelected(ChooseMethod method) const;
|
||||||
|
|
||||||
|
bool stickersShown() const {
|
||||||
|
return !_srows.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overlaps(const QRect &globalRect) {
|
||||||
|
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
|
||||||
|
|
||||||
|
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
~FieldAutocomplete();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void hideStart();
|
||||||
|
void hideFinish();
|
||||||
|
|
||||||
|
void showStart();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
void updateFiltered(bool resetScroll = false);
|
||||||
|
void recount(bool resetScroll = false);
|
||||||
|
|
||||||
|
QPixmap _cache;
|
||||||
|
internal::MentionRows _mrows;
|
||||||
|
internal::HashtagRows _hrows;
|
||||||
|
internal::BotCommandRows _brows;
|
||||||
|
StickerPack _srows;
|
||||||
|
|
||||||
|
void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll);
|
||||||
|
|
||||||
|
ChildWidget<ScrollArea> _scroll;
|
||||||
|
ChildWidget<internal::FieldAutocompleteInner> _inner;
|
||||||
|
|
||||||
|
ChatData *_chat;
|
||||||
|
UserData *_user;
|
||||||
|
ChannelData *_channel;
|
||||||
|
EmojiPtr _emoji;
|
||||||
|
QString _filter;
|
||||||
|
QRect _boundings;
|
||||||
|
bool _addInlineBots;
|
||||||
|
|
||||||
|
int32 _width, _height;
|
||||||
|
bool _hiding;
|
||||||
|
|
||||||
|
anim::fvalue a_opacity;
|
||||||
|
Animation _a_appearance;
|
||||||
|
|
||||||
|
QTimer _hideTimer;
|
||||||
|
|
||||||
|
BoxShadow _shadow;
|
||||||
|
friend class internal::FieldAutocompleteInner;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
class FieldAutocompleteInner final : public TWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows);
|
||||||
|
|
||||||
|
void clearSel(bool hidden = false);
|
||||||
|
bool moveSel(int key);
|
||||||
|
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
|
||||||
|
void setRecentInlineBotsInRows(int32 bots);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const;
|
||||||
|
void mustScrollTo(int scrollToTop, int scrollToBottom);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void onParentGeometryChanged();
|
||||||
|
void onUpdateSelected(bool force = false);
|
||||||
|
void onPreview();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
|
void enterEvent(QEvent *e) override;
|
||||||
|
void leaveEvent(QEvent *e) override;
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
void updateSelectedRow();
|
||||||
|
void setSel(int sel, bool scroll = false);
|
||||||
|
|
||||||
|
FieldAutocomplete *_parent;
|
||||||
|
MentionRows *_mrows;
|
||||||
|
HashtagRows *_hrows;
|
||||||
|
BotCommandRows *_brows;
|
||||||
|
StickerPack *_srows;
|
||||||
|
int32 _stickersPerRow, _recentInlineBotsInRows;
|
||||||
|
int32 _sel, _down;
|
||||||
|
bool _mouseSel;
|
||||||
|
QPoint _mousePos;
|
||||||
|
|
||||||
|
bool _overDelete;
|
||||||
|
|
||||||
|
bool _previewShown;
|
||||||
|
|
||||||
|
QTimer _previewTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
|
@ -2740,7 +2740,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
||||||
, _scroll(this, st::historyScroll, false)
|
, _scroll(this, st::historyScroll, false)
|
||||||
, _toHistoryEnd(this, st::historyToEnd)
|
, _toHistoryEnd(this, st::historyToEnd)
|
||||||
, _collapseComments(this)
|
, _collapseComments(this)
|
||||||
, _attachMention(this)
|
, _fieldAutocomplete(this)
|
||||||
, _reportSpamPanel(this)
|
, _reportSpamPanel(this)
|
||||||
, _send(this, lang(lng_send_button), st::btnSend)
|
, _send(this, lang(lng_send_button), st::btnSend)
|
||||||
, _unblock(this, lang(lng_unblock_button), st::btnUnblock)
|
, _unblock(this, lang(lng_unblock_button), st::btnUnblock)
|
||||||
|
@ -2827,7 +2827,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
||||||
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
|
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
|
||||||
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
|
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
|
||||||
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
|
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
|
||||||
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckMentionDropdown()), Qt::QueuedConnection);
|
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);
|
||||||
|
|
||||||
_fieldBarCancel.hide();
|
_fieldBarCancel.hide();
|
||||||
|
|
||||||
|
@ -2849,10 +2849,12 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
||||||
_collapseComments.hide();
|
_collapseComments.hide();
|
||||||
_collapseComments.installEventFilter(this);
|
_collapseComments.installEventFilter(this);
|
||||||
|
|
||||||
_attachMention.hide();
|
_fieldAutocomplete->hide();
|
||||||
connect(&_attachMention, SIGNAL(chosen(QString)), this, SLOT(onMentionHashtagOrBotCommandInsert(QString)));
|
connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
|
||||||
connect(&_attachMention, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
|
connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
|
||||||
_field.installEventFilter(&_attachMention);
|
connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
|
||||||
|
connect(_fieldAutocomplete, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onStickerSend(DocumentData*)));
|
||||||
|
_field.installEventFilter(_fieldAutocomplete);
|
||||||
updateFieldSubmitSettings();
|
updateFieldSubmitSettings();
|
||||||
|
|
||||||
_field.hide();
|
_field.hide();
|
||||||
|
@ -2911,12 +2913,24 @@ void HistoryWidget::onStickersUpdated() {
|
||||||
updateStickersByEmoji();
|
updateStickersByEmoji();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::onMentionHashtagOrBotCommandInsert(QString str) {
|
void HistoryWidget::onMentionInsert(UserData *user) {
|
||||||
if (str.at(0) == '/') { // bot command
|
QString replacement, entityTag;
|
||||||
|
if (user->username.isEmpty()) {
|
||||||
|
replacement = App::peerName(user);
|
||||||
|
entityTag = qsl("mention://peer.") + QString::number(user->id);
|
||||||
|
} else {
|
||||||
|
replacement = '@' + user->username;
|
||||||
|
}
|
||||||
|
_field.insertMentionHashtagOrBotCommand(replacement, entityTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method) {
|
||||||
|
// 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);
|
App::sendBotCommand(_peer, nullptr, str);
|
||||||
setFieldText(_field.getLastText().mid(_field.textCursor().position()));
|
setFieldText(_field.getLastText().mid(_field.textCursor().position()));
|
||||||
} else {
|
} else {
|
||||||
_field.onMentionHashtagOrBotCommandInsert(str);
|
_field.insertMentionHashtagOrBotCommand(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2952,8 +2966,8 @@ void HistoryWidget::updateInlineBotQuery() {
|
||||||
} else {
|
} else {
|
||||||
_emojiPan.queryInlineBot(_inlineBot, _peer, query);
|
_emojiPan.queryInlineBot(_inlineBot, _peer, query);
|
||||||
}
|
}
|
||||||
if (!_attachMention.isHidden()) {
|
if (!_fieldAutocomplete->isHidden()) {
|
||||||
_attachMention.hideStart();
|
_fieldAutocomplete->hideStart();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clearInlineBot();
|
clearInlineBot();
|
||||||
|
@ -2964,13 +2978,13 @@ void HistoryWidget::updateStickersByEmoji() {
|
||||||
int32 len = 0;
|
int32 len = 0;
|
||||||
if (EmojiPtr emoji = emojiFromText(_field.getLastText(), &len)) {
|
if (EmojiPtr emoji = emojiFromText(_field.getLastText(), &len)) {
|
||||||
if (_field.getLastText().size() <= len) {
|
if (_field.getLastText().size() <= len) {
|
||||||
_attachMention.showStickers(emoji);
|
_fieldAutocomplete->showStickers(emoji);
|
||||||
} else {
|
} else {
|
||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!len) {
|
if (!len) {
|
||||||
_attachMention.showStickers(EmojiPtr(0));
|
_fieldAutocomplete->showStickers(EmojiPtr(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3239,8 +3253,8 @@ void HistoryWidget::updateStickers() {
|
||||||
|
|
||||||
void HistoryWidget::notify_botCommandsChanged(UserData *user) {
|
void HistoryWidget::notify_botCommandsChanged(UserData *user) {
|
||||||
if (_peer && (_peer == user || !_peer->isUser())) {
|
if (_peer && (_peer == user || !_peer->isUser())) {
|
||||||
if (_attachMention.clearFilteredBotCommands()) {
|
if (_fieldAutocomplete->clearFilteredBotCommands()) {
|
||||||
onCheckMentionDropdown();
|
onCheckFieldAutocomplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3873,7 +3887,7 @@ bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
|
||||||
return (_attachDragDocument.overlaps(globalRect) ||
|
return (_attachDragDocument.overlaps(globalRect) ||
|
||||||
_attachDragPhoto.overlaps(globalRect) ||
|
_attachDragPhoto.overlaps(globalRect) ||
|
||||||
_attachType.overlaps(globalRect) ||
|
_attachType.overlaps(globalRect) ||
|
||||||
_attachMention.overlaps(globalRect) ||
|
_fieldAutocomplete->overlaps(globalRect) ||
|
||||||
_emojiPan.overlaps(globalRect));
|
_emojiPan.overlaps(globalRect));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3986,7 +4000,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
_botStart.hide();
|
_botStart.hide();
|
||||||
_joinChannel.hide();
|
_joinChannel.hide();
|
||||||
_muteUnmute.hide();
|
_muteUnmute.hide();
|
||||||
_attachMention.hide();
|
_fieldAutocomplete->hide();
|
||||||
_field.hide();
|
_field.hide();
|
||||||
_fieldBarCancel.hide();
|
_fieldBarCancel.hide();
|
||||||
_attachDocument.hide();
|
_attachDocument.hide();
|
||||||
|
@ -4047,7 +4061,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_kbShown = false;
|
_kbShown = false;
|
||||||
_attachMention.hide();
|
_fieldAutocomplete->hide();
|
||||||
_send.hide();
|
_send.hide();
|
||||||
if (_inlineBotCancel) _inlineBotCancel->hide();
|
if (_inlineBotCancel) _inlineBotCancel->hide();
|
||||||
_botStart.hide();
|
_botStart.hide();
|
||||||
|
@ -4071,7 +4085,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} else if (_canSendMessages) {
|
} else if (_canSendMessages) {
|
||||||
onCheckMentionDropdown();
|
onCheckFieldAutocomplete();
|
||||||
if (isBotStart()) {
|
if (isBotStart()) {
|
||||||
if (isBotStart()) {
|
if (isBotStart()) {
|
||||||
_unblock.hide();
|
_unblock.hide();
|
||||||
|
@ -4190,7 +4204,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_attachMention.hide();
|
_fieldAutocomplete->hide();
|
||||||
_send.hide();
|
_send.hide();
|
||||||
if (_inlineBotCancel) _inlineBotCancel->hide();
|
if (_inlineBotCancel) _inlineBotCancel->hide();
|
||||||
_unblock.hide();
|
_unblock.hide();
|
||||||
|
@ -4807,7 +4821,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
|
||||||
_saveDraftStart = getms();
|
_saveDraftStart = getms();
|
||||||
onDraftSave();
|
onDraftSave();
|
||||||
|
|
||||||
if (!_attachMention.isHidden()) _attachMention.hideStart();
|
if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
|
||||||
if (!_attachType.isHidden()) _attachType.hideStart();
|
if (!_attachType.isHidden()) _attachType.hideStart();
|
||||||
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
||||||
|
|
||||||
|
@ -5018,7 +5032,7 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo
|
||||||
_attachDocument.hide();
|
_attachDocument.hide();
|
||||||
_attachPhoto.hide();
|
_attachPhoto.hide();
|
||||||
_attachEmoji.hide();
|
_attachEmoji.hide();
|
||||||
_attachMention.hide();
|
_fieldAutocomplete->hide();
|
||||||
_broadcast.hide();
|
_broadcast.hide();
|
||||||
_silent.hide();
|
_silent.hide();
|
||||||
_kbShow.hide();
|
_kbShow.hide();
|
||||||
|
@ -5826,7 +5840,7 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
|
||||||
}
|
}
|
||||||
int32 onlineCount = 0;
|
int32 onlineCount = 0;
|
||||||
bool onlyMe = true;
|
bool onlyMe = true;
|
||||||
for (MentionRows::const_iterator i = _peer->asChannel()->mgInfo->lastParticipants.cbegin(), e = _peer->asChannel()->mgInfo->lastParticipants.cend(); i != e; ++i) {
|
for (auto i = _peer->asChannel()->mgInfo->lastParticipants.cbegin(), e = _peer->asChannel()->mgInfo->lastParticipants.cend(); i != e; ++i) {
|
||||||
if ((*i)->onlineTill > t) {
|
if ((*i)->onlineTill > t) {
|
||||||
++onlineCount;
|
++onlineCount;
|
||||||
if (onlyMe && (*i) != App::self()) onlyMe = false;
|
if (onlyMe && (*i) != App::self()) onlyMe = false;
|
||||||
|
@ -5937,7 +5951,7 @@ void HistoryWidget::clearInlineBot() {
|
||||||
_field.finishPlaceholder();
|
_field.finishPlaceholder();
|
||||||
}
|
}
|
||||||
_emojiPan.clearInlineBot();
|
_emojiPan.clearInlineBot();
|
||||||
onCheckMentionDropdown();
|
onCheckFieldAutocomplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::inlineBotChanged() {
|
void HistoryWidget::inlineBotChanged() {
|
||||||
|
@ -5967,7 +5981,7 @@ void HistoryWidget::onFieldFocused() {
|
||||||
if (_list) _list->clearSelectedItems(true);
|
if (_list) _list->clearSelectedItems(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::onCheckMentionDropdown() {
|
void HistoryWidget::onCheckFieldAutocomplete() {
|
||||||
if (!_history || _a_show.animating()) return;
|
if (!_history || _a_show.animating()) return;
|
||||||
|
|
||||||
bool start = false;
|
bool start = false;
|
||||||
|
@ -5977,7 +5991,7 @@ void HistoryWidget::onCheckMentionDropdown() {
|
||||||
if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
|
if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
|
||||||
if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
|
if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
|
||||||
}
|
}
|
||||||
_attachMention.showFiltered(_peer, query, start);
|
_fieldAutocomplete->showFiltered(_peer, query, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::updateFieldPlaceholder() {
|
void HistoryWidget::updateFieldPlaceholder() {
|
||||||
|
@ -6451,14 +6465,14 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) {
|
||||||
if (_scroll.y() != st::replyHeight) {
|
if (_scroll.y() != st::replyHeight) {
|
||||||
_scroll.move(0, st::replyHeight);
|
_scroll.move(0, st::replyHeight);
|
||||||
_reportSpamPanel.move(0, st::replyHeight);
|
_reportSpamPanel.move(0, st::replyHeight);
|
||||||
_attachMention.setBoundings(_scroll.geometry());
|
_fieldAutocomplete->setBoundings(_scroll.geometry());
|
||||||
}
|
}
|
||||||
_pinnedBar->cancel.move(width() - _pinnedBar->cancel.width(), 0);
|
_pinnedBar->cancel.move(width() - _pinnedBar->cancel.width(), 0);
|
||||||
_pinnedBar->shadow.setGeometry(0, st::replyHeight, width(), st::lineWidth);
|
_pinnedBar->shadow.setGeometry(0, st::replyHeight, width(), st::lineWidth);
|
||||||
} else if (_scroll.y() != 0) {
|
} else if (_scroll.y() != 0) {
|
||||||
_scroll.move(0, 0);
|
_scroll.move(0, 0);
|
||||||
_reportSpamPanel.move(0, 0);
|
_reportSpamPanel.move(0, 0);
|
||||||
_attachMention.setBoundings(_scroll.geometry());
|
_fieldAutocomplete->setBoundings(_scroll.geometry());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateListSize(false, false, { ScrollChangeAdd, App::main() ? App::main()->contentScrollAddToY() : 0 });
|
updateListSize(false, false, { ScrollChangeAdd, App::main() ? App::main()->contentScrollAddToY() : 0 });
|
||||||
|
@ -6566,7 +6580,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||||
visibleAreaUpdated();
|
visibleAreaUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
_attachMention.setBoundings(_scroll.geometry());
|
_fieldAutocomplete->setBoundings(_scroll.geometry());
|
||||||
_toHistoryEnd.move((width() - _toHistoryEnd.width()) / 2, _scroll.y() + _scroll.height() - _toHistoryEnd.height() - st::historyToEndSkip);
|
_toHistoryEnd.move((width() - _toHistoryEnd.width()) / 2, _scroll.y() + _scroll.height() - _toHistoryEnd.height() - st::historyToEndSkip);
|
||||||
updateCollapseCommentsVisibility();
|
updateCollapseCommentsVisibility();
|
||||||
}
|
}
|
||||||
|
@ -6934,9 +6948,8 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::onFieldTabbed() {
|
void HistoryWidget::onFieldTabbed() {
|
||||||
QString sel = _attachMention.isHidden() ? QString() : _attachMention.getSelected();
|
if (!_fieldAutocomplete->isHidden()) {
|
||||||
if (!sel.isEmpty()) {
|
_fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||||
_field.onMentionHashtagOrBotCommandInsert(sel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7014,7 +7027,7 @@ void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot
|
||||||
Local::writeRecentHashtagsAndBots();
|
Local::writeRecentHashtagsAndBots();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_attachMention.isHidden()) _attachMention.hideStart();
|
if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
|
||||||
if (!_attachType.isHidden()) _attachType.hideStart();
|
if (!_attachType.isHidden()) _attachType.hideStart();
|
||||||
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
||||||
|
|
||||||
|
@ -7171,14 +7184,14 @@ void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti
|
||||||
|
|
||||||
App::historyRegRandom(randomId, newId);
|
App::historyRegRandom(randomId, newId);
|
||||||
|
|
||||||
if (_attachMention.stickersShown()) {
|
if (_fieldAutocomplete->stickersShown()) {
|
||||||
clearFieldText();
|
clearFieldText();
|
||||||
_saveDraftText = true;
|
_saveDraftText = true;
|
||||||
_saveDraftStart = getms();
|
_saveDraftStart = getms();
|
||||||
onDraftSave();
|
onDraftSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_attachMention.isHidden()) _attachMention.hideStart();
|
if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
|
||||||
if (!_attachType.isHidden()) _attachType.hideStart();
|
if (!_attachType.isHidden()) _attachType.hideStart();
|
||||||
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
||||||
|
|
||||||
|
@ -7225,7 +7238,7 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption)
|
||||||
|
|
||||||
App::historyRegRandom(randomId, newId);
|
App::historyRegRandom(randomId, newId);
|
||||||
|
|
||||||
if (!_attachMention.isHidden()) _attachMention.hideStart();
|
if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
|
||||||
if (!_attachType.isHidden()) _attachType.hideStart();
|
if (!_attachType.isHidden()) _attachType.hideStart();
|
||||||
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
|
||||||
|
|
||||||
|
@ -7659,8 +7672,8 @@ void HistoryWidget::onCancel() {
|
||||||
} else {
|
} else {
|
||||||
onFieldBarCancel();
|
onFieldBarCancel();
|
||||||
}
|
}
|
||||||
} else if (!_attachMention.isHidden()) {
|
} else if (!_fieldAutocomplete->isHidden()) {
|
||||||
_attachMention.hideStart();
|
_fieldAutocomplete->hideStart();
|
||||||
} else {
|
} else {
|
||||||
Ui::showChatsList();
|
Ui::showChatsList();
|
||||||
emit cancelled();
|
emit cancelled();
|
||||||
|
@ -7677,7 +7690,7 @@ void HistoryWidget::onFullPeerUpdated(PeerData *data) {
|
||||||
}
|
}
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
}
|
}
|
||||||
onCheckMentionDropdown();
|
onCheckFieldAutocomplete();
|
||||||
updateReportSpamStatus();
|
updateReportSpamStatus();
|
||||||
_list->updateBotInfo();
|
_list->updateBotInfo();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/boxshadow.h"
|
#include "ui/boxshadow.h"
|
||||||
#include "dropdown.h"
|
#include "dropdown.h"
|
||||||
#include "history/history_common.h"
|
#include "history/history_common.h"
|
||||||
|
#include "history/field_autocomplete.h"
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
|
namespace Layout {
|
||||||
|
class ItemBase;
|
||||||
|
} // namespace Layout
|
||||||
|
class Result;
|
||||||
|
} // namespace InlineBots
|
||||||
|
|
||||||
class HistoryWidget;
|
class HistoryWidget;
|
||||||
class HistoryInner : public TWidget, public AbstractTooltipShower {
|
class HistoryInner : public TWidget, public AbstractTooltipShower {
|
||||||
|
@ -483,13 +491,6 @@ enum TextUpdateEventsFlags {
|
||||||
TextUpdateEventsSendTyping = 0x02,
|
TextUpdateEventsSendTyping = 0x02,
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace InlineBots {
|
|
||||||
namespace Layout {
|
|
||||||
class ItemBase;
|
|
||||||
} // namespace Layout
|
|
||||||
class Result;
|
|
||||||
} // namespace InlineBots
|
|
||||||
|
|
||||||
class HistoryWidget : public TWidget, public RPCSender {
|
class HistoryWidget : public TWidget, public RPCSender {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -759,7 +760,6 @@ public slots:
|
||||||
|
|
||||||
void activate();
|
void activate();
|
||||||
void onStickersUpdated();
|
void onStickersUpdated();
|
||||||
void onMentionHashtagOrBotCommandInsert(QString str);
|
|
||||||
void onTextChange();
|
void onTextChange();
|
||||||
|
|
||||||
void onFieldTabbed();
|
void onFieldTabbed();
|
||||||
|
@ -777,7 +777,7 @@ public slots:
|
||||||
|
|
||||||
void onFieldFocused();
|
void onFieldFocused();
|
||||||
void onFieldResize();
|
void onFieldResize();
|
||||||
void onCheckMentionDropdown();
|
void onCheckFieldAutocomplete();
|
||||||
void onScrollTimer();
|
void onScrollTimer();
|
||||||
|
|
||||||
void onForwardSelected();
|
void onForwardSelected();
|
||||||
|
@ -806,6 +806,8 @@ public slots:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
|
void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method);
|
||||||
|
void onMentionInsert(UserData *user);
|
||||||
void onInlineBotCancel();
|
void onInlineBotCancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1003,7 +1005,7 @@ private:
|
||||||
IconedButton _toHistoryEnd;
|
IconedButton _toHistoryEnd;
|
||||||
CollapseButton _collapseComments;
|
CollapseButton _collapseComments;
|
||||||
|
|
||||||
MentionsDropdown _attachMention;
|
ChildWidget<FieldAutocomplete> _fieldAutocomplete;
|
||||||
|
|
||||||
UserData *_inlineBot = nullptr;
|
UserData *_inlineBot = nullptr;
|
||||||
QString _inlineBotUsername;
|
QString _inlineBotUsername;
|
||||||
|
|
|
@ -230,6 +230,8 @@ public:
|
||||||
|
|
||||||
void updateUnreadCounter();
|
void updateUnreadCounter();
|
||||||
|
|
||||||
|
QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon);
|
||||||
|
|
||||||
bool contentOverlapped(const QRect &globalRect);
|
bool contentOverlapped(const QRect &globalRect);
|
||||||
bool contentOverlapped(QWidget *w, QPaintEvent *e) {
|
bool contentOverlapped(QWidget *w, QPaintEvent *e) {
|
||||||
return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size()));
|
return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size()));
|
||||||
|
@ -282,8 +284,6 @@ public slots:
|
||||||
void onLogoutSure();
|
void onLogoutSure();
|
||||||
void updateGlobalMenu(); // for OS X top menu
|
void updateGlobalMenu(); // for OS X top menu
|
||||||
|
|
||||||
QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon);
|
|
||||||
|
|
||||||
void notifyUpdateAllPhotos();
|
void notifyUpdateAllPhotos();
|
||||||
|
|
||||||
void app_activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button);
|
void app_activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button);
|
||||||
|
|
|
@ -851,7 +851,6 @@ namespace {
|
||||||
_psShadowWindows.setColor(_shInactive);
|
_psShadowWindows.setColor(_shInactive);
|
||||||
}
|
}
|
||||||
if (Global::started()) {
|
if (Global::started()) {
|
||||||
QMetaObject::invokeMethod(App::wnd(), "updateCounter", Qt::QueuedConnection);
|
|
||||||
App::wnd()->update();
|
App::wnd()->update();
|
||||||
}
|
}
|
||||||
} return false;
|
} return false;
|
||||||
|
|
|
@ -852,7 +852,6 @@ namespace {
|
||||||
// _psShadowWindows.setColor(_shInactive);
|
// _psShadowWindows.setColor(_shInactive);
|
||||||
//}
|
//}
|
||||||
if (Global::started()) {
|
if (Global::started()) {
|
||||||
QMetaObject::invokeMethod(App::wnd(), "updateCounter", Qt::QueuedConnection);
|
|
||||||
App::wnd()->update();
|
App::wnd()->update();
|
||||||
}
|
}
|
||||||
} return false;
|
} return false;
|
||||||
|
|
|
@ -360,7 +360,7 @@ QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
|
void FlatTextarea::insertMentionHashtagOrBotCommand(const QString &data, const QString &entityTag) {
|
||||||
QTextCursor c(textCursor());
|
QTextCursor c(textCursor());
|
||||||
int32 pos = c.position();
|
int32 pos = c.position();
|
||||||
|
|
||||||
|
@ -383,17 +383,16 @@ void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
|
||||||
if ((i == pos - p || (t.at(i - 1) == '/' ? t.at(i).isLetterOrNumber() : t.at(i).isLetter()) || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
if ((i == pos - p || (t.at(i - 1) == '/' ? t.at(i).isLetterOrNumber() : t.at(i).isLetter()) || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
||||||
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
|
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
|
||||||
int till = p + i;
|
int till = p + i;
|
||||||
for (; (till < e) && (till - p - i + 1 < str.size()); ++till) {
|
for (; (till < e) && (till - p - i + 1 < data.size()); ++till) {
|
||||||
if (t.at(till - p).toLower() != str.at(till - p - i + 1).toLower()) {
|
if (t.at(till - p).toLower() != data.at(till - p - i + 1).toLower()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (till - p - i + 1 == str.size() && till < e && t.at(till - p) == ' ') {
|
if (till - p - i + 1 == data.size() && till < e && t.at(till - p) == ' ') {
|
||||||
++till;
|
++till;
|
||||||
}
|
}
|
||||||
c.setPosition(till, QTextCursor::KeepAnchor);
|
c.setPosition(till, QTextCursor::KeepAnchor);
|
||||||
c.insertText(str + ' ');
|
break;
|
||||||
return;
|
|
||||||
} else if ((i == pos - p || t.at(i).isLetter()) && t.at(i - 1) == '@' && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
|
} else if ((i == pos - p || t.at(i).isLetter()) && t.at(i - 1) == '@' && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
|
||||||
mentionInCommand = true;
|
mentionInCommand = true;
|
||||||
--i;
|
--i;
|
||||||
|
@ -406,7 +405,14 @@ void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
c.insertText(str + ' ');
|
if (entityTag.isEmpty()) {
|
||||||
|
c.insertText(data + ' ');
|
||||||
|
} else {
|
||||||
|
QTextCharFormat fmt;
|
||||||
|
fmt.setForeground(st::defaultTextStyle.linkFg);
|
||||||
|
c.insertText(data, fmt);
|
||||||
|
c.insertText(qsl(" "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const {
|
void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const {
|
||||||
|
|
|
@ -94,6 +94,8 @@ public:
|
||||||
|
|
||||||
void setTextFast(const QString &text, bool clearUndoHistory = true);
|
void setTextFast(const QString &text, bool clearUndoHistory = true);
|
||||||
|
|
||||||
|
void insertMentionHashtagOrBotCommand(const QString &data, const QString &entityTag = QString());
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void onTouchTimer();
|
void onTouchTimer();
|
||||||
|
@ -104,8 +106,6 @@ public slots:
|
||||||
void onUndoAvailable(bool avail);
|
void onUndoAvailable(bool avail);
|
||||||
void onRedoAvailable(bool avail);
|
void onRedoAvailable(bool avail);
|
||||||
|
|
||||||
void onMentionHashtagOrBotCommandInsert(QString str);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void resized();
|
void resized();
|
||||||
|
|
|
@ -270,6 +270,10 @@
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Debug\moc_field_autocomplete.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="GeneratedFiles\Debug\moc_fileuploader.cpp">
|
<ClCompile Include="GeneratedFiles\Debug\moc_fileuploader.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
@ -557,6 +561,10 @@
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Deploy\moc_field_autocomplete.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="GeneratedFiles\Deploy\moc_fileuploader.cpp">
|
<ClCompile Include="GeneratedFiles\Deploy\moc_fileuploader.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
@ -870,6 +878,10 @@
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Release\moc_field_autocomplete.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="GeneratedFiles\Release\moc_fileuploader.cpp">
|
<ClCompile Include="GeneratedFiles\Release\moc_fileuploader.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
@ -1086,6 +1098,7 @@
|
||||||
<ClCompile Include="SourceFiles\fileuploader.cpp" />
|
<ClCompile Include="SourceFiles\fileuploader.cpp" />
|
||||||
<ClCompile Include="SourceFiles\history.cpp" />
|
<ClCompile Include="SourceFiles\history.cpp" />
|
||||||
<ClCompile Include="SourceFiles\historywidget.cpp" />
|
<ClCompile Include="SourceFiles\historywidget.cpp" />
|
||||||
|
<ClCompile Include="SourceFiles\history\field_autocomplete.cpp" />
|
||||||
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_internal.cpp" />
|
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_internal.cpp" />
|
||||||
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_item.cpp" />
|
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_item.cpp" />
|
||||||
<ClCompile Include="SourceFiles\inline_bots\inline_bot_result.cpp" />
|
<ClCompile Include="SourceFiles\inline_bots\inline_bot_result.cpp" />
|
||||||
|
@ -1266,6 +1279,20 @@
|
||||||
<ClInclude Include="SourceFiles\dialogs\dialogs_layout.h" />
|
<ClInclude Include="SourceFiles\dialogs\dialogs_layout.h" />
|
||||||
<ClInclude Include="SourceFiles\dialogs\dialogs_list.h" />
|
<ClInclude Include="SourceFiles\dialogs\dialogs_list.h" />
|
||||||
<ClInclude Include="SourceFiles\dialogs\dialogs_row.h" />
|
<ClInclude Include="SourceFiles\dialogs\dialogs_row.h" />
|
||||||
|
<CustomBuild Include="SourceFiles\history\field_autocomplete.h">
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||||
|
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing field_autocomplete.h...</Message>
|
||||||
|
<Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||||
|
<Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||||
|
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing field_autocomplete.h...</Message>
|
||||||
|
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||||
|
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include"</Command>
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||||
|
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing field_autocomplete.h...</Message>
|
||||||
|
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||||
|
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
|
||||||
|
</CustomBuild>
|
||||||
<ClInclude Include="SourceFiles\history\history_common.h" />
|
<ClInclude Include="SourceFiles\history\history_common.h" />
|
||||||
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_internal.h" />
|
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_internal.h" />
|
||||||
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_item.h" />
|
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_item.h" />
|
||||||
|
|
|
@ -1104,6 +1104,18 @@
|
||||||
<ClCompile Include="SourceFiles\ui\style\style_core_icon.cpp">
|
<ClCompile Include="SourceFiles\ui\style\style_core_icon.cpp">
|
||||||
<Filter>SourceFiles\ui\style</Filter>
|
<Filter>SourceFiles\ui\style</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Deploy\moc_field_autocomplete.cpp">
|
||||||
|
<Filter>GeneratedFiles\Deploy</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="SourceFiles\history\field_autocomplete.cpp">
|
||||||
|
<Filter>SourceFiles\history</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Debug\moc_field_autocomplete.cpp">
|
||||||
|
<Filter>GeneratedFiles\Debug</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GeneratedFiles\Release\moc_field_autocomplete.cpp">
|
||||||
|
<Filter>GeneratedFiles\Release</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="SourceFiles\stdafx.h">
|
<ClInclude Include="SourceFiles\stdafx.h">
|
||||||
|
@ -1513,6 +1525,9 @@
|
||||||
<CustomBuild Include="SourceFiles\core\basic_types.h">
|
<CustomBuild Include="SourceFiles\core\basic_types.h">
|
||||||
<Filter>SourceFiles\core</Filter>
|
<Filter>SourceFiles\core</Filter>
|
||||||
</CustomBuild>
|
</CustomBuild>
|
||||||
|
<CustomBuild Include="SourceFiles\history\field_autocomplete.h">
|
||||||
|
<Filter>SourceFiles\history</Filter>
|
||||||
|
</CustomBuild>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Resources\langs\lang_it.strings">
|
<None Include="Resources\langs\lang_it.strings">
|
||||||
|
|
Loading…
Reference in New Issue