From d64892584d3d64e83acef5c2a6a9f36470a792a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 Jun 2016 13:37:29 +0300 Subject: [PATCH 01/60] ClipReader was moved to a separate namespace and different files. --- Telegram/SourceFiles/app.cpp | 4 +- Telegram/SourceFiles/app.h | 6 +- Telegram/SourceFiles/history.cpp | 21 +- Telegram/SourceFiles/history.h | 18 +- .../inline_bot_layout_internal.cpp | 18 +- .../inline_bots/inline_bot_layout_internal.h | 6 +- Telegram/SourceFiles/layerwidget.cpp | 18 +- Telegram/SourceFiles/layerwidget.h | 6 +- Telegram/SourceFiles/localimageloader.cpp | 3 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 302 ++++ .../SourceFiles/media/media_clip_ffmpeg.h | 89 ++ .../media/media_clip_implementation.cpp | 43 + .../media/media_clip_implementation.h | 60 + .../SourceFiles/media/media_clip_qtgif.cpp | 106 ++ Telegram/SourceFiles/media/media_clip_qtgif.h | 53 + .../SourceFiles/media/media_clip_reader.cpp | 731 ++++++++++ .../SourceFiles/media/media_clip_reader.h | 222 +++ Telegram/SourceFiles/mediaview.cpp | 109 +- Telegram/SourceFiles/mediaview.h | 4 +- Telegram/SourceFiles/structs.cpp | 13 +- Telegram/SourceFiles/ui/animation.cpp | 1263 +---------------- Telegram/SourceFiles/ui/animation.h | 216 +-- Telegram/Telegram.vcxproj | 33 + Telegram/Telegram.vcxproj.filters | 36 + 24 files changed, 1877 insertions(+), 1503 deletions(-) create mode 100644 Telegram/SourceFiles/media/media_clip_ffmpeg.cpp create mode 100644 Telegram/SourceFiles/media/media_clip_ffmpeg.h create mode 100644 Telegram/SourceFiles/media/media_clip_implementation.cpp create mode 100644 Telegram/SourceFiles/media/media_clip_implementation.h create mode 100644 Telegram/SourceFiles/media/media_clip_qtgif.cpp create mode 100644 Telegram/SourceFiles/media/media_clip_qtgif.h create mode 100644 Telegram/SourceFiles/media/media_clip_reader.cpp create mode 100644 Telegram/SourceFiles/media/media_clip_reader.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 256421f30..9d4e55165 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2462,11 +2462,11 @@ namespace { return ::sharedContactItems; } - void regGifItem(ClipReader *reader, HistoryItem *item) { + void regGifItem(Media::Clip::Reader *reader, HistoryItem *item) { ::gifItems.insert(reader, item); } - void unregGifItem(ClipReader *reader) { + void unregGifItem(Media::Clip::Reader *reader) { ::gifItems.remove(reader); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index d2e06ce10..1c80ba848 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -39,7 +39,7 @@ typedef QHash PhotoItems; typedef QHash DocumentItems; typedef QHash WebPageItems; typedef QHash SharedContactItems; -typedef QHash GifItems; +typedef QHash GifItems; typedef QHash PhotosData; typedef QHash DocumentsData; @@ -257,8 +257,8 @@ namespace App { const SharedContactItems &sharedContactItems(); QString phoneFromSharedContact(int32 userId); - void regGifItem(ClipReader *reader, HistoryItem *item); - void unregGifItem(ClipReader *reader); + void regGifItem(Media::Clip::Reader *reader, HistoryItem *item); + void unregGifItem(Media::Clip::Reader *reader); void stopGifItems(); void regMuted(PeerData *peer, int32 changeIn); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index ae585def9..ded75552a 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "history/history_service_layout.h" #include "data/data_drafts.h" +#include "media/media_clip_reader.h" #include "lang.h" #include "mainwidget.h" #include "application.h" @@ -2906,15 +2907,17 @@ void HistoryItem::setUnreadBarFreezed() { } } -void HistoryItem::clipCallback(ClipReaderNotification notification) { +void HistoryItem::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + HistoryMedia *media = getMedia(); if (!media) return; - ClipReader *reader = media ? media->getClipReader() : 0; + Reader *reader = media ? media->getClipReader() : 0; if (!reader) return; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { bool stopped = false; if (reader->paused()) { if (MainWidget *m = App::main()) { @@ -2933,7 +2936,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) { } } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (!reader->currentDisplayed()) { Ui::repaintHistoryItem(this); } @@ -4526,13 +4529,13 @@ void HistoryGif::initDimensions() { bool bubble = _parent->hasBubble(); int32 tw = 0, th = 0; - if (gif() && _gif->state() == ClipError) { + if (gif() && _gif->state() == Media::Clip::State::Error) { if (!_gif->autoplay()) { Ui::showLayer(new InformBox(lang(lng_gif_error))); } App::unregGifItem(_gif); delete _gif; - _gif = BadClipReader; + _gif = Media::Clip::BadReader; } if (gif() && _gif->ready()) { @@ -4637,7 +4640,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading(); bool selected = (selection == FullSelection); - if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { + if (loaded && !gif() && _gif != Media::Clip::BadReader && cAutoPlayGif()) { Ui::autoplayMediaInlineAsync(_parent->fullId()); } @@ -4684,7 +4687,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) { + if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) { float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); @@ -4848,7 +4851,7 @@ bool HistoryGif::playInline(bool autoplay) { if (!cAutoPlayGif()) { App::stopGifItems(); } - _gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback)); + _gif = new Media::Clip::Reader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback)); App::regGifItem(_gif, _parent); if (gif()) _gif->setAutoplay(); } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index a38257e02..8f867bb09 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1412,7 +1412,7 @@ public: return _text.isEmpty() && !_media; } - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); virtual ~HistoryItem(); @@ -1656,7 +1656,7 @@ public: virtual DocumentData *getDocument() { return nullptr; } - virtual ClipReader *getClipReader() { + virtual Media::Clip::Reader *getClipReader() { return nullptr; } @@ -2140,7 +2140,7 @@ public: DocumentData *getDocument() override { return _data; } - ClipReader *getClipReader() override { + Media::Clip::Reader *getClipReader() override { return gif(); } @@ -2189,12 +2189,12 @@ private: int32 _thumbw, _thumbh; Text _caption; - ClipReader *_gif; - ClipReader *gif() { - return (_gif == BadClipReader) ? nullptr : _gif; + Media::Clip::Reader *_gif; + Media::Clip::Reader *gif() { + return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } - const ClipReader *gif() const { - return (_gif == BadClipReader) ? nullptr : _gif; + const Media::Clip::Reader *gif() const { + return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } void setStatusSize(int32 newSize) const; @@ -2377,7 +2377,7 @@ public: DocumentData *getDocument() override { return _attach ? _attach->getDocument() : 0; } - ClipReader *getClipReader() override { + Media::Clip::Reader *getClipReader() override { return _attach ? _attach->getClipReader() : 0; } bool playInline(bool autoplay) override { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index c3fa01dd6..5b9f7d89b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "inline_bots/inline_bot_result.h" +#include "media/media_clip_reader.h" #include "localstorage.h" #include "mainwidget.h" #include "lang.h" @@ -131,9 +132,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons document->automaticLoad(nullptr); bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); - if (loaded && !gif() && _gif != BadClipReader) { + if (loaded && !gif() && _gif != Media::Clip::BadReader) { Gif *that = const_cast(this); - that->_gif = new ClipReader(document->location(), document->data(), func(that, &Gif::clipCallback)); + that->_gif = new Media::Clip::Reader(document->location(), document->data(), func(that, &Gif::clipCallback)); if (gif()) _gif->setAutoplay(); } @@ -162,7 +163,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons } } - if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) { + if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) { float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1; if (_animation && _animation->_a_over.animating(context->ms)) { float64 over = _animation->_a_over.current(); @@ -326,13 +327,14 @@ void Gif::step_radial(uint64 ms, bool timer) { } } -void Gif::clipCallback(ClipReaderNotification notification) { +void Gif::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { if (gif()) { - if (_gif->state() == ClipError) { + if (_gif->state() == State::Error) { delete _gif; - _gif = BadClipReader; + _gif = BadReader; getShownDocument()->forget(); } else if (_gif->ready() && !_gif->started()) { int32 height = st::inlineMediaHeight; @@ -348,7 +350,7 @@ void Gif::clipCallback(ClipReaderNotification notification) { update(); } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (gif() && !_gif->currentDisplayed()) { update(); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 7095f8828..85a8ccc08 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -92,10 +92,10 @@ private: return ~StateFlags(flag); } - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; ClickHandlerPtr _delete; bool gif() const { - return (!_gif || _gif == BadClipReader) ? false : true; + return (!_gif || _gif == Media::Clip::BadReader) ? false : true; } mutable QPixmap _thumb; void prepareThumb(int32 width, int32 height, const QSize &frame) const; @@ -104,7 +104,7 @@ private: bool isRadialAnimation(uint64 ms) const; void step_radial(uint64 ms, bool timer); - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); struct AnimationData { AnimationData(AnimationCallbacks &&callbacks) diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 54d061526..08f840da6 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "lang.h" +#include "media/media_clip_reader.h" #include "layerwidget.h" #include "application.h" #include "mainwindow.h" @@ -346,9 +347,9 @@ QPixmap MediaPreviewWidget::currentImage() const { } else { _document->automaticLoad(nullptr); if (_document->loaded()) { - if (!_gif && _gif != BadClipReader) { - MediaPreviewWidget *that = const_cast(this); - that->_gif = new ClipReader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback)); + if (!_gif && _gif != Media::Clip::BadReader) { + auto that = const_cast(this); + that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback)); if (gif()) _gif->setAutoplay(); } } @@ -385,12 +386,13 @@ QPixmap MediaPreviewWidget::currentImage() const { return _cache; } -void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) { +void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; switch (notification) { - case ClipReaderReinit: { - if (gif() && _gif->state() == ClipError) { + case NotificationReinit: { + if (gif() && _gif->state() == State::Error) { delete _gif; - _gif = BadClipReader; + _gif = BadReader; } if (gif() && _gif->ready() && !_gif->started()) { @@ -401,7 +403,7 @@ void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) { update(); } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (gif() && !_gif->currentDisplayed()) { update(); } diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h index e1d408f1e..c5ba79eb7 100644 --- a/Telegram/SourceFiles/layerwidget.h +++ b/Telegram/SourceFiles/layerwidget.h @@ -135,12 +135,12 @@ private: Animation _a_shown; DocumentData *_document = nullptr; PhotoData *_photo = nullptr; - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; bool gif() const { - return (!_gif || _gif == BadClipReader) ? false : true; + return (!_gif || _gif == Media::Clip::BadReader) ? false : true; } - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); enum CacheStatus { CacheNotLoaded, diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index f484ccb9b..cec36b9f4 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "audio.h" #include "boxes/photosendbox.h" +#include "media/media_clip_reader.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang.h" @@ -330,7 +331,7 @@ void FileLoadTask::process() { } if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) { QImage cover; - MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover); + MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover); if (animatedAttribute.type() == mtpc_documentAttributeVideo) { int32 cw = cover.width(), ch = cover.height(); if (cw < 20 * ch && ch < 20 * cw) { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp new file mode 100644 index 000000000..d10568f70 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -0,0 +1,302 @@ +/* +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 "media/media_clip_ffmpeg.h" + +namespace Media { +namespace Clip { +namespace internal { + +FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { + _frame = av_frame_alloc(); + av_init_packet(&_avpkt); + _avpkt.data = NULL; + _avpkt.size = 0; +} + +bool FFMpegReaderImplementation::readNextFrame() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + + int res; + while (true) { + if (_avpkt.size > 0) { // previous packet not finished + res = 0; + } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { + if (res != AVERROR_EOF || !_hadFrame) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + + bool finished = (res < 0); + if (finished) { + _avpkt.data = NULL; + _avpkt.size = 0; + } else { + rememberPacket(); + } + + int32 got_frame = 0; + int32 decoded = _avpkt.size; + if (_avpkt.stream_index == _streamId) { + if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + if (res == AVERROR_INVALIDDATA) { // try to skip bad packet + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + + if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file + return false; + } + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + if (res > 0) decoded = res; + } else if (_audioStreamId >= 0 && _avpkt.stream_index == _audioStreamId) { + freePacket(); + continue; + } + if (!finished) { + _avpkt.data += decoded; + _avpkt.size -= decoded; + if (_avpkt.size <= 0) freePacket(); + } + + if (got_frame) { + int64 duration = av_frame_get_pkt_duration(_frame); + int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; + int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + _currentFrameDelay = _nextFrameDelay; + if (_frameMs + _currentFrameDelay < frameMs) { + _currentFrameDelay = int32(frameMs - _frameMs); + } + if (duration == AV_NOPTS_VALUE) { + _nextFrameDelay = 0; + } else { + _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + _frameMs = frameMs; + + _hadFrame = _frameRead = true; + return true; + } + + if (finished) { + if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + } + } + avcodec_flush_buffers(_codecContext); + _hadFrame = false; + _frameMs = 0; + } + } + + return false; +} + +bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(_frameRead); + _frameRead = false; + + if (!_width || !_height) { + _width = _frame->width; + _height = _frame->height; + if (!_width || !_height) { + LOG(("Gif Error: Bad frame size %1").arg(logData())); + return false; + } + } + + QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); + if (to.isNull() || to.size() != toSize) { + to = QImage(toSize, QImage::Format_ARGB32); + } + hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); + if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { + int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); + uchar *s = _frame->data[0], *d = to.bits(); + for (int32 i = 0, l = _frame->height; i < l; ++i) { + memcpy(d + i * dbpl, s + i * sbpl, bpl); + } + } else { + if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { + _swsSize = toSize; + _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); + } + uint8_t * toData[1] = { to.bits() }; + int toLinesize[1] = { to.bytesPerLine() }, res; + if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { + LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); + return false; + } + } + + av_frame_unref(_frame); + return true; +} + +int FFMpegReaderImplementation::nextFrameDelay() { + return _currentFrameDelay; +} + +bool FFMpegReaderImplementation::start(bool onlyGifv) { + initDevice(); + if (!_device->open(QIODevice::ReadOnly)) { + LOG(("Gif Error: Unable to open device %1").arg(logData())); + return false; + } + _ioBuffer = (uchar*)av_malloc(AVBlockSize); + _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); + _fmtContext = avformat_alloc_context(); + if (!_fmtContext) { + LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); + return false; + } + _fmtContext->pb = _ioContext; + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { + _ioBuffer = 0; + + LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { + LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); + if (_streamId < 0) { + LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); + return false; + } + + // Get a pointer to the codec context for the audio stream + _codecContext = _fmtContext->streams[_streamId]->codec; + _codec = avcodec_find_decoder(_codecContext->codec_id); + + _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); + if (onlyGifv) { + if (_audioStreamId >= 0) { // should be no audio stream + return false; + } + if (dataSize() > AnimationInMemory) { + return false; + } + if (_codecContext->codec_id != AV_CODEC_ID_H264) { + return false; + } + } + av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + return true; +} + +QString FFMpegReaderImplementation::logData() const { + return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); +} + +int FFMpegReaderImplementation::duration() const { + if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; + return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; +} + +FFMpegReaderImplementation::~FFMpegReaderImplementation() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + if (_ioContext) av_free(_ioContext); + if (_codecContext) avcodec_close(_codecContext); + if (_swsContext) sws_freeContext(_swsContext); + if (_opened) { + avformat_close_input(&_fmtContext); + } else if (_ioBuffer) { + av_free(_ioBuffer); + } + if (_fmtContext) avformat_free_context(_fmtContext); + av_frame_free(&_frame); + freePacket(); +} + +void FFMpegReaderImplementation::rememberPacket() { + if (!_packetWas) { + _packetSize = _avpkt.size; + _packetData = _avpkt.data; + _packetWas = true; + } +} + +void FFMpegReaderImplementation::freePacket() { + if (_packetWas) { + _avpkt.size = _packetSize; + _avpkt.data = _packetData; + _packetWas = false; + av_packet_unref(&_avpkt); + } +} + +int FFMpegReaderImplementation::_read(void *opaque, uint8_t *buf, int buf_size) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + return int(l->_device->read((char*)(buf), buf_size)); +} + +int64_t FFMpegReaderImplementation::_seek(void *opaque, int64_t offset, int whence) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; + case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; + case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; + } + return -1; +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h new file mode 100644 index 000000000..3850ebf49 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -0,0 +1,89 @@ +/* +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 + +extern "C" { +#include +#include +#include +#include +} + +#include "media/media_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +class FFMpegReaderImplementation : public ReaderImplementation { +public: + + FFMpegReaderImplementation(FileLocation *location, QByteArray *data); + + bool readNextFrame() override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int nextFrameDelay() override; + bool start(bool onlyGifv) override; + + int duration() const; + QString logData() const; + + ~FFMpegReaderImplementation(); + +private: + void rememberPacket(); + void freePacket(); + + static int _read(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek(void *opaque, int64_t offset, int whence); + + uchar *_ioBuffer = nullptr; + AVIOContext *_ioContext = nullptr; + AVFormatContext *_fmtContext = nullptr; + AVCodec *_codec = nullptr; + AVCodecContext *_codecContext = nullptr; + int _streamId = 0; + AVFrame *_frame = nullptr; + bool _opened = false; + bool _hadFrame = false; + bool _frameRead = false; + + int _audioStreamId = 0; + + AVPacket _avpkt; + int _packetSize = 0; + uint8_t *_packetData = nullptr; + bool _packetWas = false; + + int _width = 0; + int _height = 0; + SwsContext *_swsContext = nullptr; + QSize _swsSize; + + int64 _frameMs = 0; + int _nextFrameDelay = 0; + int _currentFrameDelay = 0; + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_implementation.cpp b/Telegram/SourceFiles/media/media_clip_implementation.cpp new file mode 100644 index 000000000..9526074cb --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_implementation.cpp @@ -0,0 +1,43 @@ +/* +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 "media/media_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +void ReaderImplementation::initDevice() { + if (_data->isEmpty()) { + if (_file.isOpen()) _file.close(); + _file.setFileName(_location->name()); + _dataSize = _file.size(); + } else { + if (_buffer.isOpen()) _buffer.close(); + _buffer.setBuffer(_data); + _dataSize = _data->size(); + } + _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h new file mode 100644 index 000000000..30f34c4bc --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -0,0 +1,60 @@ +/* +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 + +class FileLocation; + +namespace Media { +namespace Clip { +namespace internal { + +class ReaderImplementation { +public: + + ReaderImplementation(FileLocation *location, QByteArray *data) + : _location(location) + , _data(data) { + } + virtual bool readNextFrame() = 0; + virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; + virtual int nextFrameDelay() = 0; + virtual bool start(bool onlyGifv) = 0; + virtual ~ReaderImplementation() { + } + int64 dataSize() const { + return _dataSize; + } + +protected: + FileLocation *_location; + QByteArray *_data; + QFile _file; + QBuffer _buffer; + QIODevice *_device = nullptr; + int64 _dataSize = 0; + + void initDevice(); + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp new file mode 100644 index 000000000..2ec76997c --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -0,0 +1,106 @@ +/* +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 "media/media_clip_qtgif.h" + +namespace Media { +namespace Clip { +namespace internal { + +QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { +} + +bool QtGifReaderImplementation::readNextFrame() { + if (_reader) _frameDelay = _reader->nextImageDelay(); + if (_framesLeft < 1 && !jumpToStart()) { + return false; + } + + _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it + if (!_reader->read(&_frame) || _frame.isNull()) { + return false; + } + --_framesLeft; + return true; +} + +bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(!_frame.isNull()); + if (size.isEmpty() || size == _frame.size()) { + 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(); + } + } else { + to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + hasAlpha = _frame.hasAlphaChannel(); + _frame = QImage(); + return true; +} + +int QtGifReaderImplementation::nextFrameDelay() { + return _frameDelay; +} + +bool QtGifReaderImplementation::start(bool onlyGifv) { + if (onlyGifv) return false; + return jumpToStart(); +} + +QtGifReaderImplementation::~QtGifReaderImplementation() { + deleteAndMark(_reader); +} + +bool QtGifReaderImplementation::jumpToStart() { + if (_reader && _reader->jumpToImage(0)) { + _framesLeft = _reader->imageCount(); + return true; + } + + delete _reader; + initDevice(); + _reader = new QImageReader(_device); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + _reader->setAutoTransform(true); +#endif + if (!_reader->canRead() || !_reader->supportsAnimation()) { + return false; + } + _framesLeft = _reader->imageCount(); + if (_framesLeft < 1) { + return false; + } + return true; +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h new file mode 100644 index 000000000..5e8efa063 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -0,0 +1,53 @@ +/* +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 "media/media_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +class QtGifReaderImplementation : public ReaderImplementation { +public: + + QtGifReaderImplementation(FileLocation *location, QByteArray *data); + + bool readNextFrame() override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int nextFrameDelay() override; + bool start(bool onlyGifv) override; + + ~QtGifReaderImplementation(); + +private: + bool jumpToStart(); + + QImageReader *_reader = nullptr; + int _framesLeft = 0; + int _frameDelay = 0; + QImage _frame; + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp new file mode 100644 index 000000000..ca96eb418 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -0,0 +1,731 @@ +/* +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 "media/media_clip_reader.h" + +extern "C" { +#include +#include +#include +#include +} + +#include "media/media_clip_ffmpeg.h" +#include "media/media_clip_qtgif.h" +#include "mainwidget.h" +#include "mainwindow.h" + +namespace Media { +namespace Clip { +namespace { + +QVector threads; +QVector managers; + +QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { + bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); + bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); + if (badSize || needOuter || hasAlpha || request.rounded) { + int32 factor(request.factor); + bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); + if (newcache) { + cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(factor); + } + { + Painter p(&cache); + if (newcache) { + if (request.framew < request.outerw) { + p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); + p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); + } + if (request.frameh < request.outerh) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); + } + } + if (hasAlpha) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); + } + QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); + if (badSize) { + p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to(position, QSize(request.framew / factor, request.frameh / factor)); + QRect from(0, 0, original.width(), original.height()); + p.drawImage(to, original, from, Qt::ColorOnly); + } else { + p.drawImage(position, original); + } + } + if (request.rounded) { + imageRound(cache); + } + return QPixmap::fromImage(cache, Qt::ColorOnly); + } + return QPixmap::fromImage(original, Qt::ColorOnly); +} + +} // namespace + +Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode) +: _callback(std_::move(callback)) +, _mode(mode) { + if (threads.size() < ClipThreadsCount) { + _threadIndex = threads.size(); + threads.push_back(new QThread()); + managers.push_back(new Manager(threads.back())); + threads.back()->start(); + } else { + _threadIndex = int32(rand_value() % threads.size()); + int32 loadLevel = 0x7FFFFFFF; + for (int32 i = 0, l = threads.size(); i < l; ++i) { + int32 level = managers.at(i)->loadLevel(); + if (level < loadLevel) { + _threadIndex = i; + loadLevel = level; + } + } + } + managers.at(_threadIndex)->append(this, location, data); +} + +Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForRequestStep) { + i = 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = (step / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + i = 0; + } else if (step == WaitingForRequestStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = ((step + 2) / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) const { + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { + if (index) *index = 0; + return 0; + } + i = ((step + 4) / 2) % 3; + if (index) *index = i; + return _frames + i; +} + +void Reader::moveToNextShow() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + } else if (step == WaitingForRequestStep) { + _step.storeRelease(WaitingForFirstFrameStep); + } else if (step == WaitingForFirstFrameStep) { + } else if (!(step % 2)) { + _step.storeRelease(step + 1); + } +} + +void Reader::moveToNextWrite() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + _step.storeRelease(WaitingForRequestStep); + } else if (step == WaitingForRequestStep) { + } else if (step == WaitingForFirstFrameStep) { + _step.storeRelease(0); + } else if (step % 2) { + _step.storeRelease((step + 1) % 6); + } +} + +void Reader::callback(Reader *reader, int32 threadIndex, Notification notification) { + // check if reader is not deleted already + if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader)) { + reader->_callback.call(notification); + } +} + +void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { + if (managers.size() <= _threadIndex) error(); + if (_state == State::Error) return; + + if (_step.loadAcquire() == WaitingForRequestStep) { + int factor = cIntRetinaFactor(); + FrameRequest request; + request.factor = factor; + request.framew = framew * factor; + request.frameh = frameh * factor; + request.outerw = outerw * factor; + request.outerh = outerh * factor; + request.rounded = rounded; + _frames[0].request = _frames[1].request = _frames[2].request = request; + moveToNextShow(); + managers.at(_threadIndex)->start(this); + } +} + +QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { + Frame *frame = frameToShow(); + t_assert(frame != 0); + + if (ms) { + frame->displayed.storeRelease(1); + if (_paused.loadAcquire()) { + _paused.storeRelease(0); + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->update(this); + } + } + } else { + frame->displayed.storeRelease(-1); // displayed, but should be paused + } + + int32 factor(cIntRetinaFactor()); + if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { + moveToNextShow(); + return frame->pix; + } + + frame->request.framew = framew * factor; + frame->request.frameh = frameh * factor; + frame->request.outerw = outerw * factor; + frame->request.outerh = outerh * factor; + + QImage cacheForResize; + frame->original.setDevicePixelRatio(factor); + frame->pix = QPixmap(); + frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); + + Frame *other = frameToWriteNext(true); + if (other) other->request = frame->request; + + moveToNextShow(); + + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->update(this); + } + + return frame->pix; +} + +bool Reader::ready() const { + if (_width && _height) return true; + + Frame *frame = frameToShow(); + if (frame) { + _width = frame->original.width(); + _height = frame->original.height(); + return true; + } + return false; +} + +int32 Reader::width() const { + return _width; +} + +int32 Reader::height() const { + return _height; +} + +State Reader::state() const { + return _state; +} + +void Reader::stop() { + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->stop(this); + _width = _height = 0; + } +} + +void Reader::error() { + _state = State::Error; +} + +Reader::~Reader() { + stop(); +} + +class ReaderPrivate { +public: + + ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) { + if (_data.isEmpty() && !_location->accessEnable()) { + error(); + return; + } + _accessed = true; + } + + ProcessResult start(uint64 ms) { + if (!_implementation && !init()) { + return error(); + } + if (frame() && frame()->original.isNull()) { + if (!_implementation->readNextFrame()) { + return error(); + } + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { + return error(); + } + _width = frame()->original.width(); + _height = frame()->original.height(); + return ProcessResult::Started; + } + return ProcessResult::Wait; + } + + ProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit + if (_state == State::Error) return ProcessResult::Error; + + if (!_request.valid()) { + return start(ms); + } + + if (!_paused && ms >= _nextFrameWhen) { + return ProcessResult::Repaint; + } + return ProcessResult::Wait; + } + + ProcessResult finishProcess(uint64 ms) { + if (!readNextFrame()) { + return error(); + } + if (ms >= _nextFrameWhen && !readNextFrame(true)) { + return error(); + } + if (!renderFrame()) { + return error(); + } + return ProcessResult::CopyFrame; + } + + uint64 nextFrameDelay() { + int32 delay = _implementation->nextFrameDelay(); + return qMax(delay, 5); + } + + bool readNextFrame(bool keepup = false) { + if (!_implementation->readNextFrame()) { + return false; + } + _nextFrameWhen += nextFrameDelay(); + if (keepup) { + _nextFrameWhen = qMax(_nextFrameWhen, getms()); + } + return true; + } + + bool renderFrame() { + t_assert(frame() != 0 && _request.valid()); + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { + return false; + } + frame()->original.setDevicePixelRatio(_request.factor); + frame()->pix = QPixmap(); + frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); + frame()->when = _nextFrameWhen; + return true; + } + + bool init() { + if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { + QFile f(_location->name()); + if (f.open(QIODevice::ReadOnly)) { + _data = f.readAll(); + if (f.error() != QFile::NoError) { + _data = QByteArray(); + } + } + } + + _implementation = new internal::FFMpegReaderImplementation(_location, &_data); +// _implementation = new QtGifReaderImplementation(_location, &_data); + return _implementation->start(false); + } + + ProcessResult error() { + stop(); + _state = State::Error; + return ProcessResult::Error; + } + + void stop() { + delete _implementation; + _implementation = 0; + + if (_location) { + if (_accessed) { + _location->accessDisable(); + } + delete _location; + _location = 0; + } + _accessed = false; + } + + ~ReaderPrivate() { + stop(); + deleteAndMark(_location); + deleteAndMark(_implementation); + _data.clear(); + } + +private: + + Reader *_interface; + State _state = State::Reading; + + QByteArray _data; + FileLocation *_location; + bool _accessed = false; + + QBuffer _buffer; + internal::ReaderImplementation *_implementation = nullptr; + + FrameRequest _request; + struct Frame { + QPixmap pix; + QImage original, cache; + bool alpha = true; + uint64 when = 0; + }; + Frame _frames[3]; + int _frame = 0; + Frame *frame() { + return _frames + _frame; + } + + int _width = 0; + int _height = 0; + + uint64 _nextFrameWhen = 0; + + bool _paused = false; + + friend class Manager; + +}; + +Manager::Manager(QThread *thread) : _processingInThread(0), _needReProcess(false) { + moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(thread, SIGNAL(finished()), this, SLOT(finish())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + + _timer.setSingleShot(true); + _timer.moveToThread(thread); + connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); + + anim::registerClipManager(this); +} + +void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) { + reader->_private = new ReaderPrivate(reader, location, data); + _loadLevel.fetchAndAddRelaxed(AverageGifSize); + update(reader); +} + +void Manager::start(Reader *reader) { + update(reader); +} + +void Manager::update(Reader *reader) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator i = _readerPointers.constFind(reader); + if (i == _readerPointers.cend()) { + lock.unlock(); + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, MutableAtomicInt(1)); + } else { + i->v.storeRelease(1); + } + emit processDelayed(); +} + +void Manager::stop(Reader *reader) { + if (!carries(reader)) return; + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.remove(reader); + emit processDelayed(); +} + +bool Manager::carries(Reader *reader) const { + QReadLocker lock(&_readerPointersMutex); + return _readerPointers.contains(reader); +} + +Manager::ReaderPointers::iterator Manager::unsafeFindReaderPointer(ReaderPrivate *reader) { + ReaderPointers::iterator it = _readerPointers.find(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); +} + +Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(ReaderPrivate *reader) const { + ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); +} + +bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (result == ProcessResult::Error) { + if (it != _readerPointers.cend()) { + it.key()->error(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + + lock.unlock(); + QWriteLocker lock(&_readerPointersMutex); + ReaderPointers::iterator i = unsafeFindReaderPointer(reader); + if (i != _readerPointers.cend()) _readerPointers.erase(i); + } + return false; + } + if (it == _readerPointers.cend()) { + return false; + } + + if (result == ProcessResult::Started) { + _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); + } + if (!reader->_paused && result == ProcessResult::Repaint) { + int32 ishowing, iprevious; + Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); + t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); + if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown + if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { + reader->_paused = true; + it.key()->_paused.storeRelease(1); + result = ProcessResult::Paused; + } + } + } + if (result == ProcessResult::Started || result == ProcessResult::CopyFrame) { + t_assert(reader->_frame >= 0); + Reader::Frame *frame = it.key()->_frames + reader->_frame; + frame->clear(); + frame->pix = reader->frame()->pix; + frame->original = reader->frame()->original; + frame->displayed.storeRelease(0); + if (result == ProcessResult::Started) { + reader->_nextFrameWhen = ms; + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + } + } else if (result == ProcessResult::Paused) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + } else if (result == ProcessResult::Repaint) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint); + } + return true; +} + +Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) { + if (!handleProcessResult(reader, result, ms)) { + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + delete reader; + return ResultHandleRemove; + } + + _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (_processingInThread->isInterruptionRequested()) { + return ResultHandleStop; + } + + if (result == ProcessResult::Repaint) { + { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (it != _readerPointers.cend()) { + int32 index = 0; + Reader *r = it.key(); + Reader::Frame *frame = it.key()->frameToWrite(&index); + if (frame) { + frame->clear(); + } else { + t_assert(!reader->_request.valid()); + } + reader->_frame = index; + } + } + return handleResult(reader, reader->finishProcess(ms), ms); + } + + return ResultHandleContinue; +} + +void Manager::process() { + if (_processingInThread) { + _needReProcess = true; + return; + } + + _timer.stop(); + _processingInThread = thread(); + + uint64 ms = getms(), minms = ms + 86400 * 1000ULL; + { + QReadLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + if (it->v.loadAcquire()) { + Readers::iterator i = _readers.find(it.key()->_private); + if (i == _readers.cend()) { + _readers.insert(it.key()->_private, 0); + } else { + i.value() = ms; + if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { + i.key()->_paused = false; + } + } + Reader::Frame *frame = it.key()->frameToWrite(); + if (frame) it.key()->_private->_request = frame->request; + it->v.storeRelease(0); + } + } + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + ReaderPrivate *reader = i.key(); + if (i.value() <= ms) { + ResultHandleState state = handleResult(reader, reader->process(ms), ms); + if (state == ResultHandleRemove) { + i = _readers.erase(i); + continue; + } else if (state == ResultHandleStop) { + _processingInThread = 0; + return; + } + ms = getms(); + i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); + } + if (!reader->_paused && i.value() < minms) { + minms = i.value(); + } + ++i; + } + + ms = getms(); + if (_needReProcess || minms <= ms) { + _needReProcess = false; + _timer.start(1); + } else { + _timer.start(minms - ms); + } + + _processingInThread = 0; +} + +void Manager::finish() { + _timer.stop(); + clear(); +} + +void Manager::clear() { + { + QWriteLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + it.key()->_private = 0; + } + _readerPointers.clear(); + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { + delete i.key(); + } + _readers.clear(); +} + +Manager::~Manager() { + clear(); +} + +MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover) { + FileLocation localloc(StorageFilePartial, fname); + QByteArray localdata(data); + + auto reader = std_::make_unique(&localloc, &localdata); + if (reader->start(true)) { + bool hasAlpha = false; + if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { + if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { + if (hasAlpha) { + QImage cacheForResize; + FrameRequest request; + request.framew = request.outerw = cover.width(); + request.frameh = request.outerh = cover.height(); + request.factor = 1; + cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); + } + int duration = reader->duration(); + return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); + } + } + } + return MTP_documentAttributeFilename(MTP_string(fname)); +} + +void Finish() { + if (!threads.isEmpty()) { + for (int32 i = 0, l = threads.size(); i < l; ++i) { + threads.at(i)->quit(); + DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); + threads.at(i)->wait(); + delete managers.at(i); + delete threads.at(i); + } + threads.clear(); + managers.clear(); + } +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h new file mode 100644 index 000000000..a0b160e70 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -0,0 +1,222 @@ +/* +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 + +class FileLocation; + +namespace Media { +namespace Clip { + +enum class State { + Reading, + Error, +}; + +struct FrameRequest { + bool valid() const { + return factor > 0; + } + int factor = 0; + int framew = 0; + int frameh = 0; + int outerw = 0; + int outerh = 0; + bool rounded = false; +}; + +enum ReaderSteps { + WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size + WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request + WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay +}; + +class ReaderPrivate; +class Reader { +public: + + using Callback = Function; + enum class Mode { + Gif, + Video, + }; + + Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif); + static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted + + void setAutoplay() { + _autoplay = true; + } + bool autoplay() const { + return _autoplay; + } + + void start(int framew, int frameh, int outerw, int outerh, bool rounded); + QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); + QPixmap frameOriginal() const { + Frame *frame = frameToShow(); + if (!frame) return QPixmap(); + QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); + result.detach(); + return result; + } + bool currentDisplayed() const { + Frame *frame = frameToShow(); + return frame ? (frame->displayed.loadAcquire() != 0) : true; + } + bool paused() const { + return _paused.loadAcquire(); + } + int threadIndex() const { + return _threadIndex; + } + + int width() const; + int height() const; + + State state() const; + bool started() const { + int step = _step.loadAcquire(); + return (step == WaitingForFirstFrameStep) || (step >= 0); + } + bool ready() const; + + void stop(); + void error(); + + ~Reader(); + +private: + + Callback _callback; + Mode _mode; + + State _state = State::Reading; + + mutable int _width = 0; + mutable int _height = 0; + + // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 + mutable QAtomicInt _step = WaitingForDimensionsStep; + struct Frame { + Frame() : displayed(false) { + } + void clear() { + pix = QPixmap(); + original = QImage(); + } + QPixmap pix; + QImage original; + FrameRequest request; + QAtomicInt displayed; + }; + mutable Frame _frames[3]; + Frame *frameToShow(int *index = 0) const; // 0 means not ready + Frame *frameToWrite(int *index = 0) const; // 0 means not ready + Frame *frameToWriteNext(bool check, int *index = 0) const; + void moveToNextShow() const; + void moveToNextWrite() const; + + QAtomicInt _paused = 0; + int32 _threadIndex; + + bool _autoplay = false; + + friend class Manager; + + ReaderPrivate *_private = nullptr; + +}; + +enum class ProcessResult { + Error, + Started, + Paused, + Repaint, + CopyFrame, + Wait, +}; + +class Manager : public QObject { + Q_OBJECT + +public: + + Manager(QThread *thread); + int32 loadLevel() const { + return _loadLevel.load(); + } + void append(Reader *reader, const FileLocation &location, const QByteArray &data); + void start(Reader *reader); + void update(Reader *reader); + void stop(Reader *reader); + bool carries(Reader *reader) const; + ~Manager(); + +signals: + void processDelayed(); + + void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); + +public slots: + void process(); + void finish(); + +private: + + void clear(); + + QAtomicInt _loadLevel; + struct MutableAtomicInt { + MutableAtomicInt(int value) : v(value) { + } + mutable QAtomicInt v; + }; + typedef QMap ReaderPointers; + ReaderPointers _readerPointers; + mutable QReadWriteLock _readerPointersMutex; + + ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const; + ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader); + + bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms); + + enum ResultHandleState { + ResultHandleRemove, + ResultHandleStop, + ResultHandleContinue, + }; + ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms); + + typedef QMap Readers; + Readers _readers; + + QTimer _timer; + QThread *_processingInThread; + bool _needReProcess; + +}; + +MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover); + +void Finish(); + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 9f3d29fab..dae547c5f 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -26,52 +26,55 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" +#include "media/media_clip_reader.h" namespace { - class SaveMsgClickHandler : public ClickHandler { - public: - SaveMsgClickHandler(MediaView *view) : _view(view) { - } +class SaveMsgClickHandler : public ClickHandler { +public: - void onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - _view->showSaveMsgFile(); - } - } - - private: - - MediaView *_view; - }; - - TextParseOptions _captionTextOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - TextParseOptions _captionBotOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - - bool typeHasMediaOverview(MediaOverviewType type) { - switch (type) { - case OverviewPhotos: - case OverviewVideos: - case OverviewMusicFiles: - case OverviewFiles: - case OverviewVoiceFiles: - case OverviewLinks: return true; - default: break; - } - return false; + SaveMsgClickHandler(MediaView *view) : _view(view) { } + + void onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton) { + _view->showSaveMsgFile(); + } + } + +private: + + MediaView *_view; +}; + +TextParseOptions _captionTextOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _captionBotOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +bool typeHasMediaOverview(MediaOverviewType type) { + switch (type) { + case OverviewPhotos: + case OverviewVideos: + case OverviewMusicFiles: + case OverviewFiles: + case OverviewVoiceFiles: + case OverviewLinks: return true; + default: break; + } + return false; } +} // namespace + MediaView::MediaView() : TWidget(App::wnd()) , _animStarted(getms()) , _docDownload(this, lang(lng_media_download), st::mvDocLink) @@ -219,7 +222,7 @@ bool MediaView::gifShown() const { _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); const_cast(this)->_current = QPixmap(); } - return _gif->state() != ClipError; + return _gif->state() != Media::Clip::State::Error; } return false; } @@ -479,13 +482,13 @@ void MediaView::step_radial(uint64 ms, bool timer) { if (timer && _radial.animating()) { update(radialRect()); } - if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation())) { - if (!_doc->data().isEmpty() && _doc->isAnimation()) { + if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) { + if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); } else { const FileLocation &location(_doc->location(true)); if (location.accessEnable()) { - if (_doc->isAnimation() || QImageReader(location.name()).canRead()) { + if (_doc->isAnimation() || _doc->isVideo() || QImageReader(location.name()).canRead()) { displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); } location.accessDisable(); @@ -654,13 +657,15 @@ void MediaView::onDocClick() { } } -void MediaView::clipCallback(ClipReaderNotification notification) { +void MediaView::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + if (!_gif) return; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { - if (_gif->state() == ClipError) { + if (_gif->state() == State::Error) { _current = QPixmap(); } displayDocument(_doc, item); @@ -669,7 +674,7 @@ void MediaView::clipCallback(ClipReaderNotification notification) { } } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (!_gif->currentDisplayed()) { update(_x, _y, _w, _h); } @@ -1027,7 +1032,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { } void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL - if (!doc || !doc->isAnimation() || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { + if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { stopGif(); } _doc = doc; @@ -1049,20 +1054,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty _doc->automaticLoad(item); const FileLocation &location(_doc->location(true)); - if (!_doc->data().isEmpty() && _doc->isAnimation()) { + if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { if (!_gif) { if (_doc->dimensions.width() && _doc->dimensions.height()) { _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } - _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); } } else if (location.accessEnable()) { - if (_doc->isAnimation()) { + if (_doc->isAnimation() || _doc->isVideo()) { if (!_gif) { if (_doc->dimensions.width() && _doc->dimensions.height()) { _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } - _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); } } else { if (QImageReader(location.name()).canRead()) { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index ae07799ac..904e4bd41 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -71,7 +71,7 @@ public: void activateControls(); void onDocClick(); - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); PeerData *ui_getPeerForMouseAction(); ~MediaView(); @@ -169,7 +169,7 @@ private: bool _pressed = false; int32 _dragging = 0; QPixmap _current; - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full bool fileShown() const; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 4b3e006d7..b5c7eb34f 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -947,9 +947,10 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } bool playVoice = data->voice() && audioPlayer(); bool playMusic = data->song() && audioPlayer(); + bool playVideo = data->isVideo() && audioPlayer(); bool playAnimation = data->isAnimation() && item && item->getMedia(); const FileLocation &location(data->location(true)); - if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playAnimation))) { + if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { if (playVoice) { AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; @@ -975,6 +976,16 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { audioPlayer()->play(song); if (App::main()) App::main()->documentPlayProgress(song); } + } else if (playVideo) { + if (!data->data().isEmpty()) { + App::wnd()->showDocument(data, item); + } else if (location.accessEnable()) { + App::wnd()->showDocument(data, item); + location.accessDisable(); + } else { + psOpenFile(location.name()); + } + if (App::main()) App::main()->mediaMarkRead(data); } else if (data->voice() || data->isVideo()) { psOpenFile(location.name()); if (App::main()) App::main()->mediaMarkRead(data); diff --git a/Telegram/SourceFiles/ui/animation.cpp b/Telegram/SourceFiles/ui/animation.cpp index 03b194fe9..8fcc98cce 100644 --- a/Telegram/SourceFiles/ui/animation.cpp +++ b/Telegram/SourceFiles/ui/animation.cpp @@ -19,99 +19,85 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" - #include "animation.h" -extern "C" { -#include -#include -#include -#include -} - -#include "mainwidget.h" -#include "mainwindow.h" +#include "media/media_clip_reader.h" namespace { - AnimationManager *_manager = 0; - QVector _clipThreads; - QVector _clipManagers; -}; + +AnimationManager *_manager = nullptr; + +} // namespace namespace anim { - float64 linear(const float64 &delta, const float64 &dt) { - return delta * dt; - } +float64 linear(const float64 &delta, const float64 &dt) { + return delta * dt; +} - float64 sineInOut(const float64 &delta, const float64 &dt) { - return -(delta / 2) * (cos(M_PI * dt) - 1); - } +float64 sineInOut(const float64 &delta, const float64 &dt) { + return -(delta / 2) * (cos(M_PI * dt) - 1); +} - float64 halfSine(const float64 &delta, const float64 &dt) { - return delta * sin(M_PI * dt / 2); - } +float64 halfSine(const float64 &delta, const float64 &dt) { + return delta * sin(M_PI * dt / 2); +} - float64 easeOutBack(const float64 &delta, const float64 &dt) { - static const float64 s = 1.70158; +float64 easeOutBack(const float64 &delta, const float64 &dt) { + static const float64 s = 1.70158; - const float64 t = dt - 1; - return delta * (t * t * ((s + 1) * t + s) + 1); - } + const float64 t = dt - 1; + return delta * (t * t * ((s + 1) * t + s) + 1); +} - float64 easeInCirc(const float64 &delta, const float64 &dt) { - return -delta * (sqrt(1 - dt * dt) - 1); - } +float64 easeInCirc(const float64 &delta, const float64 &dt) { + return -delta * (sqrt(1 - dt * dt) - 1); +} - float64 easeOutCirc(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1; - return delta * sqrt(1 - t * t); - } +float64 easeOutCirc(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1; + return delta * sqrt(1 - t * t); +} - float64 easeInCubic(const float64 &delta, const float64 &dt) { - return delta * dt * dt * dt; - } +float64 easeInCubic(const float64 &delta, const float64 &dt) { + return delta * dt * dt * dt; +} - float64 easeOutCubic(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1; - return delta * (t * t * t + 1); - } +float64 easeOutCubic(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1; + return delta * (t * t * t + 1); +} - float64 easeInQuint(const float64 &delta, const float64 &dt) { - const float64 t2 = dt * dt; - return delta * t2 * t2 * dt; - } +float64 easeInQuint(const float64 &delta, const float64 &dt) { + const float64 t2 = dt * dt; + return delta * t2 * t2 * dt; +} - float64 easeOutQuint(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1, t2 = t * t; - return delta * (t2 * t2 * t + 1); - } +float64 easeOutQuint(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1, t2 = t * t; + return delta * (t2 * t2 * t + 1); +} - void startManager() { - stopManager(); +void startManager() { + 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(); - DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); - _clipThreads.at(i)->wait(); - delete _clipManagers.at(i); - delete _clipThreads.at(i); - } - _clipThreads.clear(); - _clipManagers.clear(); - } - } + _manager = new AnimationManager(); } +void stopManager() { + delete _manager; + _manager = nullptr; + + Media::Clip::Finish(); +} + +void registerClipManager(Media::Clip::Manager *manager) { + manager->connect(manager, SIGNAL(callback(Media::Clip::Reader*,qint32,qint32)), _manager, SLOT(clipCallback(Media::Clip::Reader*,qint32,qint32))); +} + +} // anim + void Animation::start() { if (!_manager) return; @@ -190,1137 +176,6 @@ void AnimationManager::timeout() { } } -void AnimationManager::clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification) { - ClipReader::callback(reader, threadIndex, ClipReaderNotification(notification)); -} - -QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { - bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); - bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); - if (badSize || needOuter || hasAlpha || request.rounded) { - int32 factor(request.factor); - bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); - if (newcache) { - cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); - cache.setDevicePixelRatio(factor); - } - { - Painter p(&cache); - if (newcache) { - if (request.framew < request.outerw) { - p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); - p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); - } - if (request.frameh < request.outerh) { - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); - } - } - if (hasAlpha) { - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); - } - QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); - if (badSize) { - p.setRenderHint(QPainter::SmoothPixmapTransform); - QRect to(position, QSize(request.framew / factor, request.frameh / factor)); - QRect from(0, 0, original.width(), original.height()); - p.drawImage(to, original, from, Qt::ColorOnly); - } else { - p.drawImage(position, original); - } - } - 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, Callback &&callback) -: _callback(std_::move(callback)) -, _state(ClipReading) -, _width(0) -, _height(0) -, _step(WaitingForDimensionsStep) -, _paused(0) -, _autoplay(false) -, _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 = int32(rand_value() % _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); -} - -ClipReader::Frame *ClipReader::frameToShow(int32 *index) const { // 0 means not ready - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep) { - if (index) *index = 0; - return 0; - } else if (step == WaitingForRequestStep) { - i = 0; - } else if (step == WaitingForFirstFrameStep) { - i = 0; - } else { - i = (step / 2) % 3; - } - if (index) *index = i; - return _frames + i; -} - -ClipReader::Frame *ClipReader::frameToWrite(int32 *index) const { // 0 means not ready - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep) { - i = 0; - } else if (step == WaitingForRequestStep) { - if (index) *index = 0; - return 0; - } else if (step == WaitingForFirstFrameStep) { - i = 0; - } else { - i = ((step + 2) / 2) % 3; - } - if (index) *index = i; - return _frames + i; -} - -ClipReader::Frame *ClipReader::frameToWriteNext(bool checkNotWriting, int32 *index) const { - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { - if (index) *index = 0; - return 0; - } - i = ((step + 4) / 2) % 3; - if (index) *index = i; - return _frames + i; -} - -void ClipReader::moveToNextShow() const { - int32 step = _step.loadAcquire(); - if (step == WaitingForDimensionsStep) { - } else if (step == WaitingForRequestStep) { - _step.storeRelease(WaitingForFirstFrameStep); - } else if (step == WaitingForFirstFrameStep) { - } else if (!(step % 2)) { - _step.storeRelease(step + 1); - } -} - -void ClipReader::moveToNextWrite() const { - int32 step = _step.loadAcquire(); - if (step == WaitingForDimensionsStep) { - _step.storeRelease(WaitingForRequestStep); - } else if (step == WaitingForRequestStep) { - } else if (step == WaitingForFirstFrameStep) { - _step.storeRelease(0); - } else if (step % 2) { - _step.storeRelease((step + 1) % 6); - } -} - -void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { - // check if reader is not deleted already - if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { - reader->_callback.call(notification); - } -} - -void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { - if (_clipManagers.size() <= _threadIndex) error(); - if (_state == ClipError) return; - - if (_step.loadAcquire() == WaitingForRequestStep) { - int32 factor(cIntRetinaFactor()); - ClipFrameRequest request; - request.factor = factor; - request.framew = framew * factor; - request.frameh = frameh * factor; - request.outerw = outerw * factor; - request.outerh = outerh * factor; - request.rounded = rounded; - _frames[0].request = _frames[1].request = _frames[2].request = request; - moveToNextShow(); - _clipManagers.at(_threadIndex)->start(this); - } -} - -QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { - Frame *frame = frameToShow(); - t_assert(frame != 0); - - if (ms) { - frame->displayed.storeRelease(1); - if (_paused.loadAcquire()) { - _paused.storeRelease(0); - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->update(this); - } - } - } else { - frame->displayed.storeRelease(-1); // displayed, but should be paused - } - - int32 factor(cIntRetinaFactor()); - if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { - moveToNextShow(); - return frame->pix; - } - - frame->request.framew = framew * factor; - frame->request.frameh = frameh * factor; - frame->request.outerw = outerw * factor; - frame->request.outerh = outerh * factor; - - QImage cacheForResize; - frame->original.setDevicePixelRatio(factor); - frame->pix = QPixmap(); - frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); - - Frame *other = frameToWriteNext(true); - if (other) other->request = frame->request; - - moveToNextShow(); - - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->update(this); - } - - return frame->pix; -} - -bool ClipReader::ready() const { - if (_width && _height) return true; - - Frame *frame = frameToShow(); - if (frame) { - _width = frame->original.width(); - _height = frame->original.height(); - return true; - } - return false; -} - -int32 ClipReader::width() const { - return _width; -} - -int32 ClipReader::height() const { - return _height; -} - -ClipState ClipReader::state() const { - return _state; -} - -void ClipReader::stop() { - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->stop(this); - _width = _height = 0; - } -} - -void ClipReader::error() { - _state = ClipError; -} - -ClipReader::~ClipReader() { - stop(); -} - -class ClipReaderImplementation { -public: - - ClipReaderImplementation(FileLocation *location, QByteArray *data) - : _location(location) - , _data(data) - , _device(0) - , _dataSize(0) { - } - virtual bool readNextFrame() = 0; - virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; - virtual int32 nextFrameDelay() = 0; - virtual bool start(bool onlyGifv) = 0; - virtual ~ClipReaderImplementation() { - } - int64 dataSize() const { - return _dataSize; - } - -protected: - FileLocation *_location; - QByteArray *_data; - QFile _file; - QBuffer _buffer; - QIODevice *_device; - int64 _dataSize; - - void initDevice() { - if (_data->isEmpty()) { - if (_file.isOpen()) _file.close(); - _file.setFileName(_location->name()); - _dataSize = _file.size(); - } else { - if (_buffer.isOpen()) _buffer.close(); - _buffer.setBuffer(_data); - _dataSize = _data->size(); - } - _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); - } - -}; - -class QtGifReaderImplementation : public ClipReaderImplementation{ -public: - - QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) - , _reader(0) - , _framesLeft(0) - , _frameDelay(0) { - } - - bool readNextFrame() { - if (_reader) _frameDelay = _reader->nextImageDelay(); - if (_framesLeft < 1 && !jumpToStart()) { - return false; - } - - _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it - if (!_reader->read(&_frame) || _frame.isNull()) { - return false; - } - --_framesLeft; - return true; - } - - bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { - t_assert(!_frame.isNull()); - if (size.isEmpty() || size == _frame.size()) { - 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(); - } - } else { - to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - hasAlpha = _frame.hasAlphaChannel(); - _frame = QImage(); - return true; - } - - int32 nextFrameDelay() { - return _frameDelay; - } - - bool start(bool onlyGifv) { - if (onlyGifv) return false; - return jumpToStart(); - } - - ~QtGifReaderImplementation() { - deleteAndMark(_reader); - } - -private: - QImageReader *_reader; - int32 _framesLeft, _frameDelay; - QImage _frame; - - bool jumpToStart() { - if (_reader && _reader->jumpToImage(0)) { - _framesLeft = _reader->imageCount(); - return true; - } - - delete _reader; - initDevice(); - _reader = new QImageReader(_device); -#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) - _reader->setAutoTransform(true); -#endif - if (!_reader->canRead() || !_reader->supportsAnimation()) { - return false; - } - _framesLeft = _reader->imageCount(); - if (_framesLeft < 1) { - return false; - } - return true; - } - -}; - -class FFMpegReaderImplementation : public ClipReaderImplementation { -public: - - FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) - , _ioBuffer(0) - , _ioContext(0) - , _fmtContext(0) - , _codec(0) - , _codecContext(0) - , _streamId(0) - , _frame(0) - , _opened(false) - , _hadFrame(false) - , _frameRead(false) - , _packetSize(0) - , _packetData(0) - , _packetWas(false) - , _width(0) - , _height(0) - , _swsContext(0) - , _frameMs(0) - , _nextFrameDelay(0) - , _currentFrameDelay(0) { - _frame = av_frame_alloc(); - av_init_packet(&_avpkt); - _avpkt.data = NULL; - _avpkt.size = 0; - } - - bool readNextFrame() { - if (_frameRead) { - av_frame_unref(_frame); - _frameRead = false; - } - - int res; - while (true) { - if (_avpkt.size > 0) { // previous packet not finished - res = 0; - } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { - if (res != AVERROR_EOF || !_hadFrame) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - - bool finished = (res < 0); - if (finished) { - _avpkt.data = NULL; - _avpkt.size = 0; - } else { - rememberPacket(); - } - - int32 got_frame = 0; - int32 decoded = _avpkt.size; - if (_avpkt.stream_index == _streamId) { - if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - if (res == AVERROR_INVALIDDATA) { // try to skip bad packet - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; - } - - if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file - return false; - } - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; - } - if (res > 0) decoded = res; - } - if (!finished) { - _avpkt.data += decoded; - _avpkt.size -= decoded; - if (_avpkt.size <= 0) freePacket(); - } - - if (got_frame) { - int64 duration = av_frame_get_pkt_duration(_frame); - int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; - int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - _currentFrameDelay = _nextFrameDelay; - if (_frameMs + _currentFrameDelay < frameMs) { - _currentFrameDelay = int32(frameMs - _frameMs); - } - if (duration == AV_NOPTS_VALUE) { - _nextFrameDelay = 0; - } else { - _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - _frameMs = frameMs; - - _hadFrame = _frameRead = true; - return true; - } - - if (finished) { - if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { - if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { - if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { - if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - } - } - avcodec_flush_buffers(_codecContext); - _hadFrame = false; - _frameMs = 0; - } - } - - return false; - } - - bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { - t_assert(_frameRead); - _frameRead = false; - - if (!_width || !_height) { - _width = _frame->width; - _height = _frame->height; - if (!_width || !_height) { - LOG(("Gif Error: Bad frame size %1").arg(logData())); - return false; - } - } - - QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); - if (to.isNull() || to.size() != toSize) { - to = QImage(toSize, QImage::Format_ARGB32); - } - hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); - if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { - int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); - uchar *s = _frame->data[0], *d = to.bits(); - for (int32 i = 0, l = _frame->height; i < l; ++i) { - memcpy(d + i * dbpl, s + i * sbpl, bpl); - } - } else { - if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { - _swsSize = toSize; - _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); - } - uint8_t * toData[1] = { to.bits() }; - int toLinesize[1] = { to.bytesPerLine() }, res; - if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { - LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); - return false; - } - } - - av_frame_unref(_frame); - return true; - } - - int32 nextFrameDelay() { - return _currentFrameDelay; - } - - QString logData() const { - return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); - } - - bool start(bool onlyGifv) { - initDevice(); - if (!_device->open(QIODevice::ReadOnly)) { - LOG(("Gif Error: Unable to open device %1").arg(logData())); - return false; - } - _ioBuffer = (uchar*)av_malloc(AVBlockSize); - _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); - _fmtContext = avformat_alloc_context(); - if (!_fmtContext) { - LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); - return false; - } - _fmtContext->pb = _ioContext; - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { - _ioBuffer = 0; - - LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - _opened = true; - - if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { - LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); - if (_streamId < 0) { - LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); - return false; - } - - // Get a pointer to the codec context for the audio stream - _codecContext = _fmtContext->streams[_streamId]->codec; - _codec = avcodec_find_decoder(_codecContext->codec_id); - - if (onlyGifv) { - if (av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) >= 0) { // should be no audio stream - return false; - } - if (dataSize() > AnimationInMemory) { - return false; - } - if (_codecContext->codec_id != AV_CODEC_ID_H264) { - return false; - } - } - av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { - LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - return true; - } - - int32 duration() const { - if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; - return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - - ~FFMpegReaderImplementation() { - if (_frameRead) { - av_frame_unref(_frame); - _frameRead = false; - } - if (_ioContext) av_free(_ioContext); - if (_codecContext) avcodec_close(_codecContext); - if (_swsContext) sws_freeContext(_swsContext); - if (_opened) { - avformat_close_input(&_fmtContext); - } else if (_ioBuffer) { - av_free(_ioBuffer); - } - if (_fmtContext) avformat_free_context(_fmtContext); - av_frame_free(&_frame); - freePacket(); - } - -private: - uchar *_ioBuffer; - AVIOContext *_ioContext; - AVFormatContext *_fmtContext; - AVCodec *_codec; - AVCodecContext *_codecContext; - int32 _streamId; - AVFrame *_frame; - bool _opened, _hadFrame, _frameRead; - - AVPacket _avpkt; - int _packetSize; - uint8_t *_packetData; - bool _packetWas; - void rememberPacket() { - if (!_packetWas) { - _packetSize = _avpkt.size; - _packetData = _avpkt.data; - _packetWas = true; - } - } - void freePacket() { - if (_packetWas) { - _avpkt.size = _packetSize; - _avpkt.data = _packetData; - _packetWas = false; - av_packet_unref(&_avpkt); - } - } - - int32 _width, _height; - SwsContext *_swsContext; - QSize _swsSize; - - int64 _frameMs; - int32 _nextFrameDelay, _currentFrameDelay; - - static int _read(void *opaque, uint8_t *buf, int buf_size) { - FFMpegReaderImplementation *l = reinterpret_cast(opaque); - return int(l->_device->read((char*)(buf), buf_size)); - } - - static int64_t _seek(void *opaque, int64_t offset, int whence) { - FFMpegReaderImplementation *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; - case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; - case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; - } - return -1; - } - -}; - -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) - , _implementation(0) - , _frame(0) - , _width(0) - , _height(0) - , _nextFrameWhen(0) - , _paused(false) { - if (_data.isEmpty() && !_location->accessEnable()) { - error(); - return; - } - _accessed = true; - } - - ClipProcessResult start(uint64 ms) { - if (!_implementation && !init()) { - return error(); - } - if (frame() && frame()->original.isNull()) { - if (!_implementation->readNextFrame()) { - return error(); - } - if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { - return error(); - } - _width = frame()->original.width(); - _height = frame()->original.height(); - return ClipProcessStarted; - } - 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 (!_paused && ms >= _nextFrameWhen) { - return ClipProcessRepaint; - } - return ClipProcessWait; - } - - ClipProcessResult finishProcess(uint64 ms) { - if (!readNextFrame()) { - return error(); - } - if (ms >= _nextFrameWhen && !readNextFrame(true)) { - return error(); - } - if (!renderFrame()) { - return error(); - } - return ClipProcessCopyFrame; - } - - uint64 nextFrameDelay() { - int32 delay = _implementation->nextFrameDelay(); - return qMax(delay, 5); - } - - bool readNextFrame(bool keepup = false) { - if (!_implementation->readNextFrame()) { - return false; - } - _nextFrameWhen += nextFrameDelay(); - if (keepup) { - _nextFrameWhen = qMax(_nextFrameWhen, getms()); - } - return true; - } - - bool renderFrame() { - t_assert(frame() != 0 && _request.valid()); - if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { - return false; - } - frame()->original.setDevicePixelRatio(_request.factor); - frame()->pix = QPixmap(); - frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); - frame()->when = _nextFrameWhen; - return true; - } - - bool init() { - if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { - QFile f(_location->name()); - if (f.open(QIODevice::ReadOnly)) { - _data = f.readAll(); - if (f.error() != QFile::NoError) { - _data = QByteArray(); - } - } - } - - _implementation = new FFMpegReaderImplementation(_location, &_data); -// _implementation = new QtGifReaderImplementation(_location, &_data); - return _implementation->start(false); - } - - ClipProcessResult error() { - stop(); - _state = ClipError; - return ClipProcessError; - } - - void stop() { - delete _implementation; - _implementation = 0; - - if (_location) { - if (_accessed) { - _location->accessDisable(); - } - delete _location; - _location = 0; - } - _accessed = false; - } - - ~ClipReaderPrivate() { - stop(); - deleteAndMark(_location); - deleteAndMark(_implementation); - _data.clear(); - } - -private: - - ClipReader *_interface; - ClipState _state; - - QByteArray _data; - FileLocation *_location; - bool _accessed; - - QBuffer _buffer; - ClipReaderImplementation *_implementation; - - ClipFrameRequest _request; - struct Frame { - Frame() : alpha(true), when(0) { - } - QPixmap pix; - QImage original, cache; - bool alpha; - uint64 when; - }; - Frame _frames[3]; - int32 _frame; - Frame *frame() { - return _frames + _frame; - } - - int32 _width, _height; - - uint64 _nextFrameWhen; - - bool _paused; - - friend class ClipReadManager; - -}; - -ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0), _needReProcess(false) { - moveToThread(thread); - connect(thread, SIGNAL(started()), this, SLOT(process())); - connect(thread, SIGNAL(finished()), this, SLOT(finish())); - 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(callback(ClipReader*,qint32,qint32)), _manager, SLOT(clipCallback(ClipReader*,qint32,qint32))); -} - -void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { - reader->_private = new ClipReaderPrivate(reader, location, data); - _loadLevel.fetchAndAddRelaxed(AverageGifSize); - update(reader); -} - -void ClipReadManager::start(ClipReader *reader) { - update(reader); -} - -void ClipReadManager::update(ClipReader *reader) { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator i = _readerPointers.constFind(reader); - if (i == _readerPointers.cend()) { - lock.unlock(); - - QWriteLocker lock(&_readerPointersMutex); - _readerPointers.insert(reader, MutableAtomicInt(1)); - } else { - i->v.storeRelease(1); - } - emit processDelayed(); -} - -void ClipReadManager::stop(ClipReader *reader) { - if (!carries(reader)) return; - - QWriteLocker lock(&_readerPointersMutex); - _readerPointers.remove(reader); - emit processDelayed(); -} - -bool ClipReadManager::carries(ClipReader *reader) const { - QReadLocker lock(&_readerPointersMutex); - return _readerPointers.contains(reader); -} - -ClipReadManager::ReaderPointers::iterator ClipReadManager::unsafeFindReaderPointer(ClipReaderPrivate *reader) { - ReaderPointers::iterator it = _readerPointers.find(reader->_interface); - - // could be a new reader which was realloced in the same address - return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); -} - -ClipReadManager::ReaderPointers::const_iterator ClipReadManager::constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const { - ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); - - // could be a new reader which was realloced in the same address - return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); -} - -bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); - if (result == ClipProcessError) { - if (it != _readerPointers.cend()) { - it.key()->error(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - - lock.unlock(); - QWriteLocker lock(&_readerPointersMutex); - ReaderPointers::iterator i = unsafeFindReaderPointer(reader); - if (i != _readerPointers.cend()) _readerPointers.erase(i); - } - return false; - } - if (it == _readerPointers.cend()) { - return false; - } - - if (result == ClipProcessStarted) { - _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); - } - if (!reader->_paused && result == ClipProcessRepaint) { - int32 ishowing, iprevious; - ClipReader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); - t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); - if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown - if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { - reader->_paused = true; - it.key()->_paused.storeRelease(1); - result = ClipProcessPaused; - } - } - } - if (result == ClipProcessStarted || result == ClipProcessCopyFrame) { - t_assert(reader->_frame >= 0); - ClipReader::Frame *frame = it.key()->_frames + reader->_frame; - frame->clear(); - frame->pix = reader->frame()->pix; - frame->original = reader->frame()->original; - frame->displayed.storeRelease(0); - if (result == ClipProcessStarted) { - reader->_nextFrameWhen = ms; - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - } - } else if (result == ClipProcessPaused) { - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - } else if (result == ClipProcessRepaint) { - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderRepaint); - } - return true; -} - -ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { - if (!handleProcessResult(reader, result, ms)) { - _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); - delete reader; - return ResultHandleRemove; - } - - _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); - if (_processingInThread->isInterruptionRequested()) { - return ResultHandleStop; - } - - if (result == ClipProcessRepaint) { - { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); - if (it != _readerPointers.cend()) { - int32 index = 0; - ClipReader *r = it.key(); - ClipReader::Frame *frame = it.key()->frameToWrite(&index); - if (frame) { - frame->clear(); - } else { - t_assert(!reader->_request.valid()); - } - reader->_frame = index; - } - } - return handleResult(reader, reader->finishProcess(ms), ms); - } - - return ResultHandleContinue; -} - -void ClipReadManager::process() { - if (_processingInThread) { - _needReProcess = true; - return; - } - - _timer.stop(); - _processingInThread = thread(); - - uint64 ms = getms(), minms = ms + 86400 * 1000ULL; - { - QReadLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - if (it->v.loadAcquire()) { - Readers::iterator i = _readers.find(it.key()->_private); - if (i == _readers.cend()) { - _readers.insert(it.key()->_private, 0); - } else { - i.value() = ms; - if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { - i.key()->_paused = false; - } - } - ClipReader::Frame *frame = it.key()->frameToWrite(); - if (frame) it.key()->_private->_request = frame->request; - it->v.storeRelease(0); - } - } - } - - for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { - ClipReaderPrivate *reader = i.key(); - if (i.value() <= ms) { - ResultHandleState state = handleResult(reader, reader->process(ms), ms); - if (state == ResultHandleRemove) { - i = _readers.erase(i); - continue; - } else if (state == ResultHandleStop) { - _processingInThread = 0; - return; - } - ms = getms(); - i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); - } - if (!reader->_paused && i.value() < minms) { - minms = i.value(); - } - ++i; - } - - ms = getms(); - if (_needReProcess || minms <= ms) { - _needReProcess = false; - _timer.start(1); - } else { - _timer.start(minms - ms); - } - - _processingInThread = 0; -} - -void ClipReadManager::finish() { - _timer.stop(); - clear(); -} - -void ClipReadManager::clear() { - { - QWriteLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - it.key()->_private = 0; - } - _readerPointers.clear(); - } - - for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { - delete i.key(); - } - _readers.clear(); -} - -ClipReadManager::~ClipReadManager() { - clear(); -} - -MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover) { - FileLocation localloc(StorageFilePartial, fname); - QByteArray localdata(data); - - FFMpegReaderImplementation *reader = new FFMpegReaderImplementation(&localloc, &localdata); - if (reader->start(true)) { - bool hasAlpha = false; - if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { - if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { - if (hasAlpha) { - QImage cacheForResize; - ClipFrameRequest request; - request.framew = request.outerw = cover.width(); - request.frameh = request.outerh = cover.height(); - request.factor = 1; - cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); - } - int32 duration = reader->duration(); - delete reader; - return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); - } - } - } - delete reader; - return MTP_documentAttributeFilename(MTP_string(fname)); +void AnimationManager::clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification) { + Media::Clip::Reader::callback(reader, threadIndex, Media::Clip::Notification(notification)); } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index ff1a944ed..3482ef0e9 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -24,6 +24,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #include +namespace Media { +namespace Clip { + +class Reader; +static Reader * const BadReader = SharedMemoryLocation(); + +class Manager; + +enum Notification { + NotificationReinit, + NotificationRepaint, +}; + +} // namespace Clip +} // namespace Media + namespace anim { typedef float64 (*transition)(const float64 &delta, const float64 &dt); @@ -193,6 +209,7 @@ namespace anim { void startManager(); void stopManager(); + void registerClipManager(Media::Clip::Manager *manager); }; @@ -480,8 +497,6 @@ if ((animation).isNull()) { \ ENSURE_ANIMATION(animation, updateCallback, from); \ (animation).start((to), (duration), (transition)) -class ClipReader; - class AnimationManager : public QObject { Q_OBJECT @@ -494,7 +509,7 @@ public: public slots: void timeout(); - void clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification); + void clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); private: typedef QMap AnimatingObjects; @@ -503,198 +518,3 @@ private: bool _iterating; }; - -class FileLocation; - -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; -}; - -enum ClipReaderNotification { - ClipReaderReinit, - ClipReaderRepaint, -}; - -enum ClipReaderSteps { - WaitingForDimensionsStep = -3, // before ClipReaderPrivate read the first image and got the original frame size - WaitingForRequestStep = -2, // before ClipReader got the original frame size and prepared the frame request - WaitingForFirstFrameStep = -1, // before ClipReaderPrivate got the frame request and started waiting for the 1-2 delay -}; - -class ClipReaderPrivate; -class ClipReader { -public: - - using Callback = Function; - - ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback); - static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted - - void setAutoplay() { - _autoplay = true; - } - bool autoplay() const { - return _autoplay; - } - - void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); - QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms); - QPixmap frameOriginal() const { - Frame *frame = frameToShow(); - if (!frame) return QPixmap(); - QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); - result.detach(); - return result; - } - bool currentDisplayed() const { - Frame *frame = frameToShow(); - return frame ? (frame->displayed.loadAcquire() != 0) : true; - } - bool paused() const { - return _paused.loadAcquire(); - } - int32 threadIndex() const { - return _threadIndex; - } - - int32 width() const; - int32 height() const; - - ClipState state() const; - bool started() const { - int32 step = _step.loadAcquire(); - return (step == WaitingForFirstFrameStep) || (step >= 0); - } - bool ready() const; - - void stop(); - void error(); - - ~ClipReader(); - -private: - - Callback _callback; - - ClipState _state; - - mutable int32 _width, _height; - - mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 - struct Frame { - Frame() : displayed(false) { - } - void clear() { - pix = QPixmap(); - original = QImage(); - } - QPixmap pix; - QImage original; - ClipFrameRequest request; - QAtomicInt displayed; - }; - mutable Frame _frames[3]; - Frame *frameToShow(int32 *index = 0) const; // 0 means not ready - Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready - Frame *frameToWriteNext(bool check, int32 *index = 0) const; - void moveToNextShow() const; - void moveToNextWrite() const; - - QAtomicInt _paused; - int32 _threadIndex; - - bool _autoplay; - - friend class ClipReadManager; - - ClipReaderPrivate *_private; - -}; - -static ClipReader * const BadClipReader = SharedMemoryLocation(); - -enum ClipProcessResult { - ClipProcessError, - ClipProcessStarted, - ClipProcessPaused, - ClipProcessRepaint, - ClipProcessCopyFrame, - ClipProcessWait, -}; - -class ClipReadManager : public QObject { - Q_OBJECT - -public: - - ClipReadManager(QThread *thread); - int32 loadLevel() const { - return _loadLevel.load(); - } - void append(ClipReader *reader, const FileLocation &location, const QByteArray &data); - void start(ClipReader *reader); - void update(ClipReader *reader); - void stop(ClipReader *reader); - bool carries(ClipReader *reader) const; - ~ClipReadManager(); - -signals: - - void processDelayed(); - - void callback(ClipReader *reader, qint32 threadIndex, qint32 notification); - -public slots: - - void process(); - void finish(); - -private: - - void clear(); - - QAtomicInt _loadLevel; - struct MutableAtomicInt { - MutableAtomicInt(int value) : v(value) { - } - mutable QAtomicInt v; - }; - typedef QMap ReaderPointers; - ReaderPointers _readerPointers; - mutable QReadWriteLock _readerPointersMutex; - - ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const; - ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader); - - bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); - - enum ResultHandleState { - ResultHandleRemove, - ResultHandleStop, - ResultHandleContinue, - }; - ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); - - typedef QMap Readers; - Readers _readers; - - QTimer _timer; - QThread *_processingInThread; - bool _needReProcess; - -}; - -MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 5b8fc9a82..532a644d2 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -371,6 +371,10 @@ true true + + true + true + true true @@ -701,6 +705,10 @@ true true + + true + true + true true @@ -1062,6 +1070,10 @@ true true + + true + true + true true @@ -1250,6 +1262,10 @@ + + + + @@ -1508,6 +1524,23 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_reader.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_reader.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_reader.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.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" + + + + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 2d1a1f393..64116e19b 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -112,6 +112,9 @@ {385d4cd5-f702-41b7-9e39-707d16b118d5} + + {a281888a-8b70-4e95-9b03-ebcb02837df4} + @@ -1344,6 +1347,27 @@ SourceFiles\platform\linux + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1595,6 +1619,15 @@ SourceFiles\platform\linux + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1885,6 +1918,9 @@ SourceFiles\ui + + SourceFiles\media + From 991c6ddd99b4be84c62d1fc6f10e2fbf7924ecb9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 Jun 2016 19:25:21 +0300 Subject: [PATCH 02/60] Saving featured stickers for new 0.9.57 version in local storage. --- Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/apiwrap.cpp | 6 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 39 ++- Telegram/SourceFiles/boxes/stickersetbox.h | 16 +- Telegram/SourceFiles/core/version.h | 6 +- Telegram/SourceFiles/dropdown.cpp | 15 +- Telegram/SourceFiles/facades.cpp | 6 + Telegram/SourceFiles/facades.h | 13 +- Telegram/SourceFiles/history.cpp | 4 +- .../history/field_autocomplete.cpp | 4 +- Telegram/SourceFiles/historywidget.cpp | 124 ++++++++- Telegram/SourceFiles/historywidget.h | 4 + Telegram/SourceFiles/localstorage.cpp | 99 ++++++-- Telegram/SourceFiles/localstorage.h | 1 + Telegram/SourceFiles/mainwidget.cpp | 18 +- Telegram/SourceFiles/mtproto/core_types.h | 5 +- Telegram/SourceFiles/mtproto/scheme.tl | 12 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 55 +++- Telegram/SourceFiles/mtproto/scheme_auto.h | 237 ++++++++++++++++-- Telegram/SourceFiles/structs.cpp | 4 +- Telegram/SourceFiles/ui/text/text.h | 2 +- Telegram/Telegram.xcodeproj/project.pbxproj | 4 +- Telegram/build/version | 8 +- 24 files changed, 578 insertions(+), 120 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index b3f7cf3e2..a04176115 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,56,0 - PRODUCTVERSION 0,9,56,0 + FILEVERSION 0,9,57,0 + PRODUCTVERSION 0,9,57,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.56.0" + VALUE "FileVersion", "0.9.57.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.56.0" + VALUE "ProductVersion", "0.9.57.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 1cdd13d51..edc6944fb 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,56,0 - PRODUCTVERSION 0,9,56,0 + FILEVERSION 0,9,57,0 + PRODUCTVERSION 0,9,57,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.56.0" + VALUE "FileVersion", "0.9.57.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.56.0" + VALUE "ProductVersion", "0.9.57.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0a13d6be2..e0dc51135 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -929,7 +929,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) if (d.vset.type() != mtpc_stickerSet) return; const auto &s(d.vset.c_stickerSet()); - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); auto it = sets.find(setId); if (it == sets.cend()) return; @@ -937,7 +937,9 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) it->hash = s.vhash.v; it->shortName = qs(s.vshort_name); it->title = stickerSetTitle(s); - it->flags = s.vflags.v; + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); + it->flags = s.vflags.v | clientFlags; + it->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded; const auto &d_docs(d.vdocuments.c_vector().v); auto custom = sets.find(Stickers::CustomSetId); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 10ad1a6ec..0f23423d5 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -30,16 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() -, _loaded(false) -, _setId(0) -, _setAccess(0) -, _setCount(0) -, _setHash(0) -, _setFlags(0) -, _bottom(0) -, _input(set) -, _installRequest(0) -, _previewShown(-1) { +, _input(set) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; @@ -117,17 +108,20 @@ bool StickerSetInner::failedSet(const RPCError &error) { } void StickerSetInner::installDone(const MTPBool &result) { - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); _setFlags &= ~MTPDstickerSet::Flag::f_disabled; + _setFlags |= MTPDstickerSet::Flag::f_installed; auto it = sets.find(_setId); if (it == sets.cend()) { it = sets.insert(_setId, Stickers::Set(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)); + } else { + it.value().flags = _setFlags; } it.value().stickers = _pack; it.value().emoji = _emoji; - Stickers::Order &order(Global::RefStickerSetsOrder()); + auto &order = Global::RefStickerSetsOrder(); int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { @@ -257,7 +251,7 @@ bool StickerSetInner::loaded() const { int32 StickerSetInner::notInstalled() const { if (!_loaded) return 0; auto it = Global::StickerSets().constFind(_setId); - if (it == Global::StickerSets().cend() || (it->flags & MTPDstickerSet::Flag::f_disabled)) return _pack.size(); + if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_disabled)) return _pack.size(); return 0; } @@ -682,10 +676,10 @@ void StickersInner::rebuild() { int32 namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x() - qMax(qMax(_returnWidth, _removeWidth), _restoreWidth); clear(); - const Stickers::Order &order(Global::StickerSetsOrder()); + auto &order = Global::StickerSetsOrder(); _animStartTimes.reserve(order.size()); - const Stickers::Sets &sets(Global::StickerSets()); + auto &sets = Global::StickerSets(); for (int i = 0, l = order.size(); i < l; ++i) { auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { @@ -913,7 +907,7 @@ void StickersBox::onSave() { bool writeRecent = false; RecentStickerPack &recent(cGetRecentStickers()); - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); QVector reorder = _inner.getOrder(), disabled = _inner.getDisabledSets(); for (int32 i = 0, l = disabled.size(); i < l; ++i) { @@ -936,7 +930,9 @@ void StickersBox::onSave() { _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); int removeIndex = Global::StickerSetsOrder().indexOf(it->id); if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); - sets.erase(it); + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)) { + sets.erase(it); + } } } } @@ -955,7 +951,10 @@ void StickersBox::onSave() { } } for (auto it = sets.begin(); it != sets.cend();) { - if (it->id == Stickers::CustomSetId || it->id == Stickers::RecentSetId || order.contains(it->id)) { + if (it->id == Stickers::CustomSetId + || it->id == Stickers::RecentSetId + || (it->flags & MTPDstickerSet_ClientFlag::f_featured) + || order.contains(it->id)) { ++it; } else { it = sets.erase(it); @@ -991,8 +990,8 @@ void StickersBox::showAll() { int32 stickerPacksCount(bool includeDisabledOfficial) { int32 result = 0; - const Stickers::Order &order(Global::StickerSetsOrder()); - const Stickers::Sets &sets(Global::StickerSets()); + auto &order = Global::StickerSetsOrder(); + auto &sets = Global::StickerSets(); for (int i = 0, l = order.size(); i < l; ++i) { auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index fc2c9a513..9b6cd9891 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -67,19 +67,21 @@ private: StickerPack _pack; StickersByEmojiMap _emoji; - bool _loaded; - uint64 _setId, _setAccess; + bool _loaded = false; + uint64 _setId = 0; + uint64 _setAccess = 0; QString _title, _setTitle, _setShortName; - int32 _setCount, _setHash; - MTPDstickerSet::Flags _setFlags; + int32 _setCount = 0; + int32 _setHash = 0; + MTPDstickerSet::Flags _setFlags = 0; - int32 _bottom; + int32 _bottom = 0; MTPInputStickerSet _input; - mtpRequestId _installRequest; + mtpRequestId _installRequest = 0; QTimer _previewTimer; - int32 _previewShown; + int32 _previewShown = -1; }; class StickerSetBox : public ScrollableBox, public RPCSender { diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 68d7dbbd0..7e443e781 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9056; -constexpr str_const AppVersionStr = "0.9.56"; -constexpr bool AppAlphaVersion = false; +constexpr int AppVersion = 9057; +constexpr str_const AppVersionStr = "0.9.57"; +constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index d862a660d..d4db768d2 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -1508,7 +1508,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { break; } } - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); auto it = sets.find(Stickers::CustomSetId); if (it != sets.cend()) { for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { @@ -1628,8 +1628,8 @@ void StickerPanInner::hideFinish(bool completely) { void StickerPanInner::refreshStickers() { clearSelection(true); - const Stickers::Sets &sets(Global::StickerSets()); - _sets.clear(); _sets.reserve(sets.size() + 1); + _sets.clear(); + _sets.reserve(Global::StickerSetsOrder().size() + 1); refreshRecentStickers(false); for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) { @@ -2085,7 +2085,7 @@ bool StickerPanInner::ui_isInlineItemBeingChosen() { } void StickerPanInner::appendSet(uint64 setId) { - const Stickers::Sets &sets(Global::StickerSets()); + auto &sets = Global::StickerSets(); auto it = sets.constFind(setId); if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_disabled) || it->stickers.isEmpty()) return; @@ -3584,7 +3584,7 @@ void EmojiPan::onSwitch() { } else { if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { s_inner.showStickerSet(Stickers::DefaultSetId); - } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSets().isEmpty()) { + } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSetsOrder().isEmpty()) { s_inner.showStickerSet(Stickers::NoneSetId); } else { s_inner.updateShowingSavedGifs(); @@ -3652,7 +3652,10 @@ void EmojiPan::onRemoveSetSure() { ++i; } } - Global::RefStickerSets().erase(it); + it->flags &= ~MTPDstickerSet::Flag::f_installed; + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)) { + Global::RefStickerSets().erase(it); + } int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); refreshStickers(); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 4a3597d48..3e1a18143 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -560,6 +560,9 @@ struct Data { Stickers::Sets StickerSets; Stickers::Order StickerSetsOrder; uint64 LastStickersUpdate = 0; + Stickers::Order FeaturedStickerSetsOrder; + Stickers::UnreadMap FeaturedUnreadSets; + uint64 LastFeaturedStickersUpdate = 0; MTP::DcOptions DcOptions; @@ -626,6 +629,9 @@ DefineRefVar(Global, PendingItemsMap, PendingRepaintItems); DefineVar(Global, Stickers::Sets, StickerSets); DefineVar(Global, Stickers::Order, StickerSetsOrder); DefineVar(Global, uint64, LastStickersUpdate); +DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); +DefineVar(Global, Stickers::UnreadMap, FeaturedUnreadSets); +DefineVar(Global, uint64, LastFeaturedStickersUpdate); DefineVar(Global, MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index ab621be03..f8831e798 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -181,7 +181,14 @@ static const uint64 DefaultSetId = 0; // for backward compatibility static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL, RecentSetId = 0xFFFFFFFFFFFFFFFEULL; static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel struct Set { - Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id), access(access), title(title), shortName(shortName), count(count), hash(hash), flags(flags) { + Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) + : id(id) + , access(access) + , title(title) + , shortName(shortName) + , count(count) + , hash(hash) + , flags(flags) { } uint64 id, access; QString title, shortName; @@ -192,6 +199,7 @@ struct Set { }; using Sets = QMap; using Order = QList; +using UnreadMap = OrderedSet; } // namespace Stickers @@ -241,6 +249,9 @@ DeclareRefVar(PendingItemsMap, PendingRepaintItems); DeclareVar(Stickers::Sets, StickerSets); DeclareVar(Stickers::Order, StickerSetsOrder); DeclareVar(uint64, LastStickersUpdate); +DeclareVar(Stickers::Order, FeaturedStickerSetsOrder); +DeclareVar(Stickers::UnreadMap, FeaturedUnreadSets); +DeclareVar(uint64, LastFeaturedStickersUpdate); DeclareVar(MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 2665065c4..a29b92053 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2868,7 +2868,9 @@ bool HistoryItem::unread() const { if (id > 0) { if (id < history()->outboxReadBefore) return false; - if (auto channel = history()->peer->asChannel()) { + if (auto user = history()->peer->asUser()) { + if (user->botInfo) return false; + } else if (auto channel = history()->peer->asChannel()) { if (!channel->isMegagroup()) return false; } } diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index c62e7637e..67995b523 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -153,8 +153,8 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { StickerPack srows; if (_emoji) { QMap setsToRequest; - Stickers::Sets &sets(Global::RefStickerSets()); - const Stickers::Order &order(Global::StickerSetsOrder()); + auto &sets = Global::RefStickerSets(); + auto &order = Global::StickerSetsOrder(); for (int i = 0, l = order.size(); i < l; ++i) { auto it = sets.find(order.at(i)); if (it != sets.cend()) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 9ef08f7d3..3bdd9da63 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "data/data_drafts.h" #include "history/history_service_layout.h" #include "profile/profile_members_widget.h" +#include "core/click_handler_types.h" #include "lang.h" #include "application.h" #include "mainwidget.h" @@ -3519,12 +3520,18 @@ void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) { } void HistoryWidget::updateStickers() { - if (!Global::LastStickersUpdate() || getms(true) >= Global::LastStickersUpdate() + StickersUpdateTimeout) { + auto now = getms(true); + if (!Global::LastStickersUpdate() || now >= Global::LastStickersUpdate() + StickersUpdateTimeout) { if (!_stickersUpdateRequest) { _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); } } - if (!cLastSavedGifsUpdate() || getms(true) >= cLastSavedGifsUpdate() + StickersUpdateTimeout) { + if (!Global::LastFeaturedStickersUpdate() || now >= Global::LastFeaturedStickersUpdate() + StickersUpdateTimeout) { + if (!_featuredStickersUpdateRequest) { + _featuredStickersUpdateRequest = MTP::send(MTPmessages_GetFeaturedStickers(MTP_int(Local::countFeaturedStickersHash())), rpcDone(&HistoryWidget::featuredStickersGot), rpcFail(&HistoryWidget::featuredStickersFailed)); + } + } + if (!cLastSavedGifsUpdate() || now >= cLastSavedGifsUpdate() + StickersUpdateTimeout) { if (!_savedGifsUpdateRequest) { _savedGifsUpdateRequest = MTP::send(MTPmessages_GetSavedGifs(MTP_int(Local::countSavedGifsHash())), rpcDone(&HistoryWidget::savedGifsGot), rpcFail(&HistoryWidget::savedGifsFailed)); } @@ -3641,17 +3648,17 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { const auto &d_sets(d.vsets.c_vector().v); - Stickers::Order &setsOrder(Global::RefStickerSetsOrder()); + auto &setsOrder = Global::RefStickerSetsOrder(); setsOrder.clear(); - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); QMap setsToRequest; - for (auto i = sets.begin(), e = sets.end(); i != e; ++i) { - i->access = 0; // mark for removing + for (auto &set : sets) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; // mark for removing } - for (int i = 0, l = d_sets.size(); i != l; ++i) { - if (d_sets.at(i).type() == mtpc_stickerSet) { - const auto &set(d_sets.at(i).c_stickerSet()); + for_const (auto &setData, d_sets) { + if (setData.type() == mtpc_stickerSet) { + const auto &set(setData.c_stickerSet()); auto it = sets.find(set.vid.v); QString title = stickerSetTitle(set); if (it == sets.cend()) { @@ -3660,7 +3667,8 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); - it->flags = set.vflags.v; + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); + it->flags = set.vflags.v | clientFlags; if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { it->count = set.vcount.v; it->hash = set.vhash.v; @@ -3678,9 +3686,9 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { bool writeRecent = false; RecentStickerPack &recent(cGetRecentStickers()); for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) { - if (it->id == Stickers::CustomSetId || it->access != 0) { - ++it; - } else { + bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); + bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); + if (!installed) { // remove not mine sets from recent stickers for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { i = recent.erase(i); @@ -3689,6 +3697,10 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { ++i; } } + } + if (installed || featured) { + ++it; + } else { it = sets.erase(it); } } @@ -3720,6 +3732,90 @@ bool HistoryWidget::stickersFailed(const RPCError &error) { return true; } +void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stickers) { + Global::SetLastFeaturedStickersUpdate(getms(true)); + _featuredStickersUpdateRequest = 0; + + if (stickers.type() != mtpc_messages_featuredStickers) return; + auto &d(stickers.c_messages_featuredStickers()); + + auto &d_sets(d.vsets.c_vector().v); + + auto &setsOrder = Global::RefFeaturedStickerSetsOrder(); + setsOrder.clear(); + + auto &sets = Global::RefStickerSets(); + QMap setsToRequest; + for (auto &set : sets) { + set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing + } + for (int i = 0, l = d_sets.size(); i != l; ++i) { + if (d_sets.at(i).type() == mtpc_stickerSet) { + const auto &set(d_sets.at(i).c_stickerSet()); + auto it = sets.find(set.vid.v); + QString title = stickerSetTitle(set); + if (it == sets.cend()) { + it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded)); + } else { + it->access = set.vaccess_hash.v; + it->title = title; + it->shortName = qs(set.vshort_name); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); + it->flags = set.vflags.v | clientFlags | MTPDstickerSet_ClientFlag::f_featured; + if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { + it->count = set.vcount.v; + it->hash = set.vhash.v; + it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set + } + } + setsOrder.push_back(set.vid.v); + if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + setsToRequest.insert(set.vid.v, set.vaccess_hash.v); + } + } + } + for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) { + bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); + bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); + if (installed || featured) { + ++it; + } else { + it = sets.erase(it); + } + } + + auto &unreadFeatured = Global::RefFeaturedUnreadSets(); + unreadFeatured.clear(); + for_const (auto &unreadSetId, d.vunread.c_vector().v) { + unreadFeatured.insert(unreadSetId.v); + } + + if (Local::countFeaturedStickersHash() != d.vhash.v) { + LOG(("API Error: received featured stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countFeaturedStickersHash())); + } + + if (!setsToRequest.isEmpty() && App::api()) { + for (QMap::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { + App::api()->scheduleStickerSetRequest(i.key(), i.value()); + } + App::api()->requestStickerSets(); + } + + Local::writeStickers(); + + if (App::main()) emit App::main()->stickersUpdated(); +} + +bool HistoryWidget::featuredStickersFailed(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + LOG(("App Fail: Failed to get featured stickers!")); + + Global::SetLastFeaturedStickersUpdate(getms(true)); + _featuredStickersUpdateRequest = 0; + return true; +} + void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) { cSetLastSavedGifsUpdate(getms(true)); _savedGifsUpdateRequest = 0; @@ -5592,6 +5688,8 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC toast.text = qs(answerData.vmessage); Ui::Toast::Show(App::wnd(), toast); } + } else if (answerData.has_url()) { + UrlClickHandler::doOpen(qs(answerData.vurl)); } } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index c2474c014..ff68e4e3e 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -1005,6 +1005,10 @@ private: void stickersGot(const MTPmessages_AllStickers &stickers); bool stickersFailed(const RPCError &error); + mtpRequestId _featuredStickersUpdateRequest = 0; + void featuredStickersGot(const MTPmessages_FeaturedStickers &stickers); + bool featuredStickersFailed(const RPCError &error); + mtpRequestId _savedGifsUpdateRequest = 0; void savedGifsGot(const MTPmessages_SavedGifs &gifs); bool savedGifsFailed(const RPCError &error); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 6ab0612b8..3c2ab76d4 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3038,7 +3038,7 @@ namespace Local { void writeStickers() { if (!_working()) return; - const Stickers::Sets &sets(Global::StickerSets()); + auto &sets = Global::StickerSets(); if (sets.isEmpty()) { if (_stickersKey) { clearKey(_stickersKey); @@ -3050,31 +3050,36 @@ namespace Local { int32 setsCount = 0; QByteArray hashToWrite; quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); - for (auto i = sets.cbegin(); i != sets.cend(); ++i) { - bool notLoaded = (i->flags & MTPDstickerSet_ClientFlag::f_not_loaded); + for_const (auto &set, sets) { + bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); if (notLoaded) { - if (!(i->flags & MTPDstickerSet::Flag::f_disabled) || (i->flags & MTPDstickerSet::Flag::f_official)) { // waiting to receive + if (!(set.flags & MTPDstickerSet::Flag::f_disabled) + || (set.flags & MTPDstickerSet::Flag::f_official) + || (set.flags & MTPDstickerSet_ClientFlag::f_featured)) { // waiting to receive return; } } else { - if (i->stickers.isEmpty()) continue; + if (set.stickers.isEmpty()) continue; } // id + access + title + shortName + stickersCount + hash + flags - size += sizeof(quint64) * 2 + Serialize::stringSize(i->title) + Serialize::stringSize(i->shortName) + sizeof(quint32) + sizeof(qint32) * 2; - for (StickerPack::const_iterator j = i->stickers.cbegin(), e = i->stickers.cend(); j != e; ++j) { - size += Serialize::Document::sizeInStream(*j); + size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2; + for_const (auto &sticker, set.stickers) { + size += Serialize::Document::sizeInStream(sticker); } if (AppVersion > 9018) { size += sizeof(qint32); // emojiCount - for (StickersByEmojiMap::const_iterator j = i->emoji.cbegin(), e = i->emoji.cend(); j != e; ++j) { + for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); } } ++setsCount; } + size += sizeof(qint32) + (Global::StickerSetsOrder().size() * sizeof(quint64)); + size += sizeof(qint32) + (Global::FeaturedStickerSetsOrder().size() * sizeof(quint64)); + size += sizeof(qint32) + (Global::FeaturedUnreadSets().size() * sizeof(quint64)); if (!_stickersKey) { _stickersKey = genKey(); @@ -3083,10 +3088,17 @@ namespace Local { } EncryptedDescriptor data(size); data.stream << quint32(setsCount) << hashToWrite; - _writeStickerSet(data.stream, Stickers::CustomSetId); - for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) { - _writeStickerSet(data.stream, *i); + for_const (auto &set, sets) { + _writeStickerSet(data.stream, set.id); } + data.stream << Global::StickerSetsOrder(); + data.stream << Global::FeaturedStickerSetsOrder(); + + data.stream << qint32(Global::FeaturedUnreadSets().size()); + for_const (auto setId, Global::FeaturedUnreadSets()) { + data.stream << quint64(setId); + } + FileWriteDescriptor file(_stickersKey); file.writeEncrypted(data); } @@ -3103,17 +3115,17 @@ namespace Local { return; } - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); sets.clear(); - Stickers::Order &order(Global::RefStickerSetsOrder()); + auto &order = Global::RefStickerSetsOrder(); order.clear(); - RecentStickerPack &recent(cRefRecentStickers()); + auto &recent = cRefRecentStickers(); recent.clear(); - Stickers::Set &def(sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official)).value()); - Stickers::Set &custom(sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value()); + auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed)).value(); + auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed)).value(); QMap read; while (!stickers.stream.atEnd()) { @@ -3179,12 +3191,18 @@ namespace Local { return; } - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); sets.clear(); - Stickers::Order &order(Global::RefStickerSetsOrder()); + auto &order = Global::RefStickerSetsOrder(); order.clear(); + auto &featuredOrder = Global::RefFeaturedStickerSetsOrder(); + featuredOrder.clear(); + + auto &unreadFeatured = Global::RefFeaturedUnreadSets(); + unreadFeatured.clear(); + quint32 cnt; QByteArray hash; stickers.stream >> cnt >> hash; // ignore hash, it is counted @@ -3205,19 +3223,27 @@ namespace Local { setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); } } + if (stickers.version < 9057) { + setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); + } if (setId == Stickers::DefaultSetId) { setTitle = lang(lng_stickers_default_set); setFlags |= qFlags(MTPDstickerSet::Flag::f_official); - order.push_front(setId); + if (stickers.version < 9057) { + order.push_front(setId); + } } else if (setId == Stickers::CustomSetId) { setTitle = lang(lng_custom_stickers); } else if (setId) { - order.push_back(setId); + if (stickers.version < 9057) { + order.push_back(setId); + } } else { continue; } - Stickers::Set &set(sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value()); + + auto &set = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value(); if (scnt < 0) { // disabled not loaded set set.count = -scnt; continue; @@ -3261,6 +3287,22 @@ namespace Local { } } } + + // Read orders of installed and featured stickers. + if (stickers.version >= 9057) { + stickers.stream >> order; + stickers.stream >> featuredOrder; + + qint32 unreadCount = 0; + stickers.stream >> unreadCount; + for (int i = 0; i < unreadCount; ++i) { + quint64 setId = 0; + stickers.stream >> setId; + if (setId) { + unreadFeatured.insert(setId); + } + } + } } int32 countStickersHash(bool checkOfficial) { @@ -3284,6 +3326,19 @@ namespace Local { return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; } + int32 countFeaturedStickersHash() { + uint32 acc = 0; + auto &featured(Global::FeaturedStickerSetsOrder()); + for_const (auto setId, featured) { + acc = (acc * 20261) + uint32(setId >> 32); + acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF); + if (Global::FeaturedUnreadSets().contains(setId)) { + acc = (acc * 20261) + 1U; + } + } + return int32(acc & 0x7FFFFFFF); + } + int32 countSavedGifsHash() { uint32 acc = 0; const SavedGifs &saved(cSavedGifs()); diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index 0f9a0fd11..204ac320a 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -156,6 +156,7 @@ namespace Local { void writeStickers(); void readStickers(); int32 countStickersHash(bool checkOfficial = false); + int32 countFeaturedStickersHash(); void writeSavedGifs(); void readSavedGifs(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fab677930..6cb95b58c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3700,7 +3700,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) { if (!found) { Stickers::Sets::iterator it = sets.find(Stickers::CustomSetId); if (it == sets.cend()) { - it = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)); + it = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed)); } it->stickers.push_back(sticker); ++it->count; @@ -4646,7 +4646,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { Stickers::Sets &sets(Global::RefStickerSets()); auto it = sets.find(s.vid.v); if (it == sets.cend()) { - it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v)); + it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed)); + } else { + it->flags |= MTPDstickerSet::Flag::f_installed; } const auto &v(set.vdocuments.c_vector().v); @@ -4703,9 +4705,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateStickerSetsOrder: { - const auto &d(update.c_updateStickerSetsOrder()); - const auto &order(d.vorder.c_vector().v); - const auto &sets(Global::StickerSets()); + auto &d = update.c_updateStickerSetsOrder(); + auto &order = d.vorder.c_vector().v; + auto &sets = Global::StickerSets(); Stickers::Order result; for (int32 i = 0, l = order.size(); i < l; ++i) { if (sets.constFind(order.at(i).v) == sets.cend()) { @@ -4728,6 +4730,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { App::main()->updateStickers(); } break; + case mtpc_updateReadFeaturedStickers: { + Global::RefFeaturedUnreadSets().clear(); + Local::writeStickers(); + emit stickersUpdated(); + } break; + ////// Cloud saved GIFs case mtpc_updateSavedGifs: { cSetLastSavedGifsUpdate(0); diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 0fed750d1..96eebe2c3 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -1060,8 +1060,11 @@ enum class MTPDstickerSet_ClientFlag : int32 { // sticker set is not yet loaded f_not_loaded = (1 << 30), + // sticker set is one of featured (should be saved locally) + f_featured = (1 << 29), + // update this when adding new client side flags - MIN_FIELD = (1 << 30), + MIN_FIELD = (1 << 29), }; DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet) diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index aac8a23bf..777de9b15 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -392,6 +392,7 @@ updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#2cbd95af query_id:long user_id:int msg_id:InputBotInlineMessageID data:bytes = Update; updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; +updateReadFeaturedStickers#571d2742 = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -670,7 +671,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; -messages.botCallbackAnswer#1264f1c6 flags:# alert:flags.1?true message:flags.0?string = messages.BotCallbackAnswer; +messages.botCallbackAnswer#31fde6e4 flags:# alert:flags.1?true allow_pip:flags.2?true message:flags.0?string url:flags.3?string = messages.BotCallbackAnswer; messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; @@ -696,6 +697,9 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< draftMessageEmpty#ba4baec5 = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; +messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; +messages.featuredStickers#ed6392b7 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -826,10 +830,12 @@ messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEdi messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#a6e94f04 peer:InputPeer msg_id:int data:bytes = messages.BotCallbackAnswer; -messages.setBotCallbackAnswer#481c591a flags:# alert:flags.1?true query_id:long message:flags.0?string = Bool; +messages.setBotCallbackAnswer#70dc0fa3 flags:# alert:flags.1?true allow_pip:flags.2?true query_id:long message:flags.0?string url:flags.3?string = Bool; messages.getPeerDialogs#2d9776b9 peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; +messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; +messages.readFeaturedStickers#e21cbb = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; @@ -880,4 +886,4 @@ channels.exportMessageLink#c846d22d channel:InputChannel id:int = ExportedMessag channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates; -// LAYER 53 +// LAYER 54 diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 1e1415574..f22248eaa 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -3012,6 +3012,10 @@ void _serialize_updateDraftMessage(MTPStringLogger &to, int32 stage, int32 lev, } } +void _serialize_updateReadFeaturedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ updateReadFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_updates_state(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -5586,7 +5590,9 @@ void _serialize_messages_botCallbackAnswer(MTPStringLogger &to, int32 stage, int switch (stage) { case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; - case 2: to.add(" message: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" allow_pip: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_allow_pip) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 3: to.add(" message: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" url: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -5745,6 +5751,25 @@ void _serialize_draftMessage(MTPStringLogger &to, int32 stage, int32 lev, Types } } +void _serialize_messages_featuredStickersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ messages_featuredStickersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_messages_featuredStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_featuredStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" sets: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" unread: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6335,8 +6360,10 @@ void _serialize_messages_setBotCallbackAnswer(MTPStringLogger &to, int32 stage, switch (stage) { case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; - case 2: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" message: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" allow_pip: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_allow_pip) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 3: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" message: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 5: to.add(" url: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6361,6 +6388,10 @@ void _serialize_messages_saveDraft(MTPStringLogger &to, int32 stage, int32 lev, } } +void _serialize_messages_readFeaturedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ messages_readFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_upload_saveFilePart(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8027,6 +8058,19 @@ void _serialize_messages_getPeerDialogs(MTPStringLogger &to, int32 stage, int32 } } +void _serialize_messages_getFeaturedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getFeaturedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -8484,6 +8528,7 @@ namespace { _serializers.insert(mtpc_updateInlineBotCallbackQuery, _serialize_updateInlineBotCallbackQuery); _serializers.insert(mtpc_updateReadChannelOutbox, _serialize_updateReadChannelOutbox); _serializers.insert(mtpc_updateDraftMessage, _serialize_updateDraftMessage); + _serializers.insert(mtpc_updateReadFeaturedStickers, _serialize_updateReadFeaturedStickers); _serializers.insert(mtpc_updates_state, _serialize_updates_state); _serializers.insert(mtpc_updates_differenceEmpty, _serialize_updates_differenceEmpty); _serializers.insert(mtpc_updates_difference, _serialize_updates_difference); @@ -8697,6 +8742,8 @@ namespace { _serializers.insert(mtpc_contacts_topPeers, _serialize_contacts_topPeers); _serializers.insert(mtpc_draftMessageEmpty, _serialize_draftMessageEmpty); _serializers.insert(mtpc_draftMessage, _serialize_draftMessage); + _serializers.insert(mtpc_messages_featuredStickersNotModified, _serialize_messages_featuredStickersNotModified); + _serializers.insert(mtpc_messages_featuredStickers, _serialize_messages_featuredStickers); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -8743,6 +8790,7 @@ namespace { _serializers.insert(mtpc_messages_editInlineBotMessage, _serialize_messages_editInlineBotMessage); _serializers.insert(mtpc_messages_setBotCallbackAnswer, _serialize_messages_setBotCallbackAnswer); _serializers.insert(mtpc_messages_saveDraft, _serialize_messages_saveDraft); + _serializers.insert(mtpc_messages_readFeaturedStickers, _serialize_messages_readFeaturedStickers); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); _serializers.insert(mtpc_help_saveAppLog, _serialize_help_saveAppLog); @@ -8861,6 +8909,7 @@ namespace { _serializers.insert(mtpc_messages_getMessageEditData, _serialize_messages_getMessageEditData); _serializers.insert(mtpc_messages_getBotCallbackAnswer, _serialize_messages_getBotCallbackAnswer); _serializers.insert(mtpc_messages_getPeerDialogs, _serialize_messages_getPeerDialogs); + _serializers.insert(mtpc_messages_getFeaturedStickers, _serialize_messages_getFeaturedStickers); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 90eff1aa9..15b3d4942 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -30,7 +30,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org namespace MTP { namespace internal { -static constexpr mtpPrime CurrentLayer = 53; +static constexpr mtpPrime CurrentLayer = 54; class TypeCreator; @@ -288,6 +288,7 @@ enum { mtpc_updateInlineBotCallbackQuery = 0x2cbd95af, mtpc_updateReadChannelOutbox = 0x25d6c9c7, mtpc_updateDraftMessage = 0xee2bb969, + mtpc_updateReadFeaturedStickers = 0x571d2742, mtpc_updates_state = 0xa56c2a3e, mtpc_updates_differenceEmpty = 0x5d75a138, mtpc_updates_difference = 0xf49ca0, @@ -485,7 +486,7 @@ enum { mtpc_auth_sentCodeTypeSms = 0xc000bba2, mtpc_auth_sentCodeTypeCall = 0x5353e5a7, mtpc_auth_sentCodeTypeFlashCall = 0xab03c6d9, - mtpc_messages_botCallbackAnswer = 0x1264f1c6, + mtpc_messages_botCallbackAnswer = 0x31fde6e4, mtpc_messages_messageEditData = 0x26b5dde6, mtpc_inputBotInlineMessageID = 0x890c3d89, mtpc_inlineBotSwitchPM = 0x3c20629f, @@ -501,6 +502,8 @@ enum { mtpc_contacts_topPeers = 0x70b772a8, mtpc_draftMessageEmpty = 0xba4baec5, mtpc_draftMessage = 0xfd8e711f, + mtpc_messages_featuredStickersNotModified = 0x4ede3cf, + mtpc_messages_featuredStickers = 0xed6392b7, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -624,10 +627,12 @@ enum { mtpc_messages_editMessage = 0xce91e4ca, mtpc_messages_editInlineBotMessage = 0x130c2c85, mtpc_messages_getBotCallbackAnswer = 0xa6e94f04, - mtpc_messages_setBotCallbackAnswer = 0x481c591a, + mtpc_messages_setBotCallbackAnswer = 0x70dc0fa3, mtpc_messages_getPeerDialogs = 0x2d9776b9, mtpc_messages_saveDraft = 0xbc39e14b, mtpc_messages_getAllDrafts = 0x6a3f8d65, + mtpc_messages_getFeaturedStickers = 0x2dacca4f, + mtpc_messages_readFeaturedStickers = 0xe21cbb, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -1353,6 +1358,9 @@ class MTPDcontacts_topPeers; class MTPdraftMessage; class MTPDdraftMessage; +class MTPmessages_featuredStickers; +class MTPDmessages_featuredStickers; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1527,6 +1535,7 @@ typedef MTPBoxed MTPTopPeerCategory; typedef MTPBoxed MTPTopPeerCategoryPeers; typedef MTPBoxed MTPcontacts_TopPeers; typedef MTPBoxed MTPDraftMessage; +typedef MTPBoxed MTPmessages_FeaturedStickers; // Type classes definitions @@ -9532,6 +9541,43 @@ private: }; typedef MTPBoxed MTPDraftMessage; +class MTPmessages_featuredStickers : private mtpDataOwner { +public: + MTPmessages_featuredStickers() : mtpDataOwner(0), _type(0) { + } + MTPmessages_featuredStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDmessages_featuredStickers &_messages_featuredStickers() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_featuredStickers) throw mtpErrorWrongTypeId(_type, mtpc_messages_featuredStickers); + split(); + return *(MTPDmessages_featuredStickers*)data; + } + const MTPDmessages_featuredStickers &c_messages_featuredStickers() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_featuredStickers) throw mtpErrorWrongTypeId(_type, mtpc_messages_featuredStickers); + return *(const MTPDmessages_featuredStickers*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_featuredStickers(mtpTypeId type); + explicit MTPmessages_featuredStickers(MTPDmessages_featuredStickers *_data); + + friend class MTP::internal::TypeCreator; + + mtpTypeId _type; +}; +typedef MTPBoxed MTPmessages_FeaturedStickers; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -14274,23 +14320,28 @@ class MTPDmessages_botCallbackAnswer : public mtpDataImpl(v)); } bool is_alert() const { return vflags.v & Flag::f_alert; } + bool is_allow_pip() const { return vflags.v & Flag::f_allow_pip; } bool has_message() const { return vflags.v & Flag::f_message; } + bool has_url() const { return vflags.v & Flag::f_url; } MTPDmessages_botCallbackAnswer() { } - MTPDmessages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message) : vflags(_flags), vmessage(_message) { + MTPDmessages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message, const MTPstring &_url) : vflags(_flags), vmessage(_message), vurl(_url) { } MTPflags vflags; MTPstring vmessage; + MTPstring vurl; }; class MTPDmessages_messageEditData : public mtpDataImpl { @@ -14412,6 +14463,18 @@ public: MTPint vdate; }; +class MTPDmessages_featuredStickers : public mtpDataImpl { +public: + MTPDmessages_featuredStickers() { + } + MTPDmessages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) : vhash(_hash), vsets(_sets), vunread(_unread) { + } + + MTPint vhash; + MTPVector vsets; + MTPVector vunread; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -20204,30 +20267,35 @@ class MTPmessages_setBotCallbackAnswer { // RPC method 'messages.setBotCallbackA public: enum class Flag : int32 { f_alert = (1 << 1), + f_allow_pip = (1 << 2), f_message = (1 << 0), + f_url = (1 << 3), - MAX_FIELD = (1 << 1), + MAX_FIELD = (1 << 3), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } bool is_alert() const { return vflags.v & Flag::f_alert; } + bool is_allow_pip() const { return vflags.v & Flag::f_allow_pip; } bool has_message() const { return vflags.v & Flag::f_message; } + bool has_url() const { return vflags.v & Flag::f_url; } MTPflags vflags; MTPlong vquery_id; MTPstring vmessage; + MTPstring vurl; MTPmessages_setBotCallbackAnswer() { } MTPmessages_setBotCallbackAnswer(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setBotCallbackAnswer) { read(from, end, cons); } - MTPmessages_setBotCallbackAnswer(const MTPflags &_flags, const MTPlong &_query_id, const MTPstring &_message) : vflags(_flags), vquery_id(_query_id), vmessage(_message) { + MTPmessages_setBotCallbackAnswer(const MTPflags &_flags, const MTPlong &_query_id, const MTPstring &_message, const MTPstring &_url) : vflags(_flags), vquery_id(_query_id), vmessage(_message), vurl(_url) { } uint32 innerLength() const { - return vflags.innerLength() + vquery_id.innerLength() + (has_message() ? vmessage.innerLength() : 0); + return vflags.innerLength() + vquery_id.innerLength() + (has_message() ? vmessage.innerLength() : 0) + (has_url() ? vurl.innerLength() : 0); } mtpTypeId type() const { return mtpc_messages_setBotCallbackAnswer; @@ -20236,11 +20304,13 @@ public: vflags.read(from, end); vquery_id.read(from, end); if (has_message()) { vmessage.read(from, end); } else { vmessage = MTPstring(); } + if (has_url()) { vurl.read(from, end); } else { vurl = MTPstring(); } } void write(mtpBuffer &to) const { vflags.write(to); vquery_id.write(to); if (has_message()) vmessage.write(to); + if (has_url()) vurl.write(to); } typedef MTPBool ResponseType; @@ -20255,7 +20325,7 @@ public: } MTPmessages_SetBotCallbackAnswer(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_SetBotCallbackAnswer(const MTPflags &_flags, const MTPlong &_query_id, const MTPstring &_message) : MTPBoxed(MTPmessages_setBotCallbackAnswer(_flags, _query_id, _message)) { + MTPmessages_SetBotCallbackAnswer(const MTPflags &_flags, const MTPlong &_query_id, const MTPstring &_message, const MTPstring &_url) : MTPBoxed(MTPmessages_setBotCallbackAnswer(_flags, _query_id, _message, _url)) { } }; @@ -20396,6 +20466,76 @@ public: } }; +class MTPmessages_getFeaturedStickers { // RPC method 'messages.getFeaturedStickers' +public: + MTPint vhash; + + MTPmessages_getFeaturedStickers() { + } + MTPmessages_getFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getFeaturedStickers) { + read(from, end, cons); + } + MTPmessages_getFeaturedStickers(MTPint _hash) : vhash(_hash) { + } + + uint32 innerLength() const { + return vhash.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getFeaturedStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getFeaturedStickers) { + vhash.read(from, end); + } + void write(mtpBuffer &to) const { + vhash.write(to); + } + + typedef MTPmessages_FeaturedStickers ResponseType; +}; +class MTPmessages_GetFeaturedStickers : public MTPBoxed { +public: + MTPmessages_GetFeaturedStickers() { + } + MTPmessages_GetFeaturedStickers(const MTPmessages_getFeaturedStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetFeaturedStickers(MTPint _hash) : MTPBoxed(MTPmessages_getFeaturedStickers(_hash)) { + } +}; + +class MTPmessages_readFeaturedStickers { // RPC method 'messages.readFeaturedStickers' +public: + MTPmessages_readFeaturedStickers() { + } + MTPmessages_readFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_readFeaturedStickers) { + read(from, end, cons); + } + + uint32 innerLength() const { + return 0; + } + mtpTypeId type() const { + return mtpc_messages_readFeaturedStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_readFeaturedStickers) { + } + void write(mtpBuffer &to) const { + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_ReadFeaturedStickers : public MTPBoxed { +public: + MTPmessages_ReadFeaturedStickers() { + } + MTPmessages_ReadFeaturedStickers(const MTPmessages_readFeaturedStickers &v) : MTPBoxed(v) { + } + MTPmessages_ReadFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -22937,6 +23077,9 @@ public: inline static MTPupdate new_updateDraftMessage(const MTPPeer &_peer, const MTPDraftMessage &_draft) { return MTPupdate(new MTPDupdateDraftMessage(_peer, _draft)); } + inline static MTPupdate new_updateReadFeaturedStickers() { + return MTPupdate(mtpc_updateReadFeaturedStickers); + } inline static MTPupdates_state new_updates_state(MTPint _pts, MTPint _qts, MTPint _date, MTPint _seq, MTPint _unread_count) { return MTPupdates_state(new MTPDupdates_state(_pts, _qts, _date, _seq, _unread_count)); } @@ -23528,8 +23671,8 @@ public: inline static MTPauth_sentCodeType new_auth_sentCodeTypeFlashCall(const MTPstring &_pattern) { return MTPauth_sentCodeType(new MTPDauth_sentCodeTypeFlashCall(_pattern)); } - inline static MTPmessages_botCallbackAnswer new_messages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message) { - return MTPmessages_botCallbackAnswer(new MTPDmessages_botCallbackAnswer(_flags, _message)); + inline static MTPmessages_botCallbackAnswer new_messages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message, const MTPstring &_url) { + return MTPmessages_botCallbackAnswer(new MTPDmessages_botCallbackAnswer(_flags, _message, _url)); } inline static MTPmessages_messageEditData new_messages_messageEditData(const MTPflags &_flags) { return MTPmessages_messageEditData(new MTPDmessages_messageEditData(_flags)); @@ -23576,6 +23719,12 @@ public: inline static MTPdraftMessage new_draftMessage(const MTPflags &_flags, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPVector &_entities, MTPint _date) { return MTPdraftMessage(new MTPDdraftMessage(_flags, _reply_to_msg_id, _message, _entities, _date)); } + inline static MTPmessages_featuredStickers new_messages_featuredStickersNotModified() { + return MTPmessages_featuredStickers(mtpc_messages_featuredStickersNotModified); + } + inline static MTPmessages_featuredStickers new_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { + return MTPmessages_featuredStickers(new MTPDmessages_featuredStickers(_hash, _sets, _unread)); + } }; } // namespace internal @@ -28793,6 +28942,7 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vpeer.read(from, end); v.vdraft.read(from, end); } break; + case mtpc_updateReadFeaturedStickers: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPupdate"); } } @@ -29126,6 +29276,7 @@ inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) { case mtpc_updateInlineBotCallbackQuery: setData(new MTPDupdateInlineBotCallbackQuery()); break; case mtpc_updateReadChannelOutbox: setData(new MTPDupdateReadChannelOutbox()); break; case mtpc_updateDraftMessage: setData(new MTPDupdateDraftMessage()); break; + case mtpc_updateReadFeaturedStickers: break; default: throw mtpErrorBadTypeId(type, "MTPupdate"); } } @@ -29373,6 +29524,9 @@ inline MTPupdate MTP_updateReadChannelOutbox(MTPint _channel_id, MTPint _max_id) inline MTPupdate MTP_updateDraftMessage(const MTPPeer &_peer, const MTPDraftMessage &_draft) { return MTP::internal::TypeCreator::new_updateDraftMessage(_peer, _draft); } +inline MTPupdate MTP_updateReadFeaturedStickers() { + return MTP::internal::TypeCreator::new_updateReadFeaturedStickers(); +} inline MTPupdates_state::MTPupdates_state() : mtpDataOwner(new MTPDupdates_state()) { } @@ -34533,7 +34687,7 @@ inline MTPmessages_botCallbackAnswer::MTPmessages_botCallbackAnswer() : mtpDataO inline uint32 MTPmessages_botCallbackAnswer::innerLength() const { const MTPDmessages_botCallbackAnswer &v(c_messages_botCallbackAnswer()); - return v.vflags.innerLength() + (v.has_message() ? v.vmessage.innerLength() : 0); + return v.vflags.innerLength() + (v.has_message() ? v.vmessage.innerLength() : 0) + (v.has_url() ? v.vurl.innerLength() : 0); } inline mtpTypeId MTPmessages_botCallbackAnswer::type() const { return mtpc_messages_botCallbackAnswer; @@ -34545,17 +34699,19 @@ inline void MTPmessages_botCallbackAnswer::read(const mtpPrime *&from, const mtp MTPDmessages_botCallbackAnswer &v(_messages_botCallbackAnswer()); v.vflags.read(from, end); if (v.has_message()) { v.vmessage.read(from, end); } else { v.vmessage = MTPstring(); } + if (v.has_url()) { v.vurl.read(from, end); } else { v.vurl = MTPstring(); } } inline void MTPmessages_botCallbackAnswer::write(mtpBuffer &to) const { const MTPDmessages_botCallbackAnswer &v(c_messages_botCallbackAnswer()); v.vflags.write(to); if (v.has_message()) v.vmessage.write(to); + if (v.has_url()) v.vurl.write(to); } inline MTPmessages_botCallbackAnswer::MTPmessages_botCallbackAnswer(MTPDmessages_botCallbackAnswer *_data) : mtpDataOwner(_data) { } Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDmessages_botCallbackAnswer::Flags) -inline MTPmessages_botCallbackAnswer MTP_messages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message) { - return MTP::internal::TypeCreator::new_messages_botCallbackAnswer(_flags, _message); +inline MTPmessages_botCallbackAnswer MTP_messages_botCallbackAnswer(const MTPflags &_flags, const MTPstring &_message, const MTPstring &_url) { + return MTP::internal::TypeCreator::new_messages_botCallbackAnswer(_flags, _message, _url); } inline MTPmessages_messageEditData::MTPmessages_messageEditData() : mtpDataOwner(new MTPDmessages_messageEditData()) { @@ -34896,6 +35052,59 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDdraftMessage::Flags) inline MTPdraftMessage MTP_draftMessage(const MTPflags &_flags, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPVector &_entities, MTPint _date) { return MTP::internal::TypeCreator::new_draftMessage(_flags, _reply_to_msg_id, _message, _entities, _date); } + +inline uint32 MTPmessages_featuredStickers::innerLength() const { + switch (_type) { + case mtpc_messages_featuredStickers: { + const MTPDmessages_featuredStickers &v(c_messages_featuredStickers()); + return v.vhash.innerLength() + v.vsets.innerLength() + v.vunread.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPmessages_featuredStickers::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPmessages_featuredStickers::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_messages_featuredStickersNotModified: _type = cons; break; + case mtpc_messages_featuredStickers: _type = cons; { + if (!data) setData(new MTPDmessages_featuredStickers()); + MTPDmessages_featuredStickers &v(_messages_featuredStickers()); + v.vhash.read(from, end); + v.vsets.read(from, end); + v.vunread.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPmessages_featuredStickers"); + } +} +inline void MTPmessages_featuredStickers::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_messages_featuredStickers: { + const MTPDmessages_featuredStickers &v(c_messages_featuredStickers()); + v.vhash.write(to); + v.vsets.write(to); + v.vunread.write(to); + } break; + } +} +inline MTPmessages_featuredStickers::MTPmessages_featuredStickers(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_messages_featuredStickersNotModified: break; + case mtpc_messages_featuredStickers: setData(new MTPDmessages_featuredStickers()); break; + default: throw mtpErrorBadTypeId(type, "MTPmessages_featuredStickers"); + } +} +inline MTPmessages_featuredStickers::MTPmessages_featuredStickers(MTPDmessages_featuredStickers *_data) : mtpDataOwner(_data), _type(mtpc_messages_featuredStickers) { +} +inline MTPmessages_featuredStickers MTP_messages_featuredStickersNotModified() { + return MTP::internal::TypeCreator::new_messages_featuredStickersNotModified(); +} +inline MTPmessages_featuredStickers MTP_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { + return MTP::internal::TypeCreator::new_messages_featuredStickers(_hash, _sets, _unread); +} inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } inline MTPDmessage::Flags mtpCastFlags(MTPflags flags) { return mtpCastFlags(flags.v); } inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 4b3e006d7..5d62a0538 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -890,13 +890,13 @@ bool StickerData::setInstalled() const { switch (set.type()) { case mtpc_inputStickerSetID: { auto it = Global::StickerSets().constFind(set.c_inputStickerSetID().vid.v); - return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_disabled); + return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_disabled) && (it->flags & MTPDstickerSet::Flag::f_installed); } break; case mtpc_inputStickerSetShortName: { QString name = qs(set.c_inputStickerSetShortName().vshort_name).toLower(); for (auto it = Global::StickerSets().cbegin(), e = Global::StickerSets().cend(); it != e; ++it) { if (it->shortName.toLower() == name) { - return !(it->flags & MTPDstickerSet::Flag::f_disabled); + return !(it->flags & MTPDstickerSet::Flag::f_disabled) && (it->flags & MTPDstickerSet::Flag::f_installed); } } } break; diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h index e407b4fc2..02df17957 100644 --- a/Telegram/SourceFiles/ui/text/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -291,7 +291,7 @@ inline bool chIsSpace(QChar ch, bool rich = false) { return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation); } inline bool chIsDiac(QChar ch) { // diac and variation selectors - return (ch.category() == QChar::Mark_NonSpacing) || (ch.unicode() == 1652); + return (ch.category() == QChar::Mark_NonSpacing) || (ch == 1652) || (ch >= 64606 && ch <= 64611); } inline bool chIsBad(QChar ch) { return (ch == 0) || (ch >= 8232 && ch < 8237) || (ch >= 65024 && ch < 65040 && ch != 65039) || (ch >= 127 && ch < 160 && ch != 156) || (cPlatform() == dbipMac && ch >= 0x0B00 && ch <= 0x0B7F && chIsDiac(ch) && cIsElCapitan()); // tmp hack see https://bugreports.qt.io/browse/QTBUG-48910 diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 449edf2cf..a50bf242c 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2375,7 +2375,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.56; + TDESKTOP_VERSION = 0.9.57; }; name = Release; }; @@ -2516,7 +2516,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.56; + TDESKTOP_VERSION = 0.9.57; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index 25a0b65be..4a048eef7 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9056 +AppVersion 9057 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.56 -AppVersionStr 0.9.56 -AlphaChannel 0 +AppVersionStrSmall 0.9.57 +AppVersionStr 0.9.57 +AlphaChannel 1 BetaVersion 0 From cd696ade4ee785b9ad39093e22b31e431a3fb394 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 Jun 2016 21:05:38 +0300 Subject: [PATCH 03/60] Featured stickers fully supported (unread badges, box, adding, etc). --- Telegram/Resources/art/sprite.png | Bin 180708 -> 180375 bytes Telegram/Resources/art/sprite_200x.png | Bin 244667 -> 244010 bytes Telegram/Resources/basic.style | 21 +- .../Resources/icons/mediaview_save_check.png | Bin 0 -> 454 bytes .../icons/mediaview_save_check@2x.png | Bin 0 -> 882 bytes Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/apiwrap.cpp | 6 +- Telegram/SourceFiles/app.cpp | 3 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 548 +++++++++++++----- Telegram/SourceFiles/boxes/stickersetbox.h | 227 +++++--- Telegram/SourceFiles/dialogs/dialogs.style | 1 - .../SourceFiles/dialogs/dialogs_layout.cpp | 6 +- Telegram/SourceFiles/dialogs/dialogs_layout.h | 2 + Telegram/SourceFiles/dropdown.cpp | 18 +- Telegram/SourceFiles/dropdown.h | 1 + Telegram/SourceFiles/facades.cpp | 4 +- Telegram/SourceFiles/facades.h | 10 +- .../history/field_autocomplete.cpp | 4 +- Telegram/SourceFiles/history/history.style | 4 +- Telegram/SourceFiles/historywidget.cpp | 37 +- Telegram/SourceFiles/localstorage.cpp | 45 +- Telegram/SourceFiles/mainwidget.cpp | 14 +- Telegram/SourceFiles/mediaview.cpp | 2 +- Telegram/SourceFiles/mtproto/core_types.h | 5 +- 24 files changed, 656 insertions(+), 304 deletions(-) create mode 100644 Telegram/Resources/icons/mediaview_save_check.png create mode 100644 Telegram/Resources/icons/mediaview_save_check@2x.png diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 809d0201ac72064ec1d74fc61dcf930bcdc1cee1..932c1d9903fca15bd7022f818909ed8c20104a1b 100644 GIT binary patch delta 14271 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 L=v>ZMvkd)zRWe*1 delta 35306 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 T*y;!c_|ds!pi!)D7xDi9hL$$8 diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 36cd767e9..ca5150dfd 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1838,6 +1838,20 @@ stickersReorderFg: #777; stickersRowDisabledOpacity: 0.4; stickersRowDuration: 200; +stickersFeaturedHeight: 32px; +stickersFeaturedFont: contactsNameFont; +stickersFeaturedPosition: point(16px, 6px); +stickersFeaturedBadgeFont: semiboldFont; +stickersFeaturedBadgeSize: 21px; +stickersFeaturedPen: contactsNewItemFg; +stickersFeaturedUnreadBg: msgFileInBg; +stickersFeaturedUnreadSize: 5px; +stickersFeaturedUnreadSkip: 5px; +stickersFeaturedUnreadTop: 7px; +stickersFeaturedInstalled: icon { + { "mediaview_save_check", #40ace3 } +}; + emojiScroll: flatScroll(solidScroll) { deltat: 48px; } @@ -1861,6 +1875,9 @@ stickersSettings: sprite(140px, 124px, 21px, 22px); savedGifsOver: sprite(329px, 286px, 21px, 22px); savedGifsActive: sprite(350px, 286px, 21px, 22px); +stickersSettingsUnreadSize: 17px; +stickersSettingsUnreadPosition: point(4px, 5px); + emojiPanCategories: #f7f7f7; rbEmoji: flatCheckbox { @@ -2133,7 +2150,9 @@ mvCaptionRadius: 2px; mvCaptionBg: #11111180; mvCaptionFont: font(fsize); -medviewSaveMsgCheck: sprite(311px, 309px, 22px, 18px); +medviewSaveMsgCheck: icon { + { "mediaview_save_check", #ffffff } +}; medviewSaveMsgFont: font(16px); medviewSaveMsgPadding: margins(55px, 19px, 29px, 20px); medviewSaveMsgCheckPos: point(23px, 21px); diff --git a/Telegram/Resources/icons/mediaview_save_check.png b/Telegram/Resources/icons/mediaview_save_check.png new file mode 100644 index 0000000000000000000000000000000000000000..33be1af78102c3ad88a4e4164246cd3eaaa749e8 GIT binary patch literal 454 zcmV;%0XhDOP)rAFtUpcX-^5JYpj>#o}7636C# z3Ng>&z2`8=COf;|e%Z{9ZPFDixegCz5WrTSOv}TgJPzR4T!?ZIbTy zdqktrdyXL#3ZYmmz9h}(^RO(7q=sQ29*;k=vMgh@T7h$pbUOW~G?U4Ib52szG?7ZB zp7V>vfZ*ba5#+hdQEFW2vn=pSG(sj z@%#N4kH;iA9*@}Vc9c7g165VMwB=7CNfI`j4W322-S*n=?uA4m;d*@l=yW=sKhcBu w`m?Svo6X*lJ{L2_FdPmsolfEN`Q8$r0XFKIpfQA|kpKVy07*qoM6N<$g7Z4j9smFU literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/mediaview_save_check@2x.png b/Telegram/Resources/icons/mediaview_save_check@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d9d3bd85b711e17f3b232dc4d10a9b7402559f86 GIT binary patch literal 882 zcmV-&1C9KNP)*^y^>arqb|)9ySjwHa)pEgV zMJbBnLWnfdNu6@+?R;GH>9p9- zwZFe#Jm9ghF&Kuy#opcBp|Y~FIKch={a}o7vGI5uRaI4=*B2Js-Q5jc*ST0#RZ&-0 z_r0cYSVd8wX&M)+X&RcEnts<62HV)!h}YLwE>_odw6(RRtF<5N_xthk^1{V3#t;Yu zc=h&TeLf!^A0N3`!!R&3G$hKf59{@MaeI5q#R5Pu7|fJo57y)H;PUcPh@G9CHIw!8 zg_4qzoMYW?H%?Dah1kW#MYDNdPZUML`T032Dk^e@Eh{TSEEW@DS5{W6WF{Z<^z`8E z?F|4Bi^bq{I_V(a#IIQQdudj1M8Dr?`>ar9lNfK69SB2PUGzyo? z^|xQo;c(#a@Q@p>sw!$~YD@<%FE0zRCnqOxyWQCwx8yzY=H`a`Ay-#d@OV5K!{+AZ zgxHIV3sX56!9;}G+FD`f6p2KHXKrF*LWsS-zJ}N9%@+FwCL#m^0b$qAyI+osj0o%Q z@9*LB`Er5%2__=U&(90LsqyjgAJ_Nx_6nzkr>7_Q{r+5GQ^ArXVQXuP3wVEjhoUGT zB6M_gWSkZn8X9tmO$8GXTrL-mkB_;K=jUe(3=CwP7Ft?ba*a(7CQ7z~4-XGqkmwlb zx{l7y&U|3`V4~z0^VwgRU5qjG_4VZoD+Uu01_uXCV-3T==;&xZv6;a{gr%h=Q{c(T z$$VqYfn{07?(VJ-8w!Q&z?uUSC8vh7v$M3=gM$ORu;#%;$%!|SNTgsbHsJPwi3q`9 z@IN*hjaqNOv)nQf{UZzO=;(;b%gZT|NKk8QD>25blxMv@0D1;L)B#k0YybcN07*qo IM6N<$f{pC3P5=M^ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9f87cedca..789bb5709 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -685,9 +685,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Manage and reorder sticker packs"; "lng_stickers_packs" = "Sticker Packs"; "lng_stickers_reorder" = "Click and drag to reorder sticker packs"; +"lng_stickers_featured" = "Featured Stickers"; "lng_stickers_remove" = "Delete"; "lng_stickers_return" = "Undo"; "lng_stickers_restore" = "Restore"; +"lng_stickers_add" = "Add"; "lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; "lng_in_dlg_photo" = "Photo"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e0dc51135..e6c12b071 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -924,10 +924,10 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) _stickerSetRequests.remove(setId); if (result.type() != mtpc_messages_stickerSet) return; - const auto &d(result.c_messages_stickerSet()); + auto &d(result.c_messages_stickerSet()); if (d.vset.type() != mtpc_stickerSet) return; - const auto &s(d.vset.c_stickerSet()); + auto &s(d.vset.c_stickerSet()); auto &sets = Global::RefStickerSets(); auto it = sets.find(setId); @@ -937,7 +937,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) it->hash = s.vhash.v; it->shortName = qs(s.vshort_name); it->title = stickerSetTitle(s); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); it->flags = s.vflags.v | clientFlags; it->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 256421f30..094759909 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1979,6 +1979,9 @@ namespace { Global::SetStickerSets(Stickers::Sets()); Global::SetStickerSetsOrder(Stickers::Order()); Global::SetLastStickersUpdate(0); + Global::SetFeaturedStickerSetsOrder(Stickers::Order()); + Global::SetFeaturedStickerSetsUnreadCount(0); + Global::SetLastFeaturedStickersUpdate(0); cSetSavedGifs(SavedGifs()); cSetLastSavedGifsUpdate(0); cSetReportSpamStatuses(ReportSpamStatuses()); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 0f23423d5..f87cf0a9b 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "apiwrap.h" #include "localstorage.h" +#include "dialogs/dialogs_layout.h" StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() , _input(set) { @@ -47,21 +48,21 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); _emoji.clear(); if (set.type() == mtpc_messages_stickerSet) { - const auto &d(set.c_messages_stickerSet()); - const auto &v(d.vdocuments.c_vector().v); + auto &d(set.c_messages_stickerSet()); + auto &v(d.vdocuments.c_vector().v); _pack.reserve(v.size()); - for (int32 i = 0, l = v.size(); i < l; ++i) { - DocumentData *doc = App::feedDocument(v.at(i)); + for (int i = 0, l = v.size(); i < l; ++i) { + auto doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; _pack.push_back(doc); } - const auto &packs(d.vpacks.c_vector().v); - for (int32 i = 0, l = packs.size(); i < l; ++i) { + auto &packs(d.vpacks.c_vector().v); + for (int i = 0, l = packs.size(); i < l; ++i) { if (packs.at(i).type() != mtpc_stickerPack) continue; - const auto &pack(packs.at(i).c_stickerPack()); - if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { - const auto &stickers(pack.vdocuments.c_vector().v); + auto &pack(packs.at(i).c_stickerPack()); + if (auto e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + auto &stickers(pack.vdocuments.c_vector().v); StickerPack p; p.reserve(stickers.size()); for (int32 j = 0, c = stickers.size(); j < c; ++j) { @@ -74,7 +75,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } } if (d.vset.type() == mtpc_stickerSet) { - const auto &s(d.vset.c_stickerSet()); + auto &s(d.vset.c_stickerSet()); _setTitle = stickerSetTitle(s); _title = st::boxTitleFont->elided(_setTitle, width() - st::boxTitlePosition.x() - st::boxTitleHeight); _setShortName = qs(s.vshort_name); @@ -83,6 +84,15 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _setCount = s.vcount.v; _setHash = s.vhash.v; _setFlags = s.vflags.v; + auto &sets = Global::RefStickerSets(); + auto it = sets.find(_setId); + if (it != sets.cend()) { + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_unread); + _setFlags |= clientFlags; + it->flags = _setFlags; + it->stickers = _pack; + it->emoji = _emoji; + } } } @@ -116,13 +126,13 @@ void StickerSetInner::installDone(const MTPBool &result) { if (it == sets.cend()) { it = sets.insert(_setId, Stickers::Set(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)); } else { - it.value().flags = _setFlags; + it->flags = _setFlags; } - it.value().stickers = _pack; - it.value().emoji = _emoji; + it->stickers = _pack; + it->emoji = _emoji; auto &order = Global::RefStickerSetsOrder(); - int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId); + int insertAtIndex = 0, currentIndex = order.indexOf(_setId); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { order.removeAt(currentIndex); @@ -132,8 +142,8 @@ void StickerSetInner::installDone(const MTPBool &result) { auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { - for (int32 i = 0, l = _pack.size(); i < l; ++i) { - int32 removeIndex = custom->stickers.indexOf(_pack.at(i)); + for_const (auto sticker, _pack) { + int removeIndex = custom->stickers.indexOf(sticker); if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); } if (custom->stickers.isEmpty()) { @@ -141,8 +151,8 @@ void StickerSetInner::installDone(const MTPBool &result) { } } Local::writeStickers(); + emit App::main()->stickersUpdated(); emit installed(_setId); - Ui::hideLayer(); } bool StickerSetInner::installFailed(const RPCError &error) { @@ -295,7 +305,7 @@ StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st:: connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_inner, SIGNAL(installed(uint64)), this, SIGNAL(installed(uint64))); + connect(&_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); onStickersUpdated(); @@ -304,6 +314,11 @@ StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st:: prepare(); } +void StickerSetBox::onInstalled(uint64 setId) { + emit installed(setId); + onClose(); +} + void StickerSetBox::onStickersUpdated() { showAll(); } @@ -392,28 +407,44 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) { } } -StickersInner::StickersInner() : TWidget() +namespace internal { + +StickersInner::StickersInner(StickersBox::Section section) : TWidget() +, _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _aboveShadowFadeStart(0) -, _aboveShadowFadeOpacity(0, 0) , _a_shifting(animation(this, &StickersInner::step_shifting)) , _itemsTop(st::membersPadding.top()) -, _saving(false) -, _removeSel(-1) -, _removeDown(-1) , _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) , _returnWidth(st::normalFont->width(lang(lng_stickers_return))) , _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _selected(-1) -, _started(-1) -, _dragging(-1) -, _above(-1) -, _aboveShadow(st::boxShadow) -, _scrollbar(0) { +, _addText(lang(lng_stickers_add).toUpper()) +, _addWidth(st::defaultActiveButton.font->width(_addText)) +, _aboveShadow(st::boxShadow) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); setMouseTracking(true); } +void StickersInner::paintFeaturedButton(Painter &p) const { + if (!_featuredHeight) return; + + if (_selected == -1) { + p.fillRect(0, st::membersPadding.top(), width(), _featuredHeight, st::contactsBgOver); + } + p.setFont(st::stickersFeaturedFont); + p.setPen(st::stickersFeaturedPen); + p.drawTextLeft(st::stickersFeaturedPosition.x(), st::membersPadding.top() + st::stickersFeaturedPosition.y(), width(), lang(lng_stickers_featured)); + + if (auto unread = Global::FeaturedStickerSetsUnreadCount()) { + Dialogs::Layout::UnreadBadgeStyle unreadSt; + unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; + unreadSt.size = st::stickersFeaturedBadgeSize; + int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x()); + if (rtl()) unreadRight = width() - unreadRight; + int unreadTop = st::membersPadding.top() + (_featuredHeight - st::stickersFeaturedBadgeSize) / 2; + Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); + } +} + void StickersInner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); @@ -422,10 +453,13 @@ void StickersInner::paintEvent(QPaintEvent *e) { p.fillRect(r, st::white); p.setClipRect(r); + + paintFeaturedButton(p); + if (_rows.isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); - p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, _featuredHeight, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } else { p.translate(0, _itemsTop); @@ -452,33 +486,50 @@ void StickersInner::paintRow(Painter &p, int32 index) { int32 xadd = 0, yadd = s->yadd.current(); if (xadd || yadd) p.translate(xadd, yadd); - bool removeSel = (index == _removeSel && (_removeDown < 0 || index == _removeDown)); - bool removeDown = removeSel && (index == _removeDown); + if (_section == Section::Installed) { + bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown)); + bool removeDown = removeSel && (index == _actionDown); - p.setFont((removeSel ? st::linkOverFont : st::linkFont)->f); - if (removeDown) { - p.setPen(st::btnDefLink.downColor->p); - } else { - p.setPen(st::btnDefLink.color->p); - } - int32 remWidth = s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth; - QString remText = lang(s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove); - p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); - - if (index == _above) { - float64 current = _aboveShadowFadeOpacity.current(); - if (_started >= 0) { - float64 o = aboveShadowOpacity(); - if (o > current) { - _aboveShadowFadeOpacity = anim::fvalue(o, o); - current = o; - } + p.setFont(removeSel ? st::linkOverFont : st::linkFont); + if (removeDown) { + p.setPen(st::btnDefLink.downColor); + } else { + p.setPen(st::btnDefLink.color); } - p.setOpacity(current); - QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2))); - _aboveShadow.paint(p, row, st::boxShadowShift); - p.fillRect(row, st::white); - p.setOpacity(1); + int32 remWidth = s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth; + QString remText = lang(s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove); + p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); + + if (index == _above) { + float64 current = _aboveShadowFadeOpacity.current(); + if (_started >= 0) { + float64 o = aboveShadowOpacity(); + if (o > current) { + _aboveShadowFadeOpacity = anim::fvalue(o, o); + current = o; + } + } + p.setOpacity(current); + QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2))); + _aboveShadow.paint(p, row, st::boxShadowShift); + p.fillRect(row, st::white); + p.setOpacity(1); + } + } else if (s->installed) { + int addw = _addWidth - st::defaultActiveButton.width; + int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2); + int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; + st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); + } else { + int addw = _addWidth - st::defaultActiveButton.width; + int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; + QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); + + App::roundRect(p, add, st::defaultActiveButton.textBgOver); + p.setFont(st::defaultActiveButton.font); + p.setPen(st::defaultActiveButton.textFg); + p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + st::defaultActiveButton.textTop, width(), _addText, _addWidth); } if (s->disabled) p.setOpacity(st::stickersRowDisabledOpacity); @@ -487,15 +538,28 @@ void StickersInner::paintRow(Painter &p, int32 index) { QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh)); p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix); } + + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namey = st::contactsPadding.top() + st::contactsNameTop; + int statusx = namex; + int statusy = st::contactsPadding.top() + st::contactsStatusTop; + + if (s->unread) { + p.setPen(Qt::NoPen); + p.setBrush(st::stickersFeaturedUnreadBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(namex, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + namex += st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + } p.setFont(st::contactsNameFont); p.setPen(st::black); - - int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsNameTop, width(), s->title); + p.drawTextLeft(namex, namey, width(), s->title); p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), lng_stickers_count(lt_count, s->count)); + p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count)); p.setOpacity(1); if (xadd || yadd) p.translate(-xadd, -yadd); @@ -506,10 +570,12 @@ void StickersInner::mousePressEvent(QMouseEvent *e) { if (_dragging >= 0) mouseReleaseEvent(e); _mouse = e->globalPos(); onUpdateSelected(); - if (_removeSel >= 0) { - _removeDown = _removeSel; - update(0, _itemsTop + _removeSel * _rowHeight, width(), _rowHeight); - } else if (_selected >= 0) { + + _pressed = _selected; + if (_actionSel >= 0) { + _actionDown = _actionSel; + update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + } else if (_selected >= 0 && _section == Section::Installed) { _above = _dragging = _started = _selected; _dragStart = mapFromGlobal(_mouse); } @@ -557,15 +623,39 @@ void StickersInner::onUpdateSelected() { emit checkDraggingScroll(local.y()); } else { bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local); - _selected = in ? floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1) : -1; - int32 removeSel = -1; + int selected = -2; + int actionSel = -1; + if (in) { + selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); - if (_selected >= 0) { - int32 remw = _rows.at(_selected)->disabled ? (_rows.at(_selected)->official ? _restoreWidth : _returnWidth) : _removeWidth; - QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); - removeSel = rem.contains(local.x(), local.y() - _itemsTop - _selected * _rowHeight) ? _selected : -1; + if (_section == Section::Installed) { + int remw = _rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth; + QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); + actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; + } else if (_rows.at(selected)->installed) { + actionSel = -1; + } else { + int addw = _addWidth - st::defaultActiveButton.width; + int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; + QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); + actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; + } + } else if (_featuredHeight && QRect(0, st::membersPadding.top(), width(), _featuredHeight).contains(local)) { + selected = -1; + } else { + selected = -2; } - setRemoveSel(removeSel); + if (_selected != selected) { + if ((_selected == -1) != (selected == -1)) { + update(); + } + if (_section == Section::Featured && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { + setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); + } + _selected = selected; + } + setActionSel(actionSel); emit noDraggingScroll(); } } @@ -579,11 +669,23 @@ float64 StickersInner::aboveShadowOpacity() const { } void StickersInner::mouseReleaseEvent(QMouseEvent *e) { + auto pressed = _pressed; + _pressed = -2; + + if (_section == Section::Featured && _selected < 0 && pressed >= 0) { + setCursor(style::cur_default); + } + if (_saving) return; + _mouse = e->globalPos(); onUpdateSelected(); - if (_removeDown == _removeSel && _removeSel >= 0) { - _rows[_removeDown]->disabled = !_rows[_removeDown]->disabled; + if (_actionDown == _actionSel && _actionSel >= 0) { + if (_section == Section::Installed) { + _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; + } else { + installSet(_rows[_actionDown]->id); + } } else if (_dragging >= 0) { QPoint local(mapFromGlobal(_mouse)); _rows[_dragging]->yadd.start(0); @@ -594,13 +696,66 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { } _dragging = _started = -1; + } else if (pressed == _selected) { + if (_selected == -1) { + _selected = -2; + Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); + } else if (_selected >= 0 && _section == Section::Featured) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(_rows.at(pressed)->id); + if (it != sets.cend()) { + _selected = -2; + Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers); + } + } } - if (_removeDown >= 0) { - update(0, _itemsTop + _removeDown * _rowHeight, width(), _rowHeight); - _removeDown = -1; + if (_actionDown >= 0) { + update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); + _actionDown = -1; } } +void StickersInner::leaveEvent(QEvent *e) { + _mouse = QPoint(-1, -1); + onUpdateSelected(); +} + +void StickersInner::installSet(uint64 setId) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuild(); + return; + } + + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse())); + + it->flags &= ~(MTPDstickerSet::Flag::f_disabled | MTPDstickerSet_ClientFlag::f_unread); + it->flags |= MTPDstickerSet::Flag::f_installed; + + auto &order = Global::RefStickerSetsOrder(); + int insertAtIndex = 0, currentIndex = order.indexOf(setId); + if (currentIndex != insertAtIndex) { + if (currentIndex > 0) { + order.removeAt(currentIndex); + } + order.insert(insertAtIndex, setId); + } + + auto custom = sets.find(Stickers::CustomSetId); + if (custom != sets.cend()) { + for_const (auto sticker, it->stickers) { + int removeIndex = custom->stickers.indexOf(sticker); + if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); + } + if (custom->stickers.isEmpty()) { + sets.erase(custom); + } + } + Local::writeStickers(); + emit App::main()->stickersUpdated(); +} + void StickersInner::step_shifting(uint64 ms, bool timer) { bool animating = false; int32 updateMin = -1, updateMax = 0; @@ -653,72 +808,123 @@ void StickersInner::clear() { _aboveShadowFadeStart = 0; _aboveShadowFadeOpacity = anim::fvalue(0, 0); _a_shifting.stop(); - _above = _dragging = _started = -1; - _selected = -1; - _removeDown = -1; - setRemoveSel(-1); - update(); +_above = _dragging = _started = -1; +_selected = -2; +_pressed = -2; +_actionDown = -1; +setActionSel(-1); +update(); } -void StickersInner::setRemoveSel(int32 removeSel) { - if (removeSel != _removeSel) { - if (_removeSel >= 0) update(0, _itemsTop + _removeSel * _rowHeight, width(), _rowHeight); - _removeSel = removeSel; - if (_removeSel >= 0) update(0, _itemsTop + _removeSel * _rowHeight, width(), _rowHeight); - setCursor((_removeSel >= 0 && (_removeDown < 0 || _removeDown == _removeSel)) ? style::cur_pointer : style::cur_default); +void StickersInner::setActionSel(int32 actionSel) { + if (actionSel != _actionSel) { + if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + _actionSel = actionSel; + if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + if (_section == Section::Installed) { + setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default); + } } } void StickersInner::rebuild() { QList rows, rowsDisabled; - int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int32 namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x() - qMax(qMax(_returnWidth, _removeWidth), _restoreWidth); + _itemsTop = st::membersPadding.top(); + _featuredHeight = 0; + if (_section == Section::Installed && !Global::FeaturedStickerSetsOrder().isEmpty()) { + _featuredHeight = st::stickersFeaturedHeight; + _itemsTop += _featuredHeight + st::membersPadding.top(); + } + + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); + if (_section == Section::Installed) { + namew -= qMax(qMax(_returnWidth, _removeWidth), _restoreWidth); + } else { + namew -= _addWidth - st::defaultActiveButton.width; + } clear(); - auto &order = Global::StickerSetsOrder(); + auto &order = (_section == Section::Installed) ? Global::StickerSetsOrder() : Global::FeaturedStickerSetsOrder(); _animStartTimes.reserve(order.size()); auto &sets = Global::StickerSets(); - for (int i = 0, l = order.size(); i < l; ++i) { - auto it = sets.constFind(order.at(i)); - if (it != sets.cend()) { - bool disabled = (it->flags & MTPDstickerSet::Flag::f_disabled); + for_const (auto setId, order) { + auto it = sets.constFind(setId); + if (it == sets.cend()) { + continue; + } - DocumentData *sticker = it->stickers.isEmpty() ? 0 : it->stickers.at(0); - int32 pixw = 0, pixh = 0; - if (sticker) { - pixw = sticker->thumb->width(); - pixh = sticker->thumb->height(); - if (pixw > st::contactsPhotoSize) { - if (pixw > pixh) { - pixh = (pixh * st::contactsPhotoSize) / pixw; - pixw = st::contactsPhotoSize; - } else { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } else if (pixh > st::contactsPhotoSize) { + bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); + bool disabled = (_section == Section::Installed) && (it->flags & MTPDstickerSet::Flag::f_disabled); + bool official = (it->flags & MTPDstickerSet::Flag::f_official); + bool unread = (_section == Section::Featured) && _unreadSets.contains(it->id); + if (!unread && _section == Section::Featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { + unread = true; + _unreadSets.insert(it->id); + } + + DocumentData *sticker = it->stickers.isEmpty() ? 0 : it->stickers.at(0); + int32 pixw = 0, pixh = 0; + if (sticker) { + pixw = sticker->thumb->width(); + pixh = sticker->thumb->height(); + if (pixw > st::contactsPhotoSize) { + if (pixw > pixh) { + pixh = (pixh * st::contactsPhotoSize) / pixw; + pixw = st::contactsPhotoSize; + } else { pixw = (pixw * st::contactsPhotoSize) / pixh; pixh = st::contactsPhotoSize; } + } else if (pixh > st::contactsPhotoSize) { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; } - QString title = it->title; - int32 titleWidth = st::contactsNameFont->width(title); - if (titleWidth > namew) { - title = st::contactsNameFont->elided(title, namew); - } - bool official = (it->flags & MTPDstickerSet::Flag::f_official); - (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, official, disabled, pixw, pixh)); - _animStartTimes.push_back(0); - if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(it->id, it->access); - } + } + QString title = it->title; + int32 titleWidth = st::contactsNameFont->width(title); + if (titleWidth > namew) { + title = st::contactsNameFont->elided(title, namew); + } + (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, installed, official, unread, disabled, pixw, pixh)); + _animStartTimes.push_back(0); + if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(it->id, it->access); } } App::api()->requestStickerSets(); _rows = rows + rowsDisabled; resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); + + if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { + Global::SetFeaturedStickerSetsUnreadCount(0); + for (auto &set : Global::RefStickerSets()) { + set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; + } + MTP::send(MTPmessages_ReadFeaturedStickers(), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail)); + } +} + +void StickersInner::readFeaturedDone(const MTPBool &result) { + Local::writeStickers(); + emit App::main()->stickersUpdated(); +} + +bool StickersInner::readFeaturedFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + int unreadCount = 0; + for_const (auto &set, Global::StickerSets()) { + if (!(set.flags & MTPDstickerSet::Flag::f_installed)) { + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + ++unreadCount; + } + } + } + Global::SetFeaturedStickerSetsUnreadCount(unreadCount); + return true; } QVector StickersInner::getOrder() const { @@ -755,27 +961,38 @@ StickersInner::~StickersInner() { clear(); } -StickersBox::StickersBox() : ItemListBox(st::boxScroll) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +} // namespace internal + +StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) +, _section(section) +, _inner(section) , _reorderRequest(0) , _topShadow(this, st::contactsAboutShadow) -, _bottomShadow(this) , _scrollDelta(0) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) -, _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) -, _aboutHeight(st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom()) { - ItemListBox::init(&_inner, st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(), st::boxTitleHeight + _aboutHeight); +, _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) { + + int bottomSkip = st::boxPadding.bottom(); + if (_section == Section::Installed) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + + _save = new BoxButton(this, lang(lng_settings_save), st::defaultBoxButton); + connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); + + _cancel = new BoxButton(this, lang(lng_cancel), st::cancelBoxButton); + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + _bottomShadow = new ScrollableBoxShadow(this); + bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + } + ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); App::main()->updateStickers(); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - - connect(&_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); - connect(&_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); + connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); + connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); _scrollTimer.setSingleShot(false); @@ -785,7 +1002,11 @@ StickersBox::StickersBox() : ItemListBox(st::boxScroll) } int32 StickersBox::countHeight() const { - return st::boxTitleHeight + _aboutHeight + _inner.height() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); + int bottomSkip = st::boxPadding.bottom(); + if (_section == Section::Installed) { + bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + } + return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip; } void StickersBox::disenableDone(const MTPBool & result, mtpRequestId req) { @@ -805,7 +1026,7 @@ bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) { } void StickersBox::saveOrder() { - QVector order = _inner.getOrder(); + auto order = _inner->getOrder(); if (order.size() > 1) { QVector mtpOrder; mtpOrder.reserve(order.size()); @@ -839,9 +1060,11 @@ void StickersBox::paintEvent(QPaintEvent *e) { paintTitle(p, lang(lng_stickers_packs)); p.translate(0, st::boxTitleHeight); - p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); - p.setPen(st::stickersReorderFg); - _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + if (_aboutHeight > 0) { + p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); + p.setPen(st::stickersReorderFg); + _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + } } void StickersBox::closePressed() { @@ -862,18 +1085,20 @@ void StickersBox::closePressed() { void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - _inner.resize(width(), _inner.height()); + _inner->resize(width(), _inner->height()); _topShadow.setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); - _bottomShadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _save.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); - _inner.setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + if (_save) { + _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); + _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); + _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); + } } void StickersBox::onStickersUpdated() { - _inner.rebuild(); + _inner->rebuild(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner.setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); } void StickersBox::onCheckDraggingScroll(int localY) { @@ -901,7 +1126,7 @@ void StickersBox::onScrollTimer() { } void StickersBox::onSave() { - if (!_inner.savingStart()) { + if (!_inner->savingStart()) { return; } @@ -909,7 +1134,7 @@ void StickersBox::onSave() { RecentStickerPack &recent(cGetRecentStickers()); auto &sets = Global::RefStickerSets(); - QVector reorder = _inner.getOrder(), disabled = _inner.getDisabledSets(); + QVector reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); for (int32 i = 0, l = disabled.size(); i < l; ++i) { auto it = sets.find(disabled.at(i)); if (it != sets.cend()) { @@ -932,12 +1157,20 @@ void StickersBox::onSave() { if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)) { sets.erase(it); + } else { + it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_disabled); } } } } } - Stickers::Order &order(Global::RefStickerSetsOrder()); + + // Clear all installed flags, set only for sets from order. + for (auto &set : sets) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; + } + + auto &order(Global::RefStickerSetsOrder()); order.clear(); for (int i = 0, l = reorder.size(); i < l; ++i) { auto it = sets.find(reorder.at(i)); @@ -948,13 +1181,14 @@ void StickersBox::onSave() { it->flags &= ~MTPDstickerSet::Flag::f_disabled; } order.push_back(reorder.at(i)); + it->flags |= MTPDstickerSet::Flag::f_installed; } } for (auto it = sets.begin(); it != sets.cend();) { if (it->id == Stickers::CustomSetId || it->id == Stickers::RecentSetId || (it->flags & MTPDstickerSet_ClientFlag::f_featured) - || order.contains(it->id)) { + || (it->flags & MTPDstickerSet::Flag::f_installed)) { ++it; } else { it = sets.erase(it); @@ -973,18 +1207,22 @@ void StickersBox::onSave() { } void StickersBox::hideAll() { - _save.hide(); - _cancel.hide(); _topShadow.hide(); - _bottomShadow.hide(); + if (_save) { + _save->hide(); + _cancel->hide(); + _bottomShadow->hide(); + } ItemListBox::hideAll(); } void StickersBox::showAll() { - _save.show(); - _cancel.show(); _topShadow.show(); - _bottomShadow.show(); + if (_save) { + _save->show(); + _cancel->show(); + _bottomShadow->show(); + } ItemListBox::showAll(); } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 9b6cd9891..1c8e9590d 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -95,7 +95,6 @@ public: void resizeEvent(QResizeEvent *e); public slots: - void onStickersUpdated(); void onAddStickers(); void onShareStickers(); @@ -103,115 +102,38 @@ public slots: void onScroll(); -signals: +private slots: + void onInstalled(uint64 id); +signals: void installed(uint64 id); protected: - void hideAll(); void showAll(); private: - StickerSetInner _inner; ScrollableBoxShadow _shadow; BoxButton _add, _share, _cancel, _done; QString _title; + }; -class StickersInner : public TWidget { - Q_OBJECT - -public: - - StickersInner(); - - void paintEvent(QPaintEvent *e); - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - - void rebuild(); - bool savingStart() { - if (_saving) return false; - _saving = true; - return true; - } - - QVector getOrder() const; - QVector getDisabledSets() const; - - void setVisibleScrollbar(int32 width); - - ~StickersInner(); - -signals: - - void checkDraggingScroll(int localY); - void noDraggingScroll(); - -public slots: - - void onUpdateSelected(); - -private: - - void step_shifting(uint64 ms, bool timer); - void paintRow(Painter &p, int32 index); - void clear(); - void setRemoveSel(int32 removeSel); - float64 aboveShadowOpacity() const; - - int32 _rowHeight; - struct StickerSetRow { - StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool official, bool disabled, int32 pixw, int32 pixh) : id(id) - , sticker(sticker) - , count(count) - , title(title) - , official(official) - , disabled(disabled) - , pixw(pixw) - , pixh(pixh) - , yadd(0, 0) { - } - uint64 id; - DocumentData *sticker; - int32 count; - QString title; - bool official, disabled; - int32 pixw, pixh; - anim::ivalue yadd; - }; - typedef QList StickerSetRows; - StickerSetRows _rows; - QList _animStartTimes; - uint64 _aboveShadowFadeStart; - anim::fvalue _aboveShadowFadeOpacity; - Animation _a_shifting; - - int32 _itemsTop; - - bool _saving; - - int32 _removeSel, _removeDown, _removeWidth, _returnWidth, _restoreWidth; - - QPoint _mouse; - int32 _selected; - QPoint _dragStart; - int32 _started, _dragging, _above; - - BoxShadow _aboveShadow; - - int32 _scrollbar; -}; +namespace internal { +class StickersInner; +} // namespace internal class StickersBox : public ItemListBox, public RPCSender { Q_OBJECT public: - StickersBox(); + enum class Section { + Installed, + Featured, + }; + StickersBox(Section section = Section::Installed); void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); @@ -242,20 +164,135 @@ private: bool reorderFail(const RPCError &result); void saveOrder(); - StickersInner _inner; - BoxButton _save, _cancel; + Section _section; + + ChildWidget _inner; + ChildWidget _save = { nullptr }; + ChildWidget _cancel = { nullptr }; QMap _disenableRequests; mtpRequestId _reorderRequest; PlainShadow _topShadow; - ScrollableBoxShadow _bottomShadow; + ChildWidget _bottomShadow = { nullptr }; QTimer _scrollTimer; int32 _scrollDelta; - int32 _aboutWidth; + int _aboutWidth = 0; Text _about; - int32 _aboutHeight; + int _aboutHeight = 0; }; int32 stickerPacksCount(bool includeDisabledOfficial = false); + +namespace internal { + +class StickersInner : public TWidget, public RPCSender { + Q_OBJECT + +public: + using Section = StickersBox::Section; + StickersInner(Section section); + + void rebuild(); + bool savingStart() { + if (_saving) return false; + _saving = true; + return true; + } + + QVector getOrder() const; + QVector getDisabledSets() const; + + void setVisibleScrollbar(int32 width); + + ~StickersInner(); + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *e) override; + +signals: + void checkDraggingScroll(int localY); + void noDraggingScroll(); + +public slots: + void onUpdateSelected(); + +private: + void paintFeaturedButton(Painter &p) const; + + void step_shifting(uint64 ms, bool timer); + void paintRow(Painter &p, int32 index); + void clear(); + void setActionSel(int32 actionSel); + float64 aboveShadowOpacity() const; + + void installSet(uint64 setId); + void readFeaturedDone(const MTPBool &result); + bool readFeaturedFail(const RPCError &error); + + Section _section; + + int32 _rowHeight; + struct StickerSetRow { + StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, int32 pixw, int32 pixh) : id(id) + , sticker(sticker) + , count(count) + , title(title) + , installed(installed) + , official(official) + , unread(unread) + , disabled(disabled) + , pixw(pixw) + , pixh(pixh) + , yadd(0, 0) { + } + uint64 id; + DocumentData *sticker; + int32 count; + QString title; + bool installed, official, unread, disabled; + int32 pixw, pixh; + anim::ivalue yadd; + }; + typedef QList StickerSetRows; + StickerSetRows _rows; + QList _animStartTimes; + uint64 _aboveShadowFadeStart = 0; + anim::fvalue _aboveShadowFadeOpacity = { 0., 0. }; + Animation _a_shifting; + + int32 _itemsTop; + + bool _saving = false; + + int _actionSel = -1; + int _actionDown = -1; + + int _removeWidth, _returnWidth, _restoreWidth; + + QString _addText; + int _addWidth; + + int _featuredHeight = 0; + // Remember all the unread set ids to display unread dots. + OrderedSet _unreadSets; + + QPoint _mouse; + int _selected = -2; // -1 - featured stickers button + int _pressed = -2; + QPoint _dragStart; + int _started = -1; + int _dragging = -1; + int _above = -1; + + BoxShadow _aboveShadow; + + int32 _scrollbar = 0; +}; + +} // namespace internal diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index cc9afe6d6..ef1c72478 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -29,7 +29,6 @@ dialogsUnreadBgActive: #ffffff; dialogsUnreadBgMutedActive: #d3e2ee; dialogsUnreadFont: font(12px bold); dialogsUnreadHeight: 19px; -dialogsUnreadTop: 1px; dialogsUnreadPadding: 5px; dialogsBg: windowBg; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 8591ea74e..ff7043963 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -222,7 +222,7 @@ void paintUnreadCount(Painter &p, const QString &text, int x, int y, const Unrea p.setFont(st.font); p.setPen(st.active ? st::dialogsUnreadFgActive : st::dialogsUnreadFg); - p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + st::dialogsUnreadTop + st.font->ascent, text); + p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + (unreadRectHeight - st.font->height) / 2 + st.font->ascent, text); } void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { @@ -258,7 +258,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele auto counter = QString::number(unreadCount); auto mutedCounter = history->mute(); int unreadRight = w - st::dialogsPadding.x(); - int unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - st::dialogsUnreadTop; + int unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; int unreadWidth = 0; UnreadBadgeStyle st; @@ -297,7 +297,7 @@ void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool o int unreadTop = (st::dialogsImportantBarHeight - st::dialogsUnreadHeight) / 2; bool mutedHidden = (current == Dialogs::Mode::Important); QString text = mutedHidden ? qsl("Show all chats") : qsl("Hide muted chats"); - int textBaseline = unreadTop + st::dialogsUnreadTop + st::dialogsUnreadFont->ascent; + int textBaseline = unreadTop + (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2 + st::dialogsUnreadFont->ascent; p.drawText(st::dialogsPadding.x(), textBaseline, text); if (mutedHidden) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index b0e5bd8b1..f083f062f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -38,6 +38,8 @@ void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool o enum UnreadBadgeSize { UnreadBadgeInDialogs = 0, UnreadBadgeInHistoryToDown, + UnreadBadgeInStickersPanel, + UnreadBadgeInStickersBox, UnreadBadgeSizesCount }; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index d4db768d2..fd31b7c85 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/stickersetbox.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" +#include "dialogs/dialogs_layout.h" #include "historywidget.h" #include "localstorage.h" #include "lang.h" @@ -2829,6 +2830,20 @@ void EmojiPan::onSaveConfigDelayed(int32 delay) { _saveConfigTimer.start(delay); } +void EmojiPan::paintStickerSettingsIcon(Painter &p) const { + int settingsLeft = _iconsLeft + 7 * st::rbEmoji.width; + p.drawSpriteLeft(settingsLeft + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); + if (auto unread = Global::FeaturedStickerSetsUnreadCount()) { + Dialogs::Layout::UnreadBadgeStyle unreadSt; + unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel; + unreadSt.size = st::stickersSettingsUnreadSize; + int unreadRight = settingsLeft + st::rbEmoji.width - st::stickersSettingsUnreadPosition.x(); + if (rtl()) unreadRight = width() - unreadRight; + int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y(); + Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); + } +} + void EmojiPan::paintEvent(QPaintEvent *e) { Painter p(this); @@ -2846,7 +2861,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) { p.fillRect(myrtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height()), st::white->b); if (_stickersShown && s_inner.showSectionIcons()) { p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories); - p.drawSpriteLeft(_iconsLeft + 7 * st::rbEmoji.width + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); + paintStickerSettingsIcon(p); if (!_icons.isEmpty()) { int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); @@ -3093,6 +3108,7 @@ void EmojiPan::refreshStickers() { if (!_stickersShown) { s_inner.preloadImages(); } + update(); } void EmojiPan::refreshSavedGifs() { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 32faf3568..dfd81a7cf 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -656,6 +656,7 @@ signals: void updateStickers(); private: + void paintStickerSettingsIcon(Painter &p) const; void validateSelectedIcon(bool animated = false); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3e1a18143..58175e636 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -561,7 +561,7 @@ struct Data { Stickers::Order StickerSetsOrder; uint64 LastStickersUpdate = 0; Stickers::Order FeaturedStickerSetsOrder; - Stickers::UnreadMap FeaturedUnreadSets; + int FeaturedStickerSetsUnreadCount = 0; uint64 LastFeaturedStickersUpdate = 0; MTP::DcOptions DcOptions; @@ -630,7 +630,7 @@ DefineVar(Global, Stickers::Sets, StickerSets); DefineVar(Global, Stickers::Order, StickerSetsOrder); DefineVar(Global, uint64, LastStickersUpdate); DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); -DefineVar(Global, Stickers::UnreadMap, FeaturedUnreadSets); +DefineVar(Global, int, FeaturedStickerSetsUnreadCount); DefineVar(Global, uint64, LastFeaturedStickersUpdate); DefineVar(Global, MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index f8831e798..71cd432e5 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -199,7 +199,13 @@ struct Set { }; using Sets = QMap; using Order = QList; -using UnreadMap = OrderedSet; + +inline MTPInputStickerSet inputSetId(const Set &set) { + if (set.id && set.access) { + return MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access)); + } + return MTP_inputStickerSetShortName(MTP_string(set.shortName)); +} } // namespace Stickers @@ -250,7 +256,7 @@ DeclareVar(Stickers::Sets, StickerSets); DeclareVar(Stickers::Order, StickerSetsOrder); DeclareVar(uint64, LastStickersUpdate); DeclareVar(Stickers::Order, FeaturedStickerSetsOrder); -DeclareVar(Stickers::UnreadMap, FeaturedUnreadSets); +DeclareVar(int, FeaturedStickerSetsUnreadCount); DeclareVar(uint64, LastFeaturedStickersUpdate); DeclareVar(MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index 67995b523..9bd12b065 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -196,14 +196,14 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } return true; }; - auto filterNotPassedByName = [this](UserData *user) -> bool { + auto filterNotPassedByName = [this, &filterNotPassedByUsername](UserData *user) -> bool { for_const (auto &namePart, user->names) { if (namePart.startsWith(_filter, Qt::CaseInsensitive)) { bool exactUsername = (user->username.compare(_filter, Qt::CaseInsensitive) == 0); return exactUsername; } } - return true; + return filterNotPassedByUsername(user); }; bool listAllSuggestions = _filter.isEmpty(); diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 59a20ef43..5f0bd07d1 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -29,6 +29,8 @@ historyToDownArrow: icon { { "history_down_arrow", #b9b9b9, point(14px, 19px) }, }; historyToDownPaddingTop: 10px; +historyToDownBadgeFont: semiboldFont; +historyToDownBadgeSize: 22px; membersInnerScroll: flatScroll(solidScroll) { deltat: 3px; @@ -42,5 +44,3 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 8px, 3px); } -historyToDownBadgeFont: semiboldFont; -historyToDownBadgeSize: 22px; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 3bdd9da63..47a8fe198 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3667,7 +3667,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); it->flags = set.vflags.v | clientFlags; if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { it->count = set.vcount.v; @@ -3739,6 +3739,11 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic if (stickers.type() != mtpc_messages_featuredStickers) return; auto &d(stickers.c_messages_featuredStickers()); + OrderedSet unread; + for_const (auto &unreadSetId, d.vunread.c_vector().v) { + unread.insert(unreadSetId.v); + } + auto &d_sets(d.vsets.c_vector().v); auto &setsOrder = Global::RefFeaturedStickerSetsOrder(); @@ -3755,13 +3760,23 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic auto it = sets.find(set.vid.v); QString title = stickerSetTitle(set); if (it == sets.cend()) { - it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded)); + auto clientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; + if (unread.contains(set.vid.v)) { + clientFlags |= MTPDstickerSet_ClientFlag::f_unread; + } + it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | clientFlags)); } else { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded); - it->flags = set.vflags.v | clientFlags | MTPDstickerSet_ClientFlag::f_featured; + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); + it->flags = set.vflags.v | clientFlags; + it->flags |= MTPDstickerSet_ClientFlag::f_featured; + if (unread.contains(it->id)) { + it->flags |= MTPDstickerSet_ClientFlag::f_unread; + } else { + it->flags &= ~MTPDstickerSet_ClientFlag::f_unread; + } if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { it->count = set.vcount.v; it->hash = set.vhash.v; @@ -3774,21 +3789,21 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic } } } - for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) { + + int unreadCount = 0; + for (auto it = sets.begin(), e = sets.end(); it != e;) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); if (installed || featured) { + if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { + ++unreadCount; + } ++it; } else { it = sets.erase(it); } } - - auto &unreadFeatured = Global::RefFeaturedUnreadSets(); - unreadFeatured.clear(); - for_const (auto &unreadSetId, d.vunread.c_vector().v) { - unreadFeatured.insert(unreadSetId.v); - } + Global::SetFeaturedStickerSetsUnreadCount(unreadCount); if (Local::countFeaturedStickersHash() != d.vhash.v) { LOG(("API Error: received featured stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countFeaturedStickersHash())); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 3c2ab76d4..aeec35846 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3079,7 +3079,6 @@ namespace Local { } size += sizeof(qint32) + (Global::StickerSetsOrder().size() * sizeof(quint64)); size += sizeof(qint32) + (Global::FeaturedStickerSetsOrder().size() * sizeof(quint64)); - size += sizeof(qint32) + (Global::FeaturedUnreadSets().size() * sizeof(quint64)); if (!_stickersKey) { _stickersKey = genKey(); @@ -3094,11 +3093,6 @@ namespace Local { data.stream << Global::StickerSetsOrder(); data.stream << Global::FeaturedStickerSetsOrder(); - data.stream << qint32(Global::FeaturedUnreadSets().size()); - for_const (auto setId, Global::FeaturedUnreadSets()) { - data.stream << quint64(setId); - } - FileWriteDescriptor file(_stickersKey); file.writeEncrypted(data); } @@ -3200,9 +3194,6 @@ namespace Local { auto &featuredOrder = Global::RefFeaturedStickerSetsOrder(); featuredOrder.clear(); - auto &unreadFeatured = Global::RefFeaturedUnreadSets(); - unreadFeatured.clear(); - quint32 cnt; QByteArray hash; stickers.stream >> cnt >> hash; // ignore hash, it is counted @@ -3244,6 +3235,8 @@ namespace Local { } auto &set = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value(); + // We will set this flags from order lists below. + set.flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured); if (scnt < 0) { // disabled not loaded set set.count = -scnt; continue; @@ -3293,23 +3286,32 @@ namespace Local { stickers.stream >> order; stickers.stream >> featuredOrder; - qint32 unreadCount = 0; - stickers.stream >> unreadCount; - for (int i = 0; i < unreadCount; ++i) { - quint64 setId = 0; - stickers.stream >> setId; - if (setId) { - unreadFeatured.insert(setId); + // Set flags and count unread featured sets. + for_const (auto setId, order) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= MTPDstickerSet::Flag::f_installed; } } + int unreadCount = 0; + for_const (auto setId, featuredOrder) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= MTPDstickerSet_ClientFlag::f_featured; + if (it->flags & MTPDstickerSet_ClientFlag::f_unread) { + ++unreadCount; + } + } + } + Global::SetFeaturedStickerSetsUnreadCount(unreadCount); } } int32 countStickersHash(bool checkOfficial) { uint32 acc = 0; bool foundOfficial = false, foundBad = false;; - const Stickers::Sets &sets(Global::StickerSets()); - const Stickers::Order &order(Global::StickerSetsOrder()); + auto &sets = Global::StickerSets(); + auto &order = Global::StickerSetsOrder(); for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) { auto j = sets.constFind(*i); if (j != sets.cend()) { @@ -3328,11 +3330,14 @@ namespace Local { int32 countFeaturedStickersHash() { uint32 acc = 0; - auto &featured(Global::FeaturedStickerSetsOrder()); + auto &sets = Global::StickerSets(); + auto &featured = Global::FeaturedStickerSetsOrder(); for_const (auto setId, featured) { acc = (acc * 20261) + uint32(setId >> 32); acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF); - if (Global::FeaturedUnreadSets().contains(setId)) { + + auto it = sets.constFind(setId); + if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { acc = (acc * 20261) + 1U; } } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 6cb95b58c..f5a74cc59 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3378,7 +3378,6 @@ void MainWidget::stickersBox(const MTPInputStickerSet &set) { } void MainWidget::onStickersInstalled(uint64 setId) { - emit stickersUpdated(); _history->stickersInstalled(setId); } @@ -4731,9 +4730,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateReadFeaturedStickers: { - Global::RefFeaturedUnreadSets().clear(); - Local::writeStickers(); - emit stickersUpdated(); + for (auto &set : Global::RefStickerSets()) { + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; + } + } + if (Global::FeaturedStickerSetsUnreadCount()) { + Global::SetFeaturedStickerSetsUnreadCount(0); + Local::writeStickers(); + emit stickersUpdated(); + } } break; ////// Cloud saved GIFs diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 17669f57b..4b4d05a88 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1291,7 +1291,7 @@ void MediaView::paintEvent(QPaintEvent *e) { if (_saveMsgOpacity.current() > 0) { p.setOpacity(_saveMsgOpacity.current()); App::roundRect(p, _saveMsg, st::medviewSaveMsg, MediaviewSaveCorners); - p.drawSprite(_saveMsg.topLeft() + st::medviewSaveMsgCheckPos, st::medviewSaveMsgCheck); + st::medviewSaveMsgCheck.paint(p, _saveMsg.topLeft() + st::medviewSaveMsgCheckPos, width()); p.setPen(st::white->p); textstyleSet(&st::medviewSaveAsTextStyle); diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 96eebe2c3..1438fe212 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -1063,8 +1063,11 @@ enum class MTPDstickerSet_ClientFlag : int32 { // sticker set is one of featured (should be saved locally) f_featured = (1 << 29), + // sticker set is an unread featured set + f_unread = (1 << 28), + // update this when adding new client side flags - MIN_FIELD = (1 << 29), + MIN_FIELD = (1 << 28), }; DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet) From 7ca5737bc08930277bb4295717c6f3b81a3ba776 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Jun 2016 15:03:32 +0300 Subject: [PATCH 04/60] Removed SongMsgId, unified working with voice/music files in audio. --- Telegram/SourceFiles/audio.cpp | 613 +++++++----------- Telegram/SourceFiles/audio.h | 134 ++-- Telegram/SourceFiles/config.h | 3 +- Telegram/SourceFiles/history.cpp | 10 +- .../inline_bot_layout_internal.cpp | 10 +- Telegram/SourceFiles/mainwidget.cpp | 52 +- Telegram/SourceFiles/mainwidget.h | 1 - .../SourceFiles/overview/overview_layout.cpp | 10 +- Telegram/SourceFiles/overviewwidget.cpp | 6 +- Telegram/SourceFiles/playerwidget.cpp | 80 +-- Telegram/SourceFiles/playerwidget.h | 6 +- Telegram/SourceFiles/structs.cpp | 32 +- Telegram/SourceFiles/structs.h | 73 ++- 13 files changed, 395 insertions(+), 635 deletions(-) diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index 8b94575b2..dfdfd17f5 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -41,13 +41,13 @@ extern "C" { #undef iconv #undef iconv_close -iconv_t iconv_open (const char* tocode, const char* fromcode) { +iconv_t iconv_open(const char* tocode, const char* fromcode) { return libiconv_open(tocode, fromcode); } -size_t iconv (iconv_t cd, char* * inbuf, size_t *inbytesleft, char* * outbuf, size_t *outbytesleft) { +size_t iconv(iconv_t cd, char** inbuf, size_t *inbytesleft, char** outbuf, size_t *outbytesleft) { return libiconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); } -int iconv_close (iconv_t cd) { +int iconv_close(iconv_t cd) { return libiconv_close(cd); } #endif // Q_OS_MAC @@ -98,7 +98,6 @@ bool _checkALError() { } Q_DECLARE_METATYPE(AudioMsgId); -Q_DECLARE_METATYPE(SongMsgId); Q_DECLARE_METATYPE(VoiceWaveform); void audioInit() { if (!capture) { @@ -209,7 +208,6 @@ void audioInit() { if (!_checkALError()) return audioFinish(); qRegisterMetaType(); - qRegisterMetaType(); qRegisterMetaType(); player = new AudioPlayer(); @@ -263,7 +261,8 @@ void audioFinish() { cSetHasAudioPlayer(false); } -void AudioPlayer::Msg::clearData() { +void AudioPlayer::AudioMsg::clear() { + audio = AudioMsgId(); file = FileLocation(); data = QByteArray(); position = duration = 0; @@ -293,26 +292,18 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll())); connect(this, SIGNAL(songVolumeChanged()), _fader, SLOT(onSongVolumeChanged())); connect(this, SIGNAL(loaderOnStart(const AudioMsgId&,qint64)), _loader, SLOT(onStart(const AudioMsgId&,qint64))); - connect(this, SIGNAL(loaderOnStart(const SongMsgId&,qint64)), _loader, SLOT(onStart(const SongMsgId&,qint64))); connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&))); - connect(this, SIGNAL(loaderOnCancel(const SongMsgId&)), _loader, SLOT(onCancel(const SongMsgId&))); connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit())); connect(&_loaderThread, SIGNAL(started()), _loader, SLOT(onInit())); connect(&_faderThread, SIGNAL(finished()), _fader, SLOT(deleteLater())); connect(&_loaderThread, SIGNAL(finished()), _loader, SLOT(deleteLater())); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); - connect(_loader, SIGNAL(error(const SongMsgId&)), this, SLOT(onError(const SongMsgId&))); connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&))); - connect(_fader, SIGNAL(needToPreload(const SongMsgId&)), _loader, SLOT(onLoad(const SongMsgId&))); connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&))); - connect(_fader, SIGNAL(playPositionUpdated(const SongMsgId&)), this, SIGNAL(updated(const SongMsgId&))); connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&))); - connect(_fader, SIGNAL(audioStopped(const SongMsgId&)), this, SLOT(onStopped(const SongMsgId&))); connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); - connect(_fader, SIGNAL(error(const SongMsgId&)), this, SLOT(onError(const SongMsgId&))); connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(stopped(const AudioMsgId&)), Qt::QueuedConnection); - connect(this, SIGNAL(stoppedOnError(const SongMsgId&)), this, SIGNAL(stopped(const SongMsgId&)), Qt::QueuedConnection); _loaderThread.start(); _faderThread.start(); } @@ -323,31 +314,23 @@ AudioPlayer::~AudioPlayer() { player = 0; } - for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - alSourceStop(_audioData[i].source); - if (alIsBuffer(_audioData[i].buffers[0])) { - alDeleteBuffers(3, _audioData[i].buffers); - for (int32 j = 0; j < 3; ++j) { - _audioData[i].buffers[j] = _audioData[i].samplesCount[j] = 0; + auto clearAudioMsg = [](AudioMsg *msg) { + alSourceStop(msg->source); + if (alIsBuffer(msg->buffers[0])) { + alDeleteBuffers(3, msg->buffers); + for (int j = 0; j < 3; ++j) { + msg->buffers[j] = msg->samplesCount[j] = 0; } } - if (alIsSource(_audioData[i].source)) { - alDeleteSources(1, &_audioData[i].source); - _audioData[i].source = 0; - } - } - for (int32 i = 0; i < AudioSongSimultaneously; ++i) { - alSourceStop(_songData[i].source); - if (alIsBuffer(_songData[i].buffers[0])) { - alDeleteBuffers(3, _songData[i].buffers); - for (int32 j = 0; j < 3; ++j) { - _songData[i].buffers[j] = _songData[i].samplesCount[j] = 0; - } - } - if (alIsSource(_songData[i].source)) { - alDeleteSources(1, &_songData[i].source); - _songData[i].source = 0; + if (alIsSource(msg->source)) { + alDeleteSources(1, &msg->source); + msg->source = 0; } + }; + + for (int i = 0; i < AudioSimultaneousLimit; ++i) { + clearAudioMsg(dataForType(AudioMsgId::Type::Voice, i)); + clearAudioMsg(dataForType(AudioMsgId::Type::Song, i)); } _faderThread.quit(); _loaderThread.quit(); @@ -357,28 +340,45 @@ AudioPlayer::~AudioPlayer() { void AudioPlayer::onError(const AudioMsgId &audio) { emit stoppedOnError(audio); - emit unsuppressSong(); -} - -void AudioPlayer::onError(const SongMsgId &song) { - emit stoppedOnError(song); + if (audio.type() == AudioMsgId::Type::Voice) { + emit unsuppressSong(); + } } void AudioPlayer::onStopped(const AudioMsgId &audio) { emit stopped(audio); - emit unsuppressSong(); -} - -void AudioPlayer::onStopped(const SongMsgId &song) { - emit stopped(song); -} - -bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { - Msg *data = 0; - switch (type) { - case OverviewVoiceFiles: data = &_audioData[_audioCurrent]; break; - case OverviewFiles: data = &_songData[_songCurrent]; break; + if (audio.type() == AudioMsgId::Type::Voice) { + emit unsuppressSong(); } +} + +AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index) { + if (index < 0) index = *currentIndex(type); + switch (type) { + case AudioMsgId::Type::Voice: return &_audioData[index]; + case AudioMsgId::Type::Song: return &_songData[index]; + } + return nullptr; +} + +const AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index) const { + return const_cast(this)->dataForType(type, index); +} + +int *AudioPlayer::currentIndex(AudioMsgId::Type type) { + switch (type) { + case AudioMsgId::Type::Voice: return &_audioCurrent; + case AudioMsgId::Type::Song: return &_songCurrent; + } + return nullptr; +} + +const int *AudioPlayer::currentIndex(AudioMsgId::Type type) const { + return const_cast(this)->currentIndex(type); +} + +bool AudioPlayer::updateCurrentStarted(AudioMsgId::Type type, int32 pos) { + auto data = dataForType(type); if (!data) return false; if (pos < 0) { @@ -389,10 +389,7 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { } if (!_checkALError()) { setStoppedState(data, AudioPlayerStoppedAtError); - switch (type) { - case OverviewVoiceFiles: onError(_audioData[_audioCurrent].audio); break; - case OverviewFiles: onError(_songData[_songCurrent].song); break; - } + onError(data->audio); return false; } } @@ -400,12 +397,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { return true; } -bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) { - Msg *current = 0; - switch (type) { - case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; break; - case OverviewFiles: current = &_songData[_songCurrent]; break; - } +bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { + auto current = dataForType(type); if (!current) return false; switch (current->state) { @@ -429,14 +422,18 @@ bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) { } void AudioPlayer::play(const AudioMsgId &audio, int64 position) { + bool fadedStart = false; + auto type = audio.type(); AudioMsgId stopped; { QMutexLocker lock(&playerMutex); bool fadedStart = false; - AudioMsg *current = &_audioData[_audioCurrent]; + auto current = dataForType(type); + if (!current) return; + if (current->audio != audio) { - if (fadedStop(OverviewVoiceFiles, &fadedStart)) { + if (fadedStop(type, &fadedStart)) { stopped = current->audio; } if (current->audio) { @@ -444,110 +441,65 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { emit faderOnTimer(); } - int32 index = 0; - for (; index < AudioVoiceMsgSimultaneously; ++index) { - if (_audioData[index].audio == audio) { - _audioCurrent = index; + auto foundCurrent = currentIndex(type); + int index = 0; + for (; index < AudioSimultaneousLimit; ++index) { + if (dataForType(type, index)->audio == audio) { + *foundCurrent = index; break; } } - if (index == AudioVoiceMsgSimultaneously && ++_audioCurrent >= AudioVoiceMsgSimultaneously) { - _audioCurrent -= AudioVoiceMsgSimultaneously; + if (index == AudioSimultaneousLimit && ++*foundCurrent >= AudioSimultaneousLimit) { + *foundCurrent -= AudioSimultaneousLimit; } - current = &_audioData[_audioCurrent]; + current = dataForType(type); } current->audio = audio; - current->file = audio.audio->location(true); - current->data = audio.audio->data(); + current->file = audio.audio()->location(true); + current->data = audio.audio()->data(); if (current->file.isEmpty() && current->data.isEmpty()) { - setStoppedState(current, AudioPlayerStoppedAtError); - onError(audio); + if (audio.type() == AudioMsgId::Type::Song) { + setStoppedState(current); + if (!audio.audio()->loading()) { + DocumentOpenClickHandler::doOpen(audio.audio()); + } + } else { + setStoppedState(current, AudioPlayerStoppedAtError); + onError(audio); + } } else { current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; current->loading = true; emit loaderOnStart(audio, position); - emit suppressSong(); + if (type == AudioMsgId::Type::Voice) { + emit suppressSong(); + } } } if (stopped) emit updated(stopped); } -void AudioPlayer::play(const SongMsgId &song, int64 position) { - SongMsgId stopped; - { - QMutexLocker lock(&playerMutex); - - bool fadedStart = false; - SongMsg *current = &_songData[_songCurrent]; - if (current->song != song) { - if (fadedStop(OverviewFiles, &fadedStart)) { - stopped = current->song; - } - if (current->song) { - emit loaderOnCancel(current->song); - emit faderOnTimer(); - } - - int32 index = 0; - for (; index < AudioSongSimultaneously; ++index) { - if (_songData[index].song == song) { - _songCurrent = index; - break; - } - } - if (index == AudioSongSimultaneously && ++_songCurrent >= AudioSongSimultaneously) { - _songCurrent -= AudioSongSimultaneously; - } - current = &_songData[_songCurrent]; - } - current->song = song; - current->file = song.song->location(true); - current->data = song.song->data(); - if (current->file.isEmpty() && current->data.isEmpty()) { - setStoppedState(current); - if (!song.song->loading()) { - DocumentOpenClickHandler::doOpen(song.song); - } - } else { - current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; - current->loading = true; - emit loaderOnStart(song, position); - } - } - if (stopped) emit updated(stopped); -} - -bool AudioPlayer::checkCurrentALError(MediaOverviewType type) { +bool AudioPlayer::checkCurrentALError(AudioMsgId::Type type) { if (_checkALError()) return true; - switch (type) { - case OverviewVoiceFiles: - setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError); - onError(_audioData[_audioCurrent].audio); - break; - case OverviewFiles: - setStoppedState(&_songData[_songCurrent], AudioPlayerStoppedAtError); - onError(_songData[_songCurrent].song); - break; + auto data = dataForType(type); + if (!data) { + setStoppedState(data, AudioPlayerStoppedAtError); + onError(data->audio); } return false; } -void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { +void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) { QMutexLocker lock(&playerMutex); - Msg *current = 0; + auto current = dataForType(type); float64 suppressGain = 1.; switch (type) { - case OverviewVoiceFiles: - current = &_audioData[_audioCurrent]; - suppressGain = suppressAllGain; - break; - case OverviewFiles: - current = &_songData[_songCurrent]; - suppressGain = suppressSongGain * cSongVolume(); - break; + case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break; + case AudioMsgId::Type::Song: suppressGain = suppressSongGain * cSongVolume(); break; } + switch (current->state) { case AudioPlayerPausing: case AudioPlayerPaused: @@ -575,14 +527,14 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { alSourcePlay(current->source); if (!checkCurrentALError(type)) return; } - if (type == OverviewVoiceFiles) emit suppressSong(); + if (type == AudioMsgId::Type::Voice) emit suppressSong(); } break; case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: current->state = AudioPlayerPausing; updateCurrentStarted(type); - if (type == OverviewVoiceFiles) emit unsuppressSong(); + if (type == AudioMsgId::Type::Voice) emit unsuppressSong(); break; case AudioPlayerFinishing: current->state = AudioPlayerPausing; break; } @@ -592,23 +544,14 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { void AudioPlayer::seek(int64 position) { QMutexLocker lock(&playerMutex); - MediaOverviewType type = OverviewFiles; - Msg *current = 0; + auto type = AudioMsgId::Type::Song; + auto current = dataForType(type); float64 suppressGain = 1.; - AudioMsgId audio; - SongMsgId song; switch (type) { - case OverviewVoiceFiles: - current = &_audioData[_audioCurrent]; - audio = _audioData[_audioCurrent].audio; - suppressGain = suppressAllGain; - break; - case OverviewFiles: - current = &_songData[_songCurrent]; - song = _songData[_songCurrent].song; - suppressGain = suppressSongGain * cSongVolume(); - break; + case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break; + case AudioMsgId::Type::Song: suppressGain = suppressSongGain * cSongVolume(); break; } + auto audio = current->audio; bool isSource = alIsSource(current->source); bool fastSeek = (position >= current->skipStart && position < current->duration - current->skipEnd - (current->skipEnd ? AudioVoiceMsgFrequency : 0)); @@ -637,7 +580,7 @@ void AudioPlayer::seek(int64 position) { case AudioPlayerPlaying: current->state = AudioPlayerPausing; updateCurrentStarted(type); - if (type == OverviewVoiceFiles) emit unsuppressSong(); + if (type == AudioMsgId::Type::Voice) emit unsuppressSong(); break; case AudioPlayerFinishing: case AudioPlayerStopped: @@ -645,113 +588,80 @@ void AudioPlayer::seek(int64 position) { case AudioPlayerStoppedAtError: case AudioPlayerStoppedAtStart: lock.unlock(); - switch (type) { - case OverviewVoiceFiles: if (audio) return play(audio, position); - case OverviewFiles: if (song) return play(song, position); - } + return play(audio, position); } emit faderOnTimer(); } -void AudioPlayer::stop(MediaOverviewType type) { - switch (type) { - case OverviewVoiceFiles: { - AudioMsgId current; - { - QMutexLocker lock(&playerMutex); - current = _audioData[_audioCurrent].audio; - fadedStop(type); - } - if (current) emit updated(current); - } break; - - case OverviewFiles: { - SongMsgId current; - { - QMutexLocker lock(&playerMutex); - current = _songData[_songCurrent].song; - fadedStop(type); - } - if (current) emit updated(current); - } break; +void AudioPlayer::stop(AudioMsgId::Type type) { + AudioMsgId current; + { + QMutexLocker lock(&playerMutex); + current = dataForType(type)->audio; + fadedStop(type); } + if (current) emit updated(current); } void AudioPlayer::stopAndClear() { - AudioMsg *current_audio = 0; + AudioMsg *current_audio = nullptr, *current_song = nullptr; { QMutexLocker lock(&playerMutex); - current_audio = &_audioData[_audioCurrent]; - if (current_audio) { + if ((current_audio = dataForType(AudioMsgId::Type::Voice))) { setStoppedState(current_audio); } - } - SongMsg *current_song = 0; - { - QMutexLocker lock(&playerMutex); - current_song = &_songData[_songCurrent]; - if (current_song) { + if ((current_song = dataForType(AudioMsgId::Type::Song))) { setStoppedState(current_song); } } if (current_song) { - emit updated(current_song->song); + emit updated(current_song->audio); } if (current_audio) { emit updated(current_audio->audio); } { QMutexLocker lock(&playerMutex); - for (int32 index = 0; index < AudioVoiceMsgSimultaneously; ++index) { - if (_audioData[index].audio) { - emit loaderOnCancel(_audioData[index].audio); + auto clearAndCancel = [this](AudioMsgId::Type type, int index) { + auto data = dataForType(type, index); + if (data->audio) { + emit loaderOnCancel(data->audio); } - _audioData[index].clear(); - if (_songData[index].song) { - emit loaderOnCancel(_songData[index].song); - } - _songData[index].clear(); + data->clear(); + }; + for (int index = 0; index < AudioSimultaneousLimit; ++index) { + clearAndCancel(AudioMsgId::Type::Voice, index); + clearAndCancel(AudioMsgId::Type::Song, index); } } } -void AudioPlayer::currentState(AudioMsgId *audio, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { +void AudioPlayer::currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { QMutexLocker lock(&playerMutex); - AudioMsg *current = &_audioData[_audioCurrent]; + auto current = dataForType(type); + if (!current) return; + if (audio) *audio = current->audio; return currentState(current, state, position, duration, frequency); } -void AudioPlayer::currentState(SongMsgId *song, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { - QMutexLocker lock(&playerMutex); - SongMsg *current = &_songData[_songCurrent]; - if (song) *song = current->song; - return currentState(current, state, position, duration, frequency); -} - -void AudioPlayer::currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { +void AudioPlayer::currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { if (state) *state = current->state; if (position) *position = current->position; if (duration) *duration = current->duration; if (frequency) *frequency = current->frequency; } -void AudioPlayer::setStoppedState(Msg *current, AudioPlayerState state) { +void AudioPlayer::setStoppedState(AudioMsg *current, AudioPlayerState state) { current->state = state; current->position = 0; } void AudioPlayer::clearStoppedAtStart(const AudioMsgId &audio) { QMutexLocker lock(&playerMutex); - if (_audioData[_audioCurrent].audio == audio && _audioData[_audioCurrent].state == AudioPlayerStoppedAtStart) { - setStoppedState(&_audioData[_audioCurrent]); - } -} - -void AudioPlayer::clearStoppedAtStart(const SongMsgId &song) { - QMutexLocker lock(&playerMutex); - if (_songData[_songCurrent].song == song && _songData[_songCurrent].state == AudioPlayerStoppedAtStart) { - setStoppedState(&_songData[_songCurrent]); + auto data = dataForType(audio.type()); + if (data && data->audio == audio && data->state == AudioPlayerStoppedAtStart) { + setStoppedState(data); } } @@ -860,29 +770,26 @@ void AudioPlayerFader::onTimer() { suppressSongGain = qMin(suppressAllGain, _suppressSongGain.current()); suppressSongChanged = (suppressSongGain != wasSong); } - bool hasFading = (_suppressAll || _suppressSongAnim), hasPlaying = false; + bool hasFading = (_suppressAll || _suppressSongAnim); + bool hasPlaying = false; - for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - AudioPlayer::AudioMsg &m(voice->_audioData[i]); - if ((m.state & AudioPlayerStoppedMask) || m.state == AudioPlayerPaused || !m.source) continue; + auto updatePlayback = [this, voice, &hasPlaying, &hasFading](AudioMsgId::Type type, int index, float64 suppressGain, bool suppressGainChanged) { + auto data = voice->dataForType(type, index); + if ((data->state & AudioPlayerStoppedMask) || data->state == AudioPlayerPaused || !data->source) return; - int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressAllGain, suppressAudioChanged); - if (emitSignals & EmitError) emit error(m.audio); - if (emitSignals & EmitStopped) emit audioStopped(m.audio); - if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(m.audio); - if (emitSignals & EmitNeedToPreload) emit needToPreload(m.audio); + int32 emitSignals = updateOnePlayback(data, hasPlaying, hasFading, suppressGain, suppressGainChanged); + if (emitSignals & EmitError) emit error(data->audio); + if (emitSignals & EmitStopped) emit audioStopped(data->audio); + if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(data->audio); + if (emitSignals & EmitNeedToPreload) emit needToPreload(data->audio); + }; + auto suppressGainForMusic = suppressSongGain * cSongVolume(); + auto suppressGainForMusicChanged = suppressSongChanged || _songVolumeChanged; + for (int i = 0; i < AudioSimultaneousLimit; ++i) { + updatePlayback(AudioMsgId::Type::Voice, i, suppressAllGain, suppressAudioChanged); + updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged); } - for (int32 i = 0; i < AudioSongSimultaneously; ++i) { - AudioPlayer::SongMsg &m(voice->_songData[i]); - if ((m.state & AudioPlayerStoppedMask) || m.state == AudioPlayerPaused || !m.source) continue; - - int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressSongGain * cSongVolume(), suppressSongChanged || _songVolumeChanged); - if (emitSignals & EmitError) emit error(m.song); - if (emitSignals & EmitStopped) emit audioStopped(m.song); - if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(m.song); - if (emitSignals & EmitNeedToPreload) emit needToPreload(m.song); - } _songVolumeChanged = false; if (!hasFading) { @@ -907,7 +814,7 @@ void AudioPlayerFader::onTimer() { } } -int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged) { +int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged) { bool playing = false, fading = false; ALint pos = 0; @@ -1006,7 +913,7 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, return emitSignals; } -void AudioPlayerFader::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state) { +void AudioPlayerFader::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { m->state = state; m->position = 0; } @@ -1478,53 +1385,38 @@ void AudioPlayerLoaders::onInit() { } void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { - _audio = AudioMsgId(); - delete _audioLoader; - _audioLoader = 0; - + auto type = audio.type(); + clear(type); { QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); if (!voice) return; - voice->_audioData[voice->_audioCurrent].loading = true; + auto data = voice->dataForType(type); + if (!data) return; + + data->loading = true; } - loadData(OverviewVoiceFiles, static_cast(&audio), position); + loadData(audio, position); } -void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) { - _song = SongMsgId(); - delete _songLoader; - _songLoader = 0; - - { - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - voice->_songData[voice->_songCurrent].loading = true; - } - - loadData(OverviewFiles, static_cast(&song), position); -} - -void AudioPlayerLoaders::clear(MediaOverviewType type) { +void AudioPlayerLoaders::clear(AudioMsgId::Type type) { switch (type) { - case OverviewVoiceFiles: clearAudio(); break; - case OverviewFiles: clearSong(); break; + case AudioMsgId::Type::Voice: clearAudio(); break; + case AudioMsgId::Type::Song: clearSong(); break; } } -void AudioPlayerLoaders::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state) { +void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { m->state = state; m->position = 0; } -void AudioPlayerLoaders::emitError(MediaOverviewType type) { +void AudioPlayerLoaders::emitError(AudioMsgId::Type type) { switch (type) { - case OverviewVoiceFiles: emit error(clearAudio()); break; - case OverviewFiles: emit error(clearSong()); break; + case AudioMsgId::Type::Voice: emit error(clearAudio()); break; + case AudioMsgId::Type::Song: emit error(clearSong()); break; } } @@ -1532,29 +1424,26 @@ AudioMsgId AudioPlayerLoaders::clearAudio() { AudioMsgId current = _audio; _audio = AudioMsgId(); delete _audioLoader; - _audioLoader = 0; + _audioLoader = nullptr; return current; } -SongMsgId AudioPlayerLoaders::clearSong() { - SongMsgId current = _song; - _song = SongMsgId(); +AudioMsgId AudioPlayerLoaders::clearSong() { + AudioMsgId current = _song; + _song = AudioMsgId(); delete _songLoader; - _songLoader = 0; + _songLoader = nullptr; return current; } void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { - loadData(OverviewVoiceFiles, static_cast(&audio), 0); + loadData(audio, 0); } -void AudioPlayerLoaders::onLoad(const SongMsgId &song) { - loadData(OverviewFiles, static_cast(&song), 0); -} - -void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qint64 position) { +void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) { SetupError err = SetupNoErrorStarted; - AudioPlayerLoader *l = setupLoader(type, objId, err, position); + auto type = audio.type(); + AudioPlayerLoader *l = setupLoader(audio, err, position); if (!l) { if (err == SetupErrorAtStart) { emitError(type); @@ -1572,7 +1461,7 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin if (errAtStart) { { QMutexLocker lock(&playerMutex); - AudioPlayer::Msg *m = checkLoader(type); + AudioPlayer::AudioMsg *m = checkLoader(type); if (m) m->state = AudioPlayerStoppedAtStart; } emitError(type); @@ -1591,7 +1480,7 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin } QMutexLocker lock(&playerMutex); - AudioPlayer::Msg *m = checkLoader(type); + AudioPlayer::AudioMsg *m = checkLoader(type); if (!m) { clear(type); return; @@ -1662,8 +1551,8 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin audioPlayer()->resumeDevice(); switch (type) { - case OverviewVoiceFiles: alSourcef(m->source, AL_GAIN, suppressAllGain); break; - case OverviewFiles: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; + case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, suppressAllGain); break; + case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; } if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); @@ -1687,93 +1576,59 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin } } -AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const void *objId, SetupError &err, qint64 position) { +AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position) { err = SetupErrorAtStart; QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); if (!voice) return nullptr; - bool isGoodId = false; - AudioPlayer::Msg *m = 0; - AudioPlayerLoader **l = 0; - switch (type) { - case OverviewVoiceFiles: { - AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); - const AudioMsgId &audio(*static_cast(objId)); - if (msg.audio != audio || !msg.loading) { - emit error(audio); - break; - } - m = &msg; - l = &_audioLoader; - isGoodId = (_audio == audio); - } break; - case OverviewFiles: { - AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); - const SongMsgId &song(*static_cast(objId)); - if (msg.song != song || !msg.loading) { - emit error(song); - break; - } - m = &msg; - l = &_songLoader; - isGoodId = (_song == song); - } break; - } - if (!l || !m) { + auto data = voice->dataForType(audio.type()); + if (!data || data->audio != audio || !data->loading) { + emit error(audio); LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); err = SetupErrorNotPlaying; return nullptr; } - if (*l && (!isGoodId || !(*l)->check(m->file, m->data))) { + bool isGoodId = false; + AudioPlayerLoader **l = nullptr; + switch (audio.type()) { + case AudioMsgId::Type::Voice: l = &_audioLoader; isGoodId = (_audio == audio); break; + case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (_song == audio); break; + } + + if (*l && (!isGoodId || !(*l)->check(data->file, data->data))) { delete *l; - *l = 0; - switch (type) { - case OverviewVoiceFiles: _audio = AudioMsgId(); break; - case OverviewFiles: _song = SongMsgId(); break; + *l = nullptr; + switch (audio.type()) { + case AudioMsgId::Type::Voice: _audio = AudioMsgId(); break; + case AudioMsgId::Type::Song: _song = AudioMsgId(); break; } } if (!*l) { - switch (type) { - case OverviewVoiceFiles: _audio = *static_cast(objId); break; - case OverviewFiles: _song = *static_cast(objId); break; + switch (audio.type()) { + case AudioMsgId::Type::Voice: _audio = audio; break; + case AudioMsgId::Type::Song: _song = audio; break; } -// QByteArray header = m->data.mid(0, 8); -// if (header.isEmpty()) { -// QFile f(m->fname); -// if (!f.open(QIODevice::ReadOnly)) { -// LOG(("Audio Error: could not open file '%1'").arg(m->fname)); -// m->state = AudioPlayerStoppedAtStart; -// return nullptr; -// } -// header = f.read(8); -// } -// if (header.size() < 8) { -// LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m->fname).arg(m->data.isEmpty() ? QFileInfo(m->fname).size() : m->data.size())); -// m->state = AudioPlayerStoppedAtStart; -// return nullptr; -// } - - *l = new FFMpegLoader(m->file, m->data); + *l = new FFMpegLoader(data->file, data->data); if (!(*l)->open(position)) { - m->state = AudioPlayerStoppedAtStart; + data->state = AudioPlayerStoppedAtStart; return nullptr; } int64 duration = (*l)->duration(); if (duration <= 0) { - m->state = AudioPlayerStoppedAtStart; + data->state = AudioPlayerStoppedAtStart; return nullptr; } - m->duration = duration; - m->frequency = (*l)->frequency(); - if (!m->frequency) m->frequency = AudioVoiceMsgFrequency; + data->duration = duration; + data->frequency = (*l)->frequency(); + if (!data->frequency) data->frequency = AudioVoiceMsgFrequency; err = SetupNoErrorStarted; } else { - if (!m->skipEnd) { + if (!data->skipEnd) { err = SetupErrorLoadedFull; LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); return nullptr; @@ -1782,71 +1637,41 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const return *l; } -AudioPlayer::Msg *AudioPlayerLoaders::checkLoader(MediaOverviewType type) { +AudioPlayer::AudioMsg *AudioPlayerLoaders::checkLoader(AudioMsgId::Type type) { AudioPlayer *voice = audioPlayer(); if (!voice) return 0; + auto data = voice->dataForType(type); bool isGoodId = false; - AudioPlayer::Msg *m = 0; - AudioPlayerLoader **l = 0; + AudioPlayerLoader **l = nullptr; switch (type) { - case OverviewVoiceFiles: { - AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); - isGoodId = (msg.audio == _audio); - l = &_audioLoader; - m = &msg; - } break; - case OverviewFiles: { - AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); - isGoodId = (msg.song == _song); - l = &_songLoader; - m = &msg; - } break; + case AudioMsgId::Type::Voice: l = &_audioLoader; isGoodId = (data->audio == _audio); break; + case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (data->audio == _song); break; } - if (!l || !m) return 0; + if (!l || !data) return nullptr; - if (!isGoodId || !m->loading || !(*l)->check(m->file, m->data)) { + if (!isGoodId || !data->loading || !(*l)->check(data->file, data->data)) { LOG(("Audio Error: playing changed while loading")); - return 0; + return nullptr; } - return m; + return data; } void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) { - if (_audio == audio) { - _audio = AudioMsgId(); - delete _audioLoader; - _audioLoader = 0; + switch (audio.type()) { + case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break; + case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break; } QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); if (!voice) return; - for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - AudioPlayer::AudioMsg &m(voice->_audioData[i]); - if (m.audio == audio) { - m.loading = false; - } - } -} - -void AudioPlayerLoaders::onCancel(const SongMsgId &song) { - if (_song == song) { - _song = SongMsgId(); - delete _songLoader; - _songLoader = 0; - } - - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - for (int32 i = 0; i < AudioSongSimultaneously; ++i) { - AudioPlayer::SongMsg &m(voice->_songData[i]); - if (m.song == song) { - m.loading = false; + for (int i = 0; i < AudioSimultaneousLimit; ++i) { + auto data = voice->dataForType(audio.type(), i); + if (data->audio == audio) { + data->loading = false; } } } diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index 17bbf8cf0..e0ff14e5a 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -54,18 +54,15 @@ public: AudioPlayer(); void play(const AudioMsgId &audio, int64 position = 0); - void play(const SongMsgId &song, int64 position = 0); - void pauseresume(MediaOverviewType type, bool fast = false); - void seek(int64 position); // type == OverviewFiles - void stop(MediaOverviewType type); + void pauseresume(AudioMsgId::Type type, bool fast = false); + void seek(int64 position); // type == AudioMsgId::Type::Song + void stop(AudioMsgId::Type type); void stopAndClear(); - void currentState(AudioMsgId *audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); - void currentState(SongMsgId *song, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); + void currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); void clearStoppedAtStart(const AudioMsgId &audio); - void clearStoppedAtStart(const SongMsgId &song); void resumeDevice(); @@ -74,27 +71,15 @@ public: public slots: void onError(const AudioMsgId &audio); - void onError(const SongMsgId &song); - void onStopped(const AudioMsgId &audio); - void onStopped(const SongMsgId &song); signals: void updated(const AudioMsgId &audio); - void updated(const SongMsgId &song); - void stopped(const AudioMsgId &audio); - void stopped(const SongMsgId &song); - void stoppedOnError(const AudioMsgId &audio); - void stoppedOnError(const SongMsgId &song); - void loaderOnStart(const AudioMsgId &audio, qint64 position); - void loaderOnStart(const SongMsgId &song, qint64 position); - void loaderOnCancel(const AudioMsgId &audio); - void loaderOnCancel(const SongMsgId &song); void faderOnTimer(); @@ -106,68 +91,45 @@ signals: private: - bool fadedStop(MediaOverviewType type, bool *fadedStart = 0); - bool updateCurrentStarted(MediaOverviewType type, int32 pos = -1); - bool checkCurrentALError(MediaOverviewType type); + bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); + bool updateCurrentStarted(AudioMsgId::Type type, int32 pos = -1); + bool checkCurrentALError(AudioMsgId::Type type); - struct Msg { - Msg() : position(0) - , duration(0) - , frequency(AudioVoiceMsgFrequency) - , skipStart(0) - , skipEnd(0) - , loading(false) - , started(0) - , state(AudioPlayerStopped) - , source(0) - , nextBuffer(0) { - memset(buffers, 0, sizeof(buffers)); - memset(samplesCount, 0, sizeof(samplesCount)); - } + struct AudioMsg { + void clear(); - void clearData(); + AudioMsgId audio; FileLocation file; QByteArray data; - int64 position, duration; - int32 frequency; - int64 skipStart, skipEnd; - bool loading; - int64 started; - AudioPlayerState state; + int64 position = 0; + int64 duration = 0; + int32 frequency = AudioVoiceMsgFrequency; + int64 skipStart = 0; + int64 skipEnd = 0; + bool loading = false; + int64 started = 0; + AudioPlayerState state = AudioPlayerStopped; - uint32 source; - int32 nextBuffer; - uint32 buffers[3]; - int64 samplesCount[3]; - }; - struct AudioMsg : public Msg { - AudioMsg() { - } - void clear() { - audio = AudioMsgId(); - Msg::clearData(); - } - AudioMsgId audio; - }; - struct SongMsg : public Msg { - SongMsg() { - } - void clear() { - song = SongMsgId(); - Msg::clearData(); - } - SongMsgId song; + uint32 source = 0; + int32 nextBuffer = 0; + uint32 buffers[3] = { 0 }; + int64 samplesCount[3] = { 0 }; }; - void currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); - void setStoppedState(Msg *current, AudioPlayerState state = AudioPlayerStopped); + void currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); + void setStoppedState(AudioMsg *current, AudioPlayerState state = AudioPlayerStopped); - int32 _audioCurrent; - AudioMsg _audioData[AudioVoiceMsgSimultaneously]; + AudioMsg *dataForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type) + const AudioMsg *dataForType(AudioMsgId::Type type, int index = -1) const; + int *currentIndex(AudioMsgId::Type type); + const int *currentIndex(AudioMsgId::Type type) const; - int32 _songCurrent; - SongMsg _songData[AudioSongSimultaneously]; + int _audioCurrent; + AudioMsg _audioData[AudioSimultaneousLimit]; + + int _songCurrent; + AudioMsg _songData[AudioSimultaneousLimit]; QMutex _mutex; @@ -228,13 +190,9 @@ public: signals: void error(const AudioMsgId &audio); - void error(const SongMsgId &audio); void playPositionUpdated(const AudioMsgId &audio); - void playPositionUpdated(const SongMsgId &audio); void audioStopped(const AudioMsgId &audio); - void audioStopped(const SongMsgId &audio); void needToPreload(const AudioMsgId &audio); - void needToPreload(const SongMsgId &audio); void stopPauseDevice(); @@ -258,8 +216,8 @@ private: EmitPositionUpdated = 0x04, EmitNeedToPreload = 0x08, }; - int32 updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); - void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped); + int32 updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); + void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); QTimer _timer, _pauseTimer; QMutex _pauseMutex; @@ -283,7 +241,6 @@ public: signals: void error(const AudioMsgId &audio); - void error(const SongMsgId &song); void needToCheck(); public slots: @@ -291,27 +248,22 @@ public slots: void onInit(); void onStart(const AudioMsgId &audio, qint64 position); - void onStart(const SongMsgId &audio, qint64 position); - void onLoad(const AudioMsgId &audio); - void onLoad(const SongMsgId &audio); - void onCancel(const AudioMsgId &audio); - void onCancel(const SongMsgId &audio); private: AudioMsgId _audio; AudioPlayerLoader *_audioLoader; - SongMsgId _song; + AudioMsgId _song; AudioPlayerLoader *_songLoader; - void emitError(MediaOverviewType type); - void clear(MediaOverviewType type); - void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped); + void emitError(AudioMsgId::Type type); + void clear(AudioMsgId::Type type); + void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); AudioMsgId clearAudio(); - SongMsgId clearSong(); + AudioMsgId clearSong(); enum SetupError { SetupErrorAtStart = 0, @@ -319,9 +271,9 @@ private: SetupErrorLoadedFull = 2, SetupNoErrorStarted = 3, }; - void loadData(MediaOverviewType type, const void *objId, qint64 position); - AudioPlayerLoader *setupLoader(MediaOverviewType type, const void *objId, SetupError &err, qint64 position); - AudioPlayer::Msg *checkLoader(MediaOverviewType type); + void loadData(const AudioMsgId &audio, qint64 position); + AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); + AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); }; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 93119734f..c62b700b1 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -100,8 +100,7 @@ enum { // a new message from the same sender is attached to previous within 15 minutes AttachMessageToPreviousSecondsDelta = 900, - AudioVoiceMsgSimultaneously = 4, - AudioSongSimultaneously = 4, + AudioSimultaneousLimit = 4, AudioCheckPositionTimeout = 100, // 100ms per check audio pos AudioCheckPositionDelta = 2400, // update position called each 2400 samples AudioFadeTimeout = 7, // 7ms diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index ded75552a..1d0791bc0 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -4391,7 +4391,7 @@ bool HistoryDocument::updateStatusText() const { int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); } if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { @@ -4420,22 +4420,22 @@ bool HistoryDocument::updateStatusText() const { } } } else if (_data->song()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); } - if (playing == SongMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { statusSize = FileStatusSizeLoaded; } - if (!showPause && (playing == SongMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { showPause = true; } } else { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 5b9f7d89b..4f1285e90 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -859,7 +859,7 @@ bool File::updateStatusText() const { int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); } if (playing == AudioMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { @@ -870,22 +870,22 @@ bool File::updateStatusText() const { statusSize = FileStatusSizeLoaded; } } else if (document->song()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); } - if (playing == SongMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing == AudioMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { statusSize = FileStatusSizeLoaded; } - if (!showPause && (playing == SongMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) { showPause = true; } } else { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fab677930..45846a9be 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -92,8 +92,6 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window) if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); - connect(audioPlayer(), SIGNAL(updated(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&))); - connect(audioPlayer(), SIGNAL(stopped(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&))); } connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement())); @@ -1533,46 +1531,22 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) { void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { AudioMsgId playing; - AudioPlayerState state = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &state); - if (playing == audioId && state == AudioPlayerStoppedAtStart) { + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + audioPlayer()->currentState(&playing, audioId.type(), &playingState, &playingPosition, &playingDuration, &playingFrequency); + if (playing == audioId && playingState == AudioPlayerStoppedAtStart) { + playingState = AudioPlayerStopped; audioPlayer()->clearStoppedAtStart(audioId); - DocumentData *audio = audioId.audio; + DocumentData *audio = audioId.audio(); QString filepath = audio->filepath(DocumentData::FilePathResolveSaveFromData); if (!filepath.isEmpty()) { psOpenFile(filepath); } } - if (HistoryItem *item = App::histItemById(audioId.contextId)) { - Ui::repaintHistoryItem(item); - } - if (auto items = InlineBots::Layout::documentItems()) { - for (auto item : items->value(audioId.audio)) { - Ui::repaintInlineItem(item); - } - } -} - -void MainWidget::documentPlayProgress(const SongMsgId &songId) { - SongMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - if (playing == songId && playingState == AudioPlayerStoppedAtStart) { - playingState = AudioPlayerStopped; - audioPlayer()->clearStoppedAtStart(songId); - - DocumentData *document = songId.song; - QString filepath = document->filepath(DocumentData::FilePathResolveSaveFromData); - if (!filepath.isEmpty()) { - psOpenFile(filepath); - } - } - - if (playing == songId) { + if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) { _player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { @@ -1587,11 +1561,11 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { } } - if (HistoryItem *item = App::histItemById(songId.contextId)) { + if (HistoryItem *item = App::histItemById(audioId.contextId())) { Ui::repaintHistoryItem(item); } if (auto items = InlineBots::Layout::documentItems()) { - for (auto item : items->value(songId.song)) { + for (auto item : items->value(audioId.audio())) { Ui::repaintInlineItem(item); } } @@ -1628,12 +1602,12 @@ void MainWidget::documentLoadProgress(FileLoader *loader) { App::wnd()->documentUpdated(document); if (!document->loaded() && document->loading() && document->song() && audioPlayer()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - if (playing.song == document && !_player->isHidden()) { + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); + if (playing.audio() == document && !_player->isHidden()) { _player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c6e937e42..63349cdc7 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -445,7 +445,6 @@ public slots: void documentLoadProgress(FileLoader *loader); void documentLoadFailed(FileLoader *loader, bool started); void documentLoadRetry(); - void documentPlayProgress(const SongMsgId &songId); void inlineResultLoadProgress(FileLoader *loader); void inlineResultLoadFailed(FileLoader *loader, bool started); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 59faa38b1..6c05b881f 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -561,7 +561,7 @@ bool Voice::updateStatusText() const { int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); } if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { @@ -865,22 +865,22 @@ bool Document::updateStatusText() const { statusSize = _data->loadOffset(); } else if (_data->loaded()) { if (_data->song()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); } - if (playing == SongMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { statusSize = FileStatusSizeLoaded; } - if (!showPause && (playing == SongMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { showPause = true; } } else { diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 6bda8abf7..19b80726a 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -2091,11 +2091,11 @@ int32 OverviewWidget::lastScrollTop() const { int32 OverviewWidget::countBestScroll() const { if (type() == OverviewMusicFiles && audioPlayer()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); if (playing) { - int32 top = _inner.itemTop(playing.contextId); + int32 top = _inner.itemTop(playing.contextId()); if (top >= 0) { return snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax()); } diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index ca697b771..29f7941ce 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -168,14 +168,14 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) { emit audioPlayer()->songVolumeChanged(); rtlupdate(_volumeRect); } else if (_over == OverPlayback) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); if (playing == _song && playingDuration) { if (playingState == AudioPlayerPlaying || playingState == AudioPlayerStarting || playingState == AudioPlayerResuming) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } _down = OverPlayback; _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); @@ -186,7 +186,7 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) { updateDownTime(); } } else if (_over == OverFull && _song) { - if (HistoryItem *item = App::histItemById(_song.contextId)) { + if (HistoryItem *item = App::histItemById(_song.contextId())) { App::main()->showMediaOverview(item->history()->peer, OverviewMusicFiles); } } else if (_over == OverRepeat) { @@ -271,12 +271,12 @@ void PlayerWidget::updateControls() { void PlayerWidget::findCurrent() { _index = -1; - if (!_history || !_song.contextId.msg) return; + if (!_history || !_song.contextId().msg) return; const History::MediaOverview *o = &(_msgmigrated ? _migrated : _history)->overview[OverviewMusicFiles]; - if ((_msgmigrated ? _migrated : _history)->channelId() == _song.contextId.channel) { + if ((_msgmigrated ? _migrated : _history)->channelId() == _song.contextId().channel) { for (int i = 0, l = o->size(); i < l; ++i) { - if (o->at(i) == _song.contextId.msg) { + if (o->at(i) == _song.contextId().msg) { _index = i; break; } @@ -311,7 +311,7 @@ void PlayerWidget::preloadNext() { void PlayerWidget::startPlay(const FullMsgId &msgId) { if (HistoryItem *item = App::histItemById(msgId)) { if (HistoryDocument *doc = static_cast(item->getMedia())) { - audioPlayer()->play(SongMsgId(doc->getDocument(), item->fullId())); + audioPlayer()->play(AudioMsgId(doc->getDocument(), item->fullId())); updateState(); } } @@ -328,9 +328,9 @@ void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) if (_history && (_history->peer == peer || (_migrated && _migrated->peer == peer)) && type == OverviewMusicFiles) { _index = -1; History *history = _msgmigrated ? _migrated : _history; - if (history->channelId() == _song.contextId.channel && _song.contextId.msg) { + if (history->channelId() == _song.contextId().channel && _song.contextId().msg) { for (int i = 0, l = history->overview[OverviewMusicFiles].size(); i < l; ++i) { - if (history->overview[OverviewMusicFiles].at(i) == _song.contextId.msg) { + if (history->overview[OverviewMusicFiles].at(i) == _song.contextId().msg) { _index = i; preloadNext(); break; @@ -341,7 +341,7 @@ void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) } } -bool PlayerWidget::seekingSong(const SongMsgId &song) const { +bool PlayerWidget::seekingSong(const AudioMsgId &song) const { return (_down == OverPlayback) && (song == _song); } @@ -443,11 +443,11 @@ void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { Local::writeUserSettings(); } else if (_down == OverPlayback) { mouseMoveEvent(e); - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); if (playing == _song && playingDuration) { _downDuration = playingDuration; audioPlayer()->seek(qRound(_downProgress * _downDuration)); @@ -467,28 +467,28 @@ void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { void PlayerWidget::playPressed() { if (!_song || isHidden()) return; - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { if (playingState == AudioPlayerPausing || playingState == AudioPlayerPaused || playingState == AudioPlayerPausedAtEnd) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } } else { audioPlayer()->play(_song); - if (App::main()) App::main()->documentPlayProgress(_song); + if (App::main()) App::main()->audioPlayProgress(_song); } } void PlayerWidget::pausePressed() { if (!_song || isHidden()) return; - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { if (playingState == AudioPlayerStarting || playingState == AudioPlayerResuming || playingState == AudioPlayerPlaying || playingState == AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } } } @@ -496,14 +496,14 @@ void PlayerWidget::pausePressed() { void PlayerWidget::playPausePressed() { if (!_song || isHidden()) return; - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { audioPlayer()->play(_song); - if (App::main()) App::main()->documentPlayProgress(_song); + if (App::main()) App::main()->audioPlayProgress(_song); } } @@ -540,7 +540,7 @@ void PlayerWidget::nextPressed() { void PlayerWidget::stopPressed() { if (!_song || isHidden()) return; - audioPlayer()->stop(OverviewFiles); + audioPlayer()->stop(AudioMsgId::Type::Song); } void PlayerWidget::closePressed() { @@ -581,19 +581,19 @@ void PlayerWidget::step_progress(float64 ms, bool timer) { } void PlayerWidget::updateState() { - updateState(SongMsgId(), AudioPlayerStopped, 0, 0, 0); + updateState(AudioMsgId(), AudioPlayerStopped, 0, 0, 0); } -void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency) { +void PlayerWidget::updateState(AudioMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency) { if (!playing) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); } bool songChanged = false; if (playing && _song != playing) { songChanged = true; _song = playing; - if (HistoryItem *item = App::histItemById(_song.contextId)) { + if (HistoryItem *item = App::histItemById(_song.contextId())) { _history = item->history(); if (_history->peer->migrateFrom()) { _migrated = App::history(_history->peer->migrateFrom()->id); @@ -609,9 +609,9 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, _msgmigrated = false; _index = -1; } - SongData *song = _song.song->song(); + auto song = _song.audio()->song(); if (song->performer.isEmpty()) { - _name.setText(st::linkFont, song->title.isEmpty() ? (_song.song->name.isEmpty() ? qsl("Unknown Track") : _song.song->name) : song->title, _textNameOptions); + _name.setText(st::linkFont, song->title.isEmpty() ? (_song.audio()->name.isEmpty() ? qsl("Unknown Track") : _song.audio()->name) : song->title, _textNameOptions); } else { TextCustomTagsMap custom; custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); @@ -630,7 +630,7 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, } display = display / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); } else if (_song) { - display = _song.song->song()->duration; + display = _song.audio()->song()->duration; } bool showPause = false, stopped = ((playingState & AudioPlayerStoppedMask) || playingState == AudioPlayerFinishing); bool wasPlaying = (_duration != 0); @@ -641,14 +641,14 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, float64 progress = 0.; int32 loaded; float64 loadProgress = 1.; - if (duration || !_song || !_song.song || !_song.song->loading()) { + if (duration || !_song || !_song.audio() || !_song.audio()->loading()) { time = (_down == OverPlayback) ? _time : formatDurationText(display); progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; - loaded = duration ? _song.song->size : 0; + loaded = duration ? _song.audio()->size : 0; } else { - loaded = _song.song->loading() ? _song.song->loadOffset() : 0; - time = formatDownloadText(loaded, _song.song->size); - loadProgress = snap(float64(loaded) / qMax(_song.song->size, 1), 0., 1.); + loaded = _song.audio()->loading() ? _song.audio()->loadOffset() : 0; + time = formatDownloadText(loaded, _song.audio()->size); + loadProgress = snap(float64(loaded) / qMax(_song.audio()->size, 1), 0., 1.); } if (time != _time || showPause != _showPause) { if (_time != time) { @@ -688,8 +688,8 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, if (wasPlaying && playingState == AudioPlayerStoppedAtEnd) { if (_repeat) { - if (_song.song) { - audioPlayer()->play(_song); + if (_song.audio()) { + audioPlayer()->play(_song, OverviewMusicFiles); updateState(); } } else { @@ -698,6 +698,6 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, } if (songChanged) { - emit playerSongChanged(_song.contextId); + emit playerSongChanged(_song.contextId()); } } diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index 4eda5b0d7..72aa9ba98 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -47,13 +47,13 @@ public: void step_progress(float64 ms, bool timer); void step_state(uint64 ms, bool timer); - void updateState(SongMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency); + void updateState(AudioMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency); void updateState(); void clearSelection(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); - bool seekingSong(const SongMsgId &song) const; + bool seekingSong(const AudioMsgId &song) const; void openPlayer(); bool isOpened() const; @@ -115,7 +115,7 @@ private: StateAnimations _stateAnimations; Animation _a_state; - SongMsgId _song; + AudioMsgId _song; bool _msgmigrated = false; int32 _index = -1; History *_migrated = nullptr; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index b5c7eb34f..6736da8ff 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -954,9 +954,9 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { if (playVoice) { AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState); if (playing == AudioMsgId(data, msgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewVoiceFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Voice); } else { AudioMsgId audio(data, msgId); audioPlayer()->play(audio); @@ -966,15 +966,15 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } } } else if (playMusic) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); - if (playing == SongMsgId(data, msgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); + if (playing == AudioMsgId(data, msgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { - SongMsgId song(data, msgId); + AudioMsgId song(data, msgId); audioPlayer()->play(song); - if (App::main()) App::main()->documentPlayProgress(song); + if (App::main()) App::main()->audioPlayProgress(song); } } else if (playVideo) { if (!data->data().isEmpty()) { @@ -1243,9 +1243,9 @@ void DocumentData::performActionOnLoad() { if (loaded()) { AudioMsgId playing; AudioPlayerState state = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &state); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &state); if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewVoiceFiles); + audioPlayer()->pauseresume(AudioMsgId::Type::Voice); } else { audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId)); if (App::main()) App::main()->mediaMarkRead(this); @@ -1253,15 +1253,15 @@ void DocumentData::performActionOnLoad() { } } else if (playMusic) { if (loaded()) { - SongMsgId playing; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); - if (playing == SongMsgId(this, _actionOnLoadMsgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewFiles); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); + if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { - SongMsgId song(this, _actionOnLoadMsgId); + AudioMsgId song(this, _actionOnLoadMsgId); audioPlayer()->play(song); - if (App::main()) App::main()->documentPlayProgress(song); + if (App::main()) App::main()->audioPlayProgress(song); } } } else if (playAnimation) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 1a948de46..379fd0c18 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1229,51 +1229,62 @@ private: VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); -struct SongMsgId { - SongMsgId() : song(nullptr) { - } - SongMsgId(DocumentData *song, const FullMsgId &msgId) : song(song), contextId(msgId) { - } - SongMsgId(DocumentData *song, ChannelId channelId, MsgId msgId) : song(song), contextId(channelId, msgId) { - } - explicit operator bool() const { - return song; - } - DocumentData *song; - FullMsgId contextId; +class AudioMsgId { +public: + enum class Type { + Unknown, + Voice, + Song, + Video, + }; -}; -inline bool operator<(const SongMsgId &a, const SongMsgId &b) { - return quintptr(a.song) < quintptr(b.song) || (quintptr(a.song) == quintptr(b.song) && a.contextId < b.contextId); -} -inline bool operator==(const SongMsgId &a, const SongMsgId &b) { - return a.song == b.song && a.contextId == b.contextId; -} -inline bool operator!=(const SongMsgId &a, const SongMsgId &b) { - return !(a == b); -} + AudioMsgId() { + } + AudioMsgId(DocumentData *audio, const FullMsgId &msgId) : _audio(audio), _contextId(msgId) { + setType(); + } + AudioMsgId(DocumentData *audio, ChannelId channelId, MsgId msgId) : _audio(audio), _contextId(channelId, msgId) { + setType(); + } -struct AudioMsgId { - AudioMsgId() : audio(nullptr) { + Type type() const { + return _type; } - AudioMsgId(DocumentData *audio, const FullMsgId &msgId) : audio(audio), contextId(msgId) { + DocumentData *audio() const { + return _audio; } - AudioMsgId(DocumentData *audio, ChannelId channelId, MsgId msgId) : audio(audio), contextId(channelId, msgId) { + FullMsgId contextId() const { + return _contextId; } explicit operator bool() const { - return audio; + return _audio; } - DocumentData *audio; - FullMsgId contextId; + +private: + void setType() { + if (_audio->voice()) { + _type = Type::Voice; + } else if (_audio->song()) { + _type = Type::Song; + } else if (_audio->isVideo()) { + _type = Type::Video; + } else { + _type = Type::Unknown; + } + } + + DocumentData *_audio = nullptr; + Type _type = Type::Unknown; + FullMsgId _contextId; }; inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { - return quintptr(a.audio) < quintptr(b.audio) || (quintptr(a.audio) == quintptr(b.audio) && a.contextId < b.contextId); + return quintptr(a.audio()) < quintptr(b.audio()) || (quintptr(a.audio()) == quintptr(b.audio()) && a.contextId() < b.contextId()); } inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) { - return a.audio == b.audio && a.contextId == b.contextId; + return a.audio() == b.audio() && a.contextId() == b.contextId(); } inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { return !(a == b); From 8ea47c1811c20cbc058c587461190e85d7db957a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jul 2016 17:48:36 +0300 Subject: [PATCH 05/60] Opened histories stack finished, stack of states in vector_of_moveable. --- .../SourceFiles/core/vector_of_moveable.h | 5 +- Telegram/SourceFiles/facades.cpp | 10 +- Telegram/SourceFiles/facades.h | 25 ++-- Telegram/SourceFiles/history.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 28 ++-- Telegram/SourceFiles/mainwidget.cpp | 121 +++++++++++------- Telegram/SourceFiles/mainwidget.h | 30 +---- Telegram/SourceFiles/mtproto/scheme.tl | 1 + Telegram/SourceFiles/mtproto/scheme_auto.cpp | 14 ++ Telegram/SourceFiles/mtproto/scheme_auto.h | 47 +++++++ .../SourceFiles/profile/profile_cover.cpp | 2 +- .../SourceFiles/window/top_bar_widget.cpp | 2 +- 12 files changed, 184 insertions(+), 103 deletions(-) diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h index 412d87b35..608bb0796 100644 --- a/Telegram/SourceFiles/core/vector_of_moveable.h +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -141,7 +141,10 @@ private: void reallocate(int newCapacity) { auto newPlainData = operator new[](newCapacity * sizeof(T)); for (int i = 0; i < _size; ++i) { - *(reinterpret_cast(newPlainData) + i) = std_::move(*(data() + i)); + auto oldLocation = data() + i; + auto newLocation = reinterpret_cast(newPlainData) + i; + new (newLocation) T(std_::move(*oldLocation)); + oldLocation->~T(); } std::swap(_plaindata, newPlainData); _capacity = newCapacity; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 58175e636..1e8a6239d 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org Q_DECLARE_METATYPE(ClickHandlerPtr); Q_DECLARE_METATYPE(Qt::MouseButton); +Q_DECLARE_METATYPE(Ui::ShowWay); namespace App { @@ -253,13 +254,14 @@ void showPeerOverview(const PeerId &peer, MediaOverviewType type) { } } -void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) { - if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, back); +void showPeerHistory(const PeerId &peer, MsgId msgId, ShowWay way) { + if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, way); } -void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { +void showPeerHistoryAsync(const PeerId &peer, MsgId msgId, ShowWay way) { if (MainWidget *m = App::main()) { - QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); + qRegisterMetaType(); + QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId), Q_ARG(Ui::ShowWay, way)); } } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 71cd432e5..59a990bbd 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -83,22 +83,27 @@ inline void showPeerOverview(const History *history, MediaOverviewType type) { showPeerOverview(history->peer->id, type); } -void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); -inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { - showPeerHistory(peer->id, msgId, back); +enum class ShowWay { + ClearStack, + Forward, + Backward, +}; +void showPeerHistory(const PeerId &peer, MsgId msgId, ShowWay way = ShowWay::ClearStack); +inline void showPeerHistory(const PeerData *peer, MsgId msgId, ShowWay way = ShowWay::ClearStack) { + showPeerHistory(peer->id, msgId, way); } -inline void showPeerHistory(const History *history, MsgId msgId, bool back = false) { - showPeerHistory(history->peer->id, msgId, back); +inline void showPeerHistory(const History *history, MsgId msgId, ShowWay way = ShowWay::ClearStack) { + showPeerHistory(history->peer->id, msgId, way); } -inline void showPeerHistoryAtItem(const HistoryItem *item) { - showPeerHistory(item->history()->peer->id, item->id); +inline void showPeerHistoryAtItem(const HistoryItem *item, ShowWay way = ShowWay::ClearStack) { + showPeerHistory(item->history()->peer->id, item->id, way); } -void showPeerHistoryAsync(const PeerId &peer, MsgId msgId); +void showPeerHistoryAsync(const PeerId &peer, MsgId msgId, ShowWay way = ShowWay::ClearStack); inline void showChatsList() { - showPeerHistory(PeerId(0), 0); + showPeerHistory(PeerId(0), 0, ShowWay::ClearStack); } inline void showChatsListAsync() { - showPeerHistoryAsync(PeerId(0), 0); + showPeerHistoryAsync(PeerId(0), 0, ShowWay::ClearStack); } PeerData *getPeerForMouseAction(); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index a29b92053..f298cde33 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -5178,7 +5178,7 @@ int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryM } void SendMessageClickHandler::onClickImpl() const { - Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward); } void AddContactClickHandler::onClickImpl() const { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 47a8fe198..6748fe79c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3761,7 +3761,7 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic QString title = stickerSetTitle(set); if (it == sets.cend()) { auto clientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; - if (unread.contains(set.vid.v)) { + if (unread.contains(set.vid.v) || !(set.vflags.v & MTPDstickerSet::Flag::f_installed)) { clientFlags |= MTPDstickerSet_ClientFlag::f_unread; } it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | clientFlags)); @@ -4673,7 +4673,7 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) { PeerData *was = _peer; - Ui::showChatsList(); + App::main()->showBackFromStack(); Ui::showLayer(new InformBox(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); return true; } @@ -4685,7 +4685,7 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId _preloadDownRequest = 0; } else if (_firstLoadRequest == requestId) { _firstLoadRequest = 0; - Ui::showChatsList(); + App::main()->showBackFromStack(); } else if (_delayedShowAtRequest == requestId) { _delayedShowAtRequest = 0; } @@ -5704,7 +5704,7 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC Ui::Toast::Show(App::wnd(), toast); } } else if (answerData.has_url()) { - UrlClickHandler::doOpen(qs(answerData.vurl)); + HiddenUrlClickHandler(qs(answerData.vurl)).onClick(Qt::LeftButton); } } } @@ -6139,7 +6139,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (!_history) return; - int32 increaseLeft = Adaptive::OneColumn() ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0; + int32 increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0; decreaseWidth += increaseLeft; QRect rectForName(st::topBarForwardPadding.left() + increaseLeft, st::topBarForwardPadding.top(), width() - decreaseWidth - st::topBarForwardPadding.left() - st::topBarForwardPadding.right(), st::msgNameFont->height); p.setFont(st::dialogsTextFont); @@ -6154,7 +6154,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { p.setPen(st::dialogsNameFg); _peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); - if (Adaptive::OneColumn()) { + if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) { p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over); p.drawSprite(QPoint((st::topBarForwardPadding.right() - st::topBarBackwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarBackwardImg.pxHeight()) / 2), st::topBarBackwardImg); } else { @@ -6164,7 +6164,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { } QRect HistoryWidget::getMembersShowAreaGeometry() const { - int increaseLeft = Adaptive::OneColumn() ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0; + int increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0; int membersTextLeft = st::topBarForwardPadding.left() + increaseLeft; int membersTextTop = st::topBarHeight - st::topBarForwardPadding.bottom() - st::dialogsTextFont->height; int membersTextWidth = _titlePeerTextWidth; @@ -6210,8 +6210,8 @@ void HistoryWidget::onMembersDropdownHidden() { } void HistoryWidget::topBarClick() { - if (Adaptive::OneColumn()) { - Ui::showChatsList(); + if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) { + App::main()->showBackFromStack(); } else { if (_history) Ui::showPeerProfile(_peer); } @@ -6763,10 +6763,10 @@ void HistoryWidget::onReportSpamClear() { if (_clearPeer->isUser()) { App::main()->deleteConversation(_clearPeer); } else if (_clearPeer->isChat()) { - Ui::showChatsList(); + App::main()->showBackFromStack(); MTP::send(MTPmessages_DeleteChatUser(_clearPeer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _clearPeer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _clearPeer)); } else if (_clearPeer->isChannel()) { - Ui::showChatsList(); + App::main()->showBackFromStack(); if (_clearPeer->migrateFrom()) { App::main()->deleteConversation(_clearPeer->migrateFrom()); } @@ -7294,7 +7294,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Back) { - Ui::showChatsList(); + App::main()->showBackFromStack(); emit cancelled(); } else if (e->key() == Qt::Key_PageDown) { _scroll.keyPressEvent(e); @@ -8072,7 +8072,7 @@ void HistoryWidget::onCancel() { } else if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideStart(); } else { - Ui::showChatsList(); + App::main()->showBackFromStack(); emit cancelled(); } } @@ -8110,7 +8110,7 @@ void HistoryWidget::peerUpdated(PeerData *data) { } QString restriction = _peer->restrictionReason(); if (!restriction.isEmpty()) { - Ui::showChatsList(); + App::main()->showBackFromStack(); Ui::showLayer(new InformBox(restriction)); return; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f5a74cc59..23e417042 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1519,8 +1519,8 @@ void MainWidget::onSharePhoneWithBot(PeerData *recipient) { onShareContact(recipient->id, App::self()); } -void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId) { - Ui::showPeerHistory(peerId, showAtMsgId); +void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) { + Ui::showPeerHistory(peerId, showAtMsgId, way); } void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) { @@ -2016,7 +2016,7 @@ void MainWidget::ctrlEnterSubmitUpdated() { _history->updateFieldSubmitSettings(); } -void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) { +void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) { if (PeerData *peer = App::peerLoaded(peerId)) { if (peer->migrateTo()) { peer = peer->migrateTo(); @@ -2030,8 +2030,37 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac return; } } - if (!back && (!peerId || (_stack.size() == 1 && _stack[0]->type() == HistoryStackItem && _stack[0]->peer->id == peerId))) { - back = true; + + bool back = (way == Ui::ShowWay::Backward || !peerId); + bool foundInStack = !peerId; + if (foundInStack || (way == Ui::ShowWay::ClearStack)) { + for_const (auto &item, _stack) { + clearBotStartToken(item->peer); + } + _stack.clear(); + } else { + for (int i = 0, s = _stack.size(); i < s; ++i) { + if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer->id == peerId) { + foundInStack = true; + while (_stack.size() > i) { + clearBotStartToken(_stack.back()->peer); + _stack.pop_back(); + } + if (!back) { + back = true; + } + break; + } + } + } + + if (back || (way == Ui::ShowWay::ClearStack)) { + dlgUpdated(); + _peerInStack = nullptr; + _msgIdInStack = 0; + dlgUpdated(); + } else { + saveSectionInStack(); } PeerData *wasActivePeer = activePeer(); @@ -2043,10 +2072,12 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } Window::SectionSlideParams animationParams; - if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) { + if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)) || back || (way == Ui::ShowWay::Forward))) { animationParams = prepareHistoryAnimation(peerId); } - if (_history->peer() && _history->peer()->id != peerId) clearBotStartToken(_history->peer()); + if (_history->peer() && _history->peer()->id != peerId) { + clearBotStartToken(_history->peer()); + } _history->showHistory(peerId, showAtMsgId); bool noPeer = (!_history->peer() || !_history->peer()->id), onlyDialogs = noPeer && Adaptive::OneColumn(); @@ -2063,11 +2094,6 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac _overview->rpcClear(); _overview = nullptr; } - clearBotStartToken(_peerInStack); - dlgUpdated(); - _peerInStack = 0; - _msgIdInStack = 0; - _stack.clear(); } if (onlyDialogs) { _topBar->hide(); @@ -2111,6 +2137,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } _dialogs->update(); } + topBar()->showAll(); App::wnd()->getTitle()->updateBackButton(); } @@ -2167,6 +2194,21 @@ bool MainWidget::mediaTypeSwitch() { return true; } +void MainWidget::saveSectionInStack() { + if (_overview) { + _stack.push_back(std_::make_unique(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); + } else if (_wideSection) { + _stack.push_back(std_::make_unique(_wideSection->createMemento())); + } else if (_history->peer()) { + dlgUpdated(); + _peerInStack = _history->peer(); + _msgIdInStack = _history->msgId(); + dlgUpdated(); + + _stack.push_back(std_::make_unique(_peerInStack, _msgIdInStack, _history->replyReturns())); + } +} + void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool back, int32 lastScrollTop) { if (peer->migrateTo()) { peer = peer->migrateTo(); @@ -2187,17 +2229,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool animationParams = prepareOverviewAnimation(); } if (!back) { - if (_overview) { - _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_wideSection) { - _stack.push_back(new StackItemSection(_wideSection->createMemento())); - } else if (_history->peer()) { - dlgUpdated(); - _peerInStack = _history->peer(); - _msgIdInStack = _history->msgId(); - dlgUpdated(); - _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); - } + saveSectionInStack(); } if (_overview) { _overview->hide(); @@ -2222,7 +2254,9 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _overview->fastShow(); } _history->animStop(); - if (back) clearBotStartToken(_history->peer()); + if (back) { + clearBotStartToken(_history->peer()); + } _history->showHistory(0, 0); _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); @@ -2238,17 +2272,7 @@ void MainWidget::showWideSection(const Window::SectionMemento &memento) { return; } - if (_overview) { - _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_wideSection) { - _stack.push_back(new StackItemSection(_wideSection->createMemento())); - } else if (_history->peer()) { - dlgUpdated(); - _peerInStack = _history->peer(); - _msgIdInStack = _history->msgId(); - dlgUpdated(); - _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); - } + saveSectionInStack(); showWideSectionAnimated(&memento, false); } @@ -2341,6 +2365,10 @@ void MainWidget::showWideSectionAnimated(const Window::SectionMemento *memento, App::wnd()->getTitle()->updateBackButton(); } +bool MainWidget::stackIsEmpty() const { + return _stack.isEmpty(); +} + void MainWidget::showBackFromStack() { if (selectingPeer()) return; if (_stack.isEmpty()) { @@ -2348,34 +2376,33 @@ void MainWidget::showBackFromStack() { if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); return; } - StackItem *item = _stack.back(); + auto item = std_::move(_stack.back()); _stack.pop_back(); if (auto currentHistoryPeer = _history->peer()) { clearBotStartToken(currentHistoryPeer); } if (item->type() == HistoryStackItem) { dlgUpdated(); - _peerInStack = 0; + _peerInStack = nullptr; _msgIdInStack = 0; for (int32 i = _stack.size(); i > 0;) { if (_stack.at(--i)->type() == HistoryStackItem) { - _peerInStack = static_cast(_stack.at(i))->peer; - _msgIdInStack = static_cast(_stack.at(i))->msgId; + _peerInStack = static_cast(_stack.at(i).get())->peer; + _msgIdInStack = static_cast(_stack.at(i).get())->msgId; dlgUpdated(); break; } } - StackItemHistory *histItem = static_cast(item); - Ui::showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true); + StackItemHistory *histItem = static_cast(item.get()); + Ui::showPeerHistory(histItem->peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Backward); _history->setReplyReturns(histItem->peer->id, histItem->replyReturns); } else if (item->type() == SectionStackItem) { - StackItemSection *sectionItem = static_cast(item); + StackItemSection *sectionItem = static_cast(item.get()); showWideSectionAnimated(sectionItem->memento(), true); } else if (item->type() == OverviewStackItem) { - StackItemOverview *overItem = static_cast(item); + StackItemOverview *overItem = static_cast(item.get()); showMediaOverview(overItem->peer, overItem->mediaType, true, overItem->lastScrollTop); } - delete item; } void MainWidget::orderWidgets() { @@ -3343,7 +3370,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr Ui::showLayer(new ContactsBox(peer->asUser())); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. - Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward); } else { Ui::showPeerProfile(peer); } @@ -3358,7 +3385,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr _history->resizeEvent(0); } } - Ui::showPeerHistoryAsync(peer->id, msgId); + Ui::showPeerHistoryAsync(peer->id, msgId, Ui::ShowWay::Forward); } } else { MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(msgId, startToken)), rpcFail(&MainWidget::usernameResolveFail, username)); @@ -3425,7 +3452,7 @@ void MainWidget::usernameResolveDone(QPair msgIdAndStartToken, c Ui::showLayer(new ContactsBox(peer->asUser())); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. - Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward); } else { Ui::showPeerProfile(peer); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c6e937e42..baae91478 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -108,27 +108,6 @@ public: int32 lastWidth, lastScrollTop; }; -class StackItems : public QVector { -public: - bool contains(PeerData *peer) const { - for (int32 i = 0, l = size(); i < l; ++i) { - if (at(i)->peer == peer) { - return true; - } - } - return false; - } - void clear() { - for (int32 i = 0, l = size(); i < l; ++i) { - delete at(i); - } - QVector::clear(); - } - ~StackItems() { - clear(); - } -}; - enum SilentNotifiesStatus { SilentNotifiesDontChange, SilentNotifiesSetSilent, @@ -224,6 +203,7 @@ public: bool mediaTypeSwitch(); void showWideSection(const Window::SectionMemento &memento); void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1); + bool stackIsEmpty() const; void showBackFromStack(); void orderWidgets(); QRect historyRect() const; @@ -404,7 +384,7 @@ public: void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); - void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back); + void ui_showPeerHistory(quint64 peer, qint32 msgId, Ui::ShowWay way); PeerData *ui_getPeerForMouseAction(); void notify_botCommandsChanged(UserData *bot); @@ -493,7 +473,7 @@ public slots: void onSharePhoneWithBot(PeerData *recipient); - void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId); + void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way); void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId); private slots: @@ -519,6 +499,8 @@ private: Window::SectionSlideParams prepareOverviewAnimation(); Window::SectionSlideParams prepareDialogsAnimation(); + void saveSectionInStack(); + bool _started = false; uint64 failedObjId = 0; @@ -598,7 +580,7 @@ private: ChildWidget _topBar; ConfirmBox *_forwardConfirm = nullptr; // for single column layout ChildWidget _hider = { nullptr }; - StackItems _stack; + std_::vector_of_moveable> _stack; PeerData *_peerInStack = nullptr; MsgId _msgIdInStack = 0; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 777de9b15..aaa886675 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -507,6 +507,7 @@ documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = Docume documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; +documentAttributeVersion#99cd09ab version:int = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#8a8ecd32 hash:string stickers:Vector = messages.Stickers; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index f22248eaa..9b4b655c1 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -4018,6 +4018,19 @@ void _serialize_documentAttributeFilename(MTPStringLogger &to, int32 stage, int3 } } +void _serialize_documentAttributeVersion(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ documentAttributeVersion"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_messages_stickersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ messages_stickersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -8612,6 +8625,7 @@ namespace { _serializers.insert(mtpc_documentAttributeVideo, _serialize_documentAttributeVideo); _serializers.insert(mtpc_documentAttributeAudio, _serialize_documentAttributeAudio); _serializers.insert(mtpc_documentAttributeFilename, _serialize_documentAttributeFilename); + _serializers.insert(mtpc_documentAttributeVersion, _serialize_documentAttributeVersion); _serializers.insert(mtpc_messages_stickersNotModified, _serialize_messages_stickersNotModified); _serializers.insert(mtpc_messages_stickers, _serialize_messages_stickers); _serializers.insert(mtpc_stickerPack, _serialize_stickerPack); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 15b3d4942..0c0d69db2 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -372,6 +372,7 @@ enum { mtpc_documentAttributeVideo = 0x5910cccb, mtpc_documentAttributeAudio = 0x9852f9c6, mtpc_documentAttributeFilename = 0x15590068, + mtpc_documentAttributeVersion = 0x99cd09ab, mtpc_messages_stickersNotModified = 0xf1749a22, mtpc_messages_stickers = 0x8a8ecd32, mtpc_stickerPack = 0x12b299d4, @@ -1138,6 +1139,7 @@ class MTPDdocumentAttributeSticker; class MTPDdocumentAttributeVideo; class MTPDdocumentAttributeAudio; class MTPDdocumentAttributeFilename; +class MTPDdocumentAttributeVersion; class MTPmessages_stickers; class MTPDmessages_stickers; @@ -6971,6 +6973,18 @@ public: return *(const MTPDdocumentAttributeFilename*)data; } + MTPDdocumentAttributeVersion &_documentAttributeVersion() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_documentAttributeVersion) throw mtpErrorWrongTypeId(_type, mtpc_documentAttributeVersion); + split(); + return *(MTPDdocumentAttributeVersion*)data; + } + const MTPDdocumentAttributeVersion &c_documentAttributeVersion() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_documentAttributeVersion) throw mtpErrorWrongTypeId(_type, mtpc_documentAttributeVersion); + return *(const MTPDdocumentAttributeVersion*)data; + } + uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -6985,6 +6999,7 @@ private: explicit MTPdocumentAttribute(MTPDdocumentAttributeVideo *_data); explicit MTPdocumentAttribute(MTPDdocumentAttributeAudio *_data); explicit MTPdocumentAttribute(MTPDdocumentAttributeFilename *_data); + explicit MTPdocumentAttribute(MTPDdocumentAttributeVersion *_data); friend class MTP::internal::TypeCreator; @@ -12818,6 +12833,16 @@ public: MTPstring vfile_name; }; +class MTPDdocumentAttributeVersion : public mtpDataImpl { +public: + MTPDdocumentAttributeVersion() { + } + MTPDdocumentAttributeVersion(MTPint _version) : vversion(_version) { + } + + MTPint vversion; +}; + class MTPDmessages_stickers : public mtpDataImpl { public: MTPDmessages_stickers() { @@ -23329,6 +23354,9 @@ public: inline static MTPdocumentAttribute new_documentAttributeFilename(const MTPstring &_file_name) { return MTPdocumentAttribute(new MTPDdocumentAttributeFilename(_file_name)); } + inline static MTPdocumentAttribute new_documentAttributeVersion(MTPint _version) { + return MTPdocumentAttribute(new MTPDdocumentAttributeVersion(_version)); + } inline static MTPmessages_stickers new_messages_stickersNotModified() { return MTPmessages_stickers(mtpc_messages_stickersNotModified); } @@ -31459,6 +31487,10 @@ inline uint32 MTPdocumentAttribute::innerLength() const { const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename()); return v.vfile_name.innerLength(); } + case mtpc_documentAttributeVersion: { + const MTPDdocumentAttributeVersion &v(c_documentAttributeVersion()); + return v.vversion.innerLength(); + } } return 0; } @@ -31503,6 +31535,11 @@ inline void MTPdocumentAttribute::read(const mtpPrime *&from, const mtpPrime *en MTPDdocumentAttributeFilename &v(_documentAttributeFilename()); v.vfile_name.read(from, end); } break; + case mtpc_documentAttributeVersion: _type = cons; { + if (!data) setData(new MTPDdocumentAttributeVersion()); + MTPDdocumentAttributeVersion &v(_documentAttributeVersion()); + v.vversion.read(from, end); + } break; default: throw mtpErrorUnexpected(cons, "MTPdocumentAttribute"); } } @@ -31536,6 +31573,10 @@ inline void MTPdocumentAttribute::write(mtpBuffer &to) const { const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename()); v.vfile_name.write(to); } break; + case mtpc_documentAttributeVersion: { + const MTPDdocumentAttributeVersion &v(c_documentAttributeVersion()); + v.vversion.write(to); + } break; } } inline MTPdocumentAttribute::MTPdocumentAttribute(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -31546,6 +31587,7 @@ inline MTPdocumentAttribute::MTPdocumentAttribute(mtpTypeId type) : mtpDataOwner case mtpc_documentAttributeVideo: setData(new MTPDdocumentAttributeVideo()); break; case mtpc_documentAttributeAudio: setData(new MTPDdocumentAttributeAudio()); break; case mtpc_documentAttributeFilename: setData(new MTPDdocumentAttributeFilename()); break; + case mtpc_documentAttributeVersion: setData(new MTPDdocumentAttributeVersion()); break; default: throw mtpErrorBadTypeId(type, "MTPdocumentAttribute"); } } @@ -31559,6 +31601,8 @@ inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeAudio *_d } inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeFilename *_data) : mtpDataOwner(_data), _type(mtpc_documentAttributeFilename) { } +inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeVersion *_data) : mtpDataOwner(_data), _type(mtpc_documentAttributeVersion) { +} inline MTPdocumentAttribute MTP_documentAttributeImageSize(MTPint _w, MTPint _h) { return MTP::internal::TypeCreator::new_documentAttributeImageSize(_w, _h); } @@ -31578,6 +31622,9 @@ inline MTPdocumentAttribute MTP_documentAttributeAudio(const MTPflagsisHidden()) { - if (Adaptive::OneColumn()) { + if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) { _info->setPeer(h); _info->show(); } else { From 98fe307cbfe653f1551358be1f2055dd01e77e07 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jul 2016 20:43:30 +0300 Subject: [PATCH 06/60] Packet queue instead of single packet is used in ffmpeg clip reader. --- .../SourceFiles/media/media_clip_ffmpeg.cpp | 175 +++++++++++------- .../SourceFiles/media/media_clip_ffmpeg.h | 22 ++- .../media/media_clip_implementation.h | 7 +- .../SourceFiles/media/media_clip_qtgif.cpp | 4 +- Telegram/SourceFiles/media/media_clip_qtgif.h | 2 +- .../SourceFiles/media/media_clip_reader.cpp | 36 ++-- .../SourceFiles/media/media_clip_reader.h | 4 + Telegram/SourceFiles/mediaview.cpp | 3 +- 8 files changed, 163 insertions(+), 90 deletions(-) diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index d10568f70..f509310d8 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -27,9 +27,9 @@ namespace internal { FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { _frame = av_frame_alloc(); - av_init_packet(&_avpkt); - _avpkt.data = NULL; - _avpkt.size = 0; + av_init_packet(&_packetNull); + _packetNull.data = nullptr; + _packetNull.size = 0; } bool FFMpegReaderImplementation::readNextFrame() { @@ -38,57 +38,50 @@ bool FFMpegReaderImplementation::readNextFrame() { _frameRead = false; } - int res; while (true) { - if (_avpkt.size > 0) { // previous packet not finished - res = 0; - } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { - if (res != AVERROR_EOF || !_hadFrame) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + while (_packetQueue.isEmpty()) { + auto packetResult = readPacket(); + if (packetResult == PacketResult::Error) { + return false; + } else if (packetResult == PacketResult::EndOfFile) { + break; + } + } + bool eofReached = _packetQueue.isEmpty(); + + startPacket(); + + int got_frame = 0; + int decoded = 0; + auto packet = &_packetNull; + if (!_packetQueue.isEmpty()) { + packet = &_packetQueue.head(); + decoded = packet->size; + } + + int res = 0; + if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, packet)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + if (res == AVERROR_INVALIDDATA) { // try to skip bad packet + finishPacket(); + continue; + } + + eofReached = (res == AVERROR_EOF); + if (!eofReached || !_hadFrame) { // try to skip end of file return false; } } + if (res > 0) decoded = res; - bool finished = (res < 0); - if (finished) { - _avpkt.data = NULL; - _avpkt.size = 0; - } else { - rememberPacket(); - } - - int32 got_frame = 0; - int32 decoded = _avpkt.size; - if (_avpkt.stream_index == _streamId) { - if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - if (res == AVERROR_INVALIDDATA) { // try to skip bad packet - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; - } - - if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file - return false; - } - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; + if (!_packetQueue.isEmpty()) { + packet->data += decoded; + packet->size -= decoded; + if (packet->size <= 0) { + finishPacket(); } - if (res > 0) decoded = res; - } else if (_audioStreamId >= 0 && _avpkt.stream_index == _audioStreamId) { - freePacket(); - continue; - } - if (!finished) { - _avpkt.data += decoded; - _avpkt.size -= decoded; - if (_avpkt.size <= 0) freePacket(); } if (got_frame) { @@ -110,7 +103,8 @@ bool FFMpegReaderImplementation::readNextFrame() { return true; } - if (finished) { + if (eofReached) { + clearPacketQueue(); if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { @@ -176,7 +170,7 @@ int FFMpegReaderImplementation::nextFrameDelay() { return _currentFrameDelay; } -bool FFMpegReaderImplementation::start(bool onlyGifv) { +bool FFMpegReaderImplementation::start(Mode mode) { initDevice(); if (!_device->open(QIODevice::ReadOnly)) { LOG(("Gif Error: Unable to open device %1").arg(logData())); @@ -211,13 +205,14 @@ bool FFMpegReaderImplementation::start(bool onlyGifv) { LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); return false; } + _packetNull.stream_index = _streamId; // Get a pointer to the codec context for the audio stream _codecContext = _fmtContext->streams[_streamId]->codec; _codec = avcodec_find_decoder(_codecContext->codec_id); _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); - if (onlyGifv) { + if (mode == Mode::OnlyGifv) { if (_audioStreamId >= 0) { // should be no audio stream return false; } @@ -227,6 +222,8 @@ bool FFMpegReaderImplementation::start(bool onlyGifv) { if (_codecContext->codec_id != AV_CODEC_ID_H264) { return false; } + } else if (mode == Mode::Silent) { + _audioStreamId = -1; } av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { @@ -261,23 +258,75 @@ FFMpegReaderImplementation::~FFMpegReaderImplementation() { } if (_fmtContext) avformat_free_context(_fmtContext); av_frame_free(&_frame); - freePacket(); + + clearPacketQueue(); } -void FFMpegReaderImplementation::rememberPacket() { - if (!_packetWas) { - _packetSize = _avpkt.size; - _packetData = _avpkt.data; - _packetWas = true; +FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket() { + AVPacket packet; + av_init_packet(&packet); + packet.data = nullptr; + packet.size = 0; + + int res = 0; + if ((res = av_read_frame(_fmtContext, &packet)) < 0) { + if (res == AVERROR_EOF) { + return PacketResult::EndOfFile; + } + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return PacketResult::Error; + } + + bool videoPacket = (packet.stream_index == _streamId); + bool audioPacket = (_audioStreamId >= 0 && packet.stream_index == _audioStreamId); + if (audioPacket || videoPacket) { + //AVPacket packetForQueue; + //av_init_packet(&packetForQueue); + //if ((res = av_packet_ref(&packetForQueue, &packet)) < 0) { + // char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + // LOG(("Gif Error: Unable to av_packet_ref() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + // return PacketResult::Error; + //} + + if (videoPacket) { + _packetQueue.enqueue(packet); + //_packetQueue.enqueue(packetForQueue); + } else if (audioPacket) { + // queue packet to audio player + // audioPlayer()->enqueuePacket(packet, &isEnough) + //av_packet_unref(&packetForQueue); + av_packet_unref(&packet); + } + } else { + av_packet_unref(&packet); + } + //av_packet_unref(&packet); + return PacketResult::Ok; +} + +void FFMpegReaderImplementation::startPacket() { + if (!_packetStarted && !_packetQueue.isEmpty()) { + _packetStartedSize = _packetQueue.head().size; + _packetStartedData = _packetQueue.head().data; + _packetStarted = true; } } -void FFMpegReaderImplementation::freePacket() { - if (_packetWas) { - _avpkt.size = _packetSize; - _avpkt.data = _packetData; - _packetWas = false; - av_packet_unref(&_avpkt); +void FFMpegReaderImplementation::finishPacket() { + if (_packetStarted) { + _packetQueue.head().size = _packetStartedSize; + _packetQueue.head().data = _packetStartedData; + _packetStarted = false; + av_packet_unref(&_packetQueue.dequeue()); + } +} + +void FFMpegReaderImplementation::clearPacketQueue() { + finishPacket(); + auto packets = createAndSwap(_packetQueue); + for (auto &packet : packets) { + av_packet_unref(&packet); } } diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 3850ebf49..78c548282 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -41,7 +41,7 @@ public: bool readNextFrame() override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; int nextFrameDelay() override; - bool start(bool onlyGifv) override; + bool start(Mode mode) override; int duration() const; QString logData() const; @@ -49,8 +49,15 @@ public: ~FFMpegReaderImplementation(); private: - void rememberPacket(); - void freePacket(); + enum class PacketResult { + Ok, + EndOfFile, + Error, + }; + PacketResult readPacket(); + void startPacket(); + void finishPacket(); + void clearPacketQueue(); static int _read(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek(void *opaque, int64_t offset, int whence); @@ -68,10 +75,11 @@ private: int _audioStreamId = 0; - AVPacket _avpkt; - int _packetSize = 0; - uint8_t *_packetData = nullptr; - bool _packetWas = false; + QQueue _packetQueue; + AVPacket _packetNull; // for final decoding + int _packetStartedSize = 0; + uint8_t *_packetStartedData = nullptr; + bool _packetStarted = false; int _width = 0; int _height = 0; diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 30f34c4bc..c54fc4b7a 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -33,10 +33,15 @@ public: : _location(location) , _data(data) { } + enum class Mode { + OnlyGifv, + Silent, + Normal, + }; virtual bool readNextFrame() = 0; virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual int nextFrameDelay() = 0; - virtual bool start(bool onlyGifv) = 0; + virtual bool start(Mode mode) = 0; virtual ~ReaderImplementation() { } int64 dataSize() const { diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index 2ec76997c..ff911c972 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -70,8 +70,8 @@ int QtGifReaderImplementation::nextFrameDelay() { return _frameDelay; } -bool QtGifReaderImplementation::start(bool onlyGifv) { - if (onlyGifv) return false; +bool QtGifReaderImplementation::start(Mode mode) { + if (mode == Mode::OnlyGifv) return false; return jumpToStart(); } diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 5e8efa063..910d60e40 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -34,7 +34,7 @@ public: bool readNextFrame() override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; int nextFrameDelay() override; - bool start(bool onlyGifv) override; + bool start(Mode mode) override; ~QtGifReaderImplementation(); diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index ca96eb418..500275a5a 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -287,10 +287,10 @@ Reader::~Reader() { class ReaderPrivate { public: - ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) - , _data(data) - , _location(_data.isEmpty() ? new FileLocation(location) : 0) { + , _mode(reader->mode()) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) { if (_data.isEmpty() && !_location->accessEnable()) { error(); return; @@ -381,9 +381,17 @@ public: } } - _implementation = new internal::FFMpegReaderImplementation(_location, &_data); + _implementation = std_::make_unique(_location, &_data); // _implementation = new QtGifReaderImplementation(_location, &_data); - return _implementation->start(false); + + auto implementationMode = [this]() { + using ImplementationMode = internal::ReaderImplementation::Mode; + if (_mode == Reader::Mode::Gif) { + return ImplementationMode::Silent; + } + return ImplementationMode::Normal; + }; + return _implementation->start(implementationMode()); } ProcessResult error() { @@ -393,8 +401,7 @@ public: } void stop() { - delete _implementation; - _implementation = 0; + _implementation = nullptr; if (_location) { if (_accessed) { @@ -409,21 +416,20 @@ public: ~ReaderPrivate() { stop(); deleteAndMark(_location); - deleteAndMark(_implementation); _data.clear(); } private: - Reader *_interface; State _state = State::Reading; + Reader::Mode _mode; QByteArray _data; FileLocation *_location; bool _accessed = false; QBuffer _buffer; - internal::ReaderImplementation *_implementation = nullptr; + std_::unique_ptr _implementation; FrameRequest _request; struct Frame { @@ -474,7 +480,7 @@ void Manager::start(Reader *reader) { void Manager::update(Reader *reader) { QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator i = _readerPointers.constFind(reader); + auto i = _readerPointers.constFind(reader); if (i == _readerPointers.cend()) { lock.unlock(); @@ -615,9 +621,9 @@ void Manager::process() { uint64 ms = getms(), minms = ms + 86400 * 1000ULL; { QReadLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + for (auto it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { if (it->v.loadAcquire()) { - Readers::iterator i = _readers.find(it.key()->_private); + auto i = _readers.find(it.key()->_private); if (i == _readers.cend()) { _readers.insert(it.key()->_private, 0); } else { @@ -633,7 +639,7 @@ void Manager::process() { } } - for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + for (auto i = _readers.begin(), e = _readers.end(); i != e;) { ReaderPrivate *reader = i.key(); if (i.value() <= ms) { ResultHandleState state = handleResult(reader, reader->process(ms), ms); @@ -693,7 +699,7 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data QByteArray localdata(data); auto reader = std_::make_unique(&localloc, &localdata); - if (reader->start(true)) { + if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv)) { bool hasAlpha = false; if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index a0b160e70..43b2768ea 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -101,6 +101,10 @@ public: void stop(); void error(); + Mode mode() const { + return _mode; + } + ~Reader(); private: diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index ba722e41d..1f5a9d073 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1087,7 +1087,8 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty if (_doc->dimensions.width() && _doc->dimensions.height()) { _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } - _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); + auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; + _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback), mode); } } else { if (QImageReader(location.name()).canRead()) { From 616d08255c728be12fa585900f993d620ec95e10 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jul 2016 20:44:02 +0300 Subject: [PATCH 07/60] Moved audio to media/media_audio and divided to several modules. Basic video playback with sound support in mediaview added. --- Telegram/SourceFiles/app.cpp | 2 +- Telegram/SourceFiles/config.h | 4 +- Telegram/SourceFiles/core/basic_types.h | 2 +- Telegram/SourceFiles/history.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/layout.cpp | 2 +- Telegram/SourceFiles/localimageloader.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 16 +- .../{audio.cpp => media/media_audio.cpp} | 803 ++---------------- .../{audio.h => media/media_audio.h} | 98 +-- .../media/media_audio_ffmpeg_loader.cpp | 301 +++++++ .../media/media_audio_ffmpeg_loader.h | 102 +++ .../SourceFiles/media/media_audio_loader.cpp | 80 ++ .../SourceFiles/media/media_audio_loader.h | 62 ++ .../SourceFiles/media/media_audio_loaders.cpp | 423 +++++++++ .../SourceFiles/media/media_audio_loaders.h | 85 ++ .../media/media_child_ffmpeg_loader.cpp | 186 ++++ .../media/media_child_ffmpeg_loader.h | 93 ++ .../SourceFiles/media/media_clip_ffmpeg.cpp | 67 +- .../SourceFiles/media/media_clip_ffmpeg.h | 4 + .../SourceFiles/overview/overview_layout.cpp | 2 +- Telegram/SourceFiles/playerwidget.cpp | 2 +- Telegram/SourceFiles/playerwidget.h | 2 +- Telegram/SourceFiles/structs.cpp | 2 +- Telegram/SourceFiles/structs.h | 8 +- Telegram/SourceFiles/ui/twidget.h | 10 +- Telegram/Telegram.pro | 12 +- Telegram/Telegram.vcxproj | 87 +- Telegram/Telegram.vcxproj.filters | 63 +- Telegram/Telegram.xcodeproj/project.pbxproj | 20 +- Telegram/Telegram.xcodeproj/qt_preprocess.mak | 18 +- 31 files changed, 1684 insertions(+), 878 deletions(-) rename Telegram/SourceFiles/{audio.cpp => media/media_audio.cpp} (71%) rename Telegram/SourceFiles/{audio.h => media/media_audio.h} (79%) create mode 100644 Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h create mode 100644 Telegram/SourceFiles/media/media_audio_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_loader.h create mode 100644 Telegram/SourceFiles/media/media_audio_loaders.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_loaders.h create mode 100644 Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_child_ffmpeg_loader.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 9d4e55165..3c3dadca3 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" -#include "audio.h" +#include "media/media_audio.h" #include "application.h" #include "fileuploader.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index c62b700b1..6defe6349 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -107,12 +107,12 @@ enum { AudioFadeDuration = 500, AudioVoiceMsgSkip = 400, // 200ms AudioVoiceMsgFade = 300, // 300ms - AudioPreloadSamples = 5 * 48000, // preload next part if less than 5 seconds remains + AudioPreloadSamples = 2 * 48000, // preload next part if less than 5 seconds remains AudioVoiceMsgFrequency = 48000, // 48 kHz AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo - AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers + AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs) AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index c4cc892e4..6b748ae3f 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -35,7 +35,7 @@ T *getPointerAndReset(T *&ptr) { template T createAndSwap(T &value) { - T result; + T result = T(); std::swap(result, value); return result; } diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 61b78c8e8..f0f0dfea3 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -35,7 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" #include "apiwrap.h" #include "window/top_bar_widget.h" diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index c0c23cef0..8dd576800 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -40,7 +40,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "passcodewidget.h" #include "mainwindow.h" #include "fileuploader.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" #include "apiwrap.h" #include "window/top_bar_widget.h" diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 2dcd973d7..99da2c9a7 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -30,7 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" TextParseOptions _textNameOptions = { diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index cec36b9f4..ce407efbe 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "localimageloader.h" #include "ui/filedialog.h" -#include "audio.h" +#include "media/media_audio.h" #include "boxes/photosendbox.h" #include "media/media_clip_reader.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 10688692c..558d9f092 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -47,7 +47,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/downloadpathbox.h" #include "localstorage.h" #include "shortcuts.h" -#include "audio.h" +#include "media/media_audio.h" StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) , _memento(std_::move(memento)) { @@ -1565,12 +1565,14 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } } - if (HistoryItem *item = App::histItemById(audioId.contextId())) { - Ui::repaintHistoryItem(item); - } - if (auto items = InlineBots::Layout::documentItems()) { - for (auto item : items->value(audioId.audio())) { - Ui::repaintInlineItem(item); + if (audioId.type() != AudioMsgId::Type::Video) { + if (auto item = App::histItemById(audioId.contextId())) { + Ui::repaintHistoryItem(item); + } + if (auto items = InlineBots::Layout::documentItems()) { + for (auto item : items->value(audioId.audio())) { + Ui::repaintInlineItem(item); + } } } } diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp similarity index 71% rename from Telegram/SourceFiles/audio.cpp rename to Telegram/SourceFiles/media/media_audio.cpp index dfdfd17f5..52d424d40 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -19,8 +19,11 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "media/media_audio.h" -#include "audio.h" +#include "media/media_audio_ffmpeg_loader.h" +#include "media/media_child_ffmpeg_loader.h" +#include "media/media_audio_loaders.h" #include #include @@ -29,11 +32,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include extern "C" { -#include -#include -#include -#include - #ifdef Q_OS_MAC #include @@ -274,13 +272,17 @@ void AudioPlayer::AudioMsg::clear() { if (alIsSource(source)) { alSourceStop(source); } - for (int32 i = 0; i < 3; ++i) { + for (int i = 0; i < 3; ++i) { if (samplesCount[i]) { - alSourceUnqueueBuffers(source, 1, buffers + i); + ALuint buffer = 0; + // This cleans some random queued buffer, not exactly the buffers[i]. + alSourceUnqueueBuffers(source, 1, &buffer); samplesCount[i] = 0; } } nextBuffer = 0; + + videoData = nullptr; } AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0), @@ -311,7 +313,7 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { AudioPlayer::~AudioPlayer() { { QMutexLocker lock(&playerMutex); - player = 0; + player = nullptr; } auto clearAudioMsg = [](AudioMsg *msg) { @@ -332,6 +334,8 @@ AudioPlayer::~AudioPlayer() { clearAudioMsg(dataForType(AudioMsgId::Type::Voice, i)); clearAudioMsg(dataForType(AudioMsgId::Type::Song, i)); } + clearAudioMsg(&_videoData); + _faderThread.quit(); _loaderThread.quit(); _faderThread.wait(); @@ -357,6 +361,7 @@ AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index switch (type) { case AudioMsgId::Type::Voice: return &_audioData[index]; case AudioMsgId::Type::Song: return &_songData[index]; + case AudioMsgId::Type::Video: return &_videoData; } return nullptr; } @@ -369,6 +374,7 @@ int *AudioPlayer::currentIndex(AudioMsgId::Type type) { switch (type) { case AudioMsgId::Type::Voice: return &_audioCurrent; case AudioMsgId::Type::Song: return &_songCurrent; + case AudioMsgId::Type::Video: { static int videoIndex = 0; return &videoIndex; } } return nullptr; } @@ -422,7 +428,6 @@ bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { } void AudioPlayer::play(const AudioMsgId &audio, int64 position) { - bool fadedStart = false; auto type = audio.type(); AudioMsgId stopped; { @@ -479,6 +484,40 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { if (stopped) emit updated(stopped); } +void AudioPlayer::playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data) { + t_assert(audio.type() == AudioMsgId::Type::Video); + + auto type = audio.type(); + AudioMsgId stopped; + { + QMutexLocker lock(&playerMutex); + + auto current = dataForType(type); + t_assert(current != nullptr); + + fadedStop(AudioMsgId::Type::Song); + if (current->audio) { + fadedStop(type); + stopped = current->audio; + emit loaderOnCancel(current->audio); + } + emit faderOnTimer(); + current->clear(); + current->audio = audio; + current->videoData = std_::move(data); + _loader->startFromVideo(current->videoData->videoPlayId); + + current->state = AudioPlayerPlaying; + current->loading = true; + emit loaderOnStart(audio, position); + } + if (stopped) emit updated(stopped); +} + +void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { + _loader->feedFromVideo(std_::move(part)); +} + bool AudioPlayer::checkCurrentALError(AudioMsgId::Type type) { if (_checkALError()) return true; @@ -633,6 +672,8 @@ void AudioPlayer::stopAndClear() { clearAndCancel(AudioMsgId::Type::Voice, index); clearAndCancel(AudioMsgId::Type::Song, index); } + _videoData.clear(); + _loader->stopFromVideo(); } } @@ -669,6 +710,27 @@ void AudioPlayer::resumeDevice() { _fader->resumeDevice(); } + +namespace internal { + +QMutex *audioPlayerMutex() { + return &playerMutex; +} + +float64 audioSuppressGain() { + return suppressAllGain; +} + +float64 audioSuppressSongGain() { + return suppressSongGain; +} + +bool audioCheckError() { + return _checkALError(); +} + +} // namespace internal + AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) { connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart())); connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool))); @@ -699,7 +761,7 @@ bool AudioCapture::check() { } AudioCapture::~AudioCapture() { - capture = 0; + capture = nullptr; _captureThread.quit(); _captureThread.wait(); } @@ -789,6 +851,7 @@ void AudioPlayerFader::onTimer() { updatePlayback(AudioMsgId::Type::Voice, i, suppressAllGain, suppressAudioChanged); updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged); } + updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForMusic, suppressGainForMusicChanged); _songVolumeChanged = false; @@ -972,710 +1035,6 @@ void AudioPlayerFader::resumeDevice() { } } -class AudioPlayerLoader { -public: - AudioPlayerLoader(const FileLocation &file, const QByteArray &data) : file(file), access(false), data(data), dataPos(0) { - } - virtual ~AudioPlayerLoader() { - if (access) { - file.accessDisable(); - access = false; - } - } - - bool check(const FileLocation &file, const QByteArray &data) { - return this->file == file && this->data.size() == data.size(); - } - - virtual bool open(qint64 position = 0) = 0; - virtual int64 duration() = 0; - virtual int32 frequency() = 0; - virtual int32 format() = 0; - virtual int readMore(QByteArray &result, int64 &samplesAdded) = 0; // < 0 - error, 0 - nothing read, > 0 - read something - -protected: - - FileLocation file; - bool access; - QByteArray data; - - QFile f; - int32 dataPos; - - bool openFile() { - if (data.isEmpty()) { - if (f.isOpen()) f.close(); - if (!access) { - if (!file.accessEnable()) { - LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); - return false; - } - access = true; - } - f.setFileName(file.name()); - if (!f.open(QIODevice::ReadOnly)) { - LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); - return false; - } - } - dataPos = 0; - return true; - } - -}; - -class AbstractFFMpegLoader : public AudioPlayerLoader { -public: - - AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) - , freq(AudioVoiceMsgFrequency) - , len(0) - , ioBuffer(0) - , ioContext(0) - , fmtContext(0) - , codec(0) - , streamId(0) - , _opened(false) { - } - - bool open(qint64 position = 0) { - if (!AudioPlayerLoader::openFile()) { - return false; - } - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - - ioBuffer = (uchar*)av_malloc(AVBlockSize); - if (data.isEmpty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); - } else { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); - } - fmtContext = avformat_alloc_context(); - if (!fmtContext) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); - return false; - } - fmtContext->pb = ioContext; - - if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { - ioBuffer = 0; - - DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - _opened = true; - - if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); - if (streamId < 0) { - LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); - return false; - } - - freq = fmtContext->streams[streamId]->codec->sample_rate; - if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { - len = (fmtContext->duration * freq) / AV_TIME_BASE; - } else { - len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; - } - - return true; - } - - int64 duration() { - return len; - } - - int32 frequency() { - return freq; - } - - ~AbstractFFMpegLoader() { - if (ioContext) av_free(ioContext); - if (_opened) { - avformat_close_input(&fmtContext); - } else if (ioBuffer) { - av_free(ioBuffer); - } - if (fmtContext) avformat_free_context(fmtContext); - } - -protected: - - int32 freq; - int64 len; - - uchar *ioBuffer; - AVIOContext *ioContext; - AVFormatContext *fmtContext; - AVCodec *codec; - int32 streamId; - - bool _opened; - -private: - - static int _read_data(void *opaque, uint8_t *buf, int buf_size) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); - if (nbytes <= 0) { - return 0; - } - - memcpy(buf, l->data.constData() + l->dataPos, nbytes); - l->dataPos += nbytes; - return nbytes; - } - - static int64_t _seek_data(void *opaque, int64_t offset, int whence) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - int32 newPos = -1; - switch (whence) { - case SEEK_SET: newPos = offset; break; - case SEEK_CUR: newPos = l->dataPos + offset; break; - case SEEK_END: newPos = l->data.size() + offset; break; - } - if (newPos < 0 || newPos > l->data.size()) { - return -1; - } - l->dataPos = newPos; - return l->dataPos; - } - - static int _read_file(void *opaque, uint8_t *buf, int buf_size) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - return int(l->f.read((char*)(buf), buf_size)); - } - - static int64_t _seek_file(void *opaque, int64_t offset, int whence) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; - case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; - case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; - } - return -1; - } -}; - -static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16; -static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO; -static const int32 _toChannels = 2; -class FFMpegLoader : public AbstractFFMpegLoader { -public: - - FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) - , sampleSize(2 * sizeof(uint16)) - , fmt(AL_FORMAT_STEREO16) - , srcRate(AudioVoiceMsgFrequency) - , dstRate(AudioVoiceMsgFrequency) - , maxResampleSamples(1024) - , dstSamplesData(0) - , codecContext(0) - , frame(0) - , swrContext(0) { - frame = av_frame_alloc(); - } - - bool open(qint64 position = 0) { - if (!AbstractFFMpegLoader::open(position)) { - return false; - } - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - - // Get a pointer to the codec context for the audio stream - av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { - LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - codecContext = fmtContext->streams[streamId]->codec; - - uint64_t layout = codecContext->channel_layout; - inputFormat = codecContext->sample_fmt; - switch (layout) { - case AV_CH_LAYOUT_MONO: - switch (inputFormat) { - case AV_SAMPLE_FMT_U8: - case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break; - case AV_SAMPLE_FMT_S16: - case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break; - default: - sampleSize = -1; // convert needed - break; - } - break; - case AV_CH_LAYOUT_STEREO: - switch (inputFormat) { - case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break; - case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break; - default: - sampleSize = -1; // convert needed - break; - } - break; - default: - sampleSize = -1; // convert needed - break; - } - if (freq != 44100 && freq != 48000) { - sampleSize = -1; // convert needed - } - - if (sampleSize < 0) { - swrContext = swr_alloc(); - if (!swrContext) { - LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); - return false; - } - int64_t src_ch_layout = layout, dst_ch_layout = _toChannelLayout; - srcRate = freq; - AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = _toFormat; - dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq; - - av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0); - av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0); - av_opt_set_sample_fmt(swrContext, "in_sample_fmt", src_sample_fmt, 0); - av_opt_set_int(swrContext, "out_channel_layout", dst_ch_layout, 0); - av_opt_set_int(swrContext, "out_sample_rate", dstRate, 0); - av_opt_set_sample_fmt(swrContext, "out_sample_fmt", dst_sample_fmt, 0); - - if ((res = swr_init(swrContext)) < 0) { - LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - sampleSize = _toChannels * sizeof(short); - freq = dstRate; - len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP); - fmt = AL_FORMAT_STEREO16; - - maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP); - if ((res = av_samples_alloc_array_and_samples(&dstSamplesData, 0, _toChannels, maxResampleSamples, _toFormat, 0)) < 0) { - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - if (position) { - int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num); - if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) { - if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) { - } - } - //if (dstSamplesData) { - // position = qRound(srcRate * (position / float64(dstRate))); - //} - } - - return true; - } - - int32 format() { - return fmt; - } - - int readMore(QByteArray &result, int64 &samplesAdded) { - int res; - if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { - if (res != AVERROR_EOF) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - } - return -1; - } - if (avpkt.stream_index == streamId) { - av_frame_unref(frame); - int got_frame = 0; - if ((res = avcodec_decode_audio4(codecContext, frame, &got_frame, &avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - if (res == AVERROR_INVALIDDATA) return 0; // try to skip bad packet - return -1; - } - - if (got_frame) { - if (dstSamplesData) { // convert needed - int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); - if (dstSamples > maxResampleSamples) { - maxResampleSamples = dstSamples; - av_free(dstSamplesData[0]); - - if ((res = av_samples_alloc(dstSamplesData, 0, _toChannels, maxResampleSamples, _toFormat, 1)) < 0) { - dstSamplesData[0] = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return -1; - } - } - if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return -1; - } - int32 resultLen = av_samples_get_buffer_size(0, _toChannels, res, _toFormat, 1); - result.append((const char*)dstSamplesData[0], resultLen); - samplesAdded += resultLen / sampleSize; - } else { - result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); - samplesAdded += frame->nb_samples; - } - } - } - av_packet_unref(&avpkt); - return 1; - } - - ~FFMpegLoader() { - if (codecContext) avcodec_close(codecContext); - if (swrContext) swr_free(&swrContext); - if (dstSamplesData) { - if (dstSamplesData[0]) { - av_freep(&dstSamplesData[0]); - } - av_freep(&dstSamplesData); - } - av_frame_free(&frame); - } - -protected: - int32 sampleSize; - -private: - - int32 fmt; - int32 srcRate, dstRate, maxResampleSamples; - uint8_t **dstSamplesData; - - AVCodecContext *codecContext; - AVPacket avpkt; - AVSampleFormat inputFormat; - AVFrame *frame; - - SwrContext *swrContext; - -}; - -AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) { - moveToThread(thread); -} - -AudioPlayerLoaders::~AudioPlayerLoaders() { - delete _audioLoader; - delete _songLoader; -} - -void AudioPlayerLoaders::onInit() { -} - -void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { - auto type = audio.type(); - clear(type); - { - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - auto data = voice->dataForType(type); - if (!data) return; - - data->loading = true; - } - - loadData(audio, position); -} - -void AudioPlayerLoaders::clear(AudioMsgId::Type type) { - switch (type) { - case AudioMsgId::Type::Voice: clearAudio(); break; - case AudioMsgId::Type::Song: clearSong(); break; - } -} - -void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { - m->state = state; - m->position = 0; -} - -void AudioPlayerLoaders::emitError(AudioMsgId::Type type) { - switch (type) { - case AudioMsgId::Type::Voice: emit error(clearAudio()); break; - case AudioMsgId::Type::Song: emit error(clearSong()); break; - } -} - -AudioMsgId AudioPlayerLoaders::clearAudio() { - AudioMsgId current = _audio; - _audio = AudioMsgId(); - delete _audioLoader; - _audioLoader = nullptr; - return current; -} - -AudioMsgId AudioPlayerLoaders::clearSong() { - AudioMsgId current = _song; - _song = AudioMsgId(); - delete _songLoader; - _songLoader = nullptr; - return current; -} - -void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { - loadData(audio, 0); -} - -void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) { - SetupError err = SetupNoErrorStarted; - auto type = audio.type(); - AudioPlayerLoader *l = setupLoader(audio, err, position); - if (!l) { - if (err == SetupErrorAtStart) { - emitError(type); - } - return; - } - - bool started = (err == SetupNoErrorStarted), finished = false, errAtStart = started; - - QByteArray result; - int64 samplesAdded = 0, frequency = l->frequency(), format = l->format(); - while (result.size() < AudioVoiceMsgBufferSize) { - int res = l->readMore(result, samplesAdded); - if (res < 0) { - if (errAtStart) { - { - QMutexLocker lock(&playerMutex); - AudioPlayer::AudioMsg *m = checkLoader(type); - if (m) m->state = AudioPlayerStoppedAtStart; - } - emitError(type); - return; - } - finished = true; - break; - } - if (res > 0) errAtStart = false; - - QMutexLocker lock(&playerMutex); - if (!checkLoader(type)) { - clear(type); - return; - } - } - - QMutexLocker lock(&playerMutex); - AudioPlayer::AudioMsg *m = checkLoader(type); - if (!m) { - clear(type); - return; - } - - if (started) { - if (m->source) { - alSourceStop(m->source); - for (int32 i = 0; i < 3; ++i) { - if (m->samplesCount[i]) { - alSourceUnqueueBuffers(m->source, 1, m->buffers + i); - m->samplesCount[i] = 0; - } - } - m->nextBuffer = 0; - } - m->skipStart = position; - m->skipEnd = m->duration - position; - m->position = 0; - m->started = 0; - } - if (samplesAdded) { - if (!m->source) { - alGenSources(1, &m->source); - alSourcef(m->source, AL_PITCH, 1.f); - alSource3f(m->source, AL_POSITION, 0, 0, 0); - alSource3f(m->source, AL_VELOCITY, 0, 0, 0); - alSourcei(m->source, AL_LOOPING, 0); - } - if (!m->buffers[m->nextBuffer]) alGenBuffers(3, m->buffers); - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - - if (m->samplesCount[m->nextBuffer]) { - alSourceUnqueueBuffers(m->source, 1, m->buffers + m->nextBuffer); - m->skipStart += m->samplesCount[m->nextBuffer]; - } - - m->samplesCount[m->nextBuffer] = samplesAdded; - alBufferData(m->buffers[m->nextBuffer], format, result.constData(), result.size(), frequency); - alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer); - m->skipEnd -= samplesAdded; - - m->nextBuffer = (m->nextBuffer + 1) % 3; - - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - } else { - finished = true; - } - if (finished) { - m->skipEnd = 0; - m->duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2]; - clear(type); - } - m->loading = false; - if (m->state == AudioPlayerResuming || m->state == AudioPlayerPlaying || m->state == AudioPlayerStarting) { - ALint state = AL_INITIAL; - alGetSourcei(m->source, AL_SOURCE_STATE, &state); - if (_checkALError()) { - if (state != AL_PLAYING) { - audioPlayer()->resumeDevice(); - - switch (type) { - case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, suppressAllGain); break; - case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; - } - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - - alSourcePlay(m->source); - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - - emit needToCheck(); - } - } else { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - } - } -} - -AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position) { - err = SetupErrorAtStart; - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return nullptr; - - auto data = voice->dataForType(audio.type()); - if (!data || data->audio != audio || !data->loading) { - emit error(audio); - LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); - err = SetupErrorNotPlaying; - return nullptr; - } - - bool isGoodId = false; - AudioPlayerLoader **l = nullptr; - switch (audio.type()) { - case AudioMsgId::Type::Voice: l = &_audioLoader; isGoodId = (_audio == audio); break; - case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (_song == audio); break; - } - - if (*l && (!isGoodId || !(*l)->check(data->file, data->data))) { - delete *l; - *l = nullptr; - switch (audio.type()) { - case AudioMsgId::Type::Voice: _audio = AudioMsgId(); break; - case AudioMsgId::Type::Song: _song = AudioMsgId(); break; - } - } - - if (!*l) { - switch (audio.type()) { - case AudioMsgId::Type::Voice: _audio = audio; break; - case AudioMsgId::Type::Song: _song = audio; break; - } - - *l = new FFMpegLoader(data->file, data->data); - - if (!(*l)->open(position)) { - data->state = AudioPlayerStoppedAtStart; - return nullptr; - } - int64 duration = (*l)->duration(); - if (duration <= 0) { - data->state = AudioPlayerStoppedAtStart; - return nullptr; - } - data->duration = duration; - data->frequency = (*l)->frequency(); - if (!data->frequency) data->frequency = AudioVoiceMsgFrequency; - err = SetupNoErrorStarted; - } else { - if (!data->skipEnd) { - err = SetupErrorLoadedFull; - LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); - return nullptr; - } - } - return *l; -} - -AudioPlayer::AudioMsg *AudioPlayerLoaders::checkLoader(AudioMsgId::Type type) { - AudioPlayer *voice = audioPlayer(); - if (!voice) return 0; - - auto data = voice->dataForType(type); - bool isGoodId = false; - AudioPlayerLoader **l = nullptr; - switch (type) { - case AudioMsgId::Type::Voice: l = &_audioLoader; isGoodId = (data->audio == _audio); break; - case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (data->audio == _song); break; - } - if (!l || !data) return nullptr; - - if (!isGoodId || !data->loading || !(*l)->check(data->file, data->data)) { - LOG(("Audio Error: playing changed while loading")); - return nullptr; - } - - return data; -} - -void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) { - switch (audio.type()) { - case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break; - case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break; - } - - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - for (int i = 0; i < AudioSimultaneousLimit; ++i) { - auto data = voice->dataForType(audio.type(), i); - if (data->audio == audio) { - data->loading = false; - } - } -} - struct AudioCapturePrivate { AudioCapturePrivate() : device(0) @@ -2232,7 +1591,7 @@ public: FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { } - bool open(qint64 position = 0) { + bool open(qint64 position = 0) override { if (!AbstractFFMpegLoader::open()) { return false; } @@ -2287,7 +1646,7 @@ public: //} } - int32 format() { + int32 format() override { return 0; } @@ -2311,9 +1670,9 @@ public: return _coverFormat; } - int readMore(QByteArray &result, int64 &samplesAdded) { + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override { DEBUG_LOG(("Audio Read Error: should not call this")); - return -1; + return ReadResult::Error; } ~FFMpegAttributesReader() { @@ -2347,7 +1706,7 @@ public: FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) { } - bool open(qint64 position = 0) { + bool open(qint64 position = 0) override { if (!FFMpegLoader::open(position)) { return false; } @@ -2368,8 +1727,8 @@ public: buffer.resize(0); int64 samples = 0; - int res = readMore(buffer, samples); - if (res < 0) { + auto res = readMore(buffer, samples); + if (res == ReadResult::Error) { break; } if (buffer.isEmpty()) { diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/media/media_audio.h similarity index 79% rename from Telegram/SourceFiles/audio.h rename to Telegram/SourceFiles/media/media_audio.h index e0ff14e5a..27a8e15a6 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -28,24 +28,27 @@ void audioPlayNotify(); void audioFinish(); enum AudioPlayerState { - AudioPlayerStopped = 0x01, - AudioPlayerStoppedAtEnd = 0x02, + AudioPlayerStopped = 0x01, + AudioPlayerStoppedAtEnd = 0x02, AudioPlayerStoppedAtError = 0x03, AudioPlayerStoppedAtStart = 0x04, - AudioPlayerStoppedMask = 0x07, + AudioPlayerStoppedMask = 0x07, - AudioPlayerStarting = 0x08, - AudioPlayerPlaying = 0x10, - AudioPlayerFinishing = 0x18, - AudioPlayerPausing = 0x20, - AudioPlayerPaused = 0x28, - AudioPlayerPausedAtEnd = 0x30, - AudioPlayerResuming = 0x38, + AudioPlayerStarting = 0x08, + AudioPlayerPlaying = 0x10, + AudioPlayerFinishing = 0x18, + AudioPlayerPausing = 0x20, + AudioPlayerPaused = 0x28, + AudioPlayerPausedAtEnd = 0x30, + AudioPlayerResuming = 0x38, }; class AudioPlayerFader; class AudioPlayerLoaders; +struct VideoSoundData; +struct VideoSoundPart; + class AudioPlayer : public QObject { Q_OBJECT @@ -58,6 +61,10 @@ public: void seek(int64 position); // type == AudioMsgId::Type::Song void stop(AudioMsgId::Type type); + // Video player audio stream interface. + void playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data); + void feedFromVideo(VideoSoundPart &&part); + void stopAndClear(); void currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); @@ -115,6 +122,8 @@ private: int32 nextBuffer = 0; uint32 buffers[3] = { 0 }; int64 samplesCount[3] = { 0 }; + + std_::unique_ptr videoData; }; void currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); @@ -131,6 +140,8 @@ private: int _songCurrent; AudioMsg _songData[AudioSimultaneousLimit]; + AudioMsg _videoData; + QMutex _mutex; friend class AudioPlayerFader; @@ -142,6 +153,15 @@ private: }; +namespace internal { + +QMutex *audioPlayerMutex(); +float64 audioSuppressGain(); +float64 audioSuppressSongGain(); +bool audioCheckError(); + +} // namespace internal + class AudioCaptureInner; class AudioCapture : public QObject { @@ -196,7 +216,7 @@ signals: void stopPauseDevice(); -public slots: + public slots: void onInit(); void onTimer(); @@ -211,10 +231,10 @@ public slots: private: enum { - EmitError = 0x01, - EmitStopped = 0x02, + EmitError = 0x01, + EmitStopped = 0x02, EmitPositionUpdated = 0x04, - EmitNeedToPreload = 0x08, + EmitNeedToPreload = 0x08, }; int32 updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); @@ -229,54 +249,6 @@ private: }; -class AudioPlayerLoader; -class AudioPlayerLoaders : public QObject { - Q_OBJECT - -public: - - AudioPlayerLoaders(QThread *thread); - ~AudioPlayerLoaders(); - -signals: - - void error(const AudioMsgId &audio); - void needToCheck(); - -public slots: - - void onInit(); - - void onStart(const AudioMsgId &audio, qint64 position); - void onLoad(const AudioMsgId &audio); - void onCancel(const AudioMsgId &audio); - -private: - - AudioMsgId _audio; - AudioPlayerLoader *_audioLoader; - - AudioMsgId _song; - AudioPlayerLoader *_songLoader; - - void emitError(AudioMsgId::Type type); - void clear(AudioMsgId::Type type); - void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); - AudioMsgId clearAudio(); - AudioMsgId clearSong(); - - enum SetupError { - SetupErrorAtStart = 0, - SetupErrorNotPlaying = 1, - SetupErrorLoadedFull = 2, - SetupNoErrorStarted = 3, - }; - void loadData(const AudioMsgId &audio, qint64 position); - AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); - AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); - -}; - struct AudioCapturePrivate; class AudioCaptureInner : public QObject { @@ -293,7 +265,7 @@ signals: void update(quint16 level, qint32 samples); void done(QByteArray data, VoiceWaveform waveform, qint32 samples); -public slots: + public slots: void onInit(); void onStart(); diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp new file mode 100644 index 000000000..ab7a14156 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -0,0 +1,301 @@ +/* +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 "media/media_audio_ffmpeg_loader.h" + +constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; +constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; +constexpr int32 AudioToChannels = 2; + +bool AbstractFFMpegLoader::open(qint64 position) { + if (!AudioPlayerLoader::openFile()) { + return false; + } + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + ioBuffer = (uchar*)av_malloc(AVBlockSize); + if (data.isEmpty()) { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); + } else { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); + } + fmtContext = avformat_alloc_context(); + if (!fmtContext) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + fmtContext->pb = ioContext; + + if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { + ioBuffer = 0; + + DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); + if (streamId < 0) { + LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); + return false; + } + + freq = fmtContext->streams[streamId]->codec->sample_rate; + if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { + len = (fmtContext->duration * freq) / AV_TIME_BASE; + } else { + len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; + } + + return true; +} + +AbstractFFMpegLoader::~AbstractFFMpegLoader() { + if (ioContext) av_free(ioContext); + if (_opened) { + avformat_close_input(&fmtContext); + } else if (ioBuffer) { + av_free(ioBuffer); + } + if (fmtContext) avformat_free_context(fmtContext); +} + +int AbstractFFMpegLoader::_read_data(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); + if (nbytes <= 0) { + return 0; + } + + memcpy(buf, l->data.constData() + l->dataPos, nbytes); + l->dataPos += nbytes; + return nbytes; +} + +int64_t AbstractFFMpegLoader::_seek_data(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 newPos = -1; + switch (whence) { + case SEEK_SET: newPos = offset; break; + case SEEK_CUR: newPos = l->dataPos + offset; break; + case SEEK_END: newPos = l->data.size() + offset; break; + } + if (newPos < 0 || newPos > l->data.size()) { + return -1; + } + l->dataPos = newPos; + return l->dataPos; +} + +int AbstractFFMpegLoader::_read_file(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + return int(l->f.read((char*)(buf), buf_size)); +} + +int64_t AbstractFFMpegLoader::_seek_file(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; + case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; + case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; + } + return -1; +} + +FFMpegLoader::FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { + frame = av_frame_alloc(); +} + +bool FFMpegLoader::open(qint64 position) { + if (!AbstractFFMpegLoader::open(position)) { + return false; + } + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + // Get a pointer to the codec context for the audio stream + av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { + LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + codecContext = fmtContext->streams[streamId]->codec; + + uint64_t layout = codecContext->channel_layout; + inputFormat = codecContext->sample_fmt; + switch (layout) { + case AV_CH_LAYOUT_MONO: + switch (inputFormat) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break; + default: + sampleSize = -1; // convert needed + break; + } + break; + case AV_CH_LAYOUT_STEREO: + switch (inputFormat) { + case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break; + case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break; + default: + sampleSize = -1; // convert needed + break; + } + break; + default: + sampleSize = -1; // convert needed + break; + } + if (freq != 44100 && freq != 48000) { + sampleSize = -1; // convert needed + } + + if (sampleSize < 0) { + swrContext = swr_alloc(); + if (!swrContext) { + LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout; + srcRate = freq; + AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = AudioToFormat; + dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq; + + av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0); + av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0); + av_opt_set_sample_fmt(swrContext, "in_sample_fmt", src_sample_fmt, 0); + av_opt_set_int(swrContext, "out_channel_layout", dst_ch_layout, 0); + av_opt_set_int(swrContext, "out_sample_rate", dstRate, 0); + av_opt_set_sample_fmt(swrContext, "out_sample_fmt", dst_sample_fmt, 0); + + if ((res = swr_init(swrContext)) < 0) { + LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + sampleSize = AudioToChannels * sizeof(short); + freq = dstRate; + len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP); + fmt = AL_FORMAT_STEREO16; + + maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP); + if ((res = av_samples_alloc_array_and_samples(&dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 0)) < 0) { + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + if (position) { + int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num); + if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) { + if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) { + } + } + //if (dstSamplesData) { + // position = qRound(srcRate * (position / float64(dstRate))); + //} + } + + return true; +} + +AudioPlayerLoader::ReadResult FFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { + int res; + if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { + if (res != AVERROR_EOF) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + } + return ReadResult::Error; + } + if (avpkt.stream_index == streamId) { + av_frame_unref(frame); + int got_frame = 0; + if ((res = avcodec_decode_audio4(codecContext, frame, &got_frame, &avpkt)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + if (res == AVERROR_INVALIDDATA) { + return ReadResult::NotYet; // try to skip bad packet + } + return ReadResult::Error; + } + + if (got_frame) { + if (dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); + if (dstSamples > maxResampleSamples) { + maxResampleSamples = dstSamples; + av_free(dstSamplesData[0]); + + if ((res = av_samples_alloc(dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 1)) < 0) { + dstSamplesData[0] = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + return ReadResult::Error; + } + } + if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)dstSamplesData[0], resultLen); + samplesAdded += resultLen / sampleSize; + } else { + result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); + samplesAdded += frame->nb_samples; + } + } + } + av_packet_unref(&avpkt); + return ReadResult::Ok; +} + +FFMpegLoader::~FFMpegLoader() { + if (codecContext) avcodec_close(codecContext); + if (swrContext) swr_free(&swrContext); + if (dstSamplesData) { + if (dstSamplesData[0]) { + av_freep(&dstSamplesData[0]); + } + av_freep(&dstSamplesData); + } + av_frame_free(&frame); +} diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h new file mode 100644 index 000000000..02ff3d3a3 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h @@ -0,0 +1,102 @@ +/* +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 "media/media_audio_loader.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +#include + +class AbstractFFMpegLoader : public AudioPlayerLoader { +public: + AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) { + } + + bool open(qint64 position = 0) override; + + int64 duration() override { + return len; + } + + int32 frequency() override { + return freq; + } + + ~AbstractFFMpegLoader(); + +protected: + int32 freq = AudioVoiceMsgFrequency; + int64 len = 0; + + uchar *ioBuffer = nullptr; + AVIOContext *ioContext = nullptr; + AVFormatContext *fmtContext = nullptr; + AVCodec *codec = nullptr; + int32 streamId = 0; + + bool _opened = false; + +private: + static int _read_data(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek_data(void *opaque, int64_t offset, int whence); + static int _read_file(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek_file(void *opaque, int64_t offset, int whence); + +}; + +class FFMpegLoader : public AbstractFFMpegLoader { +public: + FFMpegLoader(const FileLocation &file, const QByteArray &data); + + bool open(qint64 position = 0) override; + + int32 format() override { + return fmt; + } + + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; + + ~FFMpegLoader(); + +protected: + int32 sampleSize = 2 * sizeof(uint16); + +private: + int32 fmt = AL_FORMAT_STEREO16; + int32 srcRate = AudioVoiceMsgFrequency; + int32 dstRate = AudioVoiceMsgFrequency; + int32 maxResampleSamples = 1024; + uint8_t **dstSamplesData = nullptr; + + AVCodecContext *codecContext = nullptr; + AVPacket avpkt; + AVSampleFormat inputFormat; + AVFrame *frame = nullptr; + + SwrContext *swrContext = nullptr; + +}; diff --git a/Telegram/SourceFiles/media/media_audio_loader.cpp b/Telegram/SourceFiles/media/media_audio_loader.cpp new file mode 100644 index 000000000..1f1866bf2 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loader.cpp @@ -0,0 +1,80 @@ +/* +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 "media/media_audio_loader.h" + +AudioPlayerLoader::AudioPlayerLoader(const FileLocation &file, const QByteArray &data) +: file(file) +, data(data) { +} + +AudioPlayerLoader::~AudioPlayerLoader() { + if (access) { + file.accessDisable(); + access = false; + } +} + +bool AudioPlayerLoader::check(const FileLocation &file, const QByteArray &data) { + return this->file == file && this->data.size() == data.size(); +} + +void AudioPlayerLoader::saveDecodedSamples(QByteArray *samples, int64 *samplesCount) { + t_assert(_savedSamplesCount == 0); + t_assert(_savedSamples.isEmpty()); + t_assert(!_holdsSavedSamples); + samples->swap(_savedSamples); + std::swap(*samplesCount, _savedSamplesCount); + _holdsSavedSamples = true; +} + +void AudioPlayerLoader::takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount) { + t_assert(*samplesCount == 0); + t_assert(samples->isEmpty()); + t_assert(_holdsSavedSamples); + samples->swap(_savedSamples); + std::swap(*samplesCount, _savedSamplesCount); + _holdsSavedSamples = false; +} + +bool AudioPlayerLoader::holdsSavedDecodedSamples() const { + return _holdsSavedSamples; +} + +bool AudioPlayerLoader::openFile() { + if (data.isEmpty()) { + if (f.isOpen()) f.close(); + if (!access) { + if (!file.accessEnable()) { + LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); + return false; + } + access = true; + } + f.setFileName(file.name()); + if (!f.open(QIODevice::ReadOnly)) { + LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); + return false; + } + } + dataPos = 0; + return true; +} diff --git a/Telegram/SourceFiles/media/media_audio_loader.h b/Telegram/SourceFiles/media/media_audio_loader.h new file mode 100644 index 000000000..42d15a8e1 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loader.h @@ -0,0 +1,62 @@ +/* +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 + +class AudioPlayerLoader { +public: + AudioPlayerLoader(const FileLocation &file, const QByteArray &data); + virtual ~AudioPlayerLoader(); + + virtual bool check(const FileLocation &file, const QByteArray &data); + + virtual bool open(qint64 position = 0) = 0; + virtual int64 duration() = 0; + virtual int32 frequency() = 0; + virtual int32 format() = 0; + + enum class ReadResult { + Error, + NotYet, + Ok, + Wait, + }; + virtual ReadResult readMore(QByteArray &samples, int64 &samplesCount) = 0; + + void saveDecodedSamples(QByteArray *samples, int64 *samplesCount); + void takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount); + bool holdsSavedDecodedSamples() const; + +protected: + FileLocation file; + bool access = false; + QByteArray data; + + QFile f; + int32 dataPos = 0; + + bool openFile(); + +private: + QByteArray _savedSamples; + int64 _savedSamplesCount = 0; + bool _holdsSavedSamples = false; + +}; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp new file mode 100644 index 000000000..c18e2e6e8 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -0,0 +1,423 @@ +/* +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 "media/media_audio_loaders.h" + +#include "media/media_audio.h" +#include "media/media_audio_ffmpeg_loader.h" +#include "media/media_child_ffmpeg_loader.h" + +AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _fromVideoNotify(this, "onVideoSoundAdded") { + moveToThread(thread); +} + +void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) { + bool invoke = true; + { + QMutexLocker lock(&_fromVideoMutex); + if (_fromVideoPlayId == part.videoPlayId) { + _fromVideoQueue.enqueue(*part.packet); + } else { + av_packet_unref(part.packet); + invoke = false; + } + } + if (invoke) { + _fromVideoNotify.call(); + } +} + +void AudioPlayerLoaders::startFromVideo(uint64 videoPlayId) { + QMutexLocker lock(&_fromVideoMutex); + _fromVideoPlayId = videoPlayId; + clearFromVideoQueue(); +} + +void AudioPlayerLoaders::stopFromVideo() { + startFromVideo(0); +} + +void AudioPlayerLoaders::onVideoSoundAdded() { + bool waitingAndAdded = false; + { + QMutexLocker lock(&_fromVideoMutex); + if (_videoLoader && _videoLoader->playId() == _fromVideoPlayId && !_fromVideoQueue.isEmpty()) { + _videoLoader->enqueuePackets(_fromVideoQueue); + waitingAndAdded = _videoLoader->holdsSavedDecodedSamples(); + } + } + if (waitingAndAdded) { + onLoad(_video); + } +} + +AudioPlayerLoaders::~AudioPlayerLoaders() { + QMutexLocker lock(&_fromVideoMutex); + clearFromVideoQueue(); +} + +void AudioPlayerLoaders::clearFromVideoQueue() { + auto queue = createAndSwap(_fromVideoQueue); + for (auto &packet : queue) { + av_packet_unref(&packet); + } +} + +void AudioPlayerLoaders::onInit() { +} + +void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { + auto type = audio.type(); + clear(type); + { + QMutexLocker lock(internal::audioPlayerMutex()); + AudioPlayer *voice = audioPlayer(); + if (!voice) return; + + auto data = voice->dataForType(type); + if (!data) return; + + data->loading = true; + } + + loadData(audio, position); +} + +AudioMsgId AudioPlayerLoaders::clear(AudioMsgId::Type type) { + AudioMsgId result; + switch (type) { + case AudioMsgId::Type::Voice: std::swap(result, _audio); _audioLoader = nullptr; break; + case AudioMsgId::Type::Song: std::swap(result, _song); _songLoader = nullptr; break; + case AudioMsgId::Type::Video: std::swap(result, _video); _videoLoader = nullptr; break; + } + return result; +} + +void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { + m->state = state; + m->position = 0; +} + +void AudioPlayerLoaders::emitError(AudioMsgId::Type type) { + emit error(clear(type)); +} + +void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { + loadData(audio, 0); +} + +void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) { + SetupError err = SetupNoErrorStarted; + auto type = audio.type(); + AudioPlayerLoader *l = setupLoader(audio, err, position); + if (!l) { + if (err == SetupErrorAtStart) { + emitError(type); + } + return; + } + + bool started = (err == SetupNoErrorStarted); + bool finished = false; + bool waiting = false; + bool errAtStart = started; + + QByteArray samples; + int64 samplesCount = 0; + if (l->holdsSavedDecodedSamples()) { + l->takeSavedDecodedSamples(&samples, &samplesCount); + } + while (samples.size() < AudioVoiceMsgBufferSize) { + auto res = l->readMore(samples, samplesCount); + using Result = AudioPlayerLoader::ReadResult; + if (res == Result::Error) { + if (errAtStart) { + { + QMutexLocker lock(internal::audioPlayerMutex()); + AudioPlayer::AudioMsg *m = checkLoader(type); + if (m) m->state = AudioPlayerStoppedAtStart; + } + emitError(type); + return; + } + finished = true; + break; + } else if (res == Result::Ok) { + errAtStart = false; + } else if (res == Result::Wait) { + waiting = samples.isEmpty();// (samples.size() < AudioVoiceMsgBufferSize); + if (waiting) { + l->saveDecodedSamples(&samples, &samplesCount); + } + break; + } + + QMutexLocker lock(internal::audioPlayerMutex()); + if (!checkLoader(type)) { + clear(type); + return; + } + } + + QMutexLocker lock(internal::audioPlayerMutex()); + AudioPlayer::AudioMsg *m = checkLoader(type); + if (!m) { + clear(type); + return; + } + + if (started) { + if (m->source) { + alSourceStop(m->source); + for (int32 i = 0; i < 3; ++i) { + if (m->samplesCount[i]) { + ALuint buffer = 0; + alSourceUnqueueBuffers(m->source, 1, &buffer); + m->samplesCount[i] = 0; + } + } + m->nextBuffer = 0; + } + m->skipStart = position; + m->skipEnd = m->duration - position; + m->position = 0; + m->started = 0; + } + if (samplesCount) { + if (!m->source) { + alGenSources(1, &m->source); + alSourcef(m->source, AL_PITCH, 1.f); + alSource3f(m->source, AL_POSITION, 0, 0, 0); + alSource3f(m->source, AL_VELOCITY, 0, 0, 0); + alSourcei(m->source, AL_LOOPING, 0); + } + if (!m->buffers[m->nextBuffer]) { + alGenBuffers(3, m->buffers); + } + + // If this buffer is queued, try to unqueue some buffer. + if (m->samplesCount[m->nextBuffer]) { + ALint processed = 0; + alGetSourcei(m->source, AL_BUFFERS_PROCESSED, &processed); + if (processed < 1) { // No processed buffers, wait. + l->saveDecodedSamples(&samples, &samplesCount); + return; + } + + // Unqueue some processed buffer. + ALuint buffer = 0; + alSourceUnqueueBuffers(m->source, 1, &buffer); + if (!internal::audioCheckError()) { + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + return; + } + + // Find it in the list and make it the nextBuffer. + bool found = false; + for (int i = 0; i < 3; ++i) { + if (m->buffers[i] == buffer) { + found = true; + m->nextBuffer = i; + break; + } + } + if (!found) { + LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(m->source).arg(processed)); + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + return; + } + + if (m->samplesCount[m->nextBuffer]) { + m->skipStart += m->samplesCount[m->nextBuffer]; + m->samplesCount[m->nextBuffer] = 0; + } + } + + auto frequency = l->frequency(); + auto format = l->format(); + m->samplesCount[m->nextBuffer] = samplesCount; + alBufferData(m->buffers[m->nextBuffer], format, samples.constData(), samples.size(), frequency); + + alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer); + m->skipEnd -= samplesCount; + + m->nextBuffer = (m->nextBuffer + 1) % 3; + + if (!internal::audioCheckError()) { + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + return; + } + } else { + if (waiting) { + return; + } + finished = true; + } + + if (finished) { + m->skipEnd = 0; + m->duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2]; + clear(type); + } + + m->loading = false; + if (m->state == AudioPlayerResuming || m->state == AudioPlayerPlaying || m->state == AudioPlayerStarting) { + ALint state = AL_INITIAL; + alGetSourcei(m->source, AL_SOURCE_STATE, &state); + if (internal::audioCheckError()) { + if (state != AL_PLAYING) { + audioPlayer()->resumeDevice(); + + switch (type) { + case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, internal::audioSuppressGain()); break; + case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break; + case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break; + } + if (!internal::audioCheckError()) { + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + return; + } + + alSourcePlay(m->source); + if (!internal::audioCheckError()) { + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + return; + } + + emit needToCheck(); + } + } else { + setStoppedState(m, AudioPlayerStoppedAtError); + emitError(type); + } + } +} + +AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position) { + err = SetupErrorAtStart; + QMutexLocker lock(internal::audioPlayerMutex()); + AudioPlayer *voice = audioPlayer(); + if (!voice) return nullptr; + + auto data = voice->dataForType(audio.type()); + if (!data || data->audio != audio || !data->loading) { + emit error(audio); + LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); + err = SetupErrorNotPlaying; + return nullptr; + } + + bool isGoodId = false; + AudioPlayerLoader *l = nullptr; + switch (audio.type()) { + case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (_audio == audio); break; + case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (_song == audio); break; + case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_song == audio); break; + } + + if (l && (!isGoodId || !l->check(data->file, data->data))) { + clear(audio.type()); + } + + if (!l) { + std_::unique_ptr *loader = nullptr; + switch (audio.type()) { + case AudioMsgId::Type::Voice: _audio = audio; loader = &_audioLoader; break; + case AudioMsgId::Type::Song: _song = audio; loader = &_songLoader; break; + case AudioMsgId::Type::Video: _video = audio; break; + } + + if (audio.type() == AudioMsgId::Type::Video) { + _videoLoader = std_::make_unique(std_::move(data->videoData)); + l = _videoLoader.get(); + } else { + *loader = std_::make_unique(data->file, data->data); + l = loader->get(); + } + + if (!l->open(position)) { + data->state = AudioPlayerStoppedAtStart; + return nullptr; + } + int64 duration = l->duration(); + if (duration <= 0) { + data->state = AudioPlayerStoppedAtStart; + return nullptr; + } + data->duration = duration; + data->frequency = l->frequency(); + if (!data->frequency) data->frequency = AudioVoiceMsgFrequency; + err = SetupNoErrorStarted; + } else { + if (!data->skipEnd) { + err = SetupErrorLoadedFull; + LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); + return nullptr; + } + } + return l; +} + +AudioPlayer::AudioMsg *AudioPlayerLoaders::checkLoader(AudioMsgId::Type type) { + AudioPlayer *voice = audioPlayer(); + if (!voice) return 0; + + auto data = voice->dataForType(type); + bool isGoodId = false; + AudioPlayerLoader *l = nullptr; + switch (type) { + case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (data->audio == _audio); break; + case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (data->audio == _song); break; + case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (data->audio == _video); break; + } + if (!l || !data) return nullptr; + + if (!isGoodId || !data->loading || !l->check(data->file, data->data)) { + LOG(("Audio Error: playing changed while loading")); + return nullptr; + } + + return data; +} + +void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) { + switch (audio.type()) { + case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break; + case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break; + case AudioMsgId::Type::Video: if (_video == audio) clear(audio.type()); break; + } + + QMutexLocker lock(internal::audioPlayerMutex()); + AudioPlayer *voice = audioPlayer(); + if (!voice) return; + + for (int i = 0; i < AudioSimultaneousLimit; ++i) { + auto data = voice->dataForType(audio.type(), i); + if (data->audio == audio) { + data->loading = false; + } + } +} diff --git a/Telegram/SourceFiles/media/media_audio_loaders.h b/Telegram/SourceFiles/media/media_audio_loaders.h new file mode 100644 index 000000000..f1893885a --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loaders.h @@ -0,0 +1,85 @@ +/* +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 "media/media_child_ffmpeg_loader.h" +#include "media/media_audio.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +class AudioPlayerLoader; +class ChildFFMpegLoader; +class AudioPlayerLoaders : public QObject { + Q_OBJECT + +public: + AudioPlayerLoaders(QThread *thread); + void startFromVideo(uint64 videoPlayId); + void stopFromVideo(); + void feedFromVideo(VideoSoundPart &&part); + ~AudioPlayerLoaders(); + +signals: + void error(const AudioMsgId &audio); + void needToCheck(); + +public slots: + void onInit(); + + void onStart(const AudioMsgId &audio, qint64 position); + void onLoad(const AudioMsgId &audio); + void onCancel(const AudioMsgId &audio); + + void onVideoSoundAdded(); + +private: + void clearFromVideoQueue(); + + AudioMsgId _audio, _song, _video; + std_::unique_ptr _audioLoader; + std_::unique_ptr _songLoader; + std_::unique_ptr _videoLoader; + + QMutex _fromVideoMutex; + uint64 _fromVideoPlayId; + QQueue _fromVideoQueue; + SingleDelayedCall _fromVideoNotify; + + void emitError(AudioMsgId::Type type); + AudioMsgId clear(AudioMsgId::Type type); + void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); + + enum SetupError { + SetupErrorAtStart = 0, + SetupErrorNotPlaying = 1, + SetupErrorLoadedFull = 2, + SetupNoErrorStarted = 3, + }; + void loadData(const AudioMsgId &audio, qint64 position); + AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); + AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); + +}; diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp new file mode 100644 index 000000000..1f122002e --- /dev/null +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -0,0 +1,186 @@ +/* +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 "media/media_child_ffmpeg_loader.h" + +constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; +constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; +constexpr int32 AudioToChannels = 2; + +VideoSoundData::~VideoSoundData() { + if (context) { + avcodec_close(context); + avcodec_free_context(&context); + context = nullptr; + } +} + +ChildFFMpegLoader::ChildFFMpegLoader(std_::unique_ptr &&data) : AudioPlayerLoader(FileLocation(), QByteArray()) +, _parentData(std_::move(data)) { + _frame = av_frame_alloc(); +} + +bool ChildFFMpegLoader::open(qint64 position) { + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + uint64_t layout = _parentData->context->channel_layout; + _inputFormat = _parentData->context->sample_fmt; + switch (layout) { + case AV_CH_LAYOUT_MONO: + switch (_inputFormat) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: _format = AL_FORMAT_MONO8; _sampleSize = 1; break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: _format = AL_FORMAT_MONO16; _sampleSize = sizeof(uint16); break; + default: + _sampleSize = -1; // convert needed + break; + } + break; + case AV_CH_LAYOUT_STEREO: + switch (_inputFormat) { + case AV_SAMPLE_FMT_U8: _format = AL_FORMAT_STEREO8; _sampleSize = 2; break; + case AV_SAMPLE_FMT_S16: _format = AL_FORMAT_STEREO16; _sampleSize = 2 * sizeof(uint16); break; + default: + _sampleSize = -1; // convert needed + break; + } + break; + default: + _sampleSize = -1; // convert needed + break; + } + if (_parentData->frequency != 44100 && _parentData->frequency != 48000) { + _sampleSize = -1; // convert needed + } + + if (_sampleSize < 0) { + _swrContext = swr_alloc(); + if (!_swrContext) { + LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout; + _srcRate = _parentData->frequency; + AVSampleFormat src_sample_fmt = _inputFormat, dst_sample_fmt = AudioToFormat; + _dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? AudioVoiceMsgFrequency : _parentData->frequency; + + av_opt_set_int(_swrContext, "in_channel_layout", src_ch_layout, 0); + av_opt_set_int(_swrContext, "in_sample_rate", _srcRate, 0); + av_opt_set_sample_fmt(_swrContext, "in_sample_fmt", src_sample_fmt, 0); + av_opt_set_int(_swrContext, "out_channel_layout", dst_ch_layout, 0); + av_opt_set_int(_swrContext, "out_sample_rate", _dstRate, 0); + av_opt_set_sample_fmt(_swrContext, "out_sample_fmt", dst_sample_fmt, 0); + + if ((res = swr_init(_swrContext)) < 0) { + LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + _sampleSize = AudioToChannels * sizeof(short); + _parentData->frequency = _dstRate; + _parentData->length = av_rescale_rnd(_parentData->length, _dstRate, _srcRate, AV_ROUND_UP); + _format = AL_FORMAT_STEREO16; + + _maxResampleSamples = av_rescale_rnd(AVBlockSize / _sampleSize, _dstRate, _srcRate, AV_ROUND_UP); + if ((res = av_samples_alloc_array_and_samples(&_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 0)) < 0) { + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + + return true; +} + +AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { + if (_queue.isEmpty()) { + return ReadResult::Wait; + } + + av_frame_unref(_frame); + int got_frame = 0; + int res = 0; + auto packet = _queue.dequeue(); + if ((res = avcodec_decode_audio4(_parentData->context, _frame, &got_frame, &packet)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + if (res == AVERROR_INVALIDDATA) { + return ReadResult::NotYet; // try to skip bad packet + } + return ReadResult::Error; + } + + if (got_frame) { + if (_dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP); + if (dstSamples > _maxResampleSamples) { + _maxResampleSamples = dstSamples; + av_free(_dstSamplesData[0]); + + if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) { + _dstSamplesData[0] = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + return ReadResult::Error; + } + } + if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)_dstSamplesData[0], resultLen); + samplesAdded += resultLen / _sampleSize; + } else { + result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize); + samplesAdded += _frame->nb_samples; + } + } + av_packet_unref(&packet); + return ReadResult::Ok; +} + +void ChildFFMpegLoader::enqueuePackets(QQueue &packets) { + _queue += std_::move(packets); + packets.clear(); +} + +ChildFFMpegLoader::~ChildFFMpegLoader() { + auto queue = createAndSwap(_queue); + for (auto &packet : queue) { + av_packet_unref(&packet); + } + if (_dstSamplesData) { + if (_dstSamplesData[0]) { + av_freep(&_dstSamplesData[0]); + } + av_freep(&_dstSamplesData); + } + av_frame_free(&_frame); +} diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h new file mode 100644 index 000000000..fff2058c9 --- /dev/null +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -0,0 +1,93 @@ +/* +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 "media/media_audio_loader.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +#include + +struct VideoSoundData { + uint64 videoPlayId = 0; + AVCodecContext *context = nullptr; + int32 frequency = AudioVoiceMsgFrequency; + int64 length = 0; + ~VideoSoundData(); +}; + +struct VideoSoundPart { + AVPacket *packet = nullptr; + uint64 videoPlayId = 0; +}; + +class ChildFFMpegLoader : public AudioPlayerLoader { +public: + ChildFFMpegLoader(std_::unique_ptr &&data); + + bool open(qint64 position = 0) override; + + bool check(const FileLocation &file, const QByteArray &data) override { + return true; + } + + int32 format() override { + return _format; + } + + int64 duration() override { + return _parentData->length; + } + + int32 frequency() override { + return _parentData->frequency; + } + + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; + void enqueuePackets(QQueue &packets); + + uint64 playId() const { + return _parentData->videoPlayId; + } + + ~ChildFFMpegLoader(); + +private: + int32 _sampleSize = 2 * sizeof(uint16); + int32 _format = AL_FORMAT_STEREO16; + int32 _srcRate = AudioVoiceMsgFrequency; + int32 _dstRate = AudioVoiceMsgFrequency; + int32 _maxResampleSamples = 1024; + uint8_t **_dstSamplesData = nullptr; + + std_::unique_ptr _parentData; + AVSampleFormat _inputFormat; + AVFrame *_frame = nullptr; + + SwrContext *_swrContext = nullptr; + QQueue _queue; + +}; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index f509310d8..cd20fd6d8 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -21,6 +21,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "media/media_clip_ffmpeg.h" +#include "media/media_audio.h" +#include "media/media_child_ffmpeg_loader.h" + namespace Media { namespace Clip { namespace internal { @@ -104,7 +107,12 @@ bool FFMpegReaderImplementation::readNextFrame() { } if (eofReached) { + if (_mode == Mode::Normal) { + return false; + } + clearPacketQueue(); + if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { @@ -119,6 +127,7 @@ bool FFMpegReaderImplementation::readNextFrame() { avcodec_flush_buffers(_codecContext); _hadFrame = false; _frameMs = 0; + _lastReadPacketMs = 0; } } @@ -162,6 +171,16 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q } } + // Read some future packets for audio stream. + if (_audioStreamId) { + while (_frameMs + 5000 > _lastReadPacketMs) { + auto packetResult = readPacket(); + if (packetResult != PacketResult::Ok) { + break; + } + } + } + av_frame_unref(_frame); return true; } @@ -171,6 +190,8 @@ int FFMpegReaderImplementation::nextFrameDelay() { } bool FFMpegReaderImplementation::start(Mode mode) { + _mode = mode; + initDevice(); if (!_device->open(QIODevice::ReadOnly)) { LOG(("Gif Error: Unable to open device %1").arg(logData())); @@ -207,12 +228,12 @@ bool FFMpegReaderImplementation::start(Mode mode) { } _packetNull.stream_index = _streamId; - // Get a pointer to the codec context for the audio stream + // Get a pointer to the codec context for the video stream _codecContext = _fmtContext->streams[_streamId]->codec; _codec = avcodec_find_decoder(_codecContext->codec_id); _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); - if (mode == Mode::OnlyGifv) { + if (_mode == Mode::OnlyGifv) { if (_audioStreamId >= 0) { // should be no audio stream return false; } @@ -222,7 +243,7 @@ bool FFMpegReaderImplementation::start(Mode mode) { if (_codecContext->codec_id != AV_CODEC_ID_H264) { return false; } - } else if (mode == Mode::Silent) { + } else if (_mode == Mode::Silent || !audioPlayer()) { _audioStreamId = -1; } av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); @@ -231,6 +252,35 @@ bool FFMpegReaderImplementation::start(Mode mode) { return false; } + if (_audioStreamId >= 0) { + // Get a pointer to the codec context for the audio stream + auto audioContextOriginal = _fmtContext->streams[_audioStreamId]->codec; + auto audioCodec = avcodec_find_decoder(audioContextOriginal->codec_id); + + AVCodecContext *audioContext = avcodec_alloc_context3(audioCodec); + if ((res = avcodec_copy_context(audioContext, audioContextOriginal)) != 0) { + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + av_opt_set_int(audioContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { + avcodec_free_context(&audioContext); + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + auto soundData = std_::make_unique(); + soundData->context = audioContext; + soundData->frequency = audioContextOriginal->sample_rate; + if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { + soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; + } else { + soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; + } + soundData->videoPlayId = _playId = rand_value(); + audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), 0, std_::move(soundData)); + } + return true; } @@ -281,6 +331,10 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( bool videoPacket = (packet.stream_index == _streamId); bool audioPacket = (_audioStreamId >= 0 && packet.stream_index == _audioStreamId); if (audioPacket || videoPacket) { + int64 packetPts = (packet.pts == AV_NOPTS_VALUE) ? packet.dts : packet.pts; + int64 packetMs = (packetPts * 1000LL * _fmtContext->streams[packet.stream_index]->time_base.num) / _fmtContext->streams[packet.stream_index]->time_base.den; + _lastReadPacketMs = packetMs; + //AVPacket packetForQueue; //av_init_packet(&packetForQueue); //if ((res = av_packet_ref(&packetForQueue, &packet)) < 0) { @@ -294,9 +348,10 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( //_packetQueue.enqueue(packetForQueue); } else if (audioPacket) { // queue packet to audio player - // audioPlayer()->enqueuePacket(packet, &isEnough) - //av_packet_unref(&packetForQueue); - av_packet_unref(&packet); + VideoSoundPart part; + part.packet = &packet; + part.videoPlayId = _playId; + audioPlayer()->feedFromVideo(std_::move(part)); } } else { av_packet_unref(&packet); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 78c548282..02f5f18e6 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -62,6 +62,8 @@ private: static int _read(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek(void *opaque, int64_t offset, int whence); + Mode _mode = Mode::Normal; + uchar *_ioBuffer = nullptr; AVIOContext *_ioContext = nullptr; AVFormatContext *_fmtContext = nullptr; @@ -74,6 +76,8 @@ private: bool _frameRead = false; int _audioStreamId = 0; + uint64 _playId = 0; + int64 _lastReadPacketMs = 0; QQueue _packetQueue; AVPacket _packetNull; // for final decoding diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 6c05b881f..d2accd161 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -31,7 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "fileuploader.h" #include "mainwindow.h" #include "playerwidget.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" namespace Overview { diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index 29f7941ce..40e3ae804 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "mainwidget.h" #include "localstorage.h" -#include "audio.h" +#include "media/media_audio.h" PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _a_state(animation(this, &PlayerWidget::step_state)) diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index 72aa9ba98..c8e893483 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "audio.h" +#include "media/media_audio.h" class PlayerWidget : public TWidget { Q_OBJECT diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 563af77fb..8519bd95c 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -32,7 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "apiwrap.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" namespace { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 379fd0c18..3826aa5f0 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1241,10 +1241,12 @@ public: AudioMsgId() { } AudioMsgId(DocumentData *audio, const FullMsgId &msgId) : _audio(audio), _contextId(msgId) { - setType(); + setTypeFromAudio(); } AudioMsgId(DocumentData *audio, ChannelId channelId, MsgId msgId) : _audio(audio), _contextId(channelId, msgId) { - setType(); + setTypeFromAudio(); + } + AudioMsgId(Type type) : _type(type) { } Type type() const { @@ -1262,7 +1264,7 @@ public: } private: - void setType() { + void setTypeFromAudio() { if (_audio->voice()) { _type = Type::Voice; } else if (_audio->song()) { diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 2fe3a169e..aa6701a43 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -265,23 +265,23 @@ class SingleDelayedCall : public QObject { Q_OBJECT public: - SingleDelayedCall(QObject *parent, const char *member) : QObject(parent), _pending(false), _member(member) { + SingleDelayedCall(QObject *parent, const char *member) : QObject(parent), _member(member) { } void call() { - if (!_pending) { - _pending = true; + if (!_pending.loadAcquire()) { + _pending.storeRelease(1); QMetaObject::invokeMethod(this, "makeDelayedCall", Qt::QueuedConnection); } } private slots: void makeDelayedCall() { - _pending = false; + _pending.storeRelease(0); QMetaObject::invokeMethod(parent(), _member); } private: - bool _pending; + QAtomicInt _pending = { 0 }; const char *_member; }; diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 8651364b3..1fbe514a7 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -104,7 +104,6 @@ SOURCES += \ ./SourceFiles/apiwrap.cpp \ ./SourceFiles/app.cpp \ ./SourceFiles/application.cpp \ - ./SourceFiles/audio.cpp \ ./SourceFiles/autoupdater.cpp \ ./SourceFiles/dialogswidget.cpp \ ./SourceFiles/dropdown.cpp \ @@ -172,6 +171,11 @@ SOURCES += \ ./SourceFiles/intro/intropwdcheck.cpp \ ./SourceFiles/intro/introsignup.cpp \ ./SourceFiles/intro/introstart.cpp \ + ./SourceFiles/media/media_audio.cpp \ + ./SourceFiles/media/media_clip_ffmpeg.cpp \ + ./SourceFiles/media/media_clip_implementation.cpp \ + ./SourceFiles/media/media_clip_qtgif.cpp \ + ./SourceFiles/media/media_clip_reader.cpp \ ./SourceFiles/mtproto/facade.cpp \ ./SourceFiles/mtproto/auth_key.cpp \ ./SourceFiles/mtproto/connection.cpp \ @@ -254,7 +258,6 @@ HEADERS += \ ./SourceFiles/apiwrap.h \ ./SourceFiles/app.h \ ./SourceFiles/application.h \ - ./SourceFiles/audio.h \ ./SourceFiles/autoupdater.h \ ./SourceFiles/config.h \ ./SourceFiles/countries.h \ @@ -328,6 +331,11 @@ HEADERS += \ ./SourceFiles/intro/intropwdcheck.h \ ./SourceFiles/intro/introsignup.h \ ./SourceFiles/intro/introstart.h \ + ./SourceFiles/media/media_audio.h \ + ./SourceFiles/media/media_clip_ffmpeg.h \ + ./SourceFiles/media/media_clip_implementation.h \ + ./SourceFiles/media/media_clip_qtgif.h \ + ./SourceFiles/media/media_clip_reader.h \ ./SourceFiles/mtproto/facade.h \ ./SourceFiles/mtproto/auth_key.h \ ./SourceFiles/mtproto/connection.h \ diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 532a644d2..42dc178c9 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -186,10 +186,6 @@ true true - - true - true - true true @@ -371,6 +367,14 @@ true true + + true + true + + + true + true + true true @@ -515,10 +519,6 @@ true true - - true - true - true true @@ -705,6 +705,14 @@ true true + + true + true + + + true + true + true true @@ -880,10 +888,6 @@ true true - - true - true - true true @@ -1070,6 +1074,14 @@ true true + + true + true + + + true + true + true true @@ -1204,7 +1216,6 @@ - @@ -1262,6 +1273,11 @@ + + + + + @@ -1538,6 +1554,37 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.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" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.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" + + + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.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" + + @@ -2252,20 +2299,6 @@ $(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath) - - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -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" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing usernamebox.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 64116e19b..6993d85e1 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -447,18 +447,6 @@ GeneratedFiles\Release - - SourceFiles - - - GeneratedFiles\Deploy - - - GeneratedFiles\Debug - - - GeneratedFiles\Release - SourceFiles\boxes @@ -1368,6 +1356,39 @@ SourceFiles\media + + SourceFiles\media + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + + + SourceFiles\media + + + GeneratedFiles\Deploy + + + SourceFiles\media + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + @@ -1628,6 +1649,15 @@ SourceFiles\media + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1705,9 +1735,6 @@ SourceFiles - - SourceFiles - SourceFiles\boxes @@ -1921,6 +1948,12 @@ SourceFiles\media + + SourceFiles\media + + + SourceFiles\media + diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 449edf2cf..817d47551 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -163,8 +163,8 @@ 07C8FE101CB80890007A8702 /* toast.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C8FE0C1CB80890007A8702 /* toast.cpp */; }; 07C8FE121CB80915007A8702 /* moc_toast_manager.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C8FE111CB80915007A8702 /* moc_toast_manager.cpp */; }; 07CAACD81AEA64F00058E508 /* AudioUnit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 07CAACD71AEA64F00058E508 /* AudioUnit.framework */; }; - 07D7034B19B8755A00C4EED2 /* audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D7034919B8755A00C4EED2 /* audio.cpp */; }; - 07D703BB19B88FB900C4EED2 /* moc_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */; }; + 07D7034B19B8755A00C4EED2 /* media_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D7034919B8755A00C4EED2 /* media_audio.cpp */; }; + 07D703BB19B88FB900C4EED2 /* moc_media_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */; }; 07D7954A1B5544B200DE9598 /* qtpcre in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 07D795491B5544B200DE9598 /* qtpcre */; }; 07D7EABA1A597DD000838BA2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 07D7EABC1A597DD000838BA2 /* Localizable.strings */; }; 07D8509419F5C97E00623D75 /* core_types.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D8509219F5C97E00623D75 /* core_types.cpp */; }; @@ -603,9 +603,9 @@ 07C8FE111CB80915007A8702 /* moc_toast_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_toast_manager.cpp; path = GeneratedFiles/Debug/moc_toast_manager.cpp; sourceTree = SOURCE_ROOT; }; 07CAACD71AEA64F00058E508 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 07D518D41CD0E27600F5FF59 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = version.h; path = SourceFiles/core/version.h; sourceTree = SOURCE_ROOT; }; - 07D7034919B8755A00C4EED2 /* audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = audio.cpp; path = SourceFiles/audio.cpp; sourceTree = SOURCE_ROOT; }; - 07D7034A19B8755A00C4EED2 /* audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio.h; path = SourceFiles/audio.h; sourceTree = SOURCE_ROOT; }; - 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_audio.cpp; path = GeneratedFiles/Debug/moc_audio.cpp; sourceTree = SOURCE_ROOT; }; + 07D7034919B8755A00C4EED2 /* media_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_audio.cpp; path = SourceFiles/media/media_audio.cpp; sourceTree = SOURCE_ROOT; }; + 07D7034A19B8755A00C4EED2 /* media_audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_audio.h; path = SourceFiles/media/media_audio.h; sourceTree = SOURCE_ROOT; }; + 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_audio.cpp; path = GeneratedFiles/Debug/moc_media_audio.cpp; sourceTree = SOURCE_ROOT; }; 07D795491B5544B200DE9598 /* qtpcre */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = qtpcre; path = "$(QT_PATH)/lib/libqtpcre$(QT_LIBRARY_SUFFIX).a"; sourceTree = ""; }; 07D7EABB1A597DD000838BA2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Resources/langs/en.lproj/Localizable.strings; sourceTree = ""; }; 07D7EABD1A597DD200838BA2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Resources/langs/es.lproj/Localizable.strings; sourceTree = ""; }; @@ -1413,8 +1413,8 @@ C19DF71B273A4843553518F2 /* app.h */, C20F9DD8C7B031B8E20D5653 /* application.cpp */, 09FD01F2BD652EB838A296D8 /* application.h */, - 07D7034919B8755A00C4EED2 /* audio.cpp */, - 07D7034A19B8755A00C4EED2 /* audio.h */, + 07D7034919B8755A00C4EED2 /* media_audio.cpp */, + 07D7034A19B8755A00C4EED2 /* media_audio.h */, 07C7596D1B1F7E0000662169 /* autoupdater.cpp */, 07C7596E1B1F7E0000662169 /* autoupdater.h */, 206B4F5CBD5354BCE19FF32F /* countries.h */, @@ -1500,7 +1500,7 @@ A1479F94376F9732B57C69DB /* moc_animation.cpp */, 0764D55C1ABAD71B00FBFEED /* moc_apiwrap.cpp */, E181C525E21A16F2D4396CA7 /* moc_application.cpp */, - 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */, + 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */, 07DE92A91AA4928200A18F6F /* moc_autolockbox.cpp */, 07C759711B1F7E2800662169 /* moc_autoupdater.cpp */, 078A2FC91A811C5900CCC7A0 /* moc_backgroundbox.cpp */, @@ -2013,7 +2013,7 @@ EBE29731916DB43BF49FE7A4 /* aboutbox.cpp in Compile Sources */, 4426AF526AAD86D6F73CE36F /* addcontactbox.cpp in Compile Sources */, 0716C9561D0589A700797B22 /* profile_userpic_button.cpp in Compile Sources */, - 07D7034B19B8755A00C4EED2 /* audio.cpp in Compile Sources */, + 07D7034B19B8755A00C4EED2 /* media_audio.cpp in Compile Sources */, A0A6B97F7DBEC81004EC9461 /* confirmbox.cpp in Compile Sources */, 4FEA8F51B7BC7CAC71347A1A /* connectionbox.cpp in Compile Sources */, 07C7596F1B1F7E0000662169 /* autoupdater.cpp in Compile Sources */, @@ -2024,7 +2024,7 @@ 07C8FE0F1CB80890007A8702 /* toast_widget.cpp in Compile Sources */, 0716C9741D058C8600797B22 /* moc_profile_inner_widget.cpp in Compile Sources */, 3ABE4F9B2264F770D944106D /* emojibox.cpp in Compile Sources */, - 07D703BB19B88FB900C4EED2 /* moc_audio.cpp in Compile Sources */, + 07D703BB19B88FB900C4EED2 /* moc_media_audio.cpp in Compile Sources */, 77B998AC22A13EF3DDEE07AC /* photocropbox.cpp in Compile Sources */, F278C423357CA99797EA30AB /* photosendbox.cpp in Compile Sources */, E8D95529CED88F18818C9A8B /* introwidget.cpp in Compile Sources */, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index 0707e159e..2cc42ee18 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -55,7 +55,6 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -99,6 +98,8 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -193,7 +194,6 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -237,6 +237,8 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -274,7 +276,6 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -318,6 +319,8 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -366,9 +369,6 @@ GeneratedFiles/Debug/moc_apiwrap.cpp: SourceFiles/apiwrap.h GeneratedFiles/Debug/moc_application.cpp: SourceFiles/application.h $(MOC_FILE) SourceFiles/application.h -o GeneratedFiles/Debug/moc_application.cpp -GeneratedFiles/Debug/moc_audio.cpp: SourceFiles/audio.h - $(MOC_FILE) SourceFiles/audio.h -o GeneratedFiles/Debug/moc_audio.cpp - GeneratedFiles/Debug/moc_autolockbox.cpp: SourceFiles/boxes/autolockbox.h $(MOC_FILE) SourceFiles/boxes/autolockbox.h -o GeneratedFiles/Debug/moc_autolockbox.cpp @@ -498,6 +498,12 @@ GeneratedFiles/Debug/moc_mainwidget.cpp: SourceFiles/mainwidget.h GeneratedFiles/Debug/moc_mainwindow.cpp: SourceFiles/mainwindow.h $(MOC_FILE) SourceFiles/mainwindow.h -o GeneratedFiles/Debug/moc_mainwindow.cpp +GeneratedFiles/Debug/moc_media_audio.cpp: SourceFiles/media/media_audio.h + $(MOC_FILE) SourceFiles/media/media_audio.h -o GeneratedFiles/Debug/moc_media_audio.cpp + +GeneratedFiles/Debug/moc_media_clip_reader.cpp: SourceFiles/media/media_clip_reader.h + $(MOC_FILE) SourceFiles/media/media_clip_reader.h -o GeneratedFiles/Debug/moc_media_clip_reader.cpp + GeneratedFiles/Debug/moc_mediaview.cpp: SourceFiles/mediaview.h $(MOC_FILE) SourceFiles/mediaview.h -o GeneratedFiles/Debug/moc_mediaview.cpp From 99b15719cfda9d98051249e4ab61c3f4e2410555 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jul 2016 20:44:22 +0300 Subject: [PATCH 08/60] Stopping video sound when closing mediaview (destroying Clip::Reader). Fixed launching video with sound while song is playing. --- Telegram/SourceFiles/media/media_audio.cpp | 8 ++++++- .../media/media_audio_ffmpeg_loader.cpp | 3 ++- .../SourceFiles/media/media_audio_loader.h | 1 + .../SourceFiles/media/media_audio_loaders.cpp | 24 +++++++++++++------ .../SourceFiles/media/media_audio_loaders.h | 2 +- .../media/media_child_ffmpeg_loader.cpp | 17 ++++++++----- .../media/media_child_ffmpeg_loader.h | 23 ++++++++++++++++++ .../SourceFiles/media/media_clip_ffmpeg.cpp | 23 +++++++++--------- Telegram/SourceFiles/mediaview.cpp | 7 +++--- 9 files changed, 76 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 52d424d40..ee60c59f6 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -636,8 +636,14 @@ void AudioPlayer::stop(AudioMsgId::Type type) { AudioMsgId current; { QMutexLocker lock(&playerMutex); - current = dataForType(type)->audio; + auto data = dataForType(type); + t_assert(data != nullptr); + + current = data->audio; fadedStop(type); + if (type == AudioMsgId::Type::Video) { + data->clear(); + } } if (current) emit updated(current); } diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp index ab7a14156..4fed50ca0 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -235,8 +235,9 @@ AudioPlayerLoader::ReadResult FFMpegLoader::readMore(QByteArray &result, int64 & if (res != AVERROR_EOF) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; } - return ReadResult::Error; + return ReadResult::EndOfFile; } if (avpkt.stream_index == streamId) { av_frame_unref(frame); diff --git a/Telegram/SourceFiles/media/media_audio_loader.h b/Telegram/SourceFiles/media/media_audio_loader.h index 42d15a8e1..74a749043 100644 --- a/Telegram/SourceFiles/media/media_audio_loader.h +++ b/Telegram/SourceFiles/media/media_audio_loader.h @@ -37,6 +37,7 @@ public: NotYet, Ok, Wait, + EndOfFile, }; virtual ReadResult readMore(QByteArray &samples, int64 &samplesCount) = 0; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index c18e2e6e8..96b32b2de 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -30,14 +30,14 @@ AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _fromVideoNotify(this, } void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) { - bool invoke = true; + bool invoke = false; { QMutexLocker lock(&_fromVideoMutex); if (_fromVideoPlayId == part.videoPlayId) { _fromVideoQueue.enqueue(*part.packet); + invoke = true; } else { - av_packet_unref(part.packet); - invoke = false; + FFMpeg::freePacket(part.packet); } } if (invoke) { @@ -77,7 +77,7 @@ AudioPlayerLoaders::~AudioPlayerLoaders() { void AudioPlayerLoaders::clearFromVideoQueue() { auto queue = createAndSwap(_fromVideoQueue); for (auto &packet : queue) { - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); } } @@ -124,7 +124,7 @@ void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { loadData(audio, 0); } -void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) { +void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { SetupError err = SetupNoErrorStarted; auto type = audio.type(); AudioPlayerLoader *l = setupLoader(audio, err, position); @@ -160,10 +160,13 @@ void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) { } finished = true; break; + } else if (res == Result::EndOfFile) { + finished = true; + break; } else if (res == Result::Ok) { errAtStart = false; } else if (res == Result::Wait) { - waiting = samples.isEmpty();// (samples.size() < AudioVoiceMsgBufferSize); + waiting = (samples.size() < AudioVoiceMsgBufferSize); if (waiting) { l->saveDecodedSamples(&samples, &samplesCount); } @@ -335,11 +338,12 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, Setu switch (audio.type()) { case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (_audio == audio); break; case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (_song == audio); break; - case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_song == audio); break; + case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_video == audio); break; } if (l && (!isGoodId || !l->check(data->file, data->data))) { clear(audio.type()); + l = nullptr; } if (!l) { @@ -351,6 +355,12 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, Setu } if (audio.type() == AudioMsgId::Type::Video) { + if (!data->videoData) { + data->state = AudioPlayerStoppedAtError; + emit error(audio); + LOG(("Audio Error: video sound data not ready")); + return nullptr; + } _videoLoader = std_::make_unique(std_::move(data->videoData)); l = _videoLoader.get(); } else { diff --git a/Telegram/SourceFiles/media/media_audio_loaders.h b/Telegram/SourceFiles/media/media_audio_loaders.h index f1893885a..1aaebd514 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.h +++ b/Telegram/SourceFiles/media/media_audio_loaders.h @@ -78,7 +78,7 @@ private: SetupErrorLoadedFull = 2, SetupNoErrorStarted = 3, }; - void loadData(const AudioMsgId &audio, qint64 position); + void loadData(AudioMsgId audio, qint64 position); AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index 1f122002e..f7688934d 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -113,18 +113,23 @@ bool ChildFFMpegLoader::open(qint64 position) { AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { if (_queue.isEmpty()) { - return ReadResult::Wait; + return _eofReached ? ReadResult::EndOfFile : ReadResult::Wait; } av_frame_unref(_frame); int got_frame = 0; int res = 0; auto packet = _queue.dequeue(); + _eofReached = FFMpeg::isNullPacket(packet); + if (_eofReached) { + return ReadResult::EndOfFile; + } + if ((res = avcodec_decode_audio4(_parentData->context, _frame, &got_frame, &packet)) < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); if (res == AVERROR_INVALIDDATA) { return ReadResult::NotYet; // try to skip bad packet } @@ -143,7 +148,7 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, in char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); return ReadResult::Error; } } @@ -151,7 +156,7 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, in char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); return ReadResult::Error; } int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); @@ -162,7 +167,7 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, in samplesAdded += _frame->nb_samples; } } - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); return ReadResult::Ok; } @@ -174,7 +179,7 @@ void ChildFFMpegLoader::enqueuePackets(QQueue &packets) { ChildFFMpegLoader::~ChildFFMpegLoader() { auto queue = createAndSwap(_queue); for (auto &packet : queue) { - av_packet_unref(&packet); + FFMpeg::freePacket(&packet); } if (_dstSamplesData) { if (_dstSamplesData[0]) { diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h index fff2058c9..f5873472d 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -44,6 +44,24 @@ struct VideoSoundPart { uint64 videoPlayId = 0; }; +namespace FFMpeg { + +inline bool isNullPacket(const AVPacket &packet) { + return packet.data == nullptr && packet.size == 0; +} + +inline bool isNullPacket(const AVPacket *packet) { + return isNullPacket(*packet); +} + +inline void freePacket(AVPacket *packet) { + if (!isNullPacket(packet)) { + av_packet_unref(packet); + } +} + +} // namespace FFMpeg + class ChildFFMpegLoader : public AudioPlayerLoader { public: ChildFFMpegLoader(std_::unique_ptr &&data); @@ -72,10 +90,15 @@ public: uint64 playId() const { return _parentData->videoPlayId; } + bool eofReached() const { + return _eofReached; + } ~ChildFFMpegLoader(); private: + bool _eofReached = false; + int32 _sampleSize = 2 * sizeof(uint16); int32 _format = AL_FORMAT_STEREO16; int32 _srcRate = AudioVoiceMsgFrequency; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index cd20fd6d8..55e2cd1e2 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -107,12 +107,11 @@ bool FFMpegReaderImplementation::readNextFrame() { } if (eofReached) { + clearPacketQueue(); if (_mode == Mode::Normal) { return false; } - clearPacketQueue(); - if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { @@ -294,6 +293,9 @@ int FFMpegReaderImplementation::duration() const { } FFMpegReaderImplementation::~FFMpegReaderImplementation() { + if (_mode == Mode::Normal && _audioStreamId >= 0) { + audioPlayer()->stop(AudioMsgId::Type::Video); + } if (_frameRead) { av_frame_unref(_frame); _frameRead = false; @@ -321,6 +323,13 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( int res = 0; if ((res = av_read_frame(_fmtContext, &packet)) < 0) { if (res == AVERROR_EOF) { + if (_audioStreamId >= 0) { + // queue terminating packet to audio player + VideoSoundPart part; + part.packet = &_packetNull; + part.videoPlayId = _playId; + audioPlayer()->feedFromVideo(std_::move(part)); + } return PacketResult::EndOfFile; } char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; @@ -335,17 +344,8 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( int64 packetMs = (packetPts * 1000LL * _fmtContext->streams[packet.stream_index]->time_base.num) / _fmtContext->streams[packet.stream_index]->time_base.den; _lastReadPacketMs = packetMs; - //AVPacket packetForQueue; - //av_init_packet(&packetForQueue); - //if ((res = av_packet_ref(&packetForQueue, &packet)) < 0) { - // char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - // LOG(("Gif Error: Unable to av_packet_ref() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - // return PacketResult::Error; - //} - if (videoPacket) { _packetQueue.enqueue(packet); - //_packetQueue.enqueue(packetForQueue); } else if (audioPacket) { // queue packet to audio player VideoSoundPart part; @@ -356,7 +356,6 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( } else { av_packet_unref(&packet); } - //av_packet_unref(&packet); return PacketResult::Ok; } diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 1f5a9d073..834da50ce 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -222,14 +222,14 @@ bool MediaView::gifShown() const { _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); const_cast(this)->_current = QPixmap(); } - return _gif->state() != Media::Clip::State::Error; + return true;// _gif->state() != Media::Clip::State::Error; } return false; } void MediaView::stopGif() { delete _gif; - _gif = 0; + _gif = nullptr; } void MediaView::documentUpdated(DocumentData *doc) { @@ -506,8 +506,7 @@ void MediaView::clearData() { _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); - delete _gif; - _gif = nullptr; + stopGif(); delete _menu; _menu = nullptr; _history = _migrated = nullptr; From cd36d367ed25474be893907d502af6f5ce32f3ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 8 Jul 2016 16:56:53 +0300 Subject: [PATCH 09/60] Version raised to 0.9.58. Version field of documents supported. --- Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/app.cpp | 35 +- Telegram/SourceFiles/app.h | 2 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 2 +- Telegram/SourceFiles/core/version.h | 4 +- .../inline_bots/inline_bot_result.cpp | 2 +- Telegram/SourceFiles/localimageloader.cpp | 4 +- Telegram/SourceFiles/localstorage.cpp | 6 +- .../SourceFiles/mtproto/file_download.cpp | 22 +- Telegram/SourceFiles/mtproto/file_download.h | 19 +- Telegram/SourceFiles/mtproto/scheme.tl | 14 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 78 +++- Telegram/SourceFiles/mtproto/scheme_auto.h | 337 ++++++++++++++---- .../serialize/serialize_document.cpp | 24 +- .../serialize/serialize_document.h | 6 +- Telegram/SourceFiles/structs.cpp | 29 +- Telegram/SourceFiles/structs.h | 10 +- Telegram/SourceFiles/ui/images.h | 9 +- Telegram/Telegram.xcodeproj/project.pbxproj | 4 +- Telegram/build/version | 6 +- 21 files changed, 461 insertions(+), 168 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a04176115..3132ab97a 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,0 - PRODUCTVERSION 0,9,57,0 + FILEVERSION 0,9,58,0 + PRODUCTVERSION 0,9,58,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.57.0" + VALUE "FileVersion", "0.9.58.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.0" + VALUE "ProductVersion", "0.9.58.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index edc6944fb..69822ab7a 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,0 - PRODUCTVERSION 0,9,57,0 + FILEVERSION 0,9,58,0 + PRODUCTVERSION 0,9,58,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.57.0" + VALUE "FileVersion", "0.9.58.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.0" + VALUE "ProductVersion", "0.9.58.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 048dc3bab..888a7db71 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -29,6 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" +#include "inline_bots/inline_bot_layout_item.h" #include "audio.h" #include "application.h" #include "fileuploader.h" @@ -1454,7 +1455,7 @@ namespace { switch (document.type()) { case mtpc_document: { const auto &d(document.c_document()); - return App::documentSet(d.vid.v, 0, d.vaccess_hash.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v, StorageImageLocation()); + return App::documentSet(d.vid.v, 0, d.vaccess_hash.v, d.vversion.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v, StorageImageLocation()); } break; case mtpc_documentEmpty: return App::document(document.c_documentEmpty().vid.v); } @@ -1467,14 +1468,14 @@ namespace { return feedDocument(document.c_document(), convert); } break; case mtpc_documentEmpty: { - return App::documentSet(document.c_documentEmpty().vid.v, convert, 0, 0, QVector(), QString(), ImagePtr(), 0, 0, StorageImageLocation()); + return App::documentSet(document.c_documentEmpty().vid.v, convert, 0, 0, 0, QVector(), QString(), ImagePtr(), 0, 0, StorageImageLocation()); } break; } return App::document(0); } DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert) { - return App::documentSet(document.vid.v, convert, document.vaccess_hash.v, document.vdate.v, document.vattributes.c_vector().v, qs(document.vmime_type), App::image(document.vthumb), document.vdc_id.v, document.vsize.v, App::imageLocation(document.vthumb)); + return App::documentSet(document.vid.v, convert, document.vaccess_hash.v, document.vversion.v, document.vdate.v, document.vattributes.c_vector().v, qs(document.vmime_type), App::image(document.vthumb), document.vdc_id.v, document.vsize.v, App::imageLocation(document.vthumb)); } WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert) { @@ -1631,11 +1632,13 @@ namespace { return i.value(); } - DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) { + DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) { + bool versionChanged = false; bool sentSticker = false; if (convert) { MediaKey oldKey = convert->mediaKey(); - if (convert->id != document) { + bool idChanged = (convert->id != document); + if (idChanged) { DocumentsData::iterator i = ::documentsData.find(convert->id); if (i != ::documentsData.cend() && i.value() == convert) { ::documentsData.erase(i); @@ -1647,10 +1650,11 @@ namespace { } if (date) { convert->setattributes(attributes); + versionChanged = convert->setRemoteVersion(version); convert->setRemoteLocation(dc, access); convert->date = date; convert->mime = mime; - if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { + if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height() || versionChanged)) { updateImage(convert->thumb, thumb); } convert->size = size; @@ -1660,7 +1664,7 @@ namespace { } MediaKey newKey = convert->mediaKey(); - if (newKey != oldKey) { + if (idChanged) { if (convert->voice()) { Local::copyAudio(oldKey, newKey); } else if (convert->sticker() || convert->isAnimation()) { @@ -1679,7 +1683,7 @@ namespace { if (convert) { result = convert; } else { - result = DocumentData::create(document, dc, access, attributes); + result = DocumentData::create(document, dc, access, version, attributes); result->date = date; result->mime = mime; result->thumb = thumb; @@ -1694,12 +1698,13 @@ namespace { result = i.value(); if (result != convert && date) { result->setattributes(attributes); + versionChanged = result->setRemoteVersion(version); if (!result->isValid()) { result->setRemoteLocation(dc, access); } result->date = date; result->mime = mime; - if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height())) { + if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height() || versionChanged)) { result->thumb = thumb; } result->size = size; @@ -1712,6 +1717,18 @@ namespace { if (sentSticker && App::main()) { App::main()->incrementSticker(result); } + if (versionChanged) { + if (result->sticker()) { + Local::writeStickers(); + } + auto &items = App::documentItems(); + auto i = items.constFind(result); + if (i != items.cend()) { + for (auto j = i->cbegin(), e = i->cend(); j != e; ++j) { + j.key()->setPendingInitDimensions(); + } + } + } return result; } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 9bdb14fdf..0c12a7615 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -149,7 +149,7 @@ namespace App { PhotoData *photo(const PhotoId &photo); PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full); DocumentData *document(const DocumentId &document); - DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); + DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); WebPageData *webPage(const WebPageId &webPage); WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill); LocationData *location(const LocationCoords &coords); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 6d838b0ce..78837cc1e 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -530,7 +530,7 @@ void StickersInner::paintRow(Painter &p, int32 index) { int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); - App::roundRect(p, add, st::defaultActiveButton.textBgOver); + App::roundRect(p, add, st::defaultActiveButton.textBgOver, ImageRoundRadius::Small); p.setFont(st::defaultActiveButton.font); p.setPen(st::defaultActiveButton.textFg); p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + st::defaultActiveButton.textTop, width(), _addText, _addWidth); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 7e443e781..795b8bef4 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9057; -constexpr str_const AppVersionStr = "0.9.57"; +constexpr int AppVersion = 9058; +constexpr str_const AppVersionStr = "0.9.58"; constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 29a4909c7..d3afc27b4 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -352,7 +352,7 @@ void Result::createDocument() { attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(_duration), MTPstring(), MTPstring(), MTPbytes())); } - MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_vector(attributes)); + MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_int(0), MTP_vector(attributes)); _document = App::feedDocument(document); _document->setContentUrl(_content_url); diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index f484ccb9b..3d3e162ce 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -413,9 +413,9 @@ void FileLoadTask::process() { if (voice) { attributes[0] = MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(_waveform))); attributes.resize(1); - document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector(attributes)); + document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector(attributes)); } else { - document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector(attributes)); + document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector(attributes)); if (photo.type() == mtpc_photoEmpty) { _type = PrepareDocument; } diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index aeec35846..4b2e792c2 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3145,7 +3145,7 @@ namespace Local { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } - DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); + DocumentData *doc = App::documentSet(id, 0, access, 0, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); if (!doc->sticker()) continue; if (value > 0) { @@ -3247,7 +3247,7 @@ namespace Local { Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); OrderedSet read; for (int32 j = 0; j < scnt; ++j) { - auto document = Serialize::Document::readStickerFromStream(stickers.stream, info); + auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); if (!document || !document->sticker()) continue; if (read.contains(document->id)) continue; @@ -3407,7 +3407,7 @@ namespace Local { saved.reserve(cnt); OrderedSet read; for (uint32 i = 0; i < cnt; ++i) { - DocumentData *document = Serialize::Document::readFromStream(gifs.stream); + DocumentData *document = Serialize::Document::readFromStream(gifs.version, gifs.stream); if (!document || !document->isAnimation()) continue; if (read.contains(document->id)) continue; diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index ab8a25af5..f8a5c0018 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -350,13 +350,8 @@ void FileLoader::startLoading(bool loadFirst, bool prior) { mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) -, _lastComplete(false) -, _skippedBytes(0) -, _nextRequestOffset(0) , _dc(location->dc()) -, _location(location) -, _id(0) -, _access(0) { +, _location(location) { LoaderQueues::iterator i = queues.find(MTP::dldDcId(_dc, 0)); if (i == queues.cend()) { i = queues.insert(MTP::dldDcId(_dc, 0), FileLoaderQueue(MaxFileQueries)); @@ -364,15 +359,12 @@ mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, L _queue = &i.value(); } -mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) +mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, int32 version, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) : FileLoader(to, size, type, toCache, fromCloud, autoLoading) -, _lastComplete(false) -, _skippedBytes(0) -, _nextRequestOffset(0) , _dc(dc) -, _location(0) , _id(id) -, _access(access) { +, _access(access) +, _version(version) { LoaderQueues::iterator i = queues.find(MTP::dldDcId(_dc, 0)); if (i == queues.cend()) { i = queues.insert(MTP::dldDcId(_dc, 0), FileLoaderQueue(MaxFileQueries)); @@ -421,7 +413,7 @@ bool mtpFileLoader::loadPart() { switch (_locationType) { case VideoFileLocation: case AudioFileLocation: - case DocumentFileLocation: loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_access)); break; + case DocumentFileLocation: loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_access), MTP_int(_version)); break; default: cancel(true); return false; break; } } @@ -531,7 +523,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { if (_locationType != UnknownFileLocation) { // audio, video, document - MediaKey mkey = mediaKey(_locationType, _dc, _id); + MediaKey mkey = mediaKey(_locationType, _dc, _id, _version); if (!_fname.isEmpty()) { Local::writeFileLocation(mkey, FileLocation(mtpToStorageType(_type), _fname)); } @@ -593,7 +585,7 @@ bool mtpFileLoader::tryLoadLocal() { _localTaskId = Local::startImageLoad(storageKey(*_location), this); } else { if (_toCache == LoadToCacheAsWell) { - MediaKey mkey = mediaKey(_locationType, _dc, _id); + MediaKey mkey = mediaKey(_locationType, _dc, _id, _version); if (_locationType == DocumentFileLocation) { _localTaskId = Local::startStickerImageLoad(mkey, this); } else if (_locationType == AudioFileLocation) { diff --git a/Telegram/SourceFiles/mtproto/file_download.h b/Telegram/SourceFiles/mtproto/file_download.h index f8a05d7ea..94b65fb2b 100644 --- a/Telegram/SourceFiles/mtproto/file_download.h +++ b/Telegram/SourceFiles/mtproto/file_download.h @@ -28,6 +28,8 @@ namespace MTP { enum LocationType { UnknownFileLocation = 0, + // 1, 2, etc are used as "version" value in mediaKey() method. + DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation @@ -221,9 +223,8 @@ class mtpFileLoader : public FileLoader, public RPCSender { Q_OBJECT public: - mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading); - mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &toFile, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading); + mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, int32 version, LocationType type, const QString &toFile, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading); virtual int32 currentOffset(bool includeSkipped = false) const; @@ -245,7 +246,6 @@ public: ~mtpFileLoader(); protected: - virtual bool tryLoadLocal(); virtual void cancelRequests(); @@ -256,15 +256,16 @@ protected: void partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req); bool partFailed(const RPCError &error); - bool _lastComplete; - int32 _skippedBytes; - int32 _nextRequestOffset; + bool _lastComplete = false; + int32 _skippedBytes = 0; + int32 _nextRequestOffset = 0; int32 _dc; - const StorageImageLocation *_location; + const StorageImageLocation *_location = nullptr; - uint64 _id; // for other locations - uint64 _access; + uint64 _id = 0; // for other locations + uint64 _access = 0; + int32 _version = 0; }; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index aaa886675..d1179d198 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -168,7 +168,7 @@ inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto; inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation; inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; -inputDocumentFileLocation#4e45abe9 id:long access_hash:long = InputFileLocation; +inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; inputPhotoCropAuto#ade6b004 = InputPhotoCrop; inputPhotoCrop#d9915325 crop_left:double crop_top:double crop_width:double = InputPhotoCrop; @@ -393,6 +393,7 @@ updateInlineBotCallbackQuery#2cbd95af query_id:long user_id:int msg_id:InputBotI updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; +updateRecentStickers#9a422c20 = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -455,7 +456,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#18798952 id:long access_hash:long = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#f9a39f4f id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector = Document; +document#87232bc7 id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int version:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -507,7 +508,6 @@ documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = Docume documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; -documentAttributeVersion#99cd09ab version:int = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#8a8ecd32 hash:string stickers:Vector = messages.Stickers; @@ -549,7 +549,7 @@ chatInviteEmpty#69df3769 = ExportedChatInvite; chatInviteExported#fc2e05bc link:string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#93e99b60 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string = ChatInvite; +chatInvite#2d492881 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:ChatPhoto participants:flags.4?Vector = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; @@ -701,6 +701,9 @@ draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?in messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; messages.featuredStickers#ed6392b7 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.recentStickersNotModified#b17f890 = messages.RecentStickers; +messages.recentStickers#5ce20970 hash:int stickers:Vector = messages.RecentStickers; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -837,6 +840,9 @@ messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flag messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; messages.readFeaturedStickers#e21cbb = Bool; +messages.getRecentStickers#99197c2c hash:int = messages.RecentStickers; +messages.clearRecentStickers#ab02e5d2 = Bool; +messages.getUnusedStickers#a978d356 limit:int = Vector; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 9b4b655c1..5669a5323 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -828,6 +828,7 @@ void _serialize_inputDocumentFileLocation(MTPStringLogger &to, int32 stage, int3 switch (stage) { case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -3016,6 +3017,10 @@ void _serialize_updateReadFeaturedStickers(MTPStringLogger &to, int32 stage, int to.add("{ updateReadFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_updateRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ updateRecentStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_updates_state(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -3676,7 +3681,8 @@ void _serialize_document(MTPStringLogger &to, int32 stage, int32 lev, Types &typ case 4: to.add(" size: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 5: to.add(" thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 6: to.add(" dc_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 7: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 7: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 8: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -4018,19 +4024,6 @@ void _serialize_documentAttributeFilename(MTPStringLogger &to, int32 stage, int3 } } -void _serialize_documentAttributeVersion(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ documentAttributeVersion"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_messages_stickersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ messages_stickersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -4356,6 +4349,8 @@ void _serialize_chatInvite(MTPStringLogger &to, int32 stage, int32 lev, Types &t case 3: to.add(" public: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_public) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; case 4: to.add(" megagroup: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_megagroup) { to.add("YES [ BY BIT 3 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; case 5: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 7: to.add(" participants: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_participants) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -5783,6 +5778,24 @@ void _serialize_messages_featuredStickers(MTPStringLogger &to, int32 stage, int3 } } +void _serialize_messages_recentStickersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ messages_recentStickersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_messages_recentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_recentStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" stickers: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6405,6 +6418,10 @@ void _serialize_messages_readFeaturedStickers(MTPStringLogger &to, int32 stage, to.add("{ messages_readFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_messages_clearRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ messages_clearRecentStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_upload_saveFilePart(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8084,6 +8101,32 @@ void _serialize_messages_getFeaturedStickers(MTPStringLogger &to, int32 stage, i } } +void _serialize_messages_getRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getRecentStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_getUnusedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getUnusedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -8542,6 +8585,7 @@ namespace { _serializers.insert(mtpc_updateReadChannelOutbox, _serialize_updateReadChannelOutbox); _serializers.insert(mtpc_updateDraftMessage, _serialize_updateDraftMessage); _serializers.insert(mtpc_updateReadFeaturedStickers, _serialize_updateReadFeaturedStickers); + _serializers.insert(mtpc_updateRecentStickers, _serialize_updateRecentStickers); _serializers.insert(mtpc_updates_state, _serialize_updates_state); _serializers.insert(mtpc_updates_differenceEmpty, _serialize_updates_differenceEmpty); _serializers.insert(mtpc_updates_difference, _serialize_updates_difference); @@ -8625,7 +8669,6 @@ namespace { _serializers.insert(mtpc_documentAttributeVideo, _serialize_documentAttributeVideo); _serializers.insert(mtpc_documentAttributeAudio, _serialize_documentAttributeAudio); _serializers.insert(mtpc_documentAttributeFilename, _serialize_documentAttributeFilename); - _serializers.insert(mtpc_documentAttributeVersion, _serialize_documentAttributeVersion); _serializers.insert(mtpc_messages_stickersNotModified, _serialize_messages_stickersNotModified); _serializers.insert(mtpc_messages_stickers, _serialize_messages_stickers); _serializers.insert(mtpc_stickerPack, _serialize_stickerPack); @@ -8758,6 +8801,8 @@ namespace { _serializers.insert(mtpc_draftMessage, _serialize_draftMessage); _serializers.insert(mtpc_messages_featuredStickersNotModified, _serialize_messages_featuredStickersNotModified); _serializers.insert(mtpc_messages_featuredStickers, _serialize_messages_featuredStickers); + _serializers.insert(mtpc_messages_recentStickersNotModified, _serialize_messages_recentStickersNotModified); + _serializers.insert(mtpc_messages_recentStickers, _serialize_messages_recentStickers); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -8805,6 +8850,7 @@ namespace { _serializers.insert(mtpc_messages_setBotCallbackAnswer, _serialize_messages_setBotCallbackAnswer); _serializers.insert(mtpc_messages_saveDraft, _serialize_messages_saveDraft); _serializers.insert(mtpc_messages_readFeaturedStickers, _serialize_messages_readFeaturedStickers); + _serializers.insert(mtpc_messages_clearRecentStickers, _serialize_messages_clearRecentStickers); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); _serializers.insert(mtpc_help_saveAppLog, _serialize_help_saveAppLog); @@ -8924,6 +8970,8 @@ namespace { _serializers.insert(mtpc_messages_getBotCallbackAnswer, _serialize_messages_getBotCallbackAnswer); _serializers.insert(mtpc_messages_getPeerDialogs, _serialize_messages_getPeerDialogs); _serializers.insert(mtpc_messages_getFeaturedStickers, _serialize_messages_getFeaturedStickers); + _serializers.insert(mtpc_messages_getRecentStickers, _serialize_messages_getRecentStickers); + _serializers.insert(mtpc_messages_getUnusedStickers, _serialize_messages_getUnusedStickers); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 0c0d69db2..e0e2cb897 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -112,7 +112,7 @@ enum { mtpc_inputPhoto = 0xfb95c6c4, mtpc_inputFileLocation = 0x14637196, mtpc_inputEncryptedFileLocation = 0xf5235d55, - mtpc_inputDocumentFileLocation = 0x4e45abe9, + mtpc_inputDocumentFileLocation = 0x430f0724, mtpc_inputPhotoCropAuto = 0xade6b004, mtpc_inputPhotoCrop = 0xd9915325, mtpc_inputAppEvent = 0x770656a8, @@ -289,6 +289,7 @@ enum { mtpc_updateReadChannelOutbox = 0x25d6c9c7, mtpc_updateDraftMessage = 0xee2bb969, mtpc_updateReadFeaturedStickers = 0x571d2742, + mtpc_updateRecentStickers = 0x9a422c20, mtpc_updates_state = 0xa56c2a3e, mtpc_updates_differenceEmpty = 0x5d75a138, mtpc_updates_difference = 0xf49ca0, @@ -331,7 +332,7 @@ enum { mtpc_inputDocumentEmpty = 0x72f0eaae, mtpc_inputDocument = 0x18798952, mtpc_documentEmpty = 0x36f8c871, - mtpc_document = 0xf9a39f4f, + mtpc_document = 0x87232bc7, mtpc_help_support = 0x17c6b5f6, mtpc_notifyPeer = 0x9fd40bd8, mtpc_notifyUsers = 0xb4c83b4c, @@ -372,7 +373,6 @@ enum { mtpc_documentAttributeVideo = 0x5910cccb, mtpc_documentAttributeAudio = 0x9852f9c6, mtpc_documentAttributeFilename = 0x15590068, - mtpc_documentAttributeVersion = 0x99cd09ab, mtpc_messages_stickersNotModified = 0xf1749a22, mtpc_messages_stickers = 0x8a8ecd32, mtpc_stickerPack = 0x12b299d4, @@ -398,7 +398,7 @@ enum { mtpc_chatInviteEmpty = 0x69df3769, mtpc_chatInviteExported = 0xfc2e05bc, mtpc_chatInviteAlready = 0x5a686d7c, - mtpc_chatInvite = 0x93e99b60, + mtpc_chatInvite = 0x2d492881, mtpc_inputStickerSetEmpty = 0xffb62b95, mtpc_inputStickerSetID = 0x9de7a269, mtpc_inputStickerSetShortName = 0x861cc8a0, @@ -505,6 +505,8 @@ enum { mtpc_draftMessage = 0xfd8e711f, mtpc_messages_featuredStickersNotModified = 0x4ede3cf, mtpc_messages_featuredStickers = 0xed6392b7, + mtpc_messages_recentStickersNotModified = 0xb17f890, + mtpc_messages_recentStickers = 0x5ce20970, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -634,6 +636,9 @@ enum { mtpc_messages_getAllDrafts = 0x6a3f8d65, mtpc_messages_getFeaturedStickers = 0x2dacca4f, mtpc_messages_readFeaturedStickers = 0xe21cbb, + mtpc_messages_getRecentStickers = 0x99197c2c, + mtpc_messages_clearRecentStickers = 0xab02e5d2, + mtpc_messages_getUnusedStickers = 0xa978d356, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -1139,7 +1144,6 @@ class MTPDdocumentAttributeSticker; class MTPDdocumentAttributeVideo; class MTPDdocumentAttributeAudio; class MTPDdocumentAttributeFilename; -class MTPDdocumentAttributeVersion; class MTPmessages_stickers; class MTPDmessages_stickers; @@ -1363,6 +1367,9 @@ class MTPDdraftMessage; class MTPmessages_featuredStickers; class MTPDmessages_featuredStickers; +class MTPmessages_recentStickers; +class MTPDmessages_recentStickers; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1538,6 +1545,7 @@ typedef MTPBoxed MTPTopPeerCategoryPeers; typedef MTPBoxed MTPcontacts_TopPeers; typedef MTPBoxed MTPDraftMessage; typedef MTPBoxed MTPmessages_FeaturedStickers; +typedef MTPBoxed MTPmessages_RecentStickers; // Type classes definitions @@ -6973,18 +6981,6 @@ public: return *(const MTPDdocumentAttributeFilename*)data; } - MTPDdocumentAttributeVersion &_documentAttributeVersion() { - if (!data) throw mtpErrorUninitialized(); - if (_type != mtpc_documentAttributeVersion) throw mtpErrorWrongTypeId(_type, mtpc_documentAttributeVersion); - split(); - return *(MTPDdocumentAttributeVersion*)data; - } - const MTPDdocumentAttributeVersion &c_documentAttributeVersion() const { - if (!data) throw mtpErrorUninitialized(); - if (_type != mtpc_documentAttributeVersion) throw mtpErrorWrongTypeId(_type, mtpc_documentAttributeVersion); - return *(const MTPDdocumentAttributeVersion*)data; - } - uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -6999,7 +6995,6 @@ private: explicit MTPdocumentAttribute(MTPDdocumentAttributeVideo *_data); explicit MTPdocumentAttribute(MTPDdocumentAttributeAudio *_data); explicit MTPdocumentAttribute(MTPDdocumentAttributeFilename *_data); - explicit MTPdocumentAttribute(MTPDdocumentAttributeVersion *_data); friend class MTP::internal::TypeCreator; @@ -9593,6 +9588,43 @@ private: }; typedef MTPBoxed MTPmessages_FeaturedStickers; +class MTPmessages_recentStickers : private mtpDataOwner { +public: + MTPmessages_recentStickers() : mtpDataOwner(0), _type(0) { + } + MTPmessages_recentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDmessages_recentStickers &_messages_recentStickers() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_recentStickers) throw mtpErrorWrongTypeId(_type, mtpc_messages_recentStickers); + split(); + return *(MTPDmessages_recentStickers*)data; + } + const MTPDmessages_recentStickers &c_messages_recentStickers() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_recentStickers) throw mtpErrorWrongTypeId(_type, mtpc_messages_recentStickers); + return *(const MTPDmessages_recentStickers*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_recentStickers(mtpTypeId type); + explicit MTPmessages_recentStickers(MTPDmessages_recentStickers *_data); + + friend class MTP::internal::TypeCreator; + + mtpTypeId _type; +}; +typedef MTPBoxed MTPmessages_RecentStickers; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -10185,11 +10217,12 @@ class MTPDinputDocumentFileLocation : public mtpDataImpl { @@ -12612,7 +12645,7 @@ class MTPDdocument : public mtpDataImpl { public: MTPDdocument() { } - MTPDdocument(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, const MTPVector &_attributes) : vid(_id), vaccess_hash(_access_hash), vdate(_date), vmime_type(_mime_type), vsize(_size), vthumb(_thumb), vdc_id(_dc_id), vattributes(_attributes) { + MTPDdocument(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _version, const MTPVector &_attributes) : vid(_id), vaccess_hash(_access_hash), vdate(_date), vmime_type(_mime_type), vsize(_size), vthumb(_thumb), vdc_id(_dc_id), vversion(_version), vattributes(_attributes) { } MTPlong vid; @@ -12622,6 +12655,7 @@ public: MTPint vsize; MTPPhotoSize vthumb; MTPint vdc_id; + MTPint vversion; MTPVector vattributes; }; @@ -12833,16 +12867,6 @@ public: MTPstring vfile_name; }; -class MTPDdocumentAttributeVersion : public mtpDataImpl { -public: - MTPDdocumentAttributeVersion() { - } - MTPDdocumentAttributeVersion(MTPint _version) : vversion(_version) { - } - - MTPint vversion; -}; - class MTPDmessages_stickers : public mtpDataImpl { public: MTPDmessages_stickers() { @@ -13121,8 +13145,9 @@ public: f_broadcast = (1 << 1), f_public = (1 << 2), f_megagroup = (1 << 3), + f_participants = (1 << 4), - MAX_FIELD = (1 << 3), + MAX_FIELD = (1 << 4), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } @@ -13131,14 +13156,17 @@ public: bool is_broadcast() const { return vflags.v & Flag::f_broadcast; } bool is_public() const { return vflags.v & Flag::f_public; } bool is_megagroup() const { return vflags.v & Flag::f_megagroup; } + bool has_participants() const { return vflags.v & Flag::f_participants; } MTPDchatInvite() { } - MTPDchatInvite(const MTPflags &_flags, const MTPstring &_title) : vflags(_flags), vtitle(_title) { + MTPDchatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) : vflags(_flags), vtitle(_title), vphoto(_photo), vparticipants(_participants) { } MTPflags vflags; MTPstring vtitle; + MTPChatPhoto vphoto; + MTPVector vparticipants; }; class MTPDinputStickerSetID : public mtpDataImpl { @@ -14500,6 +14528,17 @@ public: MTPVector vunread; }; +class MTPDmessages_recentStickers : public mtpDataImpl { +public: + MTPDmessages_recentStickers() { + } + MTPDmessages_recentStickers(MTPint _hash, const MTPVector &_stickers) : vhash(_hash), vstickers(_stickers) { + } + + MTPint vhash; + MTPVector vstickers; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -20561,6 +20600,115 @@ public: } }; +class MTPmessages_getRecentStickers { // RPC method 'messages.getRecentStickers' +public: + MTPint vhash; + + MTPmessages_getRecentStickers() { + } + MTPmessages_getRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getRecentStickers) { + read(from, end, cons); + } + MTPmessages_getRecentStickers(MTPint _hash) : vhash(_hash) { + } + + uint32 innerLength() const { + return vhash.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getRecentStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getRecentStickers) { + vhash.read(from, end); + } + void write(mtpBuffer &to) const { + vhash.write(to); + } + + typedef MTPmessages_RecentStickers ResponseType; +}; +class MTPmessages_GetRecentStickers : public MTPBoxed { +public: + MTPmessages_GetRecentStickers() { + } + MTPmessages_GetRecentStickers(const MTPmessages_getRecentStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetRecentStickers(MTPint _hash) : MTPBoxed(MTPmessages_getRecentStickers(_hash)) { + } +}; + +class MTPmessages_clearRecentStickers { // RPC method 'messages.clearRecentStickers' +public: + MTPmessages_clearRecentStickers() { + } + MTPmessages_clearRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_clearRecentStickers) { + read(from, end, cons); + } + + uint32 innerLength() const { + return 0; + } + mtpTypeId type() const { + return mtpc_messages_clearRecentStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_clearRecentStickers) { + } + void write(mtpBuffer &to) const { + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_ClearRecentStickers : public MTPBoxed { +public: + MTPmessages_ClearRecentStickers() { + } + MTPmessages_ClearRecentStickers(const MTPmessages_clearRecentStickers &v) : MTPBoxed(v) { + } + MTPmessages_ClearRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } +}; + +class MTPmessages_getUnusedStickers { // RPC method 'messages.getUnusedStickers' +public: + MTPint vlimit; + + MTPmessages_getUnusedStickers() { + } + MTPmessages_getUnusedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getUnusedStickers) { + read(from, end, cons); + } + MTPmessages_getUnusedStickers(MTPint _limit) : vlimit(_limit) { + } + + uint32 innerLength() const { + return vlimit.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getUnusedStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getUnusedStickers) { + vlimit.read(from, end); + } + void write(mtpBuffer &to) const { + vlimit.write(to); + } + + typedef MTPVector ResponseType; +}; +class MTPmessages_GetUnusedStickers : public MTPBoxed { +public: + MTPmessages_GetUnusedStickers() { + } + MTPmessages_GetUnusedStickers(const MTPmessages_getUnusedStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetUnusedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetUnusedStickers(MTPint _limit) : MTPBoxed(MTPmessages_getUnusedStickers(_limit)) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -22574,8 +22722,8 @@ public: inline static MTPinputFileLocation new_inputEncryptedFileLocation(const MTPlong &_id, const MTPlong &_access_hash) { return MTPinputFileLocation(new MTPDinputEncryptedFileLocation(_id, _access_hash)); } - inline static MTPinputFileLocation new_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash) { - return MTPinputFileLocation(new MTPDinputDocumentFileLocation(_id, _access_hash)); + inline static MTPinputFileLocation new_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash, MTPint _version) { + return MTPinputFileLocation(new MTPDinputDocumentFileLocation(_id, _access_hash, _version)); } inline static MTPinputPhotoCrop new_inputPhotoCropAuto() { return MTPinputPhotoCrop(mtpc_inputPhotoCropAuto); @@ -23105,6 +23253,9 @@ public: inline static MTPupdate new_updateReadFeaturedStickers() { return MTPupdate(mtpc_updateReadFeaturedStickers); } + inline static MTPupdate new_updateRecentStickers() { + return MTPupdate(mtpc_updateRecentStickers); + } inline static MTPupdates_state new_updates_state(MTPint _pts, MTPint _qts, MTPint _date, MTPint _seq, MTPint _unread_count) { return MTPupdates_state(new MTPDupdates_state(_pts, _qts, _date, _seq, _unread_count)); } @@ -23231,8 +23382,8 @@ public: inline static MTPdocument new_documentEmpty(const MTPlong &_id) { return MTPdocument(new MTPDdocumentEmpty(_id)); } - inline static MTPdocument new_document(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, const MTPVector &_attributes) { - return MTPdocument(new MTPDdocument(_id, _access_hash, _date, _mime_type, _size, _thumb, _dc_id, _attributes)); + inline static MTPdocument new_document(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _version, const MTPVector &_attributes) { + return MTPdocument(new MTPDdocument(_id, _access_hash, _date, _mime_type, _size, _thumb, _dc_id, _version, _attributes)); } inline static MTPhelp_support new_help_support(const MTPstring &_phone_number, const MTPUser &_user) { return MTPhelp_support(new MTPDhelp_support(_phone_number, _user)); @@ -23354,9 +23505,6 @@ public: inline static MTPdocumentAttribute new_documentAttributeFilename(const MTPstring &_file_name) { return MTPdocumentAttribute(new MTPDdocumentAttributeFilename(_file_name)); } - inline static MTPdocumentAttribute new_documentAttributeVersion(MTPint _version) { - return MTPdocumentAttribute(new MTPDdocumentAttributeVersion(_version)); - } inline static MTPmessages_stickers new_messages_stickersNotModified() { return MTPmessages_stickers(mtpc_messages_stickersNotModified); } @@ -23432,8 +23580,8 @@ public: inline static MTPchatInvite new_chatInviteAlready(const MTPChat &_chat) { return MTPchatInvite(new MTPDchatInviteAlready(_chat)); } - inline static MTPchatInvite new_chatInvite(const MTPflags &_flags, const MTPstring &_title) { - return MTPchatInvite(new MTPDchatInvite(_flags, _title)); + inline static MTPchatInvite new_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) { + return MTPchatInvite(new MTPDchatInvite(_flags, _title, _photo, _participants)); } inline static MTPinputStickerSet new_inputStickerSetEmpty() { return MTPinputStickerSet(mtpc_inputStickerSetEmpty); @@ -23753,6 +23901,12 @@ public: inline static MTPmessages_featuredStickers new_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { return MTPmessages_featuredStickers(new MTPDmessages_featuredStickers(_hash, _sets, _unread)); } + inline static MTPmessages_recentStickers new_messages_recentStickersNotModified() { + return MTPmessages_recentStickers(mtpc_messages_recentStickersNotModified); + } + inline static MTPmessages_recentStickers new_messages_recentStickers(MTPint _hash, const MTPVector &_stickers) { + return MTPmessages_recentStickers(new MTPDmessages_recentStickers(_hash, _stickers)); + } }; } // namespace internal @@ -25419,7 +25573,7 @@ inline uint32 MTPinputFileLocation::innerLength() const { } case mtpc_inputDocumentFileLocation: { const MTPDinputDocumentFileLocation &v(c_inputDocumentFileLocation()); - return v.vid.innerLength() + v.vaccess_hash.innerLength(); + return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vversion.innerLength(); } } return 0; @@ -25449,6 +25603,7 @@ inline void MTPinputFileLocation::read(const mtpPrime *&from, const mtpPrime *en MTPDinputDocumentFileLocation &v(_inputDocumentFileLocation()); v.vid.read(from, end); v.vaccess_hash.read(from, end); + v.vversion.read(from, end); } break; default: throw mtpErrorUnexpected(cons, "MTPinputFileLocation"); } @@ -25470,6 +25625,7 @@ inline void MTPinputFileLocation::write(mtpBuffer &to) const { const MTPDinputDocumentFileLocation &v(c_inputDocumentFileLocation()); v.vid.write(to); v.vaccess_hash.write(to); + v.vversion.write(to); } break; } } @@ -25493,8 +25649,8 @@ inline MTPinputFileLocation MTP_inputFileLocation(const MTPlong &_volume_id, MTP inline MTPinputFileLocation MTP_inputEncryptedFileLocation(const MTPlong &_id, const MTPlong &_access_hash) { return MTP::internal::TypeCreator::new_inputEncryptedFileLocation(_id, _access_hash); } -inline MTPinputFileLocation MTP_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash) { - return MTP::internal::TypeCreator::new_inputDocumentFileLocation(_id, _access_hash); +inline MTPinputFileLocation MTP_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash, MTPint _version) { + return MTP::internal::TypeCreator::new_inputDocumentFileLocation(_id, _access_hash, _version); } inline uint32 MTPinputPhotoCrop::innerLength() const { @@ -28971,6 +29127,7 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vdraft.read(from, end); } break; case mtpc_updateReadFeaturedStickers: _type = cons; break; + case mtpc_updateRecentStickers: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPupdate"); } } @@ -29305,6 +29462,7 @@ inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) { case mtpc_updateReadChannelOutbox: setData(new MTPDupdateReadChannelOutbox()); break; case mtpc_updateDraftMessage: setData(new MTPDupdateDraftMessage()); break; case mtpc_updateReadFeaturedStickers: break; + case mtpc_updateRecentStickers: break; default: throw mtpErrorBadTypeId(type, "MTPupdate"); } } @@ -29555,6 +29713,9 @@ inline MTPupdate MTP_updateDraftMessage(const MTPPeer &_peer, const MTPDraftMess inline MTPupdate MTP_updateReadFeaturedStickers() { return MTP::internal::TypeCreator::new_updateReadFeaturedStickers(); } +inline MTPupdate MTP_updateRecentStickers() { + return MTP::internal::TypeCreator::new_updateRecentStickers(); +} inline MTPupdates_state::MTPupdates_state() : mtpDataOwner(new MTPDupdates_state()) { } @@ -30864,7 +31025,7 @@ inline uint32 MTPdocument::innerLength() const { } case mtpc_document: { const MTPDdocument &v(c_document()); - return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vmime_type.innerLength() + v.vsize.innerLength() + v.vthumb.innerLength() + v.vdc_id.innerLength() + v.vattributes.innerLength(); + return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vmime_type.innerLength() + v.vsize.innerLength() + v.vthumb.innerLength() + v.vdc_id.innerLength() + v.vversion.innerLength() + v.vattributes.innerLength(); } } return 0; @@ -30891,6 +31052,7 @@ inline void MTPdocument::read(const mtpPrime *&from, const mtpPrime *end, mtpTyp v.vsize.read(from, end); v.vthumb.read(from, end); v.vdc_id.read(from, end); + v.vversion.read(from, end); v.vattributes.read(from, end); } break; default: throw mtpErrorUnexpected(cons, "MTPdocument"); @@ -30911,6 +31073,7 @@ inline void MTPdocument::write(mtpBuffer &to) const { v.vsize.write(to); v.vthumb.write(to); v.vdc_id.write(to); + v.vversion.write(to); v.vattributes.write(to); } break; } @@ -30929,8 +31092,8 @@ inline MTPdocument::MTPdocument(MTPDdocument *_data) : mtpDataOwner(_data), _typ inline MTPdocument MTP_documentEmpty(const MTPlong &_id) { return MTP::internal::TypeCreator::new_documentEmpty(_id); } -inline MTPdocument MTP_document(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, const MTPVector &_attributes) { - return MTP::internal::TypeCreator::new_document(_id, _access_hash, _date, _mime_type, _size, _thumb, _dc_id, _attributes); +inline MTPdocument MTP_document(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _version, const MTPVector &_attributes) { + return MTP::internal::TypeCreator::new_document(_id, _access_hash, _date, _mime_type, _size, _thumb, _dc_id, _version, _attributes); } inline MTPhelp_support::MTPhelp_support() : mtpDataOwner(new MTPDhelp_support()) { @@ -31487,10 +31650,6 @@ inline uint32 MTPdocumentAttribute::innerLength() const { const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename()); return v.vfile_name.innerLength(); } - case mtpc_documentAttributeVersion: { - const MTPDdocumentAttributeVersion &v(c_documentAttributeVersion()); - return v.vversion.innerLength(); - } } return 0; } @@ -31535,11 +31694,6 @@ inline void MTPdocumentAttribute::read(const mtpPrime *&from, const mtpPrime *en MTPDdocumentAttributeFilename &v(_documentAttributeFilename()); v.vfile_name.read(from, end); } break; - case mtpc_documentAttributeVersion: _type = cons; { - if (!data) setData(new MTPDdocumentAttributeVersion()); - MTPDdocumentAttributeVersion &v(_documentAttributeVersion()); - v.vversion.read(from, end); - } break; default: throw mtpErrorUnexpected(cons, "MTPdocumentAttribute"); } } @@ -31573,10 +31727,6 @@ inline void MTPdocumentAttribute::write(mtpBuffer &to) const { const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename()); v.vfile_name.write(to); } break; - case mtpc_documentAttributeVersion: { - const MTPDdocumentAttributeVersion &v(c_documentAttributeVersion()); - v.vversion.write(to); - } break; } } inline MTPdocumentAttribute::MTPdocumentAttribute(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -31587,7 +31737,6 @@ inline MTPdocumentAttribute::MTPdocumentAttribute(mtpTypeId type) : mtpDataOwner case mtpc_documentAttributeVideo: setData(new MTPDdocumentAttributeVideo()); break; case mtpc_documentAttributeAudio: setData(new MTPDdocumentAttributeAudio()); break; case mtpc_documentAttributeFilename: setData(new MTPDdocumentAttributeFilename()); break; - case mtpc_documentAttributeVersion: setData(new MTPDdocumentAttributeVersion()); break; default: throw mtpErrorBadTypeId(type, "MTPdocumentAttribute"); } } @@ -31601,8 +31750,6 @@ inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeAudio *_d } inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeFilename *_data) : mtpDataOwner(_data), _type(mtpc_documentAttributeFilename) { } -inline MTPdocumentAttribute::MTPdocumentAttribute(MTPDdocumentAttributeVersion *_data) : mtpDataOwner(_data), _type(mtpc_documentAttributeVersion) { -} inline MTPdocumentAttribute MTP_documentAttributeImageSize(MTPint _w, MTPint _h) { return MTP::internal::TypeCreator::new_documentAttributeImageSize(_w, _h); } @@ -31622,9 +31769,6 @@ inline MTPdocumentAttribute MTP_documentAttributeAudio(const MTPflags(); } } break; default: throw mtpErrorUnexpected(cons, "MTPchatInvite"); } @@ -32333,6 +32479,8 @@ inline void MTPchatInvite::write(mtpBuffer &to) const { const MTPDchatInvite &v(c_chatInvite()); v.vflags.write(to); v.vtitle.write(to); + v.vphoto.write(to); + if (v.has_participants()) v.vparticipants.write(to); } break; } } @@ -32351,8 +32499,8 @@ inline MTPchatInvite MTP_chatInviteAlready(const MTPChat &_chat) { return MTP::internal::TypeCreator::new_chatInviteAlready(_chat); } Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDchatInvite::Flags) -inline MTPchatInvite MTP_chatInvite(const MTPflags &_flags, const MTPstring &_title) { - return MTP::internal::TypeCreator::new_chatInvite(_flags, _title); +inline MTPchatInvite MTP_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) { + return MTP::internal::TypeCreator::new_chatInvite(_flags, _title, _photo, _participants); } inline uint32 MTPinputStickerSet::innerLength() const { @@ -35152,6 +35300,57 @@ inline MTPmessages_featuredStickers MTP_messages_featuredStickersNotModified() { inline MTPmessages_featuredStickers MTP_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { return MTP::internal::TypeCreator::new_messages_featuredStickers(_hash, _sets, _unread); } + +inline uint32 MTPmessages_recentStickers::innerLength() const { + switch (_type) { + case mtpc_messages_recentStickers: { + const MTPDmessages_recentStickers &v(c_messages_recentStickers()); + return v.vhash.innerLength() + v.vstickers.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPmessages_recentStickers::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPmessages_recentStickers::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_messages_recentStickersNotModified: _type = cons; break; + case mtpc_messages_recentStickers: _type = cons; { + if (!data) setData(new MTPDmessages_recentStickers()); + MTPDmessages_recentStickers &v(_messages_recentStickers()); + v.vhash.read(from, end); + v.vstickers.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPmessages_recentStickers"); + } +} +inline void MTPmessages_recentStickers::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_messages_recentStickers: { + const MTPDmessages_recentStickers &v(c_messages_recentStickers()); + v.vhash.write(to); + v.vstickers.write(to); + } break; + } +} +inline MTPmessages_recentStickers::MTPmessages_recentStickers(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_messages_recentStickersNotModified: break; + case mtpc_messages_recentStickers: setData(new MTPDmessages_recentStickers()); break; + default: throw mtpErrorBadTypeId(type, "MTPmessages_recentStickers"); + } +} +inline MTPmessages_recentStickers::MTPmessages_recentStickers(MTPDmessages_recentStickers *_data) : mtpDataOwner(_data), _type(mtpc_messages_recentStickers) { +} +inline MTPmessages_recentStickers MTP_messages_recentStickersNotModified() { + return MTP::internal::TypeCreator::new_messages_recentStickersNotModified(); +} +inline MTPmessages_recentStickers MTP_messages_recentStickers(MTPint _hash, const MTPVector &_stickers) { + return MTP::internal::TypeCreator::new_messages_recentStickers(_hash, _stickers); +} inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } inline MTPDmessage::Flags mtpCastFlags(MTPflags flags) { return mtpCastFlags(flags.v); } inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } diff --git a/Telegram/SourceFiles/serialize/serialize_document.cpp b/Telegram/SourceFiles/serialize/serialize_document.cpp index 73823edf5..6f489e690 100644 --- a/Telegram/SourceFiles/serialize/serialize_document.cpp +++ b/Telegram/SourceFiles/serialize/serialize_document.cpp @@ -37,6 +37,7 @@ namespace Serialize { void Document::writeToStream(QDataStream &stream, DocumentData *document) { stream << quint64(document->id) << quint64(document->_access) << qint32(document->date); + stream << qint32(document->_version); stream << document->name << document->mime << qint32(document->_dc) << qint32(document->size); stream << qint32(document->dimensions.width()) << qint32(document->dimensions.height()); stream << qint32(document->type); @@ -61,11 +62,16 @@ void Document::writeToStream(QDataStream &stream, DocumentData *document) { } } -DocumentData *Document::readFromStreamHelper(QDataStream &stream, const StickerSetInfo *info) { +DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info) { quint64 id, access; QString name, mime; - qint32 date, dc, size, width, height, type; + qint32 date, dc, size, width, height, type, version; stream >> id >> access >> date; + if (streamAppVersion >= 9058) { + stream >> version; + } else { + version = 0; + } stream >> name >> mime >> dc >> size; stream >> width >> height; stream >> type; @@ -122,22 +128,22 @@ DocumentData *Document::readFromStreamHelper(QDataStream &stream, const StickerS if (!dc && !access) { return nullptr; } - return App::documentSet(id, nullptr, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); + return App::documentSet(id, nullptr, access, version, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); } -DocumentData *Document::readStickerFromStream(QDataStream &stream, const StickerSetInfo &info) { - return readFromStreamHelper(stream, &info); +DocumentData *Document::readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info) { + return readFromStreamHelper(streamAppVersion, stream, &info); } -DocumentData *Document::readFromStream(QDataStream &stream) { - return readFromStreamHelper(stream, nullptr); +DocumentData *Document::readFromStream(int streamAppVersion, QDataStream &stream) { + return readFromStreamHelper(streamAppVersion, stream, nullptr); } int Document::sizeInStream(DocumentData *document) { int result = 0; - // id + access + date - result += sizeof(quint64) + sizeof(quint64) + sizeof(qint32); + // id + access + date + version + result += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32); // + namelen + name + mimelen + mime + dc + size result += stringSize(document->name) + stringSize(document->mime) + sizeof(qint32) + sizeof(qint32); // + width + height diff --git a/Telegram/SourceFiles/serialize/serialize_document.h b/Telegram/SourceFiles/serialize/serialize_document.h index b0ed77282..44ad8cd2f 100644 --- a/Telegram/SourceFiles/serialize/serialize_document.h +++ b/Telegram/SourceFiles/serialize/serialize_document.h @@ -39,12 +39,12 @@ public: }; static void writeToStream(QDataStream &stream, DocumentData *document); - static DocumentData *readStickerFromStream(QDataStream &stream, const StickerSetInfo &info); - static DocumentData *readFromStream(QDataStream &stream); + static DocumentData *readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info); + static DocumentData *readFromStream(int streamAppVersion, QDataStream &stream); static int sizeInStream(DocumentData *document); private: - static DocumentData *readFromStreamHelper(QDataStream &stream, const StickerSetInfo *info); + static DocumentData *readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info); }; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 662420bc3..cd57d76aa 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1082,10 +1082,11 @@ VoiceData::~VoiceData() { DocumentAdditionalData::~DocumentAdditionalData() { } -DocumentData::DocumentData(DocumentId id, int32 dc, uint64 accessHash, const QString &url, const QVector &attributes) +DocumentData::DocumentData(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QString &url, const QVector &attributes) : id(id) , _dc(dc) , _access(accessHash) +, _version(version) , _url(url) { setattributes(attributes); if (_dc && _access) { @@ -1094,15 +1095,15 @@ DocumentData::DocumentData(DocumentId id, int32 dc, uint64 accessHash, const QSt } DocumentData *DocumentData::create(DocumentId id) { - return new DocumentData(id, 0, 0, QString(), QVector()); + return new DocumentData(id, 0, 0, 0, QString(), QVector()); } -DocumentData *DocumentData::create(DocumentId id, int32 dc, uint64 accessHash, const QVector &attributes) { - return new DocumentData(id, dc, accessHash, QString(), attributes); +DocumentData *DocumentData::create(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QVector &attributes) { + return new DocumentData(id, dc, accessHash, version, QString(), attributes); } DocumentData *DocumentData::create(DocumentId id, const QString &url, const QVector &attributes) { - return new DocumentData(id, 0, 0, url, attributes); + return new DocumentData(id, 0, 0, 0, url, attributes); } void DocumentData::setattributes(const QVector &attributes) { @@ -1385,7 +1386,7 @@ void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMs if (!_access && !_url.isEmpty()) { _loader = new webFileLoader(_url, toFile, fromCloud, autoLoading); } else { - _loader = new mtpFileLoader(_dc, id, _access, locationType(), toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); + _loader = new mtpFileLoader(_dc, id, _access, _version, locationType(), toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); } _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*))); _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool))); @@ -1526,6 +1527,22 @@ void DocumentData::recountIsImage() { _duration = fileIsImage(name, mime) ? 1 : -1; // hack } +bool DocumentData::setRemoteVersion(int32 version) { + if (_version == version) { + return false; + } + _version = version; + _location = FileLocation(); + _data = QByteArray(); + status = FileReady; + if (loading()) { + _loader->deleteLater(); + _loader->stop(); + _loader = nullptr; + } + return true; +} + void DocumentData::setRemoteLocation(int32 dc, uint64 access) { _dc = dc; _access = access; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 1a948de46..876d6a9d6 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1068,7 +1068,7 @@ class Document; class DocumentData { public: static DocumentData *create(DocumentId id); - static DocumentData *create(DocumentId id, int32 dc, uint64 accessHash, const QVector &attributes); + static DocumentData *create(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QVector &attributes); static DocumentData *create(DocumentId id, const QString &url, const QVector &attributes); void setattributes(const QVector &attributes); @@ -1159,6 +1159,7 @@ public: _data = data; } + bool setRemoteVersion(int32 version); // Returns true if version has changed. void setRemoteLocation(int32 dc, uint64 access); void setContentUrl(const QString &url); bool hasRemoteLocation() const { @@ -1196,11 +1197,11 @@ public: int32 md5[8]; MediaKey mediaKey() const { - return ::mediaKey(locationType(), _dc, id); + return ::mediaKey(locationType(), _dc, id, _version); } private: - DocumentData(DocumentId id, int32 dc, uint64 accessHash, const QString &url, const QVector &attributes); + DocumentData(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QString &url, const QVector &attributes); friend class Serialize::Document; @@ -1208,9 +1209,10 @@ private: return voice() ? AudioFileLocation : (isVideo() ? VideoFileLocation : DocumentFileLocation); } - // Two types of location: from MTProto by dc+access or from web by url + // Two types of location: from MTProto by dc+access+version or from web by url int32 _dc = 0; uint64 _access = 0; + int32 _version = 0; QString _url; FileLocation _location; diff --git a/Telegram/SourceFiles/ui/images.h b/Telegram/SourceFiles/ui/images.h index 8596dbfc4..5bb7c2811 100644 --- a/Telegram/SourceFiles/ui/images.h +++ b/Telegram/SourceFiles/ui/images.h @@ -486,8 +486,13 @@ typedef QPair MediaKey; inline uint64 mediaMix32To64(int32 a, int32 b) { return (uint64(*reinterpret_cast(&a)) << 32) | uint64(*reinterpret_cast(&b)); } -inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { - return MediaKey(mediaMix32To64(type, dc), id); +// Old method, should not be used anymore. +//inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { +// return MediaKey(mediaMix32To64(type, dc), id); +//} +// New method when version was introduced, type is not relevant anymore (all files are Documents). +inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id, int32 version) { + return (version > 0) ? MediaKey(mediaMix32To64(version, dc), id) : MediaKey(mediaMix32To64(type, dc), id); } inline StorageKey mediaKey(const MTPDfileLocation &location) { return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index a50bf242c..e5022f18d 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2375,7 +2375,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.57; + TDESKTOP_VERSION = 0.9.58; }; name = Release; }; @@ -2516,7 +2516,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.57; + TDESKTOP_VERSION = 0.9.58; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index 4a048eef7..f5aff1e62 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9057 +AppVersion 9058 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.57 -AppVersionStr 0.9.57 +AppVersionStrSmall 0.9.58 +AppVersionStr 0.9.58 AlphaChannel 1 BetaVersion 0 From 66e2fce8d5cae8622d7ffc86101b91160d113253 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 8 Jul 2016 19:59:46 +0300 Subject: [PATCH 10/60] New design of a chat invite link import box with title, photo, users. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/app.cpp | 744 ++++++++++--------- Telegram/SourceFiles/app.h | 2 + Telegram/SourceFiles/boxes/boxes.style | 50 ++ Telegram/SourceFiles/boxes/confirmbox.cpp | 84 +++ Telegram/SourceFiles/boxes/confirmbox.h | 23 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 4 +- Telegram/SourceFiles/dialogs/dialogs.style | 4 +- Telegram/SourceFiles/mainwidget.cpp | 19 +- Telegram/Telegram.vcxproj | 3 + Telegram/Telegram.vcxproj.filters | 9 + 11 files changed, 572 insertions(+), 372 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/boxes.style diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3f3a2dc0b..d05f7de46 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -602,6 +602,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Do you want to join the channel «{title}»?"; "lng_group_invite_join" = "Join"; +"lng_group_invite_members" = "{count:_not_used_|# member|# members}, among them:"; + "lng_group_invite_link" = "Invite link:"; "lng_group_invite_create" = "Create an invite link"; "lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 888a7db71..ee5b159b7 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -383,397 +383,413 @@ namespace { return (online > now); } - UserData *feedUsers(const MTPVector &users) { - UserData *result = nullptr; - for_const (auto &user, users.c_vector().v) { - UserData *data = nullptr; - bool wasContact = false, minimal = false; - const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); + UserData *feedUser(const MTPUser &user) { + UserData *data = nullptr; + bool wasContact = false, minimal = false; + const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); - Notify::PeerUpdate update; - using UpdateFlag = Notify::PeerUpdate::Flag; + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdate::Flag; - switch (user.type()) { - case mtpc_userEmpty: { - const auto &d(user.c_userEmpty()); + switch (user.type()) { + case mtpc_userEmpty: { + auto &d(user.c_userEmpty()); - PeerId peer(peerFromUser(d.vid.v)); - data = App::user(peer); - auto canShareThisContact = data->canShareThisContactFast(); - wasContact = data->isContact(); + PeerId peer(peerFromUser(d.vid.v)); + data = App::user(peer); + auto canShareThisContact = data->canShareThisContactFast(); + wasContact = data->isContact(); - data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); - data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); + data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); + data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); + data->setName(lang(lng_deleted), QString(), QString(), QString()); + data->setPhoto(MTP_userProfilePhotoEmpty()); + data->access = UserNoAccess; + data->flags = 0; + data->setBotInfoVersion(-1); + status = &emptyStatus; + data->contact = -1; + + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; + } break; + case mtpc_user: { + auto &d(user.c_user()); + minimal = d.is_min(); + + PeerId peer(peerFromUser(d.vid.v)); + data = App::user(peer); + auto canShareThisContact = data->canShareThisContactFast(); + wasContact = data->isContact(); + if (!minimal) { + data->flags = d.vflags.v; + if (d.is_self()) { + data->input = MTP_inputPeerSelf(); + data->inputUser = MTP_inputUserSelf(); + } else if (!d.has_access_hash()) { + data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); + data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); + } else { + data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); + data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); + } + if (d.is_restricted()) { + data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); + } else { + data->setRestrictionReason(QString()); + } + } + if (d.is_deleted()) { + if (!data->phone().isEmpty()) { + data->setPhone(QString()); + update.flags |= UpdateFlag::UserPhoneChanged; + } data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; - data->flags = 0; - data->setBotInfoVersion(-1); status = &emptyStatus; - data->contact = -1; + } else { + // apply first_name and last_name from minimal user only if we don't have + // local values for first name and last name already, otherwise skip + bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty(); + QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; + QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; - if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; - if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; - } break; - case mtpc_user: { - const auto &d(user.c_user()); - minimal = d.is_min(); + QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString()); + QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); - PeerId peer(peerFromUser(d.vid.v)); - data = App::user(peer); - auto canShareThisContact = data->canShareThisContactFast(); - wasContact = data->isContact(); - if (!minimal) { - data->flags = d.vflags.v; - if (d.is_self()) { - data->input = MTP_inputPeerSelf(); - data->inputUser = MTP_inputUserSelf(); - } else if (!d.has_access_hash()) { - data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); - data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); - } else { - data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); - data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); - } - if (d.is_restricted()) { - data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); - } else { - data->setRestrictionReason(QString()); - } + bool phoneChanged = (data->phone() != phone); + if (phoneChanged) { + data->setPhone(phone); + update.flags |= UpdateFlag::UserPhoneChanged; } - if (d.is_deleted()) { - if (!data->phone().isEmpty()) { - data->setPhone(QString()); - update.flags |= UpdateFlag::UserPhoneChanged; - } - data->setName(lang(lng_deleted), QString(), QString(), QString()); - data->setPhoto(MTP_userProfilePhotoEmpty()); - data->access = UserNoAccess; - status = &emptyStatus; + bool nameChanged = (data->firstName != fname) || (data->lastName != lname); + + bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); + bool showPhoneChanged = !isServiceUser(data->id) && !d.is_self() && ((showPhone && data->contact) || (!showPhone && !data->contact)); + if (minimal) { + showPhoneChanged = false; + showPhone = !isServiceUser(data->id) && (data->id != peerFromUser(MTP::authedId())) && !data->contact; + } + + // see also Local::readPeer + + QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone; + + if (!minimal && d.is_self() && uname != data->username) { + SignalHandlers::setCrashAnnotation("Username", uname); + } + data->setName(fname, lname, pname, uname); + if (d.has_photo()) { + data->setPhoto(d.vphoto); } else { - // apply first_name and last_name from minimal user only if we don't have - // local values for first name and last name already, otherwise skip - bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty(); - QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; - QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; - - QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString()); - QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); - - bool phoneChanged = (data->phone() != phone); - if (phoneChanged) { - data->setPhone(phone); - update.flags |= UpdateFlag::UserPhoneChanged; - } - bool nameChanged = (data->firstName != fname) || (data->lastName != lname); - - bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); - bool showPhoneChanged = !isServiceUser(data->id) && !d.is_self() && ((showPhone && data->contact) || (!showPhone && !data->contact)); - if (minimal) { - showPhoneChanged = false; - showPhone = !isServiceUser(data->id) && (data->id != peerFromUser(MTP::authedId())) && !data->contact; - } - - // see also Local::readPeer - - QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone; - - if (!minimal && d.is_self() && uname != data->username) { - SignalHandlers::setCrashAnnotation("Username", uname); - } - data->setName(fname, lname, pname, uname); - if (d.has_photo()) { - data->setPhoto(d.vphoto); - } else { - data->setPhoto(MTP_userProfilePhotoEmpty()); - } - if (d.has_access_hash()) data->access = d.vaccess_hash.v; - status = d.has_status() ? &d.vstatus : &emptyStatus; + data->setPhoto(MTP_userProfilePhotoEmpty()); } - if (!minimal) { - if (d.has_bot_info_version()) { - data->setBotInfoVersion(d.vbot_info_version.v); - data->botInfo->readsAllHistory = d.is_bot_chat_history(); - if (data->botInfo->cantJoinGroups != d.is_bot_nochats()) { - data->botInfo->cantJoinGroups = d.is_bot_nochats(); - update.flags |= UpdateFlag::BotCanAddToGroups; - } - data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); - } else { - data->setBotInfoVersion(-1); - } - data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone().isEmpty() ? -1 : 0); - if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { - cRefReportSpamStatuses().insert(data->id, dbiprsHidden); - Local::writeReportSpamStatuses(); - } - if (d.is_self() && ::self != data) { - ::self = data; - if (App::wnd()) App::wnd()->updateGlobalMenu(); + if (d.has_access_hash()) data->access = d.vaccess_hash.v; + status = d.has_status() ? &d.vstatus : &emptyStatus; + } + if (!minimal) { + if (d.has_bot_info_version()) { + data->setBotInfoVersion(d.vbot_info_version.v); + data->botInfo->readsAllHistory = d.is_bot_chat_history(); + if (data->botInfo->cantJoinGroups != d.is_bot_nochats()) { + data->botInfo->cantJoinGroups = d.is_bot_nochats(); + update.flags |= UpdateFlag::BotCanAddToGroups; } + data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); + } else { + data->setBotInfoVersion(-1); } - - if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; - if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; - } break; - } - - if (!data) continue; - - if (minimal) { - if (data->loadedStatus == PeerData::NotLoaded) { - data->loadedStatus = PeerData::MinimalLoaded; + data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone().isEmpty() ? -1 : 0); + if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { + cRefReportSpamStatuses().insert(data->id, dbiprsHidden); + Local::writeReportSpamStatuses(); } - } else if (data->loadedStatus != PeerData::FullLoaded) { - data->loadedStatus = PeerData::FullLoaded; - } - - auto oldOnlineTill = data->onlineTill; - if (status && !minimal) switch (status->type()) { - case mtpc_userStatusEmpty: data->onlineTill = 0; break; - case mtpc_userStatusRecently: - if (data->onlineTill > -10) { // don't modify pseudo-online - data->onlineTill = -2; - } - break; - case mtpc_userStatusLastWeek: data->onlineTill = -3; break; - case mtpc_userStatusLastMonth: data->onlineTill = -4; break; - case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break; - case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; - } - if (oldOnlineTill != data->onlineTill) { - update.flags |= UpdateFlag::UserOnlineChanged; - } - - if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) { - data->contact = 0; - } - if (App::main()) { - if ((data->contact > 0 && !wasContact) || (wasContact && data->contact < 1)) { - Notify::userIsContactChanged(data); - } - - markPeerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdatedDelayed(update); + if (d.is_self() && ::self != data) { + ::self = data; + if (App::wnd()) App::wnd()->updateGlobalMenu(); } } - result = data; + + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; + } break; + } + + if (!data) { + return nullptr; + } + + if (minimal) { + if (data->loadedStatus == PeerData::NotLoaded) { + data->loadedStatus = PeerData::MinimalLoaded; + } + } else if (data->loadedStatus != PeerData::FullLoaded) { + data->loadedStatus = PeerData::FullLoaded; + } + + auto oldOnlineTill = data->onlineTill; + if (status && !minimal) switch (status->type()) { + case mtpc_userStatusEmpty: data->onlineTill = 0; break; + case mtpc_userStatusRecently: + if (data->onlineTill > -10) { // don't modify pseudo-online + data->onlineTill = -2; + } + break; + case mtpc_userStatusLastWeek: data->onlineTill = -3; break; + case mtpc_userStatusLastMonth: data->onlineTill = -4; break; + case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break; + case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; + } + if (oldOnlineTill != data->onlineTill) { + update.flags |= UpdateFlag::UserOnlineChanged; + } + + if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) { + data->contact = 0; + } + if (App::main()) { + if ((data->contact > 0 && !wasContact) || (wasContact && data->contact < 1)) { + Notify::userIsContactChanged(data); + } + + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); + } + } + return data; + } + + UserData *feedUsers(const MTPVector &users) { + UserData *result = nullptr; + for_const (auto &user, users.c_vector().v) { + if (auto feededUser = feedUser(user)) { + result = feededUser; + } } return result; } + PeerData *feedChat(const MTPChat &chat) { + PeerData *data = nullptr; + bool minimal = false; + + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdate::Flag; + + switch (chat.type()) { + case mtpc_chat: { + auto &d(chat.c_chat()); + + data = App::chat(peerFromChat(d.vid.v)); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); + + if (cdata->version < d.vversion.v) { + cdata->version = d.vversion.v; + cdata->invalidateParticipants(); + } + + data->input = MTP_inputPeerChat(d.vid); + cdata->setName(qs(d.vtitle)); + cdata->setPhoto(d.vphoto); + cdata->date = d.vdate.v; + + if (d.has_migrated_to() && d.vmigrated_to.type() == mtpc_inputChannel) { + const auto &c(d.vmigrated_to.c_inputChannel()); + ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id)); + if (!channel->mgInfo) { + channel->flags |= MTPDchannel::Flag::f_megagroup; + channel->flagsUpdated(); + } + if (!channel->access) { + channel->input = MTP_inputPeerChannel(c.vchannel_id, c.vaccess_hash); + channel->inputChannel = d.vmigrated_to; + channel->access = d.vmigrated_to.c_inputChannel().vaccess_hash.v; + } + bool updatedTo = (cdata->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cdata); + if (updatedTo) { + cdata->migrateToPtr = channel; + } + if (updatedFrom) { + channel->mgInfo->migrateFromPtr = cdata; + if (History *h = App::historyLoaded(cdata->id)) { + if (History *hto = App::historyLoaded(channel->id)) { + if (!h->isEmpty()) { + h->clear(true); + } + if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { + App::removeDialog(h); + } + } + } + Notify::migrateUpdated(channel); + update.flags |= UpdateFlag::MigrationChanged; + } + if (updatedTo) { + Notify::migrateUpdated(cdata); + update.flags |= UpdateFlag::MigrationChanged; + } + } + + if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { + cdata->invalidateParticipants(); + } + cdata->flags = d.vflags.v; + + cdata->count = d.vparticipants_count.v; + cdata->isForbidden = false; + if (canEdit != cdata->canEdit()) { + update.flags |= UpdateFlag::ChatCanEdit; + } + } break; + case mtpc_chatForbidden: { + auto &d(chat.c_chatForbidden()); + + data = App::chat(peerFromChat(d.vid.v)); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); + + data->input = MTP_inputPeerChat(d.vid); + cdata->setName(qs(d.vtitle)); + cdata->setPhoto(MTP_chatPhotoEmpty()); + cdata->date = 0; + cdata->count = -1; + cdata->invalidateParticipants(); + cdata->flags = 0; + cdata->isForbidden = true; + if (canEdit != cdata->canEdit()) { + update.flags |= UpdateFlag::ChatCanEdit; + } + } break; + case mtpc_channel: { + auto &d(chat.c_channel()); + + auto peerId = peerFromChannel(d.vid.v); + minimal = d.is_min(); + if (minimal) { + data = App::channelLoaded(peerId); + if (!data) { + return nullptr; // minimal is not loaded, need to make getDifference + } + } else { + data = App::channel(peerId); + data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0)); + } + + auto cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + auto canEditPhoto = cdata->canEditPhoto(); + auto canViewAdmins = cdata->canViewAdmins(); + auto canViewMembers = cdata->canViewMembers(); + auto canAddMembers = cdata->canAddMembers(); + auto wasEditor = cdata->amEditor(); + + if (minimal) { + auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; + cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); + } else { + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); + cdata->access = d.vaccess_hash.v; + cdata->date = d.vdate.v; + if (cdata->version < d.vversion.v) { + cdata->version = d.vversion.v; + } + if (d.is_restricted()) { + cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); + } else { + cdata->setRestrictionReason(QString()); + } + cdata->flags = d.vflags.v; + } + cdata->flagsUpdated(); + + QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); + cdata->setName(qs(d.vtitle), uname); + + cdata->isForbidden = false; + cdata->setPhoto(d.vphoto); + + if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; + if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; + if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; + if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; + if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; + if (wasEditor != cdata->amEditor()) { + cdata->selfAdminUpdated(); + update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); + } + } break; + case mtpc_channelForbidden: { + auto &d(chat.c_channelForbidden()); + + auto peerId = peerFromChannel(d.vid.v); + data = App::channel(peerId); + data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); + + auto cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + auto canEditPhoto = cdata->canEditPhoto(); + auto canViewAdmins = cdata->canViewAdmins(); + auto canViewMembers = cdata->canViewMembers(); + auto canAddMembers = cdata->canAddMembers(); + auto wasEditor = cdata->amEditor(); + + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); + + auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); + cdata->flags = (cdata->flags & ~mask) | (mtpCastFlags(d.vflags) & mask); + cdata->flagsUpdated(); + + cdata->setName(qs(d.vtitle), QString()); + + cdata->access = d.vaccess_hash.v; + cdata->setPhoto(MTP_chatPhotoEmpty()); + cdata->date = 0; + cdata->setMembersCount(0); + cdata->isForbidden = true; + + if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; + if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; + if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; + if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; + if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; + if (wasEditor != cdata->amEditor()) { + cdata->selfAdminUpdated(); + update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); + } + } break; + } + if (!data) { + return nullptr; + } + + if (minimal) { + if (data->loadedStatus == PeerData::NotLoaded) { + data->loadedStatus = PeerData::MinimalLoaded; + } + } else if (data->loadedStatus != PeerData::FullLoaded) { + data->loadedStatus = PeerData::FullLoaded; + } + if (App::main()) { + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); + } + } + return data; + } + PeerData *feedChats(const MTPVector &chats) { PeerData *result = nullptr; for_const (auto &chat, chats.c_vector().v) { - PeerData *data = nullptr; - bool minimal = false; - - Notify::PeerUpdate update; - using UpdateFlag = Notify::PeerUpdate::Flag; - - switch (chat.type()) { - case mtpc_chat: { - auto &d(chat.c_chat()); - - data = App::chat(peerFromChat(d.vid.v)); - auto cdata = data->asChat(); - auto canEdit = cdata->canEdit(); - - if (cdata->version < d.vversion.v) { - cdata->version = d.vversion.v; - cdata->invalidateParticipants(); - } - - data->input = MTP_inputPeerChat(d.vid); - cdata->setName(qs(d.vtitle)); - cdata->setPhoto(d.vphoto); - cdata->date = d.vdate.v; - - if (d.has_migrated_to() && d.vmigrated_to.type() == mtpc_inputChannel) { - const auto &c(d.vmigrated_to.c_inputChannel()); - ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id)); - if (!channel->mgInfo) { - channel->flags |= MTPDchannel::Flag::f_megagroup; - channel->flagsUpdated(); - } - if (!channel->access) { - channel->input = MTP_inputPeerChannel(c.vchannel_id, c.vaccess_hash); - channel->inputChannel = d.vmigrated_to; - channel->access = d.vmigrated_to.c_inputChannel().vaccess_hash.v; - } - bool updatedTo = (cdata->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cdata); - if (updatedTo) { - cdata->migrateToPtr = channel; - } - if (updatedFrom) { - channel->mgInfo->migrateFromPtr = cdata; - if (History *h = App::historyLoaded(cdata->id)) { - if (History *hto = App::historyLoaded(channel->id)) { - if (!h->isEmpty()) { - h->clear(true); - } - if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { - App::removeDialog(h); - } - } - } - Notify::migrateUpdated(channel); - update.flags |= UpdateFlag::MigrationChanged; - } - if (updatedTo) { - Notify::migrateUpdated(cdata); - update.flags |= UpdateFlag::MigrationChanged; - } - } - - if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { - cdata->invalidateParticipants(); - } - cdata->flags = d.vflags.v; - - cdata->count = d.vparticipants_count.v; - cdata->isForbidden = false; - if (canEdit != cdata->canEdit()) { - update.flags |= UpdateFlag::ChatCanEdit; - } - } break; - case mtpc_chatForbidden: { - auto &d(chat.c_chatForbidden()); - - data = App::chat(peerFromChat(d.vid.v)); - auto cdata = data->asChat(); - auto canEdit = cdata->canEdit(); - - data->input = MTP_inputPeerChat(d.vid); - cdata->setName(qs(d.vtitle)); - cdata->setPhoto(MTP_chatPhotoEmpty()); - cdata->date = 0; - cdata->count = -1; - cdata->invalidateParticipants(); - cdata->flags = 0; - cdata->isForbidden = true; - if (canEdit != cdata->canEdit()) { - update.flags |= UpdateFlag::ChatCanEdit; - } - } break; - case mtpc_channel: { - auto &d(chat.c_channel()); - - auto peerId = peerFromChannel(d.vid.v); - minimal = d.is_min(); - if (minimal) { - data = App::channelLoaded(peerId); - if (!data) { - continue; // minimal is not loaded, need to make getDifference - } - } else { - data = App::channel(peerId); - data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0)); - } - - auto cdata = data->asChannel(); - auto wasInChannel = cdata->amIn(); - auto canEditPhoto = cdata->canEditPhoto(); - auto canViewAdmins = cdata->canViewAdmins(); - auto canViewMembers = cdata->canViewMembers(); - auto canAddMembers = cdata->canAddMembers(); - auto wasEditor = cdata->amEditor(); - - if (minimal) { - auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; - cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); - } else { - cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); - cdata->access = d.vaccess_hash.v; - cdata->date = d.vdate.v; - if (cdata->version < d.vversion.v) { - cdata->version = d.vversion.v; - } - if (d.is_restricted()) { - cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); - } else { - cdata->setRestrictionReason(QString()); - } - cdata->flags = d.vflags.v; - } - cdata->flagsUpdated(); - - QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); - cdata->setName(qs(d.vtitle), uname); - - cdata->isForbidden = false; - cdata->setPhoto(d.vphoto); - - if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; - if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; - if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; - if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; - if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; - if (wasEditor != cdata->amEditor()) { - cdata->selfAdminUpdated(); - update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); - } - } break; - case mtpc_channelForbidden: { - auto &d(chat.c_channelForbidden()); - - auto peerId = peerFromChannel(d.vid.v); - data = App::channel(peerId); - data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); - - auto cdata = data->asChannel(); - auto wasInChannel = cdata->amIn(); - auto canEditPhoto = cdata->canEditPhoto(); - auto canViewAdmins = cdata->canViewAdmins(); - auto canViewMembers = cdata->canViewMembers(); - auto canAddMembers = cdata->canAddMembers(); - auto wasEditor = cdata->amEditor(); - - cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); - - auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); - cdata->flags = (cdata->flags & ~mask) | (mtpCastFlags(d.vflags) & mask); - cdata->flagsUpdated(); - - cdata->setName(qs(d.vtitle), QString()); - - cdata->access = d.vaccess_hash.v; - cdata->setPhoto(MTP_chatPhotoEmpty()); - cdata->date = 0; - cdata->setMembersCount(0); - cdata->isForbidden = true; - - if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; - if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; - if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; - if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; - if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; - if (wasEditor != cdata->amEditor()) { - cdata->selfAdminUpdated(); - update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); - } - } break; + if (auto feededChat = feedChat(chat)) { + result = feededChat; } - if (!data) continue; - - if (minimal) { - if (data->loadedStatus == PeerData::NotLoaded) { - data->loadedStatus = PeerData::MinimalLoaded; - } - } else if (data->loadedStatus != PeerData::FullLoaded) { - data->loadedStatus = PeerData::FullLoaded; - } - if (App::main()) { - markPeerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdatedDelayed(update); - } - } - result = data; } return result; } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 0c12a7615..c05ca179d 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -65,7 +65,9 @@ namespace App { bool onlineColorUse(UserData *user, TimeId now); bool onlineColorUse(TimeId online, TimeId now); + UserData *feedUser(const MTPUser &user); UserData *feedUsers(const MTPVector &users); // returns last user + PeerData *feedChat(const MTPChat &chat); PeerData *feedChats(const MTPVector &chats); // returns last chat void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated = true); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style new file mode 100644 index 000000000..42c691542 --- /dev/null +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -0,0 +1,50 @@ +/* +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 +*/ +using "basic.style"; + +confirmInviteTitle: flatLabel(labelDefFlat) { + font: font(16px semibold); + align: align(center); + width: 320px; + maxHeight: 24px; + textFg: #333333; +} +confirmInviteStatus: flatLabel(labelDefFlat) { + font: font(boxFontSize); + align: align(center); + width: 320px; + maxHeight: 20px; + textFg: windowSubTextFg; +} +confirmInviteTitleTop: 106px; +confirmInvitePhotoSize: 76px; +confirmInvitePhotoTop: 20px; +confirmInviteStatusTop: 136px; +confirmInviteUserHeight: 80px; +confirmInviteUserPhotoSize: 56px; +confirmInviteUserPhotoTop: 166px; +confirmInviteUserName: flatLabel(labelDefFlat) { + font: normalFont; + align: align(center); + width: 66px; + maxHeight: 20px; +} +confirmInviteUserNameTop: 227px; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 83ce30643..39c978ed3 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "application.h" #include "core/click_handler_types.h" +#include "styles/style_boxes.h" TextParseOptions _confirmBoxTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags @@ -520,3 +521,86 @@ void KickMemberBox::onConfirm() { App::api()->kickParticipant(channel, _member); } } + +ConfirmInviteBox::ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants) : AbstractBox() +, _title(this, st::confirmInviteTitle) +, _status(this, st::confirmInviteStatus) +, _photo(chatDefPhoto(0)) +, _participants(participants) +, _join(this, lang(lng_group_invite_join), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { + if (_participants.size() > 4) { + _participants.resize(4); + } + + _title->setText(title); + QString status; + if (_participants.isEmpty() || _participants.size() >= count) { + status = lng_chat_status_members(lt_count, count); + } else { + status = lng_group_invite_members(lt_count, count); + } + _status->setText(status); + if (photo.type() == mtpc_chatPhoto) { + auto &d = photo.c_chatPhoto(); + auto location = App::imageLocation(160, 160, d.vphoto_small); + if (!location.isNull()) { + _photo = ImagePtr(location); + if (!_photo->loaded()) { + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + _photo->load(); + } + } + } + + int h = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _join->height() + st::boxButtonPadding.bottom(); + if (!_participants.isEmpty()) { + int skip = (width() - 4 * st::confirmInviteUserPhotoSize) / 5; + int padding = skip / 2; + _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); + int sumWidth = _participants.size() * _userWidth; + int left = (width() - sumWidth) / 2; + for_const (auto user, _participants) { + auto name = new FlatLabel(this, st::confirmInviteUserName); + name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); + name->setText(user->firstName.isEmpty() ? App::peerName(user) : user->firstName); + name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); + left += _userWidth; + } + + h += st::confirmInviteUserHeight; + } + setMaxHeight(h); + + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + connect(_join, SIGNAL(clicked()), App::main(), SLOT(onInviteImport())); +} + +void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { + _title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop); + _status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop); + _join->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _join->height()); + _cancel->moveToRight(st::boxButtonPadding.right() + _join->width() + st::boxButtonPadding.left(), _join->y()); +} + +void ConfirmInviteBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + p.drawPixmap((width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, _photo->pixCircled(st::confirmInvitePhotoSize, st::confirmInvitePhotoSize)); + + int sumWidth = _participants.size() * _userWidth; + int left = (width() - sumWidth) / 2; + for_const (auto user, _participants) { + user->paintUserpicLeft(p, st::confirmInviteUserPhotoSize, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width()); + left += _userWidth; + } +} + +void ConfirmInviteBox::showAll() { + showChildren(); +} + +void ConfirmInviteBox::hideAll() { + hideChildren(); +} diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 59f27336d..ebad005d5 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -266,3 +266,26 @@ private: UserData *_member; }; + +class ConfirmInviteBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAll(); + void hideAll(); + +private: + ChildWidget _title, _status; + ImagePtr _photo; + QVector _participants; + + ChildWidget _join, _cancel; + int _userWidth = 0; + +}; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 78837cc1e..51d52bb82 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -530,9 +530,9 @@ void StickersInner::paintRow(Painter &p, int32 index) { int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); - App::roundRect(p, add, st::defaultActiveButton.textBgOver, ImageRoundRadius::Small); + App::roundRect(p, add, st::defaultActiveButton.textBg, ImageRoundRadius::Small); p.setFont(st::defaultActiveButton.font); - p.setPen(st::defaultActiveButton.textFg); + p.setPen(st::defaultActiveButton.textFg); // textBgOver / downTextTop p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + st::defaultActiveButton.textTop, width(), _addText, _addWidth); } diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 8ca6163bc..ce4681df0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -85,8 +85,8 @@ dialogsTextStyleActive: textStyle(dialogsTextStyle) { linkFgDown: dialogsTextFgActive; } dialogsTextStyleDraftActive: textStyle(dialogsTextStyle) { - linkFg: #ffd6d6; - linkFgDown: #ffd6d6; + linkFg: #c6e1f7; + linkFgDown: #c6e1f7; } dialogsNewChatIcon: icon { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 184556342..f4b456789 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3487,11 +3487,22 @@ bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { switch (invite.type()) { case mtpc_chatInvite: { - const auto &d(invite.c_chatInvite()); - ConfirmBox *box = new ConfirmBox(((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join)); + auto &d(invite.c_chatInvite()); + ((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join); + + QVector participants; + if (d.has_participants()) { + auto &v = d.vparticipants.c_vector().v; + participants.reserve(v.size()); + for_const (auto &user, v) { + if (auto feededUser = App::feedUser(user)) { + participants.push_back(feededUser); + } + } + } + auto box = std_::make_unique(qs(d.vtitle), d.vphoto, 3, participants); _inviteHash = hash; - connect(box, SIGNAL(confirmed()), this, SLOT(onInviteImport())); - Ui::showLayer(box); + Ui::showLayer(box.release()); } break; case mtpc_chatInviteAlready: { diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 5b8fc9a82..749debce7 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1185,6 +1185,7 @@ + @@ -1457,6 +1458,7 @@ + @@ -2876,6 +2878,7 @@ Compiling style %(Identity)... + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 2d1a1f393..227208438 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1344,6 +1344,9 @@ SourceFiles\platform\linux + + GeneratedFiles\styles + @@ -1595,6 +1598,9 @@ SourceFiles\platform\linux + + GeneratedFiles\styles + @@ -1966,6 +1972,9 @@ SourceFiles\profile + + SourceFiles\boxes + From 640f9d7389772a85a6480a3464296d00aa4cec41 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 8 Jul 2016 20:24:48 +0300 Subject: [PATCH 11/60] Active and pressed buttons state in featured stickers box. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 51d52bb82..1e9a3cedc 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -530,10 +530,12 @@ void StickersInner::paintRow(Painter &p, int32 index) { int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); - App::roundRect(p, add, st::defaultActiveButton.textBg, ImageRoundRadius::Small); + auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg; + App::roundRect(p, add, textBg, ImageRoundRadius::Small); p.setFont(st::defaultActiveButton.font); - p.setPen(st::defaultActiveButton.textFg); // textBgOver / downTextTop - p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + st::defaultActiveButton.textTop, width(), _addText, _addWidth); + p.setPen(st::defaultActiveButton.textFg); + int textTop = (_actionSel == index && _actionDown == index) ? st::defaultActiveButton.downTextTop : st::defaultActiveButton.textTop; + p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + textTop, width(), _addText, _addWidth); } if (s->disabled) p.setOpacity(st::stickersRowDisabledOpacity); @@ -700,7 +702,7 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { } _dragging = _started = -1; - } else if (pressed == _selected) { + } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { if (_selected == -1) { _selected = -2; Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); From 1fee0822fb2546786d421624ff8ffde52822488a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 10 Jul 2016 16:02:22 +0300 Subject: [PATCH 12/60] Moved AudioPlayerState+position+duration+frequency to a single struct. --- Telegram/SourceFiles/history.cpp | 87 ++++++++--------- .../inline_bot_layout_internal.cpp | 48 ++++------ Telegram/SourceFiles/mainwidget.cpp | 25 +++-- Telegram/SourceFiles/media/media_audio.cpp | 95 +++++++++---------- Telegram/SourceFiles/media/media_audio.h | 30 +++--- .../SourceFiles/media/media_audio_loaders.cpp | 26 ++--- .../SourceFiles/media/media_clip_ffmpeg.h | 1 - Telegram/SourceFiles/mediaview.h | 3 - .../SourceFiles/overview/overview_layout.cpp | 48 ++++------ Telegram/SourceFiles/overviewwidget.cpp | 3 +- Telegram/SourceFiles/playerwidget.cpp | 63 ++++++------ Telegram/SourceFiles/playerwidget.h | 2 +- Telegram/SourceFiles/structs.cpp | 20 ++-- 13 files changed, 198 insertions(+), 253 deletions(-) diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index f0f0dfea3..ec27c7688 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -4402,61 +4402,48 @@ bool HistoryDocument::updateStatusText() const { } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { - if (_data->voice()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - if (auto voice = Get()) { - bool was = voice->_playback; - voice->ensurePlayback(this); - if (!was || playingPosition != voice->_playback->_position) { - float64 prg = playingDuration ? snap(float64(playingPosition) / playingDuration, 0., 1.) : 0.; - if (voice->_playback->_position < playingPosition) { - voice->_playback->a_progress.start(prg); - } else { - voice->_playback->a_progress = anim::fvalue(0., prg); + statusSize = FileStatusSizeLoaded; + if (audioPlayer()) { + if (_data->voice()) { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + if (auto voice = Get()) { + bool was = voice->_playback; + voice->ensurePlayback(this); + if (!was || playbackState.position != voice->_playback->_position) { + float64 prg = playbackState.duration ? snap(float64(playbackState.position) / playbackState.duration, 0., 1.) : 0.; + if (voice->_playback->_position < playbackState.position) { + voice->_playback->a_progress.start(prg); + } else { + voice->_playback->a_progress = anim::fvalue(0., prg); + } + voice->_playback->_position = playbackState.position; + voice->_playback->_a_progress.start(); } - voice->_playback->_position = playingPosition; - voice->_playback->_a_progress.start(); + } + + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } else { + if (auto voice = Get()) { + voice->checkPlaybackFinished(); } } - - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; - if (auto voice = Get()) { - voice->checkPlaybackFinished(); + } else if (_data->song()) { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } else { + } + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { + showPause = true; } } - } else if (_data->song()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; - } - if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { - showPause = true; - } - } else { - statusSize = FileStatusSizeLoaded; } } else { statusSize = FileStatusSizeReady; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 4f1285e90..90f26f93a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -854,39 +854,29 @@ bool File::updateStatusText() const { statusSize = document->loadOffset(); } else if (document->loaded()) { if (document->voice()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; + statusSize = FileStatusSizeLoaded; if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); + if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } } } else if (document->song()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; + statusSize = FileStatusSizeLoaded; if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; - } - if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) { - showPause = true; + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } + if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) { + showPause = true; + } } } else { statusSize = FileStatusSizeLoaded; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 558d9f092..5f625b8c0 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -91,7 +91,6 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window) connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); - connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); } connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement())); @@ -1530,13 +1529,14 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) { } void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { + if (audioId.type() == AudioMsgId::Type::Video) { + return; + } + AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, audioId.type(), &playingState, &playingPosition, &playingDuration, &playingFrequency); - if (playing == audioId && playingState == AudioPlayerStoppedAtStart) { - playingState = AudioPlayerStopped; + auto playbackState = audioPlayer()->currentState(&playing, audioId.type()); + if (playing == audioId && playbackState.state == AudioPlayerStoppedAtStart) { + playbackState.state = AudioPlayerStopped; audioPlayer()->clearStoppedAtStart(audioId); DocumentData *audio = audioId.audio(); @@ -1551,9 +1551,9 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) { - _player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); + _player->updateState(playing, playbackState); - if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { if (!_player->isOpened()) { _player->openPlayer(); if (_player->isHidden() && !_a_show.animating()) { @@ -1609,12 +1609,9 @@ void MainWidget::documentLoadProgress(FileLoader *loader) { if (!document->loaded() && document->loading() && document->song() && audioPlayer()) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); if (playing.audio() == document && !_player->isHidden()) { - _player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); + _player->updateState(playing, playbackState); } } } diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index ee60c59f6..85997ee1b 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -263,12 +263,10 @@ void AudioPlayer::AudioMsg::clear() { audio = AudioMsgId(); file = FileLocation(); data = QByteArray(); - position = duration = 0; - frequency = AudioVoiceMsgFrequency; + playbackState = defaultState(); skipStart = skipEnd = 0; loading = false; started = 0; - state = AudioPlayerStopped; if (alIsSource(source)) { alSourceStop(source); } @@ -305,7 +303,7 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&))); connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&))); connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); - connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(stopped(const AudioMsgId&)), Qt::QueuedConnection); + connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)), Qt::QueuedConnection); _loaderThread.start(); _faderThread.start(); } @@ -350,7 +348,7 @@ void AudioPlayer::onError(const AudioMsgId &audio) { } void AudioPlayer::onStopped(const AudioMsgId &audio) { - emit stopped(audio); + emit updated(audio); if (audio.type() == AudioMsgId::Type::Voice) { emit unsuppressSong(); } @@ -399,7 +397,7 @@ bool AudioPlayer::updateCurrentStarted(AudioMsgId::Type type, int32 pos) { return false; } } - data->started = data->position = pos + data->skipStart; + data->started = data->playbackState.position = pos + data->skipStart; return true; } @@ -407,16 +405,16 @@ bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { auto current = dataForType(type); if (!current) return false; - switch (current->state) { + switch (current->playbackState.state) { case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: - current->state = AudioPlayerFinishing; + current->playbackState.state = AudioPlayerFinishing; updateCurrentStarted(type); if (fadedStart) *fadedStart = true; break; case AudioPlayerPausing: - current->state = AudioPlayerFinishing; + current->playbackState.state = AudioPlayerFinishing; if (fadedStart) *fadedStart = true; break; case AudioPlayerPaused: @@ -473,7 +471,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { onError(audio); } } else { - current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; + current->playbackState.state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; current->loading = true; emit loaderOnStart(audio, position); if (type == AudioMsgId::Type::Voice) { @@ -507,7 +505,7 @@ void AudioPlayer::playFromVideo(const AudioMsgId &audio, int64 position, std_::u current->videoData = std_::move(data); _loader->startFromVideo(current->videoData->videoPlayId); - current->state = AudioPlayerPlaying; + current->playbackState.state = AudioPlayerPlaying; current->loading = true; emit loaderOnStart(audio, position); } @@ -539,19 +537,19 @@ void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) { case AudioMsgId::Type::Song: suppressGain = suppressSongGain * cSongVolume(); break; } - switch (current->state) { + switch (current->playbackState.state) { case AudioPlayerPausing: case AudioPlayerPaused: case AudioPlayerPausedAtEnd: { - if (current->state == AudioPlayerPaused) { + if (current->playbackState.state == AudioPlayerPaused) { updateCurrentStarted(type); - } else if (current->state == AudioPlayerPausedAtEnd) { + } else if (current->playbackState.state == AudioPlayerPausedAtEnd) { if (alIsSource(current->source)) { - alSourcei(current->source, AL_SAMPLE_OFFSET, qMax(current->position - current->skipStart, 0LL)); + alSourcei(current->source, AL_SAMPLE_OFFSET, qMax(current->playbackState.position - current->skipStart, 0LL)); if (!checkCurrentALError(type)) return; } } - current->state = fast ? AudioPlayerPlaying : AudioPlayerResuming; + current->playbackState.state = fast ? AudioPlayerPlaying : AudioPlayerResuming; ALint state = AL_INITIAL; alGetSourcei(current->source, AL_SOURCE_STATE, &state); @@ -571,11 +569,11 @@ void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) { case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: - current->state = AudioPlayerPausing; + current->playbackState.state = AudioPlayerPausing; updateCurrentStarted(type); if (type == AudioMsgId::Type::Voice) emit unsuppressSong(); break; - case AudioPlayerFinishing: current->state = AudioPlayerPausing; break; + case AudioPlayerFinishing: current->playbackState.state = AudioPlayerPausing; break; } emit faderOnTimer(); } @@ -593,7 +591,7 @@ void AudioPlayer::seek(int64 position) { auto audio = current->audio; bool isSource = alIsSource(current->source); - bool fastSeek = (position >= current->skipStart && position < current->duration - current->skipEnd - (current->skipEnd ? AudioVoiceMsgFrequency : 0)); + bool fastSeek = (position >= current->skipStart && position < current->playbackState.duration - current->skipEnd - (current->skipEnd ? AudioVoiceMsgFrequency : 0)); if (fastSeek && isSource) { alSourcei(current->source, AL_SAMPLE_OFFSET, position - current->skipStart); if (!checkCurrentALError(type)) return; @@ -604,12 +602,12 @@ void AudioPlayer::seek(int64 position) { setStoppedState(current); if (isSource) alSourceStop(current->source); } - switch (current->state) { + switch (current->playbackState.state) { case AudioPlayerPausing: case AudioPlayerPaused: case AudioPlayerPausedAtEnd: { - if (current->state == AudioPlayerPausedAtEnd) { - current->state = AudioPlayerPaused; + if (current->playbackState.state == AudioPlayerPausedAtEnd) { + current->playbackState.state = AudioPlayerPaused; } lock.unlock(); return pauseresume(type, true); @@ -617,7 +615,7 @@ void AudioPlayer::seek(int64 position) { case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: - current->state = AudioPlayerPausing; + current->playbackState.state = AudioPlayerPausing; updateCurrentStarted(type); if (type == AudioMsgId::Type::Voice) emit unsuppressSong(); break; @@ -683,31 +681,24 @@ void AudioPlayer::stopAndClear() { } } -void AudioPlayer::currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { +AudioPlaybackState AudioPlayer::currentState(AudioMsgId *audio, AudioMsgId::Type type) { QMutexLocker lock(&playerMutex); auto current = dataForType(type); - if (!current) return; + if (!current) return AudioPlaybackState(); if (audio) *audio = current->audio; - return currentState(current, state, position, duration, frequency); -} - -void AudioPlayer::currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { - if (state) *state = current->state; - if (position) *position = current->position; - if (duration) *duration = current->duration; - if (frequency) *frequency = current->frequency; + return current->playbackState; } void AudioPlayer::setStoppedState(AudioMsg *current, AudioPlayerState state) { - current->state = state; - current->position = 0; + current->playbackState.state = state; + current->playbackState.position = 0; } void AudioPlayer::clearStoppedAtStart(const AudioMsgId &audio) { QMutexLocker lock(&playerMutex); auto data = dataForType(audio.type()); - if (data && data->audio == audio && data->state == AudioPlayerStoppedAtStart) { + if (data && data->audio == audio && data->playbackState.state == AudioPlayerStoppedAtStart) { setStoppedState(data); } } @@ -843,7 +834,7 @@ void AudioPlayerFader::onTimer() { auto updatePlayback = [this, voice, &hasPlaying, &hasFading](AudioMsgId::Type type, int index, float64 suppressGain, bool suppressGainChanged) { auto data = voice->dataForType(type, index); - if ((data->state & AudioPlayerStoppedMask) || data->state == AudioPlayerPaused || !data->source) return; + if ((data->playbackState.state & AudioPlayerStoppedMask) || data->playbackState.state == AudioPlayerPaused || !data->source) return; int32 emitSignals = updateOnePlayback(data, hasPlaying, hasFading, suppressGain, suppressGainChanged); if (emitSignals & EmitError) emit error(data->audio); @@ -894,7 +885,7 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPla if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } int32 emitSignals = 0; - switch (m->state) { + switch (m->playbackState.state) { case AudioPlayerFinishing: case AudioPlayerPausing: case AudioPlayerStarting: @@ -914,17 +905,17 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPla alSourcef(m->source, AL_GAIN, 1); if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } } - if (m->state == AudioPlayerPausing) { - m->state = AudioPlayerPausedAtEnd; + if (m->playbackState.state == AudioPlayerPausing) { + m->playbackState.state = AudioPlayerPausedAtEnd; } else { setStoppedState(m, AudioPlayerStoppedAtEnd); } emitSignals |= EmitStopped; - } else if (1000 * (pos + m->skipStart - m->started) >= AudioFadeDuration * m->frequency) { + } else if (1000 * (pos + m->skipStart - m->started) >= AudioFadeDuration * m->playbackState.frequency) { fading = false; alSourcef(m->source, AL_GAIN, 1. * suppressGain); if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } - switch (m->state) { + switch (m->playbackState.state) { case AudioPlayerFinishing: alSourceStop(m->source); if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } @@ -934,17 +925,17 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPla case AudioPlayerPausing: alSourcePause(m->source); if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } - m->state = AudioPlayerPaused; + m->playbackState.state = AudioPlayerPaused; break; case AudioPlayerStarting: case AudioPlayerResuming: - m->state = AudioPlayerPlaying; + m->playbackState.state = AudioPlayerPlaying; playing = true; break; } } else { - float64 newGain = 1000. * (pos + m->skipStart - m->started) / (AudioFadeDuration * m->frequency); - if (m->state == AudioPlayerPausing || m->state == AudioPlayerFinishing) { + float64 newGain = 1000. * (pos + m->skipStart - m->started) / (AudioFadeDuration * m->playbackState.frequency); + if (m->playbackState.state == AudioPlayerPausing || m->playbackState.state == AudioPlayerFinishing) { newGain = 1. - newGain; } alSourcef(m->source, AL_GAIN, newGain * suppressGain); @@ -966,12 +957,12 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPla if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } } } - if (state == AL_PLAYING && pos + m->skipStart - m->position >= AudioCheckPositionDelta) { - m->position = pos + m->skipStart; + if (state == AL_PLAYING && pos + m->skipStart - m->playbackState.position >= AudioCheckPositionDelta) { + m->playbackState.position = pos + m->skipStart; emitSignals |= EmitPositionUpdated; } - if (playing || m->state == AudioPlayerStarting || m->state == AudioPlayerResuming) { - if (!m->loading && m->skipEnd > 0 && m->position + AudioPreloadSamples + m->skipEnd > m->duration) { + if (playing || m->playbackState.state == AudioPlayerStarting || m->playbackState.state == AudioPlayerResuming) { + if (!m->loading && m->skipEnd > 0 && m->playbackState.position + AudioPreloadSamples + m->skipEnd > m->playbackState.duration) { m->loading = true; emitSignals |= EmitNeedToPreload; } @@ -983,8 +974,8 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPla } void AudioPlayerFader::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { - m->state = state; - m->position = 0; + m->playbackState.state = state; + m->playbackState.position = 0; } void AudioPlayerFader::onPauseTimer() { diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 27a8e15a6..3dc9fc96a 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -48,12 +48,17 @@ class AudioPlayerLoaders; struct VideoSoundData; struct VideoSoundPart; +struct AudioPlaybackState { + AudioPlayerState state = AudioPlayerStopped; + int64 position = 0; + int64 duration = 0; + int32 frequency = 0; +}; class AudioPlayer : public QObject { Q_OBJECT public: - AudioPlayer(); void play(const AudioMsgId &audio, int64 position = 0); @@ -64,10 +69,11 @@ public: // Video player audio stream interface. void playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data); void feedFromVideo(VideoSoundPart &&part); + AudioPlaybackState getStateForVideo(uint64 playId); void stopAndClear(); - void currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); + AudioPlaybackState currentState(AudioMsgId *audio, AudioMsgId::Type type); void clearStoppedAtStart(const AudioMsgId &audio); @@ -76,14 +82,11 @@ public: ~AudioPlayer(); public slots: - void onError(const AudioMsgId &audio); void onStopped(const AudioMsgId &audio); signals: - void updated(const AudioMsgId &audio); - void stopped(const AudioMsgId &audio); void stoppedOnError(const AudioMsgId &audio); void loaderOnStart(const AudioMsgId &audio, qint64 position); void loaderOnCancel(const AudioMsgId &audio); @@ -97,7 +100,6 @@ signals: void songVolumeChanged(); private: - bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); bool updateCurrentStarted(AudioMsgId::Type type, int32 pos = -1); bool checkCurrentALError(AudioMsgId::Type type); @@ -109,14 +111,11 @@ private: FileLocation file; QByteArray data; - int64 position = 0; - int64 duration = 0; - int32 frequency = AudioVoiceMsgFrequency; + AudioPlaybackState playbackState = defaultState(); int64 skipStart = 0; int64 skipEnd = 0; bool loading = false; int64 started = 0; - AudioPlayerState state = AudioPlayerStopped; uint32 source = 0; int32 nextBuffer = 0; @@ -124,9 +123,16 @@ private: int64 samplesCount[3] = { 0 }; std_::unique_ptr videoData; + + private: + static AudioPlaybackState defaultState() { + AudioPlaybackState result; + result.frequency = AudioVoiceMsgFrequency; + return result; + } + }; - void currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); void setStoppedState(AudioMsg *current, AudioPlayerState state = AudioPlayerStopped); AudioMsg *dataForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type) @@ -141,6 +147,8 @@ private: AudioMsg _songData[AudioSimultaneousLimit]; AudioMsg _videoData; + uint64 _lastVideoPlayId; + AudioPlaybackState _lastVideoPlaybackState; QMutex _mutex; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index 96b32b2de..581342300 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -112,8 +112,8 @@ AudioMsgId AudioPlayerLoaders::clear(AudioMsgId::Type type) { } void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { - m->state = state; - m->position = 0; + m->playbackState.state = state; + m->playbackState.position = 0; } void AudioPlayerLoaders::emitError(AudioMsgId::Type type) { @@ -153,7 +153,7 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { { QMutexLocker lock(internal::audioPlayerMutex()); AudioPlayer::AudioMsg *m = checkLoader(type); - if (m) m->state = AudioPlayerStoppedAtStart; + if (m) m->playbackState.state = AudioPlayerStoppedAtStart; } emitError(type); return; @@ -200,8 +200,8 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { m->nextBuffer = 0; } m->skipStart = position; - m->skipEnd = m->duration - position; - m->position = 0; + m->skipEnd = m->playbackState.duration - position; + m->playbackState.position = 0; m->started = 0; } if (samplesCount) { @@ -280,12 +280,12 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { if (finished) { m->skipEnd = 0; - m->duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2]; + m->playbackState.duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2]; clear(type); } m->loading = false; - if (m->state == AudioPlayerResuming || m->state == AudioPlayerPlaying || m->state == AudioPlayerStarting) { + if (m->playbackState.state == AudioPlayerResuming || m->playbackState.state == AudioPlayerPlaying || m->playbackState.state == AudioPlayerStarting) { ALint state = AL_INITIAL; alGetSourcei(m->source, AL_SOURCE_STATE, &state); if (internal::audioCheckError()) { @@ -356,7 +356,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, Setu if (audio.type() == AudioMsgId::Type::Video) { if (!data->videoData) { - data->state = AudioPlayerStoppedAtError; + data->playbackState.state = AudioPlayerStoppedAtError; emit error(audio); LOG(("Audio Error: video sound data not ready")); return nullptr; @@ -369,17 +369,17 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, Setu } if (!l->open(position)) { - data->state = AudioPlayerStoppedAtStart; + data->playbackState.state = AudioPlayerStoppedAtStart; return nullptr; } int64 duration = l->duration(); if (duration <= 0) { - data->state = AudioPlayerStoppedAtStart; + data->playbackState.state = AudioPlayerStoppedAtStart; return nullptr; } - data->duration = duration; - data->frequency = l->frequency(); - if (!data->frequency) data->frequency = AudioVoiceMsgFrequency; + data->playbackState.duration = duration; + data->playbackState.frequency = l->frequency(); + if (!data->playbackState.frequency) data->playbackState.frequency = AudioVoiceMsgFrequency; err = SetupNoErrorStarted; } else { if (!data->skipEnd) { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 02f5f18e6..86f54c506 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -35,7 +35,6 @@ namespace internal { class FFMpegReaderImplementation : public ReaderImplementation { public: - FFMpegReaderImplementation(FileLocation *location, QByteArray *data); bool readNextFrame() override; diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 0830fd582..34c94617d 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -26,7 +26,6 @@ class MediaView : public TWidget, public RPCSender, public ClickHandlerHost { Q_OBJECT public: - MediaView(); void paintEvent(QPaintEvent *e) override; @@ -83,7 +82,6 @@ public: void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; public slots: - void onHideControls(bool force = false); void onDropdownHiding(); @@ -109,7 +107,6 @@ public slots: void updateImage(); private: - void displayPhoto(PhotoData *photo, HistoryItem *item); void displayDocument(DocumentData *doc, HistoryItem *item); void findCurrent(); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index d2accd161..0d8a53fb5 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -556,20 +556,15 @@ bool Voice::updateStatusText() const { if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { statusSize = FileStatusSizeFailed; } else if (_data->loaded()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; + statusSize = FileStatusSizeLoaded; if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } } } else { statusSize = FileStatusSizeReady; @@ -865,23 +860,18 @@ bool Document::updateStatusText() const { statusSize = _data->loadOffset(); } else if (_data->loaded()) { if (_data->song()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; + statusSize = FileStatusSizeLoaded; if (audioPlayer()) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; - } - if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { - showPause = true; + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency)); + realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + } + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { + showPause = true; + } } } else { statusSize = FileStatusSizeLoaded; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 19b80726a..b10530130 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -2092,8 +2092,7 @@ int32 OverviewWidget::lastScrollTop() const { int32 OverviewWidget::countBestScroll() const { if (type() == OverviewMusicFiles && audioPlayer()) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); + audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); if (playing) { int32 top = _inner.itemTop(playing.contextId()); if (top >= 0) { diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index 40e3ae804..1b222e511 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -169,18 +169,15 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) { rtlupdate(_volumeRect); } else if (_over == OverPlayback) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); - if (playing == _song && playingDuration) { - if (playingState == AudioPlayerPlaying || playingState == AudioPlayerStarting || playingState == AudioPlayerResuming) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == _song && playbackState.duration) { + if (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } _down = OverPlayback; _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); - _downDuration = playingDuration; - _downFrequency = (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + _downDuration = playbackState.duration; + _downFrequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); rtlupdate(_playbackRect); updateDownTime(); @@ -444,12 +441,9 @@ void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { } else if (_down == OverPlayback) { mouseMoveEvent(e); AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); - if (playing == _song && playingDuration) { - _downDuration = playingDuration; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == _song && playbackState.duration) { + _downDuration = playbackState.duration; audioPlayer()->seek(qRound(_downProgress * _downDuration)); _showPause = true; @@ -468,10 +462,9 @@ void PlayerWidget::playPressed() { if (!_song || isHidden()) return; AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); - if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { - if (playingState == AudioPlayerPausing || playingState == AudioPlayerPaused || playingState == AudioPlayerPausedAtEnd) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { + if (playbackState.state == AudioPlayerPausing || playbackState.state == AudioPlayerPaused || playbackState.state == AudioPlayerPausedAtEnd) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } } else { @@ -484,10 +477,9 @@ void PlayerWidget::pausePressed() { if (!_song || isHidden()) return; AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); - if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { - if (playingState == AudioPlayerStarting || playingState == AudioPlayerResuming || playingState == AudioPlayerPlaying || playingState == AudioPlayerFinishing) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { + if (playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } } @@ -497,9 +489,8 @@ void PlayerWidget::playPausePressed() { if (!_song || isHidden()) return; AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); - if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { audioPlayer()->play(_song); @@ -581,12 +572,12 @@ void PlayerWidget::step_progress(float64 ms, bool timer) { } void PlayerWidget::updateState() { - updateState(AudioMsgId(), AudioPlayerStopped, 0, 0, 0); + updateState(AudioMsgId(), AudioPlaybackState()); } -void PlayerWidget::updateState(AudioMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency) { +void PlayerWidget::updateState(AudioMsgId playing, AudioPlaybackState playbackState) { if (!playing) { - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState, &playingPosition, &playingDuration, &playingFrequency); + playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); } bool songChanged = false; @@ -622,20 +613,20 @@ void PlayerWidget::updateState(AudioMsgId playing, AudioPlayerState playingState qint64 position = 0, duration = 0, display = 0; if (playing == _song) { - if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - display = position = playingPosition; - duration = playingDuration; + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + display = position = playbackState.position; + duration = playbackState.duration; } else { - display = playingDuration; + display = playbackState.duration; } - display = display / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + display = display / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); } else if (_song) { display = _song.audio()->song()->duration; } - bool showPause = false, stopped = ((playingState & AudioPlayerStoppedMask) || playingState == AudioPlayerFinishing); + bool showPause = false, stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing); bool wasPlaying = (_duration != 0); if (!stopped) { - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); } QString time; float64 progress = 0.; @@ -686,7 +677,7 @@ void PlayerWidget::updateState(AudioMsgId playing, AudioPlayerState playingState _loaded = loaded; } - if (wasPlaying && playingState == AudioPlayerStoppedAtEnd) { + if (wasPlaying && playbackState.state == AudioPlayerStoppedAtEnd) { if (_repeat) { if (_song.audio()) { audioPlayer()->play(_song, OverviewMusicFiles); diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index c8e893483..f93a91dcf 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -47,7 +47,7 @@ public: void step_progress(float64 ms, bool timer); void step_state(uint64 ms, bool timer); - void updateState(AudioMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency); + void updateState(AudioMsgId playing, AudioPlaybackState playbackState); void updateState(); void clearSelection(); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 8519bd95c..aa3d724be 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -953,9 +953,8 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { if (playVoice) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &playingState); - if (playing == AudioMsgId(data, msgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); + if (playing == AudioMsgId(data, msgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Voice); } else { AudioMsgId audio(data, msgId); @@ -967,9 +966,8 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } } else if (playMusic) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); - if (playing == AudioMsgId(data, msgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == AudioMsgId(data, msgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { AudioMsgId song(data, msgId); @@ -1252,9 +1250,8 @@ void DocumentData::performActionOnLoad() { if (playVoice) { if (loaded()) { AudioMsgId playing; - AudioPlayerState state = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice, &state); - if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); + if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Voice); } else { audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId)); @@ -1264,9 +1261,8 @@ void DocumentData::performActionOnLoad() { } else if (playMusic) { if (loaded()) { AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, AudioMsgId::Type::Song, &playingState); - if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { AudioMsgId song(this, _actionOnLoadMsgId); From 41cd4278341992861208696920fe3b351f206dc3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 10 Jul 2016 22:44:55 +0300 Subject: [PATCH 13/60] Sync video frame delay / skip by audio play progress. --- Telegram/SourceFiles/mainwidget.cpp | 1 + Telegram/SourceFiles/media/media_audio.cpp | 41 +++++++++++++++- Telegram/SourceFiles/media/media_audio.h | 12 +++-- .../SourceFiles/media/media_audio_loaders.cpp | 2 +- .../media/media_child_ffmpeg_loader.cpp | 3 +- .../media/media_child_ffmpeg_loader.h | 6 +-- .../SourceFiles/media/media_clip_ffmpeg.cpp | 47 ++++++++++++++++--- .../SourceFiles/media/media_clip_ffmpeg.h | 9 +++- .../media/media_clip_implementation.h | 12 +++-- .../SourceFiles/media/media_clip_qtgif.cpp | 28 +++++++++-- Telegram/SourceFiles/media/media_clip_qtgif.h | 6 ++- .../SourceFiles/media/media_clip_reader.cpp | 37 +++++---------- 12 files changed, 152 insertions(+), 52 deletions(-) diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 5f625b8c0..8c44c5c16 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1530,6 +1530,7 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) { void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { if (audioId.type() == AudioMsgId::Type::Video) { + audioPlayer()->videoSoundProgress(audioId); return; } diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 85997ee1b..9bc7791c2 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -482,7 +482,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { if (stopped) emit updated(stopped); } -void AudioPlayer::playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data) { +void AudioPlayer::playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { t_assert(audio.type() == AudioMsgId::Type::Video); auto type = audio.type(); @@ -502,8 +502,15 @@ void AudioPlayer::playFromVideo(const AudioMsgId &audio, int64 position, std_::u emit faderOnTimer(); current->clear(); current->audio = audio; + current->videoPlayId = videoPlayId; current->videoData = std_::move(data); - _loader->startFromVideo(current->videoData->videoPlayId); + { + QMutexLocker videoLock(&_lastVideoMutex); + _lastVideoPlayId = current->videoPlayId; + _lastVideoPlaybackWhen = 0; + _lastVideoPlaybackCorrectedMs = 0; + } + _loader->startFromVideo(current->videoPlayId); current->playbackState.state = AudioPlayerPlaying; current->loading = true; @@ -516,6 +523,36 @@ void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { _loader->feedFromVideo(std_::move(part)); } +int64 AudioPlayer::getVideoCorrectedTime(uint64 playId, uint64 systemMs) { + int64 result = systemMs; + + QMutexLocker videoLock(&_lastVideoMutex); + if (_lastVideoPlayId == playId && _lastVideoPlaybackWhen > 0) { + result = static_cast(_lastVideoPlaybackCorrectedMs); + if (systemMs > _lastVideoPlaybackWhen) { + result += (systemMs - _lastVideoPlaybackWhen); + } + } + + return result; +} + +void AudioPlayer::videoSoundProgress(const AudioMsgId &audio) { + auto type = audio.type(); + t_assert(type == AudioMsgId::Type::Video); + + QMutexLocker lock(&playerMutex); + QMutexLocker videoLock(&_lastVideoMutex); + + auto current = dataForType(type); + t_assert(current != nullptr); + + if (current->videoPlayId == _lastVideoPlayId && current->playbackState.frequency) { + _lastVideoPlaybackWhen = getms(); + _lastVideoPlaybackCorrectedMs = (current->playbackState.position * 1000ULL) / current->playbackState.frequency; + } +} + bool AudioPlayer::checkCurrentALError(AudioMsgId::Type type) { if (_checkALError()) return true; diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 3dc9fc96a..38b251a88 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -67,9 +67,10 @@ public: void stop(AudioMsgId::Type type); // Video player audio stream interface. - void playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data); + void playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position); void feedFromVideo(VideoSoundPart &&part); - AudioPlaybackState getStateForVideo(uint64 playId); + int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); + void videoSoundProgress(const AudioMsgId &audio); void stopAndClear(); @@ -122,6 +123,7 @@ private: uint32 buffers[3] = { 0 }; int64 samplesCount[3] = { 0 }; + uint64 videoPlayId = 0; std_::unique_ptr videoData; private: @@ -147,8 +149,10 @@ private: AudioMsg _songData[AudioSimultaneousLimit]; AudioMsg _videoData; - uint64 _lastVideoPlayId; - AudioPlaybackState _lastVideoPlaybackState; + uint64 _lastVideoPlayId = 0; + uint64 _lastVideoPlaybackWhen = 0; + uint64 _lastVideoPlaybackCorrectedMs = 0; + QMutex _lastVideoMutex; QMutex _mutex; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index 581342300..a45558a8d 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -361,7 +361,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, Setu LOG(("Audio Error: video sound data not ready")); return nullptr; } - _videoLoader = std_::make_unique(std_::move(data->videoData)); + _videoLoader = std_::make_unique(data->videoPlayId, std_::move(data->videoData)); l = _videoLoader.get(); } else { *loader = std_::make_unique(data->file, data->data); diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index f7688934d..c6534720b 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -33,7 +33,8 @@ VideoSoundData::~VideoSoundData() { } } -ChildFFMpegLoader::ChildFFMpegLoader(std_::unique_ptr &&data) : AudioPlayerLoader(FileLocation(), QByteArray()) +ChildFFMpegLoader::ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptr &&data) : AudioPlayerLoader(FileLocation(), QByteArray()) +, _videoPlayId(videoPlayId) , _parentData(std_::move(data)) { _frame = av_frame_alloc(); } diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h index f5873472d..903893c1d 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -32,7 +32,6 @@ extern "C" { #include struct VideoSoundData { - uint64 videoPlayId = 0; AVCodecContext *context = nullptr; int32 frequency = AudioVoiceMsgFrequency; int64 length = 0; @@ -64,7 +63,7 @@ inline void freePacket(AVPacket *packet) { class ChildFFMpegLoader : public AudioPlayerLoader { public: - ChildFFMpegLoader(std_::unique_ptr &&data); + ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptr &&data); bool open(qint64 position = 0) override; @@ -88,7 +87,7 @@ public: void enqueuePackets(QQueue &packets); uint64 playId() const { - return _parentData->videoPlayId; + return _videoPlayId; } bool eofReached() const { return _eofReached; @@ -106,6 +105,7 @@ private: int32 _maxResampleSamples = 1024; uint8_t **_dstSamplesData = nullptr; + uint64 _videoPlayId = 0; std_::unique_ptr _parentData; AVSampleFormat _inputFormat; AVFrame *_frame = nullptr; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 55e2cd1e2..31903856a 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -103,6 +103,7 @@ bool FFMpegReaderImplementation::readNextFrame() { _frameMs = frameMs; _hadFrame = _frameRead = true; + _frameTime += _currentFrameDelay; return true; } @@ -133,6 +134,44 @@ bool FFMpegReaderImplementation::readNextFrame() { return false; } +bool FFMpegReaderImplementation::readFramesTill(int64 ms) { + if (_audioStreamId >= 0) { // sync by audio stream + auto correctMs = audioPlayer()->getVideoCorrectedTime(_playId, ms); + + if (!_frameRead && !readNextFrame()) { + return false; + } + while (_frameTime <= correctMs) { + if (!readNextFrame()) { + return false; + } + } + _frameTimeCorrection = ms - correctMs; + return true; + } else { // just keep up + if (_frameRead && _frameTime > ms) { + return true; + } + if (!readNextFrame()) { + return false; + } + if (_frameTime > ms) { + return true; + } + if (!readNextFrame()) { + return false; + } + if (_frameTime <= ms) { + _frameTime = ms + 5; // keep up + } + return true; + } +} + +uint64 FFMpegReaderImplementation::framePresentationTime() const { + return static_cast(qMax(_frameTime + _frameTimeCorrection, 0LL)); +} + bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { t_assert(_frameRead); _frameRead = false; @@ -184,10 +223,6 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q return true; } -int FFMpegReaderImplementation::nextFrameDelay() { - return _currentFrameDelay; -} - bool FFMpegReaderImplementation::start(Mode mode) { _mode = mode; @@ -276,8 +311,8 @@ bool FFMpegReaderImplementation::start(Mode mode) { } else { soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; } - soundData->videoPlayId = _playId = rand_value(); - audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), 0, std_::move(soundData)); + _playId = rand_value(); + audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), _playId, std_::move(soundData), 0); } return true; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 86f54c506..0bd5d12a6 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -37,9 +37,9 @@ class FFMpegReaderImplementation : public ReaderImplementation { public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data); - bool readNextFrame() override; + bool readFramesTill(int64 ms) override; + uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; - int nextFrameDelay() override; bool start(Mode mode) override; int duration() const; @@ -48,6 +48,8 @@ public: ~FFMpegReaderImplementation(); private: + bool readNextFrame(); + enum class PacketResult { Ok, EndOfFile, @@ -93,6 +95,9 @@ private: int _nextFrameDelay = 0; int _currentFrameDelay = 0; + int64 _frameTime = 0; + int64 _frameTimeCorrection = 0; + }; } // namespace internal diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index c54fc4b7a..e2dd48361 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -28,7 +28,6 @@ namespace internal { class ReaderImplementation { public: - ReaderImplementation(FileLocation *location, QByteArray *data) : _location(location) , _data(data) { @@ -38,9 +37,16 @@ public: Silent, Normal, }; - virtual bool readNextFrame() = 0; + + // Read frames till current frame will have presentation time > ms. + virtual bool readFramesTill(int64 ms) = 0; + + // Get current frame presentation time. + virtual uint64 framePresentationTime() const = 0; + + // Render current frame to an image with specific size. virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; - virtual int nextFrameDelay() = 0; + virtual bool start(Mode mode) = 0; virtual ~ReaderImplementation() { } diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index ff911c972..8117e5e74 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -28,6 +28,29 @@ namespace internal { QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { } +bool QtGifReaderImplementation::readFramesTill(int64 ms) { + if (!_frame.isNull() && _frameTime > ms) { + return true; + } + if (!readNextFrame()) { + return false; + } + if (_frameTime > ms) { + return true; + } + if (!readNextFrame()) { + return false; + } + if (_frameTime <= ms) { + _frameTime = ms + 5; // keep up + } + return true; +} + +uint64 QtGifReaderImplementation::framePresentationTime() const { + return static_cast(qMax(_frameTime, 0LL)); +} + bool QtGifReaderImplementation::readNextFrame() { if (_reader) _frameDelay = _reader->nextImageDelay(); if (_framesLeft < 1 && !jumpToStart()) { @@ -39,6 +62,7 @@ bool QtGifReaderImplementation::readNextFrame() { return false; } --_framesLeft; + _frameTime += _frameDelay; return true; } @@ -66,10 +90,6 @@ bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QS return true; } -int QtGifReaderImplementation::nextFrameDelay() { - return _frameDelay; -} - bool QtGifReaderImplementation::start(Mode mode) { if (mode == Mode::OnlyGifv) return false; return jumpToStart(); diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 910d60e40..78205ded3 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -31,18 +31,20 @@ public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); - bool readNextFrame() override; + bool readFramesTill(int64 ms) override; + uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; - int nextFrameDelay() override; bool start(Mode mode) override; ~QtGifReaderImplementation(); private: bool jumpToStart(); + bool readNextFrame(); QImageReader *_reader = nullptr; int _framesLeft = 0; + int64 _frameTime = 0; int _frameDelay = 0; QImage _frame; diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 500275a5a..09d7d96f0 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -303,7 +303,7 @@ public: return error(); } if (frame() && frame()->original.isNull()) { - if (!_implementation->readNextFrame()) { + if (!_implementation->readFramesTill(-1)) { // Read the first frame. return error(); } if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { @@ -330,34 +330,17 @@ public: } ProcessResult finishProcess(uint64 ms) { - if (!readNextFrame()) { - return error(); - } - if (ms >= _nextFrameWhen && !readNextFrame(true)) { + if (!_implementation->readFramesTill(ms - _animationStarted)) { return error(); } + _nextFrameWhen = _animationStarted + _implementation->framePresentationTime(); + if (!renderFrame()) { return error(); } return ProcessResult::CopyFrame; } - uint64 nextFrameDelay() { - int32 delay = _implementation->nextFrameDelay(); - return qMax(delay, 5); - } - - bool readNextFrame(bool keepup = false) { - if (!_implementation->readNextFrame()) { - return false; - } - _nextFrameWhen += nextFrameDelay(); - if (keepup) { - _nextFrameWhen = qMax(_nextFrameWhen, getms()); - } - return true; - } - bool renderFrame() { t_assert(frame() != 0 && _request.valid()); if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { @@ -394,6 +377,10 @@ public: return _implementation->start(implementationMode()); } + void startedAt(uint64 ms) { + _animationStarted = _nextFrameWhen = ms; + } + ProcessResult error() { stop(); _state = State::Error; @@ -447,6 +434,7 @@ private: int _width = 0; int _height = 0; + uint64 _animationStarted = 0; uint64 _nextFrameWhen = 0; bool _paused = false; @@ -541,7 +529,8 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u if (result == ProcessResult::Started) { _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); } - if (!reader->_paused && result == ProcessResult::Repaint) { + // See if we need to pause GIF because it is not displayed right now. + if (!reader->_paused && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { int32 ishowing, iprevious; Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); @@ -561,7 +550,7 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u frame->original = reader->frame()->original; frame->displayed.storeRelease(0); if (result == ProcessResult::Started) { - reader->_nextFrameWhen = ms; + reader->startedAt(ms); it.key()->moveToNextWrite(); emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); } @@ -701,7 +690,7 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data auto reader = std_::make_unique(&localloc, &localdata); if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv)) { bool hasAlpha = false; - if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { + if (reader->readFramesTill(-1) && reader->renderFrame(cover, hasAlpha, QSize())) { if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { if (hasAlpha) { QImage cacheForResize; From 356b48bccae0f3b30c72484db250d47a79471bc1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 11 Jul 2016 21:05:46 +0300 Subject: [PATCH 14/60] Started video player UI in MediaView. --- Telegram/Resources/basic_types.style | 14 ++ .../Resources/icons/media_fullscreen_from.png | Bin 0 -> 405 bytes .../icons/media_fullscreen_from@2x.png | Bin 0 -> 1847 bytes .../Resources/icons/media_fullscreen_to.png | Bin 0 -> 388 bytes .../icons/media_fullscreen_to@2x.png | Bin 0 -> 1749 bytes Telegram/Resources/icons/media_pause.png | Bin 0 -> 98 bytes Telegram/Resources/icons/media_pause@2x.png | Bin 0 -> 133 bytes Telegram/Resources/icons/media_play.png | Bin 0 -> 281 bytes Telegram/Resources/icons/media_play@2x.png | Bin 0 -> 375 bytes Telegram/Resources/icons/media_volume.png | Bin 0 -> 148 bytes Telegram/Resources/icons/media_volume@2x.png | Bin 0 -> 209 bytes Telegram/SourceFiles/application.cpp | 6 + Telegram/SourceFiles/application.h | 1 + .../SourceFiles/codegen/style/generator.cpp | 31 +++- .../SourceFiles/codegen/style/generator.h | 1 + Telegram/SourceFiles/codegen/style/module.h | 6 +- .../media/view/media_clip_controller.cpp | 116 ++++++++++++++ .../media/view/media_clip_controller.h | 82 ++++++++++ .../media/view/media_clip_playback.cpp | 131 ++++++++++++++++ .../media/view/media_clip_playback.h | 69 +++++++++ .../view/media_clip_volume_controller.cpp | 85 +++++++++++ .../media/view/media_clip_volume_controller.h | 59 ++++++++ .../SourceFiles/media/view/mediaview.style | 74 +++++++++ Telegram/SourceFiles/mediaview.cpp | 143 +++++++++++++++--- Telegram/SourceFiles/mediaview.h | 49 ++++-- Telegram/SourceFiles/ui/animation.h | 1 - .../SourceFiles/ui/buttons/icon_button.cpp | 56 +++++++ Telegram/SourceFiles/ui/buttons/icon_button.h | 51 +++++++ .../SourceFiles/ui/effects/fade_animation.cpp | 99 ++++++++++++ .../SourceFiles/ui/effects/fade_animation.h | 57 +++++++ .../SourceFiles/ui/widgets/label_simple.cpp | 52 +++++++ .../SourceFiles/ui/widgets/label_simple.h | 48 ++++++ Telegram/SourceFiles/ui/widgets/widgets.style | 34 +++++ Telegram/Telegram.vcxproj | 93 ++++++++++++ Telegram/Telegram.vcxproj.filters | 90 +++++++++++ 35 files changed, 1407 insertions(+), 41 deletions(-) create mode 100644 Telegram/Resources/icons/media_fullscreen_from.png create mode 100644 Telegram/Resources/icons/media_fullscreen_from@2x.png create mode 100644 Telegram/Resources/icons/media_fullscreen_to.png create mode 100644 Telegram/Resources/icons/media_fullscreen_to@2x.png create mode 100644 Telegram/Resources/icons/media_pause.png create mode 100644 Telegram/Resources/icons/media_pause@2x.png create mode 100644 Telegram/Resources/icons/media_play.png create mode 100644 Telegram/Resources/icons/media_play@2x.png create mode 100644 Telegram/Resources/icons/media_volume.png create mode 100644 Telegram/Resources/icons/media_volume@2x.png create mode 100644 Telegram/SourceFiles/media/view/media_clip_controller.cpp create mode 100644 Telegram/SourceFiles/media/view/media_clip_controller.h create mode 100644 Telegram/SourceFiles/media/view/media_clip_playback.cpp create mode 100644 Telegram/SourceFiles/media/view/media_clip_playback.h create mode 100644 Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp create mode 100644 Telegram/SourceFiles/media/view/media_clip_volume_controller.h create mode 100644 Telegram/SourceFiles/media/view/mediaview.style create mode 100644 Telegram/SourceFiles/ui/buttons/icon_button.cpp create mode 100644 Telegram/SourceFiles/ui/buttons/icon_button.h create mode 100644 Telegram/SourceFiles/ui/effects/fade_animation.cpp create mode 100644 Telegram/SourceFiles/ui/effects/fade_animation.h create mode 100644 Telegram/SourceFiles/ui/widgets/label_simple.cpp create mode 100644 Telegram/SourceFiles/ui/widgets/label_simple.h create mode 100644 Telegram/SourceFiles/ui/widgets/widgets.style diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index ca5392b13..55b11e353 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -449,3 +449,17 @@ OutlineButton { font: font; padding: margins; } + +IconButton { + width: pixels; + height: pixels; + + opacity: double; + overOpacity: double; + + icon: icon; + iconPosition: point; + downIconPosition: point; + + duration: int; +} diff --git a/Telegram/Resources/icons/media_fullscreen_from.png b/Telegram/Resources/icons/media_fullscreen_from.png new file mode 100644 index 0000000000000000000000000000000000000000..6d241d992ee22bf7b8ed463ec966277bc2c0aa73 GIT binary patch literal 405 zcmV;G0c!q0H7$UZG5oe%_xa5ma z9LL5B{538cHhh#sMBtn|A+BrOh4dAYMcUsARK8-ew?Oh zXxhO`YMO?sssOm%ZaO+T=u?RI0aSXf(IV`^%O(a}+Y!65Q!j6V!{LoF5ym6esK zs!BKWf?io zcJE}zHL{oz`Fl>Mlb)U)a=9EYUc4ZkPVZA;_wVxZa_-)}OJ83fZnvA-+FDE|6QU?m zUS5tUikM6$*4NiBMuCco3OYMGv0ANcZf-I?JF@6+olY|~HANzkDDeE{%a`fy z?#AcyasB#rj7DS8wM0>*qM`y-RnJ#}JS0iNX0z!^Y;A3!sw!{az9kxsA_xMtwY7A0 zb>a1T>FDSvFh1|xJOn{Nk|ZQa+E3s-lu#6fR4N64&1R#mtqnmCP!xsn@o@|W16^HR zJbd_&*49=G2E#sscmJQwW=SLxWV6|eQ9#o)ve_&EvMl5AcqlI~M^#lMNy20@(cj-s zdwYA)u-O}yzkmPc)2B~FqtSy2oRty>8DY2EX>V_*v9S?R6uEZo+CI-8gjg)b?CdPx zzkk0N1&YCFG~#l(aJgJZekPSl5ekJE8yjPCa+3M^`NIjERteaz#z_$hg?Rq_Im5%l z1cSlCWAtxEGMQv$Wd*Mj`^$ifZcBA)~#D)G8uBY9MjX& ztgf!Iv$Lc36K6)?Pp6ckC@0DV`MUqcjT^|ajKN^w<;$0>t*z;u-I*&;ESv;EIIuMS z6Y^DOeSJOk_4OEyMr2uLYHEr|Btj;WxtI`=Wtp2E0qsHTLmX?-?$K$|%#n<%oG|_1EM26?%T@VD4$z(x4 zEm$lTt*NPr`}gnD(a}L^X{lbT=bP-Ko*?;gfk1%Aj~^2V1c3i~+{VU6Y&P483FQA2 zMIjc85s5_h*Ftu?oz~V?WchTFRZ&sF(9n?ndvkM>xw*Mh%~=ehC?ct<`Y4r35ex=d zU0ua$wbI<&e5#3m{rbiD_&CeU%V%Wje(1-jq9`O12@;6}u~-aMRmo&Bw6?Z#<;oRA zQ9SZq$j4>=oGy0nED6A#M3zV-c>Ve{nM{Vk!9nbHJ7%+452?d4?(WI=hT&P)P`-YO(V~(cQYSvBAW|1Xin+nwpwp6VNn`TrT&=F=y3Z z6n)xau~1f4MrmoOo)?Y-P1E!v_`Fe6f#&9B9z1w}*XyOezFs%#I5=wVIPKmjAW&9T zMngjb0|NsL3=GiH(sHcH{vRkH;BvVb92~^w^HEn zJ9qGSJoNST?eqNZ7qz=Ry|S{x!omWIqR`mbNL^jsiR%5cfIKiTfYa&3>2ww~9typi z$z=HO;R8>fK4p7*8?V<(Z*MQPwY3G$pBMHjBKP$4U@#a88jgcJzr4ImAP``9c$krq z5pua4s;VN2B5iGLT)ldgl9CciN=h#51(;k}S*aUa>_b8x*=&|TAi&Vj5Hm9~dZzvO z@gt%rqN*xYRaN>2xsxQo6A@5VwV>nK3r*7qg+hG&`W3(5&#PCjh{a<1{b)4G%*+gu zBw@8$>FVktkw{!zCJu+gn9XJ!4o6V}*=&}%xjCLad&cbStllQ>MmnA5-Me=fjYckA zy2RGj7U^{QBJ4+MYHE5Ei9`@Z5wqEhrfKZ#>=2K~Sy))${rmTfjEpcgHbx{8DVo$Y zO(U615)OyaG>u>|$lTlrvVttjSglr6Rb^vigP%WtGC4WP=;$bK z-n`M9oj*ZQ6xP<(n4g~~8jb3omCh6Lj~_qS+1Vi+4ik^ZF`Lb7Z*LQe#aLWi zL8`(q6ouhSiX~D4g5U-eK`6LTU4;;E4Hk<--GL6pVZpg#`QPMKTB~i+2JZBH2{~6p z1mB6Gh$KnK^SpJW3m_tBt(}n0fGg4q5E0@y=J9y^29#1Lr8Z?0Zq{Hc>EfkBneRzvDs|gflowh{WW~Q-=nogL{Lh-5eb5z75Ib> zheIuVI2=|kQXmop0mZl?yRld-y!WLB{0g7X=T0s22Fl^CRdxoR&nL^}vQ}Ylr0E4|nqrLU ihqV1cZ=?v)6pzT5GSp_BH@26+&BE8xI~lpr@w? zuh)y!Y6U=1l%j1)ecrHa5|kRih>{r1&gR?3TcQ$ zB1}$Bl1`_QWf`~Ijmcy}5QKtlRWt(o5sSrGU0p>{6dVo*cDtQp$Bq@8O8=VxfOtGk zFc?G>MSMOVb#-+B1qmoTW@UugY{ubmV6|HH$y_L;gLzj*Ok$hOMq^_m_wL=Judk1@XV2=-3U%Ni0hNfzsZ*yI7#N_px0m|*`Xj%k@yW)c zq7ks$?KCwtarf?BdV70mX=%y&?mYNer}f$r{ZZr{Fr$n$?AuP6$kP>6+v1r$Z0si}z*Cr%(ql3uYCMJZfBF%j{2 zJox>7{C+=PuQw-ofMhbs+}s>bo;)FuNYK;M!}aUe^|eAnMIumJTT5qWCmkIf8TYf1 zUs+ip5C||lJk0p`IEtc>NF*>A479hmbMD+ZeN4#$a#acVlR|E{J1_FdWRh>+zA-d3 z#LJg2_4ht~`h?MF1fZs-29L*69s&74Q50lZ&bartiMqD7Mj#MiWMqUFFJ2IdMD%CT zXq1_m8U6pIOPBC^y-3<@E~XbhiqL)!N25{J*Vm66pGu_&1Of~V4ILt{As&zO;ll?+ zQKYJ>iZf@my=kpPd$NBN&2gzg-;81U_5f?=fm&-**M+cpqopg0|=>j5=2wtxjlgY%) z%nTbF8^mHUU5KJ6q*5tDp^z@{Ac{Gi5Fb5y^w{BWu)Msihfwp52JN@Y<>Kben+yyL zaN)uQnwy(3o6Sg)gw<-rY&KI{TZ=5qghC;E zse&M={7EbQG8&Dl$Kz2SK76Q7PEM+cL_*DmbULjL4i2hqZEgB^K@jxopzTsG$>;Oo zbUI0;QpmE*yLa!{+1beuSzTRCcXu~^eSNgHwqi1wvN}W*Me6G6^h2wI$YjtN%r`g)tA{L8PtQwVkuw^tFX>Dyq6h&+{8?RoyVt#&JZ%wj^C|4xP r(zLr=E_^;8r%#{8X0w&UWZw7(pQ$nDq_{7y00000NkvXXu0mjfJbXW) literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_pause.png b/Telegram/Resources/icons/media_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae4e1ebb69de0bc1e2f3a32d31a558481f4c75c GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^fA%3x2-L*j>FVdQ&MBb@0Gl=&H~;_u literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_pause@2x.png b/Telegram/Resources/icons/media_pause@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ffba955adad7822958388d65b21a4c71af6e3481 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^NpqRoT>4clgRp4Rw@7%L;Ezn#BPgg&e IbxsLQ0C({#KL7v# literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_play.png b/Telegram/Resources/icons/media_play.png new file mode 100644 index 0000000000000000000000000000000000000000..018ef78e96366e19500683c384501efd5bcd8c4c GIT binary patch literal 281 zcmV+!0p|XRP)*6vpwl2Ac;kh{XdKtzs6NcnPz?6KEEjN3aayB_@bx&}26ZBA>;9?QdZB53l}U zn3+dH2qZ}YBI3_=T>yY#7!U*j55rf-aiFeiIF7@|bnChz&vP@FZk}gEQDg=K0HqYV zu7l@!d`!3RJIbLHnX;c f5QyWLhrIIuPCk#_;6oj+00000NkvXXu0mjf12S~i literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_play@2x.png b/Telegram/Resources/icons/media_play@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..44805a939c74bdca7447dc57fa813ffe0108a18e GIT binary patch literal 375 zcmV--0f_#IP)sl|k+<1LpM@T6Yl6cc`}SGlF`(OQ4!?+pMH5$n3D zX`0k~ZjnF9eSP;BV{T7LZW(|u{FKiH-2+QEl0pbvLp;WazVEqe4_#4o&T&=i5CYd{ zg!21ljN$t5I_FST)$4`MiiHrkmMWBMsE0a|>TC(otvV@}!*uKPEDPYRbGVKqIv;7B VNc6w!cEkVx002ovPDHLkV1gn^tc3so literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_volume.png b/Telegram/Resources/icons/media_volume.png new file mode 100644 index 0000000000000000000000000000000000000000..1b64a994d797e160c2950d833111543ee2f339e6 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_B!3HEHHR8>IRJ5myV~9uR)vFu%4k&QAUi5xk zFT8IKU)h%4W2-(av!1s>p1Lv9>u-aV%bq3tad5Stf@W0`r!TpVGYv{ yr5$ijuze7)Vf(k`P6<}Olx|*Xbhhc6cz4OByC0`zg}wn=!{F)a=d#Wzp$PyTx;icZ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/media_volume@2x.png b/Telegram/Resources/icons/media_volume@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..91c9d60376de8995bed56a1dd9c73195ba9de41d GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^5kM@#!3HGj&RT&uGdx`!LoyoQ-gM+Upupi0n6c;o z)VG^_t$NpXDV@8&Nm2hzfZ)kPXS_CxcSM}KepYD5M(bs(?4Ggw3*;1beK0-CAdqER z-Ha7)8*~i=7dE;lFFM3feZWPR^NpzRQ@ILNpDEf8t{MhjZct8I)W-0>sq+=z9%Hpt zoHJ%TwPP0EtjFPVrcO2KR@s?2g(?=az5~YuZ&--#d+Te>xX{kR&&#{P8t6s_Pgg&e IbxsLQ02V<|761SM literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index f26bbb6e3..62ba8e2d4 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -533,6 +533,12 @@ namespace Sandbox { } } + void removeEventFilter(QObject *filter) { + if (Application *a = application()) { + a->removeEventFilter(filter); + } + } + void execExternal(const QString &cmd) { DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd)); if (cmd == "show") { diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 4b6023131..78983d070 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -114,6 +114,7 @@ namespace Sandbox { bool isSavingSession(); void installEventFilter(QObject *filter); + void removeEventFilter(QObject *filter); void execExternal(const QString &cmd); diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp index 84aa5e57e..286cd7cfc 100644 --- a/Telegram/SourceFiles/codegen/style/generator.cpp +++ b/Telegram/SourceFiles/codegen/style/generator.cpp @@ -327,8 +327,11 @@ bool Generator::writeHeaderStyleNamespace() { header_->stream() << "void init_" << baseName_ << "();\n\n"; header_->popNamespace(); } + bool wroteForwardDeclarations = writeStructsForwardDeclarations(); if (module_.hasStructs()) { - header_->newline(); + if (!wroteForwardDeclarations) { + header_->newline(); + } if (!writeStructsDefinitions()) { return false; } @@ -338,6 +341,32 @@ bool Generator::writeHeaderStyleNamespace() { return true; } +bool Generator::writeStructsForwardDeclarations() { + bool hasNoExternalStructs = module_.enumVariables([this](const Variable &value) -> bool { + if (value.value.type().tag == structure::TypeTag::Struct) { + if (!module_.findStructInModule(value.value.type().name, module_)) { + return false; + } + } + return true; + }); + if (hasNoExternalStructs) { + return false; + } + + header_->newline(); + bool result = module_.enumVariables([this](const Variable &value) -> bool { + if (value.value.type().tag == structure::TypeTag::Struct) { + if (!module_.findStructInModule(value.value.type().name, module_)) { + header_->stream() << "struct " << value.value.type().name.back() << ";\n"; + } + } + return true; + }); + header_->newline(); + return result; +} + bool Generator::writeStructsDefinitions() { if (!module_.hasStructs()) { return true; diff --git a/Telegram/SourceFiles/codegen/style/generator.h b/Telegram/SourceFiles/codegen/style/generator.h index 0c5bf2151..4d7319dae 100644 --- a/Telegram/SourceFiles/codegen/style/generator.h +++ b/Telegram/SourceFiles/codegen/style/generator.h @@ -47,6 +47,7 @@ private: QString valueAssignmentCode(structure::Value value) const; bool writeHeaderStyleNamespace(); + bool writeStructsForwardDeclarations(); bool writeStructsDefinitions(); bool writeRefsDeclarations(); diff --git a/Telegram/SourceFiles/codegen/style/module.h b/Telegram/SourceFiles/codegen/style/module.h index f8a043286..3c2df33ca 100644 --- a/Telegram/SourceFiles/codegen/style/module.h +++ b/Telegram/SourceFiles/codegen/style/module.h @@ -94,6 +94,9 @@ public: return !fullpath_.isEmpty(); } + const Struct *findStructInModule(const FullName &name, const Module &module) const; + const Variable *findVariableInModule(const FullName &name, const Module &module) const; + private: QString fullpath_; std::vector> included_; @@ -102,9 +105,6 @@ private: QMap structsByName_; QMap variablesByName_; - const Struct *findStructInModule(const FullName &name, const Module &module) const; - const Variable *findVariableInModule(const FullName &name, const Module &module) const; - }; } // namespace structure diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp new file mode 100644 index 000000000..f784e7e62 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -0,0 +1,116 @@ +/* +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 "media/view/media_clip_controller.h" + +#include "media/view/media_clip_playback.h" +#include "media/view/media_clip_volume_controller.h" +#include "styles/style_mediaview.h" +#include "ui/widgets/label_simple.h" +#include "ui/effects/fade_animation.h" +#include "ui/buttons/icon_button.h" +#include "media/media_audio.h" + +namespace Media { +namespace Clip { + +Controller::Controller(QWidget *parent) : TWidget(parent) +, _playPauseResume(this, st::mediaviewPlayButton) +, _playback(this) +, _volumeController(this) +, _fullScreenToggle(this, st::mediaviewFullScreenButton) +, _playedAlready(this, st::mediaviewPlayProgressLabel) +, _toPlayLeft(this, st::mediaviewPlayProgressLabel) +, _fadeAnimation(std_::make_unique(this)) { + _fadeAnimation->show(); + connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); + connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); + connect(_playback, SIGNAL(seekProgress(int64)), this, SLOT(onSeekProgress(int64))); + connect(_playback, SIGNAL(seekFinished(int64)), this, SLOT(onSeekFinished(int64))); + connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64))); +} + +void Controller::onSeekProgress(int64 position) { + _seekPosition = position; + emit seekProgress(position); +} + +void Controller::onSeekFinished(int64 position) { + _seekPosition = -1; + emit seekFinished(position); +} + +void Controller::showAnimated() { + _fadeAnimation->fadeIn(st::mvShowDuration); +} + +void Controller::hideAnimated() { + _fadeAnimation->fadeOut(st::mvHideDuration); +} + +void Controller::updatePlayback(const AudioPlaybackState &playbackState) { + bool showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming); + if (showPause != _showPause) { + disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); + _showPause = showPause; + connect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); + + _playPauseResume->setIcon(_showPause ? &st::mediaviewPauseIcon : nullptr); + } + + _playback->updateState(playbackState); +} + +void Controller::setInFullScreen(bool inFullScreen) { + _fullScreenToggle->setIcon(inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr); + disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); + disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(fromFullScreenPressed())); + + auto handler = inFullScreen ? SIGNAL(fromFullScreenPressed()) : SIGNAL(toFullScreenPressed()); + connect(_fullScreenToggle, SIGNAL(clicked()), this, handler); +} + +void Controller::resizeEvent(QResizeEvent *e) { + int playTop = (height() - _playPauseResume->height()) / 2; + _playPauseResume->moveToLeft(playTop, playTop); + _playedAlready->moveToLeft(playTop + _playPauseResume->width() + playTop, 0); + + int fullScreenTop = (height() - _fullScreenToggle->height()) / 2; + _fullScreenToggle->moveToRight(fullScreenTop, fullScreenTop); + _toPlayLeft->moveToRight(fullScreenTop + _fullScreenToggle->width() + fullScreenTop, 0); + + _volumeController->moveToRight(fullScreenTop + _fullScreenToggle->width() + fullScreenTop, (height() - _volumeController->height()) / 2); + _playback->resize(width() - playTop - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - fullScreenTop - _fullScreenToggle->width() - fullScreenTop, _volumeController->height()); + _playback->moveToLeft(playTop + _playPauseResume->width() + playTop, (height() - _playback->height()) / 2); +} + +void Controller::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (_fadeAnimation->paint(p)) { + return; + } + + App::roundRect(p, rect(), st::medviewSaveMsg, MediaviewSaveCorners); +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h new file mode 100644 index 000000000..13bf40dd8 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -0,0 +1,82 @@ +/* +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 Ui { +class LabelSimple; +class FadeAnimation; +class IconButton; +} // namespace Ui + +struct AudioPlaybackState; + +namespace Media { +namespace Clip { + +class Playback; +class VolumeController; + +class Controller : public TWidget { + Q_OBJECT + +public: + Controller(QWidget *parent); + + void showAnimated(); + void hideAnimated(); + + void updatePlayback(const AudioPlaybackState &playbackState); + void setInFullScreen(bool inFullScreen); + +signals: + void playPressed(); + void pausePressed(); + void seekProgress(int64 position); + void seekFinished(int64 position); + void volumeChanged(float64 volume); + void toFullScreenPressed(); + void fromFullScreenPressed(); + +private slots: + void onSeekProgress(int64 position); + void onSeekFinished(int64 position); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + bool _showPause = false; + int64 _seekPosition = -1; + + ChildWidget _playPauseResume; + ChildWidget _playback; + ChildWidget _volumeController; + ChildWidget _fullScreenToggle; + ChildWidget _playedAlready; + ChildWidget _toPlayLeft; + + std_::unique_ptr _fadeAnimation; + +}; + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp new file mode 100644 index 000000000..a80b9b554 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -0,0 +1,131 @@ +/* +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 "media/view/media_clip_playback.h" + +#include "styles/style_mediaview.h" +#include "media/media_audio.h" + +namespace Media { +namespace Clip { + +Playback::Playback(QWidget *parent) : TWidget(parent) +, _a_progress(animation(this, &Playback::step_progress)) { + setCursor(style::cur_pointer); +} + +void Playback::updateState(const AudioPlaybackState &playbackState) { + qint64 position = 0, duration = playbackState.duration; + + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + position = playbackState.position; + } else if (playbackState.state == AudioPlayerStoppedAtEnd) { + position = playbackState.duration; + } else { + position = 0; + } + + float64 progress = 0.; + if (duration) { + progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; + } + if (duration != _duration || position != _position) { + if (duration && _duration) { + a_progress.start(progress); + _a_progress.start(); + } else { + a_progress = anim::fvalue(progress, progress); + _a_progress.stop(); + } + _position = position; + _duration = duration; + } +} + +void Playback::step_progress(float64 ms, bool timer) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + if (_duration && dt >= 1) { + _a_progress.stop(); + a_progress.finish(); + } else { + a_progress.update(qMin(dt, 1.), anim::linear); + } + if (timer) update(); +} + +void Playback::paintEvent(QPaintEvent *e) { + Painter p(this); + + int radius = st::mediaviewPlaybackWidth / 2; + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + auto over = _a_over.current(getms(), _over ? 1. : 0.); + int skip = (st::mediaviewSeekSize.width() / 2); + int length = (width() - st::mediaviewSeekSize.width()); + float64 prg = _mouseDown ? _downProgress : a_progress.current(); + int32 from = skip, mid = qRound(from + prg * length), end = from + length; + if (mid > from) { + p.setClipRect(0, 0, mid, height()); + p.setOpacity(over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity); + p.setBrush(st::mediaviewPlaybackActive); + p.drawRoundedRect(0, (height() - st::mediaviewPlaybackWidth) / 2, mid + radius, st::mediaviewPlaybackWidth, radius, radius); + } + if (end > mid) { + p.setClipRect(mid, 0, width() - mid, height()); + p.setOpacity(1.); + p.setBrush(st::mediaviewPlaybackInactive); + p.drawRoundedRect(mid - radius, (height() - st::mediaviewPlaybackWidth) / 2, width() - (mid - radius), st::mediaviewPlaybackWidth, radius, radius); + } + int x = mid - skip; + p.setClipRect(rect()); + p.setOpacity(over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity); + p.setBrush(st::mediaviewPlaybackActive); + p.drawRoundedRect(x, (height() - st::mediaviewSeekSize.height()) / 2, st::mediaviewSeekSize.width(), st::mediaviewSeekSize.height(), st::mediaviewSeekSize.width() / 2, st::mediaviewSeekSize.width() / 2); +} + +void Playback::mouseMoveEvent(QMouseEvent *e) { +} + +void Playback::mousePressEvent(QMouseEvent *e) { +} + +void Playback::mouseReleaseEvent(QMouseEvent *e) { +} + +void Playback::enterEvent(QEvent *e) { + setOver(true); +} + +void Playback::leaveEvent(QEvent *e) { + setOver(false); +} + +void Playback::setOver(bool over) { + if (_over == over) return; + + _over = over; + auto from = _over ? 0. : 1., to = _over ? 1. : 0.; + START_ANIMATION(_a_over, func(this, &Playback::updateCallback), from, to, st::mediaviewOverDuration, anim::linear); +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h new file mode 100644 index 000000000..d362d770e --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -0,0 +1,69 @@ +/* +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 + +struct AudioPlaybackState; + +namespace Media { +namespace Clip { + +class Playback : public TWidget { + Q_OBJECT + +public: + Playback(QWidget *parent); + + void updateState(const AudioPlaybackState &playbackState); + +signals: + void seekProgress(int64 position); + void seekFinished(int64 position); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + +private: + void step_progress(float64 ms, bool timer); + void updateCallback() { + update(); + } + void setOver(bool over); + + bool _over = false; + FloatAnimation _a_over; + + int64 _position = 0; + int64 _duration = 0; + anim::fvalue a_progress = { 0., 0. }; + Animation _a_progress; + + bool _mouseDown = false; + float64 _downProgress = 0.; + +}; + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp new file mode 100644 index 000000000..57021db79 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp @@ -0,0 +1,85 @@ +/* +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 "media/view/media_clip_volume_controller.h" + +#include "styles/style_mediaview.h" + +namespace Media { +namespace Clip { + +VolumeController::VolumeController(QWidget *parent) : TWidget(parent) { + resize(st::mediaviewVolumeSize); + setCursor(style::cur_pointer); +} + +void VolumeController::setVolume(float64 volume) { + _volume = volume; + update(); +} + +void VolumeController::paintEvent(QPaintEvent *e) { + Painter p(this); + + int32 top = (height() - st::mediaviewVolumeIcon.height()) / 2; + int32 left = (width() - st::mediaviewVolumeIcon.width()) / 2; + int32 mid = left + qRound(st::mediaviewVolumeIcon.width() * _volume); + int32 right = left + st::mediaviewVolumeIcon.width(); + + if (mid > left) { + auto over = _a_over.current(getms(), _over ? 1. : 0.); + p.setOpacity(over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity); + p.setClipRect(rtlrect(left, top, mid - left, st::mediaviewVolumeIcon.height(), width())); + st::mediaviewVolumeOnIcon.paint(p, QPoint(left, top), width()); + } + if (right > mid) { + p.setClipRect(rtlrect(mid, top, right - mid, st::mediaviewVolumeIcon.height(), width())); + st::mediaviewVolumeIcon.paint(p, QPoint(left, top), width()); + } +} + +void VolumeController::mouseMoveEvent(QMouseEvent *e) { +} + +void VolumeController::mousePressEvent(QMouseEvent *e) { +} + +void VolumeController::mouseReleaseEvent(QMouseEvent *e) { +} + +void VolumeController::enterEvent(QEvent *e) { + setOver(true); +} + +void VolumeController::leaveEvent(QEvent *e) { + setOver(false); +} + +void VolumeController::setOver(bool over) { + if (_over == over) return; + + _over = over; + auto from = _over ? 0. : 1., to = _over ? 1. : 0.; + START_ANIMATION(_a_over, func(this, &VolumeController::updateCallback), from, to, st::mediaviewOverDuration, anim::linear); +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_volume_controller.h b/Telegram/SourceFiles/media/view/media_clip_volume_controller.h new file mode 100644 index 000000000..6d891d321 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_clip_volume_controller.h @@ -0,0 +1,59 @@ +/* +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 Media { +namespace Clip { + +class VolumeController : public TWidget { + Q_OBJECT + +public: + VolumeController(QWidget *parent); + + void setVolume(float64 volume); + +signals: + void volumeChanged(float64 volume); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + +private: + void updateCallback() { + update(); + } + void setOver(bool over); + + float64 _volume = 0.; + + bool _over = false; + FloatAnimation _a_over; + +}; + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style new file mode 100644 index 000000000..7540b269b --- /dev/null +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -0,0 +1,74 @@ +/* +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 +*/ + +using "basic.style"; +using "ui/widgets/widgets.style"; + +mediaviewActiveOpacity: 1.; +mediaviewInactiveOpacity: 0.78; +mediaviewOverDuration: 150; + +mediaviewControllerSize: size(600px, 50px); +mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) { + textFg: #ffffffc7; +} +mediaviewPlayButton: IconButton { + width: 25px; + height: 24px; + + opacity: mediaviewInactiveOpacity; + overOpacity: mediaviewActiveOpacity; + + icon: icon { + { "media_play", #ffffff, point(0px, 0px) }, + }; + iconPosition: point(3px, 1px); + downIconPosition: point(3px, 1px); + + duration: mediaviewOverDuration; +} +mediaviewPauseIcon: icon { + { "media_pause", #ffffff, point(1px, 1px) } +}; + +mediaviewFullScreenButton: IconButton(mediaviewPlayButton) { + icon: icon { + { "media_fullscreen_to", #ffffff, point(0px, 0px) }, + }; + iconPosition: point(0px, 0px); + downIconPosition: point(0px, 0px); +} +mediaviewFullScreenOutIcon: icon { + { "media_fullscreen_from", #ffffff, point(0px, 0px) }, +}; + +mediaviewPlaybackActive: #ffffff; +mediaviewPlaybackInactive: #474747; +mediaviewPlaybackWidth: 3px; +mediaviewSeekSize: size(2px, 13px); + +mediaviewVolumeSize: size(44px, 12px); +mediaviewVolumeIcon: icon { + { "media_volume", mediaviewPlaybackInactive, point(0px, 0px) }, +}; +mediaviewVolumeOnIcon: icon { + { "media_volume", mediaviewPlaybackActive, point(0px, 0px) }, +}; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 834da50ce..4134d7503 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -27,6 +27,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "ui/filedialog.h" #include "media/media_clip_reader.h" +#include "media/view/media_clip_controller.h" +#include "styles/style_mediaview.h" namespace { @@ -228,8 +230,9 @@ bool MediaView::gifShown() const { } void MediaView::stopGif() { - delete _gif; _gif = nullptr; + _clipController.destroy(); + Sandbox::removeEventFilter(this); } void MediaView::documentUpdated(DocumentData *doc) { @@ -364,6 +367,7 @@ void MediaView::updateControls() { } else { _leftNavVisible = _rightNavVisible = false; } + if (!_caption.isEmpty()) { int32 skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width()); int32 maxw = qMin(qMax(width() - 2 * skipw - st::mvCaptionPadding.left() - st::mvCaptionPadding.right() - 2 * st::mvCaptionMargin.width(), int(st::msgMinWidth)), _caption.maxWidth()); @@ -372,6 +376,9 @@ void MediaView::updateControls() { } else { _captionRect = QRect(); } + if (_clipController) { + setClipControllerGeometry(); + } updateOver(mapFromGlobal(QCursor::pos())); update(); } @@ -519,7 +526,6 @@ void MediaView::clearData() { } MediaView::~MediaView() { - deleteAndMark(_gif); deleteAndMark(_menu); } @@ -551,6 +557,9 @@ void MediaView::activateControls() { a_cOpacity.start(1); if (!_a_state.animating()) _a_state.start(); } + if (_clipController) { + _clipController->showAnimated(); + } } void MediaView::onHideControls(bool force) { @@ -560,6 +569,9 @@ void MediaView::onHideControls(bool force) { _controlsAnimStarted = getms(); a_cOpacity.start(0); if (!_a_state.animating()) _a_state.start(); + if (_clipController) { + _clipController->hideAnimated(); + } } void MediaView::onDropdownHiding() { @@ -1072,24 +1084,11 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty } else { _doc->automaticLoad(item); - const FileLocation &location(_doc->location(true)); - if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { - if (!_gif) { - if (_doc->dimensions.width() && _doc->dimensions.height()) { - _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); - } - _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); - } - } else if (location.accessEnable()) { - if (_doc->isAnimation() || _doc->isVideo()) { - if (!_gif) { - if (_doc->dimensions.width() && _doc->dimensions.height()) { - _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); - } - auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; - _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback), mode); - } - } else { + if (_doc->isAnimation() || _doc->isVideo()) { + initAnimation(); + } else { + const FileLocation &location(_doc->location(true)); + if (location.accessEnable()) { if (QImageReader(location.name()).canRead()) { _current = QPixmap::fromImage(App::readImage(location.name(), 0, false), Qt::ColorOnly); } @@ -1202,6 +1201,94 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty } } +void MediaView::initAnimation() { + t_assert(_doc != nullptr); + t_assert(_doc->isAnimation() || _doc->isVideo()); + + auto &location = _doc->location(true); + if (!_doc->data().isEmpty()) { + createClipReader(); + } else if (location.accessEnable()) { + createClipReader(); + location.accessDisable(); + } +} + +void MediaView::createClipReader() { + if (_gif) return; + + t_assert(_doc != nullptr); + t_assert(_doc->isAnimation() || _doc->isVideo()); + + if (_doc->dimensions.width() && _doc->dimensions.height()) { + _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); + } + auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; + _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), mode); + + createClipController(); +} + +void MediaView::createClipController() { + if (!_doc->isVideo()) return; + + _clipController.destroy(); + _clipController = new Media::Clip::Controller(this); + setClipControllerGeometry(); + _clipController->show(); + + connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPlay())); + connect(_clipController, SIGNAL(pausePressed()), this, SLOT(onVideoPause())); + connect(_clipController, SIGNAL(seekProgress(int64)), this, SLOT(onVideoSeekProgress(int64))); + connect(_clipController, SIGNAL(seekFinished(int64)), this, SLOT(onVideoSeekFinished(int64))); + connect(_clipController, SIGNAL(volumeChanged(float64)), this, SLOT(onVideoVolumeChanged(float64))); + connect(_clipController, SIGNAL(toFullScreenPressed()), this, SLOT(onVideoToFullScreen())); + connect(_clipController, SIGNAL(fromFullScreenPressed()), this, SLOT(onVideoFromFullScreen())); + + Sandbox::removeEventFilter(this); + Sandbox::installEventFilter(this); +} + +void MediaView::setClipControllerGeometry() { + t_assert(_clipController != nullptr); + + int controllerBottom = _captionRect.isEmpty() ? height() : _captionRect.y(); + _clipController->setGeometry( + (width() - _clipController->width()) / 2, + controllerBottom - _clipController->height() - st::mvCaptionPadding.bottom() - st::mvCaptionMargin.height(), + st::mediaviewControllerSize.width(), + st::mediaviewControllerSize.height()); + myEnsureResized(_clipController); +} + +void MediaView::onVideoPlay() { + +} + +void MediaView::onVideoPause() { + +} + +void MediaView::onVideoSeekProgress(int64 position) { + +} + +void MediaView::onVideoSeekFinished(int64 position) { + +} + +void MediaView::onVideoVolumeChanged(float64 volume) { + +} + +void MediaView::onVideoToFullScreen() { + +} + +void MediaView::onVideoFromFullScreen() { + +} + void MediaView::paintEvent(QPaintEvent *e) { QRect r(e->rect()); QRegion region(e->region()); @@ -1833,9 +1920,6 @@ void MediaView::snapXY() { } void MediaView::mouseMoveEvent(QMouseEvent *e) { - bool moved = (e->pos() != _lastMouseMovePos); - _lastMouseMovePos = e->pos(); - updateOver(e->pos()); if (_lastAction.x() >= 0 && (e->pos() - _lastAction).manhattanLength() >= st::mvDeltaFromLastAction) { _lastAction = QPoint(-st::mvDeltaFromLastAction, -st::mvDeltaFromLastAction); @@ -1858,7 +1942,6 @@ void MediaView::mouseMoveEvent(QMouseEvent *e) { update(); } } - if (moved) activateControls(); } void MediaView::updateOverRect(OverState state) { @@ -2105,6 +2188,18 @@ bool MediaView::event(QEvent *e) { return QWidget::event(e); } +bool MediaView::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::MouseMove && obj->isWidgetType()) { + if (isAncestorOf(static_cast(obj))) { + auto mousePosition = mapFromGlobal(static_cast(e)->globalPos()); + bool moved = (mousePosition != _lastMouseMovePos); + _lastMouseMovePos = mousePosition; + if (moved) activateControls(); + } + } + return TWidget::eventFilter(obj, e); +} + void MediaView::hide() { _controlsHideTimer.stop(); _controlsState = ControlsShown; diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 34c94617d..03ff51eb7 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -22,23 +22,18 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dropdown.h" +namespace Media { +namespace Clip { +class Controller; +} // namespace Clip +} // namespace Media + class MediaView : public TWidget, public RPCSender, public ClickHandlerHost { Q_OBJECT public: MediaView(); - void paintEvent(QPaintEvent *e) override; - - void keyPressEvent(QKeyEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - void touchEvent(QTouchEvent *e); - - bool event(QEvent *e) override; - void hide(); void updateOver(QPoint mpos); @@ -106,12 +101,40 @@ public slots: void updateImage(); +protected: + void paintEvent(QPaintEvent *e) override; + + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + void touchEvent(QTouchEvent *e); + + bool event(QEvent *e) override; + bool eventFilter(QObject *obj, QEvent *e) override; + +private slots: + void onVideoPlay(); + void onVideoPause(); + void onVideoSeekProgress(int64 position); + void onVideoSeekFinished(int64 position); + void onVideoVolumeChanged(float64 volume); + void onVideoToFullScreen(); + void onVideoFromFullScreen(); + private: void displayPhoto(PhotoData *photo, HistoryItem *item); void displayDocument(DocumentData *doc, HistoryItem *item); void findCurrent(); void loadBack(); + void createClipController(); + void setClipControllerGeometry(); + + void initAnimation(); + void createClipReader(); + // Radial animation interface. float64 radialProgress() const; bool radialLoading() const; @@ -154,6 +177,8 @@ private: QString _dateText; QString _headerText; + ChildWidget _clipController = { nullptr }; + Text _caption; QRect _captionRect; @@ -168,7 +193,7 @@ private: bool _pressed = false; int32 _dragging = 0; QPixmap _current; - Media::Clip::Reader *_gif = nullptr; + std_::unique_ptr _gif; int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full bool fileShown() const; diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 3482ef0e9..8fc5201cf 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -486,7 +486,6 @@ using FloatAnimation = SimpleAnimation; using IntAnimation = SimpleAnimation; using ColorAnimation = SimpleAnimation; - // Macro allows us to lazily create updateCallback. #define ENSURE_ANIMATION(animation, updateCallback, from) \ if ((animation).isNull()) { \ diff --git a/Telegram/SourceFiles/ui/buttons/icon_button.cpp b/Telegram/SourceFiles/ui/buttons/icon_button.cpp new file mode 100644 index 000000000..43498c7cc --- /dev/null +++ b/Telegram/SourceFiles/ui/buttons/icon_button.cpp @@ -0,0 +1,56 @@ +/* +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 "ui/buttons/icon_button.h" + +namespace Ui { + +IconButton::IconButton(QWidget *parent, const style::IconButton &st) : Button(parent) +, _st(st) { + resize(_st.width, _st.height); + setCursor(style::cur_pointer); +} + +void IconButton::setIcon(const style::icon *icon) { + _iconOverride = icon; + update(); +} + +void IconButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto over = _a_over.current(getms(), (_state & StateOver) ? 1. : 0.); + p.setOpacity(over * _st.overOpacity + (1. - over) * _st.opacity); + + auto position = (_state & StateDown) ? _st.downIconPosition : _st.iconPosition; + (_iconOverride ? _iconOverride : &_st.icon)->paint(p, position, width()); +} + +void IconButton::onStateChanged(int oldState, ButtonStateChangeSource source) { + auto over = (_state & StateOver); + if (over != (oldState & StateOver)) { + auto from = over ? 0. : 1.; + auto to = over ? 1. : 0.; + START_ANIMATION(_a_over, func(this, &IconButton::updateCallback), from, to, _st.duration, anim::linear); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/buttons/icon_button.h b/Telegram/SourceFiles/ui/buttons/icon_button.h new file mode 100644 index 000000000..758c59567 --- /dev/null +++ b/Telegram/SourceFiles/ui/buttons/icon_button.h @@ -0,0 +1,51 @@ +/* +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 "ui/button.h" + +namespace Ui { + +class IconButton : public Button { +public: + IconButton(QWidget *parent, const style::IconButton &st); + + // Pass nullptr to restore the default icon. + void setIcon(const style::icon *icon); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(int oldState, ButtonStateChangeSource source) override; + +private: + void updateCallback() { + update(); + } + + const style::IconButton &_st; + const style::icon *_iconOverride = nullptr; + + FloatAnimation _a_over; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.cpp b/Telegram/SourceFiles/ui/effects/fade_animation.cpp new file mode 100644 index 000000000..29da4c50d --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/fade_animation.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 "ui/effects/fade_animation.h" + +namespace Ui { + +FadeAnimation::FadeAnimation(TWidget *widget) : _widget(widget) { +} + +bool FadeAnimation::paint(Painter &p) { + if (_cache.isNull()) return false; + + bool animating = _animation.animating(getms()); + + p.setOpacity(_animation.current(_visible ? 1. : 0.)); + p.drawPixmap(0, 0, _cache); + if (!animating) { + stopAnimation(); + } + return true; +} + +void FadeAnimation::setFinishedCallback(FinishedCallback &&callback) { + _finishedCallback = std_::move(callback); +} + +void FadeAnimation::show() { + _visible = true; + stopAnimation(); +} + +void FadeAnimation::hide() { + _visible = false; + stopAnimation(); +} + +void FadeAnimation::stopAnimation() { + _animation.finish(); + if (!_cache.isNull()) { + _cache = QPixmap(); + updateCallback(); + _widget->showChildren(); + _finishedCallback.call(); + } + if (_visible == _widget->isHidden()) { + _widget->setVisible(_visible); + } +} + +void FadeAnimation::fadeIn(int duration) { + if (_visible) return; + + _visible = true; + startAnimation(duration); +} + +void FadeAnimation::fadeOut(int duration) { + if (!_visible) return; + + _visible = false; + startAnimation(duration); +} + +void FadeAnimation::startAnimation(int duration) { + if (_cache.isNull()) { + _cache = myGrab(_widget); + _widget->hideChildren(); + } + START_ANIMATION(_animation, func(this, &FadeAnimation::updateCallback), _visible ? 0. : 1., _visible ? 1. : 0., duration, anim::linear); + updateCallback(); + if (_widget->isHidden()) { + _widget->show(); + } +} + +void FadeAnimation::updateCallback() { + _widget->update(); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.h b/Telegram/SourceFiles/ui/effects/fade_animation.h new file mode 100644 index 000000000..cc5793bef --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/fade_animation.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 + +class TWidget; + +namespace Ui { + +class FadeAnimation { +public: + FadeAnimation(TWidget *widget); + + bool paint(Painter &p); + + using FinishedCallback = Function; + void setFinishedCallback(FinishedCallback &&callback); + + void show(); + void hide(); + + void fadeIn(int duration); + void fadeOut(int duration); + +private: + void startAnimation(int duration); + void stopAnimation(); + + void updateCallback(); + + TWidget *_widget; + FloatAnimation _animation; + QPixmap _cache; + bool _visible = false; + + FinishedCallback _finishedCallback; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/label_simple.cpp b/Telegram/SourceFiles/ui/widgets/label_simple.cpp new file mode 100644 index 000000000..13ae9eb4f --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/label_simple.cpp @@ -0,0 +1,52 @@ +/* +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 "ui/widgets/label_simple.h" + +namespace Ui { + +LabelSimple::LabelSimple(QWidget *parent, const style::LabelSimple &st, const QString &value) : TWidget(parent) +, _st(st) { + setText(value); +} + +void LabelSimple::setText(const QString &value) { + _fullText = value; + _fullTextWidth = _st.font->width(_fullText); + if (!_st.maxWidth || _fullTextWidth <= _st.maxWidth) { + _text = _fullText; + _textWidth = _fullTextWidth; + } else { + _text = _st.font->elided(_fullText, _st.maxWidth); + _textWidth = _st.font->width(_text); + } + resize(_textWidth, _st.font->height); +} + +void LabelSimple::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setFont(_st.font); + p.setPen(_st.textFg); + p.drawTextLeft(0, 0, width(), _text, _textWidth); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/label_simple.h b/Telegram/SourceFiles/ui/widgets/label_simple.h new file mode 100644 index 000000000..6317da344 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/label_simple.h @@ -0,0 +1,48 @@ +/* +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 "styles/style_widgets.h" + +namespace Ui { + +class LabelSimple : public TWidget { +public: + LabelSimple(QWidget *parent, const style::LabelSimple &st = st::defaultLabelSimple, const QString &value = QString()); + + // This method also resizes the label. + void setText(const QString &newText); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + QString _fullText; + int _fullTextWidth; + + QString _text; + int _textWidth; + + const style::LabelSimple &_st; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style new file mode 100644 index 000000000..9b9af8124 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -0,0 +1,34 @@ +/* +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 +*/ + +using "basic.style"; + +LabelSimple { + font: font; + maxWidth: pixels; + textFg: color; +} + +defaultLabelSimple: LabelSimple { + font: normalFont; + maxWidth: 0px; + textFg: windowTextFg; +} diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 42dc178c9..e060ac8f8 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -375,10 +375,22 @@ true true + + true + true + + + true + true + true true + + true + true + true true @@ -713,10 +725,22 @@ true true + + true + true + + + true + true + true true + + true + true + true true @@ -1082,10 +1106,22 @@ true true + + true + true + + + true + true + true true + + true + true + true true @@ -1211,8 +1247,10 @@ + + @@ -1282,6 +1320,9 @@ + + + @@ -1388,10 +1429,12 @@ + + @@ -1416,6 +1459,7 @@ + @@ -1491,8 +1535,10 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -1588,6 +1634,48 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_controller.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_controller.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_controller.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" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_playback.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_playback.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_playback.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_playback.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_playback.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_playback.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" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_volume_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_volume_controller.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_volume_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_volume_controller.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_volume_controller.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/view/media_clip_volume_controller.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" + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -1934,9 +2022,11 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -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" "-fstdafx.h" "-f../../SourceFiles/ui/countryinput.h" + + @@ -2119,6 +2209,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/window/section_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" + @@ -2929,6 +3020,8 @@ true true + + Document "$(SolutionDir)$(Platform)\codegen\$(Configuration)\codegen_style.exe" "-I.\Resources" "-I.\SourceFiles" "-o.\GeneratedFiles\styles" %(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 6993d85e1..247d35253 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -115,6 +115,15 @@ {a281888a-8b70-4e95-9b03-ebcb02837df4} + + {1937d8d7-6dd1-4147-8afb-6f03daff4f0e} + + + {59996402-fc66-481c-9a12-5619112c60a5} + + + {4fdf499d-a1be-46ce-9f17-af007a72e915} + @@ -1389,6 +1398,57 @@ SourceFiles\media + + SourceFiles\ui\widgets + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media\view + + + GeneratedFiles\styles + + + GeneratedFiles\styles + + + SourceFiles\ui\effects + + + SourceFiles\ui\buttons + + + SourceFiles\media\view + + + GeneratedFiles\Deploy + + + SourceFiles\media\view + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + @@ -1658,6 +1718,18 @@ SourceFiles\media + + GeneratedFiles\styles + + + GeneratedFiles\styles + + + SourceFiles\ui\effects + + + SourceFiles\ui\buttons + @@ -1954,6 +2026,18 @@ SourceFiles\media + + SourceFiles\ui\widgets + + + SourceFiles\media\view + + + SourceFiles\media\view + + + SourceFiles\media\view + @@ -2035,6 +2119,12 @@ SourceFiles\profile + + SourceFiles\ui\widgets + + + SourceFiles\media\view + From 01d448c1bdbf20d38d5ba1e51296a9a330af5450 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 12 Jul 2016 14:38:16 +0300 Subject: [PATCH 15/60] Video play progress displayed in MediaView (in case of audio stream). --- Telegram/SourceFiles/media/media_audio.cpp | 8 ++ Telegram/SourceFiles/media/media_audio.h | 7 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 4 +- .../SourceFiles/media/media_clip_ffmpeg.h | 2 +- .../SourceFiles/media/media_clip_reader.cpp | 10 ++- .../SourceFiles/media/media_clip_reader.h | 7 +- .../media/view/media_clip_controller.cpp | 73 +++++++++++++++++-- .../media/view/media_clip_controller.h | 11 +++ .../media/view/media_clip_playback.cpp | 12 ++- .../media/view/media_clip_playback.h | 3 + .../SourceFiles/media/view/mediaview.style | 3 + Telegram/SourceFiles/mediaview.cpp | 21 ++++++ Telegram/SourceFiles/mediaview.h | 1 + .../SourceFiles/ui/effects/fade_animation.cpp | 23 ++++-- .../SourceFiles/ui/effects/fade_animation.h | 5 ++ .../SourceFiles/ui/widgets/label_simple.cpp | 16 +++- .../SourceFiles/ui/widgets/label_simple.h | 2 +- 17 files changed, 178 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 9bc7791c2..3cdee8d4e 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -718,6 +718,14 @@ void AudioPlayer::stopAndClear() { } } +AudioPlaybackState AudioPlayer::currentVideoState(uint64 videoPlayId) { + QMutexLocker lock(&playerMutex); + auto current = dataForType(AudioMsgId::Type::Video); + if (!current || current->videoPlayId != videoPlayId) return AudioPlaybackState(); + + return current->playbackState; +} + AudioPlaybackState AudioPlayer::currentState(AudioMsgId *audio, AudioMsgId::Type type) { QMutexLocker lock(&playerMutex); auto current = dataForType(type); diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 38b251a88..529e730ce 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -71,6 +71,7 @@ public: void feedFromVideo(VideoSoundPart &&part); int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); void videoSoundProgress(const AudioMsgId &audio); + AudioPlaybackState currentVideoState(uint64 videoPlayId); void stopAndClear(); @@ -215,12 +216,10 @@ class AudioPlayerFader : public QObject { Q_OBJECT public: - AudioPlayerFader(QThread *thread); void resumeDevice(); signals: - void error(const AudioMsgId &audio); void playPositionUpdated(const AudioMsgId &audio); void audioStopped(const AudioMsgId &audio); @@ -228,8 +227,7 @@ signals: void stopPauseDevice(); - public slots: - +public slots: void onInit(); void onTimer(); void onPauseTimer(); @@ -241,7 +239,6 @@ signals: void onSongVolumeChanged(); private: - enum { EmitError = 0x01, EmitStopped = 0x02, diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 31903856a..749fe795c 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -28,7 +28,8 @@ namespace Media { namespace Clip { namespace internal { -FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { +FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId) : ReaderImplementation(location, data) +, _playId(playId) { _frame = av_frame_alloc(); av_init_packet(&_packetNull); _packetNull.data = nullptr; @@ -311,7 +312,6 @@ bool FFMpegReaderImplementation::start(Mode mode) { } else { soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; } - _playId = rand_value(); audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), _playId, std_::move(soundData), 0); } diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 0bd5d12a6..ed47711b7 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -35,7 +35,7 @@ namespace internal { class FFMpegReaderImplementation : public ReaderImplementation { public: - FFMpegReaderImplementation(FileLocation *location, QByteArray *data); + FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId); bool readFramesTill(int64 ms) override; uint64 framePresentationTime() const override; diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index c5998e137..f750dcd11 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -87,7 +87,8 @@ QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode) : _callback(std_::move(callback)) -, _mode(mode) { +, _mode(mode) +, _playId(rand_value()) { if (threads.size() < ClipThreadsCount) { _threadIndex = threads.size(); threads.push_back(new QThread()); @@ -289,6 +290,7 @@ class ReaderPrivate { public: ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) , _mode(reader->mode()) + , _playId(reader->playId()) , _data(data) , _location(_data.isEmpty() ? new FileLocation(location) : 0) { if (_data.isEmpty() && !_location->accessEnable()) { @@ -364,7 +366,7 @@ public: } } - _implementation = std_::make_unique(_location, &_data); + _implementation = std_::make_unique(_location, &_data, _playId); // _implementation = new QtGifReaderImplementation(_location, &_data); auto implementationMode = [this]() { @@ -410,6 +412,7 @@ private: Reader *_interface; State _state = State::Reading; Reader::Mode _mode; + uint64 _playId; QByteArray _data; FileLocation *_location; @@ -687,7 +690,8 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data FileLocation localloc(StorageFilePartial, fname); QByteArray localdata(data); - auto reader = std_::make_unique(&localloc, &localdata); + auto playId = 0ULL; + auto reader = std_::make_unique(&localloc, &localdata, playId); if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv)) { bool hasAlpha = false; if (reader->readFramesTill(-1) && reader->renderFrame(cover, hasAlpha, QSize())) { diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 43b2768ea..85ee447cc 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -51,7 +51,6 @@ enum ReaderSteps { class ReaderPrivate; class Reader { public: - using Callback = Function; enum class Mode { Gif, @@ -68,6 +67,10 @@ public: return _autoplay; } + uint64 playId() const { + return _playId; + } + void start(int framew, int frameh, int outerw, int outerh, bool rounded); QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); QPixmap frameOriginal() const { @@ -114,6 +117,8 @@ private: State _state = State::Reading; + uint64 _playId; + mutable int _width = 0; mutable int _height = 0; diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index f784e7e62..4a89e0f6c 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -41,6 +41,8 @@ Controller::Controller(QWidget *parent) : TWidget(parent) , _toPlayLeft(this, st::mediaviewPlayProgressLabel) , _fadeAnimation(std_::make_unique(this)) { _fadeAnimation->show(); + _fadeAnimation->setFinishedCallback(func(this, &Controller::fadeFinished)); + _fadeAnimation->setUpdatedCallback(func(this, &Controller::fadeUpdated)); connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); connect(_playback, SIGNAL(seekProgress(int64)), this, SLOT(onSeekProgress(int64))); @@ -59,14 +61,38 @@ void Controller::onSeekFinished(int64 position) { } void Controller::showAnimated() { - _fadeAnimation->fadeIn(st::mvShowDuration); + startFading([this]() { + _fadeAnimation->fadeIn(st::mvShowDuration); + }); } void Controller::hideAnimated() { - _fadeAnimation->fadeOut(st::mvHideDuration); + startFading([this]() { + _fadeAnimation->fadeOut(st::mvShowDuration); + }); +} + +template +void Controller::startFading(Callback start) { + start(); + _playback->show(); +} + +void Controller::fadeFinished() { + fadeUpdated(1.); +} + +void Controller::fadeUpdated(float64 opacity) { + _playback->setFadeOpacity(opacity); } void Controller::updatePlayback(const AudioPlaybackState &playbackState) { + updatePlayPauseResumeState(playbackState); + _playback->updateState(playbackState); + updateTimeTexts(playbackState); +} + +void Controller::updatePlayPauseResumeState(const AudioPlaybackState &playbackState) { bool showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming); if (showPause != _showPause) { disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); @@ -75,8 +101,32 @@ void Controller::updatePlayback(const AudioPlaybackState &playbackState) { _playPauseResume->setIcon(_showPause ? &st::mediaviewPauseIcon : nullptr); } +} - _playback->updateState(playbackState); +void Controller::updateTimeTexts(const AudioPlaybackState &playbackState) { + qint64 position = 0, duration = playbackState.duration; + + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + position = playbackState.position; + } else if (playbackState.state == AudioPlayerStoppedAtEnd) { + position = playbackState.duration; + } else { + position = 0; + } + auto playFrequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + auto playAlready = position / playFrequency; + auto playLeft = (playbackState.duration / playFrequency) - playAlready; + + auto timeAlready = formatDurationText(playAlready); + auto minus = QChar(8722); + auto timeLeft = minus + formatDurationText(playLeft); + + auto alreadyChanged = false, leftChanged = false; + _playedAlready->setText(timeAlready, &alreadyChanged); + _toPlayLeft->setText(timeLeft, &leftChanged); + if (alreadyChanged || leftChanged) { + _fadeAnimation->refreshCache(); + } } void Controller::setInFullScreen(bool inFullScreen) { @@ -88,18 +138,29 @@ void Controller::setInFullScreen(bool inFullScreen) { connect(_fullScreenToggle, SIGNAL(clicked()), this, handler); } +void Controller::grabStart() { + showChildren(); + _playback->hide(); +} + +void Controller::grabFinish() { + hideChildren(); + _playback->show(); +} + void Controller::resizeEvent(QResizeEvent *e) { int playTop = (height() - _playPauseResume->height()) / 2; _playPauseResume->moveToLeft(playTop, playTop); - _playedAlready->moveToLeft(playTop + _playPauseResume->width() + playTop, 0); int fullScreenTop = (height() - _fullScreenToggle->height()) / 2; _fullScreenToggle->moveToRight(fullScreenTop, fullScreenTop); - _toPlayLeft->moveToRight(fullScreenTop + _fullScreenToggle->width() + fullScreenTop, 0); _volumeController->moveToRight(fullScreenTop + _fullScreenToggle->width() + fullScreenTop, (height() - _volumeController->height()) / 2); _playback->resize(width() - playTop - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - fullScreenTop - _fullScreenToggle->width() - fullScreenTop, _volumeController->height()); - _playback->moveToLeft(playTop + _playPauseResume->width() + playTop, (height() - _playback->height()) / 2); + _playback->moveToLeft(playTop + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); + + _playedAlready->moveToLeft(playTop + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); + _toPlayLeft->moveToRight(width() - (playTop + _playPauseResume->width() + playTop) - _playback->width(), st::mediaviewPlayProgressTop); } void Controller::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h index 13bf40dd8..3cf6016f3 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -46,6 +46,9 @@ public: void updatePlayback(const AudioPlaybackState &playbackState); void setInFullScreen(bool inFullScreen); + void grabStart() override; + void grabFinish() override; + signals: void playPressed(); void pausePressed(); @@ -64,6 +67,14 @@ protected: void paintEvent(QPaintEvent *e) override; private: + template + void startFading(Callback start); + void fadeFinished(); + void fadeUpdated(float64 opacity); + + void updatePlayPauseResumeState(const AudioPlaybackState &playbackState); + void updateTimeTexts(const AudioPlaybackState &playbackState); + bool _showPause = false; int64 _seekPosition = -1; diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index a80b9b554..29ca25924 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -60,6 +60,11 @@ void Playback::updateState(const AudioPlaybackState &playbackState) { } } +void Playback::setFadeOpacity(float64 opacity) { + _fadeOpacity = opacity; + update(); +} + void Playback::step_progress(float64 ms, bool timer) { float64 dt = ms / (2 * AudioVoiceMsgUpdateView); if (_duration && dt >= 1) { @@ -75,6 +80,7 @@ void Playback::paintEvent(QPaintEvent *e) { Painter p(this); int radius = st::mediaviewPlaybackWidth / 2; + p.setOpacity(_fadeOpacity); p.setPen(Qt::NoPen); p.setRenderHint(QPainter::HighQualityAntialiasing); @@ -85,19 +91,19 @@ void Playback::paintEvent(QPaintEvent *e) { int32 from = skip, mid = qRound(from + prg * length), end = from + length; if (mid > from) { p.setClipRect(0, 0, mid, height()); - p.setOpacity(over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity); + p.setOpacity(_fadeOpacity * (over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity)); p.setBrush(st::mediaviewPlaybackActive); p.drawRoundedRect(0, (height() - st::mediaviewPlaybackWidth) / 2, mid + radius, st::mediaviewPlaybackWidth, radius, radius); } if (end > mid) { p.setClipRect(mid, 0, width() - mid, height()); - p.setOpacity(1.); + p.setOpacity(_fadeOpacity); p.setBrush(st::mediaviewPlaybackInactive); p.drawRoundedRect(mid - radius, (height() - st::mediaviewPlaybackWidth) / 2, width() - (mid - radius), st::mediaviewPlaybackWidth, radius, radius); } int x = mid - skip; p.setClipRect(rect()); - p.setOpacity(over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity); + p.setOpacity(_fadeOpacity * (over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity)); p.setBrush(st::mediaviewPlaybackActive); p.drawRoundedRect(x, (height() - st::mediaviewSeekSize.height()) / 2, st::mediaviewSeekSize.width(), st::mediaviewSeekSize.height(), st::mediaviewSeekSize.width() / 2, st::mediaviewSeekSize.width() / 2); } diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h index d362d770e..a74d0ee83 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.h +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -32,6 +32,7 @@ public: Playback(QWidget *parent); void updateState(const AudioPlaybackState &playbackState); + void setFadeOpacity(float64 opacity); signals: void seekProgress(int64 position); @@ -63,6 +64,8 @@ private: bool _mouseDown = false; float64 _downProgress = 0.; + float64 _fadeOpacity = 1.; + }; } // namespace Clip diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index 7540b269b..98e48c6e1 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -28,8 +28,10 @@ mediaviewOverDuration: 150; mediaviewControllerSize: size(600px, 50px); mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) { + font: semiboldFont; textFg: #ffffffc7; } +mediaviewPlayProgressTop: 8px; mediaviewPlayButton: IconButton { width: 25px; height: 24px; @@ -63,6 +65,7 @@ mediaviewFullScreenOutIcon: icon { mediaviewPlaybackActive: #ffffff; mediaviewPlaybackInactive: #474747; mediaviewPlaybackWidth: 3px; +mediaviewPlaybackTop: 23px; mediaviewSeekSize: size(2px, 13px); mediaviewVolumeSize: size(44px, 12px); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 4134d7503..925d9d769 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -29,6 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/media_clip_reader.h" #include "media/view/media_clip_controller.h" #include "styles/style_mediaview.h" +#include "media/media_audio.h" namespace { @@ -233,6 +234,9 @@ void MediaView::stopGif() { _gif = nullptr; _clipController.destroy(); Sandbox::removeEventFilter(this); + if (audioPlayer()) { + disconnect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); + } } void MediaView::documentUpdated(DocumentData *doc) { @@ -1247,6 +1251,10 @@ void MediaView::createClipController() { Sandbox::removeEventFilter(this); Sandbox::installEventFilter(this); + + if (audioPlayer()) { + connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); + } } void MediaView::setClipControllerGeometry() { @@ -1289,6 +1297,19 @@ void MediaView::onVideoFromFullScreen() { } +void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { + if (audioId.type() != AudioMsgId::Type::Video) { + return; + } + + t_assert(_gif != nullptr); + t_assert(audioPlayer() != nullptr); + auto state = audioPlayer()->currentVideoState(_gif->playId()); + if (state.frequency) { + _clipController->updatePlayback(state); + } +} + void MediaView::paintEvent(QPaintEvent *e) { QRect r(e->rect()); QRegion region(e->region()); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 03ff51eb7..9ff96395c 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -122,6 +122,7 @@ private slots: void onVideoVolumeChanged(float64 volume); void onVideoToFullScreen(); void onVideoFromFullScreen(); + void onVideoPlayProgress(const AudioMsgId &audioId); private: void displayPhoto(PhotoData *photo, HistoryItem *item); diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.cpp b/Telegram/SourceFiles/ui/effects/fade_animation.cpp index 29da4c50d..9936c51e5 100644 --- a/Telegram/SourceFiles/ui/effects/fade_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/fade_animation.cpp @@ -29,20 +29,26 @@ FadeAnimation::FadeAnimation(TWidget *widget) : _widget(widget) { bool FadeAnimation::paint(Painter &p) { if (_cache.isNull()) return false; - bool animating = _animation.animating(getms()); - p.setOpacity(_animation.current(_visible ? 1. : 0.)); p.drawPixmap(0, 0, _cache); - if (!animating) { - stopAnimation(); - } return true; } +void FadeAnimation::refreshCache() { + if (!_cache.isNull()) { + _cache = QPixmap(); + _cache = myGrab(_widget); + } +} + void FadeAnimation::setFinishedCallback(FinishedCallback &&callback) { _finishedCallback = std_::move(callback); } +void FadeAnimation::setUpdatedCallback(UpdatedCallback &&callback) { + _updatedCallback = std_::move(callback); +} + void FadeAnimation::show() { _visible = true; stopAnimation(); @@ -93,7 +99,12 @@ void FadeAnimation::startAnimation(int duration) { } void FadeAnimation::updateCallback() { - _widget->update(); + if (_animation.animating(getms())) { + _widget->update(); + _updatedCallback.call(_animation.current()); + } else { + stopAnimation(); + } } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.h b/Telegram/SourceFiles/ui/effects/fade_animation.h index cc5793bef..aaf95129f 100644 --- a/Telegram/SourceFiles/ui/effects/fade_animation.h +++ b/Telegram/SourceFiles/ui/effects/fade_animation.h @@ -29,10 +29,14 @@ public: FadeAnimation(TWidget *widget); bool paint(Painter &p); + void refreshCache(); using FinishedCallback = Function; void setFinishedCallback(FinishedCallback &&callback); + using UpdatedCallback = Function; + void setUpdatedCallback(UpdatedCallback &&callback); + void show(); void hide(); @@ -51,6 +55,7 @@ private: bool _visible = false; FinishedCallback _finishedCallback; + UpdatedCallback _updatedCallback; }; diff --git a/Telegram/SourceFiles/ui/widgets/label_simple.cpp b/Telegram/SourceFiles/ui/widgets/label_simple.cpp index 13ae9eb4f..f7c802ad2 100644 --- a/Telegram/SourceFiles/ui/widgets/label_simple.cpp +++ b/Telegram/SourceFiles/ui/widgets/label_simple.cpp @@ -28,17 +28,29 @@ LabelSimple::LabelSimple(QWidget *parent, const style::LabelSimple &st, const QS setText(value); } -void LabelSimple::setText(const QString &value) { +void LabelSimple::setText(const QString &value, bool *outTextChanged) { + if (_fullText == value) { + if (outTextChanged) *outTextChanged = false; + return; + } + _fullText = value; _fullTextWidth = _st.font->width(_fullText); if (!_st.maxWidth || _fullTextWidth <= _st.maxWidth) { _text = _fullText; _textWidth = _fullTextWidth; } else { - _text = _st.font->elided(_fullText, _st.maxWidth); + auto newText = _st.font->elided(_fullText, _st.maxWidth); + if (newText == _text) { + if (outTextChanged) *outTextChanged = false; + return; + } + _text = newText; _textWidth = _st.font->width(_text); } resize(_textWidth, _st.font->height); + update(); + if (outTextChanged) *outTextChanged = true; } void LabelSimple::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/ui/widgets/label_simple.h b/Telegram/SourceFiles/ui/widgets/label_simple.h index 6317da344..a1b823705 100644 --- a/Telegram/SourceFiles/ui/widgets/label_simple.h +++ b/Telegram/SourceFiles/ui/widgets/label_simple.h @@ -29,7 +29,7 @@ public: LabelSimple(QWidget *parent, const style::LabelSimple &st = st::defaultLabelSimple, const QString &value = QString()); // This method also resizes the label. - void setText(const QString &newText); + void setText(const QString &newText, bool *outTextChanged = nullptr); protected: void paintEvent(QPaintEvent *e) override; From 034657dd2c4c3bf7e5f91055bb11853008028844 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 12 Jul 2016 15:28:07 +0300 Subject: [PATCH 16/60] Video play progress displayed in MediaView (in case no audio stream). --- .../SourceFiles/media/media_clip_ffmpeg.cpp | 14 +++++--- .../SourceFiles/media/media_clip_ffmpeg.h | 3 +- .../media/media_clip_implementation.h | 5 ++- .../SourceFiles/media/media_clip_qtgif.cpp | 9 +++++ Telegram/SourceFiles/media/media_clip_qtgif.h | 3 ++ .../SourceFiles/media/media_clip_reader.cpp | 33 ++++++++++++++--- .../SourceFiles/media/media_clip_reader.h | 14 ++++++-- .../media/view/media_clip_playback.cpp | 4 ++- Telegram/SourceFiles/mediaview.cpp | 36 ++++++++++++++++++- Telegram/SourceFiles/mediaview.h | 12 +++++++ 10 files changed, 117 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 749fe795c..c5744a40a 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -169,10 +169,19 @@ bool FFMpegReaderImplementation::readFramesTill(int64 ms) { } } +int64 FFMpegReaderImplementation::frameRealTime() const { + return _frameMs; +} + uint64 FFMpegReaderImplementation::framePresentationTime() const { return static_cast(qMax(_frameTime + _frameTimeCorrection, 0LL)); } +int64 FFMpegReaderImplementation::durationMs() const { + if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; + return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; +} + bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { t_assert(_frameRead); _frameRead = false; @@ -322,11 +331,6 @@ QString FFMpegReaderImplementation::logData() const { return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); } -int FFMpegReaderImplementation::duration() const { - if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; - return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; -} - FFMpegReaderImplementation::~FFMpegReaderImplementation() { if (_mode == Mode::Normal && _audioStreamId >= 0) { audioPlayer()->stop(AudioMsgId::Type::Video); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index ed47711b7..639ebb034 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -38,11 +38,12 @@ public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId); bool readFramesTill(int64 ms) override; + int64 frameRealTime() const override; uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int64 durationMs() const override; bool start(Mode mode) override; - int duration() const; QString logData() const; ~FFMpegReaderImplementation(); diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index e2dd48361..072522325 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -41,12 +41,15 @@ public: // Read frames till current frame will have presentation time > ms. virtual bool readFramesTill(int64 ms) = 0; - // Get current frame presentation time. + // Get current frame real and presentation time. + virtual int64 frameRealTime() const = 0; virtual uint64 framePresentationTime() const = 0; // Render current frame to an image with specific size. virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; + virtual int64 durationMs() const = 0; + virtual bool start(Mode mode) = 0; virtual ~ReaderImplementation() { } diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index 8117e5e74..8cb0f1cf4 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -47,6 +47,10 @@ bool QtGifReaderImplementation::readFramesTill(int64 ms) { return true; } +int64 QtGifReaderImplementation::frameRealTime() const { + return _frameRealTime; +} + uint64 QtGifReaderImplementation::framePresentationTime() const { return static_cast(qMax(_frameTime, 0LL)); } @@ -63,6 +67,7 @@ bool QtGifReaderImplementation::readNextFrame() { } --_framesLeft; _frameTime += _frameDelay; + _frameRealTime += _frameDelay; return true; } @@ -90,6 +95,10 @@ bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QS return true; } +int64 QtGifReaderImplementation::durationMs() const { + return 0; // not supported +} + bool QtGifReaderImplementation::start(Mode mode) { if (mode == Mode::OnlyGifv) return false; return jumpToStart(); diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 78205ded3..b03da95c5 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -32,8 +32,10 @@ public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); bool readFramesTill(int64 ms) override; + int64 frameRealTime() const override; uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int64 durationMs() const override; bool start(Mode mode) override; ~QtGifReaderImplementation(); @@ -44,6 +46,7 @@ private: QImageReader *_reader = nullptr; int _framesLeft = 0; + int64 _frameRealTime = 0; int64 _frameTime = 0; int _frameDelay = 0; QImage _frame; diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index f750dcd11..7630b9325 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -112,7 +112,7 @@ Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready int32 step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep) { if (index) *index = 0; - return 0; + return nullptr; } else if (step == WaitingForRequestStep) { i = 0; } else if (step == WaitingForFirstFrameStep) { @@ -130,7 +130,7 @@ Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready i = 0; } else if (step == WaitingForRequestStep) { if (index) *index = 0; - return 0; + return nullptr; } else if (step == WaitingForFirstFrameStep) { i = 0; } else { @@ -144,7 +144,7 @@ Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) cons int32 step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { if (index) *index = 0; - return 0; + return nullptr; } i = ((step + 4) / 2) % 3; if (index) *index = i; @@ -258,6 +258,21 @@ bool Reader::ready() const { return false; } +bool Reader::hasAudio() const { + return ready() ? _hasAudio : false; +} + +int64 Reader::getPositionMs() const { + if (auto frame = frameToShow()) { + return frame->positionMs; + } + return 0; +} + +int64 Reader::getDurationMs() const { + return ready() ? _durationMs : 0; +} + int32 Reader::width() const { return _width; } @@ -313,6 +328,7 @@ public: } _width = frame()->original.width(); _height = frame()->original.height(); + _durationMs = _implementation->durationMs(); return ProcessResult::Started; } return ProcessResult::Wait; @@ -335,6 +351,7 @@ public: if (!_implementation->readFramesTill(ms - _animationStarted)) { return error(); } + _nextFramePositionMs = _implementation->frameRealTime(); _nextFrameWhen = _animationStarted + _implementation->framePresentationTime(); if (!renderFrame()) { @@ -352,6 +369,7 @@ public: frame()->pix = QPixmap(); frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); frame()->when = _nextFrameWhen; + frame()->positionMs = _nextFramePositionMs; return true; } @@ -427,6 +445,9 @@ private: QImage original, cache; bool alpha = true; uint64 when = 0; + + // Counted from the end, so that positionMs <= durationMs despite keep up delays. + int64 positionMs = 0; }; Frame _frames[3]; int _frame = 0; @@ -437,8 +458,10 @@ private: int _width = 0; int _height = 0; + int64 _durationMs = 0; uint64 _animationStarted = 0; uint64 _nextFrameWhen = 0; + int64 _nextFramePositionMs = 0; bool _paused = false; @@ -531,6 +554,7 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u if (result == ProcessResult::Started) { _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); + it.key()->_durationMs = reader->_durationMs; } // See if we need to pause GIF because it is not displayed right now. if (!reader->_paused && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { @@ -552,6 +576,7 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u frame->pix = reader->frame()->pix; frame->original = reader->frame()->original; frame->displayed.storeRelease(0); + frame->positionMs = reader->frame()->positionMs; if (result == ProcessResult::Started) { reader->startedAt(ms); it.key()->moveToNextWrite(); @@ -704,7 +729,7 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data request.factor = 1; cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); } - int duration = reader->duration(); + int duration = reader->durationMs() / 1000; return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); } } diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 85ee447cc..31336a59d 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -101,6 +101,10 @@ public: } bool ready() const; + bool hasAudio() const; + int64 getPositionMs() const; + int64 getDurationMs() const; + void stop(); void error(); @@ -118,6 +122,8 @@ private: State _state = State::Reading; uint64 _playId; + bool _hasAudio = false; + int64 _durationMs = 0; mutable int _width = 0; mutable int _height = 0; @@ -125,8 +131,6 @@ private: // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 mutable QAtomicInt _step = WaitingForDimensionsStep; struct Frame { - Frame() : displayed(false) { - } void clear() { pix = QPixmap(); original = QImage(); @@ -134,7 +138,11 @@ private: QPixmap pix; QImage original; FrameRequest request; - QAtomicInt displayed; + QAtomicInt displayed = 0; + + // Should be counted from the end, + // so that positionMs <= _durationMs. + int64 positionMs = 0; }; mutable Frame _frames[3]; Frame *frameToShow(int *index = 0) const; // 0 means not ready diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 29ca25924..14f93c9e8 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -44,7 +44,9 @@ void Playback::updateState(const AudioPlaybackState &playbackState) { } float64 progress = 0.; - if (duration) { + if (position > duration) { + progress = 1.; + } else if (duration) { progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; } if (duration != _duration || position != _position) { diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 925d9d769..7e5aee2f6 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -701,10 +701,16 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { switch (notification) { case NotificationReinit: { - if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { + if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { if (_gif->state() == State::Error) { _current = QPixmap(); } + _videoIsSilent = _doc->isVideo() && !_gif->hasAudio(); + if (_videoIsSilent) { + _videoDurationMs = _gif->getDurationMs(); + _videoPositionMs = _gif->getPositionMs(); + updateSilentVideoPlaybackState(); + } displayDocument(_doc, item); } else { stopGif(); @@ -713,6 +719,10 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { case NotificationRepaint: { if (!_gif->currentDisplayed()) { + if (_videoIsSilent) { + _videoPositionMs = _gif->getPositionMs(); + updateSilentVideoPlaybackState(); + } update(_x, _y, _w, _h); } } break; @@ -1230,6 +1240,11 @@ void MediaView::createClipReader() { auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), mode); + // Correct values will be set when gif gets inited. + _videoIsSilent = false; + _videoPositionMs = 0ULL; + _videoDurationMs = _doc->duration() * 1000ULL; + createClipController(); } @@ -1305,11 +1320,30 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { t_assert(_gif != nullptr); t_assert(audioPlayer() != nullptr); auto state = audioPlayer()->currentVideoState(_gif->playId()); + updateVideoPlaybackState(state); +} + +void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) { if (state.frequency) { _clipController->updatePlayback(state); } } +void MediaView::updateSilentVideoPlaybackState() { + AudioPlaybackState state; + if (_videoPaused) { + state.state = AudioPlayerPaused; + } else if (_videoPositionMs == _videoDurationMs) { + state.state = AudioPlayerStoppedAtEnd; + } else { + state.state = AudioPlayerPlaying; + } + state.position = _videoPositionMs; + state.duration = _videoDurationMs; + state.frequency = _videoFrequencyMs; + updateVideoPlaybackState(state); +} + void MediaView::paintEvent(QPaintEvent *e) { QRect r(e->rect()); QRegion region(e->region()); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 9ff96395c..1e696c147 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -28,6 +28,8 @@ class Controller; } // namespace Clip } // namespace Media +struct AudioPlaybackState; + class MediaView : public TWidget, public RPCSender, public ClickHandlerHost { Q_OBJECT @@ -130,6 +132,9 @@ private: void findCurrent(); void loadBack(); + void updateVideoPlaybackState(const AudioPlaybackState &state); + void updateSilentVideoPlaybackState(); + void createClipController(); void setClipControllerGeometry(); @@ -197,6 +202,13 @@ private: std_::unique_ptr _gif; int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full + // Video without audio stream playback information. + bool _videoIsSilent = false; + bool _videoPaused = false; + int64 _videoPositionMs = 0; + int64 _videoDurationMs = 0; + int32 _videoFrequencyMs = 1000; // 1000 ms per second. + bool fileShown() const; bool gifShown() const; void stopGif(); From 8da39356dc33bb75caf534eac1e9b5b275c361ed Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 12 Jul 2016 17:11:59 +0300 Subject: [PATCH 17/60] Divided song volume and video volume, video volume control implemented. --- Telegram/SourceFiles/facades.cpp | 6 +++++ Telegram/SourceFiles/facades.h | 3 +++ Telegram/SourceFiles/localstorage.cpp | 16 +++++++++--- Telegram/SourceFiles/media/media_audio.cpp | 18 +++++++++---- Telegram/SourceFiles/media/media_audio.h | 4 ++- .../SourceFiles/media/media_audio_loaders.cpp | 4 +-- .../media/view/media_clip_controller.cpp | 7 +++++ .../media/view/media_clip_controller.h | 1 + .../view/media_clip_volume_controller.cpp | 22 ++++++++++++++++ .../media/view/media_clip_volume_controller.h | 2 ++ Telegram/SourceFiles/mediaview.cpp | 26 ++++++++++++++----- Telegram/SourceFiles/mediaview.h | 1 + Telegram/SourceFiles/playerwidget.cpp | 8 +++--- Telegram/SourceFiles/settings.cpp | 2 -- Telegram/SourceFiles/settings.h | 2 -- 15 files changed, 96 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 8ba195c17..b0f0a17b2 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -538,6 +538,9 @@ struct Data { int32 DebugLoggingFlags = 0; + float64 SongVolume = 0.9; + float64 VideoVolume = 0.9; + // config int32 ChatSizeMax = 200; int32 MegagroupSizeMax = 1000; @@ -606,6 +609,9 @@ DefineVar(Global, bool, ScreenIsLocked); DefineVar(Global, int32, DebugLoggingFlags); +DefineVar(Global, float64, SongVolume); +DefineVar(Global, float64, VideoVolume); + // config DefineVar(Global, int32, ChatSizeMax); DefineVar(Global, int32, MegagroupSizeMax); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index a95629dd3..4a14f5694 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -217,6 +217,9 @@ DeclareVar(bool, ScreenIsLocked); DeclareVar(int32, DebugLoggingFlags); +DeclareVar(float64, SongVolume); +DeclareVar(float64, VideoVolume); + // config DeclareVar(int32, ChatSizeMax); DeclareVar(int32, MegagroupSizeMax); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 6ab0612b8..9d30c85aa 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -541,6 +541,7 @@ namespace { dbiHiddenPinnedMessages = 0x39, dbiDialogsMode = 0x40, dbiModerateMode = 0x41, + dbiVideoVolume = 0x42, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1308,7 +1309,15 @@ namespace { stream >> v; if (!_checkStreamStatus(stream)) return false; - cSetSongVolume(snap(v / 1e6, 0., 1.)); + Global::SetSongVolume(snap(v / 1e6, 0., 1.)); + } break; + + case dbiVideoVolume: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); } break; default: @@ -1532,7 +1541,7 @@ namespace { _writeMap(WriteMapFast); } - uint32 size = 17 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 18 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + Serialize::bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); @@ -1561,7 +1570,8 @@ namespace { data.stream << quint32(dbiDownloadPath) << (cAskDownloadPath() ? QString() : cDownloadPath()) << (cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark()); data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); - data.stream << quint32(dbiSongVolume) << qint32(qRound(cSongVolume() * 1e6)); + data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); + data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast(Global::DialogsMode()); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 3cdee8d4e..b18b8ddb1 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -291,6 +291,7 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong())); connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll())); connect(this, SIGNAL(songVolumeChanged()), _fader, SLOT(onSongVolumeChanged())); + connect(this, SIGNAL(videoVolumeChanged()), _fader, SLOT(onVideoVolumeChanged())); connect(this, SIGNAL(loaderOnStart(const AudioMsgId&,qint64)), _loader, SLOT(onStart(const AudioMsgId&,qint64))); connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&))); connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit())); @@ -571,7 +572,7 @@ void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) { float64 suppressGain = 1.; switch (type) { case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break; - case AudioMsgId::Type::Song: suppressGain = suppressSongGain * cSongVolume(); break; + case AudioMsgId::Type::Song: suppressGain = suppressSongGain * Global::SongVolume(); break; } switch (current->playbackState.state) { @@ -623,7 +624,7 @@ void AudioPlayer::seek(int64 position) { float64 suppressGain = 1.; switch (type) { case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break; - case AudioMsgId::Type::Song: suppressGain = suppressSongGain * cSongVolume(); break; + case AudioMsgId::Type::Song: suppressGain = suppressSongGain * Global::SongVolume(); break; } auto audio = current->audio; @@ -887,15 +888,17 @@ void AudioPlayerFader::onTimer() { if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(data->audio); if (emitSignals & EmitNeedToPreload) emit needToPreload(data->audio); }; - auto suppressGainForMusic = suppressSongGain * cSongVolume(); + auto suppressGainForMusic = suppressSongGain * Global::SongVolume(); auto suppressGainForMusicChanged = suppressSongChanged || _songVolumeChanged; for (int i = 0; i < AudioSimultaneousLimit; ++i) { updatePlayback(AudioMsgId::Type::Voice, i, suppressAllGain, suppressAudioChanged); updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged); } - updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForMusic, suppressGainForMusicChanged); + auto suppressGainForVideo = suppressSongGain * Global::VideoVolume(); + auto suppressGainForVideoChanged = suppressSongChanged || _videoVolumeChanged; + updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForVideo, suppressGainForVideoChanged); - _songVolumeChanged = false; + _songVolumeChanged = _videoVolumeChanged = false; if (!hasFading) { if (!hasPlaying) { @@ -1067,6 +1070,11 @@ void AudioPlayerFader::onSongVolumeChanged() { onTimer(); } +void AudioPlayerFader::onVideoVolumeChanged() { + _videoVolumeChanged = true; + onTimer(); +} + void AudioPlayerFader::resumeDevice() { QMutexLocker lock(&_pauseMutex); _pauseFlag = false; diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 529e730ce..a902861f2 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -100,6 +100,7 @@ signals: void suppressAll(); void songVolumeChanged(); + void videoVolumeChanged(); private: bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); @@ -237,6 +238,7 @@ public slots: void onUnsuppressSong(); void onSuppressAll(); void onSongVolumeChanged(); + void onVideoVolumeChanged(); private: enum { @@ -252,7 +254,7 @@ private: QMutex _pauseMutex; bool _pauseFlag, _paused; - bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged; + bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged, _videoVolumeChanged; anim::fvalue _suppressAllGain, _suppressSongGain; uint64 _suppressAllStart, _suppressSongStart; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index a45558a8d..f635c8326 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -294,8 +294,8 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { switch (type) { case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, internal::audioSuppressGain()); break; - case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break; - case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break; + case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::SongVolume()); break; + case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::VideoVolume()); break; } if (!internal::audioCheckError()) { setStoppedState(m, AudioPlayerStoppedAtError); diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 4a89e0f6c..fc48fb16b 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -43,6 +43,9 @@ Controller::Controller(QWidget *parent) : TWidget(parent) _fadeAnimation->show(); _fadeAnimation->setFinishedCallback(func(this, &Controller::fadeFinished)); _fadeAnimation->setUpdatedCallback(func(this, &Controller::fadeUpdated)); + + _volumeController->setVolume(Global::VideoVolume()); + connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); connect(_playback, SIGNAL(seekProgress(int64)), this, SLOT(onSeekProgress(int64))); @@ -173,5 +176,9 @@ void Controller::paintEvent(QPaintEvent *e) { App::roundRect(p, rect(), st::medviewSaveMsg, MediaviewSaveCorners); } +void Controller::mousePressEvent(QMouseEvent *e) { + e->accept(); // Don't pass event to the MediaView. +} + } // namespace Clip } // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h index 3cf6016f3..14a5a5c13 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -65,6 +65,7 @@ private slots: protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; private: template diff --git a/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp index 57021db79..8a98dec90 100644 --- a/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp @@ -29,6 +29,7 @@ namespace Clip { VolumeController::VolumeController(QWidget *parent) : TWidget(parent) { resize(st::mediaviewVolumeSize); setCursor(style::cur_pointer); + setMouseTracking(true); } void VolumeController::setVolume(float64 volume) { @@ -57,12 +58,33 @@ void VolumeController::paintEvent(QPaintEvent *e) { } void VolumeController::mouseMoveEvent(QMouseEvent *e) { + if (_downCoord < 0) { + return; + } + int delta = e->pos().x() - _downCoord; + int left = (width() - st::mediaviewVolumeIcon.width()) / 2; + float64 startFrom = snap((_downCoord - left) / float64(st::mediaviewVolumeIcon.width()), 0., 1.); + float64 add = delta / float64(4 * st::mediaviewVolumeIcon.width()); + auto newVolume = snap(startFrom + add, 0., 1.); + changeVolume(newVolume); } void VolumeController::mousePressEvent(QMouseEvent *e) { + _downCoord = snap(e->pos().x(), 0, width()); + int left = (width() - st::mediaviewVolumeIcon.width()) / 2; + auto newVolume = snap((_downCoord - left) / float64(st::mediaviewVolumeIcon.width()), 0., 1.); + changeVolume(newVolume); +} + +void VolumeController::changeVolume(float64 newVolume) { + if (newVolume != _volume) { + setVolume(newVolume); + emit volumeChanged(_volume); + } } void VolumeController::mouseReleaseEvent(QMouseEvent *e) { + _downCoord = -1; } void VolumeController::enterEvent(QEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_volume_controller.h b/Telegram/SourceFiles/media/view/media_clip_volume_controller.h index 6d891d321..cf062ce09 100644 --- a/Telegram/SourceFiles/media/view/media_clip_volume_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_volume_controller.h @@ -47,8 +47,10 @@ private: update(); } void setOver(bool over); + void changeVolume(float64 newVolume); float64 _volume = 0.; + int _downCoord = -1; // < 0 means mouse is not pressed bool _over = false; FloatAnimation _a_over; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 7e5aee2f6..4811281ce 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -554,7 +554,9 @@ void MediaView::close() { } void MediaView::activateControls() { - if (!_menu) _controlsHideTimer.start(int(st::mvWaitHide)); + if (!_menu && !_mousePressed) { + _controlsHideTimer.start(int(st::mvWaitHide)); + } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { _controlsState = ControlsShowing; _controlsAnimStarted = getms(); @@ -567,7 +569,7 @@ void MediaView::activateControls() { } void MediaView::onHideControls(bool force) { - if (!force && (!_dropdown.isHidden() || _menu)) return; + if (!force && (!_dropdown.isHidden() || _menu || _mousePressed)) return; if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; _controlsState = ControlsHiding; _controlsAnimStarted = getms(); @@ -1301,7 +1303,8 @@ void MediaView::onVideoSeekFinished(int64 position) { } void MediaView::onVideoVolumeChanged(float64 volume) { - + Global::SetVideoVolume(volume); + emit audioPlayer()->videoVolumeChanged(); } void MediaView::onVideoToFullScreen() { @@ -2244,12 +2247,21 @@ bool MediaView::event(QEvent *e) { } bool MediaView::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::MouseMove && obj->isWidgetType()) { + auto type = e->type(); + if ((type == QEvent::MouseMove || type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease) && obj->isWidgetType()) { if (isAncestorOf(static_cast(obj))) { - auto mousePosition = mapFromGlobal(static_cast(e)->globalPos()); - bool moved = (mousePosition != _lastMouseMovePos); + auto mouseEvent = static_cast(e); + auto mousePosition = mapFromGlobal(mouseEvent->globalPos()); + bool activate = (mousePosition != _lastMouseMovePos); _lastMouseMovePos = mousePosition; - if (moved) activateControls(); + if (type == QEvent::MouseButtonPress) { + _mousePressed = true; + activate = true; + } else if (type == QEvent::MouseButtonRelease) { + _mousePressed = false; + activate = true; + } + if (activate) activateControls(); } } return TWidget::eventFilter(obj, e); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 1e696c147..8076df4e8 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -280,6 +280,7 @@ private: uint64 _controlsAnimStarted = 0; QTimer _controlsHideTimer; anim::fvalue a_cOpacity; + bool _mousePressed = false; PopupMenu *_menu = nullptr; Dropdown _dropdown; diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index 1b222e511..af71285ac 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -79,7 +79,7 @@ void PlayerWidget::paintEvent(QPaintEvent *e) { p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); int32 top = _volumeRect.y() + (_volumeRect.height() - st::playerVolume.pxHeight()) / 2; int32 left = _volumeRect.x() + (_volumeRect.width() - st::playerVolume.pxWidth()) / 2; - int32 mid = left + qRound(st::playerVolume.pxWidth() * cSongVolume()); + int32 mid = left + qRound(st::playerVolume.pxWidth() * Global::SongVolume()); int32 right = left + st::playerVolume.pxWidth(); if (rtl()) { left = width() - left; @@ -164,7 +164,7 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) { } else if (_over == OverVolume) { _down = OverVolume; _downCoord = pos.x() - _volumeRect.x(); - cSetSongVolume(snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.)); + Global::SetSongVolume(snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.)); emit audioPlayer()->songVolumeChanged(); rtlupdate(_volumeRect); } else if (_over == OverPlayback) { @@ -401,8 +401,8 @@ void PlayerWidget::updateSelected() { int32 delta = (pos.x() - _volumeRect.x()) - _downCoord; float64 startFrom = snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.); float64 add = delta / float64(4 * st::playerVolume.pxWidth()), result = snap(startFrom + add, 0., 1.); - if (result != cSongVolume()) { - cSetSongVolume(result); + if (result != Global::SongVolume()) { + Global::SetSongVolume(result); emit audioPlayer()->songVolumeChanged(); rtlupdate(_volumeRect); } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 7e0ebb89e..030bf7a67 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -148,8 +148,6 @@ bool gDialogsReceived = false; int gOtherOnline = 0; -float64 gSongVolume = 0.9; - SavedPeers gSavedPeers; SavedPeersByTime gSavedPeersByTime; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 999065bbf..c27d862fe 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -291,8 +291,6 @@ DeclareSetting(bool, DialogsReceived); DeclareSetting(int, OtherOnline); -DeclareSetting(float64, SongVolume); - class PeerData; typedef QMap SavedPeers; typedef QMultiMap SavedPeersByTime; From 647759f0d12fbd36beb5c7eed2c30ce40af8eedb Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 12 Jul 2016 21:04:34 +0300 Subject: [PATCH 18/60] Enable video files overview in MediaView. Video restart supported. --- Telegram/SourceFiles/media/media_audio.cpp | 19 ++++++ Telegram/SourceFiles/media/media_audio.h | 1 + .../SourceFiles/media/media_clip_ffmpeg.cpp | 67 ++++++++++--------- .../SourceFiles/media/media_clip_ffmpeg.h | 7 +- .../media/media_clip_implementation.h | 8 ++- .../SourceFiles/media/media_clip_qtgif.cpp | 33 ++++----- Telegram/SourceFiles/media/media_clip_qtgif.h | 9 ++- .../SourceFiles/media/media_clip_reader.cpp | 53 +++++++++++---- .../SourceFiles/media/media_clip_reader.h | 3 + .../media/view/media_clip_controller.cpp | 2 +- .../media/view/media_clip_playback.cpp | 5 +- .../media/view/media_clip_playback.h | 1 + Telegram/SourceFiles/mediaview.cpp | 58 ++++++++++++---- 13 files changed, 183 insertions(+), 83 deletions(-) diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index b18b8ddb1..db02754b4 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -281,6 +281,7 @@ void AudioPlayer::AudioMsg::clear() { nextBuffer = 0; videoData = nullptr; + videoPlayId = 0; } AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0), @@ -520,6 +521,24 @@ void AudioPlayer::playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std if (stopped) emit updated(stopped); } +void AudioPlayer::stopFromVideo(uint64 videoPlayId) { + AudioMsgId current; + { + QMutexLocker lock(&playerMutex); + auto data = dataForType(AudioMsgId::Type::Video); + t_assert(data != nullptr); + + if (data->videoPlayId != videoPlayId) { + return; + } + + current = data->audio; + fadedStop(AudioMsgId::Type::Video); + data->clear(); + } + if (current) emit updated(current); +} + void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { _loader->feedFromVideo(std_::move(part)); } diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index a902861f2..93ff6bc79 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -72,6 +72,7 @@ public: int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); void videoSoundProgress(const AudioMsgId &audio); AudioPlaybackState currentVideoState(uint64 videoPlayId); + void stopFromVideo(uint64 videoPlayId); void stopAndClear(); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index c5744a40a..68a112e53 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -36,7 +36,7 @@ FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, Q _packetNull.size = 0; } -bool FFMpegReaderImplementation::readNextFrame() { +ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { if (_frameRead) { av_frame_unref(_frame); _frameRead = false; @@ -46,7 +46,7 @@ bool FFMpegReaderImplementation::readNextFrame() { while (_packetQueue.isEmpty()) { auto packetResult = readPacket(); if (packetResult == PacketResult::Error) { - return false; + return ReadResult::Error; } else if (packetResult == PacketResult::EndOfFile) { break; } @@ -75,7 +75,7 @@ bool FFMpegReaderImplementation::readNextFrame() { eofReached = (res == AVERROR_EOF); if (!eofReached || !_hadFrame) { // try to skip end of file - return false; + return ReadResult::Error; } } if (res > 0) decoded = res; @@ -105,13 +105,13 @@ bool FFMpegReaderImplementation::readNextFrame() { _hadFrame = _frameRead = true; _frameTime += _currentFrameDelay; - return true; + return ReadResult::Success; } if (eofReached) { clearPacketQueue(); if (_mode == Mode::Normal) { - return false; + return ReadResult::Eof; } if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { @@ -120,7 +120,7 @@ bool FFMpegReaderImplementation::readNextFrame() { if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; + return ReadResult::Error; } } } @@ -132,41 +132,42 @@ bool FFMpegReaderImplementation::readNextFrame() { } } - return false; + return ReadResult::Error; } -bool FFMpegReaderImplementation::readFramesTill(int64 ms) { - if (_audioStreamId >= 0) { // sync by audio stream - auto correctMs = audioPlayer()->getVideoCorrectedTime(_playId, ms); - - if (!_frameRead && !readNextFrame()) { - return false; - } - while (_frameTime <= correctMs) { - if (!readNextFrame()) { - return false; - } - } - _frameTimeCorrection = ms - correctMs; - return true; - } else { // just keep up +ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int64 ms) { + if (_audioStreamId < 0) { // just keep up if (_frameRead && _frameTime > ms) { - return true; + return ReadResult::Success; } - if (!readNextFrame()) { - return false; - } - if (_frameTime > ms) { - return true; - } - if (!readNextFrame()) { - return false; + auto readResult = readNextFrame(); + if (readResult != ReadResult::Success || _frameTime > ms) { + return readResult; } + readResult = readNextFrame(); if (_frameTime <= ms) { _frameTime = ms + 5; // keep up } - return true; + return readResult; } + + // sync by audio stream + auto correctMs = audioPlayer()->getVideoCorrectedTime(_playId, ms); + + if (!_frameRead) { + auto readResult = readNextFrame(); + if (readResult != ReadResult::Success) { + return readResult; + } + } + while (_frameTime <= correctMs) { + auto readResult = readNextFrame(); + if (readResult != ReadResult::Success) { + return readResult; + } + } + _frameTimeCorrection = ms - correctMs; + return ReadResult::Success; } int64 FFMpegReaderImplementation::frameRealTime() const { @@ -333,7 +334,7 @@ QString FFMpegReaderImplementation::logData() const { FFMpegReaderImplementation::~FFMpegReaderImplementation() { if (_mode == Mode::Normal && _audioStreamId >= 0) { - audioPlayer()->stop(AudioMsgId::Type::Video); + audioPlayer()->stopFromVideo(_playId); } if (_frameRead) { av_frame_unref(_frame); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 639ebb034..224955832 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -37,11 +37,14 @@ class FFMpegReaderImplementation : public ReaderImplementation { public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId); - bool readFramesTill(int64 ms) override; + ReadResult readFramesTill(int64 ms) override; int64 frameRealTime() const override; uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; int64 durationMs() const override; + bool hasAudio() const override { + return (_audioStreamId >= 0); + } bool start(Mode mode) override; QString logData() const; @@ -49,7 +52,7 @@ public: ~FFMpegReaderImplementation(); private: - bool readNextFrame(); + ReadResult readNextFrame(); enum class PacketResult { Ok, diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 072522325..087d3aed5 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -38,8 +38,13 @@ public: Normal, }; + enum class ReadResult { + Success, + Error, + Eof, + }; // Read frames till current frame will have presentation time > ms. - virtual bool readFramesTill(int64 ms) = 0; + virtual ReadResult readFramesTill(int64 ms) = 0; // Get current frame real and presentation time. virtual int64 frameRealTime() const = 0; @@ -49,6 +54,7 @@ public: virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual int64 durationMs() const = 0; + virtual bool hasAudio() const = 0; virtual bool start(Mode mode) = 0; virtual ~ReaderImplementation() { diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index 8cb0f1cf4..0ef2b8d96 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -28,23 +28,19 @@ namespace internal { QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { } -bool QtGifReaderImplementation::readFramesTill(int64 ms) { +ReaderImplementation::ReadResult QtGifReaderImplementation::readFramesTill(int64 ms) { if (!_frame.isNull() && _frameTime > ms) { - return true; + return ReadResult::Success; } - if (!readNextFrame()) { - return false; - } - if (_frameTime > ms) { - return true; - } - if (!readNextFrame()) { - return false; + auto readResult = readNextFrame(); + if (readResult != ReadResult::Success || _frameTime > ms) { + return readResult; } + readResult = readNextFrame(); if (_frameTime <= ms) { _frameTime = ms + 5; // keep up } - return true; + return readResult; } int64 QtGifReaderImplementation::frameRealTime() const { @@ -55,20 +51,24 @@ uint64 QtGifReaderImplementation::framePresentationTime() const { return static_cast(qMax(_frameTime, 0LL)); } -bool QtGifReaderImplementation::readNextFrame() { +ReaderImplementation::ReadResult QtGifReaderImplementation::readNextFrame() { if (_reader) _frameDelay = _reader->nextImageDelay(); - if (_framesLeft < 1 && !jumpToStart()) { - return false; + if (_framesLeft < 1) { + if (_mode == Mode::Normal) { + return ReadResult::Eof; + } else if (!jumpToStart()) { + return ReadResult::Error; + } } _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it if (!_reader->read(&_frame) || _frame.isNull()) { - return false; + return ReadResult::Error; } --_framesLeft; _frameTime += _frameDelay; _frameRealTime += _frameDelay; - return true; + return ReadResult::Success; } bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { @@ -101,6 +101,7 @@ int64 QtGifReaderImplementation::durationMs() const { bool QtGifReaderImplementation::start(Mode mode) { if (mode == Mode::OnlyGifv) return false; + _mode = mode; return jumpToStart(); } diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index b03da95c5..852a9528c 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -31,18 +31,23 @@ public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); - bool readFramesTill(int64 ms) override; + ReadResult readFramesTill(int64 ms) override; int64 frameRealTime() const override; uint64 framePresentationTime() const override; bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; int64 durationMs() const override; + bool hasAudio() const override { + return false; + } bool start(Mode mode) override; ~QtGifReaderImplementation(); private: bool jumpToStart(); - bool readNextFrame(); + ReadResult readNextFrame(); + + Mode _mode = Mode::Normal; QImageReader *_reader = nullptr; int _framesLeft = 0; diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 7630b9325..f6b2852dd 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -297,6 +297,10 @@ void Reader::error() { _state = State::Error; } +void Reader::finished() { + _state = State::Finished; +} + Reader::~Reader() { stop(); } @@ -306,11 +310,13 @@ public: ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) , _mode(reader->mode()) , _playId(reader->playId()) - , _data(data) - , _location(_data.isEmpty() ? new FileLocation(location) : 0) { - if (_data.isEmpty() && !_location->accessEnable()) { - error(); - return; + , _data(data) { + if (_data.isEmpty()) { + _location = std_::make_unique(location); + if (!_location->accessEnable()) { + error(); + return; + } } _accessed = true; } @@ -320,7 +326,8 @@ public: return error(); } if (frame() && frame()->original.isNull()) { - if (!_implementation->readFramesTill(-1)) { // Read the first frame. + auto readResult = _implementation->readFramesTill(-1); + if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame. return error(); } if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { @@ -329,13 +336,18 @@ public: _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); + _hasAudio = _implementation->hasAudio(); return ProcessResult::Started; } return ProcessResult::Wait; } ProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit - if (_state == State::Error) return ProcessResult::Error; + if (_state == State::Error) { + return ProcessResult::Error; + } else if (_state == State::Finished) { + return ProcessResult::Finished; + } if (!_request.valid()) { return start(ms); @@ -348,7 +360,12 @@ public: } ProcessResult finishProcess(uint64 ms) { - if (!_implementation->readFramesTill(ms - _animationStarted)) { + auto readResult = _implementation->readFramesTill(ms - _animationStarted); + if (readResult == internal::ReaderImplementation::ReadResult::Eof) { + stop(); + _state = State::Finished; + return ProcessResult::Finished; + } else if (readResult == internal::ReaderImplementation::ReadResult::Error) { return error(); } _nextFramePositionMs = _implementation->frameRealTime(); @@ -384,7 +401,7 @@ public: } } - _implementation = std_::make_unique(_location, &_data, _playId); + _implementation = std_::make_unique(_location.get(), &_data, _playId); // _implementation = new QtGifReaderImplementation(_location, &_data); auto implementationMode = [this]() { @@ -414,15 +431,13 @@ public: if (_accessed) { _location->accessDisable(); } - delete _location; - _location = 0; + _location = nullptr; } _accessed = false; } ~ReaderPrivate() { stop(); - deleteAndMark(_location); _data.clear(); } @@ -433,7 +448,7 @@ private: uint64 _playId; QByteArray _data; - FileLocation *_location; + std_::unique_ptr _location; bool _accessed = false; QBuffer _buffer; @@ -458,6 +473,7 @@ private: int _width = 0; int _height = 0; + bool _hasAudio = false; int64 _durationMs = 0; uint64 _animationStarted = 0; uint64 _nextFrameWhen = 0; @@ -547,6 +563,12 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u if (i != _readerPointers.cend()) _readerPointers.erase(i); } return false; + } else if (result == ProcessResult::Finished) { + if (it != _readerPointers.cend()) { + it.key()->finished(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + } + return false; } if (it == _readerPointers.cend()) { return false; @@ -555,6 +577,7 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u if (result == ProcessResult::Started) { _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); it.key()->_durationMs = reader->_durationMs; + it.key()->_hasAudio = reader->_hasAudio; } // See if we need to pause GIF because it is not displayed right now. if (!reader->_paused && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { @@ -719,7 +742,9 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data auto reader = std_::make_unique(&localloc, &localdata, playId); if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv)) { bool hasAlpha = false; - if (reader->readFramesTill(-1) && reader->renderFrame(cover, hasAlpha, QSize())) { + auto readResult = reader->readFramesTill(-1); + auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success); + if (readFrame && reader->renderFrame(cover, hasAlpha, QSize())) { if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { if (hasAlpha) { QImage cacheForResize; diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 31336a59d..f0930b03e 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -28,6 +28,7 @@ namespace Clip { enum class State { Reading, Error, + Finished, }; struct FrameRequest { @@ -107,6 +108,7 @@ public: void stop(); void error(); + void finished(); Mode mode() const { return _mode; @@ -165,6 +167,7 @@ private: enum class ProcessResult { Error, Started, + Finished, Paused, Repaint, CopyFrame, diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index fc48fb16b..d9cf95629 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -71,7 +71,7 @@ void Controller::showAnimated() { void Controller::hideAnimated() { startFading([this]() { - _fadeAnimation->fadeOut(st::mvShowDuration); + _fadeAnimation->fadeOut(st::mvHideDuration); }); } diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 14f93c9e8..be628d0c8 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -35,7 +35,8 @@ Playback::Playback(QWidget *parent) : TWidget(parent) void Playback::updateState(const AudioPlaybackState &playbackState) { qint64 position = 0, duration = playbackState.duration; - if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + _playing = !(playbackState.state & AudioPlayerStoppedMask); + if (_playing && playbackState.state != AudioPlayerFinishing) { position = playbackState.position; } else if (playbackState.state == AudioPlayerStoppedAtEnd) { position = playbackState.duration; @@ -50,7 +51,7 @@ void Playback::updateState(const AudioPlaybackState &playbackState) { progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; } if (duration != _duration || position != _position) { - if (duration && _duration) { + if (duration && _duration && playbackState.state != AudioPlayerStopped) { a_progress.start(progress); _a_progress.start(); } else { diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h index a74d0ee83..ad1a645f3 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.h +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -65,6 +65,7 @@ private: float64 _downProgress = 0.; float64 _fadeOpacity = 1.; + bool _playing = false; }; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 4811281ce..5b7f0e675 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -349,7 +349,7 @@ void MediaView::updateControls() { _dateNav = myrtlrect(st::mvTextLeft, height() - st::mvTextTop, st::mvFont->width(_dateText), st::mvFont->height); } updateHeader(); - if (_photo || (_history && (_overview == OverviewPhotos || _overview == OverviewChatPhotos || _overview == OverviewFiles))) { + if (_photo || (_history && (_overview == OverviewPhotos || _overview == OverviewChatPhotos || _overview == OverviewFiles || _overview == OverviewVideos))) { _leftNavVisible = (_index > 0) || (_index == 0 && ( (!_msgmigrated && _history && _history->overview[_overview].size() < _history->overviewCount(_overview)) || (_msgmigrated && _migrated && _migrated->overview[_overview].size() < _migrated->overviewCount(_overview)) || @@ -554,7 +554,7 @@ void MediaView::close() { } void MediaView::activateControls() { - if (!_menu && !_mousePressed) { + if (!_menu && !_mousePressed && (!_clipController || !_clipController->geometry().contains(_lastMouseMovePos))) { _controlsHideTimer.start(int(st::mvWaitHide)); } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { @@ -564,19 +564,19 @@ void MediaView::activateControls() { if (!_a_state.animating()) _a_state.start(); } if (_clipController) { - _clipController->showAnimated(); +// _clipController->showAnimated(); } } void MediaView::onHideControls(bool force) { - if (!force && (!_dropdown.isHidden() || _menu || _mousePressed)) return; + if (!force && (!_dropdown.isHidden() || _menu || _mousePressed || (_clipController && _clipController->geometry().contains(_lastMouseMovePos)))) return; if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; _controlsState = ControlsHiding; _controlsAnimStarted = getms(); a_cOpacity.start(0); if (!_a_state.animating()) _a_state.start(); if (_clipController) { - _clipController->hideAnimated(); +// _clipController->hideAnimated(); } } @@ -706,12 +706,16 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { if (_gif->state() == State::Error) { _current = QPixmap(); - } - _videoIsSilent = _doc->isVideo() && !_gif->hasAudio(); - if (_videoIsSilent) { + } else if (_gif->state() == State::Finished) { + _videoPositionMs = _videoDurationMs; + updateSilentVideoPlaybackState(); + } else { + _videoIsSilent = _doc->isVideo() && !_gif->hasAudio(); _videoDurationMs = _gif->getDurationMs(); _videoPositionMs = _gif->getPositionMs(); - updateSilentVideoPlaybackState(); + if (_videoIsSilent) { + updateSilentVideoPlaybackState(); + } } displayDocument(_doc, item); } else { @@ -721,8 +725,8 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { case NotificationRepaint: { if (!_gif->currentDisplayed()) { + _videoPositionMs = _gif->getPositionMs(); if (_videoIsSilent) { - _videoPositionMs = _gif->getPositionMs(); updateSilentVideoPlaybackState(); } update(_x, _y, _w, _h); @@ -1016,7 +1020,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) { _canForward = _msgid > 0; _canDelete = context ? context->canDelete() : false; if (_history) { - _overview = OverviewFiles; + _overview = doc->isVideo() ? OverviewVideos : OverviewFiles; findCurrent(); } displayDocument(doc, context); @@ -1287,7 +1291,29 @@ void MediaView::setClipControllerGeometry() { } void MediaView::onVideoPlay() { + if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { + if (_gif->state() == Media::Clip::State::Error) { + displayDocument(_doc, item); + } else if (_gif->state() == Media::Clip::State::Finished) { + _current = _gif->current(_gif->width(), _gif->height(), _gif->width(), _gif->height(), getms()); + _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), Media::Clip::Reader::Mode::Video); + // Correct values will be set when gif gets inited. + _videoIsSilent = false; + _videoPositionMs = 0; + + AudioPlaybackState state; + state.state = AudioPlayerStopped; + state.position = _videoPositionMs; + state.duration = _videoDurationMs; + state.frequency = _videoFrequencyMs; + updateVideoPlaybackState(state); + } else { + // + } + } else { + stopGif(); + } } void MediaView::onVideoPause() { @@ -1329,6 +1355,9 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) { if (state.frequency) { _clipController->updatePlayback(state); + } else { // Audio has stopped already. + _videoIsSilent = true; + updateSilentVideoPlaybackState(); } } @@ -1660,6 +1689,8 @@ void MediaView::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { if (_doc && !_doc->loading() && !fileShown()) { onDocClick(); + } else if (_doc->isVideo()) { + onVideoPlay(); } } else if (e->key() == Qt::Key_Left) { moveToNext(-1); @@ -1777,7 +1808,7 @@ bool MediaView::moveToNext(int32 delta) { } return false; } - if ((_history && _overview != OverviewPhotos && _overview != OverviewChatPhotos && _overview != OverviewFiles) || (_overview == OverviewCount && !_user)) { + if ((_history && _overview != OverviewPhotos && _overview != OverviewChatPhotos && _overview != OverviewFiles && _overview != OverviewVideos) || (_overview == OverviewCount && !_user)) { return false; } if (_msgmigrated && !_history->overviewLoaded(_overview)) { @@ -1807,6 +1838,7 @@ bool MediaView::moveToNext(int32 delta) { switch (media->type()) { case MediaTypePhoto: displayPhoto(static_cast(item->getMedia())->photo(), item); preloadData(delta); break; case MediaTypeFile: + case MediaTypeVideo: case MediaTypeGif: case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break; } @@ -1870,6 +1902,7 @@ void MediaView::preloadData(int32 delta) { switch (media->type()) { case MediaTypePhoto: static_cast(media)->photo()->forget(); break; case MediaTypeFile: + case MediaTypeVideo: case MediaTypeGif: case MediaTypeSticker: media->getDocument()->forget(); break; } @@ -1895,6 +1928,7 @@ void MediaView::preloadData(int32 delta) { switch (media->type()) { case MediaTypePhoto: static_cast(media)->photo()->download(); break; case MediaTypeFile: + case MediaTypeVideo: case MediaTypeGif: { DocumentData *doc = media->getDocument(); doc->thumb->load(); From fc716af002302c988793718df501866a82a31c86 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 13 Jul 2016 14:24:31 +0300 Subject: [PATCH 19/60] Pause/resume supported in the video player in MediaView. Autoplay video in MediaView only from showDocument(), otherwise pause. --- Telegram/SourceFiles/history.cpp | 2 +- .../inline_bot_layout_internal.cpp | 2 +- Telegram/SourceFiles/media/media_audio.cpp | 89 ++++++++++++++++++- Telegram/SourceFiles/media/media_audio.h | 4 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 16 +++- .../SourceFiles/media/media_clip_ffmpeg.h | 6 ++ .../media/media_clip_implementation.h | 2 + Telegram/SourceFiles/media/media_clip_qtgif.h | 8 ++ .../SourceFiles/media/media_clip_reader.cpp | 74 ++++++++++++--- .../SourceFiles/media/media_clip_reader.h | 9 +- Telegram/SourceFiles/mediaview.cpp | 34 ++++--- Telegram/SourceFiles/mediaview.h | 4 +- Telegram/SourceFiles/structs.h | 2 +- 13 files changed, 217 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 1718390b8..6c1c672c9 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2936,7 +2936,7 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) { switch (notification) { case NotificationReinit: { bool stopped = false; - if (reader->paused()) { + if (reader->autoPausedGif()) { if (MainWidget *m = App::main()) { if (!m->isItemVisible(this)) { // stop animation if it is not visible media->stopInline(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index de6f7f0da..7683ea23b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -340,7 +340,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) { int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); _gif->start(frame.width(), frame.height(), _width, height, false); - } else if (_gif->paused() && !Ui::isInlineItemVisible(this)) { + } else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) { delete _gif; _gif = nullptr; getShownDocument()->forget(); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index db02754b4..12604b8e6 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -484,7 +484,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { if (stopped) emit updated(stopped); } -void AudioPlayer::playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { +void AudioPlayer::initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { t_assert(audio.type() == AudioMsgId::Type::Video); auto type = audio.type(); @@ -514,7 +514,7 @@ void AudioPlayer::playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std } _loader->startFromVideo(current->videoPlayId); - current->playbackState.state = AudioPlayerPlaying; + current->playbackState.state = AudioPlayerPaused; current->loading = true; emit loaderOnStart(audio, position); } @@ -539,6 +539,90 @@ void AudioPlayer::stopFromVideo(uint64 videoPlayId) { if (current) emit updated(current); } +void AudioPlayer::pauseFromVideo(uint64 videoPlayId) { + AudioMsgId current; + { + QMutexLocker lock(&playerMutex); + auto type = AudioMsgId::Type::Video; + auto data = dataForType(type); + t_assert(data != nullptr); + + if (data->videoPlayId != videoPlayId) { + return; + } + + current = data->audio; + switch (data->playbackState.state) { + case AudioPlayerStarting: + case AudioPlayerResuming: + case AudioPlayerPlaying: { + data->playbackState.state = AudioPlayerPaused; + updateCurrentStarted(type); + + ALint state = AL_INITIAL; + alGetSourcei(data->source, AL_SOURCE_STATE, &state); + if (!checkCurrentALError(type)) return; + + if (state == AL_PLAYING) { + alSourcePause(data->source); + if (!checkCurrentALError(type)) return; + } + } break; + } + emit faderOnTimer(); + } + if (current) emit updated(current); +} + +void AudioPlayer::resumeFromVideo(uint64 videoPlayId) { + AudioMsgId current; + { + QMutexLocker lock(&playerMutex); + auto type = AudioMsgId::Type::Video; + auto data = dataForType(type); + t_assert(data != nullptr); + + if (data->videoPlayId != videoPlayId) { + return; + } + + float64 suppressGain = suppressSongGain * Global::VideoVolume(); + + current = data->audio; + switch (data->playbackState.state) { + case AudioPlayerPausing: + case AudioPlayerPaused: + case AudioPlayerPausedAtEnd: { + if (data->playbackState.state == AudioPlayerPaused) { + updateCurrentStarted(type); + } else if (data->playbackState.state == AudioPlayerPausedAtEnd) { + if (alIsSource(data->source)) { + alSourcei(data->source, AL_SAMPLE_OFFSET, qMax(data->playbackState.position - data->skipStart, 0LL)); + if (!checkCurrentALError(type)) return; + } + } + data->playbackState.state = AudioPlayerPlaying; + + ALint state = AL_INITIAL; + alGetSourcei(data->source, AL_SOURCE_STATE, &state); + if (!checkCurrentALError(type)) return; + + if (state != AL_PLAYING) { + audioPlayer()->resumeDevice(); + + alSourcef(data->source, AL_GAIN, suppressGain); + if (!checkCurrentALError(type)) return; + + alSourcePlay(data->source); + if (!checkCurrentALError(type)) return; + } + } break; + } + emit faderOnTimer(); + } + if (current) emit updated(current); +} + void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { _loader->feedFromVideo(std_::move(part)); } @@ -592,6 +676,7 @@ void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) { switch (type) { case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break; case AudioMsgId::Type::Song: suppressGain = suppressSongGain * Global::SongVolume(); break; + case AudioMsgId::Type::Video: suppressGain = suppressSongGain * Global::VideoVolume(); break; } switch (current->playbackState.state) { diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 93ff6bc79..e7a392d4f 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -67,12 +67,14 @@ public: void stop(AudioMsgId::Type type); // Video player audio stream interface. - void playFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position); + void initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position); void feedFromVideo(VideoSoundPart &&part); int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); void videoSoundProgress(const AudioMsgId &audio); AudioPlaybackState currentVideoState(uint64 videoPlayId); void stopFromVideo(uint64 videoPlayId); + void pauseFromVideo(uint64 videoPlayId); + void resumeFromVideo(uint64 videoPlayId); void stopAndClear(); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 68a112e53..80153b0d3 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -183,6 +183,18 @@ int64 FFMpegReaderImplementation::durationMs() const { return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; } +void FFMpegReaderImplementation::pauseAudio() { + if (_audioStreamId >= 0) { + audioPlayer()->pauseFromVideo(_playId); + } +} + +void FFMpegReaderImplementation::resumeAudio() { + if (_audioStreamId >= 0) { + audioPlayer()->resumeFromVideo(_playId); + } +} + bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { t_assert(_frameRead); _frameRead = false; @@ -322,7 +334,7 @@ bool FFMpegReaderImplementation::start(Mode mode) { } else { soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; } - audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), _playId, std_::move(soundData), 0); + audioPlayer()->initFromVideo(AudioMsgId(AudioMsgId::Type::Video), _playId, std_::move(soundData), 0); } return true; @@ -333,7 +345,7 @@ QString FFMpegReaderImplementation::logData() const { } FFMpegReaderImplementation::~FFMpegReaderImplementation() { - if (_mode == Mode::Normal && _audioStreamId >= 0) { + if (_audioStreamId >= 0) { audioPlayer()->stopFromVideo(_playId); } if (_frameRead) { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 224955832..b048798f6 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -38,13 +38,19 @@ public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId); ReadResult readFramesTill(int64 ms) override; + int64 frameRealTime() const override; uint64 framePresentationTime() const override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int64 durationMs() const override; bool hasAudio() const override { return (_audioStreamId >= 0); } + void pauseAudio() override; + void resumeAudio() override; + bool start(Mode mode) override; QString logData() const; diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 087d3aed5..61e96fb0b 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -55,6 +55,8 @@ public: virtual int64 durationMs() const = 0; virtual bool hasAudio() const = 0; + virtual void pauseAudio() = 0; + virtual void resumeAudio() = 0; virtual bool start(Mode mode) = 0; virtual ~ReaderImplementation() { diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 852a9528c..3fc704183 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -32,13 +32,21 @@ public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); ReadResult readFramesTill(int64 ms) override; + int64 frameRealTime() const override; uint64 framePresentationTime() const override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int64 durationMs() const override; bool hasAudio() const override { return false; } + void pauseAudio() override { + } + void resumeAudio() override { + } + bool start(Mode mode) override; ~QtGifReaderImplementation(); diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index f6b2852dd..cae75409b 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -169,6 +169,9 @@ void Reader::moveToNextWrite() const { } else if (step == WaitingForRequestStep) { } else if (step == WaitingForFirstFrameStep) { _step.storeRelease(0); + + // Force paint the first frame so moveToNextShow() is called. + _frames[0].displayed.storeRelease(0); } else if (step % 2) { _step.storeRelease((step + 1) % 6); } @@ -206,8 +209,8 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, if (ms) { frame->displayed.storeRelease(1); - if (_paused.loadAcquire()) { - _paused.storeRelease(0); + if (_autoPausedGif.loadAcquire()) { + _autoPausedGif.storeRelease(0); if (managers.size() <= _threadIndex) error(); if (_state != State::Error) { managers.at(_threadIndex)->update(this); @@ -273,6 +276,18 @@ int64 Reader::getDurationMs() const { return ready() ? _durationMs : 0; } +void Reader::pauseResumeVideo() { + if (managers.size() <= _threadIndex) error(); + if (_state == State::Error) return; + + _videoPauseRequest.storeRelease(1 - _videoPauseRequest.loadAcquire()); + managers.at(_threadIndex)->start(this); +} + +bool Reader::videoPaused() const { + return _videoPauseRequest.loadAcquire() != 0; +} + int32 Reader::width() const { return _width; } @@ -352,8 +367,14 @@ public: if (!_request.valid()) { return start(ms); } + if (!_started) { + _started = true; + if (!_videoPausedAtMs) { + _implementation->resumeAudio(); + } + } - if (!_paused && ms >= _nextFrameWhen) { + if (!_autoPausedGif && !_videoPausedAtMs && ms >= _nextFrameWhen) { return ProcessResult::Repaint; } return ProcessResult::Wait; @@ -418,6 +439,24 @@ public: _animationStarted = _nextFrameWhen = ms; } + void pauseVideo(uint64 ms) { + if (_videoPausedAtMs) return; // Paused already. + + _videoPausedAtMs = ms; + _implementation->pauseAudio(); + } + + void resumeVideo(uint64 ms) { + if (!_videoPausedAtMs) return; // Not paused. + + int64 delta = static_cast(ms) - static_cast(_videoPausedAtMs); + _animationStarted += delta; + _nextFrameWhen += delta; + + _videoPausedAtMs = 0; + _implementation->resumeAudio(); + } + ProcessResult error() { stop(); _state = State::Error; @@ -479,7 +518,9 @@ private: uint64 _nextFrameWhen = 0; int64 _nextFramePositionMs = 0; - bool _paused = false; + bool _autoPausedGif = false; + bool _started = false; + uint64 _videoPausedAtMs = 0; friend class Manager; @@ -580,14 +621,14 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, u it.key()->_hasAudio = reader->_hasAudio; } // See if we need to pause GIF because it is not displayed right now. - if (!reader->_paused && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { + if (!reader->_autoPausedGif && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { int32 ishowing, iprevious; Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { - reader->_paused = true; - it.key()->_paused.storeRelease(1); + reader->_autoPausedGif = true; + it.key()->_autoPausedGif.storeRelease(1); result = ProcessResult::Paused; } } @@ -668,8 +709,13 @@ void Manager::process() { _readers.insert(it.key()->_private, 0); } else { i.value() = ms; - if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { - i.key()->_paused = false; + if (i.key()->_autoPausedGif && !it.key()->_autoPausedGif.loadAcquire()) { + i.key()->_autoPausedGif = false; + } + if (it.key()->_videoPauseRequest.loadAcquire()) { + i.key()->pauseVideo(ms); + } else { + i.key()->resumeVideo(ms); } } Reader::Frame *frame = it.key()->frameToWrite(); @@ -691,9 +737,15 @@ void Manager::process() { return; } ms = getms(); - i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); + if (reader->_videoPausedAtMs) { + i.value() = ms + 86400 * 1000ULL; + } else if (reader->_nextFrameWhen && reader->_started) { + i.value() = reader->_nextFrameWhen; + } else { + i.value() = (ms + 86400 * 1000ULL); + } } - if (!reader->_paused && i.value() < minms) { + if (!reader->_autoPausedGif && i.value() < minms) { minms = i.value(); } ++i; diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index f0930b03e..8960ec61e 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -85,9 +85,10 @@ public: Frame *frame = frameToShow(); return frame ? (frame->displayed.loadAcquire() != 0) : true; } - bool paused() const { - return _paused.loadAcquire(); + bool autoPausedGif() const { + return _autoPausedGif.loadAcquire(); } + bool videoPaused() const; int threadIndex() const { return _threadIndex; } @@ -105,6 +106,7 @@ public: bool hasAudio() const; int64 getPositionMs() const; int64 getDurationMs() const; + void pauseResumeVideo(); void stop(); void error(); @@ -153,7 +155,8 @@ private: void moveToNextShow() const; void moveToNextWrite() const; - QAtomicInt _paused = 0; + QAtomicInt _autoPausedGif = 0; + QAtomicInt _videoPauseRequest = 0; int32 _threadIndex; bool _autoplay = false; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 5b7f0e675..54cab77d5 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -222,6 +222,10 @@ bool MediaView::fileShown() const { bool MediaView::gifShown() const { if (_gif && _gif->ready()) { if (!_gif->started()) { + if (_doc->isVideo() && _autoplayVideoDocument != _doc && !_gif->videoPaused()) { + _gif->pauseResumeVideo(); + const_cast(this)->_videoPaused = _gif->videoPaused(); + } _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); const_cast(this)->_current = QPixmap(); } @@ -1023,6 +1027,9 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) { _overview = doc->isVideo() ? OverviewVideos : OverviewFiles; findCurrent(); } + if (doc->isVideo()) { + _autoplayVideoDocument = doc; + } displayDocument(doc, context); preloadData(0); activateControls(); @@ -1090,6 +1097,10 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty _photo = nullptr; _radial.stop(); + if (_autoplayVideoDocument && _doc != _autoplayVideoDocument) { + _autoplayVideoDocument = nullptr; + } + _current = QPixmap(); _caption = Text(); @@ -1262,8 +1273,8 @@ void MediaView::createClipController() { setClipControllerGeometry(); _clipController->show(); - connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPlay())); - connect(_clipController, SIGNAL(pausePressed()), this, SLOT(onVideoPause())); + connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPauseResume())); + connect(_clipController, SIGNAL(pausePressed()), this, SLOT(onVideoPauseResume())); connect(_clipController, SIGNAL(seekProgress(int64)), this, SLOT(onVideoSeekProgress(int64))); connect(_clipController, SIGNAL(seekFinished(int64)), this, SLOT(onVideoSeekFinished(int64))); connect(_clipController, SIGNAL(volumeChanged(float64)), this, SLOT(onVideoVolumeChanged(float64))); @@ -1290,11 +1301,13 @@ void MediaView::setClipControllerGeometry() { myEnsureResized(_clipController); } -void MediaView::onVideoPlay() { +void MediaView::onVideoPauseResume() { if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { if (_gif->state() == Media::Clip::State::Error) { displayDocument(_doc, item); } else if (_gif->state() == Media::Clip::State::Finished) { + _autoplayVideoDocument = _doc; + _current = _gif->current(_gif->width(), _gif->height(), _gif->width(), _gif->height(), getms()); _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), Media::Clip::Reader::Mode::Video); @@ -1309,17 +1322,17 @@ void MediaView::onVideoPlay() { state.frequency = _videoFrequencyMs; updateVideoPlaybackState(state); } else { - // + _gif->pauseResumeVideo(); + _videoPaused = _gif->videoPaused(); + if (_videoIsSilent) { + updateSilentVideoPlaybackState(); + } } } else { stopGif(); } } -void MediaView::onVideoPause() { - -} - void MediaView::onVideoSeekProgress(int64 position) { } @@ -1342,11 +1355,10 @@ void MediaView::onVideoFromFullScreen() { } void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { - if (audioId.type() != AudioMsgId::Type::Video) { + if (audioId.type() != AudioMsgId::Type::Video || !_gif) { return; } - t_assert(_gif != nullptr); t_assert(audioPlayer() != nullptr); auto state = audioPlayer()->currentVideoState(_gif->playId()); updateVideoPlaybackState(state); @@ -1690,7 +1702,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) { if (_doc && !_doc->loading() && !fileShown()) { onDocClick(); } else if (_doc->isVideo()) { - onVideoPlay(); + onVideoPauseResume(); } } else if (e->key() == Qt::Key_Left) { moveToNext(-1); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 8076df4e8..582307f8c 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -117,8 +117,7 @@ protected: bool eventFilter(QObject *obj, QEvent *e) override; private slots: - void onVideoPlay(); - void onVideoPause(); + void onVideoPauseResume(); void onVideoSeekProgress(int64 position); void onVideoSeekFinished(int64 position); void onVideoVolumeChanged(float64 volume); @@ -184,6 +183,7 @@ private: QString _headerText; ChildWidget _clipController = { nullptr }; + DocumentData *_autoplayVideoDocument = nullptr; Text _caption; QRect _captionRect; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 3826aa5f0..d34e95292 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1260,7 +1260,7 @@ public: } explicit operator bool() const { - return _audio; + return _audio || (_type == Type::Video); } private: From cb0c99acc8f9cb46a0719534b208b2b4a701b509 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 13 Jul 2016 20:34:57 +0300 Subject: [PATCH 20/60] Seek done in video player in MediaView. Some memory leaks fixed. Using pixmapFromImageInPlace() instead of QPixmap::fromImage(). --- Telegram/SourceFiles/app.cpp | 159 ++++++++++-------- Telegram/SourceFiles/application.cpp | 4 +- Telegram/SourceFiles/boxes/addcontactbox.cpp | 2 +- Telegram/SourceFiles/boxes/photocropbox.cpp | 2 +- Telegram/SourceFiles/boxes/photosendbox.cpp | 4 +- Telegram/SourceFiles/core/basic_types.cpp | 18 +- Telegram/SourceFiles/history.cpp | 33 +++- Telegram/SourceFiles/history.h | 6 +- .../history/field_autocomplete.cpp | 5 +- .../SourceFiles/history/field_autocomplete.h | 11 +- Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/intro/introsignup.cpp | 2 +- Telegram/SourceFiles/localimageloader.cpp | 12 +- Telegram/SourceFiles/localstorage.cpp | 4 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- Telegram/SourceFiles/mainwindow.cpp | 10 +- Telegram/SourceFiles/media/media_audio.cpp | 39 +++-- Telegram/SourceFiles/media/media_audio.h | 2 +- .../media/media_audio_ffmpeg_loader.cpp | 8 +- .../SourceFiles/media/media_audio_loaders.cpp | 2 +- .../media/media_child_ffmpeg_loader.cpp | 1 + .../SourceFiles/media/media_clip_ffmpeg.cpp | 32 +++- .../SourceFiles/media/media_clip_ffmpeg.h | 2 +- .../media/media_clip_implementation.h | 2 +- .../SourceFiles/media/media_clip_qtgif.cpp | 2 +- Telegram/SourceFiles/media/media_clip_qtgif.h | 2 +- .../SourceFiles/media/media_clip_reader.cpp | 45 +++-- .../SourceFiles/media/media_clip_reader.h | 6 +- .../media/view/media_clip_controller.cpp | 57 +++++-- .../media/view/media_clip_controller.h | 15 +- .../media/view/media_clip_playback.cpp | 20 ++- .../media/view/media_clip_playback.h | 6 +- Telegram/SourceFiles/mediaview.cpp | 55 +++--- Telegram/SourceFiles/mediaview.h | 7 +- .../SourceFiles/mtproto/file_download.cpp | 5 +- .../SourceFiles/overview/overview_layout.cpp | 4 +- .../platform/linux/main_window_linux.cpp | 4 +- .../platform/mac/main_window_mac.mm | 4 +- .../platform/win/main_window_win.cpp | 12 +- Telegram/SourceFiles/pspecific_win.cpp | 2 +- Telegram/SourceFiles/settingswidget.cpp | 2 +- Telegram/SourceFiles/structs.cpp | 7 +- Telegram/SourceFiles/structs.h | 2 +- Telegram/SourceFiles/title.cpp | 2 +- Telegram/SourceFiles/ui/boxshadow.cpp | 25 +-- Telegram/SourceFiles/ui/countryinput.cpp | 2 +- Telegram/SourceFiles/ui/images.cpp | 20 +-- Telegram/SourceFiles/ui/style/style_core.cpp | 2 + .../SourceFiles/ui/style/style_core_icon.cpp | 2 +- .../SourceFiles/ui/style/style_core_icon.h | 2 + .../SourceFiles/ui/style/style_core_types.cpp | 7 +- .../SourceFiles/ui/style/style_core_types.h | 1 + Telegram/SourceFiles/ui/text/text_block.cpp | 7 - 53 files changed, 419 insertions(+), 274 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index dc0d1689c..f1f396e4e 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2064,7 +2064,7 @@ namespace { cors[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0)); if (index != SmallMaskCorners && index != LargeMaskCorners) { for (int i = 0; i < 4; ++i) { - ::corners[index].p[i] = new QPixmap(QPixmap::fromImage(cors[i], Qt::ColorOnly)); + ::corners[index].p[i] = new QPixmap(pixmapFromImageInPlace(std_::move(cors[i]))); ::corners[index].p[i]->setDevicePixelRatio(cRetinaFactor()); } } @@ -2101,12 +2101,12 @@ namespace { } QImage mask[4]; - prepareCorners(LargeMaskCorners, st::msgRadius, st::white, 0, mask); + prepareCorners(LargeMaskCorners, st::msgRadius, st::white, nullptr, mask); for (int i = 0; i < 4; ++i) { ::cornersMaskLarge[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); ::cornersMaskLarge[i]->setDevicePixelRatio(cRetinaFactor()); } - prepareCorners(SmallMaskCorners, st::buttonRadius, st::white, 0, mask); + prepareCorners(SmallMaskCorners, st::buttonRadius, st::white, nullptr, mask); for (int i = 0; i < 4; ++i) { ::cornersMaskSmall[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); ::cornersMaskSmall[i]->setDevicePixelRatio(cRetinaFactor()); @@ -2267,7 +2267,7 @@ namespace { p.setCompositionMode(m); emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - ESize) / 2); } - i = map->insert(emojiKey(emoji), QPixmap::fromImage(img, Qt::ColorOnly)); + i = map->insert(emojiKey(emoji), App::pixmapFromImageInPlace(std_::move(img))); } return i.value(); } @@ -2603,13 +2603,13 @@ namespace { if (i == cornersMap.cend()) { QImage images[4]; switch (radius) { - case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, 0, images); break; - case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::msgRadius, bg, 0, images); break; + case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break; + case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::msgRadius, bg, nullptr, images); break; } CornersPixmaps pixmaps; for (int j = 0; j < 4; ++j) { - pixmaps.p[j] = new QPixmap(QPixmap::fromImage(images[j], Qt::ColorOnly)); + pixmaps.p[j] = new QPixmap(pixmapFromImageInPlace(std_::move(images[j]))); pixmaps.p[j]->setDevicePixelRatio(cRetinaFactor()); } i = cornersMap.insert(colorKey, pixmaps); @@ -2620,46 +2620,51 @@ namespace { void initBackground(int32 id, const QImage &p, bool nowrite) { if (Local::readBackground()) return; - QImage img(p); - bool remove = false; - if (p.isNull()) { - if (id == DefaultChatBackground) { - img.load(st::msgBG); - } else { - img.load(st::msgBG0); - if (cRetina()) { - img = img.scaledToWidth(img.width() * 2, Qt::SmoothTransformation); - } else if (cScale() != dbisOne) { - img = img.scaledToWidth(convertScale(img.width()), Qt::SmoothTransformation); - } - id = 0; - } - remove = true; - } - if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32_Premultiplied && img.format() != QImage::Format_RGB32) { - img = img.convertToFormat(QImage::Format_RGB32); - } - img.setDevicePixelRatio(cRetinaFactor()); - - if (!nowrite) { - Local::writeBackground(id, remove ? QImage() : img); - } - - delete cChatBackground(); - cSetChatBackground(new QPixmap(QPixmap::fromImage(img, Qt::ColorOnly))); - cSetChatBackgroundId(id); - - if (App::main()) App::main()->clearCachedBackground(); - uint64 components[3] = { 0 }, componentsScroll[3] = { 0 }, componentsPoint[3] = { 0 }; - int w = img.width(), h = img.height(), size = w * h; - const uchar *pix = img.constBits(); - if (pix) { - for (int32 i = 0, l = size * 4; i < l; i += 4) { - components[2] += pix[i + 0]; - components[1] += pix[i + 1]; - components[0] += pix[i + 2]; + int size = 0; + { + QImage img(p); + bool remove = false; + if (p.isNull()) { + if (id == DefaultChatBackground) { + img.load(st::msgBG); + } else { + img.load(st::msgBG0); + if (cRetina()) { + img = img.scaledToWidth(img.width() * 2, Qt::SmoothTransformation); + } else if (cScale() != dbisOne) { + img = img.scaledToWidth(convertScale(img.width()), Qt::SmoothTransformation); + } + id = 0; + } + remove = true; } + if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32_Premultiplied && img.format() != QImage::Format_RGB32) { + img = img.convertToFormat(QImage::Format_RGB32); + } + img.setDevicePixelRatio(cRetinaFactor()); + + if (!nowrite) { + Local::writeBackground(id, remove ? QImage() : img); + } + + int w = img.width(), h = img.height(); + size = w * h; + const uchar *pix = img.constBits(); + if (pix) { + for (int32 i = 0, l = size * 4; i < l; i += 4) { + components[2] += pix[i + 0]; + components[1] += pix[i + 1]; + components[0] += pix[i + 2]; + } + } + + delete cChatBackground(); + cSetChatBackground(new QPixmap(pixmapFromImageInPlace(std_::move(img)))); + cSetChatBackgroundId(id); + + if (App::main()) App::main()->clearCachedBackground(); + } if (size) { for (int32 i = 0; i < 3; ++i) components[i] /= size; @@ -2677,41 +2682,43 @@ namespace { uint64 max = qMax(1ULL, components[maxtomin[0]]), mid = qMax(1ULL, components[maxtomin[1]]), min = qMax(1ULL, components[maxtomin[2]]); - QImage dog = App::sprite().toImage().copy(st::msgDogImg.rect()); - QImage::Format f = dog.format(); - if (f != QImage::Format_ARGB32 && f != QImage::Format_ARGB32_Premultiplied) { - dog = dog.convertToFormat(QImage::Format_ARGB32_Premultiplied); - } - uchar *dogBits = dog.bits(); - if (max != min) { - float64 coef = float64(mid - min) / float64(max - min); - for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { - int dogmaxtomin[3] = { i, i + 1, i + 2 }; - if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) { - qSwap(dogmaxtomin[0], dogmaxtomin[1]); - } - if (dogBits[dogmaxtomin[1]] < dogBits[dogmaxtomin[2]]) { - qSwap(dogmaxtomin[1], dogmaxtomin[2]); + { + QImage dog = App::sprite().toImage().copy(st::msgDogImg.rect()); + QImage::Format f = dog.format(); + if (f != QImage::Format_ARGB32 && f != QImage::Format_ARGB32_Premultiplied) { + dog = dog.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + uchar *dogBits = dog.bits(); + if (max != min) { + float64 coef = float64(mid - min) / float64(max - min); + for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { + int dogmaxtomin[3] = { i, i + 1, i + 2 }; if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) { qSwap(dogmaxtomin[0], dogmaxtomin[1]); } + if (dogBits[dogmaxtomin[1]] < dogBits[dogmaxtomin[2]]) { + qSwap(dogmaxtomin[1], dogmaxtomin[2]); + if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) { + qSwap(dogmaxtomin[0], dogmaxtomin[1]); + } + } + uchar result[3]; + result[maxtomin[0]] = dogBits[dogmaxtomin[0]]; + result[maxtomin[2]] = dogBits[dogmaxtomin[2]]; + result[maxtomin[1]] = uchar(qRound(result[maxtomin[2]] + (result[maxtomin[0]] - result[maxtomin[2]]) * coef)); + dogBits[i] = result[2]; + dogBits[i + 1] = result[1]; + dogBits[i + 2] = result[0]; + } + } else { + for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { + uchar b = dogBits[i], g = dogBits[i + 1], r = dogBits[i + 2]; + dogBits[i] = dogBits[i + 1] = dogBits[i + 2] = (r + r + b + g + g + g) / 6; } - uchar result[3]; - result[maxtomin[0]] = dogBits[dogmaxtomin[0]]; - result[maxtomin[2]] = dogBits[dogmaxtomin[2]]; - result[maxtomin[1]] = uchar(qRound(result[maxtomin[2]] + (result[maxtomin[0]] - result[maxtomin[2]]) * coef)); - dogBits[i] = result[2]; - dogBits[i + 1] = result[1]; - dogBits[i + 2] = result[0]; - } - } else { - for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { - uchar b = dogBits[i], g = dogBits[i + 1], r = dogBits[i + 2]; - dogBits[i] = dogBits[i + 1] = dogBits[i + 2] = (r + r + b + g + g + g) / 6; } + delete cChatDogImage(); + cSetChatDogImage(new QPixmap(pixmapFromImageInPlace(std_::move(dog)))); } - delete cChatDogImage(); - cSetChatDogImage(new QPixmap(QPixmap::fromImage(dog))); memcpy(componentsScroll, components, sizeof(components)); memcpy(componentsPoint, components, sizeof(components)); @@ -2768,6 +2775,10 @@ namespace { uchar bsel = snap(qRound(((1. - alphaSel) * b + addSel) / alphaSel), 0, 0xFF); _msgServiceSelectBg = style::color(r, g, b, qRound(alphaSel * 0xFF)); + for (int i = 0; i < 4; ++i) { + delete ::corners[StickerCorners].p[i]; ::corners[StickerCorners].p[i] = nullptr; + delete ::corners[StickerSelectedCorners].p[i]; ::corners[StickerSelectedCorners].p[i] = nullptr; + } prepareCorners(StickerCorners, st::dateRadius, _msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, _msgServiceSelectBg); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 181b3d9e6..be4e3c21c 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1014,11 +1014,11 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { PreparedPhotoThumbs photoThumbs; QVector photoSizes; - QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + QPixmap thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('a', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + QPixmap medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('b', medium); photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 4dae334a5..aa44ed21a 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -595,7 +595,7 @@ void GroupInfoBox::onPhoto() { void GroupInfoBox::onPhotoReady(const QImage &img) { _photoBig = img; - _photoSmall = QPixmap::fromImage(img.scaled(st::newGroupPhotoSize * cIntRetinaFactor(), st::newGroupPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _photoSmall = App::pixmapFromImageInPlace(img.scaled(st::newGroupPhotoSize * cIntRetinaFactor(), st::newGroupPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _photoSmall.setDevicePixelRatio(cRetinaFactor()); } diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index a0c398f44..390adc7a3 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -61,7 +61,7 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) { } int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - _thumb = QPixmap::fromImage(img.scaled(s, s, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _thumb = App::pixmapFromImageInPlace(img.scaled(s, s, Qt::KeepAspectRatio, Qt::SmoothTransformation)); _thumbw = _thumb.width(); _thumbh = _thumb.height(); if (_thumbw > _thumbh) { diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index 7148a89c4..08ddb2175 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -110,7 +110,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW } _thumbx = (width() - _thumbw) / 2; - _thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); } else { if (_file->thumb.isNull()) { @@ -483,7 +483,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth) } _thumbx = (width() - _thumbw) / 2; - _thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); } diff --git a/Telegram/SourceFiles/core/basic_types.cpp b/Telegram/SourceFiles/core/basic_types.cpp index 89bbd34bf..868a5d23f 100644 --- a/Telegram/SourceFiles/core/basic_types.cpp +++ b/Telegram/SourceFiles/core/basic_types.cpp @@ -24,6 +24,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #include +#include +#include +#include +#include +#include extern "C" { #include @@ -288,10 +293,19 @@ namespace ThirdParty { } void finish() { - av_lockmgr_register(0); + av_lockmgr_register(nullptr); + + CRYPTO_cleanup_all_ex_data(); + FIPS_mode_set(0); + ENGINE_cleanup(); + CONF_modules_unload(1); + ERR_remove_state(0); + ERR_free_strings(); + ERR_remove_thread_state(nullptr); + EVP_cleanup(); delete[] _sslLocks; - _sslLocks = 0; + _sslLocks = nullptr; Platform::ThirdParty::finish(); } diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 6c1c672c9..2813792d5 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -5857,7 +5857,7 @@ HistoryWebPage::~HistoryWebPage() { } namespace { - LocationManager manager; + LocationManager *locationManager = nullptr; } void LocationManager::init() { @@ -5869,13 +5869,16 @@ void LocationManager::init() { connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(onFailed(QNetworkReply*))); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*))); - if (black) delete black; + if (black) { + delete black->v(); + delete black; + } QImage b(cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); { QPainter p(&b); p.fillRect(QRect(0, 0, cIntRetinaFactor(), cIntRetinaFactor()), st::white->b); } - QPixmap p = QPixmap::fromImage(b, Qt::ColorOnly); + QPixmap p = App::pixmapFromImageInPlace(std_::move(b)); p.setDevicePixelRatio(cRetinaFactor()); black = new ImagePtr(p, "PNG"); } @@ -5887,26 +5890,36 @@ void LocationManager::reinit() { void LocationManager::deinit() { if (manager) { delete manager; - manager = 0; + manager = nullptr; } if (black) { + delete black->v(); delete black; - black = 0; + black = nullptr; } dataLoadings.clear(); imageLoadings.clear(); } void initImageLinkManager() { - manager.init(); + if (!locationManager) { + locationManager = new LocationManager(); + locationManager->init(); + } } void reinitImageLinkManager() { - manager.reinit(); + if (locationManager) { + locationManager->reinit(); + } } void deinitImageLinkManager() { - manager.deinit(); + if (locationManager) { + locationManager->deinit(); + delete locationManager; + locationManager = nullptr; + } } void LocationManager::getData(LocationData *data) { @@ -6046,7 +6059,9 @@ void LocationData::load() { if (loading) return; loading = true; - manager.getData(this); + if (locationManager) { + locationManager->getData(this); + } } HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 8f867bb09..484e67090 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -2445,8 +2445,6 @@ struct LocationData; class LocationManager : public QObject { Q_OBJECT public: - LocationManager() : manager(0), black(0) { - } void init(); void reinit(); void deinit(); @@ -2464,10 +2462,10 @@ public slots: private: void failed(LocationData *data); - QNetworkAccessManager *manager; + QNetworkAccessManager *manager = nullptr; QMap dataLoadings, imageLoadings; QMap serverRedirects; - ImagePtr *black; + ImagePtr *black = nullptr; }; class HistoryLocation : public HistoryMedia { diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index c59f1d64f..cfeffcdf0 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -53,7 +53,7 @@ FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent) _inner->setGeometry(rect()); _scroll->setGeometry(rect()); - _scroll->setWidget(_inner); + _scroll->setOwnedWidget(_inner); _scroll->show(); _inner->show(); @@ -946,4 +946,7 @@ void FieldAutocompleteInner::onPreview() { } } +FieldAutocompleteInner::~FieldAutocompleteInner() { +} + } // namespace internal diff --git a/Telegram/SourceFiles/history/field_autocomplete.h b/Telegram/SourceFiles/history/field_autocomplete.h index 4dd85260b..71b4cba99 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.h +++ b/Telegram/SourceFiles/history/field_autocomplete.h @@ -37,7 +37,6 @@ class FieldAutocomplete final : public TWidget { Q_OBJECT public: - FieldAutocomplete(QWidget *parent); void fastHide(); @@ -79,7 +78,6 @@ public: ~FieldAutocomplete(); signals: - void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const; void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const; void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const; @@ -88,14 +86,12 @@ signals: void moderateKeyActivate(int key, bool *outHandled) const; public slots: - void hideStart(); void hideFinish(); void showStart(); private: - void paintEvent(QPaintEvent *e) override; void updateFiltered(bool resetScroll = false); @@ -146,7 +142,6 @@ class FieldAutocompleteInner final : public TWidget { Q_OBJECT public: - FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows); void clearSel(bool hidden = false); @@ -155,8 +150,9 @@ public: void setRecentInlineBotsInRows(int32 bots); -signals: + ~FieldAutocompleteInner(); +signals: void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const; void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const; void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const; @@ -164,13 +160,11 @@ signals: void mustScrollTo(int scrollToTop, int scrollToBottom); public slots: - void onParentGeometryChanged(); void onUpdateSelected(bool force = false); void onPreview(); private: - void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; @@ -199,6 +193,7 @@ private: bool _previewShown; QTimer _previewTimer; + }; } // namespace internal diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 290eeab90..3f23f6034 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -2143,7 +2143,7 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b } void HistoryInner::notifyIsBotChanged() { - BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo : nullptr; + BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr; if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) { return; } diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index ab8bfc44d..7e08d5fd7 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -242,7 +242,7 @@ void IntroSignup::onCheckRequest() { void IntroSignup::onPhotoReady(const QImage &img) { _photoBig = img; - _photoSmall = QPixmap::fromImage(img.scaled(st::introPhotoSize * cIntRetinaFactor(), st::introPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _photoSmall = App::pixmapFromImageInPlace(img.scaled(st::introPhotoSize * cIntRetinaFactor(), st::introPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _photoSmall.setDevicePixelRatio(cRetinaFactor()); } diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index ce407efbe..8419f665e 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -312,7 +312,7 @@ void FileLoadTask::process() { if (!cover.isNull()) { // cover to thumb int32 cw = cover.width(), ch = cover.height(); if (cw < 20 * ch && ch < 20 * cw) { - QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly); + QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std_::move(cover)); { QByteArray thumbFormat = "JPG"; int32 thumbQuality = 87; @@ -339,7 +339,7 @@ void FileLoadTask::process() { attributes.push_back(animatedAttribute); gif = true; - QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly); + QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std_::move(cover)); { QByteArray thumbFormat = "JPG"; int32 thumbQuality = 87; @@ -369,15 +369,15 @@ void FileLoadTask::process() { if (animated) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (_type != PrepareDocument) { - QPixmap thumb = (w > 100 || h > 100) ? QPixmap::fromImage(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage); + QPixmap thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('s', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - QPixmap medium = (w > 320 || h > 320) ? QPixmap::fromImage(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage); + QPixmap medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('m', medium); photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - QPixmap full = (w > 1280 || h > 1280) ? QPixmap::fromImage(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage); + QPixmap full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('y', full); photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); @@ -397,7 +397,7 @@ void FileLoadTask::process() { thumbname = qsl("thumb.webp"); } - QPixmap full = (w > 90 || h > 90) ? QPixmap::fromImage(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage, Qt::ColorOnly); + QPixmap full = (w > 90 || h > 90) ? App::pixmapFromImageInPlace(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage, Qt::ColorOnly); { QBuffer buffer(&thumbdata); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 9d30c85aa..11e617c13 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -2643,7 +2643,7 @@ namespace Local { case StorageFileWebp: guessFormat = "WEBP"; break; default: guessFormat = QByteArray(); break; } - pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly); + pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); if (!pixmap.isNull()) { format = guessFormat; } @@ -2910,7 +2910,7 @@ namespace Local { struct Result { Result(StorageFileType type, const QByteArray &data) : image(type, data) { QByteArray guessFormat; - pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly); + pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); if (!pixmap.isNull()) { format = guessFormat; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index bc661a7b3..f30eb1c70 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1019,13 +1019,13 @@ void MainWidget::onCacheBackground() { } _cachedX = 0; _cachedY = 0; - _cachedBackground = QPixmap::fromImage(result); + _cachedBackground = App::pixmapFromImageInPlace(std_::move(result)); } else { QRect to, from; backgroundParams(_willCacheFor, to, from); _cachedX = to.x(); _cachedY = to.y(); - _cachedBackground = QPixmap::fromImage(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + _cachedBackground = App::pixmapFromImageInPlace(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); _cachedBackground.setDevicePixelRatio(cRetinaFactor()); } _cachedFor = _willCacheFor; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index ba495270d..88e49d8a0 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -174,7 +174,7 @@ void NotifyWindow::updateNotifyDisplay() { history->peer->loadUserpic(true, true); history->peer->paintUserpicLeft(p, st::notifyPhotoSize, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width()); } else { - static QPixmap icon = QPixmap::fromImage(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + static QPixmap icon = App::pixmapFromImageInPlace(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), icon); } @@ -234,7 +234,7 @@ void NotifyWindow::updateNotifyDisplay() { } } - pm = QPixmap::fromImage(img, Qt::ColorOnly); + pm = App::pixmapFromImageInPlace(std_::move(img)); update(); } @@ -246,7 +246,7 @@ void NotifyWindow::updatePeerPhoto() { p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), peerPhoto->pix(st::notifyPhotoSize)); } peerPhoto = ImagePtr(); - pm = QPixmap::fromImage(img, Qt::ColorOnly); + pm = App::pixmapFromImageInPlace(std_::move(img)); update(); } } @@ -1882,7 +1882,7 @@ QImage MainWindow::iconWithCounter(int size, int count, style::color bg, bool sm placeSmallCounter(img, size, count, bg, QPoint(), st::counterColor); } else { QPainter p(&img); - p.drawPixmap(size / 2, size / 2, QPixmap::fromImage(iconWithCounter(-size / 2, count, bg, false), Qt::ColorOnly)); + p.drawPixmap(size / 2, size / 2, App::pixmapFromImageInPlace(iconWithCounter(-size / 2, count, bg, false))); } return img; } @@ -1952,7 +1952,7 @@ PreLaunchWindow *PreLaunchWindowInstance = 0; PreLaunchWindow::PreLaunchWindow(QString title) : TWidget(0) { Fonts::start(); - QIcon icon(QPixmap::fromImage(QImage(cPlatform() == dbipMac ? qsl(":/gui/art/iconbig256.png") : qsl(":/gui/art/icon256.png")), Qt::ColorOnly)); + QIcon icon(App::pixmapFromImageInPlace(QImage(cPlatform() == dbipMac ? qsl(":/gui/art/iconbig256.png") : qsl(":/gui/art/icon256.png")))); if (cPlatform() == dbipLinux32 || cPlatform() == dbipLinux64) { icon = QIcon::fromTheme("telegram", icon); } diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 12604b8e6..dbee88b81 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -484,14 +484,12 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { if (stopped) emit updated(stopped); } -void AudioPlayer::initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { - t_assert(audio.type() == AudioMsgId::Type::Video); - - auto type = audio.type(); +void AudioPlayer::initFromVideo(uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { AudioMsgId stopped; { QMutexLocker lock(&playerMutex); + auto type = AudioMsgId::Type::Video; auto current = dataForType(type); t_assert(current != nullptr); @@ -503,7 +501,7 @@ void AudioPlayer::initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std } emit faderOnTimer(); current->clear(); - current->audio = audio; + current->audio = AudioMsgId(AudioMsgId::Type::Video); current->videoPlayId = videoPlayId; current->videoData = std_::move(data); { @@ -516,7 +514,7 @@ void AudioPlayer::initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std current->playbackState.state = AudioPlayerPaused; current->loading = true; - emit loaderOnStart(audio, position); + emit loaderOnStart(current->audio, position); } if (stopped) emit updated(stopped); } @@ -651,7 +649,7 @@ void AudioPlayer::videoSoundProgress(const AudioMsgId &audio) { auto current = dataForType(type); t_assert(current != nullptr); - if (current->videoPlayId == _lastVideoPlayId && current->playbackState.frequency) { + if (current->videoPlayId == _lastVideoPlayId && current->playbackState.duration && current->playbackState.frequency) { _lastVideoPlaybackWhen = getms(); _lastVideoPlaybackCorrectedMs = (current->playbackState.position * 1000ULL) / current->playbackState.frequency; } @@ -1523,15 +1521,11 @@ void AudioCaptureInner::onStop(bool needResult) { if (d->device) { alcCaptureStop(d->device); alcCaptureCloseDevice(d->device); - d->device = 0; + d->device = nullptr; - if (d->ioContext) { - av_free(d->ioContext); - d->ioContext = 0; - } if (d->codecContext) { avcodec_close(d->codecContext); - d->codecContext = 0; + d->codecContext = nullptr; } if (d->srcSamplesData) { if (d->srcSamplesData[0]) { @@ -1548,23 +1542,28 @@ void AudioCaptureInner::onStop(bool needResult) { d->fullSamples = 0; if (d->swrContext) { swr_free(&d->swrContext); - d->swrContext = 0; + d->swrContext = nullptr; } if (d->opened) { avformat_close_input(&d->fmtContext); d->opened = false; - d->ioBuffer = 0; + } + if (d->ioContext) { + av_free(d->ioContext->buffer); + av_free(d->ioContext); + d->ioContext = nullptr; + d->ioBuffer = nullptr; } else if (d->ioBuffer) { av_free(d->ioBuffer); - d->ioBuffer = 0; + d->ioBuffer = nullptr; } if (d->fmtContext) { avformat_free_context(d->fmtContext); - d->fmtContext = 0; + d->fmtContext = nullptr; } - d->fmt = 0; - d->stream = 0; - d->codec = 0; + d->fmt = nullptr; + d->stream = nullptr; + d->codec = nullptr; d->lastUpdate = 0; d->levelMax = 0; diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index e7a392d4f..012725cf3 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -67,7 +67,7 @@ public: void stop(AudioMsgId::Type type); // Video player audio stream interface. - void initFromVideo(const AudioMsgId &audio, uint64 videoPlayId, std_::unique_ptr &&data, int64 position); + void initFromVideo(uint64 videoPlayId, std_::unique_ptr &&data, int64 position); void feedFromVideo(VideoSoundPart &&part); int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); void videoSoundProgress(const AudioMsgId &audio); diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp index 4fed50ca0..69bd99cd2 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -76,9 +76,12 @@ bool AbstractFFMpegLoader::open(qint64 position) { } AbstractFFMpegLoader::~AbstractFFMpegLoader() { - if (ioContext) av_free(ioContext); if (_opened) { avformat_close_input(&fmtContext); + } + if (ioContext) { + av_free(ioContext->buffer); + av_free(ioContext); } else if (ioBuffer) { av_free(ioBuffer); } @@ -221,9 +224,6 @@ bool FFMpegLoader::open(qint64 position) { if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) { } } - //if (dstSamplesData) { - // position = qRound(srcRate * (position / float64(dstRate))); - //} } return true; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index f635c8326..83ec07544 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -201,7 +201,7 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { } m->skipStart = position; m->skipEnd = m->playbackState.duration - position; - m->playbackState.position = 0; + m->playbackState.position = position; m->started = 0; } if (samplesCount) { diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index c6534720b..4ec96b1c1 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -182,6 +182,7 @@ ChildFFMpegLoader::~ChildFFMpegLoader() { for (auto &packet : queue) { FFMpeg::freePacket(&packet); } + if (_swrContext) swr_free(&_swrContext); if (_dstSamplesData) { if (_dstSamplesData[0]) { av_freep(&_dstSamplesData[0]); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 80153b0d3..460fc689e 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -233,7 +233,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q } // Read some future packets for audio stream. - if (_audioStreamId) { + if (_audioStreamId >= 0) { while (_frameMs + 5000 > _lastReadPacketMs) { auto packetResult = readPacket(); if (packetResult != PacketResult::Ok) { @@ -246,7 +246,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q return true; } -bool FFMpegReaderImplementation::start(Mode mode) { +bool FFMpegReaderImplementation::start(Mode mode, int64 positionMs) { _mode = mode; initDevice(); @@ -309,6 +309,7 @@ bool FFMpegReaderImplementation::start(Mode mode) { return false; } + std_::unique_ptr soundData; if (_audioStreamId >= 0) { // Get a pointer to the codec context for the audio stream auto audioContextOriginal = _fmtContext->streams[_audioStreamId]->codec; @@ -326,7 +327,7 @@ bool FFMpegReaderImplementation::start(Mode mode) { return false; } - auto soundData = std_::make_unique(); + soundData = std_::make_unique(); soundData->context = audioContext; soundData->frequency = audioContextOriginal->sample_rate; if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { @@ -334,7 +335,20 @@ bool FFMpegReaderImplementation::start(Mode mode) { } else { soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; } - audioPlayer()->initFromVideo(AudioMsgId(AudioMsgId::Type::Video), _playId, std_::move(soundData), 0); + } + + if (positionMs) { + int64 ts = (positionMs * _fmtContext->streams[_streamId]->time_base.den) / (1000LL * _fmtContext->streams[_streamId]->time_base.num); + if (av_seek_frame(_fmtContext, _streamId, ts, AVSEEK_FLAG_ANY) < 0) { + if (av_seek_frame(_fmtContext, _streamId, ts, 0) < 0) { + positionMs = 0; + } + } + } + + if (_audioStreamId >= 0) { + int64 position = (positionMs * soundData->frequency) / 1000LL; + audioPlayer()->initFromVideo(_playId, std_::move(soundData), position); } return true; @@ -348,22 +362,26 @@ FFMpegReaderImplementation::~FFMpegReaderImplementation() { if (_audioStreamId >= 0) { audioPlayer()->stopFromVideo(_playId); } + + clearPacketQueue(); + if (_frameRead) { av_frame_unref(_frame); _frameRead = false; } - if (_ioContext) av_free(_ioContext); if (_codecContext) avcodec_close(_codecContext); if (_swsContext) sws_freeContext(_swsContext); if (_opened) { avformat_close_input(&_fmtContext); + } + if (_ioContext) { + av_free(_ioContext->buffer); + av_free(_ioContext); } else if (_ioBuffer) { av_free(_ioBuffer); } if (_fmtContext) avformat_free_context(_fmtContext); av_frame_free(&_frame); - - clearPacketQueue(); } FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket() { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index b048798f6..cf54258d6 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -51,7 +51,7 @@ public: void pauseAudio() override; void resumeAudio() override; - bool start(Mode mode) override; + bool start(Mode mode, int64 positionMs) override; QString logData() const; diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 61e96fb0b..21d2ad53a 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -58,7 +58,7 @@ public: virtual void pauseAudio() = 0; virtual void resumeAudio() = 0; - virtual bool start(Mode mode) = 0; + virtual bool start(Mode mode, int64 positionMs) = 0; virtual ~ReaderImplementation() { } int64 dataSize() const { diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index 0ef2b8d96..d1216f213 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -99,7 +99,7 @@ int64 QtGifReaderImplementation::durationMs() const { return 0; // not supported } -bool QtGifReaderImplementation::start(Mode mode) { +bool QtGifReaderImplementation::start(Mode mode, int64 positionMs) { if (mode == Mode::OnlyGifv) return false; _mode = mode; return jumpToStart(); diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 3fc704183..4384dd9e8 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -47,7 +47,7 @@ public: void resumeAudio() override { } - bool start(Mode mode) override; + bool start(Mode mode, int64 positionMs) override; ~QtGifReaderImplementation(); diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index cae75409b..5a9cea8e4 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -85,10 +85,11 @@ QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool } // namespace -Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode) +Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode, int64 seekMs) : _callback(std_::move(callback)) , _mode(mode) -, _playId(rand_value()) { +, _playId(rand_value()) +, _seekPositionMs(seekMs) { if (threads.size() < ClipThreadsCount) { _threadIndex = threads.size(); threads.push_back(new QThread()); @@ -269,7 +270,7 @@ int64 Reader::getPositionMs() const { if (auto frame = frameToShow()) { return frame->positionMs; } - return 0; + return _seekPositionMs; } int64 Reader::getDurationMs() const { @@ -310,10 +311,12 @@ void Reader::stop() { void Reader::error() { _state = State::Error; + _private = nullptr; } void Reader::finished() { _state = State::Finished; + _private = nullptr; } Reader::~Reader() { @@ -325,6 +328,7 @@ public: ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) , _mode(reader->mode()) , _playId(reader->playId()) + , _seekPositionMs(reader->seekPositionMs()) , _data(data) { if (_data.isEmpty()) { _location = std_::make_unique(location); @@ -337,7 +341,7 @@ public: } ProcessResult start(uint64 ms) { - if (!_implementation && !init()) { + if (!_implementation && !init(_seekPositionMs)) { return error(); } if (frame() && frame()->original.isNull()) { @@ -348,6 +352,8 @@ public: if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { return error(); } + frame()->positionMs = _implementation->frameRealTime(); + _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); @@ -381,7 +387,7 @@ public: } ProcessResult finishProcess(uint64 ms) { - auto readResult = _implementation->readFramesTill(ms - _animationStarted); + auto readResult = _implementation->readFramesTill(_skippedMs + ms - _animationStarted); if (readResult == internal::ReaderImplementation::ReadResult::Eof) { stop(); _state = State::Finished; @@ -391,6 +397,11 @@ public: } _nextFramePositionMs = _implementation->frameRealTime(); _nextFrameWhen = _animationStarted + _implementation->framePresentationTime(); + if (static_cast(_nextFrameWhen) > _skippedMs) { + _nextFrameWhen -= _skippedMs; + } else { + _nextFrameWhen = 1; + } if (!renderFrame()) { return error(); @@ -411,7 +422,7 @@ public: return true; } - bool init() { + bool init(int64 positionMs) { if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { QFile f(_location->name()); if (f.open(QIODevice::ReadOnly)) { @@ -432,7 +443,8 @@ public: } return ImplementationMode::Normal; }; - return _implementation->start(implementationMode()); + _skippedMs = positionMs; + return _implementation->start(implementationMode(), positionMs); } void startedAt(uint64 ms) { @@ -485,6 +497,7 @@ private: State _state = State::Reading; Reader::Mode _mode; uint64 _playId; + int64 _seekPositionMs = 0; QByteArray _data; std_::unique_ptr _location; @@ -517,6 +530,7 @@ private: uint64 _animationStarted = 0; uint64 _nextFrameWhen = 0; int64 _nextFramePositionMs = 0; + int64 _skippedMs = 0; bool _autoPausedGif = false; bool _started = false; @@ -699,11 +713,12 @@ void Manager::process() { _timer.stop(); _processingInThread = thread(); + bool checkAllReaders = false; uint64 ms = getms(), minms = ms + 86400 * 1000ULL; { QReadLocker lock(&_readerPointersMutex); for (auto it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - if (it->v.loadAcquire()) { + if (it->v.loadAcquire() && it.key()->_private != nullptr) { auto i = _readers.find(it.key()->_private); if (i == _readers.cend()) { _readers.insert(it.key()->_private, 0); @@ -723,6 +738,7 @@ void Manager::process() { it->v.storeRelease(0); } } + checkAllReaders = (_readers.size() > _readerPointers.size()); } for (auto i = _readers.begin(), e = _readers.end(); i != e;) { @@ -744,6 +760,15 @@ void Manager::process() { } else { i.value() = (ms + 86400 * 1000ULL); } + } else if (checkAllReaders) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (it == _readerPointers.cend()) { + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + delete reader; + i = _readers.erase(i); + continue; + } } if (!reader->_autoPausedGif && i.value() < minms) { minms = i.value(); @@ -771,7 +796,7 @@ void Manager::clear() { { QWriteLocker lock(&_readerPointersMutex); for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - it.key()->_private = 0; + it.key()->_private = nullptr; } _readerPointers.clear(); } @@ -792,7 +817,7 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data auto playId = 0ULL; auto reader = std_::make_unique(&localloc, &localdata, playId); - if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv)) { + if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv, 0)) { bool hasAlpha = false; auto readResult = reader->readFramesTill(-1); auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success); diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 8960ec61e..c4620a2c1 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -58,7 +58,7 @@ public: Video, }; - Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif); + Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif, int64 seekMs = 0); static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted void setAutoplay() { @@ -71,6 +71,9 @@ public: uint64 playId() const { return _playId; } + int64 seekPositionMs() const { + return _seekPositionMs; + } void start(int framew, int frameh, int outerw, int outerh, bool rounded); QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); @@ -128,6 +131,7 @@ private: uint64 _playId; bool _hasAudio = false; int64 _durationMs = 0; + int64 _seekPositionMs = 0; mutable int _width = 0; mutable int _height = 0; diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index d9cf95629..eb9f1ae3f 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -48,19 +48,29 @@ Controller::Controller(QWidget *parent) : TWidget(parent) connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); - connect(_playback, SIGNAL(seekProgress(int64)), this, SLOT(onSeekProgress(int64))); - connect(_playback, SIGNAL(seekFinished(int64)), this, SLOT(onSeekFinished(int64))); + connect(_playback, SIGNAL(seekProgress(float64)), this, SLOT(onSeekProgress(float64))); + connect(_playback, SIGNAL(seekFinished(float64)), this, SLOT(onSeekFinished(float64))); connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64))); } -void Controller::onSeekProgress(int64 position) { - _seekPosition = position; - emit seekProgress(position); +void Controller::onSeekProgress(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + if (_seekPositionMs != positionMs) { + _seekPositionMs = positionMs; + emit seekProgress(positionMs); + refreshTimeTexts(); + } } -void Controller::onSeekFinished(int64 position) { - _seekPosition = -1; - emit seekFinished(position); +void Controller::onSeekFinished(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + _seekPositionMs = -1; + emit seekFinished(positionMs); + refreshTimeTexts(); } void Controller::showAnimated() { @@ -89,14 +99,14 @@ void Controller::fadeUpdated(float64 opacity) { _playback->setFadeOpacity(opacity); } -void Controller::updatePlayback(const AudioPlaybackState &playbackState) { +void Controller::updatePlayback(const AudioPlaybackState &playbackState, bool reset) { updatePlayPauseResumeState(playbackState); - _playback->updateState(playbackState); + _playback->updateState(playbackState, reset); updateTimeTexts(playbackState); } void Controller::updatePlayPauseResumeState(const AudioPlaybackState &playbackState) { - bool showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming); + bool showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || _seekPositionMs >= 0); if (showPause != _showPause) { disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed())); _showPause = showPause; @@ -120,11 +130,30 @@ void Controller::updateTimeTexts(const AudioPlaybackState &playbackState) { auto playAlready = position / playFrequency; auto playLeft = (playbackState.duration / playFrequency) - playAlready; - auto timeAlready = formatDurationText(playAlready); - auto minus = QChar(8722); - auto timeLeft = minus + formatDurationText(playLeft); + _lastDurationMs = (playbackState.duration * 1000LL) / playFrequency; + _timeAlready = formatDurationText(playAlready); + auto minus = QChar(8722); + _timeLeft = minus + formatDurationText(playLeft); + + if (_seekPositionMs < 0) { + refreshTimeTexts(); + } +} + +void Controller::refreshTimeTexts() { auto alreadyChanged = false, leftChanged = false; + auto timeAlready = _timeAlready; + auto timeLeft = _timeLeft; + if (_seekPositionMs >= 0) { + auto playAlready = _seekPositionMs / 1000LL; + auto playLeft = (_lastDurationMs / 1000LL) - playAlready; + + timeAlready = formatDurationText(playAlready); + auto minus = QChar(8722); + timeLeft = minus + formatDurationText(playLeft); + } + _playedAlready->setText(timeAlready, &alreadyChanged); _toPlayLeft->setText(timeLeft, &leftChanged); if (alreadyChanged || leftChanged) { diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h index 14a5a5c13..25ebfd720 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -43,7 +43,7 @@ public: void showAnimated(); void hideAnimated(); - void updatePlayback(const AudioPlaybackState &playbackState); + void updatePlayback(const AudioPlaybackState &playbackState, bool reset); void setInFullScreen(bool inFullScreen); void grabStart() override; @@ -52,15 +52,15 @@ public: signals: void playPressed(); void pausePressed(); - void seekProgress(int64 position); - void seekFinished(int64 position); + void seekProgress(int64 positionMs); + void seekFinished(int64 positionMs); void volumeChanged(float64 volume); void toFullScreenPressed(); void fromFullScreenPressed(); private slots: - void onSeekProgress(int64 position); - void onSeekFinished(int64 position); + void onSeekProgress(float64 progress); + void onSeekFinished(float64 progress); protected: void resizeEvent(QResizeEvent *e) override; @@ -75,9 +75,12 @@ private: void updatePlayPauseResumeState(const AudioPlaybackState &playbackState); void updateTimeTexts(const AudioPlaybackState &playbackState); + void refreshTimeTexts(); bool _showPause = false; - int64 _seekPosition = -1; + QString _timeAlready, _timeLeft; + int64 _seekPositionMs = -1; + int64 _lastDurationMs = 0; ChildWidget _playPauseResume; ChildWidget _playback; diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index be628d0c8..1fccb7278 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -32,11 +32,11 @@ Playback::Playback(QWidget *parent) : TWidget(parent) setCursor(style::cur_pointer); } -void Playback::updateState(const AudioPlaybackState &playbackState) { +void Playback::updateState(const AudioPlaybackState &playbackState, bool reset) { qint64 position = 0, duration = playbackState.duration; _playing = !(playbackState.state & AudioPlayerStoppedMask); - if (_playing && playbackState.state != AudioPlayerFinishing) { + if (_playing || playbackState.state == AudioPlayerStopped) { position = playbackState.position; } else if (playbackState.state == AudioPlayerStoppedAtEnd) { position = playbackState.duration; @@ -51,7 +51,7 @@ void Playback::updateState(const AudioPlaybackState &playbackState) { progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; } if (duration != _duration || position != _position) { - if (duration && _duration && playbackState.state != AudioPlayerStopped) { + if (duration && _duration && !reset) { a_progress.start(progress); _a_progress.start(); } else { @@ -112,12 +112,26 @@ void Playback::paintEvent(QPaintEvent *e) { } void Playback::mouseMoveEvent(QMouseEvent *e) { + if (_mouseDown) { + _downProgress = snap(e->pos().x() / float64(width()), 0., 1.); + emit seekProgress(_downProgress); + update(); + } } void Playback::mousePressEvent(QMouseEvent *e) { + _mouseDown = true; + _downProgress = snap(e->pos().x() / float64(width()), 0., 1.); + emit seekProgress(_downProgress); + update(); } void Playback::mouseReleaseEvent(QMouseEvent *e) { + if (_mouseDown) { + _mouseDown = false; + emit seekFinished(_downProgress); + update(); + } } void Playback::enterEvent(QEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h index ad1a645f3..190358d78 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.h +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -31,12 +31,12 @@ class Playback : public TWidget { public: Playback(QWidget *parent); - void updateState(const AudioPlaybackState &playbackState); + void updateState(const AudioPlaybackState &playbackState, bool reset); void setFadeOpacity(float64 opacity); signals: - void seekProgress(int64 position); - void seekFinished(int64 position); + void seekProgress(float64 progress); + void seekFinished(float64 progress); protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 54cab77d5..94a496819 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -236,6 +236,7 @@ bool MediaView::gifShown() const { void MediaView::stopGif() { _gif = nullptr; + _videoPaused = _videoIsSilent = false; _clipController.destroy(); Sandbox::removeEventFilter(this); if (audioPlayer()) { @@ -1121,7 +1122,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty const FileLocation &location(_doc->location(true)); if (location.accessEnable()) { if (QImageReader(location.name()).canRead()) { - _current = QPixmap::fromImage(App::readImage(location.name(), 0, false), Qt::ColorOnly); + _current = App::pixmapFromImageInPlace(App::readImage(location.name(), 0, false)); } } location.accessDisable(); @@ -1258,7 +1259,7 @@ void MediaView::createClipReader() { _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), mode); // Correct values will be set when gif gets inited. - _videoIsSilent = false; + _videoPaused = _videoIsSilent = false; _videoPositionMs = 0ULL; _videoDurationMs = _doc->duration() * 1000ULL; @@ -1306,21 +1307,7 @@ void MediaView::onVideoPauseResume() { if (_gif->state() == Media::Clip::State::Error) { displayDocument(_doc, item); } else if (_gif->state() == Media::Clip::State::Finished) { - _autoplayVideoDocument = _doc; - - _current = _gif->current(_gif->width(), _gif->height(), _gif->width(), _gif->height(), getms()); - _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), Media::Clip::Reader::Mode::Video); - - // Correct values will be set when gif gets inited. - _videoIsSilent = false; - _videoPositionMs = 0; - - AudioPlaybackState state; - state.state = AudioPlayerStopped; - state.position = _videoPositionMs; - state.duration = _videoDurationMs; - state.frequency = _videoFrequencyMs; - updateVideoPlaybackState(state); + restartVideoAtSeekPosition(0); } else { _gif->pauseResumeVideo(); _videoPaused = _gif->videoPaused(); @@ -1333,12 +1320,34 @@ void MediaView::onVideoPauseResume() { } } -void MediaView::onVideoSeekProgress(int64 position) { +void MediaView::restartVideoAtSeekPosition(int64 positionMs) { + _autoplayVideoDocument = _doc; + if (_current.isNull()) { + _current = _gif->current(_gif->width(), _gif->height(), _gif->width(), _gif->height(), getms()); + } + _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), Media::Clip::Reader::Mode::Video, positionMs); + + // Correct values will be set when gif gets inited. + _videoPaused = _videoIsSilent = false; + _videoPositionMs = positionMs; + + AudioPlaybackState state; + state.state = AudioPlayerPlaying; + state.position = _videoPositionMs; + state.duration = _videoDurationMs; + state.frequency = _videoFrequencyMs; + updateVideoPlaybackState(state, true); } -void MediaView::onVideoSeekFinished(int64 position) { +void MediaView::onVideoSeekProgress(int64 positionMs) { + if (!_videoPaused) { + onVideoPauseResume(); + } +} +void MediaView::onVideoSeekFinished(int64 positionMs) { + restartVideoAtSeekPosition(positionMs); } void MediaView::onVideoVolumeChanged(float64 volume) { @@ -1361,12 +1370,14 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { t_assert(audioPlayer() != nullptr); auto state = audioPlayer()->currentVideoState(_gif->playId()); - updateVideoPlaybackState(state); + if (state.duration) { + updateVideoPlaybackState(state); + } } -void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) { +void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state, bool reset) { if (state.frequency) { - _clipController->updatePlayback(state); + _clipController->updatePlayback(state, reset); } else { // Audio has stopped already. _videoIsSilent = true; updateSilentVideoPlaybackState(); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 582307f8c..81b7ab02a 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -118,8 +118,8 @@ protected: private slots: void onVideoPauseResume(); - void onVideoSeekProgress(int64 position); - void onVideoSeekFinished(int64 position); + void onVideoSeekProgress(int64 positionMs); + void onVideoSeekFinished(int64 positionMs); void onVideoVolumeChanged(float64 volume); void onVideoToFullScreen(); void onVideoFromFullScreen(); @@ -131,8 +131,9 @@ private: void findCurrent(); void loadBack(); - void updateVideoPlaybackState(const AudioPlaybackState &state); + void updateVideoPlaybackState(const AudioPlaybackState &state, bool reset = false); void updateSilentVideoPlaybackState(); + void restartVideoAtSeekPosition(int64 positionMs); void createClipController(); void setClipControllerGeometry(); diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index ab8a25af5..d8c588eee 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -105,9 +105,10 @@ void FileLoader::readImage(const QSize &shrinkBox) const { QImage image = App::readImage(_data, &format, false); if (!image.isNull()) { if (!shrinkBox.isEmpty() && (image.width() > shrinkBox.width() || image.height() > shrinkBox.height())) { - image = image.scaled(shrinkBox, Qt::KeepAspectRatio, Qt::SmoothTransformation); + _imagePixmap = App::pixmapFromImageInPlace(image.scaled(shrinkBox, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } else { + _imagePixmap = App::pixmapFromImageInPlace(std_::move(image)); } - _imagePixmap = QPixmap::fromImage(image, Qt::ColorOnly); _imageFormat = format; } } diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 59bb7caa4..242d7a473 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -202,7 +202,7 @@ void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const img.setDevicePixelRatio(cRetinaFactor()); _data->forget(); - _pix = QPixmap::fromImage(img, Qt::ColorOnly); + _pix = App::pixmapFromImageInPlace(std_::move(img)); } else if (!_pix.isNull()) { _pix = QPixmap(); } @@ -274,7 +274,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const img.setDevicePixelRatio(cRetinaFactor()); _data->forget(); - _pix = QPixmap::fromImage(img, Qt::ColorOnly); + _pix = App::pixmapFromImageInPlace(std_::move(img)); } else if (!_pix.isNull()) { _pix = QPixmap(); } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 8971a4976..5fe26b0fa 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -360,8 +360,8 @@ void MainWindow::psUpdateCounter() { bool muted = App::histories().unreadOnlyMuted(); style::color bg = muted ? st::counterMuteBG : st::counterBG; - icon.addPixmap(QPixmap::fromImage(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); - icon.addPixmap(QPixmap::fromImage(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); + icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); + icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); } trayIcon->setIcon(icon); } diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 8738a9ffe..602816914 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -212,8 +212,8 @@ void MainWindow::psUpdateCounter() { int32 size = cRetina() ? 44 : 22; _placeCounter(img, size, counter, bg, (dm && muted) ? st::counterMacInvColor : st::counterColor); _placeCounter(imgsel, size, counter, st::white, st::counterMacInvColor); - icon.addPixmap(QPixmap::fromImage(img, Qt::ColorOnly)); - icon.addPixmap(QPixmap::fromImage(imgsel, Qt::ColorOnly), QIcon::Selected); + icon.addPixmap(App::pixmapFromImageInPlace(std_::move(img))); + icon.addPixmap(App::pixmapFromImageInPlace(std_::move(imgsel)), QIcon::Selected); trayIcon->setIcon(icon); } } diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index d7dc77ec6..0dc587238 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -736,10 +736,10 @@ void MainWindow::psUpdateCounter() { style::color bg = muted ? st::counterMuteBG : st::counterBG; QIcon iconSmall, iconBig; - iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); - iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); - iconBig.addPixmap(QPixmap::fromImage(iconWithCounter(32, taskbarList.Get() ? 0 : counter, bg, false), Qt::ColorOnly)); - iconBig.addPixmap(QPixmap::fromImage(iconWithCounter(64, taskbarList.Get() ? 0 : counter, bg, false), Qt::ColorOnly)); + iconSmall.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, true))); + iconSmall.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, true))); + iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, taskbarList.Get() ? 0 : counter, bg, false))); + iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(64, taskbarList.Get() ? 0 : counter, bg, false))); if (trayIcon) { trayIcon->setIcon(iconSmall); } @@ -753,8 +753,8 @@ void MainWindow::psUpdateCounter() { if (taskbarList.Get()) { if (counter > 0) { QIcon iconOverlay; - iconOverlay.addPixmap(QPixmap::fromImage(iconWithCounter(-16, counter, bg, false), Qt::ColorOnly)); - iconOverlay.addPixmap(QPixmap::fromImage(iconWithCounter(-32, counter, bg, false), Qt::ColorOnly)); + iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-16, counter, bg, false))); + iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-32, counter, bg, false))); ps_iconOverlay = createHIconFromQIcon(iconOverlay, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); } QString description = counter > 0 ? QString("%1 unread messages").arg(counter) : qsl("No unread messages"); diff --git a/Telegram/SourceFiles/pspecific_win.cpp b/Telegram/SourceFiles/pspecific_win.cpp index effc56d8a..c1215a59e 100644 --- a/Telegram/SourceFiles/pspecific_win.cpp +++ b/Telegram/SourceFiles/pspecific_win.cpp @@ -585,7 +585,7 @@ namespace { if (!iconindex) { // try to read image QImage img(QString::fromWCharArray(icon)); if (!img.isNull()) { - return qt_pixmapToWinHBITMAP(QPixmap::fromImage(img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), /* HBitmapAlpha */ 2); + return qt_pixmapToWinHBITMAP(App::pixmapFromImageInPlace(img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), /* HBitmapAlpha */ 2); } } return 0; diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index afb3112e2..cd051bcf4 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -1753,7 +1753,7 @@ void SettingsInner::updateChatBackground() { p.setRenderHint(QPainter::SmoothPixmapTransform); p.drawPixmap(0, 0, st::setBackgroundSize, st::setBackgroundSize, pix, sx, sy, s, s); } - _background = QPixmap::fromImage(back); + _background = App::pixmapFromImageInPlace(std_::move(back)); _background.setDevicePixelRatio(cRetinaFactor()); _needBackgroundUpdate = false; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 8ae66bbf0..5575db300 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -220,7 +220,7 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer a newPhotoId = 0; if (id == ServiceUserId) { if (_userpic.v() == userDefPhoto(colorIndex).v()) { - newPhoto = ImagePtr(QPixmap::fromImage(App::wnd()->iconLarge().scaledToWidth(160, Qt::SmoothTransformation), Qt::ColorOnly), "PNG"); + newPhoto = ImagePtr(App::pixmapFromImageInPlace(App::wnd()->iconLarge().scaledToWidth(160, Qt::SmoothTransformation)), "PNG"); } } else { newPhoto = userDefPhoto(colorIndex); @@ -299,12 +299,11 @@ void UserData::setBotInfoVersion(int version) { botInfo->commands.clear(); Notify::botCommandsChanged(this); } - delete botInfo; - botInfo = 0; + botInfo = nullptr; Notify::userIsBotChanged(this); } } else if (!botInfo) { - botInfo = new BotInfo(); + botInfo = std_::make_unique(); botInfo->version = version; Notify::userIsBotChanged(this); } else if (botInfo->version < version) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index d34e95292..4e7ed2fab 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -471,7 +471,7 @@ public: return _about; } - BotInfo *botInfo = nullptr; + std_::unique_ptr botInfo; QString restrictionReason() const override { return _restrictionReason; diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 5aec57778..b969cd7bd 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -279,7 +279,7 @@ void TitleWidget::updateCounter() { case dbisOneAndHalf: size = -24; break; case dbisTwo: size = -32; break; } - _counter = QPixmap::fromImage(App::wnd()->iconWithCounter(size, counter, bg, false), Qt::ColorOnly); + _counter = App::pixmapFromImageInPlace(App::wnd()->iconWithCounter(size, counter, bg, false)); _counter.setDevicePixelRatio(cRetinaFactor()); update(QRect(st::titleIconPos, st::titleIconImg.pxSize())); } else { diff --git a/Telegram/SourceFiles/ui/boxshadow.cpp b/Telegram/SourceFiles/ui/boxshadow.cpp index 9e52f6229..b556d5719 100644 --- a/Telegram/SourceFiles/ui/boxshadow.cpp +++ b/Telegram/SourceFiles/ui/boxshadow.cpp @@ -56,8 +56,6 @@ BoxShadow::BoxShadow(const style::sprite &topLeft) : _size(topLeft.pxWidth()), _ m.setDevicePixelRatio(cRetinaFactor()); p.drawImage(_size, 0, m, _pixsize, 0, _pixsize, _pixsize * 2); } - _corners = QPixmap::fromImage(cornersImage, Qt::ColorOnly); - _corners.setDevicePixelRatio(cRetinaFactor()); _colors.reserve(_pixsize); uchar prev = 0; for (int i = 0; i < _pixsize; ++i) { @@ -68,15 +66,17 @@ BoxShadow::BoxShadow(const style::sprite &topLeft) : _size(topLeft.pxWidth()), _ prev = a; } if (cRetina()) { - _left = QPixmap::fromImage(cornersImage.copy(0, _pixsize - 1, _colors.size(), 1), Qt::ColorOnly); + _left = App::pixmapFromImageInPlace(cornersImage.copy(0, _pixsize - 1, _colors.size(), 1)); _left.setDevicePixelRatio(cRetinaFactor()); - _top = QPixmap::fromImage(cornersImage.copy(_pixsize - 1, 0, 1, _colors.size()), Qt::ColorOnly); + _top = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize - 1, 0, 1, _colors.size())); _top.setDevicePixelRatio(cRetinaFactor()); - _right = QPixmap::fromImage(cornersImage.copy(_pixsize * 2 - _colors.size(), _pixsize, _colors.size(), 1), Qt::ColorOnly); + _right = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize * 2 - _colors.size(), _pixsize, _colors.size(), 1)); _right.setDevicePixelRatio(cRetinaFactor()); - _bottom = QPixmap::fromImage(cornersImage.copy(_pixsize, _pixsize * 2 - _colors.size(), 1, _colors.size()), Qt::ColorOnly); + _bottom = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize, _pixsize * 2 - _colors.size(), 1, _colors.size())); _bottom.setDevicePixelRatio(cRetinaFactor()); } + _corners = App::pixmapFromImageInPlace(std_::move(cornersImage)); + _corners.setDevicePixelRatio(cRetinaFactor()); } void BoxShadow::paint(QPainter &p, const QRect &box, int32 shifty, int32 flags) { @@ -140,8 +140,6 @@ RectShadow::RectShadow(const style::icon &topLeft) : _size(topLeft.width()), _pi m.setDevicePixelRatio(cRetinaFactor()); p.drawImage(_size, 0, m, _pixsize, 0, _pixsize, _pixsize * 2); } - _corners = QPixmap::fromImage(cornersImage, Qt::ColorOnly); - _corners.setDevicePixelRatio(cRetinaFactor()); uchar prev = 0; for (int i = 0; i < _pixsize; ++i) { @@ -152,14 +150,17 @@ RectShadow::RectShadow(const style::icon &topLeft) : _size(topLeft.width()), _pi prev = a; } - _left = QPixmap::fromImage(cornersImage.copy(0, _pixsize - 1, _thickness, 1), Qt::ColorOnly); + _left = App::pixmapFromImageInPlace(cornersImage.copy(0, _pixsize - 1, _thickness, 1)); _left.setDevicePixelRatio(cRetinaFactor()); - _top = QPixmap::fromImage(cornersImage.copy(_pixsize - 1, 0, 1, _thickness), Qt::ColorOnly); + _top = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize - 1, 0, 1, _thickness)); _top.setDevicePixelRatio(cRetinaFactor()); - _right = QPixmap::fromImage(cornersImage.copy(_pixsize * 2 - _thickness, _pixsize, _thickness, 1), Qt::ColorOnly); + _right = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize * 2 - _thickness, _pixsize, _thickness, 1)); _right.setDevicePixelRatio(cRetinaFactor()); - _bottom = QPixmap::fromImage(cornersImage.copy(_pixsize, _pixsize * 2 - _thickness, 1, _thickness), Qt::ColorOnly); + _bottom = App::pixmapFromImageInPlace(cornersImage.copy(_pixsize, _pixsize * 2 - _thickness, 1, _thickness)); _bottom.setDevicePixelRatio(cRetinaFactor()); + + _corners = App::pixmapFromImageInPlace(std_::move(cornersImage)); + _corners.setDevicePixelRatio(cRetinaFactor()); } void RectShadow::paint(Painter &p, const QRect &box, int shifty, Sides sides) { diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index ae979b787..bdc4d81ff 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -106,7 +106,7 @@ CountryInput::CountryInput(QWidget *parent, const style::countryInput &st) : QWi p.setBrush(_st.bgColor->b); p.drawPolygon(trPoints, 3); } - _arrow = QPixmap::fromImage(trImage, Qt::ColorOnly); + _arrow = App::pixmapFromImageInPlace(std_::move(trImage)); _inner = QRect(0, 0, _st.width, _st.height); _arrowRect = QRect((st::inpIntroCountryCode.width - _arrow.width() - 1) / 2, _st.height, _arrow.width(), _arrow.height()); } diff --git a/Telegram/SourceFiles/ui/images.cpp b/Telegram/SourceFiles/ui/images.cpp index 2cc1e8b30..a9aa79389 100644 --- a/Telegram/SourceFiles/ui/images.cpp +++ b/Telegram/SourceFiles/ui/images.cpp @@ -64,7 +64,7 @@ ImagePtr::ImagePtr(int32 width, int32 height, const MTPFileLocation &location, I } Image::Image(const QString &file, QByteArray fmt) : _forgot(false) { - _data = QPixmap::fromImage(App::readImage(file, &fmt, false, 0, &_saved), Qt::ColorOnly); + _data = App::pixmapFromImageInPlace(App::readImage(file, &fmt, false, 0, &_saved)); _format = fmt; if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; @@ -72,7 +72,7 @@ Image::Image(const QString &file, QByteArray fmt) : _forgot(false) { } Image::Image(const QByteArray &filecontent, QByteArray fmt) : _forgot(false) { - _data = QPixmap::fromImage(App::readImage(filecontent, &fmt, false), Qt::ColorOnly); + _data = App::pixmapFromImageInPlace(App::readImage(filecontent, &fmt, false)); _format = fmt; _saved = filecontent; if (!_data.isNull()) { @@ -420,7 +420,7 @@ const QPixmap &circleMask(int width, int height) { p.drawEllipse(0, 0, width, height); } mask.setDevicePixelRatio(cRetinaFactor()); - i = masks.insert(key, QPixmap::fromImage(mask)); + i = masks.insert(key, App::pixmapFromImageInPlace(std_::move(mask))); } return i.value(); } @@ -543,7 +543,7 @@ QPixmap imagePix(QImage img, int32 w, int32 h, ImagePixOptions options, int32 ou imageRound(img, ImageRoundRadius::Small); } img.setDevicePixelRatio(cRetinaFactor()); - return QPixmap::fromImage(img, Qt::ColorOnly); + return App::pixmapFromImageInPlace(std_::move(img)); } QPixmap Image::pixNoCache(int w, int h, ImagePixOptions options, int outerw, int outerh) const { @@ -584,7 +584,7 @@ QPixmap Image::pixNoCache(int w, int h, ImagePixOptions options, int outerw, int } else if (options.testFlag(ImagePixRoundedSmall)) { imageRound(result, ImageRoundRadius::Small); } - return QPixmap::fromImage(result, Qt::ColorOnly); + return App::pixmapFromImageInPlace(std_::move(result)); } return imagePix(_data.toImage(), w, h, options, outerw, outerh); @@ -596,11 +596,11 @@ QPixmap Image::pixColoredNoCache(const style::color &add, int32 w, int32 h, bool if (_data.isNull()) return blank()->pix(); QImage img = _data.toImage(); - if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) return QPixmap::fromImage(imageColored(add, img)); + if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) return App::pixmapFromImageInPlace(imageColored(add, img)); if (h <= 0) { - return QPixmap::fromImage(imageColored(add, img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); + return App::pixmapFromImageInPlace(imageColored(add, img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation))); } - return QPixmap::fromImage(imageColored(add, img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); + return App::pixmapFromImageInPlace(imageColored(add, img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation))); } QPixmap Image::pixBlurredColoredNoCache(const style::color &add, int32 w, int32 h) const { @@ -615,7 +615,7 @@ QPixmap Image::pixBlurredColoredNoCache(const style::color &add, int32 w, int32 img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - return QPixmap::fromImage(imageColored(add, img), Qt::ColorOnly); + return App::pixmapFromImageInPlace(imageColored(add, img)); } void Image::forget() const { @@ -738,7 +738,7 @@ void RemoteImage::setData(QByteArray &bytes, const QByteArray &bytesFormat) { globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } QByteArray fmt(bytesFormat); - _data = QPixmap::fromImage(App::readImage(bytes, &fmt, false), Qt::ColorOnly); + _data = App::pixmapFromImageInPlace(App::readImage(bytes, &fmt, false)); if (!_data.isNull()) { globalAcquiredSize += int64(_data.width()) * _data.height() * 4; setInformation(bytes.size(), _data.width(), _data.height()); diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp index 253dedf45..3d490d63a 100644 --- a/Telegram/SourceFiles/ui/style/style_core.cpp +++ b/Telegram/SourceFiles/ui/style/style_core.cpp @@ -73,6 +73,8 @@ void stopManager() { internal::stopModules(); internal::destroyFonts(); internal::destroyColors(); + internal::destroyIcons(); + internal::destroySprite(); } QImage colorizeImage(const QImage &src, const color &c, const QRect &r) { diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp index 83bd66a26..95445eb1b 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp @@ -64,7 +64,7 @@ QPixmap createIconPixmap(const IconMask *mask, const Color &color) { } finalImage = colorizeImage(maskImage, color, r); finalImage.setDevicePixelRatio(cRetinaFactor()); - return QPixmap::fromImage(finalImage, Qt::ColorOnly); + return App::pixmapFromImageInPlace(std_::move(finalImage)); } } // namespace diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.h b/Telegram/SourceFiles/ui/style/style_core_icon.h index 446c6258c..b4c4d2a65 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.h +++ b/Telegram/SourceFiles/ui/style/style_core_icon.h @@ -131,5 +131,7 @@ private: }; +void destroyIcons(); + } // namespace internal } // namespace style diff --git a/Telegram/SourceFiles/ui/style/style_core_types.cpp b/Telegram/SourceFiles/ui/style/style_core_types.cpp index 774de9ee2..2f0444307 100644 --- a/Telegram/SourceFiles/ui/style/style_core_types.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_types.cpp @@ -41,7 +41,7 @@ void loadSprite() { } QString spriteFile = qsl(":/gui/art/sprite") + spriteFilePostfix + qsl(".png"); if (rtl()) { - spriteData = new QPixmap(QPixmap::fromImage(QImage(spriteFile).mirrored(true, false))); + spriteData = new QPixmap(App::pixmapFromImageInPlace(QImage(spriteFile).mirrored(true, false))); } else { spriteData = new QPixmap(spriteFile); } @@ -53,6 +53,11 @@ int spriteWidth() { return spriteWidthValue; } +void destroySprite() { + delete spriteData; + spriteData = nullptr; +} + } // namespace internal const QPixmap &spritePixmap() { diff --git a/Telegram/SourceFiles/ui/style/style_core_types.h b/Telegram/SourceFiles/ui/style/style_core_types.h index bb4296185..aa2fa782c 100644 --- a/Telegram/SourceFiles/ui/style/style_core_types.h +++ b/Telegram/SourceFiles/ui/style/style_core_types.h @@ -37,6 +37,7 @@ namespace internal { void loadSprite(); int spriteWidth(); +void destroySprite(); class Sprite { public: diff --git a/Telegram/SourceFiles/ui/text/text_block.cpp b/Telegram/SourceFiles/ui/text/text_block.cpp index 705d935f5..d8e7696ce 100644 --- a/Telegram/SourceFiles/ui/text/text_block.cpp +++ b/Telegram/SourceFiles/ui/text/text_block.cpp @@ -337,14 +337,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi layout.beginLayout(); layout.createLine(); - bool logCrashString = (rand_value() % 4 == 1); - if (logCrashString) { - SignalHandlers::setCrashAnnotationRef("CrashString", &str); - } BlockParser parser(&engine, this, minResizeWidth, _from, part); - if (logCrashString) { - SignalHandlers::clearCrashAnnotationRef("CrashString"); - } layout.endLayout(); } From 505e5a69a6aedea5f17dfe3c07a2eb2a9eef0d32 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 14 Jul 2016 14:20:46 +0300 Subject: [PATCH 21/60] Video playing in MediaView seek fixed, fullscreen mode improved. Shortcut commands now return bool, so you know if it was executed. --- Telegram/SourceFiles/historywidget.cpp | 21 +- Telegram/SourceFiles/historywidget.h | 6 +- Telegram/SourceFiles/mainwidget.cpp | 15 +- Telegram/SourceFiles/mainwidget.h | 6 +- Telegram/SourceFiles/media/media_audio.cpp | 19 +- Telegram/SourceFiles/media/media_audio.h | 2 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 86 +++++--- .../SourceFiles/media/media_clip_ffmpeg.h | 12 +- .../media/media_clip_implementation.h | 6 +- .../SourceFiles/media/media_clip_qtgif.cpp | 12 +- Telegram/SourceFiles/media/media_clip_qtgif.h | 4 +- .../SourceFiles/media/media_clip_reader.cpp | 49 +++-- Telegram/SourceFiles/mediaview.cpp | 186 +++++++++++++----- Telegram/SourceFiles/mediaview.h | 10 +- Telegram/SourceFiles/shortcuts.cpp | 103 ++++++---- 15 files changed, 367 insertions(+), 170 deletions(-) diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 3f23f6034..b695b0f0d 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3616,24 +3616,33 @@ void HistoryWidget::notify_clipStopperHidden(ClipStopperType type) { if (_list) _list->update(); } -void HistoryWidget::cmd_search() { - if (!inFocusChain() || !_peer) return; +bool HistoryWidget::cmd_search() { + if (!inFocusChain() || !_peer) return false; App::main()->searchInPeer(_peer); + return true; } -void HistoryWidget::cmd_next_chat() { +bool HistoryWidget::cmd_next_chat() { PeerData *p = 0; MsgId m = 0; App::main()->peerAfter(_peer, qMax(_showAtMsgId, 0), p, m); - if (p) Ui::showPeerHistory(p, m); + if (p) { + Ui::showPeerHistory(p, m); + return true; + } + return false; } -void HistoryWidget::cmd_previous_chat() { +bool HistoryWidget::cmd_previous_chat() { PeerData *p = 0; MsgId m = 0; App::main()->peerBefore(_peer, qMax(_showAtMsgId, 0), p, m); - if (p) Ui::showPeerHistory(p, m); + if (p) { + Ui::showPeerHistory(p, m); + return true; + } + return false; } void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index c2474c014..fdcdad95d 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -726,9 +726,9 @@ public: void notify_clipStopperHidden(ClipStopperType type); void notify_handlePendingHistoryUpdate(); - void cmd_search(); - void cmd_next_chat(); - void cmd_previous_chat(); + bool cmd_search(); + bool cmd_next_chat(); + bool cmd_previous_chat(); ~HistoryWidget(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f30eb1c70..4a9b09fa2 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -515,16 +515,19 @@ void MainWidget::notify_handlePendingHistoryUpdate() { _history->notify_handlePendingHistoryUpdate(); } -void MainWidget::cmd_search() { - _history->cmd_search(); +bool MainWidget::cmd_search() { + if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false; + return _history->cmd_search(); } -void MainWidget::cmd_next_chat() { - _history->cmd_next_chat(); +bool MainWidget::cmd_next_chat() { + if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false; + return _history->cmd_next_chat(); } -void MainWidget::cmd_previous_chat() { - _history->cmd_previous_chat(); +bool MainWidget::cmd_previous_chat() { + if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false; + return _history->cmd_previous_chat(); } void MainWidget::noHider(HistoryHider *destroyed) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 63349cdc7..c6a41c918 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -421,9 +421,9 @@ public: void notify_historyMuteUpdated(History *history); void notify_handlePendingHistoryUpdate(); - void cmd_search(); - void cmd_next_chat(); - void cmd_previous_chat(); + bool cmd_search(); + bool cmd_next_chat(); + bool cmd_previous_chat(); ~MainWidget(); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index dbee88b81..127282dd4 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -489,11 +489,24 @@ void AudioPlayer::initFromVideo(uint64 videoPlayId, std_::unique_ptrplaybackState.state) { + case AudioPlayerStarting: + case AudioPlayerResuming: + case AudioPlayerPlaying: + currentSong->playbackState.state = AudioPlayerPausing; + updateCurrentStarted(AudioMsgId::Type::Song); + break; + case AudioPlayerFinishing: currentSong->playbackState.state = AudioPlayerPausing; break; + } + auto type = AudioMsgId::Type::Video; auto current = dataForType(type); t_assert(current != nullptr); - fadedStop(AudioMsgId::Type::Song); if (current->audio) { fadedStop(type); stopped = current->audio; @@ -625,8 +638,8 @@ void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { _loader->feedFromVideo(std_::move(part)); } -int64 AudioPlayer::getVideoCorrectedTime(uint64 playId, uint64 systemMs) { - int64 result = systemMs; +int64 AudioPlayer::getVideoCorrectedTime(uint64 playId, int64 frameMs, uint64 systemMs) { + int64 result = frameMs; QMutexLocker videoLock(&_lastVideoMutex); if (_lastVideoPlayId == playId && _lastVideoPlaybackWhen > 0) { diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 012725cf3..a4fc7c785 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -69,7 +69,7 @@ public: // Video player audio stream interface. void initFromVideo(uint64 videoPlayId, std_::unique_ptr &&data, int64 position); void feedFromVideo(VideoSoundPart &&part); - int64 getVideoCorrectedTime(uint64 playId, uint64 systemMs); + int64 getVideoCorrectedTime(uint64 playId, int64 frameMs, uint64 systemMs); void videoSoundProgress(const AudioMsgId &audio); AudioPlaybackState currentVideoState(uint64 videoPlayId); void stopFromVideo(uint64 videoPlayId); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 460fc689e..44bff8947 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -44,7 +44,7 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { while (true) { while (_packetQueue.isEmpty()) { - auto packetResult = readPacket(); + auto packetResult = readAndProcessPacket(); if (packetResult == PacketResult::Error) { return ReadResult::Error; } else if (packetResult == PacketResult::EndOfFile) { @@ -135,24 +135,24 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { return ReadResult::Error; } -ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int64 ms) { +ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int64 frameMs, uint64 systemMs) { if (_audioStreamId < 0) { // just keep up - if (_frameRead && _frameTime > ms) { + if (_frameRead && _frameTime > frameMs) { return ReadResult::Success; } auto readResult = readNextFrame(); - if (readResult != ReadResult::Success || _frameTime > ms) { + if (readResult != ReadResult::Success || _frameTime > frameMs) { return readResult; } readResult = readNextFrame(); - if (_frameTime <= ms) { - _frameTime = ms + 5; // keep up + if (_frameTime <= frameMs) { + _frameTime = frameMs + 5; // keep up } return readResult; } // sync by audio stream - auto correctMs = audioPlayer()->getVideoCorrectedTime(_playId, ms); + auto correctMs = (frameMs >= 0) ? audioPlayer()->getVideoCorrectedTime(_playId, frameMs, systemMs) : frameMs; if (!_frameRead) { auto readResult = readNextFrame(); @@ -166,7 +166,9 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int6 return readResult; } } - _frameTimeCorrection = ms - correctMs; + if (frameMs >= 0) { + _frameTimeCorrection = frameMs - correctMs; + } return ReadResult::Success; } @@ -235,7 +237,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q // Read some future packets for audio stream. if (_audioStreamId >= 0) { while (_frameMs + 5000 > _lastReadPacketMs) { - auto packetResult = readPacket(); + auto packetResult = readAndProcessPacket(); if (packetResult != PacketResult::Ok) { break; } @@ -246,7 +248,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q return true; } -bool FFMpegReaderImplementation::start(Mode mode, int64 positionMs) { +bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { _mode = mode; initDevice(); @@ -300,7 +302,7 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 positionMs) { if (_codecContext->codec_id != AV_CODEC_ID_H264) { return false; } - } else if (_mode == Mode::Silent || !audioPlayer()) { + } else if (_mode == Mode::Silent || !audioPlayer() || !_playId) { _audioStreamId = -1; } av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); @@ -337,20 +339,30 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 positionMs) { } } - if (positionMs) { + if (positionMs > 0) { int64 ts = (positionMs * _fmtContext->streams[_streamId]->time_base.den) / (1000LL * _fmtContext->streams[_streamId]->time_base.num); - if (av_seek_frame(_fmtContext, _streamId, ts, AVSEEK_FLAG_ANY) < 0) { - if (av_seek_frame(_fmtContext, _streamId, ts, 0) < 0) { - positionMs = 0; + if (av_seek_frame(_fmtContext, _streamId, ts, 0) < 0) { + if (av_seek_frame(_fmtContext, _streamId, ts, AVSEEK_FLAG_BACKWARD) < 0) { + return false; } } } + AVPacket packet; + auto readResult = readPacket(&packet); + if (readResult == PacketResult::Ok && positionMs > 0) { + positionMs = countPacketMs(&packet); + } + if (_audioStreamId >= 0) { int64 position = (positionMs * soundData->frequency) / 1000LL; audioPlayer()->initFromVideo(_playId, std_::move(soundData), position); } + if (readResult == PacketResult::Ok) { + processPacket(&packet); + } + return true; } @@ -384,14 +396,13 @@ FFMpegReaderImplementation::~FFMpegReaderImplementation() { av_frame_free(&_frame); } -FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket() { - AVPacket packet; - av_init_packet(&packet); - packet.data = nullptr; - packet.size = 0; +FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket(AVPacket *packet) { + av_init_packet(packet); + packet->data = nullptr; + packet->size = 0; int res = 0; - if ((res = av_read_frame(_fmtContext, &packet)) < 0) { + if ((res = av_read_frame(_fmtContext, packet)) < 0) { if (res == AVERROR_EOF) { if (_audioStreamId >= 0) { // queue terminating packet to audio player @@ -406,27 +417,42 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return PacketResult::Error; } + return PacketResult::Ok; +} - bool videoPacket = (packet.stream_index == _streamId); - bool audioPacket = (_audioStreamId >= 0 && packet.stream_index == _audioStreamId); +void FFMpegReaderImplementation::processPacket(AVPacket *packet) { + bool videoPacket = (packet->stream_index == _streamId); + bool audioPacket = (_audioStreamId >= 0 && packet->stream_index == _audioStreamId); if (audioPacket || videoPacket) { - int64 packetPts = (packet.pts == AV_NOPTS_VALUE) ? packet.dts : packet.pts; - int64 packetMs = (packetPts * 1000LL * _fmtContext->streams[packet.stream_index]->time_base.num) / _fmtContext->streams[packet.stream_index]->time_base.den; - _lastReadPacketMs = packetMs; + _lastReadPacketMs = countPacketMs(packet); if (videoPacket) { - _packetQueue.enqueue(packet); + _packetQueue.enqueue(*packet); } else if (audioPacket) { // queue packet to audio player VideoSoundPart part; - part.packet = &packet; + part.packet = packet; part.videoPlayId = _playId; audioPlayer()->feedFromVideo(std_::move(part)); } } else { - av_packet_unref(&packet); + av_packet_unref(packet); } - return PacketResult::Ok; +} + +int64 FFMpegReaderImplementation::countPacketMs(AVPacket *packet) const { + int64 packetPts = (packet->pts == AV_NOPTS_VALUE) ? packet->dts : packet->pts; + int64 packetMs = (packetPts * 1000LL * _fmtContext->streams[packet->stream_index]->time_base.num) / _fmtContext->streams[packet->stream_index]->time_base.den; + return packetMs; +} + +FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readAndProcessPacket() { + AVPacket packet; + auto result = readPacket(&packet); + if (result == PacketResult::Ok) { + processPacket(&packet); + } + return result; } void FFMpegReaderImplementation::startPacket() { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index cf54258d6..7a9abbe9a 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -29,6 +29,8 @@ extern "C" { #include "media/media_clip_implementation.h" +struct VideoSoundData; + namespace Media { namespace Clip { namespace internal { @@ -37,7 +39,7 @@ class FFMpegReaderImplementation : public ReaderImplementation { public: FFMpegReaderImplementation(FileLocation *location, QByteArray *data, uint64 playId); - ReadResult readFramesTill(int64 ms) override; + ReadResult readFramesTill(int64 frameMs, uint64 systemMs) override; int64 frameRealTime() const override; uint64 framePresentationTime() const override; @@ -51,7 +53,7 @@ public: void pauseAudio() override; void resumeAudio() override; - bool start(Mode mode, int64 positionMs) override; + bool start(Mode mode, int64 &positionMs) override; QString logData() const; @@ -65,7 +67,11 @@ private: EndOfFile, Error, }; - PacketResult readPacket(); + PacketResult readPacket(AVPacket *packet); + void processPacket(AVPacket *packet); + int64 countPacketMs(AVPacket *packet) const; + PacketResult readAndProcessPacket(); + void startPacket(); void finishPacket(); void clearPacketQueue(); diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 21d2ad53a..54bf98944 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -43,8 +43,8 @@ public: Error, Eof, }; - // Read frames till current frame will have presentation time > ms. - virtual ReadResult readFramesTill(int64 ms) = 0; + // Read frames till current frame will have presentation time > frameMs, systemMs = getms(). + virtual ReadResult readFramesTill(int64 frameMs, uint64 systemMs) = 0; // Get current frame real and presentation time. virtual int64 frameRealTime() const = 0; @@ -58,7 +58,7 @@ public: virtual void pauseAudio() = 0; virtual void resumeAudio() = 0; - virtual bool start(Mode mode, int64 positionMs) = 0; + virtual bool start(Mode mode, int64 &positionMs) = 0; virtual ~ReaderImplementation() { } int64 dataSize() const { diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index d1216f213..18f0462b5 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -28,17 +28,17 @@ namespace internal { QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { } -ReaderImplementation::ReadResult QtGifReaderImplementation::readFramesTill(int64 ms) { - if (!_frame.isNull() && _frameTime > ms) { +ReaderImplementation::ReadResult QtGifReaderImplementation::readFramesTill(int64 frameMs, uint64 systemMs) { + if (!_frame.isNull() && _frameTime > frameMs) { return ReadResult::Success; } auto readResult = readNextFrame(); - if (readResult != ReadResult::Success || _frameTime > ms) { + if (readResult != ReadResult::Success || _frameTime > frameMs) { return readResult; } readResult = readNextFrame(); - if (_frameTime <= ms) { - _frameTime = ms + 5; // keep up + if (_frameTime <= frameMs) { + _frameTime = frameMs + 5; // keep up } return readResult; } @@ -99,7 +99,7 @@ int64 QtGifReaderImplementation::durationMs() const { return 0; // not supported } -bool QtGifReaderImplementation::start(Mode mode, int64 positionMs) { +bool QtGifReaderImplementation::start(Mode mode, int64 &positionMs) { if (mode == Mode::OnlyGifv) return false; _mode = mode; return jumpToStart(); diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h index 4384dd9e8..441cdb7d4 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.h +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -31,7 +31,7 @@ public: QtGifReaderImplementation(FileLocation *location, QByteArray *data); - ReadResult readFramesTill(int64 ms) override; + ReadResult readFramesTill(int64 frameMs, uint64 systemMs) override; int64 frameRealTime() const override; uint64 framePresentationTime() const override; @@ -47,7 +47,7 @@ public: void resumeAudio() override { } - bool start(Mode mode, int64 positionMs) override; + bool start(Mode mode, int64 &positionMs) override; ~QtGifReaderImplementation(); diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 5a9cea8e4..fee4786aa 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -341,12 +341,37 @@ public: } ProcessResult start(uint64 ms) { - if (!_implementation && !init(_seekPositionMs)) { + if (!_implementation && !init()) { return error(); } if (frame() && frame()->original.isNull()) { - auto readResult = _implementation->readFramesTill(-1); - if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame. + auto readResult = _implementation->readFramesTill(-1, ms); + if (readResult == internal::ReaderImplementation::ReadResult::Eof && _seekPositionMs > 0) { + // If seek was done to the end: try to read the first frame, + // get the frame size and return a black frame with that size. + + auto firstFramePlayId = 0LL; + auto firstFramePositionMs = 0LL; + auto reader = std_::make_unique(_location.get(), &_data, firstFramePlayId); + if (reader->start(internal::ReaderImplementation::Mode::Normal, firstFramePositionMs)) { + auto firstFrameReadResult = reader->readFramesTill(-1, ms); + if (firstFrameReadResult == internal::ReaderImplementation::ReadResult::Success) { + if (reader->renderFrame(frame()->original, frame()->alpha, QSize())) { + frame()->original.fill(QColor(0, 0, 0)); + + frame()->positionMs = _seekPositionMs; + + _width = frame()->original.width(); + _height = frame()->original.height(); + _durationMs = _implementation->durationMs(); + _hasAudio = _implementation->hasAudio(); + return ProcessResult::Started; + } + } + } + + return error(); + } else if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame. return error(); } if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { @@ -387,7 +412,8 @@ public: } ProcessResult finishProcess(uint64 ms) { - auto readResult = _implementation->readFramesTill(_skippedMs + ms - _animationStarted); + auto frameMs = _seekPositionMs + ms - _animationStarted; + auto readResult = _implementation->readFramesTill(frameMs, ms); if (readResult == internal::ReaderImplementation::ReadResult::Eof) { stop(); _state = State::Finished; @@ -397,8 +423,8 @@ public: } _nextFramePositionMs = _implementation->frameRealTime(); _nextFrameWhen = _animationStarted + _implementation->framePresentationTime(); - if (static_cast(_nextFrameWhen) > _skippedMs) { - _nextFrameWhen -= _skippedMs; + if (static_cast(_nextFrameWhen) > _seekPositionMs) { + _nextFrameWhen -= _seekPositionMs; } else { _nextFrameWhen = 1; } @@ -422,7 +448,7 @@ public: return true; } - bool init(int64 positionMs) { + bool init() { if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { QFile f(_location->name()); if (f.open(QIODevice::ReadOnly)) { @@ -443,8 +469,7 @@ public: } return ImplementationMode::Normal; }; - _skippedMs = positionMs; - return _implementation->start(implementationMode(), positionMs); + return _implementation->start(implementationMode(), _seekPositionMs); } void startedAt(uint64 ms) { @@ -530,7 +555,6 @@ private: uint64 _animationStarted = 0; uint64 _nextFrameWhen = 0; int64 _nextFramePositionMs = 0; - int64 _skippedMs = 0; bool _autoPausedGif = false; bool _started = false; @@ -816,10 +840,11 @@ MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data QByteArray localdata(data); auto playId = 0ULL; + auto seekPositionMs = 0LL; auto reader = std_::make_unique(&localloc, &localdata, playId); - if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv, 0)) { + if (reader->start(internal::ReaderImplementation::Mode::OnlyGifv, seekPositionMs)) { bool hasAlpha = false; - auto readResult = reader->readFramesTill(-1); + auto readResult = reader->readFramesTill(-1, getms()); auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success); if (readFrame && reader->renderFrame(cover, hasAlpha, QSize())) { if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 94a496819..1f07a9fc9 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -236,7 +236,8 @@ bool MediaView::gifShown() const { void MediaView::stopGif() { _gif = nullptr; - _videoPaused = _videoIsSilent = false; + _videoPaused = _videoStopped = _videoIsSilent = false; + _fullScreenVideo = false; _clipController.destroy(); Sandbox::removeEventFilter(this); if (audioPlayer()) { @@ -530,6 +531,7 @@ void MediaView::clearData() { _user = nullptr; _photo = _additionalChatPhoto = nullptr; _doc = nullptr; + _fullScreenVideo = false; _saveMsgText.clear(); _caption.clear(); } @@ -559,30 +561,42 @@ void MediaView::close() { } void MediaView::activateControls() { - if (!_menu && !_mousePressed && (!_clipController || !_clipController->geometry().contains(_lastMouseMovePos))) { + if (!_menu && !_mousePressed) { _controlsHideTimer.start(int(st::mvWaitHide)); } + if (_fullScreenVideo) { + if (_clipController) { + _clipController->showAnimated(); + } + } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { _controlsState = ControlsShowing; _controlsAnimStarted = getms(); a_cOpacity.start(1); if (!_a_state.animating()) _a_state.start(); } - if (_clipController) { -// _clipController->showAnimated(); - } } void MediaView::onHideControls(bool force) { - if (!force && (!_dropdown.isHidden() || _menu || _mousePressed || (_clipController && _clipController->geometry().contains(_lastMouseMovePos)))) return; + if (!force) { + if (!_dropdown.isHidden() + || _menu + || _mousePressed + || (_fullScreenVideo && _clipController && _clipController->geometry().contains(_lastMouseMovePos))) { + return; + } + } + if (_fullScreenVideo) { + if (_clipController) { + _clipController->hideAnimated(); + } + } if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; + _controlsState = ControlsHiding; _controlsAnimStarted = getms(); a_cOpacity.start(0); if (!_a_state.animating()) _a_state.start(); - if (_clipController) { -// _clipController->hideAnimated(); - } } void MediaView::onDropdownHiding() { @@ -713,6 +727,7 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { _current = QPixmap(); } else if (_gif->state() == State::Finished) { _videoPositionMs = _videoDurationMs; + _videoStopped = true; updateSilentVideoPlaybackState(); } else { _videoIsSilent = _doc->isVideo() && !_gif->hasAudio(); @@ -725,6 +740,8 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { displayDocument(_doc, item); } else { stopGif(); + updateControls(); + update(); } } break; @@ -1039,6 +1056,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) { void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { stopGif(); _doc = nullptr; + _fullScreenVideo = false; _photo = photo; _radial.stop(); @@ -1092,6 +1110,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { + _fullScreenVideo = false; stopGif(); } _doc = doc; @@ -1201,7 +1220,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty } else { _zoomToScreen = 0; } - if ((_w > width()) || (_h > height())) { + if ((_w > width()) || (_h > height()) || _fullScreenVideo) { _zoom = ZoomToScreenLevel; if (_zoomToScreen >= 0) { _w = qRound(_w * (_zoomToScreen + 1)); @@ -1259,7 +1278,7 @@ void MediaView::createClipReader() { _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), mode); // Correct values will be set when gif gets inited. - _videoPaused = _videoIsSilent = false; + _videoPaused = _videoIsSilent = _videoStopped = false; _videoPositionMs = 0ULL; _videoDurationMs = _doc->duration() * 1000ULL; @@ -1279,8 +1298,8 @@ void MediaView::createClipController() { connect(_clipController, SIGNAL(seekProgress(int64)), this, SLOT(onVideoSeekProgress(int64))); connect(_clipController, SIGNAL(seekFinished(int64)), this, SLOT(onVideoSeekFinished(int64))); connect(_clipController, SIGNAL(volumeChanged(float64)), this, SLOT(onVideoVolumeChanged(float64))); - connect(_clipController, SIGNAL(toFullScreenPressed()), this, SLOT(onVideoToFullScreen())); - connect(_clipController, SIGNAL(fromFullScreenPressed()), this, SLOT(onVideoFromFullScreen())); + connect(_clipController, SIGNAL(toFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); + connect(_clipController, SIGNAL(fromFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); Sandbox::removeEventFilter(this); Sandbox::installEventFilter(this); @@ -1317,6 +1336,8 @@ void MediaView::onVideoPauseResume() { } } else { stopGif(); + updateControls(); + update(); } } @@ -1329,7 +1350,7 @@ void MediaView::restartVideoAtSeekPosition(int64 positionMs) { _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), Media::Clip::Reader::Mode::Video, positionMs); // Correct values will be set when gif gets inited. - _videoPaused = _videoIsSilent = false; + _videoPaused = _videoIsSilent = _videoStopped = false; _videoPositionMs = positionMs; AudioPlaybackState state; @@ -1341,7 +1362,7 @@ void MediaView::restartVideoAtSeekPosition(int64 positionMs) { } void MediaView::onVideoSeekProgress(int64 positionMs) { - if (!_videoPaused) { + if (!_videoPaused && !_videoStopped) { onVideoPauseResume(); } } @@ -1355,12 +1376,20 @@ void MediaView::onVideoVolumeChanged(float64 volume) { emit audioPlayer()->videoVolumeChanged(); } -void MediaView::onVideoToFullScreen() { +void MediaView::onVideoToggleFullScreen() { + if (!_clipController) return; -} - -void MediaView::onVideoFromFullScreen() { + _fullScreenVideo = !_fullScreenVideo; + if (_fullScreenVideo) { + _fullScreenZoomCache = _zoom; + setZoomLevel(ZoomToScreenLevel); + } else { + setZoomLevel(_fullScreenZoomCache); + } + _clipController->setInFullScreen(_fullScreenVideo); + updateControls(); + update(); } void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { @@ -1377,6 +1406,9 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state, bool reset) { if (state.frequency) { + if (state.state & AudioPlayerStoppedMask) { + _videoStopped = true; + } _clipController->updatePlayback(state, reset); } else { // Audio has stopped already. _videoIsSilent = true; @@ -1415,11 +1447,17 @@ void MediaView::paintEvent(QPaintEvent *e) { // main bg QPainter::CompositionMode m = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Source); - p.setOpacity(st::mvBgOpacity); - for (int i = 0, l = region.rectCount(); i < l; ++i) { - p.fillRect(rs.at(i), st::mvBgColor->b); + if (_fullScreenVideo) { + for (int i = 0, l = region.rectCount(); i < l; ++i) { + p.fillRect(rs.at(i), st::black); + } + } else { + p.setOpacity(st::mvBgOpacity); + for (int i = 0, l = region.rectCount(); i < l; ++i) { + p.fillRect(rs.at(i), st::mvBgColor->b); + } + p.setCompositionMode(m); } - p.setCompositionMode(m); // photo if (_photo) { @@ -1578,7 +1616,7 @@ void MediaView::paintEvent(QPaintEvent *e) { } } - float64 co = a_cOpacity.current(); + float64 co = _fullScreenVideo ? 0. : a_cOpacity.current(); if (co > 0) { // left nav bar if (_leftNav.intersects(r) && _leftNavVisible) { @@ -1703,6 +1741,22 @@ void MediaView::paintEvent(QPaintEvent *e) { } void MediaView::keyPressEvent(QKeyEvent *e) { + if (_clipController) { + auto toggle1 = (e->key() == Qt::Key_F && e->modifiers().testFlag(Qt::ControlModifier)); + auto toggle2 = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) && (e->modifiers().testFlag(Qt::AltModifier) || e->modifiers().testFlag(Qt::ControlModifier)); + if (toggle1 || toggle2) { + onVideoToggleFullScreen(); + return; + } + if (_fullScreenVideo) { + if (e->key() == Qt::Key_Escape) { + onVideoToggleFullScreen(); + } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { + onVideoPauseResume(); + } + return; + } + } if (!_menu && e->key() == Qt::Key_Escape) { close(); } else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) { @@ -1774,36 +1828,40 @@ void MediaView::keyPressEvent(QKeyEvent *e) { ++newZoom; } } - if (_zoom != newZoom) { - float64 nx, ny, z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; - _w = gifShown() ? _gif->width() : (_current.width() / cIntRetinaFactor()); - _h = gifShown() ? _gif->height() : (_current.height() / cIntRetinaFactor()); - if (z >= 0) { - nx = (_x - width() / 2.) / (z + 1); - ny = (_y - height() / 2.) / (z + 1); - } else { - nx = (_x - width() / 2.) * (-z + 1); - ny = (_y - height() / 2.) * (-z + 1); - } - _zoom = newZoom; - z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; - if (z > 0) { - _w = qRound(_w * (z + 1)); - _h = qRound(_h * (z + 1)); - _x = qRound(nx * (z + 1) + width() / 2.); - _y = qRound(ny * (z + 1) + height() / 2.); - } else { - _w = qRound(_w / (-z + 1)); - _h = qRound(_h / (-z + 1)); - _x = qRound(nx / (-z + 1) + width() / 2.); - _y = qRound(ny / (-z + 1) + height() / 2.); - } - snapXY(); - update(); - } + setZoomLevel(newZoom); } } +void MediaView::setZoomLevel(int newZoom) { + if (_zoom == newZoom) return; + + float64 nx, ny, z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; + _w = gifShown() ? convertScale(_gif->width()) : (convertScale(_current.width()) / cIntRetinaFactor()); + _h = gifShown() ? convertScale(_gif->height()) : (convertScale(_current.height()) / cIntRetinaFactor()); + if (z >= 0) { + nx = (_x - width() / 2.) / (z + 1); + ny = (_y - height() / 2.) / (z + 1); + } else { + nx = (_x - width() / 2.) * (-z + 1); + ny = (_y - height() / 2.) * (-z + 1); + } + _zoom = newZoom; + z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; + if (z > 0) { + _w = qRound(_w * (z + 1)); + _h = qRound(_h * (z + 1)); + _x = qRound(nx * (z + 1) + width() / 2.); + _y = qRound(ny * (z + 1) + height() / 2.); + } else { + _w = qRound(_w / (-z + 1)); + _h = qRound(_h / (-z + 1)); + _x = qRound(nx / (-z + 1) + width() / 2.); + _y = qRound(ny / (-z + 1) + height() / 2.); + } + snapXY(); + update(); +} + bool MediaView::moveToNext(int32 delta) { if (_index < 0) { if (delta == -1 && _photo == _additionalChatPhoto) { @@ -2008,6 +2066,8 @@ void MediaView::mousePressEvent(QMouseEvent *e) { _down = OverMore; } else if (_over == OverClose) { _down = OverClose; + } else if (_over == OverVideo) { + _down = OverVideo; } else if (!_saveMsg.contains(e->pos()) || !_saveMsgStarted) { _pressed = true; _dragging = 0; @@ -2021,6 +2081,18 @@ void MediaView::mousePressEvent(QMouseEvent *e) { activateControls(); } +void MediaView::mouseDoubleClickEvent(QMouseEvent *e) { + updateOver(e->pos()); + + if (_over == OverVideo) { + onVideoToggleFullScreen(); + onVideoPauseResume(); + } else { + e->ignore(); + return TWidget::mouseDoubleClickEvent(e); + } +} + void MediaView::snapXY() { int32 xmin = width() - _w, xmax = 0; int32 ymin = height() - _h, ymax = 0; @@ -2137,7 +2209,9 @@ void MediaView::updateOver(QPoint pos) { if (_pressed || _dragging) return; - if (_leftNavVisible && _leftNav.contains(pos)) { + if (_fullScreenVideo) { + updateOverState(OverVideo); + } else if (_leftNavVisible && _leftNav.contains(pos)) { updateOverState(OverLeftNav); } else if (_rightNavVisible && _rightNav.contains(pos)) { updateOverState(OverRightNav); @@ -2155,6 +2229,8 @@ void MediaView::updateOver(QPoint pos) { updateOverState(OverMore); } else if (_closeNav.contains(pos)) { updateOverState(OverClose); + } else if (_doc && _doc->isVideo() && _gif && QRect(_x, _y, _w, _h).contains(pos)) { + updateOverState(OverVideo); } else if (_over != OverNone) { updateOverState(OverNone); } @@ -2185,6 +2261,8 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) { QTimer::singleShot(0, this, SLOT(onDropdown())); } else if (_over == OverClose && _down == OverClose) { close(); + } else if (_over == OverVideo && _down == OverVideo) { + onVideoPauseResume(); } else if (_pressed) { if (_dragging) { if (_dragging > 0) { @@ -2300,7 +2378,7 @@ bool MediaView::event(QEvent *e) { } } } - return QWidget::event(e); + return TWidget::event(e); } bool MediaView::eventFilter(QObject *obj, QEvent *e) { @@ -2328,7 +2406,7 @@ void MediaView::hide() { _controlsHideTimer.stop(); _controlsState = ControlsShown; a_cOpacity = anim::fvalue(1, 1); - QWidget::hide(); + TWidget::hide(); stopGif(); _radial.stop(); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 81b7ab02a..49941fe27 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -108,6 +108,7 @@ protected: void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; @@ -121,8 +122,7 @@ private slots: void onVideoSeekProgress(int64 positionMs); void onVideoSeekFinished(int64 positionMs); void onVideoVolumeChanged(float64 volume); - void onVideoToFullScreen(); - void onVideoFromFullScreen(); + void onVideoToggleFullScreen(); void onVideoPlayProgress(const AudioMsgId &audioId); private: @@ -131,6 +131,8 @@ private: void findCurrent(); void loadBack(); + void setZoomLevel(int newZoom); + void updateVideoPlaybackState(const AudioPlaybackState &state, bool reset = false); void updateSilentVideoPlaybackState(); void restartVideoAtSeekPosition(int64 positionMs); @@ -185,6 +187,8 @@ private: ChildWidget _clipController = { nullptr }; DocumentData *_autoplayVideoDocument = nullptr; + bool _fullScreenVideo = false; + int _fullScreenZoomCache = 0; Text _caption; QRect _captionRect; @@ -206,6 +210,7 @@ private: // Video without audio stream playback information. bool _videoIsSilent = false; bool _videoPaused = false; + bool _videoStopped = false; int64 _videoPositionMs = 0; int64 _videoDurationMs = 0; int32 _videoFrequencyMs = 1000; // 1000 ms per second. @@ -263,6 +268,7 @@ private: OverSave, OverMore, OverIcon, + OverVideo, }; OverState _over = OverNone; OverState _down = OverNone; diff --git a/Telegram/SourceFiles/shortcuts.cpp b/Telegram/SourceFiles/shortcuts.cpp index c87e79f86..9f782ea35 100644 --- a/Telegram/SourceFiles/shortcuts.cpp +++ b/Telegram/SourceFiles/shortcuts.cpp @@ -28,19 +28,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace ShortcutCommands { -typedef void(*Handler)(); +typedef bool(*Handler)(); -void lock_telegram() { +bool lock_telegram() { if (auto w = App::wnd()) { if (App::passcoded()) { w->passcodeWidget()->onSubmit(); + return true; } else if (cHasPasscode()) { w->setupPasscode(true); + return true; } } + return false; } -void minimize_telegram() { +bool minimize_telegram() { if (auto w = App::wnd()) { if (cWorkMode() == dbiwmTrayOnly) { w->minimizeToTray(); @@ -48,18 +51,21 @@ void minimize_telegram() { w->setWindowState(Qt::WindowMinimized); } } + return true; } -void close_telegram() { +bool close_telegram() { if (!Ui::hideWindowNoQuit()) { if (auto w = App::wnd()) { w->close(); } } + return true; } -void quit_telegram() { +bool quit_telegram() { App::quit(); + return true; } //void start_stop_recording() { @@ -70,58 +76,85 @@ void quit_telegram() { //} -void media_play() { - if (MainWidget *m = App::main()) { - m->player()->playPressed(); +bool media_play() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->playPressed(); + return true; + } } + return false; } -void media_pause() { - if (MainWidget *m = App::main()) { - m->player()->pausePressed(); +bool media_pause() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->pausePressed(); + return true; + } } + return false; } -void media_playpause() { - if (MainWidget *m = App::main()) { - m->player()->playPausePressed(); +bool media_playpause() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->playPausePressed(); + return true; + } } + return false; } -void media_stop() { - if (MainWidget *m = App::main()) { - m->player()->stopPressed(); +bool media_stop() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->stopPressed(); + return true; + } } + return false; } -void media_previous() { - if (MainWidget *m = App::main()) { - m->player()->prevPressed(); +bool media_previous() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->prevPressed(); + return true; + } } + return false; } -void media_next() { - if (MainWidget *m = App::main()) { - m->player()->nextPressed(); +bool media_next() { + if (auto m = App::main()) { + if (!m->player()->isHidden()) { + m->player()->nextPressed(); + return true; + } } + return false; } -void search() { - if (MainWidget *m = App::main()) { - m->cmd_search(); +bool search() { + if (auto m = App::main()) { + return m->cmd_search(); } + return false; } -void previous_chat() { - if (MainWidget *m = App::main()) { - m->cmd_previous_chat(); +bool previous_chat() { + if (auto m = App::main()) { + return m->cmd_previous_chat(); } + return false; } -void next_chat() { - if (MainWidget *m = App::main()) { - m->cmd_next_chat(); +bool next_chat() { + if (auto m = App::main()) { + return m->cmd_next_chat(); } + return false; } // other commands here @@ -501,8 +534,7 @@ bool launch(int shortcutId) { if (it == DataPtr->handlers.cend()) { return false; } - (*it.value())(); - return true; + return (*it.value())(); } bool launch(const QString &command) { @@ -512,8 +544,7 @@ bool launch(const QString &command) { if (it == DataPtr->commands.cend()) { return false; } - (*it.value())(); - return true; + return (*it.value())(); } void enableMediaShortcuts() { From bf4acc4e52f8c0d16a2530aa091f230b05c4c90c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 14 Jul 2016 14:59:55 +0300 Subject: [PATCH 22/60] Fixed reading stickers from old versioned local storage. Handling error when installing sticker set from featured. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 38 ++++++++++++++-- Telegram/SourceFiles/boxes/stickersetbox.h | 3 +- Telegram/SourceFiles/localstorage.cpp | 46 ++++++++++---------- Telegram/SourceFiles/mainwidget.cpp | 3 +- Telegram/SourceFiles/mtproto/scheme.tl | 2 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 3 +- Telegram/SourceFiles/mtproto/scheme_auto.h | 17 +++++--- Telegram/SourceFiles/ui/flatbutton.cpp | 1 + 8 files changed, 74 insertions(+), 39 deletions(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 1e9a3cedc..18d5a9741 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -155,11 +155,11 @@ void StickerSetInner::installDone(const MTPBool &result) { emit installed(_setId); } -bool StickerSetInner::installFailed(const RPCError &error) { +bool StickerSetInner::installFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("STICKERSETS_TOO_MUCH")) { - Ui::showLayer(new InformBox(lang(lng_stickers_too_many_packs))); + Ui::showLayer(new InformBox(lang(lng_stickers_too_many_packs)), KeepOtherLayers); } else { Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); } @@ -283,7 +283,7 @@ QString StickerSetInner::shortName() const { void StickerSetInner::install() { if (_installRequest) return; - _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&StickerSetInner::installDone), rpcFail(&StickerSetInner::installFailed)); + _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&StickerSetInner::installDone), rpcFail(&StickerSetInner::installFail)); } StickerSetInner::~StickerSetInner() { @@ -734,7 +734,7 @@ void StickersInner::installSet(uint64 setId) { return; } - MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse())); + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), RPCDoneHandlerPtr(), rpcFail(&StickersInner::installFail, setId)); it->flags &= ~(MTPDstickerSet::Flag::f_disabled | MTPDstickerSet_ClientFlag::f_unread); it->flags |= MTPDstickerSet::Flag::f_installed; @@ -762,6 +762,36 @@ void StickersInner::installSet(uint64 setId) { emit App::main()->stickersUpdated(); } +bool StickersInner::installFail(uint64 setId, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuild(); + return true; + } + + it->flags &= ~MTPDstickerSet::Flag::f_installed; + + auto &order = Global::RefStickerSetsOrder(); + int currentIndex = order.indexOf(setId); + if (currentIndex >= 0) { + order.removeAt(currentIndex); + } + + Local::writeStickers(); + emit App::main()->stickersUpdated(); + + if (error.type() == qstr("STICKERSETS_TOO_MUCH")) { + Ui::showLayer(new InformBox(lang(lng_stickers_too_many_packs)), KeepOtherLayers); + } else { + Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); + } + + return true; +} + void StickersInner::step_shifting(uint64 ms, bool timer) { bool animating = false; int32 updateMin = -1, updateMax = 0; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 1c8e9590d..28bddfac0 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -63,7 +63,7 @@ private: bool failedSet(const RPCError &error); void installDone(const MTPBool &result); - bool installFailed(const RPCError &error); + bool installFail(const RPCError &error); StickerPack _pack; StickersByEmojiMap _emoji; @@ -232,6 +232,7 @@ private: float64 aboveShadowOpacity() const; void installSet(uint64 setId); + bool installFail(uint64 setId, const RPCError &error); void readFeaturedDone(const MTPBool &result); bool readFeaturedFail(const RPCError &error); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 4b2e792c2..b4e1417a9 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3214,20 +3214,20 @@ namespace Local { setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); } } - if (stickers.version < 9057) { + if (stickers.version < 9058) { setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); } if (setId == Stickers::DefaultSetId) { setTitle = lang(lng_stickers_default_set); setFlags |= qFlags(MTPDstickerSet::Flag::f_official); - if (stickers.version < 9057) { + if (stickers.version < 9058) { order.push_front(setId); } } else if (setId == Stickers::CustomSetId) { setTitle = lang(lng_custom_stickers); } else if (setId) { - if (stickers.version < 9057) { + if (stickers.version < 9058) { order.push_back(setId); } } else { @@ -3282,29 +3282,29 @@ namespace Local { } // Read orders of installed and featured stickers. - if (stickers.version >= 9057) { + if (stickers.version >= 9058) { stickers.stream >> order; stickers.stream >> featuredOrder; - - // Set flags and count unread featured sets. - for_const (auto setId, order) { - auto it = sets.find(setId); - if (it != sets.cend()) { - it->flags |= MTPDstickerSet::Flag::f_installed; - } - } - int unreadCount = 0; - for_const (auto setId, featuredOrder) { - auto it = sets.find(setId); - if (it != sets.cend()) { - it->flags |= MTPDstickerSet_ClientFlag::f_featured; - if (it->flags & MTPDstickerSet_ClientFlag::f_unread) { - ++unreadCount; - } - } - } - Global::SetFeaturedStickerSetsUnreadCount(unreadCount); } + + // Set flags and count unread featured sets. + for_const (auto setId, order) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= MTPDstickerSet::Flag::f_installed; + } + } + int unreadCount = 0; + for_const (auto setId, featuredOrder) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= MTPDstickerSet_ClientFlag::f_featured; + if (it->flags & MTPDstickerSet_ClientFlag::f_unread) { + ++unreadCount; + } + } + } + Global::SetFeaturedStickerSetsUnreadCount(unreadCount); } int32 countStickersHash(bool checkOfficial) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f4b456789..4b55121dc 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3488,7 +3488,6 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { switch (invite.type()) { case mtpc_chatInvite: { auto &d(invite.c_chatInvite()); - ((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join); QVector participants; if (d.has_participants()) { @@ -3500,7 +3499,7 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { } } } - auto box = std_::make_unique(qs(d.vtitle), d.vphoto, 3, participants); + auto box = std_::make_unique(qs(d.vtitle), d.vphoto, d.vparticipants_count.v, participants); _inviteHash = hash; Ui::showLayer(box.release()); } break; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index d1179d198..6f98b5891 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -549,7 +549,7 @@ chatInviteEmpty#69df3769 = ExportedChatInvite; chatInviteExported#fc2e05bc link:string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#2d492881 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:ChatPhoto participants:flags.4?Vector = ChatInvite; +chatInvite#db74f558 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:ChatPhoto participants_count:int participants:flags.4?Vector = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 5669a5323..61c72b695 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -4350,7 +4350,8 @@ void _serialize_chatInvite(MTPStringLogger &to, int32 stage, int32 lev, Types &t case 4: to.add(" megagroup: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_megagroup) { to.add("YES [ BY BIT 3 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; case 5: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 7: to.add(" participants: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_participants) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 7: to.add(" participants_count: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 8: to.add(" participants: "); ++stages.back(); if (flag & MTPDchatInvite::Flag::f_participants) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index e0e2cb897..ce9c71234 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -398,7 +398,7 @@ enum { mtpc_chatInviteEmpty = 0x69df3769, mtpc_chatInviteExported = 0xfc2e05bc, mtpc_chatInviteAlready = 0x5a686d7c, - mtpc_chatInvite = 0x2d492881, + mtpc_chatInvite = 0xdb74f558, mtpc_inputStickerSetEmpty = 0xffb62b95, mtpc_inputStickerSetID = 0x9de7a269, mtpc_inputStickerSetShortName = 0x861cc8a0, @@ -13160,12 +13160,13 @@ public: MTPDchatInvite() { } - MTPDchatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) : vflags(_flags), vtitle(_title), vphoto(_photo), vparticipants(_participants) { + MTPDchatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, MTPint _participants_count, const MTPVector &_participants) : vflags(_flags), vtitle(_title), vphoto(_photo), vparticipants_count(_participants_count), vparticipants(_participants) { } MTPflags vflags; MTPstring vtitle; MTPChatPhoto vphoto; + MTPint vparticipants_count; MTPVector vparticipants; }; @@ -23580,8 +23581,8 @@ public: inline static MTPchatInvite new_chatInviteAlready(const MTPChat &_chat) { return MTPchatInvite(new MTPDchatInviteAlready(_chat)); } - inline static MTPchatInvite new_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) { - return MTPchatInvite(new MTPDchatInvite(_flags, _title, _photo, _participants)); + inline static MTPchatInvite new_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, MTPint _participants_count, const MTPVector &_participants) { + return MTPchatInvite(new MTPDchatInvite(_flags, _title, _photo, _participants_count, _participants)); } inline static MTPinputStickerSet new_inputStickerSetEmpty() { return MTPinputStickerSet(mtpc_inputStickerSetEmpty); @@ -32441,7 +32442,7 @@ inline uint32 MTPchatInvite::innerLength() const { } case mtpc_chatInvite: { const MTPDchatInvite &v(c_chatInvite()); - return v.vflags.innerLength() + v.vtitle.innerLength() + v.vphoto.innerLength() + (v.has_participants() ? v.vparticipants.innerLength() : 0); + return v.vflags.innerLength() + v.vtitle.innerLength() + v.vphoto.innerLength() + v.vparticipants_count.innerLength() + (v.has_participants() ? v.vparticipants.innerLength() : 0); } } return 0; @@ -32464,6 +32465,7 @@ inline void MTPchatInvite::read(const mtpPrime *&from, const mtpPrime *end, mtpT v.vflags.read(from, end); v.vtitle.read(from, end); v.vphoto.read(from, end); + v.vparticipants_count.read(from, end); if (v.has_participants()) { v.vparticipants.read(from, end); } else { v.vparticipants = MTPVector(); } } break; default: throw mtpErrorUnexpected(cons, "MTPchatInvite"); @@ -32480,6 +32482,7 @@ inline void MTPchatInvite::write(mtpBuffer &to) const { v.vflags.write(to); v.vtitle.write(to); v.vphoto.write(to); + v.vparticipants_count.write(to); if (v.has_participants()) v.vparticipants.write(to); } break; } @@ -32499,8 +32502,8 @@ inline MTPchatInvite MTP_chatInviteAlready(const MTPChat &_chat) { return MTP::internal::TypeCreator::new_chatInviteAlready(_chat); } Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDchatInvite::Flags) -inline MTPchatInvite MTP_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, const MTPVector &_participants) { - return MTP::internal::TypeCreator::new_chatInvite(_flags, _title, _photo, _participants); +inline MTPchatInvite MTP_chatInvite(const MTPflags &_flags, const MTPstring &_title, const MTPChatPhoto &_photo, MTPint _participants_count, const MTPVector &_participants) { + return MTP::internal::TypeCreator::new_chatInvite(_flags, _title, _photo, _participants_count, _participants); } inline uint32 MTPinputStickerSet::innerLength() const { diff --git a/Telegram/SourceFiles/ui/flatbutton.cpp b/Telegram/SourceFiles/ui/flatbutton.cpp index 365e5297e..df5a762be 100644 --- a/Telegram/SourceFiles/ui/flatbutton.cpp +++ b/Telegram/SourceFiles/ui/flatbutton.cpp @@ -349,6 +349,7 @@ void BoxButton::setText(const QString &text) { _fullText = text; _textWidth = _st.font->width(_text); resizeToText(); + update(); } void BoxButton::resizeToText() { From 95c050081c6039014f59480b5eb3b9fb2fd02b4d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 15 Jul 2016 18:58:52 +0300 Subject: [PATCH 23/60] Cloud recent sticker sets supported. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/apiwrap.cpp | 2 +- Telegram/SourceFiles/app.cpp | 1 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 114 +++++++++++++++--- Telegram/SourceFiles/boxes/stickersetbox.h | 13 +- Telegram/SourceFiles/dropdown.cpp | 62 ++++++---- Telegram/SourceFiles/facades.cpp | 2 + Telegram/SourceFiles/facades.h | 7 +- Telegram/SourceFiles/history.cpp | 4 +- Telegram/SourceFiles/historywidget.cpp | 107 ++++++++++++++-- Telegram/SourceFiles/historywidget.h | 4 + Telegram/SourceFiles/localstorage.cpp | 33 +++-- Telegram/SourceFiles/localstorage.h | 1 + Telegram/SourceFiles/mainwidget.cpp | 107 ++++++++-------- Telegram/SourceFiles/mtproto/core_types.h | 5 +- .../serialize/serialize_document.cpp | 2 +- Telegram/SourceFiles/settingswidget.cpp | 24 ---- Telegram/SourceFiles/settingswidget.h | 1 - 18 files changed, 342 insertions(+), 149 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d05f7de46..35d1ebca2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -689,6 +689,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_packs" = "Sticker Packs"; "lng_stickers_reorder" = "Click and drag to reorder sticker packs"; "lng_stickers_featured" = "Featured Stickers"; +"lng_stickers_clear_recent" = "Clear"; +"lng_stickers_clear_recent_sure" = "Are you sure you want to clear your frequently used stickers list?"; "lng_stickers_remove" = "Delete"; "lng_stickers_return" = "Undo"; "lng_stickers_restore" = "Restore"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e6c12b071..8d3a886c9 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -937,7 +937,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) it->hash = s.vhash.v; it->shortName = qs(s.vshort_name); it->title = stickerSetTitle(s); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = s.vflags.v | clientFlags; it->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index ee5b159b7..0c61a589d 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2012,6 +2012,7 @@ namespace { Global::SetStickerSets(Stickers::Sets()); Global::SetStickerSetsOrder(Stickers::Order()); Global::SetLastStickersUpdate(0); + Global::SetLastRecentStickersUpdate(0); Global::SetFeaturedStickerSetsOrder(Stickers::Order()); Global::SetFeaturedStickerSetsUnreadCount(0); Global::SetLastFeaturedStickersUpdate(0); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 18d5a9741..a55c26bb7 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -87,7 +87,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { auto &sets = Global::RefStickerSets(); auto it = sets.find(_setId); if (it != sets.cend()) { - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_unread); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_special); _setFlags |= clientFlags; it->flags = _setFlags; it->stickers = _pack; @@ -418,6 +418,7 @@ StickersInner::StickersInner(StickersBox::Section section) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &StickersInner::step_shifting)) , _itemsTop(st::membersPadding.top()) +, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) , _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) , _returnWidth(st::normalFont->width(lang(lng_stickers_return))) , _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) @@ -500,8 +501,8 @@ void StickersInner::paintRow(Painter &p, int32 index) { } else { p.setPen(st::btnDefLink.color); } - int32 remWidth = s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth; - QString remText = lang(s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove); + int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth); + QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove)); p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); if (index == _above) { @@ -581,7 +582,7 @@ void StickersInner::mousePressEvent(QMouseEvent *e) { if (_actionSel >= 0) { _actionDown = _actionSel; update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); - } else if (_selected >= 0 && _section == Section::Installed) { + } else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) { _above = _dragging = _started = _selected; _dragStart = mapFromGlobal(_mouse); } @@ -599,8 +600,12 @@ void StickersInner::onUpdateSelected() { if (_dragging >= 0) { int32 shift = 0; uint64 ms = getms(); + int firstSetIndex = 0; + if (_rows.at(firstSetIndex)->recent) { + ++firstSetIndex; + } if (_dragStart.y() > local.y() && _dragging > 0) { - shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging); + shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex); for (int32 from = _dragging, to = _dragging + shift; from > to; --from) { qSwap(_rows[from], _rows[from - 1]); _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0); @@ -635,7 +640,7 @@ void StickersInner::onUpdateSelected() { selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); if (_section == Section::Installed) { - int remw = _rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth; + int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth); QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; } else if (_rows.at(selected)->installed) { @@ -666,6 +671,34 @@ void StickersInner::onUpdateSelected() { } } +void StickersInner::onClearRecent() { + if (_clearBox) { + _clearBox->onClose(); + } + + auto &sets = Global::RefStickerSets(); + sets.remove(Stickers::CloudRecentSetId); + sets.remove(Stickers::CustomSetId); + + auto &recent = cGetRecentStickers(); + if (!recent.isEmpty()) { + recent.clear(); + Local::writeUserSettings(); + } + + Local::writeStickers(); + emit App::main()->updateStickers(); + rebuild(); + + MTP::send(MTPmessages_ClearRecentStickers()); +} + +void StickersInner::onClearBoxDestroyed(QObject *box) { + if (box == _clearBox) { + _clearBox = nullptr; + } +} + float64 StickersInner::aboveShadowOpacity() const { if (_above < 0) return 0; @@ -688,7 +721,14 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { onUpdateSelected(); if (_actionDown == _actionSel && _actionSel >= 0) { if (_section == Section::Installed) { - _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; + if (_rows[_actionDown]->recent) { + _clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent)); + connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent())); + connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*))); + Ui::showLayer(_clearBox, KeepOtherLayers); + } else { + _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; + } } else { installSet(_rows[_actionDown]->id); } @@ -876,7 +916,7 @@ void StickersInner::rebuild() { int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); if (_section == Section::Installed) { - namew -= qMax(qMax(_returnWidth, _removeWidth), _restoreWidth); + namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); } else { namew -= _addWidth - st::defaultActiveButton.width; } @@ -886,12 +926,54 @@ void StickersInner::rebuild() { _animStartTimes.reserve(order.size()); auto &sets = Global::StickerSets(); + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { + DocumentData *sticker = cloudIt->stickers.at(0); + int32 pixw = 0, pixh = 0; + if (sticker) { + pixw = sticker->thumb->width(); + pixh = sticker->thumb->height(); + if (pixw > st::contactsPhotoSize) { + if (pixw > pixh) { + pixh = (pixh * st::contactsPhotoSize) / pixw; + pixw = st::contactsPhotoSize; + } else { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + } else if (pixh > st::contactsPhotoSize) { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + } + QString title = cloudIt->title; + int32 titleWidth = st::contactsNameFont->width(title); + if (titleWidth > namew) { + title = st::contactsNameFont->elided(title, namew); + } + int count = cloudIt->stickers.size(); + int added = 0; + auto customIt = sets.constFind(Stickers::CustomSetId); + if (customIt != sets.cend()) { + added = customIt->stickers.size(); + for_const (auto &sticker, cGetRecentStickers()) { + if (customIt->stickers.indexOf(sticker.first) < 0) { + ++added; + } + } + } else { + added = cGetRecentStickers().size(); + } + rows.push_back(new StickerSetRow(cloudIt->id, cloudIt->stickers.front(), count + added, title, true, true, false, false, true, pixw, pixh)); + _animStartTimes.push_back(0); + } for_const (auto setId, order) { auto it = sets.constFind(setId); if (it == sets.cend()) { continue; } + bool recent = false; bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool disabled = (_section == Section::Installed) && (it->flags & MTPDstickerSet::Flag::f_disabled); bool official = (it->flags & MTPDstickerSet::Flag::f_official); @@ -924,7 +1006,7 @@ void StickersInner::rebuild() { if (titleWidth > namew) { title = st::contactsNameFont->elided(title, namew); } - (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, installed, official, unread, disabled, pixw, pixh)); + (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, installed, official, unread, disabled, recent, pixw, pixh)); _animStartTimes.push_back(0); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { App::api()->scheduleStickerSetRequest(it->id, it->access); @@ -973,6 +1055,9 @@ QVector StickersInner::getOrder() const { continue; } } + if (_rows.at(i)->recent) { + continue; + } result.push_back(_rows.at(i)->id); } return result; @@ -1167,7 +1252,7 @@ void StickersBox::onSave() { } bool writeRecent = false; - RecentStickerPack &recent(cGetRecentStickers()); + auto &recent = cGetRecentStickers(); auto &sets = Global::RefStickerSets(); QVector reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); @@ -1191,7 +1276,7 @@ void StickersBox::onSave() { _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); int removeIndex = Global::StickerSetsOrder().indexOf(it->id); if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); - if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)) { + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { sets.erase(it); } else { it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_disabled); @@ -1221,10 +1306,9 @@ void StickersBox::onSave() { } } for (auto it = sets.begin(); it != sets.cend();) { - if (it->id == Stickers::CustomSetId - || it->id == Stickers::RecentSetId - || (it->flags & MTPDstickerSet_ClientFlag::f_featured) - || (it->flags & MTPDstickerSet::Flag::f_installed)) { + if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) + || (it->flags & MTPDstickerSet::Flag::f_installed) + || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { ++it; } else { it = sets.erase(it); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 28bddfac0..70f65e3c3 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" +class ConfirmBox; + class StickerSetInner : public TWidget, public RPCSender { Q_OBJECT @@ -221,6 +223,8 @@ signals: public slots: void onUpdateSelected(); + void onClearRecent(); + void onClearBoxDestroyed(QObject *box); private: void paintFeaturedButton(Painter &p) const; @@ -240,7 +244,7 @@ private: int32 _rowHeight; struct StickerSetRow { - StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, int32 pixw, int32 pixh) : id(id) + StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) , sticker(sticker) , count(count) , title(title) @@ -248,6 +252,7 @@ private: , official(official) , unread(unread) , disabled(disabled) + , recent(recent) , pixw(pixw) , pixh(pixh) , yadd(0, 0) { @@ -256,7 +261,7 @@ private: DocumentData *sticker; int32 count; QString title; - bool installed, official, unread, disabled; + bool installed, official, unread, disabled, recent; int32 pixw, pixh; anim::ivalue yadd; }; @@ -274,7 +279,9 @@ private: int _actionSel = -1; int _actionDown = -1; - int _removeWidth, _returnWidth, _restoreWidth; + int _clearWidth, _removeWidth, _returnWidth, _restoreWidth; + + ConfirmBox *_clearBox = nullptr; QString _addText; int _addWidth; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 029c912a7..1c63f49ba 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -714,7 +714,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { } EmojiPanInner::EmojiPanInner() : TWidget() -, _maxHeight(int(st::emojiPanMaxHeight)) +, _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height) , _a_selected(animation(this, &EmojiPanInner::step_selected)) , _top(0) , _selected(-1) @@ -1230,7 +1230,7 @@ StickerPanInner::StickerPanInner() : TWidget() , _pressedSel(-1) , _settings(this, lang(lng_stickers_you_have)) , _previewShown(false) { - setMaxHeight(st::emojiPanMaxHeight); + setMaxHeight(st::emojiPanMaxHeight - st::rbEmoji.height); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); @@ -2111,37 +2111,49 @@ void StickerPanInner::refreshRecent() { void StickerPanInner::refreshRecentStickers(bool performResize) { _custom.clear(); clearSelection(true); - auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); - if (cGetRecentStickers().isEmpty() && (customIt == Global::StickerSets().cend() || customIt->stickers.isEmpty())) { + auto &sets = Global::StickerSets(); + auto &recent = cGetRecentStickers(); + auto customIt = sets.constFind(Stickers::CustomSetId); + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (recent.isEmpty() + && (customIt == sets.cend() || customIt->stickers.isEmpty()) + && (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) { if (!_sets.isEmpty() && _sets.at(0).id == Stickers::RecentSetId) { _sets.pop_front(); } } else { - StickerPack recent; - int32 customCnt = (customIt == Global::StickerSets().cend() ? 0 : customIt->stickers.size()); - QMap recentOnly; - recent.reserve(cGetRecentStickers().size() + customCnt); - _custom.reserve(cGetRecentStickers().size() + customCnt); - for (int32 i = 0, l = cGetRecentStickers().size(); i < l; ++i) { - DocumentData *s = cGetRecentStickers().at(i).first; - recent.push_back(s); - recentOnly.insert(s, true); + StickerPack recentPack; + int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size(); + int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size(); + recentPack.reserve(cloudCnt + recent.size() + customCnt); + _custom.reserve(cloudCnt + recent.size() + customCnt); + if (cloudCnt > 0) { + for_const (auto sticker, cloudIt->stickers) { + recentPack.push_back(sticker); + _custom.push_back(false); + } + } + for_const (auto &recentSticker, recent) { + auto sticker = recentSticker.first; + recentPack.push_back(sticker); _custom.push_back(false); } - for (int32 i = 0; i < customCnt; ++i) { - DocumentData *s = customIt->stickers.at(i); - if (recentOnly.contains(s)) { - _custom[recent.indexOf(s)] = true; - } else { - recent.push_back(s); - _custom.push_back(true); + if (customCnt > 0) { + for_const (auto &sticker, customIt->stickers) { + auto index = recentPack.indexOf(sticker); + if (index >= cloudCnt) { + _custom[index] = true; // mark stickers from recent as custom + } else { + recentPack.push_back(sticker); + _custom.push_back(true); + } } } if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) { - _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official, lang(lng_emoji_category0), recent.size() * 2, recent)); + _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_emoji_category0), recentPack.size() * 2, recentPack)); } else { - _sets[0].pack = recent; - _sets[0].hovers.resize(recent.size() * 2); + _sets[0].pack = recentPack; + _sets[0].hovers.resize(recentPack.size() * 2); } } @@ -3244,7 +3256,7 @@ void EmojiPan::step_icons(uint64 ms, bool timer) { } if (_iconsStartAnim) { - float64 dt = (ms - _iconsStartAnim) / st::stickerIconMove; + float64 dt = (ms - _iconsStartAnim) / float64(st::stickerIconMove); if (dt >= 1) { _iconsStartAnim = 0; _iconsX.finish(); @@ -3669,7 +3681,7 @@ void EmojiPan::onRemoveSetSure() { } } it->flags &= ~MTPDstickerSet::Flag::f_installed; - if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)) { + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { Global::RefStickerSets().erase(it); } int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 7dfa5513c..d4e7318c0 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -564,6 +564,7 @@ struct Data { Stickers::Sets StickerSets; Stickers::Order StickerSetsOrder; uint64 LastStickersUpdate = 0; + uint64 LastRecentStickersUpdate = 0; Stickers::Order FeaturedStickerSetsOrder; int FeaturedStickerSetsUnreadCount = 0; uint64 LastFeaturedStickersUpdate = 0; @@ -635,6 +636,7 @@ DefineRefVar(Global, PendingItemsMap, PendingRepaintItems); DefineVar(Global, Stickers::Sets, StickerSets); DefineVar(Global, Stickers::Order, StickerSetsOrder); DefineVar(Global, uint64, LastStickersUpdate); +DefineVar(Global, uint64, LastRecentStickersUpdate); DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); DefineVar(Global, int, FeaturedStickerSetsUnreadCount); DefineVar(Global, uint64, LastFeaturedStickersUpdate); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index cd9acceeb..d0d09ff29 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -183,8 +183,10 @@ enum Flags { namespace Stickers { static const uint64 DefaultSetId = 0; // for backward compatibility -static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL, RecentSetId = 0xFFFFFFFFFFFFFFFEULL; -static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel +static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL; +static const uint64 RecentSetId = 0xFFFFFFFFFFFFFFFEULL; // for emoji/stickers panel, should not appear in Sets +static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel, should not appear in Sets +static const uint64 CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; // for cloud-stored recent stickers struct Set { Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id) @@ -262,6 +264,7 @@ DeclareRefVar(PendingItemsMap, PendingRepaintItems); DeclareVar(Stickers::Sets, StickerSets); DeclareVar(Stickers::Order, StickerSetsOrder); DeclareVar(uint64, LastStickersUpdate); +DeclareVar(uint64, LastRecentStickersUpdate); DeclareVar(Stickers::Order, FeaturedStickerSetsOrder); DeclareVar(int, FeaturedStickerSetsUnreadCount); DeclareVar(uint64, LastFeaturedStickersUpdate); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index a5c8b8bd2..9534dd746 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -4076,10 +4076,10 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); - QPixmap thumb = loaded ? _data->thumb->pixSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); + QPixmap thumb = loaded ? _data->thumb->pixSingle(ImageRoundRadius::Large, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); p.drawPixmap(rthumb.topLeft(), thumb); if (selected) { - App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlaySmallCorners); + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners); } if (radial || (!loaded && !_data->loading())) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 27f01b9a4..08523d689 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3530,6 +3530,11 @@ void HistoryWidget::updateStickers() { _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); } } + if (!Global::LastRecentStickersUpdate() || now >= Global::LastRecentStickersUpdate() + StickersUpdateTimeout) { + if (!_recentStickersUpdateRequest) { + _recentStickersUpdateRequest = MTP::send(MTPmessages_GetRecentStickers(MTP_int(Local::countRecentStickersHash())), rpcDone(&HistoryWidget::recentStickersGot), rpcFail(&HistoryWidget::recentStickersFailed)); + } + } if (!Global::LastFeaturedStickersUpdate() || now >= Global::LastFeaturedStickersUpdate() + StickersUpdateTimeout) { if (!_featuredStickersUpdateRequest) { _featuredStickersUpdateRequest = MTP::send(MTPmessages_GetFeaturedStickers(MTP_int(Local::countFeaturedStickersHash())), rpcDone(&HistoryWidget::featuredStickersGot), rpcFail(&HistoryWidget::featuredStickersFailed)); @@ -3671,7 +3676,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = set.vflags.v | clientFlags; if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { it->count = set.vcount.v; @@ -3692,6 +3697,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); + bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); if (!installed) { // remove not mine sets from recent stickers for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { @@ -3702,7 +3708,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } } } - if (installed || featured) { + if (installed || featured || special) { ++it; } else { it = sets.erase(it); @@ -3736,6 +3742,92 @@ bool HistoryWidget::stickersFailed(const RPCError &error) { return true; } +void HistoryWidget::recentStickersGot(const MTPmessages_RecentStickers &stickers) { + Global::SetLastRecentStickersUpdate(getms(true)); + _recentStickersUpdateRequest = 0; + + if (stickers.type() != mtpc_messages_recentStickers) return; + auto &d = stickers.c_messages_recentStickers(); + + auto &sets = Global::RefStickerSets(); + auto it = sets.find(Stickers::CloudRecentSetId); + + auto &d_docs = d.vstickers.c_vector().v; + if (d_docs.isEmpty()) { + if (it != sets.cend()) { + sets.erase(it); + } + } else { + if (it == sets.cend()) { + it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_emoji_category0), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); + } else { + it->title = lang(lng_emoji_category0); + } + it->hash = d.vhash.v; + + auto custom = sets.find(Stickers::CustomSetId); + + StickerPack pack; + pack.reserve(d_docs.size()); + for (int32 i = 0, l = d_docs.size(); i != l; ++i) { + DocumentData *doc = App::feedDocument(d_docs.at(i)); + if (!doc || !doc->sticker()) continue; + + pack.push_back(doc); + if (custom != sets.cend()) { + int32 index = custom->stickers.indexOf(doc); + if (index >= 0) { + custom->stickers.removeAt(index); + } + } + } + if (custom != sets.cend() && custom->stickers.isEmpty()) { + sets.erase(custom); + custom = sets.end(); + } + + bool writeRecent = false; + RecentStickerPack &recent(cGetRecentStickers()); + for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { + if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + + if (pack.isEmpty()) { + sets.erase(it); + } else { + it->stickers = pack; + it->emoji.clear(); + } + + if (writeRecent) { + Local::writeUserSettings(); + } + } + + if (Local::countRecentStickersHash() != d.vhash.v) { + LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countRecentStickersHash())); + } + + Local::writeStickers(); + + if (App::main()) emit App::main()->stickersUpdated(); +} + +bool HistoryWidget::recentStickersFailed(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + LOG(("App Fail: Failed to get recent stickers!")); + + Global::SetLastRecentStickersUpdate(getms(true)); + _recentStickersUpdateRequest = 0; + return true; +} + void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stickers) { Global::SetLastFeaturedStickersUpdate(getms(true)); _featuredStickersUpdateRequest = 0; @@ -3764,16 +3856,16 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic auto it = sets.find(set.vid.v); QString title = stickerSetTitle(set); if (it == sets.cend()) { - auto clientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; + auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; if (unread.contains(set.vid.v) || !(set.vflags.v & MTPDstickerSet::Flag::f_installed)) { - clientFlags |= MTPDstickerSet_ClientFlag::f_unread; + setClientFlags |= MTPDstickerSet_ClientFlag::f_unread; } - it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | clientFlags)); + it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | setClientFlags)); } else { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = set.vflags.v | clientFlags; it->flags |= MTPDstickerSet_ClientFlag::f_featured; if (unread.contains(it->id)) { @@ -3798,7 +3890,8 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic for (auto it = sets.begin(), e = sets.end(); it != e;) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); - if (installed || featured) { + bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); + if (installed || featured || special) { if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { ++unreadCount; } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index ff68e4e3e..ee979a570 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -1005,6 +1005,10 @@ private: void stickersGot(const MTPmessages_AllStickers &stickers); bool stickersFailed(const RPCError &error); + mtpRequestId _recentStickersUpdateRequest = 0; + void recentStickersGot(const MTPmessages_RecentStickers &stickers); + bool recentStickersFailed(const RPCError &error); + mtpRequestId _featuredStickersUpdateRequest = 0; void featuredStickersGot(const MTPmessages_FeaturedStickers &stickers); bool featuredStickersFailed(const RPCError &error); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index b4e1417a9..68a443ca0 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3052,7 +3052,7 @@ namespace Local { quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); for_const (auto &set, sets) { bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); - if (notLoaded) { + if (notLoaded && !(set.flags & MTPDstickerSet_ClientFlag::f_special)) { if (!(set.flags & MTPDstickerSet::Flag::f_disabled) || (set.flags & MTPDstickerSet::Flag::f_official) || (set.flags & MTPDstickerSet_ClientFlag::f_featured)) { // waiting to receive @@ -3118,8 +3118,8 @@ namespace Local { auto &recent = cRefRecentStickers(); recent.clear(); - auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed)).value(); - auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed)).value(); + auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); + auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); QMap read; while (!stickers.stream.atEnd()) { @@ -3220,12 +3220,16 @@ namespace Local { if (setId == Stickers::DefaultSetId) { setTitle = lang(lng_stickers_default_set); - setFlags |= qFlags(MTPDstickerSet::Flag::f_official); + setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special); if (stickers.version < 9058) { order.push_front(setId); } } else if (setId == Stickers::CustomSetId) { setTitle = lang(lng_custom_stickers); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); + } else if (setId == Stickers::CloudRecentSetId) { + setTitle = lang(lng_emoji_category0); // Frequently used + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); } else if (setId) { if (stickers.version < 9058) { order.push_back(setId); @@ -3328,6 +3332,20 @@ namespace Local { return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; } + int32 countRecentStickersHash() { + uint32 acc = 0; + auto &sets = Global::StickerSets(); + auto it = sets.constFind(Stickers::CloudRecentSetId); + if (it != sets.cend()) { + for_const (auto doc, it->stickers) { + auto docId = doc->id; + acc = (acc * 20261) + uint32(docId >> 32); + acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); + } + } + return int32(acc & 0x7FFFFFFF); + } + int32 countFeaturedStickersHash() { uint32 acc = 0; auto &sets = Global::StickerSets(); @@ -3346,10 +3364,9 @@ namespace Local { int32 countSavedGifsHash() { uint32 acc = 0; - const SavedGifs &saved(cSavedGifs()); - for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { - uint64 docId = (*i)->id; - + auto &saved = cSavedGifs(); + for_const (auto doc, saved) { + auto docId = doc->id; acc = (acc * 20261) + uint32(docId >> 32); acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); } diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index 204ac320a..a4909037d 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -156,6 +156,7 @@ namespace Local { void writeStickers(); void readStickers(); int32 countStickersHash(bool checkOfficial = false); + int32 countRecentStickersHash(); int32 countFeaturedStickersHash(); void writeSavedGifs(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4b55121dc..3391afaa2 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -301,11 +301,6 @@ void MainWidget::finishForwarding(History *history, bool silent) { FullMsgId newId(peerToChannel(history->peer->id), clientMsgId()); HistoryMessage *msg = static_cast(_toForward.cbegin().value()); history->addNewForwarded(newId.msg, flags, date(MTP_int(unixtime())), showFromName ? MTP::authedId() : 0, msg); - if (HistoryMedia *media = msg->getMedia()) { - if (media->type() == MediaTypeSticker) { - App::main()->incrementSticker(media->getDocument()); - } - } App::historyRegRandom(randomId, newId); } if (forwardFrom != i.value()->history()->peer) { @@ -3679,71 +3674,60 @@ void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify, void MainWidget::incrementSticker(DocumentData *sticker) { if (!sticker || !sticker->sticker()) return; + if (sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) return; - RecentStickerPack &recent(cGetRecentStickers()); - RecentStickerPack::iterator i = recent.begin(), e = recent.end(); - for (; i != e; ++i) { + bool writeStickers = false; + auto &sets = Global::RefStickerSets(); + auto it = sets.find(Stickers::CloudRecentSetId); + if (it == sets.cend()) { + if (it == sets.cend()) { + it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_emoji_category0), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); + } else { + it->title = lang(lng_emoji_category0); + } + } + auto index = it->stickers.indexOf(sticker); + if (index > 0) { + it->stickers.removeAt(index); + } + if (index) { + it->stickers.push_front(sticker); + writeStickers = true; + } + + // Remove that sticker from old recent, now it is in cloud recent stickers. + bool writeRecent = false; + auto &recent = cGetRecentStickers(); + for (auto i = recent.begin(), e = recent.end(); i != e; ++i) { if (i->first == sticker) { - i->second = recent.begin()->second; // throw to the first place - //++i->second; - //if (i->second > 0x8000) { - // for (RecentStickerPack::iterator j = recent.begin(); j != e; ++j) { - // if (j->second > 1) { - // j->second /= 2; - // } else { - // j->second = 1; - // } - // } - //} - for (; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } + writeRecent = true; + recent.erase(i); break; } } - if (i == e) { - while (recent.size() >= StickerPanPerRow * StickerPanRowsPerPage) recent.pop_back(); - recent.push_front(qMakePair(sticker, recent.isEmpty() ? 1 : recent.begin()->second)); - //recent.push_back(qMakePair(sticker, 1)); - //for (i = recent.end() - 1; i != recent.begin(); --i) { - // if ((i - 1)->second > i->second) { - // break; - // } - // qSwap(*i, *(i - 1)); - //} + while (!recent.isEmpty() && it->stickers.size() + recent.size() > StickerPanPerRow * StickerPanRowsPerPage) { + writeRecent = true; + recent.pop_back(); } - Local::writeUserSettings(); - - bool found = false; - uint64 setId = 0; - QString setName; - switch (sticker->sticker()->set.type()) { - case mtpc_inputStickerSetID: setId = sticker->sticker()->set.c_inputStickerSetID().vid.v; break; - case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker()->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break; + if (writeRecent) { + Local::writeUserSettings(); } - Stickers::Sets &sets(Global::RefStickerSets()); - for (auto i = sets.cbegin(); i != sets.cend(); ++i) { - if (i->id == Stickers::CustomSetId || i->id == Stickers::DefaultSetId || (setId && i->id == setId) || (!setName.isEmpty() && i->shortName.toLower().trimmed() == setName)) { - for (int32 j = 0, l = i->stickers.size(); j < l; ++j) { - if (i->stickers.at(j) == sticker) { - found = true; - break; - } + + // Remove that sticker from custom stickers, now it is in cloud recent stickers. + auto custom = sets.find(Stickers::CustomSetId); + if (custom != sets.cend()) { + int removeIndex = custom->stickers.indexOf(sticker); + if (removeIndex >= 0) { + custom->stickers.removeAt(removeIndex); + if (custom->stickers.isEmpty()) { + sets.erase(custom); } - if (found) break; + writeStickers = true; } } - if (!found) { - Stickers::Sets::iterator it = sets.find(Stickers::CustomSetId); - if (it == sets.cend()) { - it = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed)); - } - it->stickers.push_back(sticker); - ++it->count; + + if (writeStickers) { Local::writeStickers(); } _history->updateRecentStickers(); @@ -4770,6 +4754,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { App::main()->updateStickers(); } break; + case mtpc_updateRecentStickers: { + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + } break; + case mtpc_updateReadFeaturedStickers: { for (auto &set : Global::RefStickerSets()) { if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 1438fe212..846b8ef71 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -1066,8 +1066,11 @@ enum class MTPDstickerSet_ClientFlag : int32 { // sticker set is an unread featured set f_unread = (1 << 28), + // special set like recent or custom stickers + f_special = (1 << 27), + // update this when adding new client side flags - MIN_FIELD = (1 << 28), + MIN_FIELD = (1 << 27), }; DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet) diff --git a/Telegram/SourceFiles/serialize/serialize_document.cpp b/Telegram/SourceFiles/serialize/serialize_document.cpp index 6f489e690..62230b8ab 100644 --- a/Telegram/SourceFiles/serialize/serialize_document.cpp +++ b/Telegram/SourceFiles/serialize/serialize_document.cpp @@ -93,7 +93,7 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & if (typeOfSet == StickerSetTypeEmpty) { attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); } else if (info) { - if (info->setId == Stickers::DefaultSetId || info->setId == Stickers::CustomSetId) { + if (info->setId == Stickers::DefaultSetId || info->setId == Stickers::CloudRecentSetId || info->setId == Stickers::CustomSetId) { typeOfSet = StickerSetTypeEmpty; } diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index afb3112e2..aa8ce6175 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -849,16 +849,10 @@ void SettingsInner::keyPressEvent(QKeyEvent *e) { connect(box.get(), SIGNAL(confirmed()), this, SLOT(onSwitchModerateMode())); Ui::showLayer(box.release()); break; - } else if (str == qstr("clearstickers")) { - auto box = std_::make_unique(qsl("Clear frequently used stickers list?")); - connect(box.get(), SIGNAL(confirmed()), this, SLOT(onClearStickers())); - Ui::showLayer(box.release()); - break; } else if ( qsl("debugmode").startsWith(str) || qsl("testmode").startsWith(str) || qsl("loadlang").startsWith(str) || - qsl("clearstickers").startsWith(str) || qsl("moderate").startsWith(str) || qsl("debugfiles").startsWith(str) || qsl("workmode").startsWith(str) || @@ -1267,24 +1261,6 @@ void SettingsInner::onShowSessions() { Ui::showLayer(box); } -void SettingsInner::onClearStickers() { - auto &recent(cGetRecentStickers()); - if (!recent.isEmpty()) { - recent.clear(); - Local::writeUserSettings(); - } - auto &sets(Global::RefStickerSets()); - auto it = sets.find(Stickers::CustomSetId); - if (it != sets.cend()) { - sets.erase(it); - Local::writeStickers(); - } - if (auto m = App::main()) { - emit m->stickersUpdated(); - } - Ui::hideLayer(); -} - void SettingsInner::onSwitchModerateMode() { Global::SetModerateModeEnabled(!Global::ModerateModeEnabled()); Local::writeUserSettings(); diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h index d88df93c8..4d125b4a6 100644 --- a/Telegram/SourceFiles/settingswidget.h +++ b/Telegram/SourceFiles/settingswidget.h @@ -187,7 +187,6 @@ public slots: void onUpdateLocalStorage(); private slots: - void onClearStickers(); void onSwitchModerateMode(); void onAskQuestion(); From 1222f914eda9e575141cae9673f26005a52fb9eb Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 16 Jul 2016 09:54:02 +0300 Subject: [PATCH 24/60] Fixed time label positioning after the text has changed. --- Telegram/SourceFiles/media/view/media_clip_controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index eb9f1ae3f..133e93330 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -157,6 +157,7 @@ void Controller::refreshTimeTexts() { _playedAlready->setText(timeAlready, &alreadyChanged); _toPlayLeft->setText(timeLeft, &leftChanged); if (alreadyChanged || leftChanged) { + resizeEvent(nullptr); _fadeAnimation->refreshCache(); } } From 12523f4c0cff32899a0273f3a004c53d835fd2a9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 17 Jul 2016 21:21:57 +0300 Subject: [PATCH 25/60] Checking self-computed crc32 magics with the ones from tl scheme. --- Telegram/SourceFiles/mtproto/generate.py | 84 +++- Telegram/SourceFiles/mtproto/scheme.tl | 4 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 478 +++++++++---------- Telegram/SourceFiles/mtproto/scheme_auto.h | 58 +-- 4 files changed, 315 insertions(+), 309 deletions(-) diff --git a/Telegram/SourceFiles/mtproto/generate.py b/Telegram/SourceFiles/mtproto/generate.py index 8cc38e379..9d2537339 100644 --- a/Telegram/SourceFiles/mtproto/generate.py +++ b/Telegram/SourceFiles/mtproto/generate.py @@ -20,6 +20,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org ''' import glob import re +import binascii # define some checked flag convertions # the key flag type should be a subset of the value flag type @@ -120,12 +121,51 @@ with open('scheme.tl') as f: else: Name = name[0:1].upper() + name[1:]; typeid = nametype.group(2); + while (len(typeid) > 0 and typeid[0] == '0'): + typeid = typeid[1:]; + if (len(typeid) == 0): + typeid = '0'; + typeid = '0x' + typeid; + + cleanline = nametype.group(1) + nametype.group(3) + '= ' + nametype.group(4); + cleanline = re.sub(r' [a-zA-Z0-9_]+\:flags\.[0-9]+\?true', '', cleanline); + cleanline = cleanline.replace('<', ' ').replace('>', ' ').replace(' ', ' '); + cleanline = re.sub(r'^ ', '', cleanline); + cleanline = re.sub(r' $', '', cleanline); + cleanline = cleanline.replace(':bytes ', ':string '); + cleanline = cleanline.replace('?bytes ', '?string '); + cleanline = cleanline.replace('{', ''); + cleanline = cleanline.replace('}', ''); + countTypeId = hex(binascii.crc32(binascii.a2b_qp(cleanline)))[2:]; + countTypeId = '0x' + countTypeId; + if (typeid != countTypeId): + print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); + continue; + params = nametype.group(3); restype = nametype.group(4); if (restype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', restype); if (templ): - restype = templ.group(1) + 'MTP' + templ.group(2).replace('.', '_') + '>'; + vectemplate = templ.group(2); + if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): + restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): + restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + else: + foundmeta = ''; + for metatype in typesDict: + for typedata in typesDict[metatype]: + if (typedata[0] == vectemplate): + foundmeta = metatype; + break; + if (len(foundmeta) > 0): + break; + if (len(foundmeta) > 0): + ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; + else: + print('Bad vector param: ' + vectemplate); + continue; else: print('Bad template type: ' + restype); continue; @@ -147,7 +187,7 @@ with open('scheme.tl') as f: boxed[resType] = restype; boxed[Name] = name; - enums.append('\tmtpc_' + name + ' = 0x' + typeid); + enums.append('\tmtpc_' + name + ' = ' + typeid); paramsList = params.strip().split(' '); prms = {}; @@ -193,7 +233,25 @@ with open('scheme.tl') as f: if (ptype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype); if (templ): - ptype = templ.group(1) + 'MTP' + templ.group(2).replace('.', '_') + '>'; + vectemplate = templ.group(2); + if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): + ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): + ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + else: + foundmeta = ''; + for metatype in typesDict: + for typedata in typesDict[metatype]: + if (typedata[0] == vectemplate): + foundmeta = metatype; + break; + if (len(foundmeta) > 0): + break; + if (len(foundmeta) > 0): + ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; + else: + print('Bad vector param: ' + vectemplate); + continue; else: print('Bad template type: ' + ptype); continue; @@ -205,7 +263,25 @@ with open('scheme.tl') as f: elif (ptype.find('<') >= 0): templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype); if (templ): - ptype = templ.group(1) + 'MTP' + templ.group(2).replace('.', '_') + '>'; + vectemplate = templ.group(2); + if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+_[A-Z]', vectemplate)): + ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string'): + ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>'; + else: + foundmeta = ''; + for metatype in typesDict: + for typedata in typesDict[metatype]: + if (typedata[0] == vectemplate): + foundmeta = metatype; + break; + if (len(foundmeta) > 0): + break; + if (len(foundmeta) > 0): + ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>'; + else: + print('Bad vector param: ' + vectemplate); + continue; else: print('Bad template type: ' + ptype); continue; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index aac8a23bf..0e5f0b11d 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -84,7 +84,7 @@ rpc_answer_dropped_running#cd78e586 = RpcDropAnswer; rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer; future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt; -future_salts#ae500895 req_msg_id:long now:int salts:vector = FutureSalts; +future_salts#ae500895 req_msg_id:long now:int salts:vector = FutureSalts; pong#347773c5 msg_id:long ping_id:long = Pong; @@ -111,7 +111,7 @@ ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong; destroy_session#e7512126 session_id:long = DestroySessionRes; -register.saveDeveloperInfo#9a5f6e95 name:string email:string phone_number:string age:int city:string = Bool; +#contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool; /////////////////////////////// ///////// Main application API diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 1e1415574..230d39b93 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -5857,19 +5857,228 @@ void _serialize_destroy_session(MTPStringLogger &to, int32 stage, int32 lev, Typ } } -void _serialize_register_saveDeveloperInfo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { +void _serialize_invokeAfterMsg(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); } else { - to.add("{ register_saveDeveloperInfo"); + to.add("{ invokeAfterMsg"); to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" email: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeAfterMsgs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeAfterMsgs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" msg_ids: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_initConnection(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ initConnection"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" device_model: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" system_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" app_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" lang_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeWithLayer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeWithLayer"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" layer: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeWithoutUpdates(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeWithoutUpdates"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_checkPhone(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_checkPhone"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_sendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPauth_sendCode::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_sendCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" age: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" city: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_resendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_resendCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_account_sendChangePhoneCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPaccount_sendChangePhoneCode::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ account_sendChangePhoneCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_signUp(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_signUp"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_signIn(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_signIn"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_importAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_importAuthorization"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_importBotAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_importBotAuthorization"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_checkPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_checkPassword"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" password_hash: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_recoverPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_recoverPassword"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6476,232 +6685,6 @@ void _serialize_channels_updateUsername(MTPStringLogger &to, int32 stage, int32 } } -void _serialize_invokeAfterMsg(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeAfterMsg"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeAfterMsgs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeAfterMsgs"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" msg_ids: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_initConnection(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ initConnection"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" device_model: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" system_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" app_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" lang_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeWithLayer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeWithLayer"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" layer: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeWithoutUpdates(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeWithoutUpdates"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_checkPhone(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_checkPhone"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_sendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - MTPauth_sendCode::Flags flag(iflag); - - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_sendCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 4: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_resendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_resendCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_account_sendChangePhoneCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - MTPaccount_sendChangePhoneCode::Flags flag(iflag); - - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ account_sendChangePhoneCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_signUp(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_signUp"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_signIn(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_signIn"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_importAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_importAuthorization"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_importBotAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_importBotAuthorization"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_checkPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_checkPassword"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" password_hash: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_recoverPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_recoverPassword"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_auth_exportAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8706,7 +8689,21 @@ namespace { _serializers.insert(mtpc_ping, _serialize_ping); _serializers.insert(mtpc_ping_delay_disconnect, _serialize_ping_delay_disconnect); _serializers.insert(mtpc_destroy_session, _serialize_destroy_session); - _serializers.insert(mtpc_register_saveDeveloperInfo, _serialize_register_saveDeveloperInfo); + _serializers.insert(mtpc_invokeAfterMsg, _serialize_invokeAfterMsg); + _serializers.insert(mtpc_invokeAfterMsgs, _serialize_invokeAfterMsgs); + _serializers.insert(mtpc_initConnection, _serialize_initConnection); + _serializers.insert(mtpc_invokeWithLayer, _serialize_invokeWithLayer); + _serializers.insert(mtpc_invokeWithoutUpdates, _serialize_invokeWithoutUpdates); + _serializers.insert(mtpc_auth_checkPhone, _serialize_auth_checkPhone); + _serializers.insert(mtpc_auth_sendCode, _serialize_auth_sendCode); + _serializers.insert(mtpc_auth_resendCode, _serialize_auth_resendCode); + _serializers.insert(mtpc_account_sendChangePhoneCode, _serialize_account_sendChangePhoneCode); + _serializers.insert(mtpc_auth_signUp, _serialize_auth_signUp); + _serializers.insert(mtpc_auth_signIn, _serialize_auth_signIn); + _serializers.insert(mtpc_auth_importAuthorization, _serialize_auth_importAuthorization); + _serializers.insert(mtpc_auth_importBotAuthorization, _serialize_auth_importBotAuthorization); + _serializers.insert(mtpc_auth_checkPassword, _serialize_auth_checkPassword); + _serializers.insert(mtpc_auth_recoverPassword, _serialize_auth_recoverPassword); _serializers.insert(mtpc_auth_logOut, _serialize_auth_logOut); _serializers.insert(mtpc_auth_resetAuthorizations, _serialize_auth_resetAuthorizations); _serializers.insert(mtpc_auth_sendInvites, _serialize_auth_sendInvites); @@ -8751,21 +8748,6 @@ namespace { _serializers.insert(mtpc_channels_editAbout, _serialize_channels_editAbout); _serializers.insert(mtpc_channels_checkUsername, _serialize_channels_checkUsername); _serializers.insert(mtpc_channels_updateUsername, _serialize_channels_updateUsername); - _serializers.insert(mtpc_invokeAfterMsg, _serialize_invokeAfterMsg); - _serializers.insert(mtpc_invokeAfterMsgs, _serialize_invokeAfterMsgs); - _serializers.insert(mtpc_initConnection, _serialize_initConnection); - _serializers.insert(mtpc_invokeWithLayer, _serialize_invokeWithLayer); - _serializers.insert(mtpc_invokeWithoutUpdates, _serialize_invokeWithoutUpdates); - _serializers.insert(mtpc_auth_checkPhone, _serialize_auth_checkPhone); - _serializers.insert(mtpc_auth_sendCode, _serialize_auth_sendCode); - _serializers.insert(mtpc_auth_resendCode, _serialize_auth_resendCode); - _serializers.insert(mtpc_account_sendChangePhoneCode, _serialize_account_sendChangePhoneCode); - _serializers.insert(mtpc_auth_signUp, _serialize_auth_signUp); - _serializers.insert(mtpc_auth_signIn, _serialize_auth_signIn); - _serializers.insert(mtpc_auth_importAuthorization, _serialize_auth_importAuthorization); - _serializers.insert(mtpc_auth_importBotAuthorization, _serialize_auth_importBotAuthorization); - _serializers.insert(mtpc_auth_checkPassword, _serialize_auth_checkPassword); - _serializers.insert(mtpc_auth_recoverPassword, _serialize_auth_recoverPassword); _serializers.insert(mtpc_auth_exportAuthorization, _serialize_auth_exportAuthorization); _serializers.insert(mtpc_auth_requestPasswordRecovery, _serialize_auth_requestPasswordRecovery); _serializers.insert(mtpc_account_getNotifySettings, _serialize_account_getNotifySettings); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 90eff1aa9..7c6769561 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -39,7 +39,7 @@ class TypeCreator; // Type id constants enum { - mtpc_resPQ = 0x05162463, + mtpc_resPQ = 0x5162463, mtpc_p_q_inner_data = 0x83c95aec, mtpc_server_DH_params_fail = 0x79cb045d, mtpc_server_DH_params_ok = 0xd0e8075c, @@ -55,7 +55,7 @@ enum { mtpc_bad_msg_notification = 0xa7eff811, mtpc_bad_server_salt = 0xedab447b, mtpc_msgs_state_req = 0xda69fb52, - mtpc_msgs_state_info = 0x04deb57d, + mtpc_msgs_state_info = 0x4deb57d, mtpc_msgs_all_info = 0x8cc0d131, mtpc_msg_detailed_info = 0x276d3ec6, mtpc_msg_new_detailed_info = 0x809db6df, @@ -64,7 +64,7 @@ enum { mtpc_rpc_answer_unknown = 0x5e2ad36e, mtpc_rpc_answer_dropped_running = 0xcd78e586, mtpc_rpc_answer_dropped = 0xa43ad8b7, - mtpc_future_salt = 0x0949d9dc, + mtpc_future_salt = 0x949d9dc, mtpc_future_salts = 0xae500895, mtpc_pong = 0x347773c5, mtpc_destroy_session_ok = 0xe22045fc, @@ -76,7 +76,6 @@ enum { mtpc_ping = 0x7abe77ec, mtpc_ping_delay_disconnect = 0xf3427b8c, mtpc_destroy_session = 0xe7512126, - mtpc_register_saveDeveloperInfo = 0x9a5f6e95, mtpc_boolFalse = 0xbc799737, mtpc_boolTrue = 0x997275b5, mtpc_true = 0x3fedd339, @@ -14750,57 +14749,6 @@ public: } }; -class MTPregister_saveDeveloperInfo { // RPC method 'register.saveDeveloperInfo' -public: - MTPstring vname; - MTPstring vemail; - MTPstring vphone_number; - MTPint vage; - MTPstring vcity; - - MTPregister_saveDeveloperInfo() { - } - MTPregister_saveDeveloperInfo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_register_saveDeveloperInfo) { - read(from, end, cons); - } - MTPregister_saveDeveloperInfo(const MTPstring &_name, const MTPstring &_email, const MTPstring &_phone_number, MTPint _age, const MTPstring &_city) : vname(_name), vemail(_email), vphone_number(_phone_number), vage(_age), vcity(_city) { - } - - uint32 innerLength() const { - return vname.innerLength() + vemail.innerLength() + vphone_number.innerLength() + vage.innerLength() + vcity.innerLength(); - } - mtpTypeId type() const { - return mtpc_register_saveDeveloperInfo; - } - void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_register_saveDeveloperInfo) { - vname.read(from, end); - vemail.read(from, end); - vphone_number.read(from, end); - vage.read(from, end); - vcity.read(from, end); - } - void write(mtpBuffer &to) const { - vname.write(to); - vemail.write(to); - vphone_number.write(to); - vage.write(to); - vcity.write(to); - } - - typedef MTPBool ResponseType; -}; -class MTPregister_SaveDeveloperInfo : public MTPBoxed { -public: - MTPregister_SaveDeveloperInfo() { - } - MTPregister_SaveDeveloperInfo(const MTPregister_saveDeveloperInfo &v) : MTPBoxed(v) { - } - MTPregister_SaveDeveloperInfo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { - } - MTPregister_SaveDeveloperInfo(const MTPstring &_name, const MTPstring &_email, const MTPstring &_phone_number, MTPint _age, const MTPstring &_city) : MTPBoxed(MTPregister_saveDeveloperInfo(_name, _email, _phone_number, _age, _city)) { - } -}; - template class MTPinvokeAfterMsg { // RPC method 'invokeAfterMsg' public: From b35c99cb0c6fa0747aafc74ab599ee91cb91a975 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 18 Jul 2016 18:39:10 +0300 Subject: [PATCH 26/60] When installing sticker set some sets can be archived. We show a box with them and describing what happend. Limit of the recent stickers count is taken from config now. --- Telegram/Resources/basic.style | 14 - Telegram/Resources/icons/stickers_add.png | Bin 0 -> 173 bytes Telegram/Resources/icons/stickers_add@2x.png | Bin 0 -> 120 bytes Telegram/Resources/langs/lang.strings | 3 +- Telegram/SourceFiles/boxes/boxes.style | 19 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 373 +++++++++++------- Telegram/SourceFiles/boxes/stickersetbox.h | 32 +- Telegram/SourceFiles/dropdown.cpp | 2 +- Telegram/SourceFiles/facades.cpp | 27 ++ Telegram/SourceFiles/facades.h | 3 + .../history/field_autocomplete.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 26 +- Telegram/SourceFiles/localstorage.cpp | 20 +- Telegram/SourceFiles/mainwidget.cpp | 2 +- Telegram/SourceFiles/mtproto/dcenter.cpp | 1 + Telegram/SourceFiles/mtproto/scheme.tl | 17 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 111 ++++-- Telegram/SourceFiles/mtproto/scheme_auto.h | 314 ++++++++++++++- Telegram/SourceFiles/structs.cpp | 4 +- 19 files changed, 730 insertions(+), 240 deletions(-) create mode 100644 Telegram/Resources/icons/stickers_add.png create mode 100644 Telegram/Resources/icons/stickers_add@2x.png diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 5b66706fe..94f8555f6 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1845,20 +1845,6 @@ stickersReorderFg: #777; stickersRowDisabledOpacity: 0.4; stickersRowDuration: 200; -stickersFeaturedHeight: 32px; -stickersFeaturedFont: contactsNameFont; -stickersFeaturedPosition: point(16px, 6px); -stickersFeaturedBadgeFont: semiboldFont; -stickersFeaturedBadgeSize: 21px; -stickersFeaturedPen: contactsNewItemFg; -stickersFeaturedUnreadBg: msgFileInBg; -stickersFeaturedUnreadSize: 5px; -stickersFeaturedUnreadSkip: 5px; -stickersFeaturedUnreadTop: 7px; -stickersFeaturedInstalled: icon { - { "mediaview_save_check", #40ace3 } -}; - emojiScroll: flatScroll(solidScroll) { deltat: 48px; } diff --git a/Telegram/Resources/icons/stickers_add.png b/Telegram/Resources/icons/stickers_add.png new file mode 100644 index 0000000000000000000000000000000000000000..ac6c2a1666abbeccdade0028480f8c5590ec26f9 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a)45>FS$5RHjbCwOxmHsE1BuIRm= z`NVho+4>6auTE9Cq2oNod*Bc*{d@24mf9=#=Dfhj*lrIGrFa5LXO6}phJGDzg=YM7o X<_U1o_;D#7Xg`CetDnm{r-UW|xgkR3 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stickers_add@2x.png b/Telegram/Resources/icons/stickers_add@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..de8948d663ddb4e7365afe1988d7aca89c556440 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj_MR?|As(G?rxXB~)z4*}Q$iB}($gji literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 35d1ebca2..bfbde65f1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -682,7 +682,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Add stickers"; "lng_stickers_share_pack" = "Share Stickers"; "lng_stickers_not_found" = "Sticker pack not found."; -"lng_stickers_too_many_packs" = "You have too many sticker packs. Please remove some first."; +"lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated."; +"lng_stickers_archived" = "Archived stickers"; "lng_stickers_copied" = "Sticker pack link copied to clipboard."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "Manage and reorder sticker packs"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 42c691542..54f43ec25 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -48,3 +48,22 @@ confirmInviteUserName: flatLabel(labelDefFlat) { maxHeight: 20px; } confirmInviteUserNameTop: 227px; + +stickersAddIcon: icon { + { "stickers_add", #ffffff }, +}; +stickersAddSize: size(30px, 24px); + +stickersFeaturedHeight: 32px; +stickersFeaturedFont: contactsNameFont; +stickersFeaturedPosition: point(16px, 6px); +stickersFeaturedBadgeFont: semiboldFont; +stickersFeaturedBadgeSize: 21px; +stickersFeaturedPen: contactsNewItemFg; +stickersFeaturedUnreadBg: msgFileInBg; +stickersFeaturedUnreadSize: 5px; +stickersFeaturedUnreadSkip: 5px; +stickersFeaturedUnreadTop: 7px; +stickersFeaturedInstalled: icon { + { "mediaview_save_check", #40ace3 } +}; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index a55c26bb7..6f863e538 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -29,6 +29,40 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "localstorage.h" #include "dialogs/dialogs_layout.h" +#include "styles/style_boxes.h" + +namespace Stickers { + +void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { + auto &v = d.vsets.c_vector().v; + auto &sets = Global::RefStickerSets(); + auto &order = Global::RefStickerSetsOrder(); + Stickers::Order archived; + archived.reserve(v.size()); + QMap setsToRequest; + for_const (auto &stickerSet, v) { + if (stickerSet.type() == mtpc_stickerSet) { + auto set = Stickers::feedSet(stickerSet.c_stickerSet()); + if (set->stickers.isEmpty()) { + setsToRequest.insert(set->id, set->access); + } + auto index = order.indexOf(set->id); + if (index >= 0) { + order.removeAt(index); + } + archived.push_back(set->id); + } + } + if (!setsToRequest.isEmpty()) { + for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { + App::api()->scheduleStickerSetRequest(i.key(), i.value()); + } + App::api()->requestStickerSets(); + } + Ui::showLayer(new StickersBox(archived), KeepOtherLayers); +} + +} // namespace Stickers StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() , _input(set) { @@ -117,10 +151,10 @@ bool StickerSetInner::failedSet(const RPCError &error) { return true; } -void StickerSetInner::installDone(const MTPBool &result) { +void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &result) { auto &sets = Global::RefStickerSets(); - _setFlags &= ~MTPDstickerSet::Flag::f_disabled; + _setFlags &= ~MTPDstickerSet::Flag::f_archived; _setFlags |= MTPDstickerSet::Flag::f_installed; auto it = sets.find(_setId); if (it == sets.cend()) { @@ -150,6 +184,11 @@ void StickerSetInner::installDone(const MTPBool &result) { sets.erase(custom); } } + + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + } + Local::writeStickers(); emit App::main()->stickersUpdated(); emit installed(_setId); @@ -158,11 +197,7 @@ void StickerSetInner::installDone(const MTPBool &result) { bool StickerSetInner::installFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; - if (error.type() == qstr("STICKERSETS_TOO_MUCH")) { - Ui::showLayer(new InformBox(lang(lng_stickers_too_many_packs)), KeepOtherLayers); - } else { - Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); - } + Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); return true; } @@ -265,7 +300,7 @@ bool StickerSetInner::loaded() const { int32 StickerSetInner::notInstalled() const { if (!_loaded) return 0; auto it = Global::StickerSets().constFind(_setId); - if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_disabled)) return _pack.size(); + if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_archived)) return _pack.size(); return 0; } @@ -425,6 +460,26 @@ StickersInner::StickersInner(StickersBox::Section section) : TWidget() , _addText(lang(lng_stickers_add).toUpper()) , _addWidth(st::defaultActiveButton.font->width(_addText)) , _aboveShadow(st::boxShadow) { + setup(); +} + +StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget() +, _section(StickersBox::Section::ArchivedPart) +, _archivedIds(archivedIds) +, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) +, _a_shifting(animation(this, &StickersInner::step_shifting)) +, _itemsTop(st::membersPadding.top()) +, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) +, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) +, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) +, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) +, _addText(lang(lng_stickers_add).toUpper()) +, _addWidth(st::defaultActiveButton.font->width(_addText)) +, _aboveShadow(st::boxShadow) { + setup(); +} + +void StickersInner::setup() { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); setMouseTracking(true); } @@ -520,26 +575,28 @@ void StickersInner::paintRow(Painter &p, int32 index) { p.fillRect(row, st::white); p.setOpacity(1); } - } else if (s->installed) { - int addw = _addWidth - st::defaultActiveButton.width; + } else if (s->installed && !s->disabled) { + int addw = st::stickersAddSize.width(); int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2); int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); } else { - int addw = _addWidth - st::defaultActiveButton.width; + int addw = st::stickersAddSize.width(); int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; - QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; + QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg; App::roundRect(p, add, textBg, ImageRoundRadius::Small); - p.setFont(st::defaultActiveButton.font); - p.setPen(st::defaultActiveButton.textFg); - int textTop = (_actionSel == index && _actionDown == index) ? st::defaultActiveButton.downTextTop : st::defaultActiveButton.textTop; - p.drawTextLeft(addx - st::defaultActiveButton.width / 2, addy + textTop, width(), _addText, _addWidth); + int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2; + int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2; + icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0; + st::stickersAddIcon.paint(p, QPoint(iconx, icony), width()); } - if (s->disabled) p.setOpacity(st::stickersRowDisabledOpacity); + if (s->disabled && _section == Section::Installed) { + p.setOpacity(st::stickersRowDisabledOpacity); + } if (s->sticker) { s->sticker->thumb->load(); QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh)); @@ -643,13 +700,13 @@ void StickersInner::onUpdateSelected() { int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth); QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; - } else if (_rows.at(selected)->installed) { + } else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) { actionSel = -1; } else { - int addw = _addWidth - st::defaultActiveButton.width; + int addw = st::stickersAddSize.width(); int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::defaultActiveButton.height) / 2; - QRect add(myrtlrect(addx, addy, addw, st::defaultActiveButton.height)); + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; + QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; } } else if (_featuredHeight && QRect(0, st::membersPadding.top(), width(), _featuredHeight).contains(local)) { @@ -661,7 +718,7 @@ void StickersInner::onUpdateSelected() { if ((_selected == -1) != (selected == -1)) { update(); } - if (_section == Section::Featured && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { + if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); } _selected = selected; @@ -711,7 +768,7 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = _pressed; _pressed = -2; - if (_section == Section::Featured && _selected < 0 && pressed >= 0) { + if (_section != Section::Installed && _selected < 0 && pressed >= 0) { setCursor(style::cur_default); } @@ -746,7 +803,7 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { if (_selected == -1) { _selected = -2; Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); - } else if (_selected >= 0 && _section == Section::Featured) { + } else if (_selected >= 0 && _section != Section::Installed) { auto &sets = Global::RefStickerSets(); auto it = sets.find(_rows.at(pressed)->id); if (it != sets.cend()) { @@ -774,9 +831,9 @@ void StickersInner::installSet(uint64 setId) { return; } - MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), RPCDoneHandlerPtr(), rpcFail(&StickersInner::installFail, setId)); + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersInner::installDone), rpcFail(&StickersInner::installFail, setId)); - it->flags &= ~(MTPDstickerSet::Flag::f_disabled | MTPDstickerSet_ClientFlag::f_unread); + it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread); it->flags |= MTPDstickerSet::Flag::f_installed; auto &order = Global::RefStickerSetsOrder(); @@ -802,6 +859,24 @@ void StickersInner::installSet(uint64 setId) { emit App::main()->stickersUpdated(); } +void StickersInner::installDone(const MTPmessages_StickerSetInstallResult &result) { + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + Local::writeStickers(); + emit App::main()->stickersUpdated(); + } + + // TEST DATA ONLY + //MTPVector v = MTP_vector(0); + //for (auto &set : Global::RefStickerSets()) { + // if (rand() < RAND_MAX / 2) { + // set.flags |= MTPDstickerSet::Flag::f_archived; + // v._vector().v.push_back(MTP_stickerSet(MTP_flags(set.flags), MTP_long(set.id), MTP_long(set.access), MTP_string(set.title), MTP_string(set.shortName), MTP_int(set.count), MTP_int(set.hash))); + // } + //} + //Stickers::applyArchivedResult(MTP_messages_stickerSetInstallResultArchive(v).c_messages_stickerSetInstallResultArchive()); +} + bool StickersInner::installFail(uint64 setId, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; @@ -823,11 +898,7 @@ bool StickersInner::installFail(uint64 setId, const RPCError &error) { Local::writeStickers(); emit App::main()->stickersUpdated(); - if (error.type() == qstr("STICKERSETS_TOO_MUCH")) { - Ui::showLayer(new InformBox(lang(lng_stickers_too_many_packs)), KeepOtherLayers); - } else { - Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); - } + Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); return true; } @@ -884,12 +955,12 @@ void StickersInner::clear() { _aboveShadowFadeStart = 0; _aboveShadowFadeOpacity = anim::fvalue(0, 0); _a_shifting.stop(); -_above = _dragging = _started = -1; -_selected = -2; -_pressed = -2; -_actionDown = -1; -setActionSel(-1); -update(); + _above = _dragging = _started = -1; + _selected = -2; + _pressed = -2; + _actionDown = -1; + setActionSel(-1); + update(); } void StickersInner::setActionSel(int32 actionSel) { @@ -922,50 +993,23 @@ void StickersInner::rebuild() { } clear(); - auto &order = (_section == Section::Installed) ? Global::StickerSetsOrder() : Global::FeaturedStickerSetsOrder(); - _animStartTimes.reserve(order.size()); + auto &order = ([this]() { + if (_section == Section::Installed) { + return Global::StickerSetsOrder(); + } else if (_section == Section::Featured) { + return Global::FeaturedStickerSetsOrder(); + } + return _archivedIds; + })(); + _rows.reserve(order.size() + 1); + _animStartTimes.reserve(order.size() + 1); auto &sets = Global::StickerSets(); - auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); - if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { - DocumentData *sticker = cloudIt->stickers.at(0); - int32 pixw = 0, pixh = 0; - if (sticker) { - pixw = sticker->thumb->width(); - pixh = sticker->thumb->height(); - if (pixw > st::contactsPhotoSize) { - if (pixw > pixh) { - pixh = (pixh * st::contactsPhotoSize) / pixw; - pixw = st::contactsPhotoSize; - } else { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } else if (pixh > st::contactsPhotoSize) { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } + if (_section == Section::Installed) { + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { + rebuildAppendSet(cloudIt.value(), namew); } - QString title = cloudIt->title; - int32 titleWidth = st::contactsNameFont->width(title); - if (titleWidth > namew) { - title = st::contactsNameFont->elided(title, namew); - } - int count = cloudIt->stickers.size(); - int added = 0; - auto customIt = sets.constFind(Stickers::CustomSetId); - if (customIt != sets.cend()) { - added = customIt->stickers.size(); - for_const (auto &sticker, cGetRecentStickers()) { - if (customIt->stickers.indexOf(sticker.first) < 0) { - ++added; - } - } - } else { - added = cGetRecentStickers().size(); - } - rows.push_back(new StickerSetRow(cloudIt->id, cloudIt->stickers.front(), count + added, title, true, true, false, false, true, pixw, pixh)); - _animStartTimes.push_back(0); } for_const (auto setId, order) { auto it = sets.constFind(setId); @@ -973,47 +1017,13 @@ void StickersInner::rebuild() { continue; } - bool recent = false; - bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); - bool disabled = (_section == Section::Installed) && (it->flags & MTPDstickerSet::Flag::f_disabled); - bool official = (it->flags & MTPDstickerSet::Flag::f_official); - bool unread = (_section == Section::Featured) && _unreadSets.contains(it->id); - if (!unread && _section == Section::Featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { - unread = true; - _unreadSets.insert(it->id); - } + rebuildAppendSet(it.value(), namew); - DocumentData *sticker = it->stickers.isEmpty() ? 0 : it->stickers.at(0); - int32 pixw = 0, pixh = 0; - if (sticker) { - pixw = sticker->thumb->width(); - pixh = sticker->thumb->height(); - if (pixw > st::contactsPhotoSize) { - if (pixw > pixh) { - pixh = (pixh * st::contactsPhotoSize) / pixw; - pixw = st::contactsPhotoSize; - } else { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } else if (pixh > st::contactsPhotoSize) { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } - QString title = it->title; - int32 titleWidth = st::contactsNameFont->width(title); - if (titleWidth > namew) { - title = st::contactsNameFont->elided(title, namew); - } - (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, installed, official, unread, disabled, recent, pixw, pixh)); - _animStartTimes.push_back(0); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { App::api()->scheduleStickerSetRequest(it->id, it->access); } } App::api()->requestStickerSets(); - _rows = rows + rowsDisabled; resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { @@ -1025,6 +1035,68 @@ void StickersInner::rebuild() { } } +void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { + bool recent = (set.id == Stickers::CloudRecentSetId); + bool installed = true; + bool official = true; + bool unread = false; + bool disabled = false; + if (!recent) { + installed = (set.flags & MTPDstickerSet::Flag::f_installed); + official = (set.flags & MTPDstickerSet::Flag::f_official); + disabled = (set.flags & MTPDstickerSet::Flag::f_archived); + if (_section == Section::Featured) { + unread = _unreadSets.contains(set.id); + if (!unread && (set.flags & MTPDstickerSet_ClientFlag::f_unread)) { + unread = true; + _unreadSets.insert(set.id); + } + } else if (_section == Section::Installed && disabled) { + return; + } + } + + auto sticker = set.stickers.at(0); + int32 pixw = 0, pixh = 0; + if (sticker) { + pixw = sticker->thumb->width(); + pixh = sticker->thumb->height(); + if (pixw > st::contactsPhotoSize) { + if (pixw > pixh) { + pixh = (pixh * st::contactsPhotoSize) / pixw; + pixw = st::contactsPhotoSize; + } else { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + } else if (pixh > st::contactsPhotoSize) { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + } + QString title = set.title; + int32 titleWidth = st::contactsNameFont->width(title); + if (titleWidth > maxNameWidth) { + title = st::contactsNameFont->elided(title, maxNameWidth); + } + int count = set.stickers.size(), added = 0; + if (recent) { + auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); + if (customIt != Global::StickerSets().cend()) { + added = customIt->stickers.size(); + for_const (auto &sticker, cGetRecentStickers()) { + if (customIt->stickers.indexOf(sticker.first) < 0) { + ++added; + } + } + } else { + added = cGetRecentStickers().size(); + } + } + _rows.push_back(new StickerSetRow(set.id, sticker, count + added, title, installed, official, unread, disabled, recent, pixw, pixh)); + _animStartTimes.push_back(0); +} + void StickersInner::readFeaturedDone(const MTPBool &result) { Local::writeStickers(); emit App::main()->stickersUpdated(); @@ -1045,8 +1117,8 @@ bool StickersInner::readFeaturedFail(const RPCError &error) { return true; } -QVector StickersInner::getOrder() const { - QVector result; +Stickers::Order StickersInner::getOrder() const { + Stickers::Order result; result.reserve(_rows.size()); for (int32 i = 0, l = _rows.size(); i < l; ++i) { if (_rows.at(i)->disabled) { @@ -1063,8 +1135,8 @@ QVector StickersInner::getOrder() const { return result; } -QVector StickersInner::getDisabledSets() const { - QVector result; +Stickers::Order StickersInner::getDisabledSets() const { + Stickers::Order result; result.reserve(_rows.size()); for (int32 i = 0, l = _rows.size(); i < l; ++i) { if (_rows.at(i)->disabled) { @@ -1087,15 +1159,24 @@ StickersInner::~StickersInner() { StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) , _section(section) , _inner(section) -, _reorderRequest(0) -, _topShadow(this, st::contactsAboutShadow) -, _scrollDelta(0) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) , _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) { + setup(); +} +StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll) +, _section(Section::ArchivedPart) +, _inner(archivedIds) +, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) +, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { + setup(); +} + +void StickersBox::setup() { int bottomSkip = st::boxPadding.bottom(); if (_section == Section::Installed) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow = new PlainShadow(this, st::contactsAboutShadow); _save = new BoxButton(this, lang(lng_settings_save), st::defaultBoxButton); connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); @@ -1105,6 +1186,12 @@ StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) _bottomShadow = new ScrollableBoxShadow(this); bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + } else if (_section == Section::ArchivedPart) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow = new PlainShadow(this, st::contactsAboutShadow); + + _save = new BoxButton(this, lang(lng_box_ok), st::defaultBoxButton); + connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); } ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); @@ -1130,7 +1217,7 @@ int32 StickersBox::countHeight() const { return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip; } -void StickersBox::disenableDone(const MTPBool & result, mtpRequestId req) { +void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) { _disenableRequests.remove(req); if (_disenableRequests.isEmpty()) { saveOrder(); @@ -1178,7 +1265,15 @@ void StickersBox::paintEvent(QPaintEvent *e) { Painter p(this); if (paint(p)) return; - paintTitle(p, lang(lng_stickers_packs)); + auto title = ([this]() { + if (_section == Section::Installed) { + return lang(lng_stickers_packs); + } else if (_section == Section::Featured) { + return lang(lng_stickers_featured); + } + return lang(lng_stickers_archived); + })(); + paintTitle(p, title); p.translate(0, st::boxTitleHeight); if (_aboutHeight > 0) { @@ -1207,10 +1302,14 @@ void StickersBox::closePressed() { void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); _inner->resize(width(), _inner->height()); - _topShadow.setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + if (_topShadow) { + _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); + } if (_save) { _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); + } + if (_cancel) { _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } @@ -1255,7 +1354,7 @@ void StickersBox::onSave() { auto &recent = cGetRecentStickers(); auto &sets = Global::RefStickerSets(); - QVector reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); + auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); for (int32 i = 0, l = disabled.size(); i < l; ++i) { auto it = sets.find(disabled.at(i)); if (it != sets.cend()) { @@ -1267,11 +1366,11 @@ void StickersBox::onSave() { ++i; } } - if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) { + if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); if (it->flags & MTPDstickerSet::Flag::f_official) { _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); - it->flags |= MTPDstickerSet::Flag::f_disabled; + it->flags |= MTPDstickerSet::Flag::f_archived; } else { _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); int removeIndex = Global::StickerSetsOrder().indexOf(it->id); @@ -1279,7 +1378,7 @@ void StickersBox::onSave() { if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { sets.erase(it); } else { - it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_disabled); + it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); } } } @@ -1296,10 +1395,10 @@ void StickersBox::onSave() { for (int i = 0, l = reorder.size(); i < l; ++i) { auto it = sets.find(reorder.at(i)); if (it != sets.cend()) { - if ((it->flags & MTPDstickerSet::Flag::f_disabled) && !disabled.contains(it->id)) { + if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); - it->flags &= ~MTPDstickerSet::Flag::f_disabled; + it->flags &= ~MTPDstickerSet::Flag::f_archived; } order.push_back(reorder.at(i)); it->flags |= MTPDstickerSet::Flag::f_installed; @@ -1327,9 +1426,13 @@ void StickersBox::onSave() { } void StickersBox::hideAll() { - _topShadow.hide(); + if (_topShadow) { + _topShadow->hide(); + } if (_save) { _save->hide(); + } + if (_cancel) { _cancel->hide(); _bottomShadow->hide(); } @@ -1337,9 +1440,13 @@ void StickersBox::hideAll() { } void StickersBox::showAll() { - _topShadow.show(); + if (_topShadow) { + _topShadow->show(); + } if (_save) { _save->show(); + } + if (_cancel) { _cancel->show(); _bottomShadow->show(); } @@ -1353,7 +1460,7 @@ int32 stickerPacksCount(bool includeDisabledOfficial) { for (int i = 0, l = order.size(); i < l; ++i) { auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { - if (!(it->flags & MTPDstickerSet::Flag::f_disabled) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { + if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { ++result; } } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 70f65e3c3..44d60e770 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -64,7 +64,7 @@ private: void gotSet(const MTPmessages_StickerSet &set); bool failedSet(const RPCError &error); - void installDone(const MTPBool &result); + void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(const RPCError &error); StickerPack _pack; @@ -130,19 +130,21 @@ class StickersBox : public ItemListBox, public RPCSender { Q_OBJECT public: - enum class Section { Installed, Featured, + Archived, + ArchivedPart, }; StickersBox(Section section = Section::Installed); + StickersBox(const Stickers::Order &archivedIds); + void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); void closePressed(); public slots: - void onStickersUpdated(); void onCheckDraggingScroll(int localY); @@ -152,15 +154,14 @@ public slots: void onSave(); protected: - void hideAll(); void showAll(); private: - + void setup(); int32 countHeight() const; - void disenableDone(const MTPBool &result, mtpRequestId req); + void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); bool disenableFail(const RPCError &error, mtpRequestId req); void reorderDone(const MTPBool &result); bool reorderFail(const RPCError &result); @@ -172,12 +173,12 @@ private: ChildWidget _save = { nullptr }; ChildWidget _cancel = { nullptr }; QMap _disenableRequests; - mtpRequestId _reorderRequest; - PlainShadow _topShadow; + mtpRequestId _reorderRequest = 0; + ChildWidget _topShadow = { nullptr }; ChildWidget _bottomShadow = { nullptr }; QTimer _scrollTimer; - int32 _scrollDelta; + int32 _scrollDelta = 0; int _aboutWidth = 0; Text _about; @@ -195,6 +196,7 @@ class StickersInner : public TWidget, public RPCSender { public: using Section = StickersBox::Section; StickersInner(Section section); + StickersInner(const Stickers::Order &archivedIds); void rebuild(); bool savingStart() { @@ -203,8 +205,8 @@ public: return true; } - QVector getOrder() const; - QVector getDisabledSets() const; + Stickers::Order getOrder() const; + Stickers::Order getDisabledSets() const; void setVisibleScrollbar(int32 width); @@ -227,6 +229,7 @@ public slots: void onClearBoxDestroyed(QObject *box); private: + void setup(); void paintFeaturedButton(Painter &p) const; void step_shifting(uint64 ms, bool timer); @@ -236,11 +239,13 @@ private: float64 aboveShadowOpacity() const; void installSet(uint64 setId); + void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(uint64 setId, const RPCError &error); void readFeaturedDone(const MTPBool &result); bool readFeaturedFail(const RPCError &error); Section _section; + Stickers::Order _archivedIds; int32 _rowHeight; struct StickerSetRow { @@ -265,7 +270,10 @@ private: int32 pixw, pixh; anim::ivalue yadd; }; - typedef QList StickerSetRows; + using StickerSetRows = QList; + + void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); + StickerSetRows _rows; QList _animStartTimes; uint64 _aboveShadowFadeStart = 0; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 1c63f49ba..6d86f12d4 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -2088,7 +2088,7 @@ bool StickerPanInner::ui_isInlineItemBeingChosen() { void StickerPanInner::appendSet(uint64 setId) { auto &sets = Global::StickerSets(); auto it = sets.constFind(setId); - if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_disabled) || it->stickers.isEmpty()) return; + if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_archived) || it->stickers.isEmpty()) return; StickerPack pack; pack.reserve(it->stickers.size()); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index d4e7318c0..80c433527 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -520,6 +520,31 @@ DefineVar(Sandbox, ConnectionProxy, PreLaunchProxy); } // namespace Sandbox +namespace Stickers { + +Set *feedSet(const MTPDstickerSet &set) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(set.vid.v); + auto title = stickerSetTitle(set); + if (it == sets.cend()) { + it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_not_loaded)); + } else { + it->access = set.vaccess_hash.v; + it->title = title; + it->shortName = qs(set.vshort_name); + auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); + it->flags = set.vflags.v | clientFlags; + if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { + it->count = set.vcount.v; + it->hash = set.vhash.v; + it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set + } + } + return &it.value(); +} + +} // namespace Stickers + namespace Global { namespace internal { @@ -556,6 +581,7 @@ struct Data { int32 PushChatLimit = 2; int32 SavedGifsLimit = 200; int32 EditTimeLimit = 172800; + int32 StickersRecentLimit = 30; HiddenPinnedMessagesMap HiddenPinnedMessages; @@ -628,6 +654,7 @@ DefineVar(Global, int32, PushChatPeriod); DefineVar(Global, int32, PushChatLimit); DefineVar(Global, int32, SavedGifsLimit); DefineVar(Global, int32, EditTimeLimit); +DefineVar(Global, int32, StickersRecentLimit); DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index d0d09ff29..d99b54bb8 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -214,6 +214,8 @@ inline MTPInputStickerSet inputSetId(const Set &set) { return MTP_inputStickerSetShortName(MTP_string(set.shortName)); } +Set *feedSet(const MTPDstickerSet &set); + } // namespace Stickers namespace Global { @@ -254,6 +256,7 @@ DeclareVar(int32, PushChatPeriod); DeclareVar(int32, PushChatLimit); DeclareVar(int32, SavedGifsLimit); DeclareVar(int32, EditTimeLimit); +DeclareVar(int32, StickersRecentLimit); typedef QMap HiddenPinnedMessagesMap; DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages); diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index 49cb16fde..a2aebf4a2 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -161,7 +161,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { if (it->emoji.isEmpty()) { setsToRequest.insert(it->id, it->access); it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; - } else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) { + } else if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji)); if (i != it->emoji.cend()) { srows += *i; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 08523d689..0e2bcfacb 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3667,27 +3667,11 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } for_const (auto &setData, d_sets) { if (setData.type() == mtpc_stickerSet) { - const auto &set(setData.c_stickerSet()); - auto it = sets.find(set.vid.v); - QString title = stickerSetTitle(set); - if (it == sets.cend()) { - it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_not_loaded)); - } else { - it->access = set.vaccess_hash.v; - it->title = title; - it->shortName = qs(set.vshort_name); - auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); - it->flags = set.vflags.v | clientFlags; - if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { - it->count = set.vcount.v; - it->hash = set.vhash.v; - it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set - } - } - if (!(it->flags & MTPDstickerSet::Flag::f_disabled) || (it->flags & MTPDstickerSet::Flag::f_official)) { - setsOrder.push_back(set.vid.v); - if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - setsToRequest.insert(set.vid.v, set.vaccess_hash.v); + auto set = Stickers::feedSet(setData.c_stickerSet()); + if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) { + setsOrder.push_back(set->id); + if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + setsToRequest.insert(set->id, set->access); } } } diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 68a443ca0..6ebdbea10 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -541,6 +541,7 @@ namespace { dbiHiddenPinnedMessages = 0x39, dbiDialogsMode = 0x40, dbiModerateMode = 0x41, + dbiStickersRecentLimit = 0x43, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -834,6 +835,14 @@ namespace { Global::SetSavedGifsLimit(limit); } break; + case dbiStickersRecentLimit: { + qint32 limit; + stream >> limit; + if (!_checkStreamStatus(stream)) return false; + + Global::SetStickersRecentLimit(limit); + } break; + case dbiMegagroupSizeMax: { qint32 maxSize; stream >> maxSize; @@ -2175,12 +2184,13 @@ namespace Local { size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password); } - size += sizeof(quint32) + sizeof(qint32) * 6; + size += sizeof(quint32) + sizeof(qint32) * 7; EncryptedDescriptor data(size); data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax()); data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax()); data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit()); + data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit()); data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); @@ -3053,7 +3063,7 @@ namespace Local { for_const (auto &set, sets) { bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); if (notLoaded && !(set.flags & MTPDstickerSet_ClientFlag::f_special)) { - if (!(set.flags & MTPDstickerSet::Flag::f_disabled) + if (!(set.flags & MTPDstickerSet::Flag::f_archived) || (set.flags & MTPDstickerSet::Flag::f_official) || (set.flags & MTPDstickerSet_ClientFlag::f_featured)) { // waiting to receive return; @@ -3155,7 +3165,9 @@ namespace Local { custom.stickers.push_back(doc); ++custom.count; } - if (recent.size() < StickerPanPerRow * StickerPanRowsPerPage && qAbs(value) > 1) recent.push_back(qMakePair(doc, qAbs(value))); + if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) { + recent.push_back(qMakePair(doc, qAbs(value))); + } } if (def.stickers.isEmpty()) { sets.remove(Stickers::DefaultSetId); @@ -3324,7 +3336,7 @@ namespace Local { } else if (j->flags & MTPDstickerSet::Flag::f_official) { foundOfficial = true; } - if (!(j->flags & MTPDstickerSet::Flag::f_disabled)) { + if (!(j->flags & MTPDstickerSet::Flag::f_archived)) { acc = (acc * 20261) + j->hash; } } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3391afaa2..9c5c00f3d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3705,7 +3705,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) { break; } } - while (!recent.isEmpty() && it->stickers.size() + recent.size() > StickerPanPerRow * StickerPanRowsPerPage) { + while (!recent.isEmpty() && it->stickers.size() + recent.size() > Global::StickersRecentLimit()) { writeRecent = true; recent.pop_back(); } diff --git a/Telegram/SourceFiles/mtproto/dcenter.cpp b/Telegram/SourceFiles/mtproto/dcenter.cpp index 7043e26b9..f15fd2fc4 100644 --- a/Telegram/SourceFiles/mtproto/dcenter.cpp +++ b/Telegram/SourceFiles/mtproto/dcenter.cpp @@ -170,6 +170,7 @@ namespace { Global::SetPushChatLimit(data.vpush_chat_limit.v); // ? Global::SetSavedGifsLimit(data.vsaved_gifs_limit.v); Global::SetEditTimeLimit(data.vedit_time_limit.v); // ? + Global::SetStickersRecentLimit(data.vstickers_recent_limit.v); configLoadedOnce = true; Local::writeSettings(); diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 6f98b5891..168945f8d 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -418,7 +418,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true id:int ip_address:string port:int = DcOption; -config#c9411388 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int disabled_features:Vector = Config; +config#f401a4bf date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int disabled_features:Vector = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -555,7 +555,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; -stickerSet#cd303b41 flags:# installed:flags.0?true disabled:flags.1?true official:flags.2?true id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet; +stickerSet#cd303b41 flags:# installed:flags.0?true archived:flags.1?true official:flags.2?true id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -672,7 +672,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; -messages.botCallbackAnswer#31fde6e4 flags:# alert:flags.1?true allow_pip:flags.2?true message:flags.0?string url:flags.3?string = messages.BotCallbackAnswer; +messages.botCallbackAnswer#31fde6e4 flags:# alert:flags.1?true message:flags.0?string url:flags.3?string = messages.BotCallbackAnswer; messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; @@ -704,6 +704,11 @@ messages.featuredStickers#ed6392b7 hash:int sets:Vector unread:Vecto messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#5ce20970 hash:int stickers:Vector = messages.RecentStickers; +messages.archivedStickers#f3475c0c count:int sets:Vector = messages.ArchivedStickers; + +messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallResult; +messages.stickerSetInstallResultArchive#192c8a4e sets:Vector = messages.StickerSetInstallResult; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -814,7 +819,7 @@ messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; -messages.installStickerSet#7b30c3a6 stickerset:InputStickerSet disabled:Bool = Bool; +messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; @@ -834,15 +839,17 @@ messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEdi messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#a6e94f04 peer:InputPeer msg_id:int data:bytes = messages.BotCallbackAnswer; -messages.setBotCallbackAnswer#70dc0fa3 flags:# alert:flags.1?true allow_pip:flags.2?true query_id:long message:flags.0?string url:flags.3?string = Bool; +messages.setBotCallbackAnswer#70dc0fa3 flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.3?string = Bool; messages.getPeerDialogs#2d9776b9 peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; messages.readFeaturedStickers#e21cbb = Bool; messages.getRecentStickers#99197c2c hash:int = messages.RecentStickers; +messages.saveRecentSticker#348e39bf id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#ab02e5d2 = Bool; messages.getUnusedStickers#a978d356 limit:int = Vector; +messages.getArchivedStickers#906e241f offset_id:long limit:int = messages.ArchivedStickers; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 61c72b695..65e157812 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -3329,7 +3329,8 @@ void _serialize_config(MTPStringLogger &to, int32 stage, int32 lev, Types &types case 17: to.add(" saved_gifs_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 18: to.add(" edit_time_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 19: to.add(" rating_e_decay: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 20: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 20: to.add(" stickers_recent_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 21: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -4399,7 +4400,7 @@ void _serialize_stickerSet(MTPStringLogger &to, int32 stage, int32 lev, Types &t switch (stage) { case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" installed: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_installed) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 2: to.add(" disabled: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_disabled) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 2: to.add(" archived: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_archived) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; case 3: to.add(" official: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_official) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; case 4: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 5: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; @@ -5599,9 +5600,8 @@ void _serialize_messages_botCallbackAnswer(MTPStringLogger &to, int32 stage, int switch (stage) { case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; - case 2: to.add(" allow_pip: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_allow_pip) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 3: to.add(" message: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 4: to.add(" url: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 2: to.add(" message: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 3: to.add(" url: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -5797,6 +5797,37 @@ void _serialize_messages_recentStickers(MTPStringLogger &to, int32 stage, int32 } } +void _serialize_messages_archivedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_archivedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" count: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" sets: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_stickerSetInstallResultSuccess(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ messages_stickerSetInstallResultSuccess }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_messages_stickerSetInstallResultArchive(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_stickerSetInstallResultArchive"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" sets: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6264,20 +6295,6 @@ void _serialize_messages_readEncryptedHistory(MTPStringLogger &to, int32 stage, } } -void _serialize_messages_installStickerSet(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ messages_installStickerSet"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" stickerset: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" disabled: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_messages_uninstallStickerSet(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6387,10 +6404,9 @@ void _serialize_messages_setBotCallbackAnswer(MTPStringLogger &to, int32 stage, switch (stage) { case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; - case 2: to.add(" allow_pip: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_allow_pip) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 3: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" message: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 5: to.add(" url: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 2: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" message: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" url: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6419,6 +6435,20 @@ void _serialize_messages_readFeaturedStickers(MTPStringLogger &to, int32 stage, to.add("{ messages_readFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_messages_saveRecentSticker(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_saveRecentSticker"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" unsave: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_messages_clearRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ messages_clearRecentStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -7985,6 +8015,20 @@ void _serialize_messages_getStickerSet(MTPStringLogger &to, int32 stage, int32 l } } +void _serialize_messages_installStickerSet(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_installStickerSet"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" stickerset: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" archived: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_messages_getDocumentByHash(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8128,6 +8172,20 @@ void _serialize_messages_getUnusedStickers(MTPStringLogger &to, int32 stage, int } } +void _serialize_messages_getArchivedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getArchivedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" offset_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -8804,6 +8862,9 @@ namespace { _serializers.insert(mtpc_messages_featuredStickers, _serialize_messages_featuredStickers); _serializers.insert(mtpc_messages_recentStickersNotModified, _serialize_messages_recentStickersNotModified); _serializers.insert(mtpc_messages_recentStickers, _serialize_messages_recentStickers); + _serializers.insert(mtpc_messages_archivedStickers, _serialize_messages_archivedStickers); + _serializers.insert(mtpc_messages_stickerSetInstallResultSuccess, _serialize_messages_stickerSetInstallResultSuccess); + _serializers.insert(mtpc_messages_stickerSetInstallResultArchive, _serialize_messages_stickerSetInstallResultArchive); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -8841,7 +8902,6 @@ namespace { _serializers.insert(mtpc_messages_discardEncryption, _serialize_messages_discardEncryption); _serializers.insert(mtpc_messages_setEncryptedTyping, _serialize_messages_setEncryptedTyping); _serializers.insert(mtpc_messages_readEncryptedHistory, _serialize_messages_readEncryptedHistory); - _serializers.insert(mtpc_messages_installStickerSet, _serialize_messages_installStickerSet); _serializers.insert(mtpc_messages_uninstallStickerSet, _serialize_messages_uninstallStickerSet); _serializers.insert(mtpc_messages_editChatAdmin, _serialize_messages_editChatAdmin); _serializers.insert(mtpc_messages_reorderStickerSets, _serialize_messages_reorderStickerSets); @@ -8851,6 +8911,7 @@ namespace { _serializers.insert(mtpc_messages_setBotCallbackAnswer, _serialize_messages_setBotCallbackAnswer); _serializers.insert(mtpc_messages_saveDraft, _serialize_messages_saveDraft); _serializers.insert(mtpc_messages_readFeaturedStickers, _serialize_messages_readFeaturedStickers); + _serializers.insert(mtpc_messages_saveRecentSticker, _serialize_messages_saveRecentSticker); _serializers.insert(mtpc_messages_clearRecentStickers, _serialize_messages_clearRecentStickers); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); @@ -8963,6 +9024,7 @@ namespace { _serializers.insert(mtpc_channels_exportInvite, _serialize_channels_exportInvite); _serializers.insert(mtpc_messages_checkChatInvite, _serialize_messages_checkChatInvite); _serializers.insert(mtpc_messages_getStickerSet, _serialize_messages_getStickerSet); + _serializers.insert(mtpc_messages_installStickerSet, _serialize_messages_installStickerSet); _serializers.insert(mtpc_messages_getDocumentByHash, _serialize_messages_getDocumentByHash); _serializers.insert(mtpc_messages_searchGifs, _serialize_messages_searchGifs); _serializers.insert(mtpc_messages_getSavedGifs, _serialize_messages_getSavedGifs); @@ -8973,6 +9035,7 @@ namespace { _serializers.insert(mtpc_messages_getFeaturedStickers, _serialize_messages_getFeaturedStickers); _serializers.insert(mtpc_messages_getRecentStickers, _serialize_messages_getRecentStickers); _serializers.insert(mtpc_messages_getUnusedStickers, _serialize_messages_getUnusedStickers); + _serializers.insert(mtpc_messages_getArchivedStickers, _serialize_messages_getArchivedStickers); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index ce9c71234..3ad9425eb 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -306,7 +306,7 @@ enum { mtpc_photos_photo = 0x20212ca8, mtpc_upload_file = 0x96a18d5, mtpc_dcOption = 0x5d8c6cc, - mtpc_config = 0xc9411388, + mtpc_config = 0xf401a4bf, mtpc_nearestDc = 0x8e1a1775, mtpc_help_appUpdate = 0x8987f311, mtpc_help_noAppUpdate = 0xc45a6536, @@ -507,6 +507,9 @@ enum { mtpc_messages_featuredStickers = 0xed6392b7, mtpc_messages_recentStickersNotModified = 0xb17f890, mtpc_messages_recentStickers = 0x5ce20970, + mtpc_messages_archivedStickers = 0xf3475c0c, + mtpc_messages_stickerSetInstallResultSuccess = 0x38641628, + mtpc_messages_stickerSetInstallResultArchive = 0x192c8a4e, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -610,7 +613,7 @@ enum { mtpc_messages_checkChatInvite = 0x3eadb1bb, mtpc_messages_importChatInvite = 0x6c50051c, mtpc_messages_getStickerSet = 0x2619a90e, - mtpc_messages_installStickerSet = 0x7b30c3a6, + mtpc_messages_installStickerSet = 0xc78fe460, mtpc_messages_uninstallStickerSet = 0xf96e55de, mtpc_messages_startBot = 0xe6df7378, mtpc_messages_getMessagesViews = 0xc4c8a55d, @@ -637,8 +640,10 @@ enum { mtpc_messages_getFeaturedStickers = 0x2dacca4f, mtpc_messages_readFeaturedStickers = 0xe21cbb, mtpc_messages_getRecentStickers = 0x99197c2c, + mtpc_messages_saveRecentSticker = 0x348e39bf, mtpc_messages_clearRecentStickers = 0xab02e5d2, mtpc_messages_getUnusedStickers = 0xa978d356, + mtpc_messages_getArchivedStickers = 0x906e241f, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -1370,6 +1375,12 @@ class MTPDmessages_featuredStickers; class MTPmessages_recentStickers; class MTPDmessages_recentStickers; +class MTPmessages_archivedStickers; +class MTPDmessages_archivedStickers; + +class MTPmessages_stickerSetInstallResult; +class MTPDmessages_stickerSetInstallResultArchive; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1546,6 +1557,8 @@ typedef MTPBoxed MTPcontacts_TopPeers; typedef MTPBoxed MTPDraftMessage; typedef MTPBoxed MTPmessages_FeaturedStickers; typedef MTPBoxed MTPmessages_RecentStickers; +typedef MTPBoxed MTPmessages_ArchivedStickers; +typedef MTPBoxed MTPmessages_StickerSetInstallResult; // Type classes definitions @@ -9625,6 +9638,74 @@ private: }; typedef MTPBoxed MTPmessages_RecentStickers; +class MTPmessages_archivedStickers : private mtpDataOwner { +public: + MTPmessages_archivedStickers(); + MTPmessages_archivedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_archivedStickers) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDmessages_archivedStickers &_messages_archivedStickers() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDmessages_archivedStickers*)data; + } + const MTPDmessages_archivedStickers &c_messages_archivedStickers() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDmessages_archivedStickers*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_archivedStickers); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_archivedStickers(MTPDmessages_archivedStickers *_data); + + friend class MTP::internal::TypeCreator; +}; +typedef MTPBoxed MTPmessages_ArchivedStickers; + +class MTPmessages_stickerSetInstallResult : private mtpDataOwner { +public: + MTPmessages_stickerSetInstallResult() : mtpDataOwner(0), _type(0) { + } + MTPmessages_stickerSetInstallResult(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDmessages_stickerSetInstallResultArchive &_messages_stickerSetInstallResultArchive() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_stickerSetInstallResultArchive) throw mtpErrorWrongTypeId(_type, mtpc_messages_stickerSetInstallResultArchive); + split(); + return *(MTPDmessages_stickerSetInstallResultArchive*)data; + } + const MTPDmessages_stickerSetInstallResultArchive &c_messages_stickerSetInstallResultArchive() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_stickerSetInstallResultArchive) throw mtpErrorWrongTypeId(_type, mtpc_messages_stickerSetInstallResultArchive); + return *(const MTPDmessages_stickerSetInstallResultArchive*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_stickerSetInstallResult(mtpTypeId type); + explicit MTPmessages_stickerSetInstallResult(MTPDmessages_stickerSetInstallResultArchive *_data); + + friend class MTP::internal::TypeCreator; + + mtpTypeId _type; +}; +typedef MTPBoxed MTPmessages_StickerSetInstallResult; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -12362,7 +12443,7 @@ class MTPDconfig : public mtpDataImpl { public: MTPDconfig() { } - MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vedit_time_limit(_edit_time_limit), vrating_e_decay(_rating_e_decay), vdisabled_features(_disabled_features) { + MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, MTPint _stickers_recent_limit, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vedit_time_limit(_edit_time_limit), vrating_e_decay(_rating_e_decay), vstickers_recent_limit(_stickers_recent_limit), vdisabled_features(_disabled_features) { } MTPint vdate; @@ -12385,6 +12466,7 @@ public: MTPint vsaved_gifs_limit; MTPint vedit_time_limit; MTPint vrating_e_decay; + MTPint vstickers_recent_limit; MTPVector vdisabled_features; }; @@ -13195,7 +13277,7 @@ class MTPDstickerSet : public mtpDataImpl { public: enum class Flag : int32 { f_installed = (1 << 0), - f_disabled = (1 << 1), + f_archived = (1 << 1), f_official = (1 << 2), MAX_FIELD = (1 << 2), @@ -13204,7 +13286,7 @@ public: friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } bool is_installed() const { return vflags.v & Flag::f_installed; } - bool is_disabled() const { return vflags.v & Flag::f_disabled; } + bool is_archived() const { return vflags.v & Flag::f_archived; } bool is_official() const { return vflags.v & Flag::f_official; } MTPDstickerSet() { @@ -14374,7 +14456,6 @@ class MTPDmessages_botCallbackAnswer : public mtpDataImpl(v)); } bool is_alert() const { return vflags.v & Flag::f_alert; } - bool is_allow_pip() const { return vflags.v & Flag::f_allow_pip; } bool has_message() const { return vflags.v & Flag::f_message; } bool has_url() const { return vflags.v & Flag::f_url; } @@ -14540,6 +14620,27 @@ public: MTPVector vstickers; }; +class MTPDmessages_archivedStickers : public mtpDataImpl { +public: + MTPDmessages_archivedStickers() { + } + MTPDmessages_archivedStickers(MTPint _count, const MTPVector &_sets) : vcount(_count), vsets(_sets) { + } + + MTPint vcount; + MTPVector vsets; +}; + +class MTPDmessages_stickerSetInstallResultArchive : public mtpDataImpl { +public: + MTPDmessages_stickerSetInstallResultArchive() { + } + MTPDmessages_stickerSetInstallResultArchive(const MTPVector &_sets) : vsets(_sets) { + } + + MTPVector vsets; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -19336,32 +19437,32 @@ public: class MTPmessages_installStickerSet { // RPC method 'messages.installStickerSet' public: MTPInputStickerSet vstickerset; - MTPBool vdisabled; + MTPBool varchived; MTPmessages_installStickerSet() { } MTPmessages_installStickerSet(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_installStickerSet) { read(from, end, cons); } - MTPmessages_installStickerSet(const MTPInputStickerSet &_stickerset, MTPBool _disabled) : vstickerset(_stickerset), vdisabled(_disabled) { + MTPmessages_installStickerSet(const MTPInputStickerSet &_stickerset, MTPBool _archived) : vstickerset(_stickerset), varchived(_archived) { } uint32 innerLength() const { - return vstickerset.innerLength() + vdisabled.innerLength(); + return vstickerset.innerLength() + varchived.innerLength(); } mtpTypeId type() const { return mtpc_messages_installStickerSet; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_installStickerSet) { vstickerset.read(from, end); - vdisabled.read(from, end); + varchived.read(from, end); } void write(mtpBuffer &to) const { vstickerset.write(to); - vdisabled.write(to); + varchived.write(to); } - typedef MTPBool ResponseType; + typedef MTPmessages_StickerSetInstallResult ResponseType; }; class MTPmessages_InstallStickerSet : public MTPBoxed { public: @@ -19371,7 +19472,7 @@ public: } MTPmessages_InstallStickerSet(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_InstallStickerSet(const MTPInputStickerSet &_stickerset, MTPBool _disabled) : MTPBoxed(MTPmessages_installStickerSet(_stickerset, _disabled)) { + MTPmessages_InstallStickerSet(const MTPInputStickerSet &_stickerset, MTPBool _archived) : MTPBoxed(MTPmessages_installStickerSet(_stickerset, _archived)) { } }; @@ -20332,7 +20433,6 @@ class MTPmessages_setBotCallbackAnswer { // RPC method 'messages.setBotCallbackA public: enum class Flag : int32 { f_alert = (1 << 1), - f_allow_pip = (1 << 2), f_message = (1 << 0), f_url = (1 << 3), @@ -20342,7 +20442,6 @@ public: friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } bool is_alert() const { return vflags.v & Flag::f_alert; } - bool is_allow_pip() const { return vflags.v & Flag::f_allow_pip; } bool has_message() const { return vflags.v & Flag::f_message; } bool has_url() const { return vflags.v & Flag::f_url; } @@ -20640,6 +20739,48 @@ public: } }; +class MTPmessages_saveRecentSticker { // RPC method 'messages.saveRecentSticker' +public: + MTPInputDocument vid; + MTPBool vunsave; + + MTPmessages_saveRecentSticker() { + } + MTPmessages_saveRecentSticker(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveRecentSticker) { + read(from, end, cons); + } + MTPmessages_saveRecentSticker(const MTPInputDocument &_id, MTPBool _unsave) : vid(_id), vunsave(_unsave) { + } + + uint32 innerLength() const { + return vid.innerLength() + vunsave.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_saveRecentSticker; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveRecentSticker) { + vid.read(from, end); + vunsave.read(from, end); + } + void write(mtpBuffer &to) const { + vid.write(to); + vunsave.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_SaveRecentSticker : public MTPBoxed { +public: + MTPmessages_SaveRecentSticker() { + } + MTPmessages_SaveRecentSticker(const MTPmessages_saveRecentSticker &v) : MTPBoxed(v) { + } + MTPmessages_SaveRecentSticker(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SaveRecentSticker(const MTPInputDocument &_id, MTPBool _unsave) : MTPBoxed(MTPmessages_saveRecentSticker(_id, _unsave)) { + } +}; + class MTPmessages_clearRecentStickers { // RPC method 'messages.clearRecentStickers' public: MTPmessages_clearRecentStickers() { @@ -20710,6 +20851,48 @@ public: } }; +class MTPmessages_getArchivedStickers { // RPC method 'messages.getArchivedStickers' +public: + MTPlong voffset_id; + MTPint vlimit; + + MTPmessages_getArchivedStickers() { + } + MTPmessages_getArchivedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getArchivedStickers) { + read(from, end, cons); + } + MTPmessages_getArchivedStickers(const MTPlong &_offset_id, MTPint _limit) : voffset_id(_offset_id), vlimit(_limit) { + } + + uint32 innerLength() const { + return voffset_id.innerLength() + vlimit.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getArchivedStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getArchivedStickers) { + voffset_id.read(from, end); + vlimit.read(from, end); + } + void write(mtpBuffer &to) const { + voffset_id.write(to); + vlimit.write(to); + } + + typedef MTPmessages_ArchivedStickers ResponseType; +}; +class MTPmessages_GetArchivedStickers : public MTPBoxed { +public: + MTPmessages_GetArchivedStickers() { + } + MTPmessages_GetArchivedStickers(const MTPmessages_getArchivedStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetArchivedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetArchivedStickers(const MTPlong &_offset_id, MTPint _limit) : MTPBoxed(MTPmessages_getArchivedStickers(_offset_id, _limit)) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -23305,8 +23488,8 @@ public: inline static MTPdcOption new_dcOption(const MTPflags &_flags, MTPint _id, const MTPstring &_ip_address, MTPint _port) { return MTPdcOption(new MTPDdcOption(_flags, _id, _ip_address, _port)); } - inline static MTPconfig new_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector &_disabled_features) { - return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _disabled_features)); + inline static MTPconfig new_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, MTPint _stickers_recent_limit, const MTPVector &_disabled_features) { + return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _stickers_recent_limit, _disabled_features)); } inline static MTPnearestDc new_nearestDc(const MTPstring &_country, MTPint _this_dc, MTPint _nearest_dc) { return MTPnearestDc(new MTPDnearestDc(_country, _this_dc, _nearest_dc)); @@ -23908,6 +24091,15 @@ public: inline static MTPmessages_recentStickers new_messages_recentStickers(MTPint _hash, const MTPVector &_stickers) { return MTPmessages_recentStickers(new MTPDmessages_recentStickers(_hash, _stickers)); } + inline static MTPmessages_archivedStickers new_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { + return MTPmessages_archivedStickers(new MTPDmessages_archivedStickers(_count, _sets)); + } + inline static MTPmessages_stickerSetInstallResult new_messages_stickerSetInstallResultSuccess() { + return MTPmessages_stickerSetInstallResult(mtpc_messages_stickerSetInstallResultSuccess); + } + inline static MTPmessages_stickerSetInstallResult new_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { + return MTPmessages_stickerSetInstallResult(new MTPDmessages_stickerSetInstallResultArchive(_sets)); + } }; } // namespace internal @@ -30246,7 +30438,7 @@ inline MTPconfig::MTPconfig() : mtpDataOwner(new MTPDconfig()) { inline uint32 MTPconfig::innerLength() const { const MTPDconfig &v(c_config()); - return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vedit_time_limit.innerLength() + v.vrating_e_decay.innerLength() + v.vdisabled_features.innerLength(); + return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vedit_time_limit.innerLength() + v.vrating_e_decay.innerLength() + v.vstickers_recent_limit.innerLength() + v.vdisabled_features.innerLength(); } inline mtpTypeId MTPconfig::type() const { return mtpc_config; @@ -30276,6 +30468,7 @@ inline void MTPconfig::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vsaved_gifs_limit.read(from, end); v.vedit_time_limit.read(from, end); v.vrating_e_decay.read(from, end); + v.vstickers_recent_limit.read(from, end); v.vdisabled_features.read(from, end); } inline void MTPconfig::write(mtpBuffer &to) const { @@ -30300,12 +30493,13 @@ inline void MTPconfig::write(mtpBuffer &to) const { v.vsaved_gifs_limit.write(to); v.vedit_time_limit.write(to); v.vrating_e_decay.write(to); + v.vstickers_recent_limit.write(to); v.vdisabled_features.write(to); } inline MTPconfig::MTPconfig(MTPDconfig *_data) : mtpDataOwner(_data) { } -inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector &_disabled_features) { - return MTP::internal::TypeCreator::new_config(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _disabled_features); +inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, MTPint _stickers_recent_limit, const MTPVector &_disabled_features) { + return MTP::internal::TypeCreator::new_config(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _stickers_recent_limit, _disabled_features); } inline MTPnearestDc::MTPnearestDc() : mtpDataOwner(new MTPDnearestDc()) { @@ -35354,6 +35548,84 @@ inline MTPmessages_recentStickers MTP_messages_recentStickersNotModified() { inline MTPmessages_recentStickers MTP_messages_recentStickers(MTPint _hash, const MTPVector &_stickers) { return MTP::internal::TypeCreator::new_messages_recentStickers(_hash, _stickers); } + +inline MTPmessages_archivedStickers::MTPmessages_archivedStickers() : mtpDataOwner(new MTPDmessages_archivedStickers()) { +} + +inline uint32 MTPmessages_archivedStickers::innerLength() const { + const MTPDmessages_archivedStickers &v(c_messages_archivedStickers()); + return v.vcount.innerLength() + v.vsets.innerLength(); +} +inline mtpTypeId MTPmessages_archivedStickers::type() const { + return mtpc_messages_archivedStickers; +} +inline void MTPmessages_archivedStickers::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_messages_archivedStickers) throw mtpErrorUnexpected(cons, "MTPmessages_archivedStickers"); + + if (!data) setData(new MTPDmessages_archivedStickers()); + MTPDmessages_archivedStickers &v(_messages_archivedStickers()); + v.vcount.read(from, end); + v.vsets.read(from, end); +} +inline void MTPmessages_archivedStickers::write(mtpBuffer &to) const { + const MTPDmessages_archivedStickers &v(c_messages_archivedStickers()); + v.vcount.write(to); + v.vsets.write(to); +} +inline MTPmessages_archivedStickers::MTPmessages_archivedStickers(MTPDmessages_archivedStickers *_data) : mtpDataOwner(_data) { +} +inline MTPmessages_archivedStickers MTP_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { + return MTP::internal::TypeCreator::new_messages_archivedStickers(_count, _sets); +} + +inline uint32 MTPmessages_stickerSetInstallResult::innerLength() const { + switch (_type) { + case mtpc_messages_stickerSetInstallResultArchive: { + const MTPDmessages_stickerSetInstallResultArchive &v(c_messages_stickerSetInstallResultArchive()); + return v.vsets.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPmessages_stickerSetInstallResult::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPmessages_stickerSetInstallResult::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_messages_stickerSetInstallResultSuccess: _type = cons; break; + case mtpc_messages_stickerSetInstallResultArchive: _type = cons; { + if (!data) setData(new MTPDmessages_stickerSetInstallResultArchive()); + MTPDmessages_stickerSetInstallResultArchive &v(_messages_stickerSetInstallResultArchive()); + v.vsets.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPmessages_stickerSetInstallResult"); + } +} +inline void MTPmessages_stickerSetInstallResult::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_messages_stickerSetInstallResultArchive: { + const MTPDmessages_stickerSetInstallResultArchive &v(c_messages_stickerSetInstallResultArchive()); + v.vsets.write(to); + } break; + } +} +inline MTPmessages_stickerSetInstallResult::MTPmessages_stickerSetInstallResult(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_messages_stickerSetInstallResultSuccess: break; + case mtpc_messages_stickerSetInstallResultArchive: setData(new MTPDmessages_stickerSetInstallResultArchive()); break; + default: throw mtpErrorBadTypeId(type, "MTPmessages_stickerSetInstallResult"); + } +} +inline MTPmessages_stickerSetInstallResult::MTPmessages_stickerSetInstallResult(MTPDmessages_stickerSetInstallResultArchive *_data) : mtpDataOwner(_data), _type(mtpc_messages_stickerSetInstallResultArchive) { +} +inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultSuccess() { + return MTP::internal::TypeCreator::new_messages_stickerSetInstallResultSuccess(); +} +inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { + return MTP::internal::TypeCreator::new_messages_stickerSetInstallResultArchive(_sets); +} inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } inline MTPDmessage::Flags mtpCastFlags(MTPflags flags) { return mtpCastFlags(flags.v); } inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index cd57d76aa..166952ee9 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -890,13 +890,13 @@ bool StickerData::setInstalled() const { switch (set.type()) { case mtpc_inputStickerSetID: { auto it = Global::StickerSets().constFind(set.c_inputStickerSetID().vid.v); - return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_disabled) && (it->flags & MTPDstickerSet::Flag::f_installed); + return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); } break; case mtpc_inputStickerSetShortName: { QString name = qs(set.c_inputStickerSetShortName().vshort_name).toLower(); for (auto it = Global::StickerSets().cbegin(), e = Global::StickerSets().cend(); it != e; ++it) { if (it->shortName.toLower() == name) { - return !(it->flags & MTPDstickerSet::Flag::f_disabled) && (it->flags & MTPDstickerSet::Flag::f_installed); + return !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); } } } break; From 207981b8c47fffd957beb64a75315ed3502f90d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 13:54:43 +0300 Subject: [PATCH 27/60] Sync video to audio stream fixed. Cute video download inside MediaView. Small round radius in webpage photo / doc nested attachments. --- Telegram/SourceFiles/app.cpp | 1 + Telegram/SourceFiles/history.cpp | 10 +- .../inline_bot_layout_internal.cpp | 2 +- Telegram/SourceFiles/layerwidget.cpp | 2 +- Telegram/SourceFiles/media/media_audio.cpp | 12 +- .../SourceFiles/media/media_clip_reader.cpp | 11 +- .../SourceFiles/media/media_clip_reader.h | 4 +- Telegram/SourceFiles/mediaview.cpp | 136 ++++++++++-------- Telegram/SourceFiles/mediaview.h | 5 +- Telegram/SourceFiles/ui/images.h | 1 + 10 files changed, 107 insertions(+), 77 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index f1f396e4e..7ee4b9b66 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2605,6 +2605,7 @@ namespace { switch (radius) { case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break; case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::msgRadius, bg, nullptr, images); break; + default: p.fillRect(x, y, w, h, bg); return; } CornersPixmaps pixmaps; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 2813792d5..7f47a5220 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -3408,11 +3408,13 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uin App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } + auto inWebPage = (_parent->getMedia() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; QPixmap pix; if (loaded) { - pix = _data->full->pixSingle(ImageRoundRadius::Large, _pixw, _pixh, width, height); + pix = _data->full->pixSingle(roundRadius, _pixw, _pixh, width, height); } else { - pix = _data->thumb->pixBlurredSingle(ImageRoundRadius::Large, _pixw, _pixh, width, height); + pix = _data->thumb->pixBlurredSingle(roundRadius, _pixw, _pixh, width, height); } QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); p.drawPixmap(rthumb.topLeft(), pix); @@ -4621,7 +4623,9 @@ int HistoryGif::resizeGetHeight(int width) { _width = qMax(_width, _parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); if (gif() && _gif->ready()) { if (!_gif->started()) { - _gif->start(_thumbw, _thumbh, _width, _height, true); + auto inWebPage = (_parent->getMedia() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + _gif->start(_thumbw, _thumbh, _width, _height, roundRadius); } } else { _width = qMax(_width, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 7683ea23b..06b1e07be 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -339,7 +339,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) { } else if (_gif->ready() && !_gif->started()) { int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); - _gif->start(frame.width(), frame.height(), _width, height, false); + _gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None); } else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) { delete _gif; _gif = nullptr; diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 08f840da6..602921043 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -397,7 +397,7 @@ void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) { if (gif() && _gif->ready() && !_gif->started()) { QSize s = currentDimensions(); - _gif->start(s.width(), s.height(), s.width(), s.height(), false); + _gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None); } update(); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 127282dd4..799c3511a 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -581,6 +581,12 @@ void AudioPlayer::pauseFromVideo(uint64 videoPlayId) { } break; } emit faderOnTimer(); + + QMutexLocker videoLock(&_lastVideoMutex); + if (_lastVideoPlayId == videoPlayId) { + _lastVideoPlaybackWhen = 0; + _lastVideoPlaybackCorrectedMs = 0; + } } if (current) emit updated(current); } @@ -663,8 +669,10 @@ void AudioPlayer::videoSoundProgress(const AudioMsgId &audio) { t_assert(current != nullptr); if (current->videoPlayId == _lastVideoPlayId && current->playbackState.duration && current->playbackState.frequency) { - _lastVideoPlaybackWhen = getms(); - _lastVideoPlaybackCorrectedMs = (current->playbackState.position * 1000ULL) / current->playbackState.frequency; + if (current->playbackState.state == AudioPlayerPlaying) { + _lastVideoPlaybackWhen = getms(); + _lastVideoPlaybackCorrectedMs = (current->playbackState.position * 1000ULL) / current->playbackState.frequency; + } } } diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index fee4786aa..0795ada90 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -43,7 +43,7 @@ QVector managers; QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); - if (badSize || needOuter || hasAlpha || request.rounded) { + if (badSize || needOuter || hasAlpha || request.radius != ImageRoundRadius::None) { int32 factor(request.factor); bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); if (newcache) { @@ -75,8 +75,8 @@ QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool p.drawImage(position, original); } } - if (request.rounded) { - imageRound(cache, ImageRoundRadius::Large); + if (request.radius != ImageRoundRadius::None) { + imageRound(cache, request.radius); } return QPixmap::fromImage(cache, Qt::ColorOnly); } @@ -185,7 +185,7 @@ void Reader::callback(Reader *reader, int32 threadIndex, Notification notificati } } -void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { +void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius) { if (managers.size() <= _threadIndex) error(); if (_state == State::Error) return; @@ -197,7 +197,7 @@ void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool request.frameh = frameh * factor; request.outerw = outerw * factor; request.outerh = outerh * factor; - request.rounded = rounded; + request.radius = radius; _frames[0].request = _frames[1].request = _frames[2].request = request; moveToNextShow(); managers.at(_threadIndex)->start(this); @@ -489,6 +489,7 @@ public: int64 delta = static_cast(ms) - static_cast(_videoPausedAtMs); _animationStarted += delta; _nextFrameWhen += delta; + LOG(("RESUME VIDEO: next when: %1, started: %2, delta: %3").arg(_nextFrameWhen).arg(_animationStarted).arg(delta)); _videoPausedAtMs = 0; _implementation->resumeAudio(); diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index c4620a2c1..d2a3f7be6 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -40,7 +40,7 @@ struct FrameRequest { int frameh = 0; int outerw = 0; int outerh = 0; - bool rounded = false; + ImageRoundRadius radius = ImageRoundRadius::None; }; enum ReaderSteps { @@ -75,7 +75,7 @@ public: return _seekPositionMs; } - void start(int framew, int frameh, int outerw, int outerh, bool rounded); + void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius); QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); QPixmap frameOriginal() const { Frame *frame = frameToShow(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 1f07a9fc9..98d4883d4 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -226,7 +226,7 @@ bool MediaView::gifShown() const { _gif->pauseResumeVideo(); const_cast(this)->_videoPaused = _gif->videoPaused(); } - _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); + _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), ImageRoundRadius::None); const_cast(this)->_current = QPixmap(); } return true;// _gif->state() != Media::Clip::State::Error; @@ -500,6 +500,9 @@ void MediaView::step_radial(uint64 ms, bool timer) { update(radialRect()); } if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) { + if (_doc->isVideo()) { + _autoplayVideoDocument = _doc; + } if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); } else { @@ -1149,6 +1152,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty } } + _docIconRect = QRect((width() - st::mvDocIconSize) / 2, (height() - st::mvDocIconSize) / 2, st::mvDocIconSize, st::mvDocIconSize); if (!fileShown()) { if (!_doc || _doc->thumb->isNull()) { int32 colorIndex = documentColorIndex(_doc, _docExt); @@ -1262,6 +1266,8 @@ void MediaView::initAnimation() { } else if (location.accessEnable()) { createClipReader(); location.accessDisable(); + } else { + _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } } @@ -1322,6 +1328,8 @@ void MediaView::setClipControllerGeometry() { } void MediaView::onVideoPauseResume() { + if (!_gif) return; + if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { if (_gif->state() == Media::Clip::State::Error) { displayDocument(_doc, item); @@ -1440,7 +1448,7 @@ void MediaView::paintEvent(QPaintEvent *e) { Painter p(this); - bool name = false, icon = false; + bool name = false; p.setClipRegion(region); @@ -1504,19 +1512,24 @@ void MediaView::paintEvent(QPaintEvent *e) { radial = _radial.animating(); radialOpacity = _radial.opacity(); } - if (radial) { - QRect inner(QPoint(_photoRadialRect.x(), _photoRadialRect.y()), st::radialSize); - p.setPen(Qt::NoPen); - p.setBrush(st::black); - p.setOpacity(radialOpacity * st::radialBgOpacity); + if (_photo) { + if (radial) { + auto inner = radialRect(); - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); + p.setPen(Qt::NoPen); + p.setBrush(st::black); + p.setOpacity(radialOpacity * st::radialBgOpacity); - p.setOpacity(1); - QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); - _radial.draw(p, arc, st::radialLine, st::white); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity(1); + QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); + _radial.draw(p, arc, st::radialLine, st::white); + } + } else if (_doc) { + paintDocRadialLoading(p, radial, radialOpacity); } if (_saveMsgStarted) { @@ -1559,7 +1572,6 @@ void MediaView::paintEvent(QPaintEvent *e) { radial = _radial.animating(); radialOpacity = _radial.opacity(); } - icon = true; if (!_doc || _doc->thumb->isNull()) { p.fillRect(_docIconRect, _docIconColor->b); if ((!_doc || _doc->loaded()) && (!radial || radialOpacity < 1)) { @@ -1575,42 +1587,17 @@ void MediaView::paintEvent(QPaintEvent *e) { p.drawPixmap(_docIconRect.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mvDocIconSize * rf, st::mvDocIconSize * rf)); } - float64 o = overLevel(OverIcon); - if (radial) { - if (!_doc->loaded() && radialOpacity < 1) { - p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity) * (1 - radialOpacity)); - p.drawSpriteCenter(_docIconRect, st::radialDownload); - } - - QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); - p.setPen(Qt::NoPen); - p.setBrush(st::black); - p.setOpacity(radialOpacity * st::radialBgOpacity); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - p.setOpacity((o * 1. + (1 - o) * st::radialCancelOpacity) * radialOpacity); - p.drawSpriteCenter(_docIconRect, st::radialCancel); - p.setOpacity(1); - - QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); - _radial.draw(p, arc, st::radialLine, st::white); - } else if (_doc && !_doc->loaded()) { - p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity)); - p.drawSpriteCenter(_docIconRect, st::radialDownload); - } + paintDocRadialLoading(p, radial, radialOpacity); } if (!_docIconRect.contains(r)) { name = true; - p.setPen(st::mvDocNameColor->p); - p.setFont(st::mvDocNameFont->f); + p.setPen(st::mvDocNameColor); + p.setFont(st::mvDocNameFont); p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocNameTop, width(), _docName, _docNameWidth); - p.setPen(st::mvDocSizeColor->p); - p.setFont(st::mvFont->f); + p.setPen(st::mvDocSizeColor); + p.setFont(st::mvFont); p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocSizeTop, width(), _docSize, _docSizeWidth); } } @@ -1740,6 +1727,35 @@ void MediaView::paintEvent(QPaintEvent *e) { } } +void MediaView::paintDocRadialLoading(Painter &p, bool radial, float64 radialOpacity) { + float64 o = overLevel(OverIcon); + if (radial) { + if (!_doc->loaded() && radialOpacity < 1) { + p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity) * (1 - radialOpacity)); + p.drawSpriteCenter(_docIconRect, st::radialDownload); + } + + QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); + p.setPen(Qt::NoPen); + p.setBrush(st::black); + p.setOpacity(radialOpacity * st::radialBgOpacity); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity((o * 1. + (1 - o) * st::radialCancelOpacity) * radialOpacity); + p.drawSpriteCenter(_docIconRect, st::radialCancel); + p.setOpacity(1); + + QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); + _radial.draw(p, arc, st::radialLine, st::white); + } else if (_doc && !_doc->loaded()) { + p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity)); + p.drawSpriteCenter(_docIconRect, st::radialDownload); + } +} + void MediaView::keyPressEvent(QKeyEvent *e) { if (_clipController) { auto toggle1 = (e->key() == Qt::Key_F && e->modifiers().testFlag(Qt::ControlModifier)); @@ -1758,13 +1774,17 @@ void MediaView::keyPressEvent(QKeyEvent *e) { } } if (!_menu && e->key() == Qt::Key_Escape) { - close(); + if (_doc && _doc->loading()) { + onDocClick(); + } else { + close(); + } } else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) { onSaveAs(); } else if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) { onCopy(); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { - if (_doc && !_doc->loading() && !fileShown()) { + if (_doc && !_doc->loading() && (!fileShown() || !_doc->loaded())) { onDocClick(); } else if (_doc->isVideo()) { onVideoPauseResume(); @@ -2229,8 +2249,14 @@ void MediaView::updateOver(QPoint pos) { updateOverState(OverMore); } else if (_closeNav.contains(pos)) { updateOverState(OverClose); - } else if (_doc && _doc->isVideo() && _gif && QRect(_x, _y, _w, _h).contains(pos)) { - updateOverState(OverVideo); + } else if (_doc && fileShown() && QRect(_x, _y, _w, _h).contains(pos)) { + if (_doc->isVideo() && _gif) { + updateOverState(OverVideo); + } else if (!_doc->loaded()) { + updateOverState(OverIcon); + } else if (_over != OverNone) { + updateOverState(OverNone); + } } else if (_over != OverNone) { updateOverState(OverNone); } @@ -2645,19 +2671,7 @@ void MediaView::updateHeader() { _headerNav = myrtlrect(st::mvTextLeft, height() - st::mvHeaderTop, hwidth, st::mvThickFont->height); } -QColor MediaView::overColor(const QColor &a, float64 ca, const QColor &b, float64 cb) { - QColor res; - float64 o = a.alphaF() * ca + b.alphaF() * cb - a.alphaF() * ca * b.alphaF() * cb; - float64 ka = (o > 0.001) ? (a.alphaF() * ca * (1 - (b.alphaF() * cb)) / o) : 0; - float64 kb = (o > 0.001) ? (b.alphaF() * cb / o) : 0; - res.setRedF(a.redF() * ka + b.redF() * kb); - res.setGreenF(a.greenF() * ka + b.greenF() * kb); - res.setBlueF(a.blueF() * ka + b.blueF() * kb); - res.setAlphaF(o); - return res; -} - -float64 MediaView::overLevel(OverState control) { +float64 MediaView::overLevel(OverState control) const { ShowingOpacities::const_iterator i = _animOpacities.constFind(control); return (i == _animOpacities.cend()) ? (_over == control ? 1 : 0) : i->current(); } diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 49941fe27..ee996a866 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -169,6 +169,8 @@ private: void step_state(uint64 ms, bool timer); void step_radial(uint64 ms, bool timer); + void paintDocRadialLoading(Painter &p, bool radial, float64 radialOpacity); + QBrush _transparentBrush; PhotoData *_photo = nullptr; @@ -315,7 +317,6 @@ private: void updateOverRect(OverState state); bool updateOverState(OverState newState); - float64 overLevel(OverState control); - QColor overColor(const QColor &a, float64 ca, const QColor &b, float64 cb); + float64 overLevel(OverState control) const; }; diff --git a/Telegram/SourceFiles/ui/images.h b/Telegram/SourceFiles/ui/images.h index 8596dbfc4..2a2ca3b2d 100644 --- a/Telegram/SourceFiles/ui/images.h +++ b/Telegram/SourceFiles/ui/images.h @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mtproto/file_download.h" enum class ImageRoundRadius { + None, Large, Small, }; From 1753db3e62e4f9dcfd83851a0710d529a55e6d9a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 14:37:01 +0300 Subject: [PATCH 28/60] Megagroup members are always shown if the server allows us to view them. Bot keyboard clearing fixed when using Clear History. --- Telegram/SourceFiles/apiwrap.cpp | 1 - Telegram/SourceFiles/app.cpp | 2 -- Telegram/SourceFiles/history.cpp | 6 +++--- Telegram/SourceFiles/historywidget.cpp | 3 ++- .../SourceFiles/profile/profile_members_widget.cpp | 6 ++++-- Telegram/SourceFiles/window/top_bar_widget.cpp | 14 +++++++++----- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0a13d6be2..08c4da448 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -543,7 +543,6 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP } if (!keyboardBotFound) { h->clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(h); } int newMembersCount = qMax(d.vcount.v, v.count()); if (newMembersCount > peer->membersCount()) { diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 7ee4b9b66..7b67ab797 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -858,7 +858,6 @@ namespace { chat->botStatus = botStatus; if (!found) { h->clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(h); } } } @@ -961,7 +960,6 @@ namespace { History *h = App::historyLoaded(chat->id); if (h && h->lastKeyboardFrom == user->id) { h->clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(h); } } if (chat->botStatus > 0 && user->botInfo) { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 7f47a5220..d94e11200 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -151,6 +151,9 @@ void History::clearLastKeyboard() { lastKeyboardHiddenId = 0; } lastKeyboardId = 0; + if (auto main = App::main()) { + main->updateBotKeyboard(this); + } } lastKeyboardInited = true; lastKeyboardFrom = 0; @@ -839,7 +842,6 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, PeerId uid = peerFromUser(d.vuser_id); if (lastKeyboardFrom == uid) { clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(this); } if (peer->isMegagroup()) { if (auto user = App::userLoaded(uid)) { @@ -2719,7 +2721,6 @@ void HistoryItem::finishEditionToEmpty() { } if (history()->lastKeyboardId == id) { history()->clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(history()); } if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) { history()->setUnreadCount(history()->unreadCount() - 1); @@ -2767,7 +2768,6 @@ void HistoryItem::destroy() { } if (history()->lastKeyboardId == id) { history()->clearLastKeyboard(); - if (App::main()) App::main()->updateBotKeyboard(history()); } if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) { history()->setUnreadCount(history()->unreadCount() - 1); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index b695b0f0d..347fc6b69 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5930,8 +5930,9 @@ void HistoryWidget::onKbToggle(bool manual) { } else { if (_history) { _history->clearLastKeyboard(); + } else { + updateBotKeyboard(); } - updateBotKeyboard(); } } else if (!_keyboard.hasMarkup() && _keyboard.forceReply()) { _kbHide.hide(); diff --git a/Telegram/SourceFiles/profile/profile_members_widget.cpp b/Telegram/SourceFiles/profile/profile_members_widget.cpp index 34563e822..472c1b64d 100644 --- a/Telegram/SourceFiles/profile/profile_members_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_members_widget.cpp @@ -447,7 +447,7 @@ void MembersWidget::fillMegagroupMembers(ChannelData *megagroup) { t_assert(megagroup->mgInfo != nullptr); if (megagroup->mgInfo->lastParticipants.isEmpty()) return; - if (!megagroup->amIn()) { + if (!megagroup->canViewMembers()) { _list.clear(); return; } @@ -458,7 +458,9 @@ void MembersWidget::fillMegagroupMembers(ChannelData *megagroup) { if (_sortByOnline) { _list.clear(); _list.reserve(membersList.size()); - addUser(megagroup, App::self())->onlineForSort = INT_MAX; + if (megagroup->amIn()) { + addUser(megagroup, App::self())->onlineForSort = INT_MAX; + } } else if (membersList.size() >= _list.size()) { if (addUsersToEnd(megagroup)) { return; diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index d21847fe2..7218dfa8d 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -72,7 +72,11 @@ void TopBarWidget::onInfoClicked() { } void TopBarWidget::onSearch() { - Shortcuts::launch(qsl("search")); + if (auto main = App::main()) { + if (auto peer = main->peer()) { + main->searchInPeer(peer); + } + } } void TopBarWidget::enterEvent(QEvent *e) { @@ -246,11 +250,11 @@ void TopBarWidget::updateMembersShowArea() { if (_selCount || App::main()->overviewPeer() || !_selPeer) { return false; } - if (_selPeer->isChat()) { - return true; + if (auto chat = _selPeer->asChat()) { + return chat->amIn(); } - if (_selPeer->isMegagroup()) { - return (_selPeer->asMegagroup()->membersCount() < Global::ChatSizeMax()); + if (auto megagroup = _selPeer->asMegagroup()) { + return megagroup->canViewMembers() && (megagroup->membersCount() < Global::ChatSizeMax()); } return false; }; From 49f6431fca455f96b0f416be82915cd1a04dcd9d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 15:31:48 +0300 Subject: [PATCH 29/60] Video play in MediaView support done in Xcode/QtCreator projects. --- Telegram/Resources/all_files.style | 2 + .../SourceFiles/media/media_clip_ffmpeg.cpp | 3 +- .../media/view/media_clip_controller.cpp | 3 + .../media/view/media_clip_controller.h | 2 + Telegram/SourceFiles/mediaview.cpp | 14 +- Telegram/Telegram.pro | 51 +++++- Telegram/Telegram.xcodeproj/project.pbxproj | 162 +++++++++++++++++- Telegram/Telegram.xcodeproj/qt_preprocess.mak | 24 +++ 8 files changed, 250 insertions(+), 11 deletions(-) diff --git a/Telegram/Resources/all_files.style b/Telegram/Resources/all_files.style index b91a1ca4f..f6a7ca6d6 100644 --- a/Telegram/Resources/all_files.style +++ b/Telegram/Resources/all_files.style @@ -27,3 +27,5 @@ using "dialogs/dialogs.style"; using "history/history.style"; using "overview/overview.style"; using "profile/profile.style"; +using "media/view/mediaview.style"; +using "ui/widgets/widgets.style"; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 44bff8947..ab29e6975 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -468,7 +468,8 @@ void FFMpegReaderImplementation::finishPacket() { _packetQueue.head().size = _packetStartedSize; _packetQueue.head().data = _packetStartedData; _packetStarted = false; - av_packet_unref(&_packetQueue.dequeue()); + av_packet_unref(&_packetQueue.head()); + _packetQueue.dequeue(); } } diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 133e93330..904b5bae1 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -210,5 +210,8 @@ void Controller::mousePressEvent(QMouseEvent *e) { e->accept(); // Don't pass event to the MediaView. } +Controller::~Controller() { +} + } // namespace Clip } // namespace Media diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h index 25ebfd720..4d27737fa 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -49,6 +49,8 @@ public: void grabStart() override; void grabFinish() override; + ~Controller(); + signals: void playPressed(); void pausePressed(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 98d4883d4..c43285bea 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1266,8 +1266,13 @@ void MediaView::initAnimation() { } else if (location.accessEnable()) { createClipReader(); location.accessDisable(); + } else if (_doc->dimensions.width() && _doc->dimensions.height()) { + int w = _doc->dimensions.width(); + int h = _doc->dimensions.height(); + _current = _doc->thumb->pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred, w / cIntRetinaFactor(), h / cIntRetinaFactor()); + if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); } else { - _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); + _current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), ImagePixSmooth | ImagePixBlurred, st::mvDocIconSize, st::mvDocIconSize); } } @@ -1278,7 +1283,12 @@ void MediaView::createClipReader() { t_assert(_doc->isAnimation() || _doc->isVideo()); if (_doc->dimensions.width() && _doc->dimensions.height()) { - _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); + int w = _doc->dimensions.width(); + int h = _doc->dimensions.height(); + _current = _doc->thumb->pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred, w / cIntRetinaFactor(), h / cIntRetinaFactor()); + if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); + } else { + _current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), ImagePixSmooth | ImagePixBlurred, st::mvDocIconSize, st::mvDocIconSize); } auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif; _gif = std_::make_unique(_doc->location(), _doc->data(), func(this, &MediaView::clipCallback), mode); diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 56057e9f5..7e1fbb32f 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -63,18 +63,23 @@ file_style_basic.target = GeneratedFiles/styles/style_basic.cpp file_style_basic.depends = style_target file_style_basic_types.target = GeneratedFiles/styles/style_basic_types.cpp file_style_basic_types.depends = style_target -file_style_overview.target = GeneratedFiles/styles/style_overview.cpp -file_style_overview.depends = style_target file_style_dialogs.target = GeneratedFiles/styles/style_dialogs.cpp file_style_dialogs.depends = style_target file_style_history.target = GeneratedFiles/styles/style_history.cpp file_style_history.depends = style_target +file_style_mediaview.target = GeneratedFiles/styles/style_mediaview.cpp +file_style_mediaview.depends = style_target +file_style_overview.target = GeneratedFiles/styles/style_overview.cpp +file_style_overview.depends = style_target file_style_profile.target = GeneratedFiles/styles/style_profile.cpp file_style_profile.depends = style_target +file_style_widgets.target = GeneratedFiles/styles/style_widgets.cpp +file_style_widgets.depends = style_target QMAKE_EXTRA_TARGETS += codegen_style codegen_numbers codegen_lang \ - file_style_basic file_style_basic_types file_style_overview \ - file_style_dialogs file_style_history file_style_profile + file_style_basic file_style_basic_types file_style_dialogs \ + file_style_history file_style_mediaview file_style_overview \ + file_style_profile file_style_widgets PRE_TARGETDEPS += style_target numbers_target lang_target @@ -97,8 +102,10 @@ SOURCES += \ ./GeneratedFiles/styles/style_basic_types.cpp \ ./GeneratedFiles/styles/style_dialogs.cpp \ ./GeneratedFiles/styles/style_history.cpp \ + ./GeneratedFiles/styles/style_mediaview.cpp \ ./GeneratedFiles/styles/style_overview.cpp \ ./GeneratedFiles/styles/style_profile.cpp \ + ./GeneratedFiles/styles/style_widgets.cpp \ ./SourceFiles/main.cpp \ ./SourceFiles/stdafx.cpp \ ./SourceFiles/apiwrap.cpp \ @@ -172,6 +179,18 @@ SOURCES += \ ./SourceFiles/intro/intropwdcheck.cpp \ ./SourceFiles/intro/introsignup.cpp \ ./SourceFiles/intro/introstart.cpp \ + ./SourceFiles/media/view/media_clip_controller.cpp \ + ./SourceFiles/media/view/media_clip_playback.cpp \ + ./SourceFiles/media/view/media_clip_volume_controller.cpp \ + ./SourceFiles/media/media_audio.cpp \ + ./SourceFiles/media/media_audio_ffmpeg_loader.cpp \ + ./SourceFiles/media/media_audio_loader.cpp \ + ./SourceFiles/media/media_audio_loaders.cpp \ + ./SourceFiles/media/media_child_ffmpeg_loader.cpp \ + ./SourceFiles/media/media_clip_ffmpeg.cpp \ + ./SourceFiles/media/media_clip_implementation.cpp \ + ./SourceFiles/media/media_clip_qtgif.cpp \ + ./SourceFiles/media/media_clip_reader.cpp \ ./SourceFiles/mtproto/facade.cpp \ ./SourceFiles/mtproto/auth_key.cpp \ ./SourceFiles/mtproto/connection.cpp \ @@ -208,9 +227,11 @@ SOURCES += \ ./SourceFiles/serialize/serialize_common.cpp \ ./SourceFiles/serialize/serialize_document.cpp \ ./SourceFiles/ui/buttons/history_down_button.cpp \ + ./SourceFiles/ui/buttons/icon_button.cpp \ ./SourceFiles/ui/buttons/left_outline_button.cpp \ ./SourceFiles/ui/buttons/peer_avatar_button.cpp \ ./SourceFiles/ui/buttons/round_button.cpp \ + ./SourceFiles/ui/effects/fade_animation.cpp \ ./SourceFiles/ui/style/style_core.cpp \ ./SourceFiles/ui/style/style_core_color.cpp \ ./SourceFiles/ui/style/style_core_font.cpp \ @@ -222,6 +243,7 @@ SOURCES += \ ./SourceFiles/ui/toast/toast.cpp \ ./SourceFiles/ui/toast/toast_manager.cpp \ ./SourceFiles/ui/toast/toast_widget.cpp \ + ./SourceFiles/ui/widgets/label_simple.cpp \ ./SourceFiles/ui/animation.cpp \ ./SourceFiles/ui/boxshadow.cpp \ ./SourceFiles/ui/button.cpp \ @@ -250,8 +272,10 @@ HEADERS += \ ./GeneratedFiles/styles/style_basic_types.h \ ./GeneratedFiles/styles/style_dialogs.h \ ./GeneratedFiles/styles/style_history.h \ + ./GeneratedFiles/styles/style_mediaview.h \ ./GeneratedFiles/styles/style_overview.h \ ./GeneratedFiles/styles/style_profile.h \ + ./GeneratedFiles/styles/style_widgets.h \ ./SourceFiles/stdafx.h \ ./SourceFiles/apiwrap.h \ ./SourceFiles/app.h \ @@ -330,6 +354,18 @@ HEADERS += \ ./SourceFiles/intro/intropwdcheck.h \ ./SourceFiles/intro/introsignup.h \ ./SourceFiles/intro/introstart.h \ + ./SourceFiles/media/view/media_clip_controller.h \ + ./SourceFiles/media/view/media_clip_playback.h \ + ./SourceFiles/media/view/media_clip_volume_controller.h \ + ./SourceFiles/media/media_audio.h \ + ./SourceFiles/media/media_audio_ffmpeg_loader.h \ + ./SourceFiles/media/media_audio_loader.h \ + ./SourceFiles/media/media_audio_loaders.h \ + ./SourceFiles/media/media_child_ffmpeg_loader.h \ + ./SourceFiles/media/media_clip_ffmpeg.h \ + ./SourceFiles/media/media_clip_implementation.h \ + ./SourceFiles/media/media_clip_qtgif.h \ + ./SourceFiles/media/media_clip_reader.h \ ./SourceFiles/mtproto/facade.h \ ./SourceFiles/mtproto/auth_key.h \ ./SourceFiles/mtproto/connection.h \ @@ -369,9 +405,11 @@ HEADERS += \ ./SourceFiles/serialize/serialize_common.h \ ./SourceFiles/serialize/serialize_document.h \ ./SourceFiles/ui/buttons/history_down_button.h \ + ./SourceFiles/ui/buttons/icon_button.h \ ./SourceFiles/ui/buttons/left_outline_button.h \ ./SourceFiles/ui/buttons/peer_avatar_button.h \ ./SourceFiles/ui/buttons/round_button.h \ + ./SourceFiles/ui/effects/fade_animation.h \ ./SourceFiles/ui/style/style_core.h \ ./SourceFiles/ui/style/style_core_color.h \ ./SourceFiles/ui/style/style_core_font.h \ @@ -383,6 +421,7 @@ HEADERS += \ ./SourceFiles/ui/toast/toast.h \ ./SourceFiles/ui/toast/toast_manager.h \ ./SourceFiles/ui/toast/toast_widget.h \ + ./SourceFiles/ui/widgets/label_simple.h \ ./SourceFiles/ui/animation.h \ ./SourceFiles/ui/boxshadow.h \ ./SourceFiles/ui/button.h \ @@ -534,5 +573,7 @@ OTHER_FILES += \ ./Resources/langs/lang_pt_BR.strings \ ./SourceFiles/dialogs/dialogs.style \ ./SourceFiles/history/history.style \ + ./SourceFiles/media/view/mediaview.style \ ./SourceFiles/overview/overview.style \ - ./SourceFiles/profile/profile.style + ./SourceFiles/profile/profile.style \ + ./SourceFiles/ui/widgets/widgets.style diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index a6bdde1c2..a571e4cdf 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -181,6 +181,29 @@ 07DE92AA1AA4928200A18F6F /* moc_autolockbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07DE92A91AA4928200A18F6F /* moc_autolockbox.cpp */; }; 07DE92AD1AA4928B00A18F6F /* moc_passcodebox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07DE92AB1AA4928B00A18F6F /* moc_passcodebox.cpp */; }; 07DE92AE1AA4928B00A18F6F /* moc_passcodewidget.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07DE92AC1AA4928B00A18F6F /* moc_passcodewidget.cpp */; }; + 07E102CC1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102BC1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.cpp */; }; + 07E102CD1D3E4B7F00BD33B1 /* media_audio_loader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102BE1D3E4B7F00BD33B1 /* media_audio_loader.cpp */; }; + 07E102CE1D3E4B7F00BD33B1 /* media_audio_loaders.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102C01D3E4B7F00BD33B1 /* media_audio_loaders.cpp */; }; + 07E102CF1D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102C21D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.cpp */; }; + 07E102D01D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102C41D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp */; }; + 07E102D11D3E4B7F00BD33B1 /* media_clip_implementation.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102C61D3E4B7F00BD33B1 /* media_clip_implementation.cpp */; }; + 07E102D21D3E4B7F00BD33B1 /* media_clip_qtgif.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102C81D3E4B7F00BD33B1 /* media_clip_qtgif.cpp */; }; + 07E102D31D3E4B7F00BD33B1 /* media_clip_reader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102CA1D3E4B7F00BD33B1 /* media_clip_reader.cpp */; }; + 07E102D61D3E4BE800BD33B1 /* style_mediaview.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102D41D3E4BE800BD33B1 /* style_mediaview.cpp */; }; + 07E102D91D3E4CB800BD33B1 /* style_widgets.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102D71D3E4CB800BD33B1 /* style_widgets.cpp */; }; + 07E102DF1D3E4DE800BD33B1 /* moc_media_audio_loaders.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102DA1D3E4DE800BD33B1 /* moc_media_audio_loaders.cpp */; }; + 07E102E01D3E4DE800BD33B1 /* moc_media_clip_controller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102DB1D3E4DE800BD33B1 /* moc_media_clip_controller.cpp */; }; + 07E102E11D3E4DE800BD33B1 /* moc_media_clip_playback.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102DC1D3E4DE800BD33B1 /* moc_media_clip_playback.cpp */; }; + 07E102E21D3E4DE800BD33B1 /* moc_media_clip_reader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102DD1D3E4DE800BD33B1 /* moc_media_clip_reader.cpp */; }; + 07E102E31D3E4DE800BD33B1 /* moc_media_clip_volume_controller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102DE1D3E4DE800BD33B1 /* moc_media_clip_volume_controller.cpp */; }; + 07E102EB1D3E4E3B00BD33B1 /* media_clip_controller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102E41D3E4E3B00BD33B1 /* media_clip_controller.cpp */; }; + 07E102EC1D3E4E3B00BD33B1 /* media_clip_playback.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102E61D3E4E3B00BD33B1 /* media_clip_playback.cpp */; }; + 07E102ED1D3E4E3B00BD33B1 /* media_clip_volume_controller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102E81D3E4E3B00BD33B1 /* media_clip_volume_controller.cpp */; }; + 07E102EE1D3E4E3B00BD33B1 /* mediaview.style in Resources */ = {isa = PBXBuildFile; fileRef = 07E102EA1D3E4E3B00BD33B1 /* mediaview.style */; }; + 07E102F31D3E4E7F00BD33B1 /* fade_animation.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102F11D3E4E7F00BD33B1 /* fade_animation.cpp */; }; + 07E102F71D3E4E8C00BD33B1 /* label_simple.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102F41D3E4E8C00BD33B1 /* label_simple.cpp */; }; + 07E102F81D3E4E8C00BD33B1 /* widgets.style in Resources */ = {isa = PBXBuildFile; fileRef = 07E102F61D3E4E8C00BD33B1 /* widgets.style */; }; + 07E102FB1D3E4E9900BD33B1 /* icon_button.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E102F91D3E4E9900BD33B1 /* icon_button.cpp */; }; 07E1B1911D12DB3F00722BC7 /* main_window_mac.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E1B1901D12DB3F00722BC7 /* main_window_mac.mm */; }; 07E1B1931D12DED700722BC7 /* moc_main_window_mac.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E1B1921D12DED700722BC7 /* moc_main_window_mac.cpp */; }; 07E1B1961D12DFD200722BC7 /* main_window.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07E1B1941D12DFD200722BC7 /* main_window.cpp */; }; @@ -635,6 +658,45 @@ 07DE92A91AA4928200A18F6F /* moc_autolockbox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_autolockbox.cpp; path = GeneratedFiles/Debug/moc_autolockbox.cpp; sourceTree = SOURCE_ROOT; }; 07DE92AB1AA4928B00A18F6F /* moc_passcodebox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_passcodebox.cpp; path = GeneratedFiles/Debug/moc_passcodebox.cpp; sourceTree = SOURCE_ROOT; }; 07DE92AC1AA4928B00A18F6F /* moc_passcodewidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_passcodewidget.cpp; path = GeneratedFiles/Debug/moc_passcodewidget.cpp; sourceTree = SOURCE_ROOT; }; + 07E102BC1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_audio_ffmpeg_loader.cpp; path = SourceFiles/media/media_audio_ffmpeg_loader.cpp; sourceTree = SOURCE_ROOT; }; + 07E102BD1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_audio_ffmpeg_loader.h; path = SourceFiles/media/media_audio_ffmpeg_loader.h; sourceTree = SOURCE_ROOT; }; + 07E102BE1D3E4B7F00BD33B1 /* media_audio_loader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_audio_loader.cpp; path = SourceFiles/media/media_audio_loader.cpp; sourceTree = SOURCE_ROOT; }; + 07E102BF1D3E4B7F00BD33B1 /* media_audio_loader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_audio_loader.h; path = SourceFiles/media/media_audio_loader.h; sourceTree = SOURCE_ROOT; }; + 07E102C01D3E4B7F00BD33B1 /* media_audio_loaders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_audio_loaders.cpp; path = SourceFiles/media/media_audio_loaders.cpp; sourceTree = SOURCE_ROOT; }; + 07E102C11D3E4B7F00BD33B1 /* media_audio_loaders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_audio_loaders.h; path = SourceFiles/media/media_audio_loaders.h; sourceTree = SOURCE_ROOT; }; + 07E102C21D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_child_ffmpeg_loader.cpp; path = SourceFiles/media/media_child_ffmpeg_loader.cpp; sourceTree = SOURCE_ROOT; }; + 07E102C31D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_child_ffmpeg_loader.h; path = SourceFiles/media/media_child_ffmpeg_loader.h; sourceTree = SOURCE_ROOT; }; + 07E102C41D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_ffmpeg.cpp; path = SourceFiles/media/media_clip_ffmpeg.cpp; sourceTree = SOURCE_ROOT; }; + 07E102C51D3E4B7F00BD33B1 /* media_clip_ffmpeg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_ffmpeg.h; path = SourceFiles/media/media_clip_ffmpeg.h; sourceTree = SOURCE_ROOT; }; + 07E102C61D3E4B7F00BD33B1 /* media_clip_implementation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_implementation.cpp; path = SourceFiles/media/media_clip_implementation.cpp; sourceTree = SOURCE_ROOT; }; + 07E102C71D3E4B7F00BD33B1 /* media_clip_implementation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_implementation.h; path = SourceFiles/media/media_clip_implementation.h; sourceTree = SOURCE_ROOT; }; + 07E102C81D3E4B7F00BD33B1 /* media_clip_qtgif.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_qtgif.cpp; path = SourceFiles/media/media_clip_qtgif.cpp; sourceTree = SOURCE_ROOT; }; + 07E102C91D3E4B7F00BD33B1 /* media_clip_qtgif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_qtgif.h; path = SourceFiles/media/media_clip_qtgif.h; sourceTree = SOURCE_ROOT; }; + 07E102CA1D3E4B7F00BD33B1 /* media_clip_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_reader.cpp; path = SourceFiles/media/media_clip_reader.cpp; sourceTree = SOURCE_ROOT; }; + 07E102CB1D3E4B7F00BD33B1 /* media_clip_reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_reader.h; path = SourceFiles/media/media_clip_reader.h; sourceTree = SOURCE_ROOT; }; + 07E102D41D3E4BE800BD33B1 /* style_mediaview.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = style_mediaview.cpp; path = GeneratedFiles/styles/style_mediaview.cpp; sourceTree = SOURCE_ROOT; }; + 07E102D51D3E4BE800BD33B1 /* style_mediaview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = style_mediaview.h; path = GeneratedFiles/styles/style_mediaview.h; sourceTree = SOURCE_ROOT; }; + 07E102D71D3E4CB800BD33B1 /* style_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = style_widgets.cpp; path = GeneratedFiles/styles/style_widgets.cpp; sourceTree = SOURCE_ROOT; }; + 07E102D81D3E4CB800BD33B1 /* style_widgets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = style_widgets.h; path = GeneratedFiles/styles/style_widgets.h; sourceTree = SOURCE_ROOT; }; + 07E102DA1D3E4DE800BD33B1 /* moc_media_audio_loaders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_audio_loaders.cpp; path = GeneratedFiles/Debug/moc_media_audio_loaders.cpp; sourceTree = SOURCE_ROOT; }; + 07E102DB1D3E4DE800BD33B1 /* moc_media_clip_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_clip_controller.cpp; path = GeneratedFiles/Debug/moc_media_clip_controller.cpp; sourceTree = SOURCE_ROOT; }; + 07E102DC1D3E4DE800BD33B1 /* moc_media_clip_playback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_clip_playback.cpp; path = GeneratedFiles/Debug/moc_media_clip_playback.cpp; sourceTree = SOURCE_ROOT; }; + 07E102DD1D3E4DE800BD33B1 /* moc_media_clip_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_clip_reader.cpp; path = GeneratedFiles/Debug/moc_media_clip_reader.cpp; sourceTree = SOURCE_ROOT; }; + 07E102DE1D3E4DE800BD33B1 /* moc_media_clip_volume_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_clip_volume_controller.cpp; path = GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp; sourceTree = SOURCE_ROOT; }; + 07E102E41D3E4E3B00BD33B1 /* media_clip_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_controller.cpp; path = SourceFiles/media/view/media_clip_controller.cpp; sourceTree = SOURCE_ROOT; }; + 07E102E51D3E4E3B00BD33B1 /* media_clip_controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_controller.h; path = SourceFiles/media/view/media_clip_controller.h; sourceTree = SOURCE_ROOT; }; + 07E102E61D3E4E3B00BD33B1 /* media_clip_playback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_playback.cpp; path = SourceFiles/media/view/media_clip_playback.cpp; sourceTree = SOURCE_ROOT; }; + 07E102E71D3E4E3B00BD33B1 /* media_clip_playback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_playback.h; path = SourceFiles/media/view/media_clip_playback.h; sourceTree = SOURCE_ROOT; }; + 07E102E81D3E4E3B00BD33B1 /* media_clip_volume_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_clip_volume_controller.cpp; path = SourceFiles/media/view/media_clip_volume_controller.cpp; sourceTree = SOURCE_ROOT; }; + 07E102E91D3E4E3B00BD33B1 /* media_clip_volume_controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_clip_volume_controller.h; path = SourceFiles/media/view/media_clip_volume_controller.h; sourceTree = SOURCE_ROOT; }; + 07E102EA1D3E4E3B00BD33B1 /* mediaview.style */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = mediaview.style; path = SourceFiles/media/view/mediaview.style; sourceTree = SOURCE_ROOT; }; + 07E102F11D3E4E7F00BD33B1 /* fade_animation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fade_animation.cpp; path = SourceFiles/ui/effects/fade_animation.cpp; sourceTree = SOURCE_ROOT; }; + 07E102F21D3E4E7F00BD33B1 /* fade_animation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fade_animation.h; path = SourceFiles/ui/effects/fade_animation.h; sourceTree = SOURCE_ROOT; }; + 07E102F41D3E4E8C00BD33B1 /* label_simple.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = label_simple.cpp; path = SourceFiles/ui/widgets/label_simple.cpp; sourceTree = SOURCE_ROOT; }; + 07E102F51D3E4E8C00BD33B1 /* label_simple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = label_simple.h; path = SourceFiles/ui/widgets/label_simple.h; sourceTree = SOURCE_ROOT; }; + 07E102F61D3E4E8C00BD33B1 /* widgets.style */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = widgets.style; path = SourceFiles/ui/widgets/widgets.style; sourceTree = SOURCE_ROOT; }; + 07E102F91D3E4E9900BD33B1 /* icon_button.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = icon_button.cpp; path = SourceFiles/ui/buttons/icon_button.cpp; sourceTree = SOURCE_ROOT; }; + 07E102FA1D3E4E9900BD33B1 /* icon_button.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = icon_button.h; path = SourceFiles/ui/buttons/icon_button.h; sourceTree = SOURCE_ROOT; }; 07E1B1781D12DAF100722BC7 /* platform_main_window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = platform_main_window.h; path = SourceFiles/platform/platform_main_window.h; sourceTree = SOURCE_ROOT; }; 07E1B1791D12DB0700722BC7 /* main_window_win.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = main_window_win.cpp; path = SourceFiles/platform/win/main_window_win.cpp; sourceTree = SOURCE_ROOT; }; 07E1B17A1D12DB0700722BC7 /* main_window_win.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = main_window_win.h; path = SourceFiles/platform/win/main_window_win.h; sourceTree = SOURCE_ROOT; }; @@ -1034,10 +1096,14 @@ 0716C99D1D08251C00797B22 /* style_dialogs.h */, 0716C99E1D08251C00797B22 /* style_history.cpp */, 0716C99F1D08251C00797B22 /* style_history.h */, + 07E102D41D3E4BE800BD33B1 /* style_mediaview.cpp */, + 07E102D51D3E4BE800BD33B1 /* style_mediaview.h */, 078500381CC94D9600168DBB /* style_overview.cpp */, 078500391CC94D9600168DBB /* style_overview.h */, 0716C95E1D058C6600797B22 /* style_profile.cpp */, 0716C95F1D058C6600797B22 /* style_profile.h */, + 07E102D71D3E4CB800BD33B1 /* style_widgets.cpp */, + 07E102D81D3E4CB800BD33B1 /* style_widgets.h */, ); name = styles; sourceTree = ""; @@ -1204,6 +1270,65 @@ name = toast; sourceTree = ""; }; + 07E102B91D3E4B4900BD33B1 /* media */ = { + isa = PBXGroup; + children = ( + 07E102BB1D3E4B5A00BD33B1 /* view */, + 07D7034919B8755A00C4EED2 /* media_audio.cpp */, + 07D7034A19B8755A00C4EED2 /* media_audio.h */, + 07E102BC1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.cpp */, + 07E102BD1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.h */, + 07E102BE1D3E4B7F00BD33B1 /* media_audio_loader.cpp */, + 07E102BF1D3E4B7F00BD33B1 /* media_audio_loader.h */, + 07E102C01D3E4B7F00BD33B1 /* media_audio_loaders.cpp */, + 07E102C11D3E4B7F00BD33B1 /* media_audio_loaders.h */, + 07E102C21D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.cpp */, + 07E102C31D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.h */, + 07E102C41D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp */, + 07E102C51D3E4B7F00BD33B1 /* media_clip_ffmpeg.h */, + 07E102C61D3E4B7F00BD33B1 /* media_clip_implementation.cpp */, + 07E102C71D3E4B7F00BD33B1 /* media_clip_implementation.h */, + 07E102C81D3E4B7F00BD33B1 /* media_clip_qtgif.cpp */, + 07E102C91D3E4B7F00BD33B1 /* media_clip_qtgif.h */, + 07E102CA1D3E4B7F00BD33B1 /* media_clip_reader.cpp */, + 07E102CB1D3E4B7F00BD33B1 /* media_clip_reader.h */, + ); + name = media; + sourceTree = ""; + }; + 07E102BB1D3E4B5A00BD33B1 /* view */ = { + isa = PBXGroup; + children = ( + 07E102E41D3E4E3B00BD33B1 /* media_clip_controller.cpp */, + 07E102E51D3E4E3B00BD33B1 /* media_clip_controller.h */, + 07E102E61D3E4E3B00BD33B1 /* media_clip_playback.cpp */, + 07E102E71D3E4E3B00BD33B1 /* media_clip_playback.h */, + 07E102E81D3E4E3B00BD33B1 /* media_clip_volume_controller.cpp */, + 07E102E91D3E4E3B00BD33B1 /* media_clip_volume_controller.h */, + 07E102EA1D3E4E3B00BD33B1 /* mediaview.style */, + ); + name = view; + sourceTree = ""; + }; + 07E102EF1D3E4E5E00BD33B1 /* effects */ = { + isa = PBXGroup; + children = ( + 07E102F11D3E4E7F00BD33B1 /* fade_animation.cpp */, + 07E102F21D3E4E7F00BD33B1 /* fade_animation.h */, + ); + name = effects; + sourceTree = ""; + }; + 07E102F01D3E4E6900BD33B1 /* widgets */ = { + isa = PBXGroup; + children = ( + 07E102F41D3E4E8C00BD33B1 /* label_simple.cpp */, + 07E102F51D3E4E8C00BD33B1 /* label_simple.h */, + 07E102F61D3E4E8C00BD33B1 /* widgets.style */, + ); + name = widgets; + sourceTree = ""; + }; 07E1B1731D12DAC000722BC7 /* platform */ = { isa = PBXGroup; children = ( @@ -1265,6 +1390,8 @@ children = ( 0716C9A81D0825A800797B22 /* history_down_button.cpp */, 0716C9A91D0825A800797B22 /* history_down_button.h */, + 07E102F91D3E4E9900BD33B1 /* icon_button.cpp */, + 07E102FA1D3E4E9900BD33B1 /* icon_button.h */, 0716C9841D0594C000797B22 /* left_outline_button.cpp */, 0716C9851D0594C000797B22 /* left_outline_button.h */, 07E373921CBBC11000934F77 /* peer_avatar_button.cpp */, @@ -1332,9 +1459,11 @@ isa = PBXGroup; children = ( 07E373901CBBBFDE00934F77 /* buttons */, + 07E102EF1D3E4E5E00BD33B1 /* effects */, 0785002B1CC94CF500168DBB /* style */, 076B1C471CBFBEB4002C0BC2 /* text */, 07C8FE071CB80884007A8702 /* toast */, + 07E102F01D3E4E6900BD33B1 /* widgets */, D3FE9C29B6A61D7C3C4B731B /* animation.cpp */, 85FABD67716E36CD8B3CA4FA /* animation.h */, 08A7682548FB7E671FF03822 /* boxshadow.cpp */, @@ -1400,6 +1529,7 @@ 076B1C561CBFC8C9002C0BC2 /* history */, 07C8FDF81CB66D80007A8702 /* inline_bots */, 5E35A03E5F2C51353EBCBF00 /* intro */, + 07E102B91D3E4B4900BD33B1 /* media */, 1A6AA22F4A758C4B5F5138FB /* mtproto */, 076B1C5C1CBFC97D002C0BC2 /* overview */, 07E1B1731D12DAC000722BC7 /* platform */, @@ -1413,8 +1543,6 @@ C19DF71B273A4843553518F2 /* app.h */, C20F9DD8C7B031B8E20D5653 /* application.cpp */, 09FD01F2BD652EB838A296D8 /* application.h */, - 07D7034919B8755A00C4EED2 /* media_audio.cpp */, - 07D7034A19B8755A00C4EED2 /* media_audio.h */, 07C7596D1B1F7E0000662169 /* autoupdater.cpp */, 07C7596E1B1F7E0000662169 /* autoupdater.h */, 206B4F5CBD5354BCE19FF32F /* countries.h */, @@ -1500,7 +1628,6 @@ A1479F94376F9732B57C69DB /* moc_animation.cpp */, 0764D55C1ABAD71B00FBFEED /* moc_apiwrap.cpp */, E181C525E21A16F2D4396CA7 /* moc_application.cpp */, - 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */, 07DE92A91AA4928200A18F6F /* moc_autolockbox.cpp */, 07C759711B1F7E2800662169 /* moc_autoupdater.cpp */, 078A2FC91A811C5900CCC7A0 /* moc_backgroundbox.cpp */, @@ -1545,6 +1672,12 @@ 3A220FD1AE5AD9FE3DC073A4 /* moc_mainwidget.cpp */, 6B46A0EE3C3B9D3B5A24946E /* moc_mainwindow.cpp */, 07A6933419927B160099CB9F /* moc_mediaview.cpp */, + 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */, + 07E102DA1D3E4DE800BD33B1 /* moc_media_audio_loaders.cpp */, + 07E102DB1D3E4DE800BD33B1 /* moc_media_clip_controller.cpp */, + 07E102DC1D3E4DE800BD33B1 /* moc_media_clip_playback.cpp */, + 07E102DD1D3E4DE800BD33B1 /* moc_media_clip_reader.cpp */, + 07E102DE1D3E4DE800BD33B1 /* moc_media_clip_volume_controller.cpp */, 0732E4AB199E268A00D50FE7 /* moc_overviewwidget.cpp */, 07DE92AB1AA4928B00A18F6F /* moc_passcodebox.cpp */, 07DE92AC1AA4928B00A18F6F /* moc_passcodewidget.cpp */, @@ -1811,9 +1944,11 @@ 0716C92A1D05893900797B22 /* basic.style in Resources */, 0716C9281D05893900797B22 /* all_files.style in Resources */, 0716C9581D0589A700797B22 /* profile.style in Resources */, + 07E102F81D3E4E8C00BD33B1 /* widgets.style in Resources */, 0716C95A1D0589B600797B22 /* overview.style in Resources */, 0716C9291D05893900797B22 /* basic_types.style in Resources */, 07D7EABA1A597DD000838BA2 /* Localizable.strings in Resources */, + 07E102EE1D3E4E3B00BD33B1 /* mediaview.style in Resources */, 0716C9A71D08258A00797B22 /* history.style in Resources */, 0716C99A1D08225000797B22 /* dialogs.style in Resources */, ); @@ -1935,12 +2070,14 @@ 076B1C5F1CBFC98F002C0BC2 /* overview_layout.cpp in Compile Sources */, 0716C9731D058C8600797B22 /* moc_profile_fixed_bar.cpp in Compile Sources */, 0716C9811D058F2400797B22 /* slide_animation.cpp in Compile Sources */, + 07E102D31D3E4B7F00BD33B1 /* media_clip_reader.cpp in Compile Sources */, 6EF5A4ECC0EF19EA016EBA3E /* dropdown.cpp in Compile Sources */, 6E4DB0CBEF415196AFD4149F /* fileuploader.cpp in Compile Sources */, 700925F3B2C6163D38140CEA /* history.cpp in Compile Sources */, 0732E4A9199E262300D50FE7 /* overviewwidget.cpp in Compile Sources */, 0716C9991D08225000797B22 /* dialogs_row.cpp in Compile Sources */, 077A4B041CA41EE2002188D2 /* moc_connection_auto.cpp in Compile Sources */, + 07E102D91D3E4CB800BD33B1 /* style_widgets.cpp in Compile Sources */, 1DF53374E3B6A31661548D08 /* historywidget.cpp in Compile Sources */, 078A2FCD1A811CA600CCC7A0 /* backgroundbox.cpp in Compile Sources */, 37A3C6C782A0E4BC7B09536B /* langloaderplain.cpp in Compile Sources */, @@ -1957,6 +2094,7 @@ 0716C94F1D0589A700797B22 /* profile_info_widget.cpp in Compile Sources */, B99CCE43EEFCD3E18F6D16D1 /* settingswidget.cpp in Compile Sources */, B8DA82DA1B195A933A0805E7 /* sysbuttons.cpp in Compile Sources */, + 07E102FB1D3E4E9900BD33B1 /* icon_button.cpp in Compile Sources */, 5CE57D44510AB2A11886AB52 /* title.cpp in Compile Sources */, 0716C97A1D058C8600797B22 /* moc_report_box.cpp in Compile Sources */, 077A4AF81CA41C38002188D2 /* connection_auto.cpp in Compile Sources */, @@ -1964,15 +2102,18 @@ 07E1B1931D12DED700722BC7 /* moc_main_window_mac.cpp in Compile Sources */, 68FFEB7CA30BF0149161B809 /* mainwindow.cpp in Compile Sources */, 0716C9831D05931400797B22 /* moc_section_widget.cpp in Compile Sources */, + 07E102CC1D3E4B7F00BD33B1 /* media_audio_ffmpeg_loader.cpp in Compile Sources */, 0CB7DE9A54CC9BF86FB7B5CA /* facade.cpp in Compile Sources */, 078500341CC94D1900168DBB /* style_core_color.cpp in Compile Sources */, 076B1C5B1CBFC8F1002C0BC2 /* top_bar_widget.cpp in Compile Sources */, DF259E9677CC63AF8754032B /* connection.cpp in Compile Sources */, 074FCB9119D36E60004C6EB2 /* moc_popupmenu.cpp in Compile Sources */, B6346B66B0A2228A91D8A5D9 /* dcenter.cpp in Compile Sources */, + 07E102CE1D3E4B7F00BD33B1 /* media_audio_loaders.cpp in Compile Sources */, 0755AEDF1AD12A80004D738A /* moc_sessionsbox.cpp in Compile Sources */, 07C8FE031CB66D97007A8702 /* inline_bot_result.cpp in Compile Sources */, 07129D6E1C16D245002DC495 /* facades.cpp in Compile Sources */, + 07E102F31D3E4E7F00BD33B1 /* fade_animation.cpp in Compile Sources */, B8CA3E1E11A7E0E7DF9E1CDE /* file_download.cpp in Compile Sources */, 07C8FE0E1CB80890007A8702 /* toast_manager.cpp in Compile Sources */, 0755AEDD1AD12A80004D738A /* moc_abstractbox.cpp in Compile Sources */, @@ -2007,6 +2148,7 @@ 0716C94B1D0589A700797B22 /* profile_block_widget.cpp in Compile Sources */, 076B1C4D1CBFBF59002C0BC2 /* text_block.cpp in Compile Sources */, 3AA6E7264581F82856FB37F7 /* text.cpp in Compile Sources */, + 07E102D61D3E4BE800BD33B1 /* style_mediaview.cpp in Compile Sources */, 077A4B061CA41EE2002188D2 /* moc_connection_tcp.cpp in Compile Sources */, FCE6518C548DF7BC82228A4A /* twidget.cpp in Compile Sources */, 071AD8D21C5E8E6D008C9E90 /* zip.c in Compile Sources */, @@ -2029,6 +2171,7 @@ F278C423357CA99797EA30AB /* photosendbox.cpp in Compile Sources */, E8D95529CED88F18818C9A8B /* introwidget.cpp in Compile Sources */, 9357E7B12AD6D88B157ACA05 /* introcode.cpp in Compile Sources */, + 07E102E11D3E4DE800BD33B1 /* moc_media_clip_playback.cpp in Compile Sources */, 4BF3F8D0797BC8A0C1FAD13C /* introphone.cpp in Compile Sources */, 4978DE680549639AE9AA9CA6 /* introsignup.cpp in Compile Sources */, 076B1C551CBFC6F2002C0BC2 /* click_handler.cpp in Compile Sources */, @@ -2040,21 +2183,25 @@ C1F9D5CA8AF3AD8EBC9D7310 /* moc_application.cpp in Compile Sources */, 07C8FE011CB66D97007A8702 /* inline_bot_layout_internal.cpp in Compile Sources */, D846C6F212B438DC2FD5FF71 /* moc_dialogswidget.cpp in Compile Sources */, + 07E102ED1D3E4E3B00BD33B1 /* media_clip_volume_controller.cpp in Compile Sources */, 0702E9A51CB8D2A8007A7495 /* serialize_common.cpp in Compile Sources */, 6C79FBC5CFA36AC3EA6ABBD4 /* moc_dropdown.cpp in Compile Sources */, 077A4AFA1CA41C38002188D2 /* connection_tcp.cpp in Compile Sources */, 822C2860FBFAF3EAE42C5A3F /* moc_fileuploader.cpp in Compile Sources */, 352349751855EF76DECA4D60 /* moc_historywidget.cpp in Compile Sources */, + 07E102EB1D3E4E3B00BD33B1 /* media_clip_controller.cpp in Compile Sources */, 4B0036C794BEA27AF9419768 /* moc_layerwidget.cpp in Compile Sources */, 07C8FE101CB80890007A8702 /* toast.cpp in Compile Sources */, 077A4AF71CA41C38002188D2 /* connection_abstract.cpp in Compile Sources */, 071AD8F81C5E99D6008C9E90 /* ioapi.c in Compile Sources */, 074756191A1372C600CA07F7 /* moc_basic_types.cpp in Compile Sources */, + 07E102D01D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp in Compile Sources */, 98E4F55DB5D8E64AB9F08C83 /* moc_localimageloader.cpp in Compile Sources */, A24E4B5B683764E07683ECEC /* moc_mainwidget.cpp in Compile Sources */, 0710CA051B0B9404001B4272 /* moc_stickersetbox.cpp in Compile Sources */, 0716C9AA1D0825A800797B22 /* history_down_button.cpp in Compile Sources */, 0716C94A1D0589A700797B22 /* profile_actions_widget.cpp in Compile Sources */, + 07E102DF1D3E4DE800BD33B1 /* moc_media_audio_loaders.cpp in Compile Sources */, 0716C9761D058C8600797B22 /* moc_profile_settings_widget.cpp in Compile Sources */, 0716C9A11D08251C00797B22 /* style_history.cpp in Compile Sources */, 0716C94D1D0589A700797B22 /* profile_cover.cpp in Compile Sources */, @@ -2069,6 +2216,7 @@ FD2FE0C564A7389A2E609EC7 /* moc_sysbuttons.cpp in Compile Sources */, E97B3CFAB59B49BACFFC5F7C /* moc_title.cpp in Compile Sources */, 07D8510819F8340A00623D75 /* moc_usernamebox.cpp in Compile Sources */, + 07E102D11D3E4B7F00BD33B1 /* media_clip_implementation.cpp in Compile Sources */, 0716C9711D058C8600797B22 /* moc_profile_block_widget.cpp in Compile Sources */, 9A0D5DDC7816FC2538EB6A96 /* moc_mainwindow.cpp in Compile Sources */, 0716C9501D0589A700797B22 /* profile_inner_widget.cpp in Compile Sources */, @@ -2078,9 +2226,11 @@ 07DE92AA1AA4928200A18F6F /* moc_autolockbox.cpp in Compile Sources */, 0716C9551D0589A700797B22 /* profile_shared_media_widget.cpp in Compile Sources */, 07B604351B46A20900CA29FE /* moc_playerwidget.cpp in Compile Sources */, + 07E102CF1D3E4B7F00BD33B1 /* media_child_ffmpeg_loader.cpp in Compile Sources */, 8F6F5D7F82036331E8C6DAE6 /* moc_connection.cpp in Compile Sources */, 0716C9531D0589A700797B22 /* profile_section_memento.cpp in Compile Sources */, 075CDF6E1D09E2D0009EA100 /* data_abstract_structure.cpp in Compile Sources */, + 07E102CD1D3E4B7F00BD33B1 /* media_audio_loader.cpp in Compile Sources */, B780F9E21269259B90A1F32A /* moc_dcenter.cpp in Compile Sources */, 07080BCF1A43588C00741A51 /* lang_auto.cpp in Compile Sources */, 07539B1D1A1416AF00083EFC /* moc_history.cpp in Compile Sources */, @@ -2098,9 +2248,12 @@ 0716C9801D058F2400797B22 /* section_widget.cpp in Compile Sources */, B2F5B08BFFBBE7E37D3863BB /* moc_button.cpp in Compile Sources */, 0716C9901D05954900797B22 /* observer.cpp in Compile Sources */, + 07E102E21D3E4DE800BD33B1 /* moc_media_clip_reader.cpp in Compile Sources */, 0716C9A01D08251C00797B22 /* style_dialogs.cpp in Compile Sources */, 0716C94E1D0589A700797B22 /* profile_fixed_bar.cpp in Compile Sources */, 6A8BC88AB464B92706EFE6FF /* moc_countryinput.cpp in Compile Sources */, + 07E102D21D3E4B7F00BD33B1 /* media_clip_qtgif.cpp in Compile Sources */, + 07E102F71D3E4E8C00BD33B1 /* label_simple.cpp in Compile Sources */, 0764D55A1ABAD6F900FBFEED /* apiwrap.cpp in Compile Sources */, 07DE92A01AA4923300A18F6F /* passcodewidget.cpp in Compile Sources */, 07E373941CBBC11000934F77 /* peer_avatar_button.cpp in Compile Sources */, @@ -2128,6 +2281,7 @@ 7062978F12EEA525893A5E6F /* moc_aboutbox.cpp in Compile Sources */, E8B28580819B882A5964561A /* moc_addcontactbox.cpp in Compile Sources */, 07B604321B46A0EC00CA29FE /* playerwidget.cpp in Compile Sources */, + 07E102E31D3E4DE800BD33B1 /* moc_media_clip_volume_controller.cpp in Compile Sources */, D6874C00733283846ACA9AB2 /* moc_confirmbox.cpp in Compile Sources */, 075FEBEC1C82336D0003ECA3 /* shortcuts.cpp in Compile Sources */, 07B817091CB9A235006F7869 /* dialogs_indexed_list.cpp in Compile Sources */, @@ -2149,11 +2303,13 @@ 0F7872E39EA570249D420912 /* moc_introwidget.cpp in Compile Sources */, 4F27F5F76AA3F78C8CA27339 /* moc_introcode.cpp in Compile Sources */, 07D8509519F5C97E00623D75 /* scheme_auto.cpp in Compile Sources */, + 07E102EC1D3E4E3B00BD33B1 /* media_clip_playback.cpp in Compile Sources */, 0747FF7F1CC6435100096FC3 /* style_basic.cpp in Compile Sources */, 0250AB6761AC71A2E3155EEA /* moc_introphone.cpp in Compile Sources */, 07E1B1AD1D1847C400722BC7 /* moc_inner_dropdown.cpp in Compile Sources */, 07D8509419F5C97E00623D75 /* core_types.cpp in Compile Sources */, 2EF5D0AC9A18F9FE9B8A1ACA /* moc_introsignup.cpp in Compile Sources */, + 07E102E01D3E4DE800BD33B1 /* moc_media_clip_controller.cpp in Compile Sources */, 07DE92AE1AA4928B00A18F6F /* moc_passcodewidget.cpp in Compile Sources */, FA603B17F803E8D6B55C2F2B /* pspecific_mac_p.mm in Compile Sources */, 07080BD21A436A5000741A51 /* lang.cpp in Compile Sources */, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index 2cc42ee18..7250e65e8 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -99,7 +99,11 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_audio_loaders.cpp\ + GeneratedFiles/Debug/moc_media_clip_controller.cpp\ + GeneratedFiles/Debug/moc_media_clip_playback.cpp\ GeneratedFiles/Debug/moc_media_clip_reader.cpp\ + GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -238,7 +242,11 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_audio_loaders.cpp\ + GeneratedFiles/Debug/moc_media_clip_controller.cpp\ + GeneratedFiles/Debug/moc_media_clip_playback.cpp\ GeneratedFiles/Debug/moc_media_clip_reader.cpp\ + GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -320,7 +328,11 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_audio_loaders.cpp\ + GeneratedFiles/Debug/moc_media_clip_controller.cpp\ + GeneratedFiles/Debug/moc_media_clip_playback.cpp\ GeneratedFiles/Debug/moc_media_clip_reader.cpp\ + GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -501,9 +513,21 @@ GeneratedFiles/Debug/moc_mainwindow.cpp: SourceFiles/mainwindow.h GeneratedFiles/Debug/moc_media_audio.cpp: SourceFiles/media/media_audio.h $(MOC_FILE) SourceFiles/media/media_audio.h -o GeneratedFiles/Debug/moc_media_audio.cpp +GeneratedFiles/Debug/moc_media_audio_loaders.cpp: SourceFiles/media/media_audio_loaders.h + $(MOC_FILE) SourceFiles/media/media_audio_loaders.h -o GeneratedFiles/Debug/moc_media_audio_loaders.cpp + +GeneratedFiles/Debug/moc_media_clip_controller.cpp: SourceFiles/media/view/media_clip_controller.h + $(MOC_FILE) SourceFiles/media/view/media_clip_controller.h -o GeneratedFiles/Debug/moc_media_clip_controller.cpp + +GeneratedFiles/Debug/moc_media_clip_playback.cpp: SourceFiles/media/view/media_clip_playback.h + $(MOC_FILE) SourceFiles/media/view/media_clip_playback.h -o GeneratedFiles/Debug/moc_media_clip_playback.cpp + GeneratedFiles/Debug/moc_media_clip_reader.cpp: SourceFiles/media/media_clip_reader.h $(MOC_FILE) SourceFiles/media/media_clip_reader.h -o GeneratedFiles/Debug/moc_media_clip_reader.cpp +GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp: SourceFiles/media/view/media_clip_volume_controller.h + $(MOC_FILE) SourceFiles/media/view/media_clip_volume_controller.h -o GeneratedFiles/Debug/moc_media_clip_volume_controller.cpp + GeneratedFiles/Debug/moc_mediaview.cpp: SourceFiles/mediaview.h $(MOC_FILE) SourceFiles/mediaview.h -o GeneratedFiles/Debug/moc_mediaview.cpp From 92f15a9ad3fe5c11fb2efd776d81b41e8fb840c8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 19:02:39 +0300 Subject: [PATCH 30/60] Fixed clang false warning on deprecated field of AVPacket. --- .../SourceFiles/media/media_audio_loaders.cpp | 6 +++-- .../SourceFiles/media/media_audio_loaders.h | 10 ++----- .../media/media_child_ffmpeg_loader.cpp | 11 +++++--- .../media/media_child_ffmpeg_loader.h | 22 +++++++++++++-- .../SourceFiles/media/media_clip_ffmpeg.cpp | 27 ++++++++++++------- .../SourceFiles/media/media_clip_ffmpeg.h | 12 ++++----- 6 files changed, 56 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index 83ec07544..5b1805b93 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -34,7 +34,7 @@ void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) { { QMutexLocker lock(&_fromVideoMutex); if (_fromVideoPlayId == part.videoPlayId) { - _fromVideoQueue.enqueue(*part.packet); + _fromVideoQueue.enqueue(FFMpeg::dataWrapFromPacket(*part.packet)); invoke = true; } else { FFMpeg::freePacket(part.packet); @@ -76,7 +76,9 @@ AudioPlayerLoaders::~AudioPlayerLoaders() { void AudioPlayerLoaders::clearFromVideoQueue() { auto queue = createAndSwap(_fromVideoQueue); - for (auto &packet : queue) { + for (auto &packetData : queue) { + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, packetData); FFMpeg::freePacket(&packet); } } diff --git a/Telegram/SourceFiles/media/media_audio_loaders.h b/Telegram/SourceFiles/media/media_audio_loaders.h index 1aaebd514..50a282b59 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.h +++ b/Telegram/SourceFiles/media/media_audio_loaders.h @@ -22,13 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/media_child_ffmpeg_loader.h" #include "media/media_audio.h" - -extern "C" { -#include -#include -#include -#include -} // extern "C" +#include "media/media_child_ffmpeg_loader.h" class AudioPlayerLoader; class ChildFFMpegLoader; @@ -65,7 +59,7 @@ private: QMutex _fromVideoMutex; uint64 _fromVideoPlayId; - QQueue _fromVideoQueue; + QQueue _fromVideoQueue; SingleDelayedCall _fromVideoNotify; void emitError(AudioMsgId::Type type); diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index 4ec96b1c1..5016b1b4f 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -120,7 +120,10 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, in av_frame_unref(_frame); int got_frame = 0; int res = 0; - auto packet = _queue.dequeue(); + + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, _queue.dequeue()); + _eofReached = FFMpeg::isNullPacket(packet); if (_eofReached) { return ReadResult::EndOfFile; @@ -172,14 +175,16 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, in return ReadResult::Ok; } -void ChildFFMpegLoader::enqueuePackets(QQueue &packets) { +void ChildFFMpegLoader::enqueuePackets(QQueue &packets) { _queue += std_::move(packets); packets.clear(); } ChildFFMpegLoader::~ChildFFMpegLoader() { auto queue = createAndSwap(_queue); - for (auto &packet : queue) { + for (auto &packetData : queue) { + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, packetData); FFMpeg::freePacket(&packet); } if (_swrContext) swr_free(&_swrContext); diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h index 903893c1d..69926bfb8 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -45,6 +45,24 @@ struct VideoSoundPart { namespace FFMpeg { +// AVPacket has a deprecated field, so when you copy an AVPacket +// variable (e.g. inside QQueue), a compile warning is emited. +// We wrap full AVPacket data in a new AVPacketDataWrap struct. +// All other fields are copied from AVPacket without modifications. +struct AVPacketDataWrap { + char __data[sizeof(AVPacket)]; +}; + +inline void packetFromDataWrap(AVPacket &packet, const AVPacketDataWrap &data) { + memcpy(&packet, &data, sizeof(data)); +} + +inline AVPacketDataWrap dataWrapFromPacket(const AVPacket &packet) { + AVPacketDataWrap data; + memcpy(&data, &packet, sizeof(data)); + return data; +} + inline bool isNullPacket(const AVPacket &packet) { return packet.data == nullptr && packet.size == 0; } @@ -84,7 +102,7 @@ public: } ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; - void enqueuePackets(QQueue &packets); + void enqueuePackets(QQueue &packets); uint64 playId() const { return _videoPlayId; @@ -111,6 +129,6 @@ private: AVFrame *_frame = nullptr; SwrContext *_swrContext = nullptr; - QQueue _queue; + QQueue _queue; }; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index ab29e6975..8e321eb16 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -56,12 +56,13 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { startPacket(); int got_frame = 0; - int decoded = 0; auto packet = &_packetNull; + AVPacket tempPacket; if (!_packetQueue.isEmpty()) { - packet = &_packetQueue.head(); - decoded = packet->size; + FFMpeg::packetFromDataWrap(tempPacket, _packetQueue.head()); + packet = &tempPacket; } + int decoded = packet->size; int res = 0; if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, packet)) < 0) { @@ -427,7 +428,7 @@ void FFMpegReaderImplementation::processPacket(AVPacket *packet) { _lastReadPacketMs = countPacketMs(packet); if (videoPacket) { - _packetQueue.enqueue(*packet); + _packetQueue.enqueue(FFMpeg::dataWrapFromPacket(*packet)); } else if (audioPacket) { // queue packet to audio player VideoSoundPart part; @@ -457,18 +458,22 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readAndProc void FFMpegReaderImplementation::startPacket() { if (!_packetStarted && !_packetQueue.isEmpty()) { - _packetStartedSize = _packetQueue.head().size; - _packetStartedData = _packetQueue.head().data; + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); + _packetStartedSize = packet.size; + _packetStartedData = packet.data; _packetStarted = true; } } void FFMpegReaderImplementation::finishPacket() { if (_packetStarted) { - _packetQueue.head().size = _packetStartedSize; - _packetQueue.head().data = _packetStartedData; + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); + packet.size = _packetStartedSize; + packet.data = _packetStartedData; _packetStarted = false; - av_packet_unref(&_packetQueue.head()); + av_packet_unref(&packet); _packetQueue.dequeue(); } } @@ -476,7 +481,9 @@ void FFMpegReaderImplementation::finishPacket() { void FFMpegReaderImplementation::clearPacketQueue() { finishPacket(); auto packets = createAndSwap(_packetQueue); - for (auto &packet : packets) { + for (auto &packetData : packets) { + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, packetData); av_packet_unref(&packet); } } diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 7a9abbe9a..c73fdc8ac 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -21,15 +21,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once extern "C" { -#include -#include -#include + #include -} + +} // extern "C" #include "media/media_clip_implementation.h" - -struct VideoSoundData; +#include "media/media_child_ffmpeg_loader.h" namespace Media { namespace Clip { @@ -96,7 +94,7 @@ private: uint64 _playId = 0; int64 _lastReadPacketMs = 0; - QQueue _packetQueue; + QQueue _packetQueue; AVPacket _packetNull; // for final decoding int _packetStartedSize = 0; uint8_t *_packetStartedData = nullptr; From fa708ada3b9d64b3d65772621f7aa0ec4e99907a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 20:00:56 +0300 Subject: [PATCH 31/60] Closed beta 9057001 for testing MediaView video player. --- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/Telegram.vcxproj | 5 ----- Telegram/Telegram.vcxproj.filters | 3 --- Telegram/build/version | 4 ++-- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a04176115..4fbc4908a 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,0 - PRODUCTVERSION 0,9,57,0 + FILEVERSION 0,9,57,1 + PRODUCTVERSION 0,9,57,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.57.0" + VALUE "FileVersion", "0.9.57.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.0" + VALUE "ProductVersion", "0.9.57.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index edc6944fb..0c3a7892d 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,0 - PRODUCTVERSION 0,9,57,0 + FILEVERSION 0,9,57,1 + PRODUCTVERSION 0,9,57,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.57.0" + VALUE "FileVersion", "0.9.57.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.0" + VALUE "ProductVersion", "0.9.57.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 7e443e781..7baecf548 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,9 +22,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -#define BETA_VERSION_MACRO (0ULL) +#define BETA_VERSION_MACRO (9057001ULL) constexpr int AppVersion = 9057; constexpr str_const AppVersionStr = "0.9.57"; -constexpr bool AppAlphaVersion = true; +constexpr bool AppAlphaVersion = false; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index e060ac8f8..474bffb8e 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -354,11 +354,6 @@ true true - - true - true - true - true true diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 247d35253..61425a2d4 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1323,9 +1323,6 @@ GeneratedFiles\Deploy - - GeneratedFiles\Debug - GeneratedFiles\Release diff --git a/Telegram/build/version b/Telegram/build/version index 4a048eef7..5658212f2 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -2,5 +2,5 @@ AppVersion 9057 AppVersionStrMajor 0.9 AppVersionStrSmall 0.9.57 AppVersionStr 0.9.57 -AlphaChannel 1 -BetaVersion 0 +AlphaChannel 0 +BetaVersion 9057001 From e320be96262344bc02efba13433de58691e302b7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jul 2016 21:09:34 +0300 Subject: [PATCH 32/60] Fixed build for QtCreator and Ubuntu. --- .travis/build.sh | 4 +++- Telegram/SourceFiles/platform/linux/linux_libs.h | 2 +- .../platform/linux/main_window_linux.cpp | 4 ++-- Telegram/Telegram.pro | 14 +++++++++++--- Telegram/build/makefile_static.sh | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.travis/build.sh b/.travis/build.sh index a04781703..899316f66 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -141,6 +141,8 @@ build() { # Patch tdesktop sed -i 's/CUSTOM_API_ID//g' "$UPSTREAM/Telegram/Telegram.pro" + sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g" "$UPSTREAM/Telegram/Telegram.pro" + sed -i 's,#xkbcommon,xkbcommon,g" "$UPSTREAM/Telegram/Telegram.pro" sed -i "s,\..*/Libraries/breakpad/,$BREAKPAD_PATH/,g" "$UPSTREAM/Telegram/Telegram.pro" local options="" @@ -321,4 +323,4 @@ check() { source ./.travis/common.sh -run \ No newline at end of file +run diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index d51db1c67..6fd9a802f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -227,7 +227,7 @@ inline GtkWindow *gtk_window_cast(Object *obj) { return g_type_cic_helper(obj, gtk_window_get_type()); } -typedef gboolean (*f_g_type_check_instance_is_a)(GTypeInstance *instance, GType iface_type) G_GNUC_PURE; +typedef gboolean (*f_g_type_check_instance_is_a)(GTypeInstance *instance, GType iface_type); extern f_g_type_check_instance_is_a g_type_check_instance_is_a; template diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 5fe26b0fa..2647ea36e 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -360,8 +360,8 @@ void MainWindow::psUpdateCounter() { bool muted = App::histories().unreadOnlyMuted(); style::color bg = muted ? st::counterMuteBG : st::counterBG; - icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); - icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); + icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, true))); + icon.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, true))); } trayIcon->setIcon(icon); } diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 7e1fbb32f..4fe6d5d37 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -111,7 +111,6 @@ SOURCES += \ ./SourceFiles/apiwrap.cpp \ ./SourceFiles/app.cpp \ ./SourceFiles/application.cpp \ - ./SourceFiles/audio.cpp \ ./SourceFiles/autoupdater.cpp \ ./SourceFiles/dialogswidget.cpp \ ./SourceFiles/dropdown.cpp \ @@ -280,7 +279,6 @@ HEADERS += \ ./SourceFiles/apiwrap.h \ ./SourceFiles/app.h \ ./SourceFiles/application.h \ - ./SourceFiles/audio.h \ ./SourceFiles/autoupdater.h \ ./SourceFiles/config.h \ ./SourceFiles/countries.h \ @@ -534,7 +532,11 @@ PKGCONFIG += \ x11\ xi\ xext\ - xkbcommon\ +# In order to work libxkbcommon must be linked statically, +# PKGCONFIG links it like "-L/usr/local/lib -lxkbcommon" +# which makes a dynamic link which leads to segfault in +# QApplication() -> createPlatformIntegration -> QXcbIntegrationPlugin::create + #xkbcommon\ openal\ libavformat\ libavcodec\ @@ -556,6 +558,12 @@ LIBS += $${QT_TDESKTOP_PATH}/plugins/platforminputcontexts/libcomposeplatforminp LIBS += ./../../../Libraries/breakpad/src/client/linux/libbreakpad_client.a +# In order to work libxkbcommon must be linked statically, +# PKGCONFIG links it like "-L/usr/local/lib -lxkbcommon" +# which makes a dynamic link which leads to segfault in +# QApplication() -> createPlatformIntegration -> QXcbIntegrationPlugin::create +LIBS += /usr/local/lib/libxkbcommon.a + RESOURCES += \ ./Resources/telegram.qrc \ ./Resources/telegram_linux.qrc \ diff --git a/Telegram/build/makefile_static.sh b/Telegram/build/makefile_static.sh index e855ce828..da1c19c1a 100755 --- a/Telegram/build/makefile_static.sh +++ b/Telegram/build/makefile_static.sh @@ -58,7 +58,6 @@ Replace '\-lICE' "$ArchDirPath\/libICE\.a" Replace '\-lfontconfig' "$ArchDirPath\/libfontconfig\.a $ArchDirPath\/libexpat\.a" Replace '\-lfreetype' "$ArchDirPath\/libfreetype\.a" Replace '\-lXext' "$ArchDirPath\/libXext\.a" -Replace '\-lxkbcommon' "$LocalDirPath\/libxkbcommon\.a" Replace '\-lopus' "$LocalDirPath\/libopus\.a" Replace '\-lopenal' "$LocalDirPath\/libopenal\.a" Replace '\-lavformat' "$LocalDirPath\/libavformat\.a" @@ -66,4 +65,5 @@ Replace '\-lavcodec' "$LocalDirPath\/libavcodec\.a" Replace '\-lswresample' "$LocalDirPath\/libswresample\.a" Replace '\-lswscale' "$LocalDirPath\/libswscale\.a" Replace '\-lavutil' "$LocalDirPath\/libavutil\.a" +Replace '\-lva-x11' "$LocalDirPath\/libva-x11\.a" Replace '\-lva' "$LocalDirPath\/libva\.a" From 81850b78c74a06c4d4e37563f6b9420135ca9ea1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 21 Jul 2016 13:09:47 +0300 Subject: [PATCH 33/60] Writing installed, featured, recent and archived stickers separately. --- Telegram/SourceFiles/apiwrap.cpp | 9 +- Telegram/SourceFiles/app.cpp | 16 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 49 +- Telegram/SourceFiles/boxes/stickersetbox.h | 2 + Telegram/SourceFiles/dropdown.cpp | 16 +- Telegram/SourceFiles/historywidget.cpp | 16 +- Telegram/SourceFiles/localstorage.cpp | 526 +++++++++++-------- Telegram/SourceFiles/localstorage.h | 10 +- Telegram/SourceFiles/mainwidget.cpp | 33 +- 9 files changed, 423 insertions(+), 254 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 8d3a886c9..9ffde7881 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1005,7 +1005,14 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) Local::writeUserSettings(); } - Local::writeStickers(); + if (it->flags & MTPDstickerSet::Flag::f_installed) { + if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { + Local::writeInstalledStickers(); + } + } + if (it->flags & MTPDstickerSet_ClientFlag::f_featured) { + Local::writeFeaturedStickers(); + } if (App::main()) emit App::main()->stickersUpdated(); } diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 0c61a589d..f3c85d3e2 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1734,8 +1734,20 @@ namespace { App::main()->incrementSticker(result); } if (versionChanged) { - if (result->sticker()) { - Local::writeStickers(); + if (result->sticker() && result->sticker()->set.type() == mtpc_inputStickerSetID) { + auto it = Global::StickerSets().constFind(result->sticker()->set.c_inputStickerSetID().vid.v); + if (it != Global::StickerSets().cend()) { + if (it->id == Stickers::CloudRecentSetId) { + Local::writeRecentStickers(); + } else if (it->flags & MTPDstickerSet::Flag::f_archived) { + Local::writeArchivedStickers(); + } else if (it->flags & MTPDstickerSet::Flag::f_installed) { + Local::writeInstalledStickers(); + } + if (it->flags & MTPDstickerSet_ClientFlag::f_featured) { + Local::writeFeaturedStickers(); + } + } } auto &items = App::documentItems(); auto i = items.constFind(result); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 6f863e538..a1df6f957 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -35,7 +35,6 @@ namespace Stickers { void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { auto &v = d.vsets.c_vector().v; - auto &sets = Global::RefStickerSets(); auto &order = Global::RefStickerSetsOrder(); Stickers::Order archived; archived.reserve(v.size()); @@ -189,7 +188,7 @@ void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &res Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); } - Local::writeStickers(); + Local::writeInstalledStickers(); emit App::main()->stickersUpdated(); emit installed(_setId); } @@ -734,8 +733,8 @@ void StickersInner::onClearRecent() { } auto &sets = Global::RefStickerSets(); - sets.remove(Stickers::CloudRecentSetId); - sets.remove(Stickers::CustomSetId); + bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0); + bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0); auto &recent = cGetRecentStickers(); if (!recent.isEmpty()) { @@ -743,7 +742,8 @@ void StickersInner::onClearRecent() { Local::writeUserSettings(); } - Local::writeStickers(); + if (removedCustom) Local::writeInstalledStickers(); + if (removedCloud) Local::writeRecentStickers(); emit App::main()->updateStickers(); rebuild(); @@ -833,8 +833,10 @@ void StickersInner::installSet(uint64 setId) { MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersInner::installDone), rpcFail(&StickersInner::installFail, setId)); + auto flags = it->flags; it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread); it->flags |= MTPDstickerSet::Flag::f_installed; + auto changedFlags = flags ^ it->flags; auto &order = Global::RefStickerSetsOrder(); int insertAtIndex = 0, currentIndex = order.indexOf(setId); @@ -855,14 +857,16 @@ void StickersInner::installSet(uint64 setId) { sets.erase(custom); } } - Local::writeStickers(); + Local::writeInstalledStickers(); + if (changedFlags & MTPDstickerSet_ClientFlag::f_unread) Local::writeFeaturedStickers(); emit App::main()->stickersUpdated(); } void StickersInner::installDone(const MTPmessages_StickerSetInstallResult &result) { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); - Local::writeStickers(); + Local::writeInstalledStickers(); + Local::writeArchivedStickers(); emit App::main()->stickersUpdated(); } @@ -895,7 +899,7 @@ bool StickersInner::installFail(uint64 setId, const RPCError &error) { order.removeAt(currentIndex); } - Local::writeStickers(); + Local::writeInstalledStickers(); emit App::main()->stickersUpdated(); Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); @@ -1098,7 +1102,7 @@ void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) } void StickersInner::readFeaturedDone(const MTPBool &result) { - Local::writeStickers(); + Local::writeFeaturedStickers(); emit App::main()->stickersUpdated(); } @@ -1121,13 +1125,7 @@ Stickers::Order StickersInner::getOrder() const { Stickers::Order result; result.reserve(_rows.size()); for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->disabled) { - auto it = Global::StickerSets().constFind(_rows.at(i)->id); - if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_official)) { - continue; - } - } - if (_rows.at(i)->recent) { + if (_rows.at(i)->disabled || _rows.at(i)->recent) { continue; } result.push_back(_rows.at(i)->id); @@ -1161,6 +1159,9 @@ StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) , _inner(section) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) , _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) { + if (section == Section::Archived) { + Local::readArchivedStickers(); + } setup(); } @@ -1169,6 +1170,7 @@ StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::b , _inner(archivedIds) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) , _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { + Local::readArchivedStickers(); setup(); } @@ -1299,6 +1301,14 @@ void StickersBox::closePressed() { } } +StickersBox::~StickersBox() { + if (_section == Section::Featured) { + Local::writeFeaturedStickers(); + } else if (_section == Section::Archived) { + Local::writeArchivedStickers(); + } +} + void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); _inner->resize(width(), _inner->height()); @@ -1387,7 +1397,9 @@ void StickersBox::onSave() { // Clear all installed flags, set only for sets from order. for (auto &set : sets) { - set.flags &= ~MTPDstickerSet::Flag::f_installed; + if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; + } } auto &order(Global::RefStickerSetsOrder()); @@ -1407,6 +1419,7 @@ void StickersBox::onSave() { for (auto it = sets.begin(); it != sets.cend();) { if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) || (it->flags & MTPDstickerSet::Flag::f_installed) + || (it->flags & MTPDstickerSet::Flag::f_archived) || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { ++it; } else { @@ -1414,7 +1427,7 @@ void StickersBox::onSave() { } } - Local::writeStickers(); + Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); emit App::main()->stickersUpdated(); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 44d60e770..4dfc9fcb1 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -144,6 +144,8 @@ public: void closePressed(); + ~StickersBox(); + public slots: void onStickersUpdated(); diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 6d86f12d4..a3327c608 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -1518,7 +1518,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { if (it->stickers.isEmpty()) { sets.erase(it); } - Local::writeStickers(); + Local::writeInstalledStickers(); refresh = true; break; } @@ -3651,8 +3651,9 @@ void EmojiPan::onSwitch() { } void EmojiPan::onRemoveSet(quint64 setId) { - auto it = Global::StickerSets().constFind(setId); - if (it != Global::StickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { _removingSetId = it->id; ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove)); connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); @@ -3663,8 +3664,9 @@ void EmojiPan::onRemoveSet(quint64 setId) { void EmojiPan::onRemoveSetSure() { Ui::hideLayer(); - auto it = Global::RefStickerSets().find(_removingSetId); - if (it != Global::RefStickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(_removingSetId); + if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { if (it->id && it->access) { MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)))); } else if (!it->shortName.isEmpty()) { @@ -3682,12 +3684,12 @@ void EmojiPan::onRemoveSetSure() { } it->flags &= ~MTPDstickerSet::Flag::f_installed; if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { - Global::RefStickerSets().erase(it); + sets.erase(it); } int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); refreshStickers(); - Local::writeStickers(); + Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); } _removingSetId = 0; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 0e2bcfacb..59630d7fc 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3663,7 +3663,9 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { auto &sets = Global::RefStickerSets(); QMap setsToRequest; for (auto &set : sets) { - set.flags &= ~MTPDstickerSet::Flag::f_installed; // mark for removing + if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; // mark for removing + } } for_const (auto &setData, d_sets) { if (setData.type() == mtpc_stickerSet) { @@ -3682,6 +3684,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); + bool archived = (it->flags & MTPDstickerSet::Flag::f_archived); if (!installed) { // remove not mine sets from recent stickers for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { @@ -3692,7 +3695,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } } } - if (installed || featured || special) { + if (installed || featured || special || archived) { ++it; } else { it = sets.erase(it); @@ -3710,7 +3713,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { App::api()->requestStickerSets(); } - Local::writeStickers(); + Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); if (App::main()) emit App::main()->stickersUpdated(); @@ -3797,7 +3800,7 @@ void HistoryWidget::recentStickersGot(const MTPmessages_RecentStickers &stickers LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countRecentStickersHash())); } - Local::writeStickers(); + Local::writeRecentStickers(); if (App::main()) emit App::main()->stickersUpdated(); } @@ -3875,7 +3878,8 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic bool installed = (it->flags & MTPDstickerSet::Flag::f_installed); bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured); bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special); - if (installed || featured || special) { + bool archived = (it->flags & MTPDstickerSet::Flag::f_archived); + if (installed || featured || special || archived) { if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { ++unreadCount; } @@ -3897,7 +3901,7 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic App::api()->requestStickerSets(); } - Local::writeStickers(); + Local::writeFeaturedStickers(); if (App::main()) emit App::main()->stickersUpdated(); } diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 6ebdbea10..c365e6606 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -479,11 +479,12 @@ namespace { lskBackground = 0x08, // no data lskUserSettings = 0x09, // no data lskRecentHashtagsAndBots = 0x0a, // no data - lskStickers = 0x0b, // no data + lskStickersOld = 0x0b, // no data lskSavedPeers = 0x0c, // no data lskReportSpamStatuses = 0x0d, // no data lskSavedGifsOld = 0x0e, // no data lskSavedGifs = 0x0f, // no data + lskStickersKeys = 0x10, // no data }; enum { @@ -571,7 +572,9 @@ namespace { uint64 _storageWebFilesSize = 0; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; - FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey = 0; + FileKey _recentStickersKeyOld = 0; + FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _archivedStickersKey = 0; + FileKey _savedGifsKey = 0; FileKey _backgroundKey = 0; bool _backgroundWasRead = false; @@ -1724,7 +1727,9 @@ namespace { StorageMap imagesMap, stickerImagesMap, audiosMap; qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; quint64 locationsKey = 0, reportSpamStatusesKey = 0; - quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0; + quint64 recentStickersKeyOld = 0; + quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, archivedStickersKey = 0; + quint64 savedGifsKey = 0; quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; @@ -1805,8 +1810,11 @@ namespace { case lskRecentHashtagsAndBots: { map.stream >> recentHashtagsAndBotsKey; } break; - case lskStickers: { - map.stream >> stickersKey; + case lskStickersOld: { + map.stream >> installedStickersKey; + } break; + case lskStickersKeys: { + map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey; } break; case lskSavedGifsOld: { quint64 key; @@ -1841,7 +1849,10 @@ namespace { _locationsKey = locationsKey; _reportSpamStatusesKey = reportSpamStatusesKey; _recentStickersKeyOld = recentStickersKeyOld; - _stickersKey = stickersKey; + _installedStickersKey = installedStickersKey; + _featuredStickersKey = featuredStickersKey; + _recentStickersKey = recentStickersKey; + _archivedStickersKey = archivedStickersKey; _savedGifsKey = savedGifsKey; _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; @@ -1914,7 +1925,9 @@ namespace { if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); - if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + mapSize += sizeof(quint32) + 4 * sizeof(quint64); + } if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); @@ -1960,8 +1973,9 @@ namespace { if (_recentStickersKeyOld) { mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); } - if (_stickersKey) { - mapData.stream << quint32(lskStickers) << quint64(_stickersKey); + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + mapData.stream << quint32(lskStickersKeys); + mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey); } if (_savedGifsKey) { mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); @@ -2247,7 +2261,9 @@ namespace Local { _webFilesMap.clear(); _storageWebFilesSize = 0; _locationsKey = _reportSpamStatusesKey = 0; - _recentStickersKeyOld = _stickersKey = _savedGifsKey = 0; + _recentStickersKeyOld = 0; + _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; + _savedGifsKey = 0; _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; _mapChanged = true; @@ -3017,26 +3033,23 @@ namespace Local { } } - void _writeStickerSet(QDataStream &stream, uint64 setId) { - auto it = Global::StickerSets().constFind(setId); - if (it == Global::StickerSets().cend()) return; - - bool notLoaded = (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded); + void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) { + bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); if (notLoaded) { - stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(-it->count) << qint32(it->hash) << qint32(it->flags); + stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(-set.count) << qint32(set.hash) << qint32(set.flags); return; } else { - if (it->stickers.isEmpty()) return; + if (set.stickers.isEmpty()) return; } - stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(it->stickers.size()) << qint32(it->hash) << qint32(it->flags); - for (StickerPack::const_iterator j = it->stickers.cbegin(), e = it->stickers.cend(); j != e; ++j) { + stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(set.stickers.size()) << qint32(set.hash) << qint32(set.flags); + for (StickerPack::const_iterator j = set.stickers.cbegin(), e = set.stickers.cend(); j != e; ++j) { Serialize::Document::writeToStream(stream, *j); } if (AppVersion > 9018) { - stream << qint32(it->emoji.size()); - for (StickersByEmojiMap::const_iterator j = it->emoji.cbegin(), e = it->emoji.cend(); j != e; ++j) { + stream << qint32(set.emoji.size()); + for (StickersByEmojiMap::const_iterator j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { stream << emojiString(j.key()) << qint32(j->size()); for (int32 k = 0, l = j->size(); k < l; ++k) { stream << quint64(j->at(k)->id); @@ -3045,67 +3058,274 @@ namespace Local { } } - void writeStickers() { + // In generic method _writeStickerSets() we look through all the sets and call a + // callback on each set to see, if we write it, skip it or abort the whole write. + enum class StickerSetCheckResult { + Write, + Skip, + Abort, + }; + + // CheckSet is a functor on Stickers::Set, which returns a StickerSetCheckResult. + template + void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) { if (!_working()) return; auto &sets = Global::StickerSets(); if (sets.isEmpty()) { - if (_stickersKey) { - clearKey(_stickersKey); - _stickersKey = 0; + if (stickersKey) { + clearKey(stickersKey); + stickersKey = 0; _mapChanged = true; } _writeMap(); - } else { - int32 setsCount = 0; - QByteArray hashToWrite; - quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); - for_const (auto &set, sets) { - bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); - if (notLoaded && !(set.flags & MTPDstickerSet_ClientFlag::f_special)) { - if (!(set.flags & MTPDstickerSet::Flag::f_archived) - || (set.flags & MTPDstickerSet::Flag::f_official) - || (set.flags & MTPDstickerSet_ClientFlag::f_featured)) { // waiting to receive - return; - } - } else { - if (set.stickers.isEmpty()) continue; - } - - // id + access + title + shortName + stickersCount + hash + flags - size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2; - for_const (auto &sticker, set.stickers) { - size += Serialize::Document::sizeInStream(sticker); - } - - if (AppVersion > 9018) { - size += sizeof(qint32); // emojiCount - for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { - size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); - } - } - - ++setsCount; - } - size += sizeof(qint32) + (Global::StickerSetsOrder().size() * sizeof(quint64)); - size += sizeof(qint32) + (Global::FeaturedStickerSetsOrder().size() * sizeof(quint64)); - - if (!_stickersKey) { - _stickersKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - EncryptedDescriptor data(size); - data.stream << quint32(setsCount) << hashToWrite; - for_const (auto &set, sets) { - _writeStickerSet(data.stream, set.id); - } - data.stream << Global::StickerSetsOrder(); - data.stream << Global::FeaturedStickerSetsOrder(); - - FileWriteDescriptor file(_stickersKey); - file.writeEncrypted(data); + return; } + int32 setsCount = 0; + QByteArray hashToWrite; + quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); + for_const (auto &set, sets) { + auto result = checkSet(set); + if (result == StickerSetCheckResult::Abort) { + return; + } else if (result == StickerSetCheckResult::Skip) { + continue; + } + + // id + access + title + shortName + stickersCount + hash + flags + size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2; + for_const (auto &sticker, set.stickers) { + size += Serialize::Document::sizeInStream(sticker); + } + + size += sizeof(qint32); // emojiCount + for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { + size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); + } + + ++setsCount; + } + if (!setsCount && order.isEmpty()) { + if (stickersKey) { + clearKey(stickersKey); + stickersKey = 0; + _mapChanged = true; + } + _writeMap(); + return; + } + size += sizeof(qint32) + (order.size() * sizeof(quint64)); + + if (!stickersKey) { + stickersKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + EncryptedDescriptor data(size); + data.stream << quint32(setsCount) << hashToWrite; + for_const (auto &set, sets) { + auto result = checkSet(set); + if (result == StickerSetCheckResult::Abort) { + return; + } else if (result == StickerSetCheckResult::Skip) { + continue; + } + _writeStickerSet(data.stream, set); + } + data.stream << order; + + FileWriteDescriptor file(stickersKey); + file.writeEncrypted(data); + } + + void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) { + FileReadDescriptor stickers; + if (!readEncryptedFile(stickers, stickersKey)) { + clearKey(stickersKey); + stickersKey = 0; + _writeMap(); + return; + } + + bool readingInstalled = (readingFlags == qFlags(MTPDstickerSet::Flag::f_installed)); + + auto &sets = Global::RefStickerSets(); + if (outOrder) outOrder->clear(); + + quint32 cnt; + QByteArray hash; + stickers.stream >> cnt >> hash; // ignore hash, it is counted + if (readingInstalled && stickers.version < 8019) { // bad data in old caches + cnt += 2; // try to read at least something + } + for (uint32 i = 0; i < cnt; ++i) { + quint64 setId = 0, setAccess = 0; + QString setTitle, setShortName; + qint32 scnt = 0; + stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; + + qint32 setHash = 0, setFlags = 0; + if (stickers.version > 8033) { + stickers.stream >> setHash >> setFlags; + if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) { + setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); + } + } + if (readingInstalled && stickers.version < 9058) { + setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); + } + + if (setId == Stickers::DefaultSetId) { + setTitle = lang(lng_stickers_default_set); + setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special); + if (readingInstalled && outOrder && stickers.version < 9058) { + outOrder->push_front(setId); + } + } else if (setId == Stickers::CustomSetId) { + setTitle = lang(lng_custom_stickers); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); + } else if (setId == Stickers::CloudRecentSetId) { + setTitle = lang(lng_emoji_category0); // Frequently used + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); + } else if (setId) { + if (readingInstalled && outOrder && stickers.version < 9058) { + outOrder->push_back(setId); + } + } else { + continue; + } + + auto it = sets.find(setId); + if (it == sets.cend()) { + // We will set this flags from order lists when reading those stickers. + setFlags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured); + it = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))); + } + auto &set = it.value(); + + if (scnt < 0) { // disabled not loaded set + if (!set.count || set.stickers.isEmpty()) { + set.count = -scnt; + } + continue; + } + + bool fillStickers = set.stickers.isEmpty(); + if (fillStickers) { + set.stickers.reserve(scnt); + set.count = 0; + } + + Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); + OrderedSet read; + for (int32 j = 0; j < scnt; ++j) { + auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); + if (!document || !document->sticker()) continue; + + if (read.contains(document->id)) continue; + read.insert(document->id); + + if (fillStickers) { + set.stickers.push_back(document); + ++set.count; + } + } + + if (stickers.version > 9018) { + qint32 emojiCount; + stickers.stream >> emojiCount; + for (int32 j = 0; j < emojiCount; ++j) { + QString emojiString; + qint32 stickersCount; + stickers.stream >> emojiString >> stickersCount; + StickerPack pack; + pack.reserve(stickersCount); + for (int32 k = 0; k < stickersCount; ++k) { + quint64 id; + stickers.stream >> id; + DocumentData *doc = App::document(id); + if (!doc || !doc->sticker()) continue; + + pack.push_back(doc); + } + if (fillStickers) { + if (auto e = emojiGetNoColor(emojiFromText(emojiString))) { + set.emoji.insert(e, pack); + } + } + } + } + } + + // Read orders of installed and featured stickers. + if (outOrder && stickers.version >= 9058) { + stickers.stream >> *outOrder; + } + + // Set flags that we dropped above from the order. + if (readingFlags && outOrder) { + for_const (auto setId, *outOrder) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= readingFlags; + } + } + } + } + + void writeInstalledStickers() { + _writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) { + if (set.id == Stickers::CloudRecentSetId) { // separate file for recent + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { + if (set.stickers.isEmpty()) { // all other special are "installed" + return StickerSetCheckResult::Skip; + } + } else if (!(set.flags & MTPDstickerSet::Flag::f_installed) || (set.flags & MTPDstickerSet::Flag::f_archived)) { + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive + return StickerSetCheckResult::Abort; + } else if (set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Global::StickerSetsOrder()); + } + + void writeFeaturedStickers() { + _writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) { + if (set.id == Stickers::CloudRecentSetId) { // separate file for recent + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { + return StickerSetCheckResult::Skip; + } else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) { + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive + return StickerSetCheckResult::Abort; + } else if (set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Global::FeaturedStickerSetsOrder()); + } + + void writeRecentStickers() { + _writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) { + if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Stickers::Order()); + } + + void writeArchivedStickers() { + _writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) { + if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Stickers::Order()); } void importOldRecentStickers() { @@ -3176,7 +3396,7 @@ namespace Local { } if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId); - writeStickers(); + writeInstalledStickers(); writeUserSettings(); clearKey(_recentStickersKeyOld); @@ -3184,145 +3404,41 @@ namespace Local { _writeMap(); } - void readStickers() { - if (!_stickersKey) { + void readInstalledStickers() { + if (!_installedStickersKey) { return importOldRecentStickers(); } - FileReadDescriptor stickers; - if (!readEncryptedFile(stickers, _stickersKey)) { - clearKey(_stickersKey); - _stickersKey = 0; - _writeMap(); - return; - } + Global::RefStickerSets().clear(); + _readStickerSets(_installedStickersKey, &Global::RefStickerSetsOrder(), qFlags(MTPDstickerSet::Flag::f_installed)); + } - auto &sets = Global::RefStickerSets(); - sets.clear(); + void readFeaturedStickers() { + _readStickerSets(_featuredStickersKey, &Global::RefFeaturedStickerSetsOrder(), qFlags(MTPDstickerSet_ClientFlag::f_featured)); - auto &order = Global::RefStickerSetsOrder(); - order.clear(); - - auto &featuredOrder = Global::RefFeaturedStickerSetsOrder(); - featuredOrder.clear(); - - quint32 cnt; - QByteArray hash; - stickers.stream >> cnt >> hash; // ignore hash, it is counted - if (stickers.version < 8019) { // bad data in old caches - cnt += 2; // try to read at least something - } - for (uint32 i = 0; i < cnt; ++i) { - quint64 setId = 0, setAccess = 0; - QString setTitle, setShortName; - qint32 scnt = 0; - stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; - - qint32 setHash = 0, setFlags = 0; - if (stickers.version > 8033) { - stickers.stream >> setHash >> setFlags; - if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) { - setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old); - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); - } - } - if (stickers.version < 9058) { - setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); - } - - if (setId == Stickers::DefaultSetId) { - setTitle = lang(lng_stickers_default_set); - setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special); - if (stickers.version < 9058) { - order.push_front(setId); - } - } else if (setId == Stickers::CustomSetId) { - setTitle = lang(lng_custom_stickers); - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); - } else if (setId == Stickers::CloudRecentSetId) { - setTitle = lang(lng_emoji_category0); // Frequently used - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); - } else if (setId) { - if (stickers.version < 9058) { - order.push_back(setId); - } - } else { - continue; - } - - auto &set = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value(); - // We will set this flags from order lists below. - set.flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured); - if (scnt < 0) { // disabled not loaded set - set.count = -scnt; - continue; - } - - set.stickers.reserve(scnt); - - Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); - OrderedSet read; - for (int32 j = 0; j < scnt; ++j) { - auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); - if (!document || !document->sticker()) continue; - - if (read.contains(document->id)) continue; - read.insert(document->id); - - set.stickers.push_back(document); - ++set.count; - } - - if (stickers.version > 9018) { - qint32 emojiCount; - stickers.stream >> emojiCount; - for (int32 j = 0; j < emojiCount; ++j) { - QString emojiString; - qint32 stickersCount; - stickers.stream >> emojiString >> stickersCount; - StickerPack pack; - pack.reserve(stickersCount); - for (int32 k = 0; k < stickersCount; ++k) { - quint64 id; - stickers.stream >> id; - DocumentData *doc = App::document(id); - if (!doc || !doc->sticker()) continue; - - pack.push_back(doc); - } - if (EmojiPtr e = emojiGetNoColor(emojiFromText(emojiString))) { - set.emoji.insert(e, pack); - } - } - } - } - - // Read orders of installed and featured stickers. - if (stickers.version >= 9058) { - stickers.stream >> order; - stickers.stream >> featuredOrder; - } - - // Set flags and count unread featured sets. - for_const (auto setId, order) { - auto it = sets.find(setId); - if (it != sets.cend()) { - it->flags |= MTPDstickerSet::Flag::f_installed; - } - } + auto &sets = Global::StickerSets(); int unreadCount = 0; - for_const (auto setId, featuredOrder) { - auto it = sets.find(setId); - if (it != sets.cend()) { - it->flags |= MTPDstickerSet_ClientFlag::f_featured; - if (it->flags & MTPDstickerSet_ClientFlag::f_unread) { - ++unreadCount; - } + for_const (auto setId, Global::FeaturedStickerSetsOrder()) { + auto it = sets.constFind(setId); + if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { + ++unreadCount; } } Global::SetFeaturedStickerSetsUnreadCount(unreadCount); } + void readRecentStickers() { + _readStickerSets(_recentStickersKey); + } + + void readArchivedStickers() { + static bool archivedStickersRead = false; + if (!archivedStickersRead) { + _readStickerSets(_archivedStickersKey); + archivedStickersRead = true; + } + } + int32 countStickersHash(bool checkOfficial) { uint32 acc = 0; bool foundOfficial = false, foundBad = false;; @@ -3941,8 +4057,8 @@ namespace Local { _recentStickersKeyOld = 0; _mapChanged = true; } - if (_stickersKey) { - _stickersKey = 0; + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; _mapChanged = true; } if (_recentHashtagsAndBotsKey) { diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index a4909037d..786a10850 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -153,8 +153,14 @@ namespace Local { void cancelTask(TaskId id); - void writeStickers(); - void readStickers(); + void writeInstalledStickers(); + void writeFeaturedStickers(); + void writeRecentStickers(); + void writeArchivedStickers(); + void readInstalledStickers(); + void readFeaturedStickers(); + void readRecentStickers(); + void readArchivedStickers(); int32 countStickersHash(bool checkOfficial = false); int32 countRecentStickersHash(); int32 countFeaturedStickersHash(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 9c5c00f3d..44d597b81 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3298,7 +3298,9 @@ void MainWidget::start(const MTPUser &user) { } _started = true; App::wnd()->sendServiceHistoryRequest(); - Local::readStickers(); + Local::readInstalledStickers(); + Local::readFeaturedStickers(); + Local::readRecentStickers(); Local::readSavedGifs(); _history->start(); } @@ -3676,7 +3678,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) { if (!sticker || !sticker->sticker()) return; if (sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) return; - bool writeStickers = false; + bool writeRecentStickers = false; auto &sets = Global::RefStickerSets(); auto it = sets.find(Stickers::CloudRecentSetId); if (it == sets.cend()) { @@ -3692,29 +3694,30 @@ void MainWidget::incrementSticker(DocumentData *sticker) { } if (index) { it->stickers.push_front(sticker); - writeStickers = true; + writeRecentStickers = true; } // Remove that sticker from old recent, now it is in cloud recent stickers. - bool writeRecent = false; + bool writeOldRecent = false; auto &recent = cGetRecentStickers(); for (auto i = recent.begin(), e = recent.end(); i != e; ++i) { if (i->first == sticker) { - writeRecent = true; + writeOldRecent = true; recent.erase(i); break; } } while (!recent.isEmpty() && it->stickers.size() + recent.size() > Global::StickersRecentLimit()) { - writeRecent = true; + writeOldRecent = true; recent.pop_back(); } - if (writeRecent) { + if (writeOldRecent) { Local::writeUserSettings(); } // Remove that sticker from custom stickers, now it is in cloud recent stickers. + bool writeInstalledStickers = false; auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { int removeIndex = custom->stickers.indexOf(sticker); @@ -3723,12 +3726,15 @@ void MainWidget::incrementSticker(DocumentData *sticker) { if (custom->stickers.isEmpty()) { sets.erase(custom); } - writeStickers = true; + writeInstalledStickers = true; } } - if (writeStickers) { - Local::writeStickers(); + if (writeInstalledStickers) { + Local::writeInstalledStickers(); + } + if (writeRecentStickers) { + Local::writeRecentStickers(); } _history->updateRecentStickers(); } @@ -4673,6 +4679,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed)); } else { it->flags |= MTPDstickerSet::Flag::f_installed; + it->flags &= ~MTPDstickerSet::Flag::f_archived; } const auto &v(set.vdocuments.c_vector().v); @@ -4722,7 +4729,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { sets.erase(custom); } } - Local::writeStickers(); + Local::writeInstalledStickers(); emit stickersUpdated(); } } @@ -4744,7 +4751,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { App::main()->updateStickers(); } else { Global::SetStickerSetsOrder(result); - Local::writeStickers(); + Local::writeInstalledStickers(); emit stickersUpdated(); } } break; @@ -4767,7 +4774,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } if (Global::FeaturedStickerSetsUnreadCount()) { Global::SetFeaturedStickerSetsUnreadCount(0); - Local::writeStickers(); + Local::writeFeaturedStickers(); emit stickersUpdated(); } } break; From faad13d07e5c6542b4e172ccba91d612e02ff6fe Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 21 Jul 2016 16:57:31 +0300 Subject: [PATCH 34/60] Showing archived stickers button, archived stickers box with loading. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/app.cpp | 1 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 412 +++++++++++++++---- Telegram/SourceFiles/boxes/stickersetbox.h | 28 +- Telegram/SourceFiles/facades.cpp | 16 + Telegram/SourceFiles/facades.h | 1 + Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/localstorage.cpp | 20 +- Telegram/SourceFiles/localstorage.h | 2 +- Telegram/SourceFiles/mainwidget.cpp | 21 +- 10 files changed, 395 insertions(+), 110 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bfbde65f1..43c72f429 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -683,7 +683,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_share_pack" = "Share Stickers"; "lng_stickers_not_found" = "Sticker pack not found."; "lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated."; -"lng_stickers_archived" = "Archived stickers"; +"lng_stickers_archived" = "Archived Stickers"; "lng_stickers_copied" = "Sticker pack link copied to clipboard."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "Manage and reorder sticker packs"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index f3c85d3e2..f8320b623 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2028,6 +2028,7 @@ namespace { Global::SetFeaturedStickerSetsOrder(Stickers::Order()); Global::SetFeaturedStickerSetsUnreadCount(0); Global::SetLastFeaturedStickersUpdate(0); + Global::SetArchivedStickerSetsOrder(Stickers::Order()); cSetSavedGifs(SavedGifs()); cSetLastSavedGifsUpdate(0); cSetReportSpamStatuses(ReportSpamStatuses()); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index a1df6f957..2d8d02d19 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -31,6 +31,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" +namespace { + +constexpr int kArchivedLimitFirstRequest = 10; +constexpr int kArchivedLimitPerPage = 30; + +} // namespace + namespace Stickers { void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { @@ -58,6 +65,7 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { } App::api()->requestStickerSets(); } + Local::writeArchivedStickers(); Ui::showLayer(new StickersBox(archived), KeepOtherLayers); } @@ -153,6 +161,13 @@ bool StickerSetInner::failedSet(const RPCError &error) { void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &result) { auto &sets = Global::RefStickerSets(); + bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived); + if (wasArchived) { + auto index = Global::RefArchivedStickerSetsOrder().indexOf(_setId); + if (index >= 0) { + Global::RefArchivedStickerSetsOrder().removeAt(index); + } + } _setFlags &= ~MTPDstickerSet::Flag::f_archived; _setFlags |= MTPDstickerSet::Flag::f_installed; auto it = sets.find(_setId); @@ -186,6 +201,8 @@ void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &res if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + } else if (wasArchived) { + Local::writeArchivedStickers(); } Local::writeInstalledStickers(); @@ -483,24 +500,22 @@ void StickersInner::setup() { setMouseTracking(true); } -void StickersInner::paintFeaturedButton(Painter &p) const { - if (!_featuredHeight) return; - - if (_selected == -1) { - p.fillRect(0, st::membersPadding.top(), width(), _featuredHeight, st::contactsBgOver); +void StickersInner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const { + if (selected) { + p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver); } p.setFont(st::stickersFeaturedFont); p.setPen(st::stickersFeaturedPen); - p.drawTextLeft(st::stickersFeaturedPosition.x(), st::membersPadding.top() + st::stickersFeaturedPosition.y(), width(), lang(lng_stickers_featured)); + p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text); - if (auto unread = Global::FeaturedStickerSetsUnreadCount()) { + if (badgeCounter) { Dialogs::Layout::UnreadBadgeStyle unreadSt; unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; unreadSt.size = st::stickersFeaturedBadgeSize; int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x()); if (rtl()) unreadRight = width() - unreadRight; - int unreadTop = st::membersPadding.top() + (_featuredHeight - st::stickersFeaturedBadgeSize) / 2; - Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); + int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2; + Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt); } } @@ -513,12 +528,22 @@ void StickersInner::paintEvent(QPaintEvent *e) { p.fillRect(r, st::white); p.setClipRect(r); - paintFeaturedButton(p); + int y = st::membersPadding.top(); + if (_hasFeaturedButton) { + auto selected = (_selected == -2); + paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount()); + y += _buttonHeight; + } + if (_hasArchivedButton) { + auto selected = (_selected == -1); + paintButton(p, y, selected, lang(lng_stickers_archived), 0); + y += _buttonHeight; + } if (_rows.isEmpty()) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); - p.drawText(QRect(0, _featuredHeight, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } else { p.translate(0, _itemsTop); @@ -708,13 +733,15 @@ void StickersInner::onUpdateSelected() { QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; } - } else if (_featuredHeight && QRect(0, st::membersPadding.top(), width(), _featuredHeight).contains(local)) { + } else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) { + selected = -2; + } else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) { selected = -1; } else { - selected = -2; + selected = -3; } if (_selected != selected) { - if ((_selected == -1) != (selected == -1)) { + if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) { update(); } if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { @@ -800,14 +827,17 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { _dragging = _started = -1; } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { - if (_selected == -1) { - _selected = -2; + if (_selected == -2) { + _selected = -3; Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); + } else if (_selected == -1) { + _selected = -3; + Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers); } else if (_selected >= 0 && _section != Section::Installed) { auto &sets = Global::RefStickerSets(); auto it = sets.find(_rows.at(pressed)->id); if (it != sets.cend()) { - _selected = -2; + _selected = -3; Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers); } } @@ -859,6 +889,13 @@ void StickersInner::installSet(uint64 setId) { } Local::writeInstalledStickers(); if (changedFlags & MTPDstickerSet_ClientFlag::f_unread) Local::writeFeaturedStickers(); + if (changedFlags & MTPDstickerSet::Flag::f_archived) { + auto index = Global::RefArchivedStickerSetsOrder().indexOf(setId); + if (index >= 0) { + Global::RefArchivedStickerSetsOrder().removeAt(index); + Local::writeArchivedStickers(); + } + } emit App::main()->stickersUpdated(); } @@ -960,8 +997,8 @@ void StickersInner::clear() { _aboveShadowFadeOpacity = anim::fvalue(0, 0); _a_shifting.stop(); _above = _dragging = _started = -1; - _selected = -2; - _pressed = -2; + _selected = -3; + _pressed = -3; _actionDown = -1; setActionSel(-1); update(); @@ -979,22 +1016,24 @@ void StickersInner::setActionSel(int32 actionSel) { } void StickersInner::rebuild() { - QList rows, rowsDisabled; - + _hasFeaturedButton = _hasArchivedButton = false; _itemsTop = st::membersPadding.top(); - _featuredHeight = 0; - if (_section == Section::Installed && !Global::FeaturedStickerSetsOrder().isEmpty()) { - _featuredHeight = st::stickersFeaturedHeight; - _itemsTop += _featuredHeight + st::membersPadding.top(); + _buttonHeight = st::stickersFeaturedHeight; + if (_section == Section::Installed) { + if (!Global::FeaturedStickerSetsOrder().isEmpty()) { + _itemsTop += _buttonHeight; + _hasFeaturedButton = true; + } + if (!Global::ArchivedStickerSetsOrder().isEmpty()) { + _itemsTop += _buttonHeight; + _hasArchivedButton = true; + } + if (_itemsTop > st::membersPadding.top()) { + _itemsTop += st::membersPadding.top(); + } } - int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); - if (_section == Section::Installed) { - namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); - } else { - namew -= _addWidth - st::defaultActiveButton.width; - } + int maxNameWidth = countMaxNameWidth(); clear(); auto &order = ([this]() { @@ -1002,6 +1041,8 @@ void StickersInner::rebuild() { return Global::StickerSetsOrder(); } else if (_section == Section::Featured) { return Global::FeaturedStickerSetsOrder(); + } else if (_section == Section::Archived) { + return Global::ArchivedStickerSetsOrder(); } return _archivedIds; })(); @@ -1012,7 +1053,7 @@ void StickersInner::rebuild() { if (_section == Section::Installed) { auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { - rebuildAppendSet(cloudIt.value(), namew); + rebuildAppendSet(cloudIt.value(), maxNameWidth); } } for_const (auto setId, order) { @@ -1021,14 +1062,14 @@ void StickersInner::rebuild() { continue; } - rebuildAppendSet(it.value(), namew); + rebuildAppendSet(it.value(), maxNameWidth); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { App::api()->scheduleStickerSetRequest(it->id, it->access); } } App::api()->requestStickerSets(); - resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); + updateSize(); if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { Global::SetFeaturedStickerSetsUnreadCount(0); @@ -1039,52 +1080,106 @@ void StickersInner::rebuild() { } } -void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { - bool recent = (set.id == Stickers::CloudRecentSetId); - bool installed = true; - bool official = true; - bool unread = false; - bool disabled = false; - if (!recent) { - installed = (set.flags & MTPDstickerSet::Flag::f_installed); - official = (set.flags & MTPDstickerSet::Flag::f_official); - disabled = (set.flags & MTPDstickerSet::Flag::f_archived); - if (_section == Section::Featured) { - unread = _unreadSets.contains(set.id); - if (!unread && (set.flags & MTPDstickerSet_ClientFlag::f_unread)) { - unread = true; - _unreadSets.insert(set.id); +void StickersInner::updateSize() { + resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); +} + +void StickersInner::updateRows() { + int maxNameWidth = countMaxNameWidth(); + auto &sets = Global::StickerSets(); + for_const (auto row, _rows) { + auto it = sets.constFind(row->id); + if (it != sets.cend()) { + auto &set = it.value(); + if (!row->sticker) { + DocumentData *sticker = nullptr; + int pixw = 0, pixh = 0; + fillSetCover(set, &sticker, &pixw, &pixh); + if (sticker) { + row->sticker = sticker; + row->pixw = pixw; + row->pixh = pixh; + } } - } else if (_section == Section::Installed && disabled) { - return; + fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled); + if (_section == Section::Installed) { + row->disabled = false; + } + row->title = fillSetTitle(set, maxNameWidth); + row->count = fillSetCount(set); } } + update(); +} - auto sticker = set.stickers.at(0); - int32 pixw = 0, pixh = 0; - if (sticker) { - pixw = sticker->thumb->width(); - pixh = sticker->thumb->height(); - if (pixw > st::contactsPhotoSize) { - if (pixw > pixh) { - pixh = (pixh * st::contactsPhotoSize) / pixw; - pixw = st::contactsPhotoSize; - } else { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } else if (pixh > st::contactsPhotoSize) { +bool StickersInner::appendSet(const Stickers::Set &set) { + for_const (auto row, _rows) { + if (row->id == set.id) { + return false; + } + } + rebuildAppendSet(set, countMaxNameWidth()); + return true; +} + +int StickersInner::countMaxNameWidth() const { + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); + if (_section == Section::Installed) { + namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); + } else { + namew -= _addWidth - st::defaultActiveButton.width; + } + return namew; +} + +void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { + bool recent = false, installed = false, official = false, unread = false, disabled = false; + fillSetFlags(set, &recent, &installed, &official, &unread, &disabled); + if (_section == Section::Installed && disabled) { + return; + } + + DocumentData *sticker = nullptr; + int pixw = 0, pixh = 0; + fillSetCover(set, &sticker, &pixw, &pixh); + + QString title = fillSetTitle(set, maxNameWidth); + int count = fillSetCount(set); + + _rows.push_back(new StickerSetRow(set.id, sticker, count, title, installed, official, unread, disabled, recent, pixw, pixh)); + _animStartTimes.push_back(0); +} + +void StickersInner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const { + if (set.stickers.isEmpty()) { + *outSticker = nullptr; + *outWidth = *outHeight = 0; + return; + } + auto sticker = *outSticker = set.stickers.front(); + + auto pixw = sticker->thumb->width(); + auto pixh = sticker->thumb->height(); + if (pixw > st::contactsPhotoSize) { + if (pixw > pixh) { + pixh = (pixh * st::contactsPhotoSize) / pixw; + pixw = st::contactsPhotoSize; + } else { pixw = (pixw * st::contactsPhotoSize) / pixh; pixh = st::contactsPhotoSize; } + } else if (pixh > st::contactsPhotoSize) { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; } - QString title = set.title; - int32 titleWidth = st::contactsNameFont->width(title); - if (titleWidth > maxNameWidth) { - title = st::contactsNameFont->elided(title, maxNameWidth); - } - int count = set.stickers.size(), added = 0; - if (recent) { + *outWidth = pixw; + *outHeight = pixh; +} + +int StickersInner::fillSetCount(const Stickers::Set &set) const { + int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0; + if (set.id == Stickers::CloudRecentSetId) { auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); if (customIt != Global::StickerSets().cend()) { added = customIt->stickers.size(); @@ -1097,8 +1192,36 @@ void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) added = cGetRecentStickers().size(); } } - _rows.push_back(new StickerSetRow(set.id, sticker, count + added, title, installed, official, unread, disabled, recent, pixw, pixh)); - _animStartTimes.push_back(0); + return result + added; +} + +QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth) const { + auto result = set.title; + int32 titleWidth = st::contactsNameFont->width(result); + if (titleWidth > maxNameWidth) { + result = st::contactsNameFont->elided(result, maxNameWidth); + } + return result; +} + +void StickersInner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) { + *outRecent = (set.id == Stickers::CloudRecentSetId); + *outInstalled = true; + *outOfficial = true; + *outUnread = false; + *outDisabled = false; + if (!*outRecent) { + *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed); + *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); + *outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived); + if (_section == Section::Featured) { + *outUnread = _unreadSets.contains(set.id); + if (!*outUnread && (set.flags & MTPDstickerSet_ClientFlag::f_unread)) { + *outUnread = true; + _unreadSets.insert(set.id); + } + } + } } void StickersInner::readFeaturedDone(const MTPBool &result) { @@ -1158,10 +1281,7 @@ StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) , _section(section) , _inner(section) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) -, _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) { - if (section == Section::Archived) { - Local::readArchivedStickers(); - } +, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) { setup(); } @@ -1170,11 +1290,87 @@ StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::b , _inner(archivedIds) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) , _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { - Local::readArchivedStickers(); setup(); } +void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) { + _archivedRequestId = 0; + if (result.type() != mtpc_messages_archivedStickers) { + return; + } + + auto &stickers = result.c_messages_archivedStickers(); + auto &archived = Global::RefArchivedStickerSetsOrder(); + if (offsetId) { + auto index = archived.indexOf(offsetId); + if (index >= 0) { + archived = archived.mid(0, index + 1); + } + } else { + archived.clear(); + } + + bool addedSet = false; + auto &v = stickers.vsets.c_vector().v; + for_const (auto &stickerSet, v) { + if (stickerSet.type() != mtpc_stickerSet) continue; + + if (auto set = Stickers::feedSet(stickerSet.c_stickerSet())) { + auto index = archived.indexOf(set->id); + if (index != archived.size() - 1) { + if (index < archived.size() - 1) { + archived.removeAt(index); + } + archived.push_back(set->id); + } + if (_section == Section::Archived) { + if (_inner->appendSet(*set)) { + addedSet = true; + if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(set->id, set->access); + } + } + } + } + } + if (_section == Section::Installed && !archived.isEmpty()) { + Local::writeArchivedStickers(); + rebuildList(); + } else if (_section == Section::Archived) { + if (addedSet) { + _inner->updateSize(); + setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); + _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + App::api()->requestStickerSets(); + } else { + _allArchivedLoaded = v.isEmpty() || (offsetId != 0); + } + } + checkLoadMoreArchived(); +} + void StickersBox::setup() { + if (_section == Section::Installed) { + Local::readArchivedStickers(); + if (Global::ArchivedStickerSetsOrder().isEmpty()) { + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); + } + } else if (_section == Section::Archived) { + // Reload the archived list. + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); + + auto &sets = Global::StickerSets(); + for_const (auto setId, Global::ArchivedStickerSetsOrder()) { + auto it = sets.constFind(setId); + if (it != sets.cend()) { + if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(setId, it->access); + } + } + } + App::api()->requestStickerSets(); + } + int bottomSkip = st::boxPadding.bottom(); if (_section == Section::Installed) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); @@ -1194,6 +1390,9 @@ void StickersBox::setup() { _save = new BoxButton(this, lang(lng_box_ok), st::defaultBoxButton); connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); + } else if (_section == Section::Archived) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow = new PlainShadow(this, st::contactsAboutShadow); } ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); @@ -1204,13 +1403,40 @@ void StickersBox::setup() { connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); + connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); _scrollTimer.setSingleShot(false); - onStickersUpdated(); + rebuildList(); prepare(); } +void StickersBox::onScroll() { + checkLoadMoreArchived(); +} + +void StickersBox::checkLoadMoreArchived() { + if (_section != Section::Archived) return; + + int scrollTop = _scroll.scrollTop(), scrollTopMax = _scroll.scrollTopMax(); + if (scrollTop + PreloadHeightsCount * _scroll.height() >= scrollTopMax) { + if (!_archivedRequestId && !_allArchivedLoaded) { + uint64 lastId = 0; + for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { + --setId; + auto it = Global::StickerSets().constFind(*setId); + if (it != Global::StickerSets().cend()) { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + lastId = it->id; + break; + } + } + } + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); + } + } +} + int32 StickersBox::countHeight() const { int bottomSkip = st::boxPadding.bottom(); if (_section == Section::Installed) { @@ -1302,9 +1528,7 @@ void StickersBox::closePressed() { } StickersBox::~StickersBox() { - if (_section == Section::Featured) { - Local::writeFeaturedStickers(); - } else if (_section == Section::Archived) { + if (_section == Section::Archived) { Local::writeArchivedStickers(); } } @@ -1326,6 +1550,14 @@ void StickersBox::resizeEvent(QResizeEvent *e) { } void StickersBox::onStickersUpdated() { + if (_section == Section::Installed || _section == Section::Featured) { + rebuildList(); + } else { + _inner->updateRows(); + } +} + +void StickersBox::rebuildList() { _inner->rebuild(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); @@ -1360,7 +1592,7 @@ void StickersBox::onSave() { return; } - bool writeRecent = false; + bool writeRecent = false, writeArchived = false; auto &recent = cGetRecentStickers(); auto &sets = Global::RefStickerSets(); @@ -1381,6 +1613,11 @@ void StickersBox::onSave() { if (it->flags & MTPDstickerSet::Flag::f_official) { _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); it->flags |= MTPDstickerSet::Flag::f_archived; + auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id); + if (index < 0) { + Global::RefArchivedStickerSetsOrder().push_front(it->id); + writeArchived = true; + } } else { _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); int removeIndex = Global::StickerSetsOrder().indexOf(it->id); @@ -1388,6 +1625,9 @@ void StickersBox::onSave() { if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { sets.erase(it); } else { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + writeArchived = true; + } it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); } } @@ -1411,6 +1651,7 @@ void StickersBox::onSave() { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); it->flags &= ~MTPDstickerSet::Flag::f_archived; + writeArchived = true; } order.push_back(reorder.at(i)); it->flags |= MTPDstickerSet::Flag::f_installed; @@ -1429,6 +1670,7 @@ void StickersBox::onSave() { Local::writeInstalledStickers(); if (writeRecent) Local::writeUserSettings(); + if (writeArchived) Local::writeArchivedStickers(); emit App::main()->stickersUpdated(); if (_disenableRequests.isEmpty()) { diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 4dfc9fcb1..42d9e23cf 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -155,6 +155,9 @@ public slots: void onSave(); +private slots: + void onScroll(); + protected: void hideAll(); void showAll(); @@ -162,6 +165,7 @@ protected: private: void setup(); int32 countHeight() const; + void rebuildList(); void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); bool disenableFail(const RPCError &error, mtpRequestId req); @@ -169,6 +173,9 @@ private: bool reorderFail(const RPCError &result); void saveOrder(); + void checkLoadMoreArchived(); + void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); + Section _section; ChildWidget _inner; @@ -186,6 +193,9 @@ private: Text _about; int _aboutHeight = 0; + mtpRequestId _archivedRequestId = 0; + bool _allArchivedLoaded = false; + }; int32 stickerPacksCount(bool includeDisabledOfficial = false); @@ -201,6 +211,9 @@ public: StickersInner(const Stickers::Order &archivedIds); void rebuild(); + void updateSize(); + void updateRows(); // refresh only pack cover stickers + bool appendSet(const Stickers::Set &set); bool savingStart() { if (_saving) return false; _saving = true; @@ -232,7 +245,7 @@ public slots: private: void setup(); - void paintFeaturedButton(Painter &p) const; + void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const; void step_shifting(uint64 ms, bool timer); void paintRow(Painter &p, int32 index); @@ -275,6 +288,12 @@ private: using StickerSetRows = QList; void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); + void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; + int fillSetCount(const Stickers::Set &set) const; + QString fillSetTitle(const Stickers::Set &set, int maxNameWidth) const; + void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled); + + int countMaxNameWidth() const; StickerSetRows _rows; QList _animStartTimes; @@ -296,12 +315,15 @@ private: QString _addText; int _addWidth; - int _featuredHeight = 0; + int _buttonHeight = 0; + bool _hasFeaturedButton = false; + bool _hasArchivedButton = false; + // Remember all the unread set ids to display unread dots. OrderedSet _unreadSets; QPoint _mouse; - int _selected = -2; // -1 - featured stickers button + int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button int _pressed = -2; QPoint _dragStart; int _started = -1; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 80c433527..783ef5afb 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -523,6 +523,8 @@ DefineVar(Sandbox, ConnectionProxy, PreLaunchProxy); namespace Stickers { Set *feedSet(const MTPDstickerSet &set) { + MTPDstickerSet::Flags flags = 0; + auto &sets = Global::RefStickerSets(); auto it = sets.find(set.vid.v); auto title = stickerSetTitle(set); @@ -532,6 +534,7 @@ Set *feedSet(const MTPDstickerSet &set) { it->access = set.vaccess_hash.v; it->title = title; it->shortName = qs(set.vshort_name); + flags = it->flags; auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); it->flags = set.vflags.v | clientFlags; if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { @@ -540,6 +543,17 @@ Set *feedSet(const MTPDstickerSet &set) { it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set } } + auto changedFlags = (flags ^ it->flags); + if (changedFlags & MTPDstickerSet::Flag::f_archived) { + auto index = Global::ArchivedStickerSetsOrder().indexOf(it->id); + if (it->flags & MTPDstickerSet::Flag::f_archived) { + if (index < 0) { + Global::RefArchivedStickerSetsOrder().push_front(it->id); + } + } else if (index >= 0) { + Global::RefArchivedStickerSetsOrder().removeAt(index); + } + } return &it.value(); } @@ -594,6 +608,7 @@ struct Data { Stickers::Order FeaturedStickerSetsOrder; int FeaturedStickerSetsUnreadCount = 0; uint64 LastFeaturedStickersUpdate = 0; + Stickers::Order ArchivedStickerSetsOrder; MTP::DcOptions DcOptions; @@ -667,6 +682,7 @@ DefineVar(Global, uint64, LastRecentStickersUpdate); DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); DefineVar(Global, int, FeaturedStickerSetsUnreadCount); DefineVar(Global, uint64, LastFeaturedStickersUpdate); +DefineVar(Global, Stickers::Order, ArchivedStickerSetsOrder); DefineVar(Global, MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index d99b54bb8..bd07881b2 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -271,6 +271,7 @@ DeclareVar(uint64, LastRecentStickersUpdate); DeclareVar(Stickers::Order, FeaturedStickerSetsOrder); DeclareVar(int, FeaturedStickerSetsUnreadCount); DeclareVar(uint64, LastFeaturedStickersUpdate); +DeclareVar(Stickers::Order, ArchivedStickerSetsOrder); DeclareVar(MTP::DcOptions, DcOptions); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 59630d7fc..48f0cc5ce 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3844,7 +3844,7 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic QString title = stickerSetTitle(set); if (it == sets.cend()) { auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; - if (unread.contains(set.vid.v) || !(set.vflags.v & MTPDstickerSet::Flag::f_installed)) { + if (unread.contains(set.vid.v)) { setClientFlags |= MTPDstickerSet_ClientFlag::f_unread; } it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | setClientFlags)); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index c365e6606..b4395b4eb 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3325,7 +3325,7 @@ namespace Local { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; - }, Stickers::Order()); + }, Global::ArchivedStickerSetsOrder()); } void importOldRecentStickers() { @@ -3434,30 +3434,28 @@ namespace Local { void readArchivedStickers() { static bool archivedStickersRead = false; if (!archivedStickersRead) { - _readStickerSets(_archivedStickersKey); + _readStickerSets(_archivedStickersKey, &Global::RefArchivedStickerSetsOrder()); archivedStickersRead = true; } } - int32 countStickersHash(bool checkOfficial) { + int32 countStickersHash(bool checkOutdatedInfo) { uint32 acc = 0; - bool foundOfficial = false, foundBad = false;; + bool foundOutdated = false; auto &sets = Global::StickerSets(); auto &order = Global::StickerSetsOrder(); for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) { auto j = sets.constFind(*i); if (j != sets.cend()) { - if (j->id == 0) { - foundBad = true; - } else if (j->flags & MTPDstickerSet::Flag::f_official) { - foundOfficial = true; - } - if (!(j->flags & MTPDstickerSet::Flag::f_archived)) { + if (j->id == Stickers::DefaultSetId) { + foundOutdated = true; + } else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special) + && !(j->flags & MTPDstickerSet::Flag::f_archived)) { acc = (acc * 20261) + j->hash; } } } - return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; + return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0; } int32 countRecentStickersHash() { diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index 786a10850..ca59fe65b 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -161,7 +161,7 @@ namespace Local { void readFeaturedStickers(); void readRecentStickers(); void readArchivedStickers(); - int32 countStickersHash(bool checkOfficial = false); + int32 countStickersHash(bool checkOutdatedInfo = false); int32 countRecentStickersHash(); int32 countFeaturedStickersHash(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 44d597b81..bfd5d191b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4667,19 +4667,23 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { ////// Cloud sticker sets case mtpc_updateNewStickerSet: { - const auto &d(update.c_updateNewStickerSet()); + auto &d = update.c_updateNewStickerSet(); + bool writeArchived = false; if (d.vstickerset.type() == mtpc_messages_stickerSet) { - const auto &set(d.vstickerset.c_messages_stickerSet()); + auto &set = d.vstickerset.c_messages_stickerSet(); if (set.vset.type() == mtpc_stickerSet) { - const auto &s(set.vset.c_stickerSet()); + auto &s = set.vset.c_stickerSet(); - Stickers::Sets &sets(Global::RefStickerSets()); + auto &sets = Global::RefStickerSets(); auto it = sets.find(s.vid.v); if (it == sets.cend()) { it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed)); } else { it->flags |= MTPDstickerSet::Flag::f_installed; - it->flags &= ~MTPDstickerSet::Flag::f_archived; + if (it->flags & MTPDstickerSet::Flag::f_archived) { + it->flags &= ~MTPDstickerSet::Flag::f_archived; + writeArchived = true; + } } const auto &v(set.vdocuments.c_vector().v); @@ -4692,12 +4696,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { it->stickers.push_back(doc); } it->emoji.clear(); - const auto &packs(set.vpacks.c_vector().v); + auto &packs = set.vpacks.c_vector().v; for (int32 i = 0, l = packs.size(); i < l; ++i) { if (packs.at(i).type() != mtpc_stickerPack) continue; - const auto &pack(packs.at(i).c_stickerPack()); + auto &pack = packs.at(i).c_stickerPack(); if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { - const auto &stickers(pack.vdocuments.c_vector().v); + auto &stickers = pack.vdocuments.c_vector().v; StickerPack p; p.reserve(stickers.size()); for (int32 j = 0, c = stickers.size(); j < c; ++j) { @@ -4730,6 +4734,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } } Local::writeInstalledStickers(); + if (writeArchived) Local::writeArchivedStickers(); emit stickersUpdated(); } } From 9fe714189da4e4158ee982e63c0054e42b0b3882 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 21 Jul 2016 20:35:55 +0300 Subject: [PATCH 35/60] updateNewMessage now can request getDifference(), if data is absent. Video sync and frame duration count improved. Seek in not 44100 and not 48000 hz audio streams fixed. --- Telegram/SourceFiles/mainwidget.cpp | 13 ++++++-- Telegram/SourceFiles/media/media_audio.cpp | 12 ++++--- .../media/media_audio_ffmpeg_loader.cpp | 5 +-- .../media/media_audio_ffmpeg_loader.h | 4 +-- .../SourceFiles/media/media_audio_loader.h | 2 +- .../SourceFiles/media/media_audio_loaders.cpp | 2 +- .../SourceFiles/media/media_audio_loaders.h | 2 +- .../media/media_child_ffmpeg_loader.cpp | 3 +- .../media/media_child_ffmpeg_loader.h | 2 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 33 +++++++++++-------- .../SourceFiles/media/media_clip_ffmpeg.h | 3 +- .../SourceFiles/media/media_clip_reader.cpp | 1 - 12 files changed, 50 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4a9b09fa2..cdf9f42a2 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4107,7 +4107,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { switch (update.type()) { case mtpc_updateNewMessage: { - const auto &d(update.c_updateNewMessage()); + auto &d = update.c_updateNewMessage(); + + DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage); + if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) { + MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); + + // This can be if this update was created by grouping + // some short message update into an updates vector. + return getDifference(); + } if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) { return; @@ -4462,7 +4471,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateNewChannelMessage: { - const auto &d(update.c_updateNewChannelMessage()); + auto &d = update.c_updateNewChannelMessage(); ChannelData *channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage); if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 799c3511a..71558181c 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -1765,8 +1765,8 @@ public: FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { } - bool open(qint64 position = 0) override { - if (!AbstractFFMpegLoader::open()) { + bool open(qint64 &position) override { + if (!AbstractFFMpegLoader::open(position)) { return false; } @@ -1862,7 +1862,8 @@ private: MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) { FFMpegAttributesReader reader(FileLocation(StorageFilePartial, fname), data); - if (reader.open()) { + qint64 position = 0; + if (reader.open(position)) { int32 duration = reader.duration() / reader.frequency(); if (reader.duration() > 0) { cover = reader.cover(); @@ -1880,7 +1881,7 @@ public: FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) { } - bool open(qint64 position = 0) override { + bool open(qint64 &position) override { if (!FFMpegLoader::open(position)) { return false; } @@ -1976,7 +1977,8 @@ private: VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) { FFMpegWaveformCounter counter(file, data); - if (counter.open()) { + qint64 position = 0; + if (counter.open(position)) { return counter.waveform(); } return VoiceWaveform(); diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp index 69bd99cd2..b9ef7e020 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -25,7 +25,7 @@ constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; constexpr int32 AudioToChannels = 2; -bool AbstractFFMpegLoader::open(qint64 position) { +bool AbstractFFMpegLoader::open(qint64 &position) { if (!AudioPlayerLoader::openFile()) { return false; } @@ -137,7 +137,7 @@ FFMpegLoader::FFMpegLoader(const FileLocation &file, const QByteArray &data) : A frame = av_frame_alloc(); } -bool FFMpegLoader::open(qint64 position) { +bool FFMpegLoader::open(qint64 &position) { if (!AbstractFFMpegLoader::open(position)) { return false; } @@ -210,6 +210,7 @@ bool FFMpegLoader::open(qint64 position) { sampleSize = AudioToChannels * sizeof(short); freq = dstRate; len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP); + position = av_rescale_rnd(position, dstRate, srcRate, AV_ROUND_DOWN); fmt = AL_FORMAT_STEREO16; maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP); diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h index 02ff3d3a3..c856be631 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h @@ -36,7 +36,7 @@ public: AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) { } - bool open(qint64 position = 0) override; + bool open(qint64 &position) override; int64 duration() override { return len; @@ -72,7 +72,7 @@ class FFMpegLoader : public AbstractFFMpegLoader { public: FFMpegLoader(const FileLocation &file, const QByteArray &data); - bool open(qint64 position = 0) override; + bool open(qint64 &position) override; int32 format() override { return fmt; diff --git a/Telegram/SourceFiles/media/media_audio_loader.h b/Telegram/SourceFiles/media/media_audio_loader.h index 74a749043..5e8a55012 100644 --- a/Telegram/SourceFiles/media/media_audio_loader.h +++ b/Telegram/SourceFiles/media/media_audio_loader.h @@ -27,7 +27,7 @@ public: virtual bool check(const FileLocation &file, const QByteArray &data); - virtual bool open(qint64 position = 0) = 0; + virtual bool open(qint64 &position) = 0; virtual int64 duration() = 0; virtual int32 frequency() = 0; virtual int32 format() = 0; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index 5b1805b93..491eb8cda 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -321,7 +321,7 @@ void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) { } } -AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position) { +AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 &position) { err = SetupErrorAtStart; QMutexLocker lock(internal::audioPlayerMutex()); AudioPlayer *voice = audioPlayer(); diff --git a/Telegram/SourceFiles/media/media_audio_loaders.h b/Telegram/SourceFiles/media/media_audio_loaders.h index 50a282b59..6e190ae95 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.h +++ b/Telegram/SourceFiles/media/media_audio_loaders.h @@ -73,7 +73,7 @@ private: SetupNoErrorStarted = 3, }; void loadData(AudioMsgId audio, qint64 position); - AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); + AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 &position); AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); }; diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index 5016b1b4f..143f9f1c6 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -39,7 +39,7 @@ ChildFFMpegLoader::ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptrfrequency = _dstRate; _parentData->length = av_rescale_rnd(_parentData->length, _dstRate, _srcRate, AV_ROUND_UP); + position = av_rescale_rnd(position, _dstRate, _srcRate, AV_ROUND_DOWN); _format = AL_FORMAT_STEREO16; _maxResampleSamples = av_rescale_rnd(AVBlockSize / _sampleSize, _dstRate, _srcRate, AV_ROUND_UP); diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h index 69926bfb8..ad92459ab 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -83,7 +83,7 @@ class ChildFFMpegLoader : public AudioPlayerLoader { public: ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptr &&data); - bool open(qint64 position = 0) override; + bool open(qint64 &position) override; bool check(const FileLocation &file, const QByteArray &data) override { return true; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 8e321eb16..3104c4d9f 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -96,7 +96,10 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { _currentFrameDelay = _nextFrameDelay; if (_frameMs + _currentFrameDelay < frameMs) { _currentFrameDelay = int32(frameMs - _frameMs); + } else if (frameMs < _frameMs + _currentFrameDelay) { + frameMs = _frameMs + _currentFrameDelay; } + if (duration == AV_NOPTS_VALUE) { _nextFrameDelay = 0; } else { @@ -129,7 +132,7 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { avcodec_flush_buffers(_codecContext); _hadFrame = false; _frameMs = 0; - _lastReadPacketMs = 0; + _lastReadVideoMs = _lastReadAudioMs = 0; } } @@ -154,7 +157,6 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int6 // sync by audio stream auto correctMs = (frameMs >= 0) ? audioPlayer()->getVideoCorrectedTime(_playId, frameMs, systemMs) : frameMs; - if (!_frameRead) { auto readResult = readNextFrame(); if (readResult != ReadResult::Success) { @@ -237,7 +239,8 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q // Read some future packets for audio stream. if (_audioStreamId >= 0) { - while (_frameMs + 5000 > _lastReadPacketMs) { + while (_frameMs + 5000 > _lastReadAudioMs + && _frameMs + 15000 > _lastReadVideoMs) { auto packetResult = readAndProcessPacket(); if (packetResult != PacketResult::Ok) { break; @@ -327,16 +330,16 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { avcodec_free_context(&audioContext); LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - soundData = std_::make_unique(); - soundData->context = audioContext; - soundData->frequency = audioContextOriginal->sample_rate; - if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { - soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; + _audioStreamId = -1; } else { - soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; + soundData = std_::make_unique(); + soundData->context = audioContext; + soundData->frequency = audioContextOriginal->sample_rate; + if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { + soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; + } else { + soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; + } } } @@ -425,11 +428,13 @@ void FFMpegReaderImplementation::processPacket(AVPacket *packet) { bool videoPacket = (packet->stream_index == _streamId); bool audioPacket = (_audioStreamId >= 0 && packet->stream_index == _audioStreamId); if (audioPacket || videoPacket) { - _lastReadPacketMs = countPacketMs(packet); - if (videoPacket) { + _lastReadVideoMs = countPacketMs(packet); + _packetQueue.enqueue(FFMpeg::dataWrapFromPacket(*packet)); } else if (audioPacket) { + _lastReadAudioMs = countPacketMs(packet); + // queue packet to audio player VideoSoundPart part; part.packet = packet; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index c73fdc8ac..e9cf5074c 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -92,7 +92,8 @@ private: int _audioStreamId = 0; uint64 _playId = 0; - int64 _lastReadPacketMs = 0; + int64 _lastReadVideoMs = 0; + int64 _lastReadAudioMs = 0; QQueue _packetQueue; AVPacket _packetNull; // for final decoding diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 0795ada90..ffb5cca8a 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -489,7 +489,6 @@ public: int64 delta = static_cast(ms) - static_cast(_videoPausedAtMs); _animationStarted += delta; _nextFrameWhen += delta; - LOG(("RESUME VIDEO: next when: %1, started: %2, delta: %3").arg(_nextFrameWhen).arg(_animationStarted).arg(delta)); _videoPausedAtMs = 0; _implementation->resumeAudio(); From cd986d6f5d8a94e889459e9f828e90bdbb12bac2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 22 Jul 2016 14:59:35 +0300 Subject: [PATCH 36/60] Fixed archived stickers loading. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 2d8d02d19..7891ed634 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -1317,7 +1317,7 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti if (auto set = Stickers::feedSet(stickerSet.c_stickerSet())) { auto index = archived.indexOf(set->id); - if (index != archived.size() - 1) { + if (archived.isEmpty() || index != archived.size() - 1) { if (index < archived.size() - 1) { archived.removeAt(index); } From 27cf45e1a97ff77cc56a9152b09423b50037cc50 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 22 Jul 2016 18:01:24 +0300 Subject: [PATCH 37/60] Moved to ffmpeg 3.1 release code. --- Telegram/SourceFiles/media/media_audio.cpp | 107 +++++++++-- Telegram/SourceFiles/media/media_audio.h | 9 +- .../media/media_audio_ffmpeg_loader.cpp | 118 +++++++----- .../media/media_audio_ffmpeg_loader.h | 2 + .../media/media_child_ffmpeg_loader.cpp | 81 +++++---- .../media/media_child_ffmpeg_loader.h | 2 + .../SourceFiles/media/media_clip_ffmpeg.cpp | 170 +++++++++--------- .../SourceFiles/media/media_clip_ffmpeg.h | 1 + doc/building-msvc.md | 4 +- doc/building-qtcreator.md | 4 +- doc/building-xcode-old.md | 2 +- doc/building-xcode.md | 2 +- 12 files changed, 318 insertions(+), 184 deletions(-) diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 71558181c..2ba1b0a88 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -1377,7 +1377,14 @@ void AudioCaptureInner::onStart() { return; } d->stream->id = d->fmtContext->nb_streams - 1; - d->codecContext = d->stream->codec; + d->codecContext = avcodec_alloc_context3(d->codec); + if (!d->codecContext) { + LOG(("Audio Error: Unable to avcodec_alloc_context3 for capture")); + onStop(false); + emit error(); + return; + } + av_opt_set_int(d->codecContext, "refcounted_frames", 1, 0); d->codecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; @@ -1439,6 +1446,13 @@ void AudioCaptureInner::onStart() { } d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0); + if ((res = avcodec_parameters_from_context(d->stream->codecpar, d->codecContext)) < 0) { + LOG(("Audio Error: Unable to avcodec_parameters_from_context for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); + onStop(false); + emit error(); + return; + } + // Write file header if ((res = avformat_write_header(d->fmtContext, 0)) < 0) { LOG(("Audio Error: Unable to avformat_write_header for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); @@ -1486,9 +1500,10 @@ void AudioCaptureInner::onStop(bool needResult) { int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (_captured.size() >= encoded + framesize) { - writeFrame(encoded, framesize); + processFrame(encoded, framesize); encoded += framesize; } + writeFrame(nullptr); // drain the codec if (encoded != _captured.size()) { d->fullSamples = 0; d->dataPos = 0; @@ -1545,7 +1560,7 @@ void AudioCaptureInner::onStop(bool needResult) { d->device = nullptr; if (d->codecContext) { - avcodec_close(d->codecContext); + avcodec_free_context(&d->codecContext); d->codecContext = nullptr; } if (d->srcSamplesData) { @@ -1648,7 +1663,7 @@ void AudioCaptureInner::onTimeout() { // Write frames int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (uint32(_captured.size()) >= encoded + framesize + fadeSamples * sizeof(short)) { - writeFrame(encoded, framesize); + processFrame(encoded, framesize); encoded += framesize; } @@ -1663,7 +1678,7 @@ void AudioCaptureInner::onTimeout() { } } -void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { +void AudioCaptureInner::processFrame(int32 offset, int32 framesize) { // Prepare audio frame if (framesize % sizeof(short)) { // in the middle of a sample @@ -1730,33 +1745,93 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { // Write audio frame - AVPacket pkt; - memset(&pkt, 0, sizeof(pkt)); // data and size must be 0; AVFrame *frame = av_frame_alloc(); - int gotPacket; - av_init_packet(&pkt); frame->nb_samples = d->dstSamples; + frame->pts = av_rescale_q(d->fullSamples, (AVRational){1, d->codecContext->sample_rate}, d->codecContext->time_base); + avcodec_fill_audio_frame(frame, d->codecContext->channels, d->codecContext->sample_fmt, d->dstSamplesData[0], d->dstSamplesSize, 0); - if ((res = avcodec_encode_audio2(d->codecContext, &pkt, frame, &gotPacket)) < 0) { - LOG(("Audio Error: Unable to avcodec_encode_audio2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + writeFrame(frame); + + d->fullSamples += samplesCnt; + + av_frame_free(&frame); +} + +void AudioCaptureInner::writeFrame(AVFrame *frame) { + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + res = avcodec_send_frame(d->codecContext, frame); + if (res == AVERROR(EAGAIN)) { + int packetsWritten = writePackets(); + if (packetsWritten < 0) { + if (frame && packetsWritten == AVERROR_EOF) { + LOG(("Audio Error: EOF in packets received when EAGAIN was got in avcodec_send_frame()")); + onStop(false); + emit error(); + } + return; + } else if (!packetsWritten) { + LOG(("Audio Error: No packets received when EAGAIN was got in avcodec_send_frame()")); + onStop(false); + emit error(); + return; + } + res = avcodec_send_frame(d->codecContext, frame); + } + if (res < 0) { + LOG(("Audio Error: Unable to avcodec_send_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); return; } - if (gotPacket) { + if (!frame) { // drain + if ((res = writePackets()) != AVERROR_EOF) { + LOG(("Audio Error: not EOF in packets received when draining the codec, result %1").arg(res)); + onStop(false); + emit error(); + } + } +} + +int AudioCaptureInner::writePackets() { + AVPacket pkt; + memset(&pkt, 0, sizeof(pkt)); // data and size must be 0; + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + int written = 0; + do { + av_init_packet(&pkt); + if ((res = avcodec_receive_packet(d->codecContext, &pkt)) < 0) { + if (res == AVERROR(EAGAIN)) { + return written; + } else if (res == AVERROR_EOF) { + return res; + } + LOG(("Audio Error: Unable to avcodec_receive_packet for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); + onStop(false); + emit error(); + return res; + } + + av_packet_rescale_ts(&pkt, d->codecContext->time_base, d->stream->time_base); pkt.stream_index = d->stream->index; if ((res = av_interleaved_write_frame(d->fmtContext, &pkt)) < 0) { LOG(("Audio Error: Unable to av_interleaved_write_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); - return; + return -1; } - } - d->fullSamples += samplesCnt; - av_frame_free(&frame); + ++written; + av_packet_unref(&pkt); + } while (true); + return written; } class FFMpegAttributesReader : public AbstractFFMpegLoader { diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index a4fc7c785..87b8cfd08 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -264,6 +264,7 @@ private: }; struct AudioCapturePrivate; +struct AVFrame; class AudioCaptureInner : public QObject { Q_OBJECT @@ -289,7 +290,13 @@ signals: private: - void writeFrame(int32 offset, int32 framesize); + void processFrame(int32 offset, int32 framesize); + + void writeFrame(AVFrame *frame); + + // Writes the packets till EAGAIN is got from av_receive_packet() + // Returns number of packets written or -1 on error + int writePackets(); AudioCapturePrivate *d; QTimer _timer; diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp index b9ef7e020..a8a4227f1 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -65,7 +65,7 @@ bool AbstractFFMpegLoader::open(qint64 &position) { return false; } - freq = fmtContext->streams[streamId]->codec->sample_rate; + freq = fmtContext->streams[streamId]->codecpar->sample_rate; if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { len = (fmtContext->duration * freq) / AV_TIME_BASE; } else { @@ -145,15 +145,26 @@ bool FFMpegLoader::open(qint64 &position) { int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - // Get a pointer to the codec context for the audio stream - av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { + auto codecParams = fmtContext->streams[streamId]->codecpar; + + codecContext = avcodec_alloc_context3(nullptr); + if (!codecContext) { + LOG(("Audio Error: Unable to avcodec_alloc_context3 for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + if ((res = avcodec_parameters_to_context(codecContext, codecParams)) < 0) { + LOG(("Audio Error: Unable to avcodec_parameters_to_context for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + av_codec_set_pkt_timebase(codecContext, fmtContext->streams[streamId]->time_base); + av_opt_set_int(codecContext, "refcounted_frames", 1, 0); + + if ((res = avcodec_open2(codecContext, codec, 0)) < 0) { LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } - codecContext = fmtContext->streams[streamId]->codec; - uint64_t layout = codecContext->channel_layout; + uint64_t layout = codecParams->channel_layout; inputFormat = codecContext->sample_fmt; switch (layout) { case AV_CH_LAYOUT_MONO: @@ -232,66 +243,81 @@ bool FFMpegLoader::open(qint64 &position) { AudioPlayerLoader::ReadResult FFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { int res; + + av_frame_unref(frame); + res = avcodec_receive_frame(codecContext, frame); + if (res >= 0) { + return readFromReadyFrame(result, samplesAdded); + } + + if (res == AVERROR_EOF) { + return ReadResult::EndOfFile; + } else if (res != AVERROR(EAGAIN)) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; + } + if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { if (res != AVERROR_EOF) { char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return ReadResult::Error; } - return ReadResult::EndOfFile; + avcodec_send_packet(codecContext, nullptr); // drain + return ReadResult::Ok; } - if (avpkt.stream_index == streamId) { - av_frame_unref(frame); - int got_frame = 0; - if ((res = avcodec_decode_audio4(codecContext, frame, &got_frame, &avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + if (avpkt.stream_index == streamId) { + res = avcodec_send_packet(codecContext, &avpkt); + if (res < 0) { av_packet_unref(&avpkt); + + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); if (res == AVERROR_INVALIDDATA) { return ReadResult::NotYet; // try to skip bad packet } return ReadResult::Error; } - - if (got_frame) { - if (dstSamplesData) { // convert needed - int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); - if (dstSamples > maxResampleSamples) { - maxResampleSamples = dstSamples; - av_free(dstSamplesData[0]); - - if ((res = av_samples_alloc(dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 1)) < 0) { - dstSamplesData[0] = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return ReadResult::Error; - } - } - if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return ReadResult::Error; - } - int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); - result.append((const char*)dstSamplesData[0], resultLen); - samplesAdded += resultLen / sampleSize; - } else { - result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); - samplesAdded += frame->nb_samples; - } - } } av_packet_unref(&avpkt); return ReadResult::Ok; } +AudioPlayerLoader::ReadResult FFMpegLoader::readFromReadyFrame(QByteArray &result, int64 &samplesAdded) { + int res = 0; + + if (dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); + if (dstSamples > maxResampleSamples) { + maxResampleSamples = dstSamples; + av_free(dstSamplesData[0]); + + if ((res = av_samples_alloc(dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 1)) < 0) { + dstSamplesData[0] = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; + } + } + if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)dstSamplesData[0], resultLen); + samplesAdded += resultLen / sampleSize; + } else { + result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); + samplesAdded += frame->nb_samples; + } + return ReadResult::Ok; +} + FFMpegLoader::~FFMpegLoader() { - if (codecContext) avcodec_close(codecContext); + if (codecContext) avcodec_free_context(&codecContext); if (swrContext) swr_free(&swrContext); if (dstSamplesData) { if (dstSamplesData[0]) { diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h index c856be631..4b9ec0349 100644 --- a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h @@ -86,6 +86,8 @@ protected: int32 sampleSize = 2 * sizeof(uint16); private: + ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded); + int32 fmt = AL_FORMAT_STEREO16; int32 srcRate = AudioVoiceMsgFrequency; int32 dstRate = AudioVoiceMsgFrequency; diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp index 143f9f1c6..8932fcde3 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -114,65 +114,78 @@ bool ChildFFMpegLoader::open(qint64 &position) { } AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { + int res; + + av_frame_unref(_frame); + res = avcodec_receive_frame(_parentData->context, _frame); + if (res >= 0) { + return readFromReadyFrame(result, samplesAdded); + } + + if (res == AVERROR_EOF) { + return ReadResult::EndOfFile; + } else if (res != AVERROR(EAGAIN)) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; + } + if (_queue.isEmpty()) { return _eofReached ? ReadResult::EndOfFile : ReadResult::Wait; } - av_frame_unref(_frame); - int got_frame = 0; - int res = 0; - AVPacket packet; FFMpeg::packetFromDataWrap(packet, _queue.dequeue()); _eofReached = FFMpeg::isNullPacket(packet); if (_eofReached) { - return ReadResult::EndOfFile; + avcodec_send_packet(_parentData->context, nullptr); // drain + return ReadResult::Ok; } - if ((res = avcodec_decode_audio4(_parentData->context, _frame, &got_frame, &packet)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - + res = avcodec_send_packet(_parentData->context, &packet); + if (res < 0) { FFMpeg::freePacket(&packet); + + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); if (res == AVERROR_INVALIDDATA) { return ReadResult::NotYet; // try to skip bad packet } return ReadResult::Error; } + FFMpeg::freePacket(&packet); + return ReadResult::Ok; +} - if (got_frame) { - if (_dstSamplesData) { // convert needed - int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP); - if (dstSamples > _maxResampleSamples) { - _maxResampleSamples = dstSamples; - av_free(_dstSamplesData[0]); +AudioPlayerLoader::ReadResult ChildFFMpegLoader::readFromReadyFrame(QByteArray &result, int64 &samplesAdded) { + int res = 0; - if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) { - _dstSamplesData[0] = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + if (_dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP); + if (dstSamples > _maxResampleSamples) { + _maxResampleSamples = dstSamples; + av_free(_dstSamplesData[0]); - FFMpeg::freePacket(&packet); - return ReadResult::Error; - } - } - if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) { + if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) { + _dstSamplesData[0] = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - FFMpeg::freePacket(&packet); + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return ReadResult::Error; } - int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); - result.append((const char*)_dstSamplesData[0], resultLen); - samplesAdded += resultLen / _sampleSize; - } else { - result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize); - samplesAdded += _frame->nb_samples; } + if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)_dstSamplesData[0], resultLen); + samplesAdded += resultLen / _sampleSize; + } else { + result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize); + samplesAdded += _frame->nb_samples; } - FFMpeg::freePacket(&packet); return ReadResult::Ok; } diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h index ad92459ab..d7542cd12 100644 --- a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -114,6 +114,8 @@ public: ~ChildFFMpegLoader(); private: + ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded); + bool _eofReached = false; int32 _sampleSize = 2 * sizeof(uint16); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 3104c4d9f..5378fea2c 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -42,77 +42,14 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { _frameRead = false; } - while (true) { - while (_packetQueue.isEmpty()) { - auto packetResult = readAndProcessPacket(); - if (packetResult == PacketResult::Error) { - return ReadResult::Error; - } else if (packetResult == PacketResult::EndOfFile) { - break; - } - } - bool eofReached = _packetQueue.isEmpty(); - - startPacket(); - - int got_frame = 0; - auto packet = &_packetNull; - AVPacket tempPacket; - if (!_packetQueue.isEmpty()) { - FFMpeg::packetFromDataWrap(tempPacket, _packetQueue.head()); - packet = &tempPacket; - } - int decoded = packet->size; - - int res = 0; - if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, packet)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - if (res == AVERROR_INVALIDDATA) { // try to skip bad packet - finishPacket(); - continue; - } - - eofReached = (res == AVERROR_EOF); - if (!eofReached || !_hadFrame) { // try to skip end of file - return ReadResult::Error; - } - } - if (res > 0) decoded = res; - - if (!_packetQueue.isEmpty()) { - packet->data += decoded; - packet->size -= decoded; - if (packet->size <= 0) { - finishPacket(); - } - } - - if (got_frame) { - int64 duration = av_frame_get_pkt_duration(_frame); - int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; - int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - _currentFrameDelay = _nextFrameDelay; - if (_frameMs + _currentFrameDelay < frameMs) { - _currentFrameDelay = int32(frameMs - _frameMs); - } else if (frameMs < _frameMs + _currentFrameDelay) { - frameMs = _frameMs + _currentFrameDelay; - } - - if (duration == AV_NOPTS_VALUE) { - _nextFrameDelay = 0; - } else { - _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - _frameMs = frameMs; - - _hadFrame = _frameRead = true; - _frameTime += _currentFrameDelay; + do { + int res = avcodec_receive_frame(_codecContext, _frame); + if (res >= 0) { + processReadFrame(); return ReadResult::Success; } - if (eofReached) { + if (res == AVERROR_EOF) { clearPacketQueue(); if (_mode == Mode::Normal) { return ReadResult::Eof; @@ -133,12 +70,70 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { _hadFrame = false; _frameMs = 0; _lastReadVideoMs = _lastReadAudioMs = 0; + + continue; + } else if (res != AVERROR(EAGAIN)) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_receive_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return ReadResult::Error; } - } + + while (_packetQueue.isEmpty()) { + auto packetResult = readAndProcessPacket(); + if (packetResult == PacketResult::Error) { + return ReadResult::Error; + } else if (packetResult == PacketResult::EndOfFile) { + break; + } + } + if (_packetQueue.isEmpty()) { + avcodec_send_packet(_codecContext, nullptr); // drain + continue; + } + + startPacket(); + + AVPacket packet; + FFMpeg::packetFromDataWrap(packet, _packetQueue.head()); + res = avcodec_send_packet(_codecContext, &packet); + if (res < 0) { + finishPacket(); + + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_send_packet() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + if (res == AVERROR_INVALIDDATA) { + continue; // try to skip bad packet + } + return ReadResult::Error; + } + finishPacket(); + } while (true); return ReadResult::Error; } +void FFMpegReaderImplementation::processReadFrame() { + int64 duration = av_frame_get_pkt_duration(_frame); + int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; + int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + _currentFrameDelay = _nextFrameDelay; + if (_frameMs + _currentFrameDelay < frameMs) { + _currentFrameDelay = int32(frameMs - _frameMs); + } else if (frameMs < _frameMs + _currentFrameDelay) { + frameMs = _frameMs + _currentFrameDelay; + } + + if (duration == AV_NOPTS_VALUE) { + _nextFrameDelay = 0; + } else { + _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + _frameMs = frameMs; + + _hadFrame = _frameRead = true; + _frameTime += _currentFrameDelay; +} + ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(int64 frameMs, uint64 systemMs) { if (_audioStreamId < 0) { // just keep up if (_frameRead && _frameTime > frameMs) { @@ -291,8 +286,18 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { } _packetNull.stream_index = _streamId; - // Get a pointer to the codec context for the video stream - _codecContext = _fmtContext->streams[_streamId]->codec; + _codecContext = avcodec_alloc_context3(nullptr); + if (!_codecContext) { + LOG(("Audio Error: Unable to avcodec_alloc_context3 %1").arg(logData())); + return false; + } + if ((res = avcodec_parameters_to_context(_codecContext, _fmtContext->streams[_streamId]->codecpar)) < 0) { + LOG(("Audio Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + av_codec_set_pkt_timebase(_codecContext, _fmtContext->streams[_streamId]->time_base); + av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); + _codec = avcodec_find_decoder(_codecContext->codec_id); _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); @@ -309,7 +314,7 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { } else if (_mode == Mode::Silent || !audioPlayer() || !_playId) { _audioStreamId = -1; } - av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; @@ -317,16 +322,19 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { std_::unique_ptr soundData; if (_audioStreamId >= 0) { - // Get a pointer to the codec context for the audio stream - auto audioContextOriginal = _fmtContext->streams[_audioStreamId]->codec; - auto audioCodec = avcodec_find_decoder(audioContextOriginal->codec_id); - - AVCodecContext *audioContext = avcodec_alloc_context3(audioCodec); - if ((res = avcodec_copy_context(audioContext, audioContextOriginal)) != 0) { - LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + AVCodecContext *audioContext = avcodec_alloc_context3(nullptr); + if (!audioContext) { + LOG(("Audio Error: Unable to avcodec_alloc_context3 %1").arg(logData())); return false; } + if ((res = avcodec_parameters_to_context(audioContext, _fmtContext->streams[_audioStreamId]->codecpar)) < 0) { + LOG(("Audio Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + av_codec_set_pkt_timebase(audioContext, _fmtContext->streams[_audioStreamId]->time_base); av_opt_set_int(audioContext, "refcounted_frames", 1, 0); + + auto audioCodec = avcodec_find_decoder(audioContext->codec_id); if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { avcodec_free_context(&audioContext); LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); @@ -334,7 +342,7 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { } else { soundData = std_::make_unique(); soundData->context = audioContext; - soundData->frequency = audioContextOriginal->sample_rate; + soundData->frequency = _fmtContext->streams[_audioStreamId]->codecpar->sample_rate; if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; } else { @@ -385,7 +393,7 @@ FFMpegReaderImplementation::~FFMpegReaderImplementation() { av_frame_unref(_frame); _frameRead = false; } - if (_codecContext) avcodec_close(_codecContext); + if (_codecContext) avcodec_free_context(&_codecContext); if (_swsContext) sws_freeContext(_swsContext); if (_opened) { avformat_close_input(&_fmtContext); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index e9cf5074c..e805a099d 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -59,6 +59,7 @@ public: private: ReadResult readNextFrame(); + void processReadFrame(); enum class PacketResult { Ok, diff --git a/doc/building-msvc.md b/doc/building-msvc.md index 58865d87e..ec67b58d6 100644 --- a/doc/building-msvc.md +++ b/doc/building-msvc.md @@ -148,7 +148,7 @@ Open **VS2015 x86 Native Tools Command Prompt.bat** (should be in **Start Menu > git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/2.8 + git checkout release/3.1 http://msys2.github.io/ > Download [msys2-x86_64-20150512.exe](http://sourceforge.net/projects/msys2/files/Base/x86_64/msys2-x86_64-20150512.exe/download) and install to **D:\\msys64** @@ -169,7 +169,7 @@ Open **VS2015 x86 Native Tools Command Prompt.bat** (should be in **Start Menu > PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH" - ./configure --toolchain=msvc --disable-programs --disable-doc --disable-everything --disable-w32threads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_d3d11va --enable-hwaccel=h264_dxva2 --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib" + ./configure --toolchain=msvc --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_d3d11va --enable-hwaccel=h264_dxva2 --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib" make make install diff --git a/doc/building-qtcreator.md b/doc/building-qtcreator.md index 18695f128..afb7e60de 100644 --- a/doc/building-qtcreator.md +++ b/doc/building-qtcreator.md @@ -69,13 +69,13 @@ In Terminal go to **/home/user/TBuild/Libraries** and run git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/2.8 + git checkout release/3.1 sudo apt-get update sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev sudo apt-get install yasm - ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-pthreads --disable-mmx --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vdpau --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=mpeg4_vdpau --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=mpeg4_vaapi --enable-hwaccel=mpeg4_vdpau --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus + ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vdpau --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=mpeg4_vdpau --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=mpeg4_vaapi --enable-hwaccel=mpeg4_vdpau --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus make sudo make install diff --git a/doc/building-xcode-old.md b/doc/building-xcode-old.md index c77daf41d..58d3c8143 100644 --- a/doc/building-xcode-old.md +++ b/doc/building-xcode-old.md @@ -153,7 +153,7 @@ Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg** and run LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig - ./configure --prefix=/usr/local/ffmpeg_old --disable-programs --disable-doc --disable-everything --disable-pthreads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.6" --extra-cxxflags="-mmacosx-version-min=10.6" --extra-ldflags="-mmacosx-version-min=10.6" + ./configure --prefix=/usr/local/ffmpeg_old --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.6" --extra-cxxflags="-mmacosx-version-min=10.6" --extra-ldflags="-mmacosx-version-min=10.6" make sudo make install diff --git a/doc/building-xcode.md b/doc/building-xcode.md index dcf0ae122..eb6bcca8a 100644 --- a/doc/building-xcode.md +++ b/doc/building-xcode.md @@ -187,7 +187,7 @@ Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg** and run: LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig - ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --disable-pthreads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vda --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=mpeg4_videotoolbox --enable-hwaccel=h264_vda --enable-hwaccel=h264_videotoolbox --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.8" --extra-cxxflags="-mmacosx-version-min=10.8" --extra-ldflags="-mmacosx-version-min=10.8" + ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --enable-protocol=file --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vda --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=mpeg4_videotoolbox --enable-hwaccel=h264_vda --enable-hwaccel=h264_videotoolbox --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.8" --extra-cxxflags="-mmacosx-version-min=10.8" --extra-ldflags="-mmacosx-version-min=10.8" make sudo make install From 02e54dd8ba0cc65bca49a0814bcd3bcf190f28b4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 22 Jul 2016 19:04:38 +0300 Subject: [PATCH 38/60] Scheme updated with covered stickers, not used yet. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 8 +- Telegram/SourceFiles/historywidget.cpp | 4 +- Telegram/SourceFiles/mtproto/scheme.tl | 14 ++- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 19 ++- Telegram/SourceFiles/mtproto/scheme_auto.h | 125 +++++++++++++++---- 5 files changed, 133 insertions(+), 37 deletions(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 7891ed634..62c8c5580 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -47,8 +47,8 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { archived.reserve(v.size()); QMap setsToRequest; for_const (auto &stickerSet, v) { - if (stickerSet.type() == mtpc_stickerSet) { - auto set = Stickers::feedSet(stickerSet.c_stickerSet()); + if (stickerSet.type() == mtpc_stickerSetCovered && stickerSet.c_stickerSetCovered().vset.type() == mtpc_stickerSet) { + auto set = Stickers::feedSet(stickerSet.c_stickerSetCovered().vset.c_stickerSet()); if (set->stickers.isEmpty()) { setsToRequest.insert(set->id, set->access); } @@ -1313,9 +1313,9 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti bool addedSet = false; auto &v = stickers.vsets.c_vector().v; for_const (auto &stickerSet, v) { - if (stickerSet.type() != mtpc_stickerSet) continue; + if (stickerSet.type() != mtpc_stickerSetCovered || stickerSet.c_stickerSetCovered().vset.type() != mtpc_stickerSet) continue; - if (auto set = Stickers::feedSet(stickerSet.c_stickerSet())) { + if (auto set = Stickers::feedSet(stickerSet.c_stickerSetCovered().vset.c_stickerSet())) { auto index = archived.indexOf(set->id); if (archived.isEmpty() || index != archived.size() - 1) { if (index < archived.size() - 1) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 48f0cc5ce..4f94fea9c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3838,8 +3838,8 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing } for (int i = 0, l = d_sets.size(); i != l; ++i) { - if (d_sets.at(i).type() == mtpc_stickerSet) { - const auto &set(d_sets.at(i).c_stickerSet()); + if (d_sets.at(i).type() == mtpc_stickerSetCovered && d_sets.at(i).c_stickerSetCovered().vset.type() == mtpc_stickerSet) { + const auto &set(d_sets.at(i).c_stickerSetCovered().vset.c_stickerSet()); auto it = sets.find(set.vid.v); QString title = stickerSetTitle(set); if (it == sets.cend()) { diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 168945f8d..41b8c66b5 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -672,7 +672,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; -messages.botCallbackAnswer#31fde6e4 flags:# alert:flags.1?true message:flags.0?string url:flags.3?string = messages.BotCallbackAnswer; +messages.botCallbackAnswer#b10df1fb flags:# alert:flags.1?true message:flags.0?string url:flags.2?string = messages.BotCallbackAnswer; messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; @@ -699,15 +699,17 @@ draftMessageEmpty#ba4baec5 = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; -messages.featuredStickers#ed6392b7 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickers#f89d88e5 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#5ce20970 hash:int stickers:Vector = messages.RecentStickers; -messages.archivedStickers#f3475c0c count:int sets:Vector = messages.ArchivedStickers; +messages.archivedStickers#4fcba9c8 count:int sets:Vector = messages.ArchivedStickers; messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallResult; -messages.stickerSetInstallResultArchive#192c8a4e sets:Vector = messages.StickerSetInstallResult; +messages.stickerSetInstallResultArchive#35e410a8 sets:Vector = messages.StickerSetInstallResult; + +stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; ---functions--- @@ -839,7 +841,7 @@ messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEdi messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#a6e94f04 peer:InputPeer msg_id:int data:bytes = messages.BotCallbackAnswer; -messages.setBotCallbackAnswer#70dc0fa3 flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.3?string = Bool; +messages.setBotCallbackAnswer#c927d44b flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string = Bool; messages.getPeerDialogs#2d9776b9 peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; @@ -848,7 +850,7 @@ messages.readFeaturedStickers#e21cbb = Bool; messages.getRecentStickers#99197c2c hash:int = messages.RecentStickers; messages.saveRecentSticker#348e39bf id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#ab02e5d2 = Bool; -messages.getUnusedStickers#a978d356 limit:int = Vector; +messages.getUnusedStickers#4309d65b limit:int = Vector; messages.getArchivedStickers#906e241f offset_id:long limit:int = messages.ArchivedStickers; updates.getState#edd4882a = updates.State; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 65e157812..6213eba12 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -5601,7 +5601,7 @@ void _serialize_messages_botCallbackAnswer(MTPStringLogger &to, int32 stage, int case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; case 2: to.add(" message: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 3: to.add(" url: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 3: to.add(" url: "); ++stages.back(); if (flag & MTPDmessages_botCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -5828,6 +5828,20 @@ void _serialize_messages_stickerSetInstallResultArchive(MTPStringLogger &to, int } } +void _serialize_stickerSetCovered(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ stickerSetCovered"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" set: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" cover: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6406,7 +6420,7 @@ void _serialize_messages_setBotCallbackAnswer(MTPStringLogger &to, int32 stage, case 1: to.add(" alert: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_alert) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; case 2: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 3: to.add(" message: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_message) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 4: to.add(" url: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 4: to.add(" url: "); ++stages.back(); if (flag & MTPmessages_setBotCallbackAnswer::Flag::f_url) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -8865,6 +8879,7 @@ namespace { _serializers.insert(mtpc_messages_archivedStickers, _serialize_messages_archivedStickers); _serializers.insert(mtpc_messages_stickerSetInstallResultSuccess, _serialize_messages_stickerSetInstallResultSuccess); _serializers.insert(mtpc_messages_stickerSetInstallResultArchive, _serialize_messages_stickerSetInstallResultArchive); + _serializers.insert(mtpc_stickerSetCovered, _serialize_stickerSetCovered); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 3ad9425eb..e883e74ee 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -487,7 +487,7 @@ enum { mtpc_auth_sentCodeTypeSms = 0xc000bba2, mtpc_auth_sentCodeTypeCall = 0x5353e5a7, mtpc_auth_sentCodeTypeFlashCall = 0xab03c6d9, - mtpc_messages_botCallbackAnswer = 0x31fde6e4, + mtpc_messages_botCallbackAnswer = 0xb10df1fb, mtpc_messages_messageEditData = 0x26b5dde6, mtpc_inputBotInlineMessageID = 0x890c3d89, mtpc_inlineBotSwitchPM = 0x3c20629f, @@ -504,12 +504,13 @@ enum { mtpc_draftMessageEmpty = 0xba4baec5, mtpc_draftMessage = 0xfd8e711f, mtpc_messages_featuredStickersNotModified = 0x4ede3cf, - mtpc_messages_featuredStickers = 0xed6392b7, + mtpc_messages_featuredStickers = 0xf89d88e5, mtpc_messages_recentStickersNotModified = 0xb17f890, mtpc_messages_recentStickers = 0x5ce20970, - mtpc_messages_archivedStickers = 0xf3475c0c, + mtpc_messages_archivedStickers = 0x4fcba9c8, mtpc_messages_stickerSetInstallResultSuccess = 0x38641628, - mtpc_messages_stickerSetInstallResultArchive = 0x192c8a4e, + mtpc_messages_stickerSetInstallResultArchive = 0x35e410a8, + mtpc_stickerSetCovered = 0x6410a5d2, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -633,7 +634,7 @@ enum { mtpc_messages_editMessage = 0xce91e4ca, mtpc_messages_editInlineBotMessage = 0x130c2c85, mtpc_messages_getBotCallbackAnswer = 0xa6e94f04, - mtpc_messages_setBotCallbackAnswer = 0x70dc0fa3, + mtpc_messages_setBotCallbackAnswer = 0xc927d44b, mtpc_messages_getPeerDialogs = 0x2d9776b9, mtpc_messages_saveDraft = 0xbc39e14b, mtpc_messages_getAllDrafts = 0x6a3f8d65, @@ -642,7 +643,7 @@ enum { mtpc_messages_getRecentStickers = 0x99197c2c, mtpc_messages_saveRecentSticker = 0x348e39bf, mtpc_messages_clearRecentStickers = 0xab02e5d2, - mtpc_messages_getUnusedStickers = 0xa978d356, + mtpc_messages_getUnusedStickers = 0x4309d65b, mtpc_messages_getArchivedStickers = 0x906e241f, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, @@ -1381,6 +1382,9 @@ class MTPDmessages_archivedStickers; class MTPmessages_stickerSetInstallResult; class MTPDmessages_stickerSetInstallResultArchive; +class MTPstickerSetCovered; +class MTPDstickerSetCovered; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1559,6 +1563,7 @@ typedef MTPBoxed MTPmessages_FeaturedStickers; typedef MTPBoxed MTPmessages_RecentStickers; typedef MTPBoxed MTPmessages_ArchivedStickers; typedef MTPBoxed MTPmessages_StickerSetInstallResult; +typedef MTPBoxed MTPStickerSetCovered; // Type classes definitions @@ -9706,6 +9711,37 @@ private: }; typedef MTPBoxed MTPmessages_StickerSetInstallResult; +class MTPstickerSetCovered : private mtpDataOwner { +public: + MTPstickerSetCovered(); + MTPstickerSetCovered(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_stickerSetCovered) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDstickerSetCovered &_stickerSetCovered() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDstickerSetCovered*)data; + } + const MTPDstickerSetCovered &c_stickerSetCovered() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDstickerSetCovered*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_stickerSetCovered); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPstickerSetCovered(MTPDstickerSetCovered *_data); + + friend class MTP::internal::TypeCreator; +}; +typedef MTPBoxed MTPStickerSetCovered; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -14457,9 +14493,9 @@ public: enum class Flag : int32 { f_alert = (1 << 1), f_message = (1 << 0), - f_url = (1 << 3), + f_url = (1 << 2), - MAX_FIELD = (1 << 3), + MAX_FIELD = (1 << 2), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } @@ -14601,11 +14637,11 @@ class MTPDmessages_featuredStickers : public mtpDataImpl &_sets, const MTPVector &_unread) : vhash(_hash), vsets(_sets), vunread(_unread) { + MTPDmessages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) : vhash(_hash), vsets(_sets), vunread(_unread) { } MTPint vhash; - MTPVector vsets; + MTPVector vsets; MTPVector vunread; }; @@ -14624,21 +14660,32 @@ class MTPDmessages_archivedStickers : public mtpDataImpl &_sets) : vcount(_count), vsets(_sets) { + MTPDmessages_archivedStickers(MTPint _count, const MTPVector &_sets) : vcount(_count), vsets(_sets) { } MTPint vcount; - MTPVector vsets; + MTPVector vsets; }; class MTPDmessages_stickerSetInstallResultArchive : public mtpDataImpl { public: MTPDmessages_stickerSetInstallResultArchive() { } - MTPDmessages_stickerSetInstallResultArchive(const MTPVector &_sets) : vsets(_sets) { + MTPDmessages_stickerSetInstallResultArchive(const MTPVector &_sets) : vsets(_sets) { } - MTPVector vsets; + MTPVector vsets; +}; + +class MTPDstickerSetCovered : public mtpDataImpl { +public: + MTPDstickerSetCovered() { + } + MTPDstickerSetCovered(const MTPStickerSet &_set, const MTPDocument &_cover) : vset(_set), vcover(_cover) { + } + + MTPStickerSet vset; + MTPDocument vcover; }; // RPC methods @@ -20434,9 +20481,9 @@ public: enum class Flag : int32 { f_alert = (1 << 1), f_message = (1 << 0), - f_url = (1 << 3), + f_url = (1 << 2), - MAX_FIELD = (1 << 3), + MAX_FIELD = (1 << 2), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } @@ -20837,7 +20884,7 @@ public: vlimit.write(to); } - typedef MTPVector ResponseType; + typedef MTPVector ResponseType; }; class MTPmessages_GetUnusedStickers : public MTPBoxed { public: @@ -24082,7 +24129,7 @@ public: inline static MTPmessages_featuredStickers new_messages_featuredStickersNotModified() { return MTPmessages_featuredStickers(mtpc_messages_featuredStickersNotModified); } - inline static MTPmessages_featuredStickers new_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { + inline static MTPmessages_featuredStickers new_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { return MTPmessages_featuredStickers(new MTPDmessages_featuredStickers(_hash, _sets, _unread)); } inline static MTPmessages_recentStickers new_messages_recentStickersNotModified() { @@ -24091,15 +24138,18 @@ public: inline static MTPmessages_recentStickers new_messages_recentStickers(MTPint _hash, const MTPVector &_stickers) { return MTPmessages_recentStickers(new MTPDmessages_recentStickers(_hash, _stickers)); } - inline static MTPmessages_archivedStickers new_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { + inline static MTPmessages_archivedStickers new_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { return MTPmessages_archivedStickers(new MTPDmessages_archivedStickers(_count, _sets)); } inline static MTPmessages_stickerSetInstallResult new_messages_stickerSetInstallResultSuccess() { return MTPmessages_stickerSetInstallResult(mtpc_messages_stickerSetInstallResultSuccess); } - inline static MTPmessages_stickerSetInstallResult new_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { + inline static MTPmessages_stickerSetInstallResult new_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { return MTPmessages_stickerSetInstallResult(new MTPDmessages_stickerSetInstallResultArchive(_sets)); } + inline static MTPstickerSetCovered new_stickerSetCovered(const MTPStickerSet &_set, const MTPDocument &_cover) { + return MTPstickerSetCovered(new MTPDstickerSetCovered(_set, _cover)); + } }; } // namespace internal @@ -35494,7 +35544,7 @@ inline MTPmessages_featuredStickers::MTPmessages_featuredStickers(MTPDmessages_f inline MTPmessages_featuredStickers MTP_messages_featuredStickersNotModified() { return MTP::internal::TypeCreator::new_messages_featuredStickersNotModified(); } -inline MTPmessages_featuredStickers MTP_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { +inline MTPmessages_featuredStickers MTP_messages_featuredStickers(MTPint _hash, const MTPVector &_sets, const MTPVector &_unread) { return MTP::internal::TypeCreator::new_messages_featuredStickers(_hash, _sets, _unread); } @@ -35574,7 +35624,7 @@ inline void MTPmessages_archivedStickers::write(mtpBuffer &to) const { } inline MTPmessages_archivedStickers::MTPmessages_archivedStickers(MTPDmessages_archivedStickers *_data) : mtpDataOwner(_data) { } -inline MTPmessages_archivedStickers MTP_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { +inline MTPmessages_archivedStickers MTP_messages_archivedStickers(MTPint _count, const MTPVector &_sets) { return MTP::internal::TypeCreator::new_messages_archivedStickers(_count, _sets); } @@ -35623,9 +35673,38 @@ inline MTPmessages_stickerSetInstallResult::MTPmessages_stickerSetInstallResult( inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultSuccess() { return MTP::internal::TypeCreator::new_messages_stickerSetInstallResultSuccess(); } -inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { +inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultArchive(const MTPVector &_sets) { return MTP::internal::TypeCreator::new_messages_stickerSetInstallResultArchive(_sets); } + +inline MTPstickerSetCovered::MTPstickerSetCovered() : mtpDataOwner(new MTPDstickerSetCovered()) { +} + +inline uint32 MTPstickerSetCovered::innerLength() const { + const MTPDstickerSetCovered &v(c_stickerSetCovered()); + return v.vset.innerLength() + v.vcover.innerLength(); +} +inline mtpTypeId MTPstickerSetCovered::type() const { + return mtpc_stickerSetCovered; +} +inline void MTPstickerSetCovered::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_stickerSetCovered) throw mtpErrorUnexpected(cons, "MTPstickerSetCovered"); + + if (!data) setData(new MTPDstickerSetCovered()); + MTPDstickerSetCovered &v(_stickerSetCovered()); + v.vset.read(from, end); + v.vcover.read(from, end); +} +inline void MTPstickerSetCovered::write(mtpBuffer &to) const { + const MTPDstickerSetCovered &v(c_stickerSetCovered()); + v.vset.write(to); + v.vcover.write(to); +} +inline MTPstickerSetCovered::MTPstickerSetCovered(MTPDstickerSetCovered *_data) : mtpDataOwner(_data) { +} +inline MTPstickerSetCovered MTP_stickerSetCovered(const MTPStickerSet &_set, const MTPDocument &_cover) { + return MTP::internal::TypeCreator::new_stickerSetCovered(_set, _cover); +} inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } inline MTPDmessage::Flags mtpCastFlags(MTPflags flags) { return mtpCastFlags(flags.v); } inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } From 26723fb82085981c4ef4a585ca0abf274eda99ac Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 22 Jul 2016 19:22:25 +0300 Subject: [PATCH 39/60] Alpha version 0.9.58: embedded video player. --- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/application.cpp | 9 ++------- Telegram/SourceFiles/core/version.h | 8 ++++---- Telegram/SourceFiles/media/media_audio.cpp | 2 +- Telegram/Telegram.xcodeproj/project.pbxproj | 4 ++-- Telegram/build/version | 10 +++++----- 7 files changed, 22 insertions(+), 27 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4fbc4908a..3132ab97a 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,1 - PRODUCTVERSION 0,9,57,1 + FILEVERSION 0,9,58,0 + PRODUCTVERSION 0,9,58,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.57.1" + VALUE "FileVersion", "0.9.58.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.1" + VALUE "ProductVersion", "0.9.58.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0c3a7892d..69822ab7a 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,57,1 - PRODUCTVERSION 0,9,57,1 + FILEVERSION 0,9,58,0 + PRODUCTVERSION 0,9,58,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.57.1" + VALUE "FileVersion", "0.9.58.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.57.1" + VALUE "ProductVersion", "0.9.58.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index be4e3c21c..d690d196f 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1051,13 +1051,8 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9057) { -#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Linux : trying to use GTK file chooser when it is available"); -#else // Q_OS_LINUX32 || Q_OS_LINUX64 - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements"); -#endif // Q_OS_LINUX32 || Q_OS_LINUX64 -// versionFeatures = langNewVersionText(); + if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9058) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Alpha version of an embedded video player"); } else if (Local::oldMapVersion() < 9056) { versionFeatures = langNewVersionText(); } else { diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 7baecf548..795b8bef4 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,9 +22,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -#define BETA_VERSION_MACRO (9057001ULL) +#define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9057; -constexpr str_const AppVersionStr = "0.9.57"; -constexpr bool AppAlphaVersion = false; +constexpr int AppVersion = 9058; +constexpr str_const AppVersionStr = "0.9.58"; +constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 2ba1b0a88..f0411644c 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -1748,7 +1748,7 @@ void AudioCaptureInner::processFrame(int32 offset, int32 framesize) { AVFrame *frame = av_frame_alloc(); frame->nb_samples = d->dstSamples; - frame->pts = av_rescale_q(d->fullSamples, (AVRational){1, d->codecContext->sample_rate}, d->codecContext->time_base); + frame->pts = av_rescale_q(d->fullSamples, AVRational{1, d->codecContext->sample_rate}, d->codecContext->time_base); avcodec_fill_audio_frame(frame, d->codecContext->channels, d->codecContext->sample_fmt, d->dstSamplesData[0], d->dstSamplesSize, 0); diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index a571e4cdf..205b86bde 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2531,7 +2531,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.57; + TDESKTOP_VERSION = 0.9.58; }; name = Release; }; @@ -2672,7 +2672,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.57; + TDESKTOP_VERSION = 0.9.58; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index 5658212f2..f5aff1e62 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9057 +AppVersion 9058 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.57 -AppVersionStr 0.9.57 -AlphaChannel 0 -BetaVersion 9057001 +AppVersionStrSmall 0.9.58 +AppVersionStr 0.9.58 +AlphaChannel 1 +BetaVersion 0 From 5b7a17e044982edd2cff47dadbd9a8cc5da2404c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 23 Jul 2016 09:39:46 +0300 Subject: [PATCH 40/60] 0.9.59 alpha version: crash on logout fixed. --- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/SourceFiles/media/media_audio.cpp | 8 +++++++- Telegram/Telegram.xcodeproj/project.pbxproj | 4 ++-- Telegram/build/version | 6 +++--- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 3132ab97a..e1c2fe29c 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,58,0 - PRODUCTVERSION 0,9,58,0 + FILEVERSION 0,9,59,0 + PRODUCTVERSION 0,9,59,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.58.0" + VALUE "FileVersion", "0.9.59.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.58.0" + VALUE "ProductVersion", "0.9.59.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 69822ab7a..d569ff46b 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,58,0 - PRODUCTVERSION 0,9,58,0 + FILEVERSION 0,9,59,0 + PRODUCTVERSION 0,9,59,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.58.0" + VALUE "FileVersion", "0.9.59.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.58.0" + VALUE "ProductVersion", "0.9.59.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 795b8bef4..43d472653 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9058; -constexpr str_const AppVersionStr = "0.9.58"; +constexpr int AppVersion = 9059; +constexpr str_const AppVersionStr = "0.9.59"; constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index f0411644c..32f2f79b9 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -357,7 +357,13 @@ void AudioPlayer::onStopped(const AudioMsgId &audio) { } AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index) { - if (index < 0) index = *currentIndex(type); + if (index < 0) { + if (auto indexPtr = currentIndex(type)) { + index = *indexPtr; + } else { + return nullptr; + } + } switch (type) { case AudioMsgId::Type::Voice: return &_audioData[index]; case AudioMsgId::Type::Song: return &_songData[index]; diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 205b86bde..249bd4e26 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2531,7 +2531,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.58; + TDESKTOP_VERSION = 0.9.59; }; name = Release; }; @@ -2672,7 +2672,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.58; + TDESKTOP_VERSION = 0.9.59; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index f5aff1e62..5cd1e6e22 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9058 +AppVersion 9059 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.58 -AppVersionStr 0.9.58 +AppVersionStrSmall 0.9.59 +AppVersionStr 0.9.59 AlphaChannel 1 BetaVersion 0 From 4cc9f1f3b4f8c58f4c4cf5db987591de4d0d19c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 24 Jul 2016 10:21:52 +0300 Subject: [PATCH 41/60] Crashfix on space/return press in MediaView when viewing a photo. --- Telegram/SourceFiles/mediaview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index c43285bea..b171ceded 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1796,7 +1796,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { if (_doc && !_doc->loading() && (!fileShown() || !_doc->loaded())) { onDocClick(); - } else if (_doc->isVideo()) { + } else if (_doc && _doc->isVideo()) { onVideoPauseResume(); } } else if (e->key() == Qt::Key_Left) { From 75ff3c3d28230a99444873baa1aff2e43e2a21ee Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 24 Jul 2016 11:53:09 +0300 Subject: [PATCH 42/60] Alpha version 0.9.60: some bugfixes in video clip reader. No more error box when click on record button (empty file send attempt). --- Telegram/Resources/winrc/Telegram.rc | 8 ++-- Telegram/Resources/winrc/Updater.rc | 8 ++-- Telegram/SourceFiles/core/version.h | 4 +- Telegram/SourceFiles/historywidget.cpp | 2 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 1 + .../SourceFiles/media/media_clip_reader.cpp | 14 ++++--- Telegram/SourceFiles/mediaview.cpp | 41 +++++++++++-------- Telegram/SourceFiles/mediaview.h | 3 +- Telegram/Telegram.xcodeproj/project.pbxproj | 4 +- Telegram/build/version | 6 +-- 10 files changed, 51 insertions(+), 40 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index e1c2fe29c..e4f013b17 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,59,0 - PRODUCTVERSION 0,9,59,0 + FILEVERSION 0,9,60,0 + PRODUCTVERSION 0,9,60,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.59.0" + VALUE "FileVersion", "0.9.60.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.59.0" + VALUE "ProductVersion", "0.9.60.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index d569ff46b..64ed6a32d 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,59,0 - PRODUCTVERSION 0,9,59,0 + FILEVERSION 0,9,60,0 + PRODUCTVERSION 0,9,60,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.59.0" + VALUE "FileVersion", "0.9.60.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.59.0" + VALUE "ProductVersion", "0.9.60.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 43d472653..ad8fb384b 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9059; -constexpr str_const AppVersionStr = "0.9.59"; +constexpr int AppVersion = 9060; +constexpr str_const AppVersionStr = "0.9.60"; constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 347fc6b69..87b1c9bb8 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3497,7 +3497,7 @@ void HistoryWidget::onRecordError() { } void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) { - if (!_peer) return; + if (!_peer || result.isEmpty()) return; App::wnd()->activateWindow(); int32 duration = samples / AudioVoiceMsgFrequency; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index 5378fea2c..acf016310 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -303,6 +303,7 @@ bool FFMpegReaderImplementation::start(Mode mode, int64 &positionMs) { _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); if (_mode == Mode::OnlyGifv) { if (_audioStreamId >= 0) { // should be no audio stream + _audioStreamId = -1; // do not attempt to access audioPlayer() return false; } if (dataSize() > AnimationInMemory) { diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index ffb5cca8a..043d149e3 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -630,16 +630,18 @@ Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(Re bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) { QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + auto it = constUnsafeFindReaderPointer(reader); if (result == ProcessResult::Error) { if (it != _readerPointers.cend()) { - it.key()->error(); - emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); - lock.unlock(); QWriteLocker lock(&_readerPointersMutex); - ReaderPointers::iterator i = unsafeFindReaderPointer(reader); - if (i != _readerPointers.cend()) _readerPointers.erase(i); + + auto i = unsafeFindReaderPointer(reader); + if (i != _readerPointers.cend()) { + i.key()->error(); + emit callback(i.key(), i.key()->threadIndex(), NotificationReinit); + _readerPointers.erase(i); + } } return false; } else if (result == ProcessResult::Finished) { diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index b171ceded..f03dacae2 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -239,7 +239,6 @@ void MediaView::stopGif() { _videoPaused = _videoStopped = _videoIsSilent = false; _fullScreenVideo = false; _clipController.destroy(); - Sandbox::removeEventFilter(this); if (audioPlayer()) { disconnect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); } @@ -437,7 +436,7 @@ void MediaView::step_state(uint64 ms, bool timer) { if (dt >= 1) { a_cOpacity.finish(); _controlsState = (_controlsState == ControlsShowing ? ControlsShown : ControlsHidden); - setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer)); + updateCursor(); } else { a_cOpacity.update(dt, anim::linear); } @@ -450,6 +449,10 @@ void MediaView::step_state(uint64 ms, bool timer) { } } +void MediaView::updateCursor() { + setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer)); +} + float64 MediaView::radialProgress() const { if (_doc) { return _doc->progress(); @@ -547,6 +550,7 @@ void MediaView::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) setCursor((active || ClickHandler::getPressed()) ? style::cur_pointer : style::cur_default); update(QRegion(_saveMsg) + _captionRect); } + void MediaView::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { setCursor((pressed || ClickHandler::getActive()) ? style::cur_pointer : style::cur_default); update(QRegion(_saveMsg) + _captionRect); @@ -1317,9 +1321,6 @@ void MediaView::createClipController() { connect(_clipController, SIGNAL(toFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); connect(_clipController, SIGNAL(fromFullScreenPressed()), this, SLOT(onVideoToggleFullScreen())); - Sandbox::removeEventFilter(this); - Sandbox::installEventFilter(this); - if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&))); } @@ -1403,6 +1404,7 @@ void MediaView::onVideoToggleFullScreen() { setZoomLevel(ZoomToScreenLevel); } else { setZoomLevel(_fullScreenZoomCache); + _clipController->showAnimated(); } _clipController->setInFullScreen(_fullScreenVideo); @@ -2101,7 +2103,7 @@ void MediaView::mousePressEvent(QMouseEvent *e) { } else if (!_saveMsg.contains(e->pos()) || !_saveMsgStarted) { _pressed = true; _dragging = 0; - setCursor(style::cur_default); + updateCursor(); _mStart = e->pos(); _xStart = _x; _yStart = _y; @@ -2205,10 +2207,8 @@ bool MediaView::updateOverState(OverState newState) { _animOpacities.insert(_over, anim::fvalue(0, 1)); } if (!_a_state.animating()) _a_state.start(); - setCursor(style::cur_pointer); - } else { - setCursor(style::cur_default); } + updateCursor(); } return result; } @@ -2438,15 +2438,22 @@ bool MediaView::eventFilter(QObject *obj, QEvent *e) { return TWidget::eventFilter(obj, e); } -void MediaView::hide() { - _controlsHideTimer.stop(); - _controlsState = ControlsShown; - a_cOpacity = anim::fvalue(1, 1); - TWidget::hide(); - stopGif(); - _radial.stop(); +void MediaView::setVisible(bool visible) { + if (!visible) { + _controlsHideTimer.stop(); + _controlsState = ControlsShown; + a_cOpacity = anim::fvalue(1, 1); + } + TWidget::setVisible(visible); + if (visible) { + Sandbox::installEventFilter(this); + } else { + Sandbox::removeEventFilter(this); - Notify::clipStopperHidden(ClipStopperMediaview); + stopGif(); + _radial.stop(); + Notify::clipStopperHidden(ClipStopperMediaview); + } } void MediaView::onMenuDestroy(QObject *obj) { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index ee996a866..c33222545 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -36,7 +36,7 @@ class MediaView : public TWidget, public RPCSender, public ClickHandlerHost { public: MediaView(); - void hide(); + void setVisible(bool visible) override; void updateOver(QPoint mpos); @@ -131,6 +131,7 @@ private: void findCurrent(); void loadBack(); + void updateCursor(); void setZoomLevel(int newZoom); void updateVideoPlaybackState(const AudioPlaybackState &state, bool reset = false); diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 249bd4e26..624061e50 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2531,7 +2531,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.59; + TDESKTOP_VERSION = 0.9.60; }; name = Release; }; @@ -2672,7 +2672,7 @@ SDKROOT = macosx; SYMROOT = ./../Mac; TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.59; + TDESKTOP_VERSION = 0.9.60; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index 5cd1e6e22..1921f28f9 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9059 +AppVersion 9060 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.59 -AppVersionStr 0.9.59 +AppVersionStrSmall 0.9.60 +AppVersionStr 0.9.60 AlphaChannel 1 BetaVersion 0 From 82a0ac28ad56b42616d41cd36c809d7b19d92643 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 26 Jul 2016 12:17:44 +0300 Subject: [PATCH 43/60] Handling new 2fa reset account error codes. Archived stickers description text layout fixed in 100% scale. --- Telegram/Resources/langs/lang.strings | 5 +++++ Telegram/SourceFiles/boxes/stickersetbox.cpp | 6 ++--- Telegram/SourceFiles/intro/intropwdcheck.cpp | 23 +++++++++++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 43c72f429..5cda59404 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Reset your account"; "lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?"; "lng_signin_reset" = "Reset"; +"lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:_not_used_|# hour|# hours} {count_minutes:_not_used_|# minute|# minutes}"; +"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:_not_used_|# minute|# minutes}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}"; +"lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; "lng_signup_title" = "Information and photo"; "lng_signup_desc" = "Please enter your name and\nupload a photo."; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 62c8c5580..1a002920d 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -1280,7 +1280,7 @@ StickersInner::~StickersInner() { StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) , _section(section) , _inner(section) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) +, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) , _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) { setup(); } @@ -1288,7 +1288,7 @@ StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll) , _section(Section::ArchivedPart) , _inner(archivedIds) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) +, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) , _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { setup(); } @@ -1507,7 +1507,7 @@ void StickersBox::paintEvent(QPaintEvent *e) { if (_aboutHeight > 0) { p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); p.setPen(st::stickersReorderFg); - _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + _about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } } diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index ae6d34348..c7da076c9 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -340,7 +340,28 @@ bool IntroPwdCheck::deleteFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; sentRequest = 0; - showError(lang(lng_server_error)); + + auto type = error.type(); + if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { + int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); + int days = (seconds + 59) / 86400; + int hours = ((seconds + 59) % 86400) / 3600; + int minutes = ((seconds + 59) % 3600) / 60; + QString when; + if (days > 0) { + when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes); + } else if (hours > 0) { + when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes); + } else { + when = lng_signin_reset_in_minutes(lt_count_minutes, minutes); + } + Ui::showLayer(new InformBox(lng_signin_reset_wait(lt_phone_number, App::formatPhone(intro()->getPhone()), lt_when, when))); + } else if (type == qstr("2FA_RECENT_CONFIRM")) { + Ui::showLayer(new InformBox(lang(lng_signin_reset_cancelled))); + } else { + Ui::hideLayer(); + showError(lang(lng_server_error)); + } return true; } From b0a8787eaf76a36f087de7c9d9d915f8bb699781 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 26 Jul 2016 12:22:27 +0300 Subject: [PATCH 44/60] Linking libav-drm statically. --- Telegram/build/makefile_static.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/build/makefile_static.sh b/Telegram/build/makefile_static.sh index da1c19c1a..014698e90 100755 --- a/Telegram/build/makefile_static.sh +++ b/Telegram/build/makefile_static.sh @@ -66,4 +66,5 @@ Replace '\-lswresample' "$LocalDirPath\/libswresample\.a" Replace '\-lswscale' "$LocalDirPath\/libswscale\.a" Replace '\-lavutil' "$LocalDirPath\/libavutil\.a" Replace '\-lva-x11' "$LocalDirPath\/libva-x11\.a" +Replace '\-lva-drm' "$LocalDirPath\/libva-drm\.a" Replace '\-lva' "$LocalDirPath\/libva\.a" From 832163c1b7331d9aa0638ecd2dc481a7763bdcb0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 26 Jul 2016 15:09:40 +0300 Subject: [PATCH 45/60] Displaying time in chat list for all timestamps in the last 20 hours. Fixed possible crash in MediaView video player seek after clip error. Fixed possible crash in native event filter on Windows. Removed unused lng_stickers_add key. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 6 +----- Telegram/SourceFiles/boxes/stickersetbox.h | 3 --- Telegram/SourceFiles/dialogs/dialogs_layout.cpp | 7 ++++++- Telegram/SourceFiles/media/media_clip_reader.cpp | 6 +++--- Telegram/SourceFiles/mediaview.cpp | 10 +++++++--- .../platform/win/windows_event_filter.cpp | 12 +++++++++--- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 1a002920d..adb47c99a 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -473,8 +473,6 @@ StickersInner::StickersInner(StickersBox::Section section) : TWidget() , _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) , _returnWidth(st::normalFont->width(lang(lng_stickers_return))) , _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _addText(lang(lng_stickers_add).toUpper()) -, _addWidth(st::defaultActiveButton.font->width(_addText)) , _aboveShadow(st::boxShadow) { setup(); } @@ -489,8 +487,6 @@ StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget() , _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) , _returnWidth(st::normalFont->width(lang(lng_stickers_return))) , _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _addText(lang(lng_stickers_add).toUpper()) -, _addWidth(st::defaultActiveButton.font->width(_addText)) , _aboveShadow(st::boxShadow) { setup(); } @@ -1128,7 +1124,7 @@ int StickersInner::countMaxNameWidth() const { if (_section == Section::Installed) { namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); } else { - namew -= _addWidth - st::defaultActiveButton.width; + namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width; } return namew; } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 42d9e23cf..cde1293f1 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -312,9 +312,6 @@ private: ConfirmBox *_clearBox = nullptr; - QString _addText; - int _addWidth; - int _buttonHeight = 0; bool _hasFeaturedButton = false; bool _hasArchivedButton = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 29e8ea46d..cb19698b9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -33,11 +33,16 @@ namespace Layout { namespace { +// Show all dates that are in the last 20 hours in time format. +constexpr int kRecentlyInSeconds = 20 * 3600; + void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool active) { QDateTime now(QDateTime::currentDateTime()), lastTime(date); QDate nowDate(now.date()), lastDate(lastTime.date()); QString dt; - if (lastDate == nowDate) { + bool wasSameDay = (lastDate == nowDate); + bool wasRecently = qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds; + if (wasSameDay || wasRecently) { dt = lastTime.toString(cTimeFormat()); } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { dt = langDayOfWeek(lastDate); diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 043d149e3..5d8756317 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -110,7 +110,7 @@ Reader::Reader(const FileLocation &location, const QByteArray &data, Callback && } Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready - int32 step = _step.loadAcquire(), i; + int step = _step.loadAcquire(), i; if (step == WaitingForDimensionsStep) { if (index) *index = 0; return nullptr; @@ -205,8 +205,8 @@ void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, Image } QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { - Frame *frame = frameToShow(); - t_assert(frame != 0); + auto frame = frameToShow(); + t_assert(frame != nullptr); if (ms) { frame->displayed.storeRelease(1); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 3afe8ae3f..62307f3bd 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -731,7 +731,10 @@ void MediaView::clipCallback(Media::Clip::Notification notification) { case NotificationReinit: { if (auto item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { if (_gif->state() == State::Error) { - _current = QPixmap(); + stopGif(); + updateControls(); + update(); + break; } else if (_gif->state() == State::Finished) { _videoPositionMs = _videoDurationMs; _videoStopped = true; @@ -1118,7 +1121,10 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { _fullScreenVideo = false; + _current = QPixmap(); stopGif(); + } else if (gifShown()) { + _current = QPixmap(); } _doc = doc; _photo = nullptr; @@ -1128,8 +1134,6 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty _autoplayVideoDocument = nullptr; } - _current = QPixmap(); - _caption = Text(); if (_doc) { if (_doc->sticker()) { diff --git a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp index f782a9eb9..0d9331782 100644 --- a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp +++ b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp @@ -103,7 +103,10 @@ bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPa } } return false; - case WM_NCPAINT: if (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8) return false; *result = 0; return true; + case WM_NCPAINT: { + if (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8) return false; + if (result) *result = 0; + } return true; case WM_NCCALCSIZE: { WINDOWPLACEMENT wp; @@ -120,12 +123,13 @@ bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPa } } } - *result = 0; + if (result) *result = 0; return true; } case WM_NCACTIVATE: { - *result = DefWindowProc(hWnd, msg, wParam, -1); + auto res = DefWindowProc(hWnd, msg, wParam, -1); + if (result) *result = res; } return true; case WM_WINDOWPOSCHANGING: @@ -172,6 +176,8 @@ bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPa } return false; case WM_NCHITTEST: { + if (!result) return false; + POINTS p = MAKEPOINTS(lParam); RECT r; GetWindowRect(hWnd, &r); From 0721b80555fec3c2e6200518412f15ca790d127e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 26 Jul 2016 19:11:33 +0300 Subject: [PATCH 46/60] Moved Enter key press handle from HistoryInner to HistoryWidget. --- Telegram/SourceFiles/historywidget.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 87b1c9bb8..a46a42433 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1481,10 +1481,6 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) { if (!_selected.isEmpty() && selectedForDelete == selectedForForward) { _widget->onDeleteSelected(); } - } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { - if (_selected.isEmpty()) { - _widget->onListEnterPressed(); - } } else { e->ignore(); } @@ -7216,6 +7212,8 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } _scroll.keyPressEvent(e); } + } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { + onListEnterPressed(); } else { e->ignore(); } From 0228941fdbbdbc61ac0b72f7ada19096d12d2fb1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 27 Jul 2016 16:48:52 +0300 Subject: [PATCH 47/60] Using /usr/local libssl, libcrypto and libz when linking the build. The previous version crashed in gtk file picker on Gentoo with KDE. See #2278 --- Telegram/build/makefile_static.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/build/makefile_static.sh b/Telegram/build/makefile_static.sh index 014698e90..3aad822f3 100755 --- a/Telegram/build/makefile_static.sh +++ b/Telegram/build/makefile_static.sh @@ -38,7 +38,9 @@ fi Replace () { CheckCommand="grep -ci '$1' Makefile" + set +e CheckCount=$(eval $CheckCommand) + set -e if [ "$CheckCount" -gt 0 ]; then echo "Requested '$1' to '$2', found - replacing.." ReplaceCommand="sed -i'.~' 's/$1/$2/g' Makefile" @@ -48,9 +50,6 @@ Replace () { fi } -Replace '\-lssl' "$ArchDirPath\/libssl\.a" -Replace '\-lcrypto' "$ArchDirPath\/libcrypto\.a" -Replace '\-lz' "$ArchDirPath\/libz\.a" Replace '\-llzma' "$ArchDirPath\/liblzma\.a" Replace '\-lXi' "$ArchDirPath\/libXi\.a $ArchDirPath\/libXext\.a" Replace '\-lSM' "$ArchDirPath\/libSM\.a" @@ -58,6 +57,7 @@ Replace '\-lICE' "$ArchDirPath\/libICE\.a" Replace '\-lfontconfig' "$ArchDirPath\/libfontconfig\.a $ArchDirPath\/libexpat\.a" Replace '\-lfreetype' "$ArchDirPath\/libfreetype\.a" Replace '\-lXext' "$ArchDirPath\/libXext\.a" +Replace '\-lz' "$LocalDirPath\/libz\.a" Replace '\-lopus' "$LocalDirPath\/libopus\.a" Replace '\-lopenal' "$LocalDirPath\/libopenal\.a" Replace '\-lavformat' "$LocalDirPath\/libavformat\.a" From c8b89620f5557038a49609f5453ce99e4956b85a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 27 Jul 2016 21:22:15 +0300 Subject: [PATCH 48/60] Private beta version 9061001 --- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 2 +- Telegram/build/version | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index ec047c8e2..3f6ffbd82 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,61,0 - PRODUCTVERSION 0,9,61,0 + FILEVERSION 0,9,61,1 + PRODUCTVERSION 0,9,61,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.61.0" + VALUE "FileVersion", "0.9.61.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.61.0" + VALUE "ProductVersion", "0.9.61.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index e28d17a5b..e182e0268 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,61,0 - PRODUCTVERSION 0,9,61,0 + FILEVERSION 0,9,61,1 + PRODUCTVERSION 0,9,61,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.61.0" + VALUE "FileVersion", "0.9.61.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.61.0" + VALUE "ProductVersion", "0.9.61.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 9c9fe8880..183631fb6 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -#define BETA_VERSION_MACRO (0ULL) +#define BETA_VERSION_MACRO (9061001ULL) constexpr int AppVersion = 9061; constexpr str_const AppVersionStr = "0.9.61"; diff --git a/Telegram/build/version b/Telegram/build/version index 00c77ce37..c8d09bb2c 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,4 +3,4 @@ AppVersionStrMajor 0.9 AppVersionStrSmall 0.9.61 AppVersionStr 0.9.61 AlphaChannel 0 -BetaVersion 0 +BetaVersion 9061001 From c0ea6ce3461f71c061e5616df4a3b4cd0d537907 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 27 Jul 2016 21:24:32 +0300 Subject: [PATCH 49/60] Langs updated for 9061001 private beta version. --- Telegram/Resources/langs/lang.strings | 5 ++- Telegram/Resources/langs/lang_de.strings | 14 ++++++++- Telegram/Resources/langs/lang_es.strings | 14 ++++++++- Telegram/Resources/langs/lang_it.strings | 34 ++++++++++++++------- Telegram/Resources/langs/lang_ko.strings | 18 +++++++++-- Telegram/Resources/langs/lang_nl.strings | 32 +++++++++++++------ Telegram/Resources/langs/lang_pt_BR.strings | 14 ++++++++- 7 files changed, 101 insertions(+), 30 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5cda59404..2047222c6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -191,8 +191,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?"; "lng_signin_reset" = "Reset"; "lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}"; -"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:_not_used_|# hour|# hours} {count_minutes:_not_used_|# minute|# minutes}"; -"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:_not_used_|# minute|# minutes}"; +"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}"; +"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}"; "lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}"; "lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; @@ -700,7 +700,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_remove" = "Delete"; "lng_stickers_return" = "Undo"; "lng_stickers_restore" = "Restore"; -"lng_stickers_add" = "Add"; "lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; "lng_in_dlg_photo" = "Photo"; diff --git a/Telegram/Resources/langs/lang_de.strings b/Telegram/Resources/langs/lang_de.strings index 5a27b31c2..78d3feff3 100644 --- a/Telegram/Resources/langs/lang_de.strings +++ b/Telegram/Resources/langs/lang_de.strings @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Konto zurücksetzen"; "lng_signin_sure_reset" = "Hinweis!\n\nDu verlierst du alle Chats und Nachrichten, ebenso deine geteilten Bilder und Videos.\n\nKonto wirklich zurücksetzen?"; "lng_signin_reset" = "Zurücksetzen"; +"lng_signin_reset_wait" = "Da dein Konto {phone_number} aktiv und durch ein Kennwort geschützt ist, löschen wir es aus Sicherheitsgründen in einer Woche. Du kannst den Vorgang jederzeit abbrechen.\n\nDu kannst dein Konto zurücksetzen in:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 Tage|# Tag|# Tage} {count_hours:0 Stunden|# Stunde|# Stunden} {count_minutes:0 Minuten|# Minute|# Minuten}"; +"lng_signin_reset_in_hours" = "{count_hours:0 Stunden|# Stunde|# Stunden} {count_minutes:0 Minuten|# Minute|# Minuten}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 Minuten|# Minute|# Minuten}"; +"lng_signin_reset_cancelled" = "Deine vorherigen Versuche das Konto zurückzusetzen wurden durch den aktiven Nutzer abgebrochen. Bitte in 7 Tagen erneut probieren."; "lng_signup_title" = "Information und Bild"; "lng_signup_desc" = "Bitte trage deinen Namen ein \nund lade ein Bild hoch."; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Möchtest du dem Kanal «{title}» beitreten?"; "lng_group_invite_join" = "Beitreten"; +"lng_group_invite_members" = "{count:_not_used_|# Mitglied|# Mitglieder}, darunter:"; + "lng_group_invite_link" = "Einladungslink:"; "lng_group_invite_create" = "Neuer Link"; "lng_group_invite_about" = "Jeder, der Telegram installiert hat,\nkann anhand dieses Links in deine Gruppe."; @@ -680,11 +687,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Sticker hinzufügen"; "lng_stickers_share_pack" = "Sticker teilen"; "lng_stickers_not_found" = "Sticker-Paket nicht gefunden."; +"lng_stickers_packs_archived" = "Einige deiner unbenutzen Sticker wurden archiviert, damit du Platz für neue Sticker hast."; +"lng_stickers_archived" = "Archivierte Sticker"; "lng_stickers_copied" = "Sticker-Paket Link in die Zwischenablage kopiert."; "lng_stickers_default_set" = "Große Denker"; "lng_stickers_you_have" = "Sticker-Pakete verwalten"; "lng_stickers_packs" = "Sticker-Pakete"; "lng_stickers_reorder" = "Paket gedrückt halten und verschieben um die Anordnung zu ändern"; +"lng_stickers_featured" = "Besondere Sticker"; +"lng_stickers_clear_recent" = "Leeren"; +"lng_stickers_clear_recent_sure" = "Zuletzt benutzte Sticker leeren?"; "lng_stickers_remove" = "Löschen"; "lng_stickers_return" = "Rückgängig"; "lng_stickers_restore" = "Zeigen"; @@ -881,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_forward" = "Weiterleiten"; "lng_selected_count" = "{count:_not_used_|# Nachricht|# Nachrichten}"; "lng_selected_cancel_sure_this" = "Upload abbrechen?"; -"lng_selected_upload_stop" = "Abbrechen"; +"lng_selected_upload_stop" = "Stoppen"; "lng_selected_delete_sure_this" = "Diese Nachricht wirklich löschen?"; "lng_selected_delete_sure" = "Willst du {count:_not_used_|# Nachricht|# Nachrichten} löschen?"; "lng_delete_photo_sure" = "Dieses Bild wirklich löschen?"; diff --git a/Telegram/Resources/langs/lang_es.strings b/Telegram/Resources/langs/lang_es.strings index c44a14d84..1651a5c81 100644 --- a/Telegram/Resources/langs/lang_es.strings +++ b/Telegram/Resources/langs/lang_es.strings @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Restablecer tu cuenta"; "lng_signin_sure_reset" = "¡Advertencia!\n\n¡Perderás todos tus chats y mensajes, junto con la multimedia y archivos compartidos!\n\n¿Quieres restablecer tu cuenta?"; "lng_signin_reset" = "Restablecer"; +"lng_signin_reset_wait" = "Como la cuenta {phone_number} está activa y protegida con una contraseña, la eliminaremos en 1 semana, por motivos de seguridad. Puedes cancelar el proceso en cualquier momento.\n\nPodrás restablecer tu cuenta en:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 días|# día|# días} {count_hours:0 horas|# hora|# horas} {count_minutes:0 minutos|# minuto|# minutos}"; +"lng_signin_reset_in_hours" = "{count_hours:0 horas|# hora|# horas} {count_minutes:0 minutos|# minuto|# minutos}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 minutos|# minuto|# minutos}"; +"lng_signin_reset_cancelled" = "Tus intentos recientes para restablecer la cuenta fueron cancelados por su usuario activo. Por favor, reinténtalo en 7 días."; "lng_signup_title" = "Información y foto"; "lng_signup_desc" = "Por favor, pon tu nombre \ny una foto."; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "¿Quieres unirte al canal «{title}»?"; "lng_group_invite_join" = "Unirme"; +"lng_group_invite_members" = "{count:_not_used_|# miembro|# miembros}, entre ellos:"; + "lng_group_invite_link" = "Enlace de invitación:"; "lng_group_invite_create" = "Crear un enlace de invitación"; "lng_group_invite_about" = "Los usuarios de Telegram podrán unirse\na tu grupo a través de este enlace."; @@ -680,11 +687,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Añadir stickers"; "lng_stickers_share_pack" = "Compartir stickers"; "lng_stickers_not_found" = "Pack de stickers no encontrado."; +"lng_stickers_packs_archived" = "Algunos de los stickers que no usas fueron archivados. Así tendrás más espacio para los packs que activaste."; +"lng_stickers_archived" = "Stickers archivados"; "lng_stickers_copied" = "Enlace del pack de stickers copiado al portapapeles."; "lng_stickers_default_set" = "Grandes personajes"; "lng_stickers_you_have" = "Administrar y ordenar los packs de stickers"; "lng_stickers_packs" = "Packs de stickers"; "lng_stickers_reorder" = "Haz clic y arrastra para ordenar los packs"; +"lng_stickers_featured" = "Stickers destacados"; +"lng_stickers_clear_recent" = "Borrar"; +"lng_stickers_clear_recent_sure" = "¿Quieres borrar la lista de stickers usados frecuentemente?"; "lng_stickers_remove" = "Eliminar"; "lng_stickers_return" = "Deshacer"; "lng_stickers_restore" = "Restaurar"; @@ -880,7 +892,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete" = "Eliminar"; "lng_selected_forward" = "Reenviar"; "lng_selected_count" = "{count:_not_used_|# mensaje|# mensajes}"; -"lng_selected_cancel_sure_this" = "¿Cancelar envío?"; +"lng_selected_cancel_sure_this" = "¿Detener el envío?"; "lng_selected_upload_stop" = "Detener"; "lng_selected_delete_sure_this" = "¿Quieres eliminar este mensaje?"; "lng_selected_delete_sure" = "¿Quieres eliminar {count:_not_used_|# mensaje|# mensajes}?"; diff --git a/Telegram/Resources/langs/lang_it.strings b/Telegram/Resources/langs/lang_it.strings index b1fc73663..69f87f22f 100644 --- a/Telegram/Resources/langs/lang_it.strings +++ b/Telegram/Resources/langs/lang_it.strings @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Ripristina il tuo account"; "lng_signin_sure_reset" = "Attenzione!\n\nPerderai tutte le chat e i messaggi, insieme a tutti i media e i file condivisi!\n\nVuoi ripristinare il tuo account?"; "lng_signin_reset" = "Ripristina"; +"lng_signin_reset_wait" = "Dato che l'account {phone_number} è attivo e protetto da una password, lo elimineremo in 1 settimana per motivi di sicurezza. Puoi annullare questo processo in qualsiasi momento.\n\nSarai in grado di ripristinare il tuo account tra:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 giorni|# giorno|# giorni} {count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}"; +"lng_signin_reset_in_hours" = "{count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 minuti|# minuto|# minuti}"; +"lng_signin_reset_cancelled" = "I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni."; "lng_signup_title" = "Info e foto"; "lng_signup_desc" = "Inserisci il tuo nome e\ncarica una foto."; @@ -202,7 +207,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_new_channel_name" = "Nome canale"; "lng_no_contacts" = "Non hai contatti"; "lng_no_chats" = "Le tua chat saranno qui"; -"lng_contacts_loading" = "Caricamento..."; +"lng_contacts_loading" = "Carico..."; "lng_contacts_not_found" = "Nessun contatto trovato"; "lng_dlg_search_chat" = "Cerca in questa chat"; "lng_dlg_search_channel" = "Cerca in questo canale"; @@ -211,7 +216,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_save" = "Salva"; "lng_settings_upload" = "Imposta foto profilo"; "lng_settings_crop_profile" = "Seleziona un riquadro per la tua foto profilo"; -"lng_settings_uploading_photo" = "Caricamento foto..."; +"lng_settings_uploading_photo" = "Carico foto..."; "lng_username_title" = "Username"; "lng_username_about" = "Puoi scegliere un username su Telegram.\nSe lo fai, le altre persone potranno trovarti\ntramite questo username e contattarti \nsenza conoscere il tuo numero di telefono.\n\nPuoi usare a-z, 0-9 e underscore.\nLa lunghezza minima è di 5 caratteri."; @@ -348,7 +353,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cloud_password_email" = "Inserisci email di recupero"; "lng_cloud_password_bad_email" = "E-mail non valida, riprova con un'altra."; "lng_cloud_password_about" = "La password sarà richiesta quando ti connetti da un nuovo dispositivo insieme al codice."; -"lng_cloud_password_about_recover" = "Attenzione! Sei sicuro di non voler\naggiungere un'e-mail di recupero?\n\nSe dimentichi la tua password, perderai\nl'accesso al tuo account Telegram."; +"lng_cloud_password_about_recover" = "Attenzione! Sicuro di non voler\naggiungere un'e-mail di recupero?\n\nSe dimentichi la tua password, perderai\nl'accesso al tuo account Telegram."; "lng_cloud_password_skip_email" = "Salta e-mail"; "lng_cloud_password_almost" = "Abbiamo inviato un link di conferma\nall'e-mail che ci hai fornito. La verifica in due passaggi sarà attivata non appena aprirai quel link."; "lng_cloud_password_was_set" = "Verifica in due passaggi abilitata."; @@ -445,7 +450,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?"; "lng_profile_sure_kick_channel" = "Rimuovere {user} dal canale?"; "lng_profile_sure_kick_admin" = "Rimuovere {user} dagli amministratori?"; -"lng_profile_loading" = "Caricamento..."; +"lng_profile_loading" = "Carico..."; "lng_profile_shared_media" = "Media condivisi"; "lng_profile_no_media" = "Nessun media in questa chat."; "lng_profile_photos" = "{count:_not_used_|# foto|# foto}"; @@ -528,10 +533,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_sure_delete_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi con {contact}?\n\nQuesta azione non può essere annullata."; "lng_sure_delete_group_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi in «{group}»?\n\nQuesta azione non può essere annullata."; "lng_sure_delete_and_exit" = "Sicuro di voler eliminare tutta la cronologia dei messaggi e abbandonare «{group}»?\n\nQuesta azione non può essere annullata."; -"lng_sure_leave_channel" = "Sei sicuro di voler lasciare\nquesto canale?"; -"lng_sure_delete_channel" = "Sei sicuro di voler eliminare questo canale? Tutti i membri verranno rimossi e i messaggi verranno persi."; +"lng_sure_leave_channel" = "Sicuro di voler lasciare\nquesto canale?"; +"lng_sure_delete_channel" = "Sicuro di voler eliminare questo canale? Tutti i membri verranno rimossi e i messaggi verranno persi."; "lng_sure_leave_group" = "Sicuro di voler lasciare questo gruppo?\nQuesta azione non può essere annullata."; -"lng_sure_delete_group" = "Sei sicuro di voler eliminare questo gruppo? Tutti i membri verranno rimossi e i messaggi verranno persi."; +"lng_sure_delete_group" = "Sicuro di voler eliminare questo gruppo? Tutti i membri verranno rimossi e i messaggi verranno persi."; "lng_message_empty" = "Messaggio vuoto"; "lng_message_unsupported" = "Questo messaggio non è supportato dalla tua versione di Telegram Desktop. Per favore, aggiorna all'ultima versione dalle Impostazioni o installalo da {link}"; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Vuoi unirti al canale «{title}»?"; "lng_group_invite_join" = "Unisciti"; +"lng_group_invite_members" = "{count:_not_used_|# membro|# membri}, tra cui:"; + "lng_group_invite_link" = "Link di invito:"; "lng_group_invite_create" = "Crea un link di invito"; "lng_group_invite_about" = "Gli utenti di Telegram potranno \nunirsi al tuo gruppo aprendo il link."; @@ -680,15 +687,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Aggiungi sticker"; "lng_stickers_share_pack" = "Condividi sticker"; "lng_stickers_not_found" = "Set di sticker non trovato."; +"lng_stickers_packs_archived" = "Alcuni dei tuoi sticker non usati sono stati archiviati per fare spazio ai set che hai attivato."; +"lng_stickers_archived" = "Sticker archiviati"; "lng_stickers_copied" = "Link degli sticker copiato negli appunti."; "lng_stickers_default_set" = "Grandi menti"; "lng_stickers_you_have" = "Organizza e riordina i set di sticker"; "lng_stickers_packs" = "Set di sticker"; "lng_stickers_reorder" = "Clicca e trascina per riordinare i set di sticker"; +"lng_stickers_featured" = "Sticker in primo piano"; +"lng_stickers_clear_recent" = "Elimina"; +"lng_stickers_clear_recent_sure" = "Sicuro di voler eliminare la lista degli sticker usati di recente?"; "lng_stickers_remove" = "Elimina"; "lng_stickers_return" = "Annulla"; "lng_stickers_restore" = "Ripristina"; -"lng_stickers_count" = "{count:Caricamento...|# sticker|# sticker}"; +"lng_stickers_count" = "{count:Carico...|# sticker|# sticker}"; "lng_in_dlg_photo" = "Foto"; "lng_in_dlg_video" = "File video"; @@ -704,9 +716,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam" = "Segnala spam"; "lng_report_spam_hide" = "Nascondi"; "lng_report_spam_thanks" = "Grazie per la tua segnalazione!"; -"lng_report_spam_sure" = "Sei sicuro di voler segnalare questo utente come spam?"; -"lng_report_spam_sure_group" = "Sei sicuro di voler segnalare dello spam in questo gruppo?"; -"lng_report_spam_sure_channel" = "Sei sicuro di voler segnalare dello spam in questo canale?"; +"lng_report_spam_sure" = "Sicuro di voler segnalare questo utente come spam?"; +"lng_report_spam_sure_group" = "Sicuro di voler segnalare dello spam in questo gruppo?"; +"lng_report_spam_sure_channel" = "Sicuro di voler segnalare dello spam in questo canale?"; "lng_report_spam_ok" = "Segnala"; "lng_cant_send_to_not_contact" = "Spiacenti, ma al momento puoi scrivere\nsolo ai contatti reciproci.\n{more_info}"; "lng_cant_invite_not_contact" = "Spiacenti, ma al momento puoi aggiungere\nai gruppi solo contatti reciproci.\n{more_info}"; diff --git a/Telegram/Resources/langs/lang_ko.strings b/Telegram/Resources/langs/lang_ko.strings index 40d4bf50a..844f9b32f 100644 --- a/Telegram/Resources/langs/lang_ko.strings +++ b/Telegram/Resources/langs/lang_ko.strings @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "계정 초기화"; "lng_signin_sure_reset" = "경고!\n\n계정 초기화 진행시 모든 대화,\n메시지 및 공유받은 미디어와 파일이 삭제가 됩니다.\n\n계정 초기화를 진행하시겠습니까?"; "lng_signin_reset" = "초기화"; +"lng_signin_reset_wait" = "{phone_number} 계정이 사용중이고 비밀번호 설정이 되어져 있어, 보안을 위하여 1주일 이후에 삭제가 될 예정입니다. 이 설정은 언제든지 취소 할 수 있습니다.\n\n계정은 다음시간 이후에 초기화가 됩니다:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 일|# 일|# 일} {count_hours:0 시간|# 시간|# 시간} {count_minutes:0 분|# 분|# 분}"; +"lng_signin_reset_in_hours" = "{count_hours:0 시간|# 시간|# 시간} {count_minutes:0 분|# 분|# 분}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 분|# 분|# 분}"; +"lng_signin_reset_cancelled" = "현재 사용중인 사용자가 요청하신 계정 초기화를 취소 하였습니다.\n7일 이후에 다시 시도해주세요."; "lng_signup_title" = "개인정보 및 사진"; "lng_signup_desc" = "이름을 입력해주시고,\n사진을 업로드해주세요."; @@ -442,8 +447,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_delete_and_exit" = "나가기"; "lng_profile_kick" = "삭제"; "lng_profile_admin" = "관리자"; -"lng_profile_sure_kick" = "{user}를 추방하시겠습니까?"; -"lng_profile_sure_kick_channel" = "{user}를 추방하시겠습니까?"; +"lng_profile_sure_kick" = "{user}를 내보내시겠습니까?"; +"lng_profile_sure_kick_channel" = "{user}를 내보내시겠습니까?"; "lng_profile_sure_kick_admin" = "{user}를 관리자에서 제외 하시겠습니까?"; "lng_profile_loading" = "로드중.."; "lng_profile_shared_media" = "공유된 미디어"; @@ -544,7 +549,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_you_joined" = "채널에 입장하였습니다."; "lng_action_add_you_group" = "{from}님께서 그룹에 초대해주셨습니다."; "lng_action_you_joined_group" = "그룹방에 참여하였습니다."; -"lng_action_kick_user" = "{from} 님께서 {user} 님을 추방하셨습니다."; +"lng_action_kick_user" = "{from} 님께서 {user} 님을 내보내셨습니다."; "lng_action_user_left" = "{from} 님이 그룹을 나가셨습니다."; "lng_action_user_joined" = "{from} 님이 그룹에 들어오셨습니다."; "lng_action_user_joined_by_link" = "초대링크를 타고 {from} 님이 그룹에 참여하였습니다."; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "«{title}» 채널에 참여하시겠습니까?"; "lng_group_invite_join" = "참여"; +"lng_group_invite_members" = "{count:_not_used_|# 회원|# 회원}, 참여자 현황:"; + "lng_group_invite_link" = "초대링크: "; "lng_group_invite_create" = "초대링크 생성"; "lng_group_invite_about" = "이 링크를 통하여,\n그룹방에 초대가 가능합니다."; @@ -680,11 +687,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "스티커 추가"; "lng_stickers_share_pack" = "스티커 공유"; "lng_stickers_not_found" = "스티커 팩을 찾을 수 없습니다."; +"lng_stickers_packs_archived" = "활성화된 스티커를 사용할 수 있는 공간이 필요하여 미사용 스티커중 일부는 보관되어집니다."; +"lng_stickers_archived" = "보관된 스티커"; "lng_stickers_copied" = "클립보드에 스티커 팩 링크가 복사 되었습니다."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "스티커팩 관리 및 변경"; "lng_stickers_packs" = "스티커팩"; "lng_stickers_reorder" = "클릭과 드래그를 통하여 스태커 팩을 변경하세요"; +"lng_stickers_featured" = "사용중인 스티커"; +"lng_stickers_clear_recent" = "초기화"; +"lng_stickers_clear_recent_sure" = "자주 사용하는 스티커 리스트를 초기화 하겠습니까?"; "lng_stickers_remove" = "삭제"; "lng_stickers_return" = "실행취소"; "lng_stickers_restore" = "복구"; diff --git a/Telegram/Resources/langs/lang_nl.strings b/Telegram/Resources/langs/lang_nl.strings index a3ccbc221..d6f6bd61d 100644 --- a/Telegram/Resources/langs/lang_nl.strings +++ b/Telegram/Resources/langs/lang_nl.strings @@ -123,11 +123,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_server_error" = "Interne serverfout."; "lng_flood_error" = "Teveel pogingen. Probeer het later opnieuw."; "lng_gif_error" = "Er is iets een fout opgetreden bij het lezen van de GIF :("; -"lng_edit_error" = "Je mag dit bericht niet bewerken"; +"lng_edit_error" = "Je mag dit bericht niet wijzigen"; "lng_join_channel_error" = "Je bent lid van teveel kanalen of supergroepen, verlaat er wat om hier lid te worden."; "lng_edit_deleted" = "Bericht is gewist"; "lng_edit_too_long" = "Je bericht is te lang"; -"lng_edit_message" = "Bericht bewerken"; +"lng_edit_message" = "Bericht wijzigen"; "lng_edit_message_text" = "Nieuw bericht..."; "lng_deleted" = "Onbekend"; "lng_deleted_message" = "Verwijderd bericht"; @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Account resetten"; "lng_signin_sure_reset" = "Let op:\n\nAl je chats, berichten en alle andere data gaan verloren als je verder gaat!\n\nEcht je account resetten?"; "lng_signin_reset" = "Reset"; +"lng_signin_reset_wait" = "Het account met nummer {phone_number} is nog actief en beveiligd met een wachtwoord, je verzoek tot verwijdering stellen we daarom uit met 1 week.\nJe kunt dit proces ieder moment annuleren.\n\nJe account kan reset worden op:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 dagen|# dag|# dagen} {count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}"; +"lng_signin_reset_in_hours" = "{count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 minuten|# minuut|# minuten}"; +"lng_signin_reset_cancelled" = "De actieve gebruiker heeft de recente poging om dit account te resetten geannuleerd. Probeer het over 7 dagen nog eens."; "lng_signup_title" = "Informatie en foto"; "lng_signup_desc" = "Voer je naam en\nupload een foto."; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Wil je lid worden van het kanaal \"{title}\"?"; "lng_group_invite_join" = "Lid worden"; +"lng_group_invite_members" = "{count:_not_used_|# lid|# leden}, waaronder:"; + "lng_group_invite_link" = "Uitnodigingslink:"; "lng_group_invite_create" = "Uitnodigingslink maken"; "lng_group_invite_about" = "Gebruikers kunnen lid worden\nvan je groep via deze link."; @@ -618,9 +625,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_channel_via" = "Doorgestuurd van {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Antwoord op"; -"lng_edited" = "bewerkt"; -"lng_edited_date" = "Bewerkt: {date}"; -"lng_cancel_edit_post_sure" = "Bewerken annuleren?"; +"lng_edited" = "gewijzigd"; +"lng_edited_date" = "Gewijzigd: {date}"; +"lng_cancel_edit_post_sure" = "Wijzigen annuleren?"; "lng_cancel_edit_post_yes" = "Ja"; "lng_cancel_edit_post_no" = "Nee"; @@ -680,11 +687,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Stickers toevoegen"; "lng_stickers_share_pack" = "Stickers delen"; "lng_stickers_not_found" = "Stickerbundel niet gevonden."; +"lng_stickers_packs_archived" = "Een aantal ongebruikte stickers zijn gearchiveerd om ruimte te maken voor je nieuwe stickerbundels"; +"lng_stickers_archived" = "Gearchiveerde stickers"; "lng_stickers_copied" = "Stickerbundel-link gekopieerd naar klembord"; "lng_stickers_default_set" = "Grote geesten"; "lng_stickers_you_have" = "Beheer en sorteer stickerbundels"; "lng_stickers_packs" = "Stickerbundels"; "lng_stickers_reorder" = "Klik en sleep om stickerbundels te herschikken"; +"lng_stickers_featured" = "Uitgelichte stickers"; +"lng_stickers_clear_recent" = "Wissen"; +"lng_stickers_clear_recent_sure" = "Lijst met veelgebruikte stickers echt wissen?"; "lng_stickers_remove" = "Verwijder"; "lng_stickers_return" = "Ongedaan maken"; "lng_stickers_restore" = "Herstellen"; @@ -813,7 +825,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_save_gif" = "GIF opslaan"; "lng_context_to_msg" = "Naar bericht gaan"; "lng_context_reply_msg" = "Antwoord"; -"lng_context_edit_msg" = "Bewerken"; +"lng_context_edit_msg" = "Wijzig"; "lng_context_forward_msg" = "Bericht doorsturen"; "lng_context_delete_msg" = "Bericht verwijderen"; "lng_context_select_msg" = "Bericht kiezen"; @@ -851,12 +863,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_contact_phone" = "Telefoonnummer"; "lng_enter_contact_data" = "Nieuw contact"; -"lng_edit_group_title" = "Groepsnaam bewerken"; -"lng_edit_contact_title" = "Naam bewerken"; +"lng_edit_group_title" = "Groepsnaam wijzigen"; +"lng_edit_contact_title" = "Naam wijzigen"; "lng_edit_channel_title" = "Kanaal wijzigen"; "lng_edit_sign_messages" = "Ondertekenen"; -"lng_edit_group" = "Groep bewerken"; -"lng_edit_self_title" = "Je naam bewerken"; +"lng_edit_group" = "Groep wijzigen"; +"lng_edit_self_title" = "Je naam wijzigen"; "lng_confirm_contact_data" = "Nieuw contact"; "lng_add_contact" = "Opslaan"; "lng_add_contact_button" = "Nieuw contact"; diff --git a/Telegram/Resources/langs/lang_pt_BR.strings b/Telegram/Resources/langs/lang_pt_BR.strings index 26a4c1491..00e2dc316 100644 --- a/Telegram/Resources/langs/lang_pt_BR.strings +++ b/Telegram/Resources/langs/lang_pt_BR.strings @@ -178,7 +178,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_password" = "Sua senha"; "lng_signin_code" = "Código do e-mail"; "lng_signin_recover" = "Esqueceu sua senha?"; -"lng_signin_recover_title" = "Resetar senha"; +"lng_signin_recover_title" = "Redefinir senha"; "lng_signin_hint" = "Dica: {password_hint}"; "lng_signin_recover_hint" = "Código enviado para {recover_email}"; "lng_signin_bad_password" = "Você colocou uma senha errada."; @@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Apagar sua conta"; "lng_signin_sure_reset" = "Atenção!\n\nVocê perderá todos seus chats e mensagens, juntamente com quaisquer mídias e arquivos!\n\nVocê deseja apagar sua conta?"; "lng_signin_reset" = "Apagar"; +"lng_signin_reset_wait" = "Uma vez que a conta {phone_number} está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança. Você pode cancelar esse processo a qualquer momento.\n\nVocê poderá restaurar sua conta em:\n{when}"; +"lng_signin_reset_in_days" = "{count_days:0 dia|# dia|# dias} {count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}"; +"lng_signin_reset_in_hours" = "{count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}"; +"lng_signin_reset_in_minutes" = "{count_minutes:0 minuto|# minuto|# minutos}"; +"lng_signin_reset_cancelled" = "Sua tentativa recente de restaurar essa conta foi cancelada pelo usuário ativo. Tente novamente em 7 dias."; "lng_signup_title" = "Informação e foto"; "lng_signup_desc" = "Por favor, insira nome e\ncarregue uma foto."; @@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Você deseja entrar no canal «{title}»?"; "lng_group_invite_join" = "Entrar"; +"lng_group_invite_members" = "{count:_not_used_|# membro|# membros}, entre eles:"; + "lng_group_invite_link" = "Link de convite:"; "lng_group_invite_create" = "Criar um link de convite"; "lng_group_invite_about" = "Usuários do Telegram poderão entrar\nem seu grupo clicando no link."; @@ -680,11 +687,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_add_pack" = "Adicionar aos Stickers"; "lng_stickers_share_pack" = "Compartilhar Stickers"; "lng_stickers_not_found" = "Pacote de sticker não encontrado."; +"lng_stickers_packs_archived" = "Alguns de seus stickers não usados foram arquivados para dar espaço aos pacotes que você ativou."; +"lng_stickers_archived" = "Stickers Arquivados"; "lng_stickers_copied" = "Link copiado para a área de transferência."; "lng_stickers_default_set" = "Grandes Mentes"; "lng_stickers_you_have" = "Gerenciar e reordenar os pacotes de sticker"; "lng_stickers_packs" = "Pacotes de Sticker"; "lng_stickers_reorder" = "Clique e arraste para reordenar os pacotes"; +"lng_stickers_featured" = "Stickers em Destaque"; +"lng_stickers_clear_recent" = "Limpar"; +"lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar sua lista de stickers usados frequentemente?"; "lng_stickers_remove" = "Remover"; "lng_stickers_return" = "Desfazer"; "lng_stickers_restore" = "Restaurar"; From d9cc70e72be3a2718f900f78b5902ea86ff5effe Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 27 Jul 2016 21:36:45 +0300 Subject: [PATCH 50/60] Prepared 9061001 closed beta to build in Xcode and QtCreator. --- Telegram/Resources/all_files.style | 1 + Telegram/SourceFiles/boxes/confirmbox.h | 4 ++-- Telegram/SourceFiles/boxes/stickersetbox.cpp | 2 +- Telegram/Telegram.pro | 10 +++++++--- Telegram/Telegram.xcodeproj/project.pbxproj | 6 ++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/all_files.style b/Telegram/Resources/all_files.style index f6a7ca6d6..442ad90d7 100644 --- a/Telegram/Resources/all_files.style +++ b/Telegram/Resources/all_files.style @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org using "basic_types.style"; using "basic.style"; +using "boxes/boxes.style"; using "dialogs/dialogs.style"; using "history/history.style"; using "overview/overview.style"; diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index ebad005d5..11dabfaef 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -277,8 +277,8 @@ protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; - void showAll(); - void hideAll(); + void showAll() override; + void hideAll() override; private: ChildWidget _title, _status; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index adb47c99a..5176838ac 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -1032,7 +1032,7 @@ void StickersInner::rebuild() { int maxNameWidth = countMaxNameWidth(); clear(); - auto &order = ([this]() { + auto &order = ([this]() -> const Stickers::Order & { if (_section == Section::Installed) { return Global::StickerSetsOrder(); } else if (_section == Section::Featured) { diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 4fe6d5d37..f348f3fc9 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -63,6 +63,8 @@ file_style_basic.target = GeneratedFiles/styles/style_basic.cpp file_style_basic.depends = style_target file_style_basic_types.target = GeneratedFiles/styles/style_basic_types.cpp file_style_basic_types.depends = style_target +file_style_boxes.target = GeneratedFiles/styles/style_boxes.cpp +file_style_boxes.depends = style_target file_style_dialogs.target = GeneratedFiles/styles/style_dialogs.cpp file_style_dialogs.depends = style_target file_style_history.target = GeneratedFiles/styles/style_history.cpp @@ -77,9 +79,9 @@ file_style_widgets.target = GeneratedFiles/styles/style_widgets.cpp file_style_widgets.depends = style_target QMAKE_EXTRA_TARGETS += codegen_style codegen_numbers codegen_lang \ - file_style_basic file_style_basic_types file_style_dialogs \ - file_style_history file_style_mediaview file_style_overview \ - file_style_profile file_style_widgets + file_style_basic file_style_basic_types file_style_boxes \ + file_style_dialogs file_style_history file_style_mediaview \ + file_style_overview file_style_profile file_style_widgets PRE_TARGETDEPS += style_target numbers_target lang_target @@ -100,6 +102,7 @@ SOURCES += \ ./GeneratedFiles/numbers.cpp \ ./GeneratedFiles/styles/style_basic.cpp \ ./GeneratedFiles/styles/style_basic_types.cpp \ + ./GeneratedFiles/styles/style_boxes.cpp \ ./GeneratedFiles/styles/style_dialogs.cpp \ ./GeneratedFiles/styles/style_history.cpp \ ./GeneratedFiles/styles/style_mediaview.cpp \ @@ -269,6 +272,7 @@ HEADERS += \ ./GeneratedFiles/numbers.h \ ./GeneratedFiles/styles/style_basic.h \ ./GeneratedFiles/styles/style_basic_types.h \ + ./GeneratedFiles/styles/style_boxes.h \ ./GeneratedFiles/styles/style_dialogs.h \ ./GeneratedFiles/styles/style_history.h \ ./GeneratedFiles/styles/style_mediaview.h \ diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index a1d6e7c93..7524d6ad7 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ 0785004D1CCA847400168DBB /* libqtfreetype.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 0785004C1CCA847400168DBB /* libqtfreetype.a */; }; 078A2FCA1A811C5900CCC7A0 /* moc_backgroundbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 078A2FC91A811C5900CCC7A0 /* moc_backgroundbox.cpp */; }; 078A2FCD1A811CA600CCC7A0 /* backgroundbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 078A2FCB1A811CA600CCC7A0 /* backgroundbox.cpp */; }; + 079B51411D4935C0002FB6AF /* style_boxes.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 079B513F1D4935C0002FB6AF /* style_boxes.cpp */; }; 07A69332199277BA0099CB9F /* mediaview.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07A69330199277BA0099CB9F /* mediaview.cpp */; }; 07A6933519927B160099CB9F /* moc_mediaview.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07A6933419927B160099CB9F /* moc_mediaview.cpp */; }; 07AF95F41AFD03B90060B057 /* qrc_telegram_emojis.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07AF95F21AFD03B90060B057 /* qrc_telegram_emojis.cpp */; }; @@ -582,6 +583,8 @@ 078A2FCC1A811CA600CCC7A0 /* backgroundbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = backgroundbox.h; path = SourceFiles/boxes/backgroundbox.h; sourceTree = SOURCE_ROOT; }; 078DD0241A48DD9E00DD14CC /* lang_de.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lang_de.strings; path = Resources/langs/lang_de.strings; sourceTree = SOURCE_ROOT; }; 078DD0251A48DD9E00DD14CC /* lang_nl.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lang_nl.strings; path = Resources/langs/lang_nl.strings; sourceTree = SOURCE_ROOT; }; + 079B513F1D4935C0002FB6AF /* style_boxes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = style_boxes.cpp; path = GeneratedFiles/styles/style_boxes.cpp; sourceTree = SOURCE_ROOT; }; + 079B51401D4935C0002FB6AF /* style_boxes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = style_boxes.h; path = GeneratedFiles/styles/style_boxes.h; sourceTree = SOURCE_ROOT; }; 07A190511A723E0A004287AE /* lang_ko.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lang_ko.strings; path = Resources/langs/lang_ko.strings; sourceTree = SOURCE_ROOT; }; 07A69330199277BA0099CB9F /* mediaview.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mediaview.cpp; path = SourceFiles/mediaview.cpp; sourceTree = SOURCE_ROOT; }; 07A69331199277BA0099CB9F /* mediaview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mediaview.h; path = SourceFiles/mediaview.h; sourceTree = SOURCE_ROOT; }; @@ -1092,6 +1095,8 @@ 0747FF7D1CC6435100096FC3 /* style_basic.h */, 0747FF7A1CC6435100096FC3 /* style_basic_types.cpp */, 0747FF7B1CC6435100096FC3 /* style_basic_types.h */, + 079B513F1D4935C0002FB6AF /* style_boxes.cpp */, + 079B51401D4935C0002FB6AF /* style_boxes.h */, 0716C99C1D08251C00797B22 /* style_dialogs.cpp */, 0716C99D1D08251C00797B22 /* style_dialogs.h */, 0716C99E1D08251C00797B22 /* style_history.cpp */, @@ -2164,6 +2169,7 @@ BA41D511A9BBCA09365DF88C /* downloadpathbox.cpp in Compile Sources */, 07DB67511AD07CB800A51329 /* intropwdcheck.cpp in Compile Sources */, 07C8FE0F1CB80890007A8702 /* toast_widget.cpp in Compile Sources */, + 079B51411D4935C0002FB6AF /* style_boxes.cpp in Compile Sources */, 0716C9741D058C8600797B22 /* moc_profile_inner_widget.cpp in Compile Sources */, 3ABE4F9B2264F770D944106D /* emojibox.cpp in Compile Sources */, 07D703BB19B88FB900C4EED2 /* moc_media_audio.cpp in Compile Sources */, From 5df54371b5d847bf302bdb4eca70ab7be8bea5b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 28 Jul 2016 20:01:08 +0300 Subject: [PATCH 51/60] ConfirmPhoneBox support added. Limiting StartUrl to 8k symbols to prevent share url text overflow. Better working with many cases of regular expression switch. --- Telegram/Resources/langs/lang.strings | 7 + Telegram/SourceFiles/application.cpp | 2 +- Telegram/SourceFiles/boxes/boxes.style | 6 + .../SourceFiles/boxes/confirmphonebox.cpp | 305 +++++++++++ Telegram/SourceFiles/boxes/confirmphonebox.h | 105 ++++ Telegram/SourceFiles/boxes/passcodebox.h | 18 +- Telegram/SourceFiles/boxes/usernamebox.cpp | 5 +- .../SourceFiles/core/click_handler_types.cpp | 39 +- Telegram/SourceFiles/core/qthelp_regex.h | 71 +++ Telegram/SourceFiles/core/qthelp_url.cpp | 52 ++ Telegram/SourceFiles/core/qthelp_url.h | 40 ++ Telegram/SourceFiles/intro/introcode.h | 3 - Telegram/SourceFiles/mainwidget.cpp | 80 +-- Telegram/SourceFiles/mainwindow.cpp | 2 +- Telegram/SourceFiles/mtproto/generate.py | 6 +- Telegram/SourceFiles/mtproto/scheme.tl | 4 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 512 ++++++++++-------- Telegram/SourceFiles/mtproto/scheme_auto.h | 154 ++++++ .../profile/profile_info_widget.cpp | 2 +- Telegram/SourceFiles/settings.cpp | 2 +- Telegram/SourceFiles/settingswidget.h | 1 - Telegram/SourceFiles/ui/flatlabel.cpp | 2 +- Telegram/SourceFiles/ui/text/text.h | 7 - Telegram/SourceFiles/ui/twidget.h | 7 + Telegram/Telegram.vcxproj | 30 + Telegram/Telegram.vcxproj.filters | 26 +- 26 files changed, 1169 insertions(+), 319 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/confirmphonebox.cpp create mode 100644 Telegram/SourceFiles/boxes/confirmphonebox.h create mode 100644 Telegram/SourceFiles/core/qthelp_regex.h create mode 100644 Telegram/SourceFiles/core/qthelp_url.cpp create mode 100644 Telegram/SourceFiles/core/qthelp_url.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2047222c6..3a45dfae8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -939,6 +939,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "This link is broken or has expired."; +"lng_confirm_phone_title" = "Cancel account reset"; +"lng_confirm_phone_about" = "Somebody with access to your phone number {phone} has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf this wasn't you, please enter the code we've just sent you via SMS to your number."; +"lng_confirm_phone_success" = "Success!\n\nThe deletion process was cancelled for your account {phone}. You may close this window now."; +"lng_confirm_phone_send" = "Send"; +"lng_confirm_phone_enter_code" = "Please enter the code."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index d690d196f..d270991e9 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -264,7 +264,7 @@ void Application::readClients() { } } else if (cmd.startsWith(qsl("OPEN:"))) { if (cStartUrl().isEmpty()) { - startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)); + startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192); } } else { LOG(("Application Error: unknown command %1 passed in local socket").arg(QString(cmd.constData(), cmd.length()))); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 54f43ec25..ba77c0abe 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -67,3 +67,9 @@ stickersFeaturedUnreadTop: 7px; stickersFeaturedInstalled: icon { { "mediaview_save_check", #40ace3 } }; + +confirmPhoneAboutLabel: flatLabel(labelDefFlat) { + width: 282px; +} +confirmPhoneCodeField: InputField(defaultInputField) { +} diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.cpp b/Telegram/SourceFiles/boxes/confirmphonebox.cpp new file mode 100644 index 000000000..dbd749515 --- /dev/null +++ b/Telegram/SourceFiles/boxes/confirmphonebox.cpp @@ -0,0 +1,305 @@ +/* +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 "boxes/confirmphonebox.h" + +#include "styles/style_boxes.h" +#include "boxes/confirmbox.h" +#include "mainwidget.h" +#include "lang.h" + +namespace { + +QPointer CurrentConfirmPhoneBox = nullptr; + +} // namespace + +void ConfirmPhoneBox::start(const QString &phone, const QString &hash) { + if (CurrentConfirmPhoneBox) { + if (CurrentConfirmPhoneBox->getPhone() == phone) return; + delete CurrentConfirmPhoneBox; + } + if (auto main = App::main()) { + CurrentConfirmPhoneBox = new ConfirmPhoneBox(main, phone, hash); + } +} + +ConfirmPhoneBox::ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash) : AbstractBox(st::boxWidth) +, _phone(phone) +, _hash(hash) { + setParent(parent); + + MTPaccount_SendConfirmPhoneCode::Flags flags = 0; + _sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(flags), MTP_string(hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail)); +} + +void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { + _sendCodeRequestId = 0; + + auto &resultInner = result.c_auth_sentCode(); + switch (resultInner.vtype.type()) { + case mtpc_auth_sentCodeTypeApp: LOG(("Error: should not be in-app code!")); break; + case mtpc_auth_sentCodeTypeSms: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeSms().vlength.v; break; + case mtpc_auth_sentCodeTypeCall: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeCall().vlength.v; break; + case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; + } + if (resultInner.has_next_type() && resultInner.vnext_type.type() == mtpc_auth_codeTypeCall) { + setCallStatus({ CallState::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 }); + } else { + setCallStatus({ CallState::Disabled, 0 }); + } + launch(); +} + +bool ConfirmPhoneBox::sendCodeFail(const RPCError &error) { + auto errorText = lang(lng_server_error); + if (MTP::isFloodError(error)) { + errorText = lang(lng_flood_error); + } else if (MTP::isDefaultHandledError(error)) { + return false; + } else if (error.code() == 400) { + errorText = lang(lng_confirm_phone_link_invalid); + } + _sendCodeRequestId = 0; + Ui::showLayer(new InformBox(errorText)); + deleteLater(); + return true; +} + +void ConfirmPhoneBox::setCallStatus(const CallStatus &status) { + _callStatus = status; + if (_callStatus.state == CallState::Waiting) { + _callTimer.start(1000); + } +} + +void ConfirmPhoneBox::launch() { + setBlueTitle(true); + + _about = new FlatLabel(this, st::confirmPhoneAboutLabel); + TextWithEntities aboutText; + auto formattedPhone = App::formatPhone(_phone); + aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone); + auto phonePosition = aboutText.text.indexOf(formattedPhone); + if (phonePosition >= 0) { + aboutText.entities.push_back(EntityInText(EntityInTextBold, phonePosition, formattedPhone.size())); + } + _about->setMarkedText(aboutText); + + _code = new InputField(this, st::confirmPhoneCodeField, lang(lng_code_ph)); + + _send = new BoxButton(this, lang(lng_confirm_phone_send), st::defaultBoxButton); + _cancel = new BoxButton(this, lang(lng_cancel), st::cancelBoxButton); + + setMaxHeight(st::boxTitleHeight + st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip + _send->height() + st::boxButtonPadding.bottom()); + + connect(_send, SIGNAL(clicked()), this, SLOT(onSendCode())); + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + connect(_code, SIGNAL(changed()), this, SLOT(onCodeChanged())); + connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode())); + + connect(&_callTimer, SIGNAL(timeout()), this, SLOT(onCallStatusTimer())); + + prepare(); + + Ui::showLayer(this); +} + +void ConfirmPhoneBox::onCallStatusTimer() { + if (_callStatus.state == CallState::Waiting) { + if (--_callStatus.timeout <= 0) { + _callStatus.state = CallState::Calling; + _callTimer.stop(); + MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_hash)), rpcDone(&ConfirmPhoneBox::callDone)); + } + } + update(); +} + +void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) { + if (_callStatus.state == CallState::Calling) { + _callStatus.state = CallState::Called; + update(); + } +} + +void ConfirmPhoneBox::onSendCode() { + if (_sendCodeRequestId) { + return; + } + auto code = _code->getLastText(); + if (code.isEmpty()) { + _code->showError(); + return; + } + + _code->setDisabled(true); + setFocus(); + + showError(QString()); + + _sendCodeRequestId = MTP::send(MTPaccount_ConfirmPhone(MTP_string(_hash), MTP_string(_code->getLastText())), rpcDone(&ConfirmPhoneBox::confirmDone), rpcFail(&ConfirmPhoneBox::confirmFail)); +} + +void ConfirmPhoneBox::confirmDone(const MTPBool &result) { + _sendCodeRequestId = 0; + Ui::showLayer(new InformBox(lng_confirm_phone_success(lt_phone, App::formatPhone(_phone)))); +} + +bool ConfirmPhoneBox::confirmFail(const RPCError &error) { + auto errorText = lang(lng_server_error); + if (MTP::isFloodError(error)) { + errorText = lang(lng_flood_error); + } else if (MTP::isDefaultHandledError(error)) { + return false; + } else { + auto &errorType = error.type(); + if (errorType == qstr("PHONE_CODE_EMPTY") || errorType == qstr("PHONE_CODE_INVALID")) { + errorText = lang(lng_bad_code); + } + } + _sendCodeRequestId = 0; + _code->setDisabled(false); + _code->setFocus(); + showError(errorText); + return true; +} + +void ConfirmPhoneBox::onCodeChanged() { + if (_fixing) return; + + _fixing = true; + QString newText, now = _code->getLastText(); + int oldPos = _code->textCursor().position(), newPos = -1; + int oldLen = now.size(), digitCount = 0; + for_const (auto ch, now) { + if (ch.isDigit()) { + ++digitCount; + } + } + + if (_sentCodeLength > 0 && digitCount > _sentCodeLength) { + digitCount = _sentCodeLength; + } + bool strict = (_sentCodeLength > 0 && digitCount == _sentCodeLength); + + newText.reserve(oldLen); + int i = 0; + for_const (auto ch, now) { + if (i++ == oldPos) { + newPos = newText.length(); + } + if (ch.isDigit()) { + if (!digitCount--) { + break; + } + newText += ch; + if (strict && !digitCount) { + break; + } + } + } + if (newPos < 0) { + newPos = newText.length(); + } + if (newText != now) { + now = newText; + _code->setText(now); + _code->setCursorPosition(newPos); + } + _fixing = false; + + showError(QString()); + //if (strict) { + // onSendCode(); + //} +} + +void ConfirmPhoneBox::showError(const QString &error) { + _error = error; + if (!_error.isEmpty()) { + _code->showError(); + } + update(); +} + +void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_confirm_phone_title)); + + p.setFont(st::boxTextFont); + auto callText = getCallText(); + if (!callText.isEmpty()) { + p.setPen(st::usernameDefaultFg); + auto callTextRectLeft = st::usernamePadding.left(); + auto callTextRectTop = _about->y() + _about->height(); + auto callTextRectWidth = width() - 2 * st::usernamePadding.left(); + auto callTextRect = QRect(callTextRectLeft, callTextRectTop, callTextRectWidth, st::usernameSkip); + p.drawText(callTextRect, callText, style::al_left); + } + auto errorText = _error; + if (errorText.isEmpty()) { + p.setPen(st::usernameDefaultFg); + errorText = lang(lng_confirm_phone_enter_code); + } else { + p.setPen(st::setErrColor); + } + auto errorTextRectLeft = st::usernamePadding.left(); + auto errorTextRectTop = _code->y() + _code->height(); + auto errorTextRectWidth = width() - 2 * st::usernamePadding.left(); + auto errorTextRect = QRect(errorTextRectLeft, errorTextRectTop, errorTextRectWidth, st::usernameSkip); + p.drawText(errorTextRect, errorText, style::al_left); +} + +QString ConfirmPhoneBox::getCallText() const { + switch (_callStatus.state) { + case CallState::Waiting: { + if (_callStatus.timeout >= 3600) { + return lng_code_call(lt_minutes, qsl("%1:%2").arg(_callStatus.timeout / 3600).arg((_callStatus.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); + } + return lng_code_call(lt_minutes, QString::number(_callStatus.timeout / 60), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); + } break; + case CallState::Calling: return lang(lng_code_calling); + case CallState::Called: return lang(lng_code_called); + } + return QString(); +} + +void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) { + _code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height()); + _code->moveToLeft(st::usernamePadding.left(), st::boxTitleHeight + st::usernamePadding.top()); + + _about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip); + + _send->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _send->height()); + _cancel->moveToRight(st::boxButtonPadding.right() + _send->width() + st::boxButtonPadding.left(), _send->y()); + + AbstractBox::resizeEvent(e); +} + +ConfirmPhoneBox::~ConfirmPhoneBox() { + if (_sendCodeRequestId) { + MTP::cancel(_sendCodeRequestId); + } +} diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.h b/Telegram/SourceFiles/boxes/confirmphonebox.h new file mode 100644 index 000000000..46cbebc48 --- /dev/null +++ b/Telegram/SourceFiles/boxes/confirmphonebox.h @@ -0,0 +1,105 @@ +/* +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 "boxes/abstractbox.h" + +class FlatLabel; + +class ConfirmPhoneBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + static void start(const QString &phone, const QString &hash); + + ~ConfirmPhoneBox(); + +private slots: + void onCallStatusTimer(); + void onSendCode(); + void onCodeChanged(); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void hideAll() override { + hideChildren(); + } + void showAll() override { + showChildren(); + } + void showDone() override { + _code->setFocus(); + } + +private: + ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash); + void sendCodeDone(const MTPauth_SentCode &result); + bool sendCodeFail(const RPCError &error); + + void callDone(const MTPauth_SentCode &result); + + void confirmDone(const MTPBool &result); + bool confirmFail(const RPCError &error); + + QString getPhone() const { + return _phone; + } + void launch(); + + enum CallState { + Waiting, + Calling, + Called, + Disabled, + }; + struct CallStatus { + CallState state; + int timeout; + }; + void setCallStatus(const CallStatus &status); + QString getCallText() const; + + void showError(const QString &error); + + mtpRequestId _sendCodeRequestId = 0; + + QString _phone; + QString _hash; + + // If we receive the code length, we autosubmit _code field when enough symbols is typed. + int _sentCodeLength = 0; + + mtpRequestId _checkCodeRequestId = 0; + + ChildWidget _about = { nullptr }; + ChildWidget _send = { nullptr }; + ChildWidget _cancel = { nullptr }; + ChildWidget _code = { nullptr }; + + // Flag for not calling onTextChanged() recursively. + bool _fixing = false; + QString _error; + + CallStatus _callStatus; + QTimer _callTimer; + +}; diff --git a/Telegram/SourceFiles/boxes/passcodebox.h b/Telegram/SourceFiles/boxes/passcodebox.h index be908ae1a..eecdf664f 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.h +++ b/Telegram/SourceFiles/boxes/passcodebox.h @@ -26,15 +26,10 @@ class PasscodeBox : public AbstractBox, public RPCSender { Q_OBJECT public: - PasscodeBox(bool turningOff = false); PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false); - void init(); - void paintEvent(QPaintEvent *e); - void resizeEvent(QResizeEvent *e); - -public slots: +private slots: void onSave(bool force = false); void onBadOldPasscode(); void onOldChanged(); @@ -47,16 +42,17 @@ public slots: void onSubmit(); signals: - void reloadPassword(); protected: - - void hideAll(); - void showAll(); - void showDone(); + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void hideAll() override; + void showAll() override; + void showDone() override; private: + void init(); void setPasswordDone(const MTPBool &result); bool setPasswordFail(const RPCError &error); diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index 7d7bb157f..3070baf2e 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -83,21 +83,18 @@ void UsernameBox::paintEvent(QPaintEvent *e) { paintTitle(p, lang(lng_username_title)); + p.setFont(st::boxTextFont); if (!_copiedTextLink.isEmpty()) { p.setPen(st::usernameDefaultFg); - p.setFont(st::boxTextFont); p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _copiedTextLink); } else if (!_errorText.isEmpty()) { p.setPen(st::setErrColor); - p.setFont(st::boxTextFont); p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText); } else if (!_goodText.isEmpty()) { p.setPen(st::setGoodColor); - p.setFont(st::boxTextFont); p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText); } else { p.setPen(st::usernameDefaultFg); - p.setFont(st::boxTextFont); p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), lang(lng_username_choose)); } p.setPen(st::black); diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 23b34fb76..3726fa358 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -24,6 +24,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "pspecific.h" #include "boxes/confirmbox.h" +#include "core/qthelp_regex.h" +#include "core/qthelp_url.h" QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); @@ -31,23 +33,28 @@ QString UrlClickHandler::copyToClipboardContextItemText() const { namespace { -QString tryConvertUrlToLocal(const QString &url) { - QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeShareUrl = QRegularExpression(qsl("^https?://telegram\\.me/share/url\\?(.+)$"), QRegularExpression::CaseInsensitiveOption).match(url); - if (telegramMeGroup.hasMatch()) { - return qsl("tg://join?invite=") + myUrlEncode(telegramMeGroup.captured(1)); - } else if (telegramMeStickers.hasMatch()) { - return qsl("tg://addstickers?set=") + myUrlEncode(telegramMeStickers.captured(1)); - } else if (telegramMeShareUrl.hasMatch()) { - return qsl("tg://msg_url?") + telegramMeShareUrl.captured(1); - } else if (telegramMeUser.hasMatch()) { - QString params = url.mid(telegramMeUser.captured(0).size()), postParam; - if (QRegularExpression(qsl("^/\\d+/?(?:\\?|$)")).match(telegramMeUser.captured(2)).hasMatch()) { - postParam = qsl("&post=") + telegramMeUser.captured(3); +QString tryConvertUrlToLocal(QString url) { + if (url.size() > 8192) url = url.mid(0, 8192); + + using namespace qthelp; + auto matchOptions = RegExOption::CaseInsensitive; + if (auto telegramMeMatch = regex_match(qsl("https?://telegram\\.me/(.+)$"), url, matchOptions)) { + auto query = telegramMeMatch->capturedRef(1); + if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) { + return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1)); + } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) { + return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1)); + } else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) { + return qsl("tg://msg_url?") + shareUrlMatch->captured(1); + } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) { + return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1); + } else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) { + QString params = url.mid(usernameMatch->captured(0).size()), postParam; + if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) { + postParam = qsl("&post=") + usernameMatch->captured(3); + } + return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params); } - return qsl("tg://resolve/?domain=") + myUrlEncode(telegramMeUser.captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params); } return url; } diff --git a/Telegram/SourceFiles/core/qthelp_regex.h b/Telegram/SourceFiles/core/qthelp_regex.h new file mode 100644 index 000000000..6ecd20a4f --- /dev/null +++ b/Telegram/SourceFiles/core/qthelp_regex.h @@ -0,0 +1,71 @@ +/* +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 qthelp { + +class RegularExpressionMatch { +public: + RegularExpressionMatch(QRegularExpressionMatch &&match) : data_(std_::move(match)) { + } + RegularExpressionMatch(RegularExpressionMatch &&other) : data_(std_::move(other.data_)) { + } + QRegularExpressionMatch *operator->() { + return &data_; + } + const QRegularExpressionMatch *operator->() const { + return &data_; + } + explicit operator bool() const { + return data_.hasMatch(); + } + +private: + QRegularExpressionMatch data_; + +}; + +enum class RegExOption { + None = QRegularExpression::NoPatternOption, + CaseInsensitive = QRegularExpression::CaseInsensitiveOption, + DotMatchesEverything = QRegularExpression::DotMatchesEverythingOption, + Multiline = QRegularExpression::MultilineOption, + ExtendedSyntax = QRegularExpression::ExtendedPatternSyntaxOption, + InvertedGreediness = QRegularExpression::InvertedGreedinessOption, + DontCapture = QRegularExpression::DontCaptureOption, + UseUnicodeProperties = QRegularExpression::UseUnicodePropertiesOption, + OptimizeOnFirstUsage = QRegularExpression::OptimizeOnFirstUsageOption, + DontAutomaticallyOptimize = QRegularExpression::DontAutomaticallyOptimizeOption, +}; +Q_DECLARE_FLAGS(RegExOptions, RegExOption); +Q_DECLARE_OPERATORS_FOR_FLAGS(RegExOptions); + +inline RegularExpressionMatch regex_match(const QString &string, const QString &subject, RegExOptions options = 0) { + auto qtOptions = QRegularExpression::PatternOptions(static_cast(options)); + return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subject)); +} + +inline RegularExpressionMatch regex_match(const QString &string, const QStringRef &subjectRef, RegExOptions options = 0) { + auto qtOptions = QRegularExpression::PatternOptions(static_cast(options)); + return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subjectRef)); +} + +} // namespace qthelp diff --git a/Telegram/SourceFiles/core/qthelp_url.cpp b/Telegram/SourceFiles/core/qthelp_url.cpp new file mode 100644 index 000000000..8acd3791d --- /dev/null +++ b/Telegram/SourceFiles/core/qthelp_url.cpp @@ -0,0 +1,52 @@ +/* +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 "core/qthelp_url.h" + +namespace qthelp { + +QMap url_parse_params(const QString ¶ms, UrlParamNameTransform transform) { + QMap result; + + auto transformParamName = [transform](const QString &name) { + if (transform == UrlParamNameTransform::ToLower) { + return name.toLower(); + } + return name; + }; + + auto paramsList = params.split('&'); + for_const (auto ¶m, paramsList) { + // Skip params without a name (starting with '='). + if (auto separatorPosition = param.indexOf('=')) { + auto paramName = param; + auto paramValue = QString(); + if (separatorPosition > 0) { + paramName = param.mid(0, separatorPosition); + paramValue = url_decode(param.mid(separatorPosition + 1)); + } + result.insert(transformParamName(paramName), paramValue); + } + } + return result; +} + +} // namespace qthelp diff --git a/Telegram/SourceFiles/core/qthelp_url.h b/Telegram/SourceFiles/core/qthelp_url.h new file mode 100644 index 000000000..c6a904494 --- /dev/null +++ b/Telegram/SourceFiles/core/qthelp_url.h @@ -0,0 +1,40 @@ +/* +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 qthelp { + +inline QString url_encode(const QString &part) { + return QString::fromLatin1(QUrl::toPercentEncoding(part)); +} + +inline QString url_decode(const QString &encoded) { + return QUrl::fromPercentEncoding(encoded.toUtf8()); +} + +enum class UrlParamNameTransform { + NoTransform, + ToLower, +}; +// Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map. +QMap url_parse_params(const QString ¶ms, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform); + +} // namespace qthelp diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index 7e6bfad3d..ab62224ca 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -29,15 +29,12 @@ class CodeInput final : public FlatInput { Q_OBJECT public: - CodeInput(QWidget *parent, const style::flatInput &st, const QString &ph); signals: - void codeEntered(); protected: - void correctValue(const QString &was, QString &now); }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 7ab4ce6d7..ed2fa90b0 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -45,9 +45,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/stickersetbox.h" #include "boxes/contactsbox.h" #include "boxes/downloadpathbox.h" +#include "boxes/confirmphonebox.h" #include "localstorage.h" #include "shortcuts.h" #include "media/media_audio.h" +#include "core/qthelp_regex.h" +#include "core/qthelp_url.h" StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) , _memento(std_::move(memento)) { @@ -56,6 +59,8 @@ StackItemSection::StackItemSection(std_::unique_ptr &&me StackItemSection::~StackItemSection() { } +#include "boxes/confirmphonebox.h" + MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _a_show(animation(this, &MainWidget::step_show)) , _dialogsWidth(st::dialogsWidthMin) @@ -3286,51 +3291,50 @@ bool MainWidget::started() { void MainWidget::openLocalUrl(const QString &url) { QString u(url.trimmed()); - if (u.startsWith(qstr("tg://resolve"), Qt::CaseInsensitive)) { - QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u); - if (m.hasMatch()) { - QString params = u.mid(m.capturedLength(0)); + if (u.size() > 8192) u = u.mid(0, 8192); - QString start, startToken; - QRegularExpressionMatch startparam = QRegularExpression(qsl("(^|&)(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+)(&|$)")).match(params); - if (startparam.hasMatch()) { - start = startparam.captured(2); - startToken = startparam.captured(3); - } + if (!u.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { + return; + } - MsgId post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId; - QRegularExpressionMatch postparam = QRegularExpression(qsl("(^|&)post=(\\d+)(&|$)")).match(params); - if (postparam.hasMatch()) { - post = postparam.captured(2).toInt(); - } - - openPeerByName(m.captured(1), post, startToken); + using namespace qthelp; + auto matchOptions = RegExOption::CaseInsensitive; + if (auto joinChatMatch = regex_match(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), u, matchOptions)) { + joinGroupByHash(joinChatMatch->captured(1)); + } else if (auto stickerSetMatch = regex_match(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), u, matchOptions)) { + stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1)))); + } else if (auto shareUrlMatch = regex_match(qsl("^tg://msg_url/?\\?(.+)(#|$)"), u, matchOptions)) { + auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower); + auto url = params.value(qsl("url")); + if (!url.isEmpty()) { + shareUrlLayer(url, params.value("text")); } - } else if (u.startsWith(qstr("tg://join"), Qt::CaseInsensitive)) { - QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u); - if (m.hasMatch()) { - joinGroupByHash(m.captured(1)); + } else if (auto confirmPhoneMatch = regex_match(qsl("^tg://confirmphone/?\\?(.+)(#|$)"), u, matchOptions)) { + auto params = url_parse_params(confirmPhoneMatch->captured(1), UrlParamNameTransform::ToLower); + auto phone = params.value(qsl("phone")); + auto hash = params.value(qsl("hash")); + if (!phone.isEmpty() && !hash.isEmpty()) { + ConfirmPhoneBox::start(phone, hash); } - } else if (u.startsWith(qstr("tg://addstickers"), Qt::CaseInsensitive)) { - QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u); - if (m.hasMatch()) { - stickersBox(MTP_inputStickerSetShortName(MTP_string(m.captured(1)))); - } - } else if (u.startsWith(qstr("tg://msg_url"), Qt::CaseInsensitive)) { - QRegularExpressionMatch m = QRegularExpression(qsl("^tg://msg_url/?\\?(.+)(#|$)"), QRegularExpression::CaseInsensitiveOption).match(u); - if (m.hasMatch()) { - QStringList params = m.captured(1).split('&'); - QString url, text; - for (int32 i = 0, l = params.size(); i < l; ++i) { - if (params.at(i).startsWith(qstr("url="), Qt::CaseInsensitive)) { - url = myUrlDecode(params.at(i).mid(4)); - } else if (params.at(i).startsWith(qstr("text="), Qt::CaseInsensitive)) { - text = myUrlDecode(params.at(i).mid(5)); + } else if (auto usernameMatch = regex_match(qsl("^tg://resolve/?\\?(.+)(#|$)"), u, matchOptions)) { + auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower); + auto domain = params.value(qsl("domain")); + if (auto domainMatch = regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) { + auto start = qsl("start"); + auto startToken = params.value(start); + if (startToken.isEmpty()) { + start = qsl("startgroup"); + startToken = params.value(start); + if (startToken.isEmpty()) { + start = QString(); } } - if (!url.isEmpty()) { - shareUrlLayer(url, text); + auto post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId; + auto postParam = params.value(qsl("post")); + if (auto postId = postParam.toInt()) { + post = postId; } + openPeerByName(domain, post, startToken); } } } diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 88e49d8a0..0fe9a2283 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -1072,7 +1072,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) { if (obj == Application::instance()) { QString url = static_cast(e)->url().toEncoded().trimmed(); if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { - cSetStartUrl(url); + cSetStartUrl(url.mid(0, 8192)); if (!cStartUrl().isEmpty() && App::main() && App::self()) { App::main()->openLocalUrl(cStartUrl()); cSetStartUrl(QString()); diff --git a/Telegram/SourceFiles/mtproto/generate.py b/Telegram/SourceFiles/mtproto/generate.py index 9d2537339..aaa0ecc94 100644 --- a/Telegram/SourceFiles/mtproto/generate.py +++ b/Telegram/SourceFiles/mtproto/generate.py @@ -136,8 +136,10 @@ with open('scheme.tl') as f: cleanline = cleanline.replace('?bytes ', '?string '); cleanline = cleanline.replace('{', ''); cleanline = cleanline.replace('}', ''); - countTypeId = hex(binascii.crc32(binascii.a2b_qp(cleanline)))[2:]; - countTypeId = '0x' + countTypeId; + countTypeId = binascii.crc32(binascii.a2b_qp(cleanline)); + if (countTypeId < 0): + countTypeId += 2 ** 32; + countTypeId = '0x' + re.sub(r'^0x|L$', '', hex(countTypeId)); if (typeid != countTypeId): print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); continue; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 96162e540..c418d0fdf 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -111,7 +111,7 @@ ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong; destroy_session#e7512126 session_id:long = DestroySessionRes; -#contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool; +contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool; /////////////////////////////// ///////// Main application API @@ -760,6 +760,8 @@ account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings; account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool; +account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; +account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 21ef6b800..34298d2bd 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -5954,228 +5954,19 @@ void _serialize_destroy_session(MTPStringLogger &to, int32 stage, int32 lev, Typ } } -void _serialize_invokeAfterMsg(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { +void _serialize_contest_saveDeveloperInfo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); } else { - to.add("{ invokeAfterMsg"); + to.add("{ contest_saveDeveloperInfo"); to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeAfterMsgs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeAfterMsgs"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" msg_ids: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_initConnection(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ initConnection"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" device_model: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" system_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" app_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" lang_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeWithLayer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeWithLayer"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" layer: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_invokeWithoutUpdates(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ invokeWithoutUpdates"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_checkPhone(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_checkPhone"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_sendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - MTPauth_sendCode::Flags flag(iflag); - - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_sendCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 0: to.add(" vk_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 4: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_resendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_resendCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_account_sendChangePhoneCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - MTPaccount_sendChangePhoneCode::Flags flag(iflag); - - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ account_sendChangePhoneCode"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_signUp(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_signUp"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_signIn(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_signIn"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_importAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_importAuthorization"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_importBotAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_importBotAuthorization"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_checkPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_checkPassword"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" password_hash: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_auth_recoverPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ auth_recoverPassword"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" age: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" city: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6384,6 +6175,20 @@ void _serialize_account_updatePasswordSettings(MTPStringLogger &to, int32 stage, } } +void _serialize_account_confirmPhone(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ account_confirmPhone"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_contacts_deleteContacts(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6791,6 +6596,250 @@ void _serialize_channels_updateUsername(MTPStringLogger &to, int32 stage, int32 } } +void _serialize_invokeAfterMsg(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeAfterMsg"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeAfterMsgs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeAfterMsgs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" msg_ids: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_initConnection(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ initConnection"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" device_model: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" system_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" app_version: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" lang_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeWithLayer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeWithLayer"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" layer: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_invokeWithoutUpdates(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ invokeWithoutUpdates"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" query: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_checkPhone(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_checkPhone"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_sendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPauth_sendCode::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_sendCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPauth_sendCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_resendCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_resendCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_account_sendChangePhoneCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPaccount_sendChangePhoneCode::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ account_sendChangePhoneCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPaccount_sendChangePhoneCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_account_sendConfirmPhoneCode(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPaccount_sendConfirmPhoneCode::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ account_sendConfirmPhoneCode"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" allow_flashcall: "); ++stages.back(); if (flag & MTPaccount_sendConfirmPhoneCode::Flag::f_allow_flashcall) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" current_number: "); ++stages.back(); if (flag & MTPaccount_sendConfirmPhoneCode::Flag::f_current_number) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_signUp(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_signUp"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_signIn(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_signIn"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" phone_number: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" phone_code_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" phone_code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_importAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_importAuthorization"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_importBotAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_importBotAuthorization"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_checkPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_checkPassword"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" password_hash: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_auth_recoverPassword(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_recoverPassword"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" code: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_auth_exportAuthorization(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8872,21 +8921,7 @@ namespace { _serializers.insert(mtpc_ping, _serialize_ping); _serializers.insert(mtpc_ping_delay_disconnect, _serialize_ping_delay_disconnect); _serializers.insert(mtpc_destroy_session, _serialize_destroy_session); - _serializers.insert(mtpc_invokeAfterMsg, _serialize_invokeAfterMsg); - _serializers.insert(mtpc_invokeAfterMsgs, _serialize_invokeAfterMsgs); - _serializers.insert(mtpc_initConnection, _serialize_initConnection); - _serializers.insert(mtpc_invokeWithLayer, _serialize_invokeWithLayer); - _serializers.insert(mtpc_invokeWithoutUpdates, _serialize_invokeWithoutUpdates); - _serializers.insert(mtpc_auth_checkPhone, _serialize_auth_checkPhone); - _serializers.insert(mtpc_auth_sendCode, _serialize_auth_sendCode); - _serializers.insert(mtpc_auth_resendCode, _serialize_auth_resendCode); - _serializers.insert(mtpc_account_sendChangePhoneCode, _serialize_account_sendChangePhoneCode); - _serializers.insert(mtpc_auth_signUp, _serialize_auth_signUp); - _serializers.insert(mtpc_auth_signIn, _serialize_auth_signIn); - _serializers.insert(mtpc_auth_importAuthorization, _serialize_auth_importAuthorization); - _serializers.insert(mtpc_auth_importBotAuthorization, _serialize_auth_importBotAuthorization); - _serializers.insert(mtpc_auth_checkPassword, _serialize_auth_checkPassword); - _serializers.insert(mtpc_auth_recoverPassword, _serialize_auth_recoverPassword); + _serializers.insert(mtpc_contest_saveDeveloperInfo, _serialize_contest_saveDeveloperInfo); _serializers.insert(mtpc_auth_logOut, _serialize_auth_logOut); _serializers.insert(mtpc_auth_resetAuthorizations, _serialize_auth_resetAuthorizations); _serializers.insert(mtpc_auth_sendInvites, _serialize_auth_sendInvites); @@ -8904,6 +8939,7 @@ namespace { _serializers.insert(mtpc_account_updateDeviceLocked, _serialize_account_updateDeviceLocked); _serializers.insert(mtpc_account_resetAuthorization, _serialize_account_resetAuthorization); _serializers.insert(mtpc_account_updatePasswordSettings, _serialize_account_updatePasswordSettings); + _serializers.insert(mtpc_account_confirmPhone, _serialize_account_confirmPhone); _serializers.insert(mtpc_contacts_deleteContacts, _serialize_contacts_deleteContacts); _serializers.insert(mtpc_contacts_block, _serialize_contacts_block); _serializers.insert(mtpc_contacts_unblock, _serialize_contacts_unblock); @@ -8933,6 +8969,22 @@ namespace { _serializers.insert(mtpc_channels_editAbout, _serialize_channels_editAbout); _serializers.insert(mtpc_channels_checkUsername, _serialize_channels_checkUsername); _serializers.insert(mtpc_channels_updateUsername, _serialize_channels_updateUsername); + _serializers.insert(mtpc_invokeAfterMsg, _serialize_invokeAfterMsg); + _serializers.insert(mtpc_invokeAfterMsgs, _serialize_invokeAfterMsgs); + _serializers.insert(mtpc_initConnection, _serialize_initConnection); + _serializers.insert(mtpc_invokeWithLayer, _serialize_invokeWithLayer); + _serializers.insert(mtpc_invokeWithoutUpdates, _serialize_invokeWithoutUpdates); + _serializers.insert(mtpc_auth_checkPhone, _serialize_auth_checkPhone); + _serializers.insert(mtpc_auth_sendCode, _serialize_auth_sendCode); + _serializers.insert(mtpc_auth_resendCode, _serialize_auth_resendCode); + _serializers.insert(mtpc_account_sendChangePhoneCode, _serialize_account_sendChangePhoneCode); + _serializers.insert(mtpc_account_sendConfirmPhoneCode, _serialize_account_sendConfirmPhoneCode); + _serializers.insert(mtpc_auth_signUp, _serialize_auth_signUp); + _serializers.insert(mtpc_auth_signIn, _serialize_auth_signIn); + _serializers.insert(mtpc_auth_importAuthorization, _serialize_auth_importAuthorization); + _serializers.insert(mtpc_auth_importBotAuthorization, _serialize_auth_importBotAuthorization); + _serializers.insert(mtpc_auth_checkPassword, _serialize_auth_checkPassword); + _serializers.insert(mtpc_auth_recoverPassword, _serialize_auth_recoverPassword); _serializers.insert(mtpc_auth_exportAuthorization, _serialize_auth_exportAuthorization); _serializers.insert(mtpc_auth_requestPasswordRecovery, _serialize_auth_requestPasswordRecovery); _serializers.insert(mtpc_account_getNotifySettings, _serialize_account_getNotifySettings); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index f4ce9d502..ff8bb5e72 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -76,6 +76,7 @@ enum { mtpc_ping = 0x7abe77ec, mtpc_ping_delay_disconnect = 0xf3427b8c, mtpc_destroy_session = 0xe7512126, + mtpc_contest_saveDeveloperInfo = 0x9a5f6e95, mtpc_boolFalse = 0xbc799737, mtpc_boolTrue = 0x997275b5, mtpc_true = 0x3fedd339, @@ -555,6 +556,8 @@ enum { mtpc_account_getPassword = 0x548a30f5, mtpc_account_getPasswordSettings = 0xbc8d11bb, mtpc_account_updatePasswordSettings = 0xfa7c4b86, + mtpc_account_sendConfirmPhoneCode = 0x1516d7bd, + mtpc_account_confirmPhone = 0x5f2178c3, mtpc_users_getUsers = 0xd91a548, mtpc_users_getFullUser = 0xca30a5b1, mtpc_contacts_getStatuses = 0xc4a353ee, @@ -15025,6 +15028,57 @@ public: } }; +class MTPcontest_saveDeveloperInfo { // RPC method 'contest.saveDeveloperInfo' +public: + MTPint vvk_id; + MTPstring vname; + MTPstring vphone_number; + MTPint vage; + MTPstring vcity; + + MTPcontest_saveDeveloperInfo() { + } + MTPcontest_saveDeveloperInfo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contest_saveDeveloperInfo) { + read(from, end, cons); + } + MTPcontest_saveDeveloperInfo(MTPint _vk_id, const MTPstring &_name, const MTPstring &_phone_number, MTPint _age, const MTPstring &_city) : vvk_id(_vk_id), vname(_name), vphone_number(_phone_number), vage(_age), vcity(_city) { + } + + uint32 innerLength() const { + return vvk_id.innerLength() + vname.innerLength() + vphone_number.innerLength() + vage.innerLength() + vcity.innerLength(); + } + mtpTypeId type() const { + return mtpc_contest_saveDeveloperInfo; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contest_saveDeveloperInfo) { + vvk_id.read(from, end); + vname.read(from, end); + vphone_number.read(from, end); + vage.read(from, end); + vcity.read(from, end); + } + void write(mtpBuffer &to) const { + vvk_id.write(to); + vname.write(to); + vphone_number.write(to); + vage.write(to); + vcity.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPcontest_SaveDeveloperInfo : public MTPBoxed { +public: + MTPcontest_SaveDeveloperInfo() { + } + MTPcontest_SaveDeveloperInfo(const MTPcontest_saveDeveloperInfo &v) : MTPBoxed(v) { + } + MTPcontest_SaveDeveloperInfo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPcontest_SaveDeveloperInfo(MTPint _vk_id, const MTPstring &_name, const MTPstring &_phone_number, MTPint _age, const MTPstring &_city) : MTPBoxed(MTPcontest_saveDeveloperInfo(_vk_id, _name, _phone_number, _age, _city)) { + } +}; + template class MTPinvokeAfterMsg { // RPC method 'invokeAfterMsg' public: @@ -16881,6 +16935,106 @@ public: } }; +class MTPaccount_sendConfirmPhoneCode { // RPC method 'account.sendConfirmPhoneCode' +public: + enum class Flag : int32 { + f_allow_flashcall = (1 << 0), + f_current_number = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_allow_flashcall() const { return vflags.v & Flag::f_allow_flashcall; } + bool has_current_number() const { return vflags.v & Flag::f_current_number; } + + MTPflags vflags; + MTPstring vhash; + MTPBool vcurrent_number; + + MTPaccount_sendConfirmPhoneCode() { + } + MTPaccount_sendConfirmPhoneCode(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_account_sendConfirmPhoneCode) { + read(from, end, cons); + } + MTPaccount_sendConfirmPhoneCode(const MTPflags &_flags, const MTPstring &_hash, MTPBool _current_number) : vflags(_flags), vhash(_hash), vcurrent_number(_current_number) { + } + + uint32 innerLength() const { + return vflags.innerLength() + vhash.innerLength() + (has_current_number() ? vcurrent_number.innerLength() : 0); + } + mtpTypeId type() const { + return mtpc_account_sendConfirmPhoneCode; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_account_sendConfirmPhoneCode) { + vflags.read(from, end); + vhash.read(from, end); + if (has_current_number()) { vcurrent_number.read(from, end); } else { vcurrent_number = MTPBool(); } + } + void write(mtpBuffer &to) const { + vflags.write(to); + vhash.write(to); + if (has_current_number()) vcurrent_number.write(to); + } + + typedef MTPauth_SentCode ResponseType; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPaccount_sendConfirmPhoneCode::Flags) + +class MTPaccount_SendConfirmPhoneCode : public MTPBoxed { +public: + MTPaccount_SendConfirmPhoneCode() { + } + MTPaccount_SendConfirmPhoneCode(const MTPaccount_sendConfirmPhoneCode &v) : MTPBoxed(v) { + } + MTPaccount_SendConfirmPhoneCode(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPaccount_SendConfirmPhoneCode(const MTPflags &_flags, const MTPstring &_hash, MTPBool _current_number) : MTPBoxed(MTPaccount_sendConfirmPhoneCode(_flags, _hash, _current_number)) { + } +}; + +class MTPaccount_confirmPhone { // RPC method 'account.confirmPhone' +public: + MTPstring vphone_code_hash; + MTPstring vphone_code; + + MTPaccount_confirmPhone() { + } + MTPaccount_confirmPhone(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_account_confirmPhone) { + read(from, end, cons); + } + MTPaccount_confirmPhone(const MTPstring &_phone_code_hash, const MTPstring &_phone_code) : vphone_code_hash(_phone_code_hash), vphone_code(_phone_code) { + } + + uint32 innerLength() const { + return vphone_code_hash.innerLength() + vphone_code.innerLength(); + } + mtpTypeId type() const { + return mtpc_account_confirmPhone; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_account_confirmPhone) { + vphone_code_hash.read(from, end); + vphone_code.read(from, end); + } + void write(mtpBuffer &to) const { + vphone_code_hash.write(to); + vphone_code.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPaccount_ConfirmPhone : public MTPBoxed { +public: + MTPaccount_ConfirmPhone() { + } + MTPaccount_ConfirmPhone(const MTPaccount_confirmPhone &v) : MTPBoxed(v) { + } + MTPaccount_ConfirmPhone(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPaccount_ConfirmPhone(const MTPstring &_phone_code_hash, const MTPstring &_phone_code) : MTPBoxed(MTPaccount_confirmPhone(_phone_code_hash, _phone_code)) { + } +}; + class MTPusers_getUsers { // RPC method 'users.getUsers' public: MTPVector vid; diff --git a/Telegram/SourceFiles/profile/profile_info_widget.cpp b/Telegram/SourceFiles/profile/profile_info_widget.cpp index 12d7e8ec9..bbb62d9e2 100644 --- a/Telegram/SourceFiles/profile/profile_info_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_info_widget.cpp @@ -140,7 +140,7 @@ void InfoWidget::refreshAbout() { }; _about.destroy(); - auto aboutText = getAboutText(); + auto aboutText = textClean(getAboutText()); if (!aboutText.isEmpty()) { _about = new FlatLabel(this, st::profileBlockTextPart); _about->show(); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 030bf7a67..f24dcbb36 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -237,7 +237,7 @@ void settingsParseArgs(int argc, char *argv[]) { gWorkingDir = dir; } } else if (string("--") == argv[i] && i + 1 < argc) { - gStartUrl = fromUtf8Safe(argv[++i]); + gStartUrl = fromUtf8Safe(argv[++i]).mid(0, 8192); } } } diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h index 4d125b4a6..8dffd1ed1 100644 --- a/Telegram/SourceFiles/settingswidget.h +++ b/Telegram/SourceFiles/settingswidget.h @@ -26,7 +26,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/observer.h" class MainWindow; -class Settings; class Slider : public QWidget { Q_OBJECT diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index ebacebdc8..94ce3882c 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -32,7 +32,7 @@ namespace { Qt::LayoutDirectionAuto, // dir }; TextParseOptions _labelMarkedOptions = { - TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands, // flags + TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h index 02df17957..d88980375 100644 --- a/Telegram/SourceFiles/ui/text/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -404,11 +404,4 @@ inline bool chIsParagraphSeparator(QChar ch) { return false; } -inline QString myUrlEncode(const QString &str) { - return QString::fromLatin1(QUrl::toPercentEncoding(str)); -} -inline QString myUrlDecode(const QString &enc) { - return QUrl::fromPercentEncoding(enc.toUtf8()); -} - void emojiDraw(QPainter &p, EmojiPtr e, int x, int y); diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index aa6701a43..6760a2698 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -199,6 +199,13 @@ public: } } + QPointer weakThis() { + return QPointer(this); + } + QPointer weakThis() const { + return QPointer(this); + } + virtual ~TWidget() { } diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index ff510d164..fe6751439 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -210,6 +210,10 @@ true true + + true + true + true true @@ -550,6 +554,10 @@ true true + + true + true + true true @@ -931,6 +939,10 @@ true true + + true + true + true true @@ -1257,6 +1269,7 @@ + @@ -1272,6 +1285,7 @@ + @@ -1551,9 +1565,25 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/report_box.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" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing confirmphonebox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/confirmphonebox.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -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" "-IC:\Program Files (x86)\Visual Leak Detector\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing confirmphonebox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/confirmphonebox.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -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_debug\Debug\include" "-IC:\Program Files (x86)\Visual Leak Detector\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing confirmphonebox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/confirmphonebox.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" "-IC:\Program Files (x86)\Visual Leak Detector\include" + + + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 3ef0c488f..ef9cabae0 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1449,6 +1449,21 @@ GeneratedFiles\styles + + SourceFiles\core + + + SourceFiles\boxes + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + @@ -1733,6 +1748,12 @@ GeneratedFiles\styles + + SourceFiles\core + + + SourceFiles\core + @@ -2041,6 +2062,9 @@ SourceFiles\media\view + + SourceFiles\boxes + @@ -2137,4 +2161,4 @@ Resources\winrc - + \ No newline at end of file From bcd3f6ef3dda349468309c49d795b68ad6967b99 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 29 Jul 2016 11:05:00 +0100 Subject: [PATCH 52/60] Fixed phone confirm: sending correct hash (from auth.sentCode). --- Telegram/SourceFiles/boxes/confirmphonebox.cpp | 13 +++++++------ Telegram/SourceFiles/boxes/confirmphonebox.h | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.cpp b/Telegram/SourceFiles/boxes/confirmphonebox.cpp index dbd749515..af9c48d85 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.cpp +++ b/Telegram/SourceFiles/boxes/confirmphonebox.cpp @@ -48,7 +48,7 @@ ConfirmPhoneBox::ConfirmPhoneBox(QWidget *parent, const QString &phone, const QS setParent(parent); MTPaccount_SendConfirmPhoneCode::Flags flags = 0; - _sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(flags), MTP_string(hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail)); + _sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(flags), MTP_string(_hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail)); } void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { @@ -61,6 +61,7 @@ void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { case mtpc_auth_sentCodeTypeCall: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeCall().vlength.v; break; case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; } + _phoneHash = qs(resultInner.vphone_code_hash); if (resultInner.has_next_type() && resultInner.vnext_type.type() == mtpc_auth_codeTypeCall) { setCallStatus({ CallState::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 }); } else { @@ -129,7 +130,7 @@ void ConfirmPhoneBox::onCallStatusTimer() { if (--_callStatus.timeout <= 0) { _callStatus.state = CallState::Calling; _callTimer.stop(); - MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_hash)), rpcDone(&ConfirmPhoneBox::callDone)); + MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone)); } } update(); @@ -157,7 +158,7 @@ void ConfirmPhoneBox::onSendCode() { showError(QString()); - _sendCodeRequestId = MTP::send(MTPaccount_ConfirmPhone(MTP_string(_hash), MTP_string(_code->getLastText())), rpcDone(&ConfirmPhoneBox::confirmDone), rpcFail(&ConfirmPhoneBox::confirmFail)); + _sendCodeRequestId = MTP::send(MTPaccount_ConfirmPhone(MTP_string(_phoneHash), MTP_string(_code->getLastText())), rpcDone(&ConfirmPhoneBox::confirmDone), rpcFail(&ConfirmPhoneBox::confirmFail)); } void ConfirmPhoneBox::confirmDone(const MTPBool &result) { @@ -229,9 +230,9 @@ void ConfirmPhoneBox::onCodeChanged() { _fixing = false; showError(QString()); - //if (strict) { - // onSendCode(); - //} + if (strict) { + onSendCode(); + } } void ConfirmPhoneBox::showError(const QString &error) { diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.h b/Telegram/SourceFiles/boxes/confirmphonebox.h index 46cbebc48..a19b8a371 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.h +++ b/Telegram/SourceFiles/boxes/confirmphonebox.h @@ -82,8 +82,10 @@ private: mtpRequestId _sendCodeRequestId = 0; - QString _phone; - QString _hash; + // _hash from the link for account.sendConfirmPhoneCode call. + // _phoneHash from auth.sentCode for account.confirmPhone call. + QString _phone, _hash; + QString _phoneHash; // If we receive the code length, we autosubmit _code field when enough symbols is typed. int _sentCodeLength = 0; From a4dbe6f096a8f14d66409b17348b5d2d7a0744cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 29 Jul 2016 11:29:15 +0100 Subject: [PATCH 53/60] ConfirmPhoneBox added to Xcode and QtCreator projects. "Frequently used" done with two lang keys (for emoji/stickers). "Featured Stickers" -> "Trending Stickers", langs updated. --- Telegram/Resources/langs/lang.strings | 3 ++- Telegram/Resources/langs/lang_it.strings | 10 ++++----- Telegram/Resources/langs/lang_nl.strings | 2 +- Telegram/Resources/langs/lang_pt_BR.strings | 2 +- Telegram/SourceFiles/dropdown.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 4 ++-- Telegram/SourceFiles/localstorage.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 4 ++-- Telegram/Telegram.pro | 5 +++++ Telegram/Telegram.xcodeproj/project.pbxproj | 22 +++++++++++++++++++ Telegram/Telegram.xcodeproj/qt_preprocess.mak | 6 +++++ 11 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3a45dfae8..cfacc7161 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Objects"; "lng_emoji_category7" = "Symbols & Flags"; +"lng_recent_stickers" = "Frequently used"; "lng_switch_stickers" = "Stickers"; "lng_switch_stickers_gifs" = "GIFs & Stickers"; "lng_switch_emoji" = "Emoji"; @@ -694,7 +695,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Manage and reorder sticker packs"; "lng_stickers_packs" = "Sticker Packs"; "lng_stickers_reorder" = "Click and drag to reorder sticker packs"; -"lng_stickers_featured" = "Featured Stickers"; +"lng_stickers_featured" = "Trending Stickers"; "lng_stickers_clear_recent" = "Clear"; "lng_stickers_clear_recent_sure" = "Are you sure you want to clear your frequently used stickers list?"; "lng_stickers_remove" = "Delete"; diff --git a/Telegram/Resources/langs/lang_it.strings b/Telegram/Resources/langs/lang_it.strings index 69f87f22f..4a35586e5 100644 --- a/Telegram/Resources/langs/lang_it.strings +++ b/Telegram/Resources/langs/lang_it.strings @@ -190,7 +190,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Ripristina il tuo account"; "lng_signin_sure_reset" = "Attenzione!\n\nPerderai tutte le chat e i messaggi, insieme a tutti i media e i file condivisi!\n\nVuoi ripristinare il tuo account?"; "lng_signin_reset" = "Ripristina"; -"lng_signin_reset_wait" = "Dato che l'account {phone_number} è attivo e protetto da una password, lo elimineremo in 1 settimana per motivi di sicurezza. Puoi annullare questo processo in qualsiasi momento.\n\nSarai in grado di ripristinare il tuo account tra:\n{when}"; +"lng_signin_reset_wait" = "Dato che l'account {phone_number} è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza. Puoi annullare questo processo in qualsiasi momento.\n\nSarai in grado di ripristinare il tuo account tra:\n{when}"; "lng_signin_reset_in_days" = "{count_days:0 giorni|# giorno|# giorni} {count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}"; "lng_signin_reset_in_hours" = "{count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}"; "lng_signin_reset_in_minutes" = "{count_minutes:0 minuti|# minuto|# minuti}"; @@ -590,7 +590,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_convert_about" = "Nei supergruppi:"; "lng_profile_convert_feature1" = "— I nuovi membri vedono tutta la cronologia"; "lng_profile_convert_feature2" = "— I messaggi eliminati scompaiono per tutti"; -"lng_profile_convert_feature3" = "— Gli amministratori possono fissare i messaggi"; +"lng_profile_convert_feature3" = "— Gli admin possono fissare i messaggi importanti"; "lng_profile_convert_feature4" = "— Il creatore può creare un link pubblico per il gruppo"; "lng_profile_convert_warning" = "{bold_start}Nota:{bold_end} Questa azione non può essere annullata"; "lng_profile_convert_confirm" = "Converti"; @@ -662,7 +662,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_auto_groups" = "Gruppi e canali"; "lng_media_auto_play" = "Autoriproduzione"; -"lng_emoji_category0" = "Utilizzate di frequente"; +"lng_emoji_category0" = "Usate di frequente"; "lng_emoji_category1" = "Persone"; "lng_emoji_category2" = "Natura"; "lng_emoji_category3" = "Cibo e bevande"; @@ -695,8 +695,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_packs" = "Set di sticker"; "lng_stickers_reorder" = "Clicca e trascina per riordinare i set di sticker"; "lng_stickers_featured" = "Sticker in primo piano"; -"lng_stickers_clear_recent" = "Elimina"; -"lng_stickers_clear_recent_sure" = "Sicuro di voler eliminare la lista degli sticker usati di recente?"; +"lng_stickers_clear_recent" = "Cancella"; +"lng_stickers_clear_recent_sure" = "Sicuro di voler cancellare la lista degli sticker usati di recente?"; "lng_stickers_remove" = "Elimina"; "lng_stickers_return" = "Annulla"; "lng_stickers_restore" = "Ripristina"; diff --git a/Telegram/Resources/langs/lang_nl.strings b/Telegram/Resources/langs/lang_nl.strings index d6f6bd61d..1624f97b1 100644 --- a/Telegram/Resources/langs/lang_nl.strings +++ b/Telegram/Resources/langs/lang_nl.strings @@ -190,7 +190,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_account" = "Account resetten"; "lng_signin_sure_reset" = "Let op:\n\nAl je chats, berichten en alle andere data gaan verloren als je verder gaat!\n\nEcht je account resetten?"; "lng_signin_reset" = "Reset"; -"lng_signin_reset_wait" = "Het account met nummer {phone_number} is nog actief en beveiligd met een wachtwoord, je verzoek tot verwijdering stellen we daarom uit met 1 week.\nJe kunt dit proces ieder moment annuleren.\n\nJe account kan reset worden op:\n{when}"; +"lng_signin_reset_wait" = "Account {phone_number} is actief en beveiligd met een wachtwoord, het verwijderen stellen we daarom 1 week uit.\nJe kunt dit proces ieder moment annuleren.\n\nJe account kan gereset worden over:\n{when}"; "lng_signin_reset_in_days" = "{count_days:0 dagen|# dag|# dagen} {count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}"; "lng_signin_reset_in_hours" = "{count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}"; "lng_signin_reset_in_minutes" = "{count_minutes:0 minuten|# minuut|# minuten}"; diff --git a/Telegram/Resources/langs/lang_pt_BR.strings b/Telegram/Resources/langs/lang_pt_BR.strings index 00e2dc316..6b84a078b 100644 --- a/Telegram/Resources/langs/lang_pt_BR.strings +++ b/Telegram/Resources/langs/lang_pt_BR.strings @@ -696,7 +696,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_reorder" = "Clique e arraste para reordenar os pacotes"; "lng_stickers_featured" = "Stickers em Destaque"; "lng_stickers_clear_recent" = "Limpar"; -"lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar sua lista de stickers usados frequentemente?"; +"lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar seu histórico de stickers frequentes?"; "lng_stickers_remove" = "Remover"; "lng_stickers_return" = "Desfazer"; "lng_stickers_restore" = "Restaurar"; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index a3327c608..72c1cf737 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -2150,7 +2150,7 @@ void StickerPanInner::refreshRecentStickers(bool performResize) { } } if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) { - _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_emoji_category0), recentPack.size() * 2, recentPack)); + _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack)); } else { _sets[0].pack = recentPack; _sets[0].hovers.resize(recentPack.size() * 2); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index f92cdcc51..b587be32c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3751,9 +3751,9 @@ void HistoryWidget::recentStickersGot(const MTPmessages_RecentStickers &stickers } } else { if (it == sets.cend()) { - it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_emoji_category0), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); + it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); } else { - it->title = lang(lng_emoji_category0); + it->title = lang(lng_recent_stickers); } it->hash = d.vhash.v; diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index dcd58c50a..f8d4a1786 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3196,7 +3196,7 @@ namespace Local { setTitle = lang(lng_custom_stickers); setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); } else if (setId == Stickers::CloudRecentSetId) { - setTitle = lang(lng_emoji_category0); // Frequently used + setTitle = lang(lng_recent_stickers); setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); } else if (setId) { if (readingInstalled && outOrder && stickers.version < 9061) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ed2fa90b0..673365a72 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3662,9 +3662,9 @@ void MainWidget::incrementSticker(DocumentData *sticker) { auto it = sets.find(Stickers::CloudRecentSetId); if (it == sets.cend()) { if (it == sets.cend()) { - it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_emoji_category0), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); + it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special))); } else { - it->title = lang(lng_emoji_category0); + it->title = lang(lng_recent_stickers); } } auto index = it->stickers.indexOf(sticker); diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index f348f3fc9..9f26dc072 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -147,6 +147,7 @@ SOURCES += \ ./SourceFiles/boxes/autolockbox.cpp \ ./SourceFiles/boxes/backgroundbox.cpp \ ./SourceFiles/boxes/confirmbox.cpp \ + ./SourceFiles/boxes/confirmphonebox.cpp \ ./SourceFiles/boxes/connectionbox.cpp \ ./SourceFiles/boxes/contactsbox.cpp \ ./SourceFiles/boxes/downloadpathbox.cpp \ @@ -163,6 +164,7 @@ SOURCES += \ ./SourceFiles/core/click_handler.cpp \ ./SourceFiles/core/click_handler_types.cpp \ ./SourceFiles/core/observer.cpp \ + ./SourceFiles/core/qthelp_url.cpp \ ./SourceFiles/data/data_abstract_structure.cpp \ ./SourceFiles/data/data_drafts.cpp \ ./SourceFiles/dialogs/dialogs_indexed_list.cpp \ @@ -318,6 +320,7 @@ HEADERS += \ ./SourceFiles/boxes/autolockbox.h \ ./SourceFiles/boxes/backgroundbox.h \ ./SourceFiles/boxes/confirmbox.h \ + ./SourceFiles/boxes/confirmphonebox.h \ ./SourceFiles/boxes/connectionbox.h \ ./SourceFiles/boxes/contactsbox.h \ ./SourceFiles/boxes/downloadpathbox.h \ @@ -334,6 +337,8 @@ HEADERS += \ ./SourceFiles/core/click_handler.h \ ./SourceFiles/core/click_handler_types.h \ ./SourceFiles/core/observer.h \ + ./SourceFiles/core/qthelp_regex.h \ + ./SourceFiles/core/qthelp_url.h \ ./SourceFiles/core/vector_of_moveable.h \ ./SourceFiles/core/version.h \ ./SourceFiles/data/data_abstract_structure.h \ diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 7524d6ad7..45327f417 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -142,6 +142,10 @@ 078A2FCA1A811C5900CCC7A0 /* moc_backgroundbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 078A2FC91A811C5900CCC7A0 /* moc_backgroundbox.cpp */; }; 078A2FCD1A811CA600CCC7A0 /* backgroundbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 078A2FCB1A811CA600CCC7A0 /* backgroundbox.cpp */; }; 079B51411D4935C0002FB6AF /* style_boxes.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 079B513F1D4935C0002FB6AF /* style_boxes.cpp */; }; + 079B51461D4B636C002FB6AF /* qthelp_url.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 079B51441D4B636C002FB6AF /* qthelp_url.cpp */; }; + 079B514A1D4B6398002FB6AF /* boxes.style in Resources */ = {isa = PBXBuildFile; fileRef = 079B51471D4B6398002FB6AF /* boxes.style */; }; + 079B514B1D4B6398002FB6AF /* confirmphonebox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 079B51481D4B6398002FB6AF /* confirmphonebox.cpp */; }; + 079B514D1D4B6423002FB6AF /* moc_confirmphonebox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 079B514C1D4B6423002FB6AF /* moc_confirmphonebox.cpp */; }; 07A69332199277BA0099CB9F /* mediaview.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07A69330199277BA0099CB9F /* mediaview.cpp */; }; 07A6933519927B160099CB9F /* moc_mediaview.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07A6933419927B160099CB9F /* moc_mediaview.cpp */; }; 07AF95F41AFD03B90060B057 /* qrc_telegram_emojis.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07AF95F21AFD03B90060B057 /* qrc_telegram_emojis.cpp */; }; @@ -585,6 +589,13 @@ 078DD0251A48DD9E00DD14CC /* lang_nl.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lang_nl.strings; path = Resources/langs/lang_nl.strings; sourceTree = SOURCE_ROOT; }; 079B513F1D4935C0002FB6AF /* style_boxes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = style_boxes.cpp; path = GeneratedFiles/styles/style_boxes.cpp; sourceTree = SOURCE_ROOT; }; 079B51401D4935C0002FB6AF /* style_boxes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = style_boxes.h; path = GeneratedFiles/styles/style_boxes.h; sourceTree = SOURCE_ROOT; }; + 079B51431D4B636C002FB6AF /* qthelp_regex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = qthelp_regex.h; path = SourceFiles/core/qthelp_regex.h; sourceTree = SOURCE_ROOT; }; + 079B51441D4B636C002FB6AF /* qthelp_url.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = qthelp_url.cpp; path = SourceFiles/core/qthelp_url.cpp; sourceTree = SOURCE_ROOT; }; + 079B51451D4B636C002FB6AF /* qthelp_url.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = qthelp_url.h; path = SourceFiles/core/qthelp_url.h; sourceTree = SOURCE_ROOT; }; + 079B51471D4B6398002FB6AF /* boxes.style */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = boxes.style; path = SourceFiles/boxes/boxes.style; sourceTree = SOURCE_ROOT; }; + 079B51481D4B6398002FB6AF /* confirmphonebox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = confirmphonebox.cpp; path = SourceFiles/boxes/confirmphonebox.cpp; sourceTree = SOURCE_ROOT; }; + 079B51491D4B6398002FB6AF /* confirmphonebox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = confirmphonebox.h; path = SourceFiles/boxes/confirmphonebox.h; sourceTree = SOURCE_ROOT; }; + 079B514C1D4B6423002FB6AF /* moc_confirmphonebox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_confirmphonebox.cpp; path = GeneratedFiles/Debug/moc_confirmphonebox.cpp; sourceTree = SOURCE_ROOT; }; 07A190511A723E0A004287AE /* lang_ko.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lang_ko.strings; path = Resources/langs/lang_ko.strings; sourceTree = SOURCE_ROOT; }; 07A69330199277BA0099CB9F /* mediaview.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mediaview.cpp; path = SourceFiles/mediaview.cpp; sourceTree = SOURCE_ROOT; }; 07A69331199277BA0099CB9F /* mediaview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mediaview.h; path = SourceFiles/mediaview.h; sourceTree = SOURCE_ROOT; }; @@ -1151,6 +1162,9 @@ 076B1C531CBFC6F2002C0BC2 /* click_handler.h */, 0716C98D1D05954900797B22 /* observer.cpp */, 0716C98E1D05954900797B22 /* observer.h */, + 079B51431D4B636C002FB6AF /* qthelp_regex.h */, + 079B51441D4B636C002FB6AF /* qthelp_url.cpp */, + 079B51451D4B636C002FB6AF /* qthelp_url.h */, 0716C98F1D05954900797B22 /* vector_of_moveable.h */, 07D518D41CD0E27600F5FF59 /* version.h */, ); @@ -1639,6 +1653,7 @@ 074756181A1372C600CA07F7 /* moc_basic_types.cpp */, 46292F489228B60010794CE4 /* moc_button.cpp */, CE7FFE194127BD789A2C877A /* moc_confirmbox.cpp */, + 079B514C1D4B6423002FB6AF /* moc_confirmphonebox.cpp */, B714EA71A09A832FAA846A0A /* moc_connection.cpp */, 077A4AFF1CA41EE2002188D2 /* moc_connection_abstract.cpp */, 077A4B001CA41EE2002188D2 /* moc_connection_auto.cpp */, @@ -1729,8 +1744,11 @@ 07DE92A41AA4925B00A18F6F /* autolockbox.h */, 078A2FCB1A811CA600CCC7A0 /* backgroundbox.cpp */, 078A2FCC1A811CA600CCC7A0 /* backgroundbox.h */, + 079B51471D4B6398002FB6AF /* boxes.style */, 6610564B876E47D289A596DB /* confirmbox.cpp */, 1DEFC0760BB9340529F582F7 /* confirmbox.h */, + 079B51481D4B6398002FB6AF /* confirmphonebox.cpp */, + 079B51491D4B6398002FB6AF /* confirmphonebox.h */, 51355181C0E6689B0B764543 /* connectionbox.cpp */, 8EB83A4D34226609E79A613A /* connectionbox.h */, 8C800AAC9549E6E9E7046BED /* contactsbox.cpp */, @@ -1952,6 +1970,7 @@ 07E102F81D3E4E8C00BD33B1 /* widgets.style in Resources */, 0716C95A1D0589B600797B22 /* overview.style in Resources */, 0716C9291D05893900797B22 /* basic_types.style in Resources */, + 079B514A1D4B6398002FB6AF /* boxes.style in Resources */, 07D7EABA1A597DD000838BA2 /* Localizable.strings in Resources */, 07E102EE1D3E4E3B00BD33B1 /* mediaview.style in Resources */, 0716C9A71D08258A00797B22 /* history.style in Resources */, @@ -2204,6 +2223,7 @@ 07E102D01D3E4B7F00BD33B1 /* media_clip_ffmpeg.cpp in Compile Sources */, 98E4F55DB5D8E64AB9F08C83 /* moc_localimageloader.cpp in Compile Sources */, A24E4B5B683764E07683ECEC /* moc_mainwidget.cpp in Compile Sources */, + 079B51461D4B636C002FB6AF /* qthelp_url.cpp in Compile Sources */, 0710CA051B0B9404001B4272 /* moc_stickersetbox.cpp in Compile Sources */, 0716C9AA1D0825A800797B22 /* history_down_button.cpp in Compile Sources */, 0716C94A1D0589A700797B22 /* profile_actions_widget.cpp in Compile Sources */, @@ -2250,6 +2270,7 @@ 07A69332199277BA0099CB9F /* mediaview.cpp in Compile Sources */, 9A523F51135FD4E2464673A6 /* moc_session.cpp in Compile Sources */, 076B1C631CBFCC53002C0BC2 /* moc_top_bar_widget.cpp in Compile Sources */, + 079B514B1D4B6398002FB6AF /* confirmphonebox.cpp in Compile Sources */, C329997D36D34D568CE16C9A /* moc_animation.cpp in Compile Sources */, 0716C9801D058F2400797B22 /* section_widget.cpp in Compile Sources */, B2F5B08BFFBBE7E37D3863BB /* moc_button.cpp in Compile Sources */, @@ -2296,6 +2317,7 @@ 074968D01A44D14C00394F46 /* languagebox.cpp in Compile Sources */, 077A4AF91CA41C38002188D2 /* connection_http.cpp in Compile Sources */, 07BE85121A20961F008ACB9F /* moc_localstorage.cpp in Compile Sources */, + 079B514D1D4B6423002FB6AF /* moc_confirmphonebox.cpp in Compile Sources */, 07E1B1961D12DFD200722BC7 /* main_window.cpp in Compile Sources */, 07AF95F41AFD03B90060B057 /* qrc_telegram_emojis.cpp in Compile Sources */, 07C759721B1F7E2800662169 /* moc_autoupdater.cpp in Compile Sources */, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index 7250e65e8..24baedfab 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -61,6 +61,7 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_basic_types.cpp\ GeneratedFiles/Debug/moc_button.cpp\ GeneratedFiles/Debug/moc_confirmbox.cpp\ + GeneratedFiles/Debug/moc_confirmphonebox.cpp\ GeneratedFiles/Debug/moc_connection.cpp\ GeneratedFiles/Debug/moc_connection_abstract.cpp\ GeneratedFiles/Debug/moc_connection_auto.cpp\ @@ -204,6 +205,7 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_basic_types.cpp\ GeneratedFiles/Debug/moc_button.cpp\ GeneratedFiles/Debug/moc_confirmbox.cpp\ + GeneratedFiles/Debug/moc_confirmphonebox.cpp\ GeneratedFiles/Debug/moc_connection.cpp\ GeneratedFiles/Debug/moc_connection_abstract.cpp\ GeneratedFiles/Debug/moc_connection_auto.cpp\ @@ -290,6 +292,7 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_basic_types.cpp\ GeneratedFiles/Debug/moc_button.cpp\ GeneratedFiles/Debug/moc_confirmbox.cpp\ + GeneratedFiles/Debug/moc_confirmphonebox.cpp\ GeneratedFiles/Debug/moc_connection.cpp\ GeneratedFiles/Debug/moc_connection_abstract.cpp\ GeneratedFiles/Debug/moc_connection_auto.cpp\ @@ -399,6 +402,9 @@ GeneratedFiles/Debug/moc_button.cpp: SourceFiles/ui/button.h GeneratedFiles/Debug/moc_confirmbox.cpp: SourceFiles/boxes/confirmbox.h $(MOC_FILE) SourceFiles/boxes/confirmbox.h -o GeneratedFiles/Debug/moc_confirmbox.cpp +GeneratedFiles/Debug/moc_confirmphonebox.cpp: SourceFiles/boxes/confirmphonebox.h + $(MOC_FILE) SourceFiles/boxes/confirmphonebox.h -o GeneratedFiles/Debug/moc_confirmphonebox.cpp + GeneratedFiles/Debug/moc_connection.cpp: SourceFiles/mtproto/connection.h $(MOC_FILE) SourceFiles/mtproto/connection.h -o GeneratedFiles/Debug/moc_connection.cpp From 0291888c01353d4a31561fca99de4778963adb8d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 29 Jul 2016 17:33:49 +0100 Subject: [PATCH 54/60] A couple of rare crashes fixed. --- Telegram/SourceFiles/mainwidget.cpp | 5 ++++- Telegram/SourceFiles/media/view/media_clip_controller.cpp | 2 +- Telegram/SourceFiles/media/view/media_clip_playback.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index cdf9f42a2..c99aec5e1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3742,7 +3742,10 @@ int32 MainWidget::dlgsWidth() const { MainWidget::~MainWidget() { if (App::main() == this) _history->showHistory(0, 0); - delete _hider; + if (HistoryHider *hider = _hider) { + _hider = nullptr; + delete hider; + } MTP::clearGlobalHandlers(); if (App::wnd()) App::wnd()->noMain(this); diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 904b5bae1..18c43dbae 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -59,8 +59,8 @@ void Controller::onSeekProgress(float64 progress) { auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; - emit seekProgress(positionMs); refreshTimeTexts(); + emit seekProgress(positionMs); // This may destroy Controller. } } diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 1fccb7278..2b1f82148 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -122,8 +122,8 @@ void Playback::mouseMoveEvent(QMouseEvent *e) { void Playback::mousePressEvent(QMouseEvent *e) { _mouseDown = true; _downProgress = snap(e->pos().x() / float64(width()), 0., 1.); - emit seekProgress(_downProgress); update(); + emit seekProgress(_downProgress); // This may destroy Playback. } void Playback::mouseReleaseEvent(QMouseEvent *e) { From d31701e906fbf1609fe9b2eb494c500423c93291 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 30 Jul 2016 11:03:44 +0100 Subject: [PATCH 55/60] Fixed client side waveform counter infinite loop. Backported Qt 5.6.1 crash fix in cocoa integration to Qt patch. --- Telegram/Patches/qtbase_5_6_0.diff | 28 +++++++++++++++++++ Telegram/SourceFiles/media/media_audio.cpp | 2 +- .../SourceFiles/media/media_clip_ffmpeg.cpp | 2 +- .../media/media_clip_implementation.h | 2 +- .../SourceFiles/media/media_clip_qtgif.cpp | 2 +- .../SourceFiles/media/media_clip_reader.cpp | 4 +-- 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Telegram/Patches/qtbase_5_6_0.diff b/Telegram/Patches/qtbase_5_6_0.diff index dd4c477bf..e1e270564 100644 --- a/Telegram/Patches/qtbase_5_6_0.diff +++ b/Telegram/Patches/qtbase_5_6_0.diff @@ -11300,6 +11300,34 @@ index ca92103..225d85f 100644 QPainter p(&m_qImage); p.setCompositionMode(QPainter::CompositionMode_Source); const QVector rects = region.rects(); +diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm +index 6bec6b1..f14d6ee 100644 +--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm ++++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm +@@ -422,14 +422,20 @@ void QCocoaIntegration::updateScreens() + } + siblings << screen; + } ++ ++ ++// Patch: backport crash fix from Qt 5.6.1 ++ // Set virtual siblings list. All screens in mScreens are siblings, because we ignored the ++ // mirrors. Note that some of the screens we update the siblings list for here may be deleted ++ // below, but update anyway to keep the to-be-deleted screens out of the siblings list. ++ foreach (QCocoaScreen* screen, mScreens) ++ screen->setVirtualSiblings(siblings); ++ + // Now the leftovers in remainingScreens are no longer current, so we can delete them. + foreach (QCocoaScreen* screen, remainingScreens) { + mScreens.removeOne(screen); + destroyScreen(screen); + } +- // All screens in mScreens are siblings, because we ignored the mirrors. +- foreach (QCocoaScreen* screen, mScreens) +- screen->setVirtualSiblings(siblings); + } + + QCocoaScreen *QCocoaIntegration::screenAtIndex(int index) diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index c2d206f..0f9b512 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 32f2f79b9..f16d9de2a 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -1984,7 +1984,7 @@ public: int64 samples = 0; auto res = readMore(buffer, samples); - if (res == ReadResult::Error) { + if (res == ReadResult::Error || res == ReadResult::EndOfFile) { break; } if (buffer.isEmpty()) { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index acf016310..5fd4f0bae 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -52,7 +52,7 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { if (res == AVERROR_EOF) { clearPacketQueue(); if (_mode == Mode::Normal) { - return ReadResult::Eof; + return ReadResult::EndOfFile; } if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h index 54bf98944..303c8ee2a 100644 --- a/Telegram/SourceFiles/media/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -41,7 +41,7 @@ public: enum class ReadResult { Success, Error, - Eof, + EndOfFile, }; // Read frames till current frame will have presentation time > frameMs, systemMs = getms(). virtual ReadResult readFramesTill(int64 frameMs, uint64 systemMs) = 0; diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp index 18f0462b5..b5209d0cc 100644 --- a/Telegram/SourceFiles/media/media_clip_qtgif.cpp +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -55,7 +55,7 @@ ReaderImplementation::ReadResult QtGifReaderImplementation::readNextFrame() { if (_reader) _frameDelay = _reader->nextImageDelay(); if (_framesLeft < 1) { if (_mode == Mode::Normal) { - return ReadResult::Eof; + return ReadResult::EndOfFile; } else if (!jumpToStart()) { return ReadResult::Error; } diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 043d149e3..f310dd1b9 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -346,7 +346,7 @@ public: } if (frame() && frame()->original.isNull()) { auto readResult = _implementation->readFramesTill(-1, ms); - if (readResult == internal::ReaderImplementation::ReadResult::Eof && _seekPositionMs > 0) { + if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile && _seekPositionMs > 0) { // If seek was done to the end: try to read the first frame, // get the frame size and return a black frame with that size. @@ -414,7 +414,7 @@ public: ProcessResult finishProcess(uint64 ms) { auto frameMs = _seekPositionMs + ms - _animationStarted; auto readResult = _implementation->readFramesTill(frameMs, ms); - if (readResult == internal::ReaderImplementation::ReadResult::Eof) { + if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) { stop(); _state = State::Finished; return ProcessResult::Finished; From c34181ef3e7a8234db90d1afc322a2c6f766c7f0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 31 Jul 2016 10:29:15 +0100 Subject: [PATCH 56/60] Edit messages to yourself for any amount of time. Active inline keyboard button rendering rounding radius fixed. --- Telegram/SourceFiles/app.cpp | 2 +- Telegram/SourceFiles/history.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 7b67ab797..f3d177592 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2109,7 +2109,7 @@ namespace { ::cornersMaskSmall[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); ::cornersMaskSmall[i]->setDevicePixelRatio(cRetinaFactor()); } - prepareCorners(WhiteCorners, st::buttonRadius, st::white); + prepareCorners(WhiteCorners, st::dateRadius, st::white); prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceSelectBg); prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index d94e11200..8bf762fea 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2833,7 +2833,9 @@ void HistoryItem::setId(MsgId newId) { } bool HistoryItem::canEdit(const QDateTime &cur) const { - if (id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false; + auto messageToMyself = (peerToUser(_history->peer->id) == MTP::authedId()); + auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit()); + if (id < 0 || messageTooOld) return false; if (auto msg = toHistoryMessage()) { if (msg->Has() || msg->Has()) return false; @@ -2854,7 +2856,7 @@ bool HistoryItem::canEdit(const QDateTime &cur) const { auto channel = _history->peer->asChannel(); return (channel->amCreator() || (channel->amEditor() && out())); } - return out() || (peerToUser(_history->peer->id) == MTP::authedId()); + return out() || messageToMyself; } return false; } From f7860fc8953c881e152484fd2f63b65998340973 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 1 Aug 2016 10:19:47 +0100 Subject: [PATCH 57/60] InnoSetup updated to version 5.9 and added pt_BR language. --- Telegram/build/setup.iss | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Telegram/build/setup.iss b/Telegram/build/setup.iss index ff2b39366..6f8bd3c33 100644 --- a/Telegram/build/setup.iss +++ b/Telegram/build/setup.iss @@ -28,9 +28,17 @@ SolidCompression=yes DisableStartupPrompt=yes PrivilegesRequired=lowest VersionInfoVersion={#MyAppVersion}.0 +CloseApplications=force +DisableDirPage=no +DisableProgramGroupPage=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "de"; MessagesFile: "compiler:Languages\German.isl" +Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "pt_BR"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" @@ -70,13 +78,6 @@ Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tcache" Type: filesandordirs; Name: "{userappdata}\{#MyAppName}\tdumps" Type: dirifempty; Name: "{userappdata}\{#MyAppName}" -[Languages] -Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" -Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl" -Name: "de"; MessagesFile: "compiler:Languages\German.isl" -Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl" -Name: "pt"; MessagesFile: "compiler:Languages\Portuguese.isl" - [Code] procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var ResultCode: Integer; From 12bcc88839aac4c6d2b49c98ea930271951c27bc Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Aug 2016 12:02:53 +0100 Subject: [PATCH 58/60] Stable version 0.10: langs updated, video player design improvements. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/Resources/langs/lang_de.strings | 12 +++++++++-- Telegram/Resources/langs/lang_es.strings | 10 ++++++++- Telegram/Resources/langs/lang_it.strings | 10 ++++++++- Telegram/Resources/langs/lang_ko.strings | 12 +++++++++-- Telegram/Resources/langs/lang_nl.strings | 12 +++++++++-- Telegram/Resources/langs/lang_pt_BR.strings | 18 +++++++++++----- Telegram/Resources/winrc/Telegram.rc | 8 +++---- Telegram/Resources/winrc/Updater.rc | 8 +++---- Telegram/SourceFiles/app.cpp | 3 ++- Telegram/SourceFiles/application.cpp | 2 +- Telegram/SourceFiles/core/version.h | 6 +++--- .../media/view/media_clip_controller.cpp | 14 ++++++------- .../media/view/media_clip_playback.cpp | 21 +++++++++++++------ .../view/media_clip_volume_controller.cpp | 2 +- .../SourceFiles/media/view/mediaview.style | 12 +++++++---- Telegram/Telegram.xcodeproj/project.pbxproj | 8 +++---- Telegram/build/version | 10 ++++----- 18 files changed, 116 insertions(+), 54 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cfacc7161..f356cc219 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -934,7 +934,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_minor" = "— Bug fixes and other minor improvements"; -"lng_new_version_text" = "— Fixed photo viewer to handle screen resolution change correctly\n— Fixed forwarding photos via drag-n-drop\n— Various design improvements and other bug fixes"; +"lng_new_version_text" = "— Trending stickers. Check out and install noteworthy sticker packs from the new tab in Settings.\n— Archived stickers. Unused stickers are now archived automatically when you go over the 200 limit.\n— Group previews. Preview groups before joining them via invite link – see who else is in the group before joining.\n— New internal video player.\n— Improved design for chats."; "lng_menu_insert_unicode" = "Insert Unicode control character"; diff --git a/Telegram/Resources/langs/lang_de.strings b/Telegram/Resources/langs/lang_de.strings index 78d3feff3..052545c7e 100644 --- a/Telegram/Resources/langs/lang_de.strings +++ b/Telegram/Resources/langs/lang_de.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Objekte"; "lng_emoji_category7" = "Symbole & Flaggen"; +"lng_recent_stickers" = "Häufig genutzt"; "lng_switch_stickers" = "Sticker"; "lng_switch_stickers_gifs" = "GIFs & Sticker"; "lng_switch_emoji" = "Emoji"; @@ -694,7 +695,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Sticker-Pakete verwalten"; "lng_stickers_packs" = "Sticker-Pakete"; "lng_stickers_reorder" = "Paket gedrückt halten und verschieben um die Anordnung zu ändern"; -"lng_stickers_featured" = "Besondere Sticker"; +"lng_stickers_featured" = "Angesagte Sticker"; "lng_stickers_clear_recent" = "Leeren"; "lng_stickers_clear_recent_sure" = "Zuletzt benutzte Sticker leeren?"; "lng_stickers_remove" = "Löschen"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}"; "lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen"; -"lng_new_version_text" = "— Fehlerbehebung: Bildbetrachter sollte Änderungen der Bildschirmauflösung korrekt handhaben\n— Fehlerbehebung: Bilder per Drag’n’Drop weiterleiten\n— Verschiedene optische Verbesserungen und sonstige Fehlerbehebungen"; +"lng_new_version_text" = "— Angesagte Sticker: In den Einstellungen, im Bereich Sticker, stehen interessante Stickerpakete bereit.\n— Archivierte Sticker: Unbenutze Sticker werden automatisch archiviert, sobald das Limit (200) erreicht wird.\n— Gruppenvorschau: Vor dem Betreten der Gruppe wird dir angezeigt, wer bereits Mitglied ist.\n— Neuer interner Videoplayer.\n— Verbessertes Chatdesign."; "lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen"; "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "Dieser Link ist abgelaufen oder ungültig."; +"lng_confirm_phone_title" = "Zurücksetzung abbrechen"; +"lng_confirm_phone_about" = "Jemand mit Zugang zu deiner Telefonnummer {phone} hat die Kontolöschung und Zurücksetzung der zweistufige Bestätigung beantragt.\n\nWenn du das nicht selbst gewesen bist, tippe den Code der SMS ein, den wir dir gerade gesendet haben."; +"lng_confirm_phone_success" = "Geschafft!\n\nDer Löschvorgang für dein Konto {phone} wurde abgebrochen. Du kannst dieses Fenster jetzt schließen."; +"lng_confirm_phone_send" = "Senden"; +"lng_confirm_phone_enter_code" = "Bitte den Code eingeben."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/Resources/langs/lang_es.strings b/Telegram/Resources/langs/lang_es.strings index 1651a5c81..005bc2fa4 100644 --- a/Telegram/Resources/langs/lang_es.strings +++ b/Telegram/Resources/langs/lang_es.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Objetos"; "lng_emoji_category7" = "Símbolos y banderas"; +"lng_recent_stickers" = "Uso frecuente"; "lng_switch_stickers" = "Stickers"; "lng_switch_stickers_gifs" = "GIF y stickers"; "lng_switch_emoji" = "Emoji"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop ha sido actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}"; "lng_new_version_minor" = "— Corrección de errores y otras mejoras menores"; -"lng_new_version_text" = "— Corrección en el visor de fotos, para manejar correctamente el cambio de la resolución de la pantalla\n— Corrección en el reenvío de fotos a través de arrastrar y soltar \n— Varias mejoras de diseño y corrección de otros errores"; +"lng_new_version_text" = "— Stickers destacados. Mira e instala packs de stickers desde la nueva pestaña en Ajustes.\n— Stickers archivados. Ahora, los packs de stickers que no usas, se archivan automáticamente cuando superas el límite de 200.\n— Vista previa de grupos. Mira un grupo antes de unirte a él a través del enlace de invitación. Mira quién está en el grupo antes de unirte.\n— Nuevo reproductor interno de vídeo.\n— Diseño mejorado de chats."; "lng_menu_insert_unicode" = "Insertar caracteres de control Unicode"; "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "El enlace está roto o ha expirado."; +"lng_confirm_phone_title" = "No restablecer la cuenta"; +"lng_confirm_phone_about" = "Alguien, con acceso a tu número de teléfono {phone}, solicitó eliminar tu cuenta de Telegram y restablecer tu contraseña de la verificación en dos pasos.\n\nSi no eras tú, por favor, inserta el código que enviamos por SMS a tu número."; +"lng_confirm_phone_success" = "¡Listo!\n\nEl proceso de eliminación de la cuenta {phone} fue cancelado. Puedes cerrar esta ventana ahora."; +"lng_confirm_phone_send" = "Enviar"; +"lng_confirm_phone_enter_code" = "Por favor, pon el código."; + // Not used "lng_topbar_info" = "Información"; diff --git a/Telegram/Resources/langs/lang_it.strings b/Telegram/Resources/langs/lang_it.strings index 4a35586e5..b90c6fee3 100644 --- a/Telegram/Resources/langs/lang_it.strings +++ b/Telegram/Resources/langs/lang_it.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Oggetti"; "lng_emoji_category7" = "Simboli e bandiere"; +"lng_recent_stickers" = "Usati di frequente"; "lng_switch_stickers" = "Sticker"; "lng_switch_stickers_gifs" = "GIF e Sticker"; "lng_switch_emoji" = "Emoji"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli aggiornamenti è disponibile qui:\n{link}"; "lng_new_version_minor" = "— Risoluzione di problemi e altri miglioramenti minori"; -"lng_new_version_text" = "— Il visualizzatore delle foto ora gestisce i cambi di risoluzione dello schermo correttamente\n— Risolto l'inoltro di foto via drag-n-drop\n— Miglioramenti di design e risoluzione di problemi"; +"lng_new_version_text" = "— Sticker in primo piano. Visualizza e installa set di sticker degni di nota dalla nuova scheda nelle Impostazioni.\n— Sticker archiviati. Gli sticker non utilizzati sono ora archiviati automaticamente quando vai oltre il limite dei 200 set.\n— Anteprime dei gruppi. Visualizza l'anteprima dei gruppi prima di unirti tramite link di invito - guarda chi è nel gruppo prima di unirti.\n— Nuovo lettore video interno.\n— Design migliorato per le chat."; "lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode"; "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "Questo link non funziona o è scaduto."; +"lng_confirm_phone_title" = "Annulla ripristino account"; +"lng_confirm_phone_about" = "Qualcuno con accesso al tuo numero di telefono {phone} ha richiesto l'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi.\n\nSe non sei stato tu, per favore inserisci il codice che abbiamo appena inviato come SMS al tuo numero."; +"lng_confirm_phone_success" = "Fatto!\n\nIl processo di eliminazione per il tuo account {phone} è stato annullato. Puoi chiudere questa finestra ora."; +"lng_confirm_phone_send" = "Invia"; +"lng_confirm_phone_enter_code" = "Per favore inserisci il codice."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/Resources/langs/lang_ko.strings b/Telegram/Resources/langs/lang_ko.strings index 844f9b32f..9d341f089 100644 --- a/Telegram/Resources/langs/lang_ko.strings +++ b/Telegram/Resources/langs/lang_ko.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "물건"; "lng_emoji_category7" = "심볼 및 국기"; +"lng_recent_stickers" = "자주 사용"; "lng_switch_stickers" = "스티커"; "lng_switch_stickers_gifs" = "GIF & 스티커"; "lng_switch_emoji" = "이모티콘"; @@ -694,7 +695,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "스티커팩 관리 및 변경"; "lng_stickers_packs" = "스티커팩"; "lng_stickers_reorder" = "클릭과 드래그를 통하여 스태커 팩을 변경하세요"; -"lng_stickers_featured" = "사용중인 스티커"; +"lng_stickers_featured" = "인기 스티커"; "lng_stickers_clear_recent" = "초기화"; "lng_stickers_clear_recent_sure" = "자주 사용하는 스티커 리스트를 초기화 하겠습니까?"; "lng_stickers_remove" = "삭제"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}"; "lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상"; -"lng_new_version_text" = "— 해상도 변경에 따라 사진 뷰어가 정상적으로 표시되도록 수정\n— 드래그앤드롭으로 사진 전송 수정\n— 디자인 및 버그 수정"; +"lng_new_version_text" = "— 인기스티커. 설정내 새로운 탭으로 인기있는 스티커를 확인하고 설치\n— 보관스티커. 스티커 개수가 200개 이상일 경우 미사용 스티커는 보관으로 자동이관\n— 그룹방 미리보기. 초대링크로 입장하기전에 미리보기 - 그룹방 참여인원 확인가능\n— 새로운 내부 비디오 플레이어\n— 대화 디자인 향상"; "lng_menu_insert_unicode" = "유니코드 문자를 입력하세요."; "lng_full_name" = "{last_name} {first_name}"; +"lng_confirm_phone_link_invalid" = "링크가 깨졌거나 폐기되었습니다."; +"lng_confirm_phone_title" = "계정 초기화 취소"; +"lng_confirm_phone_about" = "{phone} 번호에 접근이 가능한 누군가가 해당 계정 및 2단계 비밀번호 초기화를 요청하였습니다.\n\n만약 본인이 아니실 경우 방금 보내드린 코드를 본인 번호에 SMS로 전송해주세요."; +"lng_confirm_phone_success" = "성공!\n\n{phone} 계정에 대한 삭제 프로세스가 취소 되었습니다. 이창을 당으셔도 됩니다."; +"lng_confirm_phone_send" = "보내기"; +"lng_confirm_phone_enter_code" = "코드를 입력해주세요"; + // Not used "lng_topbar_info" = "정보"; diff --git a/Telegram/Resources/langs/lang_nl.strings b/Telegram/Resources/langs/lang_nl.strings index 1624f97b1..1f0de9a1b 100644 --- a/Telegram/Resources/langs/lang_nl.strings +++ b/Telegram/Resources/langs/lang_nl.strings @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Objecten"; "lng_emoji_category7" = "Symbolen en vlaggen"; +"lng_recent_stickers" = "Veelgebruikt"; "lng_switch_stickers" = "Stickers"; "lng_switch_stickers_gifs" = "GIF's & stickers"; "lng_switch_emoji" = "Emoji"; @@ -694,7 +695,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Beheer en sorteer stickerbundels"; "lng_stickers_packs" = "Stickerbundels"; "lng_stickers_reorder" = "Klik en sleep om stickerbundels te herschikken"; -"lng_stickers_featured" = "Uitgelichte stickers"; +"lng_stickers_featured" = "Populaire stickers"; "lng_stickers_clear_recent" = "Wissen"; "lng_stickers_clear_recent_sure" = "Lijst met veelgebruikte stickers echt wissen?"; "lng_stickers_remove" = "Verwijder"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}"; "lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen"; -"lng_new_version_text" = "— Probleem met foto-weergave opgelost\n— Probleem met doorsturen via slepen opgelost\n— Diverse designverbeteringen en andere opgeloste problemen"; +"lng_new_version_text" = "— Populaire stickers. Zoek je nieuwe stickerbundels om te installeren? Populaire stickerbundels zijn nu te vinden onder instellingen.\n— Gearchiveerde stickers. Ongebruikte stickers archiveren we automatisch als je over de limiet van 200 stickerbundels gaat.\n— Voorvertoningen van groepen. Bekijk een groep voordat je besluit lid te worden.\n— Nieuwe ingebouwde videospeler.\n— Designverbeteringen voor de chatinterface."; "lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen"; "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "Deze link is defect of verlopen."; +"lng_confirm_phone_title" = "Account reset annuleren"; +"lng_confirm_phone_about" = "Iemand met toegang tot nummer {phone} heeft geprobeerd om je Telegram account te verwijderen en je twee-staps-verificatie wachtwoord te resetten.\n\nAls jij dit niet was, geef dan de code in die we zojuist per SMS hebben gestuurd."; +"lng_confirm_phone_success" = "De verwijdering van account: {phone} is geannuleerd.\nJe kunt dit venster nu sluiten."; +"lng_confirm_phone_send" = "Stuur"; +"lng_confirm_phone_enter_code" = "Geef de code in."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/Resources/langs/lang_pt_BR.strings b/Telegram/Resources/langs/lang_pt_BR.strings index 6b84a078b..0be537892 100644 --- a/Telegram/Resources/langs/lang_pt_BR.strings +++ b/Telegram/Resources/langs/lang_pt_BR.strings @@ -194,7 +194,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_days" = "{count_days:0 dia|# dia|# dias} {count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}"; "lng_signin_reset_in_hours" = "{count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}"; "lng_signin_reset_in_minutes" = "{count_minutes:0 minuto|# minuto|# minutos}"; -"lng_signin_reset_cancelled" = "Sua tentativa recente de restaurar essa conta foi cancelada pelo usuário ativo. Tente novamente em 7 dias."; +"lng_signin_reset_cancelled" = "Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias."; "lng_signup_title" = "Informação e foto"; "lng_signup_desc" = "Por favor, insira nome e\ncarregue uma foto."; @@ -219,7 +219,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_uploading_photo" = "Carregando foto..."; "lng_username_title" = "Nome de usuário"; -"lng_username_about" = "Você pode escolher um nome de usuário no Telegram.\nAssim, outras pessoas poderão te encontrar\npelo nome de usuário e entrar em contato\nsem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é 5 caracteres."; +"lng_username_about" = "Você pode escolher um nome de usuário.\nAssim, outras pessoas poderão encontrar\nvocê pelo nome de usuário e entrar em\ncontato sem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é de 5 caracteres."; "lng_username_choose" = "Escolha seu nome de usuário"; "lng_username_invalid" = "Nome de usuário inválido."; "lng_username_occupied" = "Nome de usuário ocupado."; @@ -662,7 +662,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_auto_groups" = "Grupos e canais"; "lng_media_auto_play" = "Reproduzir automaticamente"; -"lng_emoji_category0" = "Frequentemente usado"; +"lng_emoji_category0" = "Usados frequentemente"; "lng_emoji_category1" = "Pessoas"; "lng_emoji_category2" = "Natureza"; "lng_emoji_category3" = "Comidas e Bebidas"; @@ -671,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_emoji_category6" = "Objetos"; "lng_emoji_category7" = "Símbolos e Bandeiras"; +"lng_recent_stickers" = "Usados frequentemente"; "lng_switch_stickers" = "Stickers"; "lng_switch_stickers_gifs" = "GIFs e Stickers"; "lng_switch_emoji" = "Emoji"; @@ -694,7 +695,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Gerenciar e reordenar os pacotes de sticker"; "lng_stickers_packs" = "Pacotes de Sticker"; "lng_stickers_reorder" = "Clique e arraste para reordenar os pacotes"; -"lng_stickers_featured" = "Stickers em Destaque"; +"lng_stickers_featured" = "Stickers Populares"; "lng_stickers_clear_recent" = "Limpar"; "lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar seu histórico de stickers frequentes?"; "lng_stickers_remove" = "Remover"; @@ -933,12 +934,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}"; "lng_new_version_minor" = "— Resolução de bugs e outras melhorias menores"; -"lng_new_version_text" = "— O visualizador de fotos agora maneja corretamente a mudança de resolução da tela\n— O bug ao encaminhar fotos arrastando e soltando foi resolvido. \n— Várias melhorias de design e outras resoluções de bugs"; +"lng_new_version_text" = "— Stickers populares. Veja e instale pacotes de stickers em destaque através da nova aba em Configurações.\n— Stickers arquivados. Stickers sem uso agora são arquivados automaticamente quando você chegar ao limite dos 200.\n— Pré-visualização de grupos. Visualize os grupos antes de entrar através do link de convite - veja quem mais está no grupo antes de entrar.\n— Novo reprodutor de vídeo interno.\n— Design melhorado nas conversas."; "lng_menu_insert_unicode" = "Inserir caractere de controle Unicode"; "lng_full_name" = "{first_name} {last_name}"; +"lng_confirm_phone_link_invalid" = "Esse link está quebrado ou expirado."; +"lng_confirm_phone_title" = "Cancelar exclusão da conta"; +"lng_confirm_phone_about" = "Alguém com acesso ao seu número de telefone {phone} solicitou a exclusão de sua conta do Telegram e redefiniu sua senha de Verificação em Duas Etapas.\n\nSe não foi você, por favor insira o código que te enviamos via SMS."; +"lng_confirm_phone_success" = "Sucesso!\n\nO processo de exclusão foi cancelado em sua conta {phone}. Você pode fechar essa janela agora."; +"lng_confirm_phone_send" = "Enviar"; +"lng_confirm_phone_enter_code" = "Por favor, insira o código."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 3f6ffbd82..1f9b3443e 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,61,1 - PRODUCTVERSION 0,9,61,1 + FILEVERSION 0,10,0,0 + PRODUCTVERSION 0,10,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.61.1" + VALUE "FileVersion", "0.10.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.61.1" + VALUE "ProductVersion", "0.10.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index e182e0268..5b7b96389 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,61,1 - PRODUCTVERSION 0,9,61,1 + FILEVERSION 0,10,0,0 + PRODUCTVERSION 0,10,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.61.1" + VALUE "FileVersion", "0.10.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.61.1" + VALUE "ProductVersion", "0.10.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index ad53b349b..54cf58492 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #endif #include "styles/style_overview.h" +#include "styles/style_mediaview.h" #include "lang.h" #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" @@ -2169,7 +2170,7 @@ namespace { prepareCorners(InShadowCorners, st::msgRadius, st::msgInShadow); prepareCorners(InSelectedShadowCorners, st::msgRadius, st::msgInShadowSelected); prepareCorners(ForwardCorners, st::msgRadius, st::forwardBg); - prepareCorners(MediaviewSaveCorners, st::msgRadius, st::medviewSaveMsg); + prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::medviewSaveMsg); prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover); prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover); prepareCorners(BotKeyboardCorners, st::buttonRadius, st::botKbBg); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index d270991e9..c2eeae415 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1053,7 +1053,7 @@ void AppClass::checkMapVersion() { QString versionFeatures; if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9058) { versionFeatures = QString::fromUtf8("\xe2\x80\x94 Alpha version of an embedded video player"); - } else if (Local::oldMapVersion() < 9056) { + } else if (Local::oldMapVersion() < 10000) { versionFeatures = langNewVersionText(); } else { versionFeatures = lang(lng_new_version_minor).trimmed(); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 183631fb6..d0cb462d6 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,9 +22,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -#define BETA_VERSION_MACRO (9061001ULL) +#define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 9061; -constexpr str_const AppVersionStr = "0.9.61"; +constexpr int AppVersion = 10000; +constexpr str_const AppVersionStr = "0.10"; constexpr bool AppAlphaVersion = false; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 18c43dbae..8b5708e88 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -183,17 +183,17 @@ void Controller::grabFinish() { void Controller::resizeEvent(QResizeEvent *e) { int playTop = (height() - _playPauseResume->height()) / 2; - _playPauseResume->moveToLeft(playTop, playTop); + _playPauseResume->moveToLeft(st::mediaviewPlayPauseLeft, playTop); int fullScreenTop = (height() - _fullScreenToggle->height()) / 2; - _fullScreenToggle->moveToRight(fullScreenTop, fullScreenTop); + _fullScreenToggle->moveToRight(st::mediaviewPlayPauseLeft, fullScreenTop); - _volumeController->moveToRight(fullScreenTop + _fullScreenToggle->width() + fullScreenTop, (height() - _volumeController->height()) / 2); - _playback->resize(width() - playTop - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - fullScreenTop - _fullScreenToggle->width() - fullScreenTop, _volumeController->height()); - _playback->moveToLeft(playTop + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); + _volumeController->moveToRight(st::mediaviewPlayPauseLeft + _fullScreenToggle->width() + st::mediaviewVolumeLeft, (height() - _volumeController->height()) / 2); + _playback->resize(width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewPlayPauseLeft, st::mediaviewSeekSize.height()); + _playback->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); - _playedAlready->moveToLeft(playTop + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); - _toPlayLeft->moveToRight(width() - (playTop + _playPauseResume->width() + playTop) - _playback->width(), st::mediaviewPlayProgressTop); + _playedAlready->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); + _toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - _playback->width(), st::mediaviewPlayProgressTop); } void Controller::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 2b1f82148..6dfbaed15 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -61,6 +61,7 @@ void Playback::updateState(const AudioPlaybackState &playbackState, bool reset) _position = position; _duration = duration; } + update(); } void Playback::setFadeOpacity(float64 opacity) { @@ -87,7 +88,9 @@ void Playback::paintEvent(QPaintEvent *e) { p.setPen(Qt::NoPen); p.setRenderHint(QPainter::HighQualityAntialiasing); - auto over = _a_over.current(getms(), _over ? 1. : 0.); + auto ms = getms(); + _a_progress.step(ms); + auto over = _a_over.current(ms, _over ? 1. : 0.); int skip = (st::mediaviewSeekSize.width() / 2); int length = (width() - st::mediaviewSeekSize.width()); float64 prg = _mouseDown ? _downProgress : a_progress.current(); @@ -104,11 +107,17 @@ void Playback::paintEvent(QPaintEvent *e) { p.setBrush(st::mediaviewPlaybackInactive); p.drawRoundedRect(mid - radius, (height() - st::mediaviewPlaybackWidth) / 2, width() - (mid - radius), st::mediaviewPlaybackWidth, radius, radius); } - int x = mid - skip; - p.setClipRect(rect()); - p.setOpacity(_fadeOpacity * (over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity)); - p.setBrush(st::mediaviewPlaybackActive); - p.drawRoundedRect(x, (height() - st::mediaviewSeekSize.height()) / 2, st::mediaviewSeekSize.width(), st::mediaviewSeekSize.height(), st::mediaviewSeekSize.width() / 2, st::mediaviewSeekSize.width() / 2); + if (over > 0) { + int x = mid - skip; + p.setClipRect(rect()); + p.setOpacity(_fadeOpacity * st::mediaviewActiveOpacity); + auto seekButton = QRect(x, (height() - st::mediaviewSeekSize.height()) / 2, st::mediaviewSeekSize.width(), st::mediaviewSeekSize.height()); + int remove = ((1. - over) * st::mediaviewSeekSize.width()) / 2.; + if (remove * 2 < st::mediaviewSeekSize.width()) { + p.setBrush(st::mediaviewPlaybackActive); + p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove))); + } + } } void Playback::mouseMoveEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp index 8a98dec90..8ffed03f2 100644 --- a/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_volume_controller.cpp @@ -40,7 +40,7 @@ void VolumeController::setVolume(float64 volume) { void VolumeController::paintEvent(QPaintEvent *e) { Painter p(this); - int32 top = (height() - st::mediaviewVolumeIcon.height()) / 2; + int32 top = st::mediaviewVolumeIconTop; int32 left = (width() - st::mediaviewVolumeIcon.width()) / 2; int32 mid = left + qRound(st::mediaviewVolumeIcon.width() * _volume); int32 right = left + st::mediaviewVolumeIcon.width(); diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index 98e48c6e1..4f8938ac7 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -31,7 +31,7 @@ mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) { font: semiboldFont; textFg: #ffffffc7; } -mediaviewPlayProgressTop: 8px; +mediaviewPlayProgressTop: 11px; mediaviewPlayButton: IconButton { width: 25px; height: 24px; @@ -50,6 +50,8 @@ mediaviewPlayButton: IconButton { mediaviewPauseIcon: icon { { "media_pause", #ffffff, point(1px, 1px) } }; +mediaviewPlayPauseLeft: 17px; +mediaviewVolumeLeft: 15px; mediaviewFullScreenButton: IconButton(mediaviewPlayButton) { icon: icon { @@ -65,13 +67,15 @@ mediaviewFullScreenOutIcon: icon { mediaviewPlaybackActive: #ffffff; mediaviewPlaybackInactive: #474747; mediaviewPlaybackWidth: 3px; -mediaviewPlaybackTop: 23px; -mediaviewSeekSize: size(2px, 13px); +mediaviewPlaybackTop: 28px; +mediaviewSeekSize: size(11px, 11px); -mediaviewVolumeSize: size(44px, 12px); +mediaviewVolumeSize: size(44px, 18px); mediaviewVolumeIcon: icon { { "media_volume", mediaviewPlaybackInactive, point(0px, 0px) }, }; mediaviewVolumeOnIcon: icon { { "media_volume", mediaviewPlaybackActive, point(0px, 0px) }, }; +mediaviewVolumeIconTop: 6px; +mediaviewControllerRadius: 25px; diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 45327f417..4538d8758 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -2558,8 +2558,8 @@ QT_PATH = "/usr/local/tdesktop/Qt-5.6.0"; SDKROOT = macosx; SYMROOT = ./../Mac; - TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.61; + TDESKTOP_MAJOR_VERSION = 0.10; + TDESKTOP_VERSION = 0.10; }; name = Release; }; @@ -2699,8 +2699,8 @@ QT_PATH = "/usr/local/tdesktop/Qt-5.6.0"; SDKROOT = macosx; SYMROOT = ./../Mac; - TDESKTOP_MAJOR_VERSION = 0.9; - TDESKTOP_VERSION = 0.9.61; + TDESKTOP_MAJOR_VERSION = 0.10; + TDESKTOP_VERSION = 0.10; }; name = Debug; }; diff --git a/Telegram/build/version b/Telegram/build/version index c8d09bb2c..4dc03752b 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 9061 -AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.61 -AppVersionStr 0.9.61 +AppVersion 10000 +AppVersionStrMajor 0.10 +AppVersionStrSmall 0.10 +AppVersionStr 0.10.0 AlphaChannel 0 -BetaVersion 9061001 +BetaVersion 0 From 4dded3d09c6ac845bbdae4a4b06c91fbbd53c004 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Aug 2016 13:32:44 +0100 Subject: [PATCH 59/60] Stable version 0.10: video player graphics updated. --- .../Resources/icons/media_fullscreen_from.png | Bin 405 -> 223 bytes .../icons/media_fullscreen_from@2x.png | Bin 1847 -> 399 bytes .../Resources/icons/media_fullscreen_to.png | Bin 388 -> 225 bytes .../icons/media_fullscreen_to@2x.png | Bin 1749 -> 396 bytes Telegram/Resources/icons/media_pause.png | Bin 98 -> 14780 bytes Telegram/Resources/icons/media_pause@2x.png | Bin 133 -> 14834 bytes Telegram/Resources/icons/media_play.png | Bin 281 -> 337 bytes Telegram/Resources/icons/media_play@2x.png | Bin 375 -> 381 bytes .../media/view/media_clip_controller.cpp | 6 +++--- .../SourceFiles/media/view/mediaview.style | 11 ++++++----- 10 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Telegram/Resources/icons/media_fullscreen_from.png b/Telegram/Resources/icons/media_fullscreen_from.png index 6d241d992ee22bf7b8ed463ec966277bc2c0aa73..7b3ae509d1722d1bca92ceaed9d96a5955e31105 100644 GIT binary patch literal 223 zcmV<503iQ~P)s@CDa{B~Q$&8(*LCpeAI;OiEp@ELp^atfckeP@Ztc~_+NeDG>- ZsRNn>N4N1zFU9}>002ovPDHLkV1mCnV#)vj literal 405 zcmV;G0c!q0H7$UZG5oe%_xa5ma z9LL5B{538cHhh#sMBtn|A+BrOh4dA=%ynqlgeKK^jR-+~}BlHKMG-$t0WLgXW07@NNYeYoU zTF0*Q15@ab2%ZLFtwp6&75W372A-(gC3IvyEj)$Kp-=Pa;W7MGbO;;#9bbxOt$2tr zrUuOquSE9)XY1oPqvgXBeP1p-^J66wZj8yTDG$DFTfY?#>2i-}@bcj?{7=w79mMvJ znWhPxa{vJF+q_yqy!Wv0d-eQfS&r9a6~8;A6~8lIICZ{o>Kt%uEg)j^pvut>l~S;- zYtv%hdl<*@4p%lu#hr67&vVma!!R_1O7iDFKjK_7K#7>002ovPDHLkV1nB{ul4`{ literal 1847 zcmV-72gvw|P)YMcUsARK8-ew?Oh zXxhO`YMO?sssOm%ZaO+T=u?RI0aSXf(IV`^%O(a}+Y!65Q!j6V!{LoF5ym6esK zs!BKWf?io zcJE}zHL{oz`Fl>Mlb)U)a=9EYUc4ZkPVZA;_wVxZa_-)}OJ83fZnvA-+FDE|6QU?m zUS5tUikM6$*4NiBMuCco3OYMGv0ANcZf-I?JF@6+olY|~HANzkDDeE{%a`fy z?#AcyasB#rj7DS8wM0>*qM`y-RnJ#}JS0iNX0z!^Y;A3!sw!{az9kxsA_xMtwY7A0 zb>a1T>FDSvFh1|xJOn{Nk|ZQa+E3s-lu#6fR4N64&1R#mtqnmCP!xsn@o@|W16^HR zJbd_&*49=G2E#sscmJQwW=SLxWV6|eQ9#o)ve_&EvMl5AcqlI~M^#lMNy20@(cj-s zdwYA)u-O}yzkmPc)2B~FqtSy2oRty>8DY2EX>V_*v9S?R6uEZo+CI-8gjg)b?CdPx zzkk0N1&YCFG~#l(aJgJZekPSl5ekJE8yjPCa+3M^`NIjERteaz#z_$hg?Rq_Im5%l z1cSlCWAtxEGMQv$Wd*Mj`^$ifZcBA)~#D)G8uBY9MjX& ztgf!Iv$Lc36K6)?Pp6ckC@0DV`MUqcjT^|ajKN^w<;$0>t*z;u-I*&;ESv;EIIuMS z6Y^DOeSJOk_4OEyMr2uLYHEr|Btj;WxtI`=Wtp2E0qsHTLmX?-?$K$|%#n<%oG|_1EM26?%T@VD4$z(x4 zEm$lTt*NPr`}gnD(a}L^X{lbT=bP-Ko*?;gfk1%Aj~^2V1c3i~+{VU6Y&P483FQA2 zMIjc85s5_h*Ftu?oz~V?WchTFRZ&sF(9n?ndvkM>xw*Mh%~=ehC?ct<`Y4r35ex=d zU0ua$wbI<&e5#3m{rbiD_&CeU%V%Wje(1-jq9`O12@;6}u~-aMRmo&Bw6?Z#<;oRA zQ9SZq$j4>=oGy0nED6A#M3zV-c>Ve{nM{Vk!9nbHJ7%+452?d4?(WI=hT&P)P`-YO(V~(cQYSvBAW|1Xin+nwpwp6VNn`TrT&=F=y3Z z6n)xau~1f4MrmoOo)?Y-P1E!v_`Fe6f#&9B9z1w}*XyOezFs%#I5=wVIPKmjAW&9T zMngjb0|NsL3=GiH(sHcH{vRkH;BvVb92~^w^HEn zJ9qGSJoNST?eqNZ7qz=Ry|S{x!omWIqR`mbNL^jsiR%5cfIKiTfYa&3>2ww~9typi z$z=HO;R8>fK4p7*8?V<(Z*MQPwY3G$pBMHjBKP$4U@#a88jgcJzr4ImAP``9c$krq z5pua4s;VN2B5iGLT)ldgl9CciN=h#51(;k}S*aUa>_b8x*=&|TAi&Vj5Hm9~dZzvO z@gt%rqN*xYRaN>2xsxQo6A@5VwV>nK3r*7qg+hG&`W3(5&#PCjh{a<1{b)4G%*+gu zBw@8$>FVktkw{!zCJu+gn9XJ!4o6V}*=&}%xjCLad&cbStllQ>MmnA5-Me=fjYckA zy2RGj7U^{QBJ4+MYHE5Ei9`@Z5wqEhrfKZ#>=2K~Sy))${rmTfjEpcgHbx{8DVo$Y zO(U615)OyaG>u>|$lTlrvVttjSglr6Rb^vigP%WtGC4WP=;$bK z-n`M9oj*ZQ6xP<(n4g~~8jb3omCh6Lj~_qS+1Vi+4ik^ZF`Lb7Z*LQe#aLWi3jxG!>_>oh*Iz?aKGdfyzel;=a{;Ce&$i22lqarLWPFsZ)P9~ zGfNksyHiA#W_#1$1gPrp^0wgr@)~Nb?ZY1g&*2;3F+91%`<=&G{6_!E7?jzEImS00000NkvXXu0mjfiOFR= literal 388 zcmV-~0ek+5P) zL8`(q6ouhSiX~D4g5U-eK`6LTU4;;E4Hk<--GL6pVZpg#`QPMKTB~i+2JZBH2{~6p z1mB6Gh$KnK^SpJW3m_tBt(}n0fGg4q5E0@y=J9y^29#1Lr8Z?0Zq{Hc>EfkBneRzvDs|gflowh{WW~Q-=nogL{Lh-5eb5z75Ib> zheIuVI2=|kQXmop0mZl?yRld-y!WLB{0g7X=T0s22Fl^CRdxoR&nL^}vQ}Ylr0E4|nqrLU ihqV1cZ=?jXde>4h8Q#U0RRLQ=EoYq zjT$_J07N8iOCF%KRGf1#j-zOdloGUU3jm5U9%F>Q??q!YO|$tlLL1>#Y<0lX7Dwhdo(qJ6Xg%}hUm}mo1(9S zo9*M3q2+KRvv)6pzT5GSp_BH@26+&BE8xI~lpr@w? zuh)y!Y6U=1l%j1)ecrHa5|kRih>{r1&gR?3TcQ$ zB1}$Bl1`_QWf`~Ijmcy}5QKtlRWt(o5sSrGU0p>{6dVo*cDtQp$Bq@8O8=VxfOtGk zFc?G>MSMOVb#-+B1qmoTW@UugY{ubmV6|HH$y_L;gLzj*Ok$hOMq^_m_wL=Judk1@XV2=-3U%Ni0hNfzsZ*yI7#N_px0m|*`Xj%k@yW)c zq7ks$?KCwtarf?BdV70mX=%y&?mYNer}f$r{ZZr{Fr$n$?AuP6$kP>6+v1r$Z0si}z*Cr%(ql3uYCMJZfBF%j{2 zJox>7{C+=PuQw-ofMhbs+}s>bo;)FuNYK;M!}aUe^|eAnMIumJTT5qWCmkIf8TYf1 zUs+ip5C||lJk0p`IEtc>NF*>A479hmbMD+ZeN4#$a#acVlR|E{J1_FdWRh>+zA-d3 z#LJg2_4ht~`h?MF1fZs-29L*69s&74Q50lZ&bartiMqD7Mj#MiWMqUFFJ2IdMD%CT zXq1_m8U6pIOPBC^y-3<@E~XbhiqL)!N25{J*Vm66pGu_&1Of~V4ILt{As&zO;ll?+ zQKYJ>iZf@my=kpPd$NBN&2gzg-;81U_5f?=fm&-**M+cpqopg0|=>j5=2wtxjlgY%) z%nTbF8^mHUU5KJ6q*5tDp^z@{Ac{Gi5Fb5y^w{BWu)Msihfwp52JN@Y<>Kben+yyL zaN)uQnwy(3o6Sg)gw<-rY&KI{TZ=5qghC;E zse&M={7EbQG8&Dl$Kz2SK76Q7PEM+cL_*DmbULjL4i2hqZEgB^K@jxopzTsG$>;Oo zbUI0;QpmE*yLa!{+1beuSzTRCcXu~^eSNgHwqi1wvN}W*Me6G6^h2wI$YjtN%r`g)tA{L8PtQwVkuw^tFX>Dyq6h&+{8?RoyVt#&JZ%wj^C|4xP r(zLr=E_^;8r%#{8X0w&UWZw7(pQ$nDq_{7y00000NkvXXu0mjfJbXW) diff --git a/Telegram/Resources/icons/media_pause.png b/Telegram/Resources/icons/media_pause.png index 2ae4e1ebb69de0bc1e2f3a32d31a558481f4c75c..f4291828dcb0b59ca919aa14399dde201d47b5ba 100644 GIT binary patch literal 14780 zcmeI3J8UCG7{}*8LPt;v1QH0EmG2NBu=ioFA1m8Y;s>#WLzMVLQgNHzv9pTz!S4F( zL=-$K1QbMo5Tc+#qB`*?DL?`wBr2LK5Cts=2~oi{7iQLvU4OAhcj(Gl#a@5&&Hwvm ze&5Vwn|*V8>$%mH$5s$RtF6uY4!J)_J`Z2Kk9;3}a-&6V_j{ZB0YVQvL_ZhMM;||m zQ1Nf8vls5QpVti6&giClfHNc8BfSwSuZ=w2IKrWDfP0oxN&WTpHz~m~E2$UNw$%1& z_|V!s@$v4-R>wFwGD>D@t-4YkX(WJ+LtPlzeJ9XHl~fd0Bfn|0m=dBc;ZY^EP7?}y z?QNmv`dCmi`LrQt6`@ed$ZAO`=AIQ4NzRK>PLxY&IjfPsoFq)HRCR^?D*I+v+o?Av z>4;TH9fqN&iQ;fL%nY*`*Y6Q|sU%8@s3>XDBOM$&p*~7G!KHDKX`DI^4BzrX%XI`A zS3htEVI`HKiDuViUbZ(&??*#34H5UTjh!$cI(eba zJ9I-gICPh!oVk}KCN*idXSo-S*R~g?4#LJYf?$Glp)%+kdsy7TfjjUG+_*-ja%s`* zL#rDXXD$-;EFC)@SxaQpGnPe5VO*gynX6Xwu^zg9$94PF@haP%A{1&hx~kNSDm>NJ z4a=cqpDv3~7S}wEi0gWYt8`7Kr9xU!I&wi%a+)MRBgvX1C4!85f*5Vrw7SQM7{#=d z?T|$+DVn4vVFNzIE#8TUAn&jA@ygR8ne6GhfK9 zH&t0~ESYjn7Jx*uSAbW~E)vtxD|ldiL? z934TLZqQ@7YS5jH&9XSx8V?J~tp8iUar}gQd5+;Bc37vvI8@YYd62;EhC|)Q>pikx zZuig{N1m6$6s)~5U!&=&Jzvu@J)t$S9?01As19U_pKFUVkNfY@y7mWA|AHcsOO)yN zM4EI%={{SUbVF&Zyy944RheW1%?TC#H!?oocF7YDuI5!WtCmIB3>Dkf2>18u7CGMn zx-#X$L<1cQh5NBkAwZ&Jkuc$y2*(9XJex$@YTQbme8@wWID6_kx8r8};cotjXLL8C zf>=BW0KzsoE;b+F<+uP4w#jj^`2a7+1%R+kj*HC)csVWrgl%$MY(ButaRDG~ljCCZ z0bY&^0AZUP7n=|8a$Eoi+vK>|e1MnZ0zlX%$HnFYyc`z*!ZtZBHXq>SxBw8g$#Jpy z058V{fUr%Di_HgkIW7Q%ZE{>}KETUy0U&IX<6`pxUXBX@VVfKmn-B1ETmT5$HaRXfAK>M<01&pxak2RTFUJLduuYDO%?EfnE&zmWGI6cM z?=fPByu3IhZz^reZ4dK<UHR>LD1JWpe&>T9mCH99rx)I9XY9vUFaK2dtoZM@ v&HR_|zVpgk^?E7$)%#EWa}PpiXZNDhTN}HtZLEDt0-{!9tA2gs>g)dj-u3v@ delta 68 zcmdl}oHRkjPSexHF~p)b`Op9V|LfUw8YWH@bmnOfVpChQNTn&z*^O7yH08>Khv^&) XZKCWd{TCP-8Gyjk)z4*}Q$iB}nsgX} diff --git a/Telegram/Resources/icons/media_pause@2x.png b/Telegram/Resources/icons/media_pause@2x.png index ffba955adad7822958388d65b21a4c71af6e3481..24a8b16f04367301ebbad5b35fa49504002b3039 100644 GIT binary patch literal 14834 zcmeI3Pi)&%9LL`npwVg5Bn}`>mM5ekNNgugnpjRomb5eyDWywAJ2aSMzqHm8+q2yz z?F5?OGPoddfdNN^*d&I8II#mT0SAs83W9FZGzkPkATE<81m)TJW9QfOX+t|ctNHVL zzxVz9e*XO4d-Wx+&CNcwZ{m>&gwVd~Ol6*Y^5k`B&;8{6-OWSqlaB|SnI#XQy$=Sj zUFe8FdHHjmGmQfO zT&5=0oHAZ9XVzVObbYq2ub`D#ppAWv>$Y2#r*sQ^7*`?RgJFT^!YTgA z0$&Oea*MS&u57!Q%OtZ2T}r38$$V1Eou7B~x3Gz*K zjixeRneL|}R)JsfeMb?5PN$RXq?5M0Oyv2zAf|*=DnVu>yfw?$x(UlW*b6dM9JD!uxegN1qQ*{iOO zr%sbwIXLd_eX|)gXCzYK*|B!qHFuB&o}MgR3cU`g$cr0_Ey>403lZ{ z2dgTR%y5Tlnr>PF*~e4?%A%e}5phNHaWPnv32`!!O4X%FB_%7O^tdP~q8JO(n+amn zY{P7>#bV?VV!BQiHBrmyc#POg7=!E~L-V!UiV~k2i(}|Y({|gMUo_j=G8P^62Qm(K3sqDM`aL zlyq87S1OVym!|WQG?hIfXT>RbdRofnGLscqR)t`@5TB4B=jf5DoI;@nI>ri@BB4xxM8cS(FYGli_G}VvtKKMi@*xjh!tkkU z+m0LVhr9VDx}&=p6~tmm01&##aMAeyFT(|Z&`pMm&IfoIE&zmXGF)^%z{_v}Aas-A zqVoY>h6@0pn+zA75AZTv00`Y=xafR;AOY~5W2~5 z(fI%`!v%oQO@@ok2Y4AS0EBKbTy#Fb%Wwf8bd%wt^8sFl3jm>;3>Tdb@G@Kg2;F43 z=zM^e;Q~PDCc{PN1H23u075qzE;=9JWw-zky2)_S`2a7&1%S{^hKtSzco{ALgl;li zbUwh#Z~-86lZtC1`W+*-$d4C0vfq61>uECgOtn&~cYpl-2WkHo;-2!e-~IIEKW`i;S=TQuyme*c`t#rZ zyl8*3dHno^kBtvDk2g2gZ(NT&nB35qEkl1{j(Tm>F>|IR%t*D?Tsr>mdKI;Vst05J$C#{d8T diff --git a/Telegram/Resources/icons/media_play.png b/Telegram/Resources/icons/media_play.png index 018ef78e96366e19500683c384501efd5bcd8c4c..4b845b51936ddfef10138f4119bf0537f637806a 100644 GIT binary patch literal 337 zcmV-X0j~auP)E3#73YtR-cFt==JE3L)`N^L)jt zUd9im+>f1|*`Hk3#csC)5wS~CO403hqvP>NLI`5BO}=CiRaFJswxRJ>lt>6cQc40q>~_xQGq&3;ubp8S5XUj^Vc+jJilTsF7`zd8 zU579X`D=eVo%m~iy*6vpwl2Ac;kh{XdKtzs6NcnPz?6KEEjN3aayB_@bx&}26ZBA>;9?QdZB53l}U zn3+dH2qZ}YBI3_=T>yY#7!U*j55rf-aiFeiIF7@|bnChz&vP@FZk}gEQDg=K0HqYV zu7l@!d`!3RJIbLHnX;c f5QyWLhrIIuPCk#_;6oj+00000NkvXXu0mjf12S~i diff --git a/Telegram/Resources/icons/media_play@2x.png b/Telegram/Resources/icons/media_play@2x.png index 44805a939c74bdca7447dc57fa813ffe0108a18e..74911abe630998c024db916522521762e6aee8c9 100644 GIT binary patch literal 381 zcmV-@0fPRCP)2sw7E!x;kH=x~P|tLqxR@7oWV=NUy&;QRf8i0~mxa~y|=i1vN|{lS(IJOh;caH125JJ$p bu4HpRVee^s;{J=I00000NkvXXu0mjf?x?2! literal 375 zcmV--0f_#IP)sl|k+<1LpM@T6Yl6cc`}SGlF`(OQ4!?+pMH5$n3D zX`0k~ZjnF9eSP;BV{T7LZW(|u{FKiH-2+QEl0pbvLp;WazVEqe4_#4o&T&=i5CYd{ zg!21ljN$t5I_FST)$4`MiiHrkmMWBMsE0a|>TC(otvV@}!*uKPEDPYRbGVKqIv;7B VNc6w!cEkVx002ovPDHLkV1gn^tc3so diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 8b5708e88..796aee8cc 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -186,10 +186,10 @@ void Controller::resizeEvent(QResizeEvent *e) { _playPauseResume->moveToLeft(st::mediaviewPlayPauseLeft, playTop); int fullScreenTop = (height() - _fullScreenToggle->height()) / 2; - _fullScreenToggle->moveToRight(st::mediaviewPlayPauseLeft, fullScreenTop); + _fullScreenToggle->moveToRight(st::mediaviewFullScreenLeft, fullScreenTop); - _volumeController->moveToRight(st::mediaviewPlayPauseLeft + _fullScreenToggle->width() + st::mediaviewVolumeLeft, (height() - _volumeController->height()) / 2); - _playback->resize(width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewPlayPauseLeft, st::mediaviewSeekSize.height()); + _volumeController->moveToRight(st::mediaviewFullScreenLeft + _fullScreenToggle->width() + st::mediaviewVolumeLeft, (height() - _volumeController->height()) / 2); + _playback->resize(width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewFullScreenLeft, st::mediaviewSeekSize.height()); _playback->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); _playedAlready->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index 4f8938ac7..ef83075fc 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -40,7 +40,7 @@ mediaviewPlayButton: IconButton { overOpacity: mediaviewActiveOpacity; icon: icon { - { "media_play", #ffffff, point(0px, 0px) }, + { "media_play", #ffffff, point(3px, 0px) }, }; iconPosition: point(3px, 1px); downIconPosition: point(3px, 1px); @@ -51,14 +51,15 @@ mediaviewPauseIcon: icon { { "media_pause", #ffffff, point(1px, 1px) } }; mediaviewPlayPauseLeft: 17px; +mediaviewFullScreenLeft: 17px; mediaviewVolumeLeft: 15px; mediaviewFullScreenButton: IconButton(mediaviewPlayButton) { icon: icon { { "media_fullscreen_to", #ffffff, point(0px, 0px) }, }; - iconPosition: point(0px, 0px); - downIconPosition: point(0px, 0px); + iconPosition: point(0px, 1px); + downIconPosition: point(0px, 1px); } mediaviewFullScreenOutIcon: icon { { "media_fullscreen_from", #ffffff, point(0px, 0px) }, @@ -70,12 +71,12 @@ mediaviewPlaybackWidth: 3px; mediaviewPlaybackTop: 28px; mediaviewSeekSize: size(11px, 11px); -mediaviewVolumeSize: size(44px, 18px); +mediaviewVolumeSize: size(44px, 20px); mediaviewVolumeIcon: icon { { "media_volume", mediaviewPlaybackInactive, point(0px, 0px) }, }; mediaviewVolumeOnIcon: icon { { "media_volume", mediaviewPlaybackActive, point(0px, 0px) }, }; -mediaviewVolumeIconTop: 6px; +mediaviewVolumeIconTop: 8px; mediaviewControllerRadius: 25px; From e87c1b6024065dee56b184a3444e6c16d8907d17 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Aug 2016 19:18:23 +0100 Subject: [PATCH 60/60] Open links from bot buttons with regular UrlClickHandler. Fix render glitch in dialogs widget after search. --- Telegram/SourceFiles/dialogswidget.cpp | 1 + Telegram/SourceFiles/facades.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 66196dff7..eed96774f 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -2214,6 +2214,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa _searchRequest = 0; onListScroll(); + update(); } } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3f82c3d85..936ea552d 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -78,7 +78,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { case HistoryMessageReplyMarkup::Button::Url: { auto url = QString::fromUtf8(button->data); - HiddenUrlClickHandler(url).onClick(Qt::LeftButton); + UrlClickHandler(url).onClick(Qt::LeftButton); } break; case HistoryMessageReplyMarkup::Button::RequestLocation: { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index b587be32c..45b1a2792 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5794,7 +5794,7 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC Ui::Toast::Show(App::wnd(), toast); } } else if (answerData.has_url()) { - HiddenUrlClickHandler(qs(answerData.vurl)).onClick(Qt::LeftButton); + UrlClickHandler(qs(answerData.vurl)).onClick(Qt::LeftButton); } } }