diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 06ae05cea..9ac49a605 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -368,6 +368,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_profile_files_header" = "Files overview"; "lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages} »"; "lng_profile_audios_header" = "Voice messages overview"; +"lng_profile_audio_files_header" = "Playlist"; "lng_profile_show_all_types" = "Show all types"; "lng_profile_copy_phone" = "Copy phone number"; @@ -602,7 +603,7 @@ Copyright (c) 2014 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" = "— Forward photos, media and stickers with drag-n-drop\n— Drag-n-drop text messages by timestamp to forward them\n— Larger stickers panel\n— IPv6 checkbox added to Connection Type in Settings"; +"lng_new_version_text" = "— Improved in-app media playback\n— Bug fixes and other minor improvements"; "lng_menu_insert_unicode" = "Insert Unicode control character"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 197816c12..1afa28f89 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1971,3 +1971,29 @@ botDescSkip: 8px; suppressAll: 0.2; suppressSong: 0.05; + +playerHeight: 44px; +playerBg: #e4e9ef; +playerFg: #54748f; +playerTimeFg: #a4afba; +playerLineHeight: 3px; +playerMoverSize: size(2px, 7px); +playerLineActive: #6389a8; +playerLineInactive: #bac7d4; +playerSkip: 8px; +playerNameStyle: textStyle(defaultTextStyle) { + lnkColor: #6389a8; + lnkDownColor: #6389a8; + lnkFlags: font(fsize semibold); + lnkOverFlags: font(fsize semibold); +} +playerPlay: sprite(377px, 109px, 19px, 22px); +playerPause: sprite(379px, 131px, 17px, 20px); +playerNext: sprite(374px, 151px, 22px, 14px); +playerPrev: sprite(374px, 165px, 22px, 14px); +playerClose: sprite(361px, 97px, 12px, 12px); +playerFull: sprite(365px, 109px, 12px, 12px); +playerVolume: sprite(352px, 179px, 44px, 12px); +playerInactiveOpacity: 0.8; +playerUnavailableOpacity: 0.3; +playerDuration: 200; diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 3e973cd2a..d52ac2e85 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index 97e9e2403..84138ee01 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index f5c39e43a..79978f250 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -273,8 +273,9 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong())); connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong())); connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll())); - connect(this, SIGNAL(loaderOnStart(const AudioMsgId&)), _loader, SLOT(onStart(const AudioMsgId&))); - connect(this, SIGNAL(loaderOnStart(const SongMsgId&)), _loader, SLOT(onStart(const SongMsgId&))); + 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())); @@ -292,6 +293,8 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { 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(); } @@ -335,12 +338,12 @@ AudioPlayer::~AudioPlayer() { } void AudioPlayer::onError(const AudioMsgId &audio) { - emit stopped(audio); + emit stoppedOnError(audio); emit unsuppressSong(); } void AudioPlayer::onError(const SongMsgId &song) { - emit stopped(song); + emit stoppedOnError(song); } void AudioPlayer::onStopped(const AudioMsgId &audio) { @@ -366,20 +369,20 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { } else { pos = 0; } - } - if (!_checkALError()) { - data->state = AudioPlayerStopped; - switch (type) { - case OverviewAudios: onError(_audioData[_audioCurrent].audio); break; - case OverviewDocuments: onError(_songData[_songCurrent].song); break; + if (!_checkALError()) { + setStoppedState(data, AudioPlayerStoppedAtError); + switch (type) { + case OverviewAudios: onError(_audioData[_audioCurrent].audio); break; + case OverviewDocuments: onError(_songData[_songCurrent].song); break; + } + return false; } - return false; } data->started = data->position = pos + data->skipStart; return true; } -bool AudioPlayer::startedOther(MediaOverviewType type, bool &fadedStart) { +bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) { Msg *current = 0; switch (type) { case OverviewAudios: current = &_audioData[_audioCurrent]; break; @@ -393,20 +396,21 @@ bool AudioPlayer::startedOther(MediaOverviewType type, bool &fadedStart) { case AudioPlayerPlaying: current->state = AudioPlayerFinishing; updateCurrentStarted(type); - fadedStart = true; + if (fadedStart) *fadedStart = true; break; case AudioPlayerPausing: current->state = AudioPlayerFinishing; - fadedStart = true; + if (fadedStart) *fadedStart = true; break; case AudioPlayerPaused: - current->state = AudioPlayerStopped; + case AudioPlayerPausedAtEnd: + setStoppedState(current); return true; } return false; } -void AudioPlayer::play(const AudioMsgId &audio) { +void AudioPlayer::play(const AudioMsgId &audio, int64 position) { AudioMsgId stopped; { QMutexLocker lock(&playerMutex); @@ -414,7 +418,7 @@ void AudioPlayer::play(const AudioMsgId &audio) { bool fadedStart = false; AudioMsg *current = &_audioData[_audioCurrent]; if (current->audio != audio) { - if (startedOther(OverviewAudios, fadedStart)) { + if (fadedStop(OverviewAudios, &fadedStart)) { stopped = current->audio; } if (current->audio) { @@ -438,19 +442,19 @@ void AudioPlayer::play(const AudioMsgId &audio) { current->fname = audio.audio->already(true); current->data = audio.audio->data; if (current->fname.isEmpty() && current->data.isEmpty()) { - current->state = AudioPlayerStopped; + setStoppedState(current, AudioPlayerStoppedAtError); onError(audio); - } else if (updateCurrentStarted(OverviewAudios, 0)) { + } else { current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; current->loading = true; - emit loaderOnStart(audio); + emit loaderOnStart(audio, position); emit suppressSong(); } } if (stopped) emit updated(stopped); } -void AudioPlayer::play(const SongMsgId &song) { +void AudioPlayer::play(const SongMsgId &song, int64 position) { SongMsgId stopped; { QMutexLocker lock(&playerMutex); @@ -458,7 +462,7 @@ void AudioPlayer::play(const SongMsgId &song) { bool fadedStart = false; SongMsg *current = &_songData[_songCurrent]; if (current->song != song) { - if (startedOther(OverviewDocuments, fadedStart)) { + if (fadedStop(OverviewDocuments, &fadedStart)) { stopped = current->song; } if (current->song) { @@ -482,18 +486,38 @@ void AudioPlayer::play(const SongMsgId &song) { current->fname = song.song->already(true); current->data = song.song->data; if (current->fname.isEmpty() && current->data.isEmpty()) { - current->state = AudioPlayerStopped; - onError(song); - } else if (updateCurrentStarted(OverviewDocuments, 0)) { + setStoppedState(current); + if (!song.song->loader) { + DocumentOpenLink::doOpen(song.song); + song.song->openOnSave = song.song->openOnSaveMsgId = 0; + if (song.song->loader) song.song->loader->start(true, true); + } + } else { current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; current->loading = true; - emit loaderOnStart(song); + emit loaderOnStart(song, position); } } if (stopped) emit updated(stopped); } -void AudioPlayer::pauseresume(MediaOverviewType type) { +bool AudioPlayer::checkCurrentALError(MediaOverviewType type) { + if (_checkALError()) return true; + + switch (type) { + case OverviewAudios: + setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError); + onError(_audioData[_audioCurrent].audio); + break; + case OverviewDocuments: + setStoppedState(&_songData[_songCurrent], AudioPlayerStoppedAtError); + onError(_songData[_songCurrent].song); + break; + } + return false; +} + +void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { QMutexLocker lock(&playerMutex); Msg *current = 0; @@ -505,21 +529,38 @@ void AudioPlayer::pauseresume(MediaOverviewType type) { break; case OverviewDocuments: current = &_songData[_songCurrent]; - suppressGain = suppressSongGain; + suppressGain = suppressSongGain * cSongVolume(); break; } switch (current->state) { case AudioPlayerPausing: case AudioPlayerPaused: + case AudioPlayerPausedAtEnd: { if (current->state == AudioPlayerPaused) { updateCurrentStarted(type); + } else if (current->state == AudioPlayerPausedAtEnd) { + if (alIsSource(current->source)) { + alSourcei(current->source, AL_SAMPLE_OFFSET, qMax(current->position - current->skipStart, 0LL)); + if (!checkCurrentALError(type)) return; + } + } + current->state = fast ? AudioPlayerPlaying : AudioPlayerResuming; + + ALint state = AL_INITIAL; + alGetSourcei(current->source, AL_SOURCE_STATE, &state); + if (!checkCurrentALError(type)) return; + + if (state != AL_PLAYING) { + audioPlayer()->resumeDevice(); + + alSourcef(current->source, AL_GAIN, suppressGain); + if (!checkCurrentALError(type)) return; + + alSourcePlay(current->source); + if (!checkCurrentALError(type)) return; } - current->state = AudioPlayerResuming; - resumeDevice(); - alSourcef(current->source, AL_GAIN, suppressGain); - alSourcePlay(current->source); if (type == OverviewAudios) emit suppressSong(); - break; + } break; case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: @@ -532,6 +573,78 @@ void AudioPlayer::pauseresume(MediaOverviewType type) { emit faderOnTimer(); } +void AudioPlayer::seek(int64 position) { + QMutexLocker lock(&playerMutex); + + MediaOverviewType type = OverviewDocuments; + Msg *current = 0; + float64 suppressGain = 1.; + AudioMsgId audio; + SongMsgId song; + switch (type) { + case OverviewAudios: + current = &_audioData[_audioCurrent]; + audio = _audioData[_audioCurrent].audio; + suppressGain = suppressAllGain; + break; + case OverviewDocuments: + current = &_songData[_songCurrent]; + song = _songData[_songCurrent].song; + suppressGain = suppressSongGain * cSongVolume(); + break; + } + + bool isSource = alIsSource(current->source); + bool fastSeek = (position >= current->skipStart && position < current->duration - current->skipEnd - (current->skipEnd ? AudioVoiceMsgFrequency : 0)); + if (fastSeek && isSource) { + alSourcei(current->source, AL_SAMPLE_OFFSET, position - current->skipStart); + if (!checkCurrentALError(type)) return; + alSourcef(current->source, AL_GAIN, 1. * suppressGain); + if (!checkCurrentALError(type)) return; + updateCurrentStarted(type, position - current->skipStart); + } else { + setStoppedState(current); + if (isSource) alSourceStop(current->source); + } + switch (current->state) { + case AudioPlayerPausing: + case AudioPlayerPaused: + case AudioPlayerPausedAtEnd: { + if (current->state == AudioPlayerPausedAtEnd) { + current->state = AudioPlayerPaused; + } + lock.unlock(); + return pauseresume(type, true); + } break; + case AudioPlayerStarting: + case AudioPlayerResuming: + case AudioPlayerPlaying: + current->state = AudioPlayerPausing; + updateCurrentStarted(type); + if (type == OverviewAudios) emit unsuppressSong(); + break; + case AudioPlayerFinishing: + case AudioPlayerStopped: + case AudioPlayerStoppedAtEnd: + case AudioPlayerStoppedAtError: + case AudioPlayerStoppedAtStart: + lock.unlock(); + switch (type) { + case OverviewAudios: if (audio) return play(audio, position); + case OverviewDocuments: if (song) return play(song, position); + } + } + emit faderOnTimer(); +} + +void AudioPlayer::stop(MediaOverviewType type) { + fadedStop(type); + switch (type) { + case OverviewAudios: if (_audioData[_audioCurrent].audio) emit updated(_audioData[_audioCurrent].audio); break; + case OverviewDocuments: if (_songData[_songCurrent].song) emit updated(_songData[_songCurrent].song); break; + } +} + void AudioPlayer::currentState(AudioMsgId *audio, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { QMutexLocker lock(&playerMutex); AudioMsg *current = &_audioData[_audioCurrent]; @@ -553,17 +666,22 @@ void AudioPlayer::currentState(Msg *current, AudioPlayerState *state, int64 *pos if (frequency) *frequency = current->frequency; } +void AudioPlayer::setStoppedState(Msg *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) { - _audioData[_audioCurrent].state = AudioPlayerStopped; + setStoppedState(&_audioData[_audioCurrent]); } } void AudioPlayer::clearStoppedAtStart(const SongMsgId &song) { QMutexLocker lock(&playerMutex); if (_songData[_songCurrent].song == song && _songData[_songCurrent].state == AudioPlayerStoppedAtStart) { - _songData[_songCurrent].state = AudioPlayerStopped; + setStoppedState(&_songData[_songCurrent]); } } @@ -676,7 +794,7 @@ void AudioPlayerFader::onTimer() { for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { AudioPlayer::AudioMsg &m(voice->_audioData[i]); - if (m.state == AudioPlayerStopped || m.state == AudioPlayerStoppedAtStart || m.state == AudioPlayerPaused || !m.source) continue; + if ((m.state & AudioPlayerStoppedMask) || m.state == AudioPlayerPaused || !m.source) continue; int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressAllGain, suppressAudioChanged); if (emitSignals & EmitError) emit error(m.audio); @@ -687,14 +805,15 @@ void AudioPlayerFader::onTimer() { for (int32 i = 0; i < AudioSongSimultaneously; ++i) { AudioPlayer::SongMsg &m(voice->_songData[i]); - if (m.state == AudioPlayerStopped || m.state == AudioPlayerStoppedAtStart || m.state == AudioPlayerPaused || !m.source) continue; + if ((m.state & AudioPlayerStoppedMask) || m.state == AudioPlayerPaused || !m.source) continue; - int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressSongGain, suppressSongChanged); + 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) { if (!hasPlaying) { @@ -724,11 +843,9 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, ALint pos = 0; ALint state = AL_INITIAL; alGetSourcei(m->source, AL_SAMPLE_OFFSET, &pos); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } alGetSourcei(m->source, AL_SOURCE_STATE, &state); - if (!_checkALError()) { - m->state = AudioPlayerStopped; - return EmitError; - } + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } int32 emitSignals = 0; switch (m->state) { @@ -746,17 +863,33 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, if (state != AL_PLAYING) { fading = false; if (m->source) { - alSourcef(m->source, AL_GAIN, 1); alSourceStop(m->source); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } + alSourcef(m->source, AL_GAIN, 1); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } + } + if (m->state == AudioPlayerPausing) { + m->state = AudioPlayerPausedAtEnd; + } else { + setStoppedState(m, AudioPlayerStoppedAtEnd); } - m->state = AudioPlayerStopped; emitSignals |= EmitStopped; } else if (1000 * (pos + m->skipStart - m->started) >= AudioFadeDuration * m->frequency) { fading = false; alSourcef(m->source, AL_GAIN, 1. * suppressGain); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } switch (m->state) { - case AudioPlayerFinishing: alSourceStop(m->source); m->state = AudioPlayerStopped; break; - case AudioPlayerPausing: alSourcePause(m->source); m->state = AudioPlayerPaused; break; + case AudioPlayerFinishing: + alSourceStop(m->source); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } + setStoppedState(m); + state = AL_STOPPED; + break; + case AudioPlayerPausing: + alSourcePause(m->source); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } + m->state = AudioPlayerPaused; + break; case AudioPlayerStarting: case AudioPlayerResuming: m->state = AudioPlayerPlaying; @@ -769,18 +902,22 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, newGain = 1. - newGain; } alSourcef(m->source, AL_GAIN, newGain * suppressGain); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } } } else if (playing && (state == AL_PLAYING || !m->loading)) { if (state != AL_PLAYING) { playing = false; if (m->source) { alSourceStop(m->source); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } alSourcef(m->source, AL_GAIN, 1); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } } - m->state = AudioPlayerStopped; + setStoppedState(m, AudioPlayerStoppedAtEnd); emitSignals |= EmitStopped; } else if (suppressGainChanged) { alSourcef(m->source, AL_GAIN, suppressGain); + if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; } } } if (state == AL_PLAYING && pos + m->skipStart - m->position >= AudioCheckPositionDelta) { @@ -799,6 +936,11 @@ int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, return emitSignals; } +void AudioPlayerFader::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state) { + m->state = state; + m->position = 0; +} + void AudioPlayerFader::onPauseTimer() { QMutexLocker lock(&_pauseMutex); if (_pauseFlag) { @@ -838,6 +980,11 @@ void AudioPlayerFader::onSuppressAll() { onTimer(); } +void AudioPlayerFader::onSongVolumeChanged() { + _songVolumeChanged = true; + onTimer(); +} + void AudioPlayerFader::resumeDevice() { QMutexLocker lock(&_pauseMutex); _pauseFlag = false; @@ -859,7 +1006,7 @@ public: return this->fname == fname && this->data.size() == data.size(); } - virtual bool open() = 0; + virtual bool open(qint64 position = 0) = 0; virtual int64 duration() = 0; virtual int32 frequency() = 0; virtual int32 format() = 0; @@ -904,7 +1051,7 @@ public: frame = av_frame_alloc(); } - bool open() { + bool open(qint64 position = 0) { if (!AudioPlayerLoader::openFile()) { return false; } @@ -950,7 +1097,7 @@ public: } freq = fmtContext->streams[streamId]->codec->sample_rate; - len = (fmtContext->streams[streamId]->duration * freq) / fmtContext->streams[streamId]->time_base.den; + len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; uint64_t layout = fmtContext->streams[streamId]->codec->channel_layout; inputFormat = fmtContext->streams[streamId]->codec->sample_fmt; switch (layout) { @@ -1016,6 +1163,16 @@ public: 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; } @@ -1180,18 +1337,18 @@ AudioPlayerLoaders::~AudioPlayerLoaders() { void AudioPlayerLoaders::onInit() { } -void AudioPlayerLoaders::onStart(const AudioMsgId &audio) { +void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { _audio = AudioMsgId(); delete _audioLoader; _audioLoader = 0; - onLoad(audio); + loadData(OverviewAudios, static_cast(&audio), position); } -void AudioPlayerLoaders::onStart(const SongMsgId &song) { +void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) { _song = SongMsgId(); delete _songLoader; _songLoader = 0; - onLoad(song); + loadData(OverviewDocuments, static_cast(&song), position); } void AudioPlayerLoaders::clear(MediaOverviewType type) { @@ -1201,6 +1358,11 @@ void AudioPlayerLoaders::clear(MediaOverviewType type) { } } +void AudioPlayerLoaders::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state) { + m->state = state; + m->position = 0; +} + void AudioPlayerLoaders::emitError(MediaOverviewType type) { switch (type) { case OverviewAudios: emit error(clearAudio()); break; @@ -1225,16 +1387,16 @@ SongMsgId AudioPlayerLoaders::clearSong() { } void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { - loadData(OverviewAudios, static_cast(&audio)); + loadData(OverviewAudios, static_cast(&audio), 0); } void AudioPlayerLoaders::onLoad(const SongMsgId &song) { - loadData(OverviewDocuments, static_cast(&song)); + loadData(OverviewDocuments, static_cast(&song), 0); } -void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId) { +void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qint64 position) { SetupError err = SetupNoErrorStarted; - AudioPlayerLoader *l = setupLoader(type, objId, err); + AudioPlayerLoader *l = setupLoader(type, objId, err, position); if (!l) { if (err == SetupErrorAtStart) { emitError(type); @@ -1277,19 +1439,22 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId) { } 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); - alSourcef(m->source, AL_GAIN, 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()) { - m->state = AudioPlayerStopped; + setStoppedState(m, AudioPlayerStoppedAtError); emitError(type); return; } @@ -1307,7 +1472,7 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId) { m->nextBuffer = (m->nextBuffer + 1) % 3; if (!_checkALError()) { - m->state = AudioPlayerStopped; + setStoppedState(m, AudioPlayerStoppedAtError); emitError(type); return; } @@ -1326,18 +1491,34 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId) { if (_checkALError()) { if (state != AL_PLAYING) { audioPlayer()->resumeDevice(); + switch (type) { case OverviewAudios: alSourcef(m->source, AL_GAIN, suppressAllGain); break; - case OverviewDocuments: alSourcef(m->source, AL_GAIN, suppressSongGain); break; + case OverviewDocuments: 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(MediaOverviewType type, const void *objId, SetupError &err) { +AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const void *objId, SetupError &err, qint64 position) { err = SetupErrorAtStart; QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); @@ -1410,7 +1591,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const *l = new FFMpegLoader(m->fname, m->data); int ret; - if (!(*l)->open()) { + if (!(*l)->open(position)) { m->state = AudioPlayerStoppedAtStart; return 0; } @@ -1422,10 +1603,6 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const m->duration = duration; m->frequency = (*l)->frequency(); if (!m->frequency) m->frequency = AudioVoiceMsgFrequency; - m->skipStart = 0; - m->skipEnd = duration; - m->position = 0; - m->started = 0; err = SetupNoErrorStarted; } else { if (!m->skipEnd) { @@ -1985,7 +2162,7 @@ public: _opened(false) { } - bool open() { + bool open(qint64 position = 0) { if (!AudioPlayerLoader::openFile()) { return false; } diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index f28a21d5e..323613c72 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -25,14 +25,19 @@ void audioPlayNotify(); void audioFinish(); enum AudioPlayerState { - AudioPlayerStopped, - AudioPlayerStoppedAtStart, - AudioPlayerStarting, - AudioPlayerPlaying, - AudioPlayerFinishing, - AudioPlayerPausing, - AudioPlayerPaused, - AudioPlayerResuming, + AudioPlayerStopped = 0x01, + AudioPlayerStoppedAtEnd = 0x02, + AudioPlayerStoppedAtError = 0x03, + AudioPlayerStoppedAtStart = 0x04, + AudioPlayerStoppedMask = 0x07, + + AudioPlayerStarting = 0x08, + AudioPlayerPlaying = 0x10, + AudioPlayerFinishing = 0x18, + AudioPlayerPausing = 0x20, + AudioPlayerPaused = 0x28, + AudioPlayerPausedAtEnd = 0x30, + AudioPlayerResuming = 0x38, }; class AudioPlayerFader; @@ -45,9 +50,11 @@ public: AudioPlayer(); - void play(const AudioMsgId &audio); - void play(const SongMsgId &song); - void pauseresume(MediaOverviewType type); + 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 == OverviewDocuments + void stop(MediaOverviewType type); 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); @@ -75,21 +82,28 @@ signals: void stopped(const AudioMsgId &audio); void stopped(const SongMsgId &song); - void loaderOnStart(const AudioMsgId &audio); - void loaderOnStart(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(); + void suppressSong(); void unsuppressSong(); void suppressAll(); + void songVolumeChanged(); + private: - bool startedOther(MediaOverviewType type, bool &fadedStart); + bool fadedStop(MediaOverviewType type, bool *fadedStart = 0); bool updateCurrentStarted(MediaOverviewType type, int32 pos = -1); + bool checkCurrentALError(MediaOverviewType type); struct Msg { Msg() : position(0), duration(0), frequency(AudioVoiceMsgFrequency), skipStart(0), skipEnd(0), loading(0), started(0), @@ -124,6 +138,7 @@ private: }; void currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); + void setStoppedState(Msg *current, AudioPlayerState state = AudioPlayerStopped); int32 _audioCurrent; AudioMsg _audioData[AudioVoiceMsgSimultaneously]; @@ -210,6 +225,7 @@ public slots: void onSuppressSong(); void onUnsuppressSong(); void onSuppressAll(); + void onSongVolumeChanged(); private: @@ -220,12 +236,13 @@ private: EmitNeedToPreload = 0x08, }; int32 updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); + void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped); QTimer _timer, _pauseTimer; QMutex _pauseMutex; bool _pauseFlag, _paused; - bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim; + bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged; anim::fvalue _suppressAllGain, _suppressSongGain; uint64 _suppressAllStart, _suppressSongStart; @@ -250,8 +267,8 @@ public slots: void onInit(); - void onStart(const AudioMsgId &audio); - void onStart(const SongMsgId &audio); + void onStart(const AudioMsgId &audio, qint64 position); + void onStart(const SongMsgId &audio, qint64 position); void onLoad(const AudioMsgId &audio); void onLoad(const SongMsgId &audio); @@ -269,6 +286,7 @@ private: void emitError(MediaOverviewType type); void clear(MediaOverviewType type); + void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped); AudioMsgId clearAudio(); SongMsgId clearSong(); @@ -278,8 +296,8 @@ private: SetupErrorLoadedFull = 2, SetupNoErrorStarted = 3, }; - void loadData(MediaOverviewType type, const void *objId); - AudioPlayerLoader *setupLoader(MediaOverviewType type, const void *objId, SetupError &err); + 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); }; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 31c3d0965..e887ecb72 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -87,7 +87,7 @@ enum { AudioVoiceMsgSimultaneously = 4, AudioSongSimultaneously = 4, AudioCheckPositionTimeout = 100, // 100ms per check audio pos - AudioCheckPositionDelta = 4800, // update position called each 4800 samples + AudioCheckPositionDelta = 2400, // update position called each 2400 samples AudioFadeTimeout = 7, // 7ms AudioFadeDuration = 500, AudioVoiceMsgSkip = 400, // 200ms diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index f81270584..b346267d1 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1929,9 +1929,16 @@ void DialogsWidget::resizeEvent(QResizeEvent *e) { _addContact.move(w - _addContact.width() - st::dlgPaddingHor, _filter.y()); _cancelSearch.move(w - _cancelSearch.width() - st::dlgPaddingHor, _filter.y()); scroll.move(0, _filter.height() + 2 * st::dlgFilterPadding); + + int32 addToY = App::main() ? App::main()->contentScrollAddToY() : 0; + int32 newScrollY = scroll.scrollTop() + addToY; scroll.resize(w, height() - _filter.y() - _filter.height() - st::dlgFilterPadding - st::dlgPaddingVer); list.resize(w, list.height()); - onListScroll(); + if (addToY) { + scroll.scrollToY(newScrollY); + } else { + onListScroll(); + } } void DialogsWidget::keyPressEvent(QKeyEvent *e) { diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index cf41f42c3..1b755bd0d 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -2611,6 +2611,15 @@ bool MentionsInner::moveSel(int direction) { } bool MentionsInner::select() { + QString sel = getSelected(); + if (!sel.isEmpty()) { + emit chosen(sel); + return true; + } + return false; +} + +QString MentionsInner::getSelected() const { int32 maxSel = (_rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size()); if (_sel >= 0 && _sel < maxSel) { QString result; @@ -2628,10 +2637,9 @@ bool MentionsInner::select() { result = '/' + command.command; } } - emit chosen(result); - return true; + return result; } - return false; + return QString(); } void MentionsInner::mousePressEvent(QMouseEvent *e) { @@ -2991,6 +2999,10 @@ int32 MentionsDropdown::innerBottom() { return _scroll.scrollTop() + _scroll.height(); } +QString MentionsDropdown::getSelected() const { + return _inner.getSelected(); +} + bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) { if (isHidden()) return QWidget::eventFilter(obj, e); if (e->type() == QEvent::KeyPress) { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 7335a17ff..7a43c6b1f 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -503,6 +503,8 @@ public: bool moveSel(int direction); bool select(); + QString getSelected() const; + signals: void chosen(QString mentionOrHashtag); @@ -552,6 +554,7 @@ public: int32 innerBottom(); bool eventFilter(QObject *obj, QEvent *e); + QString getSelected() const; ~MentionsDropdown(); diff --git a/Telegram/SourceFiles/gui/style_core.h b/Telegram/SourceFiles/gui/style_core.h index 06157981a..4421091f7 100644 --- a/Telegram/SourceFiles/gui/style_core.h +++ b/Telegram/SourceFiles/gui/style_core.h @@ -26,8 +26,17 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include #include +inline QPoint rtlpoint(int x, int y, int outerw) { + return QPoint(rtl() ? (outerw - x) : x, y); +} +inline QPoint rtlpoint(const QPoint &p, int outerw) { + return rtl() ? QPoint(outerw - p.x(), p.y()) : p; +} inline QRect rtlrect(int x, int y, int w, int h, int outerw) { - return rtl() ? QRect(outerw - x - w, y, w, h) : QRect(x, y, w, h); + return QRect(rtl() ? (outerw - x - w) : x, y, w, h); +} +inline QRect rtlrect(const QRect &r, int outerw) { + return rtl() ? QRect(outerw - r.x() - r.width(), r.y(), r.width(), r.height()) : r; } inline QRect centerrect(const QRect &inRect, const QRect &rect) { return QRect(inRect.x() + (inRect.width() - rect.width()) / 2, inRect.y() + (inRect.height() - rect.height()) / 2, rect.width(), rect.height()); diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index d1de1358f..4b03a03a6 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org namespace { - const QRegularExpression _reDomain(QString::fromUtf8("(?_font; int flags = block->flags(); - if (!flags && block->lnkIndex()) { + if (block->lnkIndex()) { const TextLinkPtr &l(_t->_links.at(block->lnkIndex() - 1)); if (l == _overLnk) { if (l == _downLnk || !_downLnk) { - flags = _textStyle->lnkOverFlags->flags(); + newFont = _textStyle->lnkOverFlags; } else { - flags = _textStyle->lnkFlags->flags(); + newFont = _textStyle->lnkFlags; } } else { - flags = _textStyle->lnkFlags->flags(); + newFont = _textStyle->lnkFlags; } + } else { + flags = block->flags(); + if (flags & TextBlockBold) newFont = newFont->bold(); + if (flags & TextBlockItalic) newFont = newFont->italic(); + if (flags & TextBlockUnderline) newFont = newFont->underline(); } - if (flags & TextBlockBold) newFont = newFont->bold(); - if (flags & TextBlockItalic) newFont = newFont->italic(); - if (flags & TextBlockUnderline) newFont = newFont->underline(); if (newFont != _f) { _f = newFont; _e->fnt = _f->f; diff --git a/Telegram/SourceFiles/gui/twidget.h b/Telegram/SourceFiles/gui/twidget.h index c1359f7f5..36da53381 100644 --- a/Telegram/SourceFiles/gui/twidget.h +++ b/Telegram/SourceFiles/gui/twidget.h @@ -17,20 +17,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -class Widget : public QWidget { -public: - - Widget(QWidget *parent = 0) : QWidget(parent) { - } - void moveToLeft(int x, int y, int outerw) { - move(rtl() ? (outerw - x - width()) : x, y); - } - void moveToRight(int x, int y, int outerw) { - move(rtl() ? x : (outerw - x - width()), y); - } - -}; - namespace App { const QPixmap &sprite(); } @@ -119,14 +105,20 @@ public: void drawSpriteCenter(const QRect &in, const style::sprite &sprite) { return drawPixmap(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), App::sprite(), sprite); } + void drawSpriteCenterLeft(const QRect &in, int outerw, const style::sprite &sprite) { + return drawPixmapLeft(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), outerw, App::sprite(), sprite); + } + void drawSpriteCenterRight(const QRect &in, int outerw, const style::sprite &sprite) { + return drawPixmapRight(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), outerw, App::sprite(), sprite); + } }; -class TWidget : public Widget { +class TWidget : public QWidget { Q_OBJECT public: - TWidget(QWidget *parent = 0) : Widget(parent) { + TWidget(QWidget *parent = 0) : QWidget(parent) { } TWidget *tparent() { return qobject_cast(parentWidget()); @@ -140,6 +132,27 @@ public: virtual void enterFromChildEvent(QEvent *e) { // e -- from leaveEvent() of child TWidget } + void moveToLeft(int x, int y, int outerw) { + move(rtl() ? (outerw - x - width()) : x, y); + } + void moveToRight(int x, int y, int outerw) { + move(rtl() ? x : (outerw - x - width()), y); + } + QPoint myrtlpoint(int x, int y) const { + return rtlpoint(x, y, width()); + } + QPoint myrtlpoint(const QPoint p) const { + return rtlpoint(p, width()); + } + QRect myrtlrect(int x, int y, int w, int h) const { + return rtlrect(x, y, w, h, width()); + } + QRect myrtlrect(const QRect &r) { + return rtlrect(r, width()); + } + void rtlupdate(const QRect &r) { + update(myrtlrect(r)); + } bool event(QEvent *e) { return QWidget::event(e); } @@ -149,12 +162,12 @@ protected: void enterEvent(QEvent *e) { TWidget *p(tparent()); if (p) p->leaveToChildEvent(e); - return Widget::enterEvent(e); + return QWidget::enterEvent(e); } void leaveEvent(QEvent *e) { TWidget *p(tparent()); if (p) p->enterFromChildEvent(e); - return Widget::leaveEvent(e); + return QWidget::leaveEvent(e); } private: diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 2e2b5d79c..c272b0fd1 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -372,6 +372,28 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { return changed; } +void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { + if (_overviewIds[type].isEmpty()) return; + + History::MediaOverviewIds::iterator i = _overviewIds[type].find(msgId); + if (i == _overviewIds[type].cend()) return; + + _overviewIds[type].erase(i); + for (History::MediaOverview::iterator i = _overview[type].begin(), e = _overview[type].end(); i != e; ++i) { + if ((*i) == msgId) { + _overview[type].erase(i); + if (_overviewCount[type] > 0) { + --_overviewCount[type]; + if (!_overviewCount[type]) { + _overviewCount[type] = -1; + } + } + break; + } + } + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); +} + bool DialogsList::del(const PeerId &peerId, DialogRow *replacedBy) { RowByPeer::iterator i = rowByPeer.find(peerId); if (i == rowByPeer.cend()) return false; @@ -803,13 +825,23 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem * } HistoryMedia *media = adding->getMedia(true); if (media) { - MediaOverviewType t = mediaToOverviewType(media->type()); + HistoryMediaType mt = media->type(); + MediaOverviewType t = mediaToOverviewType(mt); if (t != OverviewCount) { if (_overviewIds[t].constFind(adding->id) == _overviewIds[t].cend()) { _overview[t].push_back(adding->id); _overviewIds[t].insert(adding->id, NullType()); if (_overviewCount[t] > 0) ++_overviewCount[t]; - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer); + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, t); + } + if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { + t = OverviewAudioDocuments; + if (_overviewIds[t].constFind(adding->id) == _overviewIds[t].cend()) { + _overview[t].push_back(adding->id); + _overviewIds[t].insert(adding->id, NullType()); + if (_overviewCount[t] > 0) ++_overviewCount[t]; + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, t); + } } } } @@ -933,16 +965,27 @@ void History::addToFront(const QVector &slice) { ++skip; if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors + int32 mask = 0; QList *lastAuthors = peer->chat ? &(peer->asChat()->lastAuthors) : 0; for (int32 i = block->size(); i > 0; --i) { HistoryItem *item = (*block)[i - 1]; HistoryMedia *media = item->getMedia(true); if (media) { - MediaOverviewType t = mediaToOverviewType(media->type()); + HistoryMediaType mt = media->type(); + MediaOverviewType t = mediaToOverviewType(mt); if (t != OverviewCount) { if (_overviewIds[t].constFind(item->id) == _overviewIds[t].cend()) { _overview[t].push_front(item->id); _overviewIds[t].insert(item->id, NullType()); + mask |= (1 << t); + } + if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { + t = OverviewAudioDocuments; + if (_overviewIds[t].constFind(item->id) == _overviewIds[t].cend()) { + _overview[t].push_front(item->id); + _overviewIds[t].insert(item->id, NullType()); + mask |= (1 << t); + } } } } @@ -987,7 +1030,9 @@ void History::addToFront(const QVector &slice) { } } } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer); + for (int32 t = 0; t < OverviewCount; ++t) { + if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); + } } } else { delete block; @@ -1062,10 +1107,14 @@ void History::addToBack(const QVector &slice) { delete block; } if (!wasLoadedAtBottom && loadedAtBottom()) { // add all loaded photos to overview + int32 mask = 0; for (int32 i = 0; i < OverviewCount; ++i) { if (_overviewCount[i] == 0) continue; // all loaded - _overview[i].clear(); - _overviewIds[i].clear(); + if (!_overview[i].isEmpty() || !_overviewIds[i].isEmpty()) { + _overview[i].clear(); + _overviewIds[i].clear(); + mask |= (1 << i); + } } for (int32 i = 0; i < size(); ++i) { HistoryBlock *b = (*this)[i]; @@ -1073,15 +1122,29 @@ void History::addToBack(const QVector &slice) { HistoryItem *item = (*b)[j]; HistoryMedia *media = item->getMedia(true); if (media) { - MediaOverviewType t = mediaToOverviewType(media->type()); - if (t != OverviewCount && _overviewCount[t] != 0) { - _overview[t].push_back(item->id); - _overviewIds[t].insert(item->id, NullType()); + HistoryMediaType mt = media->type(); + MediaOverviewType t = mediaToOverviewType(mt); + if (t != OverviewCount) { + if (_overviewCount[t] != 0) { + _overview[t].push_back(item->id); + _overviewIds[t].insert(item->id, NullType()); + mask |= (1 << t); + } + if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { + t = OverviewAudioDocuments; + if (_overviewCount[t] != 0) { + _overview[t].push_back(item->id); + _overviewIds[t].insert(item->id, NullType()); + mask |= (1 << t); + } + } } } } } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer); + for (int32 t = 0; t < OverviewCount; ++t) { + if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); + } } if (wasEmpty && !isEmpty()) { HistoryBlock *dateBlock = new HistoryBlock(this); @@ -1328,11 +1391,13 @@ void History::clear(bool leaveItems) { showFrom = 0; } for (int32 i = 0; i < OverviewCount; ++i) { - if (_overviewCount[i] == 0) _overviewCount[i] = _overview[i].size(); - _overview[i].clear(); - _overviewIds[i].clear(); + if (!_overview[i].isEmpty() || !_overviewIds[i].isEmpty()) { + if (_overviewCount[i] == 0) _overviewCount[i] = _overview[i].size(); + _overview[i].clear(); + _overviewIds[i].clear(); + if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(i)); + } } - if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer); for (Parent::const_iterator i = cbegin(), e = cend(); i != e; ++i) { if (leaveItems) { (*i)->clear(true); @@ -1580,23 +1645,10 @@ void HistoryItem::destroy() { } HistoryMedia *m = getMedia(true); MediaOverviewType t = m ? mediaToOverviewType(m->type()) : OverviewCount; - if (t != OverviewCount && !history()->_overviewIds[t].isEmpty()) { - History::MediaOverviewIds::iterator i = history()->_overviewIds[t].find(id); - if (i != history()->_overviewIds[t].cend()) { - history()->_overviewIds[t].erase(i); - for (History::MediaOverview::iterator i = history()->_overview[t].begin(), e = history()->_overview[t].end(); i != e; ++i) { - if ((*i) == id) { - history()->_overview[t].erase(i); - if (history()->_overviewCount[t] > 0) { - --history()->_overviewCount[t]; - if (!history()->_overviewCount[t]) { - history()->_overviewCount[t] = -1; - } - } - break; - } - } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(history()->peer); + if (t != OverviewCount) { + history()->eraseFromOverview(t, id); + if (m->type() == MediaTypeDocument && static_cast(m)->document()->song()) { + history()->eraseFromOverview(OverviewAudioDocuments, id); } } delete this; @@ -2339,8 +2391,9 @@ void HistoryVideo::draw(QPainter &p, const HistoryItem *parent, bool selected, i p.setPen(status->p); if (data->loader) { - if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { - _dldDone = data->loader->currentOffset(); + int32 offset = data->loader->currentOffset(); + if (_dldTextCache.isEmpty() || _dldDone != offset) { + _dldDone = offset; _dldTextCache = formatDownloadText(_dldDone, data->size); } statusText = _dldTextCache; @@ -2563,7 +2616,7 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; } else if (already || hasdata) { bool showPause = false; - if (playing.msgId == parent->id && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) { + if (playing.msgId == parent->id && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { @@ -2572,8 +2625,9 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i img = out ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); } else { if (data->loader) { - if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { - _dldDone = data->loader->currentOffset(); + int32 offset = data->loader->currentOffset(); + if (_dldTextCache.isEmpty() || _dldDone != offset) { + _dldDone = offset; _dldTextCache = formatDownloadText(_dldDone, data->size); } statusText = _dldTextCache; @@ -2733,7 +2787,7 @@ namespace { SongData *song = document->song(); if (!song || (song->title.isEmpty() && song->performer.isEmpty())) return document->name; if (song->performer.isEmpty()) return song->title; - return song->performer + QString::fromUtf8(" \xe2\x80\x94 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); + return song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); } } @@ -2905,17 +2959,19 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; } else if (already || hasdata) { bool showPause = false; - if (playing.msgId == parent->id && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) { + if (playing.msgId == parent->id && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { statusText = formatDurationText(data->song()->duration); } + if (!showPause && playing.msgId == parent->id && App::main() && App::main()->player()->seekingSong(playing)) showPause = true; img = out ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); } else { if (data->loader) { - if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { - _dldDone = data->loader->currentOffset(); + int32 offset = data->loader->currentOffset(); + if (_dldTextCache.isEmpty() || _dldDone != offset) { + _dldDone = offset; _dldTextCache = formatDownloadText(_dldDone, data->size); } statusText = _dldTextCache; @@ -2936,8 +2992,9 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected } statusText = _uplTextCache; } else if (data->loader) { - if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { - _dldDone = data->loader->currentOffset(); + int32 offset = data->loader->currentOffset(); + if (_dldTextCache.isEmpty() || _dldDone != offset) { + _dldDone = offset; _dldTextCache = formatDownloadText(_dldDone, data->size); } statusText = _dldTextCache; @@ -3147,8 +3204,8 @@ HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() , pixw(1), pixh(1), data(document), lastw(0) { data->thumb->load(); - if (!data->sticker->alt.isEmpty()) { - _emoji = data->sticker->alt; + if (!data->sticker()->alt.isEmpty()) { + _emoji = data->sticker()->alt; } } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index c952e5f73..f13c0f922 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -106,6 +106,7 @@ enum MediaOverviewType { OverviewVideos, OverviewDocuments, OverviewAudios, + OverviewAudioDocuments, OverviewCount }; @@ -127,6 +128,7 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { case OverviewVideos: return MTP_inputMessagesFilterVideo(); case OverviewDocuments: return MTP_inputMessagesFilterDocument(); case OverviewAudios: return MTP_inputMessagesFilterAudio(); + case OverviewAudioDocuments: return MTP_inputMessagesFilterAudioDocuments(); default: type = OverviewCount; break; } return MTPMessagesFilter(); @@ -286,6 +288,8 @@ struct History : public QList { MediaOverviewIds _overviewIds[OverviewCount]; int32 _overviewCount[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded + void eraseFromOverview(MediaOverviewType type, MsgId msgId); + static const int32 ScrollMax = INT_MAX; }; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index e9e0344b1..6570c1da7 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -2225,6 +2225,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel())); + connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed())); connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize())); connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); connect(&imageLoader, SIGNAL(imageReady()), this, SLOT(onPhotoReady())); @@ -3501,20 +3502,7 @@ bool HistoryWidget::showStep(float64 ms) { _bgAnimCache = _animCache = _animTopBarCache = _bgAnimTopBarCache = QPixmap(); App::main()->topBar()->stopAnim(); App::main()->topBar()->enableShadow(); - if (hist && hist->readyForWork()) { - _scroll.show(); - if (hist->lastScrollTop == History::ScrollMax) { - _scroll.scrollToY(hist->lastScrollTop); - } - - onListScroll(); - } - if (hist) { - if (!_histInited) checkUnreadLoaded(); - if (_histNeedUpdate) updateListSize(); - } - updateControlsVisibility(); - App::wnd()->setInnerFocus(); + doneShow(); } else { a_bgCoord.update(dt1, st::introHideFunc); a_bgAlpha.update(dt1, st::introAlphaHideFunc); @@ -3526,6 +3514,23 @@ bool HistoryWidget::showStep(float64 ms) { return res; } +void HistoryWidget::doneShow() { + if (hist && hist->readyForWork()) { + _scroll.show(); + if (hist->lastScrollTop == History::ScrollMax) { + _scroll.scrollToY(hist->lastScrollTop); + } + + onListScroll(); + } + if (hist) { + if (!_histInited) checkUnreadLoaded(); + if (_histNeedUpdate) updateListSize(); + } + updateControlsVisibility(); + App::wnd()->setInnerFocus(); +} + void HistoryWidget::animStop() { if (!_showAnim.animating()) return; _showAnim.stop(); @@ -4502,7 +4507,7 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _attachPhoto.move(_attachDocument.x(), _attachDocument.y()); _replyForwardPreviewCancel.move(width() - _replyForwardPreviewCancel.width(), _field.y() - st::sendPadding - _replyForwardPreviewCancel.height()); - updateListSize(); + updateListSize(App::main() ? App::main()->contentScrollAddToY() : 0); bool kbShowShown = hist && !_kbShown && _keyboard.hasMarkup(); _field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width() - (kbShowShown ? _kbShow.width() : 0) - (_cmdStartShown ? _cmdStart.width() : 0), _field.height()); @@ -4841,6 +4846,13 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } } +void HistoryWidget::onFieldTabbed() { + QString sel = _attachMention.isHidden() ? QString() : _attachMention.getSelected(); + if (!sel.isEmpty()) { + _field.onMentionHashtagOrBotCommandInsert(sel); + } +} + void HistoryWidget::onStickerSend(DocumentData *sticker) { if (!hist || !sticker) return; @@ -5403,9 +5415,9 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { return; } - bool hasTopBar = !App::main()->topBar()->isHidden(); + bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); QRect fill(0, 0, width(), App::main()->height()); - int fromy = hasTopBar ? (-st::topBarHeight) : 0, x = 0, y = 0; + int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { const QPixmap &pix(*cChatBackground()); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index e5204709c..0dced5131 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -416,6 +416,7 @@ public: void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false); bool showStep(float64 ms); void animStop(); + void doneShow(); QPoint clampMousePosition(QPoint point); @@ -528,6 +529,7 @@ public slots: void onMentionHashtagOrBotCommandInsert(QString str); void onTextChange(); + void onFieldTabbed(); void onStickerSend(DocumentData *sticker); void onVisibleChanged(); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 243a93f44..c0f979b7b 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -1036,6 +1036,14 @@ namespace { cSetDialogLastPath(path); } break; + case dbiSongVolume: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetSongVolume(snap(v / 1e6, 0., 1.)); + } break; + default: LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); return false; @@ -1257,7 +1265,7 @@ namespace { _writeMap(WriteMapFast); } - uint32 size = 11 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()); 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)); @@ -1278,6 +1286,7 @@ namespace { data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); data.stream << quint32(dbiEmojiTab) << qint32(cEmojiTab()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); + data.stream << quint32(dbiSongVolume) << qint32(qRound(cSongVolume() * 1e6)); { RecentEmojisPreload v(cRecentEmojisPreload()); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 635f152fc..c1a80ed29 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -277,7 +277,7 @@ void TopBarWidget::showAll() { resizeEvent(0); return; } - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = App::main() ? App::main()->profilePeer() : 0, *o = App::main() ? App::main()->overviewPeer() : 0; if (p && (p->chat || p->asUser()->contact >= 0)) { if (p->chat) { if (p->asChat()->forbidden) { @@ -324,7 +324,7 @@ void TopBarWidget::showAll() { _mediaType.hide(); } } - if (App::main() && App::main()->historyPeer() && !p && _clearSelection.isHidden() && !cWideMode()) { + if (App::main() && App::main()->historyPeer() && !o && !p && _clearSelection.isHidden() && !cWideMode()) { _info.show(); } else { _info.hide(); @@ -350,9 +350,12 @@ MainWidget *TopBarWidget::main() { return static_cast(parentWidget()); } -MainWidget::MainWidget(Window *window) : QWidget(window), _started(0), failedObjId(0), _toForwardNameVersion(0), _dialogsWidth(st::dlgMinWidth), -dialogs(this), history(this), profile(0), overview(0), _topBar(this), _forwardConfirm(0), hider(0), _mediaType(this), _mediaTypeMask(0), -updGoodPts(0), updLastPts(0), updPtsCount(0), updDate(0), updQts(-1), updSeq(0), updInited(false), updSkipPtsUpdateLevel(0), _onlineRequest(0), _lastWasOnline(false), _lastSetOnline(0), _isIdle(false), +MainWidget::MainWidget(Window *window) : QWidget(window), +_started(0), failedObjId(0), _toForwardNameVersion(0), _dialogsWidth(st::dlgMinWidth), +dialogs(this), history(this), profile(0), overview(0), _player(this), _topBar(this), +_forwardConfirm(0), hider(0), _contentScrollAddToY(0), _playerHeight(0), _mediaType(this), _mediaTypeMask(0), +updGoodPts(0), updLastPts(0), updPtsCount(0), updDate(0), updQts(-1), updSeq(0), updInited(false), updSkipPtsUpdateLevel(0), +_onlineRequest(0), _lastWasOnline(false), _lastSetOnline(0), _isIdle(false), _failDifferenceTimeout(1), _lastUpdateTime(0), _cachedX(0), _cachedY(0), _background(0), _api(new ApiWrap(this)) { setGeometry(QRect(0, st::titleHeight, App::wnd()->width(), App::wnd()->height() - st::titleHeight)); @@ -397,7 +400,10 @@ _failDifferenceTimeout(1), _lastUpdateTime(0), _cachedX(0), _cachedY(0), _backgr App::wnd()->getTitle()->updateBackButton(); _topBar.hide(); + _player.hide(); + _topBar.raise(); + _player.raise(); dialogs.raise(); _mediaType.raise(); @@ -613,7 +619,7 @@ void MainWidget::noHider(HistoryHider *destroyed) { onPeerShown(history.peer()); if (profile || overview || (history.peer() && history.peer()->id)) { dialogs.enableShadow(false); - QPixmap animCache = myGrab(this, QRect(0, st::topBarHeight, _dialogsWidth, height() - st::topBarHeight)), + QPixmap animCache = myGrab(this, QRect(0, _playerHeight + st::topBarHeight, _dialogsWidth, height() - _playerHeight - st::topBarHeight)), animTopBarCache = myGrab(this, QRect(_topBar.x(), _topBar.y(), _topBar.width(), st::topBarHeight)); dialogs.enableShadow(); _topBar.enableShadow(); @@ -651,7 +657,7 @@ void MainWidget::hiderLayer(HistoryHider *h) { hider->hide(); dialogs.enableShadow(false); - QPixmap animCache = myGrab(this, QRect(0, 0, _dialogsWidth, height())); + QPixmap animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); dialogs.enableShadow(); _topBar.enableShadow(); @@ -1138,13 +1144,14 @@ void MainWidget::overviewPreloaded(PeerData *peer, const MTPmessages_Messages &r } } - mediaOverviewUpdated(peer); + mediaOverviewUpdated(peer, type); } -void MainWidget::mediaOverviewUpdated(PeerData *peer) { - if (profile) profile->mediaOverviewUpdated(peer); +void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { + if (profile) profile->mediaOverviewUpdated(peer, type); + if (!_player.isHidden()) _player.mediaOverviewUpdated(peer, type); if (overview && overview->peer() == peer) { - overview->mediaOverviewUpdated(peer); + overview->mediaOverviewUpdated(peer, type); int32 mask = 0; History *h = peer ? App::historyLoaded(peer->id) : 0; @@ -1336,7 +1343,7 @@ void MainWidget::photosLoaded(History *h, const MTPmessages_Messages &msgs, mtpR h->_overview[type].push_front(item->id); } } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(h->peer); + if (App::wnd()) App::wnd()->mediaOverviewUpdated(h->peer, type); } void MainWidget::partWasRead(PeerData *peer, const MTPmessages_AffectedHistory &result) { @@ -1417,7 +1424,7 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) { AudioMsgId playing; AudioPlayerState state = AudioPlayerStopped; audioPlayer()->currentState(&playing, &state); - if (playing.msgId == audio->openOnSaveMsgId && state != AudioPlayerStopped) { + if (playing.msgId == audio->openOnSaveMsgId && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewAudios); } else { audioPlayer()->play(AudioMsgId(audio, audio->openOnSaveMsgId)); @@ -1480,9 +1487,12 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { void MainWidget::documentPlayProgress(const SongMsgId &songId) { SongMsgId playing; - AudioPlayerState state = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &state); - if (playing == songId && state == AudioPlayerStoppedAtStart) { + 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; @@ -1518,11 +1528,33 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { } } + if (playing == songId) { + _player.updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); + + if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (_player.isHidden()) { + _player.clearSelection(); + _player.show(); + _playerHeight = _contentScrollAddToY = _player.height(); + resizeEvent(0); + } + } + } + if (HistoryItem *item = App::histItemById(songId.msgId)) { msgUpdated(item->history()->peer->id, item); } } +void MainWidget::hidePlayer() { + if (!_player.isHidden()) { + _player.hide(); + _contentScrollAddToY = -_player.height(); + _playerHeight = 0; + resizeEvent(0); + } +} + void MainWidget::audioLoadFailed(mtpFileLoader *loader, bool started) { loadFailed(loader, started, SLOT(audioLoadRetry())); AudioData *audio = App::audio(loader->objId()); @@ -1552,10 +1584,12 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { SongMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == item->id && playingState != AudioPlayerStopped) { + if (playing.msgId == item->id && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewDocuments); } else { - audioPlayer()->play(SongMsgId(document, item->id)); + SongMsgId song(document, item->id); + audioPlayer()->play(song); + if (App::main()) App::main()->documentPlayProgress(song); } } else if(document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { QImageReader reader(already); @@ -1589,6 +1623,21 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { } } App::wnd()->documentUpdated(document); + + if (audioPlayer()) { + SongMsgId 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()) { + if (document->loader) { + _player.updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); + } else { + audioPlayer()->play(playing); + } + } + } } void MainWidget::documentLoadFailed(mtpFileLoader *loader, bool started) { @@ -1848,19 +1897,19 @@ void MainWidget::showPeer(quint64 peerId, qint32 msgId, bool back, bool force) { hider = 0; } if (force || !selectingPeer()) { - if (!animating() && ((history.isHidden() && (profile || overview)) || (!cWideMode() && (history.isHidden() || !peerId)))) { + if (!animating() && ((history.isHidden() && history.activePeer() && (profile || overview)) || (!cWideMode() && (history.isHidden() || !peerId)))) { dialogs.enableShadow(false); if (peerId) { _topBar.enableShadow(false); if (cWideMode()) { - animCache = myGrab(this, QRect(_dialogsWidth, st::topBarHeight, width() - _dialogsWidth, height() - st::topBarHeight)); + animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight + st::topBarHeight, width() - _dialogsWidth, height() - _playerHeight - st::topBarHeight)); } else { - animCache = myGrab(this, QRect(0, st::topBarHeight, _dialogsWidth, height() - st::topBarHeight)); + animCache = myGrab(this, QRect(0, _playerHeight + st::topBarHeight, _dialogsWidth, height() - _playerHeight - st::topBarHeight)); } } else if (cWideMode()) { - animCache = myGrab(this, QRect(_dialogsWidth, 0, width() - _dialogsWidth, height())); + animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); } else { - animCache = myGrab(this, QRect(0, 0, _dialogsWidth, height())); + animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } if (peerId || cWideMode()) { animTopBarCache = myGrab(this, QRect(_topBar.x(), _topBar.y(), _topBar.width(), st::topBarHeight)); @@ -1909,6 +1958,8 @@ void MainWidget::showPeer(quint64 peerId, qint32 msgId, bool back, bool force) { history.show(); if (!animCache.isNull()) { history.animShow(animCache, animTopBarCache, back); + } else { + QTimer::singleShot(0, this, SLOT(setInnerFocus())); } } } @@ -1958,8 +2009,12 @@ PeerData *MainWidget::profilePeer() { return profile ? profile->peer() : 0; } +PeerData *MainWidget::overviewPeer() { + return overview ? overview->peer() : 0; +} + bool MainWidget::mediaTypeSwitch() { - if (!overview) return false; + if (!overview || (overview->type() == OverviewAudioDocuments)) return false; for (int32 i = 0; i < OverviewCount; ++i) { if (!(_mediaTypeMask & ~(1 << i))) { @@ -1974,13 +2029,21 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool if (overview && overview->peer() == peer) { if (overview->type() != type) { overview->switchType(type); + } else if (type == OverviewAudioDocuments) { // hack for player + showBackFromStack(); } return; } dialogs.enableShadow(false); _topBar.enableShadow(false); - QPixmap animCache = myGrab(this, history.geometry()), animTopBarCache = myGrab(this, QRect(_topBar.x(), _topBar.y(), _topBar.width(), st::topBarHeight)); + QRect topBarRect = QRect(_topBar.x(), _topBar.y(), _topBar.width(), st::topBarHeight); + QRect historyRect = QRect(history.x(), topBarRect.y() + topBarRect.height(), history.width(), history.y() + history.height() - topBarRect.y() - topBarRect.height()); + QPixmap animCache, animTopBarCache; + if (!animating() && (!cWideMode() || profile || overview || history.peer())) { + animCache = myGrab(this, historyRect); + animTopBarCache = myGrab(this, topBarRect); + } dialogs.enableShadow(); _topBar.enableShadow(); if (!back) { @@ -1988,7 +2051,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _stack.push_back(new StackItemOverview(overview->peer(), overview->type(), overview->lastWidth(), overview->lastScrollTop())); } else if (profile) { _stack.push_back(new StackItemProfile(profile->peer(), profile->lastScrollTop(), profile->allMediaShown())); - } else { + } else if (history.peer()) { _stack.push_back(new StackItemHistory(history.peer(), history.lastWidth(), history.lastScrollTop(), history.replyReturns(), history.kbWasHidden())); } } @@ -2009,12 +2072,19 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _mediaTypeMask = 0; _topBar.show(); resizeEvent(0); - mediaOverviewUpdated(peer); - overview->animShow(animCache, animTopBarCache, back, lastScrollTop); + mediaOverviewUpdated(peer, type); + if (!animCache.isNull()) { + overview->animShow(animCache, animTopBarCache, back, lastScrollTop); + } else { + overview->show(); + overview->activate(); + } history.animStop(); history.showPeer(0, 0, false, true); history.hide(); + if (!cWideMode()) dialogs.hide(); _topBar.raise(); + _player.raise(); dialogs.raise(); _mediaType.raise(); if (hider) hider->raise(); @@ -2060,6 +2130,7 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop, history.showPeer(0, 0, false, true); history.hide(); _topBar.raise(); + _player.raise(); dialogs.raise(); _mediaType.raise(); if (hider) hider->raise(); @@ -2067,7 +2138,16 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop, } void MainWidget::showBackFromStack() { - if (_stack.isEmpty() || selectingPeer()) return; + if (selectingPeer()) return; + if (_stack.isEmpty()) { + if (cWideMode()) { + showPeer(0, 0, false, true); + QTimer::singleShot(0, this, SLOT(setInnerFocus())); + } else { + onShowDialogs(); + } + return; + } StackItem *item = _stack.back(); _stack.pop_back(); if (item->type() == HistoryStackItem) { @@ -2313,19 +2393,26 @@ void MainWidget::resizeEvent(QResizeEvent *e) { if (cWideMode()) { _dialogsWidth = snap((width() * 5) / 14, st::dlgMinWidth, st::dlgMaxWidth); dialogs.setGeometry(0, 0, _dialogsWidth + st::dlgShadow, height()); - _topBar.setGeometry(_dialogsWidth, 0, width() - _dialogsWidth, st::topBarHeight + st::titleShadow); - history.setGeometry(_dialogsWidth, tbh, width() - _dialogsWidth, height() - tbh); + _player.setGeometry(_dialogsWidth, 0, width() - _dialogsWidth, _player.height()); + _topBar.setGeometry(_dialogsWidth, _playerHeight, width() - _dialogsWidth, st::topBarHeight + st::titleShadow); + history.setGeometry(_dialogsWidth, _playerHeight + tbh, width() - _dialogsWidth, height() - _playerHeight - tbh); if (hider) hider->setGeometry(QRect(_dialogsWidth, 0, width() - _dialogsWidth, height())); } else { _dialogsWidth = width(); - dialogs.setGeometry(0, 0, _dialogsWidth + st::dlgShadow, height()); - _topBar.setGeometry(0, 0, _dialogsWidth, st::topBarHeight + st::titleShadow); - history.setGeometry(0, tbh, _dialogsWidth, height() - tbh); + _player.setGeometry(0, 0, _dialogsWidth, _player.height()); + dialogs.setGeometry(0, _playerHeight, _dialogsWidth + st::dlgShadow, height() - _playerHeight); + _topBar.setGeometry(0, _playerHeight, _dialogsWidth, st::topBarHeight + st::titleShadow); + history.setGeometry(0, _playerHeight + tbh, _dialogsWidth, height() - _playerHeight - tbh); if (hider) hider->setGeometry(QRect(0, 0, _dialogsWidth, height())); } - _mediaType.move(width() - _mediaType.width(), st::topBarHeight); + _mediaType.move(width() - _mediaType.width(), _playerHeight + st::topBarHeight); if (profile) profile->setGeometry(history.geometry()); if (overview) overview->setGeometry(history.geometry()); + _contentScrollAddToY = 0; +} + +int32 MainWidget::contentScrollAddToY() const { + return _contentScrollAddToY; } void MainWidget::keyPressEvent(QKeyEvent *e) { @@ -2355,8 +2442,14 @@ void MainWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) { } void MainWidget::topBarShadowParams(int32 &x, float64 &o) { - if (!profile && !overview && dialogs.isHidden()) { - history.topBarShadowParams(x, o); + if (!cWideMode() && dialogs.isHidden()) { + if (profile) { + if (!history.activePeer()) profile->topBarShadowParams(x, o); + } else if (overview) { + if (!history.activePeer()) overview->topBarShadowParams(x, o); + } else { + history.topBarShadowParams(x, o); + } } } @@ -2384,6 +2477,10 @@ TopBarWidget *MainWidget::topBar() { return &_topBar; } +PlayerWidget *MainWidget::player() { + return &_player; +} + void MainWidget::onTopBarClick() { if (profile) { profile->topBarClick(); @@ -3451,7 +3548,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } } App::markPeerUpdated(user); - if (App::wnd()) App::wnd()->mediaOverviewUpdated(user); + if (App::wnd()) App::wnd()->mediaOverviewUpdated(user, OverviewCount); } } break; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 30c14ce49..7a7e6582b 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -21,6 +21,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "historywidget.h" #include "profilewidget.h" #include "overviewwidget.h" +#include "playerwidget.h" #include "apiwrap.h" class Window; @@ -189,6 +190,9 @@ public: void topBarShadowParams(int32 &x, float64 &o); TopBarWidget *topBar(); + PlayerWidget *player(); + int32 contentScrollAddToY() const; + void animShow(const QPixmap &bgAnimCache, bool back = false); bool animStep(float64 ms); @@ -233,6 +237,7 @@ public: PeerData *activePeer(); MsgId activeMsgId(); PeerData *profilePeer(); + PeerData *overviewPeer(); bool mediaTypeSwitch(); void showPeerProfile(PeerData *peer, bool back = false, int32 lastScrollTop = -1, bool allMediaShown = false); void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1); @@ -313,7 +318,7 @@ public: void searchMessages(const QString &query); void preloadOverviews(PeerData *peer); - void mediaOverviewUpdated(PeerData *peer); + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void changingMsgId(HistoryItem *row, MsgId newId); void itemRemoved(HistoryItem *item); void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); @@ -391,6 +396,7 @@ public slots: void documentLoadFailed(mtpFileLoader *loader, bool started); void documentLoadRetry(); void documentPlayProgress(const SongMsgId &songId); + void hidePlayer(); void setInnerFocus(); void dialogsCancelled(); @@ -490,12 +496,16 @@ private: HistoryWidget history; ProfileWidget *profile; OverviewWidget *overview; + PlayerWidget _player; TopBarWidget _topBar; ConfirmBox *_forwardConfirm; // for narrow mode HistoryHider *hider; StackItems _stack; QPixmap profileAnimCache; + int32 _playerHeight; + int32 _contentScrollAddToY; + Dropdown _mediaType; int32 _mediaTypeMask; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 56441c375..2036a644f 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -136,19 +136,19 @@ void MediaView::moveToScreen() { } int32 navSkip = 2 * st::mvControlMargin + st::mvControlSize; - _closeNav = rtlrect(width() - st::mvControlMargin - st::mvControlSize, st::mvControlMargin, st::mvControlSize, st::mvControlSize, width()); + _closeNav = myrtlrect(width() - st::mvControlMargin - st::mvControlSize, st::mvControlMargin, st::mvControlSize, st::mvControlSize); _closeNavIcon = centersprite(_closeNav, st::mvClose); - _leftNav = rtlrect(st::mvControlMargin, navSkip, st::mvControlSize, height() - 2 * navSkip, width()); + _leftNav = myrtlrect(st::mvControlMargin, navSkip, st::mvControlSize, height() - 2 * navSkip); _leftNavIcon = centersprite(_leftNav, st::mvLeft); - _rightNav = rtlrect(width() - st::mvControlMargin - st::mvControlSize, navSkip, st::mvControlSize, height() - 2 * navSkip, width()); + _rightNav = myrtlrect(width() - st::mvControlMargin - st::mvControlSize, navSkip, st::mvControlSize, height() - 2 * navSkip); _rightNavIcon = centersprite(_rightNav, st::mvRight); _saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2); } -void MediaView::mediaOverviewUpdated(PeerData *peer) { +void MediaView::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { if (!_photo && !_doc) return; - if (_history && _history->peer == peer) { + if (_history && _history->peer == peer && type == _overview) { _index = -1; for (int i = 0, l = _history->_overview[_overview].size(); i < l; ++i) { if (_history->_overview[_overview].at(i) == _msgid) { @@ -158,7 +158,7 @@ void MediaView::mediaOverviewUpdated(PeerData *peer) { } updateControls(); preloadData(0); - } else if (_user == peer) { + } else if (_user == peer && type == OverviewCount) { if (!_photo) return; _index = -1; @@ -192,7 +192,7 @@ void MediaView::changingMsgId(HistoryItem *row, MsgId newId) { if (row->id == _msgid) { _msgid = newId; } - mediaOverviewUpdated(row->history()->peer); + mediaOverviewUpdated(row->history()->peer, _overview); } void MediaView::updateDocSize() { @@ -258,9 +258,9 @@ void MediaView::updateControls() { } _saveVisible = ((_photo && _photo->full->loaded()) || (_doc && (!_doc->already(true).isEmpty() || (_current.isNull() && _currentGif.isNull())))); - _saveNav = rtlrect(width() - st::mvIconSize.width() * 2, height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height(), width()); + _saveNav = myrtlrect(width() - st::mvIconSize.width() * 2, height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height()); _saveNavIcon = centersprite(_saveNav, st::mvSave); - _moreNav = rtlrect(width() - st::mvIconSize.width(), height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height(), width()); + _moreNav = myrtlrect(width() - st::mvIconSize.width(), height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height()); _moreNavIcon = centersprite(_moreNav, st::mvMore); QDateTime d(date(_photo ? _photo->date : _doc->date)), dNow(date(unixtime())); @@ -273,11 +273,11 @@ void MediaView::updateControls() { } if (_from) { _fromName.setText(st::mvFont, _from->name); - _nameNav = rtlrect(st::mvTextLeft, height() - st::mvTextTop, qMin(_fromName.maxWidth(), width() / 3), st::mvFont->height, width()); - _dateNav = rtlrect(st::mvTextLeft + _nameNav.width() + st::mvTextSkip, height() - st::mvTextTop, st::mvFont->m.width(_dateText), st::mvFont->height, width()); + _nameNav = myrtlrect(st::mvTextLeft, height() - st::mvTextTop, qMin(_fromName.maxWidth(), width() / 3), st::mvFont->height); + _dateNav = myrtlrect(st::mvTextLeft + _nameNav.width() + st::mvTextSkip, height() - st::mvTextTop, st::mvFont->m.width(_dateText), st::mvFont->height); } else { _nameNav = QRect(); - _dateNav = rtlrect(st::mvTextLeft, height() - st::mvTextTop, st::mvFont->m.width(_dateText), st::mvFont->height, width()); + _dateNav = myrtlrect(st::mvTextLeft, height() - st::mvTextTop, st::mvFont->m.width(_dateText), st::mvFont->height); } updateHeader(); if (_photo) { @@ -349,7 +349,6 @@ bool MediaView::animStep(float64 msp) { a_cOpacity.finish(); _controlsState = (_controlsState == ControlsShowing ? ControlsShown : ControlsHidden); setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer)); - LOG(("Finished with controls!")); } else { a_cOpacity.update(dt, anim::linear); } @@ -410,7 +409,6 @@ void MediaView::close() { void MediaView::activateControls() { _controlsHideTimer.start(int(st::mvWaitHide)); if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { - LOG(("Showing controls..")); _controlsState = ControlsShowing; _controlsAnimStarted = getms(); a_cOpacity.start(1); @@ -421,7 +419,6 @@ void MediaView::activateControls() { void MediaView::onHideControls(bool force) { if (!force && !_dropdown.isHidden()) return; if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; - LOG(("Hiding controls..")); _controlsState = ControlsHiding; _controlsAnimStarted = getms(); a_cOpacity.start(0); @@ -906,7 +903,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // _docSize is updated in updateControls() _docRect = QRect((width() - st::mvDocSize.width()) / 2, (height() - st::mvDocSize.height()) / 2, st::mvDocSize.width(), st::mvDocSize.height()); - _docIconRect = rtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocBlue.pxWidth(), st::mvDocBlue.pxHeight(), width()); + _docIconRect = myrtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocBlue.pxWidth(), st::mvDocBlue.pxHeight()); } else if (!_current.isNull()) { _current.setDevicePixelRatio(cRetinaFactor()); _w = _current.width() / cIntRetinaFactor(); @@ -1264,11 +1261,6 @@ void MediaView::paintEvent(QPaintEvent *e) { } } } - -// static uint64 t = getms(); -// uint64 t2 = getms(); -// LOG(("paint: %1, wait: %2, name: %3, icon: %4").arg(t2 - ms).arg(t2 - t).arg(logBool(name)).arg(logBool(icon))); -// t = t2; } void MediaView::keyPressEvent(QKeyEvent *e) { @@ -1828,7 +1820,7 @@ void MediaView::findCurrent() { } } - if (_history->_overviewCount[_overview] < 0) { + if (_history->_overviewCount[_overview] < 0 || (!_index && _history->_overviewCount[_overview] > 0)) { loadBack(); } } @@ -1877,7 +1869,7 @@ void MediaView::userPhotosLoaded(UserData *u, const MTPphotos_Photos &photos, mt photo->thumb->load(); u->photos.push_back(photo); } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(u); + if (App::wnd()) App::wnd()->mediaOverviewUpdated(u, OverviewCount); } void MediaView::updateHeader() { @@ -1913,7 +1905,7 @@ void MediaView::updateHeader() { hwidth = width() / 3; _headerText = st::mvThickFont->m.elidedText(_headerText, Qt::ElideMiddle, hwidth); } - _headerNav = rtlrect(st::mvTextLeft, height() - st::mvHeaderTop, hwidth, st::mvThickFont->height, width()); + _headerNav = myrtlrect(st::mvTextLeft, height() - st::mvHeaderTop, hwidth, st::mvThickFont->height); } // //void MediaView::updatePolaroid() { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 6735a2317..11b99e266 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -55,7 +55,7 @@ public: updateOver(mapFromGlobal(QCursor::pos())); } - void mediaOverviewUpdated(PeerData *peer); + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void documentUpdated(DocumentData *doc); void changingMsgId(HistoryItem *row, MsgId newId); void updateDocSize(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index e3801a826..9b1b7e095 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1681,8 +1681,12 @@ void OverviewWidget::onScroll() { } void OverviewWidget::resizeEvent(QResizeEvent *e) { + int32 st = _scroll.scrollTop(); _scroll.resize(size()); - int32 newScrollTop = _inner.resizeToWidth(width(), _scroll.scrollTop(), height()); + int32 newScrollTop = _inner.resizeToWidth(width(), st, height()); + if (int32 addToY = App::main() ? App::main()->contentScrollAddToY() : 0) { + newScrollTop += addToY; + } if (newScrollTop != _scroll.scrollTop()) { _noDropResizeIndex = true; _scroll.scrollToY(newScrollTop); @@ -1700,33 +1704,36 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { return; } - bool hasTopBar = !App::main()->topBar()->isHidden(); QRect r(e->rect()); if (type() == OverviewPhotos) { p.fillRect(r, st::white->b); - } else if (cTileBackground()) { - int left = r.left(), top = r.top(), right = r.left() + r.width(), bottom = r.top() + r.height(); - if (right > 0 && bottom > 0) { - QRect fill(left, top + (hasTopBar ? st::topBarHeight : 0), right, bottom + (hasTopBar ? st::topBarHeight : 0)); - - if (hasTopBar) p.translate(0, -st::topBarHeight); - p.fillRect(fill, QBrush(*cChatBackground())); - if (hasTopBar) p.translate(0, st::topBarHeight); - } } else { + bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); QRect fill(0, 0, width(), App::main()->height()); - int fromy = hasTopBar ? (-st::topBarHeight) : 0, x = 0, y = 0; + int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { - bool smooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform); - p.setRenderHint(QPainter::SmoothPixmapTransform); + const QPixmap &pix(*cChatBackground()); + if (cTileBackground()) { + int left = r.left(), top = r.top(), right = r.left() + r.width(), bottom = r.top() + r.height(); + float64 w = pix.width() / cRetinaFactor(), h = pix.height() / cRetinaFactor(); + int sx = qFloor(left / w), sy = qFloor((top - fromy) / h), cx = qCeil(right / w), cy = qCeil((bottom - fromy) / h); + for (int i = sx; i < cx; ++i) { + for (int j = sy; j < cy; ++j) { + p.drawPixmap(QPointF(i * w, fromy + j * h), pix); + } + } + } else { + bool smooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform); + p.setRenderHint(QPainter::SmoothPixmapTransform); - QRect to, from; - App::main()->backgroundParams(fill, to, from); - to.moveTop(to.top() + fromy); - p.drawPixmap(to, *cChatBackground(), from); + QRect to, from; + App::main()->backgroundParams(fill, to, from); + to.moveTop(to.top() + fromy); + p.drawPixmap(to, pix, from); - if (!smooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false); + if (!smooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false); + } } else { p.drawPixmap(x, fromy + y, cached); } @@ -1760,6 +1767,13 @@ void OverviewWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) } } +void OverviewWidget::topBarShadowParams(int32 &x, float64 &o) { + if (animating() && a_coord.current() >= 0) { + x = a_coord.current(); + o = a_alpha.current(); + } +} + void OverviewWidget::topBarClick() { App::main()->showBackFromStack(); } @@ -1781,6 +1795,7 @@ void OverviewWidget::switchType(MediaOverviewType type) { case OverviewVideos: _header = lang(lng_profile_videos_header); break; case OverviewDocuments: _header = lang(lng_profile_files_header); break; case OverviewAudios: _header = lang(lng_profile_audios_header); break; + case OverviewAudioDocuments: _header = lang(lng_profile_audio_files_header); break; } noSelectingScroll(); App::main()->topBar()->showSelected(0); @@ -1847,10 +1862,7 @@ bool OverviewWidget::animStep(float64 ms) { a_alpha.finish(); _bgAnimCache = _animCache = _animTopBarCache = _bgAnimTopBarCache = QPixmap(); App::main()->topBar()->stopAnim(); - _scroll.show(); - _scroll.scrollToY(_scrollSetAfterShow); - activate(); - onScroll(); + doneShow(); } else { a_bgCoord.update(dt1, st::introHideFunc); a_bgAlpha.update(dt1, st::introAlphaHideFunc); @@ -1862,8 +1874,15 @@ bool OverviewWidget::animStep(float64 ms) { return res; } -void OverviewWidget::mediaOverviewUpdated(PeerData *p) { - if (peer() == p) { +void OverviewWidget::doneShow() { + _scroll.show(); + _scroll.scrollToY(_scrollSetAfterShow); + activate(); + onScroll(); +} + +void OverviewWidget::mediaOverviewUpdated(PeerData *p, MediaOverviewType t) { + if (peer() == p && t == type()) { _inner.mediaOverviewUpdated(); onScroll(); updateTopBarSelection(); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index f280f1ac8..3f3b82db3 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -142,6 +142,7 @@ private: } CachedItem; typedef QVector CachedItems; CachedItems _items; + int32 _width, _height, _minHeight, _addToY; // selection support, like in HistoryWidget @@ -199,6 +200,7 @@ public: void scrollBy(int32 add); void paintTopBar(QPainter &p, float64 over, int32 decreaseWidth); + void topBarShadowParams(int32 &x, float64 &o); void topBarClick(); PeerData *peer() const; @@ -212,7 +214,9 @@ public: void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); bool animStep(float64 ms); - void mediaOverviewUpdated(PeerData *peer); + void doneShow(); + + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void changingMsgId(HistoryItem *row, MsgId newId); void msgUpdated(PeerId peer, const HistoryItem *msg); void itemRemoved(HistoryItem *item); diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp new file mode 100644 index 000000000..dd829cd1a --- /dev/null +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -0,0 +1,548 @@ +/* +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. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "style.h" +#include "lang.h" + +#include "boxes/addcontactbox.h" +#include "application.h" +#include "window.h" +#include "playerwidget.h" +#include "mainwidget.h" + +#include "localstorage.h" + +#include "audio.h" + +PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent), +_prevAvailable(false), _nextAvailable(false), _fullAvailable(false), +_over(OverNone), _down(OverNone), _downCoord(0), _downProgress(0.), _downFrequency(AudioVoiceMsgFrequency), +_stateAnim(animFunc(this, &PlayerWidget::stateStep)), +_index(-1), _history(0), _showPause(false), _position(0), _duration(0), _loaded(0), +a_progress(0., 0.), a_loadProgress(0., 0.), _progressAnim(animFunc(this, &PlayerWidget::progressStep)) { + resize(st::wndMinWidth, st::playerHeight); + setMouseTracking(true); + memset(_stateHovers, 0, sizeof(_stateHovers)); +} + +void PlayerWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + QRect r(e->rect()), checkr(myrtlrect(r)); + p.fillRect(r, st::playerBg->b); + + if (!_playbackRect.contains(checkr)) { + if (_fullAvailable && checkr.intersects(_prevRect)) { + if (_prevAvailable) { + float64 o = _stateHovers[OverPrev]; + p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); + } else { + p.setOpacity(st::playerUnavailableOpacity); + } + p.drawSpriteCenterLeft(_prevRect, width(), st::playerPrev); + } + if (checkr.intersects(_playRect)) { + float64 o = _stateHovers[OverPlay]; + p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); + p.drawSpriteCenterLeft(_playRect, width(), (_showPause || _down == OverPlayback) ? st::playerPause : st::playerPlay); + } + if (_fullAvailable && checkr.intersects(_nextRect)) { + if (_nextAvailable) { + float64 o = _stateHovers[OverNext]; + p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); + } else { + p.setOpacity(st::playerUnavailableOpacity); + } + p.drawSpriteCenterLeft(_nextRect, width(), st::playerNext); + } + if (checkr.intersects(_closeRect)) { + float64 o = _stateHovers[OverClose]; + p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); + p.drawSpriteCenterLeft(_closeRect, width(), st::playerClose); + } + if (checkr.intersects(_volumeRect)) { + float64 o = _stateHovers[OverVolume]; + 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 right = left + st::playerVolume.pxWidth(); + if (rtl()) { + left = width() - left; + mid = width() - mid; + right = width() - right; + if (mid < left) { + p.drawPixmap(QRect(mid, top, left - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.x() + (mid - right) * cIntRetinaFactor(), st::playerVolume.y(), (left - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); + } + if (right < mid) { + p.setOpacity(st::playerUnavailableOpacity); + p.drawPixmap(QRect(right, top, mid - right, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.x(), st::playerVolume.y(), (mid - right) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); + } + } else { + if (mid > left) { + p.drawPixmap(QRect(left, top, mid - left, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.x(), st::playerVolume.y(), (mid - left) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); + } + if (right > mid) { + p.setOpacity(st::playerUnavailableOpacity); + p.drawPixmap(QRect(mid, top, right - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.x() + (mid - left) * cIntRetinaFactor(), st::playerVolume.y(), (right - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); + } + } + } + if (_fullAvailable && checkr.intersects(_fullRect)) { + float64 o = _stateHovers[OverFull]; + p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); + p.drawSpriteCenterLeft(_fullRect, width(), st::playerFull); + } + p.setOpacity(1.); + + p.setPen(st::playerTimeFg->p); + p.setFont(st::linkFont->f); + p.drawTextLeft(_infoRect.x() + _infoRect.width() - _timeWidth, _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, width(), _time, _timeWidth); + + textstyleSet(&st::playerNameStyle); + p.setPen(st::playerFg->p); + _name.drawElided(p, _infoRect.x() + (rtl() ? (_timeWidth + st::playerSkip) : 0), _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, _infoRect.width() - _timeWidth - st::playerSkip); + textstyleRestore(); + } + + if (_duration) { + float64 prg = (_down == OverPlayback) ? _downProgress : a_progress.current(); + int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + prg * _playbackRect.width()), end = _playbackRect.x() + _playbackRect.width(); + if (mid > from) { + p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineActive->b); + } + if (end > mid) { + p.fillRect(rtl() ? (width() - end) : mid, height() - st::playerLineHeight, end - mid, st::playerLineHeight, st::playerLineInactive->b); + } + if (_stateHovers[OverPlayback] > 0) { + p.setOpacity(_stateHovers[OverPlayback]); + + int32 x = mid - (st::playerMoverSize.width() / 2); + p.fillRect(rtl() ? (width() - x - st::playerMoverSize.width()) : x, height() - st::playerMoverSize.height(), st::playerMoverSize.width(), st::playerMoverSize.height(), st::playerLineActive->b); + } + } else if (a_loadProgress.current() > 0) { + int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + a_loadProgress.current() * _playbackRect.width()); + if (mid > from) { + p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineInactive->b); + } + } +} + +void PlayerWidget::mousePressEvent(QMouseEvent *e) { + QPoint pos(myrtlpoint(e->pos())); + + if (e->button() == Qt::LeftButton) { + _down = OverNone; + if (_song && _over == OverPlay) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &playingState); + if (playing == _song && !(playingState & AudioPlayerStoppedMask)) { + audioPlayer()->pauseresume(OverviewDocuments); + } else { + audioPlayer()->play(_song); + if (App::main()) App::main()->documentPlayProgress(_song); + } + return; + } else if (_over == OverPrev) { + const History::MediaOverview *o = _history ? &_history->_overview[OverviewAudioDocuments] : 0; + if (audioPlayer() && o && _index > 0 && _index <= o->size() && !o->isEmpty()) { + startPlay(o->at(_index - 1)); + } + } else if (_over == OverNext) { + const History::MediaOverview *o = _history ? &_history->_overview[OverviewAudioDocuments] : 0; + if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) { + startPlay(o->at(_index + 1)); + } + } else if (_over == OverClose) { + _down = OverClose; + } 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.)); + emit audioPlayer()->songVolumeChanged(); + rtlupdate(_volumeRect); + } else if (_over == OverPlayback) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + if (playing == _song && playingDuration) { + if (playingState == AudioPlayerPlaying || playingState == AudioPlayerStarting || playingState == AudioPlayerResuming) { + audioPlayer()->pauseresume(OverviewDocuments); + } + _down = OverPlayback; + _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); + _downDuration = playingDuration; + _downFrequency = (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + + rtlupdate(_playbackRect); + updateDownTime(); + } + } else if (_over == OverFull && _song) { + if (HistoryItem *item = App::histItemById(_song.msgId)) { + App::main()->showMediaOverview(item->history()->peer, OverviewAudioDocuments); + } + } + } +} + +void PlayerWidget::updateDownTime() { + QString time = formatDurationText(qRound(_downDuration * _downProgress) / _downFrequency); + if (time != _time) { + _time = time; + _timeWidth = st::linkFont->m.width(_time); + rtlupdate(_infoRect); + } +} + +void PlayerWidget::updateOverState(OverState newState) { + bool result = true; + if (_over != newState) { + updateOverRect(_over); + updateOverRect(newState); + if (_over != OverNone) { + _stateAnimations.remove(_over); + _stateAnimations[-_over] = getms() - ((1. - _stateHovers[_over]) * st::playerDuration); + if (!_stateAnim.animating()) _stateAnim.start(); + } else { + result = false; + } + _over = newState; + if (newState != OverNone) { + _stateAnimations.remove(-_over); + _stateAnimations[_over] = getms() - (_stateHovers[_over] * st::playerDuration); + if (!_stateAnim.animating()) _stateAnim.start(); + setCursor(style::cur_pointer); + } else { + setCursor(style::cur_default); + } + } +} + +void PlayerWidget::updateOverRect(OverState state) { + switch (state) { + case OverPrev: rtlupdate(_prevRect); break; + case OverPlay: rtlupdate(_playRect); break; + case OverNext: rtlupdate(_nextRect); break; + case OverClose: rtlupdate(_closeRect); break; + case OverVolume: rtlupdate(_volumeRect); break; + case OverFull: rtlupdate(_fullRect); break; + case OverPlayback: rtlupdate(_playbackRect); break; + } +} + +void PlayerWidget::updateControls() { + _fullAvailable = (_index >= 0); + _prevAvailable = _fullAvailable && (_index > 0); + _nextAvailable = _fullAvailable && (_index < _history->_overview[OverviewAudioDocuments].size() - 1); + resizeEvent(0); + update(); + + if (_index >= 0 && _index < MediaOverviewStartPerPage) { + if (_history->_overviewCount[OverviewAudioDocuments] < 0 || _history->_overviewCount[OverviewAudioDocuments] > 0) { + if (App::main()) App::main()->loadMediaBack(_history->peer, OverviewAudioDocuments); + } + } +} + +void PlayerWidget::findCurrent() { + _index = -1; + if (!_history) return; + + const History::MediaOverview *o = &_history->_overview[OverviewAudioDocuments]; + for (int i = 0, l = o->size(); i < l; ++i) { + if (o->at(i) == _song.msgId) { + _index = i; + break; + } + } + if (_index < 0) return; + + if (_index < o->size() - 1) { + if (HistoryItem *next = App::histItemById(o->at(_index + 1))) { + if (HistoryDocument *document = static_cast(next->getMedia())) { + if (document->document()->already(true).isEmpty() && document->document()->data.isEmpty()) { + if (!document->document()->loader) { + DocumentOpenLink::doOpen(document->document()); + document->document()->openOnSave = document->document()->openOnSaveMsgId = 0; + } + } + } + } + } +} + +void PlayerWidget::startPlay(MsgId msgId) { + if (HistoryItem *item = App::histItemById(msgId)) { + if (HistoryDocument *doc = static_cast(item->getMedia())) { + audioPlayer()->play(SongMsgId(doc->document(), item->id)); + updateState(); + } + } +} + +void PlayerWidget::clearSelection() { + for (StateAnimations::const_iterator i = _stateAnimations.cbegin(); i != _stateAnimations.cend(); ++i) { + _stateHovers[qAbs(i.key())] = 0; + } + _stateAnimations.clear(); +} + +void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { + if (_history && _history->peer == peer && type == OverviewAudioDocuments) { + _index = -1; + for (int i = 0, l = _history->_overview[OverviewAudioDocuments].size(); i < l; ++i) { + if (_history->_overview[OverviewAudioDocuments].at(i) == _song.msgId) { + _index = i; + break; + } + } + updateControls(); + } +} + +bool PlayerWidget::seekingSong(const SongMsgId &song) const { + return (_down == OverPlayback) && (song == _song); +} + +bool PlayerWidget::stateStep(float64 ms) { + bool result = false; + uint64 ms = getms(); + for (StateAnimations::iterator i = _stateAnimations.begin(); i != _stateAnimations.cend();) { + int32 over = qAbs(i.key()); + updateOverRect(OverState(over)); + + float64 dt = float64(ms - i.value()) / st::playerDuration; + if (dt >= 1) { + _stateHovers[over] = (i.key() > 0) ? 1 : 0; + i = _stateAnimations.erase(i); + } else { + _stateHovers[over] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + return !_stateAnimations.isEmpty(); +} + +void PlayerWidget::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void PlayerWidget::leaveEvent(QEvent *e) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void PlayerWidget::updateSelected() { + QPoint pos(myrtlpoint(mapFromGlobal(_lastMousePos))); + + if (_down == OverVolume) { + 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); + emit audioPlayer()->songVolumeChanged(); + rtlupdate(_volumeRect); + } + } else if (_down == OverPlayback) { + _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); + rtlupdate(_playbackRect); + updateDownTime(); + } else if (_down == OverNone) { + bool inInfo = ((pos.x() >= _infoRect.x()) && (pos.x() < _fullRect.x() + _fullRect.width()) && (pos.y() >= _playRect.y()) && (pos.y() <= _playRect.y() + _playRect.height())); + if (_prevAvailable && _prevRect.contains(pos)) { + updateOverState(OverPrev); + } else if (_nextAvailable && _nextRect.contains(pos)) { + updateOverState(OverNext); + } else if (_playRect.contains(pos)) { + updateOverState(OverPlay); + } else if (_closeRect.contains(pos)) { + updateOverState(OverClose); + } else if (_volumeRect.contains(pos)) { + updateOverState(OverVolume); + } else if (_duration && _playbackRect.contains(pos)) { + updateOverState(OverPlayback); + } else if (_fullAvailable && inInfo) { + updateOverState(OverFull); + } else if (_over != OverNone) { + updateOverState(OverNone); + } + } +} + +void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { + if (_down == OverVolume) { + mouseMoveEvent(e); + Local::writeUserSettings(); + } else if (_down == OverPlayback) { + mouseMoveEvent(e); + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + if (playing == _song && playingDuration) { + _downDuration = playingDuration; + audioPlayer()->seek(qRound(_downProgress * _downDuration)); + + _showPause = true; + + a_progress = anim::fvalue(_downProgress, _downProgress); + _progressAnim.stop(); + } + update(); + } else if (_down == OverClose && _over == OverClose) { + if (_song) { + audioPlayer()->stop(OverviewDocuments); + if (App::main()) App::main()->hidePlayer(); + } + } + _down = OverNone; +} + +void PlayerWidget::resizeEvent(QResizeEvent *e) { + int32 availh = (height() - st::playerLineHeight); + int32 ch = st::playerPlay.pxHeight() + st::playerSkip, ct = (availh - ch) / 2; + _playbackRect = QRect(cWideMode() ? st::dlgShadow : 0, height() - st::playerMoverSize.height(), width() - (cWideMode() ? st::dlgShadow : 0), st::playerMoverSize.height()); + _prevRect = _fullAvailable ? QRect(st::playerSkip / 2, ct, st::playerPrev.pxWidth() + st::playerSkip, ch) : QRect(); + _playRect = QRect(_fullAvailable ? (_prevRect.x() + _prevRect.width()) : (st::playerSkip / 2), ct, st::playerPlay.pxWidth() + st::playerSkip, ch); + _nextRect = _fullAvailable ? QRect(_playRect.x() + _playRect.width(), ct, st::playerNext.pxWidth() + st::playerSkip, ch) : QRect(); + + _closeRect = QRect(width() - st::playerSkip / 2 - st::playerClose.pxWidth() - st::playerSkip, ct, st::playerClose.pxWidth() + st::playerSkip, ch); + _volumeRect = QRect(_closeRect.x() - st::playerVolume.pxWidth() - st::playerSkip, ct, st::playerVolume.pxWidth() + st::playerSkip, ch); + _fullRect = _fullAvailable ? QRect(_volumeRect.x() - st::playerFull.pxWidth() - st::playerSkip, ct, st::playerFull.pxWidth() + st::playerSkip, ch) : QRect(); + + int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width())); + _infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _volumeRect.x()) - infoLeft - st::playerSkip, availh); + update(); +} + +bool PlayerWidget::progressStep(float64 ms) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + bool res = true; + if (_duration && dt >= 1) { + a_progress.finish(); + a_loadProgress.finish(); + res = false; + } else { + a_progress.update(qMin(dt, 1.), anim::linear); + a_loadProgress.update(1. - (st::radialDuration / (st::radialDuration + ms)), anim::linear); + } + rtlupdate(_playbackRect); + return res; +} + +void PlayerWidget::updateState() { + updateState(SongMsgId(), AudioPlayerStopped, 0, 0, 0); +} + +void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency) { + if (!playing) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + bool songChanged = false; + if (playing && _song != playing) { + songChanged = true; + _song = playing; + if (HistoryItem *item = App::histItemById(_song.msgId)) { + _history = item->history(); + findCurrent(); + } else { + _history = 0; + _index = -1; + } + SongData *song = _song.song->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); + } else { + TextCustomTagsMap custom; + custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); + _name.setRichText(st::linkFont, QString::fromUtf8("[c]%1[/c] \xe2\x80\x93 %2").arg(textRichPrepare(song->performer)).arg(song->title.isEmpty() ? qsl("Unknown Track") : textRichPrepare(song->title)), _textNameOptions, custom); + } + updateControls(); + } + + qint64 position = 0, duration = 0, display = 0; + if (playing == _song) { + if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + display = position = playingPosition; + duration = playingDuration; + } else { + display = playingDuration; + } + display = display / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + } else if (_song) { + display = _song.song->song()->duration; + } + QString time = (_down == OverPlayback) ? _time : formatDurationText(display); + bool showPause = false, stopped = ((playingState & AudioPlayerStoppedMask) || playingState == AudioPlayerFinishing); + bool wasPlaying = !!_duration; + if (!stopped) { + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } + float64 progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; + int32 loaded = duration ? _song.song->size : (_song.song->loader ? _song.song->loader->currentOffset() : 0); + float64 loadProgress = (duration || !_song.song->loader) ? 1. : snap(float64(loaded) / qMax(_song.song->size, 1), 0., 1.); + if (time != _time || showPause != _showPause) { + if (_time != time) { + _time = time; + _timeWidth = st::linkFont->m.width(_time); + } + _showPause = showPause; + if (duration != _duration || position != _position || loaded != _loaded) { + if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { + a_progress.start(progress); + a_loadProgress.start(loadProgress); + _progressAnim.start(); + } else { + a_progress = anim::fvalue(progress, progress); + a_loadProgress = anim::fvalue(loadProgress, loadProgress); + _progressAnim.stop(); + } + _position = position; + _duration = duration; + _loaded = loaded; + } + update(); + } else if (duration != _duration || position != _position || loaded != _loaded) { + if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { + a_progress.start(progress); + a_loadProgress.start(loadProgress); + _progressAnim.start(); + } else { + a_progress = anim::fvalue(progress, progress); + a_loadProgress = anim::fvalue(loadProgress, loadProgress); + _progressAnim.stop(); + } + _position = position; + _duration = duration; + _loaded = loaded; + } + + if (wasPlaying && playingState == AudioPlayerStoppedAtEnd) { + const History::MediaOverview *o = _history ? &_history->_overview[OverviewAudioDocuments] : 0; + if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) { + startPlay(o->at(_index + 1)); + } + } +} diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h new file mode 100644 index 000000000..6b8d7ac39 --- /dev/null +++ b/Telegram/SourceFiles/playerwidget.h @@ -0,0 +1,100 @@ +/* +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. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "audio.h" + +class PlayerWidget : public TWidget { + Q_OBJECT + +public: + + PlayerWidget(QWidget *parent); + + void paintEvent(QPaintEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void leaveEvent(QEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void resizeEvent(QResizeEvent *e); + + bool progressStep(float64 ms); + bool stateStep(float64 ms); + + void updateState(SongMsgId 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; + +private: + + enum OverState { + OverNone = 0, + OverPrev, + OverPlay, + OverNext, + OverClose, + OverVolume, + OverFull, + OverPlayback, + + OverStateCount + }; + void updateDownTime(); + void updateOverState(OverState newState); + void updateOverRect(OverState state); + + void updateControls(); + void findCurrent(); + + void startPlay(MsgId msgId); + + QPoint _lastMousePos; + void updateSelected(); + + bool _prevAvailable, _nextAvailable, _fullAvailable; + OverState _over, _down; + int32 _downCoord; + int64 _downDuration; + int32 _downFrequency; + float64 _downProgress; + + float64 _stateHovers[OverStateCount]; + typedef QMap StateAnimations; + StateAnimations _stateAnimations; + Animation _stateAnim; + + SongMsgId _song; + int32 _index; + History *_history; + QRect _playRect, _prevRect, _nextRect, _playbackRect; + QRect _closeRect, _volumeRect, _fullRect, _infoRect; + int32 _timeWidth; + QString _time; + Text _name; + bool _showPause; + int64 _position, _duration; + int32 _loaded; + + anim::fvalue a_progress, a_loadProgress; + Animation _progressAnim; + +}; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 8581d072b..a0221743b 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -579,6 +579,8 @@ void ProfileInner::paintEvent(QPaintEvent *e) { p.setPen(st::black->p); int oneState = 0; // < 0 - loading, 0 - no media, > 0 - link shown for (int i = 0; i < OverviewCount; ++i) { + if (i == OverviewAudioDocuments) continue; + int32 count = (_hist->_overviewCount[i] > 0) ? _hist->_overviewCount[i] : (_hist->_overviewCount[i] == 0 ? _hist->_overview[i].size() : -1); if (count < 0) { if (!oneState) oneState = count; @@ -859,6 +861,8 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { _mediaShowAll.move(_left + _width - _mediaShowAll.width(), top); int wasCount = 0; // < 0 - loading, 0 - no media, > 0 - link shown for (int i = 0; i < OverviewCount; ++i) { + if (i == OverviewAudioDocuments) continue; + if (_allMediaTypes) { int32 count = (_hist->_overviewCount[i] > 0) ? _hist->_overviewCount[i] : (_hist->_overviewCount[i] == 0 ? _hist->_overview[i].size() : -1); if (count > 0) { @@ -957,7 +961,7 @@ void ProfileInner::updateNotifySettings() { _enableNotifications.setChecked(_peer->notify == EmptyNotifySettings || _peer->notify == UnknownNotifySettings || _peer->notify->mute < unixtime()); } -void ProfileInner::mediaOverviewUpdated(PeerData *peer) { +void ProfileInner::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { if (peer == _peer) { resizeEvent(0); showAll(); @@ -1029,6 +1033,8 @@ void ProfileInner::showAll() { // shared media bool first = false, wasCount = false, manyCounts = false; for (int i = 0; i < OverviewCount; ++i) { + if (i == OverviewAudioDocuments) continue; + int32 count = (_hist->_overviewCount[i] > 0) ? _hist->_overviewCount[i] : (_hist->_overviewCount[i] == 0 ? _hist->_overview[i].size() : -1); if (count > 0) { if (wasCount) { @@ -1136,8 +1142,13 @@ void ProfileWidget::onScroll() { } void ProfileWidget::resizeEvent(QResizeEvent *e) { + int32 addToY = App::main() ? App::main()->contentScrollAddToY() : 0; + int32 newScrollY = _scroll.scrollTop() + addToY; _scroll.resize(size()); _inner.resize(width(), _inner.height()); + if (addToY) { + _scroll.scrollToY(newScrollY); + } } void ProfileWidget::mousePressEvent(QMouseEvent *e) { @@ -1176,6 +1187,13 @@ void ProfileWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) } } +void ProfileWidget::topBarShadowParams(int32 &x, float64 &o) { + if (animating() && a_coord.current() >= 0) { + x = a_coord.current(); + o = a_alpha.current(); + } +} + void ProfileWidget::topBarClick() { App::main()->showBackFromStack(); } @@ -1253,8 +1271,8 @@ void ProfileWidget::updateNotifySettings() { _inner.updateNotifySettings(); } -void ProfileWidget::mediaOverviewUpdated(PeerData *peer) { - _inner.mediaOverviewUpdated(peer); +void ProfileWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { + _inner.mediaOverviewUpdated(peer, type); } void ProfileWidget::clear() { diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index 4ecaa77d5..7acc3dfe3 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -53,7 +53,7 @@ public: void loadProfilePhotos(int32 yFrom); void updateNotifySettings(); - void mediaOverviewUpdated(PeerData *peer); + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); ~ProfileInner(); @@ -187,6 +187,7 @@ public: void dropEvent(QDropEvent *e); void paintTopBar(QPainter &p, float64 over, int32 decreaseWidth); + void topBarShadowParams(int32 &x, float64 &o); void topBarClick(); PeerData *peer() const; @@ -200,7 +201,7 @@ public: void updateOnlineDisplayTimer(); void updateNotifySettings(); - void mediaOverviewUpdated(PeerData *peer); + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void clear(); ~ProfileWidget(); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 3634fb63c..a770c64a9 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -156,6 +156,8 @@ int gNotifyDefaultDelay = 1500; int gOtherOnline = 0; +float64 gSongVolume = 0.9; + void settingsParseArgs(int argc, char *argv[]) { #ifdef Q_OS_MAC gCustomNotifies = (QSysInfo::macVersion() < QSysInfo::MV_10_8); diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 39d789b4a..48faec016 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -304,4 +304,6 @@ DeclareSetting(int, NotifyDefaultDelay); DeclareSetting(int, OtherOnline); +DeclareSetting(float64, SongVolume); + void settingsParseArgs(int argc, char *argv[]); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 522c712e7..c9bbaad73 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -448,7 +448,7 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const { AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == App::hoveredLinkItem()->id && playingState != AudioPlayerStopped) { + if (playing.msgId == App::hoveredLinkItem()->id && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewAudios); } else { audioPlayer()->play(AudioMsgId(data, App::hoveredLinkItem()->id)); @@ -545,9 +545,8 @@ QString AudioData::already(bool check) { return location.name; } -void DocumentOpenLink::onClick(Qt::MouseButton button) const { - DocumentData *data = document(); - if (!data->date || button != Qt::LeftButton) return; +void DocumentOpenLink::doOpen(DocumentData *data) { + if (!data->date) return; bool play = data->song() && App::hoveredLinkItem() && audioPlayer(); QString already = data->already(true); @@ -556,10 +555,12 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const { SongMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == App::hoveredLinkItem()->id && playingState != AudioPlayerStopped) { + if (playing.msgId == App::hoveredLinkItem()->id && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewDocuments); } else { - audioPlayer()->play(SongMsgId(data, App::hoveredLinkItem()->id)); + SongMsgId song(data, App::hoveredLinkItem()->id); + audioPlayer()->play(song); + if (App::main()) App::main()->documentPlayProgress(song); } } else if (data->size < MediaViewImageSizeLimit) { QImageReader reader(already); @@ -604,6 +605,11 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const { } } +void DocumentOpenLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + doOpen(document()); +} + void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) { if (!data->date) return; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index d73c90d22..7849674ad 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -627,6 +627,7 @@ class DocumentOpenLink : public DocumentLink { public: DocumentOpenLink(DocumentData *document) : DocumentLink(document) { } + static void doOpen(DocumentData *document); void onClick(Qt::MouseButton button) const; }; diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index 2c7684f11..5d8ee7a0e 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -273,6 +273,7 @@ enum DataBlockId { dbiRecentStickers = 0x26, dbiDcOption = 0x27, dbiTryIPv6 = 0x28, + dbiSongVolume = 0x29, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index bb218b9c9..00f713269 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -1712,10 +1712,10 @@ void Window::sendPaths() { } } -void Window::mediaOverviewUpdated(PeerData *peer) { - if (main) main->mediaOverviewUpdated(peer); +void Window::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { + if (main) main->mediaOverviewUpdated(peer, type); if (!_mediaView || _mediaView->isHidden()) return; - _mediaView->mediaOverviewUpdated(peer); + _mediaView->mediaOverviewUpdated(peer, type); } void Window::documentUpdated(DocumentData *doc) { diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h index 3835f573c..321cbae27 100644 --- a/Telegram/SourceFiles/window.h +++ b/Telegram/SourceFiles/window.h @@ -226,7 +226,7 @@ public: void sendPaths(); - void mediaOverviewUpdated(PeerData *peer); + void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void documentUpdated(DocumentData *doc); void changingMsgId(HistoryItem *row, MsgId newId); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index f392e9280..3d511a4c0 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -352,6 +352,10 @@ true true + + true + true + true true @@ -618,6 +622,10 @@ true true + + true + true + true true @@ -909,6 +917,10 @@ true true + + true + true + true true @@ -1044,6 +1056,7 @@ + true @@ -1993,6 +2006,20 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\mpg123-1.22.1\ports\MSVC++" "-I.\..\..\Libraries\mpg123-1.22.1\src\libmpg123" "-I.\..\..\Libraries\faad2-2.7\include" "-I.\..\..\Libraries\faad2-2.7\common\mp4ff" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" "-fstdafx.h" "-f../../SourceFiles/passcodewidget.h" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing playerwidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/playerwidget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing playerwidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/playerwidget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing playerwidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/playerwidget.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\mpg123-1.22.1\ports\MSVC++" "-I.\..\..\Libraries\mpg123-1.22.1\src\libmpg123" "-I.\..\..\Libraries\faad2-2.7\include" "-I.\..\..\Libraries\faad2-2.7\common\mp4ff" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 6694d680f..b3cb689af 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -903,6 +903,18 @@ Generated Files\Release + + Source Files + + + Generated Files\Deploy + + + Generated Files\Debug + + + Generated Files\Release + @@ -1198,6 +1210,9 @@ Source Files + + Source Files +