info_content_widget.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "info/info_content_widget.h"
  8. #include "api/api_who_reacted.h"
  9. #include "boxes/peer_list_box.h"
  10. #include "data/data_chat.h"
  11. #include "data/data_channel.h"
  12. #include "data/data_session.h"
  13. #include "data/data_forum_topic.h"
  14. #include "data/data_forum.h"
  15. #include "info/profile/info_profile_widget.h"
  16. #include "info/media/info_media_widget.h"
  17. #include "info/common_groups/info_common_groups_widget.h"
  18. #include "info/info_layer_widget.h"
  19. #include "info/info_section_widget.h"
  20. #include "info/info_controller.h"
  21. #include "lang/lang_keys.h"
  22. #include "main/main_session.h"
  23. #include "ui/controls/swipe_handler.h"
  24. #include "ui/widgets/scroll_area.h"
  25. #include "ui/widgets/fields/input_field.h"
  26. #include "ui/wrap/padding_wrap.h"
  27. #include "ui/search_field_controller.h"
  28. #include "ui/ui_utility.h"
  29. #include "window/window_peer_menu.h"
  30. #include "window/window_session_controller.h"
  31. #include "styles/style_info.h"
  32. #include "styles/style_profile.h"
  33. #include "styles/style_layers.h"
  34. #include <QtCore/QCoreApplication>
  35. namespace Info {
  36. ContentWidget::ContentWidget(
  37. QWidget *parent,
  38. not_null<Controller*> controller)
  39. : RpWidget(parent)
  40. , _controller(controller)
  41. , _scroll(
  42. this,
  43. (_controller->wrap() == Wrap::Search
  44. ? st::infoSharedMediaScroll
  45. : st::defaultScrollArea)) {
  46. using namespace rpl::mappers;
  47. setAttribute(Qt::WA_OpaquePaintEvent);
  48. _controller->wrapValue(
  49. ) | rpl::start_with_next([this](Wrap value) {
  50. if (value != Wrap::Layer) {
  51. applyAdditionalScroll(0);
  52. }
  53. _bg = (value == Wrap::Layer)
  54. ? st::boxBg
  55. : st::profileBg;
  56. update();
  57. }, lifetime());
  58. if (_controller->section().type() != Section::Type::Profile) {
  59. rpl::combine(
  60. _controller->wrapValue(),
  61. _controller->searchEnabledByContent(),
  62. (_1 == Wrap::Layer) && _2
  63. ) | rpl::distinct_until_changed(
  64. ) | rpl::start_with_next([this](bool shown) {
  65. refreshSearchField(shown);
  66. }, lifetime());
  67. }
  68. rpl::merge(
  69. _scrollTopSkip.changes(),
  70. _scrollBottomSkip.changes()
  71. ) | rpl::start_with_next([this] {
  72. updateControlsGeometry();
  73. }, lifetime());
  74. }
  75. void ContentWidget::resizeEvent(QResizeEvent *e) {
  76. updateControlsGeometry();
  77. }
  78. void ContentWidget::updateControlsGeometry() {
  79. if (!_innerWrap) {
  80. return;
  81. }
  82. _innerWrap->resizeToWidth(width());
  83. auto newScrollTop = _scroll->scrollTop() + _topDelta;
  84. auto scrollGeometry = rect().marginsRemoved(
  85. { 0, _scrollTopSkip.current(), 0, _scrollBottomSkip.current() });
  86. if (_scroll->geometry() != scrollGeometry) {
  87. _scroll->setGeometry(scrollGeometry);
  88. }
  89. if (!_scroll->isHidden()) {
  90. if (_topDelta) {
  91. _scroll->scrollToY(newScrollTop);
  92. }
  93. auto scrollTop = _scroll->scrollTop();
  94. _innerWrap->setVisibleTopBottom(
  95. scrollTop,
  96. scrollTop + _scroll->height());
  97. }
  98. }
  99. std::shared_ptr<ContentMemento> ContentWidget::createMemento() {
  100. auto result = doCreateMemento();
  101. _controller->saveSearchState(result.get());
  102. return result;
  103. }
  104. void ContentWidget::setIsStackBottom(bool isStackBottom) {
  105. _isStackBottom = isStackBottom;
  106. }
  107. bool ContentWidget::isStackBottom() const {
  108. return _isStackBottom;
  109. }
  110. void ContentWidget::paintEvent(QPaintEvent *e) {
  111. auto p = QPainter(this);
  112. if (_paintPadding.isNull()) {
  113. p.fillRect(e->rect(), _bg);
  114. } else {
  115. const auto &r = e->rect();
  116. const auto padding = QMargins(
  117. 0,
  118. std::min(0, (r.top() - _paintPadding.top())),
  119. 0,
  120. std::min(0, (r.bottom() - _paintPadding.bottom())));
  121. p.fillRect(r + padding, _bg);
  122. }
  123. }
  124. void ContentWidget::setGeometryWithTopMoved(
  125. const QRect &newGeometry,
  126. int topDelta) {
  127. _topDelta = topDelta;
  128. auto willBeResized = (size() != newGeometry.size());
  129. if (geometry() != newGeometry) {
  130. setGeometry(newGeometry);
  131. }
  132. if (!willBeResized) {
  133. QResizeEvent fake(size(), size());
  134. QCoreApplication::sendEvent(this, &fake);
  135. }
  136. _topDelta = 0;
  137. }
  138. Ui::RpWidget *ContentWidget::doSetInnerWidget(
  139. object_ptr<RpWidget> inner) {
  140. using namespace rpl::mappers;
  141. _innerWrap = _scroll->setOwnedWidget(
  142. object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
  143. this,
  144. std::move(inner),
  145. _innerWrap ? _innerWrap->padding() : style::margins()));
  146. _innerWrap->move(0, 0);
  147. setupSwipeHandler(_innerWrap);
  148. // MSVC BUG + REGRESSION rpl::mappers::tuple :(
  149. rpl::combine(
  150. _scroll->scrollTopValue(),
  151. _scroll->heightValue(),
  152. _innerWrap->entity()->desiredHeightValue()
  153. ) | rpl::start_with_next([this](
  154. int top,
  155. int height,
  156. int desired) {
  157. const auto bottom = top + height;
  158. _innerDesiredHeight = desired;
  159. _innerWrap->setVisibleTopBottom(top, bottom);
  160. _scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0));
  161. }, _innerWrap->lifetime());
  162. rpl::combine(
  163. _scroll->heightValue(),
  164. _innerWrap->entity()->heightValue(),
  165. _controller->wrapValue()
  166. ) | rpl::start_with_next([=](
  167. int scrollHeight,
  168. int innerHeight,
  169. Wrap wrap) {
  170. const auto added = (wrap == Wrap::Layer)
  171. ? 0
  172. : std::max(scrollHeight - innerHeight, 0);
  173. if (_addedHeight != added) {
  174. _addedHeight = added;
  175. updateInnerPadding();
  176. }
  177. }, _innerWrap->lifetime());
  178. updateInnerPadding();
  179. return _innerWrap->entity();
  180. }
  181. int ContentWidget::scrollTillBottom(int forHeight) const {
  182. const auto scrollHeight = forHeight
  183. - _scrollTopSkip.current()
  184. - _scrollBottomSkip.current();
  185. const auto scrollBottom = _scroll->scrollTop() + scrollHeight;
  186. const auto desired = _innerDesiredHeight;
  187. return std::max(desired - scrollBottom, 0);
  188. }
  189. rpl::producer<int> ContentWidget::scrollTillBottomChanges() const {
  190. return _scrollTillBottomChanges.events();
  191. }
  192. void ContentWidget::setScrollTopSkip(int scrollTopSkip) {
  193. _scrollTopSkip = scrollTopSkip;
  194. }
  195. void ContentWidget::setScrollBottomSkip(int scrollBottomSkip) {
  196. _scrollBottomSkip = scrollBottomSkip;
  197. }
  198. rpl::producer<int> ContentWidget::scrollHeightValue() const {
  199. return _scroll->heightValue();
  200. }
  201. void ContentWidget::applyAdditionalScroll(int additionalScroll) {
  202. if (_additionalScroll != additionalScroll) {
  203. _additionalScroll = additionalScroll;
  204. if (_innerWrap) {
  205. updateInnerPadding();
  206. }
  207. }
  208. }
  209. void ContentWidget::updateInnerPadding() {
  210. const auto addedToBottom = std::max(_additionalScroll, _addedHeight);
  211. _innerWrap->setPadding({ 0, 0, 0, addedToBottom });
  212. }
  213. void ContentWidget::applyMaxVisibleHeight(int maxVisibleHeight) {
  214. if (_maxVisibleHeight != maxVisibleHeight) {
  215. _maxVisibleHeight = maxVisibleHeight;
  216. update();
  217. }
  218. }
  219. rpl::producer<int> ContentWidget::desiredHeightValue() const {
  220. using namespace rpl::mappers;
  221. return rpl::combine(
  222. _innerWrap->entity()->desiredHeightValue(),
  223. _scrollTopSkip.value(),
  224. _scrollBottomSkip.value()
  225. //) | rpl::map(_1 + _2 + _3);
  226. ) | rpl::map([=](int desired, int, int) {
  227. return desired
  228. + _scrollTopSkip.current()
  229. + _scrollBottomSkip.current();
  230. });
  231. }
  232. rpl::producer<bool> ContentWidget::desiredShadowVisibility() const {
  233. using namespace rpl::mappers;
  234. return rpl::combine(
  235. _scroll->scrollTopValue(),
  236. _scrollTopSkip.value()
  237. ) | rpl::map((_1 > 0) || (_2 > 0));
  238. }
  239. bool ContentWidget::hasTopBarShadow() const {
  240. return (_scroll->scrollTop() > 0);
  241. }
  242. void ContentWidget::setInnerFocus() {
  243. if (_searchField) {
  244. _searchField->setFocus();
  245. } else {
  246. _innerWrap->entity()->setFocus();
  247. }
  248. }
  249. int ContentWidget::scrollTopSave() const {
  250. return _scroll->scrollTop();
  251. }
  252. rpl::producer<int> ContentWidget::scrollTopValue() const {
  253. return _scroll->scrollTopValue();
  254. }
  255. void ContentWidget::scrollTopRestore(int scrollTop) {
  256. _scroll->scrollToY(scrollTop);
  257. }
  258. void ContentWidget::scrollTo(const Ui::ScrollToRequest &request) {
  259. _scroll->scrollTo(request);
  260. }
  261. bool ContentWidget::floatPlayerHandleWheelEvent(QEvent *e) {
  262. return _scroll->viewportEvent(e);
  263. }
  264. QRect ContentWidget::floatPlayerAvailableRect() const {
  265. return mapToGlobal(_scroll->geometry());
  266. }
  267. void ContentWidget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
  268. const auto peer = _controller->key().peer();
  269. const auto topic = _controller->key().topic();
  270. if (!peer && !topic) {
  271. return;
  272. }
  273. Window::FillDialogsEntryMenu(
  274. _controller->parentController(),
  275. Dialogs::EntryState{
  276. .key = (topic
  277. ? Dialogs::Key{ topic }
  278. : Dialogs::Key{ peer->owner().history(peer) }),
  279. .section = Dialogs::EntryState::Section::Profile,
  280. },
  281. addAction);
  282. }
  283. void ContentWidget::checkBeforeCloseByEscape(Fn<void()> close) {
  284. if (_searchField) {
  285. if (!_searchField->empty()) {
  286. _searchField->setText({});
  287. } else {
  288. close();
  289. }
  290. } else {
  291. close();
  292. }
  293. }
  294. rpl::producer<SelectedItems> ContentWidget::selectedListValue() const {
  295. return rpl::single(SelectedItems(Storage::SharedMediaType::Photo));
  296. }
  297. void ContentWidget::setPaintPadding(const style::margins &padding) {
  298. _paintPadding = padding;
  299. }
  300. void ContentWidget::setViewport(
  301. rpl::producer<not_null<QEvent*>> &&events) const {
  302. std::move(
  303. events
  304. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  305. _scroll->viewportEvent(e);
  306. }, _scroll->lifetime());
  307. }
  308. auto ContentWidget::titleStories()
  309. -> rpl::producer<Dialogs::Stories::Content> {
  310. return nullptr;
  311. }
  312. void ContentWidget::saveChanges(FnMut<void()> done) {
  313. done();
  314. }
  315. void ContentWidget::refreshSearchField(bool shown) {
  316. auto search = _controller->searchFieldController();
  317. if (search && shown) {
  318. auto rowView = search->createRowView(
  319. this,
  320. st::infoLayerMediaSearch);
  321. _searchWrap = std::move(rowView.wrap);
  322. _searchField = rowView.field;
  323. const auto view = _searchWrap.get();
  324. widthValue(
  325. ) | rpl::start_with_next([=](int newWidth) {
  326. view->resizeToWidth(newWidth);
  327. view->moveToLeft(0, 0);
  328. }, view->lifetime());
  329. view->show();
  330. _searchField->setFocus();
  331. setScrollTopSkip(view->heightNoMargins() - st::lineWidth);
  332. } else {
  333. if (Ui::InFocusChain(this)) {
  334. setFocus();
  335. }
  336. _searchWrap = nullptr;
  337. setScrollTopSkip(0);
  338. }
  339. }
  340. int ContentWidget::scrollBottomSkip() const {
  341. return _scrollBottomSkip.current();
  342. }
  343. rpl::producer<int> ContentWidget::scrollBottomSkipValue() const {
  344. return _scrollBottomSkip.value();
  345. }
  346. rpl::producer<bool> ContentWidget::desiredBottomShadowVisibility() {
  347. using namespace rpl::mappers;
  348. return rpl::combine(
  349. _scroll->scrollTopValue(),
  350. _scrollBottomSkip.value(),
  351. _scroll->heightValue()
  352. ) | rpl::map([=](int scroll, int skip, int) {
  353. return ((skip > 0) && (scroll < _scroll->scrollTopMax()));
  354. });
  355. }
  356. not_null<Ui::ScrollArea*> ContentWidget::scroll() const {
  357. return _scroll.data();
  358. }
  359. void ContentWidget::setupSwipeHandler(not_null<Ui::RpWidget*> widget) {
  360. Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=](
  361. Ui::Controls::SwipeContextData data) {
  362. if (data.translation > 0) {
  363. if (!_swipeBackData.callback) {
  364. _swipeBackData = Ui::Controls::SetupSwipeBack(
  365. this,
  366. []() -> std::pair<QColor, QColor> {
  367. return {
  368. st::historyForwardChooseBg->c,
  369. st::historyForwardChooseFg->c,
  370. };
  371. });
  372. }
  373. _swipeBackData.callback(data);
  374. return;
  375. } else if (_swipeBackData.lifetime) {
  376. _swipeBackData = {};
  377. }
  378. }, [=](int, Qt::LayoutDirection direction) {
  379. return (direction == Qt::RightToLeft && _controller->hasBackButton())
  380. ? Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  381. checkBeforeClose(crl::guard(this, [=] {
  382. _controller->parentController()->hideLayer();
  383. _controller->showBackFromStack();
  384. }));
  385. })
  386. : Ui::Controls::SwipeHandlerFinishData();
  387. });
  388. }
  389. Key ContentMemento::key() const {
  390. if (const auto topic = this->topic()) {
  391. return Key(topic);
  392. } else if (const auto peer = this->peer()) {
  393. return Key(peer);
  394. } else if (const auto poll = this->poll()) {
  395. return Key(poll, pollContextId());
  396. } else if (const auto self = settingsSelf()) {
  397. return Settings::Tag{ self };
  398. } else if (const auto stories = storiesPeer()) {
  399. return Stories::Tag{ stories, storiesTab() };
  400. } else if (statisticsTag().peer) {
  401. return statisticsTag();
  402. } else if (const auto starref = starrefPeer()) {
  403. return BotStarRef::Tag(starref, starrefType());
  404. } else if (const auto who = reactionsWhoReadIds()) {
  405. return Key(who, _reactionsSelected, _pollReactionsContextId);
  406. } else if (const auto another = globalMediaSelf()) {
  407. return GlobalMedia::Tag{ another };
  408. } else {
  409. return Downloads::Tag();
  410. }
  411. }
  412. ContentMemento::ContentMemento(
  413. not_null<PeerData*> peer,
  414. Data::ForumTopic *topic,
  415. PeerId migratedPeerId)
  416. : _peer(peer)
  417. , _migratedPeerId((!topic && peer->migrateFrom())
  418. ? peer->migrateFrom()->id
  419. : 0)
  420. , _topic(topic) {
  421. if (_topic) {
  422. _peer->owner().itemIdChanged(
  423. ) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
  424. if (_topic->rootId() == change.oldId) {
  425. _topic = _topic->forum()->topicFor(change.newId.msg);
  426. }
  427. }, _lifetime);
  428. }
  429. }
  430. ContentMemento::ContentMemento(Settings::Tag settings)
  431. : _settingsSelf(settings.self.get()) {
  432. }
  433. ContentMemento::ContentMemento(Downloads::Tag downloads) {
  434. }
  435. ContentMemento::ContentMemento(Stories::Tag stories)
  436. : _storiesPeer(stories.peer)
  437. , _storiesTab(stories.tab) {
  438. }
  439. ContentMemento::ContentMemento(Statistics::Tag statistics)
  440. : _statisticsTag(statistics) {
  441. }
  442. ContentMemento::ContentMemento(BotStarRef::Tag starref)
  443. : _starrefPeer(starref.peer)
  444. , _starrefType(starref.type) {
  445. }
  446. ContentMemento::ContentMemento(GlobalMedia::Tag global)
  447. : _globalMediaSelf(global.self) {
  448. }
  449. ContentMemento::ContentMemento(
  450. std::shared_ptr<Api::WhoReadList> whoReadIds,
  451. FullMsgId contextId,
  452. Data::ReactionId selected)
  453. : _reactionsWhoReadIds(whoReadIds
  454. ? whoReadIds
  455. : std::make_shared<Api::WhoReadList>())
  456. , _reactionsSelected(selected)
  457. , _pollReactionsContextId(contextId) {
  458. }
  459. } // namespace Info