mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'master' into src
Conflicts: qtox.pro src/widget/form/settings/avform.h src/widget/selfcamview.cpp src/widget/selfcamview.h src/widget/widget.cpp widget/selfcamview.h widget/videosurface.h
This commit is contained in:
commit
bf75750166
BIN
audio/ToxicIncomingCall.pcm
Normal file
BIN
audio/ToxicIncomingCall.pcm
Normal file
Binary file not shown.
16
qtox.pro
16
qtox.pro
|
@ -20,7 +20,7 @@
|
|||
# See the COPYING file for more details.
|
||||
|
||||
|
||||
QT += core gui network xml
|
||||
QT += core gui network xml opengl
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qtox
|
||||
|
@ -36,6 +36,7 @@ TRANSLATIONS = translations/de.ts \
|
|||
translations/fr.ts \
|
||||
translations/it.ts \
|
||||
translations/ru.ts \
|
||||
translations/pirate.ts \
|
||||
translations/pl.ts \
|
||||
translations/fi.ts \
|
||||
translations/mannol.ts \
|
||||
|
@ -67,7 +68,7 @@ win32 {
|
|||
LIBS += -Wl,-Bdynamic -ltbb -lv4l1 -lv4l2 -lgnutls -lrtmp -lgnutls -lavformat -lavcodec -lavutil -lavfilter -lswscale -lusb-1.0
|
||||
|
||||
} else {
|
||||
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui
|
||||
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
|
||||
}
|
||||
|
||||
contains(JENKINS, YES) {
|
||||
|
@ -110,7 +111,6 @@ HEADERS += src/widget/form/addfriendform.h \
|
|||
src/friendlist.h \
|
||||
src/misc/cdata.h \
|
||||
src/misc/cstring.h \
|
||||
src/widget/selfcamview.h \
|
||||
src/widget/camera.h \
|
||||
src/widget/netcamview.h \
|
||||
src/misc/smileypack.h \
|
||||
|
@ -131,7 +131,10 @@ HEADERS += src/widget/form/addfriendform.h \
|
|||
src/widget/tool/chatactions/filetransferaction.h \
|
||||
src/widget/tool/chatactions/systemmessageaction.h \
|
||||
src/widget/tool/chatactions/actionaction.h \
|
||||
src/widget/maskablepixmapwidget.h
|
||||
src/widget/maskablepixmapwidget.h \
|
||||
src/videosource.h \
|
||||
src/cameraworker.h \
|
||||
src/widget/videosurface.h
|
||||
|
||||
SOURCES += \
|
||||
src/widget/form/addfriendform.cpp \
|
||||
|
@ -157,7 +160,6 @@ SOURCES += \
|
|||
src/misc/settings.cpp \
|
||||
src/misc/cdata.cpp \
|
||||
src/misc/cstring.cpp \
|
||||
src/widget/selfcamview.cpp \
|
||||
src/widget/camera.cpp \
|
||||
src/widget/netcamview.cpp \
|
||||
src/misc/smileypack.cpp \
|
||||
|
@ -177,4 +179,6 @@ SOURCES += \
|
|||
src/widget/tool/chatactions/filetransferaction.cpp \
|
||||
src/widget/tool/chatactions/systemmessageaction.cpp \
|
||||
src/widget/tool/chatactions/actionaction.cpp \
|
||||
src/widget/maskablepixmapwidget.cpp
|
||||
src/widget/maskablepixmapwidget.cpp \
|
||||
src/cameraworker.cpp \
|
||||
src/widget/videosurface.cpp
|
||||
|
|
2
res.qrc
2
res.qrc
|
@ -123,6 +123,7 @@
|
|||
<file>ui/fileTransferInstance/emptyRGreenFileButton.png</file>
|
||||
<file>ui/fileTransferInstance/emptyRRedFileButton.png</file>
|
||||
<file>audio/notification.pcm</file>
|
||||
<file>audio/ToxicIncomingCall.pcm</file>
|
||||
<file>img/settings/general.png</file>
|
||||
<file>img/settings/identity.png</file>
|
||||
<file>img/settings/privacy.png</file>
|
||||
|
@ -138,5 +139,6 @@
|
|||
<file>ui/window/statusPanel.css</file>
|
||||
<file>ui/settings/mainContent.css</file>
|
||||
<file>ui/settings/mainHead.css</file>
|
||||
<file>translations/pirate.qm</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
205
src/cameraworker.cpp
Normal file
205
src/cameraworker.cpp
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
This program is libre software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "cameraworker.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
CameraWorker::CameraWorker(int index)
|
||||
: clock(nullptr)
|
||||
, camIndex(index)
|
||||
, refCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
void CameraWorker::onStart()
|
||||
{
|
||||
clock = new QTimer(this);
|
||||
clock->setSingleShot(false);
|
||||
clock->setInterval(5);
|
||||
|
||||
connect(clock, &QTimer::timeout, this, &CameraWorker::doWork);
|
||||
|
||||
emit started();
|
||||
}
|
||||
|
||||
void CameraWorker::_suspend()
|
||||
{
|
||||
qDebug() << "Suspend";
|
||||
clock->stop();
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
void CameraWorker::_resume()
|
||||
{
|
||||
qDebug() << "Resume";
|
||||
subscribe();
|
||||
clock->start();
|
||||
}
|
||||
|
||||
void CameraWorker::_setProp(int prop, double val)
|
||||
{
|
||||
props[prop] = val;
|
||||
|
||||
if (cam.isOpened())
|
||||
cam.set(prop, val);
|
||||
}
|
||||
|
||||
double CameraWorker::_getProp(int prop)
|
||||
{
|
||||
if (!props.contains(prop))
|
||||
{
|
||||
subscribe();
|
||||
props[prop] = cam.get(prop);
|
||||
unsubscribe();
|
||||
qDebug() << "ASKED " << prop << " VAL " << props[prop];
|
||||
}
|
||||
|
||||
return props.value(prop);
|
||||
}
|
||||
|
||||
void CameraWorker::probeResolutions()
|
||||
{
|
||||
if (resolutions.isEmpty())
|
||||
{
|
||||
subscribe();
|
||||
|
||||
// probe resolutions (TODO: add more)
|
||||
QList<QSize> propbeRes = {
|
||||
QSize( 160, 120), // QQVGA
|
||||
QSize( 320, 240), // HVGA
|
||||
QSize(1024, 768), // XGA
|
||||
QSize( 432, 240), // WQVGA
|
||||
QSize( 640, 360), // nHD
|
||||
};
|
||||
|
||||
for (QSize res : propbeRes)
|
||||
{
|
||||
cam.set(CV_CAP_PROP_FRAME_WIDTH, res.width());
|
||||
cam.set(CV_CAP_PROP_FRAME_HEIGHT, res.height());
|
||||
|
||||
double w = cam.get(CV_CAP_PROP_FRAME_WIDTH);
|
||||
double h = cam.get(CV_CAP_PROP_FRAME_HEIGHT);
|
||||
|
||||
//qDebug() << "PROBING:" << res << " got " << w << h;
|
||||
|
||||
if (!resolutions.contains(QSize(w,h)))
|
||||
resolutions.append(QSize(w,h));
|
||||
}
|
||||
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
qDebug() << resolutions;
|
||||
|
||||
emit resProbingFinished(resolutions);
|
||||
}
|
||||
|
||||
void CameraWorker::applyProps()
|
||||
{
|
||||
if (!cam.isOpened())
|
||||
return;
|
||||
|
||||
for(int prop : props.keys())
|
||||
cam.set(prop, props.value(prop));
|
||||
}
|
||||
|
||||
void CameraWorker::subscribe()
|
||||
{
|
||||
if (refCount == 0)
|
||||
{
|
||||
if (!cam.isOpened())
|
||||
{
|
||||
cam.open(camIndex);
|
||||
applyProps(); // restore props
|
||||
}
|
||||
}
|
||||
|
||||
refCount++;
|
||||
}
|
||||
|
||||
void CameraWorker::unsubscribe()
|
||||
{
|
||||
refCount--;
|
||||
|
||||
if(refCount <= 0)
|
||||
{
|
||||
cam.release();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraWorker::doWork()
|
||||
{
|
||||
if (!cam.isOpened())
|
||||
return;
|
||||
|
||||
if (queue.size() > 3)
|
||||
{
|
||||
queue.dequeue();
|
||||
return;
|
||||
}
|
||||
|
||||
cam >> frame;
|
||||
//qDebug() << "Decoding frame";
|
||||
mutex.lock();
|
||||
|
||||
queue.enqueue(frame);
|
||||
mutex.unlock();
|
||||
|
||||
emit newFrameAvailable();
|
||||
}
|
||||
|
||||
bool CameraWorker::hasFrame()
|
||||
{
|
||||
mutex.lock();
|
||||
bool b = !queue.empty();
|
||||
mutex.unlock();
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
cv::Mat3b CameraWorker::dequeueFrame()
|
||||
{
|
||||
mutex.lock();
|
||||
cv::Mat3b f = queue.dequeue();
|
||||
mutex.unlock();
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void CameraWorker::suspend()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "_suspend");
|
||||
}
|
||||
|
||||
void CameraWorker::resume()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "_resume");
|
||||
}
|
||||
|
||||
void CameraWorker::setProp(int prop, double val)
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "_setProp", Q_ARG(int, prop), Q_ARG(double, val));
|
||||
}
|
||||
|
||||
double CameraWorker::getProp(int prop)
|
||||
{
|
||||
double ret = 0.0;
|
||||
QMetaObject::invokeMethod(this, "_getProp", Qt::BlockingQueuedConnection, Q_RETURN_ARG(double, ret), Q_ARG(int, prop));
|
||||
|
||||
return ret;
|
||||
}
|
77
src/cameraworker.h
Normal file
77
src/cameraworker.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
This program is libre software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef CAMERAWORKER_H
|
||||
#define CAMERAWORKER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QQueue>
|
||||
#include <QSize>
|
||||
|
||||
#include "opencv2/opencv.hpp"
|
||||
|
||||
class QTimer;
|
||||
|
||||
class CameraWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CameraWorker(int index);
|
||||
void doWork();
|
||||
bool hasFrame();
|
||||
cv::Mat3b dequeueFrame();
|
||||
|
||||
void suspend();
|
||||
void resume();
|
||||
void setProp(int prop, double val);
|
||||
double getProp(int prop); // blocking call!
|
||||
void probeResolutions();
|
||||
|
||||
public slots:
|
||||
void onStart();
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void newFrameAvailable();
|
||||
void resProbingFinished(QList<QSize> res);
|
||||
|
||||
private slots:
|
||||
void _suspend();
|
||||
void _resume();
|
||||
void _setProp(int prop, double val);
|
||||
double _getProp(int prop);
|
||||
|
||||
private:
|
||||
void applyProps();
|
||||
void subscribe();
|
||||
void unsubscribe();
|
||||
|
||||
private:
|
||||
QMutex mutex;
|
||||
QQueue<cv::Mat3b> queue;
|
||||
QTimer* clock;
|
||||
cv::VideoCapture cam;
|
||||
cv::Mat3b frame;
|
||||
int camIndex;
|
||||
QMap<int, double> props;
|
||||
QList<QSize> resolutions;
|
||||
int refCount;
|
||||
};
|
||||
|
||||
#endif // CAMERAWORKER_H
|
|
@ -57,7 +57,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled
|
|||
if (calls[callId].videoEnabled)
|
||||
{
|
||||
calls[callId].sendVideoTimer->start();
|
||||
Camera::getInstance()->suscribe();
|
||||
Camera::getInstance()->subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,12 +73,12 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core)
|
|||
{
|
||||
calls[callId].videoEnabled = false;
|
||||
calls[callId].sendVideoTimer->stop();
|
||||
Camera::getInstance()->unsuscribe();
|
||||
Camera::getInstance()->unsubscribe();
|
||||
emit ((Core*)core)->avMediaChange(friendId, callId, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Camera::getInstance()->suscribe();
|
||||
Camera::getInstance()->subscribe();
|
||||
calls[callId].videoEnabled = true;
|
||||
calls[callId].sendVideoTimer->start();
|
||||
emit ((Core*)core)->avMediaChange(friendId, callId, true);
|
||||
|
@ -161,7 +161,7 @@ void Core::cleanupCall(int callId)
|
|||
calls[callId].sendAudioTimer->stop();
|
||||
calls[callId].sendVideoTimer->stop();
|
||||
if (calls[callId].videoEnabled)
|
||||
Camera::getInstance()->unsuscribe();
|
||||
Camera::getInstance()->unsubscribe();
|
||||
alcCaptureStop(alInDev);
|
||||
}
|
||||
|
||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -19,6 +19,7 @@
|
|||
#include <QApplication>
|
||||
#include <QFontDatabase>
|
||||
#include <QTranslator>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QDebug>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -47,7 +48,24 @@ int main(int argc, char *argv[])
|
|||
QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
|
||||
|
||||
Widget* w = Widget::getInstance();
|
||||
w->show();
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable() == false)
|
||||
{
|
||||
qWarning() << "No system tray detected!";
|
||||
w->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
QSystemTrayIcon *icon = new QSystemTrayIcon(w);
|
||||
QObject::connect(icon,
|
||||
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
||||
w,
|
||||
SLOT(onIconClick()));
|
||||
icon->setIcon(w->windowIcon());
|
||||
icon->show();
|
||||
if(Settings::getInstance().getAutostartInTray() == false)
|
||||
w->show();
|
||||
}
|
||||
|
||||
|
||||
int errorcode = a.exec();
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
|
||||
|
||||
|
||||
This file is part of Tox Qt GUI.
|
||||
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
|
@ -10,7 +10,7 @@
|
|||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
|
@ -110,6 +110,7 @@ void Settings::load()
|
|||
enableIPv6 = s.value("enableIPv6", true).toBool();
|
||||
useTranslations = s.value("useTranslations", true).toBool();
|
||||
makeToxPortable = s.value("makeToxPortable", false).toBool();
|
||||
autostartInTray = s.value("autostartInTray", false).toBool();
|
||||
forceTCP = s.value("forceTCP", false).toBool();
|
||||
useProxy = s.value("useProxy", false).toBool();
|
||||
proxyAddr = s.value("proxyAddr", "").toString();
|
||||
|
@ -213,6 +214,7 @@ void Settings::save(QString path)
|
|||
s.setValue("enableIPv6", enableIPv6);
|
||||
s.setValue("useTranslations",useTranslations);
|
||||
s.setValue("makeToxPortable",makeToxPortable);
|
||||
s.setValue("autostartInTray",autostartInTray);
|
||||
s.setValue("useProxy", useProxy);
|
||||
s.setValue("forceTCP", forceTCP);
|
||||
s.setValue("proxyAddr", proxyAddr);
|
||||
|
@ -349,6 +351,16 @@ void Settings::setMakeToxPortable(bool newValue)
|
|||
save();
|
||||
}
|
||||
|
||||
bool Settings::getAutostartInTray() const
|
||||
{
|
||||
return autostartInTray;
|
||||
}
|
||||
|
||||
void Settings::setAutostartInTray(bool newValue)
|
||||
{
|
||||
autostartInTray = newValue;
|
||||
}
|
||||
|
||||
bool Settings::getUseTranslations() const
|
||||
{
|
||||
return useTranslations;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
|
||||
|
||||
|
||||
This file is part of Tox Qt GUI.
|
||||
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
|
@ -10,7 +10,7 @@
|
|||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
|
@ -49,12 +49,15 @@ public:
|
|||
bool getMakeToxPortable() const;
|
||||
void setMakeToxPortable(bool newValue);
|
||||
|
||||
bool getAutostartInTray() const;
|
||||
void setAutostartInTray(bool newValue);
|
||||
|
||||
bool getUseTranslations() const;
|
||||
void setUseTranslations(bool newValue);
|
||||
|
||||
bool getForceTCP() const;
|
||||
void setForceTCP(bool newValue);
|
||||
|
||||
|
||||
QString getProxyAddr() const;
|
||||
void setProxyAddr(const QString& newValue);
|
||||
|
||||
|
@ -165,7 +168,8 @@ private:
|
|||
bool enableIPv6;
|
||||
bool useTranslations;
|
||||
static bool makeToxPortable;
|
||||
|
||||
bool autostartInTray;
|
||||
|
||||
bool forceTCP;
|
||||
|
||||
bool useProxy;
|
||||
|
|
27
src/videosource.h
Normal file
27
src/videosource.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef VIDEOSOURCE_H
|
||||
#define VIDEOSOURCE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSize>
|
||||
|
||||
class VideoSource : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual void* getData() = 0; // a pointer to a frame
|
||||
virtual int getDataSize() = 0; // size of a frame in bytes
|
||||
|
||||
virtual void lock() = 0; // locks a frame so that it can't change
|
||||
virtual void unlock() = 0;
|
||||
|
||||
virtual QSize resolution() = 0; // resolution of a frame
|
||||
|
||||
virtual void subscribe() = 0;
|
||||
virtual void unsubscribe() = 0;
|
||||
|
||||
signals:
|
||||
void frameAvailable();
|
||||
|
||||
};
|
||||
|
||||
#endif // VIDEOSOURCE_H
|
|
@ -16,68 +16,72 @@
|
|||
|
||||
#include "camera.h"
|
||||
#include "widget.h"
|
||||
#include "src/cameraworker.h"
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
using namespace cv;
|
||||
Camera* Camera::instance = nullptr;
|
||||
|
||||
Camera::Camera()
|
||||
: refcount{0}
|
||||
: refcount(0)
|
||||
, workerThread(nullptr)
|
||||
, worker(nullptr)
|
||||
{
|
||||
worker = new CameraWorker(0);
|
||||
workerThread = new QThread();
|
||||
|
||||
worker->moveToThread(workerThread);
|
||||
|
||||
connect(workerThread, &QThread::started, worker, &CameraWorker::onStart);
|
||||
connect(workerThread, &QThread::finished, worker, &CameraWorker::deleteLater);
|
||||
connect(workerThread, &QThread::deleteLater, worker, &CameraWorker::deleteLater);
|
||||
connect(worker, &CameraWorker::started, this, &Camera::onWorkerStarted);
|
||||
connect(worker, &CameraWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable);
|
||||
connect(worker, &CameraWorker::resProbingFinished, this, &Camera::onResProbingFinished);
|
||||
workerThread->start();
|
||||
}
|
||||
|
||||
void Camera::suscribe()
|
||||
void Camera::onWorkerStarted()
|
||||
{
|
||||
worker->probeResolutions();
|
||||
}
|
||||
|
||||
Camera::~Camera()
|
||||
{
|
||||
workerThread->exit();
|
||||
workerThread->deleteLater();
|
||||
}
|
||||
|
||||
void Camera::subscribe()
|
||||
{
|
||||
if (refcount <= 0)
|
||||
{
|
||||
refcount = 1;
|
||||
cam.open(0);
|
||||
}
|
||||
else
|
||||
refcount++;
|
||||
worker->resume();
|
||||
|
||||
refcount++;
|
||||
}
|
||||
|
||||
void Camera::unsuscribe()
|
||||
void Camera::unsubscribe()
|
||||
{
|
||||
refcount--;
|
||||
|
||||
if (refcount <= 0)
|
||||
{
|
||||
cam.release();
|
||||
worker->suspend();
|
||||
refcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Mat Camera::getLastFrame()
|
||||
{
|
||||
Mat frame;
|
||||
cam >> frame;
|
||||
return frame;
|
||||
}
|
||||
|
||||
QImage Camera::getLastImage()
|
||||
{
|
||||
Mat3b src = getLastFrame();
|
||||
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
|
||||
for (int y = 0; y < src.rows; ++y)
|
||||
{
|
||||
const cv::Vec3b *srcrow = src[y];
|
||||
QRgb *destrow = (QRgb*)dest.scanLine(y);
|
||||
for (int x = 0; x < src.cols; ++x)
|
||||
destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
vpx_image Camera::getLastVPXImage()
|
||||
{
|
||||
Mat3b frame = getLastFrame();
|
||||
lock();
|
||||
vpx_image img;
|
||||
int w = frame.size().width, h = frame.size().height;
|
||||
int w = currFrame.size().width, h = currFrame.size().height;
|
||||
vpx_img_alloc(&img, VPX_IMG_FMT_I420, w, h, 1); // I420 == YUV420P, same as YV12 with U and V switched
|
||||
|
||||
size_t i=0, j=0;
|
||||
for( int line = 0; line < h; ++line )
|
||||
{
|
||||
const cv::Vec3b *srcrow = frame[line];
|
||||
const cv::Vec3b *srcrow = currFrame[line];
|
||||
if( !(line % 2) )
|
||||
{
|
||||
for( int x = 0; x < w; x += 2 )
|
||||
|
@ -112,10 +116,123 @@ vpx_image Camera::getLastVPXImage()
|
|||
}
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
return img;
|
||||
}
|
||||
|
||||
QList<QSize> Camera::getSupportedResolutions()
|
||||
{
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
QSize Camera::getBestVideoMode()
|
||||
{
|
||||
int bestScore = 0;
|
||||
QSize bestRes;
|
||||
|
||||
for (QSize res : getSupportedResolutions())
|
||||
{
|
||||
int score = res.width() * res.height();
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestRes = res;
|
||||
}
|
||||
}
|
||||
|
||||
return bestRes;
|
||||
}
|
||||
|
||||
void Camera::setResolution(QSize res)
|
||||
{
|
||||
worker->setProp(CV_CAP_PROP_FRAME_WIDTH, res.width());
|
||||
worker->setProp(CV_CAP_PROP_FRAME_HEIGHT, res.height());
|
||||
}
|
||||
|
||||
QSize Camera::getResolution()
|
||||
{
|
||||
return QSize(worker->getProp(CV_CAP_PROP_FRAME_WIDTH), worker->getProp(CV_CAP_PROP_FRAME_HEIGHT));
|
||||
}
|
||||
|
||||
void Camera::setProp(Camera::Prop prop, double val)
|
||||
{
|
||||
switch (prop)
|
||||
{
|
||||
case BRIGHTNESS:
|
||||
worker->setProp(CV_CAP_PROP_BRIGHTNESS, val);
|
||||
break;
|
||||
case SATURATION:
|
||||
worker->setProp(CV_CAP_PROP_SATURATION, val);
|
||||
break;
|
||||
case CONTRAST:
|
||||
worker->setProp(CV_CAP_PROP_CONTRAST, val);
|
||||
break;
|
||||
case HUE:
|
||||
worker->setProp(CV_CAP_PROP_HUE, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::getProp(Camera::Prop prop)
|
||||
{
|
||||
switch (prop)
|
||||
{
|
||||
case BRIGHTNESS:
|
||||
return worker->getProp(CV_CAP_PROP_BRIGHTNESS);
|
||||
case SATURATION:
|
||||
return worker->getProp(CV_CAP_PROP_SATURATION);
|
||||
case CONTRAST:
|
||||
return worker->getProp(CV_CAP_PROP_CONTRAST);
|
||||
case HUE:
|
||||
return worker->getProp(CV_CAP_PROP_HUE);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void Camera::onNewFrameAvailable()
|
||||
{
|
||||
emit frameAvailable();
|
||||
}
|
||||
|
||||
void Camera::onResProbingFinished(QList<QSize> res)
|
||||
{
|
||||
resolutions = res;
|
||||
}
|
||||
|
||||
void *Camera::getData()
|
||||
{
|
||||
return currFrame.data;
|
||||
}
|
||||
|
||||
int Camera::getDataSize()
|
||||
{
|
||||
return currFrame.total() * currFrame.channels();
|
||||
}
|
||||
|
||||
void Camera::lock()
|
||||
{
|
||||
mutex.lock();
|
||||
|
||||
if (worker->hasFrame())
|
||||
currFrame = worker->dequeueFrame();
|
||||
}
|
||||
|
||||
void Camera::unlock()
|
||||
{
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
QSize Camera::resolution()
|
||||
{
|
||||
return QSize(currFrame.cols, currFrame.rows);
|
||||
}
|
||||
|
||||
Camera* Camera::getInstance()
|
||||
{
|
||||
return Widget::getInstance()->getCamera();
|
||||
if (!instance)
|
||||
instance = new Camera();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,13 @@
|
|||
#define CAMERA_H
|
||||
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include "vpx/vpx_image.h"
|
||||
#include "opencv2/opencv.hpp"
|
||||
#include "src/videosource.h"
|
||||
|
||||
class CameraWorker;
|
||||
|
||||
/**
|
||||
* This class is a wrapper to share a camera's captured video frames
|
||||
|
@ -27,20 +32,60 @@
|
|||
* the camera only when needed, and giving access to the last frames
|
||||
**/
|
||||
|
||||
class Camera
|
||||
class Camera : public VideoSource
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Camera();
|
||||
enum Prop {
|
||||
BRIGHTNESS,
|
||||
SATURATION,
|
||||
CONTRAST,
|
||||
HUE,
|
||||
};
|
||||
|
||||
~Camera();
|
||||
|
||||
static Camera* getInstance(); ///< Returns the global widget's Camera instance
|
||||
void suscribe(); ///< Call this once before trying to get frames
|
||||
void unsuscribe(); ///< Call this once when you don't need frames anymore
|
||||
cv::Mat getLastFrame(); ///< Get the last captured frame
|
||||
QImage getLastImage(); ///< Convert the last frame to a QImage (can be expensive !)
|
||||
vpx_image getLastVPXImage(); ///< Convert the last frame to a vpx_image (can be expensive !)
|
||||
|
||||
QList<QSize> getSupportedResolutions();
|
||||
QSize getBestVideoMode();
|
||||
|
||||
void setResolution(QSize res);
|
||||
QSize getResolution();
|
||||
|
||||
void setProp(Prop prop, double val);
|
||||
double getProp(Prop prop);
|
||||
|
||||
// VideoSource interface
|
||||
virtual void *getData();
|
||||
virtual int getDataSize();
|
||||
virtual void lock();
|
||||
virtual void unlock();
|
||||
virtual QSize resolution();
|
||||
virtual void subscribe();
|
||||
virtual void unsubscribe();
|
||||
|
||||
protected:
|
||||
Camera();
|
||||
|
||||
private:
|
||||
int refcount; ///< Number of users suscribed to the camera
|
||||
cv::VideoCapture cam; ///< OpenCV camera capture opbject
|
||||
cv::Mat3b currFrame;
|
||||
QMutex mutex;
|
||||
|
||||
QThread* workerThread;
|
||||
CameraWorker* worker;
|
||||
|
||||
QList<QSize> resolutions;
|
||||
|
||||
static Camera* instance;
|
||||
|
||||
private slots:
|
||||
void onWorkerStarted();
|
||||
void onNewFrameAvailable();
|
||||
void onResProbingFinished(QList<QSize> res);
|
||||
|
||||
};
|
||||
|
||||
#endif // CAMERA_H
|
||||
|
|
|
@ -18,17 +18,16 @@
|
|||
#include "src/widget/camera.h"
|
||||
#include "ui_avsettings.h"
|
||||
|
||||
AVForm::AVForm(Camera* cam) :
|
||||
AVForm::AVForm() :
|
||||
GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png"))
|
||||
{
|
||||
bodyUI = new Ui::AVSettings;
|
||||
bodyUI->setupUi(this);
|
||||
|
||||
camView = new SelfCamView(cam, this);
|
||||
bodyUI->videoGroup->layout()->addWidget(camView);
|
||||
camView->hide(); // hide by default
|
||||
//cam->setVideoMode(cam->getBestVideoMode());
|
||||
camView = new VideoSurface(Camera::getInstance(), this);
|
||||
|
||||
connect(bodyUI->testVideoBtn, &QPushButton::clicked, this, &AVForm::onTestVideoPressed);
|
||||
bodyUI->CamViewLayout->addWidget(camView);
|
||||
}
|
||||
|
||||
AVForm::~AVForm()
|
||||
|
@ -36,22 +35,44 @@ AVForm::~AVForm()
|
|||
delete bodyUI;
|
||||
}
|
||||
|
||||
void AVForm::showTestVideo()
|
||||
void AVForm::present()
|
||||
{
|
||||
bodyUI->testVideoBtn->setText(tr("Hide video preview","On a button"));
|
||||
camView->show();
|
||||
bodyUI->videoModescomboBox->clear();
|
||||
QList<QSize> res = Camera::getInstance()->getSupportedResolutions();
|
||||
for (QSize r : res)
|
||||
bodyUI->videoModescomboBox->addItem(QString("%1x%2").arg(QString::number(r.width()),QString::number(r.height())));
|
||||
|
||||
bodyUI->ContrastSlider->setValue(Camera::getInstance()->getProp(Camera::CONTRAST)*100);
|
||||
bodyUI->BrightnessSlider->setValue(Camera::getInstance()->getProp(Camera::BRIGHTNESS)*100);
|
||||
bodyUI->SaturationSlider->setValue(Camera::getInstance()->getProp(Camera::SATURATION)*100);
|
||||
bodyUI->HueSlider->setValue(Camera::getInstance()->getProp(Camera::HUE)*100);
|
||||
}
|
||||
|
||||
void AVForm::closeTestVideo()
|
||||
void AVForm::on_ContrastSlider_sliderMoved(int position)
|
||||
{
|
||||
bodyUI->testVideoBtn->setText(tr("Show video preview","On a button"));
|
||||
camView->close();
|
||||
Camera::getInstance()->setProp(Camera::CONTRAST, position / 100.0);
|
||||
}
|
||||
|
||||
void AVForm::onTestVideoPressed()
|
||||
void AVForm::on_SaturationSlider_sliderMoved(int position)
|
||||
{
|
||||
if (camView->isVisible())
|
||||
closeTestVideo();
|
||||
else
|
||||
showTestVideo();
|
||||
Camera::getInstance()->setProp(Camera::SATURATION, position / 100.0);
|
||||
}
|
||||
|
||||
void AVForm::on_BrightnessSlider_sliderMoved(int position)
|
||||
{
|
||||
Camera::getInstance()->setProp(Camera::BRIGHTNESS, position / 100.0);
|
||||
}
|
||||
|
||||
void AVForm::on_HueSlider_sliderMoved(int position)
|
||||
{
|
||||
Camera::getInstance()->setProp(Camera::HUE, position / 100.0);
|
||||
}
|
||||
|
||||
void AVForm::on_videoModescomboBox_currentIndexChanged(const QString &arg1)
|
||||
{
|
||||
QStringList resStr = arg1.split("x");
|
||||
int w = resStr[0].toInt();
|
||||
int h = resStr[0].toInt();
|
||||
|
||||
Camera::getInstance()->setResolution(QSize(w,h));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#define AVFORM_H
|
||||
|
||||
#include "genericsettings.h"
|
||||
#include "src/widget/selfcamview.h"
|
||||
#include "src/widget/videosurface.h"
|
||||
#include <QGroupBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
|
@ -33,20 +33,21 @@ class AVForm : public GenericForm
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AVForm(Camera* cam);
|
||||
AVForm();
|
||||
~AVForm();
|
||||
virtual void present();
|
||||
|
||||
private slots:
|
||||
void onTestVideoPressed();
|
||||
|
||||
void on_ContrastSlider_sliderMoved(int position);
|
||||
void on_SaturationSlider_sliderMoved(int position);
|
||||
void on_BrightnessSlider_sliderMoved(int position);
|
||||
void on_HueSlider_sliderMoved(int position);
|
||||
void on_videoModescomboBox_currentIndexChanged(const QString &arg1);
|
||||
|
||||
private:
|
||||
Ui::AVSettings *bodyUI;
|
||||
|
||||
SelfCamView* camView;
|
||||
|
||||
void showTestVideo();
|
||||
void closeTestVideo();
|
||||
|
||||
VideoSurface* camView;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,27 +6,143 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<width>394</width>
|
||||
<height>391</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Volume Settings (Stubs)</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Playback</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSlider" name="horizontalSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Microphone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="horizontalSlider_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="videoGroup">
|
||||
<property name="title">
|
||||
<string>Video settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="testVideoBtn">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Show video preview</string>
|
||||
<string>Modes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="videoModescomboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Hue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="HueSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Brightness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSlider" name="BrightnessSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Saturation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSlider" name="SaturationSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Contrast</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSlider" name="ContrastSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QVBoxLayout" name="CamViewLayout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -38,7 +154,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
|
|
@ -31,13 +31,14 @@ GeneralForm::GeneralForm() :
|
|||
bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6());
|
||||
bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations());
|
||||
bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable());
|
||||
bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray());
|
||||
|
||||
for (auto entry : SmileyPack::listSmileyPacks())
|
||||
{
|
||||
bodyUI->smileyPackBrowser->addItem(entry.first, entry.second);
|
||||
}
|
||||
bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack()));
|
||||
|
||||
|
||||
bodyUI->cbUDPDisabled->setChecked(Settings::getInstance().getForceTCP());
|
||||
bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr());
|
||||
int port = Settings::getInstance().getProxyPort();
|
||||
|
@ -46,10 +47,11 @@ GeneralForm::GeneralForm() :
|
|||
|
||||
bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy());
|
||||
onUseProxyUpdated();
|
||||
|
||||
|
||||
connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated);
|
||||
connect(bodyUI->cbUseTranslations, &QCheckBox::stateChanged, this, &GeneralForm::onUseTranslationUpdated);
|
||||
connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated);
|
||||
connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray);
|
||||
connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int)));
|
||||
// new syntax can't handle overloaded signals... (at least not in a pretty way)
|
||||
connect(bodyUI->cbUDPDisabled, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated);
|
||||
|
@ -78,6 +80,11 @@ void GeneralForm::onMakeToxPortableUpdated()
|
|||
Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked());
|
||||
}
|
||||
|
||||
void GeneralForm::onSetAutostartInTray()
|
||||
{
|
||||
Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked());
|
||||
}
|
||||
|
||||
void GeneralForm::onSmileyBrowserIndexChanged(int index)
|
||||
{
|
||||
QString filename = bodyUI->smileyPackBrowser->itemData(index).toString();
|
||||
|
|
|
@ -36,6 +36,7 @@ private slots:
|
|||
void onEnableIPv6Updated();
|
||||
void onUseTranslationUpdated();
|
||||
void onMakeToxPortableUpdated();
|
||||
void onSetAutostartInTray();
|
||||
void onSmileyBrowserIndexChanged(int index);
|
||||
void onUDPUpdated();
|
||||
void onProxyAddrEdited();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>527</width>
|
||||
<height>367</height>
|
||||
<height>369</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -46,6 +46,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="startInTray">
|
||||
<property name="text">
|
||||
<string>Start in tray</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
GenericForm(const QString &name, const QPixmap &icon) : formName(name), formIcon(icon) {;}
|
||||
~GenericForm() {;}
|
||||
|
||||
virtual void updateContent() {;}
|
||||
virtual void present() {}
|
||||
QString getFormName() {return formName;}
|
||||
QPixmap getFormIcon() {return formIcon;}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ void IdentityForm::onStatusMessageEdited()
|
|||
Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text());
|
||||
}
|
||||
|
||||
void IdentityForm::updateContent()
|
||||
void IdentityForm::present()
|
||||
{
|
||||
toxId->setText(Core::getInstance()->getSelfId().toString());
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
void setUserName(const QString &name);
|
||||
void setStatusMessage(const QString &msg);
|
||||
|
||||
virtual void updateContent();
|
||||
virtual void present();
|
||||
|
||||
signals:
|
||||
void userNameChanged(QString);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include <QTabBar>
|
||||
#include <QStackedWidget>
|
||||
|
||||
SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent)
|
||||
SettingsWidget::SettingsWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
body = new QWidget(this);
|
||||
|
@ -55,7 +55,7 @@ SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent)
|
|||
GeneralForm *gfrm = new GeneralForm;
|
||||
ifrm = new IdentityForm;
|
||||
PrivacyForm *pfrm = new PrivacyForm;
|
||||
AVForm *avfrm = new AVForm(cam);
|
||||
AVForm *avfrm = new AVForm;
|
||||
|
||||
GenericForm *cfgForms[] = {gfrm, ifrm, pfrm, avfrm};
|
||||
for (auto cfgForm : cfgForms)
|
||||
|
@ -85,8 +85,8 @@ void SettingsWidget::show(Ui::MainWindow& ui)
|
|||
void SettingsWidget::onTabChanged(int index)
|
||||
{
|
||||
this->settingsWidgets->setCurrentIndex(index);
|
||||
GenericForm *currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index));
|
||||
currentWidget->updateContent();
|
||||
GenericForm* currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index));
|
||||
currentWidget->present();
|
||||
nameLabel->setText(currentWidget->getFormName());
|
||||
imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation));
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class SettingsWidget : public QWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SettingsWidget(Camera* cam, QWidget* parent = nullptr);
|
||||
SettingsWidget(QWidget* parent = nullptr);
|
||||
~SettingsWidget();
|
||||
|
||||
void show(Ui::MainWindow &ui);
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
This program is libre software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "selfcamview.h"
|
||||
#include "camera.h"
|
||||
#include <QCloseEvent>
|
||||
#include <QShowEvent>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
SelfCamView::SelfCamView(Camera* Cam, QWidget* parent)
|
||||
: QWidget(parent), displayLabel{new QLabel},
|
||||
mainLayout{new QHBoxLayout()}, cam(Cam), updateDisplayTimer{new QTimer}
|
||||
{
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(SelfCamView::tr("Tox video test","Title of the window to test the video/webcam"));
|
||||
setMinimumSize(320,240);
|
||||
|
||||
updateDisplayTimer->setInterval(5);
|
||||
updateDisplayTimer->setSingleShot(false);
|
||||
|
||||
displayLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
mainLayout->addWidget(displayLabel);
|
||||
|
||||
connect(updateDisplayTimer, SIGNAL(timeout()), this, SLOT(updateDisplay()));
|
||||
}
|
||||
|
||||
void SelfCamView::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
cam->unsuscribe();
|
||||
updateDisplayTimer->stop();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void SelfCamView::showEvent(QShowEvent* event)
|
||||
{
|
||||
cam->suscribe();
|
||||
updateDisplayTimer->start();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void SelfCamView::updateDisplay()
|
||||
{
|
||||
displayLabel->setPixmap(QPixmap::fromImage(cam->getLastImage()).scaled(displayLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
void SelfCamView::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
Q_UNUSED(e)
|
||||
updateDisplay();
|
||||
}
|
197
src/widget/videosurface.cpp
Normal file
197
src/widget/videosurface.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
This program is libre software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "videosurface.h"
|
||||
#include "camera.h"
|
||||
#include <QTimer>
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QDebug>
|
||||
|
||||
VideoSurface::VideoSurface(VideoSource *Source, QWidget* parent)
|
||||
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
|
||||
, source(Source)
|
||||
, pbo(nullptr)
|
||||
, program(nullptr)
|
||||
, textureId(0)
|
||||
, pboAllocSize(0)
|
||||
, uploadFrame(false)
|
||||
, hasSubscribed(false)
|
||||
{
|
||||
setFixedSize(source->resolution());
|
||||
}
|
||||
|
||||
VideoSurface::~VideoSurface()
|
||||
{
|
||||
if (pbo)
|
||||
delete pbo;
|
||||
|
||||
if (textureId != 0)
|
||||
glDeleteTextures(1, &textureId);
|
||||
|
||||
source->unsubscribe();
|
||||
}
|
||||
|
||||
void VideoSurface::hideEvent(QHideEvent *ev)
|
||||
{
|
||||
if (hasSubscribed)
|
||||
{
|
||||
source->unsubscribe();
|
||||
hasSubscribed = false;
|
||||
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL);
|
||||
}
|
||||
|
||||
QGLWidget::hideEvent(ev);
|
||||
}
|
||||
|
||||
void VideoSurface::showEvent(QShowEvent *ev)
|
||||
{
|
||||
if (!hasSubscribed)
|
||||
{
|
||||
source->subscribe();
|
||||
hasSubscribed = true;
|
||||
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL);
|
||||
}
|
||||
|
||||
QGLWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
void VideoSurface::initializeGL()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void VideoSurface::paintGL()
|
||||
{
|
||||
if (!pbo)
|
||||
{
|
||||
qDebug() << "Creating pbo, program";
|
||||
|
||||
// pbo
|
||||
pbo = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer);
|
||||
pbo->setUsagePattern(QOpenGLBuffer::StreamDraw);
|
||||
pbo->create();
|
||||
|
||||
// shaders
|
||||
program = new QOpenGLShaderProgram;
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Vertex,
|
||||
"attribute vec4 vertices;"
|
||||
"varying vec2 coords;"
|
||||
"void main() {"
|
||||
" gl_Position = vec4(vertices.xy,0.0,1.0);"
|
||||
" coords = vertices.xy*vec2(0.5,0.5)+vec2(0.5,0.5);"
|
||||
"}");
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Fragment,
|
||||
"uniform sampler2D texture0;"
|
||||
"varying vec2 coords;"
|
||||
"void main() {"
|
||||
" vec4 color = texture2D(texture0,coords*vec2(1.0, -1.0));"
|
||||
" gl_FragColor = vec4(color.b, color.g, color.r, 1);"
|
||||
"}");
|
||||
|
||||
program->bindAttributeLocation("vertices", 0);
|
||||
program->link();
|
||||
}
|
||||
|
||||
if (res != source->resolution())
|
||||
{
|
||||
qDebug() << "Change resolution " << res << " to " << source->resolution();
|
||||
res = source->resolution();
|
||||
|
||||
// a texture used to render the pbo (has the match the pixelformat of the source)
|
||||
glGenTextures(1,&textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glTexImage2D(GL_TEXTURE_2D,0, GL_RGB, res.width(), res.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
setFixedSize(res);
|
||||
}
|
||||
|
||||
|
||||
if (uploadFrame)
|
||||
{
|
||||
source->lock();
|
||||
void* frame = source->getData();
|
||||
int frameBytes = source->getDataSize();
|
||||
|
||||
if (pboAllocSize != frameBytes && frameBytes > 0)
|
||||
{
|
||||
qDebug() << "Resize pbo " << frameBytes << "bytes (was" << pboAllocSize << ") res " << source->resolution();
|
||||
|
||||
pbo->bind();
|
||||
pbo->allocate(frameBytes);
|
||||
pbo->release();
|
||||
|
||||
pboAllocSize = frameBytes;
|
||||
}
|
||||
|
||||
// transfer data
|
||||
pbo->bind();
|
||||
|
||||
void* ptr = pbo->map(QOpenGLBuffer::WriteOnly);
|
||||
if (ptr && frame)
|
||||
memcpy(ptr, frame, frameBytes);
|
||||
pbo->unmap();
|
||||
|
||||
source->unlock();
|
||||
|
||||
//transfer pbo data to texture
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,0,0,0, res.width(), res.height(), GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
pbo->release();
|
||||
|
||||
uploadFrame = false;
|
||||
}
|
||||
|
||||
// render pbo
|
||||
float values[] = {
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
1, 1
|
||||
};
|
||||
|
||||
program->setAttributeArray(0, GL_FLOAT, values, 2);
|
||||
|
||||
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glViewport(0, 0, width(), height());
|
||||
|
||||
program->bind();
|
||||
program->enableAttributeArray(0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
|
||||
//draw fullscreen quad
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
program->disableAttributeArray(0);
|
||||
program->release();
|
||||
}
|
||||
|
||||
void VideoSurface::updateGL()
|
||||
{
|
||||
uploadFrame = true;
|
||||
QGLWidget::updateGL();
|
||||
}
|
||||
|
||||
|
|
@ -17,39 +17,42 @@
|
|||
#ifndef SELFCAMVIEW_H
|
||||
#define SELFCAMVIEW_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QGLWidget>
|
||||
|
||||
class QCloseEvent;
|
||||
class QShowEvent;
|
||||
class QPainter;
|
||||
class Camera;
|
||||
class QLabel;
|
||||
class QHBoxLayout;
|
||||
class QOpenGLBuffer;
|
||||
class QOpenGLShaderProgram;
|
||||
class QTimer;
|
||||
class VideoSource;
|
||||
|
||||
class SelfCamView : public QWidget
|
||||
class VideoSurface : public QGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SelfCamView(Camera* Cam, QWidget *parent=0);
|
||||
VideoSurface(VideoSource* source, QWidget* parent=0);
|
||||
~VideoSurface();
|
||||
|
||||
private slots:
|
||||
void updateDisplay();
|
||||
|
||||
private:
|
||||
void closeEvent(QCloseEvent*);
|
||||
void showEvent(QShowEvent*);
|
||||
void paint(QPainter *painter);
|
||||
virtual void hideEvent(QHideEvent* ev);
|
||||
virtual void showEvent(QShowEvent* ev);
|
||||
|
||||
// QGLWidget interface
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
virtual void initializeGL();
|
||||
virtual void paintGL();
|
||||
virtual void updateGL();
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
QLabel *displayLabel;
|
||||
QHBoxLayout* mainLayout;
|
||||
Camera* cam;
|
||||
QTimer* updateDisplayTimer;
|
||||
VideoSource* source;
|
||||
QOpenGLBuffer* pbo;
|
||||
QOpenGLShaderProgram* program;
|
||||
GLuint textureId;
|
||||
int pboAllocSize;
|
||||
QSize res;
|
||||
bool uploadFrame;
|
||||
bool hasSubscribed;
|
||||
|
||||
};
|
||||
|
||||
#endif // SELFCAMVIEW_H
|
|
@ -27,7 +27,6 @@
|
|||
#include "groupwidget.h"
|
||||
#include "form/groupchatform.h"
|
||||
#include "src/misc/style.h"
|
||||
#include "selfcamview.h"
|
||||
#include "friendlistwidget.h"
|
||||
#include "camera.h"
|
||||
#include "form/chatform.h"
|
||||
|
@ -102,8 +101,7 @@ Widget::Widget(QWidget *parent)
|
|||
ui->statusButton->setProperty("status", "offline");
|
||||
Style::repolish(ui->statusButton);
|
||||
|
||||
camera = new Camera;
|
||||
settingsWidget = new SettingsWidget(camera);
|
||||
settingsWidget = new SettingsWidget();
|
||||
|
||||
// Disable some widgets until we're connected to the DHT
|
||||
ui->statusButton->setEnabled(false);
|
||||
|
@ -119,7 +117,7 @@ Widget::Widget(QWidget *parent)
|
|||
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
|
||||
|
||||
coreThread = new QThread(this);
|
||||
core = new Core(camera, coreThread);
|
||||
core = new Core(Camera::getInstance(), coreThread);
|
||||
core->moveToThread(coreThread);
|
||||
connect(coreThread, &QThread::started, core, &Core::start);
|
||||
|
||||
|
@ -145,6 +143,7 @@ Widget::Widget(QWidget *parent)
|
|||
connect(core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived);
|
||||
connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged);
|
||||
connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated);
|
||||
connect(core, &Core::avInvite, this, &Widget::playRingtone);
|
||||
|
||||
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)));
|
||||
|
@ -217,11 +216,6 @@ QString Widget::getUsername()
|
|||
return core->getUsername();
|
||||
}
|
||||
|
||||
Camera* Widget::getCamera()
|
||||
{
|
||||
return camera;
|
||||
}
|
||||
|
||||
void Widget::onAvatarClicked()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath());
|
||||
|
@ -343,6 +337,14 @@ void Widget::onTransferClicked()
|
|||
activeChatroomWidget = nullptr;
|
||||
}
|
||||
|
||||
void Widget::onIconClick()
|
||||
{
|
||||
if(this->isHidden() == true)
|
||||
this->show();
|
||||
else
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void Widget::onSettingsClicked()
|
||||
{
|
||||
hideMainForms();
|
||||
|
@ -535,6 +537,26 @@ void Widget::newMessageAlert()
|
|||
alSourcePlay(core->alMainSource);
|
||||
}
|
||||
|
||||
void Widget::playRingtone()
|
||||
{
|
||||
QApplication::alert(this);
|
||||
|
||||
static QFile sndFile1(":audio/ToxicIncomingCall.pcm"); // for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever
|
||||
static QByteArray sndData1;
|
||||
if (sndData1.isEmpty())
|
||||
{
|
||||
sndFile1.open(QIODevice::ReadOnly);
|
||||
sndData1 = sndFile1.readAll();
|
||||
sndFile1.close();
|
||||
}
|
||||
|
||||
ALuint buffer;
|
||||
alGenBuffers(1, &buffer);
|
||||
alBufferData(buffer, AL_FORMAT_MONO16, sndData1.data(), sndData1.size(), 44100);
|
||||
alSourcei(core->alMainSource, AL_BUFFER, buffer);
|
||||
alSourcePlay(core->alMainSource);
|
||||
}
|
||||
|
||||
void Widget::onFriendRequestReceived(const QString& userId, const QString& message)
|
||||
{
|
||||
FriendRequestDialog dialog(this, userId, message);
|
||||
|
|
|
@ -34,7 +34,7 @@ class GenericChatroomWidget;
|
|||
class Group;
|
||||
struct Friend;
|
||||
class QSplitter;
|
||||
class SelfCamView;
|
||||
class VideoSurface;
|
||||
class QMenu;
|
||||
class Core;
|
||||
class Camera;
|
||||
|
@ -104,6 +104,8 @@ private slots:
|
|||
void setStatusBusy();
|
||||
void onMessageSendResult(int friendId, const QString& message, int messageId);
|
||||
void onGroupSendResult(int groupId, const QString& message, int result);
|
||||
void playRingtone();
|
||||
void onIconClick();
|
||||
|
||||
private:
|
||||
void hideMainForms();
|
||||
|
@ -122,7 +124,6 @@ private:
|
|||
static Widget* instance;
|
||||
GenericChatroomWidget* activeChatroomWidget;
|
||||
FriendListWidget* contactListWidget;
|
||||
Camera* camera;
|
||||
MaskablePixmapWidget* profilePicture;
|
||||
bool notify(QObject *receiver, QEvent *event);
|
||||
};
|
||||
|
|
BIN
translations/pirate.qm
Normal file
BIN
translations/pirate.qm
Normal file
Binary file not shown.
545
translations/pirate.ts
Normal file
545
translations/pirate.ts
Normal file
|
@ -0,0 +1,545 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.0" language="en_PIRATE">
|
||||
<context>
|
||||
<name>AVForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="22"/>
|
||||
<source>Audio/Video settings</source>
|
||||
<translation type="unfinished">Hollerin'/Crystals o' Far-Seein' settings</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="41"/>
|
||||
<source>Hide video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation type="unfinished">Hide crystals o' Far-Seein' preview</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="47"/>
|
||||
<source>Show video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation type="unfinished">See crystals o' Far-Seein' preview</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AVSettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="20"/>
|
||||
<source>Video settings</source>
|
||||
<translation type="unfinished">Crystals o' Far-Seein' settings</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="26"/>
|
||||
<source>Show video preview</source>
|
||||
<translation type="unfinished">See crystals o' Far-Seein' preview</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AddFriendForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="34"/>
|
||||
<source>Add Friends</source>
|
||||
<translation>Recruit yer crew</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="37"/>
|
||||
<source>Tox ID</source>
|
||||
<comment>Tox ID of the person you're sending a friend request to</comment>
|
||||
<translation>Tox ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="38"/>
|
||||
<source>Message</source>
|
||||
<comment>The message you send in friend requests</comment>
|
||||
<translation type="unfinished">Parchment Bottle of recruiting</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="39"/>
|
||||
<source>Send friend request</source>
|
||||
<translation type="unfinished">Send recruitment letter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="40"/>
|
||||
<source>Tox me maybe?</source>
|
||||
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
|
||||
<translation type="unfinished">Tox, arrr?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="96"/>
|
||||
<source>Please fill in a valid Tox ID</source>
|
||||
<comment>Tox ID of the friend you're sending a friend request to</comment>
|
||||
<translation type="unfinished">Throw in here Tox ID, but a rightful one!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="99"/>
|
||||
<source>You can't add yourself as a friend!</source>
|
||||
<comment>When trying to add your own Tox ID as friend</comment>
|
||||
<translation type="unfinished">Yer cannot add yerself in yer crew!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="116"/>
|
||||
<source>This address does not exist</source>
|
||||
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
|
||||
<translation type="unfinished">Recruitment address yer usin' ain't there</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="120"/>
|
||||
<source>Error while looking up DNS</source>
|
||||
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
|
||||
<translation type="unfinished">DNS witchery errored upon looking</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="126"/>
|
||||
<source>Unexpected number of text records</source>
|
||||
<comment>Error with the DNS</comment>
|
||||
<translation type="unfinished">All numbery that DNS brought upon was unexpectable</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="132"/>
|
||||
<source>Unexpected number of values in text record</source>
|
||||
<comment>Error with the DNS</comment>
|
||||
<translation type="unfinished">All things </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="139"/>
|
||||
<source>The DNS lookup does not contain any Tox ID</source>
|
||||
<comment>Error with the DNS</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="145"/>
|
||||
<location filename="../widget/form/addfriendform.cpp" line="151"/>
|
||||
<source>The DNS lookup does not contain a valid Tox ID</source>
|
||||
<comment>Error with the DNS</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/chatform.cpp" line="105"/>
|
||||
<source>Send a file</source>
|
||||
<translation type="unfinished">Send treasure</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Core</name>
|
||||
<message>
|
||||
<location filename="../core.cpp" line="1068"/>
|
||||
<source>Encrypted profile</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core.cpp" line="1069"/>
|
||||
<source>Your tox profile seems to be encrypted, qTox can't open it
|
||||
Do you want to erase this profile ?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileTransferInstance</name>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="208"/>
|
||||
<source>Save a file</source>
|
||||
<comment>Title of the file saving dialog</comment>
|
||||
<translation type="unfinished">Receive treasure</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="219"/>
|
||||
<source>Location not writable</source>
|
||||
<comment>Title of permissions popup</comment>
|
||||
<translation type="unfinished">Cannot bury treasure here</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="219"/>
|
||||
<source>You do not have permission to write that location. Choose another, or cancel the save dialog.</source>
|
||||
<comment>text of permissions popup</comment>
|
||||
<translation type="unfinished">Captain did't give permission bury that treasure here! Find another location or abandon hope of keepin' it.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FilesForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/filesform.cpp" line="30"/>
|
||||
<source>Transfered Files</source>
|
||||
<comment>"Headline" of the window</comment>
|
||||
<translation type="unfinished">Treasures given/received</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/filesform.cpp" line="38"/>
|
||||
<source>Downloads</source>
|
||||
<translation type="unfinished">Received</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/filesform.cpp" line="39"/>
|
||||
<source>Uploads</source>
|
||||
<translation type="unfinished">Sent</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FriendRequestDialog</name>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="30"/>
|
||||
<source>Friend request</source>
|
||||
<comment>Title of the window to aceept/deny a friend request</comment>
|
||||
<translation type="unfinished">Recruitment</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="32"/>
|
||||
<source>Someone wants to make friends with you</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="33"/>
|
||||
<source>User ID:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="37"/>
|
||||
<source>Friend request message:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="44"/>
|
||||
<source>Accept</source>
|
||||
<comment>Accept a friend request</comment>
|
||||
<translation type="unfinished">Arr</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/tool/friendrequestdialog.cpp" line="45"/>
|
||||
<source>Reject</source>
|
||||
<comment>Reject a friend request</comment>
|
||||
<translation type="unfinished">No</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FriendWidget</name>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="48"/>
|
||||
<source>Copy friend ID</source>
|
||||
<comment>Menu to copy the Tox ID of that friend</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="49"/>
|
||||
<source>Invite in group</source>
|
||||
<comment>Menu to invite a friend in a groupchat</comment>
|
||||
<translation type="unfinished">Recruit in group</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="59"/>
|
||||
<source>Remove friend</source>
|
||||
<comment>Menu to remove the friend from our friendlist</comment>
|
||||
<translation type="unfinished">Throw out traitor</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GeneralForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalform.cpp" line="26"/>
|
||||
<source>General Settings</source>
|
||||
<translation type="unfinished">O' ship settings</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GeneralSettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="29"/>
|
||||
<source>General Settings</source>
|
||||
<translation type="unfinished">O' ship settings</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="35"/>
|
||||
<source>Use translations</source>
|
||||
<extracomment>Text on a checkbox to enable translations</extracomment>
|
||||
<translation type="unfinished">Linguistics use</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="42"/>
|
||||
<source>Save settings to the working directory instead of the usual conf dir</source>
|
||||
<extracomment>describes makeToxPortable checkbox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="45"/>
|
||||
<source>Make Tox portable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="55"/>
|
||||
<source>Theme</source>
|
||||
<translation type="unfinished">Lookin'</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="61"/>
|
||||
<source>Smiley Pack</source>
|
||||
<extracomment>Text on smiley pack label</extracomment>
|
||||
<translation type="unfinished">Teeth showin' box</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="74"/>
|
||||
<source>Connection Settings</source>
|
||||
<translation type="unfinished">Sailin' setting</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="80"/>
|
||||
<source>Enable IPv6 (recommended)</source>
|
||||
<extracomment>Text on a checkbox to enable IPv6</extracomment>
|
||||
<translation type="unfinished">Use IPv6 (good for ya)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="87"/>
|
||||
<source>This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary.</source>
|
||||
<extracomment>force tcp checkbox tooltip</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="90"/>
|
||||
<source>Disable UDP (not recommended)</source>
|
||||
<extracomment>Text on checkbox to disable UDP</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="97"/>
|
||||
<source>Use proxy (SOCKS5)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="106"/>
|
||||
<source>Address</source>
|
||||
<extracomment>Text on proxy addr label</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="116"/>
|
||||
<source>Port</source>
|
||||
<extracomment>Text on proxy port label</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GenericChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="149"/>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="155"/>
|
||||
<source>Save chat log</source>
|
||||
<translation type="unfinished">Write log of yer talkin'</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GroupChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="47"/>
|
||||
<source>%1 users in chat</source>
|
||||
<comment>Number of users in chat</comment>
|
||||
<translation type="unfinished">%1 mates in chat</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="85"/>
|
||||
<source>%1 users in chat</source>
|
||||
<translation type="unfinished">%1 mates in chat</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GroupWidget</name>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="39"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="59"/>
|
||||
<source>%1 users in chat</source>
|
||||
<translation type="unfinished">%1 mates in chat</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="41"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="61"/>
|
||||
<source>0 users in chat</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="48"/>
|
||||
<source>Quit group</source>
|
||||
<comment>Menu to quit a groupchat</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IdentityForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identityform.cpp" line="29"/>
|
||||
<source>Your identity</source>
|
||||
<translation type="unfinished">Yer self</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IdentitySettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="20"/>
|
||||
<source>Public Information</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="26"/>
|
||||
<source>Name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="36"/>
|
||||
<source>Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="49"/>
|
||||
<source>Tox ID</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="55"/>
|
||||
<source>Your Tox ID (click to copy)</source>
|
||||
<translation type="unfinished">Yer Tox ID (hit to steal)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="20"/>
|
||||
<source>qTox</source>
|
||||
<translation>qTox</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="859"/>
|
||||
<source>Your name</source>
|
||||
<translation>Yer name</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="941"/>
|
||||
<source>Your status</source>
|
||||
<translation>Yer status</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1089"/>
|
||||
<source>Add friends</source>
|
||||
<translation>Recruit crew</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1115"/>
|
||||
<source>Create a group chat</source>
|
||||
<translation>Make a chat with yer mates</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1147"/>
|
||||
<source>View completed file transfers</source>
|
||||
<translation>See treasures given and received</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1179"/>
|
||||
<source>Change your settings</source>
|
||||
<translation>Change yer setting'</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1761"/>
|
||||
<source>Close</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1764"/>
|
||||
<source>Ctrl+Q</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PrivacyForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/privacyform.cpp" line="21"/>
|
||||
<source>Privacy settings</source>
|
||||
<translation>Secrecy settin'</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SelfCamView</name>
|
||||
<message>
|
||||
<location filename="../widget/selfcamview.cpp" line="33"/>
|
||||
<source>Tox video test</source>
|
||||
<comment>Title of the window to test the video/webcam</comment>
|
||||
<translation>Tox crystals o' Far-Seein' test</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Widget</name>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="88"/>
|
||||
<source>Online</source>
|
||||
<comment>Button to set your status to 'Online'</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="90"/>
|
||||
<source>Away</source>
|
||||
<comment>Button to set your status to 'Away'</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="92"/>
|
||||
<source>Busy</source>
|
||||
<comment>Button to set your status to 'Busy'</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="225"/>
|
||||
<source>Choose a profile picture</source>
|
||||
<translation>Choose yer flag</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="232"/>
|
||||
<location filename="../widget/widget.cpp" line="239"/>
|
||||
<location filename="../widget/widget.cpp" line="260"/>
|
||||
<source>Error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="232"/>
|
||||
<source>Unable to open this file</source>
|
||||
<translation>Treasure cannot be opened</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="239"/>
|
||||
<source>Unable to read this image</source>
|
||||
<translation type="unfinished">Cannot see treasure</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="260"/>
|
||||
<source>This image is too big</source>
|
||||
<translation type="unfinished">Yer flag too big</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="287"/>
|
||||
<source>Toxcore failed to start, the application will terminate after you close this message.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="296"/>
|
||||
<source>toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart.</source>
|
||||
<comment>popup text</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="611"/>
|
||||
<source><Unknown></source>
|
||||
<comment>Placeholder when we don't know someone's name in a group chat</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
Binary file not shown.
|
@ -1,26 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="uk_UA">
|
||||
<TS version="2.0" language="uk_UA">
|
||||
<context>
|
||||
<name>AVForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="22"/>
|
||||
<source>Audio/Video settings</source>
|
||||
<translation>Параметри аудіо/відео</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="41"/>
|
||||
<source>Hide video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation>Приховати вікно перегляду</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avform.cpp" line="47"/>
|
||||
<source>Show video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation>Показати вікно перегляду</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AVPage</name>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="132"/>
|
||||
<source>Video Settings</source>
|
||||
<translation type="obsolete">Параметри відео</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation type="obsolete">Показати вікно попереднього перегляду</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation type="obsolete">Приховати вікно попереднього перегляду</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AVSettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation>Form</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="20"/>
|
||||
<source>Video settings</source>
|
||||
<translation>Параметри відео</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="136"/>
|
||||
<location filename="../widget/settingsdialog.cpp" line="163"/>
|
||||
<location filename="../widget/form/settings/avsettings.ui" line="26"/>
|
||||
<source>Show video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation>Показати вікно попереднього перегляду</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="157"/>
|
||||
<source>Hide video preview</source>
|
||||
<comment>On a button</comment>
|
||||
<translation>Приховати вікно попереднього перегляду</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AddFriendForm</name>
|
||||
|
@ -105,27 +139,42 @@
|
|||
<context>
|
||||
<name>ChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/chatform.cpp" line="88"/>
|
||||
<location filename="../widget/form/chatform.cpp" line="105"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Надіслати файл</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Core</name>
|
||||
<message>
|
||||
<location filename="../core.cpp" line="1068"/>
|
||||
<source>Encrypted profile</source>
|
||||
<translation>Зашифрований профіль</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core.cpp" line="1069"/>
|
||||
<source>Your tox profile seems to be encrypted, qTox can't open it
|
||||
Do you want to erase this profile ?</source>
|
||||
<translation>Схоже, що ваш tox-профіль зашифровано, qTox не може його відкрити.
|
||||
Бажаєте стерти цей профіль?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileTransferInstance</name>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="205"/>
|
||||
<location filename="../filetransferinstance.cpp" line="208"/>
|
||||
<source>Save a file</source>
|
||||
<comment>Title of the file saving dialog</comment>
|
||||
<translation>Зберегти файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="216"/>
|
||||
<location filename="../filetransferinstance.cpp" line="219"/>
|
||||
<source>Location not writable</source>
|
||||
<comment>Title of permissions popup</comment>
|
||||
<translation>Немає прав на запис</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../filetransferinstance.cpp" line="216"/>
|
||||
<location filename="../filetransferinstance.cpp" line="219"/>
|
||||
<source>You do not have permission to write that location. Choose another, or cancel the save dialog.</source>
|
||||
<comment>text of permissions popup</comment>
|
||||
<translation>Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу.</translation>
|
||||
|
@ -189,71 +238,153 @@
|
|||
<context>
|
||||
<name>FriendWidget</name>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="90"/>
|
||||
<location filename="../widget/friendwidget.cpp" line="48"/>
|
||||
<source>Copy friend ID</source>
|
||||
<comment>Menu to copy the Tox ID of that friend</comment>
|
||||
<translation>Копіювати дружній ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="91"/>
|
||||
<location filename="../widget/friendwidget.cpp" line="49"/>
|
||||
<source>Invite in group</source>
|
||||
<comment>Menu to invite a friend in a groupchat</comment>
|
||||
<translation>Запросити до групи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/friendwidget.cpp" line="101"/>
|
||||
<location filename="../widget/friendwidget.cpp" line="59"/>
|
||||
<source>Remove friend</source>
|
||||
<comment>Menu to remove the friend from our friendlist</comment>
|
||||
<translation>Вилучити з друзів</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GeneralForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalform.cpp" line="26"/>
|
||||
<source>General Settings</source>
|
||||
<translation>Основні параметри</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GeneralPage</name>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="31"/>
|
||||
<source>General Settings</source>
|
||||
<translation type="obsolete">Основні параметри</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable IPv6 (recommended)</source>
|
||||
<comment>Text on a checkbox to enable IPv6</comment>
|
||||
<translation type="obsolete">Дозволити IPv6 (рекомендовано)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use translations</source>
|
||||
<comment>Text on a checkbox to enable translations</comment>
|
||||
<translation type="obsolete">Використовувати мову системи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Make Tox portable</source>
|
||||
<comment>Text on a checkbox to make qTox a portable application</comment>
|
||||
<translation type="obsolete">Портативний запуск</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save settings to the working directory instead of the usual conf dir</source>
|
||||
<comment>describes makeToxPortable checkbox</comment>
|
||||
<translation type="obsolete">Зберігати налаштування в робочий теці</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Theme</source>
|
||||
<translation type="obsolete">Графічна тема</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Smiley Pack</source>
|
||||
<translation type="obsolete">Графічний пакунок емоційних картинок</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GeneralSettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation>Form</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="29"/>
|
||||
<source>General Settings</source>
|
||||
<translation>Основні параметри</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="34"/>
|
||||
<source>Enable IPv6 (recommended)</source>
|
||||
<comment>Text on a checkbox to enable IPv6</comment>
|
||||
<translation>Дозволити IPv6 (рекомендовано)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="36"/>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="35"/>
|
||||
<source>Use translations</source>
|
||||
<comment>Text on a checkbox to enable translations</comment>
|
||||
<extracomment>Text on a checkbox to enable translations</extracomment>
|
||||
<translation>Використовувати мову системи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="38"/>
|
||||
<source>Make Tox portable</source>
|
||||
<comment>Text on a checkbox to make qTox a portable application</comment>
|
||||
<translation>Портативний запуск</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="39"/>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="42"/>
|
||||
<source>Save settings to the working directory instead of the usual conf dir</source>
|
||||
<comment>describes makeToxPortable checkbox</comment>
|
||||
<extracomment>describes makeToxPortable checkbox</extracomment>
|
||||
<translation>Зберігати налаштування в робочий теці</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="48"/>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="45"/>
|
||||
<source>Make Tox portable</source>
|
||||
<translation>Портативний запуск</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="55"/>
|
||||
<source>Theme</source>
|
||||
<translation>Графічна тема</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="49"/>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="61"/>
|
||||
<source>Smiley Pack</source>
|
||||
<extracomment>Text on smiley pack label</extracomment>
|
||||
<translation>Графічний пакунок емоційних картинок</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="74"/>
|
||||
<source>Connection Settings</source>
|
||||
<translation>Параметри підключення</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="80"/>
|
||||
<source>Enable IPv6 (recommended)</source>
|
||||
<extracomment>Text on a checkbox to enable IPv6</extracomment>
|
||||
<translation>Дозволити IPv6 (рекомендовано)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="87"/>
|
||||
<source>This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary.</source>
|
||||
<extracomment>force tcp checkbox tooltip</extracomment>
|
||||
<translation>Це дозволяє використовувати tox, наприклад, поверх протоколу Tor. Але це збульшує навантаження на мережу, тому використовуйте лише в разі необхідності.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="90"/>
|
||||
<source>Disable UDP (not recommended)</source>
|
||||
<extracomment>Text on checkbox to disable UDP</extracomment>
|
||||
<translation>Вимкнути IPv6 (не рекомендовано)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="97"/>
|
||||
<source>Use proxy (SOCKS5)</source>
|
||||
<translation>Використовувати проксі (SOCKS5)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="106"/>
|
||||
<source>Address</source>
|
||||
<extracomment>Text on proxy addr label</extracomment>
|
||||
<translation>Адреса</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/generalsettings.ui" line="116"/>
|
||||
<source>Port</source>
|
||||
<extracomment>Text on proxy port label</extracomment>
|
||||
<translation>Порт</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GenericChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="144"/>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="150"/>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="149"/>
|
||||
<location filename="../widget/form/genericchatform.cpp" line="155"/>
|
||||
<source>Save chat log</source>
|
||||
<translation>Зберегти чат</translation>
|
||||
</message>
|
||||
|
@ -261,18 +392,17 @@
|
|||
<context>
|
||||
<name>GroupChatForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="45"/>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="47"/>
|
||||
<source>%1 users in chat</source>
|
||||
<comment>Number of users in chat</comment>
|
||||
<translation>Користувачів у чаті: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="84"/>
|
||||
<source><Unknown></source>
|
||||
<translation><Невідомо></translation>
|
||||
<translation type="obsolete"><Невідомо></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="91"/>
|
||||
<location filename="../widget/form/groupchatform.cpp" line="85"/>
|
||||
<source>%1 users in chat</source>
|
||||
<translation>Користувачів у чаті: %1</translation>
|
||||
</message>
|
||||
|
@ -280,52 +410,88 @@
|
|||
<context>
|
||||
<name>GroupWidget</name>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="60"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="102"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="39"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="59"/>
|
||||
<source>%1 users in chat</source>
|
||||
<translation>Користувачів у чаті: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="62"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="104"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="41"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="61"/>
|
||||
<source>0 users in chat</source>
|
||||
<translation>Немає користувачів</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/groupwidget.cpp" line="85"/>
|
||||
<location filename="../widget/groupwidget.cpp" line="48"/>
|
||||
<source>Quit group</source>
|
||||
<comment>Menu to quit a groupchat</comment>
|
||||
<translation>Вийти з групи</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IdentityForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identityform.cpp" line="29"/>
|
||||
<source>Your identity</source>
|
||||
<translation>Ваш ідентифікатор</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IdentityPage</name>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="82"/>
|
||||
<source>Public Information</source>
|
||||
<translation type="obsolete">Публічна інформація</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Name</source>
|
||||
<comment>Username/nick</comment>
|
||||
<translation type="obsolete">Ім'я</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Status</source>
|
||||
<comment>Status message</comment>
|
||||
<translation type="obsolete">Статус</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tox ID</source>
|
||||
<translation type="obsolete">Tox ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your Tox ID</source>
|
||||
<translation type="obsolete">Ваш Tox ID</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IdentitySettings</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="14"/>
|
||||
<source>Form</source>
|
||||
<translation>Form</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="20"/>
|
||||
<source>Public Information</source>
|
||||
<translation>Публічна інформація</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="83"/>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="26"/>
|
||||
<source>Name</source>
|
||||
<comment>Username/nick</comment>
|
||||
<translation>Ім'я</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="85"/>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="36"/>
|
||||
<source>Status</source>
|
||||
<comment>Status message</comment>
|
||||
<translation>Статус</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="95"/>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="49"/>
|
||||
<source>Tox ID</source>
|
||||
<translation>Tox ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="96"/>
|
||||
<source>Your Tox ID</source>
|
||||
<translation>Ваш Tox ID</translation>
|
||||
<location filename="../widget/form/settings/identitysettings.ui" line="55"/>
|
||||
<source>Your Tox ID (click to copy)</source>
|
||||
<translation>Ваш Tox ID (клацніть аби скопіювати)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -336,46 +502,54 @@
|
|||
<translation>qTox</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1909"/>
|
||||
<location filename="../mainwindow.ui" line="859"/>
|
||||
<source>Your name</source>
|
||||
<translation>Ваше ім'я</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="1991"/>
|
||||
<location filename="../mainwindow.ui" line="941"/>
|
||||
<source>Your status</source>
|
||||
<translation>Ваш статус</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="2557"/>
|
||||
<location filename="../mainwindow.ui" line="1089"/>
|
||||
<source>Add friends</source>
|
||||
<translation>Додати друзів</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="2583"/>
|
||||
<location filename="../mainwindow.ui" line="1115"/>
|
||||
<source>Create a group chat</source>
|
||||
<translation>Створити груповий чат</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="2615"/>
|
||||
<location filename="../mainwindow.ui" line="1147"/>
|
||||
<source>View completed file transfers</source>
|
||||
<translation>Переглянути завершені передачі файлів</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="2647"/>
|
||||
<location filename="../mainwindow.ui" line="1179"/>
|
||||
<source>Change your settings</source>
|
||||
<translation>Змінити параметри</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="3229"/>
|
||||
<location filename="../mainwindow.ui" line="1761"/>
|
||||
<source>Close</source>
|
||||
<translation>Закрити</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.ui" line="3232"/>
|
||||
<location filename="../mainwindow.ui" line="1764"/>
|
||||
<source>Ctrl+Q</source>
|
||||
<translation>Ctrl+Q</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PrivacyForm</name>
|
||||
<message>
|
||||
<location filename="../widget/form/settings/privacyform.cpp" line="21"/>
|
||||
<source>Privacy settings</source>
|
||||
<translation>Параметри приватності</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SelfCamView</name>
|
||||
<message>
|
||||
|
@ -388,95 +562,98 @@
|
|||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="197"/>
|
||||
<source>qTox – Settings</source>
|
||||
<translation>qTox - Параметри</translation>
|
||||
<translation type="obsolete">qTox - Параметри</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="224"/>
|
||||
<source>General</source>
|
||||
<translation>Основні</translation>
|
||||
<translation type="obsolete">Основні</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="230"/>
|
||||
<source>Identity</source>
|
||||
<translation>Ідентифікація</translation>
|
||||
<translation type="obsolete">Ідентифікація</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="236"/>
|
||||
<source>Privacy</source>
|
||||
<translation>Приватність</translation>
|
||||
<translation type="obsolete">Приватність</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="242"/>
|
||||
<source>Audio/Video</source>
|
||||
<translation>Аудіо/Відео</translation>
|
||||
<translation type="obsolete">Аудіо/Відео</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="251"/>
|
||||
<source>Ok</source>
|
||||
<translation>Гаразд</translation>
|
||||
<translation type="obsolete">Гаразд</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="252"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Скасувати</translation>
|
||||
<translation type="obsolete">Скасувати</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/settingsdialog.cpp" line="253"/>
|
||||
<source>Apply</source>
|
||||
<translation>Застосувати</translation>
|
||||
<translation type="obsolete">Застосувати</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Widget</name>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="141"/>
|
||||
<location filename="../widget/widget.cpp" line="90"/>
|
||||
<source>Online</source>
|
||||
<comment>Button to set your status to 'Online'</comment>
|
||||
<translation>В мережі</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="143"/>
|
||||
<location filename="../widget/widget.cpp" line="92"/>
|
||||
<source>Away</source>
|
||||
<comment>Button to set your status to 'Away'</comment>
|
||||
<translation>Відійшов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="145"/>
|
||||
<location filename="../widget/widget.cpp" line="94"/>
|
||||
<source>Busy</source>
|
||||
<comment>Button to set your status to 'Busy'</comment>
|
||||
<translation>Зайнятий</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="285"/>
|
||||
<location filename="../widget/widget.cpp" line="228"/>
|
||||
<source>Choose a profile picture</source>
|
||||
<translation>Оберіть зображення для профілю</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="292"/>
|
||||
<location filename="../widget/widget.cpp" line="299"/>
|
||||
<location filename="../widget/widget.cpp" line="320"/>
|
||||
<location filename="../widget/widget.cpp" line="235"/>
|
||||
<location filename="../widget/widget.cpp" line="242"/>
|
||||
<location filename="../widget/widget.cpp" line="263"/>
|
||||
<source>Error</source>
|
||||
<translation>Помилка</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="292"/>
|
||||
<location filename="../widget/widget.cpp" line="235"/>
|
||||
<source>Unable to open this file</source>
|
||||
<translation>Неможливо відкрити цей файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="299"/>
|
||||
<location filename="../widget/widget.cpp" line="242"/>
|
||||
<source>Unable to read this image</source>
|
||||
<translation>Неможливо прочитати це зображення</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="320"/>
|
||||
<location filename="../widget/widget.cpp" line="263"/>
|
||||
<source>This image is too big</source>
|
||||
<translation>Зображення завелике</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="664"/>
|
||||
<location filename="../widget/widget.cpp" line="290"/>
|
||||
<source>Toxcore failed to start, the application will terminate after you close this message.</source>
|
||||
<translation>Помилка запуску ядра tox, програма буде завершена після закриття цього повідомлення.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widget/widget.cpp" line="299"/>
|
||||
<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="../widget/widget.cpp" line="634"/>
|
||||
<source><Unknown></source>
|
||||
<comment>Placeholder when we don't know someone's name in a group chat</comment>
|
||||
<translation><Невідомо></translation>
|
||||
|
|
Loading…
Reference in New Issue
Block a user