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);
{
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());
@ -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);
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

416
translations/uk.ts vendored
View File

@ -37,32 +37,42 @@
<translation>Пристрій захоплення</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="98"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="95"/>
<source>Rescan audio devices</source>
<translation>Пересканувати аудіо пристрої</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="102"/>
<source>Filter audio</source>
<translation>Фільтр звуку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="112"/>
<source>Video Settings</source>
<translation>Параметри відео</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="109"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="123"/>
<source>Resolution</source>
<translation>Роздільна здатність</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="126"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="140"/>
<source>Hue</source>
<translation>Відтінок</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="140"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="154"/>
<source>Brightness</source>
<translation>Яскравість</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="154"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="168"/>
<source>Saturation</source>
<translation>Насиченість</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="168"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="182"/>
<source>Contrast</source>
<translation>Контраст</translation>
</message>
@ -177,52 +187,72 @@ Ignore the proxy and connect to the Internet directly ?</source>
<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>Завантажити історію…</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="135"/>
<location filename="../src/widget/form/chatform.cpp" line="162"/>
<source>Send a file</source>
<translation>Надіслати файл</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>Файл не читається</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="170"/>
<source>qTox wasn&apos;t able to open %1</source>
<translation>qTox не може відкрити файл %1</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="175"/>
<source>Bad Idea</source>
<translation>Погана ідея</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="145"/>
<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>Ви намагаєтесь передати спеціальний (послідовний) файл, це не так працює!</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="251"/>
<source>%1 calling</source>
<translation>%1 викликає</translation>
<location filename="../src/widget/form/chatform.cpp" line="283"/>
<source>%1 is calling</source>
<translation>%1 дзвонить</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="320"/>
<location filename="../src/widget/form/chatform.cpp" line="356"/>
<source>%1 stopped calling</source>
<translation>%1 припинив виклик</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="376"/>
<location filename="../src/widget/form/chatform.cpp" line="413"/>
<source>Calling to %1</source>
<translation>Викликаємо %1</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="801"/>
<location filename="../src/widget/form/chatform.cpp" line="705"/>
<source>Failed to send file &quot;%1&quot;</source>
<translation>Не вдалось відправити файл «%1»</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="839"/>
<source>Call with %1 ended. %2</source>
<translation>Виклик із %1 завершено. %2</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="820"/>
<location filename="../src/widget/form/chatform.cpp" line="858"/>
<source>Call duration: </source>
<translation>Тривалість дзвінка: </translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="517"/>
<location filename="../src/widget/form/chatform.cpp" line="905"/>
<source>is typing...</source>
<translation>набирає...</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="554"/>
<source>Call rejected</source>
<translation>Дзвінок відхилено</translation>
</message>
@ -238,104 +268,104 @@ Ignore the proxy and connect to the Internet directly ?</source>
<context>
<name>Core</name>
<message>
<location filename="../src/core.cpp" line="223"/>
<location filename="../src/core.cpp" line="234"/>
<source>Toxing on qTox</source>
<translation>Вітання з qTox</translation>
</message>
<message>
<location filename="../src/core.cpp" line="224"/>
<location filename="../src/core.cpp" line="235"/>
<source>qTox User</source>
<translation>Користувач qTox</translation>
</message>
<message>
<location filename="../src/core.cpp" line="724"/>
<location filename="../src/core.cpp" line="739"/>
<source>Friend is already added</source>
<translation>Друга вже додано</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1165"/>
<location filename="../src/core.cpp" line="1180"/>
<source>Encryption error</source>
<translation>Помилка шифрування</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1165"/>
<location filename="../src/core.cpp" line="1180"/>
<source>The .tox file is encrypted, but encryption was not checked, continuing regardless.</source>
<translation>Файл .tox зашифровано, але шифрування не перевірено, попри це продовжуємо.</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1172"/>
<location filename="../src/core.cpp" line="1187"/>
<source>Tox datafile decryption password</source>
<translation>Розшифрування файлу даних Tox</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1174"/>
<location filename="../src/core.cpp" line="1186"/>
<location filename="../src/core.cpp" line="1250"/>
<location filename="../src/core.cpp" line="1189"/>
<location filename="../src/core.cpp" line="1201"/>
<location filename="../src/core.cpp" line="1265"/>
<source>Password error</source>
<translation>Помилка паролю</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1174"/>
<location filename="../src/core.cpp" line="1250"/>
<location filename="../src/core.cpp" line="1189"/>
<location filename="../src/core.cpp" line="1265"/>
<source>Failed to setup password.
Empty password.</source>
<translation>Не вдалось встановити пароль.
Пароль пустий.</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1182"/>
<location filename="../src/core.cpp" line="1197"/>
<source>Try Again</source>
<translation>Спробуйте ще раз</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1183"/>
<location filename="../src/core.cpp" line="1198"/>
<source>Change profile</source>
<translation>Змінити профіль</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1184"/>
<location filename="../src/core.cpp" line="1199"/>
<source>Reinit current profile</source>
<translation>Пере ініціалізувати поточний профіль</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1187"/>
<location filename="../src/core.cpp" line="1202"/>
<source>Wrong password has been entered</source>
<translation>Введено хибний пароль</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1248"/>
<location filename="../src/core.cpp" line="1263"/>
<source>History Log decryption password</source>
<translation>Розшифрування історії</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1256"/>
<source>Your history is encrypted with different password
<location filename="../src/core.cpp" line="1271"/>
<source>Your history is encrypted with different password.
Do you want to try another password?</source>
<translation>Ваша історія зашифрована інакшим паролем
<translation>Ваша історія зашифрована інакшим паролем.
Бажаєте спробувати інший пароль?</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1255"/>
<location filename="../src/core.cpp" line="1281"/>
<source>History</source>
<translation>Історія</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1281"/>
<source>Due to incorret password history will be disabled.</source>
<translation>Введено некоректний пароль, звітування вимкнено.</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1270"/>
<source>Encrypted log</source>
<translation>Зашифрований звіт</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1266"/>
<source>Loggin</source>
<translation>Звітування</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1266"/>
<source>Due to incorret password logging will be disabled</source>
<translation>Введено некоректний пароль, звітування вимкнено</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1339"/>
<location filename="../src/core.cpp" line="1354"/>
<source>NO Password</source>
<translation>Відсутній пароль</translation>
</message>
<message>
<location filename="../src/core.cpp" line="1339"/>
<location filename="../src/core.cpp" line="1354"/>
<source>Will be saved without encryption!</source>
<translation>Буде збережено без шифрування!</translation>
</message>
@ -447,14 +477,16 @@ Do you want to try another password?</source>
<translation>Автоматично приймати файли від даного друга</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="226"/>
<location filename="../src/widget/friendwidget.cpp" line="238"/>
<source>User alias</source>
<translation>Псевдонім користувача</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="226"/>
<source>Alias:</source>
<translation>Псевдонім:</translation>
<location filename="../src/widget/friendwidget.cpp" line="238"/>
<source>You can also set this by clicking the chat form name.
Alias:</source>
<translation>Ви також можете встановити його натиснувши на назву форми в чаті.
Псевдонім:</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="109"/>
@ -477,25 +509,25 @@ Do you want to try another password?</source>
<translation>Основні</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalform.cpp" line="73"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="79"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="81"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="87"/>
<source>None</source>
<translation>Відсутній</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalform.cpp" line="223"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="242"/>
<source>Choose an auto accept directory</source>
<comment>popup title</comment>
<translation>Оберіть теку, для автоматичного отримання файлів</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalform.cpp" line="280"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="299"/>
<source>Call active</source>
<comment>popup title</comment>
<translation>Дзвінок активний</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalform.cpp" line="281"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="300"/>
<source>You can&apos;t disconnect while a call is active!</source>
<comment>popup text</comment>
<translation>Ви не можете від&apos;єднатись під час активного дзвінка!</translation>
@ -526,47 +558,52 @@ Do you want to try another password?</source>
<translation>Портативний запуск</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="101"/>
<source>Show system tray icon</source>
<translation>Показувати піктрограму в системному лотку</translation>
<location filename="../src/widget/form/settings/generalsettings.ui" line="99"/>
<source>System tray integration</source>
<translation>Інтеграція із системним лотком</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="114"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="108"/>
<source>Show system tray icon</source>
<translation>Показувати піктограму в системному лотку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="123"/>
<source>Start in tray</source>
<translation>Запускати у системному лотку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="127"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="136"/>
<source>Close to tray</source>
<translation>Закривати до лотку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="140"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="149"/>
<source>Minimize to tray</source>
<translation>Мінімізувати до лотку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="149"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="168"/>
<source>Show contacts&apos; status changes</source>
<translation>Показувати зміну статусів контактів</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="179"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="198"/>
<source>Provided in minutes</source>
<translation>Встановлено в хвилинах</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="198"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="217"/>
<source>Set to 0 to disable</source>
<translation>Встановіть 0, аби вимкнути</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="204"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="223"/>
<source> minutes</source>
<translation> хвилин</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="266"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="285"/>
<source>Theme</source>
<translation>Графічна тема</translation>
</message>
@ -576,128 +613,148 @@ Do you want to try another password?</source>
<translation>Мова інтерфейсу</translation>
</message>
<message>
<source>Show system tray</source>
<translation type="vanished">Показувати в системному лотку</translation>
<location filename="../src/widget/form/settings/generalsettings.ui" line="158"/>
<source>Light icon</source>
<translation>Світлі піктограми</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="156"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="175"/>
<source>Check for updates on startup (unstable)</source>
<translation>Перевіряти оновлення під час запуску (нестабільна функція)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="163"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="182"/>
<source>Focus qTox when a message is received</source>
<translation>Перехоплювати фокус вікна при отриманні повідомлення</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="170"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="189"/>
<source>Faux offline messaging</source>
<translation>Фальшивий поза мережевий обмін повідомленнями</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="185"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="204"/>
<source>Auto away after (0 to disable)</source>
<translation>Авто-статус «Відійшов» (0=вимкнено)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="221"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="240"/>
<source>You can set this on a per-friend basis by right clicking them.</source>
<comment>autoaccept cb tooltip</comment>
<translation type="unfinished"></translation>
<translation>Ви також можете встановити це значення до кожного друга окремо викликавши правою кнопкою меню навпроти нього.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="224"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="243"/>
<source>Autoaccept files</source>
<translation>Автоматично приймати файли</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="233"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="252"/>
<source>Save files in</source>
<translation>Зберігати файли до</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="246"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="265"/>
<source>PushButton</source>
<translation>Тисніть кнопку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="272"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="291"/>
<source>Use emoticons</source>
<translation>Використовувати смайлики</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="281"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="309"/>
<source>Smiley Pack</source>
<extracomment>Text on smiley pack label</extracomment>
<translation>Пакунок смайликів</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="359"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="383"/>
<source>Style</source>
<translation>Стиль</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="380"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="400"/>
<source>Theme color</source>
<translation>Колір графічної теми</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="401"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="417"/>
<source>Emoticon size</source>
<translation>Розмір смайликів</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="417"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="433"/>
<source> px</source>
<translation> px</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="437"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="449"/>
<source>Timestamp format</source>
<translation>Формати часового відбитку</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="459"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="471"/>
<source>Connection Settings</source>
<translation>Параметри підключення</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="471"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="483"/>
<source>Enable IPv6 (recommended)</source>
<extracomment>Text on a checkbox to enable IPv6</extracomment>
<translation>Дозволити IPv6 (рекомендовано)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="478"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="490"/>
<source>Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary.</source>
<extracomment>force tcp checkbox tooltip</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="481"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="493"/>
<source>Enable UDP (recommended)</source>
<extracomment>Text on checkbox to disable UDP</extracomment>
<translation>Дозволити UDP (рекомендовано)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="488"/>
<source>Use proxy (SOCKS5)</source>
<translation>Використовувати проксі (SOCKS5)</translation>
<location filename="../src/widget/form/settings/generalsettings.ui" line="502"/>
<source>Proxy type</source>
<translation>Тип проксі</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="500"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="516"/>
<source>None</source>
<translation>Відсутній</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="521"/>
<source>SOCKS5</source>
<translation>SOCKS5</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="526"/>
<source>HTTP</source>
<translation>HTTP</translation>
</message>
<message>
<source>Use proxy (SOCKS5)</source>
<translation type="vanished">Використовувати проксі (SOCKS5)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="541"/>
<source>Address</source>
<extracomment>Text on proxy addr label</extracomment>
<translation>Адреса</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="510"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="551"/>
<source>Port</source>
<extracomment>Text on proxy port label</extracomment>
<translation>Порт</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="529"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="570"/>
<source>Reconnect</source>
<comment>reconnect button</comment>
<translation>Повторно під&apos;єднатись</translation>
@ -706,53 +763,53 @@ Do you want to try another password?</source>
<context>
<name>GenericChatForm</name>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="62"/>
<location filename="../src/widget/form/genericchatform.cpp" line="68"/>
<source>Send message</source>
<translation>Відправити повідомлення</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="64"/>
<location filename="../src/widget/form/genericchatform.cpp" line="70"/>
<source>Smileys</source>
<translation>Смайлики</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="68"/>
<location filename="../src/widget/form/genericchatform.cpp" line="74"/>
<source>Send file(s)</source>
<translation>Відправити файл(и)</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="71"/>
<location filename="../src/widget/form/genericchatform.cpp" line="77"/>
<source>Audio call: RED means you&apos;re on a call</source>
<translation>Аудіо дзвінок: Червоний - дзвінок активний</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="74"/>
<location filename="../src/widget/form/genericchatform.cpp" line="80"/>
<source>Video call: RED means you&apos;re on a call</source>
<translation>Відео дзвінок: Червоний - дзвінок активний</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="77"/>
<location filename="../src/widget/form/genericchatform.cpp" line="83"/>
<source>Toggle speakers volume: RED is OFF</source>
<translation>Перемкнути стан гучності відтворення: Червоний - вимкнено</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="80"/>
<location filename="../src/widget/form/genericchatform.cpp" line="86"/>
<source>Toggle microphone: RED is OFF</source>
<translation>Перемкнути рівень підсилення мікрофону: Червоний - вимкнено</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="146"/>
<location filename="../src/widget/form/genericchatform.cpp" line="188"/>
<location filename="../src/widget/form/genericchatform.cpp" line="149"/>
<location filename="../src/widget/form/genericchatform.cpp" line="192"/>
<source>Save chat log</source>
<translation>Зберегти чат</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="147"/>
<location filename="../src/widget/form/genericchatform.cpp" line="150"/>
<source>Clear displayed messages</source>
<translation>Очистити показані повідомлення</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="305"/>
<location filename="../src/widget/form/genericchatform.cpp" line="297"/>
<source>Cleared</source>
<translation>Очищено</translation>
</message>
@ -760,13 +817,13 @@ Do you want to try another password?</source>
<context>
<name>GroupChatForm</name>
<message>
<location filename="../src/widget/form/groupchatform.cpp" line="58"/>
<location filename="../src/widget/form/groupchatform.cpp" line="57"/>
<source>%1 users in chat</source>
<comment>Number of users in chat</comment>
<translation>Користувачів у чаті: %1</translation>
</message>
<message>
<location filename="../src/widget/form/groupchatform.cpp" line="109"/>
<location filename="../src/widget/form/groupchatform.cpp" line="108"/>
<source>%1 users in chat</source>
<translation>Користувачів у чаті: %1</translation>
</message>
@ -774,23 +831,40 @@ Do you want to try another password?</source>
<context>
<name>GroupWidget</name>
<message>
<location filename="../src/widget/groupwidget.cpp" line="42"/>
<location filename="../src/widget/groupwidget.cpp" line="64"/>
<location filename="../src/widget/groupwidget.cpp" line="43"/>
<location filename="../src/widget/groupwidget.cpp" line="80"/>
<source>%1 users in chat</source>
<translation>Користувачів у чаті: %1</translation>
</message>
<message>
<location filename="../src/widget/groupwidget.cpp" line="44"/>
<location filename="../src/widget/groupwidget.cpp" line="66"/>
<location filename="../src/widget/groupwidget.cpp" line="45"/>
<location filename="../src/widget/groupwidget.cpp" line="82"/>
<source>0 users in chat</source>
<translation>Немає користувачів</translation>
</message>
<message>
<location filename="../src/widget/groupwidget.cpp" line="53"/>
<location filename="../src/widget/groupwidget.cpp" line="55"/>
<source>Quit group</source>
<comment>Menu to quit a groupchat</comment>
<translation>Вийти з групи</translation>
</message>
<message>
<location filename="../src/widget/groupwidget.cpp" line="54"/>
<source>Set title...</source>
<translation>Встановити заголовок…</translation>
</message>
<message>
<location filename="../src/widget/groupwidget.cpp" line="67"/>
<source>Group title</source>
<translation>Заголовок групи</translation>
</message>
<message>
<location filename="../src/widget/groupwidget.cpp" line="67"/>
<source>You can also set this by clicking the chat form name.
Title:</source>
<translation>Ви також можете встановити його натиснувши на назву форми в чаті.
Заголовок:</translation>
</message>
</context>
<context>
<name>IdentityForm</name>
@ -881,42 +955,44 @@ Do you want to try another password?</source>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="199"/>
<source>Are you sure you want to delete this profile?</source>
<source>Are you sure you want to delete this profile?
Associated friend information and chat logs will be deleted as well.</source>
<comment>deletion confirmation text</comment>
<translation>Дійсно вилучити даний профіль?</translation>
<translation>Дійсно вилучити даний профіль?
Пов&apos;язана інформація про друзів та історія спілкування також буде вилучена.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="211"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="218"/>
<source>Import profile</source>
<comment>import dialog title</comment>
<translation>Імпортувати профіль</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="213"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="220"/>
<source>Tox save file (*.tox)</source>
<comment>import dialog filter</comment>
<translation>Файл збереження Tox (*.tox)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="223"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="230"/>
<source>Ignoring non-Tox file</source>
<comment>popup title</comment>
<translation>Ігнорування не Tox файлу</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="224"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="231"/>
<source>Warning: you&apos;ve chosen a file that is not a Tox save file; ignoring.</source>
<comment>popup text</comment>
<translation>Увага: вказаний вами файл не є файлом збереження Tox; ігнорую.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="230"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="237"/>
<source>Profile already exists</source>
<comment>import confirm title</comment>
<translation>Профіль вже існує</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="231"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="238"/>
<source>A profile named &quot;%1&quot; already exists. Do you want to erase it?</source>
<comment>import confirm text</comment>
<translation>Профіль із назвою «%1» вже існує. Бажаєте стерти його?</translation>
@ -1104,9 +1180,13 @@ Do you want to delete old history file?</source>
<context>
<name>PrivacySettings</name>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="50"/>
<source>Typing Notification</source>
<translation>Увімкнути сповіщення про набір</translation>
<translation type="vanished">Увімкнути сповіщення про набір</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="50"/>
<source>Send Typing Notifications</source>
<translation>Надсилати сповіщення про набір</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="60"/>
@ -1187,13 +1267,13 @@ Do you want to delete old history file?</source>
<translation>%1.tox успішно імпортовано</translation>
</message>
<message>
<location filename="../src/autoupdate.cpp" line="431"/>
<location filename="../src/autoupdate.cpp" line="457"/>
<source>Update</source>
<comment>The title of a message box</comment>
<translation>Оновити</translation>
</message>
<message>
<location filename="../src/autoupdate.cpp" line="432"/>
<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>Доступне оновлення, бажаєте завантажити його зараз?
@ -1204,6 +1284,16 @@ It will be installed when qTox restarts.</source>
<source>Tox URI to parse</source>
<translation>Tox URI для розбору</translation>
</message>
<message>
<location filename="../src/main.cpp" line="69"/>
<source>Starts new instance and loads specified profile.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/main.cpp" line="69"/>
<source>profile</source>
<translation>профіль</translation>
</message>
<message>
<location filename="../src/misc/style.cpp" line="72"/>
<source>Default</source>
@ -1294,6 +1384,13 @@ It will be installed when qTox restarts.</source>
<comment>Error with the DNS</comment>
<translation>Відповідь DNS не містить жодного коректного Tox ID</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 type="unfinished"></translation>
</message>
</context>
<context>
<name>ToxURIDialog</name>
@ -1334,159 +1431,158 @@ It will be installed when qTox restarts.</source>
<context>
<name>Widget</name>
<message>
<location filename="../src/widget/widget.cpp" line="77"/>
<location filename="../src/widget/widget.cpp" line="92"/>
<source>Online</source>
<translation>В мережі</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="80"/>
<location filename="../src/widget/widget.cpp" line="95"/>
<source>Away</source>
<translation>Відійшов</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="83"/>
<location filename="../src/widget/widget.cpp" line="98"/>
<source>Busy</source>
<translation>Зайнятий</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="86"/>
<location filename="../src/widget/widget.cpp" line="101"/>
<source>&amp;Quit</source>
<translation>&amp;Вийти</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="89"/>
<source>Change status to:</source>
<translation>Змінити статус на:</translation>
<translation type="vanished">Змінити статус на:</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="163"/>
<location filename="../src/widget/widget.cpp" line="173"/>
<source>Online</source>
<comment>Button to set your status to &apos;Online&apos;</comment>
<translation>В мережі</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="165"/>
<location filename="../src/widget/widget.cpp" line="175"/>
<source>Away</source>
<comment>Button to set your status to &apos;Away&apos;</comment>
<translation>Відійшов</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="167"/>
<location filename="../src/widget/widget.cpp" line="177"/>
<source>Busy</source>
<comment>Button to set your status to &apos;Busy&apos;</comment>
<translation>Зайнятий</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="408"/>
<location filename="../src/widget/widget.cpp" line="431"/>
<source>Choose a profile</source>
<translation>Оберіть профіль</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="409"/>
<location filename="../src/widget/widget.cpp" line="432"/>
<source>Please choose which identity to use</source>
<translation>Оберіть ідентифікатор для використання</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="430"/>
<location filename="../src/widget/widget.cpp" line="453"/>
<source>Choose a profile picture</source>
<translation>Оберіть зображення для профілю</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="437"/>
<location filename="../src/widget/widget.cpp" line="444"/>
<location filename="../src/widget/widget.cpp" line="465"/>
<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>Помилка</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="437"/>
<location filename="../src/widget/widget.cpp" line="460"/>
<source>Unable to open this file</source>
<translation>Неможливо відкрити цей файл</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="444"/>
<location filename="../src/widget/widget.cpp" line="467"/>
<source>Unable to read this image</source>
<translation>Неможливо прочитати це зображення</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="465"/>
<location filename="../src/widget/widget.cpp" line="488"/>
<source>This image is too big</source>
<translation>Зображення завелике</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="492"/>
<location filename="../src/widget/widget.cpp" line="528"/>
<source>Toxcore failed to start, the application will terminate after you close this message.</source>
<translation>Помилка запуску ядра tox, програма буде завершена після закриття цього повідомлення.</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="501"/>
<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>Помилка запуску ядра tox із поточними параметрами проксі. qTox не працює; змініть параметри і перезапустіть.</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="541"/>
<location filename="../src/widget/widget.cpp" line="576"/>
<source>Add friend</source>
<translation>Додати друга</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="553"/>
<location filename="../src/widget/widget.cpp" line="588"/>
<source>File transfers</source>
<translation>Передачі файлів</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="578"/>
<location filename="../src/widget/widget.cpp" line="616"/>
<source>Settings</source>
<translation>Параметри</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="677"/>
<location filename="../src/widget/widget.cpp" line="716"/>
<source>Couldn&apos;t request friendship</source>
<translation>Не вдалось надіслати запит на дружбу</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="705"/>
<location filename="../src/widget/widget.cpp" line="744"/>
<source>away</source>
<comment>contact status</comment>
<translation>Відійшов</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="707"/>
<location filename="../src/widget/widget.cpp" line="746"/>
<source>busy</source>
<comment>contact status</comment>
<translation>Зайнятий</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="709"/>
<location filename="../src/widget/widget.cpp" line="748"/>
<source>offline</source>
<comment>contact status</comment>
<translation>Поза мережею</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="711"/>
<location filename="../src/widget/widget.cpp" line="750"/>
<source>online</source>
<comment>contact status</comment>
<translation>В мережі</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="714"/>
<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 тепер вже відомий як %2</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="948"/>
<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;Невідомо&gt;</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="971"/>
<location filename="../src/widget/widget.cpp" line="1015"/>
<source>%1 has set the title to %2</source>
<translation>%1 встановив тему %2</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="1120"/>
<location filename="../src/widget/widget.cpp" line="1166"/>
<source>Message failed to send</source>
<translation>Не вдалось відправити повідомлення</translation>
</message>