453 lines
13 KiB
C++
453 lines
13 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
an unofficial desktop 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.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014 John Preston, https://tdesktop.com
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "lang.h"
|
|
|
|
#include "layerwidget.h"
|
|
#include "application.h"
|
|
#include "window.h"
|
|
#include "mainwidget.h"
|
|
#include "gui/filedialog.h"
|
|
|
|
BackgroundWidget::BackgroundWidget(QWidget *parent, LayeredWidget *w) : QWidget(parent), w(w), _hidden(0),
|
|
aBackground(0), aBackgroundFunc(anim::easeOutCirc), hiding(false), shadow(st::boxShadow) {
|
|
w->setParent(this);
|
|
setGeometry(0, 0, App::wnd()->width(), App::wnd()->height());
|
|
aBackground.start(1);
|
|
anim::start(this);
|
|
show();
|
|
connect(w, SIGNAL(closed()), this, SLOT(onInnerClose()));
|
|
connect(w, SIGNAL(resized()), this, SLOT(update()));
|
|
w->setFocus();
|
|
}
|
|
|
|
void BackgroundWidget::paintEvent(QPaintEvent *e) {
|
|
bool trivial = (rect() == e->rect());
|
|
|
|
QPainter p(this);
|
|
if (!trivial) {
|
|
p.setClipRect(e->rect());
|
|
}
|
|
p.setOpacity(st::layerAlpha * aBackground.current());
|
|
p.fillRect(rect(), st::layerBG->b);
|
|
|
|
p.setOpacity(aBackground.current());
|
|
shadow.paint(p, w->boxRect());
|
|
}
|
|
|
|
void BackgroundWidget::keyPressEvent(QKeyEvent *e) {
|
|
if (e->key() == Qt::Key_Escape) {
|
|
startHide();
|
|
}
|
|
}
|
|
|
|
void BackgroundWidget::mousePressEvent(QMouseEvent *e) {
|
|
}
|
|
|
|
void BackgroundWidget::onClose() {
|
|
startHide();
|
|
}
|
|
|
|
void BackgroundWidget::onInnerClose() {
|
|
if (_hidden) {
|
|
w->deleteLater();
|
|
w = _hidden;
|
|
_hidden = 0;
|
|
w->show();
|
|
resizeEvent(0);
|
|
w->animStep(1);
|
|
update();
|
|
} else {
|
|
onClose();
|
|
}
|
|
}
|
|
|
|
void BackgroundWidget::startHide() {
|
|
if (App::main()) App::main()->setInnerFocus();
|
|
hiding = true;
|
|
aBackground.start(0);
|
|
anim::start(this);
|
|
w->startHide();
|
|
}
|
|
|
|
void BackgroundWidget::resizeEvent(QResizeEvent *e) {
|
|
w->parentResized();
|
|
}
|
|
|
|
void BackgroundWidget::replaceInner(LayeredWidget *n) {
|
|
if (_hidden) _hidden->deleteLater();
|
|
_hidden = w;
|
|
_hidden->hide();
|
|
w = n;
|
|
w->setParent(this);
|
|
connect(w, SIGNAL(closed()), this, SLOT(onInnerClose()));
|
|
connect(w, SIGNAL(resized()), this, SLOT(update()));
|
|
w->show();
|
|
resizeEvent(0);
|
|
w->animStep(1);
|
|
update();
|
|
}
|
|
|
|
bool BackgroundWidget::animStep(float64 ms) {
|
|
float64 dt = ms / (hiding ? st::layerHideDuration : st::layerSlideDuration);
|
|
w->animStep(dt);
|
|
bool res = true;
|
|
if (dt >= 1) {
|
|
aBackground.finish();
|
|
if (hiding) {
|
|
QTimer::singleShot(0, App::wnd(), SLOT(layerHidden()));
|
|
}
|
|
res = false;
|
|
} else {
|
|
aBackground.update(dt, aBackgroundFunc);
|
|
}
|
|
update();
|
|
return res;
|
|
}
|
|
|
|
BackgroundWidget::~BackgroundWidget() {
|
|
if (App::wnd()) App::wnd()->noBox(this);
|
|
w->deleteLater();
|
|
if (_hidden) _hidden->deleteLater();
|
|
}
|
|
|
|
LayerWidget::LayerWidget(QWidget *parent, PhotoData *photo, HistoryItem *item) : QWidget(parent)
|
|
, photo(photo)
|
|
, video(0)
|
|
, aBackground(0)
|
|
, aOver(0)
|
|
, iX(App::wnd()->width() / 2)
|
|
, iY(App::wnd()->height() / 2)
|
|
, iW(0)
|
|
, iCoordFunc(anim::sineInOut)
|
|
, aBackgroundFunc(anim::easeOutCirc)
|
|
, aOverFunc(anim::linear)
|
|
, hiding(false)
|
|
, _touchPress(false)
|
|
, _touchMove(false)
|
|
, _touchRightButton(false)
|
|
, _menu(0)
|
|
{
|
|
int32 x, y, w;
|
|
if (App::wnd()->getPhotoCoords(photo, x, y, w)) {
|
|
iX = anim::ivalue(x);
|
|
iY = anim::ivalue(y);
|
|
iW = anim::ivalue(w);
|
|
}
|
|
photo->full->load();
|
|
setGeometry(0, 0, App::wnd()->width(), App::wnd()->height());
|
|
aBackground.start(1);
|
|
aOver.start(1);
|
|
anim::start(this);
|
|
show();
|
|
setFocus();
|
|
App::contextItem(item);
|
|
|
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
|
_touchTimer.setSingleShot(true);
|
|
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
|
}
|
|
|
|
LayerWidget::LayerWidget(QWidget *parent, VideoData *video, HistoryItem *item) : QWidget(parent)
|
|
, photo(0)
|
|
, video(video)
|
|
, aBackground(0)
|
|
, aOver(0)
|
|
, iX(App::wnd()->width() / 2)
|
|
, iY(App::wnd()->height() / 2)
|
|
, iW(0)
|
|
, iCoordFunc(anim::sineInOut)
|
|
, aBackgroundFunc(anim::easeOutCirc)
|
|
, aOverFunc(anim::linear)
|
|
, hiding(false)
|
|
, _touchPress(false)
|
|
, _touchMove(false)
|
|
, _touchRightButton(false)
|
|
, _menu(0)
|
|
{
|
|
int32 x, y, w;
|
|
if (App::wnd()->getVideoCoords(video, x, y, w)) {
|
|
iX = anim::ivalue(x);
|
|
iY = anim::ivalue(y);
|
|
iW = anim::ivalue(w);
|
|
}
|
|
setGeometry(0, 0, App::wnd()->width(), App::wnd()->height());
|
|
aBackground.start(1);
|
|
aOver.start(1);
|
|
anim::start(this);
|
|
show();
|
|
setFocus();
|
|
App::contextItem(item);
|
|
|
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
|
_touchTimer.setSingleShot(true);
|
|
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
|
}
|
|
|
|
PhotoData *LayerWidget::photoShown() {
|
|
return hiding ? 0 : photo;
|
|
}
|
|
|
|
void LayerWidget::onTouchTimer() {
|
|
_touchRightButton = true;
|
|
}
|
|
|
|
bool LayerWidget::event(QEvent *e) {
|
|
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
|
|
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
|
if (ev->device()->type() == QTouchDevice::TouchScreen) {
|
|
touchEvent(ev);
|
|
return true;
|
|
}
|
|
}
|
|
return QWidget::event(e);
|
|
}
|
|
|
|
void LayerWidget::touchEvent(QTouchEvent *e) {
|
|
switch (e->type()) {
|
|
case QEvent::TouchBegin:
|
|
if (_touchPress || e->touchPoints().isEmpty()) return;
|
|
_touchTimer.start(QApplication::startDragTime());
|
|
_touchPress = true;
|
|
_touchMove = _touchRightButton = false;
|
|
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
|
|
break;
|
|
|
|
case QEvent::TouchUpdate:
|
|
if (!_touchPress || e->touchPoints().isEmpty()) return;
|
|
if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
|
|
_touchMove = true;
|
|
}
|
|
break;
|
|
|
|
case QEvent::TouchEnd:
|
|
if (!_touchPress) return;
|
|
if (!_touchMove && App::wnd()) {
|
|
Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton);
|
|
QPoint mapped(mapFromGlobal(_touchStart)), winMapped(App::wnd()->mapFromGlobal(_touchStart));
|
|
|
|
QMouseEvent pressEvent(QEvent::MouseButtonPress, mapped, winMapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers());
|
|
pressEvent.accept();
|
|
mousePressEvent(&pressEvent);
|
|
|
|
QMouseEvent releaseEvent(QEvent::MouseButtonRelease, mapped, winMapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers());
|
|
mouseReleaseEvent(&releaseEvent);
|
|
|
|
if (_touchRightButton) {
|
|
QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart);
|
|
contextMenuEvent(&contextEvent);
|
|
}
|
|
}
|
|
_touchTimer.stop();
|
|
_touchPress = _touchMove = _touchRightButton = false;
|
|
break;
|
|
|
|
case QEvent::TouchCancel:
|
|
_touchPress = false;
|
|
_touchTimer.stop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LayerWidget::onMenuDestroy(QObject *obj) {
|
|
if (_menu == obj) {
|
|
_menu = 0;
|
|
}
|
|
}
|
|
|
|
void LayerWidget::paintEvent(QPaintEvent *e) {
|
|
bool trivial = (rect() == e->rect());
|
|
|
|
QPainter p(this);
|
|
if (!trivial) {
|
|
p.setClipRect(e->rect());
|
|
}
|
|
p.setOpacity(st::layerAlpha * aBackground.current());
|
|
p.fillRect(rect(), st::layerBG->b);
|
|
if (iW.current()) {
|
|
if (!hiding) p.setOpacity(aOver.current());
|
|
if (animating()) {
|
|
const QPixmap &pm((photo ? (photo->full->loaded() ? photo->full : photo->thumb) : video->thumb)->pix());
|
|
int32 h = pm.width() ? (pm.height() * iW.current() / pm.width()) : 1;
|
|
p.drawPixmap(iX.current(), iY.current(), iW.current(), h, pm);
|
|
if (!hiding) {
|
|
p.setOpacity(1);
|
|
p.setClipRect(App::wnd()->photoRect(), Qt::IntersectClip);
|
|
p.drawPixmap(iX.current(), iY.current(), iW.current(), h, pm);
|
|
}
|
|
} else {
|
|
const QPixmap &pm((photo ? (photo->full->loaded() ? photo->full : photo->thumb) : video->thumb)->pixNoCache(iW.current(), 0, !animating()));
|
|
p.drawPixmap(iX.current(), iY.current(), pm);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerWidget::keyPressEvent(QKeyEvent *e) {
|
|
if (!_menu && e->key() == Qt::Key_Escape) {
|
|
startHide();
|
|
} else if (photo && photo->full->loaded() && (e == QKeySequence::Save || e == QKeySequence::SaveAs)) {
|
|
QString file;
|
|
if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) {
|
|
if (!file.isEmpty()) {
|
|
photo->full->pix().toImage().save(file, "JPG");
|
|
}
|
|
}
|
|
} else if (photo && photo->full->loaded() && (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier)))) {
|
|
QApplication::clipboard()->setPixmap(photo->full->pix());
|
|
}
|
|
}
|
|
|
|
void LayerWidget::mousePressEvent(QMouseEvent *e) {
|
|
if (_menu) return;
|
|
if (e->button() == Qt::LeftButton) startHide();
|
|
}
|
|
|
|
void LayerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
|
if (photo && photo->full->loaded() && !hiding) {
|
|
if (_menu) {
|
|
_menu->deleteLater();
|
|
_menu = 0;
|
|
}
|
|
_menu = new QMenu(this);
|
|
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
|
|
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
|
|
_menu->addAction(lang(lng_context_close_image), this, SLOT(startHide()))->setEnabled(true);
|
|
if (App::contextItem()) {
|
|
if (dynamic_cast<HistoryMessage*>(App::contextItem())) {
|
|
_menu->addAction(lang(lng_context_forward_image), this, SLOT(forwardMessage()))->setEnabled(true);
|
|
}
|
|
_menu->addAction(lang(lng_context_delete_image), this, SLOT(deleteMessage()))->setEnabled(true);
|
|
} else if ((App::self() && App::self()->photoId == photo->id) || (photo->chat && photo->chat->photoId == photo->id)) {
|
|
_menu->addAction(lang(lng_context_delete_image), this, SLOT(deleteMessage()))->setEnabled(true);
|
|
}
|
|
_menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
_menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
|
|
_menu->popup(e->globalPos());
|
|
e->accept();
|
|
}
|
|
}
|
|
|
|
void LayerWidget::deleteMessage() {
|
|
if (!App::contextItem()) {
|
|
if (App::self() && photo && App::self()->photoId == photo->id) {
|
|
App::app()->peerClearPhoto(App::self()->id);
|
|
} else if (photo->chat && photo->chat->photoId == photo->id) {
|
|
App::app()->peerClearPhoto(photo->chat->id);
|
|
}
|
|
startHide();
|
|
} else {
|
|
App::wnd()->layerHidden();
|
|
App::main()->deleteLayer();
|
|
}
|
|
}
|
|
|
|
void LayerWidget::forwardMessage() {
|
|
startHide();
|
|
App::main()->forwardLayer();
|
|
}
|
|
|
|
void LayerWidget::saveContextImage() {
|
|
if (!photo || !photo->full->loaded() || hiding) return;
|
|
|
|
QString file;
|
|
if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) {
|
|
if (!file.isEmpty()) {
|
|
photo->full->pix().toImage().save(file, "JPG");
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerWidget::copyContextImage() {
|
|
if (!photo || !photo->full->loaded() || hiding) return;
|
|
|
|
QApplication::clipboard()->setPixmap(photo->full->pix());
|
|
}
|
|
|
|
void LayerWidget::startHide() {
|
|
hiding = true;
|
|
aBackground.start(0);
|
|
anim::start(this);
|
|
}
|
|
|
|
void LayerWidget::resizeEvent(QResizeEvent *e) {
|
|
int32 w = width() - st::layerPadding.left() - st::layerPadding.right(), h = height() - st::layerPadding.top() - st::layerPadding.bottom();
|
|
int32 iw = (photo ? photo->full : video->thumb)->width(), ih = (photo ? photo->full : video->thumb)->height();
|
|
if (!iw || !ih) {
|
|
iw = ih = 1;
|
|
} else {
|
|
switch (cScale()) {
|
|
case dbisOneAndQuarter: iw = qRound(float64(iw) * 1.25 - 0.01); ih = qRound(float64(ih) * 1.25 - 0.01); break;
|
|
case dbisOneAndHalf: iw = qRound(float64(iw) * 1.5 - 0.01); ih = qRound(float64(ih) * 1.5 - 0.01); break;
|
|
case dbisTwo: iw *= 2; ih *= 2; break;
|
|
}
|
|
}
|
|
if (w >= iw && h >= ih) {
|
|
iW.start(iw);
|
|
iX.start(st::layerPadding.left() + (w - iw) / 2);
|
|
iY.start(st::layerPadding.top() + (h - ih) / 2);
|
|
} else if (w * ih > iw * h) {
|
|
int32 nw = qRound(iw * float64(h) / ih);
|
|
iW.start(nw);
|
|
iX.start(st::layerPadding.left() + (w - nw) / 2);
|
|
iY.start(st::layerPadding.top());
|
|
} else {
|
|
int32 nh = qRound(ih * float64(w) / iw);
|
|
iW.start(w);
|
|
iX.start(st::layerPadding.left());
|
|
iY.start(st::layerPadding.top() + (h - nh) / 2);
|
|
}
|
|
if (!animating() || hiding) {
|
|
iX.finish();
|
|
iY.finish();
|
|
iW.finish();
|
|
}
|
|
}
|
|
|
|
bool LayerWidget::animStep(float64 ms) {
|
|
float64 dt = ms / (hiding ? st::layerHideDuration : st::layerSlideDuration);
|
|
bool res = true;
|
|
if (dt >= 1) {
|
|
aBackground.finish();
|
|
aOver.finish();
|
|
iX.finish();
|
|
iY.finish();
|
|
iW.finish();
|
|
if (hiding) {
|
|
QTimer::singleShot(0, App::wnd(), SLOT(layerHidden()));
|
|
}
|
|
res = false;
|
|
} else {
|
|
aBackground.update(dt, aBackgroundFunc);
|
|
if (!hiding) {
|
|
aOver.update(dt, aOverFunc);
|
|
iX.update(dt, iCoordFunc);
|
|
iY.update(dt, iCoordFunc);
|
|
iW.update(dt, iCoordFunc);
|
|
}
|
|
}
|
|
update();
|
|
return res;
|
|
}
|
|
|
|
LayerWidget::~LayerWidget() {
|
|
if (App::wnd()) App::wnd()->noLayer(this);
|
|
delete _menu;
|
|
}
|