/* 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 "window/notifications_manager_default.h" #include "platform/platform_notifications_manager.h" #include "mainwindow.h" #include "lang.h" #include "ui/buttons/icon_button.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" namespace Window { namespace Notifications { namespace Default { namespace { // 3 desktop notifies at the same time. constexpr int kNotifyWindowsCount = 3; NeverFreedPointer ManagerInstance; int notificationWidth() { static auto result = ([] { auto replyWidth = st::defaultBoxButton.font->width(lang(lng_notification_reply).toUpper()) - st::defaultBoxButton.width; auto textLeft = st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft; auto minWidth = textLeft + replyWidth + st::boxButtonPadding.right(); return qMax(st::notifyMinWidth, minWidth); })(); return result; } int notificationMaxHeight() { return st::notifyMinHeight + st::notifyReplyArea.heightMax + st::notifyBorderWidth; } } // namespace void start() { ManagerInstance.makeIfNull(); } Manager *manager() { return ManagerInstance.data(); } void finish() { ManagerInstance.clear(); } Manager::Manager() { subscribe(FileDownload::ImageLoaded(), [this] { for_const (auto notification, _notifications) { notification->updatePeerPhoto(); } }); _inputCheckTimer.setTimeoutHandler([this] { checkLastInput(); }); } void Manager::checkLastInput() { auto waiting = false; for_const (auto notification, _notifications) { if (!notification->checkLastInput()) { waiting = true; } } if (waiting) { _inputCheckTimer.start(300); } } void Manager::startAllHiding() { auto hasReplyingNotification = false; for_const (auto notification, _notifications) { if (notification->isReplying()) { hasReplyingNotification = true; break; } } if (!hasReplyingNotification) { int notHidingCount = 0; for_const (auto notification, _notifications) { if (notification->isShowing()) { ++notHidingCount; } else { notification->startHiding(); } } notHidingCount += _queuedNotifications.size(); if (_hideAll && notHidingCount < 2) { _hideAll->startHiding(); } } } void Manager::stopAllHiding() { for_const (auto notification, _notifications) { notification->stopHiding(); } if (_hideAll) { _hideAll->stopHiding(); } } void Manager::showNextFromQueue() { if (!_queuedNotifications.isEmpty()) { int count = kNotifyWindowsCount; for_const (auto notification, _notifications) { if (notification->isUnlinked()) continue; --count; } if (count > 0) { auto position = notificationStartPosition(); do { auto queued = _queuedNotifications.front(); _queuedNotifications.pop_front(); auto notification = std_::make_unique( queued.history, queued.peer, queued.author, queued.item, queued.forwardedCount, position); Platform::Notifications::defaultNotificationShown(notification.get()); _notifications.push_back(notification.release()); --count; } while (count > 0 && !_queuedNotifications.isEmpty()); _positionsOutdated = true; checkLastInput(); } } if (_positionsOutdated) { moveWidgets(); } } QPoint Manager::notificationStartPosition() const { auto r = psDesktopRect(); auto x = r.x() + r.width() - notificationWidth() - st::notifyDeltaX; auto y = r.y() + r.height(); return QPoint(x, y); } void Manager::moveWidgets() { auto startPosition = notificationStartPosition(); auto top = startPosition.y(); int firstLeft = 0, firstTopCurrent = 0, firstTop = 0, count = 0; for (int i = _notifications.size(); i != 0;) { auto notification = _notifications[--i]; if (notification->isUnlinked()) continue; top -= notification->height() + st::notifyDeltaY; notification->moveTop(top); firstLeft = notification->x(); firstTopCurrent = notification->y(); firstTop = top; ++count; } if (count > 1 || !_queuedNotifications.isEmpty()) { auto deltaY = st::notifyHideAll.height + st::notifyDeltaY; if (!_hideAll) { _hideAll = new HideAllButton(QPoint(firstLeft, firstTopCurrent - deltaY)); } _hideAll->moveTop(firstTop - deltaY); _hideAll->stopHiding(); } else if (_hideAll) { _hideAll->startHidingFast(); } } void Manager::changeNotificationHeight(Notification *notification, int newHeight) { auto deltaHeight = newHeight - notification->height(); if (!deltaHeight) return; notification->addToHeight(deltaHeight, Notification::AddToHeight::Above); auto index = _notifications.indexOf(notification); if (index > 0) { for (int i = 0; i != index; ++i) { auto notification = _notifications[i]; if (notification->isUnlinked()) continue; notification->addToTop(-deltaHeight); } } if (_hideAll) { _hideAll->addToTop(-deltaHeight); } } void Manager::unlinkFromShown(Notification *remove) { if (remove) { if (remove->unlinkHistory()) { _positionsOutdated = true; } } showNextFromQueue(); } void Manager::removeFromShown(Notification *remove) { if (remove) { auto index = _notifications.indexOf(remove); if (index >= 0) { _notifications.removeAt(index); _positionsOutdated = true; } } showNextFromQueue(); } void Manager::removeHideAll(HideAllButton *remove) { if (remove == _hideAll) { _hideAll = nullptr; } } void Manager::doShowNotification(HistoryItem *item, int forwardedCount) { _queuedNotifications.push_back(QueuedNotification(item, forwardedCount)); showNextFromQueue(); } void Manager::doClearAll() { _queuedNotifications.clear(); for_const (auto notification, _notifications) { notification->unlinkHistory(); } showNextFromQueue(); } void Manager::doClearAllFast() { _queuedNotifications.clear(); auto notifications = createAndSwap(_notifications); for_const (auto notification, notifications) { notification->deleteLater(); } showNextFromQueue(); } void Manager::doClearFromHistory(History *history) { for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) { if (i->history == history) { i = _queuedNotifications.erase(i); } else { ++i; } } for_const (auto notification, _notifications) { if (notification->unlinkHistory(history)) { _positionsOutdated = true; } } showNextFromQueue(); } void Manager::doClearFromItem(HistoryItem *item) { for_const (auto notification, _notifications) { // Calls unlinkFromShown() -> showNextFromQueue() notification->itemRemoved(item); } } void Manager::doUpdateAll() { for_const (auto notification, _notifications) { notification->updateNotifyDisplay(); } } Manager::~Manager() { clearAllFast(); } namespace internal { Widget::Widget(QPoint position) : TWidget(nullptr) , _opacityDuration(st::notifyFastAnim) , a_opacity(0, 1) , a_func(anim::linear) , _a_appearance(animation(this, &Widget::step_appearance)) , a_top(position.y()) , _a_movement(animation(this, &Notification::step_movement)) { setWindowOpacity(0.); setAttribute(Qt::WA_OpaquePaintEvent); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint); setAttribute(Qt::WA_MacAlwaysShowToolWindow); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); _a_appearance.start(); } void Widget::step_appearance(float64 ms, bool timer) { float64 dt = ms / float64(_opacityDuration); if (dt >= 1) { a_opacity.finish(); _a_appearance.stop(); if (_hiding) { deleteLater(); } } else { a_opacity.update(dt, a_func); } setWindowOpacity(a_opacity.current()); update(); } void Widget::step_movement(float64 ms, bool timer) { float64 dt = ms / float64(st::notifyFastAnim); if (dt >= 1) { a_top.finish(); } else { a_top.update(dt, anim::linear); } move(x(), a_top.current()); update(); } void Widget::hideSlow() { animHide(st::notifySlowHide, st::notifySlowHideFunc); } void Widget::hideFast() { animHide(st::notifyFastAnim, anim::linear); } void Widget::hideStop() { if (_hiding) { _opacityDuration = st::notifyFastAnim; a_func = anim::linear; a_opacity.start(1); _hiding = false; _a_appearance.start(); } } void Widget::animHide(float64 duration, anim::transition func) { _opacityDuration = duration; a_func = func; a_opacity.start(0); _hiding = true; _a_appearance.start(); } void Widget::moveTop(int top) { a_top.start(top); _a_movement.start(); } void Widget::addToHeight(int add, AddToHeight aboveOrBelow) { int newHeight = height() + add; if (aboveOrBelow == AddToHeight::Above) { a_top.add(-add); } updateGeometry(x(), a_top.current(), width(), newHeight); } void Widget::updateGeometry(int x, int y, int width, int height) { setGeometry(x, y, width, height); update(); } void Widget::addToTop(int add) { a_top.add(add); move(x(), a_top.current()); } Background::Background(QWidget *parent) : TWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void Background::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(rect(), st::notifyBg); p.fillRect(0, 0, st::notifyBorderWidth, height(), st::notifyBorder); p.fillRect(width() - st::notifyBorderWidth, 0, st::notifyBorderWidth, height(), st::notifyBorder); p.fillRect(st::notifyBorderWidth, height() - st::notifyBorderWidth, width() - 2 * st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder); } Notification::Notification(History *history, PeerData *peer, PeerData *author, HistoryItem *msg, int forwardedCount, QPoint position) : Widget(position) , _history(history) , _peer(peer) , _author(author) , _item(msg) , _forwardedCount(forwardedCount) #if defined Q_OS_WIN && !defined Q_OS_WINRT , _started(GetTickCount()) #endif // Q_OS_WIN && !Q_OS_WINRT , _close(this, st::notifyClose) , _reply(this, lang(lng_notification_reply), st::defaultBoxButton) { updateGeometry(position.x(), position.y(), notificationWidth(), st::notifyMinHeight); _userpicLoaded = _peer ? _peer->userpicLoaded() : true; updateNotifyDisplay(); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideByTimer())); _close->setClickedCallback([this] { unlinkHistoryInManager(); }); _close->setAcceptBoth(true); _close->moveToRight(st::notifyClosePos.x(), st::notifyClosePos.y()); _close->show(); _reply->setClickedCallback([this] { showReplyField(); }); _replyPadding = st::notifyMinHeight - st::notifyPhotoPos.y() - st::notifyPhotoSize; _reply->moveToRight(_replyPadding, height() - _reply->height() - _replyPadding); _reply->hide(); prepareActionsCache(); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint); setAttribute(Qt::WA_MacAlwaysShowToolWindow); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); show(); } void Notification::prepareActionsCache() { auto replyCache = myGrab(_reply); auto fadeWidth = st::notifyFadeRight.width(); auto actionsTop = st::notifyTextTop + st::msgNameFont->height; auto actionsCacheWidth = _reply->width() + _replyPadding + fadeWidth; auto actionsCacheHeight = height() - actionsTop; auto actionsCacheImg = QImage(actionsCacheWidth * cIntRetinaFactor(), actionsCacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); actionsCacheImg.fill(st::transparent->c); { Painter p(&actionsCacheImg); st::notifyFadeRight.fill(p, rtlrect(0, 0, fadeWidth, actionsCacheHeight, actionsCacheWidth)); p.fillRect(rtlrect(fadeWidth, 0, actionsCacheWidth - fadeWidth, actionsCacheHeight, actionsCacheWidth), st::notifyBg); p.drawPixmapRight(_replyPadding, _reply->y() - actionsTop, actionsCacheWidth, replyCache); } _buttonsCache = App::pixmapFromImageInPlace(std_::move(actionsCacheImg)); } bool Notification::checkLastInput() { if (!_waitingForInput) return true; auto wasUserInput = true; // TODO #if defined Q_OS_WIN && !defined Q_OS_WINRT LASTINPUTINFO lii; lii.cbSize = sizeof(LASTINPUTINFO); BOOL res = GetLastInputInfo(&lii); wasUserInput = (!res || lii.dwTime >= _started); #endif // Q_OS_WIN && !Q_OS_WINRT if (wasUserInput) { _waitingForInput = false; if (!isReplying()) { _hideTimer.start(st::notifyWaitLongHide); } return true; } return false; } void Notification::onReplyResize() { changeHeight(st::notifyMinHeight + _replyArea->height() + st::notifyBorderWidth); } void Notification::onReplySubmit(bool ctrlShiftEnter) { sendReply(); } void Notification::onReplyCancel() { unlinkHistoryInManager(); } void Notification::updateGeometry(int x, int y, int width, int height) { if (height > st::notifyMinHeight) { if (!_background) { _background = new Background(this); } _background->setGeometry(0, st::notifyMinHeight, width, height - st::notifyMinHeight); } else if (_background) { _background.destroy(); } Widget::updateGeometry(x, y, width, height); } void Notification::paintEvent(QPaintEvent *e) { Painter p(this); p.setClipRect(e->rect()); p.drawPixmap(0, 0, _cache); auto buttonsLeft = st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft; auto buttonsTop = st::notifyTextTop + st::msgNameFont->height; if (a_actionsOpacity.animating(getms())) { p.setOpacity(a_actionsOpacity.current()); p.drawPixmapRight(0, buttonsTop, width(), _buttonsCache); } else if (_actionsVisible) { p.drawPixmapRight(0, buttonsTop, width(), _buttonsCache); } } void Notification::actionsOpacityCallback() { update(); if (!a_actionsOpacity.animating() && _actionsVisible) { _reply->show(); } } void Notification::updateNotifyDisplay() { if (!_history || !_peer || (!_item && _forwardedCount < 2)) return; int32 w = width(), h = height(); QImage img(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); if (cRetina()) img.setDevicePixelRatio(cRetinaFactor()); img.fill(st::notifyBg->c); { Painter p(&img); p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); if (!App::passcoded() && Global::NotifyView() <= dbinvShowName) { _history->peer->loadUserpic(true, true); _history->peer->paintUserpicLeft(p, st::notifyPhotoSize, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width()); } else { static QPixmap icon = App::pixmapFromImageInPlace(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), icon); } int32 itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width; QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height); if (!App::passcoded() && Global::NotifyView() <= dbinvShowName) { if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_history->peer, false)) { chatTypeIcon->paint(p, rectForName.topLeft(), w); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } } if (!App::passcoded() && Global::NotifyView() <= dbinvShowPreview) { const HistoryItem *textCachedFor = 0; Text itemTextCache(itemWidth); QRect r(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height, itemWidth, 2 * st::dialogsTextFont->height); if (_item) { bool active = false; _item->drawInDialog(p, r, active, textCachedFor, itemTextCache); } else if (_forwardedCount > 1) { p.setFont(st::dialogsTextFont); if (_author) { itemTextCache.setText(st::dialogsTextFont, _author->name); p.setPen(st::dialogsTextFgService); itemTextCache.drawElided(p, r.left(), r.top(), r.width(), st::dialogsTextFont->height); r.setTop(r.top() + st::dialogsTextFont->height); } p.setPen(st::dialogsTextFg); p.drawText(r.left(), r.top() + st::dialogsTextFont->ascent, lng_forward_messages(lt_count, _forwardedCount)); } } else { static QString notifyText = st::dialogsTextFont->elided(lang(lng_notification_preview), itemWidth); p.setFont(st::dialogsTextFont); p.setPen(st::dialogsTextFgService); p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText); } p.setPen(st::dialogsNameFg); if (!App::passcoded() && Global::NotifyView() <= dbinvShowName) { _history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } else { p.setFont(st::msgNameFont); static QString notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); } } _cache = App::pixmapFromImageInPlace(std_::move(img)); update(); } void Notification::updatePeerPhoto() { if (_userpicLoaded || !_peer || !_peer->userpicLoaded()) { return; } _userpicLoaded = true; auto img = _cache.toImage(); { Painter p(&img); _peer->paintUserpicLeft(p, st::notifyPhotoSize, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width()); } _cache = App::pixmapFromImageInPlace(std_::move(img)); } void Notification::itemRemoved(HistoryItem *deleted) { if (_item && _item == deleted) { _item = nullptr; unlinkHistoryInManager(); } } void Notification::unlinkHistoryInManager() { if (auto manager = ManagerInstance.data()) { manager->unlinkFromShown(this); } } void Notification::toggleActionButtons(bool visible) { if (_actionsVisible != visible) { _actionsVisible = visible; a_actionsOpacity.start([this] { actionsOpacityCallback(); }, _actionsVisible ? 0. : 1., _actionsVisible ? 1. : 0., st::notifyActionsDuration); _reply->hide(); } } void Notification::showReplyField() { if (_replyArea) return; stopHiding(); _background = new Background(this); _background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth); _background->show(); _replyArea = new InputArea(this, st::notifyReplyArea, lang(lng_message_ph), QString()); _replyArea->resize(width() - st::notifySendReply.width - 2 * st::notifyBorderWidth, st::notifySendReply.height); _replyArea->moveToLeft(st::notifyBorderWidth, st::notifyMinHeight); _replyArea->show(); _replyArea->setFocus(); _replyArea->setMaxLength(MaxMessageSize); _replyArea->setCtrlEnterSubmit(CtrlEnterSubmitBoth); connect(_replyArea, SIGNAL(resized()), this, SLOT(onReplyResize())); connect(_replyArea, SIGNAL(submitted(bool)), this, SLOT(onReplySubmit(bool))); connect(_replyArea, SIGNAL(cancelled()), this, SLOT(onReplyCancel())); _replySend = new Ui::IconButton(this, st::notifySendReply); _replySend->moveToRight(st::notifyBorderWidth, st::notifyMinHeight); _replySend->show(); _replySend->setClickedCallback([this] { sendReply(); }); toggleActionButtons(false); onReplyResize(); update(); } void Notification::sendReply() { if (!_history) return; if (auto manager = ManagerInstance.data()) { auto peerId = _history->peer->id; auto msgId = _item ? _item->id : ShowAtUnreadMsgId; manager->notificationReplied(peerId, msgId, _replyArea->getLastText()); manager->startAllHiding(); } } void Notification::changeHeight(int newHeight) { if (auto manager = ManagerInstance.data()) { manager->changeNotificationHeight(this, newHeight); } } bool Notification::unlinkHistory(History *history) { auto unlink = _history && (history == _history || !history); if (unlink) { if (_history->peer->id != 4456802837) { int a = 0; } hideFast(); _history = nullptr; _item = nullptr; } return unlink; } void Notification::enterEvent(QEvent *e) { if (!_history) return; if (auto manager = ManagerInstance.data()) { manager->stopAllHiding(); } if (!_replyArea) { toggleActionButtons(true); } } void Notification::leaveEvent(QEvent *e) { if (!_history) return; if (auto manager = ManagerInstance.data()) { manager->startAllHiding(); } toggleActionButtons(false); } void Notification::startHiding() { if (!_history) return; hideSlow(); } void Notification::mousePressEvent(QMouseEvent *e) { if (!_history) return; if (e->button() == Qt::RightButton) { unlinkHistoryInManager(); } else { e->ignore(); if (auto manager = ManagerInstance.data()) { auto peerId = _history->peer->id; auto msgId = _item ? _item->id : ShowAtUnreadMsgId; manager->notificationActivated(peerId, msgId); } } } void Notification::stopHiding() { if (!_history) return; _hideTimer.stop(); Widget::hideStop(); } void Notification::onHideByTimer() { startHiding(); } Notification::~Notification() { if (auto manager = ManagerInstance.data()) { manager->removeFromShown(this); } } HideAllButton::HideAllButton(QPoint position) : Widget(position) { setCursor(style::cur_pointer); updateGeometry(position.x(), position.y(), notificationWidth(), st::notifyHideAll.height); hide(); createWinId(); show(); } void HideAllButton::startHiding() { hideSlow(); } void HideAllButton::startHidingFast() { hideFast(); } void HideAllButton::stopHiding() { hideStop(); } HideAllButton::~HideAllButton() { if (auto manager = ManagerInstance.data()) { manager->removeHideAll(this); } } void HideAllButton::enterEvent(QEvent *e) { _mouseOver = true; update(); } void HideAllButton::leaveEvent(QEvent *e) { _mouseOver = false; update(); } void HideAllButton::mousePressEvent(QMouseEvent *e) { _mouseDown = true; } void HideAllButton::mouseReleaseEvent(QMouseEvent *e) { auto mouseDown = createAndSwap(_mouseDown); if (mouseDown && _mouseOver) { if (auto manager = ManagerInstance.data()) { manager->clearAll(); } } } void HideAllButton::paintEvent(QPaintEvent *e) { Painter p(this); p.setClipRect(e->rect()); p.fillRect(rect(), _mouseOver ? st::notifyHideAll.textBgOver : st::notifyHideAll.textBg); p.fillRect(0, 0, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, height() - st::notifyBorderWidth, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder); p.fillRect(width() - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder); p.setFont(st::btnDefLink.font); p.setPen(st::btnDefLink.color); p.drawText(rect(), lang(lng_notification_hide_all), style::al_center); } } // namespace internal } // namespace Default } // namespace Notifications } // namespace Window