tdesktop/Telegram/SourceFiles/window/notifications_manager_default.cpp
Kirsan 9c562931a2 Respect user settings "Send by ..." for:
forward dialog
send file dialog
edit caption dialog
notification replay
schedule messages
new channel dialog
group description edit dialog
create poll dialog
rate call dialog
report bot dialog
support mode
2020-02-11 12:29:34 +04:00

1034 lines
29 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/notifications_manager_default.h"
#include "platform/platform_notifications_manager.h"
#include "core/application.h"
#include "lang/lang_keys.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/platform/ui_platform_utility.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "ui/empty_userpic.h"
#include "ui/ui_utility.h"
#include "dialogs/dialogs_layout.h"
#include "window/themes/window_theme.h"
#include "storage/file_download.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "platform/platform_specific.h"
#include "base/call_delayed.h"
#include "facades.h"
#include "app.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
#include <QtCore/QCoreApplication>
namespace Window {
namespace Notifications {
namespace Default {
namespace {
int notificationMaxHeight() {
return st::notifyMinHeight + st::notifyReplyArea.heightMax + st::notifyBorderWidth;
}
QPoint notificationStartPosition() {
auto r = psDesktopRect();
auto isLeft = Notify::IsLeftCorner(Global::NotificationsCorner());
auto isTop = Notify::IsTopCorner(Global::NotificationsCorner());
auto x = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX);
auto y = isTop ? r.y() : (r.y() + r.height());
return QPoint(x, y);
}
internal::Widget::Direction notificationShiftDirection() {
auto isTop = Notify::IsTopCorner(Global::NotificationsCorner());
return isTop ? internal::Widget::Direction::Down : internal::Widget::Direction::Up;
}
} // namespace
std::unique_ptr<Manager> Create(System *system) {
return std::make_unique<Manager>(system);
}
Manager::Manager(System *system)
: Notifications::Manager(system)
, _inputCheckTimer([=] { checkLastInput(); }) {
subscribe(system->session().downloaderTaskFinished(), [this] {
for (const auto &notification : _notifications) {
notification->updatePeerPhoto();
}
});
subscribe(system->settingsChanged(), [this](ChangeType change) {
settingsChanged(change);
});
}
Manager::QueuedNotification::QueuedNotification(
not_null<HistoryItem*> item,
int forwardedCount)
: history(item->history())
, peer(history->peer)
, author(item->notificationHeader())
, item((forwardedCount < 2) ? item.get() : nullptr)
, forwardedCount(forwardedCount)
, fromScheduled((item->out() || peer->isSelf()) && item->isFromScheduled()) {
}
QPixmap Manager::hiddenUserpicPlaceholder() const {
if (_hiddenUserpicPlaceholder.isNull()) {
_hiddenUserpicPlaceholder = App::pixmapFromImageInPlace(Core::App().logoNoMargin().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
_hiddenUserpicPlaceholder.setDevicePixelRatio(cRetinaFactor());
}
return _hiddenUserpicPlaceholder;
}
bool Manager::hasReplyingNotification() const {
for_const (auto &notification, _notifications) {
if (notification->isReplying()) {
return true;
}
}
return false;
}
void Manager::settingsChanged(ChangeType change) {
if (change == ChangeType::Corner) {
auto startPosition = notificationStartPosition();
auto shiftDirection = notificationShiftDirection();
for_const (auto &notification, _notifications) {
notification->updatePosition(startPosition, shiftDirection);
}
if (_hideAll) {
_hideAll->updatePosition(startPosition, shiftDirection);
}
} else if (change == ChangeType::MaxCount) {
int allow = Global::NotificationsCount();
for (int i = _notifications.size(); i != 0;) {
auto &notification = _notifications[--i];
if (notification->isUnlinked()) continue;
if (--allow < 0) {
notification->unlinkHistory();
}
}
if (allow > 0) {
for (int i = 0; i != allow; ++i) {
showNextFromQueue();
}
}
} else if (change == ChangeType::DemoIsShown) {
auto demoIsShown = Global::NotificationsDemoIsShown();
_demoMasterOpacity.start([this] { demoMasterOpacityCallback(); }, demoIsShown ? 1. : 0., demoIsShown ? 0. : 1., st::notifyFastAnim);
}
}
void Manager::demoMasterOpacityCallback() {
for_const (auto &notification, _notifications) {
notification->updateOpacity();
}
if (_hideAll) {
_hideAll->updateOpacity();
}
}
float64 Manager::demoMasterOpacity() const {
return _demoMasterOpacity.value(Global::NotificationsDemoIsShown() ? 0. : 1.);
}
void Manager::checkLastInput() {
auto replying = hasReplyingNotification();
auto waiting = false;
for_const (auto &notification, _notifications) {
if (!notification->checkLastInput(replying)) {
waiting = true;
}
}
if (waiting) {
_inputCheckTimer.callOnce(300);
}
}
void Manager::startAllHiding() {
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() {
auto guard = gsl::finally([this] {
if (_positionsOutdated) {
moveWidgets();
}
});
if (_queuedNotifications.empty()) {
return;
}
int count = Global::NotificationsCount();
for_const (auto &notification, _notifications) {
if (notification->isUnlinked()) continue;
--count;
}
if (count <= 0) {
return;
}
auto startPosition = notificationStartPosition();
auto startShift = 0;
auto shiftDirection = notificationShiftDirection();
do {
auto queued = _queuedNotifications.front();
_queuedNotifications.pop_front();
auto notification = std::make_unique<Notification>(
this,
queued.history,
queued.peer,
queued.author,
queued.item,
queued.forwardedCount,
queued.fromScheduled,
startPosition,
startShift,
shiftDirection);
_notifications.push_back(std::move(notification));
--count;
} while (count > 0 && !_queuedNotifications.empty());
_positionsOutdated = true;
checkLastInput();
}
void Manager::moveWidgets() {
auto shift = st::notifyDeltaY;
int lastShift = 0, lastShiftCurrent = 0, count = 0;
for (int i = _notifications.size(); i != 0;) {
auto &notification = _notifications[--i];
if (notification->isUnlinked()) continue;
notification->changeShift(shift);
shift += notification->height() + st::notifyDeltaY;
lastShiftCurrent = notification->currentShift();
lastShift = shift;
++count;
}
if (count > 1 || !_queuedNotifications.empty()) {
auto deltaY = st::notifyHideAllHeight + st::notifyDeltaY;
if (!_hideAll) {
_hideAll = std::make_unique<HideAllButton>(this, notificationStartPosition(), lastShiftCurrent, notificationShiftDirection());
}
_hideAll->changeShift(lastShift);
_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);
auto it = std::find_if(_notifications.cbegin(), _notifications.cend(), [notification](auto &item) {
return (item.get() == notification);
});
if (it != _notifications.cend()) {
for (auto i = _notifications.cbegin(); i != it; ++i) {
auto &notification = *i;
if (notification->isUnlinked()) continue;
notification->addToShift(deltaHeight);
}
}
if (_hideAll) {
_hideAll->addToShift(deltaHeight);
}
}
void Manager::unlinkFromShown(Notification *remove) {
if (remove) {
if (remove->unlinkHistory()) {
_positionsOutdated = true;
}
}
showNextFromQueue();
}
void Manager::removeWidget(internal::Widget *remove) {
if (remove == _hideAll.get()) {
_hideAll.reset();
} else if (remove) {
const auto it = ranges::find(
_notifications,
remove,
&std::unique_ptr<Notification>::get);
if (it != end(_notifications)) {
_notifications.erase(it);
_positionsOutdated = true;
}
}
showNextFromQueue();
}
void Manager::doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) {
_queuedNotifications.emplace_back(item, forwardedCount);
showNextFromQueue();
}
void Manager::doClearAll() {
_queuedNotifications.clear();
for (const auto &notification : _notifications) {
notification->unlinkHistory();
}
showNextFromQueue();
}
void Manager::doClearAllFast() {
_queuedNotifications.clear();
base::take(_notifications);
base::take(_hideAll);
}
void Manager::doClearFromHistory(not_null<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(not_null<HistoryItem*> item) {
_queuedNotifications.erase(std::remove_if(_queuedNotifications.begin(), _queuedNotifications.end(), [&](auto &queued) {
return (queued.item == item);
}), _queuedNotifications.cend());
auto showNext = false;
for_const (auto &notification, _notifications) {
if (notification->unlinkItem(item)) {
showNext = true;
}
}
if (showNext) {
// This call invalidates _notifications iterators.
showNextFromQueue();
}
}
void Manager::doUpdateAll() {
for_const (auto &notification, _notifications) {
notification->updateNotifyDisplay();
}
}
Manager::~Manager() {
clearAllFast();
}
namespace internal {
Widget::Widget(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection)
: TWidget(nullptr)
, _manager(manager)
, _startPosition(startPosition)
, _direction(shiftDirection)
, _shift(shift)
, _shiftAnimation([=](crl::time now) {
return shiftAnimationCallback(now);
}) {
setWindowOpacity(0.);
setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint)
| Qt::WindowStaysOnTopHint
| Qt::BypassWindowManagerHint
| Qt::NoDropShadowWindowHint
| Qt::Tool);
setAttribute(Qt::WA_MacAlwaysShowToolWindow);
setAttribute(Qt::WA_OpaquePaintEvent);
Ui::Platform::InitOnTopPanel(this);
_a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
}
void Widget::destroyDelayed() {
hide();
if (_deleted) return;
_deleted = true;
// Ubuntu has a lag if a fully transparent widget is destroyed immediately.
base::call_delayed(1000, this, [this] {
manager()->removeWidget(this);
});
}
void Widget::opacityAnimationCallback() {
updateOpacity();
update();
if (!_a_opacity.animating() && _hiding) {
destroyDelayed();
}
}
bool Widget::shiftAnimationCallback(crl::time now) {
if (anim::Disabled()) {
now += st::notifyFastAnim;
}
const auto dt = (now - _shiftAnimation.started())
/ float64(st::notifyFastAnim);
if (dt >= 1.) {
_shift.finish();
} else {
_shift.update(dt, anim::linear);
}
moveByShift();
return (dt < 1.);
}
void Widget::hideSlow() {
if (anim::Disabled()) {
_hiding = true;
base::call_delayed(
st::notifySlowHide,
this,
[=, guard = _hidingDelayed.make_guard()] {
if (guard && _hiding) {
hideFast();
}
});
} else {
hideAnimated(st::notifySlowHide, anim::easeInCirc);
}
}
void Widget::hideFast() {
hideAnimated(st::notifyFastAnim, anim::linear);
}
void Widget::hideStop() {
if (_hiding) {
_hiding = false;
_hidingDelayed = {};
_a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
}
}
void Widget::hideAnimated(float64 duration, const anim::transition &func) {
_hiding = true;
_a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., duration, func);
}
void Widget::updateOpacity() {
setWindowOpacity(_a_opacity.value(_hiding ? 0. : 1.) * _manager->demoMasterOpacity());
}
void Widget::changeShift(int top) {
_shift.start(top);
_shiftAnimation.start();
}
void Widget::updatePosition(QPoint startPosition, Direction shiftDirection) {
_startPosition = startPosition;
_direction = shiftDirection;
moveByShift();
}
void Widget::addToHeight(int add) {
auto newHeight = height() + add;
auto newPosition = computePosition(newHeight);
updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight);
Ui::Platform::UpdateOverlayed(this);
}
void Widget::updateGeometry(int x, int y, int width, int height) {
setGeometry(x, y, width, height);
update();
}
void Widget::addToShift(int add) {
_shift.add(add);
moveByShift();
}
void Widget::moveByShift() {
move(computePosition(height()));
}
QPoint Widget::computePosition(int height) const {
auto realShift = qRound(_shift.current());
if (_direction == Direction::Up) {
realShift = -realShift - height;
}
return QPoint(_startPosition.x(), _startPosition.y() + realShift);
}
Background::Background(QWidget *parent) : TWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
void Background::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(rect(), st::notificationBg);
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(
not_null<Manager*> manager,
not_null<History*> history,
not_null<PeerData*> peer,
const QString &author,
HistoryItem *item,
int forwardedCount,
bool fromScheduled,
QPoint startPosition,
int shift,
Direction shiftDirection)
: Widget(manager, startPosition, shift, shiftDirection)
, _started(crl::now())
, _history(history)
, _peer(peer)
, _author(author)
, _item(item)
, _forwardedCount(forwardedCount)
, _fromScheduled(fromScheduled)
, _close(this, st::notifyClose)
, _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) {
subscribe(Lang::Current().updated(), [this] { refreshLang(); });
auto position = computePosition(st::notifyMinHeight);
updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight);
_userpicLoaded = _peer ? _peer->userpicLoaded() : true;
updateNotifyDisplay();
_hideTimer.setSingleShot(true);
connect(&_hideTimer, &QTimer::timeout, [=] { startHiding(); });
_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;
updateReplyGeometry();
_reply->hide();
prepareActionsCache();
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
if (data.paletteChanged()) {
updateNotifyDisplay();
if (!_buttonsCache.isNull()) {
prepareActionsCache();
}
update();
if (_background) {
_background->update();
}
}
});
show();
}
void Notification::updateReplyGeometry() {
_reply->moveToRight(_replyPadding, height() - _reply->height() - _replyPadding);
}
void Notification::refreshLang() {
InvokeQueued(this, [this] { updateReplyGeometry(); });
}
void Notification::prepareActionsCache() {
auto replyCache = Ui::GrabWidget(_reply);
auto fadeWidth = st::notifyFadeRight.width();
auto actionsTop = st::notifyTextTop + st::msgNameFont->height;
auto replyRight = _replyPadding - st::notifyBorderWidth;
auto actionsCacheWidth = _reply->width() + replyRight + fadeWidth;
auto actionsCacheHeight = height() - actionsTop - st::notifyBorderWidth;
auto actionsCacheImg = QImage(QSize(actionsCacheWidth, actionsCacheHeight) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
actionsCacheImg.setDevicePixelRatio(cRetinaFactor());
actionsCacheImg.fill(Qt::transparent);
{
Painter p(&actionsCacheImg);
st::notifyFadeRight.fill(p, style::rtlrect(0, 0, fadeWidth, actionsCacheHeight, actionsCacheWidth));
p.fillRect(style::rtlrect(fadeWidth, 0, actionsCacheWidth - fadeWidth, actionsCacheHeight, actionsCacheWidth), st::notificationBg);
p.drawPixmapRight(replyRight, _reply->y() - actionsTop, actionsCacheWidth, replyCache);
}
_buttonsCache = App::pixmapFromImageInPlace(std::move(actionsCacheImg));
}
bool Notification::checkLastInput(bool hasReplyingNotifications) {
if (!_waitingForInput) return true;
const auto waitForUserInput = Platform::LastUserInputTimeSupported()
? (Core::App().lastNonIdleTime() <= _started)
: false;
if (!waitForUserInput) {
_waitingForInput = false;
if (!hasReplyingNotifications) {
_hideTimer.start(st::notifyWaitLongHide);
}
return true;
}
return false;
}
void Notification::replyResized() {
changeHeight(st::notifyMinHeight + _replyArea->height() + st::notifyBorderWidth);
}
void Notification::replyCancel() {
unlinkHistoryInManager();
}
void Notification::updateGeometry(int x, int y, int width, int height) {
if (height > st::notifyMinHeight) {
if (!_background) {
_background.create(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()) {
p.setOpacity(a_actionsOpacity.value(1.));
p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
} else if (_actionsVisible) {
p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
}
}
void Notification::actionsOpacityCallback() {
update();
if (!a_actionsOpacity.animating() && _actionsVisible) {
_reply->show();
}
}
void Notification::updateNotifyDisplay() {
if (!_history || !_peer || (!_item && _forwardedCount < 2)) return;
const auto options = Manager::getNotificationOptions(_item);
_hideReplyButton = options.hideReplyButton;
int32 w = width(), h = height();
QImage img(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
img.setDevicePixelRatio(cRetinaFactor());
img.fill(st::notificationBg->c);
{
Painter p(&img);
p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
if (!options.hideNameAndPhoto) {
if (_fromScheduled && _history->peer->isSelf()) {
Ui::EmptyUserpic::PaintSavedMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
_userpicLoaded = true;
} else {
_history->peer->loadUserpic();
_history->peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
}
} else {
p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder());
_userpicLoaded = true;
}
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);
const auto reminder = _fromScheduled && _history->peer->isSelf();
if (!options.hideNameAndPhoto) {
if (_fromScheduled) {
static const auto emoji = Ui::Emoji::Find(QString::fromUtf8("\xF0\x9F\x93\x85"));
const auto size = Ui::Emoji::GetSizeNormal() / cIntRetinaFactor();
const auto top = rectForName.top() + (st::msgNameFont->height - size) / 2;
Ui::Emoji::Draw(p, emoji, Ui::Emoji::GetSizeNormal(), rectForName.left(), top);
rectForName.setLeft(rectForName.left() + size + st::msgNameFont->spacew);
}
if (const auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_history->peer, false, false)) {
chatTypeIcon->paint(p, rectForName.topLeft(), w);
rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
}
}
if (!options.hideMessageText) {
auto itemTextCache = Ui::Text::String(itemWidth);
auto r = QRect(
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
st::notifyItemTop + st::msgNameFont->height,
itemWidth,
2 * st::dialogsTextFont->height);
p.setTextPalette(st::dialogsTextPalette);
p.setPen(st::dialogsTextFg);
p.setFont(st::dialogsTextFont);
const auto text = _item
? _item->inDialogsText(reminder
? HistoryItem::DrawInDialog::WithoutSender
: HistoryItem::DrawInDialog::Normal)
: ((!_author.isEmpty()
? textcmdLink(1, _author)
: QString())
+ (_forwardedCount > 1
? ('\n' + tr::lng_forward_messages(
tr::now,
lt_count,
_forwardedCount))
: QString()));
const auto Options = TextParseOptions{
TextParseRichText
| (_forwardedCount > 1 ? TextParseMultiline : 0),
0,
0,
Qt::LayoutDirectionAuto,
};
itemTextCache.setText(st::dialogsTextStyle, text, Options);
itemTextCache.drawElided(
p,
r.left(),
r.top(),
r.width(),
r.height() / st::dialogsTextFont->height);
p.restoreTextPalette();
} else {
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,
st::dialogsTextFont->elided(
tr::lng_notification_preview(tr::now),
itemWidth));
}
p.setPen(st::dialogsNameFg);
if (options.hideNameAndPhoto) {
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);
} else if (reminder) {
p.setFont(st::msgNameFont);
p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, tr::lng_notification_reminder(tr::now));
} else {
_history->peer->nameText().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
}
}
_cache = App::pixmapFromImageInPlace(std::move(img));
if (!canReply()) {
toggleActionButtons(false);
}
update();
}
void Notification::updatePeerPhoto() {
if (_userpicLoaded || !_peer || !_peer->userpicLoaded()) {
return;
}
_userpicLoaded = true;
auto img = _cache.toImage();
{
Painter p(&img);
_peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
}
_cache = App::pixmapFromImageInPlace(std::move(img));
update();
}
bool Notification::unlinkItem(HistoryItem *deleted) {
auto unlink = (_item && _item == deleted);
if (unlink) {
_item = nullptr;
unlinkHistory();
}
return unlink;
}
bool Notification::canReply() const {
return !_hideReplyButton
&& (_item != nullptr)
&& !Core::App().locked()
&& (Global::NotifyView() <= dbinvShowPreview);
}
void Notification::unlinkHistoryInManager() {
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->clearState();
_reply->hide();
}
}
void Notification::showReplyField() {
if (!_item) {
return;
}
activateWindow();
if (_replyArea) {
_replyArea->setFocus();
return;
}
stopHiding();
_background.create(this);
_background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth);
_background->show();
_replyArea.create(
this,
st::notifyReplyArea,
Ui::InputField::Mode::MultiLine,
tr::lng_message_ph());
_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->setSubmitSettings(_item->history()->session().settings().sendSubmitWay());
_replyArea->setInstantReplaces(Ui::InstantReplaces::Default());
_replyArea->setInstantReplacesEnabled(
_item->history()->session().settings().replaceEmojiValue());
_replyArea->setMarkdownReplacesEnabled(rpl::single(true));
// Catch mouse press event to activate the window.
QCoreApplication::instance()->installEventFilter(this);
connect(_replyArea, &Ui::InputField::resized, [=] { replyResized(); });
connect(_replyArea, &Ui::InputField::submitted, [=] { sendReply(); });
connect(_replyArea, &Ui::InputField::cancelled, [=] { replyCancel(); });
_replySend.create(this, st::notifySendReply);
_replySend->moveToRight(st::notifyBorderWidth, st::notifyMinHeight);
_replySend->show();
_replySend->setClickedCallback([this] { sendReply(); });
toggleActionButtons(false);
replyResized();
update();
}
void Notification::sendReply() {
if (!_history) return;
auto peerId = _history->peer->id;
auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
manager()->notificationReplied(
peerId,
msgId,
_replyArea->getTextWithAppliedMarkdown());
manager()->startAllHiding();
}
void Notification::changeHeight(int newHeight) {
manager()->changeNotificationHeight(this, newHeight);
}
bool Notification::unlinkHistory(History *history) {
const auto unlink = _history && (history == _history || !history);
if (unlink) {
hideFast();
_history = nullptr;
_item = nullptr;
}
return unlink;
}
void Notification::enterEventHook(QEvent *e) {
if (!_history) return;
manager()->stopAllHiding();
if (!_replyArea && canReply()) {
toggleActionButtons(true);
}
}
void Notification::leaveEventHook(QEvent *e) {
if (!_history) return;
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();
auto peerId = _history->peer->id;
auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
manager()->notificationActivated(peerId, msgId);
}
}
bool Notification::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::MouseButtonPress) {
if (auto receiver = qobject_cast<QWidget*>(o)) {
if (isAncestorOf(receiver)) {
activateWindow();
}
}
}
return false;
}
void Notification::stopHiding() {
if (!_history) return;
_hideTimer.stop();
Widget::hideStop();
}
HideAllButton::HideAllButton(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection)
: Widget(manager, startPosition, shift, shiftDirection) {
setCursor(style::cur_pointer);
auto position = computePosition(st::notifyHideAllHeight);
updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight);
hide();
createWinId();
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
if (data.paletteChanged()) {
update();
}
});
show();
}
void HideAllButton::startHiding() {
hideSlow();
}
void HideAllButton::startHidingFast() {
hideFast();
}
void HideAllButton::stopHiding() {
hideStop();
}
void HideAllButton::enterEventHook(QEvent *e) {
_mouseOver = true;
update();
}
void HideAllButton::leaveEventHook(QEvent *e) {
_mouseOver = false;
update();
}
void HideAllButton::mousePressEvent(QMouseEvent *e) {
_mouseDown = true;
}
void HideAllButton::mouseReleaseEvent(QMouseEvent *e) {
auto mouseDown = base::take(_mouseDown);
if (mouseDown && _mouseOver) {
manager()->clearAll();
}
}
void HideAllButton::paintEvent(QPaintEvent *e) {
Painter p(this);
p.setClipRect(e->rect());
p.fillRect(rect(), _mouseOver ? st::lightButtonBgOver : st::lightButtonBg);
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::defaultLinkButton.font);
p.setPen(_mouseOver ? st::lightButtonFgOver : st::lightButtonFg);
p.drawText(rect(), tr::lng_notification_hide_all(tr::now), style::al_center);
}
} // namespace internal
} // namespace Default
} // namespace Notifications
} // namespace Window