diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4c2760266..1532561ac 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -459,6 +459,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_copy_fullname" = "Copy name"; "lng_profile_drop_area_title" = "Drop your image here"; "lng_profile_drop_area_subtitle" = "to set it as a group photo"; +"lng_profile_drop_area_subtitle_channel" = "to set it as a channel photo"; "lng_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 093b8fba7..260a5268c 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2304,6 +2304,10 @@ namespace { return result; } + QPixmap pixmapFromImageInPlace(QImage &&image) { + return QPixmap::fromImage(std_::forward(image), Qt::ColorOnly); + } + void regPhotoItem(PhotoData *data, HistoryItem *item) { ::photoItems[data].insert(item, NullType()); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 4c58c332e..c4428e309 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -234,6 +234,7 @@ namespace App { QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0); QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0); + QPixmap pixmapFromImageInPlace(QImage &&image); void regPhotoItem(PhotoData *data, HistoryItem *item); void unregPhotoItem(PhotoData *data, HistoryItem *item); diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 0ad979dab..c5bc8cdf5 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -109,10 +109,9 @@ void AboutBox::paintEvent(QPaintEvent *e) { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString _getCrashReportFile(const QMimeData *m) { - if (!m || m->urls().size() != 1) return QString(); + if (!m || m->urls().size() != 1 || !m->urls().at(0).isLocalFile()) return QString(); - QString file(m->urls().at(0).toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(m->urls().at(0)); return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString(); } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index a81ccd818..9d2e3123f 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5590,8 +5590,7 @@ DragState HistoryWidget::getDragState(const QMimeData *d) { for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return DragStateNone; - QString file(i->toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(*i); QFileInfo info(file); if (info.isDir()) return DragStateNone; @@ -8420,8 +8419,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) { for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return QStringList(); - QString file(i->toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(*i); QFileInfo info(file); uint64 s = info.size(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 42fd4e156..c3fa01dd6 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -216,9 +216,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_delete && p == _delete) { bool wasactive = (_state & StateFlag::DeleteOver); if (active != wasactive) { - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update)); - _a_deleteOver.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_a_deleteOver, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear); if (active) { _state |= StateFlag::DeleteOver; } else { @@ -231,9 +230,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (active != wasactive) { if (!getShownDocument()->loaded()) { ensureAnimation(); - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update)); - _animation->_a_over.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_animation->_a_over, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear); } if (active) { _state |= StateFlag::Over; @@ -413,9 +411,8 @@ void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (active != _active) { _active = active; - float64 from = _active ? 0 : 1, to = _active ? 1 : 0; - EnsureAnimation(_a_over, from, func(this, &Sticker::update)); - _a_over.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_a_over, func(this, &Sticker::update), from, to, st::stickersRowDuration, anim::linear); } } ItemBase::clickHandlerActiveChanged(p, active); diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 5173d4e17..89431c03f 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -36,6 +36,7 @@ profileTopBarBackPosition: point(32px, 17px); profileMarginTop: 13px; profilePhotoSize: 112px; profilePhotoLeft: 35px; +profilePhotoDuration: 500; profileNameLeft: 26px; profileNameTop: 9px; profileNameLabel: flatLabel(labelDefFlat) { @@ -78,13 +79,14 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) { profileDropAreaBg: profileBg; profileDropAreaFg: #3fb0e4; -profileDropAreaPadding: margins(30px, 20px, 30px, 20px); +profileDropAreaPadding: margins(25px, 3px, 25px, 20px); profileDropAreaTitleFont: font(24px); -profileDropAreaTitleTop: 36px; +profileDropAreaTitleTop: 30px; profileDropAreaSubtitleFont: font(16px); -profileDropAreaSubtitleTop: 72px; +profileDropAreaSubtitleTop: 68px; profileDropAreaBorderFg: profileDropAreaFg; profileDropAreaBorderWidth: 3px; +profileDropAreaDuration: 200; profileDividerFg: black; profileDividerLeft: icon { diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 0a1c8556c..5a0e8741d 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_cover.h" #include "styles/style_profile.h" +#include "profile/profile_cover_drop_area.h" +#include "profile/profile_userpic_button.h" #include "ui/buttons/round_button.h" #include "ui/filedialog.h" #include "observer_peer.h" @@ -71,109 +73,13 @@ const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserC } // namespace -class PhotoButton final : public Button, public Notify::Observer { -public: - PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { - resize(st::profilePhotoSize, st::profilePhotoSize); - - processNewPeerPhoto(); - - Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &PhotoButton::notifyPeerUpdated); - FileDownload::registerImageLoadedObserver(this, &PhotoButton::notifyImageLoaded); - } - -protected: - void paintEvent(QPaintEvent *e) { - Painter p(this); - p.drawPixmap(0, 0, _userpic); - } - -private: - void notifyPeerUpdated(const Notify::PeerUpdate &update) { - if (update.peer != _peer) { - return; - } - - processNewPeerPhoto(); - this->update(); - } - - void notifyImageLoaded() { - if (_waiting && _peer->userpicLoaded()) { - _waiting = false; - _userpic = _peer->genUserpic(st::profilePhotoSize); - update(); - } - } - - void processNewPeerPhoto() { - bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); - setCursor(hasPhoto ? style::cur_pointer : style::cur_default); - _waiting = !_peer->userpicLoaded(); - if (_waiting) { - _peer->loadUserpic(true); - } else { - _userpic = _peer->genUserpic(st::profilePhotoSize); - } - } - - PeerData *_peer; - bool _waiting = false; - QPixmap _userpic; - -}; - -class DropArea : public TWidget { -public: - DropArea(QWidget *parent) : TWidget(parent) { - } - - void showAnimated() { - show(); - } - -protected: - void paintEvent(QPaintEvent *e) override { - Painter p(this); - p.fillRect(e->rect(), st::profileDropAreaBg); - - if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return; - if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return; - - auto border = st::profileDropAreaBorderWidth; - auto &borderFg = st::profileDropAreaBorderFg; - auto inner = rect().marginsRemoved(st::profileDropAreaPadding); - p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg); - p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg); - p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg); - p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg); - - auto title = lang(lng_profile_drop_area_title); - int titleWidth = st::profileDropAreaTitleFont->width(title); - int titleLeft = inner.x() + (inner.width() - titleWidth) / 2; - int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent; - p.setFont(st::profileDropAreaTitleFont); - p.setPen(st::profileDropAreaFg); - p.drawText(titleLeft, titleTop, title); - - auto subtitle = lang(lng_profile_drop_area_subtitle); - int subtitleWidth = st::profileDropAreaSubtitleFont->width(subtitle); - int subtitleLeft = inner.x() + (inner.width() - subtitleWidth) / 2; - int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent; - p.setFont(st::profileDropAreaSubtitleFont); - p.setPen(st::profileDropAreaFg); - p.drawText(subtitleLeft, subtitleTop, subtitle); - } - -}; - CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _peerUser(peer->asUser()) , _peerChat(peer->asChat()) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) -, _photoButton(this, peer) +, _userpicButton(this, peer) , _name(this, QString(), st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); setAcceptDrops(true); @@ -185,7 +91,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated); - connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); + connect(_userpicButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); refreshNameText(); refreshStatusText(); @@ -207,19 +113,19 @@ void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; newHeight += st::profileMarginTop; - _photoButton->moveToLeft(st::profilePhotoLeft, newHeight); + _userpicButton->moveToLeft(st::profilePhotoLeft, newHeight); - int infoLeft = _photoButton->x() + _photoButton->width(); + int infoLeft = _userpicButton->x() + _userpicButton->width(); int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); - int nameTop = _photoButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); + int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); _name.moveToLeft(nameLeft, nameTop); int nameWidth = width() - infoLeft - st::profileNameLeft - st::profileButtonSkip; nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); _name.resizeToWidth(nameWidth); - _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); + _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); - int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft; + int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft; for_const (auto button, _buttons) { button->moveToLeft(buttonLeft, st::profileButtonTop); buttonLeft += button->width() + st::profileButtonSkip; @@ -233,10 +139,15 @@ void CoverWidget::resizeToWidth(int newWidth) { newHeight += st::profileBlocksTop; + resizeDropArea(); resize(newWidth, newHeight); update(); } +void CoverWidget::showFinished() { + _userpicButton->showFinished(); +} + void CoverWidget::paintEvent(QPaintEvent *e) { Painter p(this); @@ -249,21 +160,109 @@ void CoverWidget::paintEvent(QPaintEvent *e) { paintDivider(p); } +void CoverWidget::resizeDropArea() { + if (_dropArea) { + _dropArea->setGeometry(0, 0, width(), _dividerTop); + } +} + +void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) { + if (_dropArea == dropArea) { + _dropArea->deleteLater(); + _dropArea = nullptr; + } +} + +bool CoverWidget::canEditPhoto() const { + if (_peerChat && _peerChat->canEdit()) { + return true; + } else if (_peerMegagroup && _peerMegagroup->canEditPhoto()) { + return true; + } else if (_peerChannel && _peerChannel->canEditPhoto()) { + return true; + } + return false; +} + +bool CoverWidget::mimeDataHasImage(const QMimeData *mimeData) const { + if (!mimeData) return false; + + if (mimeData->hasImage()) return true; + + auto uriListFormat = qsl("text/uri-list"); + if (!mimeData->hasFormat(uriListFormat)) return false; + + auto &urls = mimeData->urls(); + if (urls.size() != 1) return false; + + auto &url = urls.at(0); + if (!url.isLocalFile()) return false; + + auto file = psConvertFileUrl(url); + + QFileInfo info(file); + if (info.isDir()) return false; + + quint64 s = info.size(); + if (s >= MaxUploadDocumentSize) return false; + + for (auto &ext : cImgExtensions()) { + if (file.endsWith(ext, Qt::CaseInsensitive)) { + return true; + } + } + return false; +} + void CoverWidget::dragEnterEvent(QDragEnterEvent *e) { - _dropArea = new DropArea(this); - _dropArea->setGeometry(0, 0, width(), _dividerTop); + if (!canEditPhoto() || !mimeDataHasImage(e->mimeData())) { + e->ignore(); + return; + } + if (!_dropArea) { + auto title = lang(lng_profile_drop_area_title); + QString subtitle; + if (_peerChat || _peerMegagroup) { + subtitle = lang(lng_profile_drop_area_subtitle); + } else { + subtitle = lang(lng_profile_drop_area_subtitle_channel); + } + _dropArea = new CoverDropArea(this, title, subtitle); + resizeDropArea(); + } _dropArea->showAnimated(); + e->setDropAction(Qt::CopyAction); e->accept(); } void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) { - delete _dropArea; - _dropArea = nullptr; + if (_dropArea && !_dropArea->hiding()) { + _dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden)); + } } void CoverWidget::dropEvent(QDropEvent *e) { - delete _dropArea; - _dropArea = nullptr; + auto mimeData = e->mimeData(); + + QImage img; + if (mimeData->hasImage()) { + img = qvariant_cast(mimeData->imageData()); + } else { + auto &urls = mimeData->urls(); + if (urls.size() == 1) { + auto &url = urls.at(0); + if (url.isLocalFile()) { + img = App::readImage(psConvertFileUrl(url)); + } + } + } + + if (!_dropArea->hiding()) { + _dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden)); + } + e->acceptProposedAction(); + + showSetPhotoBox(img); } void CoverWidget::paintDivider(Painter &p) { @@ -439,6 +438,10 @@ void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) img = App::readImage(update.filePaths.front()); } + showSetPhotoBox(img); +} + +void CoverWidget::showSetPhotoBox(const QImage &img) { if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { Ui::showLayer(new InformBox(lang(lng_bad_photo))); return; diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index b5b67ef24..179050452 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -35,8 +35,8 @@ struct PeerUpdate; namespace Profile { class BackButton; -class PhotoButton; -class DropArea; +class UserpicButton; +class CoverDropArea; class CoverWidget final : public TWidget, public Notify::Observer { Q_OBJECT @@ -47,6 +47,8 @@ public: // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth); + void showFinished(); + private slots: void onPhotoShow(); @@ -83,14 +85,20 @@ private: void paintDivider(Painter &p); + bool canEditPhoto() const; + void showSetPhotoBox(const QImage &img); + void resizeDropArea(); + void dropAreaHidden(CoverDropArea *dropArea); + bool mimeDataHasImage(const QMimeData *mimeData) const; + PeerData *_peer; UserData *_peerUser; ChatData *_peerChat; ChannelData *_peerChannel; ChannelData *_peerMegagroup; - ChildWidget _photoButton; - ChildWidget _dropArea = { nullptr }; + ChildWidget _userpicButton; + ChildWidget _dropArea = { nullptr }; FlatLabel _name; diff --git a/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp b/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp new file mode 100644 index 000000000..e3370f66d --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp @@ -0,0 +1,99 @@ +/* +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 "profile/profile_cover_drop_area.h" + +#include "styles/style_profile.h" + +namespace Profile { + +CoverDropArea::CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle) : TWidget(parent) +, _title(title) +, _subtitle(subtitle) +, _titleWidth(st::profileDropAreaTitleFont->width(_title)) +, _subtitleWidth(st::profileDropAreaSubtitleFont->width(_subtitle)) { +} + +void CoverDropArea::showAnimated() { + show(); + _hiding = false; + setupAnimation(); +} + +void CoverDropArea::hideAnimated(HideFinishCallback &&callback) { + _hideFinishCallback = std_::move(callback); + _hiding = true; + setupAnimation(); +} + +void CoverDropArea::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (_a_appearance.animating(getms())) { + p.setOpacity(_a_appearance.current()); + p.drawPixmap(0, 0, _cache); + return; + } + + if (!_cache.isNull()) { + _cache = QPixmap(); + if (_hiding) { + hide(); + _hideFinishCallback.call(this); + return; + } + } + + p.fillRect(e->rect(), st::profileDropAreaBg); + + if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return; + if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return; + + auto border = st::profileDropAreaBorderWidth; + auto &borderFg = st::profileDropAreaBorderFg; + auto inner = rect().marginsRemoved(st::profileDropAreaPadding); + p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg); + p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg); + p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg); + p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg); + + int titleLeft = inner.x() + (inner.width() - _titleWidth) / 2; + int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent; + p.setFont(st::profileDropAreaTitleFont); + p.setPen(st::profileDropAreaFg); + p.drawText(titleLeft, titleTop, _title); + + int subtitleLeft = inner.x() + (inner.width() - _subtitleWidth) / 2; + int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent; + p.setFont(st::profileDropAreaSubtitleFont); + p.setPen(st::profileDropAreaFg); + p.drawText(subtitleLeft, subtitleTop, _subtitle); +} + +void CoverDropArea::setupAnimation() { + if (_cache.isNull()) { + _cache = myGrab(this); + } + auto from = _hiding ? 1. : 0., to = _hiding ? 0. : 1.; + START_ANIMATION(_a_appearance, func(this, &CoverDropArea::refreshCallback), from, to, st::profileDropAreaDuration, anim::linear); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover_drop_area.h b/Telegram/SourceFiles/profile/profile_cover_drop_area.h new file mode 100644 index 000000000..871523ff8 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover_drop_area.h @@ -0,0 +1,57 @@ +/* +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 +*/ +#pragma once + +namespace Profile { + +class CoverDropArea : public TWidget { +public: + CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle); + + void showAnimated(); + + using HideFinishCallback = Function; + void hideAnimated(HideFinishCallback &&callback); + + bool hiding() const { + return _hiding; + } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void refreshCallback() { + update(); + } + void setupAnimation(); + + QString _title, _subtitle; + int _titleWidth, _subtitleWidth; + + QPixmap _cache; + FloatAnimation _a_appearance; + bool _hiding = false; + HideFinishCallback _hideFinishCallback; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 9c3b30f64..5b79a34b3 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -97,7 +97,6 @@ void FixedBar::onDeleteContact() { } - void FixedBar::resizeToWidth(int newWidth) { int newHeight = 0; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index d606c7435..4820efdbb 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -56,6 +56,10 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { } } +void InnerWidget::showFinished() { + _cover->showFinished(); +} + void InnerWidget::decreaseAdditionalHeight(int removeHeight) { resizeToWidth(width(), height() - removeHeight); } diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index 6a1ad1500..8b2f63b40 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -41,6 +41,8 @@ public: // Updates the area that is visible inside the scroll container. void setVisibleTopBottom(int visibleTop, int visibleBottom); + void showFinished(); + signals: void cancelled(); diff --git a/Telegram/SourceFiles/profile/profile_userpic_button.cpp b/Telegram/SourceFiles/profile/profile_userpic_button.cpp new file mode 100644 index 000000000..dc117f7bf --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_userpic_button.cpp @@ -0,0 +1,117 @@ +/* +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 "profile/profile_userpic_button.h" + +#include "styles/style_profile.h" +#include "observer_peer.h" +#include "mtproto/file_download.h" + +namespace Profile { + +UserpicButton::UserpicButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { + resize(st::profilePhotoSize, st::profilePhotoSize); + + processPeerPhoto(); + _notShownYet = _waiting; + if (!_waiting) { + _userpic = prepareUserpicPixmap(); + } + + Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &UserpicButton::notifyPeerUpdated); + FileDownload::registerImageLoadedObserver(this, &UserpicButton::notifyImageLoaded); +} + +void UserpicButton::showFinished() { + if (_notShownYet && !_waiting) { + _notShownYet = false; + _a_appearance.finish(); + START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear); + } +} + +void UserpicButton::paintEvent(QPaintEvent *e) { + Painter p(this); + if (_a_appearance.animating(getms())) { + p.drawPixmap(0, 0, _oldUserpic); + p.setOpacity(_a_appearance.current()); + } + p.drawPixmap(0, 0, _userpic); +} + +void UserpicButton::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != _peer) { + return; + } + + processNewPeerPhoto(); + this->update(); +} + +void UserpicButton::notifyImageLoaded() { + if (_waiting && _peer->userpicLoaded()) { + _waiting = false; + startNewPhotoShowing(); + } +} + +void UserpicButton::processPeerPhoto() { + bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); + setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + _waiting = !_peer->userpicLoaded(); + if (_waiting) { + _peer->loadUserpic(true); + } +} + +void UserpicButton::processNewPeerPhoto() { + processPeerPhoto(); + if (!_waiting) { + startNewPhotoShowing(); + } +} + +void UserpicButton::startNewPhotoShowing() { + _oldUserpic = myGrab(this); + _userpic = prepareUserpicPixmap(); + + if (_notShownYet) { + return; + } + + _a_appearance.finish(); + START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear); + update(); +} + +QPixmap UserpicButton::prepareUserpicPixmap() const { + auto retina = cIntRetinaFactor(); + auto size = st::profilePhotoSize * retina; + QImage image(size, size, QImage::Format_ARGB32_Premultiplied); + { + Painter p(&image); + p.fillRect(0, 0, size, size, st::profileBg); + _peer->paintUserpic(p, st::profilePhotoSize, 0, 0); + } + return App::pixmapFromImageInPlace(std_::move(image)); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_userpic_button.h b/Telegram/SourceFiles/profile/profile_userpic_button.h new file mode 100644 index 000000000..8dc63b1f6 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_userpic_button.h @@ -0,0 +1,64 @@ +/* +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 +*/ +#pragma once + +#include "core/observer.h" + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Profile { + +class UserpicButton final : public Button, public Notify::Observer { +public: + UserpicButton(QWidget *parent, PeerData *peer); + + // If at the first moment the _userpic was not loaded, + // we need to show it animated after the profile is fully shown. + void showFinished(); + +protected: + void paintEvent(QPaintEvent *e); + +private: + void notifyPeerUpdated(const Notify::PeerUpdate &update); + void notifyImageLoaded(); + + void refreshCallback() { + update(); + } + + void processPeerPhoto(); + void processNewPeerPhoto(); + void startNewPhotoShowing(); + QPixmap prepareUserpicPixmap() const; + + bool _notShownYet; + + PeerData *_peer; + bool _waiting = false; + QPixmap _userpic, _oldUserpic; + FloatAnimation _a_appearance; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index ca3672185..3e80d6e69 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -130,6 +130,7 @@ void Widget::showAnimatedHook() { void Widget::showFinishedHook() { _fixedBar->setAnimatingMode(false); + _inner->showFinished(); } } // namespace Profile diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index 0804e8deb..fa4c5a3d9 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -160,8 +160,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index 7cac0e6c4..87d8ac1f8 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -898,8 +898,12 @@ void psSendToMenu(bool send, bool silent) { void psUpdateOverlayed(QWidget *widget) { } -QString psConvertFileUrl(const QString &url) { - return objc_convertFileUrl(url); +QString psConvertFileUrl(const QUrl &url) { + auto urlString = url.toLocalFile(); + if (urlString.startsWith(qsl("/.file/id="))) { + return objc_convertFileUrl(urlString); + } + return urlString; } void psDownloadPathEnableAccess() { diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index 596f9c866..cfeafabc8 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -187,7 +187,7 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); -QString psConvertFileUrl(const QString &url); +QString psConvertFileUrl(const QUrl &url); void psDownloadPathEnableAccess(); QByteArray psDownloadPathBookmark(const QString &path); diff --git a/Telegram/SourceFiles/pspecific_win.h b/Telegram/SourceFiles/pspecific_win.h index e6303f27f..e85046568 100644 --- a/Telegram/SourceFiles/pspecific_win.h +++ b/Telegram/SourceFiles/pspecific_win.h @@ -164,8 +164,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(TWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/pspecific_winrt.h b/Telegram/SourceFiles/pspecific_winrt.h index 75238b789..06722c1ab 100644 --- a/Telegram/SourceFiles/pspecific_winrt.h +++ b/Telegram/SourceFiles/pspecific_winrt.h @@ -161,8 +161,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(TWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 71796e7b3..78996a17e 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -362,7 +362,6 @@ AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacks template class SimpleAnimation { public: - using Callback = Function; SimpleAnimation() { @@ -464,7 +463,16 @@ using FloatAnimation = SimpleAnimation; using IntAnimation = SimpleAnimation; using ColorAnimation = SimpleAnimation; -#define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); } + +// Macro allows us to lazily create updateCallback. +#define ENSURE_ANIMATION(animation, updateCallback, from) \ +if ((animation).isNull()) { \ + (animation).setup((from), (updateCallback)); \ +} + +#define START_ANIMATION(animation, updateCallback, from, to, duration, transition) \ +ENSURE_ANIMATION(animation, updateCallback, from); \ +(animation).start((to), (duration), (transition)) class ClipReader; diff --git a/Telegram/SourceFiles/ui/flatbutton.cpp b/Telegram/SourceFiles/ui/flatbutton.cpp index 5462b395c..691f6e9b5 100644 --- a/Telegram/SourceFiles/ui/flatbutton.cpp +++ b/Telegram/SourceFiles/ui/flatbutton.cpp @@ -335,10 +335,10 @@ void EmojiButton::paintEvent(QPaintEvent *e) { void EmojiButton::setLoading(bool loading) { if (_loading != loading) { - EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::updateCallback)); - a_loading.start(loading ? 1. : 0., st::emojiCircleDuration); _loading = loading; - if (_loading) { + auto from = loading ? 0. : 1., to = loading ? 1. : 0.; + START_ANIMATION(a_loading, func(this, &EmojiButton::updateCallback), from, to, st::emojiCircleDuration, anim::linear); + if (loading) { _a_loading.start(); } else { _a_loading.stop(); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index ec36b0357..17d801473 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1215,9 +1215,11 @@ + + true @@ -1560,7 +1562,9 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 7a28c5dd6..7412f0303 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1209,6 +1209,12 @@ SourceFiles + + SourceFiles\profile + + + SourceFiles\profile + @@ -1406,6 +1412,12 @@ SourceFiles + + SourceFiles\profile + + + SourceFiles\profile +