diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c712b264f6..c73defa5b2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -432,6 +432,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_delete_contact" = "Delete"; "lng_profile_set_group_photo" = "Set Photo"; "lng_profile_add_participant" = "Add Members"; +"lng_profile_view_channel" = "View Channel"; +"lng_profile_join_channel" = "Join"; "lng_profile_delete_and_exit" = "Leave"; "lng_profile_kick" = "Remove"; "lng_profile_admin" = "admin"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 5c9ab777c7..a43eced3e7 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "apiwrap.h" #include "numbers.h" +#include "observer_peer.h" namespace { App::LaunchState _launchState = App::Launched; @@ -365,20 +366,23 @@ namespace { } UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated) { - UserData *data = 0; - const auto &v(users.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &user(*i); - data = 0; - bool wasContact = false, minimal = false; + UserData *result = nullptr; + for_const (auto &user, users.c_vector().v) { + UserData *data = nullptr; + bool wasContact = false, canShareContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdateFlag; + switch (user.type()) { case mtpc_userEmpty: { const auto &d(user.c_userEmpty()); PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); + auto canShareThisContact = data->canShareThisContact(); + data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); data->setName(lang(lng_deleted), QString(), QString(), QString()); @@ -389,6 +393,10 @@ namespace { wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; + + if (canShareThisContact != data->canShareThisContact()) { + update.flags |= UpdateFlag::UserCanShareContact; + } } break; case mtpc_user: { const auto &d(user.c_user()); @@ -396,6 +404,8 @@ namespace { PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); + auto canShareThisContact = data->canShareThisContact(); + if (!minimal) { data->flags = d.vflags.v; if (d.is_self()) { @@ -478,6 +488,10 @@ namespace { if (App::wnd()) App::wnd()->updateGlobalMenu(); } } + + if (canShareThisContact != data->canShareThisContact()) { + update.flags |= UpdateFlag::UserCanShareContact; + } } break; } @@ -513,22 +527,33 @@ namespace { if (emitPeerUpdated) { App::main()->peerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdated(update); + } } else { markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); + } } } + result = data; } - return data; + return result; } PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated) { - PeerData *data = 0; - const auto &v(chats.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &chat(*i); - data = 0; + PeerData *result = nullptr; + for_const (auto &chat, chats.c_vector().v) { + PeerData *data = nullptr; bool minimal = false; + + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdateFlag; + switch (chat.type()) { case mtpc_chat: { const auto &d(chat.c_chat()); @@ -621,6 +646,8 @@ namespace { } ChannelData *cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + if (minimal) { int32 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); @@ -644,6 +671,10 @@ namespace { cdata->isForbidden = false; cdata->flagsUpdated(); cdata->setPhoto(d.vphoto); + + if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + update.flags |= UpdateFlag::ChannelAmIn; + } } break; case mtpc_channelForbidden: { const auto &d(chat.c_channelForbidden()); @@ -653,6 +684,8 @@ namespace { data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); ChannelData *cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); cdata->setName(qs(d.vtitle), QString()); @@ -662,6 +695,10 @@ namespace { cdata->date = 0; cdata->count = 0; cdata->isForbidden = true; + + if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + update.flags |= UpdateFlag::ChannelAmIn; + } } break; } if (!data) continue; @@ -676,12 +713,21 @@ namespace { if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdated(update); + } } else { markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); + } } } + result = data; } - return data; + return result; } void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) { @@ -1245,6 +1291,7 @@ namespace { App::main()->peerUpdated(i.key()); } } + Notify::peerUpdatedSendDelayed(); } PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert) { diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 444742a816..2d37dff66f 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -222,7 +222,8 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { if (isHidden() || !App::main()) return; const auto &d(res.c_contacts_importedContacts()); - App::feedUsers(d.vusers); + App::feedUsers(d.vusers, false); + App::emitPeerUpdated(); const auto &v(d.vimported.c_vector().v); UserData *user = nullptr; diff --git a/Telegram/SourceFiles/core/basic_types.cpp b/Telegram/SourceFiles/core/basic_types.cpp index 79851ca379..e3fc726429 100644 --- a/Telegram/SourceFiles/core/basic_types.cpp +++ b/Telegram/SourceFiles/core/basic_types.cpp @@ -44,7 +44,6 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; #include // Base types compile-time check - static_assert(sizeof(char) == 1, "Basic types size check failed"); static_assert(sizeof(uchar) == 1, "Basic types size check failed"); static_assert(sizeof(int16) == 2, "Basic types size check failed"); diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 13bcd4e999..4907c2855c 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -421,6 +421,24 @@ inline bool operator!=(std::nullptr_t a, const unique_ptr &b) noexcept { return !(a == b); } +using _yes = char(&)[1]; +using _no = char(&)[2]; + +template +struct _host { + operator Base*() const; + operator Derived*(); +}; + +template +struct is_base_of { + template + static _yes check(Derived*, T); + static _no check(Base*, int); + + static constexpr bool value = sizeof(check(_host(), int())) == sizeof(_yes); +}; + } // namespace std_ #include "logs.h" @@ -1182,18 +1200,22 @@ NullFunctionImplementation NullFunctionImplementation::S template class Function { public: - Function() : _implementation(&NullFunctionImplementation::SharedInstance) {} + Function() : _implementation(nullImpl()) {} Function(FunctionImplementation *implementation) : _implementation(implementation) {} Function(const Function &other) = delete; Function &operator=(const Function &other) = delete; Function(Function &&other) : _implementation(other._implementation) { - other._implementation = &NullFunctionImplementation::SharedInstance; + other._implementation = nullImpl(); } Function &operator=(Function &&other) { std::swap(_implementation, other._implementation); return *this; } + bool isNull() const { + return (_implementation == nullImpl()); + } + R call(Args... args) { return _implementation->call(args...); } ~Function() { if (_implementation) { @@ -1204,6 +1226,10 @@ public: } private: + static FunctionImplementation *nullImpl() { + return &NullFunctionImplementation::SharedInstance; + } + FunctionImplementation *_implementation; }; diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp new file mode 100644 index 0000000000..5a8f2d389b --- /dev/null +++ b/Telegram/SourceFiles/core/observer.cpp @@ -0,0 +1,62 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "core/observer.h" + +namespace Notify { +namespace { + +UnregisterObserverCallback UnregisterCallbacks[256]; + +} // namespace + +// Observer base interface. +Observer::~Observer() { + for_const (auto connection, _connections) { + unregisterObserver(connection); + } +} + +void Observer::observerRegistered(ConnectionId connection) { + _connections.push_back(connection); +} + +void unregisterObserver(ConnectionId connection) { + auto event = static_cast(connection >> 24); + auto connectionIndex = int(connection & 0x00FFFFFFU) - 1; + if (connectionIndex >= 0 && UnregisterCallbacks[event]) { + UnregisterCallbacks[event](connectionIndex); + } +} + +UnregisterObserverCallbackCreator::UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback) { + UnregisterCallbacks[event] = callback; +} + +namespace internal { + +void observerRegisteredDefault(Observer *observer, ConnectionId connection) { + observer->observerRegistered(connection); +} + +} // namespace internal + +} // namespace Notify diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h new file mode 100644 index 0000000000..2f992cb0b2 --- /dev/null +++ b/Telegram/SourceFiles/core/observer.h @@ -0,0 +1,134 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/vector_of_moveable.h" + +namespace Notify { + +class Observer; +using ConnectionId = uint32; + +// Each observer type should have observerRegistered(Notify::ConnectionId connection) method. +// Usually it is done by deriving the type from the Notify::Observer base class. +// In destructor it should call Notify::unregisterObserver(connection) for all the connections. + +namespace internal { +void observerRegisteredDefault(Observer *observer, ConnectionId connection); +} // namespace internal + +void unregisterObserver(ConnectionId connection); + +class Observer { +public: + virtual ~Observer() = 0; + +private: + void observerRegistered(ConnectionId connection); + friend void internal::observerRegisteredDefault(Observer *observer, ConnectionId connection); + + QVector _connections; + +}; + +using ObservedEvent = uchar; +inline ConnectionId observerConnectionId(ObservedEvent event, int connectionIndex) { + t_assert(connectionIndex >= 0 && connectionIndex < 0x01000000); + return (static_cast(event) << 24) | (connectionIndex + 1); +} + +using UnregisterObserverCallback = void(*)(int connectionIndex); + +// Usage: UnregisterObserverCallbackCreator creator(myEvent, myCallback); in global scope. +class UnregisterObserverCallbackCreator { +public: + UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback); + +}; + +// Handler is one of Function<> instantiations. +template +struct ObserversList { + struct Entry { + Flags flags; + Handler handler; + }; + std_::vector_of_moveable entries; + QVector freeIndices; +}; + +template +int registerObserver(ObserversList &list, Flags flags, Handler &&handler) { + while (!list.freeIndices.isEmpty()) { + auto freeIndex = list.freeIndices.back(); + list.freeIndices.pop_back(); + + if (freeIndex < list.entries.size()) { + list.entries[freeIndex] = { flags, std_::move(handler) }; + return freeIndex; + } + } + list.entries.push_back({ flags, std_::move(handler) }); + return list.entries.size() - 1; +} + +template +void unregisterObserver(ObserversList &list, int connectionIndex) { + auto &entries(list.entries); + if (entries.size() <= connectionIndex) return; + + if (entries.size() == connectionIndex + 1) { + for (entries.pop_back(); !entries.isEmpty() && entries.back().handler.isNull();) { + entries.pop_back(); + } + } else { + entries[connectionIndex].handler = Handler(); + list.freeIndices.push_back(connectionIndex); + } +} + +template +void notifyObservers(ObserversList &list, Flags flags, Args&&... args) { + for (auto &entry : list.entries) { + if (!entry.handler.isNull() && (flags & entry.flags)) { + entry.handler.call(std_::forward(args)...); + } + } +} + +namespace internal { + +template +struct ObserverRegisteredGeneric { + static void call(ObserverType *observer, ConnectionId connection) { + observer->observerRegistered(connection); + } +}; + +template +struct ObserverRegisteredGeneric { + static void call(ObserverType *observer, ConnectionId connection) { + observerRegisteredDefault(observer, connection); + } +}; + +} // namespace internal +} // namespace Notify diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h new file mode 100644 index 0000000000..412d87b35e --- /dev/null +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -0,0 +1,153 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +// some minimal implementation of std::vector() for moveable (but not copiable) types. +namespace std_ { + +template +class vector_of_moveable { + typedef vector_of_moveable Self; + int _size = 0, _capacity = 0; + void *_plaindata = nullptr; + +public: + inline T *data() { + return reinterpret_cast(_plaindata); + } + inline const T *data() const { + return reinterpret_cast(_plaindata); + } + + inline bool operator==(const Self &other) const { + if (this == &other) return true; + if (_size != other._size) return false; + for (int i = 0; i < _size; ++i) { + if (data()[i] != other.data()[i]) { + return false; + } + } + return true; + } + inline bool operator!=(const Self &other) const { return !(*this == other); } + inline int size() const { return _size; } + inline bool isEmpty() const { return _size == 0; } + inline void clear() { + for (int i = 0; i < _size; ++i) { + data()[i].~T(); + } + _size = 0; + + operator delete[](_plaindata); + _plaindata = nullptr; + _capacity = 0; + } + + typedef T *iterator; + typedef const T *const_iterator; + + // STL style + inline iterator begin() { return data(); } + inline const_iterator begin() const { return data(); } + inline const_iterator cbegin() const { return data(); } + inline iterator end() { return data() + _size; } + inline const_iterator end() const { return data() + _size; } + inline const_iterator cend() const { return data() + _size; } + inline iterator erase(iterator it) { + T tmp = std_::move(*it); + for (auto next = it + 1, e = end(); next != e; ++next) { + auto prev = next - 1; + *prev = std_::move(*next); + } + --_size; + return it; + } + + inline iterator insert(const_iterator pos, T &&value) { + int insertAtIndex = pos - begin(); + if (_size + 1 > _capacity) { + reallocate(_capacity + (_capacity > 1 ? _capacity / 2 : 1)); + } + auto insertAt = begin() + insertAtIndex, e = end(); + if (insertAt == e) { + new (&(*insertAt)) T(std_::move(value)); + } else { + auto prev = e - 1; + new (&(*e)) T(std_::move(*prev)); + for (auto it = prev; it != insertAt; --it) { + *it = std_::move(*--prev); + } + *insertAt = std_::move(value); + } + ++_size; + return insertAt; + } + inline void push_back(T &&value) { + insert(end(), std_::forward(value)); + } + inline void pop_back() { + erase(end() - 1); + } + inline T &front() { + return *begin(); + } + inline const T &front() const { + return *begin(); + } + inline T &back() { + return *(end() - 1); + } + inline const T &back() const { + return *(end() - 1); + } + inline bool empty() const { return _size == 0; } + + inline T &operator[](int index) { + return data()[index]; + } + inline const T &operator[](int index) const { + return data()[index]; + } + inline const T &at(int index) const { + if (index < 0 || index >= _size) { + throw std::out_of_range(""); + } + return data()[index]; + } + + inline ~vector_of_moveable() { + clear(); + } + +private: + void reallocate(int newCapacity) { + auto newPlainData = operator new[](newCapacity * sizeof(T)); + for (int i = 0; i < _size; ++i) { + *(reinterpret_cast(newPlainData) + i) = std_::move(*(data() + i)); + } + std::swap(_plaindata, newPlainData); + _capacity = newCapacity; + operator delete[](newPlainData); + } + +}; + +} // namespace std_ diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index defd469937..b1aebc849e 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -21,10 +21,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "profile/profile_section_memento.h" +#include "core/vector_of_moveable.h" +#include "core/click_handler_types.h" #include "mainwindow.h" #include "mainwidget.h" #include "application.h" -#include "core/click_handler_types.h" #include "boxes/confirmbox.h" #include "layerwidget.h" #include "lang.h" diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp new file mode 100644 index 0000000000..9094ce5c27 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -0,0 +1,104 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "observer_peer.h" + +#include "core/observer.h" + +namespace Notify { +namespace internal { +namespace { + +constexpr ObservedEvent PeerUpdateEvent = 0x01; +ObserversList PeerUpdateObservers; + +void UnregisterCallback(int connectionIndex) { + unregisterObserver(PeerUpdateObservers, connectionIndex); +} +UnregisterObserverCallbackCreator creator(PeerUpdateEvent, UnregisterCallback); + +QVector SmallPeerUpdates; +QMap AllPeerUpdates; + +} // namespace + +ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) { + auto connectionId = registerObserver(PeerUpdateObservers, events, std_::forward(handler)); + t_assert(connectionId >= 0 && connectionId < 0x01000000); + return (static_cast(PeerUpdateEvent) << 24) | static_cast(connectionId + 1); +} + +void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { + mergeTo.flags |= mergeFrom.flags; + + // merge fields used in mergeFrom.flags +} + +} // namespace internal + +void peerUpdated(const PeerUpdate &update) { + notifyObservers(internal::PeerUpdateObservers, update.flags, update); +} + +void peerUpdatedDelayed(const PeerUpdate &update) { + int alreadySavedCount = internal::SmallPeerUpdates.size(); + for (int i = 0; i < alreadySavedCount; ++i) { + if (internal::SmallPeerUpdates.at(i).peer == update.peer) { + internal::mergePeerUpdate(internal::SmallPeerUpdates[i], update); + return; + } + } + if (internal::AllPeerUpdates.isEmpty()) { + if (alreadySavedCount < 5) { + internal::SmallPeerUpdates.push_back(update); + } else { + internal::AllPeerUpdates.insert(update.peer, update); + } + } else { + auto it = internal::AllPeerUpdates.find(update.peer); + if (it != internal::AllPeerUpdates.cend()) { + internal::mergePeerUpdate(it.value(), update); + return; + } + internal::AllPeerUpdates.insert(update.peer, update); + } +} + +void peerUpdatedSendDelayed() { + if (internal::SmallPeerUpdates.isEmpty()) return; + + decltype(internal::SmallPeerUpdates) smallList; + decltype(internal::AllPeerUpdates) allList; + std::swap(smallList, internal::SmallPeerUpdates); + std::swap(allList, internal::AllPeerUpdates); + for_const (auto &update, smallList) { + peerUpdated(update); + } + for_const (auto &update, allList) { + peerUpdated(update); + } + if (internal::SmallPeerUpdates.isEmpty()) { + std::swap(smallList, internal::SmallPeerUpdates); + internal::SmallPeerUpdates.resize(0); + } +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h new file mode 100644 index 0000000000..76a7514b94 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.h @@ -0,0 +1,70 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/observer.h" + +namespace Notify { + +// Generic notifications about updates of some PeerData. +// You can subscribe to them by Notify::registerPeerObserver(). + +enum class PeerUpdateFlag { + //PeerNameChanged = 0x0001, + + UserCanShareContact = 0x1001, + + ChatCanEdit = 0x2001, + + MegagroupCanEditPhoto = 0x4001, + MegagroupCanAddMembers = 0x4002, + + ChannelAmIn = 0x8001, +}; +Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag); +Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags); +struct PeerUpdate { + PeerData *peer = nullptr; + PeerUpdateFlags flags = 0; +}; + +void peerUpdated(const PeerUpdate &update); +void peerUpdatedDelayed(const PeerUpdate &update); +void peerUpdatedSendDelayed(); + +namespace internal { + +using PeerUpdateHandler = Function; +ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler); + +} // namespace internal + +template +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::value>; + ObserverRegistered::call(observer, connection); +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 33b2131678..24de8384aa 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "ui/buttons/round_button.h" +#include "observer_peer.h" #include "lang.h" #include "apiwrap.h" #include "mainwidget.h" @@ -58,6 +59,12 @@ private: }; +const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserCanShareContact + | Notify::PeerUpdateFlag::ChatCanEdit + | Notify::PeerUpdateFlag::MegagroupCanEditPhoto + | Notify::PeerUpdateFlag::MegagroupCanAddMembers + | Notify::PeerUpdateFlag::ChannelAmIn; + } // namespace class PhotoButton final : public Button { @@ -91,14 +98,17 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _photoButton(this, peer) { setAttribute(Qt::WA_OpaquePaintEvent); + using Flag = Notify::PeerUpdateFlag; + auto observeEvents = ButtonsUpdateFlags; + Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); + _photoButton->photoUpdated(); connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); _nameText.setText(st::profileNameFont, App::peerName(_peer)); updateStatusText(); - _primaryButton = new Ui::RoundButton(this, "SEND MESSAGE", st::profilePrimaryButton); - _secondaryButton = new Ui::RoundButton(this, "SHARE CONTACT", st::profileSecondaryButton); + updateButtons(); } void CoverWidget::onPhotoShow() { @@ -111,14 +121,6 @@ void CoverWidget::onPhotoShow() { } } -void CoverWidget::onSetPhoto() { - -} - -void CoverWidget::onAddMember() { - -} - void CoverWidget::onSendMessage() { } @@ -127,6 +129,14 @@ void CoverWidget::onShareContact() { } +void CoverWidget::onSetPhoto() { + +} + +void CoverWidget::onAddMember() { + +} + void CoverWidget::onJoin() { } @@ -244,4 +254,86 @@ bool CoverWidget::isUsingMegagroupOnlineCount() const { return true; } +void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.flags & ButtonsUpdateFlags) { + updateButtons(); + } +} + +void CoverWidget::updateButtons() { + if (_peerUser) { + setUserButtons(); + } else if (_peerChat) { + setChatButtons(); + } else if (_peerMegagroup) { + setMegagroupButtons(); + } else if (_peerChannel) { + setChannelButtons(); + } + resizeToWidth(width()); +} + +void CoverWidget::setUserButtons() { + setPrimaryButton(lang(lng_profile_send_message), SLOT(onSendMessage())); + if (_peerUser->canShareThisContact()) { + setSecondaryButton(lang(lng_profile_share_contact), SLOT(onShareContact())); + } else { + clearSecondaryButton(); + } +} + +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(); + } +} + +void CoverWidget::setMegagroupButtons() { + if (_peerMegagroup->canEditPhoto()) { + setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } else { + clearPrimaryButton(); + } + if (_peerMegagroup->canAddParticipants()) { + setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember())); + } else { + clearSecondaryButton(); + } +} + +void CoverWidget::setChannelButtons() { + if (_peerChannel->amCreator()) { + setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } else if (_peerChannel->amIn()) { + setPrimaryButton(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(); + } +} + +void CoverWidget::setSecondaryButton(const QString &text, const char *slot) { + delete _secondaryButton; + _secondaryButton = nullptr; + if (!text.isEmpty()) { + _secondaryButton = new Ui::RoundButton(this, text, st::profileSecondaryButton); + connect(_secondaryButton, SIGNAL(clicked()), this, slot); + _secondaryButton->show(); + } +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 2c6d1b7ec8..ae84f15e15 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -20,16 +20,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "core/observer.h" + namespace Ui { class RoundButton; } // namespace Ui +namespace Notify { +struct PeerUpdate; +} // namespace Notify + namespace Profile { class BackButton; class PhotoButton; -class CoverWidget final : public TWidget { +class CoverWidget final : public TWidget, public Notify::Observer { Q_OBJECT public: @@ -41,10 +47,10 @@ public: private slots: void onPhotoShow(); - void onSetPhoto(); - void onAddMember(); void onSendMessage(); void onShareContact(); + void onSetPhoto(); + void onAddMember(); void onJoin(); void onViewChannel(); @@ -55,6 +61,24 @@ private: void updateStatusText(); bool isUsingMegagroupOnlineCount() const; + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + void updateButtons(); + void setUserButtons(); + void setChatButtons(); + 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 paintDivider(Painter &p); PeerData *_peer; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index af7d1717e5..d943a3eaf0 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -42,7 +42,7 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) _fixedBarShadow->raise(); updateAdaptiveLayout(); - _scroll->setWidget(_inner); + _scroll->setOwnedWidget(_inner); _scroll->move(0, _fixedBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index b9eb32b17a..da3d2d293a 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -419,6 +419,9 @@ public: bool canWrite() const { return access != UserNoAccess; } + bool canShareThisContact() const { + return contact >= 0; + } MTPInputUser inputUser; @@ -675,9 +678,6 @@ public: bool isPublic() const { return flags & MTPDchannel::Flag::f_username; } - bool canEditUsername() const { - return amCreator() && (flagsFull & MTPDchannelFull::Flag::f_can_set_username); - } bool amCreator() const { return flags & MTPDchannel::Flag::f_creator; } @@ -715,6 +715,12 @@ public: bool canAddParticipants() const { return amCreator() || amEditor() || (flags & MTPDchannel::Flag::f_democracy); } + bool canEditPhoto() const { + return amCreator() || (amEditor() && isMegagroup()); + } + bool canEditUsername() const { + return amCreator() && (flagsFull & MTPDchannelFull::Flag::f_can_set_username); + } // ImagePtr photoFull; QString invitationUrl; diff --git a/Telegram/SourceFiles/ui/scrollarea.cpp b/Telegram/SourceFiles/ui/scrollarea.cpp index aa0508a63a..3f5a93c3e1 100644 --- a/Telegram/SourceFiles/ui/scrollarea.cpp +++ b/Telegram/SourceFiles/ui/scrollarea.cpp @@ -718,6 +718,10 @@ void ScrollArea::setWidget(QWidget *w) { hor.raise(); vert.raise(); } + if (_ownsWidget) { + _ownsWidget = false; + delete takeWidget(); + } QScrollArea::setWidget(w); if (w) { w->setAutoFillBackground(false); @@ -738,6 +742,11 @@ void ScrollArea::setWidget(QWidget *w) { } } +void ScrollArea::setOwnedWidget(QWidget *widget) { + setWidget(widget); + _ownsWidget = true; +} + QWidget *ScrollArea::takeWidget() { if (_other) { delete _other; @@ -785,5 +794,7 @@ bool ScrollArea::focusNextPrevChild(bool next) { } ScrollArea::~ScrollArea() { - takeWidget(); + if (!_ownsWidget) { + takeWidget(); + } } diff --git a/Telegram/SourceFiles/ui/scrollarea.h b/Telegram/SourceFiles/ui/scrollarea.h index 6004df418d..2e25044164 100644 --- a/Telegram/SourceFiles/ui/scrollarea.h +++ b/Telegram/SourceFiles/ui/scrollarea.h @@ -185,6 +185,7 @@ public: int scrollTop() const; void setWidget(QWidget *widget); + void setOwnedWidget(QWidget *widget); QWidget *takeWidget(); void rangeChanged(int oldMax, int newMax, bool vertical); @@ -237,6 +238,7 @@ private: void touchDeaccelerate(int32 elapsed); bool _disabled; + bool _ownsWidget = false; // if true, the widget is deleted in destructor. style::flatScroll _st; ScrollBar hor, vert; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index bc4ea1730f..ec36b03571 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1162,6 +1162,7 @@ + @@ -1206,6 +1207,7 @@ + @@ -1356,6 +1358,8 @@ + + @@ -1484,6 +1488,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/mtproto/session.h" + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -2554,6 +2559,7 @@ + .\GeneratedFiles\lang_auto.h diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 6564b3fcbf..7a28c5dd67 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1203,6 +1203,12 @@ SourceFiles\ui\buttons + + SourceFiles\core + + + SourceFiles + @@ -1391,6 +1397,15 @@ SourceFiles\ui\buttons + + SourceFiles\core + + + SourceFiles\core + + + SourceFiles + @@ -1704,6 +1719,7 @@ Resources\langs +