From 8c7a35c97309427bcea2776046a2beb76b18abee Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 Mar 2015 18:42:15 +0300 Subject: [PATCH] add users to groups by usernames, copy username from context menu in profile, 0.7.25.dev version --- Telegram/PrepareWin.bat | 8 +- Telegram/Resources/lang.strings | 6 +- Telegram/SourceFiles/application.cpp | 8 +- .../SourceFiles/boxes/addparticipantbox.cpp | 567 ++++++++++++++---- .../SourceFiles/boxes/addparticipantbox.h | 45 +- Telegram/SourceFiles/boxes/newgroupbox.cpp | 467 ++++++++++++--- Telegram/SourceFiles/boxes/newgroupbox.h | 42 +- Telegram/SourceFiles/config.h | 4 +- Telegram/SourceFiles/dropdown.cpp | 32 +- Telegram/SourceFiles/dropdown.h | 2 + Telegram/SourceFiles/mainwidget.cpp | 2 + Telegram/SourceFiles/mtproto/mtp.cpp | 1 + Telegram/SourceFiles/profilewidget.cpp | 7 + Telegram/SourceFiles/profilewidget.h | 1 + Telegram/Telegram.plist | 2 +- Telegram/Telegram.rc | Bin 5540 -> 5540 bytes Telegram/Telegram.xcodeproj/project.pbxproj | 12 +- Telegram/Version.sh | 2 +- 18 files changed, 963 insertions(+), 245 deletions(-) diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat index 434d6b44c..693090d9a 100644 --- a/Telegram/PrepareWin.bat +++ b/Telegram/PrepareWin.bat @@ -1,9 +1,9 @@ @echo OFF -set "AppVersion=7024" -set "AppVersionStrSmall=0.7.24" -set "AppVersionStr=0.7.24" -set "AppVersionStrFull=0.7.24.0" +set "AppVersion=7025" +set "AppVersionStrSmall=0.7.25" +set "AppVersionStr=0.7.25" +set "AppVersionStrFull=0.7.25.0" set "DevChannel=1" if %DevChannel% neq 0 goto preparedev diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 27eb641df..fdabc754b 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -313,6 +313,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Next"; "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_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_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_version7022_appstore" = "— 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_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"; diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 5c89084c1..77549ac4d 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -653,10 +653,10 @@ void Application::checkMapVersion() { psRegisterCustomScheme(); if (Local::oldMapVersion()) { QString versionFeatures; - if (DevChannel && Local::oldMapVersion() < 7024) { - 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"); - } else if (!DevChannel && Local::oldMapVersion() < 7023) { - versionFeatures = lang(lng_new_version7022).trimmed().replace('@', qsl("@") + QChar(0x200D)); + if (DevChannel && Local::oldMapVersion() < 7025) { + 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() < 7026) { + versionFeatures = lang(lng_new_version7026).trimmed(); } if (!versionFeatures.isEmpty()) { versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); diff --git a/Telegram/SourceFiles/boxes/addparticipantbox.cpp b/Telegram/SourceFiles/boxes/addparticipantbox.cpp index 4b8a14cf1..6fd97197a 100644 --- a/Telegram/SourceFiles/boxes/addparticipantbox.cpp +++ b/Telegram/SourceFiles/boxes/addparticipantbox.cpp @@ -28,6 +28,8 @@ _sel(0), _filteredSel(-1), _mouseSel(false), _selCount(0), +_searching(false), +_byUsernameSel(-1), _addContactLnk(this, lang(lng_add_contact_button)) { connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); @@ -101,6 +103,16 @@ void AddParticipantInner::loadProfilePhotos(int32 yFrom) { 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()) { int32 from = yFrom / rh; if (from < 0) from = 0; @@ -112,6 +124,16 @@ void AddParticipantInner::loadProfilePhotos(int32 yFrom) { _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); if (i == _contactsData.cend()) { _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->name.setText(st::profileListNameFont, user->name, _textNameOptions); data->online = App::onlineText(user, _time); @@ -134,12 +156,9 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro 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(); - UserData *user = row->history->peer->asUser(); - ContactData *data = contactData(row); - if (data->inchat || data->check || _selCount + _chat->count >= cMaxGroupCount()) { 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)); } + bool uname = (data->online.at(0) == '@'); p.setFont(st::profileSubFont->f); - if (data->inchat || data->check) { - p.setPen(st::white->p); + if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) { + 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 { - 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) { @@ -177,40 +212,85 @@ void AddParticipantInner::paintEvent(QPaintEvent *e) { _time = unixtime(); 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; if (_filter.isEmpty()) { - if (_contacts->list.count) { - _contacts->list.adjustCurrent(yFrom, rh); + if (_contacts->list.count || !_byUsername.isEmpty()) { + if (_contacts->list.count) { + _contacts->list.adjustCurrent(yFrom, rh); - DialogRow *drawFrom = _contacts->list.current; - p.translate(0, drawFrom->pos * rh); - while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) { - paintDialog(p, drawFrom, (drawFrom == _sel)); - p.translate(0, rh); - drawFrom = drawFrom->next; + DialogRow *drawFrom = _contacts->list.current; + p.translate(0, drawFrom->pos * rh); + while (drawFrom != _contacts->list.end && drawFrom->pos * rh < yTo) { + paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel)); + p.translate(0, rh); + 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 { p.setFont(st::noContactsFont->f); 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 { - if (_filtered.isEmpty()) { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { p.setFont(st::noContactsFont->f); 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 { - int32 from = yFrom / rh; - if (from < 0) from = 0; - if (from < _filtered.size()) { - int32 to = (r.bottom() / rh) + 1; - if (to > _filtered.size()) to = _filtered.size(); + if (!_filtered.isEmpty()) { + int32 from = (yFrom >= 0) ? (yFrom / rh) : 0; + if (from < _filtered.size()) { + int32 to = (yTo / rh) + 1; + if (to > _filtered.size()) to = _filtered.size(); - p.translate(0, from * rh); - for (; from < to; ++from) { - paintDialog(p, _filtered[from], (_filteredSel == from)); - p.translate(0, rh); + p.translate(0, from * rh); + for (; from < to; ++from) { + paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from)); + 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) { setMouseTracking(false); - updateSel(); + if (_sel || _filteredSel >= 0 || _byUsernameSel >= 0) { + _sel = 0; + _filteredSel = _byUsernameSel = -1; + parentWidget()->update(); + } } void AddParticipantInner::mouseMoveEvent(QMouseEvent *e) { @@ -245,30 +329,112 @@ void AddParticipantInner::chooseParticipant() { _time = unixtime(); int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from; if (_filter.isEmpty()) { - if (!_sel || contactData(_sel)->inchat) return; - changeCheckState(_sel); + if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { + if (d_byUsername[_byUsernameSel]->inchat) return; + changeCheckState(d_byUsername[_byUsernameSel]); + } else { + if (!_sel || contactData(_sel)->inchat) return; + changeCheckState(_sel); + } } 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]; - changeCheckState(row); - - PeerData *peer = row->history->peer; + ContactData *moving = d_byUsernameFiltered[_byUsernameSel]; + int32 i = 0, l = d_byUsername.size(); + for (; i < l; ++i) { + 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(); } parentWidget()->update(); } void AddParticipantInner::changeCheckState(DialogRow *row) { - if (contactData(row)->check) { - contactData(row)->check = false; + changeCheckState(contactData(row)); +} + +void AddParticipantInner::changeCheckState(ContactData *data) { + if (data->check) { + data->check = false; --_selCount; } else if (_selCount + _chat->count < cMaxGroupCount()) { - contactData(row)->check = true; + data->check = true; ++_selCount; } } +void AddParticipantInner::peopleReceived(const QString &query, const QVector &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::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() { return _chat; } @@ -281,6 +447,11 @@ QVector AddParticipantInner::selected() { 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; } @@ -288,23 +459,31 @@ void AddParticipantInner::updateSel() { if (!_mouseSel) return; QPoint p(mapFromGlobal(_lastMousePos)); + bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; if (_filter.isEmpty()) { - DialogRow *newSel = rect().contains(p) ? _contacts->list.rowAtY(p.y(), rh) : 0; - if (newSel != _sel) { + DialogRow *newSel = (in && (p.y() >= 0) && (p.y() < _contacts->list.count * rh)) ? _contacts->list.rowAtY(p.y(), rh) : 0; + 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; + _byUsernameSel = byUsernameSel; parentWidget()->update(); - } + } } else { - int32 newFilteredSel = (p.y() >= 0 && rect().contains(p)) ? (p.y() / rh) : -1; - if (newFilteredSel != _filteredSel) { + int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * rh) ? (p.y() / rh) : -1; + 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; + _byUsernameSel = byUsernameSel; parentWidget()->update(); } } } void AddParticipantInner::updateFilter(QString filter) { + _lastQuery = filter.toLower().trimmed(); filter = textSearchKey(filter); _time = unixtime(); @@ -324,22 +503,32 @@ void AddParticipantInner::updateFilter(QString filter) { if (_filter != filter) { int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2); _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()) { + _sel = 0; if (_contacts->list.count) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - resize(width(), _contacts->list.count * rh); _sel = _contacts->list.begin; while (_sel->next->next &&& contactData(_sel)->inchat) { _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 { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); 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; - while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) { - ++_filteredSel; + _filteredSel = -1; + if (!_filtered.isEmpty()) { + 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()) { - resize(width(), _filtered.size() * rh); - } else { - resize(width(), st::noContactsHeight); - } + refresh(); + + _searching = true; + emit searchByUsername(); } if (parentWidget()) parentWidget()->update(); loadProfilePhotos(0); @@ -427,7 +647,13 @@ void AddParticipantInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newR } 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; } } @@ -441,82 +667,106 @@ void AddParticipantInner::selectSkip(int32 dir) { _mouseSel = false; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir; if (_filter.isEmpty()) { + int cur = 0; if (_sel) { - if (dir > 0) { - while (dir && _sel->next->next) { - _sel = _sel->next; - --dir; + for (DialogRow *i = _contacts->list.begin; i != _sel; i = i->next) { + ++cur; + } + } 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; - } - if (contactData(_sel)->inchat) { - while (contactData(_sel)->inchat && _sel->prev) { + } + } else { + while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->inchat) { + --_byUsernameSel; + } + if (_byUsernameSel < 0) { + if (_contacts->list.count) { + if (!_sel) _sel = _contacts->list.end->prev; + for (; _sel && contactData(_sel)->inchat;) { _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 (contactData(_sel)->inchat) { - _sel = 0; - } else { - 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 { + 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 (_filteredSel < 0 && dir > 1) { - _filteredSel = 0; - } - _filteredSel += dir; - while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) { + while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->inchat) { ++_filteredSel; } - if (_filteredSel >= _filtered.size()) { - _filteredSel = _filtered.size() - 1; - } - while (_filteredSel > 0 && contactData(_filtered[_filteredSel])->inchat) { - --_filteredSel; - } - } else if (_filteredSel > 0) { - _filteredSel += dir; - if (_filteredSel < 0) { - _filteredSel = 0; - } - if (_filteredSel < _filtered.size() - 1) { - while (_filteredSel > 0 && contactData(_filtered[_filteredSel])->inchat) { - --_filteredSel; + if (_filteredSel < 0 || _filteredSel >= _filtered.size()) { + _filteredSel = -1; + if (!_byUsernameFiltered.isEmpty()) { + if (_byUsernameSel < 0) _byUsernameSel = 0; + for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->inchat;) { + ++_byUsernameSel; + } + if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; } } - while (_filteredSel < _filtered.size() - 1 && contactData(_filtered[_filteredSel])->inchat) { - ++_filteredSel; + } else { + 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 (contactData(_filtered[_filteredSel])->inchat) { - _filteredSel = -1; - } else { - 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(); @@ -534,7 +784,7 @@ AddParticipantBox::AddParticipantBox(ChatData *chat) : _filter(this, st::contactsFilter, lang(lng_participant_filter)), _invite(this, lang(lng_participant_invite), st::btnSelectDone), _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; _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(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); 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(); _cache = myGrab(this, rect()); 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() { _filter.hide(); _scroll.hide(); @@ -643,7 +962,7 @@ void AddParticipantBox::animStep(float64 dt) { _filter.setFocus(); } } else { - a_opacity.update(dt, af_opacity); + a_opacity.update(dt, anim::linear); } update(); } diff --git a/Telegram/SourceFiles/boxes/addparticipantbox.h b/Telegram/SourceFiles/boxes/addparticipantbox.h index dd881e1fe..60c666903 100644 --- a/Telegram/SourceFiles/boxes/addparticipantbox.h +++ b/Telegram/SourceFiles/boxes/addparticipantbox.h @@ -22,6 +22,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org class AddParticipantInner : public QWidget, public RPCSender { Q_OBJECT +private: + + struct ContactData; + public: AddParticipantInner(ChatData *chat); @@ -33,7 +37,7 @@ public: void mousePressEvent(QMouseEvent *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 selectSkip(int32 dir); @@ -42,6 +46,11 @@ public: void loadProfilePhotos(int32 yFrom); void chooseParticipant(); void changeCheckState(DialogRow *row); + void changeCheckState(ContactData *data); + + void peopleReceived(const QString &query, const QVector &people); + + void refresh(); ChatData *chat(); QVector selected(); @@ -52,6 +61,7 @@ signals: void mustScrollTo(int ymin, int ymax); void selectAllQuery(); + void searchByUsername(); public slots: @@ -76,23 +86,32 @@ private: int32 _selCount; - typedef struct { + struct ContactData { Text name; QString online; bool inchat; bool check; - } ContactData; + }; typedef QMap ContactsData; ContactsData _contactsData; ContactData *contactData(DialogRow *row); + bool _searching; + QString _lastQuery; + typedef QVector ByUsernameRows; + typedef QVector 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; LinkButton _addContactLnk; }; -class AddParticipantBox : public LayeredWidget { +class AddParticipantBox : public LayeredWidget, public RPCSender { Q_OBJECT public: @@ -113,6 +132,9 @@ public slots: void onScroll(); void onInvite(); + bool onSearchByUsername(bool searchCache = false); + void onNeedSearchByUsername(); + private: void hideAll(); @@ -129,5 +151,18 @@ private: QPixmap _cache; 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 PeopleCache; + PeopleCache _peopleCache; + + typedef QMap PeopleQueries; + PeopleQueries _peopleQueries; }; diff --git a/Telegram/SourceFiles/boxes/newgroupbox.cpp b/Telegram/SourceFiles/boxes/newgroupbox.cpp index 99fac49f9..1ced35edf 100644 --- a/Telegram/SourceFiles/boxes/newgroupbox.cpp +++ b/Telegram/SourceFiles/boxes/newgroupbox.cpp @@ -22,12 +22,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "window.h" +#include "confirmbox.h" + NewGroupInner::NewGroupInner() : _contacts(&App::main()->contactsList()), _sel(0), _filteredSel(-1), _mouseSel(false), _selCount(0), +_searching(false), +_byUsernameSel(-1), _addContactLnk(this, lang(lng_add_contact_button)) { connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); @@ -88,6 +92,16 @@ void NewGroupInner::loadProfilePhotos(int32 yFrom) { 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()) { int32 from = yFrom / rh; if (from < 0) from = 0; @@ -99,6 +113,16 @@ void NewGroupInner::loadProfilePhotos(int32 yFrom) { _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; } -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(); - UserData *user = row->history->peer->asUser(); - ContactData *data = contactData(row); - if (_selCount >= cMaxGroupCount() && !data->check) { 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)); } + bool uname = (data->online.at(0) == '@'); p.setFont(st::profileSubFont->f); - if (data->check) { - p.setPen(st::white->p); + if (uname && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) { + 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 { - 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) { @@ -163,40 +200,86 @@ void NewGroupInner::paintEvent(QPaintEvent *e) { _time = unixtime(); 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; if (_filter.isEmpty()) { - if (_contacts->list.count) { - _contacts->list.adjustCurrent(yFrom, rh); + if (_contacts->list.count || !_byUsername.isEmpty()) { + if (_contacts->list.count) { + _contacts->list.adjustCurrent(yFrom, rh); - DialogRow *drawFrom = _contacts->list.current; - p.translate(0, drawFrom->pos * rh); - while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) { - paintDialog(p, drawFrom, (drawFrom == _sel)); - p.translate(0, rh); - drawFrom = drawFrom->next; + DialogRow *drawFrom = _contacts->list.current; + p.translate(0, drawFrom->pos * rh); + while (drawFrom != _contacts->list.end && drawFrom->pos * rh < r.bottom()) { + paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel)); + p.translate(0, rh); + 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 { p.setFont(st::noContactsFont->f); 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 { - if (_filtered.isEmpty()) { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { p.setFont(st::noContactsFont->f); 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 { - int32 from = yFrom / rh; - if (from < 0) from = 0; - if (from < _filtered.size()) { - int32 to = (r.bottom() / rh) + 1; - if (to > _filtered.size()) to = _filtered.size(); + if (!_filtered.isEmpty()) { + int32 from = yFrom / rh; + if (from < 0) from = 0; + if (from < _filtered.size()) { + int32 to = (r.bottom() / rh) + 1; + if (to > _filtered.size()) to = _filtered.size(); - p.translate(0, from * rh); - for (; from < to; ++from) { - paintDialog(p, _filtered[from], (_filteredSel == from)); - p.translate(0, rh); + p.translate(0, from * rh); + for (; from < to; ++from) { + paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from)); + 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) { setMouseTracking(false); - updateSel(); + if (_sel || _filteredSel >= 0 || _byUsernameSel >= 0) { + _sel = 0; + _filteredSel = _byUsernameSel = -1; + parentWidget()->update(); + } } void NewGroupInner::mouseMoveEvent(QMouseEvent *e) { @@ -228,11 +315,15 @@ void NewGroupInner::mousePressEvent(QMouseEvent *e) { } void NewGroupInner::changeCheckState(DialogRow *row) { - if (contactData(row)->check) { - contactData(row)->check = false; + changeCheckState(contactData(row)); +} + +void NewGroupInner::changeCheckState(ContactData *data) { + if (data->check) { + data->check = false; --_selCount; } else if (_selCount < cMaxGroupCount()) { - contactData(row)->check = true; + data->check = true; ++_selCount; } } @@ -240,42 +331,125 @@ void NewGroupInner::changeCheckState(DialogRow *row) { void NewGroupInner::chooseParticipant() { int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from; if (_filter.isEmpty()) { - if (!_sel) return; - changeCheckState(_sel); + if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { + changeCheckState(d_byUsername[_byUsernameSel]); + } else { + if (!_sel) return; + changeCheckState(_sel); + } } else { - if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return; + if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { + changeCheckState(d_byUsernameFiltered[_byUsernameSel]); - DialogRow *row = _filtered[_filteredSel]; - changeCheckState(row); - - PeerData *peer = row->history->peer; + ContactData *moving = d_byUsernameFiltered[_byUsernameSel]; + int32 i = 0, l = d_byUsername.size(); + for (; i < l; ++i) { + 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(); } parentWidget()->update(); } +void NewGroupInner::peopleReceived(const QString &query, const QVector &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::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() { if (!_mouseSel) return; - int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; QPoint p(mapFromGlobal(_lastMousePos)); + bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); + int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2; if (_filter.isEmpty()) { - DialogRow *newSel = rect().contains(p) ? _contacts->list.rowAtY(p.y(), rh) : 0; - if (newSel != _sel) { + DialogRow *newSel = (in && (p.y() >= 0) && (p.y() < _contacts->list.count * rh)) ? _contacts->list.rowAtY(p.y(), rh) : 0; + 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; + _byUsernameSel = byUsernameSel; parentWidget()->update(); } } else { - int32 newFilteredSel = (p.y() >= 0 && rect().contains(p)) ? (p.y() / rh) : -1; - if (newFilteredSel != _filteredSel) { + int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * rh) ? (p.y() / rh) : -1; + 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; + _byUsernameSel = byUsernameSel; parentWidget()->update(); } } } void NewGroupInner::updateFilter(QString filter) { + _lastQuery = filter.toLower().trimmed(); filter = textSearchKey(filter); QStringList f; @@ -294,20 +468,23 @@ void NewGroupInner::updateFilter(QString filter) { if (_filter != filter) { int32 rh = (st::profileListPhotoSize + st::profileListPadding.height() * 2); _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()) { - resize(width(), _contacts->list.count * rh); + _sel = 0; if (_contacts->list.count) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - resize(width(), _contacts->list.count * rh); _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 { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); 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; + _byUsernameSel = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1; - if (!_filtered.isEmpty()) { - resize(width(), _filtered.size() * rh); - } else { - resize(width(), st::noContactsHeight); - } + refresh(); + + _searching = true; + emit searchByUsername(); } if (parentWidget()) parentWidget()->update(); loadProfilePhotos(0); @@ -392,7 +591,13 @@ void NewGroupInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { } 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; } } @@ -405,41 +610,52 @@ void NewGroupInner::selectSkip(int32 dir) { _mouseSel = false; int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir; if (_filter.isEmpty()) { + int cur = 0; if (_sel) { - if (dir > 0) { - while (dir && _sel->next->next) { - _sel = _sel->next; - --dir; - } - } else { - while (dir && _sel->prev) { - _sel = _sel->prev; - ++dir; - } + for (DialogRow *i = _contacts->list.begin; i != _sel; i = i->next) { + ++cur; } - } else if (dir > 0 && _contacts->list.count) { - _sel = _contacts->list.begin; + } 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 (_sel) { 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 { - if (dir > 0) { - if (_filteredSel < 0 && dir > 1) { - _filteredSel = 0; - } - _filteredSel += dir; - if (_filteredSel >= _filtered.size()) { - _filteredSel = _filtered.size() - 1; - } - } else if (_filteredSel > 0) { - _filteredSel += dir; - if (_filteredSel < 0) { - _filteredSel = 0; - } + 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 (_filteredSel >= 0) { 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(); @@ -460,6 +676,11 @@ QVector NewGroupInner::selectedInputs() { 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; } @@ -469,6 +690,11 @@ PeerData *NewGroupInner::selectedUser() { return i.key(); } } + for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { + if (d_byUsername[i]->check) { + return _byUsername[i]; + } + } return 0; } @@ -495,12 +721,81 @@ NewGroupBox::NewGroupBox() : _scroll(this, st::newGroupScroll), _inner(), connect(&_filter, SIGNAL(cancelled()), this, SLOT(onClose())); connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); 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(); _cache = myGrab(this, rect()); 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() { _filter.hide(); _scroll.hide(); diff --git a/Telegram/SourceFiles/boxes/newgroupbox.h b/Telegram/SourceFiles/boxes/newgroupbox.h index 93a2bc383..4313a147e 100644 --- a/Telegram/SourceFiles/boxes/newgroupbox.h +++ b/Telegram/SourceFiles/boxes/newgroupbox.h @@ -22,6 +22,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org class NewGroupInner : public QWidget { Q_OBJECT +private: + + struct ContactData; + public: NewGroupInner(); @@ -33,7 +37,7 @@ public: void mousePressEvent(QMouseEvent *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 selectSkip(int32 dir); @@ -45,6 +49,11 @@ public: void loadProfilePhotos(int32 yFrom); void changeCheckState(DialogRow *row); + void changeCheckState(ContactData *data); + + void peopleReceived(const QString &query, const QVector &people); + + void refresh(); ~NewGroupInner(); @@ -52,6 +61,7 @@ signals: void mustScrollTo(int ymin, int ymax); void selectAllQuery(); + void searchByUsername(); public slots: @@ -74,17 +84,26 @@ private: int32 _filteredSel; bool _mouseSel; - typedef struct { + struct ContactData { Text name; QString online; bool check; - } ContactData; + }; typedef QMap ContactsData; ContactsData _contactsData; int32 _selCount; ContactData *contactData(DialogRow *row); + bool _searching; + QString _lastQuery; + typedef QVector ByUsernameRows; + typedef QVector 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; LinkButton _addContactLnk; @@ -111,6 +130,9 @@ public slots: void onNext(); void onScroll(); + bool onSearchByUsername(bool searchCache = false); + void onNeedSearchByUsername(); + private: void hideAll(); @@ -126,6 +148,20 @@ private: QPixmap _cache; 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 PeopleCache; + PeopleCache _peopleCache; + + typedef QMap PeopleQueries; + PeopleQueries _peopleQueries; }; class CreateGroupBox : public LayeredWidget, public RPCSender { diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 77ec99c09..8c0a778ee 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 7024; -static const wchar_t *AppVersionStr = L"0.7.24"; +static const int32 AppVersion = 7025; +static const wchar_t *AppVersionStr = L"0.7.25"; static const bool DevChannel = true; static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index e7236d136..a4f62bf4e 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -900,26 +900,40 @@ void MentionsInner::paintEvent(QPaintEvent *e) { 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); } + p.setPen(st::black->p); if (_rows->isEmpty()) { QString tag = st::mentionFont->m.elidedText('#' + _hrows->at(last - i - 1), Qt::ElideRight, htagwidth); p.setFont(st::mentionFont->f); p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, tag); } else { UserData *user = _rows->at(last - i - 1); - QString uname = user->username; - int32 unamewidth = atwidth + st::mentionFont->m.width(uname), namewidth = user->nameText.maxWidth(); + 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 firstwidth = st::mentionFont->m.width(first), secondwidth = st::mentionFont->m.width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); if (availwidth < unamewidth + namewidth) { namewidth = (availwidth * namewidth) / (namewidth + unamewidth); unamewidth = availwidth - namewidth; - uname = st::mentionFont->m.elidedText('@' + uname, Qt::ElideRight, unamewidth); - } else { - uname = '@' + uname; + if (firstwidth <= unamewidth) { + if (firstwidth < unamewidth) { + 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(); 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); 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; } +const QString &MentionsDropdown::filter() const { + return _filter; +} + int32 MentionsDropdown::innerTop() { return _scroll.scrollTop(); } @@ -1257,7 +1275,7 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) { return true; } else if (ev->key() == Qt::Key_Down) { 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(); } } diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index cc5f2e8b5..7ba6bf008 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -291,6 +291,8 @@ public: bool animStep(float64 ms); + const QString &filter() const; + int32 innerTop(); int32 innerBottom(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 7bf7a3845..f7f97dc68 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -718,6 +718,8 @@ void MainWidget::addParticipantDone(ChatData *chat, const MTPmessages_StatedMess } 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 } return false; diff --git a/Telegram/SourceFiles/mtproto/mtp.cpp b/Telegram/SourceFiles/mtproto/mtp.cpp index 8411ceed0..14a0b764f 100644 --- a/Telegram/SourceFiles/mtproto/mtp.cpp +++ b/Telegram/SourceFiles/mtproto/mtp.cpp @@ -200,6 +200,7 @@ namespace { } } else { secs = m.captured(1).toInt(); + if (secs >= 60) return false; } uint64 sendAt = getms(true) + secs * 1000 + 10; DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end(); diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 244e1dd31..3f3ac69c9 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -717,6 +717,9 @@ void ProfileInner::contextMenuEvent(QContextMenuEvent *e) { if (info.contains(mapFromGlobal(e->globalPos()))) { _menu = new ContextMenu(this); _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(); connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); _menu->popup(e->globalPos()); @@ -735,6 +738,10 @@ void ProfileInner::onCopyPhone() { QApplication::clipboard()->setText(_phoneText); } +void ProfileInner::onCopyUsername() { + QApplication::clipboard()->setText('@' + _peerUser->username); +} + bool ProfileInner::animStep(float64 ms) { float64 dt = ms / st::setPhotoDuration; bool res = true; diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index 25a19b361..ee96bafe5 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -91,6 +91,7 @@ public slots: void onMenuDestroy(QObject *obj); void onCopyPhone(); + void onCopyUsername(); private: diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index 6404ac886..3044d9008 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -11,7 +11,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.7.24 + 0.7.25 CFBundleSignature ???? CFBundleURLTypes diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index 4bd3ccf752f3c45454a7b5cc0f380c4a28c6ab6d..144d213b8d284d11d0fa3d7e68a9d2274392b40d 100644 GIT binary patch delta 53 zcmZ3Yy+nIM5ig_Z