diff --git a/README.md b/README.md index 26bd66749..2c55936ed 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,6 @@ This client runs on Windows, Linux and Mac natively.
##Developer overview: -[GitStats](http://207.12.89.155/index.html)
+[GitStats](http://104.219.184.93/index.html)
[Mac & Linux jenkins](https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/)
-[Windows jenkins](http://207.12.89.155:8080)
+[Windows jenkins](http://104.219.184.93:8080)
diff --git a/qtox.pro b/qtox.pro index 7ad00b4b3..d8c76f360 100644 --- a/qtox.pro +++ b/qtox.pro @@ -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 diff --git a/res.qrc b/res.qrc index 0b461bf8e..d4a844ede 100644 --- a/res.qrc +++ b/res.qrc @@ -127,6 +127,7 @@ translations/mannol.qm translations/pirate.qm translations/pl.qm + translations/pt.qm translations/ru.qm translations/sv.qm translations/uk.qm diff --git a/src/audio.cpp b/src/audio.cpp index d90d5d48e..64e0b0961 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -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 #include +#include #include std::atomic 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: "<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; +} diff --git a/src/audio.h b/src/audio.h index 9c4f87353..6fb8e7107 100644 --- a/src/audio.h +++ b/src/audio.h @@ -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 userCount; + static ALCdevice* alOutDev, *alInDev; + static QMutex* audioInLock, *audioOutLock; }; #endif // AUDIO_H diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 8c2a2ef02..4d6dbb87e 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -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 newFlist = parseFlist(newFlistData); QList diff = genUpdateDiff(newFlist); + if (abortFlag) + return false; + qDebug() << "AutoUpdater: Need to update "< updateFlist = parseFlist(updateFlistData); QList 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; +} diff --git a/src/autoupdate.h b/src/autoupdate.h index 4462db105..3937e3b20 100644 --- a/src/autoupdate.h +++ b/src/autoupdate.h @@ -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 diff --git a/src/coreav.cpp b/src/coreav.cpp index 5fb12e128..b26be579c 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -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); - -// 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)->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)->avStarting(friendId, call_index, false); -// } - -// delete transSettings; -//} - -//void Core::onAvEnding(void* _toxav, int32_t call_index, void* core) -//{ -// ToxAv* toxav = static_cast(_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)->avEnding(friendId, call_index); -//} - void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core) { ToxAv* toxav = static_cast(_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, diff --git a/src/group.cpp b/src/group.cpp index 6a5ff4d8f..a75d933a2 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -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) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 1f0e8a0c8..4efacb541 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -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(); diff --git a/src/toxdns.cpp b/src/toxdns.cpp index 1a5e3b0c5..5afdc73a0 100644 --- a/src/toxdns.cpp +++ b/src/toxdns.cpp @@ -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 diff --git a/src/toxdns.h b/src/toxdns.h index a7a644041..91b907d5a 100644 --- a/src/toxdns.h +++ b/src/toxdns.h @@ -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 { diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index a0440e976..0874e696e 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -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(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(); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index e22ad213a..031de32b7 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -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 ftransWidgets; void startCounter(); @@ -106,6 +109,7 @@ private: QString secondsToDHMS(quint32 duration); QHash receipts; QMap undeliveredMsgs; + bool isTyping; }; #endif // CHATFORM_H diff --git a/src/widget/form/loadhistorydialog.cpp b/src/widget/form/loadhistorydialog.cpp index 3d7e36535..33c33bcc5 100644 --- a/src/widget/form/loadhistorydialog.cpp +++ b/src/widget/form/loadhistorydialog.cpp @@ -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; } diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index d3d80de9b..732e00a00 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -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"}; diff --git a/src/widget/form/settings/privacysettings.ui b/src/widget/form/settings/privacysettings.ui index 84b4aecda..72262c327 100644 --- a/src/widget/form/settings/privacysettings.ui +++ b/src/widget/form/settings/privacysettings.ui @@ -43,11 +43,11 @@ - - false + + - Typing Notification + Send Typing Notifications diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index 176850e82..667143b3d 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -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(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(s)); if (res != layouts.end()) @@ -61,8 +61,11 @@ QLayout* FriendListWidget::getFriendLayout(Status s) return layouts[static_cast(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); } diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index ec44ba717..1dbddd45c 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -21,7 +21,7 @@ #include #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 layouts; - QLayout *groupLayout; + QHash layouts; + QVBoxLayout *groupLayout; QGridLayout *mainLayout; }; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 949024b29..7673bea4b 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -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); } diff --git a/src/widget/widget.h b/src/widget/widget.h index dd6bb3d94..ad6ad8b04 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -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); diff --git a/translations/i18n.pri b/translations/i18n.pri index 668a89f07..d76b12b0c 100644 --- a/translations/i18n.pri +++ b/translations/i18n.pri @@ -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) { diff --git a/translations/it.ts b/translations/it.ts index 0d76e67b6..116745236 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -187,67 +187,72 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? ChatForm - + Load History... Carica log... - + Send a file Invia un file - + File not read Impossibile leggere il file - + qTox wasn't able to open %1 qTox non è riuscito ad aprire %1 - + Bad Idea Pessima idea - + You're trying to send a special (sequential) file, that's not going to work! Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 is calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Failed to send file "%1" Invio del file "%1" fallito - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + + is typing... + sta scrivendo... + + + Call rejected Chiamata rifiutata @@ -801,7 +806,7 @@ Soprannome: Rimuovi messaggi visualizzati - + Cleared Pulito @@ -1173,8 +1178,8 @@ Vuoi eliminare il vecchio file? PrivacySettings - Typing Notification - Notifica quando qualcuno sta scrivendo + Send Typing Notifications + Permetti ai miei contatti di vedere quando sto scrivendo @@ -1255,13 +1260,13 @@ Vuoi eliminare il vecchio file? %1.tox è stato importato con successo - + Update The title of a message box Nuova versione - + An update is available, do you want to download it now? It will be installed when qTox restarts. È disponibile una nuova versione di qTox, vuoi scaricarla adesso? @@ -1372,6 +1377,15 @@ Verrà installata al riavvio del programma. Error with the DNS La risposta del server DNS non contiene un Tox ID valido + + + + It appears that qTox has to use the old tox1 protocol. +Unfortunately tox1 is not secure. Should it be used anyway? + Sembra che qTox debba usare il vecchio protocollo tox1. +Sfortunatamente il protocollo tox1 non è sicuro. +Usare comunque il protocollo tox1? + ToxURIDialog @@ -1450,116 +1464,116 @@ Verrà installata al riavvio del programma. Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio diff --git a/translations/pt.ts b/translations/pt.ts new file mode 100644 index 000000000..47792278d --- /dev/null +++ b/translations/pt.ts @@ -0,0 +1,1578 @@ + + + + + AVForm + + + Audio/Video + Áudio/Vídeo + + + + AVSettings + + + Audio Settings + Configurações de Áudio + + + + Microphone + Volume do Microfone + + + + Playback + Volume de Reprodução + + + + Playback device + Dispositivo de Reprodução + + + + Capture device + Dispositivo de Captura + + + + Rescan audio devices + Atualizar dispositivos de áudio + + + + Filter audio + Filtrar áudio + + + + Video Settings + Configurações de Vídeo + + + + Resolution + Resolução + + + + Hue + Matiz + + + + Brightness + Brilho + + + + Saturation + Saturação + + + + Contrast + Contraste + + + + AddFriendForm + + + Add Friends + Adicionar Contatos + + + + Tox ID + Tox ID of the person you're sending a friend request to + ID Tox + + + + Message + The message you send in friend requests + Mensagem + + + + Send friend request + Enviar requisição de contato + + + + Tox me maybe? + Default message in friend requests if the field is left blank. Write something appropriate! + Olá! Gostaria de adicionar você aos meus contatos no Tox. + + + + Please fill in a valid Tox ID + Tox ID of the friend you're sending a friend request to + Por favor preencha um ID Tox válido + + + + You can't add yourself as a friend! + When trying to add your own Tox ID as friend + Você não pode adicionar a si mesmo como contato! + + + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly? + qTox precisa usar o DNS do Tox, mas não pe capaz de fazer isso através de um proxy. +Deve-se ignorar as configurações de proxy e conectar diretamente à internet? + + + + This Tox ID does not exist + DNS error + Este ID Tox não existe + + + + AdvancedForm + + + Advanced + Avançado + + + + FULL - very safe, slowest (recommended) + COMPLETO - muito seguro, lento (recomendado) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + NORMAL - praticamente tão seguro quanto o COMPLETO, cerca de 20% mais rápido que o completo + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + DESLIGADO - desabilita toda a segurança, quando algo de errado ocorre seu histórico pode ser perdido, é o mais rápido (não recomendado) + + + + AdvancedSettings + + + Form + Avançado + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANTE</span></p><p><span style=" color:#ff0000;">A menos que você </span><span style=" font-weight:600; color:#ff0000;">realmente</span><span style=" color:#ff0000;"> saiba o que está fazendo, por favor </span><span style=" font-weight:600; color:#ff0000;">não</span><span style=" color:#ff0000;"> altere nada aqui. Alterações feitas aqui podem causar problemas ao qTox, e mesmo a perda de seus dados, com seu histórico, por exemplo.</span></p></body></html> + + + + Reset to default settings + Restaurar às configurações padrão + + + + History + Histórico + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Escrita sincronizada no BD</span></a></p></body></html> + + + + ChatForm + + + Load History... + Carregar Histórico... + + + + Send a file + Enviar um arquivo + + + + File not read + Arquivo não lido + + + + qTox wasn't able to open %1 + qTox não foi capaz de abrir %1 + + + + Bad Idea + Má Idéia + + + + You're trying to send a special (sequential) file, that's not going to work! + Você está tentando enviar um arquivo especial (sequencial), isso não vai funcionar! + + + + %1 is calling + %1 está chamando + + + + %1 stopped calling + %1 cancelou a chamada + + + + Calling to %1 + Chamando %1 + + + + Call rejected + Chamada rejeitada + + + + Failed to send file "%1" + Falha ao enviar o arquivo "%1" + + + + Call with %1 ended. %2 + Chamada para %1 terminada. %2 + + + + Call duration: + Duração da chamada: + + + + is typing... + está digitando... + + + + ChatTextEdit + + + Type your message here... + Digite sua mensagem aqui... + + + + Core + + + Toxing on qTox + Toxing on qTox + + + + qTox User + Usuário do qTox + + + + Friend is already added + Contato já adicionado + + + + Encryption error + Erro de criptografia + + + + The .tox file is encrypted, but encryption was not checked, continuing regardless. + O arquivo .tox é criptografado, mas a critpografia não foi verificada, continuando mesmo assim. + + + + Tox datafile decryption password + Senha de criptografia do arquivo de dados + + + + + + Password error + Senha incorreta + + + + + Failed to setup password. +Empty password. + Falha na definição da senha. +Senha em branco. + + + + Try Again + Tente novamente + + + + Change profile + Mudar perfil + + + + Reinit current profile + Reiniciar o perfil atual + + + + Wrong password has been entered + Senha incorreta fornecida + + + + History Log decryption password + Senha de criptografia do arquivo de histórico + + + + Encrypted log + Histórico criptografado + + + + Your history is encrypted with different password. +Do you want to try another password? + Seu histórico foi criptografado com uma senha diferente. +Você quer tentar outra senha? + + + + History + Histórico + + + + Due to incorret password history will be disabled. + Devido à senha incorreta, o histórico será desabilitado. + + + + NO Password + SEM senha + + + + Will be saved without encryption! + Será armazenado sem criptografia! + + + + FileTransferInstance + + + Save a file + Title of the file saving dialog + Salvar arquivo + + + + Location not writable + Title of permissions popup + Impossível gravar aqui + + + + You do not have permission to write that location. Choose another, or cancel the save dialog. + text of permissions popup + Você não possui permissão de escrita aqui. Escolha outro local ou cancele a operação. + + + + ETA + Tempo + + + + FilesForm + + + Transfered Files + "Headline" of the window + Arquivos transferidos + + + + Downloads + Recebidos + + + + Uploads + Enviados + + + + FriendRequestDialog + + + Friend request + Title of the window to aceept/deny a friend request + Solicitação de contato + + + + Someone wants to make friends with you + Alguém quer adicionar você como contato + + + + User ID: + ID do usuário: + + + + Friend request message: + Mensagem de requisição contato: + + + + Accept + Accept a friend request + Aceitar + + + + Reject + Reject a friend request + Rejeitar + + + + FriendWidget + + + Invite to group + Menu to invite a friend to a groupchat + Convidar para grupo + + + + Copy friend ID + Menu to copy the Tox ID of that friend + Copiar ID do contato + + + + Set alias... + Apelido... + + + + Auto accept files from this friend + context menu entry + Aceitar arquivos automaticamente deste contato + + + + Remove friend + Menu to remove the friend from our friendlist + Remover contato + + + + Choose an auto accept directory + popup title + Escolher um diretório para aceitar arquivos automaticamente + + + + User alias + Apelido do usuário + + + + You can also set this by clicking the chat form name. +Alias: + Também pode ser definido clicando no nome do chat. +Apelido: + + + + GeneralForm + + + General + Geral + + + + + None + Nenhum + + + + Choose an auto accept directory + popup title + Escolher um diretório para aceitar arquivos automaticamente + + + + Call active + popup title + Chamada ativa + + + + You can't disconnect while a call is active! + popup text + Você não pode desconectar enquanto uma chamada estiver ativa! + + + + GeneralSettings + + + General Settings + Configurações Gerais + + + + + The translation may not load until qTox restarts. + A tradução pode não ser atualizada antes do qTox ser reinicializado. + + + + Translation + Idioma + + + + Save settings to the working directory instead of the usual conf dir + describes makeToxPortable checkbox + Armazena as configurações no diretório de trabalho ao invés do diretório de configurações usual + + + + Make Tox portable + Deixe o Tox portável + + + + System tray integration + Integração com a bandeja do sistema + + + + Show system tray icon + Mostrar ícone na bandeja + + + + Start in tray + Inicializar na bandeja + + + + Close to tray + Fechar para a bandeja + + + + Minimize to tray + Minimizar para a bandeja + + + + Light icon + Ícone claro + + + + Show contacts' status changes + Mostar alterações no status dos contatos + + + + Check for updates on startup (unstable) + Checar atualizações na inicialização (instável) + + + + Focus qTox when a message is received + Destacar o qTox quando receber uma mensagem + + + + Faux offline messaging + Envio de mensagens "offline" falso + + + + Provided in minutes + Em minutos + + + + Auto away after (0 to disable) + Ficar ausente após (0 para desativar) + + + + Set to 0 to disable + Defina 0 para desativar + + + + minutes + minutos + + + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Você pode definir esta configuração por contato clicando com o botão direito sobre eles. + + + + Autoaccept files + Aceitar arquivos automaticamente + + + + Save files in + Armazenar arquivos em + + + + PushButton + Clique + + + + Theme + Tema + + + + Use emoticons + Usar emoticons + + + + Smiley Pack + Text on smiley pack label + Conjunto de emoticons + + + + Style + Estilo + + + + Theme color + Cor do tema + + + + Emoticon size + Tamanho do emoticon + + + + px + px + + + + Timestamp format + Formato da hora + + + + Connection Settings + Configuraçẽs da Conexão + + + + Enable IPv6 (recommended) + Text on a checkbox to enable IPv6 + Permitir IPv6 (recomendado) + + + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Desabilitar esta opção permite, por exemplo, utilizar a rede Tor. Ela adiciona mais dados à rede Tor no entanto, portanto desmarque apenas se necessário. + + + + Enable UDP (recommended) + Text on checkbox to disable UDP + Permitir UDP (recomendado) + + + + Proxy type + Tipo de proxy + + + + None + Nenum + + + + SOCKS5 + SOCKS5 + + + + HTTP + HTTP + + + + Address + Text on proxy addr label + Endereço + + + + Port + Text on proxy port label + Porta + + + + Reconnect + reconnect button + Reconectar + + + + GenericChatForm + + + Send message + Enviar mensagem + + + + Smileys + Emoticons + + + + Send file(s) + Enviar arquivo(s) + + + + Audio call: RED means you're on a call + Chamada e áudio: VERMELHO significa que você está em uma chamada + + + + Video call: RED means you're on a call + Chamada e vídeo: VERMELHO significa que você está em uma chamada + + + + Toggle speakers volume: RED is OFF + Alternar volume de saída: VERMELHO é desligado + + + + Toggle microphone: RED is OFF + Alternar volume de entrada: VERMELHO é desligado + + + + + Save chat log + Armazenar histórico da conversa + + + + Clear displayed messages + Remover mensagens + + + + Cleared + Removidas + + + + GroupChatForm + + + %1 users in chat + Number of users in chat + %1 usuários no grupo + + + + %1 users in chat + %1 usuários no grupo + + + + GroupWidget + + + + %1 users in chat + %1 usuários no grupo + + + + + 0 users in chat + nenhum usuário no grupo + + + + Set title... + Defina o título... + + + + Quit group + Menu to quit a groupchat + Sair do grupo + + + + Group title + Título do grupo + + + + You can also set this by clicking the chat form name. +Title: + É possível definir clicando no nome do grupo. +Título: + + + + IdentityForm + + + Identity + Identidade + + + + Call active + popup title + Chamada ativa + + + + You can't switch profiles while a call is active! + popup text + Não é possível trocar de perfil enquanto uma chamada estiver ativa! + + + + Rename "%1" + renaming a profile + Renomear "%1" + + + + Profile already exists + rename confirm title + O perfil já existe + + + + A profile named "%1" already exists. Do you want to erase it? + rename confirm text + Um perfil chamado "%1" já existe. Deseja sobrescrevê-lo? + + + + Export profile + save dialog title + Esportar perfil + + + + Tox save file (*.tox) + save dialog filter + Armazenar arquivo Tox (*.tox) + + + + Failed to remove file + Falha ao remover o arquivo + + + + The file you chose to overwrite could not be removed first. + O arquivo escolhido para sobrescrever não pôde ser removido. + + + + Failed to copy file + Falha ao copiar o arquivo + + + + The file you chose could not be written to. + O arquivo que você escolheu não pôde ser escrito. + + + + Profile currently loaded + current profile deletion warning title + Perfil carregado no momento + + + + This profile is currently in use. Please load a different profile before deleting this one. + current profile deletion warning text + Esper perfil está atualmente em uso. Por favor carrege outro perfil antes de excluir este. + + + + Deletion imminent! + deletion confirmation title + Exclusão iminente! + + + + Are you sure you want to delete this profile? +Associated friend information and chat logs will be deleted as well. + deletion confirmation text + Tem certeza que deseja excuir este perfil? As informações de contatos e histórico também serão removidos. + + + + Import profile + import dialog title + Importar perfil + + + + Tox save file (*.tox) + import dialog filter + Arquivo Tox (*.tox) + + + + Ignoring non-Tox file + popup title + Ignorando arquivo não Tox + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Atenção: foi escolhido um arquivo que não é um arquivo Tox. Ignorando. + + + + Profile already exists + import confirm title + O perfil já existe + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Um perfil chamado "%1" já existe. Deseja sobrescrevê-lo? + + + + IdentitySettings + + + Public Information + Informações Públicas + + + + Name + Nome + + + + Status + Status + + + + Tox ID + ID Tox + + + + Your Tox ID (click to copy) + Seu ID Tox (clique para copiar) + + + + Profiles + Perfis + + + + Available profiles: + Perfis disponíveis: + + + + + Switching profiles is disabled during calls + tooltip + alternar entre perfis não está habilitado durante chamadas + + + + Load + load profile button + Carregar + + + + Rename + rename profile button + Renomear + + + + Export + export profile button + Exportar + + + + This is useful to remain safe on public computers + delete profile button tooltip + Útil para ficar seguro em computadores públicos + + + + Delete + delete profile button + Excluir + + + + Import a profile + import profile button + Importar um perfil + + + + New Tox ID + new profile button + Novo ID Tox + + + + InputPasswordDialog + + + Password Dialog + Senha + + + + Input password: + Digite sua Senha: + + + + LoadHistoryDialog + + + Load History Dialog + Carregar Histórico + + + + Load history from: + Carregar histórico de: + + + + MainWindow + + + Your name + Seu nome + + + + Your status + Seu status + + + + Add friends + Adicionar contatos + + + + Create a group chat + Criar um grupo + + + + View completed file transfers + Ver transferências de arquivos completadas + + + + Change your settings + Alterar suas configurações + + + + Close + Fechar + + + + NetCamView + + + Tox video + Vídeo Tox + + + + PrivacyForm + + + Privacy + Privacidade + + + + Encrypted log + Histórico criptografado + + + + You already have history log file encrypted with different password +Do you want to delete old history file? + Seu histórico foi criptografado com uma senha diferente. +Você quer remover este arquivo de histórico antigo? + + + + PrivacySettings + + + Send Typing Notifications + Enviar notificação de digitação + + + + Keep History (unstable) + Manter histórico (instável) + + + + Encryption + Criptografia + + + + Encrypt Tox datafile + Criptografar os arquivos Tox + + + + Encrypt History + Criptografar histórico + + + + Nospam + Anti-span + + + + HHHHHHHH + HHHHHHHH + + + + Generate random nospam + Gerar um número anti-span aleatório + + + + QObject + + + Update + The title of a message box + Atualizar + + + + An update is available, do you want to download it now? +It will be installed when qTox restarts. + Uma atualização está disponível, você deseja baixá-la agora? +Ela será instalada quando o qTox for reiniciado. + + + + Tox URI to parse + UTI Tox para interpretar + + + + Starts new instance and loads specified profile. + Inicia uma nova instância e carrega o perfil especificado. + + + + profile + perfil + + + + Default + Padrão + + + + Blue + Azul + + + + Olive + Verde-oliva + + + + Red + Vermelho + + + + Violet + Violeta + + + + Ignoring non-Tox file + popup title + Ignorando arquivo não Tox + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Atenção: foi escolhido um arquivo que não é um arquivo Tox. Ignorando. + + + + Profile already exists + import confirm title + O perfil já existe + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Um perfil chamado "%1" já existe. Deseja sobrescrevê-lo? + + + + Profile imported + Perfil importado + + + + %1.tox was successfully imported + %1.tox importado com sucesso + + + + Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Olá! Gostaria de adicionar você aos meus contatos no Tox. + + + + SetPasswordDialog + + + Type Password + Digite a Senha + + + + Repeat Password + Repita a Senha + + + + ToxDNS + + + The connection timed out + The DNS gives the Tox ID associated to toxme.se addresses + O tempo da conexão expirou + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Este endereço não existe + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Erro ao consultar o DNS + + + + No text record found + Error with the DNS + Nenhum registro encontrado + + + + Unexpected number of values in text record + Error with the DNS + Número de entradas inesperado + + + + The version of Tox DNS used by this server is not supported + Error with the DNS + A versão do DNS Tox utilizada por este servidor não é suportada + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + A resposta do DNS não contém nenhum ID Tox + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + A resposta do DNS não contém um ID Tox válido + + + + + It appears that qTox has to use the old tox1 protocol. +Unfortunately tox1 is not secure. Should it be used anyway? + parece que o qTox está usando o protocolo tox1 antigo. +Infelizmente o tox1 não é seguro. Deve ele ser usado mesmo assim? + + + + ToxURIDialog + + + Add a friend + Title of the window to add a friend through Tox URI + Adicionar um contato + + + + Do you want to add %1 as a friend? + Você deseja adicionar %1 como seu contato? + + + + User ID: + ID do usuário: + + + + Friend request message: + Mensagem de requisição contato: + + + + Send + Send a friend request + Enviar + + + + Cancel + Don't send a friend request + Cancelar + + + + Widget + + + Online + Online + + + + Away + Ausente + + + + Busy + Ocupado + + + + &Quit + &Sair + + + + Online + Button to set your status to 'Online' + Online + + + + Away + Button to set your status to 'Away' + Ausente + + + + Busy + Button to set your status to 'Busy' + Ocupado + + + + Choose a profile + Escolha um perfil + + + + Please choose which identity to use + Por favor escolha qual identidade usar + + + + Choose a profile picture + Escolha uma imagem para o perfil + + + + + + Error + Erro + + + + Unable to open this file + Não foi possível abrir este arquivo + + + + Unable to read this image + Não foi possível ler esta imagem + + + + This image is too big + Esta imagem é muito grande + + + + Toxcore failed to start, the application will terminate after you close this message. + O Toxcore falhou ao inicializar, a aplicação será finalizada assim que esta mensagem for fechada. + + + + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. + popup text + O Toxcore falhou ao inicializar suas configurações de proxy. O qTox não pode ser executado, por favor modifique suas configurações e reinicialize o aplicativo. + + + + Add friend + Adicionar contato + + + + File transfers + Transferências de arquivo + + + + Settings + Configurações + + + + Couldn't request friendship + Não foi possível adicionar o contato + + + + away + contact status + ausente + + + + busy + contact status + ocupado + + + + offline + contact status + offline + + + + online + contact status + online + + + + %1 is now %2 + e.g. "Dubslow is now online" + %1 agora é %2 + + + + <Unknown> + Placeholder when we don't know someone's name in a group chat + <Desconhecido> + + + + %1 has set the title to %2 + %1 alterou o título para %2 + + + + Message failed to send + Falha no envio da mensagem + + + diff --git a/translations/uk.ts b/translations/uk.ts index a2ecf9015..51953bc5d 100644 --- a/translations/uk.ts +++ b/translations/uk.ts @@ -37,32 +37,42 @@ Пристрій захоплення - + + Rescan audio devices + Пересканувати аудіо пристрої + + + + Filter audio + Фільтр звуку + + + Video Settings Параметри відео - + Resolution Роздільна здатність - + Hue Відтінок - + Brightness Яскравість - + Saturation Насиченість - + Contrast Контраст @@ -112,7 +122,7 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. -Ignore the proxy and connect to the Internet directly ? +Ignore the proxy and connect to the Internet directly? qTox потребує Tox DNS, але не може скористатися ним через проксі. Проігнорувати проксі та під'єднатися напряму? @@ -177,52 +187,72 @@ Ignore the proxy and connect to the Internet directly ? ChatForm - + Load History... Завантажити історію… - + Send a file Надіслати файл - + + File not read + Файл не читається + + + + qTox wasn't able to open %1 + qTox не може відкрити файл %1 + + + Bad Idea Погана ідея - + You're trying to send a special (sequential) file, that's not going to work! Ви намагаєтесь передати спеціальний (послідовний) файл, це не так працює! - - %1 calling - %1 викликає + + %1 is calling + %1 дзвонить - + %1 stopped calling %1 припинив виклик - + Calling to %1 Викликаємо %1 - + + Failed to send file "%1" + Не вдалось відправити файл «%1» + + + Call with %1 ended. %2 Виклик із %1 завершено. %2 - + Call duration: Тривалість дзвінка: - + + is typing... + набирає... + + + Call rejected Дзвінок відхилено @@ -238,104 +268,104 @@ Ignore the proxy and connect to the Internet directly ? Core - + Toxing on qTox Вітання з qTox - + qTox User Користувач qTox - + Friend is already added Друга вже додано - + Encryption error Помилка шифрування - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифровано, але шифрування не перевірено, попри це продовжуємо. - + Tox datafile decryption password Розшифрування файлу даних Tox - - - + + + Password error Помилка паролю - - + + Failed to setup password. Empty password. Не вдалось встановити пароль. Пароль пустий. - + Try Again Спробуйте ще раз - + Change profile Змінити профіль - + Reinit current profile Пере ініціалізувати поточний профіль - + Wrong password has been entered Введено хибний пароль - + History Log decryption password Розшифрування історії - - Your history is encrypted with different password + + Your history is encrypted with different password. Do you want to try another password? - Ваша історія зашифрована інакшим паролем + Ваша історія зашифрована інакшим паролем. Бажаєте спробувати інший пароль? - + + History + Історія + + + + Due to incorret password history will be disabled. + Введено некоректний пароль, звітування вимкнено. + + + Encrypted log Зашифрований звіт - - Loggin - Звітування - - - - Due to incorret password logging will be disabled - Введено некоректний пароль, звітування вимкнено - - - + NO Password Відсутній пароль - + Will be saved without encryption! Буде збережено без шифрування! @@ -447,14 +477,16 @@ Do you want to try another password? Автоматично приймати файли від даного друга - + User alias Псевдонім користувача - - Alias: - Псевдонім: + + You can also set this by clicking the chat form name. +Alias: + Ви також можете встановити його натиснувши на назву форми в чаті. +Псевдонім: @@ -477,25 +509,25 @@ Do you want to try another password? Основні - - + + None Відсутній - + Choose an auto accept directory popup title Оберіть теку, для автоматичного отримання файлів - + Call active popup title Дзвінок активний - + You can't disconnect while a call is active! popup text Ви не можете від'єднатись під час активного дзвінка! @@ -526,47 +558,52 @@ Do you want to try another password? Портативний запуск - - Show system tray icon - Показувати піктрограму в системному лотку + + System tray integration + Інтеграція із системним лотком - + + Show system tray icon + Показувати піктограму в системному лотку + + + Start in tray Запускати у системному лотку - + Close to tray Закривати до лотку - + Minimize to tray Мінімізувати до лотку - + Show contacts' status changes Показувати зміну статусів контактів - + Provided in minutes Встановлено в хвилинах - + Set to 0 to disable Встановіть 0, аби вимкнути - + minutes хвилин - + Theme Графічна тема @@ -576,128 +613,148 @@ Do you want to try another password? Мова інтерфейсу - Show system tray - Показувати в системному лотку + + Light icon + Світлі піктограми - + Check for updates on startup (unstable) Перевіряти оновлення під час запуску (нестабільна функція) - + Focus qTox when a message is received Перехоплювати фокус вікна при отриманні повідомлення - + Faux offline messaging Фальшивий поза мережевий обмін повідомленнями - + Auto away after (0 to disable) Авто-статус «Відійшов» (0=вимкнено) - + You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip - + Ви також можете встановити це значення до кожного друга окремо викликавши правою кнопкою меню навпроти нього. - + Autoaccept files Автоматично приймати файли - + Save files in Зберігати файли до - + PushButton Тисніть кнопку - + Use emoticons Використовувати смайлики - + Smiley Pack Text on smiley pack label Пакунок смайликів - + Style Стиль - + Theme color Колір графічної теми - + Emoticon size Розмір смайликів - + px px - + Timestamp format Формати часового відбитку - + Connection Settings Параметри підключення - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Дозволити IPv6 (рекомендовано) - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip - + Enable UDP (recommended) Text on checkbox to disable UDP Дозволити UDP (рекомендовано) - - Use proxy (SOCKS5) - Використовувати проксі (SOCKS5) + + Proxy type + Тип проксі - + + None + Відсутній + + + + SOCKS5 + SOCKS5 + + + + HTTP + HTTP + + + Use proxy (SOCKS5) + Використовувати проксі (SOCKS5) + + + Address Text on proxy addr label Адреса - + Port Text on proxy port label Порт - + Reconnect reconnect button Повторно під'єднатись @@ -706,53 +763,53 @@ Do you want to try another password? GenericChatForm - + Send message Відправити повідомлення - + Smileys Смайлики - + Send file(s) Відправити файл(и) - + Audio call: RED means you're on a call Аудіо дзвінок: Червоний - дзвінок активний - + Video call: RED means you're on a call Відео дзвінок: Червоний - дзвінок активний - + Toggle speakers volume: RED is OFF Перемкнути стан гучності відтворення: Червоний - вимкнено - + Toggle microphone: RED is OFF Перемкнути рівень підсилення мікрофону: Червоний - вимкнено - - + + Save chat log Зберегти чат - + Clear displayed messages Очистити показані повідомлення - + Cleared Очищено @@ -760,13 +817,13 @@ Do you want to try another password? GroupChatForm - + %1 users in chat Number of users in chat Користувачів у чаті: %1 - + %1 users in chat Користувачів у чаті: %1 @@ -774,23 +831,40 @@ Do you want to try another password? GroupWidget - - + + %1 users in chat Користувачів у чаті: %1 - - + + 0 users in chat Немає користувачів - + Quit group Menu to quit a groupchat Вийти з групи + + + Set title... + Встановити заголовок… + + + + Group title + Заголовок групи + + + + You can also set this by clicking the chat form name. +Title: + Ви також можете встановити його натиснувши на назву форми в чаті. +Заголовок: + IdentityForm @@ -881,42 +955,44 @@ Do you want to try another password? - Are you sure you want to delete this profile? + Are you sure you want to delete this profile? +Associated friend information and chat logs will be deleted as well. deletion confirmation text - Дійсно вилучити даний профіль? + Дійсно вилучити даний профіль? +Пов'язана інформація про друзів та історія спілкування також буде вилучена. - + Import profile import dialog title Імпортувати профіль - + Tox save file (*.tox) import dialog filter Файл збереження Tox (*.tox) - + Ignoring non-Tox file popup title Ігнорування не Tox файлу - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Увага: вказаний вами файл не є файлом збереження Tox; ігнорую. - + Profile already exists import confirm title Профіль вже існує - + A profile named "%1" already exists. Do you want to erase it? import confirm text Профіль із назвою «%1» вже існує. Бажаєте стерти його? @@ -1104,9 +1180,13 @@ Do you want to delete old history file? PrivacySettings - Typing Notification - Увімкнути сповіщення про набір + Увімкнути сповіщення про набір + + + + Send Typing Notifications + Надсилати сповіщення про набір @@ -1187,14 +1267,14 @@ Do you want to delete old history file? %1.tox успішно імпортовано - + Update The title of a message box Оновити - - An update is available, do you want to download it now ? + + An update is available, do you want to download it now? It will be installed when qTox restarts. Доступне оновлення, бажаєте завантажити його зараз? Оновлення буде встановлено після перезапуску qTox. @@ -1204,6 +1284,16 @@ It will be installed when qTox restarts. Tox URI to parse Tox URI для розбору + + + Starts new instance and loads specified profile. + + + + + profile + профіль + Default @@ -1294,6 +1384,13 @@ It will be installed when qTox restarts. Error with the DNS Відповідь DNS не містить жодного коректного Tox ID + + + + It appears that qTox has to use the old tox1 protocol. +Unfortunately tox1 is not secure. Should it be used anyway? + + ToxURIDialog @@ -1305,7 +1402,7 @@ It will be installed when qTox restarts. - Do you want to add %1 as a friend ? + Do you want to add %1 as a friend? Бажаєте додати %1 як друга? @@ -1334,159 +1431,158 @@ It will be installed when qTox restarts. Widget - + Online В мережі - + Away Відійшов - + Busy Зайнятий - + &Quit &Вийти - Change status to: - Змінити статус на: + Змінити статус на: - + Online Button to set your status to 'Online' В мережі - + Away Button to set your status to 'Away' Відійшов - + Busy Button to set your status to 'Busy' Зайнятий - + Choose a profile Оберіть профіль - + Please choose which identity to use Оберіть ідентифікатор для використання - + Choose a profile picture Оберіть зображення для профілю - - - + + + Error Помилка - + Unable to open this file Неможливо відкрити цей файл - + Unable to read this image Неможливо прочитати це зображення - + This image is too big Зображення завелике - + Toxcore failed to start, the application will terminate after you close this message. Помилка запуску ядра tox, програма буде завершена після закриття цього повідомлення. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Помилка запуску ядра tox із поточними параметрами проксі. qTox не працює; змініть параметри і перезапустіть. - + Add friend Додати друга - + File transfers Передачі файлів - + Settings Параметри - + Couldn't request friendship Не вдалось надіслати запит на дружбу - + away contact status Відійшов - + busy contact status Зайнятий - + offline contact status Поза мережею - + online contact status В мережі - + %1 is now %2 e.g. "Dubslow is now online" %1 тепер вже відомий як %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Невідомо> - + %1 has set the title to %2 %1 встановив тему %2 - + Message failed to send Не вдалось відправити повідомлення