New profile cover actions by buttons done.

Two new types of Observers: image loaded and async file dialog.
This commit is contained in:
John Preston 2016-05-25 20:59:21 +03:00
parent a510bb54ec
commit 46ad43bb1e
29 changed files with 578 additions and 139 deletions

View File

@ -97,6 +97,10 @@ void ApiWrap::resolveMessageDatas() {
}
}
void ApiWrap::updatesReceived(const MTPUpdates &updates) {
App::main()->sentUpdatesReceived(updates);
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) {
switch (msgs.type()) {
case mtpc_messages_messages: {
@ -679,6 +683,45 @@ void ApiWrap::requestStickerSets() {
}
}
void ApiWrap::joinChannel(ChannelData *channel) {
if (channel->amIn()) {
channelAmInUpdated(channel);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = MTP::send(MTPchannels_JoinChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel));
_channelAmInRequests.insert(channel, requestId);
}
}
void ApiWrap::leaveChannel(ChannelData *channel) {
if (!channel->amIn()) {
channelAmInUpdated(channel);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = MTP::send(MTPchannels_LeaveChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel));
_channelAmInRequests.insert(channel, requestId);
}
}
void ApiWrap::channelAmInUpdated(ChannelData *channel) {
Notify::PeerUpdate update(channel);
update.flags |= Notify::PeerUpdateFlag::ChannelAmIn;
Notify::peerUpdatedDelayed(update);
Notify::peerUpdatedSendDelayed();
}
void ApiWrap::channelAmInDone(ChannelData *channel, const MTPUpdates &updates) {
_channelAmInRequests.remove(channel);
updatesReceived(updates);
}
bool ApiWrap::channelAmInFail(ChannelData *channel, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
_channelAmInRequests.remove(channel);
return true;
}
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
_stickerSetRequests.remove(setId);

View File

@ -28,7 +28,7 @@ public:
ApiWrap(QObject *parent);
void init();
typedef SharedCallback<void, ChannelData*, MsgId> RequestMessageDataCallback;
using RequestMessageDataCallback = SharedCallback<void, ChannelData*, MsgId>;
void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr<RequestMessageDataCallback> callback);
void requestFullPeer(PeerData *peer);
@ -50,6 +50,9 @@ public:
void scheduleStickerSetRequest(uint64 setId, uint64 access);
void requestStickerSets();
void joinChannel(ChannelData *channel);
void leaveChannel(ChannelData *channel);
~ApiWrap();
signals:
@ -65,6 +68,8 @@ public slots:
private:
void updatesReceived(const MTPUpdates &updates);
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req);
struct MessageDataRequest {
MessageDataRequest() : req(0) {
@ -120,4 +125,9 @@ private:
void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
bool gotStickerSetFail(uint64 setId, const RPCError &error);
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
void channelAmInUpdated(ChannelData *channel);
void channelAmInDone(ChannelData *channel, const MTPUpdates &updates);
bool channelAmInFail(ChannelData *channel, const RPCError &error);
};

View File

@ -369,7 +369,7 @@ namespace {
UserData *result = nullptr;
for_const (auto &user, users.c_vector().v) {
UserData *data = nullptr;
bool wasContact = false, canShareContact = false, minimal = false;
bool wasContact = false, minimal = false;
const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty();
Notify::PeerUpdate update;
@ -548,12 +548,13 @@ namespace {
switch (chat.type()) {
case mtpc_chat: {
const auto &d(chat.c_chat());
auto &d(chat.c_chat());
data = App::chat(peerFromChat(d.vid.v));
data->input = MTP_inputPeerChat(d.vid);
auto cdata = data->asChat();
auto canEdit = cdata->canEdit();
ChatData *cdata = data->asChat();
data->input = MTP_inputPeerChat(d.vid);
cdata->setNameDelayed(qs(d.vtitle));
cdata->setPhoto(d.vphoto);
cdata->date = d.vdate.v;
@ -604,14 +605,18 @@ namespace {
cdata->version = d.vversion.v;
cdata->invalidateParticipants();
}
if (canEdit != cdata->canEdit()) {
update.flags |= UpdateFlag::ChatCanEdit;
}
} break;
case mtpc_chatForbidden: {
const auto &d(chat.c_chatForbidden());
auto &d(chat.c_chatForbidden());
data = App::chat(peerFromChat(d.vid.v));
data->input = MTP_inputPeerChat(d.vid);
auto cdata = data->asChat();
auto canEdit = cdata->canEdit();
ChatData *cdata = data->asChat();
data->input = MTP_inputPeerChat(d.vid);
cdata->setNameDelayed(qs(d.vtitle));
cdata->setPhoto(MTP_chatPhotoEmpty());
cdata->date = 0;
@ -619,27 +624,32 @@ namespace {
cdata->invalidateParticipants();
cdata->flags = 0;
cdata->isForbidden = true;
if (canEdit != cdata->canEdit()) {
update.flags |= UpdateFlag::ChatCanEdit;
}
} break;
case mtpc_channel: {
const auto &d(chat.c_channel());
auto &d(chat.c_channel());
PeerId peer(peerFromChannel(d.vid.v));
auto peerId = peerFromChannel(d.vid.v);
minimal = d.is_min();
if (minimal) {
data = App::channelLoaded(peer);
data = App::channelLoaded(peerId);
if (!data) {
continue; // minimal is not loaded, need to make getDifference
}
} else {
data = App::channel(peer);
data = App::channel(peerId);
data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
}
ChannelData *cdata = data->asChannel();
auto cdata = data->asChannel();
auto wasInChannel = cdata->amIn();
auto canEditPhoto = cdata->canEditPhoto();
auto canAddMembers = cdata->canAddParticipants();
if (minimal) {
int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy;
MTPDchannel::Flags mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy;
cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask);
} else {
cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
@ -662,19 +672,27 @@ namespace {
cdata->flagsUpdated();
cdata->setPhoto(d.vphoto);
if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) {
if (wasInChannel != cdata->amIn()) {
update.flags |= UpdateFlag::ChannelAmIn;
}
if (canEditPhoto != cdata->canEditPhoto()) {
update.flags |= UpdateFlag::ChannelCanEditPhoto;
}
if (canAddMembers != cdata->canAddParticipants()) {
update.flags |= UpdateFlag::ChannelCanAddMembers;
}
} break;
case mtpc_channelForbidden: {
const auto &d(chat.c_channelForbidden());
auto &d(chat.c_channelForbidden());
PeerId peer(peerFromChannel(d.vid.v));
data = App::channel(peer);
auto peerId = peerFromChannel(d.vid.v);
data = App::channel(peerId);
data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash);
ChannelData *cdata = data->asChannel();
auto cdata = data->asChannel();
auto wasInChannel = cdata->amIn();
auto canEditPhoto = cdata->canEditPhoto();
auto canAddMembers = cdata->canAddParticipants();
cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
@ -686,9 +704,15 @@ namespace {
cdata->count = 0;
cdata->isForbidden = true;
if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) {
if (wasInChannel != cdata->amIn()) {
update.flags |= UpdateFlag::ChannelAmIn;
}
if (canEditPhoto != cdata->canEditPhoto()) {
update.flags |= UpdateFlag::ChannelCanEditPhoto;
}
if (canAddMembers != cdata->canAddParticipants()) {
update.flags |= UpdateFlag::ChannelCanAddMembers;
}
} break;
}
if (!data) continue;

View File

@ -27,10 +27,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "lang.h"
#include "boxes/confirmbox.h"
#include "ui/filedialog.h"
#include "langloaderplain.h"
#include "localstorage.h"
#include "autoupdater.h"
#include "core/observer.h"
#include "observer_peer.h"
namespace {
void mtpStateChanged(int32 dc, int32 state) {
@ -817,6 +819,7 @@ void AppClass::doMtpUnpause() {
void AppClass::selfPhotoCleared(const MTPUserProfilePhoto &result) {
if (!App::self()) return;
App::self()->setPhoto(result);
Notify::peerUpdatedSendDelayed();
emit peerPhotoDone(App::self()->id);
}
@ -906,6 +909,14 @@ void AppClass::call_handleUnreadCounterUpdate() {
}
}
void AppClass::call_handleFileDialogQueue() {
while (true) {
if (!FileDialog::processQuery()) {
return;
}
}
}
void AppClass::killDownloadSessions() {
uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {

View File

@ -203,6 +203,7 @@ public slots:
void call_handleHistoryUpdate();
void call_handleUnreadCounterUpdate();
void call_handleFileDialogQueue();
private:

View File

@ -2003,11 +2003,11 @@ MembersFilter MembersInner::filter() const {
return _filter;
}
QMap<UserData*, bool> MembersInner::already() const {
MembersAlreadyIn MembersInner::already() const {
MembersAlreadyIn result;
for (int32 i = 0, l = _rows.size(); i < l; ++i) {
if (_rows.at(i)->isUser()) {
result.insert(_rows.at(i)->asUser(), true);
for_const (auto peer, _rows) {
if (peer->isUser()) {
result.insert(peer->asUser());
}
}
return result;

View File

@ -31,7 +31,7 @@ enum MembersFilter {
MembersFilterRecent,
MembersFilterAdmins,
};
typedef QMap<UserData*, bool> MembersAlreadyIn;
using MembersAlreadyIn = OrderedSet<UserData*>;
QString cantInviteError();
@ -318,7 +318,7 @@ public:
}
void clearSel();
QMap<UserData*, bool> already() const;
MembersAlreadyIn already() const;
~MembersInner();

View File

@ -33,6 +33,13 @@ T *getPointerAndReset(T *&ptr) {
return result;
}
template <typename T>
T createAndSwap(T &value) {
T result;
std::swap(result, value);
return result;
}
struct NullType {
};

View File

@ -30,6 +30,8 @@ NeverFreedPointer<StartCallbacksList> StartCallbacks;
NeverFreedPointer<FinishCallbacksList> FinishCallbacks;
UnregisterObserverCallback UnregisterCallbacks[256]/* = { nullptr }*/;
ObservedEvent LastRegisteredEvent/* = 0*/;
} // namespace
void startObservers() {
@ -50,17 +52,18 @@ void finishObservers() {
FinishCallbacks.clear();
}
ObservedEventRegistrator::ObservedEventRegistrator(ObservedEvent event
, StartObservedEventCallback startCallback
ObservedEventRegistrator::ObservedEventRegistrator(StartObservedEventCallback startCallback
, FinishObservedEventCallback finishCallback
, UnregisterObserverCallback unregisterCallback) {
_event = LastRegisteredEvent++;
StartCallbacks.makeIfNull();
StartCallbacks->push_back(startCallback);
FinishCallbacks.makeIfNull();
FinishCallbacks->push_back(finishCallback);
UnregisterCallbacks[event] = unregisterCallback;
UnregisterCallbacks[_event] = unregisterCallback;
}
// Observer base interface.

View File

@ -42,10 +42,16 @@ using FinishObservedEventCallback = void(*)();
// unregisterCallback will be used to destroy connections.
class ObservedEventRegistrator {
public:
ObservedEventRegistrator(ObservedEvent event
, StartObservedEventCallback startCallback
, FinishObservedEventCallback finishCallback
, UnregisterObserverCallback unregisterCallback);
ObservedEventRegistrator(StartObservedEventCallback startCallback,
FinishObservedEventCallback finishCallback,
UnregisterObserverCallback unregisterCallback);
inline ObservedEvent event() const {
return _event;
}
private:
ObservedEvent _event;
};
@ -88,8 +94,12 @@ struct ObserversList {
QVector<int> freeIndices;
};
// If no filtering by flags is done, you can use this value in both
// Notify::registerObserver() and Notify::notifyObservers()
constexpr int UniversalFlag = 0x01;
template <typename Flags, typename Handler>
int registerObserver(ObserversList<Flags, Handler> &list, Flags flags, Handler &&handler) {
ConnectionId registerObserver(ObservedEvent event, ObserversList<Flags, Handler> &list, Flags flags, Handler &&handler) {
while (!list.freeIndices.isEmpty()) {
auto freeIndex = list.freeIndices.back();
list.freeIndices.pop_back();
@ -100,7 +110,8 @@ int registerObserver(ObserversList<Flags, Handler> &list, Flags flags, Handler &
}
}
list.entries.push_back({ flags, std_::move(handler) });
return list.entries.size() - 1;
int connectionIndex = list.entries.size() - 1;
return (static_cast<uint32>(event) << 24) | static_cast<uint32>(connectionIndex + 1);
}
template <typename Flags, typename Handler>
@ -131,17 +142,26 @@ namespace internal {
template <typename ObserverType, int>
struct ObserverRegisteredGeneric {
static void call(ObserverType *observer, ConnectionId connection) {
static inline void call(ObserverType *observer, ConnectionId connection) {
observer->observerRegistered(connection);
}
};
template<typename ObserverType>
template <typename ObserverType>
struct ObserverRegisteredGeneric<ObserverType, true> {
static void call(ObserverType *observer, ConnectionId connection) {
static inline void call(ObserverType *observer, ConnectionId connection) {
observerRegisteredDefault(observer, connection);
}
};
} // namespace internal
template <typename ObserverType>
inline void observerRegistered(ObserverType *observer, ConnectionId connection) {
// For derivatives of the Observer class we call special friend function observerRegistered().
// For all other classes we call just a member function observerRegistered().
using ObserverRegistered = internal::ObserverRegisteredGeneric<ObserverType, std_::is_base_of<Observer, ObserverType>::value>;
ObserverRegistered::call(observer, connection);
}
} // namespace Notify

View File

@ -519,6 +519,7 @@ struct Data {
uint64 LaunchId = 0;
SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" };
SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" };
SingleDelayedCall HandleFileDialogQueue = { App::app(), "call_handleFileDialogQueue" };
Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout;
bool AdaptiveForWide = true;
@ -582,6 +583,7 @@ void finish() {
DefineReadOnlyVar(Global, uint64, LaunchId);
DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate);
DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate);
DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue);
DefineVar(Global, Adaptive::Layout, AdaptiveLayout);
DefineVar(Global, bool, AdaptiveForWide);

View File

@ -204,6 +204,7 @@ void finish();
DeclareReadOnlyVar(uint64, LaunchId);
DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate);
DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate);
DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue);
DeclareVar(Adaptive::Layout, AdaptiveLayout);
DeclareVar(bool, AdaptiveForWide);

View File

@ -22,7 +22,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mtproto/core_types.h"
#include "mtproto/session.h"
#include "mtproto/file_download.h"
namespace MTP {

View File

@ -208,6 +208,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
}
emit App::wnd()->imageLoaded();
emit progress(this);
FileDownload::internal::notifyImageLoaded();
loadNext();
}
@ -549,6 +550,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): not done yet, _lastComplete=%2, _size=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(Logs::b(_lastComplete)).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests)));
}
emit progress(this);
if (_complete) {
FileDownload::internal::notifyImageLoaded();
}
loadNext();
}
@ -678,6 +682,7 @@ void webFileLoader::onFinished(const QByteArray &data) {
Local::writeWebFile(_url, _data);
}
emit progress(this);
FileDownload::internal::notifyImageLoaded();
loadNext();
}
@ -1089,3 +1094,45 @@ namespace MTP {
++GlobalPriority;
}
}
namespace FileDownload {
namespace {
using internal::ImageLoadedHandler;
using ImageLoadedObserversList = Notify::ObserversList<int, ImageLoadedHandler>;
NeverFreedPointer<ImageLoadedObserversList> ImageLoadedObservers;
void StartCallback() {
ImageLoadedObservers.makeIfNull();
}
void FinishCallback() {
ImageLoadedObservers.clear();
}
void UnregisterCallback(int connectionIndex) {
t_assert(!ImageLoadedObservers.isNull());
Notify::unregisterObserver(*ImageLoadedObservers, connectionIndex);
}
Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback);
bool Started() {
return !ImageLoadedObservers.isNull();
}
} // namespace
namespace internal {
Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler) {
t_assert(Started());
auto connectionId = Notify::registerObserver(creator.event(), *ImageLoadedObservers
, Notify::UniversalFlag, std_::forward<ImageLoadedHandler>(handler));
return connectionId;
}
void notifyImageLoaded() {
Notify::notifyObservers(*ImageLoadedObservers, Notify::UniversalFlag);
}
} // namespace internal
}

View File

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
namespace MTP {
void clearLoaderPriorities();
}
@ -391,3 +393,21 @@ static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation<WebL
void reinitWebLoadManager();
void stopWebLoadManager();
namespace FileDownload {
namespace internal {
using ImageLoadedHandler = Function<void>;
Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler);
void notifyImageLoaded();
} // namespace internal
template <typename ObserverType>
void registerImageLoadedObserver(ObserverType *observer, void (ObserverType::*handler)()) {
auto connection = internal::plainRegisterImageLoadedObserver(func(observer, handler));
Notify::observerRegistered(observer, connection);
}
} // namespace FileDownload

View File

@ -32,7 +32,6 @@ namespace Notify {
namespace internal {
namespace {
constexpr ObservedEvent PeerUpdateEvent = 0x01;
using PeerObserversList = ObserversList<PeerUpdateFlags, PeerUpdateHandler>;
NeverFreedPointer<PeerObserversList> PeerUpdateObservers;
@ -55,7 +54,7 @@ void UnregisterCallback(int connectionIndex) {
t_assert(!PeerUpdateObservers.isNull());
unregisterObserver(*PeerUpdateObservers, connectionIndex);
}
ObservedEventRegistrator creator(PeerUpdateEvent, StartCallback, FinishCallback, UnregisterCallback);
ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback);
bool Started() {
return !PeerUpdateObservers.isNull();
@ -65,9 +64,9 @@ bool Started() {
ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) {
t_assert(Started());
auto connectionId = registerObserver(*PeerUpdateObservers, events, std_::forward<PeerUpdateHandler>(handler));
t_assert(connectionId >= 0 && connectionId < 0x01000000);
return (static_cast<uint32>(PeerUpdateEvent) << 24) | static_cast<uint32>(connectionId + 1);
auto connectionId = registerObserver(creator.event(), *PeerUpdateObservers
, events, std_::forward<PeerUpdateHandler>(handler));
return connectionId;
}
void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) {
@ -116,10 +115,8 @@ void peerUpdatedSendDelayed() {
if (internal::SmallUpdates->isEmpty()) return;
internal::SmallUpdatesList smallList;
internal::AllUpdatesList allList;
std::swap(smallList, *internal::SmallUpdates);
std::swap(allList, *internal::AllUpdates);
auto smallList = createAndSwap(*internal::SmallUpdates);
auto allList = createAndSwap(*internal::AllUpdates);
for_const (auto &update, smallList) {
notifyObservers(*internal::PeerUpdateObservers, update.flags, update);
}

View File

@ -30,16 +30,17 @@ namespace Notify {
// 0xFFFF0000U for specific peer updates (valid for user / chat / channel).
enum class PeerUpdateFlag {
NameChanged = 0x00000001U,
UsernameChanged = 0x00000002U,
NameChanged = 0x00000001U,
UsernameChanged = 0x00000002U,
PhotoChanged = 0x00000004U,
UserCanShareContact = 0x00010000U,
UserCanShareContact = 0x00010000U,
ChatCanEdit = 0x00010000U,
ChatCanEdit = 0x00010000U,
ChannelAmIn = 0x00010000U,
MegagroupCanEditPhoto = 0x00020000U,
MegagroupCanAddMembers = 0x00040000U,
ChannelAmIn = 0x00010000U,
ChannelCanEditPhoto = 0x00020000U,
ChannelCanAddMembers = 0x00040000U,
};
Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag);
Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags);
@ -68,11 +69,7 @@ ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler
template <typename ObserverType>
void registerPeerObserver(PeerUpdateFlags events, ObserverType *observer, void (ObserverType::*handler)(const PeerUpdate &)) {
auto connection = internal::plainRegisterPeerObserver(events, func(observer, handler));
// For derivatives of the Observer class we call special friend function observerRegistered().
// For all other classes we call just a member function observerRegistered().
using ObserverRegistered = internal::ObserverRegisteredGeneric<ObserverType, std_::is_base_of<Observer, ObserverType>::value>;
ObserverRegistered::call(observer, connection);
observerRegistered(observer, connection);
}
} // namespace Notify

View File

@ -23,8 +23,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "styles/style_profile.h"
#include "ui/buttons/round_button.h"
#include "ui/filedialog.h"
#include "observer_peer.h"
#include "boxes/confirmbox.h"
#include "boxes/contactsbox.h"
#include "boxes/photocropbox.h"
#include "lang.h"
#include "apiwrap.h"
#include "mainwidget.h"
@ -62,31 +65,61 @@ private:
const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserCanShareContact
| Notify::PeerUpdateFlag::ChatCanEdit
| Notify::PeerUpdateFlag::MegagroupCanEditPhoto
| Notify::PeerUpdateFlag::MegagroupCanAddMembers
| Notify::PeerUpdateFlag::ChannelCanEditPhoto
| Notify::PeerUpdateFlag::ChannelCanAddMembers
| Notify::PeerUpdateFlag::ChannelAmIn;
} // namespace
class PhotoButton final : public Button {
class PhotoButton final : public Button, public Notify::Observer {
public:
PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) {
resize(st::profilePhotoSize, st::profilePhotoSize);
}
void photoUpdated() {
bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId);
setCursor(hasPhoto ? style::cur_pointer : style::cur_default);
processNewPeerPhoto();
Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &PhotoButton::notifyPeerUpdated);
FileDownload::registerImageLoadedObserver(this, &PhotoButton::notifyImageLoaded);
}
protected:
void paintEvent(QPaintEvent *e) {
Painter p(this);
_peer->paintUserpic(p, st::profilePhotoSize, 0, 0);
p.drawPixmap(0, 0, _userpic);
}
private:
void notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != _peer) {
return;
}
processNewPeerPhoto();
this->update();
}
void notifyImageLoaded() {
if (_waiting && _peer->userpicLoaded()) {
_waiting = false;
_userpic = _peer->genUserpic(st::profilePhotoSize);
update();
}
}
void processNewPeerPhoto() {
bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId);
setCursor(hasPhoto ? style::cur_pointer : style::cur_default);
_waiting = !_peer->userpicLoaded();
if (_waiting) {
_peer->loadUserpic(true);
} else {
_userpic = _peer->genUserpic(st::profilePhotoSize);
}
}
PeerData *_peer;
bool _waiting = false;
QPixmap _userpic;
};
@ -101,8 +134,8 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent)
auto observeEvents = ButtonsUpdateFlags | Notify::PeerUpdateFlag::NameChanged;
Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated);
FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated);
_photoButton->photoUpdated();
connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow()));
refreshNameText();
@ -132,12 +165,9 @@ void CoverWidget::resizeToWidth(int newWidth) {
_statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop);
int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft;
if (_primaryButton) {
_primaryButton->moveToLeft(buttonLeft, st::profileButtonTop);
buttonLeft += _primaryButton->width() + st::profileButtonSkip;
}
if (_secondaryButton) {
_secondaryButton->moveToLeft(buttonLeft, st::profileButtonTop);
for_const (auto button, _buttons) {
button->moveToLeft(buttonLeft, st::profileButtonTop);
buttonLeft += button->width() + st::profileButtonSkip;
}
newHeight += st::profilePhotoSize;
@ -178,13 +208,14 @@ void CoverWidget::paintDivider(Painter &p) {
}
void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer == _peer) {
if ((update.flags & ButtonsUpdateFlags) != 0) {
refreshButtons();
}
if (update.flags & Notify::PeerUpdateFlag::NameChanged) {
refreshNameText();
}
if (update.peer != _peer) {
return;
}
if ((update.flags & ButtonsUpdateFlags) != 0) {
refreshButtons();
}
if (update.flags & Notify::PeerUpdateFlag::NameChanged) {
refreshNameText();
}
}
@ -247,6 +278,7 @@ bool CoverWidget::isUsingMegagroupOnlineCount() const {
}
void CoverWidget::refreshButtons() {
clearButtons();
if (_peerUser) {
setUserButtons();
} else if (_peerChat) {
@ -260,65 +292,51 @@ void CoverWidget::refreshButtons() {
}
void CoverWidget::setUserButtons() {
setPrimaryButton(lang(lng_profile_send_message), SLOT(onSendMessage()));
addButton(lang(lng_profile_send_message), SLOT(onSendMessage()));
if (_peerUser->canShareThisContact()) {
setSecondaryButton(lang(lng_profile_share_contact), SLOT(onShareContact()));
} else {
clearSecondaryButton();
addButton(lang(lng_profile_share_contact), SLOT(onShareContact()));
}
}
void CoverWidget::setChatButtons() {
if (_peerChat->canEdit()) {
setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember()));
} else {
clearPrimaryButton();
clearSecondaryButton();
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
addButton(lang(lng_profile_add_participant), SLOT(onAddMember()));
}
}
void CoverWidget::setMegagroupButtons() {
if (_peerMegagroup->canEditPhoto()) {
setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
} else {
clearPrimaryButton();
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
}
if (_peerMegagroup->canAddParticipants()) {
setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember()));
} else {
clearSecondaryButton();
addButton(lang(lng_profile_add_participant), SLOT(onAddMember()));
}
}
void CoverWidget::setChannelButtons() {
if (_peerChannel->amCreator()) {
setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
} else if (_peerChannel->amIn()) {
setPrimaryButton(lang(lng_profile_view_channel), SLOT(onViewChannel()));
addButton(lang(lng_profile_view_channel), SLOT(onViewChannel()));
} else {
setPrimaryButton(lang(lng_profile_join_channel), SLOT(onJoin()));
}
clearSecondaryButton();
}
void CoverWidget::setPrimaryButton(const QString &text, const char *slot) {
delete _primaryButton;
_primaryButton = nullptr;
if (!text.isEmpty()) {
_primaryButton = new Ui::RoundButton(this, text, st::profilePrimaryButton);
connect(_primaryButton, SIGNAL(clicked()), this, slot);
_primaryButton->show();
addButton(lang(lng_profile_join_channel), SLOT(onJoin()));
}
}
void CoverWidget::setSecondaryButton(const QString &text, const char *slot) {
delete _secondaryButton;
_secondaryButton = nullptr;
void CoverWidget::clearButtons() {
auto buttons = createAndSwap(_buttons);
for_const (auto button, buttons) {
delete button;
}
}
void CoverWidget::addButton(const QString &text, const char *slot) {
if (!text.isEmpty()) {
_secondaryButton = new Ui::RoundButton(this, text, st::profileSecondaryButton);
connect(_secondaryButton, SIGNAL(clicked()), this, slot);
_secondaryButton->show();
auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton;
_buttons.push_back(new Ui::RoundButton(this, text, buttonStyle));
connect(_buttons.back(), SIGNAL(clicked()), this, slot);
_buttons.back()->show();
}
}
@ -331,7 +349,37 @@ void CoverWidget::onShareContact() {
}
void CoverWidget::onSetPhoto() {
QStringList imgExtensions(cImgExtensions());
QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)"));
_setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter);
}
void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) {
if (_setPhotoFileQueryId != update.queryId) {
return;
}
_setPhotoFileQueryId = 0;
if (update.filePaths.isEmpty() && update.remoteContent.isEmpty()) {
return;
}
QImage img;
if (!update.remoteContent.isEmpty()) {
img = App::readImage(update.remoteContent);
} else {
img = App::readImage(update.filePaths.front());
}
if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) {
Ui::showLayer(new InformBox(lang(lng_bad_photo)));
return;
}
auto box = new PhotoCropBox(img, _peer);
connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart()));
Ui::showLayer(box);
}
void CoverWidget::onAddMember() {
@ -339,15 +387,17 @@ void CoverWidget::onAddMember() {
Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
} else if (_peerChannel && _peerChannel->mgInfo) {
MembersAlreadyIn already;
for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) {
already.insert(*i, true);
for_const (auto user, _peerChannel->mgInfo->lastParticipants) {
already.insert(user);
}
Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
}
}
void CoverWidget::onJoin() {
if (!_peerChannel) return;
App::api()->joinChannel(_peerChannel);
}
void CoverWidget::onViewChannel() {

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once
#include "core/observer.h"
#include "ui/filedialog.h"
namespace Ui {
class RoundButton;
@ -60,6 +61,7 @@ protected:
private:
// Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update);
void refreshNameText();
void refreshStatusText();
@ -71,14 +73,8 @@ private:
void setMegagroupButtons();
void setChannelButtons();
void setPrimaryButton(const QString &text, const char *slot);
void setSecondaryButton(const QString &text, const char *slot);
void clearPrimaryButton() {
setPrimaryButton(QString(), nullptr);
}
void clearSecondaryButton() {
setSecondaryButton(QString(), nullptr);
}
void clearButtons();
void addButton(const QString &text, const char *slot);
void paintDivider(Painter &p);
@ -97,10 +93,11 @@ private:
QPoint _statusPosition;
QString _statusText;
int _dividerTop;
QList<Ui::RoundButton*> _buttons;
ChildWidget<Ui::RoundButton> _primaryButton = { nullptr };
ChildWidget<Ui::RoundButton> _secondaryButton = { nullptr };
int _dividerTop = 0;
FileDialog::QueryId _setPhotoFileQueryId = 0;
};

View File

@ -38,7 +38,7 @@ public:
}
protected:
void paintEvent(QPaintEvent *e) {
void paintEvent(QPaintEvent *e) override {
Painter p(this);
p.fillRect(e->rect(), st::profileBg);
@ -48,6 +48,11 @@ protected:
p.setPen(st::profileTopBarBackFg);
p.drawTextLeft(st::profileTopBarBackPosition.x(), st::profileTopBarBackPosition.y(), width(), lang(lng_menu_back));
}
void onStateChanged(int oldState, ButtonStateChangeSource source) override {
if ((_state & Button::StateDown) && !(oldState & Button::StateDown)) {
emit clicked();
}
}
private:

View File

@ -45,7 +45,7 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
int notDisplayedAtBottom = height() - _visibleBottom;
if (notDisplayedAtBottom > 0) {
// decreaseAdditionalHeight(notDisplayedAtBottom);
decreaseAdditionalHeight(notDisplayedAtBottom);
}
//loadProfilePhotos(_visibleTop);
@ -66,8 +66,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.fillRect(e->rect(), st::profileBg);
}
void InnerWidget::mousePressEvent(QMouseEvent *e) { // TEMP for testing
Ui::showPeerOverview(_peer, OverviewPhotos);
void InnerWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
emit cancelled();
}
}
int InnerWidget::resizeGetHeight(int newWidth) {

View File

@ -41,9 +41,12 @@ public:
// Updates the area that is visible inside the scroll container.
void setVisibleTopBottom(int visibleTop, int visibleBottom);
signals:
void cancelled();
protected:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override; // TEMP for testing
void keyPressEvent(QKeyEvent *e) override;
private:
// Resizes content and counts natural widget height for the desired width.

View File

@ -48,6 +48,7 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent)
connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(updateSelected()));
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
connect(_inner, SIGNAL(cancelled()), _fixedBar, SLOT(onBack()));
}
void Widget::updateAdaptiveLayout() {

View File

@ -396,8 +396,8 @@ void ProfileInner::onAddParticipant() {
Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
} else if (_peerChannel && _peerChannel->mgInfo) {
MembersAlreadyIn already;
for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) {
already.insert(*i, true);
for_const (auto user, _peerChannel->mgInfo->lastParticipants) {
already.insert(user);
}
Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
}

View File

@ -229,6 +229,10 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer a
if (App::main()) {
emit App::main()->peerPhotoChanged(this);
}
Notify::PeerUpdate update(this);
update.flags = Notify::PeerUpdateFlag::PhotoChanged;
Notify::peerUpdatedDelayed(update);
}
}
@ -401,6 +405,10 @@ void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Loc
setUserpic(newPhoto);
photoLoc = newPhotoLoc;
emit App::main()->peerPhotoChanged(this);
Notify::PeerUpdate update(this);
update.flags = Notify::PeerUpdateFlag::PhotoChanged;
Notify::peerUpdatedDelayed(update);
}
}
@ -439,6 +447,10 @@ void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see
setUserpic(newPhoto);
photoLoc = newPhotoLoc;
if (App::main()) emit App::main()->peerPhotoChanged(this);
Notify::PeerUpdate update(this);
update.flags = Notify::PeerUpdateFlag::PhotoChanged;
Notify::peerUpdatedDelayed(update);
}
}

View File

@ -309,6 +309,9 @@ public:
void loadUserpic(bool loadFirst = false, bool prior = true) {
_userpic->load(loadFirst, prior);
}
bool userpicLoaded() const {
return _userpic->loaded();
}
StorageKey userpicUniqueKey() const;
void saveUserpic(const QString &path) const;
QPixmap genUserpic(int size) const;

View File

@ -234,3 +234,150 @@ QString filedialogNextFilename(const QString &name, const QString &cur, const QS
}
return result;
}
namespace FileDialog {
namespace {
using internal::QueryUpdateHandler;
using QueryObserversList = Notify::ObserversList<int, QueryUpdateHandler>;
NeverFreedPointer<QueryObserversList> QueryUpdateObservers;
struct Query {
enum class Type {
ReadFile,
ReadFiles,
WriteFile,
ReadFolder,
};
Query(Type type
, const QString &caption = QString()
, const QString &filter = QString()
, const QString &filePath = QString()) : id(rand_value<QueryId>())
, type(type)
, caption(caption)
, filter(filter)
, filePath(filePath) {
}
QueryId id;
Type type;
QString caption, filter, filePath;
};
using QueryList = QList<Query>;
NeverFreedPointer<QueryList> Queries;
void StartCallback() {
QueryUpdateObservers.makeIfNull();
Queries.makeIfNull();
}
void FinishCallback() {
QueryUpdateObservers.clear();
Queries.clear();
}
void UnregisterCallback(int connectionIndex) {
t_assert(!QueryUpdateObservers.isNull());
Notify::unregisterObserver(*QueryUpdateObservers, connectionIndex);
}
Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback);
bool Started() {
return !QueryUpdateObservers.isNull();
}
} // namespace
QueryId queryReadFile(const QString &caption, const QString &filter) {
t_assert(Started());
Queries->push_back(Query(Query::Type::ReadFile, caption, filter));
Global::RefHandleFileDialogQueue().call();
return Queries->back().id;
}
QueryId queryReadFiles(const QString &caption, const QString &filter) {
t_assert(Started());
Queries->push_back(Query(Query::Type::ReadFiles, caption, filter));
Global::RefHandleFileDialogQueue().call();
return Queries->back().id;
}
QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath) {
t_assert(Started());
Queries->push_back(Query(Query::Type::WriteFile, caption, filter, filePath));
Global::RefHandleFileDialogQueue().call();
return Queries->back().id;
}
QueryId queryReadFolder(const QString &caption) {
t_assert(Started());
Queries->push_back(Query(Query::Type::ReadFolder, caption));
Global::RefHandleFileDialogQueue().call();
return Queries->back().id;
}
bool processQuery() {
if (!Started() || !Global::started() || Queries->isEmpty()) return false;
auto query = Queries->front();
Queries->pop_front();
QueryUpdate update(query.id);
switch (query.type) {
case Query::Type::ReadFile: {
QString file;
QByteArray remoteContent;
if (filedialogGetOpenFile(file, remoteContent, query.caption, query.filter)) {
if (!file.isEmpty()) {
update.filePaths.push_back(file);
}
update.remoteContent = remoteContent;
}
} break;
case Query::Type::ReadFiles: {
QStringList files;
QByteArray remoteContent;
if (filedialogGetOpenFiles(files, remoteContent, query.caption, query.filter)) {
update.filePaths = files;
update.remoteContent = remoteContent;
}
} break;
case Query::Type::WriteFile: {
QString file;
if (filedialogGetSaveFile(file, query.caption, query.filter, query.filePath)) {
if (!file.isEmpty()) {
update.filePaths.push_back(file);
}
}
} break;
case Query::Type::ReadFolder: {
QString folder;
if (filedialogGetDir(folder, query.caption)) {
if (!folder.isEmpty()) {
update.filePaths.push_back(folder);
}
}
} break;
}
// No one know what happened during filedialogGet*() call in the event loop.
if (!Started() || !Global::started()) return false;
Notify::notifyObservers(*QueryUpdateObservers, Notify::UniversalFlag, update);
return true;
}
namespace internal {
Notify::ConnectionId plainRegisterObserver(QueryUpdateHandler &&handler) {
t_assert(Started());
auto connectionId = Notify::registerObserver(creator.event(), *QueryUpdateObservers
, Notify::UniversalFlag, std_::forward<QueryUpdateHandler>(handler));
return connectionId;
}
} // namespace internal
} // namespace FileDialog

View File

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
void filedialogInit();
bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter);
bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter);
@ -28,3 +30,38 @@ bool filedialogGetDir(QString &dir, const QString &caption);
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false);
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString());
namespace FileDialog {
using QueryId = uint64;
struct QueryUpdate {
QueryUpdate(QueryId id) : queryId(id) {
}
QueryId queryId;
QStringList filePaths;
QByteArray remoteContent;
};
QueryId queryReadFile(const QString &caption, const QString &filter);
QueryId queryReadFiles(const QString &caption, const QString &filter);
QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath);
QueryId queryReadFolder(const QString &caption);
// Returns false if no need to call it anymore right now.
// NB! This function enters an event loop.
bool processQuery();
namespace internal {
using QueryUpdateHandler = Function<void, const QueryUpdate&>;
Notify::ConnectionId plainRegisterObserver(QueryUpdateHandler &&handler);
} // namespace internal
template <typename ObserverType>
void registerObserver(ObserverType *observer, void (ObserverType::*handler)(const QueryUpdate &)) {
auto connection = internal::plainRegisterObserver(func(observer, handler));
Notify::observerRegistered(observer, connection);
}
} // namespace FileDialog

View File

@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <QtGui/QPixmap>
#include "mtproto/file_download.h"
QImage imageBlur(QImage img);
void imageRound(QImage &img);