add users to groups by usernames, copy username from context menu in profile, 0.7.25.dev version

This commit is contained in:
John Preston 2015-03-25 18:42:15 +03:00
parent 489b151d49
commit 8c7a35c973
18 changed files with 963 additions and 245 deletions

View File

@ -1,9 +1,9 @@
@echo OFF @echo OFF
set "AppVersion=7024" set "AppVersion=7025"
set "AppVersionStrSmall=0.7.24" set "AppVersionStrSmall=0.7.25"
set "AppVersionStr=0.7.24" set "AppVersionStr=0.7.25"
set "AppVersionStrFull=0.7.24.0" set "AppVersionStrFull=0.7.25.0"
set "DevChannel=1" set "DevChannel=1"
if %DevChannel% neq 0 goto preparedev if %DevChannel% neq 0 goto preparedev

View File

@ -313,6 +313,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_create_group_next" = "Next"; "lng_create_group_next" = "Next";
"lng_create_group_title" = "New Group"; "lng_create_group_title" = "New Group";
"lng_failed_add_participant" = "Could not add user. Try again later.";
"lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?"; "lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?";
"lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone."; "lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
@ -489,8 +491,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
"lng_new_version_minor" = "— Bug fixes and other minor improvements"; "lng_new_version_minor" = "— Bug fixes and other minor improvements";
"lng_new_version7022" = "— Reply to specific messages in groups\n— Mention @usernames in groups to notify multiple users"; "lng_new_version7026" = "— Add a comment before forwarded messages\n— Hashtags suggestions in new message and search fields (based on recent searches)\n— Button to jump back to a reply after viewing the original message\n— Add people to groups by username";
"lng_new_version7022_appstore" = "— Reply to specific messages in groups\n— Mention @usernames in groups to notify multiple users"; "lng_new_version7026_appstore" = "— Add a comment before forwarded messages\n— Hashtags suggestions in new message and search fields (based on recent searches)\n— Button to jump back to a reply after viewing the original message\n— Add people to groups by username";
"lng_menu_insert_unicode" = "Insert Unicode control character"; "lng_menu_insert_unicode" = "Insert Unicode control character";

View File

@ -653,10 +653,10 @@ void Application::checkMapVersion() {
psRegisterCustomScheme(); psRegisterCustomScheme();
if (Local::oldMapVersion()) { if (Local::oldMapVersion()) {
QString versionFeatures; QString versionFeatures;
if (DevChannel && Local::oldMapVersion() < 7024) { if (DevChannel && Local::oldMapVersion() < 7025) {
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Forward messages with comment or sticker before them\n\xe2\x80\x94 Recent hashtags autocomplete in message and search fields\n\xe2\x80\x94 Move from reply to original message and back"); versionFeatures = QString::fromUtf8("\xe2\x80\x94 Add people to groups by username\n\xe2\x80\x94 Copy @username from profile context menu").replace('@', qsl("@") + QChar(0x200D));
} else if (!DevChannel && Local::oldMapVersion() < 7023) { } else if (!DevChannel && Local::oldMapVersion() < 7026) {
versionFeatures = lang(lng_new_version7022).trimmed().replace('@', qsl("@") + QChar(0x200D)); versionFeatures = lang(lng_new_version7026).trimmed();
} }
if (!versionFeatures.isEmpty()) { if (!versionFeatures.isEmpty()) {
versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog"));

View File

@ -28,6 +28,8 @@ _sel(0),
_filteredSel(-1), _filteredSel(-1),
_mouseSel(false), _mouseSel(false),
_selCount(0), _selCount(0),
_searching(false),
_byUsernameSel(-1),
_addContactLnk(this, lang(lng_add_contact_button)) { _addContactLnk(this, lang(lng_add_contact_button)) {
connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact()));
@ -101,6 +103,16 @@ void AddParticipantInner::loadProfilePhotos(int32 yFrom) {
preloadFrom->history->peer->photo->load(); preloadFrom->history->peer->photo->load();
} }
} }
yFrom -= _contacts->list.count * rh + st::searchedBarHeight;
yTo -= _contacts->list.count * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsername.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsername.size()) to = _byUsername.size();
for (; from < to; ++from) {
_byUsername[from]->photo->load();
}
}
} else if (!_filtered.isEmpty()) { } else if (!_filtered.isEmpty()) {
int32 from = yFrom / rh; int32 from = yFrom / rh;
if (from < 0) from = 0; if (from < 0) from = 0;
@ -112,6 +124,16 @@ void AddParticipantInner::loadProfilePhotos(int32 yFrom) {
_filtered[from]->history->peer->photo->load(); _filtered[from]->history->peer->photo->load();
} }
} }
yFrom -= _filtered.size() * rh + st::searchedBarHeight;
yTo -= _filtered.size() * rh + st::searchedBarHeight;
from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsernameFiltered.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsernameFiltered.size()) to = _byUsernameFiltered.size();
for (; from < to; ++from) {
_byUsernameFiltered[from]->photo->load();
}
}
} }
} }
@ -122,7 +144,7 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro
ContactsData::const_iterator i = _contactsData.constFind(user); ContactsData::const_iterator i = _contactsData.constFind(user);
if (i == _contactsData.cend()) { if (i == _contactsData.cend()) {
_contactsData.insert(user, data = new ContactData()); _contactsData.insert(user, data = new ContactData());
data->inchat = _chat->participants.constFind(user) != _chat->participants.cend(); data->inchat = _chat->participants.contains(user);
data->check = false; data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions); data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user, _time); data->online = App::onlineText(user, _time);
@ -134,12 +156,9 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro
return data; return data;
} }
void AddParticipantInner::paintDialog(QPainter &p, DialogRow *row, bool sel) { void AddParticipantInner::paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel) {
int32 left = st::profileListPadding.width(); int32 left = st::profileListPadding.width();
UserData *user = row->history->peer->asUser();
ContactData *data = contactData(row);
if (data->inchat || data->check || _selCount + _chat->count >= cMaxGroupCount()) { if (data->inchat || data->check || _selCount + _chat->count >= cMaxGroupCount()) {
sel = false; sel = false;
} }
@ -161,13 +180,29 @@ void AddParticipantInner::paintDialog(QPainter &p, DialogRow *row, bool sel) {
p.drawPixmap(QPoint(width() - st::profileCheckRect.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::profileCheckRect.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), (data->check ? st::profileCheckActiveRect : st::profileCheckRect)); p.drawPixmap(QPoint(width() - st::profileCheckRect.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::profileCheckRect.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), (data->check ? st::profileCheckActiveRect : st::profileCheckRect));
} }
bool uname = (data->online.at(0) == '@');
p.setFont(st::profileSubFont->f); p.setFont(st::profileSubFont->f);
if (data->inchat || data->check) { if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) {
p.setPen(st::white->p); int32 availw = width() - (left + st::profileListPhotoSize + st::profileListPadding.width() * 2);
QString first = '@' + user->username.mid(0, _lastQuery.size()), second = user->username.mid(_lastQuery.size());
int32 w = st::profileSubFont->m.width(first);
if (w >= availw || second.isEmpty()) {
p.setPen(st::profileOnlineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, st::profileSubFont->m.elidedText(first, Qt::ElideRight, availw));
} else {
p.setPen(st::profileOnlineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, first);
p.setPen(st::profileOfflineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width() + w, st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, st::profileSubFont->m.elidedText(second, Qt::ElideRight, availw - w));
}
} else { } else {
p.setPen((App::onlineColorUse(user->onlineTill, _time) ? st::profileOnlineColor : st::profileOfflineColor)->p); if (data->inchat || data->check) {
p.setPen(st::white->p);
} else {
p.setPen(((uname || App::onlineColorUse(user->onlineTill, _time)) ? st::profileOnlineColor : st::profileOfflineColor)->p);
}
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, data->online);
} }
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, data->online);
} }
void AddParticipantInner::paintEvent(QPaintEvent *e) { void AddParticipantInner::paintEvent(QPaintEvent *e) {
@ -177,40 +212,85 @@ void AddParticipantInner::paintEvent(QPaintEvent *e) {
_time = unixtime(); _time = unixtime();
p.fillRect(r, st::white->b); p.fillRect(r, st::white->b);
int32 yFrom = r.top(); int32 yFrom = r.top(), yTo = r.bottom();
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
if (_contacts->list.count) { if (_contacts->list.count || !_byUsername.isEmpty()) {
_contacts->list.adjustCurrent(yFrom, rh); if (_contacts->list.count) {
_contacts->list.adjustCurrent(yFrom, rh);
DialogRow *drawFrom = _contacts->list.current; DialogRow *drawFrom = _contacts->list.current;
p.translate(0, drawFrom->pos * rh); p.translate(0, drawFrom->pos * rh);
while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) { while (drawFrom != _contacts->list.end && drawFrom->pos * rh < yTo) {
paintDialog(p, drawFrom, (drawFrom == _sel)); paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel));
p.translate(0, rh); p.translate(0, rh);
drawFrom = drawFrom->next; drawFrom = drawFrom->next;
}
}
if (!_byUsername.isEmpty()) {
p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b);
p.setFont(st::searchedBarFont->f);
p.setPen(st::searchedBarColor->p);
p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center);
p.translate(0, st::searchedBarHeight);
yFrom -= _contacts->list.count * rh + st::searchedBarHeight;
yTo -= _contacts->list.count * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsername.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsername.size()) to = _byUsername.size();
p.translate(0, from * rh);
for (; from < to; ++from) {
paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
p.translate(0, rh);
}
}
} }
} else { } else {
p.setFont(st::noContactsFont->f); p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p); p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_contacts : lng_contacts_loading), style::al_center); p.drawText(QRect(0, 0, width(), st::noContactsHeight - ((cContactsReceived() && !_searching) ? st::noContactsFont->height : 0)), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_loading), style::al_center);
} }
} else { } else {
if (_filtered.isEmpty()) { if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
p.setFont(st::noContactsFont->f); p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p); p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_not_found), style::al_center); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_loading), style::al_center);
} else { } else {
int32 from = yFrom / rh; if (!_filtered.isEmpty()) {
if (from < 0) from = 0; int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _filtered.size()) { if (from < _filtered.size()) {
int32 to = (r.bottom() / rh) + 1; int32 to = (yTo / rh) + 1;
if (to > _filtered.size()) to = _filtered.size(); if (to > _filtered.size()) to = _filtered.size();
p.translate(0, from * rh); p.translate(0, from * rh);
for (; from < to; ++from) { for (; from < to; ++from) {
paintDialog(p, _filtered[from], (_filteredSel == from)); paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from));
p.translate(0, rh); p.translate(0, rh);
}
}
}
if (!_byUsernameFiltered.isEmpty()) {
p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b);
p.setFont(st::searchedBarFont->f);
p.setPen(st::searchedBarColor->p);
p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center);
p.translate(0, st::searchedBarHeight);
yFrom -= _filtered.size() * rh + st::searchedBarHeight;
yTo -= _filtered.size() * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsernameFiltered.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsernameFiltered.size()) to = _byUsernameFiltered.size();
p.translate(0, from * rh);
for (; from < to; ++from) {
paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
p.translate(0, rh);
}
} }
} }
} }
@ -223,7 +303,11 @@ void AddParticipantInner::enterEvent(QEvent *e) {
void AddParticipantInner::leaveEvent(QEvent *e) { void AddParticipantInner::leaveEvent(QEvent *e) {
setMouseTracking(false); setMouseTracking(false);
updateSel(); if (_sel || _filteredSel >= 0 || _byUsernameSel >= 0) {
_sel = 0;
_filteredSel = _byUsernameSel = -1;
parentWidget()->update();
}
} }
void AddParticipantInner::mouseMoveEvent(QMouseEvent *e) { void AddParticipantInner::mouseMoveEvent(QMouseEvent *e) {
@ -245,30 +329,112 @@ void AddParticipantInner::chooseParticipant() {
_time = unixtime(); _time = unixtime();
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
if (!_sel || contactData(_sel)->inchat) return; if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
changeCheckState(_sel); if (d_byUsername[_byUsernameSel]->inchat) return;
changeCheckState(d_byUsername[_byUsernameSel]);
} else {
if (!_sel || contactData(_sel)->inchat) return;
changeCheckState(_sel);
}
} else { } else {
if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->inchat) return; if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
if (d_byUsernameFiltered[_byUsernameSel]->inchat) return;
changeCheckState(d_byUsernameFiltered[_byUsernameSel]);
DialogRow *row = _filtered[_filteredSel]; ContactData *moving = d_byUsernameFiltered[_byUsernameSel];
changeCheckState(row); int32 i = 0, l = d_byUsername.size();
for (; i < l; ++i) {
PeerData *peer = row->history->peer; if (d_byUsername[i] == moving) {
break;
}
}
if (i == l) {
d_byUsername.push_back(moving);
_byUsername.push_back(_byUsernameFiltered[_byUsernameSel]);
for (i = 0, l = _byUsernameDatas.size(); i < l;) {
if (_byUsernameDatas[i] == moving) {
_byUsernameDatas.removeAt(i);
--l;
} else {
++i;
}
}
}
} else {
if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->inchat) return;
changeCheckState(_filtered[_filteredSel]);
}
emit selectAllQuery(); emit selectAllQuery();
} }
parentWidget()->update(); parentWidget()->update();
} }
void AddParticipantInner::changeCheckState(DialogRow *row) { void AddParticipantInner::changeCheckState(DialogRow *row) {
if (contactData(row)->check) { changeCheckState(contactData(row));
contactData(row)->check = false; }
void AddParticipantInner::changeCheckState(ContactData *data) {
if (data->check) {
data->check = false;
--_selCount; --_selCount;
} else if (_selCount + _chat->count < cMaxGroupCount()) { } else if (_selCount + _chat->count < cMaxGroupCount()) {
contactData(row)->check = true; data->check = true;
++_selCount; ++_selCount;
} }
} }
void AddParticipantInner::peopleReceived(const QString &query, const QVector<MTPContactFound> &people) {
_lastQuery = query.toLower().trimmed();
if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
int32 already = _byUsernameFiltered.size();
_byUsernameFiltered.reserve(already + people.size());
d_byUsernameFiltered.reserve(already + people.size());
for (QVector<MTPContactFound>::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) {
int32 uid = i->c_contactFound().vuser_id.v, j = 0;
for (; j < already; ++j) {
if (_byUsernameFiltered[j]->id == uid) break;
}
if (j == already) {
UserData *u = App::user(uid);
ContactData *d = new ContactData();
_byUsernameDatas.push_back(d);
d->inchat = _chat->participants.contains(u);
d->check = false;
d->name.setText(st::profileListNameFont, u->name, _textNameOptions);
d->online = '@' + u->username;
_byUsernameFiltered.push_back(u);
d_byUsernameFiltered.push_back(d);
}
}
_searching = false;
refresh();
}
void AddParticipantInner::refresh() {
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) {
if (_contacts->list.count || !_byUsername.isEmpty()) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), (_contacts->list.count * rh) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * rh)));
} else {
if (cContactsReceived()) {
if (_addContactLnk.isHidden()) _addContactLnk.show();
} else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
}
resize(width(), st::noContactsHeight);
}
} else {
if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), st::noContactsHeight);
} else {
resize(width(), (_filtered.size() * rh) + (_byUsernameFiltered.isEmpty() ? 0 : (st::searchedBarHeight + _byUsernameFiltered.size() * rh)));
}
}
}
ChatData *AddParticipantInner::chat() { ChatData *AddParticipantInner::chat() {
return _chat; return _chat;
} }
@ -281,6 +447,11 @@ QVector<UserData*> AddParticipantInner::selected() {
result.push_back(i.key()); result.push_back(i.key());
} }
} }
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
if (d_byUsername[i]->check) {
result.push_back(_byUsername[i]);
}
}
return result; return result;
} }
@ -288,23 +459,31 @@ void AddParticipantInner::updateSel() {
if (!_mouseSel) return; if (!_mouseSel) return;
QPoint p(mapFromGlobal(_lastMousePos)); QPoint p(mapFromGlobal(_lastMousePos));
bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos));
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
DialogRow *newSel = rect().contains(p) ? _contacts->list.rowAtY(p.y(), rh) : 0; DialogRow *newSel = (in && (p.y() >= 0) && (p.y() < _contacts->list.count * rh)) ? _contacts->list.rowAtY(p.y(), rh) : 0;
if (newSel != _sel) { int32 byUsernameSel = (in && p.y() >= _contacts->list.count * rh + st::searchedBarHeight) ? ((p.y() - _contacts->list.count * rh - st::searchedBarHeight) / rh) : -1;
if (byUsernameSel >= _byUsername.size()) byUsernameSel = -1;
if (newSel != _sel || byUsernameSel != _byUsernameSel) {
_sel = newSel; _sel = newSel;
_byUsernameSel = byUsernameSel;
parentWidget()->update(); parentWidget()->update();
} }
} else { } else {
int32 newFilteredSel = (p.y() >= 0 && rect().contains(p)) ? (p.y() / rh) : -1; int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * rh) ? (p.y() / rh) : -1;
if (newFilteredSel != _filteredSel) { int32 byUsernameSel = (in && p.y() >= _filtered.size() * rh + st::searchedBarHeight) ? ((p.y() - _filtered.size() * rh - st::searchedBarHeight) / rh) : -1;
if (byUsernameSel >= _byUsernameFiltered.size()) byUsernameSel = -1;
if (newFilteredSel != _filteredSel || byUsernameSel != _byUsernameSel) {
_filteredSel = newFilteredSel; _filteredSel = newFilteredSel;
_byUsernameSel = byUsernameSel;
parentWidget()->update(); parentWidget()->update();
} }
} }
} }
void AddParticipantInner::updateFilter(QString filter) { void AddParticipantInner::updateFilter(QString filter) {
_lastQuery = filter.toLower().trimmed();
filter = textSearchKey(filter); filter = textSearchKey(filter);
_time = unixtime(); _time = unixtime();
@ -324,22 +503,32 @@ void AddParticipantInner::updateFilter(QString filter) {
if (_filter != filter) { if (_filter != filter) {
int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2); int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2);
_filter = filter; _filter = filter;
_byUsernameFiltered.clear();
d_byUsernameFiltered.clear();
for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) {
delete _byUsernameDatas[i];
}
_byUsernameDatas.clear();
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
_sel = 0;
if (_contacts->list.count) { if (_contacts->list.count) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), _contacts->list.count * rh);
_sel = _contacts->list.begin; _sel = _contacts->list.begin;
while (_sel->next->next &&& contactData(_sel)->inchat) { while (_sel->next->next &&& contactData(_sel)->inchat) {
_sel = _sel->next; _sel = _sel->next;
} }
} else {
resize(width(), st::noContactsHeight);
if (cContactsReceived()) {
if (_addContactLnk.isHidden()) _addContactLnk.show();
} else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
}
} }
if (!_sel && !_byUsername.isEmpty()) {
_byUsernameSel = 0;
while (_byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat) {
++_byUsernameSel;
}
if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1;
} else {
_byUsernameSel = -1;
}
refresh();
} else { } else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide(); if (!_addContactLnk.isHidden()) _addContactLnk.hide();
QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi; QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
@ -381,17 +570,48 @@ void AddParticipantInner::updateFilter(QString filter) {
} }
} }
} }
_byUsernameFiltered.reserve(_byUsername.size());
d_byUsernameFiltered.reserve(d_byUsername.size());
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
const PeerData::Names &names(_byUsername[i]->names);
PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
for (fi = fb; fi != fe; ++fi) {
QString filterName(*fi);
for (ni = nb; ni != ne; ++ni) {
if (ni->startsWith(*fi)) {
break;
}
}
if (ni == ne) {
break;
}
}
if (fi == fe) {
_byUsernameFiltered.push_back(_byUsername[i]);
d_byUsernameFiltered.push_back(d_byUsername[i]);
}
}
} }
_filteredSel = _filtered.isEmpty() ? -1 : 0; _filteredSel = -1;
while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) { if (!_filtered.isEmpty()) {
++_filteredSel; for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->inchat;) {
++_filteredSel;
}
if (_filteredSel == _filtered.size()) _filteredSel = -1;
}
_byUsernameSel = -1;
if (_filteredSel < 0 && !_byUsernameFiltered.isEmpty()) {
for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->inchat;) {
++_byUsernameSel;
}
if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1;
} }
if (!_filtered.isEmpty()) { refresh();
resize(width(), _filtered.size() * rh);
} else { _searching = true;
resize(width(), st::noContactsHeight); emit searchByUsername();
}
} }
if (parentWidget()) parentWidget()->update(); if (parentWidget()) parentWidget()->update();
loadProfilePhotos(0); loadProfilePhotos(0);
@ -427,7 +647,13 @@ void AddParticipantInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newR
} }
AddParticipantInner::~AddParticipantInner() { AddParticipantInner::~AddParticipantInner() {
for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) { for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
delete *i;
}
for (ByUsernameDatas::const_iterator i = d_byUsername.cbegin(), e = d_byUsername.cend(); i != e; ++i) {
delete *i;
}
for (ByUsernameDatas::const_iterator i = _byUsernameDatas.cbegin(), e = _byUsernameDatas.cend(); i != e; ++i) {
delete *i; delete *i;
} }
} }
@ -441,82 +667,106 @@ void AddParticipantInner::selectSkip(int32 dir) {
_mouseSel = false; _mouseSel = false;
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
int cur = 0;
if (_sel) { if (_sel) {
if (dir > 0) { for (DialogRow *i = _contacts->list.begin; i != _sel; i = i->next) {
while (dir && _sel->next->next) { ++cur;
_sel = _sel->next; }
--dir; } else {
cur = (_byUsernameSel >= 0) ? (_contacts->list.count + _byUsernameSel) : -1;
}
cur += dir;
if (cur <= 0) {
_sel = _contacts->list.count ? _contacts->list.begin : 0;
_byUsernameSel = (!_contacts->list.count && !_byUsername.isEmpty()) ? 0 : -1;
} else if (cur >= _contacts->list.count) {
_sel = 0;
_byUsernameSel = cur - _contacts->list.count;
if (_byUsernameSel >= _byUsername.size()) _byUsernameSel = _byUsername.size() - 1;
} else {
for (_sel = _contacts->list.begin; cur; _sel = _sel->next) {
--cur;
}
_byUsernameSel = -1;
}
if (dir > 0) {
while (_sel && _sel->next && contactData(_sel)->inchat) {
_sel = _sel->next;
}
if (!_sel || !_sel->next) {
_sel = 0;
if (!_byUsername.isEmpty()) {
if (_byUsernameSel < 0) _byUsernameSel = 0;
for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat;) {
++_byUsernameSel;
}
if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1;
} }
while (contactData(_sel)->inchat && _sel->next->next) { }
_sel = _sel->next; } else {
} while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->inchat) {
if (contactData(_sel)->inchat) { --_byUsernameSel;
while (contactData(_sel)->inchat && _sel->prev) { }
if (_byUsernameSel < 0) {
if (_contacts->list.count) {
if (!_sel) _sel = _contacts->list.end->prev;
for (; _sel && contactData(_sel)->inchat;) {
_sel = _sel->prev; _sel = _sel->prev;
} }
} }
} else {
while (dir && _sel->prev) {
_sel = _sel->prev;
++dir;
}
while (contactData(_sel)->inchat && _sel->prev) {
_sel = _sel->prev;
}
if (contactData(_sel)->inchat) {
while (contactData(_sel)->inchat && _sel->next->next) {
_sel = _sel->next;
}
}
}
} else if (dir > 0 && _contacts->list.count) {
_sel = _contacts->list.begin;
while (contactData(_sel)->inchat && _sel->next->next) {
_sel = _sel->next;
} }
} }
if (_sel) { if (_sel) {
if (contactData(_sel)->inchat) { emit mustScrollTo(_sel->pos * rh, (_sel->pos + 1) * rh);
_sel = 0; } else if (_byUsernameSel >= 0) {
} else { emit mustScrollTo((_contacts->list.count + _byUsernameSel) * rh + st::searchedBarHeight, (_contacts->list.count + _byUsernameSel + 1) * rh + st::searchedBarHeight);
emit mustScrollTo(_sel->pos * rh, (_sel->pos + 1) * rh);
}
} }
} else { } else {
int cur = (_filteredSel >= 0) ? _filteredSel : ((_byUsernameSel >= 0) ? (_filtered.size() + _byUsernameSel) : -1);
cur += dir;
if (cur <= 0) {
_filteredSel = _filtered.isEmpty() ? -1 : 0;
_byUsernameSel = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1;
} else if (cur >= _filtered.size()) {
_filteredSel = -1;
_byUsernameSel = cur - _filtered.size();
if (_byUsernameSel >= _byUsernameFiltered.size()) _byUsernameSel = _byUsernameFiltered.size() - 1;
} else {
_filteredSel = cur;
_byUsernameSel = -1;
}
if (dir > 0) { if (dir > 0) {
if (_filteredSel < 0 && dir > 1) { while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->inchat) {
_filteredSel = 0;
}
_filteredSel += dir;
while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) {
++_filteredSel; ++_filteredSel;
} }
if (_filteredSel >= _filtered.size()) { if (_filteredSel < 0 || _filteredSel >= _filtered.size()) {
_filteredSel = _filtered.size() - 1; _filteredSel = -1;
} if (!_byUsernameFiltered.isEmpty()) {
while (_filteredSel > 0 && contactData(_filtered[_filteredSel])->inchat) { if (_byUsernameSel < 0) _byUsernameSel = 0;
--_filteredSel; for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->inchat;) {
} ++_byUsernameSel;
} else if (_filteredSel > 0) { }
_filteredSel += dir; if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1;
if (_filteredSel < 0) {
_filteredSel = 0;
}
if (_filteredSel < _filtered.size() - 1) {
while (_filteredSel > 0 && contactData(_filtered[_filteredSel])->inchat) {
--_filteredSel;
} }
} }
while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) { } else {
++_filteredSel; while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->inchat) {
--_byUsernameSel;
}
if (_byUsernameSel < 0) {
if (!_filtered.isEmpty()) {
if (_filteredSel < 0) _filteredSel = _filtered.size() - 1;
for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->inchat;) {
--_filteredSel;
}
}
} }
} }
if (_filteredSel >= 0) { if (_filteredSel >= 0) {
if (contactData(_filtered[_filteredSel])->inchat) { emit mustScrollTo(_filteredSel * rh, (_filteredSel + 1) * rh);
_filteredSel = -1; } else if (_byUsernameSel >= 0) {
} else { int skip = _filtered.size() * rh + st::searchedBarHeight;
emit mustScrollTo(_filteredSel * rh, (_filteredSel + 1) * rh); emit mustScrollTo(skip + _byUsernameSel * rh, skip + (_byUsernameSel + 1) * rh);
}
} }
} }
parentWidget()->update(); parentWidget()->update();
@ -534,7 +784,7 @@ AddParticipantBox::AddParticipantBox(ChatData *chat) :
_filter(this, st::contactsFilter, lang(lng_participant_filter)), _filter(this, st::contactsFilter, lang(lng_participant_filter)),
_invite(this, lang(lng_participant_invite), st::btnSelectDone), _invite(this, lang(lng_participant_invite), st::btnSelectDone),
_cancel(this, lang(lng_cancel), st::btnSelectCancel), _cancel(this, lang(lng_cancel), st::btnSelectCancel),
_hiding(false), a_opacity(0, 1), af_opacity(anim::linear) { _hiding(false), a_opacity(0, 1) {
_width = st::participantWidth; _width = st::participantWidth;
_height = App::wnd()->height() - st::boxPadding.top() - st::boxPadding.bottom(); _height = App::wnd()->height() - st::boxPadding.top() - st::boxPadding.bottom();
@ -553,12 +803,81 @@ AddParticipantBox::AddParticipantBox(ChatData *chat) :
connect(&_filter, SIGNAL(cancelled()), this, SLOT(onClose())); connect(&_filter, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int)));
connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll())); connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll()));
connect(&_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
_searchTimer.setSingleShot(true);
connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
showAll(); showAll();
_cache = myGrab(this, rect()); _cache = myGrab(this, rect());
hideAll(); hideAll();
} }
bool AddParticipantBox::onSearchByUsername(bool searchCache) {
QString q = _filter.text().trimmed();
if (q.isEmpty()) {
if (_peopleRequest) {
_peopleRequest = 0;
}
return true;
}
if (q.size() >= MinUsernameLength) {
if (searchCache) {
PeopleCache::const_iterator i = _peopleCache.constFind(q);
if (i != _peopleCache.cend()) {
_peopleQuery = q;
_peopleRequest = 0;
peopleReceived(i.value(), 0);
return true;
}
} else if (_peopleQuery != q) {
_peopleQuery = q;
_peopleFull = false;
_peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&AddParticipantBox::peopleReceived), rpcFail(&AddParticipantBox::peopleFailed));
_peopleQueries.insert(_peopleRequest, _peopleQuery);
}
}
return false;
}
void AddParticipantBox::onNeedSearchByUsername() {
if (!onSearchByUsername(true)) {
_searchTimer.start(AutoSearchTimeout);
}
}
void AddParticipantBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) {
QString q = _peopleQuery;
PeopleQueries::iterator i = _peopleQueries.find(req);
if (i != _peopleQueries.cend()) {
q = i.value();
_peopleCache[q] = result;
_peopleQueries.erase(i);
}
if (_peopleRequest == req) {
switch (result.type()) {
case mtpc_contacts_found: {
App::feedUsers(result.c_contacts_found().vusers);
_inner.peopleReceived(q, result.c_contacts_found().vresults.c_vector().v);
} break;
}
_peopleRequest = 0;
_inner.updateSel();
onScroll();
}
}
bool AddParticipantBox::peopleFailed(const RPCError &error, mtpRequestId req) {
if (_peopleRequest == req) {
_peopleRequest = 0;
_peopleFull = true;
}
return true;
}
void AddParticipantBox::hideAll() { void AddParticipantBox::hideAll() {
_filter.hide(); _filter.hide();
_scroll.hide(); _scroll.hide();
@ -643,7 +962,7 @@ void AddParticipantBox::animStep(float64 dt) {
_filter.setFocus(); _filter.setFocus();
} }
} else { } else {
a_opacity.update(dt, af_opacity); a_opacity.update(dt, anim::linear);
} }
update(); update();
} }

View File

@ -22,6 +22,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
class AddParticipantInner : public QWidget, public RPCSender { class AddParticipantInner : public QWidget, public RPCSender {
Q_OBJECT Q_OBJECT
private:
struct ContactData;
public: public:
AddParticipantInner(ChatData *chat); AddParticipantInner(ChatData *chat);
@ -33,7 +37,7 @@ public:
void mousePressEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e);
void resizeEvent(QResizeEvent *e); void resizeEvent(QResizeEvent *e);
void paintDialog(QPainter &p, DialogRow *row, bool sel); void paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel);
void updateFilter(QString filter = QString()); void updateFilter(QString filter = QString());
void selectSkip(int32 dir); void selectSkip(int32 dir);
@ -42,6 +46,11 @@ public:
void loadProfilePhotos(int32 yFrom); void loadProfilePhotos(int32 yFrom);
void chooseParticipant(); void chooseParticipant();
void changeCheckState(DialogRow *row); void changeCheckState(DialogRow *row);
void changeCheckState(ContactData *data);
void peopleReceived(const QString &query, const QVector<MTPContactFound> &people);
void refresh();
ChatData *chat(); ChatData *chat();
QVector<UserData*> selected(); QVector<UserData*> selected();
@ -52,6 +61,7 @@ signals:
void mustScrollTo(int ymin, int ymax); void mustScrollTo(int ymin, int ymax);
void selectAllQuery(); void selectAllQuery();
void searchByUsername();
public slots: public slots:
@ -76,23 +86,32 @@ private:
int32 _selCount; int32 _selCount;
typedef struct { struct ContactData {
Text name; Text name;
QString online; QString online;
bool inchat; bool inchat;
bool check; bool check;
} ContactData; };
typedef QMap<UserData*, ContactData*> ContactsData; typedef QMap<UserData*, ContactData*> ContactsData;
ContactsData _contactsData; ContactsData _contactsData;
ContactData *contactData(DialogRow *row); ContactData *contactData(DialogRow *row);
bool _searching;
QString _lastQuery;
typedef QVector<UserData*> ByUsernameRows;
typedef QVector<ContactData*> ByUsernameDatas;
ByUsernameRows _byUsername, _byUsernameFiltered;
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
ByUsernameDatas _byUsernameDatas;
int32 _byUsernameSel;
QPoint _lastMousePos; QPoint _lastMousePos;
LinkButton _addContactLnk; LinkButton _addContactLnk;
}; };
class AddParticipantBox : public LayeredWidget { class AddParticipantBox : public LayeredWidget, public RPCSender {
Q_OBJECT Q_OBJECT
public: public:
@ -113,6 +132,9 @@ public slots:
void onScroll(); void onScroll();
void onInvite(); void onInvite();
bool onSearchByUsername(bool searchCache = false);
void onNeedSearchByUsername();
private: private:
void hideAll(); void hideAll();
@ -129,5 +151,18 @@ private:
QPixmap _cache; QPixmap _cache;
anim::fvalue a_opacity; anim::fvalue a_opacity;
anim::transition af_opacity;
void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req);
bool peopleFailed(const RPCError &error, mtpRequestId req);
QTimer _searchTimer;
QString _peopleQuery;
bool _peopleFull;
mtpRequestId _peopleRequest;
typedef QMap<QString, MTPcontacts_Found> PeopleCache;
PeopleCache _peopleCache;
typedef QMap<mtpRequestId, QString> PeopleQueries;
PeopleQueries _peopleQueries;
}; };

View File

@ -22,12 +22,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "mainwidget.h" #include "mainwidget.h"
#include "window.h" #include "window.h"
#include "confirmbox.h"
NewGroupInner::NewGroupInner() : NewGroupInner::NewGroupInner() :
_contacts(&App::main()->contactsList()), _contacts(&App::main()->contactsList()),
_sel(0), _sel(0),
_filteredSel(-1), _filteredSel(-1),
_mouseSel(false), _mouseSel(false),
_selCount(0), _selCount(0),
_searching(false),
_byUsernameSel(-1),
_addContactLnk(this, lang(lng_add_contact_button)) { _addContactLnk(this, lang(lng_add_contact_button)) {
connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact()));
@ -88,6 +92,16 @@ void NewGroupInner::loadProfilePhotos(int32 yFrom) {
preloadFrom->history->peer->photo->load(); preloadFrom->history->peer->photo->load();
} }
} }
yFrom -= _contacts->list.count * rh + st::searchedBarHeight;
yTo -= _contacts->list.count * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsername.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsername.size()) to = _byUsername.size();
for (; from < to; ++from) {
_byUsername[from]->photo->load();
}
}
} else if (!_filtered.isEmpty()) { } else if (!_filtered.isEmpty()) {
int32 from = yFrom / rh; int32 from = yFrom / rh;
if (from < 0) from = 0; if (from < 0) from = 0;
@ -99,6 +113,16 @@ void NewGroupInner::loadProfilePhotos(int32 yFrom) {
_filtered[from]->history->peer->photo->load(); _filtered[from]->history->peer->photo->load();
} }
} }
yFrom -= _filtered.size() * rh + st::searchedBarHeight;
yTo -= _filtered.size() * rh + st::searchedBarHeight;
from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsernameFiltered.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsernameFiltered.size()) to = _byUsernameFiltered.size();
for (; from < to; ++from) {
_byUsernameFiltered[from]->photo->load();
}
}
} }
} }
@ -120,12 +144,9 @@ NewGroupInner::ContactData *NewGroupInner::contactData(DialogRow *row) {
return data; return data;
} }
void NewGroupInner::paintDialog(QPainter &p, DialogRow *row, bool sel) { void NewGroupInner::paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel) {
int32 left = st::profileListPadding.width(); int32 left = st::profileListPadding.width();
UserData *user = row->history->peer->asUser();
ContactData *data = contactData(row);
if (_selCount >= cMaxGroupCount() && !data->check) { if (_selCount >= cMaxGroupCount() && !data->check) {
sel = false; sel = false;
} }
@ -147,13 +168,29 @@ void NewGroupInner::paintDialog(QPainter &p, DialogRow *row, bool sel) {
p.drawPixmap(QPoint(width() - st::profileCheckRect.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::profileCheckRect.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), (data->check ? st::profileCheckActiveRect : st::profileCheckRect)); p.drawPixmap(QPoint(width() - st::profileCheckRect.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::profileCheckRect.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), (data->check ? st::profileCheckActiveRect : st::profileCheckRect));
} }
bool uname = (data->online.at(0) == '@');
p.setFont(st::profileSubFont->f); p.setFont(st::profileSubFont->f);
if (data->check) { if (uname && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) {
p.setPen(st::white->p); int32 availw = width() - (left + st::profileListPhotoSize + st::profileListPadding.width() * 2);
QString first = '@' + user->username.mid(0, _lastQuery.size()), second = user->username.mid(_lastQuery.size());
int32 w = st::profileSubFont->m.width(first);
if (w >= availw || second.isEmpty()) {
p.setPen(st::profileOnlineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, st::profileSubFont->m.elidedText(first, Qt::ElideRight, availw));
} else {
p.setPen(st::profileOnlineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, first);
p.setPen(st::profileOfflineColor->p);
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width() + w, st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, st::profileSubFont->m.elidedText(second, Qt::ElideRight, availw - w));
}
} else { } else {
p.setPen((App::onlineColorUse(user->onlineTill, _time) ? st::profileOnlineColor : st::profileOfflineColor)->p); if (data->check) {
p.setPen(st::white->p);
} else {
p.setPen(((uname || App::onlineColorUse(user->onlineTill, _time)) ? st::profileOnlineColor : st::profileOfflineColor)->p);
}
p.drawText(left + st::profileListPhotoSize + st::participantDelta, st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, data->online);
} }
p.drawText(left + st::profileListPhotoSize + st::participantDelta, st::profileListPadding.height() + st::profileListPhotoSize - 6, data->online);
} }
void NewGroupInner::paintEvent(QPaintEvent *e) { void NewGroupInner::paintEvent(QPaintEvent *e) {
@ -163,40 +200,86 @@ void NewGroupInner::paintEvent(QPaintEvent *e) {
_time = unixtime(); _time = unixtime();
p.fillRect(r, st::white->b); p.fillRect(r, st::white->b);
int32 yFrom = r.top(); int32 yFrom = r.top(), yTo = r.bottom();
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
if (_contacts->list.count) { if (_contacts->list.count || !_byUsername.isEmpty()) {
_contacts->list.adjustCurrent(yFrom, rh); if (_contacts->list.count) {
_contacts->list.adjustCurrent(yFrom, rh);
DialogRow *drawFrom = _contacts->list.current; DialogRow *drawFrom = _contacts->list.current;
p.translate(0, drawFrom->pos * rh); p.translate(0, drawFrom->pos * rh);
while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) { while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) {
paintDialog(p, drawFrom, (drawFrom == _sel)); paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel));
p.translate(0, rh); p.translate(0, rh);
drawFrom = drawFrom->next; drawFrom = drawFrom->next;
}
}
if (!_byUsername.isEmpty()) {
p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b);
p.setFont(st::searchedBarFont->f);
p.setPen(st::searchedBarColor->p);
p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center);
p.translate(0, st::searchedBarHeight);
yFrom -= _contacts->list.count * rh + st::searchedBarHeight;
yTo -= _contacts->list.count * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsername.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsername.size()) to = _byUsername.size();
p.translate(0, from * rh);
for (; from < to; ++from) {
paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
p.translate(0, rh);
}
}
} }
} else { } else {
p.setFont(st::noContactsFont->f); p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p); p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_contacts : lng_contacts_loading), style::al_center); p.drawText(QRect(0, 0, width(), st::noContactsHeight - ((cContactsReceived() && !_searching) ? st::noContactsFont->height : 0)), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_loading), style::al_center);
} }
} else { } else {
if (_filtered.isEmpty()) { if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
p.setFont(st::noContactsFont->f); p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p); p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_not_found), style::al_center); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_not_found), style::al_center);
} else { } else {
int32 from = yFrom / rh; if (!_filtered.isEmpty()) {
if (from < 0) from = 0; int32 from = yFrom / rh;
if (from < _filtered.size()) { if (from < 0) from = 0;
int32 to = (r.bottom() / rh) + 1; if (from < _filtered.size()) {
if (to > _filtered.size()) to = _filtered.size(); int32 to = (r.bottom() / rh) + 1;
if (to > _filtered.size()) to = _filtered.size();
p.translate(0, from * rh); p.translate(0, from * rh);
for (; from < to; ++from) { for (; from < to; ++from) {
paintDialog(p, _filtered[from], (_filteredSel == from)); paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from));
p.translate(0, rh); p.translate(0, rh);
}
}
}
if (!_byUsernameFiltered.isEmpty()) {
p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b);
p.setFont(st::searchedBarFont->f);
p.setPen(st::searchedBarColor->p);
p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center);
p.translate(0, st::searchedBarHeight);
yFrom -= _filtered.size() * rh + st::searchedBarHeight;
yTo -= _filtered.size() * rh + st::searchedBarHeight;
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
if (from < _byUsernameFiltered.size()) {
int32 to = (yTo / rh) + 1;
if (to > _byUsernameFiltered.size()) to = _byUsernameFiltered.size();
p.translate(0, from * rh);
for (; from < to; ++from) {
paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
p.translate(0, rh);
}
} }
} }
} }
@ -209,7 +292,11 @@ void NewGroupInner::enterEvent(QEvent *e) {
void NewGroupInner::leaveEvent(QEvent *e) { void NewGroupInner::leaveEvent(QEvent *e) {
setMouseTracking(false); setMouseTracking(false);
updateSel(); if (_sel || _filteredSel >= 0 || _byUsernameSel >= 0) {
_sel = 0;
_filteredSel = _byUsernameSel = -1;
parentWidget()->update();
}
} }
void NewGroupInner::mouseMoveEvent(QMouseEvent *e) { void NewGroupInner::mouseMoveEvent(QMouseEvent *e) {
@ -228,11 +315,15 @@ void NewGroupInner::mousePressEvent(QMouseEvent *e) {
} }
void NewGroupInner::changeCheckState(DialogRow *row) { void NewGroupInner::changeCheckState(DialogRow *row) {
if (contactData(row)->check) { changeCheckState(contactData(row));
contactData(row)->check = false; }
void NewGroupInner::changeCheckState(ContactData *data) {
if (data->check) {
data->check = false;
--_selCount; --_selCount;
} else if (_selCount < cMaxGroupCount()) { } else if (_selCount < cMaxGroupCount()) {
contactData(row)->check = true; data->check = true;
++_selCount; ++_selCount;
} }
} }
@ -240,42 +331,125 @@ void NewGroupInner::changeCheckState(DialogRow *row) {
void NewGroupInner::chooseParticipant() { void NewGroupInner::chooseParticipant() {
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
if (!_sel) return; if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
changeCheckState(_sel); changeCheckState(d_byUsername[_byUsernameSel]);
} else {
if (!_sel) return;
changeCheckState(_sel);
}
} else { } else {
if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return; if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
changeCheckState(d_byUsernameFiltered[_byUsernameSel]);
DialogRow *row = _filtered[_filteredSel]; ContactData *moving = d_byUsernameFiltered[_byUsernameSel];
changeCheckState(row); int32 i = 0, l = d_byUsername.size();
for (; i < l; ++i) {
PeerData *peer = row->history->peer; if (d_byUsername[i] == moving) {
break;
}
}
if (i == l) {
d_byUsername.push_back(moving);
_byUsername.push_back(_byUsernameFiltered[_byUsernameSel]);
for (i = 0, l = _byUsernameDatas.size(); i < l;) {
if (_byUsernameDatas[i] == moving) {
_byUsernameDatas.removeAt(i);
--l;
} else {
++i;
}
}
}
} else {
if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return;
changeCheckState(_filtered[_filteredSel]);
}
emit selectAllQuery(); emit selectAllQuery();
} }
parentWidget()->update(); parentWidget()->update();
} }
void NewGroupInner::peopleReceived(const QString &query, const QVector<MTPContactFound> &people) {
_lastQuery = query.toLower().trimmed();
if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
int32 already = _byUsernameFiltered.size();
_byUsernameFiltered.reserve(already + people.size());
d_byUsernameFiltered.reserve(already + people.size());
for (QVector<MTPContactFound>::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) {
int32 uid = i->c_contactFound().vuser_id.v, j = 0;
for (; j < already; ++j) {
if (_byUsernameFiltered[j]->id == uid) break;
}
if (j == already) {
UserData *u = App::user(uid);
ContactData *d = new ContactData();
_byUsernameDatas.push_back(d);
d->check = false;
d->name.setText(st::profileListNameFont, u->name, _textNameOptions);
d->online = '@' + u->username;
_byUsernameFiltered.push_back(u);
d_byUsernameFiltered.push_back(d);
}
}
_searching = false;
refresh();
}
void NewGroupInner::refresh() {
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) {
if (_contacts->list.count || !_byUsername.isEmpty()) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), (_contacts->list.count * rh) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * rh)));
} else {
if (cContactsReceived()) {
if (_addContactLnk.isHidden()) _addContactLnk.show();
} else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
}
resize(width(), st::noContactsHeight);
}
} else {
if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), st::noContactsHeight);
} else {
resize(width(), (_filtered.size() * rh) + (_byUsernameFiltered.isEmpty() ? 0 : (st::searchedBarHeight + _byUsernameFiltered.size() * rh)));
}
}
}
void NewGroupInner::updateSel() { void NewGroupInner::updateSel() {
if (!_mouseSel) return; if (!_mouseSel) return;
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
QPoint p(mapFromGlobal(_lastMousePos)); QPoint p(mapFromGlobal(_lastMousePos));
bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos));
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
DialogRow *newSel = rect().contains(p) ? _contacts->list.rowAtY(p.y(), rh) : 0; DialogRow *newSel = (in && (p.y() >= 0) && (p.y() < _contacts->list.count * rh)) ? _contacts->list.rowAtY(p.y(), rh) : 0;
if (newSel != _sel) { int32 byUsernameSel = (in && p.y() >= _contacts->list.count * rh + st::searchedBarHeight) ? ((p.y() - _contacts->list.count * rh - st::searchedBarHeight) / rh) : -1;
if (byUsernameSel >= _byUsername.size()) byUsernameSel = -1;
if (newSel != _sel || byUsernameSel != _byUsernameSel) {
_sel = newSel; _sel = newSel;
_byUsernameSel = byUsernameSel;
parentWidget()->update(); parentWidget()->update();
} }
} else { } else {
int32 newFilteredSel = (p.y() >= 0 && rect().contains(p)) ? (p.y() / rh) : -1; int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * rh) ? (p.y() / rh) : -1;
if (newFilteredSel != _filteredSel) { int32 byUsernameSel = (in && p.y() >= _filtered.size() * rh + st::searchedBarHeight) ? ((p.y() - _filtered.size() * rh - st::searchedBarHeight) / rh) : -1;
if (byUsernameSel >= _byUsernameFiltered.size()) byUsernameSel = -1;
if (newFilteredSel != _filteredSel || byUsernameSel != _byUsernameSel) {
_filteredSel = newFilteredSel; _filteredSel = newFilteredSel;
_byUsernameSel = byUsernameSel;
parentWidget()->update(); parentWidget()->update();
} }
} }
} }
void NewGroupInner::updateFilter(QString filter) { void NewGroupInner::updateFilter(QString filter) {
_lastQuery = filter.toLower().trimmed();
filter = textSearchKey(filter); filter = textSearchKey(filter);
QStringList f; QStringList f;
@ -294,20 +468,23 @@ void NewGroupInner::updateFilter(QString filter) {
if (_filter != filter) { if (_filter != filter) {
int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2); int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2);
_filter = filter; _filter = filter;
_byUsernameFiltered.clear();
d_byUsernameFiltered.clear();
for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) {
delete _byUsernameDatas[i];
}
_byUsernameDatas.clear();
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
resize(width(), _contacts->list.count * rh); _sel = 0;
if (_contacts->list.count) { if (_contacts->list.count) {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), _contacts->list.count * rh);
_sel = _contacts->list.begin; _sel = _contacts->list.begin;
} else {
resize(width(), st::noContactsHeight);
if (cContactsReceived()) {
if (_addContactLnk.isHidden()) _addContactLnk.show();
} else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
}
} }
if (!_sel && !_byUsername.isEmpty()) {
_byUsernameSel = 0;
}
refresh();
} else { } else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide(); if (!_addContactLnk.isHidden()) _addContactLnk.hide();
QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi; QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
@ -349,14 +526,36 @@ void NewGroupInner::updateFilter(QString filter) {
} }
} }
} }
_byUsernameFiltered.reserve(_byUsername.size());
d_byUsernameFiltered.reserve(d_byUsername.size());
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
const PeerData::Names &names(_byUsername[i]->names);
PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
for (fi = fb; fi != fe; ++fi) {
QString filterName(*fi);
for (ni = nb; ni != ne; ++ni) {
if (ni->startsWith(*fi)) {
break;
}
}
if (ni == ne) {
break;
}
}
if (fi == fe) {
_byUsernameFiltered.push_back(_byUsername[i]);
d_byUsernameFiltered.push_back(d_byUsername[i]);
}
}
} }
_filteredSel = _filtered.isEmpty() ? -1 : 0; _filteredSel = _filtered.isEmpty() ? -1 : 0;
_byUsernameSel = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1;
if (!_filtered.isEmpty()) { refresh();
resize(width(), _filtered.size() * rh);
} else { _searching = true;
resize(width(), st::noContactsHeight); emit searchByUsername();
}
} }
if (parentWidget()) parentWidget()->update(); if (parentWidget()) parentWidget()->update();
loadProfilePhotos(0); loadProfilePhotos(0);
@ -392,7 +591,13 @@ void NewGroupInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) {
} }
NewGroupInner::~NewGroupInner() { NewGroupInner::~NewGroupInner() {
for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) { for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
delete *i;
}
for (ByUsernameDatas::const_iterator i = d_byUsername.cbegin(), e = d_byUsername.cend(); i != e; ++i) {
delete *i;
}
for (ByUsernameDatas::const_iterator i = _byUsernameDatas.cbegin(), e = _byUsernameDatas.cend(); i != e; ++i) {
delete *i; delete *i;
} }
} }
@ -405,41 +610,52 @@ void NewGroupInner::selectSkip(int32 dir) {
_mouseSel = false; _mouseSel = false;
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir;
if (_filter.isEmpty()) { if (_filter.isEmpty()) {
int cur = 0;
if (_sel) { if (_sel) {
if (dir > 0) { for (DialogRow *i = _contacts->list.begin; i != _sel; i = i->next) {
while (dir && _sel->next->next) { ++cur;
_sel = _sel->next;
--dir;
}
} else {
while (dir && _sel->prev) {
_sel = _sel->prev;
++dir;
}
} }
} else if (dir > 0 && _contacts->list.count) { } else {
_sel = _contacts->list.begin; cur = (_byUsernameSel >= 0) ? (_contacts->list.count + _byUsernameSel) : -1;
}
cur += dir;
if (cur <= 0) {
_sel = _contacts->list.count ? _contacts->list.begin : 0;
_byUsernameSel = (!_contacts->list.count && !_byUsername.isEmpty()) ? 0 : -1;
} else if (cur >= _contacts->list.count) {
_sel = 0;
_byUsernameSel = cur - _contacts->list.count;
if (_byUsernameSel >= _byUsername.size()) _byUsernameSel = _byUsername.size() - 1;
} else {
for (_sel = _contacts->list.begin; cur; _sel = _sel->next) {
--cur;
}
_byUsernameSel = -1;
} }
if (_sel) { if (_sel) {
emit mustScrollTo(_sel->pos * rh, (_sel->pos + 1) * rh); emit mustScrollTo(_sel->pos * rh, (_sel->pos + 1) * rh);
} else if (_byUsernameSel >= 0) {
emit mustScrollTo((_contacts->list.count + _byUsernameSel) * rh + st::searchedBarHeight, (_contacts->list.count + _byUsernameSel + 1) * rh + st::searchedBarHeight);
} }
} else { } else {
if (dir > 0) { int cur = (_filteredSel >= 0) ? _filteredSel : ((_byUsernameSel >= 0) ? (_filtered.size() + _byUsernameSel) : -1);
if (_filteredSel < 0 && dir > 1) { cur += dir;
_filteredSel = 0; if (cur <= 0) {
} _filteredSel = _filtered.isEmpty() ? -1 : 0;
_filteredSel += dir; _byUsernameSel = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1;
if (_filteredSel >= _filtered.size()) { } else if (cur >= _filtered.size()) {
_filteredSel = _filtered.size() - 1; _filteredSel = -1;
} _byUsernameSel = cur - _filtered.size();
} else if (_filteredSel > 0) { if (_byUsernameSel >= _byUsernameFiltered.size()) _byUsernameSel = _byUsernameFiltered.size() - 1;
_filteredSel += dir; } else {
if (_filteredSel < 0) { _filteredSel = cur;
_filteredSel = 0; _byUsernameSel = -1;
}
} }
if (_filteredSel >= 0) { if (_filteredSel >= 0) {
emit mustScrollTo(_filteredSel * rh, (_filteredSel + 1) * rh); emit mustScrollTo(_filteredSel * rh, (_filteredSel + 1) * rh);
} else if (_byUsernameSel >= 0) {
int skip = _filtered.size() * rh + st::searchedBarHeight;
emit mustScrollTo(skip + _byUsernameSel * rh, skip + (_byUsernameSel + 1) * rh);
} }
} }
parentWidget()->update(); parentWidget()->update();
@ -460,6 +676,11 @@ QVector<MTPInputUser> NewGroupInner::selectedInputs() {
result.push_back(i.key()->inputUser); result.push_back(i.key()->inputUser);
} }
} }
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
if (d_byUsername[i]->check) {
result.push_back(_byUsername[i]->inputUser);
}
}
return result; return result;
} }
@ -469,6 +690,11 @@ PeerData *NewGroupInner::selectedUser() {
return i.key(); return i.key();
} }
} }
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
if (d_byUsername[i]->check) {
return _byUsername[i];
}
}
return 0; return 0;
} }
@ -495,12 +721,81 @@ NewGroupBox::NewGroupBox() : _scroll(this, st::newGroupScroll), _inner(),
connect(&_filter, SIGNAL(cancelled()), this, SLOT(onClose())); connect(&_filter, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int)));
connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll())); connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll()));
connect(&_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
_searchTimer.setSingleShot(true);
connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
showAll(); showAll();
_cache = myGrab(this, rect()); _cache = myGrab(this, rect());
hideAll(); hideAll();
} }
bool NewGroupBox::onSearchByUsername(bool searchCache) {
QString q = _filter.text().trimmed();
if (q.isEmpty()) {
if (_peopleRequest) {
_peopleRequest = 0;
}
return true;
}
if (q.size() >= MinUsernameLength) {
if (searchCache) {
PeopleCache::const_iterator i = _peopleCache.constFind(q);
if (i != _peopleCache.cend()) {
_peopleQuery = q;
_peopleRequest = 0;
peopleReceived(i.value(), 0);
return true;
}
} else if (_peopleQuery != q) {
_peopleQuery = q;
_peopleFull = false;
_peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&NewGroupBox::peopleReceived), rpcFail(&NewGroupBox::peopleFailed));
_peopleQueries.insert(_peopleRequest, _peopleQuery);
}
}
return false;
}
void NewGroupBox::onNeedSearchByUsername() {
if (!onSearchByUsername(true)) {
_searchTimer.start(AutoSearchTimeout);
}
}
void NewGroupBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) {
QString q = _peopleQuery;
PeopleQueries::iterator i = _peopleQueries.find(req);
if (i != _peopleQueries.cend()) {
q = i.value();
_peopleCache[q] = result;
_peopleQueries.erase(i);
}
if (_peopleRequest == req) {
switch (result.type()) {
case mtpc_contacts_found: {
App::feedUsers(result.c_contacts_found().vusers);
_inner.peopleReceived(q, result.c_contacts_found().vresults.c_vector().v);
} break;
}
_peopleRequest = 0;
_inner.updateSel();
onScroll();
}
}
bool NewGroupBox::peopleFailed(const RPCError &error, mtpRequestId req) {
if (_peopleRequest == req) {
_peopleRequest = 0;
_peopleFull = true;
}
return true;
}
void NewGroupBox::hideAll() { void NewGroupBox::hideAll() {
_filter.hide(); _filter.hide();
_scroll.hide(); _scroll.hide();

View File

@ -22,6 +22,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
class NewGroupInner : public QWidget { class NewGroupInner : public QWidget {
Q_OBJECT Q_OBJECT
private:
struct ContactData;
public: public:
NewGroupInner(); NewGroupInner();
@ -33,7 +37,7 @@ public:
void mousePressEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e);
void resizeEvent(QResizeEvent *e); void resizeEvent(QResizeEvent *e);
void paintDialog(QPainter &p, DialogRow *row, bool sel); void paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel);
void updateFilter(QString filter = QString()); void updateFilter(QString filter = QString());
void selectSkip(int32 dir); void selectSkip(int32 dir);
@ -45,6 +49,11 @@ public:
void loadProfilePhotos(int32 yFrom); void loadProfilePhotos(int32 yFrom);
void changeCheckState(DialogRow *row); void changeCheckState(DialogRow *row);
void changeCheckState(ContactData *data);
void peopleReceived(const QString &query, const QVector<MTPContactFound> &people);
void refresh();
~NewGroupInner(); ~NewGroupInner();
@ -52,6 +61,7 @@ signals:
void mustScrollTo(int ymin, int ymax); void mustScrollTo(int ymin, int ymax);
void selectAllQuery(); void selectAllQuery();
void searchByUsername();
public slots: public slots:
@ -74,17 +84,26 @@ private:
int32 _filteredSel; int32 _filteredSel;
bool _mouseSel; bool _mouseSel;
typedef struct { struct ContactData {
Text name; Text name;
QString online; QString online;
bool check; bool check;
} ContactData; };
typedef QMap<UserData*, ContactData*> ContactsData; typedef QMap<UserData*, ContactData*> ContactsData;
ContactsData _contactsData; ContactsData _contactsData;
int32 _selCount; int32 _selCount;
ContactData *contactData(DialogRow *row); ContactData *contactData(DialogRow *row);
bool _searching;
QString _lastQuery;
typedef QVector<UserData*> ByUsernameRows;
typedef QVector<ContactData*> ByUsernameDatas;
ByUsernameRows _byUsername, _byUsernameFiltered;
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
ByUsernameDatas _byUsernameDatas;
int32 _byUsernameSel;
QPoint _lastMousePos; QPoint _lastMousePos;
LinkButton _addContactLnk; LinkButton _addContactLnk;
@ -111,6 +130,9 @@ public slots:
void onNext(); void onNext();
void onScroll(); void onScroll();
bool onSearchByUsername(bool searchCache = false);
void onNeedSearchByUsername();
private: private:
void hideAll(); void hideAll();
@ -126,6 +148,20 @@ private:
QPixmap _cache; QPixmap _cache;
anim::fvalue a_opacity; anim::fvalue a_opacity;
void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req);
bool peopleFailed(const RPCError &error, mtpRequestId req);
QTimer _searchTimer;
QString _peopleQuery;
bool _peopleFull;
mtpRequestId _peopleRequest;
typedef QMap<QString, MTPcontacts_Found> PeopleCache;
PeopleCache _peopleCache;
typedef QMap<mtpRequestId, QString> PeopleQueries;
PeopleQueries _peopleQueries;
}; };
class CreateGroupBox : public LayeredWidget, public RPCSender { class CreateGroupBox : public LayeredWidget, public RPCSender {

View File

@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
static const int32 AppVersion = 7024; static const int32 AppVersion = 7025;
static const wchar_t *AppVersionStr = L"0.7.24"; static const wchar_t *AppVersionStr = L"0.7.25";
static const bool DevChannel = true; static const bool DevChannel = true;
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";

View File

@ -900,26 +900,40 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
if (_rows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); if (_rows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon);
} }
p.setPen(st::black->p);
if (_rows->isEmpty()) { if (_rows->isEmpty()) {
QString tag = st::mentionFont->m.elidedText('#' + _hrows->at(last - i - 1), Qt::ElideRight, htagwidth); QString tag = st::mentionFont->m.elidedText('#' + _hrows->at(last - i - 1), Qt::ElideRight, htagwidth);
p.setFont(st::mentionFont->f); p.setFont(st::mentionFont->f);
p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, tag); p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, tag);
} else { } else {
UserData *user = _rows->at(last - i - 1); UserData *user = _rows->at(last - i - 1);
QString uname = user->username; QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1);
int32 unamewidth = atwidth + st::mentionFont->m.width(uname), namewidth = user->nameText.maxWidth(); int32 firstwidth = st::mentionFont->m.width(first), secondwidth = st::mentionFont->m.width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth();
if (availwidth < unamewidth + namewidth) { if (availwidth < unamewidth + namewidth) {
namewidth = (availwidth * namewidth) / (namewidth + unamewidth); namewidth = (availwidth * namewidth) / (namewidth + unamewidth);
unamewidth = availwidth - namewidth; unamewidth = availwidth - namewidth;
uname = st::mentionFont->m.elidedText('@' + uname, Qt::ElideRight, unamewidth); if (firstwidth <= unamewidth) {
} else { if (firstwidth < unamewidth) {
uname = '@' + uname; first = st::mentionFont->m.elidedText(first, Qt::ElideRight, unamewidth);
} else if (!second.isEmpty()) {
first = st::mentionFont->m.elidedText(first + second, Qt::ElideRight, unamewidth);
second = QString();
}
} else {
second = st::mentionFont->m.elidedText(second, Qt::ElideRight, unamewidth - firstwidth);
}
} }
user->photo->load(); user->photo->load();
p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pix(st::mentionPhotoSize)); p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pix(st::mentionPhotoSize));
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
p.setFont(st::mentionFont->f); p.setFont(st::mentionFont->f);
p.drawText(2 * st::mentionPadding.left() + st::mentionPhotoSize + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, uname);
p.setPen(st::profileOnlineColor->p);
p.drawText(2 * st::mentionPadding.left() + st::mentionPhotoSize + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
if (!second.isEmpty()) {
p.setPen(st::profileOfflineColor->p);
p.drawText(2 * st::mentionPadding.left() + st::mentionPhotoSize + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} }
} }
@ -1240,6 +1254,10 @@ bool MentionsDropdown::animStep(float64 ms) {
return res; return res;
} }
const QString &MentionsDropdown::filter() const {
return _filter;
}
int32 MentionsDropdown::innerTop() { int32 MentionsDropdown::innerTop() {
return _scroll.scrollTop(); return _scroll.scrollTop();
} }
@ -1257,7 +1275,7 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) {
return true; return true;
} else if (ev->key() == Qt::Key_Down) { } else if (ev->key() == Qt::Key_Down) {
return _inner.moveSel(1); return _inner.moveSel(1);
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { } else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Space) {
return _inner.select(); return _inner.select();
} }
} }

View File

@ -291,6 +291,8 @@ public:
bool animStep(float64 ms); bool animStep(float64 ms);
const QString &filter() const;
int32 innerTop(); int32 innerTop();
int32 innerBottom(); int32 innerBottom();

View File

@ -718,6 +718,8 @@ void MainWidget::addParticipantDone(ChatData *chat, const MTPmessages_StatedMess
} }
bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &e) { bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &e) {
ConfirmBox *box = new ConfirmBox(lang(lng_failed_add_participant), true);
App::wnd()->showLayer(box);
if (e.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group if (e.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
} }
return false; return false;

View File

@ -200,6 +200,7 @@ namespace {
} }
} else { } else {
secs = m.captured(1).toInt(); secs = m.captured(1).toInt();
if (secs >= 60) return false;
} }
uint64 sendAt = getms(true) + secs * 1000 + 10; uint64 sendAt = getms(true) + secs * 1000 + 10;
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end(); DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();

View File

@ -717,6 +717,9 @@ void ProfileInner::contextMenuEvent(QContextMenuEvent *e) {
if (info.contains(mapFromGlobal(e->globalPos()))) { if (info.contains(mapFromGlobal(e->globalPos()))) {
_menu = new ContextMenu(this); _menu = new ContextMenu(this);
_menu->addAction(lang(lng_profile_copy_phone), this, SLOT(onCopyPhone()))->setEnabled(true); _menu->addAction(lang(lng_profile_copy_phone), this, SLOT(onCopyPhone()))->setEnabled(true);
if (_peerUser && !_peerUser->username.isEmpty()) {
_menu->addAction(lang(lng_context_copy_mention), this, SLOT(onCopyUsername()))->setEnabled(true);
}
_menu->deleteOnHide(); _menu->deleteOnHide();
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
_menu->popup(e->globalPos()); _menu->popup(e->globalPos());
@ -735,6 +738,10 @@ void ProfileInner::onCopyPhone() {
QApplication::clipboard()->setText(_phoneText); QApplication::clipboard()->setText(_phoneText);
} }
void ProfileInner::onCopyUsername() {
QApplication::clipboard()->setText('@' + _peerUser->username);
}
bool ProfileInner::animStep(float64 ms) { bool ProfileInner::animStep(float64 ms) {
float64 dt = ms / st::setPhotoDuration; float64 dt = ms / st::setPhotoDuration;
bool res = true; bool res = true;

View File

@ -91,6 +91,7 @@ public slots:
void onMenuDestroy(QObject *obj); void onMenuDestroy(QObject *obj);
void onCopyPhone(); void onCopyPhone();
void onCopyUsername();
private: private:

View File

@ -11,7 +11,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.7.24</string> <string>0.7.25</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

Binary file not shown.

View File

@ -1667,7 +1667,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.7.24; CURRENT_PROJECT_VERSION = 0.7.25;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@ -1685,7 +1685,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COPY_PHASE_STRIP = YES; COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = 0.7.24; CURRENT_PROJECT_VERSION = 0.7.25;
GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
GCC_OPTIMIZATION_LEVEL = fast; GCC_OPTIMIZATION_LEVEL = fast;
GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h; GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@ -1711,10 +1711,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = ""; CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.7.24; CURRENT_PROJECT_VERSION = 0.7.25;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DYLIB_COMPATIBILITY_VERSION = 0.7; DYLIB_COMPATIBILITY_VERSION = 0.7;
DYLIB_CURRENT_VERSION = 0.7.24; DYLIB_CURRENT_VERSION = 0.7.25;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = ""; FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@ -1852,10 +1852,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = ""; CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.7.24; CURRENT_PROJECT_VERSION = 0.7.25;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DYLIB_COMPATIBILITY_VERSION = 0.7; DYLIB_COMPATIBILITY_VERSION = 0.7;
DYLIB_CURRENT_VERSION = 0.7.24; DYLIB_CURRENT_VERSION = 0.7.25;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = ""; FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES;

View File

@ -1,2 +1,2 @@
echo 7024 0.7.24 1 echo 7025 0.7.25 1
# AppVersion AppVersionStr DevChannel # AppVersion AppVersionStr DevChannel