1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'master' into chatlog_v3_1

Conflicts:
	src/widget/chatareawidget.cpp
	src/widget/form/chatform.h
	src/widget/form/genericchatform.cpp
This commit is contained in:
krepa098 2015-01-09 19:41:16 +01:00
commit d9e15fb0ba
25 changed files with 2194 additions and 345 deletions

View File

@ -37,6 +37,6 @@ This client runs on Windows, Linux and Mac natively.<br/>
##Developer overview:
[GitStats](http://207.12.89.155/index.html)<br/>
[GitStats](http://104.219.184.93/index.html)<br/>
[Mac & Linux jenkins](https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/)<br/>
[Windows jenkins](http://207.12.89.155:8080)<br/>
[Windows jenkins](http://104.219.184.93:8080)<br/>

View File

@ -78,6 +78,14 @@ win32 {
LIBS += -liphlpapi -L$$PWD/libs/lib -lsodium -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lopencv_core248 -lopencv_highgui248 -lopencv_imgproc248 -lOpenAL32 -lopus
LIBS += -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32 -lz
contains(DEFINES, QTOX_FILTER_AUDIO) {
contains(STATICPKG, YES) {
LIBS += -Wl,-Bstatic -lfilteraudio
} else {
LIBS += -lfilteraudio
}
}
} else {
macx {
BUNDLEID = im.tox.qtox

View File

@ -127,6 +127,7 @@
<file>translations/mannol.qm</file>
<file>translations/pirate.qm</file>
<file>translations/pl.qm</file>
<file>translations/pt.qm</file>
<file>translations/ru.qm</file>
<file>translations/sv.qm</file>
<file>translations/uk.qm</file>

View File

@ -15,22 +15,40 @@
*/
// Output some extra debug info
#define AUDIO_DEBUG 1
// Fix a 7 years old openal-soft/alsa bug
// http://blog.gmane.org/gmane.comp.lib.openal.devel/month=20080501
// If set to 1, the capture will be started as long as the device is open
#define FIX_SND_PCM_PREPARE_BUG 0
#include "audio.h"
#include "src/core.h"
#include <QDebug>
#include <QThread>
#include <QMutexLocker>
#include <cassert>
std::atomic<int> Audio::userCount{0};
Audio* Audio::instance{nullptr};
QThread* Audio::audioThread{nullptr};
QMutex* Audio::audioInLock{nullptr};
QMutex* Audio::audioOutLock{nullptr};
ALCdevice* Audio::alInDev{nullptr};
ALCdevice* Audio::alOutDev{nullptr};
ALCcontext* Audio::alContext{nullptr};
ALuint Audio::alMainSource{0};
void audioDebugLog(QString msg)
{
#if (AUDIO_DEBUG)
qDebug()<<"Audio: "<<msg;
#endif
}
Audio& Audio::getInstance()
{
if (!instance)
@ -39,6 +57,8 @@ Audio& Audio::getInstance()
audioThread = new QThread(instance);
audioThread->setObjectName("qTox Audio");
audioThread->start();
audioInLock = new QMutex(QMutex::Recursive);
audioOutLock = new QMutex(QMutex::Recursive);
instance->moveToThread(audioThread);
}
return *instance;
@ -46,18 +66,46 @@ Audio& Audio::getInstance()
void Audio::suscribeInput()
{
if (!alInDev)
{
qWarning()<<"Audio::suscribeInput: input device is closed";
return;
}
audioDebugLog("suscribing");
QMutexLocker lock(audioInLock);
if (!userCount++ && alInDev)
{
#if (!FIX_SND_PCM_PREPARE_BUG)
audioDebugLog("starting capture");
alcCaptureStart(alInDev);
#endif
}
}
void Audio::unsuscribeInput()
{
if (!alInDev)
{
qWarning()<<"Audio::unsuscribeInput: input device is closed";
return;
}
audioDebugLog("unsuscribing");
QMutexLocker lock(audioInLock);
if (!--userCount && alInDev)
{
#if (!FIX_SND_PCM_PREPARE_BUG)
audioDebugLog("stopping capture");
alcCaptureStop(alInDev);
#endif
}
}
void Audio::openInput(const QString& inDevDescr)
{
audioDebugLog("Trying to open input "+inDevDescr);
QMutexLocker lock(audioInLock);
auto* tmp = alInDev;
alInDev = nullptr;
if (tmp)
@ -80,11 +128,22 @@ void Audio::openInput(const QString& inDevDescr)
// Restart the capture if necessary
if (userCount.load() != 0 && alInDev)
{
alcCaptureStart(alInDev);
}
else
{
#if (FIX_SND_PCM_PREPARE_BUG)
alcCaptureStart(alInDev);
#endif
}
}
void Audio::openOutput(const QString& outDevDescr)
{
audioDebugLog("Trying to open output "+outDevDescr);
QMutexLocker lock(audioOutLock);
auto* tmp = alOutDev;
alOutDev = nullptr;
if (outDevDescr.isEmpty())
@ -97,11 +156,8 @@ void Audio::openOutput(const QString& outDevDescr)
}
else
{
if (alContext)
{
alcMakeContextCurrent(nullptr);
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
alcDestroyContext(alContext);
}
if (tmp)
alcCloseDevice(tmp);
alContext=alcCreateContext(alOutDev,nullptr);
@ -122,26 +178,48 @@ void Audio::openOutput(const QString& outDevDescr)
void Audio::closeInput()
{
audioDebugLog("Closing input");
QMutexLocker lock(audioInLock);
if (alInDev)
alcCaptureCloseDevice(alInDev);
userCount = 0;
{
if (alcCaptureCloseDevice(alInDev) == ALC_TRUE)
{
alInDev = nullptr;
userCount = 0;
}
else
{
qWarning() << "Audio: Failed to close input";
}
}
}
void Audio::closeOutput()
{
if (alContext)
{
alcMakeContextCurrent(nullptr);
audioDebugLog("Closing output");
QMutexLocker lock(audioOutLock);
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
alcDestroyContext(alContext);
}
if (alOutDev)
alcCloseDevice(alOutDev);
{
if (alcCloseDevice(alOutDev) == ALC_TRUE)
{
alOutDev = nullptr;
}
else
{
qWarning() << "Audio: Failed to close output";
}
}
}
void Audio::playMono16Sound(const QByteArray& data)
{
QMutexLocker lock(audioOutLock);
if (!alOutDev)
return;
ALuint buffer;
alGenBuffers(1, &buffer);
alBufferData(buffer, AL_FORMAT_MONO16, data.data(), data.size(), 44100);
@ -163,6 +241,8 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
{
assert(QThread::currentThread() == audioThread);
QMutexLocker lock(audioOutLock);
ToxGroupCall& call = Core::groupCalls[group];
if (!call.active || call.muteVol)
@ -178,6 +258,8 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
{
assert(channels == 1 || channels == 2);
QMutexLocker lock(audioOutLock);
ALuint bufid;
ALint processed = 0, queued = 16;
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
@ -210,3 +292,27 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
if (state != AL_PLAYING)
alSourcePlay(alSource);
}
bool Audio::isInputReady()
{
return (alInDev && userCount);
}
bool Audio::isOutputClosed()
{
return (alOutDev);
}
bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
{
QMutexLocker lock(audioInLock);
ALint samples=0;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples < framesize)
return false;
memset(buf, 0, framesize * 2 * av_DefaultSettings.audio_channels); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
return true;
}

View File

@ -34,6 +34,7 @@ class QString;
class QByteArray;
class QTimer;
class QThread;
class QMutex;
struct Tox;
class Audio : QObject
@ -52,7 +53,11 @@ public:
static void closeInput(); ///< Close an input device, please don't use unless everyone's unsuscribed
static void closeOutput(); ///< Close an output device
static bool isInputReady(); ///< Returns true if the input device is open and suscribed to
static bool isOutputClosed(); ///< Returns true if the output device is open
static void playMono16Sound(const QByteArray& data); ///< Play a 44100Hz mono 16bit PCM sound
static bool tryCaptureSamples(uint8_t* buf, int framesize); ///< Does nothing and return false on failure
/// May be called from any thread, will always queue a call to playGroupAudio
/// The first and last argument are ignored, but allow direct compatibility with toxcore
@ -66,7 +71,6 @@ public slots:
public:
static QThread* audioThread;
static ALCdevice* alOutDev, *alInDev;
static ALCcontext* alContext;
static ALuint alMainSource;
@ -77,6 +81,8 @@ private:
private:
static Audio* instance;
static std::atomic<int> userCount;
static ALCdevice* alOutDev, *alInDev;
static QMutex* audioInLock, *audioOutLock;
};
#endif // AUDIO_H

View File

@ -64,6 +64,7 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES];
const QString AutoUpdater::checkURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/version";
const QString AutoUpdater::flistURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/flist";
const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/files/";
bool AutoUpdater::abortFlag{false};
bool AutoUpdater::isUpdateAvailable()
{
@ -251,7 +252,11 @@ AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta)
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(filesURI+fileMeta.id)));
while (!reply->isFinished())
{
if (abortFlag)
return file;
qApp->processEvents();
}
if (reply->error() != QNetworkReply::NoError)
{
@ -280,6 +285,9 @@ bool AutoUpdater::downloadUpdate()
QList<UpdateFileMeta> newFlist = parseFlist(newFlistData);
QList<UpdateFileMeta> diff = genUpdateDiff(newFlist);
if (abortFlag)
return false;
qDebug() << "AutoUpdater: Need to update "<<diff.size()<<" files";
// Create an empty directory to download updates into
@ -308,6 +316,9 @@ bool AutoUpdater::downloadUpdate()
// Download and write each new file
for (UpdateFileMeta fileMeta : diff)
{
if (abortFlag)
return false;
qDebug() << "AutoUpdater: Downloading '"+fileMeta.installpath+"' ...";
// Create subdirs if necessary
@ -317,6 +328,8 @@ bool AutoUpdater::downloadUpdate()
// Download
UpdateFile file = getUpdateFile(fileMeta);
if (abortFlag)
return false;
if (file.data.isNull())
{
qWarning() << "AutoUpdater::downloadUpdate: Error downloading a file, aborting...";
@ -357,7 +370,7 @@ bool AutoUpdater::isLocalUpdateReady()
if (!updateDir.exists())
return false;
// Check that we have a flist and that every file on the diff exists
// Check that we have a flist and generate a diff
QFile updateFlistFile(updateDirStr+"flist");
if (!updateFlistFile.open(QIODevice::ReadOnly))
return false;
@ -367,9 +380,23 @@ bool AutoUpdater::isLocalUpdateReady()
QList<UpdateFileMeta> updateFlist = parseFlist(updateFlistData);
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist);
// If the update wasn't downloaded correctly, redownload it
// We don't check signatures to not block qTox too long, the updater will do it anyway
for (UpdateFileMeta fileMeta : diff)
{
if (!QFile::exists(updateDirStr+fileMeta.installpath))
{
QtConcurrent::run(&AutoUpdater::downloadUpdate);
return false;
}
QFile f(updateDirStr+fileMeta.installpath);
if (f.size() != (int64_t)fileMeta.size)
{
QtConcurrent::run(&AutoUpdater::downloadUpdate);
return false;
}
}
return true;
}
@ -433,3 +460,8 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
downloadUpdate();
}
}
void AutoUpdater::abortUpdates()
{
abortFlag = true;
}

View File

@ -78,15 +78,18 @@ public:
/// Will try to download an update, if successful returns true and qTox will apply it after a restart
/// Will try to follow qTox's proxy settings, may block and processEvents
static bool downloadUpdate();
/// Returns true if an update is downloaded and ready to be installed
/// If so, call installLocalUpdate. If not, call downloadUpdate.
/// This only checks that we downloaded an update and didn't stop in the middle, not that every file is still valid
/// Returns true if an update is downloaded and ready to be installed,
/// if so, call installLocalUpdate.
/// If an update was partially downloaded, the function will resume asynchronously and return false
static bool isLocalUpdateReady();
/// Launches the qTox updater to try to install the local update and exits immediately
/// Will not check that the update actually exists, use isLocalUpdateReady first for that
/// The qTox updater will restart us after the update is done
/// Note: If we fail to start the qTox updater, we will delete the update and exit
[[ noreturn ]] static void installLocalUpdate();
/// Aborting will make some functions try to return early
/// Call before qTox exits to avoid the updater running in the background
static void abortUpdates();
protected:
/// Parses and validates a flist file. Returns an empty list on error
@ -118,6 +121,7 @@ private:
static const QString filesURI; ///< URI of the actual files of the latest version
static const QString updaterBin; ///< Path to the qtox-updater binary
static unsigned char key[];
static bool abortFlag; ///< If true, try to abort everything.
};
#endif // AUTOUPDATE_H

View File

@ -214,6 +214,7 @@ void Core::cleanupCall(int callId)
if (calls[callId].videoEnabled)
Camera::getInstance()->unsubscribe();
Audio::unsuscribeInput();
toxav_kill_transmission(Core::getInstance()->toxav, callId);
}
void Core::playCallAudio(void* toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data)
@ -236,7 +237,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav)
if (!calls[callId].active)
return;
if (calls[callId].muteMic || !Audio::alInDev)
if (calls[callId].muteMic || !Audio::isInputReady())
{
calls[callId].sendAudioTimer->start();
return;
@ -246,17 +247,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav)
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize], dest[bufsize];
bool frame = false;
ALint samples;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples >= framesize)
{
memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
frame = 1;
}
if (frame)
if (Audio::tryCaptureSamples(buf, framesize))
{
int r;
if ((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0)
@ -268,10 +259,9 @@ void Core::sendCallAudio(int callId, ToxAv* toxav)
#ifdef QTOX_FILTER_AUDIO
if (filterer[callId])
{
filterer[callId]->filterAudio((int16_t*) buf, framesize);
}
#endif
if ((r = toxav_send_audio(toxav, callId, dest, r)) < 0)
{
qDebug() << "Core: toxav_send_audio error";
@ -418,59 +408,6 @@ void Core::onAvRinging(void* _toxav, int32_t call_index, void* core)
}
}
//void Core::onAvStarting(void* _toxav, int32_t call_index, void* core)
//{
// ToxAv* toxav = static_cast<ToxAv*>(_toxav);
// int friendId = toxav_get_peer_id(toxav, call_index, 0);
// if (friendId < 0)
// {
// qWarning() << "Core: Received invalid AV starting";
// return;
// }
// ToxAvCSettings* transSettings = new ToxAvCSettings;
// int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
// if (err != ErrorNone)
// {
// qWarning() << "Core::onAvStarting: error getting call type";
// delete transSettings;
// return;
// }
// if (transSettings->call_type == TypeVideo)
// {
// qDebug() << QString("Core: AV starting from %1 with video").arg(friendId);
// prepareCall(friendId, call_index, toxav, true);
// emit static_cast<Core*>(core)->avStarting(friendId, call_index, true);
// }
// else
// {
// qDebug() << QString("Core: AV starting from %1 without video").arg(friendId);
// prepareCall(friendId, call_index, toxav, false);
// emit static_cast<Core*>(core)->avStarting(friendId, call_index, false);
// }
// delete transSettings;
//}
//void Core::onAvEnding(void* _toxav, int32_t call_index, void* core)
//{
// ToxAv* toxav = static_cast<ToxAv*>(_toxav);
// int friendId = toxav_get_peer_id(toxav, call_index, 0);
// if (friendId < 0)
// {
// qWarning() << "Core: Received invalid AV ending";
// return;
// }
// qDebug() << QString("Core: AV ending from %1").arg(friendId);
// cleanupCall(call_index);
// emit static_cast<Core*>(core)->avEnding(friendId, call_index);
//}
void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
@ -669,7 +606,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav)
if (!groupCalls[groupId].active)
return;
if (groupCalls[groupId].muteMic || !Audio::alInDev)
if (groupCalls[groupId].muteMic || !Audio::isInputReady())
{
groupCalls[groupId].sendAudioTimer->start();
return;
@ -679,17 +616,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav)
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize];
bool frame = false;
ALint samples;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples >= framesize)
{
memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
frame = 1;
}
if (frame)
if (Audio::tryCaptureSamples(buf, framesize))
{
int r;
if ((r = toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf,

View File

@ -145,7 +145,9 @@ GroupWidget *Group::getGroupWidget()
QStringList Group::getPeerList() const
{
return peers.values();
QStringList peerNames(peers.values());
peerNames.sort(Qt::CaseInsensitive);
return peerNames;
}
void Group::setEventFlag(int f)

View File

@ -189,7 +189,7 @@ void Settings::load()
s.endGroup();
s.beginGroup("Privacy");
typingNotification = s.value("typingNotification", false).toBool();
typingNotification = s.value("typingNotification", true).toBool();
enableLogging = s.value("enableLogging", false).toBool();
encryptLogs = s.value("encryptLogs", false).toBool();
encryptTox = s.value("encryptTox", false).toBool();

View File

@ -219,6 +219,11 @@ fallbackOnTox1:
tox_dns3_kill(tox_dns3);
#if TOX1_SILENT_FALLBACK
toxIdStr = queryTox1(record, silent);
#elif TOX1_ASK_FALLBACK
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol.\n\
Unfortunately tox1 is not secure. Should it be used anyway?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
if (btn == QMessageBox::Ok)
queryTox1(record, silent);
#endif
return toxIdStr;
}
@ -258,6 +263,11 @@ ToxID ToxDNS::resolveToxAddress(const QString &address, bool silent)
{
#if TOX1_SILENT_FALLBACK
toxId = ToxID::fromString(queryTox1(address, silent));
#elif TOX1_ASK_FALLBACK
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol.\n\
Unfortunately tox1 is not secure. Should it be used anyway?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
if (btn == QMessageBox::Ok)
toxId = ToxID::fromString(queryTox1(address, silent));
#else
return toxId;
#endif

View File

@ -25,6 +25,9 @@
/// Tox1 is not encrypted, it's unsafe
#define TOX1_SILENT_FALLBACK 0
/// That said if the user insists ...
#define TOX1_ASK_FALLBACK 1
/// Handles tox1 and tox3 DNS queries
class ToxDNS : public QObject
{

View File

@ -55,6 +55,15 @@ ChatForm::ChatForm(Friend* chatFriend)
statusMessageLabel->setFont(Style::getFont(Style::Medium));
statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
isTypingLabel = new QLabel();
QFont font = isTypingLabel->font();
font.setItalic(true);
font.setPixelSize(8);
isTypingLabel->setFont(font);
QVBoxLayout* mainLayout = dynamic_cast<QVBoxLayout*>(layout());
mainLayout->insertWidget(1, isTypingLabel);
netcam = new NetCamView();
timer = nullptr;
@ -72,6 +81,7 @@ ChatForm::ChatForm(Friend* chatFriend)
connect(callButton, &QPushButton::clicked, this, &ChatForm::onCallTriggered);
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered);
connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered);
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
@ -133,6 +143,21 @@ void ChatForm::onSendTriggered()
msgEdit->clear();
}
void ChatForm::onTextEditChanged()
{
bool isNowTyping;
if (!Settings::getInstance().isTypingNotificationEnabled())
isNowTyping = false;
else
isNowTyping = msgEdit->toPlainText().length() > 0;
if (isTyping != isNowTyping)
{
isTyping = isNowTyping;
Core::getInstance()->sendTyping(f->getFriendID(), isTyping);
}
}
void ChatForm::onAttachClicked()
{
QStringList paths = QFileDialog::getOpenFileNames(0,tr("Send a file"));
@ -211,10 +236,11 @@ void ChatForm::onFileRecvRequest(ToxFile file)
void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
{
qDebug() << "onAvInvite";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvInvite";
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
@ -248,10 +274,11 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
void ChatForm::onAvStart(int FriendId, int CallId, bool video)
{
qDebug() << "onAvStart";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvStart";
audioInputFlag = true;
audioOutputFlag = true;
callId = CallId;
@ -282,11 +309,12 @@ void ChatForm::onAvStart(int FriendId, int CallId, bool video)
void ChatForm::onAvCancel(int FriendId, int)
{
qDebug() << "onAvCancel";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvCancel";
stopCounter();
audioInputFlag = false;
@ -311,11 +339,11 @@ void ChatForm::onAvCancel(int FriendId, int)
void ChatForm::onAvEnd(int FriendId, int)
{
qDebug() << "onAvEnd";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvEnd";
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
@ -338,10 +366,11 @@ void ChatForm::onAvEnd(int FriendId, int)
void ChatForm::onAvRinging(int FriendId, int CallId, bool video)
{
qDebug() << "onAvRinging";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvRinging";
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
@ -367,11 +396,11 @@ void ChatForm::onAvRinging(int FriendId, int CallId, bool video)
void ChatForm::onAvStarting(int FriendId, int CallId, bool video)
{
qDebug() << "onAvStarting";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvStarting";
callButton->disconnect();
videoButton->disconnect();
if (video)
@ -398,11 +427,11 @@ void ChatForm::onAvStarting(int FriendId, int CallId, bool video)
void ChatForm::onAvEnding(int FriendId, int)
{
qDebug() << "onAvEnding";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvEnding";
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
@ -427,11 +456,11 @@ void ChatForm::onAvEnding(int FriendId, int)
void ChatForm::onAvRequestTimeout(int FriendId, int)
{
qDebug() << "onAvRequestTimeout";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvRequestTimeout";
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
@ -454,11 +483,11 @@ void ChatForm::onAvRequestTimeout(int FriendId, int)
void ChatForm::onAvPeerTimeout(int FriendId, int)
{
qDebug() << "onAvPeerTimeout";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvPeerTimeout";
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
@ -481,11 +510,11 @@ void ChatForm::onAvPeerTimeout(int FriendId, int)
void ChatForm::onAvRejected(int FriendId, int)
{
qDebug() << "onAvRejected";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvRejected";
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
@ -510,11 +539,11 @@ void ChatForm::onAvRejected(int FriendId, int)
void ChatForm::onAvMediaChange(int FriendId, int CallId, bool video)
{
qDebug() << "onAvMediaChange";
if (FriendId != f->getFriendID() || CallId != callId)
return;
qDebug() << "onAvMediaChange";
if (video)
{
netcam->show(Core::getInstance()->getVideoSourceFromCall(CallId), f->getDisplayedName());
@ -535,7 +564,7 @@ void ChatForm::onAnswerCallTriggered()
}
void ChatForm::onHangupCallTriggered()
{
{
qDebug() << "onHangupCallTriggered";
audioInputFlag = false;
@ -571,11 +600,11 @@ void ChatForm::onVideoCallTriggered()
void ChatForm::onAvCallFailed(int FriendId)
{
qDebug() << "onAvCallFailed";
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvCallFailed";
audioInputFlag = false;
audioOutputFlag = false;
callButton->disconnect();
@ -852,6 +881,14 @@ void ChatForm::dischargeReceipt(int receipt)
}
}
void ChatForm::setFriendTyping(bool isTyping)
{
if (isTyping)
isTypingLabel->setText(f->getDisplayedName() + " " + tr("is typing..."));
else
isTypingLabel->clear();
}
void ChatForm::clearReciepts()
{
receipts.clear();

View File

@ -39,6 +39,7 @@ public:
void loadHistory(QDateTime since, bool processUndelivered = false);
void dischargeReceipt(int receipt);
void setFriendTyping(bool isTyping);
signals:
void sendFile(int32_t friendId, QString, QString, long long);
@ -75,6 +76,7 @@ public slots:
private slots:
void onSendTriggered();
void onTextEditChanged();
void onAttachClicked();
void onCallTriggered();
void onVideoCallTriggered();
@ -99,6 +101,7 @@ private:
QLabel *callDuration;
QTimer *timer;
QElapsedTimer timeElapsed;
QLabel *isTypingLabel;
QHash<uint, FileTransferInstance*> ftransWidgets;
void startCounter();
@ -106,6 +109,7 @@ private:
QString secondsToDHMS(quint32 duration);
QHash<int, int> receipts;
QMap<int, ChatMessage::Ptr> undeliveredMsgs;
bool isTyping;
};
#endif // CHATFORM_H

View File

@ -32,5 +32,11 @@ LoadHistoryDialog::~LoadHistoryDialog()
QDateTime LoadHistoryDialog::getFromDate()
{
QDateTime res(ui->fromDate->selectedDate());
if (res.date().month() != ui->fromDate->monthShown() || res.date().year() != ui->fromDate->yearShown())
{
QDate newDate(ui->fromDate->yearShown(), ui->fromDate->monthShown(), 1);
res.setDate(newDate);
}
return res;
}

View File

@ -30,8 +30,8 @@
#include "src/autoupdate.h"
static QStringList locales = {"bg", "de", "en", "es", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"};
static QStringList langs = {"Български", "Deutsch", "English", "Español", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"};
static QStringList locales = {"bg", "de", "en", "es", "fr", "it", "mannol", "pirate", "pl", "pt", "ru", "fi", "sv", "uk"};
static QStringList langs = {"Български", "Deutsch", "English", "Español", "Français", "Italiano", "mannol", "Pirate", "Polski", "Português", "Русский", "Suomi", "Svenska", "Українська"};
static QStringList timeFormats = {"hh:mm AP", "hh:mm", "hh:mm:ss AP", "hh:mm:ss"};

View File

@ -43,11 +43,11 @@
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="cbTypingNotification">
<property name="enabled">
<bool>false</bool>
<property name="toolTip">
<string extracomment="Your friends will be able to see when you are typing."/>
</property>
<property name="text">
<string>Typing Notification</string>
<string>Send Typing Notifications</string>
</property>
</widget>
</item>

View File

@ -32,7 +32,7 @@ FriendListWidget::FriendListWidget(QWidget *parent) :
for (Status s : {Status::Online, Status::Away, Status::Busy, Status::Offline})
{
QLayout *l = new QVBoxLayout();
QVBoxLayout *l = new QVBoxLayout();
l->setSpacing(0);
l->setMargin(0);
@ -46,12 +46,12 @@ FriendListWidget::FriendListWidget(QWidget *parent) :
mainLayout->addLayout(layouts[static_cast<int>(Status::Offline)], 4, 0);
}
QLayout* FriendListWidget::getGroupLayout()
QVBoxLayout* FriendListWidget::getGroupLayout()
{
return groupLayout;
}
QLayout* FriendListWidget::getFriendLayout(Status s)
QVBoxLayout* FriendListWidget::getFriendLayout(Status s)
{
auto res = layouts.find(static_cast<int>(s));
if (res != layouts.end())
@ -61,8 +61,11 @@ QLayout* FriendListWidget::getFriendLayout(Status s)
return layouts[static_cast<int>(Status::Online)];
}
void FriendListWidget::moveWidget(QWidget *w, Status s)
void FriendListWidget::moveWidget(QWidget *w, Status s, int hasNewEvents)
{
mainLayout->removeWidget(w);
getFriendLayout(s)->addWidget(w);
if (hasNewEvents == 0)
getFriendLayout(s)->addWidget(w);
else
getFriendLayout(s)->insertWidget(0, w);
}

View File

@ -21,7 +21,7 @@
#include <QHash>
#include "src/corestructs.h"
class QLayout;
class QVBoxLayout;
class QGridLayout;
class QPixmap;
@ -31,17 +31,17 @@ class FriendListWidget : public QWidget
public:
explicit FriendListWidget(QWidget *parent = 0);
QLayout* getGroupLayout();
QLayout* getFriendLayout(Status s);
void moveWidget(QWidget *w, Status s);
QVBoxLayout* getGroupLayout();
QVBoxLayout* getFriendLayout(Status s);
void moveWidget(QWidget *w, Status s, int hasNewEvents);
signals:
public slots:
private:
QHash<int, QLayout*> layouts;
QLayout *groupLayout;
QHash<int, QVBoxLayout*> layouts;
QVBoxLayout *groupLayout;
QGridLayout *mainLayout;
};

View File

@ -242,6 +242,7 @@ void Widget::init()
connect(core, &Core::avInvite, this, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection);
connect(core, &Core::blockingGetPassword, this, &Widget::getPassword, Qt::BlockingQueuedConnection);
connect(core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int)));
connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(int,QString,int)));
@ -318,6 +319,7 @@ Widget::~Widget()
coreThread->wait(500); // In case of deadlock (can happen with QtAudio/PA bugs)
if (!coreThread->isFinished())
coreThread->terminate();
AutoUpdater::abortUpdates();
delete core;
delete settingsWidget;
delete addFriendForm;
@ -722,7 +724,7 @@ void Widget::onFriendStatusChanged(int friendId, Status status)
if (!f)
return;
contactListWidget->moveWidget(f->getFriendWidget(), status);
contactListWidget->moveWidget(f->getFriendWidget(), status, f->getEventFlag());
bool isActualChange = f->getStatus() != status;
@ -1176,6 +1178,14 @@ void Widget::getPassword(QString info, int passtype, uint8_t* salt)
}
}
void Widget::onFriendTypingChanged(int friendId, bool isTyping)
{
Friend* f = FriendList::findFriend(friendId);
if (!f)
return;
f->getChatForm()->setFriendTyping(isTyping);
}
void Widget::onSetShowSystemTray(bool newValue){
icon->setVisible(newValue);
}

View File

@ -132,6 +132,7 @@ private slots:
void onIconClick(QSystemTrayIcon::ActivationReason);
void onUserAwayCheck();
void getPassword(QString info, int passtype, uint8_t* salt);
void onFriendTypingChanged(int friendId, bool isTyping);
void onSetShowSystemTray(bool newValue);
void onSplitterMoved(int pos, int index);

View File

@ -11,7 +11,8 @@ TRANSLATIONS = translations/es.ts \
translations/pl.ts \
translations/ru.ts \
translations/sv.ts \
translations/uk.ts
translations/uk.ts \
translations/pt.ts
#rules to generate ts
isEmpty(QMAKE_LUPDATE) {

96
translations/it.ts vendored
View File

@ -187,67 +187,72 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?</tr
<context>
<name>ChatForm</name>
<message>
<location filename="../src/widget/form/chatform.cpp" line="66"/>
<location filename="../src/widget/form/chatform.cpp" line="75"/>
<source>Load History...</source>
<translation>Carica log...</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="137"/>
<location filename="../src/widget/form/chatform.cpp" line="162"/>
<source>Send a file</source>
<translation>Invia un file</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="145"/>
<location filename="../src/widget/form/chatform.cpp" line="170"/>
<source>File not read</source>
<translation>Impossibile leggere il file</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="145"/>
<location filename="../src/widget/form/chatform.cpp" line="170"/>
<source>qTox wasn&apos;t able to open %1</source>
<translation>qTox non è riuscito ad aprire %1</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="150"/>
<location filename="../src/widget/form/chatform.cpp" line="175"/>
<source>Bad Idea</source>
<translation>Pessima idea</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="150"/>
<location filename="../src/widget/form/chatform.cpp" line="175"/>
<source>You&apos;re trying to send a special (sequential) file, that&apos;s not going to work!</source>
<translation>Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà!</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="257"/>
<location filename="../src/widget/form/chatform.cpp" line="283"/>
<source>%1 is calling</source>
<translation>%1 ti sta chiamando</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="328"/>
<location filename="../src/widget/form/chatform.cpp" line="356"/>
<source>%1 stopped calling</source>
<translation>%1 ha fermato la chiamata</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="384"/>
<location filename="../src/widget/form/chatform.cpp" line="413"/>
<source>Calling to %1</source>
<translation>Stai chiamando %1</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="676"/>
<location filename="../src/widget/form/chatform.cpp" line="705"/>
<source>Failed to send file &quot;%1&quot;</source>
<translation>Invio del file &quot;%1&quot; fallito</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="810"/>
<location filename="../src/widget/form/chatform.cpp" line="839"/>
<source>Call with %1 ended. %2</source>
<translation>Chiamata con %1 terminata. %2</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="829"/>
<location filename="../src/widget/form/chatform.cpp" line="858"/>
<source>Call duration: </source>
<translation>Durata chiamata: </translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="525"/>
<location filename="../src/widget/form/chatform.cpp" line="905"/>
<source>is typing...</source>
<translation>sta scrivendo...</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="554"/>
<source>Call rejected</source>
<translation>Chiamata rifiutata</translation>
</message>
@ -801,7 +806,7 @@ Soprannome:</translation>
<translation>Rimuovi messaggi visualizzati</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="296"/>
<location filename="../src/widget/form/genericchatform.cpp" line="297"/>
<source>Cleared</source>
<translation>Pulito</translation>
</message>
@ -1173,8 +1178,8 @@ Vuoi eliminare il vecchio file?</translation>
<name>PrivacySettings</name>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="50"/>
<source>Typing Notification</source>
<translation>Notifica quando qualcuno sta scrivendo</translation>
<source>Send Typing Notifications</source>
<translation>Permetti ai miei contatti di vedere quando sto scrivendo</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="60"/>
@ -1255,13 +1260,13 @@ Vuoi eliminare il vecchio file?</translation>
<translation>%1.tox è stato importato con successo</translation>
</message>
<message>
<location filename="../src/autoupdate.cpp" line="430"/>
<location filename="../src/autoupdate.cpp" line="457"/>
<source>Update</source>
<comment>The title of a message box</comment>
<translation>Nuova versione</translation>
</message>
<message>
<location filename="../src/autoupdate.cpp" line="431"/>
<location filename="../src/autoupdate.cpp" line="458"/>
<source>An update is available, do you want to download it now?
It will be installed when qTox restarts.</source>
<translation>È disponibile una nuova versione di qTox, vuoi scaricarla adesso?
@ -1372,6 +1377,15 @@ Verrà installata al riavvio del programma.</translation>
<comment>Error with the DNS</comment>
<translation>La risposta del server DNS non contiene un Tox ID valido</translation>
</message>
<message>
<location filename="../src/toxdns.cpp" line="223"/>
<location filename="../src/toxdns.cpp" line="267"/>
<source>It appears that qTox has to use the old tox1 protocol.
Unfortunately tox1 is not secure. Should it be used anyway?</source>
<translation>Sembra che qTox debba usare il vecchio protocollo tox1.
Sfortunatamente il protocollo tox1 non è sicuro.
Usare comunque il protocollo tox1?</translation>
</message>
</context>
<context>
<name>ToxURIDialog</name>
@ -1450,116 +1464,116 @@ Verrà installata al riavvio del programma.</translation>
<translation>Occupato</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="429"/>
<location filename="../src/widget/widget.cpp" line="431"/>
<source>Choose a profile</source>
<translation>Scegli un profilo</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="430"/>
<location filename="../src/widget/widget.cpp" line="432"/>
<source>Please choose which identity to use</source>
<translation>Per favore scegli quale identità usare</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="451"/>
<location filename="../src/widget/widget.cpp" line="453"/>
<source>Choose a profile picture</source>
<translation>Scegli un&apos;immagine per il profilo</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="458"/>
<location filename="../src/widget/widget.cpp" line="465"/>
<location filename="../src/widget/widget.cpp" line="486"/>
<location filename="../src/widget/widget.cpp" line="460"/>
<location filename="../src/widget/widget.cpp" line="467"/>
<location filename="../src/widget/widget.cpp" line="488"/>
<source>Error</source>
<translation>Errore</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="458"/>
<location filename="../src/widget/widget.cpp" line="460"/>
<source>Unable to open this file</source>
<translation>Impossibile aprire il file</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="465"/>
<location filename="../src/widget/widget.cpp" line="467"/>
<source>Unable to read this image</source>
<translation>Impossibile leggere l&apos;immagine</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="486"/>
<location filename="../src/widget/widget.cpp" line="488"/>
<source>This image is too big</source>
<translation>L&apos;immagine è troppo grande</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="526"/>
<location filename="../src/widget/widget.cpp" line="528"/>
<source>Toxcore failed to start, the application will terminate after you close this message.</source>
<translation>Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio.</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="535"/>
<location filename="../src/widget/widget.cpp" line="537"/>
<source>toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart.</source>
<comment>popup text</comment>
<translation>Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma.</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="574"/>
<location filename="../src/widget/widget.cpp" line="576"/>
<source>Add friend</source>
<translation>Aggiungi contatto</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="586"/>
<location filename="../src/widget/widget.cpp" line="588"/>
<source>File transfers</source>
<translation>Files trasferiti</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="614"/>
<location filename="../src/widget/widget.cpp" line="616"/>
<source>Settings</source>
<translation>Impostazioni</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="714"/>
<location filename="../src/widget/widget.cpp" line="716"/>
<source>Couldn&apos;t request friendship</source>
<translation>Impossibile inviare la richiesta d&apos;amicizia</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="742"/>
<location filename="../src/widget/widget.cpp" line="744"/>
<source>away</source>
<comment>contact status</comment>
<translation>assente</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="744"/>
<location filename="../src/widget/widget.cpp" line="746"/>
<source>busy</source>
<comment>contact status</comment>
<translation>occupato</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="746"/>
<location filename="../src/widget/widget.cpp" line="748"/>
<source>offline</source>
<comment>contact status</comment>
<translation>offline</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="748"/>
<location filename="../src/widget/widget.cpp" line="750"/>
<source>online</source>
<comment>contact status</comment>
<translation>online</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="751"/>
<location filename="../src/widget/widget.cpp" line="753"/>
<source>%1 is now %2</source>
<comment>e.g. &quot;Dubslow is now online&quot;</comment>
<translation>%1 è ora %2</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="988"/>
<location filename="../src/widget/widget.cpp" line="990"/>
<source>&lt;Unknown&gt;</source>
<comment>Placeholder when we don&apos;t know someone&apos;s name in a group chat</comment>
<translation>&lt;Sconosciuto&gt;</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="1013"/>
<location filename="../src/widget/widget.cpp" line="1015"/>
<source>%1 has set the title to %2</source>
<translation>%1 ha impostato il titolo in %2</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="1164"/>
<location filename="../src/widget/widget.cpp" line="1166"/>
<source>Message failed to send</source>
<translation>Impossibile inviare il messaggio</translation>
</message>

1578
translations/pt.ts vendored Normal file

File diff suppressed because it is too large Load Diff

422
translations/uk.ts vendored

File diff suppressed because it is too large Load Diff