mirror of https://github.com/procxx/kepka.git
				
				
				
			New profile cover actions by buttons done.
Two new types of Observers: image loaded and async file dialog.
This commit is contained in:
		
							parent
							
								
									a510bb54ec
								
							
						
					
					
						commit
						46ad43bb1e
					
				|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
| }; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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(); ) { | ||||
|  |  | |||
|  | @ -203,6 +203,7 @@ public slots: | |||
| 
 | ||||
| 	void call_handleHistoryUpdate(); | ||||
| 	void call_handleUnreadCounterUpdate(); | ||||
| 	void call_handleFileDialogQueue(); | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 { | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
 | ||||
| } | ||||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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() { | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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: | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -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() { | ||||
|  |  | |||
|  | @ -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)); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue