diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index bbec08d02..9c234ea0b 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1240,6 +1240,8 @@ msgFileInPlaySelected: sprite(180px, 164px, 20px, 18px); msgFileOverDuration: 200; msgFileRadialLine: 4px; +msgVideoSize: size(320px, 240px); + sendPadding: 9px; btnSend: flatButton(btnDefFlat) { color: btnYesColor; @@ -2186,7 +2188,7 @@ mediaviewLoader: size(78px, 33px); mediaviewLoaderPoint: size(9px, 9px); mediaviewLoaderSkip: 9px; -minPhotoSize: 100px; +minPhotoSize: 104px; maxMediaSize: 420px; maxStickerSize: 256px; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index d54927271..f8cafc0c9 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -74,6 +74,7 @@ namespace { DocumentItems documentItems; WebPageItems webPageItems; SharedContactItems sharedContactItems; + GifItems gifItems; typedef QMap > RepliesTo; RepliesTo repliesTo; @@ -1991,6 +1992,7 @@ namespace App { ::documentItems.clear(); ::webPageItems.clear(); ::sharedContactItems.clear(); + ::gifItems.clear(); ::repliesTo.clear(); lastPhotos.clear(); lastPhotosMap.clear(); @@ -2425,6 +2427,18 @@ namespace App { return ::sharedContactItems; } + void regGifItem(ClipReader *reader, HistoryItem *item) { + ::gifItems.insert(reader, item); + } + + void unregGifItem(ClipReader *reader) { + ::gifItems.remove(reader); + } + + const GifItems &gifItems() { + return ::gifItems; + } + QString phoneFromSharedContact(int32 userId) { SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId); if (i != ::sharedContactItems.cend()) { diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 23ab34bd3..465c2fa7c 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -39,6 +39,7 @@ typedef QMap AudioItems; typedef QMap DocumentItems; typedef QMap WebPageItems; typedef QMap SharedContactItems; +typedef QMap GifItems; struct ReplyMarkup { ReplyMarkup(int32 flags = 0) : flags(flags) { } @@ -250,6 +251,10 @@ namespace App { const SharedContactItems &sharedContactItems(); QString phoneFromSharedContact(int32 userId); + void regGifItem(ClipReader *reader, HistoryItem *item); + void unregGifItem(ClipReader *reader); + const GifItems &gifItems(); + void regMuted(PeerData *peer, int32 changeIn); void unregMuted(PeerData *peer); void updateMuted(); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 8a5457413..3888f20d6 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -83,6 +83,7 @@ enum { LocalEncryptKeySize = 256, // 2048 bit AnimationTimerDelta = 7, + ClipThreadsCount = 8, SaveRecentEmojisTimeout = 3000, // 3 secs SaveWindowPositionTimeout = 1000, // 1 sec @@ -108,12 +109,14 @@ enum { AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers - AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded + AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over - StickerInMemory = 1024 * 1024, // 1024 Kb stickers hold in memory, auto loaded and displayed inline + StickerInMemory = 2 * 1024 * 1024, // 1 Mb stickers hold in memory, auto loaded and displayed inline StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker + AnimationInMemory = 2 * 1024 * 1024, // 2 Mb gif and mp4 animations held in memory while playing + MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index e0d7c2308..2fe92a5fb 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -27,6 +27,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org namespace { AnimationManager *_manager = 0; + QVector _clipThreads; + QVector _clipManagers; }; namespace anim { @@ -79,13 +81,24 @@ namespace anim { } void startManager() { - delete _manager; + stopManager(); + _manager = new AnimationManager(); + } void stopManager() { delete _manager; _manager = 0; + if (!_clipThreads.isEmpty()) { + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + _clipThreads.at(i)->quit(); + _clipThreads.at(i)->wait(); + delete _clipManagers.at(i); + } + _clipThreads.clear(); + _clipManagers.clear(); + } } } @@ -105,6 +118,27 @@ void Animation::stop() { _manager->stop(this); } +void AnimationManager::clipReinit(ClipReader *reader) { + const GifItems &items(App::gifItems()); + GifItems::const_iterator it = items.constFind(reader); + if (it != items.cend()) { + it.value()->initDimensions(); + if (App::main()) emit App::main()->itemResized(it.value(), true); + } +} + +void AnimationManager::clipRedraw(ClipReader *reader) { + if (reader->currentDisplayed()) { + return; + } + + const GifItems &items(App::gifItems()); + GifItems::const_iterator it = items.constFind(reader); + if (it != items.cend()) { + Ui::redrawHistoryItem(it.value()); + } +} + void AnimatedGif::step_frame(float64 ms, bool timer) { int32 f = frame; while (f < images.size() && ms > delays[f]) { @@ -148,6 +182,13 @@ void AnimatedGif::step_frame(float64 ms, bool timer) { if (img.size() != QSize(w, h)) img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); images[f] = img; frames[f] = QPixmap(); + for (int32 i = 0; i < f; ++i) { + if (!images[i].isNull() || !frames[i].isNull()) { + images[i] = QImage(); + frames[i] = QPixmap(); + break; + } + } } } if (frame != f) { @@ -253,3 +294,461 @@ const QPixmap &AnimatedGif::current(int32 width, int32 height, bool rounded) { } return frames[frame]; } + +QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool smooth) { + bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); + bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); + if (badSize || needOuter || request.rounded) { + int32 factor(request.factor); + bool fill = false; + if (cache.width() != request.outerw || cache.height() != request.outerh) { + cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); + if (request.framew < request.outerw || request.frameh < request.outerh || original.hasAlphaChannel()) { + fill = true; + } + cache.setDevicePixelRatio(factor); + } + { + Painter p(&cache); + if (fill) p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black); + if (smooth && badSize) p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor), request.framew / factor, request.frameh / factor); + QRect from(0, 0, original.width() / factor, original.height() / factor); + p.drawImage(to, original, from, Qt::ColorOnly); + } + if (request.rounded) { + imageRound(cache); + } + return QPixmap::fromImage(cache, Qt::ColorOnly); + } + return QPixmap::fromImage(original, Qt::ColorOnly); +} + +ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _state(ClipReading) +, _width(0) +, _height(0) +, _currentDisplayed(1) +, _private(0) { + if (_clipThreads.size() < ClipThreadsCount) { + _threadIndex = _clipThreads.size(); + _clipThreads.push_back(new QThread()); + _clipManagers.push_back(new ClipReadManager(_clipThreads.back())); + _clipThreads.back()->start(); + } else { + _threadIndex = rand() % _clipThreads.size(); + int32 loadLevel = 0x7FFFFFFF; + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + int32 level = _clipManagers.at(i)->loadLevel(); + if (level < loadLevel) { + _threadIndex = i; + loadLevel = level; + } + } + } + _clipManagers.at(_threadIndex)->append(this, location, data); +} + +void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { + int32 factor(cIntRetinaFactor()); + _request.factor = factor; + _request.framew = framew * factor; + _request.frameh = frameh * factor; + _request.outerw = outerw * factor; + _request.outerh = outerh * factor; + _request.rounded = rounded; + _clipManagers.at(_threadIndex)->start(this); +} + +QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh) { + _currentDisplayed.storeRelease(1); + + int32 factor(cIntRetinaFactor()); + QPixmap result(_current); + if (result.width() == outerw * factor && result.height() == outerh * factor) { + return result; + } + + _request.framew = framew * factor; + _request.frameh = frameh * factor; + _request.outerw = outerw * factor; + _request.outerh = outerh * factor; + + QImage current(_currentOriginal); + result = _current = QPixmap(); + result = _current = _prepareFrame(_request, current, _cacheForResize, true); + + _clipManagers.at(_threadIndex)->update(this); + + return result; +} + +bool ClipReader::ready() const { + if (_width && _height) return true; + + QImage first(_currentOriginal); + if (first.isNull()) return false; + + _width = first.width(); + _height = first.height(); + return true; +} + +int32 ClipReader::width() const { + return _width; +} + +int32 ClipReader::height() const { + return _height; +} + +ClipState ClipReader::state() const { + return _state; +} + +void ClipReader::stop() { + _clipManagers.at(_threadIndex)->stop(this); + _width = _height = 0; +} + +void ClipReader::error() { + _private = 0; + _state = ClipError; +} + +ClipReader::~ClipReader() { + stop(); +} + +class ClipReaderPrivate { +public: + + ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) + , _state(ClipReading) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) + , _accessed(false) + , _buffer(_data.isEmpty() ? 0 : &_data) + , _reader(0) + , _currentMs(0) + , _nextUpdateMs(0) { + + if (_data.isEmpty() && !_location->accessEnable()) { + error(); + return; + } + _accessed = true; + } + + ClipProcessResult start(uint64 ms) { + _nextUpdateMs = ms + 86400 * 1000ULL; + if (!_reader && !restartReader(true)) { + return error(); + } + if (_currentOriginal.isNull()) { + if (!readNextFrame(_currentOriginal)) { + return error(); + } + --_framesLeft; + return ClipProcessReinit; + } + return ClipProcessWait; + } + + ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit + if (_state == ClipError) return ClipProcessError; + + if (!_request.valid()) { + return start(ms); + } + + if (_current.isNull()) { // first frame read, but not yet prepared + _currentOriginal.setDevicePixelRatio(_request.factor); + + _currentMs = ms; + _current = _prepareFrame(_request, _currentOriginal, _currentCache, true); + + if (!prepareNextFrame()) { + return error(); + } + return ClipProcessStarted; + } else if (ms >= _nextUpdateMs) { + swapBuffers(); + return ClipProcessRedraw; + } + return ClipProcessWait; + } + + ClipProcessResult finishProcess(uint64 ms) { + if (!prepareNextFrame()) { + return error(); + } + + if (ms >= _nextUpdateMs) { // we are late + swapBuffers(ms); // keep up + return ClipProcessRedraw; + } + return ClipProcessWait; + } + + uint64 nextFrameDelay() { + return qMax(_reader->nextImageDelay(), 5); + } + + void swapBuffers(uint64 ms = 0) { + _currentMs = qMax(ms, _nextUpdateMs); + qSwap(_currentOriginal, _nextOriginal); + qSwap(_current, _next); + qSwap(_currentCache, _nextCache); + } + + bool readNextFrame(QImage &to) { + QImage frame; // QGifHandler always reads first to internal QImage and returns it + if (!_reader->read(&frame)) { + return false; + } + int32 w = frame.width(), h = frame.height(); + if (to.width() == w && to.height() == h && to.format() == frame.format()) { + if (to.byteCount() != frame.byteCount()) { + int bpl = qMin(to.bytesPerLine(), frame.bytesPerLine()); + for (int i = 0; i < h; ++i) { + memcpy(to.scanLine(i), frame.constScanLine(i), bpl); + } + } else { + memcpy(to.bits(), frame.constBits(), frame.byteCount()); + } + } else { + to = frame.copy(); + } + return true; + } + + bool prepareNextFrame() { + _nextUpdateMs = _currentMs + nextFrameDelay(); + if (!_framesLeft) { + if (_reader->jumpToImage(0)) { + _framesLeft = _reader->imageCount(); + } else if (!restartReader()) { + return false; + } + } + if (!readNextFrame(_nextOriginal)) { + return false; + } + _nextOriginal.setDevicePixelRatio(_request.factor); + --_framesLeft; + _next = QPixmap(); + _next = _prepareFrame(_request, _nextOriginal, _nextCache, true); + return true; + } + + bool restartReader(bool first = false) { + if (first && _data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { + QFile f(_location->name()); + if (f.open(QIODevice::ReadOnly)) { + _data = f.readAll(); + if (f.error() == QFile::NoError) { + _buffer.setBuffer(&_data); + } else { + _data = QByteArray(); + } + } + } else if (!_data.isEmpty()) { + _buffer.close(); + } + delete _reader; + + if (_data.isEmpty()) { + _reader = new QImageReader(_location->name()); + } else { + _reader = new QImageReader(&_buffer); + } + if (!_reader->canRead() || !_reader->supportsAnimation()) { + return false; + } + _framesLeft = _reader->imageCount(); + if (_framesLeft < 1) { + return false; + } + return true; + } + + ClipProcessResult error() { + stop(); + _state = ClipError; + return ClipProcessError; + } + + void stop() { + delete _reader; + _reader = 0; + + if (_location) { + if (_accessed) { + _location->accessDisable(); + } + delete _location; + _location = 0; + } + _accessed = false; + } + + ~ClipReaderPrivate() { + stop(); + setBadPointer(_location); + setBadPointer(_reader); + } + +private: + + ClipReader *_interface; + ClipState _state; + + QByteArray _data; + FileLocation *_location; + bool _accessed; + + QBuffer _buffer; + QImageReader *_reader; + + ClipFrameRequest _request; + QPixmap _current, _next; + QImage _currentOriginal, _nextOriginal, _currentCache, _nextCache; + + int32 _framesLeft; + uint64 _currentMs, _nextUpdateMs; + + friend class ClipReadManager; + +}; + +ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0) { + moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + + _timer.setSingleShot(true); + _timer.moveToThread(thread); + connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); + + connect(this, SIGNAL(reinit(ClipReader*)), _manager, SLOT(clipReinit(ClipReader*))); + connect(this, SIGNAL(redraw(ClipReader*)), _manager, SLOT(clipRedraw(ClipReader*))); +} + +void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { + reader->_private = new ClipReaderPrivate(reader, location, data); + update(reader); +} + +void ClipReadManager::start(ClipReader *reader) { + update(reader); +} + +void ClipReadManager::update(ClipReader *reader) { + QMutexLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, reader->_private); + emit processDelayed(); +} + +void ClipReadManager::stop(ClipReader *reader) { + QMutexLocker lock(&_readerPointersMutex); + _readerPointers.remove(reader); + emit processDelayed(); +} + +bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result) { + QMutexLocker lock(&_readerPointersMutex); + ReaderPointers::iterator it = _readerPointers.find(reader->_interface); + if (result == ClipProcessError) { + if (it != _readerPointers.cend()) { + it.key()->error(); + _readerPointers.erase(it); + it = _readerPointers.end(); + } + } + if (it == _readerPointers.cend()) { + return false; + } + + if (result == ClipProcessReinit || result == ClipProcessRedraw || result == ClipProcessStarted) { + it.key()->_current = reader->_current; + it.key()->_currentOriginal = reader->_currentOriginal; + it.key()->_currentDisplayed.storeRelease(0); + if (result == ClipProcessReinit) { + emit reinit(it.key()); + } else if (result == ClipProcessRedraw) { + emit redraw(it.key()); + } + } + return true; +} + +ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { + if (!handleProcessResult(reader, result)) { + delete reader; + return ResultHandleRemove; + } + + _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (_processingInThread->isInterruptionRequested()) { + return ResultHandleStop; + } + + if (result == ClipProcessRedraw) { + return handleResult(reader, reader->finishProcess(ms), ms); + } + + return ResultHandleContinue; +} + +void ClipReadManager::process() { + if (_processingInThread) return; + + _timer.stop(); + _processingInThread = thread(); + + uint64 ms = getms(), minms = ms + 86400 * 1000ULL; + { + QMutexLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator i = _readerPointers.begin(), e = _readerPointers.end(); i != e; ++i) { + if (i.value()) { + Readers::iterator it = _readers.find(i.value()); + if (it == _readers.cend()) { + _readers.insert(i.value(), 0); + } else { + it.value() = ms; + } + i.value()->_request = i.key()->_request; + i.value() = 0; + } + } + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + if (i.value() <= ms) { + ClipProcessResult result = i.key()->process(ms); + + ResultHandleState state = handleResult(i.key(), result, ms); + if (state == ResultHandleRemove) { + i = _readers.erase(i); + continue; + } else if (state == ResultHandleStop) { + _processingInThread = 0; + return; + } + i.value() = i.key()->_nextUpdateMs; + } + if (i.value() < minms) { + minms = i.value(); + } + ++i; + } + + ms = getms(); + if (minms <= ms) { + _timer.start(1); + } else { + _timer.start(minms - ms); + } + + _processingInThread = 0; +} diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index e20191387..d2c9b4e5f 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -335,6 +335,8 @@ AnimationCallbacks *animation(Param param, Type *obj, typename AnimationCallback return new AnimationCallbacksAbsoluteWithParam(param, obj, method); } +class ClipReader; + class AnimationManager : public QObject { Q_OBJECT @@ -403,6 +405,9 @@ public slots: } } + void clipReinit(ClipReader *reader); + void clipRedraw(ClipReader *reader); + private: typedef QMap AnimatingObjects; @@ -470,3 +475,122 @@ private: Animation _a_frames; }; + +enum ClipState { + ClipReading, + ClipError, +}; + +struct ClipFrameRequest { + ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) { + } + bool valid() const { + return factor > 0; + } + int32 factor; + int32 framew, frameh; + int32 outerw, outerh; + bool rounded; +}; + +class ClipReaderPrivate; +class ClipReader { +public: + + ClipReader(const FileLocation &location, const QByteArray &data); + + void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); + QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh); + bool currentDisplayed() const { + return _currentDisplayed.loadAcquire() > 0; + } + + int32 width() const; + int32 height() const; + + ClipState state() const; + bool started() const { + return _request.valid(); + } + bool ready() const; + + void stop(); + void error(); + + ~ClipReader(); + +private: + + ClipState _state; + + ClipFrameRequest _request; + + mutable int32 _width, _height; + + QPixmap _current; + QImage _currentOriginal, _cacheForResize; + QAtomicInt _currentDisplayed; + int32 _threadIndex; + + friend class ClipReadManager; + + ClipReaderPrivate *_private; + +}; + +enum ClipProcessResult { + ClipProcessError, + ClipProcessStarted, + ClipProcessReinit, + ClipProcessRedraw, + ClipProcessWait, +}; + +class ClipReadManager : public QObject { + Q_OBJECT + +public: + + ClipReadManager(QThread *thread); + int32 loadLevel() const { + return _loadLevel.loadAcquire(); + } + void append(ClipReader *reader, const FileLocation &location, const QByteArray &data); + void start(ClipReader *reader); + void update(ClipReader *reader); + void stop(ClipReader *reader); + +signals: + + void processDelayed(); + + void reinit(ClipReader *reader); + void redraw(ClipReader *reader); + +public slots: + + void process(); + +private: + + QAtomicInt _loadLevel; + typedef QMap ReaderPointers; + ReaderPointers _readerPointers; + QMutex _readerPointersMutex; + + bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result); + + enum ResultHandleState { + ResultHandleRemove, + ResultHandleStop, + ResultHandleContinue, + }; + ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); + + typedef QMap Readers; + Readers _readers; + + QTimer _timer; + QThread *_processingInThread; + +}; diff --git a/Telegram/SourceFiles/gui/flatinput.cpp b/Telegram/SourceFiles/gui/flatinput.cpp index dd387c23e..5d0a25ace 100644 --- a/Telegram/SourceFiles/gui/flatinput.cpp +++ b/Telegram/SourceFiles/gui/flatinput.cpp @@ -1300,7 +1300,6 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri resize(_st.width, _st.height); _inner.setWordWrapMode(QTextOption::NoWrap); - _inner.setLineWrapMode(QTextEdit::NoWrap); setAttribute(Qt::WA_OpaquePaintEvent); diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h index 384da3da1..92f7115bc 100644 --- a/Telegram/SourceFiles/gui/images.h +++ b/Telegram/SourceFiles/gui/images.h @@ -65,7 +65,7 @@ public: const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const; const QPixmap &pixColored(const style::color &add, int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurredColored(const style::color &add, int32 w = 0, int32 h = 0) const; - const QPixmap &pixSingle(int32 w, int32 y, int32 outerw, int32 outerh) const; + const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false, bool blurred = false, bool rounded = false, int32 outerw = -1, int32 outerh = -1) const; QPixmap pixColoredNoCache(const style::color &add, int32 w = 0, int32 h = 0, bool smooth = false) const; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 7713a5708..3982977e4 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -3182,7 +3182,7 @@ HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, const QString &caption, Histo , _openl(new PhotoLink(_data)) , _pixw(1) , _pixh(1) -, _caption(st::minPhotoSize) { +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { if (!caption.isEmpty()) { _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } @@ -3193,8 +3193,7 @@ HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) , _data(App::feedPhoto(photo)) , _openl(new PhotoLink(_data, chat)) , _pixw(1) -, _pixh(1) -, _caption(st::minPhotoSize) { +, _pixh(1) { w = width; init(); } @@ -3225,18 +3224,21 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) { if (parent->toHistoryMessage()) { w = tw; + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int32 maxActualWidth = qMax(w, minWidth); + _maxw = qMax(maxActualWidth, th); + _minh = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + maxActualWidth += st::mediaPadding.left() + st::mediaPadding.right(); + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + _minh += st::mediaCaptionSkip + _caption.countHeight(maxActualWidth - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + } } else { th = w; // square chat photo updates - } - _maxw = qMax(qMax(w, int32(st::minPhotoSize)), th); - _minh = qMax(th, int32(st::minPhotoSize)); - if (bubble) { - _maxw += st::mediaPadding.left() + st::mediaPadding.right(); - _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); - if (!_caption.isEmpty()) { - _maxw = qMax(_maxw, st::msgPadding.left() + _caption.maxWidth() + st::msgPadding.right()); - _minh += st::mediaCaptionSkip + _caption.minHeight() + st::msgPadding.bottom(); - } + _maxw = _minh = w; } } @@ -3269,7 +3271,9 @@ int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { } if (_pixw < 1) _pixw = 1; if (_pixh < 1) _pixh = 1; - w = qMax(_pixw, int16(st::minPhotoSize)); + + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + w = qMax(_pixw, int16(minWidth)); _height = qMax(_pixh, int16(st::minPhotoSize)); if (bubble) { _height += st::mediaPadding.top() + st::mediaPadding.bottom(); @@ -3399,7 +3403,7 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b pix = _data->thumb->pixBlurredSingle(_pixw, _pixh, width, height); } - p.drawPixmap(skipx, skipy, pix); + p.drawPixmapLeft(skipx, skipy, w, pix); if (!full) { uint64 dt = itemAnimations().animate(parent, ms); int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); @@ -3422,11 +3426,10 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b } if (selected) { - App::roundRect(p, skipx, skipy, width, height, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + App::roundRect(p, rtlrect(skipx, skipy, width, height, w), textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } // date - QString time(parent->timeText()); if (_caption.isEmpty()) { int32 fullRight = skipx + width, fullBottom = skipy + height; parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); @@ -3481,232 +3484,53 @@ QString formatDurationAndSizeText(qint64 duration, qint64 size) { return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size)); } +QString formatGifAndSizeText(qint64 size) { + return lng_duration_and_size(lt_duration, qsl("GIF"), lt_size, formatSizeText(size)); +} + QString formatPlayedText(qint64 played, qint64 duration) { return lng_duration_played(lt_played, formatDurationText(played), lt_duration, formatDurationText(duration)); } -HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryMedia() -, data(App::feedVideo(video)) -, _openl(new VideoOpenLink(data)) -, _savel(new VideoSaveLink(data)) -, _cancell(new VideoCancelLink(data)) -, _caption(st::minPhotoSize) -, _dldDone(0) -, _uplDone(0) { - if (!caption.isEmpty()) { - _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); +namespace { + QString documentName(DocumentData *document) { + SongData *song = document->song(); + if (!song || (song->title.isEmpty() && song->performer.isEmpty())) return document->name; + + if (song->performer.isEmpty()) return song->title; + + return song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); } - _size = formatDurationAndSizeText(data->duration, data->size); - - data->thumb->load(); - - int32 tw = data->thumb->width(), th = data->thumb->height(); - if (data->thumb->isNull() || !tw || !th) { - _thumbw = 0; - } else if (tw > th) { - _thumbw = (tw * st::mediaThumbSize) / th; - } else { - _thumbw = st::mediaThumbSize; - } -} - -void HistoryVideo::initDimensions(const HistoryItem *parent) { - if (_caption.hasSkipBlock()) _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); - - _maxw = st::msgMaxWidth; - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - - _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (_caption.isEmpty()) { - _height = _minh; - } else { - _minh += st::webPagePhotoSkip + _caption.minHeight(); - } -} - -void HistoryVideo::regItem(HistoryItem *item) { - App::regVideoItem(data, item); -} - -void HistoryVideo::unregItem(HistoryItem *item) { - App::unregVideoItem(data, item); -} - -const QString HistoryVideo::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); -} - -const QString HistoryVideo::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); -} - -bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - int32 height = _height; - if (width < 0) { - width = w; - } else if (!_caption.isEmpty()) { - height = countHeight(parent, width); - } - if (width >= _maxw) { - width = _maxw; - } - return (x >= 0 && y >= 0 && x < width && y < height); -} - -int32 HistoryVideo::countHeight(const HistoryItem *parent, int32 width) const { - if (_caption.isEmpty()) return _height; - - if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; + int32 videoMaxStatusWidth(VideoData *video) { + int32 result = st::normalFont->width(formatDownloadText(video->size, video->size)); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(video->duration, video->size))); + return result; } - int32 h = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (!_caption.isEmpty()) { - int32 textw = width - st::mediaPadding.left() - st::mediaPadding.right(); - h += st::webPagePhotoSkip + _caption.countHeight(textw); - } - return h; -} - -void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - int32 height = _height; - if (width < 0) { - width = w; - } else if (!_caption.isEmpty()) { - height = countHeight(parent, width); - } - if (width < 1) return; - - int skipy = 0, replyFrom = 0, fwdFrom = 0; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; + int32 audioMaxStatusWidth(AudioData *audio) { + int32 result = st::normalFont->width(formatDownloadText(audio->size, audio->size)); + result = qMax(result, st::normalFont->width(formatPlayedText(audio->duration, audio->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(audio->duration, audio->size))); + return result; } - bool inDate = parent->pointInTime(width, height, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - int32 tw = width - st::mediaPadding.left() - st::mediaPadding.right(); - if (x >= st::mediaPadding.left() && y >= skipy + st::mediaPadding.top() && x < st::mediaPadding.left() + tw && y < skipy + st::mediaPadding.top() + st::mediaThumbSize && !data->loader && data->access) { - lnk = _openl; - return; - } - if (!_caption.isEmpty() && x >= st::mediaPadding.left() && x < st::mediaPadding.left() + tw && y >= skipy + st::mediaPadding.top() + st::mediaThumbSize + st::webPagePhotoSkip) { - bool inText = false; - _caption.getState(lnk, inText, x - st::mediaPadding.left(), y - skipy - st::mediaPadding.top() - st::mediaThumbSize - st::webPagePhotoSkip, tw); - state = inDate ? HistoryInDateCursorState : (inText ? HistoryInTextCursorState : HistoryDefaultCursorState); - } -} - -HistoryMedia *HistoryVideo::clone() const { - return new HistoryVideo(*this); -} - -void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { - if (w < st::msgPadding.left() + st::msgPadding.right() + 1) return; - int32 width = w, height = _height, skipx = 0, skipy = 0; - - data->thumb->checkload(); - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; - } - - style::color bg(outbg ? (selected ? st::msgOutBgSelected : st::msgOutBg) : (selected ? st::msgInBgSelected : st::msgInBg)); - style::color sh(outbg ? (selected ? st::msgOutShadowSelected : st::msgOutShadow) : (selected ? st::msgInShadowSelected : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, height, bg, cors, &sh); - - //if (_thumbw) { - // p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); - //} else { - // p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), (outbg ? st::mediaDocOutImg : st::mediaDocInImg)); - //} - if (selected) { - App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); - - p.setFont(st::normalFont->f); - p.setPen(st::black->c); - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::normalFont->ascent, lang(lng_media_video)); - - QString statusText; - - style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); - p.setPen(status->p); - - if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - if (data->status == FileUploadFailed || data->status == FileDownloadFailed) { - statusText = lang(lng_attach_failed); - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; + int32 documentMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + if (SongData *song = document->song()) { + result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); } else { - statusText = _size; + result = qMax(result, st::normalFont->width(formatSizeText(document->size))); } + return result; } - int32 texty = skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::normalFont->height; - p.drawText(tleft, texty + st::normalFont->ascent, statusText); - if (parent->isMediaUnread()) { - int32 w = st::normalFont->width(statusText); - if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= twidth) { - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.setPen(Qt::NoPen); - p.setBrush((outbg ? (selected ? st::mediaOutUnreadFgSelected : st::mediaOutUnreadFg) : (selected ? st::mediaInUnreadFgSelected : st::mediaInUnreadFg))->b); - p.drawEllipse(QRect(tleft + w + st::mediaUnreadSkip, texty + ((st::normalFont->height - st::mediaUnreadSize) / 2), st::mediaUnreadSize, st::mediaUnreadSize)); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } - } - if (!_caption.isEmpty()) { - p.setPen(st::black->p); - _caption.draw(p, st::mediaPadding.left(), skipy + st::mediaPadding.top() + st::mediaThumbSize + st::webPagePhotoSkip, width - st::mediaPadding.left() - st::mediaPadding.right()); - } -} -int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { - w = qMin(width, _maxw); - if (_caption.isEmpty()) return _height; - - _height = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (!_caption.isEmpty()) { - int32 textw = w - st::mediaPadding.left() - st::mediaPadding.right(); - _height += st::webPagePhotoSkip + _caption.countHeight(textw); + int32 gifMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size))); + return result; } - return _height; -} - -ImagePtr HistoryVideo::replyPreview() { - if (data->replyPreview->isNull() && !data->thumb->isNull()) { - if (data->thumb->loaded()) { - int w = data->thumb->width(), h = data->thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - data->replyPreview = ImagePtr(w > h ? data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - data->thumb->load(); - } - } - return data->replyPreview; } HistoryFileMedia::HistoryFileMedia() : HistoryMedia() @@ -3737,9 +3561,9 @@ void HistoryFileMedia::setLinks(ITextLink *openl, ITextLink *savel, ITextLink *c void HistoryFileMedia::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const { _statusSize = newSize; if (_statusSize == FileStatusSizeReady) { - _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : formatSizeText(fullSize); + _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeLoaded) { - _statusText = (duration >= 0) ? formatDurationText(duration) : formatSizeText(fullSize); + _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); } else if (_statusSize == FileStatusSizeFailed) { _statusText = lang(lng_attach_failed); } else if (_statusSize >= 0) { @@ -3793,37 +3617,325 @@ void HistoryFileMedia::checkAnimationFinished() { HistoryFileMedia::~HistoryFileMedia() { if (_animation) { delete _animation; - setBadLink(_animation); + setBadPointer(_animation); } } -namespace { - QString documentName(DocumentData *document) { - SongData *song = document->song(); - if (!song || (song->title.isEmpty() && song->performer.isEmpty())) return document->name; - - if (song->performer.isEmpty()) return song->title; - - return song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); +HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryFileMedia() +, _data(App::feedVideo(video)) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) +, _thumbw(1) { + if (!caption.isEmpty()) { + _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } - int32 documentMaxStatusWidth(DocumentData *document) { - int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); - if (SongData *song = document->song()) { - result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); - } else { - result = qMax(result, st::normalFont->width(formatSizeText(document->size))); + setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); + + setStatusSize(FileStatusSizeReady); + + _data->thumb->load(); +} + +void HistoryVideo::initDimensions(const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } + + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + w = _thumbw = qMax(tw, 1); + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(w, minWidth); + _minh = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + _minh += st::mediaCaptionSkip + _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } - return result; + } +} + +void HistoryVideo::setStatusSize(int32 newSize) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, 0); +} + +void HistoryVideo::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loader) { + statusSize = _data->loader->currentOffset(); + } else if (!_data->already().isEmpty()) { + statusSize = FileStatusSizeLoaded; + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize); + } +} + +void HistoryVideo::regItem(HistoryItem *item) { + App::regVideoItem(_data, item); +} + +void HistoryVideo::unregItem(HistoryItem *item) { + App::unregVideoItem(_data, item); +} + +const QString HistoryVideo::inDialogsText() const { + return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); +} + +const QString HistoryVideo::inHistoryText() const { + return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); +} + +bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { + if (width < 0) width = w; + return (x >= 0 && y >= 0 && x < width && y < _height); +} + +int32 HistoryVideo::countHeight(const HistoryItem *parent, int32 width) const { + if (width < 0) width = w; + if (width >= _maxw) { + width = _maxw; } - int32 audioMaxStatusWidth(AudioData *audio) { - int32 result = st::normalFont->width(formatDownloadText(audio->size, audio->size)); - result = qMax(result, st::normalFont->width(formatPlayedText(audio->duration, audio->duration))); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(audio->duration, audio->size))); - return result; + bool bubble = parent->hasBubble(); + + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + int32 h = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + tw += st::mediaPadding.left() + st::mediaPadding.right(); + h += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + h += st::mediaCaptionSkip + _caption.countHeight(tw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + } + return h; +} + +void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { + bool bubble = parent->hasBubble(); + + if (width < 0) width = w; + if (width < 1) return; + + int skipx = 0, skipy = 0, height = _height; + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + if (!_caption.isEmpty()) { + int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); + if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { + bool inText = false; + _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); + state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + } + height -= st::mediaCaptionSkip; + } + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + } + if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { + lnk = _data->already().isEmpty() ? (_data->loader ? _cancell : _savel) : _openl; + if (_caption.isEmpty()) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + if (inDate) { + state = HistoryInDateCursorState; + } + } + return; + } +} + +HistoryMedia *HistoryVideo::clone() const { + return new HistoryVideo(*this); +} + +void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (w < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 width = w, height = _height, skipx = 0, skipy = 0; + int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + + bool bubble = parent->hasBubble(); + bool fromChannel = parent->fromChannel(), out = parent->out(), outbg = out && !fromChannel; + + if (_data->loader) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + updateStatusText(parent); + bool radial = isRadialAnimation(ms); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + } + } else { + App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + _data->thumb->checkload(); + + QRect rthumb(rtlrect(skipx, skipy, width, height, w)); + + QPixmap pix = _data->thumb->pixBlurredSingle(_thumbw, 0, width, height); + p.drawPixmap(rthumb.topLeft(), pix); + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (_animation && _animation->_a_thumbOver.animating()) { + _animation->_a_thumbOver.step(ms); + float64 over = _animation->a_thumbOver.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.setBrush(st::black); + } else { + bool over = textlnkDrawOver(_data->loader ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (!selected && _animation) { + p.setOpacity(1); + } + + style::sprite icon; + if (!_data->already().isEmpty()) { + icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else if (_data->loader) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, selected ? st::msgInBgSelected : st::msgInBg); + } + + int32 statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = skipy + st::msgDateImgDelta + st::msgDateImgPadding.y(); + int32 statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); + int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, w), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, w, _statusText, statusW - 2 * st::msgDateImgPadding.x()); + + // date + if (_caption.isEmpty()) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); + } else { + p.setPen(st::black); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + } +} + +int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + w = _thumbw = tw; + + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + w = qMax(w, minWidth); + + _height = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + w += st::mediaPadding.left() + st::mediaPadding.right(); + _height += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + int32 captionw = w - st::msgPadding.left() - st::msgPadding.right(); + _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + } + } + return _height; +} + +ImagePtr HistoryVideo::replyPreview() { + if (_data->replyPreview->isNull() && !_data->thumb->isNull()) { + if (_data->thumb->loaded()) { + int w = _data->thumb->width(), h = _data->thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + _data->replyPreview = ImagePtr(w > h ? _data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : _data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + _data->thumb->load(); + } + } + return _data->replyPreview; } HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryFileMedia() @@ -4454,23 +4566,55 @@ ImagePtr HistoryDocument::replyPreview() { return _data->makeReplyPreview(); } -HistoryGif::HistoryGif(DocumentData *document) : HistoryMedia() +HistoryGif::HistoryGif(DocumentData *document) : HistoryFileMedia() , _data(document) -, _openl(new DocumentOpenLink(_data)) -, _savel(new DocumentSaveLink(_data)) -, _cancell(new DocumentCancelLink(_data)) -, _name(documentName(_data)) -, _statusSize(-1) { +, _thumbw(1) +, _thumbh(1) +, _gif(0) { + setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(FileStatusSizeReady); + _data->thumb->load(); } void HistoryGif::initDimensions(const HistoryItem *parent) { - if (parent == animated.msg) { - _maxw = animated.w / cIntRetinaFactor(); - _minh = animated.h / cIntRetinaFactor(); + bool bubble = parent->hasBubble(); + int32 tw = 0, th = 0; + if (_gif && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); } else { - + tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = convertScale(_data->thumb->width()); + th = convertScale(_data->thumb->height()); + } } + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + if (!tw || !th) { + tw = th = 1; + } + _thumbw = tw; + _thumbh = th; + _maxw = qMax(tw, int32(st::minPhotoSize)); + _minh = qMax(th, int32(st::minPhotoSize)); + if (!_gif || !_gif->ready()) { + _maxw = qMax(_maxw, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(_maxw, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + } + if (bubble) { + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); + } + w = _maxw; _height = _minh; } @@ -4478,25 +4622,115 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo if (w < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 width = w, height = _height, skipx = 0, skipy = 0; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - bool already = !_data->already().isEmpty(), hasdata = !_data->data.isEmpty(); - if (parent == animated.msg) { - int32 pw = animated.w / cIntRetinaFactor(), ph = animated.h / cIntRetinaFactor(); - if (width < pw) { - pw = width; - ph = (pw == w) ? _height : (pw * animated.h / animated.w); - if (ph < 1) ph = 1; - } + bool animating = (_gif && _gif->started()); + bool bubble = parent->hasBubble(); + bool fromChannel = parent->fromChannel(), out = parent->out(), outbg = out && !fromChannel; - App::roundShadow(p, 0, 0, pw, ph, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - - p.drawPixmap(0, 0, animated.current(pw * cIntRetinaFactor(), ph * cIntRetinaFactor(), true)); - if (selected) { - App::roundRect(p, 0, 0, pw, ph, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + if (!animating) { + if (_data->loader) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } } - return; + updateStatusText(parent); + } + bool radial = !animating && isRadialAnimation(ms); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + } else { + App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + _data->thumb->checkload(); + + QRect rthumb(rtlrect(skipx, skipy, width, height, w)); + + if (animating) { + p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height)); + } else { + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height)); + } + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } + if (!animating) { + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (_animation && _animation->_a_thumbOver.animating()) { + _animation->_a_thumbOver.step(ms); + float64 over = _animation->a_thumbOver.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.setBrush(st::black); + } else { + bool over = textlnkDrawOver(_data->loader ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (!selected && _animation) { + p.setOpacity(1); + } + + style::sprite icon; + if (!_data->already().isEmpty()) { + icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else if (_data->loader) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, selected ? st::msgInBgSelected : st::msgInBg); + } + + int32 statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = skipy + st::msgDateImgDelta + st::msgDateImgPadding.y(); + int32 statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); + int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, w), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, w, _statusText, statusW - 2 * st::msgDateImgPadding.x()); + + // date + int32 fullRight = skipx + width, fullBottom = skipy + height; + parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); + } +} + +void HistoryGif::setStatusSize(int32 newSize) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); +} + +void HistoryGif::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loader) { + statusSize = _data->loader->currentOffset(); + } else if (!_data->already().isEmpty()) { + statusSize = FileStatusSizeLoaded; + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize); + } } void HistoryGif::regItem(HistoryItem *item) { @@ -4514,40 +4748,69 @@ void HistoryGif::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, b } int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { - w = qMin(width, _maxw); - if (parent == animated.msg) { - if (w > st::maxMediaSize) { - w = st::maxMediaSize; + bool bubble = parent->hasBubble(); + + int32 tw = 0, th = 0; + if (_gif && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); + } else { + tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = convertScale(_data->thumb->width()); + th = convertScale(_data->thumb->height()); } - _height = animated.h / cIntRetinaFactor(); - if (animated.w / cIntRetinaFactor() > w) { - _height = (w * _height / (animated.w / cIntRetinaFactor())); - if (_height <= 0) _height = 1; + } + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + if (!tw || !th) { + tw = th = 1; + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + _thumbw = tw; + _thumbh = th; + + w = qMax(tw, int32(st::minPhotoSize)); + _height = qMax(th, int32(st::minPhotoSize)); + if (_gif && _gif->ready()) { + if (!_gif->started()) { + _gif->start(_thumbw, _thumbh, w, _height, true); } } else { - _height = _minh; + w = qMax(w, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + w = qMax(w, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); } + if (bubble) { + w += st::mediaPadding.left() + st::mediaPadding.right(); + _height += st::mediaPadding.top() + st::mediaPadding.bottom(); + } + return _height; } const QString HistoryGif::inDialogsText() const { - return _name.isEmpty() ? lang(lng_in_dlg_file) : _name; + return _data->name.isEmpty() ? lang(lng_in_dlg_file) : _data->name; } const QString HistoryGif::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_file) + (_data->name.isEmpty() ? QString() : (qsl(" : ") + _data->name)) + qsl(" ]"); } bool HistoryGif::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; - } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - return (x >= 0 && y >= 0 && x < width && y < h); - } return (x >= 0 && y >= 0 && x < width && y < _height); } @@ -4556,29 +4819,75 @@ int32 HistoryGif::countHeight(const HistoryItem *parent, int32 width) const { if (width >= _maxw) { width = _maxw; } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - return h; + + bool bubble = parent->hasBubble(); + + int32 tw = 0, th = 0; + if (_gif && _gif->started()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); + } else { + tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = convertScale(_data->thumb->width()); + th = convertScale(_data->thumb->height()); + } } - return _height; + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + if (!tw || !th) { + tw = th = 1; + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + + int32 h = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + tw += st::mediaPadding.left() + st::mediaPadding.right(); + h += st::mediaPadding.top() + st::mediaPadding.bottom(); + } + return h; } void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { + bool bubble = parent->hasBubble(); + if (width < 0) width = w; if (width < 1) return; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; + int skipx = 0, skipy = 0, height = _height; + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - lnk = (x >= 0 && y >= 0 && x < width && y < h) ? _openl : TextLinkPtr(); + if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { + if (_gif && _gif->started()) { + lnk = _openl; + } else { + lnk = _data->already().isEmpty() ? (_data->loader ? _cancell : _savel) : _openl; + } + + int32 fullRight = skipx + width, fullBottom = skipy + height; + bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + if (inDate) { + state = HistoryInDateCursorState; + } return; } - } HistoryMedia *HistoryGif::clone() const { @@ -4589,6 +4898,25 @@ ImagePtr HistoryGif::replyPreview() { return _data->makeReplyPreview(); } +void HistoryGif::play(HistoryItem *parent) { + if (_gif) { + App::unregGifItem(_gif); + delete _gif; + _gif = 0; + } else { + _gif = new ClipReader(_data->location(), _data->data); + App::regGifItem(_gif, parent); + } +} + +HistoryGif::~HistoryGif() { + if (_gif) { + App::unregGifItem(_gif); + delete _gif; + setBadPointer(_gif); + } +} + HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() , pixw(1), pixh(1), data(document), lastw(0) { @@ -6338,6 +6666,8 @@ void HistoryMessage::initMediaFromText(QString ¤tText) { void HistoryMessage::initMediaFromDocument(DocumentData *doc) { if (doc->sticker()) { _media = new HistorySticker(doc); + } else if (doc->type == AnimatedDocument) { + _media = new HistoryGif(doc); } else { _media = new HistoryDocument(doc); } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index f183c12e8..517014e79 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1015,9 +1015,6 @@ public: virtual int32 timeLeft() const { return 0; } - virtual QString timeText() const { - return QString(); - } virtual int32 timeWidth() const { return 0; } @@ -1303,61 +1300,6 @@ QString formatDurationText(qint64 duration); QString formatDurationAndSizeText(qint64 duration, qint64 size); QString formatPlayedText(qint64 played, qint64 duration); -class HistoryVideo : public HistoryMedia { -public: - - HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; - int32 resize(int32 width, const HistoryItem *parent); - HistoryMediaType type() const { - return MediaTypeVideo; - } - const QString inDialogsText() const; - const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - int32 countHeight(const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - bool uploading() const { - return (data->status == FileUploading); - } - HistoryMedia *clone() const; - - void regItem(HistoryItem *item); - void unregItem(HistoryItem *item); - - bool hasReplyPreview() const { - return !data->thumb->isNull(); - } - ImagePtr replyPreview(); - - bool needsBubble(const HistoryItem *parent) const { - return !_caption.isEmpty() || parent->toHistoryReply(); - } - bool customInfoLayout() const { - return _caption.isEmpty(); - } - bool hideFromName() const { - return true; - } - bool hideForwardedFrom() const { - return true; - } - -private: - VideoData *data; - TextLinkPtr _openl, _savel, _cancell; - - Text _caption; - - QString _size; - int32 _thumbw; - - mutable QString _dldTextCache, _uplTextCache; - mutable int32 _dldDone, _uplDone; -}; - class HistoryFileMedia : public HistoryMedia { public: @@ -1381,6 +1323,7 @@ protected: mutable int32 _statusSize; mutable QString _statusText; + // duration = -1 - no duration, duration = -2 - "GIF" duration void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; void step_thumbOver(const HistoryItem *parent, float64 ms, bool timer); @@ -1414,6 +1357,70 @@ protected: }; +class HistoryVideo : public HistoryFileMedia { +public: + + HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent); + void initDimensions(const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + int32 resize(int32 width, const HistoryItem *parent); + HistoryMediaType type() const { + return MediaTypeVideo; + } + const QString inDialogsText() const; + const QString inHistoryText() const; + bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; + int32 countHeight(const HistoryItem *parent, int32 width = -1) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; + bool uploading() const { + return (_data->status == FileUploading); + } + HistoryMedia *clone() const; + + void regItem(HistoryItem *item); + void unregItem(HistoryItem *item); + + bool hasReplyPreview() const { + return !_data->thumb->isNull(); + } + ImagePtr replyPreview(); + + bool needsBubble(const HistoryItem *parent) const { + return !_caption.isEmpty() || parent->toHistoryReply(); + } + bool customInfoLayout() const { + return _caption.isEmpty(); + } + bool hideFromName() const { + return true; + } + bool hideForwardedFrom() const { + return true; + } + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loader; + } + bool dataLoaded() const { + return !_data->already().isEmpty(); + } + +private: + VideoData *_data; + Text _caption; + int32 _thumbw; + + void setStatusSize(int32 newSize) const; + void updateStatusText(const HistoryItem *parent) const; + +}; + class HistoryAudio : public HistoryFileMedia { public: @@ -1463,6 +1470,7 @@ protected: private: AudioData *_data; + void setStatusSize(int32 newSize, qint64 realDuration = 0) const; bool updateStatusText(const HistoryItem *parent) const; // returns showPause @@ -1549,7 +1557,7 @@ private: }; -class HistoryGif : public HistoryMedia { +class HistoryGif : public HistoryFileMedia { public: HistoryGif(DocumentData *document); @@ -1584,27 +1592,42 @@ public: } ImagePtr replyPreview(); - void drawInPlaylist(Painter &p, const HistoryItem *parent, bool selected, bool over, int32 width) const; - TextLinkPtr linkInPlaylist(); - bool needsBubble(const HistoryItem *parent) const { return parent->toHistoryReply(); } bool customInfoLayout() const { return true; } + bool hideFromName() const { + return true; + } + bool hideForwardedFrom() const { + return true; + } + + void play(HistoryItem *parent); + ~HistoryGif(); + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loader; + } + bool dataLoaded() const { + return !_data->already().isEmpty() && !_data->data.isEmpty(); + } private: DocumentData *_data; - TextLinkPtr _openl, _savel, _cancell; + int32 _thumbw, _thumbh; + ClipReader *_gif; - int32 _namew; - QString _name, _size; - int32 _thumbw, _thumbx, _thumby; - - mutable QString _statusText; - mutable int32 _statusSize; // -1 will contain just size string, -2 will contain "failed" language key + void setStatusSize(int32 newSize) const; + void updateStatusText(const HistoryItem *parent) const; }; @@ -1970,9 +1993,6 @@ public: } return result; } - QString timeText() const { - return _timeText; - } int32 timeWidth() const { return _timeWidth; } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index b95941913..7a637791d 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5366,6 +5366,8 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, const MTPInputFil DocumentData *document = 0; if (HistoryDocument *media = dynamic_cast(item->getMedia())) { document = media->document(); + } else if (HistoryGif *media = dynamic_cast(item->getMedia())) { + document = media->document(); } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { document = media->document(); } @@ -5395,6 +5397,8 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, const MTPInp DocumentData *document = 0; if (HistoryDocument *media = dynamic_cast(item->getMedia())) { document = media->document(); + } else if (HistoryGif *media = dynamic_cast(item->getMedia())) { + document = media->document(); } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { document = media->document(); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 97a9ad020..a67beecb7 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -437,7 +437,6 @@ MainWidget::MainWidget(Window *window) : TWidget(window) connect(&_topBar, SIGNAL(clicked()), this, SLOT(onTopBarClick())); connect(&history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId))); connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); - connect(this, SIGNAL(showPeerAsync(quint64,qint32)), this, SLOT(showPeerHistory(quint64,qint32)), Qt::QueuedConnection); if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); @@ -1886,17 +1885,15 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { } else if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { const FileLocation &location(document->location(true)); if (location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && item) { - startGif(item, location); - } else if (item) { + if (item && item->getMedia() && item->getMedia()->type() == MediaTypeGif) { + static_cast(item->getMedia())->play(item); + } else { + QImageReader reader(location.name()); + if (reader.canRead() && item) { App::wnd()->showDocument(document, item); } else { psOpenFile(already); } - } else { - psOpenFile(already); } location.accessDisable(); } else { diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index ecad9645f..6dda51a59 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -719,6 +719,18 @@ void VideoData::save(const QString &toFile) { loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(videoLoadProgress(mtpFileLoader*))); loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(videoLoadFailed(mtpFileLoader*, bool))); loader->start(); + + notifyLayoutChanged(); +} + +void VideoData::notifyLayoutChanged() const { + const VideoItems &items(App::videoItems()); + VideoItems::const_iterator i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Notify::historyItemLayoutChanged(j.key()); + } + } } QString VideoData::already(bool check) { @@ -874,17 +886,15 @@ void DocumentOpenLink::doOpen(DocumentData *data) { if (App::main()) App::main()->documentPlayProgress(song); } } else if (data->size < MediaViewImageSizeLimit && location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) { - startGif(App::hoveredLinkItem(), location); - } else if (App::hoveredLinkItem() || App::contextItem()) { + if (App::hoveredLinkItem() && App::hoveredLinkItem()->getMedia() && App::hoveredLinkItem()->getMedia()->type() == MediaTypeGif) { + static_cast(App::hoveredLinkItem()->getMedia())->play(App::hoveredLinkItem()); + } else { + QImageReader reader(location.name()); + if (reader.canRead() && (App::hoveredLinkItem() || App::contextItem())) { App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); } else { psOpenFile(location.name()); } - } else { - psOpenFile(location.name()); } location.accessDisable(); } else { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 67c767cf2..f9dc10b82 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -809,6 +809,8 @@ struct VideoData { l->cancel(); l->deleteLater(); l->rpcInvalidate(); + + notifyLayoutChanged(); } _location = FileLocation(); if (!beforeDownload) { @@ -824,11 +826,19 @@ struct VideoData { loader->deleteLater(); loader->rpcInvalidate(); loader = 0; + + notifyLayoutChanged(); } + void notifyLayoutChanged() const; + QString already(bool check = false); const FileLocation &location(bool check = false); + float64 progress() const { + return loader ? loader->currentProgress() : ((status == FileDownloadFailed || _location.name().isEmpty()) ? 0 : 1); + } + VideoId id; uint64 access; int32 date; diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index cf0ede271..010e6d43c 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #pragma once template -void setBadLink(Type *&link) { +void setBadPointer(Type *&link) { link = reinterpret_cast(0x00000bad); }