mirror of https://github.com/procxx/kepka.git
Merge branch 'docs' of https://bitbucket.org/johnprestonmail/telegram-desktop into docs
This commit is contained in:
commit
79d65c1828
|
@ -1240,6 +1240,8 @@ msgFileInPlaySelected: sprite(180px, 164px, 20px, 18px);
|
|||
msgFileOverDuration: 200;
|
||||
msgFileRadialLine: 4px;
|
||||
|
||||
msgVideoSize: size(320px, 240px);
|
||||
|
||||
sendPadding: 9px;
|
||||
btnSend: flatButton(btnDefFlat) {
|
||||
color: btnYesColor;
|
||||
|
@ -2186,7 +2188,7 @@ mediaviewLoader: size(78px, 33px);
|
|||
mediaviewLoaderPoint: size(9px, 9px);
|
||||
mediaviewLoaderSkip: 9px;
|
||||
|
||||
minPhotoSize: 100px;
|
||||
minPhotoSize: 104px;
|
||||
maxMediaSize: 420px;
|
||||
maxStickerSize: 256px;
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ namespace {
|
|||
DocumentItems documentItems;
|
||||
WebPageItems webPageItems;
|
||||
SharedContactItems sharedContactItems;
|
||||
GifItems gifItems;
|
||||
|
||||
typedef QMap<HistoryItem*, QMap<HistoryReply*, bool> > RepliesTo;
|
||||
RepliesTo repliesTo;
|
||||
|
@ -1991,6 +1992,7 @@ namespace App {
|
|||
::documentItems.clear();
|
||||
::webPageItems.clear();
|
||||
::sharedContactItems.clear();
|
||||
::gifItems.clear();
|
||||
::repliesTo.clear();
|
||||
lastPhotos.clear();
|
||||
lastPhotosMap.clear();
|
||||
|
@ -2425,6 +2427,18 @@ namespace App {
|
|||
return ::sharedContactItems;
|
||||
}
|
||||
|
||||
void regGifItem(ClipReader *reader, HistoryItem *item) {
|
||||
::gifItems.insert(reader, item);
|
||||
}
|
||||
|
||||
void unregGifItem(ClipReader *reader) {
|
||||
::gifItems.remove(reader);
|
||||
}
|
||||
|
||||
const GifItems &gifItems() {
|
||||
return ::gifItems;
|
||||
}
|
||||
|
||||
QString phoneFromSharedContact(int32 userId) {
|
||||
SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId);
|
||||
if (i != ::sharedContactItems.cend()) {
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef QMap<AudioData*, HistoryItemsMap> AudioItems;
|
|||
typedef QMap<DocumentData*, HistoryItemsMap> DocumentItems;
|
||||
typedef QMap<WebPageData*, HistoryItemsMap> WebPageItems;
|
||||
typedef QMap<int32, HistoryItemsMap> SharedContactItems;
|
||||
typedef QMap<ClipReader*, HistoryItem*> GifItems;
|
||||
struct ReplyMarkup {
|
||||
ReplyMarkup(int32 flags = 0) : flags(flags) {
|
||||
}
|
||||
|
@ -250,6 +251,10 @@ namespace App {
|
|||
const SharedContactItems &sharedContactItems();
|
||||
QString phoneFromSharedContact(int32 userId);
|
||||
|
||||
void regGifItem(ClipReader *reader, HistoryItem *item);
|
||||
void unregGifItem(ClipReader *reader);
|
||||
const GifItems &gifItems();
|
||||
|
||||
void regMuted(PeerData *peer, int32 changeIn);
|
||||
void unregMuted(PeerData *peer);
|
||||
void updateMuted();
|
||||
|
|
|
@ -83,6 +83,7 @@ enum {
|
|||
LocalEncryptKeySize = 256, // 2048 bit
|
||||
|
||||
AnimationTimerDelta = 7,
|
||||
ClipThreadsCount = 8,
|
||||
|
||||
SaveRecentEmojisTimeout = 3000, // 3 secs
|
||||
SaveWindowPositionTimeout = 1000, // 1 sec
|
||||
|
@ -108,12 +109,14 @@ enum {
|
|||
AudioVoiceMsgUpdateView = 100, // 100ms
|
||||
AudioVoiceMsgChannels = 2, // stereo
|
||||
AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers
|
||||
AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded
|
||||
AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded
|
||||
AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
|
||||
|
||||
StickerInMemory = 1024 * 1024, // 1024 Kb stickers hold in memory, auto loaded and displayed inline
|
||||
StickerInMemory = 2 * 1024 * 1024, // 1 Mb stickers hold in memory, auto loaded and displayed inline
|
||||
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
|
||||
|
||||
AnimationInMemory = 2 * 1024 * 1024, // 2 Mb gif and mp4 animations held in memory while playing
|
||||
|
||||
MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app
|
||||
MaxZoomLevel = 7, // x8
|
||||
ZoomToScreenLevel = 1024, // just constant
|
||||
|
|
|
@ -27,6 +27,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
|
|||
|
||||
namespace {
|
||||
AnimationManager *_manager = 0;
|
||||
QVector<QThread*> _clipThreads;
|
||||
QVector<ClipReadManager*> _clipManagers;
|
||||
};
|
||||
|
||||
namespace anim {
|
||||
|
@ -79,13 +81,24 @@ namespace anim {
|
|||
}
|
||||
|
||||
void startManager() {
|
||||
delete _manager;
|
||||
stopManager();
|
||||
|
||||
_manager = new AnimationManager();
|
||||
|
||||
}
|
||||
|
||||
void stopManager() {
|
||||
delete _manager;
|
||||
_manager = 0;
|
||||
if (!_clipThreads.isEmpty()) {
|
||||
for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) {
|
||||
_clipThreads.at(i)->quit();
|
||||
_clipThreads.at(i)->wait();
|
||||
delete _clipManagers.at(i);
|
||||
}
|
||||
_clipThreads.clear();
|
||||
_clipManagers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,6 +118,27 @@ void Animation::stop() {
|
|||
_manager->stop(this);
|
||||
}
|
||||
|
||||
void AnimationManager::clipReinit(ClipReader *reader) {
|
||||
const GifItems &items(App::gifItems());
|
||||
GifItems::const_iterator it = items.constFind(reader);
|
||||
if (it != items.cend()) {
|
||||
it.value()->initDimensions();
|
||||
if (App::main()) emit App::main()->itemResized(it.value(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationManager::clipRedraw(ClipReader *reader) {
|
||||
if (reader->currentDisplayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GifItems &items(App::gifItems());
|
||||
GifItems::const_iterator it = items.constFind(reader);
|
||||
if (it != items.cend()) {
|
||||
Ui::redrawHistoryItem(it.value());
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedGif::step_frame(float64 ms, bool timer) {
|
||||
int32 f = frame;
|
||||
while (f < images.size() && ms > delays[f]) {
|
||||
|
@ -148,6 +182,13 @@ void AnimatedGif::step_frame(float64 ms, bool timer) {
|
|||
if (img.size() != QSize(w, h)) img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
images[f] = img;
|
||||
frames[f] = QPixmap();
|
||||
for (int32 i = 0; i < f; ++i) {
|
||||
if (!images[i].isNull() || !frames[i].isNull()) {
|
||||
images[i] = QImage();
|
||||
frames[i] = QPixmap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (frame != f) {
|
||||
|
@ -253,3 +294,461 @@ const QPixmap &AnimatedGif::current(int32 width, int32 height, bool rounded) {
|
|||
}
|
||||
return frames[frame];
|
||||
}
|
||||
|
||||
QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool smooth) {
|
||||
bool badSize = (original.width() != request.framew) || (original.height() != request.frameh);
|
||||
bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh);
|
||||
if (badSize || needOuter || request.rounded) {
|
||||
int32 factor(request.factor);
|
||||
bool fill = false;
|
||||
if (cache.width() != request.outerw || cache.height() != request.outerh) {
|
||||
cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied);
|
||||
if (request.framew < request.outerw || request.frameh < request.outerh || original.hasAlphaChannel()) {
|
||||
fill = true;
|
||||
}
|
||||
cache.setDevicePixelRatio(factor);
|
||||
}
|
||||
{
|
||||
Painter p(&cache);
|
||||
if (fill) p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black);
|
||||
if (smooth && badSize) p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
QRect to((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor), request.framew / factor, request.frameh / factor);
|
||||
QRect from(0, 0, original.width() / factor, original.height() / factor);
|
||||
p.drawImage(to, original, from, Qt::ColorOnly);
|
||||
}
|
||||
if (request.rounded) {
|
||||
imageRound(cache);
|
||||
}
|
||||
return QPixmap::fromImage(cache, Qt::ColorOnly);
|
||||
}
|
||||
return QPixmap::fromImage(original, Qt::ColorOnly);
|
||||
}
|
||||
|
||||
ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _state(ClipReading)
|
||||
, _width(0)
|
||||
, _height(0)
|
||||
, _currentDisplayed(1)
|
||||
, _private(0) {
|
||||
if (_clipThreads.size() < ClipThreadsCount) {
|
||||
_threadIndex = _clipThreads.size();
|
||||
_clipThreads.push_back(new QThread());
|
||||
_clipManagers.push_back(new ClipReadManager(_clipThreads.back()));
|
||||
_clipThreads.back()->start();
|
||||
} else {
|
||||
_threadIndex = rand() % _clipThreads.size();
|
||||
int32 loadLevel = 0x7FFFFFFF;
|
||||
for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) {
|
||||
int32 level = _clipManagers.at(i)->loadLevel();
|
||||
if (level < loadLevel) {
|
||||
_threadIndex = i;
|
||||
loadLevel = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
_clipManagers.at(_threadIndex)->append(this, location, data);
|
||||
}
|
||||
|
||||
void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) {
|
||||
int32 factor(cIntRetinaFactor());
|
||||
_request.factor = factor;
|
||||
_request.framew = framew * factor;
|
||||
_request.frameh = frameh * factor;
|
||||
_request.outerw = outerw * factor;
|
||||
_request.outerh = outerh * factor;
|
||||
_request.rounded = rounded;
|
||||
_clipManagers.at(_threadIndex)->start(this);
|
||||
}
|
||||
|
||||
QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh) {
|
||||
_currentDisplayed.storeRelease(1);
|
||||
|
||||
int32 factor(cIntRetinaFactor());
|
||||
QPixmap result(_current);
|
||||
if (result.width() == outerw * factor && result.height() == outerh * factor) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_request.framew = framew * factor;
|
||||
_request.frameh = frameh * factor;
|
||||
_request.outerw = outerw * factor;
|
||||
_request.outerh = outerh * factor;
|
||||
|
||||
QImage current(_currentOriginal);
|
||||
result = _current = QPixmap();
|
||||
result = _current = _prepareFrame(_request, current, _cacheForResize, true);
|
||||
|
||||
_clipManagers.at(_threadIndex)->update(this);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ClipReader::ready() const {
|
||||
if (_width && _height) return true;
|
||||
|
||||
QImage first(_currentOriginal);
|
||||
if (first.isNull()) return false;
|
||||
|
||||
_width = first.width();
|
||||
_height = first.height();
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 ClipReader::width() const {
|
||||
return _width;
|
||||
}
|
||||
|
||||
int32 ClipReader::height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
ClipState ClipReader::state() const {
|
||||
return _state;
|
||||
}
|
||||
|
||||
void ClipReader::stop() {
|
||||
_clipManagers.at(_threadIndex)->stop(this);
|
||||
_width = _height = 0;
|
||||
}
|
||||
|
||||
void ClipReader::error() {
|
||||
_private = 0;
|
||||
_state = ClipError;
|
||||
}
|
||||
|
||||
ClipReader::~ClipReader() {
|
||||
stop();
|
||||
}
|
||||
|
||||
class ClipReaderPrivate {
|
||||
public:
|
||||
|
||||
ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader)
|
||||
, _state(ClipReading)
|
||||
, _data(data)
|
||||
, _location(_data.isEmpty() ? new FileLocation(location) : 0)
|
||||
, _accessed(false)
|
||||
, _buffer(_data.isEmpty() ? 0 : &_data)
|
||||
, _reader(0)
|
||||
, _currentMs(0)
|
||||
, _nextUpdateMs(0) {
|
||||
|
||||
if (_data.isEmpty() && !_location->accessEnable()) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
_accessed = true;
|
||||
}
|
||||
|
||||
ClipProcessResult start(uint64 ms) {
|
||||
_nextUpdateMs = ms + 86400 * 1000ULL;
|
||||
if (!_reader && !restartReader(true)) {
|
||||
return error();
|
||||
}
|
||||
if (_currentOriginal.isNull()) {
|
||||
if (!readNextFrame(_currentOriginal)) {
|
||||
return error();
|
||||
}
|
||||
--_framesLeft;
|
||||
return ClipProcessReinit;
|
||||
}
|
||||
return ClipProcessWait;
|
||||
}
|
||||
|
||||
ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit
|
||||
if (_state == ClipError) return ClipProcessError;
|
||||
|
||||
if (!_request.valid()) {
|
||||
return start(ms);
|
||||
}
|
||||
|
||||
if (_current.isNull()) { // first frame read, but not yet prepared
|
||||
_currentOriginal.setDevicePixelRatio(_request.factor);
|
||||
|
||||
_currentMs = ms;
|
||||
_current = _prepareFrame(_request, _currentOriginal, _currentCache, true);
|
||||
|
||||
if (!prepareNextFrame()) {
|
||||
return error();
|
||||
}
|
||||
return ClipProcessStarted;
|
||||
} else if (ms >= _nextUpdateMs) {
|
||||
swapBuffers();
|
||||
return ClipProcessRedraw;
|
||||
}
|
||||
return ClipProcessWait;
|
||||
}
|
||||
|
||||
ClipProcessResult finishProcess(uint64 ms) {
|
||||
if (!prepareNextFrame()) {
|
||||
return error();
|
||||
}
|
||||
|
||||
if (ms >= _nextUpdateMs) { // we are late
|
||||
swapBuffers(ms); // keep up
|
||||
return ClipProcessRedraw;
|
||||
}
|
||||
return ClipProcessWait;
|
||||
}
|
||||
|
||||
uint64 nextFrameDelay() {
|
||||
return qMax(_reader->nextImageDelay(), 5);
|
||||
}
|
||||
|
||||
void swapBuffers(uint64 ms = 0) {
|
||||
_currentMs = qMax(ms, _nextUpdateMs);
|
||||
qSwap(_currentOriginal, _nextOriginal);
|
||||
qSwap(_current, _next);
|
||||
qSwap(_currentCache, _nextCache);
|
||||
}
|
||||
|
||||
bool readNextFrame(QImage &to) {
|
||||
QImage frame; // QGifHandler always reads first to internal QImage and returns it
|
||||
if (!_reader->read(&frame)) {
|
||||
return false;
|
||||
}
|
||||
int32 w = frame.width(), h = frame.height();
|
||||
if (to.width() == w && to.height() == h && to.format() == frame.format()) {
|
||||
if (to.byteCount() != frame.byteCount()) {
|
||||
int bpl = qMin(to.bytesPerLine(), frame.bytesPerLine());
|
||||
for (int i = 0; i < h; ++i) {
|
||||
memcpy(to.scanLine(i), frame.constScanLine(i), bpl);
|
||||
}
|
||||
} else {
|
||||
memcpy(to.bits(), frame.constBits(), frame.byteCount());
|
||||
}
|
||||
} else {
|
||||
to = frame.copy();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool prepareNextFrame() {
|
||||
_nextUpdateMs = _currentMs + nextFrameDelay();
|
||||
if (!_framesLeft) {
|
||||
if (_reader->jumpToImage(0)) {
|
||||
_framesLeft = _reader->imageCount();
|
||||
} else if (!restartReader()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!readNextFrame(_nextOriginal)) {
|
||||
return false;
|
||||
}
|
||||
_nextOriginal.setDevicePixelRatio(_request.factor);
|
||||
--_framesLeft;
|
||||
_next = QPixmap();
|
||||
_next = _prepareFrame(_request, _nextOriginal, _nextCache, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool restartReader(bool first = false) {
|
||||
if (first && _data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) {
|
||||
QFile f(_location->name());
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
_data = f.readAll();
|
||||
if (f.error() == QFile::NoError) {
|
||||
_buffer.setBuffer(&_data);
|
||||
} else {
|
||||
_data = QByteArray();
|
||||
}
|
||||
}
|
||||
} else if (!_data.isEmpty()) {
|
||||
_buffer.close();
|
||||
}
|
||||
delete _reader;
|
||||
|
||||
if (_data.isEmpty()) {
|
||||
_reader = new QImageReader(_location->name());
|
||||
} else {
|
||||
_reader = new QImageReader(&_buffer);
|
||||
}
|
||||
if (!_reader->canRead() || !_reader->supportsAnimation()) {
|
||||
return false;
|
||||
}
|
||||
_framesLeft = _reader->imageCount();
|
||||
if (_framesLeft < 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ClipProcessResult error() {
|
||||
stop();
|
||||
_state = ClipError;
|
||||
return ClipProcessError;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
delete _reader;
|
||||
_reader = 0;
|
||||
|
||||
if (_location) {
|
||||
if (_accessed) {
|
||||
_location->accessDisable();
|
||||
}
|
||||
delete _location;
|
||||
_location = 0;
|
||||
}
|
||||
_accessed = false;
|
||||
}
|
||||
|
||||
~ClipReaderPrivate() {
|
||||
stop();
|
||||
setBadPointer(_location);
|
||||
setBadPointer(_reader);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ClipReader *_interface;
|
||||
ClipState _state;
|
||||
|
||||
QByteArray _data;
|
||||
FileLocation *_location;
|
||||
bool _accessed;
|
||||
|
||||
QBuffer _buffer;
|
||||
QImageReader *_reader;
|
||||
|
||||
ClipFrameRequest _request;
|
||||
QPixmap _current, _next;
|
||||
QImage _currentOriginal, _nextOriginal, _currentCache, _nextCache;
|
||||
|
||||
int32 _framesLeft;
|
||||
uint64 _currentMs, _nextUpdateMs;
|
||||
|
||||
friend class ClipReadManager;
|
||||
|
||||
};
|
||||
|
||||
ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0) {
|
||||
moveToThread(thread);
|
||||
connect(thread, SIGNAL(started()), this, SLOT(process()));
|
||||
connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection);
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
_timer.moveToThread(thread);
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(process()));
|
||||
|
||||
connect(this, SIGNAL(reinit(ClipReader*)), _manager, SLOT(clipReinit(ClipReader*)));
|
||||
connect(this, SIGNAL(redraw(ClipReader*)), _manager, SLOT(clipRedraw(ClipReader*)));
|
||||
}
|
||||
|
||||
void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) {
|
||||
reader->_private = new ClipReaderPrivate(reader, location, data);
|
||||
update(reader);
|
||||
}
|
||||
|
||||
void ClipReadManager::start(ClipReader *reader) {
|
||||
update(reader);
|
||||
}
|
||||
|
||||
void ClipReadManager::update(ClipReader *reader) {
|
||||
QMutexLocker lock(&_readerPointersMutex);
|
||||
_readerPointers.insert(reader, reader->_private);
|
||||
emit processDelayed();
|
||||
}
|
||||
|
||||
void ClipReadManager::stop(ClipReader *reader) {
|
||||
QMutexLocker lock(&_readerPointersMutex);
|
||||
_readerPointers.remove(reader);
|
||||
emit processDelayed();
|
||||
}
|
||||
|
||||
bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result) {
|
||||
QMutexLocker lock(&_readerPointersMutex);
|
||||
ReaderPointers::iterator it = _readerPointers.find(reader->_interface);
|
||||
if (result == ClipProcessError) {
|
||||
if (it != _readerPointers.cend()) {
|
||||
it.key()->error();
|
||||
_readerPointers.erase(it);
|
||||
it = _readerPointers.end();
|
||||
}
|
||||
}
|
||||
if (it == _readerPointers.cend()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result == ClipProcessReinit || result == ClipProcessRedraw || result == ClipProcessStarted) {
|
||||
it.key()->_current = reader->_current;
|
||||
it.key()->_currentOriginal = reader->_currentOriginal;
|
||||
it.key()->_currentDisplayed.storeRelease(0);
|
||||
if (result == ClipProcessReinit) {
|
||||
emit reinit(it.key());
|
||||
} else if (result == ClipProcessRedraw) {
|
||||
emit redraw(it.key());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) {
|
||||
if (!handleProcessResult(reader, result)) {
|
||||
delete reader;
|
||||
return ResultHandleRemove;
|
||||
}
|
||||
|
||||
_processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
|
||||
if (_processingInThread->isInterruptionRequested()) {
|
||||
return ResultHandleStop;
|
||||
}
|
||||
|
||||
if (result == ClipProcessRedraw) {
|
||||
return handleResult(reader, reader->finishProcess(ms), ms);
|
||||
}
|
||||
|
||||
return ResultHandleContinue;
|
||||
}
|
||||
|
||||
void ClipReadManager::process() {
|
||||
if (_processingInThread) return;
|
||||
|
||||
_timer.stop();
|
||||
_processingInThread = thread();
|
||||
|
||||
uint64 ms = getms(), minms = ms + 86400 * 1000ULL;
|
||||
{
|
||||
QMutexLocker lock(&_readerPointersMutex);
|
||||
for (ReaderPointers::iterator i = _readerPointers.begin(), e = _readerPointers.end(); i != e; ++i) {
|
||||
if (i.value()) {
|
||||
Readers::iterator it = _readers.find(i.value());
|
||||
if (it == _readers.cend()) {
|
||||
_readers.insert(i.value(), 0);
|
||||
} else {
|
||||
it.value() = ms;
|
||||
}
|
||||
i.value()->_request = i.key()->_request;
|
||||
i.value() = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) {
|
||||
if (i.value() <= ms) {
|
||||
ClipProcessResult result = i.key()->process(ms);
|
||||
|
||||
ResultHandleState state = handleResult(i.key(), result, ms);
|
||||
if (state == ResultHandleRemove) {
|
||||
i = _readers.erase(i);
|
||||
continue;
|
||||
} else if (state == ResultHandleStop) {
|
||||
_processingInThread = 0;
|
||||
return;
|
||||
}
|
||||
i.value() = i.key()->_nextUpdateMs;
|
||||
}
|
||||
if (i.value() < minms) {
|
||||
minms = i.value();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
ms = getms();
|
||||
if (minms <= ms) {
|
||||
_timer.start(1);
|
||||
} else {
|
||||
_timer.start(minms - ms);
|
||||
}
|
||||
|
||||
_processingInThread = 0;
|
||||
}
|
||||
|
|
|
@ -335,6 +335,8 @@ AnimationCallbacks *animation(Param param, Type *obj, typename AnimationCallback
|
|||
return new AnimationCallbacksAbsoluteWithParam<Type, Param>(param, obj, method);
|
||||
}
|
||||
|
||||
class ClipReader;
|
||||
|
||||
class AnimationManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -403,6 +405,9 @@ public slots:
|
|||
}
|
||||
}
|
||||
|
||||
void clipReinit(ClipReader *reader);
|
||||
void clipRedraw(ClipReader *reader);
|
||||
|
||||
private:
|
||||
|
||||
typedef QMap<Animation*, NullType> AnimatingObjects;
|
||||
|
@ -470,3 +475,122 @@ private:
|
|||
Animation _a_frames;
|
||||
|
||||
};
|
||||
|
||||
enum ClipState {
|
||||
ClipReading,
|
||||
ClipError,
|
||||
};
|
||||
|
||||
struct ClipFrameRequest {
|
||||
ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) {
|
||||
}
|
||||
bool valid() const {
|
||||
return factor > 0;
|
||||
}
|
||||
int32 factor;
|
||||
int32 framew, frameh;
|
||||
int32 outerw, outerh;
|
||||
bool rounded;
|
||||
};
|
||||
|
||||
class ClipReaderPrivate;
|
||||
class ClipReader {
|
||||
public:
|
||||
|
||||
ClipReader(const FileLocation &location, const QByteArray &data);
|
||||
|
||||
void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded);
|
||||
QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh);
|
||||
bool currentDisplayed() const {
|
||||
return _currentDisplayed.loadAcquire() > 0;
|
||||
}
|
||||
|
||||
int32 width() const;
|
||||
int32 height() const;
|
||||
|
||||
ClipState state() const;
|
||||
bool started() const {
|
||||
return _request.valid();
|
||||
}
|
||||
bool ready() const;
|
||||
|
||||
void stop();
|
||||
void error();
|
||||
|
||||
~ClipReader();
|
||||
|
||||
private:
|
||||
|
||||
ClipState _state;
|
||||
|
||||
ClipFrameRequest _request;
|
||||
|
||||
mutable int32 _width, _height;
|
||||
|
||||
QPixmap _current;
|
||||
QImage _currentOriginal, _cacheForResize;
|
||||
QAtomicInt _currentDisplayed;
|
||||
int32 _threadIndex;
|
||||
|
||||
friend class ClipReadManager;
|
||||
|
||||
ClipReaderPrivate *_private;
|
||||
|
||||
};
|
||||
|
||||
enum ClipProcessResult {
|
||||
ClipProcessError,
|
||||
ClipProcessStarted,
|
||||
ClipProcessReinit,
|
||||
ClipProcessRedraw,
|
||||
ClipProcessWait,
|
||||
};
|
||||
|
||||
class ClipReadManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ClipReadManager(QThread *thread);
|
||||
int32 loadLevel() const {
|
||||
return _loadLevel.loadAcquire();
|
||||
}
|
||||
void append(ClipReader *reader, const FileLocation &location, const QByteArray &data);
|
||||
void start(ClipReader *reader);
|
||||
void update(ClipReader *reader);
|
||||
void stop(ClipReader *reader);
|
||||
|
||||
signals:
|
||||
|
||||
void processDelayed();
|
||||
|
||||
void reinit(ClipReader *reader);
|
||||
void redraw(ClipReader *reader);
|
||||
|
||||
public slots:
|
||||
|
||||
void process();
|
||||
|
||||
private:
|
||||
|
||||
QAtomicInt _loadLevel;
|
||||
typedef QMap<ClipReader*, ClipReaderPrivate*> ReaderPointers;
|
||||
ReaderPointers _readerPointers;
|
||||
QMutex _readerPointersMutex;
|
||||
|
||||
bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result);
|
||||
|
||||
enum ResultHandleState {
|
||||
ResultHandleRemove,
|
||||
ResultHandleStop,
|
||||
ResultHandleContinue,
|
||||
};
|
||||
ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms);
|
||||
|
||||
typedef QMap<ClipReaderPrivate*, uint64> Readers;
|
||||
Readers _readers;
|
||||
|
||||
QTimer _timer;
|
||||
QThread *_processingInThread;
|
||||
|
||||
};
|
||||
|
|
|
@ -1300,7 +1300,6 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri
|
|||
resize(_st.width, _st.height);
|
||||
|
||||
_inner.setWordWrapMode(QTextOption::NoWrap);
|
||||
_inner.setLineWrapMode(QTextEdit::NoWrap);
|
||||
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ public:
|
|||
const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixColored(const style::color &add, int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixBlurredColored(const style::color &add, int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixSingle(int32 w, int32 y, int32 outerw, int32 outerh) const;
|
||||
const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const;
|
||||
const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const;
|
||||
QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false, bool blurred = false, bool rounded = false, int32 outerw = -1, int32 outerh = -1) const;
|
||||
QPixmap pixColoredNoCache(const style::color &add, int32 w = 0, int32 h = 0, bool smooth = false) const;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1015,9 +1015,6 @@ public:
|
|||
virtual int32 timeLeft() const {
|
||||
return 0;
|
||||
}
|
||||
virtual QString timeText() const {
|
||||
return QString();
|
||||
}
|
||||
virtual int32 timeWidth() const {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1303,61 +1300,6 @@ QString formatDurationText(qint64 duration);
|
|||
QString formatDurationAndSizeText(qint64 duration, qint64 size);
|
||||
QString formatPlayedText(qint64 played, qint64 duration);
|
||||
|
||||
class HistoryVideo : public HistoryMedia {
|
||||
public:
|
||||
|
||||
HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent);
|
||||
void initDimensions(const HistoryItem *parent);
|
||||
|
||||
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const;
|
||||
int32 resize(int32 width, const HistoryItem *parent);
|
||||
HistoryMediaType type() const {
|
||||
return MediaTypeVideo;
|
||||
}
|
||||
const QString inDialogsText() const;
|
||||
const QString inHistoryText() const;
|
||||
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
|
||||
int32 countHeight(const HistoryItem *parent, int32 width = -1) const;
|
||||
void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
|
||||
bool uploading() const {
|
||||
return (data->status == FileUploading);
|
||||
}
|
||||
HistoryMedia *clone() const;
|
||||
|
||||
void regItem(HistoryItem *item);
|
||||
void unregItem(HistoryItem *item);
|
||||
|
||||
bool hasReplyPreview() const {
|
||||
return !data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview();
|
||||
|
||||
bool needsBubble(const HistoryItem *parent) const {
|
||||
return !_caption.isEmpty() || parent->toHistoryReply();
|
||||
}
|
||||
bool customInfoLayout() const {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
bool hideFromName() const {
|
||||
return true;
|
||||
}
|
||||
bool hideForwardedFrom() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
VideoData *data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
|
||||
Text _caption;
|
||||
|
||||
QString _size;
|
||||
int32 _thumbw;
|
||||
|
||||
mutable QString _dldTextCache, _uplTextCache;
|
||||
mutable int32 _dldDone, _uplDone;
|
||||
};
|
||||
|
||||
class HistoryFileMedia : public HistoryMedia {
|
||||
public:
|
||||
|
||||
|
@ -1381,6 +1323,7 @@ protected:
|
|||
mutable int32 _statusSize;
|
||||
mutable QString _statusText;
|
||||
|
||||
// duration = -1 - no duration, duration = -2 - "GIF" duration
|
||||
void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const;
|
||||
|
||||
void step_thumbOver(const HistoryItem *parent, float64 ms, bool timer);
|
||||
|
@ -1414,6 +1357,70 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
class HistoryVideo : public HistoryFileMedia {
|
||||
public:
|
||||
|
||||
HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent);
|
||||
void initDimensions(const HistoryItem *parent);
|
||||
|
||||
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const;
|
||||
int32 resize(int32 width, const HistoryItem *parent);
|
||||
HistoryMediaType type() const {
|
||||
return MediaTypeVideo;
|
||||
}
|
||||
const QString inDialogsText() const;
|
||||
const QString inHistoryText() const;
|
||||
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
|
||||
int32 countHeight(const HistoryItem *parent, int32 width = -1) const;
|
||||
void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
|
||||
bool uploading() const {
|
||||
return (_data->status == FileUploading);
|
||||
}
|
||||
HistoryMedia *clone() const;
|
||||
|
||||
void regItem(HistoryItem *item);
|
||||
void unregItem(HistoryItem *item);
|
||||
|
||||
bool hasReplyPreview() const {
|
||||
return !_data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview();
|
||||
|
||||
bool needsBubble(const HistoryItem *parent) const {
|
||||
return !_caption.isEmpty() || parent->toHistoryReply();
|
||||
}
|
||||
bool customInfoLayout() const {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
bool hideFromName() const {
|
||||
return true;
|
||||
}
|
||||
bool hideForwardedFrom() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
float64 dataProgress() const {
|
||||
return _data->progress();
|
||||
}
|
||||
bool dataFinished() const {
|
||||
return !_data->loader;
|
||||
}
|
||||
bool dataLoaded() const {
|
||||
return !_data->already().isEmpty();
|
||||
}
|
||||
|
||||
private:
|
||||
VideoData *_data;
|
||||
Text _caption;
|
||||
int32 _thumbw;
|
||||
|
||||
void setStatusSize(int32 newSize) const;
|
||||
void updateStatusText(const HistoryItem *parent) const;
|
||||
|
||||
};
|
||||
|
||||
class HistoryAudio : public HistoryFileMedia {
|
||||
public:
|
||||
|
||||
|
@ -1463,6 +1470,7 @@ protected:
|
|||
|
||||
private:
|
||||
AudioData *_data;
|
||||
|
||||
void setStatusSize(int32 newSize, qint64 realDuration = 0) const;
|
||||
bool updateStatusText(const HistoryItem *parent) const; // returns showPause
|
||||
|
||||
|
@ -1549,7 +1557,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class HistoryGif : public HistoryMedia {
|
||||
class HistoryGif : public HistoryFileMedia {
|
||||
public:
|
||||
|
||||
HistoryGif(DocumentData *document);
|
||||
|
@ -1584,27 +1592,42 @@ public:
|
|||
}
|
||||
ImagePtr replyPreview();
|
||||
|
||||
void drawInPlaylist(Painter &p, const HistoryItem *parent, bool selected, bool over, int32 width) const;
|
||||
TextLinkPtr linkInPlaylist();
|
||||
|
||||
bool needsBubble(const HistoryItem *parent) const {
|
||||
return parent->toHistoryReply();
|
||||
}
|
||||
bool customInfoLayout() const {
|
||||
return true;
|
||||
}
|
||||
bool hideFromName() const {
|
||||
return true;
|
||||
}
|
||||
bool hideForwardedFrom() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void play(HistoryItem *parent);
|
||||
~HistoryGif();
|
||||
|
||||
protected:
|
||||
|
||||
float64 dataProgress() const {
|
||||
return _data->progress();
|
||||
}
|
||||
bool dataFinished() const {
|
||||
return !_data->loader;
|
||||
}
|
||||
bool dataLoaded() const {
|
||||
return !_data->already().isEmpty() && !_data->data.isEmpty();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
DocumentData *_data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
int32 _thumbw, _thumbh;
|
||||
ClipReader *_gif;
|
||||
|
||||
int32 _namew;
|
||||
QString _name, _size;
|
||||
int32 _thumbw, _thumbx, _thumby;
|
||||
|
||||
mutable QString _statusText;
|
||||
mutable int32 _statusSize; // -1 will contain just size string, -2 will contain "failed" language key
|
||||
void setStatusSize(int32 newSize) const;
|
||||
void updateStatusText(const HistoryItem *parent) const;
|
||||
|
||||
};
|
||||
|
||||
|
@ -1970,9 +1993,6 @@ public:
|
|||
}
|
||||
return result;
|
||||
}
|
||||
QString timeText() const {
|
||||
return _timeText;
|
||||
}
|
||||
int32 timeWidth() const {
|
||||
return _timeWidth;
|
||||
}
|
||||
|
|
|
@ -5366,6 +5366,8 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, const MTPInputFil
|
|||
DocumentData *document = 0;
|
||||
if (HistoryDocument *media = dynamic_cast<HistoryDocument*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
} else if (HistoryGif *media = dynamic_cast<HistoryGif*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
} else if (HistorySticker *media = dynamic_cast<HistorySticker*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
}
|
||||
|
@ -5395,6 +5397,8 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, const MTPInp
|
|||
DocumentData *document = 0;
|
||||
if (HistoryDocument *media = dynamic_cast<HistoryDocument*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
} else if (HistoryGif *media = dynamic_cast<HistoryGif*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
} else if (HistorySticker *media = dynamic_cast<HistorySticker*>(item->getMedia())) {
|
||||
document = media->document();
|
||||
}
|
||||
|
|
|
@ -437,7 +437,6 @@ MainWidget::MainWidget(Window *window) : TWidget(window)
|
|||
connect(&_topBar, SIGNAL(clicked()), this, SLOT(onTopBarClick()));
|
||||
connect(&history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId)));
|
||||
connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
|
||||
connect(this, SIGNAL(showPeerAsync(quint64,qint32)), this, SLOT(showPeerHistory(quint64,qint32)), Qt::QueuedConnection);
|
||||
if (audioPlayer()) {
|
||||
connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&)));
|
||||
connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&)));
|
||||
|
@ -1886,17 +1885,15 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) {
|
|||
} else if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) {
|
||||
const FileLocation &location(document->location(true));
|
||||
if (location.accessEnable()) {
|
||||
QImageReader reader(location.name());
|
||||
if (reader.canRead()) {
|
||||
if (reader.supportsAnimation() && reader.imageCount() > 1 && item) {
|
||||
startGif(item, location);
|
||||
} else if (item) {
|
||||
if (item && item->getMedia() && item->getMedia()->type() == MediaTypeGif) {
|
||||
static_cast<HistoryGif*>(item->getMedia())->play(item);
|
||||
} else {
|
||||
QImageReader reader(location.name());
|
||||
if (reader.canRead() && item) {
|
||||
App::wnd()->showDocument(document, item);
|
||||
} else {
|
||||
psOpenFile(already);
|
||||
}
|
||||
} else {
|
||||
psOpenFile(already);
|
||||
}
|
||||
location.accessDisable();
|
||||
} else {
|
||||
|
|
|
@ -719,6 +719,18 @@ void VideoData::save(const QString &toFile) {
|
|||
loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(videoLoadProgress(mtpFileLoader*)));
|
||||
loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(videoLoadFailed(mtpFileLoader*, bool)));
|
||||
loader->start();
|
||||
|
||||
notifyLayoutChanged();
|
||||
}
|
||||
|
||||
void VideoData::notifyLayoutChanged() const {
|
||||
const VideoItems &items(App::videoItems());
|
||||
VideoItems::const_iterator i = items.constFind(const_cast<VideoData*>(this));
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
Notify::historyItemLayoutChanged(j.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString VideoData::already(bool check) {
|
||||
|
@ -874,17 +886,15 @@ void DocumentOpenLink::doOpen(DocumentData *data) {
|
|||
if (App::main()) App::main()->documentPlayProgress(song);
|
||||
}
|
||||
} else if (data->size < MediaViewImageSizeLimit && location.accessEnable()) {
|
||||
QImageReader reader(location.name());
|
||||
if (reader.canRead()) {
|
||||
if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) {
|
||||
startGif(App::hoveredLinkItem(), location);
|
||||
} else if (App::hoveredLinkItem() || App::contextItem()) {
|
||||
if (App::hoveredLinkItem() && App::hoveredLinkItem()->getMedia() && App::hoveredLinkItem()->getMedia()->type() == MediaTypeGif) {
|
||||
static_cast<HistoryGif*>(App::hoveredLinkItem()->getMedia())->play(App::hoveredLinkItem());
|
||||
} else {
|
||||
QImageReader reader(location.name());
|
||||
if (reader.canRead() && (App::hoveredLinkItem() || App::contextItem())) {
|
||||
App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
|
||||
} else {
|
||||
psOpenFile(location.name());
|
||||
}
|
||||
} else {
|
||||
psOpenFile(location.name());
|
||||
}
|
||||
location.accessDisable();
|
||||
} else {
|
||||
|
|
|
@ -809,6 +809,8 @@ struct VideoData {
|
|||
l->cancel();
|
||||
l->deleteLater();
|
||||
l->rpcInvalidate();
|
||||
|
||||
notifyLayoutChanged();
|
||||
}
|
||||
_location = FileLocation();
|
||||
if (!beforeDownload) {
|
||||
|
@ -824,11 +826,19 @@ struct VideoData {
|
|||
loader->deleteLater();
|
||||
loader->rpcInvalidate();
|
||||
loader = 0;
|
||||
|
||||
notifyLayoutChanged();
|
||||
}
|
||||
|
||||
void notifyLayoutChanged() const;
|
||||
|
||||
QString already(bool check = false);
|
||||
const FileLocation &location(bool check = false);
|
||||
|
||||
float64 progress() const {
|
||||
return loader ? loader->currentProgress() : ((status == FileDownloadFailed || _location.name().isEmpty()) ? 0 : 1);
|
||||
}
|
||||
|
||||
VideoId id;
|
||||
uint64 access;
|
||||
int32 date;
|
||||
|
|
|
@ -21,7 +21,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
template <typename Type>
|
||||
void setBadLink(Type *&link) {
|
||||
void setBadPointer(Type *&link) {
|
||||
link = reinterpret_cast<Type*>(0x00000bad);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue