notifications_manager_default.cpp 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297
  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 "window/notifications_manager_default.h"
  8. #include "platform/platform_notifications_manager.h"
  9. #include "platform/platform_specific.h"
  10. #include "core/application.h"
  11. #include "core/ui_integration.h"
  12. #include "chat_helpers/message_field.h"
  13. #include "lang/lang_keys.h"
  14. #include "ui/widgets/buttons.h"
  15. #include "ui/widgets/fields/input_field.h"
  16. #include "ui/platform/ui_platform_utility.h"
  17. #include "ui/text/text_options.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/emoji_config.h"
  20. #include "ui/empty_userpic.h"
  21. #include "ui/painter.h"
  22. #include "ui/power_saving.h"
  23. #include "ui/ui_utility.h"
  24. #include "data/data_session.h"
  25. #include "data/data_forum_topic.h"
  26. #include "data/stickers/data_custom_emoji.h"
  27. #include "dialogs/ui/dialogs_layout.h"
  28. #include "window/window_controller.h"
  29. #include "storage/file_download.h"
  30. #include "main/main_session.h"
  31. #include "main/main_account.h"
  32. #include "history/history.h"
  33. #include "history/history_item.h"
  34. #include "history/view/history_view_item_preview.h"
  35. #include "base/platform/base_platform_last_input.h"
  36. #include "base/call_delayed.h"
  37. #include "styles/style_dialogs.h"
  38. #include "styles/style_layers.h"
  39. #include "styles/style_window.h"
  40. #include <QtGui/QGuiApplication>
  41. #include <QtGui/QScreen>
  42. namespace Window {
  43. namespace Notifications {
  44. namespace Default {
  45. namespace {
  46. [[nodiscard]] QPoint notificationStartPosition() {
  47. const auto corner = Core::App().settings().notificationsCorner();
  48. const auto window = Core::App().activePrimaryWindow();
  49. const auto r = window
  50. ? window->widget()->desktopRect()
  51. : QGuiApplication::primaryScreen()->availableGeometry();
  52. const auto isLeft = Core::Settings::IsLeftCorner(corner);
  53. const auto isTop = Core::Settings::IsTopCorner(corner);
  54. const auto x = (isLeft == rtl())
  55. ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX)
  56. : (r.x() + st::notifyDeltaX);
  57. const auto y = isTop ? r.y() : (r.y() + r.height());
  58. return QPoint(x, y);
  59. }
  60. internal::Widget::Direction notificationShiftDirection() {
  61. auto isTop = Core::Settings::IsTopCorner(Core::App().settings().notificationsCorner());
  62. return isTop ? internal::Widget::Direction::Down : internal::Widget::Direction::Up;
  63. }
  64. } // namespace
  65. std::unique_ptr<Manager> Create(System *system) {
  66. return std::make_unique<Manager>(system);
  67. }
  68. Manager::Manager(System *system)
  69. : Notifications::Manager(system)
  70. , _inputCheckTimer([=] { checkLastInput(); }) {
  71. system->settingsChanged(
  72. ) | rpl::start_with_next([=](ChangeType change) {
  73. settingsChanged(change);
  74. }, _lifetime);
  75. }
  76. Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
  77. : history(fields.item->history())
  78. , topicRootId(fields.item->topicRootId())
  79. , peer(history->peer)
  80. , reaction(fields.reactionId)
  81. , author(!fields.reactionFrom
  82. ? fields.item->notificationHeader()
  83. : (fields.reactionFrom != peer)
  84. ? fields.reactionFrom->name()
  85. : QString())
  86. , item((fields.forwardedCount < 2) ? fields.item.get() : nullptr)
  87. , forwardedCount(fields.forwardedCount)
  88. , fromScheduled(reaction.empty() && (fields.item->out() || peer->isSelf())
  89. && fields.item->isFromScheduled()) {
  90. }
  91. QPixmap Manager::hiddenUserpicPlaceholder() const {
  92. if (_hiddenUserpicPlaceholder.isNull()) {
  93. const auto ratio = style::DevicePixelRatio();
  94. _hiddenUserpicPlaceholder = Ui::PixmapFromImage(
  95. LogoNoMargin().scaled(
  96. st::notifyPhotoSize * ratio,
  97. st::notifyPhotoSize * ratio,
  98. Qt::IgnoreAspectRatio,
  99. Qt::SmoothTransformation));
  100. _hiddenUserpicPlaceholder.setDevicePixelRatio(ratio);
  101. }
  102. return _hiddenUserpicPlaceholder;
  103. }
  104. bool Manager::hasReplyingNotification() const {
  105. for (const auto &notification : _notifications) {
  106. if (notification->isReplying()) {
  107. return true;
  108. }
  109. }
  110. return false;
  111. }
  112. void Manager::settingsChanged(ChangeType change) {
  113. if (change == ChangeType::Corner) {
  114. auto startPosition = notificationStartPosition();
  115. auto shiftDirection = notificationShiftDirection();
  116. for (const auto &notification : _notifications) {
  117. notification->updatePosition(startPosition, shiftDirection);
  118. }
  119. if (_hideAll) {
  120. _hideAll->updatePosition(startPosition, shiftDirection);
  121. }
  122. } else if (change == ChangeType::MaxCount) {
  123. int allow = Core::App().settings().notificationsCount();
  124. for (int i = _notifications.size(); i != 0;) {
  125. auto &notification = _notifications[--i];
  126. if (notification->isUnlinked()) continue;
  127. if (--allow < 0) {
  128. notification->unlinkHistory();
  129. }
  130. }
  131. if (allow > 0) {
  132. for (int i = 0; i != allow; ++i) {
  133. showNextFromQueue();
  134. }
  135. }
  136. } else if ((change == ChangeType::DemoIsShown)
  137. || (change == ChangeType::DemoIsHidden)) {
  138. _demoIsShown = (change == ChangeType::DemoIsShown);
  139. _demoMasterOpacity.start(
  140. [=] { demoMasterOpacityCallback(); },
  141. _demoIsShown ? 1. : 0.,
  142. _demoIsShown ? 0. : 1.,
  143. st::notifyFastAnim);
  144. }
  145. }
  146. void Manager::demoMasterOpacityCallback() {
  147. for (const auto &notification : _notifications) {
  148. notification->updateOpacity();
  149. }
  150. if (_hideAll) {
  151. _hideAll->updateOpacity();
  152. }
  153. }
  154. float64 Manager::demoMasterOpacity() const {
  155. return _demoMasterOpacity.value(_demoIsShown ? 0. : 1.);
  156. }
  157. void Manager::checkLastInput() {
  158. auto replying = hasReplyingNotification();
  159. auto waiting = false;
  160. const auto lastInputTime = base::Platform::LastUserInputTimeSupported()
  161. ? std::make_optional(Core::App().lastNonIdleTime())
  162. : std::nullopt;
  163. for (const auto &notification : _notifications) {
  164. if (!notification->checkLastInput(replying, lastInputTime)) {
  165. waiting = true;
  166. }
  167. }
  168. if (waiting) {
  169. _inputCheckTimer.callOnce(300);
  170. }
  171. }
  172. void Manager::startAllHiding() {
  173. if (!hasReplyingNotification()) {
  174. for (const auto &notification : _notifications) {
  175. notification->startHiding();
  176. }
  177. if (_hideAll && _queuedNotifications.size() < 2) {
  178. _hideAll->startHiding();
  179. }
  180. }
  181. }
  182. void Manager::stopAllHiding() {
  183. for (const auto &notification : _notifications) {
  184. notification->stopHiding();
  185. }
  186. if (_hideAll) {
  187. _hideAll->stopHiding();
  188. }
  189. }
  190. void Manager::showNextFromQueue() {
  191. auto guard = gsl::finally([this] {
  192. if (_positionsOutdated) {
  193. moveWidgets();
  194. }
  195. });
  196. if (_queuedNotifications.empty()) {
  197. return;
  198. }
  199. int count = Core::App().settings().notificationsCount();
  200. for (const auto &notification : _notifications) {
  201. if (notification->isUnlinked()) continue;
  202. --count;
  203. }
  204. if (count <= 0) {
  205. return;
  206. }
  207. auto startPosition = notificationStartPosition();
  208. auto startShift = 0;
  209. auto shiftDirection = notificationShiftDirection();
  210. do {
  211. auto queued = _queuedNotifications.front();
  212. _queuedNotifications.pop_front();
  213. subscribeToSession(&queued.history->session());
  214. _notifications.push_back(std::make_unique<Notification>(
  215. this,
  216. queued.history,
  217. queued.topicRootId,
  218. queued.peer,
  219. queued.author,
  220. queued.item,
  221. queued.reaction,
  222. queued.forwardedCount,
  223. queued.fromScheduled,
  224. startPosition,
  225. startShift,
  226. shiftDirection));
  227. --count;
  228. } while (count > 0 && !_queuedNotifications.empty());
  229. _positionsOutdated = true;
  230. checkLastInput();
  231. }
  232. void Manager::subscribeToSession(not_null<Main::Session*> session) {
  233. auto i = _subscriptions.find(session);
  234. if (i == _subscriptions.end()) {
  235. i = _subscriptions.emplace(session).first;
  236. session->account().sessionChanges(
  237. ) | rpl::start_with_next([=] {
  238. _subscriptions.remove(session);
  239. }, i->second.lifetime);
  240. } else if (i->second.subscription) {
  241. return;
  242. }
  243. session->downloaderTaskFinished(
  244. ) | rpl::start_with_next([=] {
  245. auto found = false;
  246. for (const auto &notification : _notifications) {
  247. if (const auto history = notification->maybeHistory()) {
  248. if (&history->session() == session) {
  249. notification->updatePeerPhoto();
  250. found = true;
  251. }
  252. }
  253. }
  254. if (!found) {
  255. _subscriptions[session].subscription.destroy();
  256. }
  257. }, i->second.subscription);
  258. }
  259. void Manager::moveWidgets() {
  260. auto shift = st::notifyDeltaY;
  261. int lastShift = 0, lastShiftCurrent = 0, count = 0;
  262. for (int i = _notifications.size(); i != 0;) {
  263. auto &notification = _notifications[--i];
  264. if (notification->isUnlinked()) continue;
  265. notification->changeShift(shift);
  266. shift += notification->height() + st::notifyDeltaY;
  267. lastShiftCurrent = notification->currentShift();
  268. lastShift = shift;
  269. ++count;
  270. }
  271. if (count > 1 || !_queuedNotifications.empty()) {
  272. if (!_hideAll) {
  273. _hideAll = std::make_unique<HideAllButton>(this, notificationStartPosition(), lastShiftCurrent, notificationShiftDirection());
  274. }
  275. _hideAll->changeShift(lastShift);
  276. _hideAll->stopHiding();
  277. } else if (_hideAll) {
  278. _hideAll->startHidingFast();
  279. }
  280. }
  281. void Manager::changeNotificationHeight(Notification *notification, int newHeight) {
  282. auto deltaHeight = newHeight - notification->height();
  283. if (!deltaHeight) return;
  284. notification->addToHeight(deltaHeight);
  285. auto it = std::find_if(_notifications.cbegin(), _notifications.cend(), [notification](auto &item) {
  286. return (item.get() == notification);
  287. });
  288. if (it != _notifications.cend()) {
  289. for (auto i = _notifications.cbegin(); i != it; ++i) {
  290. auto &notification = *i;
  291. if (notification->isUnlinked()) continue;
  292. notification->addToShift(deltaHeight);
  293. }
  294. }
  295. if (_hideAll) {
  296. _hideAll->addToShift(deltaHeight);
  297. }
  298. }
  299. void Manager::unlinkFromShown(Notification *remove) {
  300. if (remove) {
  301. if (remove->unlinkHistory()) {
  302. _positionsOutdated = true;
  303. }
  304. }
  305. showNextFromQueue();
  306. }
  307. void Manager::removeWidget(internal::Widget *remove) {
  308. if (remove == _hideAll.get()) {
  309. _hideAll.reset();
  310. } else if (remove) {
  311. const auto it = ranges::find(
  312. _notifications,
  313. remove,
  314. &std::unique_ptr<Notification>::get);
  315. if (it != end(_notifications)) {
  316. _notifications.erase(it);
  317. _positionsOutdated = true;
  318. }
  319. }
  320. showNextFromQueue();
  321. }
  322. void Manager::doShowNotification(NotificationFields &&fields) {
  323. _queuedNotifications.emplace_back(std::move(fields));
  324. showNextFromQueue();
  325. }
  326. void Manager::doClearAll() {
  327. _queuedNotifications.clear();
  328. for (const auto &notification : _notifications) {
  329. notification->unlinkHistory();
  330. }
  331. showNextFromQueue();
  332. }
  333. void Manager::doClearAllFast() {
  334. _queuedNotifications.clear();
  335. base::take(_notifications);
  336. base::take(_hideAll);
  337. }
  338. void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
  339. const auto history = topic->history();
  340. const auto topicRootId = topic->rootId();
  341. for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
  342. if (i->history == history && i->topicRootId == topicRootId) {
  343. i = _queuedNotifications.erase(i);
  344. } else {
  345. ++i;
  346. }
  347. }
  348. for (const auto &notification : _notifications) {
  349. if (notification->unlinkHistory(history, topicRootId)) {
  350. _positionsOutdated = true;
  351. }
  352. }
  353. showNextFromQueue();
  354. }
  355. void Manager::doClearFromHistory(not_null<History*> history) {
  356. for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
  357. if (i->history == history) {
  358. i = _queuedNotifications.erase(i);
  359. } else {
  360. ++i;
  361. }
  362. }
  363. for (const auto &notification : _notifications) {
  364. if (notification->unlinkHistory(history)) {
  365. _positionsOutdated = true;
  366. }
  367. }
  368. showNextFromQueue();
  369. }
  370. void Manager::doClearFromSession(not_null<Main::Session*> session) {
  371. for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
  372. if (&i->history->session() == session) {
  373. i = _queuedNotifications.erase(i);
  374. } else {
  375. ++i;
  376. }
  377. }
  378. for (const auto &notification : _notifications) {
  379. if (notification->unlinkSession(session)) {
  380. _positionsOutdated = true;
  381. }
  382. }
  383. showNextFromQueue();
  384. }
  385. void Manager::doClearFromItem(not_null<HistoryItem*> item) {
  386. _queuedNotifications.erase(std::remove_if(_queuedNotifications.begin(), _queuedNotifications.end(), [&](auto &queued) {
  387. return (queued.item == item);
  388. }), _queuedNotifications.cend());
  389. auto showNext = false;
  390. for (const auto &notification : _notifications) {
  391. if (notification->unlinkItem(item)) {
  392. showNext = true;
  393. }
  394. }
  395. if (showNext) {
  396. // This call invalidates _notifications iterators.
  397. showNextFromQueue();
  398. }
  399. }
  400. bool Manager::doSkipToast() const {
  401. return Platform::Notifications::SkipToastForCustom();
  402. }
  403. void Manager::doMaybePlaySound(Fn<void()> playSound) {
  404. Platform::Notifications::MaybePlaySoundForCustom(std::move(playSound));
  405. }
  406. void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
  407. Platform::Notifications::MaybeFlashBounceForCustom(std::move(flashBounce));
  408. }
  409. void Manager::doUpdateAll() {
  410. for (const auto &notification : _notifications) {
  411. notification->updateNotifyDisplay();
  412. }
  413. }
  414. Manager::~Manager() {
  415. clearAllFast();
  416. }
  417. namespace internal {
  418. Widget::Widget(
  419. not_null<Manager*> manager,
  420. QPoint startPosition,
  421. int shift,
  422. Direction shiftDirection)
  423. : _manager(manager)
  424. , _startPosition(startPosition)
  425. , _direction(shiftDirection)
  426. , _shift(shift)
  427. , _shiftAnimation([=](crl::time now) {
  428. return shiftAnimationCallback(now);
  429. }) {
  430. setWindowOpacity(0.);
  431. setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint)
  432. | Qt::WindowStaysOnTopHint
  433. | Qt::BypassWindowManagerHint
  434. | Qt::NoDropShadowWindowHint
  435. | Qt::Tool);
  436. setAttribute(Qt::WA_MacAlwaysShowToolWindow);
  437. setAttribute(Qt::WA_OpaquePaintEvent);
  438. Ui::Platform::InitOnTopPanel(this);
  439. _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
  440. }
  441. void Widget::opacityAnimationCallback() {
  442. updateOpacity();
  443. update();
  444. if (!_a_opacity.animating() && _hiding) {
  445. if (underMouse()) {
  446. // The notification is leaving from under the cursor, but in such case leave hook is not
  447. // triggered automatically. But we still want the manager to start hiding notifications
  448. // (see #28813).
  449. manager()->startAllHiding();
  450. }
  451. manager()->removeWidget(this); // Deletes `this`
  452. }
  453. }
  454. bool Widget::shiftAnimationCallback(crl::time now) {
  455. if (anim::Disabled()) {
  456. now += st::notifyFastAnim;
  457. }
  458. const auto dt = (now - _shiftAnimation.started())
  459. / float64(st::notifyFastAnim);
  460. if (dt >= 1.) {
  461. _shift.finish();
  462. } else {
  463. _shift.update(dt, anim::linear);
  464. }
  465. moveByShift();
  466. return (dt < 1.);
  467. }
  468. void Widget::hideSlow() {
  469. if (anim::Disabled()) {
  470. _hiding = true;
  471. base::call_delayed(
  472. st::notifySlowHide,
  473. this,
  474. [=, guard = _hidingDelayed.make_guard()] {
  475. if (guard && _hiding) {
  476. hideFast();
  477. }
  478. });
  479. } else {
  480. hideAnimated(st::notifySlowHide, anim::easeInCirc);
  481. }
  482. }
  483. void Widget::hideFast() {
  484. hideAnimated(st::notifyFastAnim, anim::linear);
  485. }
  486. void Widget::hideStop() {
  487. if (_hiding) {
  488. _hiding = false;
  489. _hidingDelayed = {};
  490. _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
  491. }
  492. }
  493. void Widget::hideAnimated(float64 duration, const anim::transition &func) {
  494. _hiding = true;
  495. // Stop the previous animation so as to make sure that the notification
  496. // is fully restored before hiding it again.
  497. // Relates to https://github.com/telegramdesktop/tdesktop/issues/28811.
  498. _a_opacity.stop();
  499. _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., duration, func);
  500. }
  501. void Widget::updateOpacity() {
  502. setWindowOpacity(_a_opacity.value(_hiding ? 0. : 1.) * _manager->demoMasterOpacity());
  503. }
  504. void Widget::changeShift(int top) {
  505. _shift.start(top);
  506. _shiftAnimation.start();
  507. }
  508. void Widget::updatePosition(QPoint startPosition, Direction shiftDirection) {
  509. _startPosition = startPosition;
  510. _direction = shiftDirection;
  511. moveByShift();
  512. }
  513. void Widget::addToHeight(int add) {
  514. auto newHeight = height() + add;
  515. auto newPosition = computePosition(newHeight);
  516. updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight);
  517. Ui::ForceFullRepaintSync(this);
  518. }
  519. void Widget::updateGeometry(int x, int y, int width, int height) {
  520. move(x, y);
  521. setFixedSize(width, height);
  522. update();
  523. }
  524. void Widget::addToShift(int add) {
  525. _shift.add(add);
  526. moveByShift();
  527. }
  528. void Widget::moveByShift() {
  529. move(computePosition(height()));
  530. }
  531. QPoint Widget::computePosition(int height) const {
  532. auto realShift = qRound(_shift.current());
  533. if (_direction == Direction::Up) {
  534. realShift = -realShift - height;
  535. }
  536. return QPoint(_startPosition.x(), _startPosition.y() + realShift);
  537. }
  538. Background::Background(QWidget *parent) : TWidget(parent) {
  539. setAttribute(Qt::WA_OpaquePaintEvent);
  540. }
  541. void Background::paintEvent(QPaintEvent *e) {
  542. Painter p(this);
  543. p.fillRect(rect(), st::notificationBg);
  544. p.fillRect(0, 0, st::notifyBorderWidth, height(), st::notifyBorder);
  545. p.fillRect(width() - st::notifyBorderWidth, 0, st::notifyBorderWidth, height(), st::notifyBorder);
  546. p.fillRect(st::notifyBorderWidth, height() - st::notifyBorderWidth, width() - 2 * st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
  547. }
  548. Notification::Notification(
  549. not_null<Manager*> manager,
  550. not_null<History*> history,
  551. MsgId topicRootId,
  552. not_null<PeerData*> peer,
  553. const QString &author,
  554. HistoryItem *item,
  555. const Data::ReactionId &reaction,
  556. int forwardedCount,
  557. bool fromScheduled,
  558. QPoint startPosition,
  559. int shift,
  560. Direction shiftDirection)
  561. : Widget(manager, startPosition, shift, shiftDirection)
  562. , _peer(peer)
  563. , _started(crl::now())
  564. , _history(history)
  565. , _topic(history->peer->forumTopicFor(topicRootId))
  566. , _topicRootId(topicRootId)
  567. , _userpicView(_peer->createUserpicView())
  568. , _author(author)
  569. , _reaction(reaction)
  570. , _item(item)
  571. , _forwardedCount(forwardedCount)
  572. , _fromScheduled(fromScheduled)
  573. , _close(this, st::notifyClose)
  574. , _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) {
  575. Lang::Updated(
  576. ) | rpl::start_with_next([=] {
  577. refreshLang();
  578. }, lifetime());
  579. if (_topic) {
  580. _topic->destroyed(
  581. ) | rpl::start_with_next([=] {
  582. unlinkHistory();
  583. }, lifetime());
  584. }
  585. auto position = computePosition(st::notifyMinHeight);
  586. updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight);
  587. _userpicLoaded = !Ui::PeerUserpicLoading(_userpicView);
  588. updateNotifyDisplay();
  589. _hideTimer.setSingleShot(true);
  590. connect(&_hideTimer, &QTimer::timeout, [=] { startHiding(); });
  591. _close->setClickedCallback([this] {
  592. unlinkHistoryInManager();
  593. });
  594. _close->setAcceptBoth(true);
  595. _close->moveToRight(st::notifyClosePos.x(), st::notifyClosePos.y());
  596. _close->show();
  597. _reply->setClickedCallback([this] {
  598. showReplyField();
  599. });
  600. _replyPadding = st::notifyMinHeight - st::notifyPhotoPos.y() - st::notifyPhotoSize;
  601. updateReplyGeometry();
  602. _reply->hide();
  603. prepareActionsCache();
  604. style::PaletteChanged(
  605. ) | rpl::start_with_next([=] {
  606. updateNotifyDisplay();
  607. if (!_buttonsCache.isNull()) {
  608. prepareActionsCache();
  609. }
  610. update();
  611. if (_background) {
  612. _background->update();
  613. }
  614. }, lifetime());
  615. show();
  616. }
  617. void Notification::updateReplyGeometry() {
  618. _reply->moveToRight(_replyPadding, height() - _reply->height() - _replyPadding);
  619. }
  620. void Notification::refreshLang() {
  621. InvokeQueued(this, [this] { updateReplyGeometry(); });
  622. }
  623. void Notification::prepareActionsCache() {
  624. auto replyCache = Ui::GrabWidget(_reply);
  625. auto fadeWidth = st::notifyFadeRight.width();
  626. auto actionsTop = st::notifyTextTop + st::semiboldFont->height;
  627. auto replyRight = _replyPadding - st::notifyBorderWidth;
  628. auto actionsCacheWidth = _reply->width() + replyRight + fadeWidth;
  629. auto actionsCacheHeight = height() - actionsTop - st::notifyBorderWidth;
  630. auto actionsCacheImg = QImage(
  631. QSize(actionsCacheWidth, actionsCacheHeight)
  632. * style::DevicePixelRatio(),
  633. QImage::Format_ARGB32_Premultiplied);
  634. actionsCacheImg.setDevicePixelRatio(style::DevicePixelRatio());
  635. actionsCacheImg.fill(Qt::transparent);
  636. {
  637. Painter p(&actionsCacheImg);
  638. st::notifyFadeRight.fill(p, style::rtlrect(0, 0, fadeWidth, actionsCacheHeight, actionsCacheWidth));
  639. p.fillRect(style::rtlrect(fadeWidth, 0, actionsCacheWidth - fadeWidth, actionsCacheHeight, actionsCacheWidth), st::notificationBg);
  640. p.drawPixmapRight(replyRight, _reply->y() - actionsTop, actionsCacheWidth, replyCache);
  641. }
  642. _buttonsCache = Ui::PixmapFromImage(std::move(actionsCacheImg));
  643. }
  644. bool Notification::checkLastInput(
  645. bool hasReplyingNotifications,
  646. std::optional<crl::time> lastInputTime) {
  647. if (!_waitingForInput) return true;
  648. using namespace Platform::Notifications;
  649. const auto waitForUserInput = WaitForInputForCustom()
  650. && lastInputTime.has_value()
  651. && (*lastInputTime <= _started);
  652. if (!waitForUserInput) {
  653. _waitingForInput = false;
  654. if (!hasReplyingNotifications) {
  655. _hideTimer.start(st::notifyWaitLongHide);
  656. }
  657. return true;
  658. }
  659. return false;
  660. }
  661. void Notification::replyResized() {
  662. changeHeight(st::notifyMinHeight + _replyArea->height() + st::notifyBorderWidth);
  663. }
  664. void Notification::replyCancel() {
  665. unlinkHistoryInManager();
  666. }
  667. void Notification::updateGeometry(int x, int y, int width, int height) {
  668. if (height > st::notifyMinHeight) {
  669. if (!_background) {
  670. _background.create(this);
  671. }
  672. _background->setGeometry(0, st::notifyMinHeight, width, height - st::notifyMinHeight);
  673. } else if (_background) {
  674. _background.destroy();
  675. }
  676. Widget::updateGeometry(x, y, width, height);
  677. }
  678. void Notification::paintEvent(QPaintEvent *e) {
  679. repaintText();
  680. Painter p(this);
  681. p.setClipRect(e->rect());
  682. p.drawImage(0, 0, _cache);
  683. auto buttonsTop = st::notifyTextTop + st::semiboldFont->height;
  684. if (a_actionsOpacity.animating()) {
  685. p.setOpacity(a_actionsOpacity.value(1.));
  686. p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
  687. } else if (_actionsVisible) {
  688. p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
  689. }
  690. }
  691. void Notification::actionsOpacityCallback() {
  692. update();
  693. if (!a_actionsOpacity.animating() && _actionsVisible) {
  694. _reply->show();
  695. }
  696. }
  697. void Notification::customEmojiCallback() {
  698. if (_textsRepaintScheduled) {
  699. return;
  700. }
  701. _textsRepaintScheduled = true;
  702. crl::on_main(this, [=] { repaintText(); });
  703. }
  704. void Notification::repaintText() {
  705. if (!_textsRepaintScheduled) {
  706. return;
  707. }
  708. _textsRepaintScheduled = false;
  709. if (_cache.isNull()) {
  710. return;
  711. }
  712. Painter p(&_cache);
  713. const auto adjusted = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
  714. const auto skip = (adjusted - st::emojiSize + 1) / 2;
  715. const auto margin = QMargins{ skip, skip, skip, skip };
  716. p.fillRect(_titleRect.marginsAdded(margin), st::notificationBg);
  717. p.fillRect(_textRect.marginsAdded(margin), st::notificationBg);
  718. paintTitle(p);
  719. paintText(p);
  720. update();
  721. }
  722. void Notification::paintTitle(Painter &p) {
  723. p.setPen(st::dialogsNameFg);
  724. p.setFont(st::semiboldFont);
  725. _titleCache.draw(p, {
  726. .position = _titleRect.topLeft(),
  727. .availableWidth = _titleRect.width(),
  728. .palette = &st::dialogsTextPalette,
  729. .spoiler = Ui::Text::DefaultSpoilerCache(),
  730. .pausedEmoji = On(PowerSaving::kEmojiChat),
  731. .pausedSpoiler = On(PowerSaving::kChatSpoiler),
  732. .elisionLines = 1,
  733. });
  734. }
  735. void Notification::paintText(Painter &p) {
  736. p.setPen(st::dialogsTextFg);
  737. p.setFont(st::dialogsTextFont);
  738. _textCache.draw(p, {
  739. .position = _textRect.topLeft(),
  740. .availableWidth = _textRect.width(),
  741. .palette = &st::dialogsTextPalette,
  742. .spoiler = Ui::Text::DefaultSpoilerCache(),
  743. .pausedEmoji = On(PowerSaving::kEmojiChat),
  744. .pausedSpoiler = On(PowerSaving::kChatSpoiler),
  745. .elisionHeight = _textRect.height(),
  746. });
  747. }
  748. void Notification::updateNotifyDisplay() {
  749. if (!_history || (!_item && _forwardedCount < 2)) {
  750. return;
  751. }
  752. const auto options = manager()->getNotificationOptions(
  753. _item,
  754. (_reaction.empty()
  755. ? Data::ItemNotificationType::Message
  756. : Data::ItemNotificationType::Reaction));
  757. _hideReplyButton = options.hideReplyButton;
  758. int32 w = width(), h = height();
  759. auto img = QImage(
  760. size() * style::DevicePixelRatio(),
  761. QImage::Format_ARGB32_Premultiplied);
  762. img.setDevicePixelRatio(style::DevicePixelRatio());
  763. img.fill(st::notificationBg->c);
  764. {
  765. Painter p(&img);
  766. p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
  767. p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
  768. p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
  769. p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
  770. if (!options.hideNameAndPhoto) {
  771. if (_fromScheduled && _history->peer->isSelf()) {
  772. Ui::EmptyUserpic::PaintSavedMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
  773. _userpicLoaded = true;
  774. } else if (_history->peer->isRepliesChat()) {
  775. Ui::EmptyUserpic::PaintRepliesMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
  776. _userpicLoaded = true;
  777. } else {
  778. _userpicView = _history->peer->createUserpicView();
  779. _history->peer->loadUserpic();
  780. _history->peer->paintUserpicLeft(p, _userpicView, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
  781. }
  782. } else {
  783. p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder());
  784. _userpicLoaded = true;
  785. }
  786. int32 itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width;
  787. QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::semiboldFont->height);
  788. const auto reminder = _fromScheduled && _history->peer->isSelf();
  789. if (!options.hideNameAndPhoto) {
  790. if (_fromScheduled) {
  791. static const auto emoji = Ui::Emoji::Find(QString::fromUtf8("\xF0\x9F\x93\x85"));
  792. const auto size = Ui::Emoji::GetSizeNormal()
  793. / style::DevicePixelRatio();
  794. const auto top = rectForName.top() + (st::semiboldFont->height - size) / 2;
  795. Ui::Emoji::Draw(p, emoji, Ui::Emoji::GetSizeNormal(), rectForName.left(), top);
  796. rectForName.setLeft(rectForName.left() + size + st::semiboldFont->spacew);
  797. }
  798. const auto chatTypeIcon = _topic
  799. ? nullptr
  800. : Dialogs::Ui::ChatTypeIcon(_history->peer);
  801. if (chatTypeIcon) {
  802. chatTypeIcon->paint(p, rectForName.topLeft(), w);
  803. rectForName.setLeft(rectForName.left()
  804. + chatTypeIcon->width()
  805. + st::dialogsChatTypeSkip);
  806. }
  807. }
  808. const auto composeText = !options.hideMessageText
  809. || (!_reaction.empty() && !options.hideNameAndPhoto);
  810. if (composeText) {
  811. auto old = base::take(_textCache);
  812. _textCache = Ui::Text::String(itemWidth);
  813. auto r = QRect(
  814. st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
  815. st::notifyItemTop + st::semiboldFont->height,
  816. itemWidth,
  817. 2 * st::dialogsTextFont->height);
  818. const auto text = !_reaction.empty()
  819. ? (!_author.isEmpty()
  820. ? Ui::Text::Colorized(_author).append(' ')
  821. : TextWithEntities()
  822. ).append(Manager::ComposeReactionNotification(
  823. _item,
  824. _reaction,
  825. options.hideMessageText))
  826. : _item
  827. ? _item->toPreview({
  828. .hideSender = reminder,
  829. .generateImages = false,
  830. .spoilerLoginCode = options.spoilerLoginCode,
  831. }).text
  832. : ((!_author.isEmpty()
  833. ? Ui::Text::Colorized(_author)
  834. : TextWithEntities()
  835. ).append(_forwardedCount > 1
  836. ? ('\n' + tr::lng_forward_messages(
  837. tr::now,
  838. lt_count,
  839. _forwardedCount))
  840. : QString()));
  841. const auto options = TextParseOptions{
  842. (TextParseColorized
  843. | TextParseMarkdown
  844. | (_forwardedCount > 1 ? TextParseMultiline : 0)),
  845. 0,
  846. 0,
  847. Qt::LayoutDirectionAuto,
  848. };
  849. const auto context = Core::TextContext({
  850. .session = &_history->session(),
  851. .repaint = [=] { customEmojiCallback(); },
  852. });
  853. _textCache.setMarkedText(
  854. st::dialogsTextStyle,
  855. text,
  856. options,
  857. context);
  858. _textRect = r;
  859. paintText(p);
  860. if (!_textCache.hasPersistentAnimation() && !_topic) {
  861. _textCache = Ui::Text::String();
  862. }
  863. } else {
  864. p.setFont(st::dialogsTextFont);
  865. p.setPen(st::dialogsTextFgService);
  866. p.drawText(
  867. st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
  868. st::notifyItemTop + st::semiboldFont->height + st::dialogsTextFont->ascent,
  869. st::dialogsTextFont->elided(
  870. tr::lng_notification_preview(tr::now),
  871. itemWidth));
  872. }
  873. const auto topicWithChat = [&]() -> TextWithEntities {
  874. const auto name = st::wrap_rtl(_history->peer->name());
  875. return _topic
  876. ? _topic->titleWithIcon().append(u" ("_q + name + ')')
  877. : TextWithEntities{ name };
  878. };
  879. auto title = options.hideNameAndPhoto
  880. ? TextWithEntities{ u"Telegram Desktop"_q }
  881. : reminder
  882. ? tr::lng_notification_reminder(tr::now, Ui::Text::WithEntities)
  883. : topicWithChat();
  884. const auto fullTitle = manager()->addTargetAccountName(
  885. std::move(title),
  886. &_history->session());
  887. const auto context = Core::TextContext({
  888. .session = &_history->session(),
  889. .repaint = [=] { customEmojiCallback(); },
  890. });
  891. _titleCache.setMarkedText(
  892. st::semiboldTextStyle,
  893. fullTitle,
  894. Ui::NameTextOptions(),
  895. context);
  896. _titleRect = rectForName;
  897. paintTitle(p);
  898. }
  899. _cache = std::move(img);
  900. if (!canReply()) {
  901. toggleActionButtons(false);
  902. }
  903. update();
  904. }
  905. void Notification::updatePeerPhoto() {
  906. if (_userpicLoaded) {
  907. return;
  908. }
  909. _userpicView = _peer->createUserpicView();
  910. if (Ui::PeerUserpicLoading(_userpicView)) {
  911. return;
  912. }
  913. _userpicLoaded = true;
  914. Painter p(&_cache);
  915. p.fillRect(
  916. style::rtlrect(
  917. QRect(
  918. st::notifyPhotoPos,
  919. QSize(st::notifyPhotoSize, st::notifyPhotoSize)),
  920. width()),
  921. st::notificationBg);
  922. _peer->paintUserpicLeft(
  923. p,
  924. _userpicView,
  925. st::notifyPhotoPos.x(),
  926. st::notifyPhotoPos.y(),
  927. width(),
  928. st::notifyPhotoSize);
  929. _userpicView = {};
  930. update();
  931. }
  932. bool Notification::unlinkItem(HistoryItem *deleted) {
  933. auto unlink = (_item && _item == deleted);
  934. if (unlink) {
  935. _item = nullptr;
  936. unlinkHistory();
  937. }
  938. return unlink;
  939. }
  940. bool Notification::canReply() const {
  941. return !_hideReplyButton
  942. && (_item != nullptr)
  943. && !Core::App().passcodeLocked()
  944. && (Core::App().settings().notifyView()
  945. <= Core::Settings::NotifyView::ShowPreview);
  946. }
  947. void Notification::unlinkHistoryInManager() {
  948. manager()->unlinkFromShown(this);
  949. }
  950. void Notification::toggleActionButtons(bool visible) {
  951. if (_actionsVisible != visible) {
  952. _actionsVisible = visible;
  953. a_actionsOpacity.start([this] { actionsOpacityCallback(); }, _actionsVisible ? 0. : 1., _actionsVisible ? 1. : 0., st::notifyActionsDuration);
  954. _reply->clearState();
  955. _reply->hide();
  956. }
  957. }
  958. void Notification::showReplyField() {
  959. if (!_item) {
  960. return;
  961. }
  962. raise();
  963. activateWindow();
  964. if (_replyArea) {
  965. _replyArea->setFocus();
  966. return;
  967. }
  968. stopHiding();
  969. _background.create(this);
  970. _background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth);
  971. _background->show();
  972. _replyArea.create(
  973. this,
  974. st::notifyReplyArea,
  975. Ui::InputField::Mode::MultiLine,
  976. tr::lng_message_ph());
  977. _replyArea->resize(width() - st::notifySendReply.width - 2 * st::notifyBorderWidth, st::notifySendReply.height);
  978. _replyArea->moveToLeft(st::notifyBorderWidth, st::notifyMinHeight);
  979. _replyArea->show();
  980. _replyArea->setFocus();
  981. _replyArea->setMaxLength(MaxMessageSize);
  982. _replyArea->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
  983. InitMessageFieldHandlers({
  984. .session = &_item->history()->session(),
  985. .field = _replyArea.data(),
  986. });
  987. // Catch mouse press event to activate the window.
  988. QCoreApplication::instance()->installEventFilter(this);
  989. _replyArea->heightChanges(
  990. ) | rpl::start_with_next([=] {
  991. replyResized();
  992. }, _replyArea->lifetime());
  993. _replyArea->submits(
  994. ) | rpl::start_with_next([=] { sendReply(); }, _replyArea->lifetime());
  995. _replyArea->cancelled(
  996. ) | rpl::start_with_next([=] {
  997. replyCancel();
  998. }, _replyArea->lifetime());
  999. _replySend.create(this, st::notifySendReply);
  1000. _replySend->moveToRight(st::notifyBorderWidth, st::notifyMinHeight);
  1001. _replySend->show();
  1002. _replySend->setClickedCallback([this] { sendReply(); });
  1003. toggleActionButtons(false);
  1004. replyResized();
  1005. update();
  1006. }
  1007. void Notification::sendReply() {
  1008. if (!_history) return;
  1009. manager()->notificationReplied(
  1010. myId(),
  1011. _replyArea->getTextWithAppliedMarkdown());
  1012. manager()->startAllHiding();
  1013. }
  1014. Notifications::Manager::NotificationId Notification::myId() const {
  1015. if (!_history) {
  1016. return {};
  1017. }
  1018. return { .contextId = {
  1019. .sessionId = _history->session().uniqueId(),
  1020. .peerId = _history->peer->id,
  1021. .topicRootId = _topicRootId,
  1022. }, .msgId = _item ? _item->id : ShowAtUnreadMsgId };
  1023. }
  1024. void Notification::changeHeight(int newHeight) {
  1025. manager()->changeNotificationHeight(this, newHeight);
  1026. }
  1027. bool Notification::unlinkHistory(History *history, MsgId topicRootId) {
  1028. const auto unlink = _history
  1029. && (history == _history || !history)
  1030. && (topicRootId == _topicRootId || !topicRootId);
  1031. if (unlink) {
  1032. hideFast();
  1033. _history = nullptr;
  1034. _topic = nullptr;
  1035. _item = nullptr;
  1036. }
  1037. return unlink;
  1038. }
  1039. bool Notification::unlinkSession(not_null<Main::Session*> session) {
  1040. const auto unlink = _history && (&_history->session() == session);
  1041. if (unlink) {
  1042. hideFast();
  1043. _history = nullptr;
  1044. _item = nullptr;
  1045. }
  1046. return unlink;
  1047. }
  1048. void Notification::enterEventHook(QEnterEvent *e) {
  1049. if (!_history) {
  1050. return;
  1051. }
  1052. manager()->stopAllHiding();
  1053. if (!_replyArea && canReply()) {
  1054. toggleActionButtons(true);
  1055. }
  1056. }
  1057. void Notification::leaveEventHook(QEvent *e) {
  1058. if (!_history) {
  1059. return;
  1060. }
  1061. manager()->startAllHiding();
  1062. toggleActionButtons(false);
  1063. }
  1064. void Notification::startHiding() {
  1065. if (!_history) return;
  1066. hideSlow();
  1067. }
  1068. void Notification::mousePressEvent(QMouseEvent *e) {
  1069. if (!_history) return;
  1070. if (e->button() == Qt::RightButton) {
  1071. unlinkHistoryInManager();
  1072. } else {
  1073. e->ignore();
  1074. manager()->notificationActivated(myId());
  1075. }
  1076. }
  1077. bool Notification::eventFilter(QObject *o, QEvent *e) {
  1078. if (e->type() == QEvent::MouseButtonPress) {
  1079. if (auto receiver = qobject_cast<QWidget*>(o)) {
  1080. if (isAncestorOf(receiver)) {
  1081. raise();
  1082. activateWindow();
  1083. }
  1084. }
  1085. }
  1086. return false;
  1087. }
  1088. void Notification::stopHiding() {
  1089. if (!_history) return;
  1090. _hideTimer.stop();
  1091. Widget::hideStop();
  1092. }
  1093. HideAllButton::HideAllButton(
  1094. not_null<Manager*> manager,
  1095. QPoint startPosition,
  1096. int shift,
  1097. Direction shiftDirection)
  1098. : Widget(manager, startPosition, shift, shiftDirection) {
  1099. setCursor(style::cur_pointer);
  1100. auto position = computePosition(st::notifyHideAllHeight);
  1101. updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight);
  1102. style::PaletteChanged(
  1103. ) | rpl::start_with_next([=] {
  1104. update();
  1105. }, lifetime());
  1106. show();
  1107. }
  1108. void HideAllButton::startHiding() {
  1109. hideSlow();
  1110. }
  1111. void HideAllButton::startHidingFast() {
  1112. hideFast();
  1113. }
  1114. void HideAllButton::stopHiding() {
  1115. hideStop();
  1116. }
  1117. void HideAllButton::enterEventHook(QEnterEvent *e) {
  1118. _mouseOver = true;
  1119. update();
  1120. }
  1121. void HideAllButton::leaveEventHook(QEvent *e) {
  1122. _mouseOver = false;
  1123. update();
  1124. }
  1125. void HideAllButton::mousePressEvent(QMouseEvent *e) {
  1126. _mouseDown = true;
  1127. }
  1128. void HideAllButton::mouseReleaseEvent(QMouseEvent *e) {
  1129. auto mouseDown = base::take(_mouseDown);
  1130. if (mouseDown && _mouseOver) {
  1131. manager()->clearAll();
  1132. }
  1133. }
  1134. void HideAllButton::paintEvent(QPaintEvent *e) {
  1135. Painter p(this);
  1136. p.setClipRect(e->rect());
  1137. p.fillRect(rect(), _mouseOver ? st::lightButtonBgOver : st::lightButtonBg);
  1138. p.fillRect(0, 0, width(), st::notifyBorderWidth, st::notifyBorder);
  1139. p.fillRect(0, height() - st::notifyBorderWidth, width(), st::notifyBorderWidth, st::notifyBorder);
  1140. p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder);
  1141. p.fillRect(width() - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder);
  1142. p.setFont(st::defaultLinkButton.font);
  1143. p.setPen(_mouseOver ? st::lightButtonFgOver : st::lightButtonFg);
  1144. p.drawText(rect(), tr::lng_notification_hide_all(tr::now), style::al_center);
  1145. }
  1146. } // namespace internal
  1147. } // namespace Default
  1148. } // namespace Notifications
  1149. } // namespace Window