2017-10-31 18:25:22 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-10-31 18:25:22 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-10-31 18:25:22 +00:00
|
|
|
*/
|
|
|
|
#include "info/info_controller.h"
|
|
|
|
|
|
|
|
#include "ui/search_field_controller.h"
|
2017-11-06 07:31:18 +00:00
|
|
|
#include "data/data_shared_media.h"
|
2017-10-31 18:25:22 +00:00
|
|
|
#include "info/info_content_widget.h"
|
|
|
|
#include "info/info_memento.h"
|
2017-11-03 15:47:08 +00:00
|
|
|
#include "info/media/info_media_widget.h"
|
2022-02-24 15:04:24 +00:00
|
|
|
#include "core/application.h"
|
2020-06-12 12:12:34 +00:00
|
|
|
#include "data/data_changes.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_peer.h"
|
|
|
|
#include "data/data_channel.h"
|
|
|
|
#include "data/data_chat.h"
|
2022-09-27 12:05:47 +00:00
|
|
|
#include "data/data_forum_topic.h"
|
2022-10-04 15:34:45 +00:00
|
|
|
#include "data/data_forum.h"
|
2019-01-18 12:27:37 +00:00
|
|
|
#include "data/data_session.h"
|
2020-01-15 11:53:42 +00:00
|
|
|
#include "data/data_media_types.h"
|
2022-02-24 15:04:24 +00:00
|
|
|
#include "data/data_download_manager.h"
|
2020-01-15 11:53:42 +00:00
|
|
|
#include "history/history_item.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2019-06-06 10:21:40 +00:00
|
|
|
#include "window/window_session_controller.h"
|
2017-10-31 18:25:22 +00:00
|
|
|
|
|
|
|
namespace Info {
|
|
|
|
|
2018-01-22 19:51:38 +00:00
|
|
|
Key::Key(not_null<PeerData*> peer) : _value(peer) {
|
|
|
|
}
|
|
|
|
|
2022-09-27 12:05:47 +00:00
|
|
|
Key::Key(not_null<Data::ForumTopic*> topic) : _value(topic) {
|
|
|
|
}
|
|
|
|
|
2018-09-05 19:05:49 +00:00
|
|
|
Key::Key(Settings::Tag settings) : _value(settings) {
|
|
|
|
}
|
|
|
|
|
2022-02-24 15:04:24 +00:00
|
|
|
Key::Key(Downloads::Tag downloads) : _value(downloads) {
|
|
|
|
}
|
|
|
|
|
2020-01-15 11:53:42 +00:00
|
|
|
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
|
|
|
: _value(PollKey{ poll, contextId }) {
|
|
|
|
}
|
|
|
|
|
2018-01-22 19:51:38 +00:00
|
|
|
PeerData *Key::peer() const {
|
2020-08-31 08:14:53 +00:00
|
|
|
if (const auto peer = std::get_if<not_null<PeerData*>>(&_value)) {
|
2018-01-22 19:51:38 +00:00
|
|
|
return *peer;
|
2022-09-27 12:05:47 +00:00
|
|
|
} else if (const auto topic = this->topic()) {
|
2022-09-27 16:52:35 +00:00
|
|
|
return topic->channel();
|
2022-09-27 12:05:47 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Data::ForumTopic *Key::topic() const {
|
|
|
|
if (const auto topic = std::get_if<not_null<Data::ForumTopic*>>(
|
|
|
|
&_value)) {
|
|
|
|
return *topic;
|
2018-01-22 19:51:38 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-09-05 19:05:49 +00:00
|
|
|
UserData *Key::settingsSelf() const {
|
2020-08-31 08:14:53 +00:00
|
|
|
if (const auto tag = std::get_if<Settings::Tag>(&_value)) {
|
2018-09-05 19:05:49 +00:00
|
|
|
return tag->self;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-02-24 15:04:24 +00:00
|
|
|
bool Key::isDownloads() const {
|
|
|
|
return v::is<Downloads::Tag>(_value);
|
|
|
|
}
|
|
|
|
|
2020-01-15 11:53:42 +00:00
|
|
|
PollData *Key::poll() const {
|
2020-08-31 08:14:53 +00:00
|
|
|
if (const auto data = std::get_if<PollKey>(&_value)) {
|
2020-01-15 11:53:42 +00:00
|
|
|
return data->poll;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
FullMsgId Key::pollContextId() const {
|
2020-08-31 08:14:53 +00:00
|
|
|
if (const auto data = std::get_if<PollKey>(&_value)) {
|
2020-01-15 11:53:42 +00:00
|
|
|
return data->contextId;
|
|
|
|
}
|
|
|
|
return FullMsgId();
|
|
|
|
}
|
|
|
|
|
2017-12-09 15:13:06 +00:00
|
|
|
rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
|
|
|
|
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
|
|
|
int limitBefore,
|
|
|
|
int limitAfter) const {
|
2020-06-08 09:06:50 +00:00
|
|
|
Expects(peer() != nullptr);
|
|
|
|
|
2021-07-01 12:00:09 +00:00
|
|
|
const auto isScheduled = [&] {
|
2021-12-09 07:32:54 +00:00
|
|
|
const auto peerId = peer()->id;
|
|
|
|
if (const auto item = session().data().message(peerId, aroundId)) {
|
2021-07-01 12:00:09 +00:00
|
|
|
return item->isScheduled();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}();
|
|
|
|
|
2022-10-11 15:08:19 +00:00
|
|
|
const auto mediaViewer = isScheduled
|
2021-07-01 12:00:09 +00:00
|
|
|
? SharedScheduledMediaViewer
|
|
|
|
: SharedMediaMergedViewer;
|
2022-10-11 15:08:19 +00:00
|
|
|
const auto topicId = isScheduled
|
|
|
|
? SparseIdsMergedSlice::kScheduledTopicId
|
|
|
|
: topic()
|
|
|
|
? topic()->rootId()
|
|
|
|
: MsgId(0);
|
2021-07-01 12:00:09 +00:00
|
|
|
|
|
|
|
return mediaViewer(
|
2020-06-08 09:06:50 +00:00
|
|
|
&session(),
|
2017-12-09 15:13:06 +00:00
|
|
|
SharedMediaMergedKey(
|
|
|
|
SparseIdsMergedSlice::Key(
|
2020-06-08 09:06:50 +00:00
|
|
|
peer()->id,
|
2022-10-11 15:08:19 +00:00
|
|
|
topicId,
|
2017-12-09 15:13:06 +00:00
|
|
|
migratedPeerId(),
|
|
|
|
aroundId),
|
|
|
|
section().mediaType()),
|
|
|
|
limitBefore,
|
|
|
|
limitAfter);
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
|
|
|
|
return rpl::single(QString());
|
|
|
|
}
|
|
|
|
|
2022-03-11 11:14:07 +00:00
|
|
|
rpl::producer<QString> AbstractController::searchQueryValue() const {
|
|
|
|
return rpl::single(QString());
|
|
|
|
}
|
|
|
|
|
2019-06-06 10:21:40 +00:00
|
|
|
AbstractController::AbstractController(
|
|
|
|
not_null<Window::SessionController*> parent)
|
|
|
|
: SessionNavigation(&parent->session())
|
2019-01-18 12:27:37 +00:00
|
|
|
, _parent(parent) {
|
|
|
|
}
|
|
|
|
|
2020-06-08 09:06:50 +00:00
|
|
|
PeerData *AbstractController::peer() const {
|
|
|
|
return key().peer();
|
2019-01-04 11:09:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PeerId AbstractController::migratedPeerId() const {
|
|
|
|
if (const auto peer = migrated()) {
|
|
|
|
return peer->id;
|
|
|
|
}
|
|
|
|
return PeerId(0);
|
|
|
|
}
|
|
|
|
|
2020-01-15 11:53:42 +00:00
|
|
|
PollData *AbstractController::poll() const {
|
|
|
|
if (const auto item = session().data().message(pollContextId())) {
|
|
|
|
if (const auto media = item->media()) {
|
|
|
|
return media->poll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-12-09 15:13:06 +00:00
|
|
|
void AbstractController::showSection(
|
2020-12-14 14:48:10 +00:00
|
|
|
std::shared_ptr<Window::SectionMemento> memento,
|
2017-12-09 15:13:06 +00:00
|
|
|
const Window::SectionShow ¶ms) {
|
|
|
|
return parentController()->showSection(std::move(memento), params);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractController::showBackFromStack(
|
|
|
|
const Window::SectionShow ¶ms) {
|
|
|
|
return parentController()->showBackFromStack(params);
|
|
|
|
}
|
|
|
|
|
2020-09-22 15:05:07 +00:00
|
|
|
void AbstractController::showPeerHistory(
|
|
|
|
PeerId peerId,
|
|
|
|
const Window::SectionShow ¶ms,
|
|
|
|
MsgId msgId) {
|
|
|
|
return parentController()->showPeerHistory(peerId, params, msgId);
|
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
Controller::Controller(
|
|
|
|
not_null<WrapWidget*> widget,
|
2019-06-06 10:21:40 +00:00
|
|
|
not_null<Window::SessionController*> window,
|
2017-10-31 18:25:22 +00:00
|
|
|
not_null<ContentMemento*> memento)
|
2017-12-09 15:13:06 +00:00
|
|
|
: AbstractController(window)
|
|
|
|
, _widget(widget)
|
2018-09-05 19:05:49 +00:00
|
|
|
, _key(memento->key())
|
2017-10-31 18:25:22 +00:00
|
|
|
, _migrated(memento->migratedPeerId()
|
2020-06-08 09:06:50 +00:00
|
|
|
? window->session().data().peer(memento->migratedPeerId()).get()
|
2017-10-31 18:25:22 +00:00
|
|
|
: nullptr)
|
|
|
|
, _section(memento->section()) {
|
2017-11-03 15:47:08 +00:00
|
|
|
updateSearchControllers(memento);
|
2017-11-06 12:14:44 +00:00
|
|
|
setupMigrationViewer();
|
2022-10-04 15:34:45 +00:00
|
|
|
setupTopicViewer();
|
2017-11-06 12:14:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::setupMigrationViewer() {
|
2018-01-22 19:51:38 +00:00
|
|
|
const auto peer = _key.peer();
|
2022-10-12 12:57:17 +00:00
|
|
|
if (_key.topic()
|
|
|
|
|| !peer
|
|
|
|
|| (!peer->isChat() && !peer->isChannel())
|
|
|
|
|| _migrated) {
|
2017-11-06 12:14:44 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-06-12 12:12:34 +00:00
|
|
|
peer->session().changes().peerFlagsValue(
|
2018-01-22 19:51:38 +00:00
|
|
|
peer,
|
2020-06-12 12:12:34 +00:00
|
|
|
Data::PeerUpdate::Flag::Migration
|
|
|
|
) | rpl::filter([=] {
|
|
|
|
return peer->migrateTo() || (peer->migrateFrom() != _migrated);
|
|
|
|
}) | rpl::start_with_next([=] {
|
2022-10-17 16:29:48 +00:00
|
|
|
replaceWith(std::make_shared<Memento>(peer, _section));
|
2017-12-22 07:05:20 +00:00
|
|
|
}, lifetime());
|
2017-10-31 18:25:22 +00:00
|
|
|
}
|
|
|
|
|
2022-10-17 16:29:48 +00:00
|
|
|
void Controller::replaceWith(std::shared_ptr<Memento> memento) {
|
|
|
|
const auto window = parentController();
|
|
|
|
auto params = Window::SectionShow(
|
|
|
|
Window::SectionShow::Way::Backward,
|
|
|
|
anim::type::instant,
|
|
|
|
anim::activation::background);
|
|
|
|
if (wrap() == Wrap::Side) {
|
|
|
|
params.thirdColumn = true;
|
|
|
|
}
|
|
|
|
InvokeQueued(_widget, [=, memento = std::move(memento)]() mutable {
|
|
|
|
window->showSection(std::move(memento), params);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-10-04 15:34:45 +00:00
|
|
|
void Controller::setupTopicViewer() {
|
|
|
|
session().data().itemIdChanged(
|
|
|
|
) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
|
|
|
|
if (const auto topic = _key.topic()) {
|
2022-10-17 16:29:48 +00:00
|
|
|
if (topic->rootId() == change.oldId
|
|
|
|
|| (topic->peer()->id == change.newId.peer
|
|
|
|
&& topic->rootId() == change.newId.msg)) {
|
|
|
|
const auto now = topic->forum()->topicFor(change.newId.msg);
|
|
|
|
_key = Key(now);
|
|
|
|
replaceWith(std::make_shared<Memento>(now, _section));
|
2022-10-04 15:34:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}, _lifetime);
|
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
Wrap Controller::wrap() const {
|
|
|
|
return _widget->wrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<Wrap> Controller::wrapValue() const {
|
|
|
|
return _widget->wrapValue();
|
2017-11-03 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
bool Controller::validateMementoPeer(
|
|
|
|
not_null<ContentMemento*> memento) const {
|
2020-06-08 09:06:50 +00:00
|
|
|
return memento->peer() == peer()
|
2018-01-22 19:51:38 +00:00
|
|
|
&& memento->migratedPeerId() == migratedPeerId()
|
2018-09-05 19:05:49 +00:00
|
|
|
&& memento->settingsSelf() == settingsSelf();
|
2017-10-31 18:25:22 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 15:47:08 +00:00
|
|
|
void Controller::setSection(not_null<ContentMemento*> memento) {
|
|
|
|
_section = memento->section();
|
|
|
|
updateSearchControllers(memento);
|
2017-10-31 18:25:22 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 15:47:08 +00:00
|
|
|
void Controller::updateSearchControllers(
|
|
|
|
not_null<ContentMemento*> memento) {
|
2017-11-17 13:23:36 +00:00
|
|
|
using Type = Section::Type;
|
2022-02-24 15:04:24 +00:00
|
|
|
const auto type = _section.type();
|
|
|
|
const auto isMedia = (type == Type::Media);
|
|
|
|
const auto mediaType = isMedia
|
2017-10-31 18:25:22 +00:00
|
|
|
? _section.mediaType()
|
|
|
|
: Section::MediaType::kCount;
|
2022-02-24 15:04:24 +00:00
|
|
|
const auto hasMediaSearch = isMedia
|
2017-10-31 18:25:22 +00:00
|
|
|
&& SharedMediaAllowSearch(mediaType);
|
2022-03-11 11:14:07 +00:00
|
|
|
const auto hasCommonGroupsSearch = (type == Type::CommonGroups);
|
|
|
|
const auto hasDownloadsSearch = (type == Type::Downloads);
|
2022-02-24 15:04:24 +00:00
|
|
|
const auto hasMembersSearch = (type == Type::Members)
|
|
|
|
|| (type == Type::Profile);
|
|
|
|
const auto searchQuery = memento->searchFieldQuery();
|
2017-10-31 18:25:22 +00:00
|
|
|
if (isMedia) {
|
|
|
|
_searchController
|
2019-11-27 08:02:56 +00:00
|
|
|
= std::make_unique<Api::DelayedSearchController>(&session());
|
2017-11-03 15:47:08 +00:00
|
|
|
auto mediaMemento = dynamic_cast<Media::Memento*>(memento.get());
|
|
|
|
Assert(mediaMemento != nullptr);
|
|
|
|
_searchController->restoreState(
|
|
|
|
mediaMemento->searchState());
|
2017-10-31 18:25:22 +00:00
|
|
|
} else {
|
|
|
|
_searchController = nullptr;
|
|
|
|
}
|
2022-03-11 11:14:07 +00:00
|
|
|
if (hasMediaSearch
|
|
|
|
|| hasCommonGroupsSearch
|
|
|
|
|| hasDownloadsSearch
|
|
|
|
|| hasMembersSearch) {
|
2017-10-31 18:25:22 +00:00
|
|
|
_searchFieldController
|
2017-11-03 15:47:08 +00:00
|
|
|
= std::make_unique<Ui::SearchFieldController>(
|
|
|
|
searchQuery);
|
2017-11-03 18:26:14 +00:00
|
|
|
if (_searchController) {
|
2017-12-22 07:05:20 +00:00
|
|
|
_searchFieldController->queryValue(
|
|
|
|
) | rpl::start_with_next([=](QString &&query) {
|
|
|
|
_searchController->setQuery(
|
|
|
|
produceSearchQuery(std::move(query)));
|
|
|
|
}, _searchFieldController->lifetime());
|
2017-11-03 18:26:14 +00:00
|
|
|
}
|
|
|
|
_seachEnabledByContent = memento->searchEnabledByContent();
|
2017-12-08 08:14:30 +00:00
|
|
|
_searchStartsFocused = memento->searchStartsFocused();
|
2017-10-31 18:25:22 +00:00
|
|
|
} else {
|
|
|
|
_searchFieldController = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-03 15:47:08 +00:00
|
|
|
void Controller::saveSearchState(not_null<ContentMemento*> memento) {
|
|
|
|
if (_searchFieldController) {
|
|
|
|
memento->setSearchFieldQuery(
|
|
|
|
_searchFieldController->query());
|
2017-11-03 18:26:14 +00:00
|
|
|
memento->setSearchEnabledByContent(
|
|
|
|
_seachEnabledByContent.current());
|
2017-11-03 15:47:08 +00:00
|
|
|
}
|
|
|
|
if (_searchController) {
|
|
|
|
auto mediaMemento = dynamic_cast<Media::Memento*>(
|
|
|
|
memento.get());
|
|
|
|
Assert(mediaMemento != nullptr);
|
|
|
|
mediaMemento->setSearchState(_searchController->saveState());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-04 07:33:33 +00:00
|
|
|
void Controller::showSection(
|
2020-12-14 14:48:10 +00:00
|
|
|
std::shared_ptr<Window::SectionMemento> memento,
|
2017-12-04 07:33:33 +00:00
|
|
|
const Window::SectionShow ¶ms) {
|
2020-11-21 22:37:05 +00:00
|
|
|
if (!_widget->showInternal(memento.get(), params)) {
|
2017-12-09 15:13:06 +00:00
|
|
|
AbstractController::showSection(std::move(memento), params);
|
2017-12-04 07:33:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::showBackFromStack(const Window::SectionShow ¶ms) {
|
|
|
|
if (!_widget->showBackFromStackInternal(params)) {
|
2017-12-09 15:13:06 +00:00
|
|
|
AbstractController::showBackFromStack(params);
|
2017-12-04 07:33:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 04:02:51 +00:00
|
|
|
void Controller::removeFromStack(const std::vector<Section> §ions) const {
|
|
|
|
_widget->removeFromStack(sections);
|
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
auto Controller::produceSearchQuery(
|
2017-11-03 15:47:08 +00:00
|
|
|
const QString &query) const -> SearchQuery {
|
2018-01-22 19:51:38 +00:00
|
|
|
Expects(_key.peer() != nullptr);
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
auto result = SearchQuery();
|
|
|
|
result.type = _section.mediaType();
|
2018-01-22 19:51:38 +00:00
|
|
|
result.peerId = _key.peer()->id;
|
2022-10-12 12:57:17 +00:00
|
|
|
result.topicRootId = _key.topic() ? _key.topic()->rootId() : 0;
|
2017-11-03 15:47:08 +00:00
|
|
|
result.query = query;
|
2017-10-31 18:25:22 +00:00
|
|
|
result.migratedPeerId = _migrated ? _migrated->id : PeerId(0);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-11-21 09:20:56 +00:00
|
|
|
rpl::producer<bool> Controller::searchEnabledByContent() const {
|
|
|
|
return _seachEnabledByContent.value();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> Controller::mediaSourceQueryValue() const {
|
2022-03-11 11:14:07 +00:00
|
|
|
return _searchController->currentQueryValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> Controller::searchQueryValue() const {
|
|
|
|
return searchFieldController()->queryValue();
|
2017-11-21 09:20:56 +00:00
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
|
|
|
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
|
|
|
int limitBefore,
|
|
|
|
int limitAfter) const {
|
|
|
|
auto query = _searchController->currentQuery();
|
|
|
|
if (!query.query.isEmpty()) {
|
|
|
|
return _searchController->idsSlice(
|
|
|
|
aroundId,
|
|
|
|
limitBefore,
|
|
|
|
limitAfter);
|
|
|
|
}
|
|
|
|
|
|
|
|
return SharedMediaMergedViewer(
|
2020-06-08 09:06:50 +00:00
|
|
|
&session(),
|
2017-10-31 18:25:22 +00:00
|
|
|
SharedMediaMergedKey(
|
|
|
|
SparseIdsMergedSlice::Key(
|
|
|
|
query.peerId,
|
2022-10-11 15:08:19 +00:00
|
|
|
query.topicRootId,
|
2017-10-31 18:25:22 +00:00
|
|
|
query.migratedPeerId,
|
|
|
|
aroundId),
|
|
|
|
query.type),
|
|
|
|
limitBefore,
|
|
|
|
limitAfter);
|
|
|
|
}
|
|
|
|
|
2022-05-05 20:20:05 +00:00
|
|
|
std::any &Controller::stepDataReference() {
|
|
|
|
return _stepData;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::takeStepData(not_null<Controller*> another) {
|
|
|
|
_stepData = base::take(another->_stepData);
|
|
|
|
}
|
|
|
|
|
2017-10-31 18:25:22 +00:00
|
|
|
Controller::~Controller() = default;
|
|
|
|
|
|
|
|
} // namespace Info
|