1
0
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:
dubslow 2014-10-10 17:21:54 -05:00
commit bf75750166
32 changed files with 1838 additions and 298 deletions

BIN
audio/ToxicIncomingCall.pcm Normal file

Binary file not shown.

View File

@ -20,7 +20,7 @@
# See the COPYING file for more details. # 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 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtox TARGET = qtox
@ -36,6 +36,7 @@ TRANSLATIONS = translations/de.ts \
translations/fr.ts \ translations/fr.ts \
translations/it.ts \ translations/it.ts \
translations/ru.ts \ translations/ru.ts \
translations/pirate.ts \
translations/pl.ts \ translations/pl.ts \
translations/fi.ts \ translations/fi.ts \
translations/mannol.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 LIBS += -Wl,-Bdynamic -ltbb -lv4l1 -lv4l2 -lgnutls -lrtmp -lgnutls -lavformat -lavcodec -lavutil -lavfilter -lswscale -lusb-1.0
} else { } 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) { contains(JENKINS, YES) {
@ -110,7 +111,6 @@ HEADERS += src/widget/form/addfriendform.h \
src/friendlist.h \ src/friendlist.h \
src/misc/cdata.h \ src/misc/cdata.h \
src/misc/cstring.h \ src/misc/cstring.h \
src/widget/selfcamview.h \
src/widget/camera.h \ src/widget/camera.h \
src/widget/netcamview.h \ src/widget/netcamview.h \
src/misc/smileypack.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/filetransferaction.h \
src/widget/tool/chatactions/systemmessageaction.h \ src/widget/tool/chatactions/systemmessageaction.h \
src/widget/tool/chatactions/actionaction.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 += \ SOURCES += \
src/widget/form/addfriendform.cpp \ src/widget/form/addfriendform.cpp \
@ -157,7 +160,6 @@ SOURCES += \
src/misc/settings.cpp \ src/misc/settings.cpp \
src/misc/cdata.cpp \ src/misc/cdata.cpp \
src/misc/cstring.cpp \ src/misc/cstring.cpp \
src/widget/selfcamview.cpp \
src/widget/camera.cpp \ src/widget/camera.cpp \
src/widget/netcamview.cpp \ src/widget/netcamview.cpp \
src/misc/smileypack.cpp \ src/misc/smileypack.cpp \
@ -177,4 +179,6 @@ SOURCES += \
src/widget/tool/chatactions/filetransferaction.cpp \ src/widget/tool/chatactions/filetransferaction.cpp \
src/widget/tool/chatactions/systemmessageaction.cpp \ src/widget/tool/chatactions/systemmessageaction.cpp \
src/widget/tool/chatactions/actionaction.cpp \ src/widget/tool/chatactions/actionaction.cpp \
src/widget/maskablepixmapwidget.cpp src/widget/maskablepixmapwidget.cpp \
src/cameraworker.cpp \
src/widget/videosurface.cpp

View File

@ -123,6 +123,7 @@
<file>ui/fileTransferInstance/emptyRGreenFileButton.png</file> <file>ui/fileTransferInstance/emptyRGreenFileButton.png</file>
<file>ui/fileTransferInstance/emptyRRedFileButton.png</file> <file>ui/fileTransferInstance/emptyRRedFileButton.png</file>
<file>audio/notification.pcm</file> <file>audio/notification.pcm</file>
<file>audio/ToxicIncomingCall.pcm</file>
<file>img/settings/general.png</file> <file>img/settings/general.png</file>
<file>img/settings/identity.png</file> <file>img/settings/identity.png</file>
<file>img/settings/privacy.png</file> <file>img/settings/privacy.png</file>
@ -138,5 +139,6 @@
<file>ui/window/statusPanel.css</file> <file>ui/window/statusPanel.css</file>
<file>ui/settings/mainContent.css</file> <file>ui/settings/mainContent.css</file>
<file>ui/settings/mainHead.css</file> <file>ui/settings/mainHead.css</file>
<file>translations/pirate.qm</file>
</qresource> </qresource>
</RCC> </RCC>

205
src/cameraworker.cpp Normal file
View 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
View 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

View File

@ -57,7 +57,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled
if (calls[callId].videoEnabled) if (calls[callId].videoEnabled)
{ {
calls[callId].sendVideoTimer->start(); 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].videoEnabled = false;
calls[callId].sendVideoTimer->stop(); calls[callId].sendVideoTimer->stop();
Camera::getInstance()->unsuscribe(); Camera::getInstance()->unsubscribe();
emit ((Core*)core)->avMediaChange(friendId, callId, false); emit ((Core*)core)->avMediaChange(friendId, callId, false);
} }
else else
{ {
Camera::getInstance()->suscribe(); Camera::getInstance()->subscribe();
calls[callId].videoEnabled = true; calls[callId].videoEnabled = true;
calls[callId].sendVideoTimer->start(); calls[callId].sendVideoTimer->start();
emit ((Core*)core)->avMediaChange(friendId, callId, true); emit ((Core*)core)->avMediaChange(friendId, callId, true);
@ -161,7 +161,7 @@ void Core::cleanupCall(int callId)
calls[callId].sendAudioTimer->stop(); calls[callId].sendAudioTimer->stop();
calls[callId].sendVideoTimer->stop(); calls[callId].sendVideoTimer->stop();
if (calls[callId].videoEnabled) if (calls[callId].videoEnabled)
Camera::getInstance()->unsuscribe(); Camera::getInstance()->unsubscribe();
alcCaptureStop(alInDev); alcCaptureStop(alInDev);
} }

View File

@ -19,6 +19,7 @@
#include <QApplication> #include <QApplication>
#include <QFontDatabase> #include <QFontDatabase>
#include <QTranslator> #include <QTranslator>
#include <QSystemTrayIcon>
#include <QDebug> #include <QDebug>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -47,7 +48,24 @@ int main(int argc, char *argv[])
QFontDatabase::addApplicationFont("://DejaVuSans.ttf"); QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
Widget* w = Widget::getInstance(); 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(); int errorcode = a.exec();

View File

@ -1,8 +1,8 @@
/* /*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com> Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI. This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details. See the COPYING file for more details.
*/ */
@ -110,6 +110,7 @@ void Settings::load()
enableIPv6 = s.value("enableIPv6", true).toBool(); enableIPv6 = s.value("enableIPv6", true).toBool();
useTranslations = s.value("useTranslations", true).toBool(); useTranslations = s.value("useTranslations", true).toBool();
makeToxPortable = s.value("makeToxPortable", false).toBool(); makeToxPortable = s.value("makeToxPortable", false).toBool();
autostartInTray = s.value("autostartInTray", false).toBool();
forceTCP = s.value("forceTCP", false).toBool(); forceTCP = s.value("forceTCP", false).toBool();
useProxy = s.value("useProxy", false).toBool(); useProxy = s.value("useProxy", false).toBool();
proxyAddr = s.value("proxyAddr", "").toString(); proxyAddr = s.value("proxyAddr", "").toString();
@ -213,6 +214,7 @@ void Settings::save(QString path)
s.setValue("enableIPv6", enableIPv6); s.setValue("enableIPv6", enableIPv6);
s.setValue("useTranslations",useTranslations); s.setValue("useTranslations",useTranslations);
s.setValue("makeToxPortable",makeToxPortable); s.setValue("makeToxPortable",makeToxPortable);
s.setValue("autostartInTray",autostartInTray);
s.setValue("useProxy", useProxy); s.setValue("useProxy", useProxy);
s.setValue("forceTCP", forceTCP); s.setValue("forceTCP", forceTCP);
s.setValue("proxyAddr", proxyAddr); s.setValue("proxyAddr", proxyAddr);
@ -349,6 +351,16 @@ void Settings::setMakeToxPortable(bool newValue)
save(); save();
} }
bool Settings::getAutostartInTray() const
{
return autostartInTray;
}
void Settings::setAutostartInTray(bool newValue)
{
autostartInTray = newValue;
}
bool Settings::getUseTranslations() const bool Settings::getUseTranslations() const
{ {
return useTranslations; return useTranslations;

View File

@ -1,8 +1,8 @@
/* /*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com> Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI. This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details. See the COPYING file for more details.
*/ */
@ -49,12 +49,15 @@ public:
bool getMakeToxPortable() const; bool getMakeToxPortable() const;
void setMakeToxPortable(bool newValue); void setMakeToxPortable(bool newValue);
bool getAutostartInTray() const;
void setAutostartInTray(bool newValue);
bool getUseTranslations() const; bool getUseTranslations() const;
void setUseTranslations(bool newValue); void setUseTranslations(bool newValue);
bool getForceTCP() const; bool getForceTCP() const;
void setForceTCP(bool newValue); void setForceTCP(bool newValue);
QString getProxyAddr() const; QString getProxyAddr() const;
void setProxyAddr(const QString& newValue); void setProxyAddr(const QString& newValue);
@ -165,7 +168,8 @@ private:
bool enableIPv6; bool enableIPv6;
bool useTranslations; bool useTranslations;
static bool makeToxPortable; static bool makeToxPortable;
bool autostartInTray;
bool forceTCP; bool forceTCP;
bool useProxy; bool useProxy;

27
src/videosource.h Normal file
View 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

View File

@ -16,68 +16,72 @@
#include "camera.h" #include "camera.h"
#include "widget.h" #include "widget.h"
#include "src/cameraworker.h"
#include <QDebug>
#include <QThread>
using namespace cv; Camera* Camera::instance = nullptr;
Camera::Camera() 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) if (refcount <= 0)
{ worker->resume();
refcount = 1;
cam.open(0); refcount++;
}
else
refcount++;
} }
void Camera::unsuscribe() void Camera::unsubscribe()
{ {
refcount--; refcount--;
if (refcount <= 0) if (refcount <= 0)
{ {
cam.release(); worker->suspend();
refcount = 0; 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() vpx_image Camera::getLastVPXImage()
{ {
Mat3b frame = getLastFrame(); lock();
vpx_image img; 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 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; size_t i=0, j=0;
for( int line = 0; line < h; ++line ) for( int line = 0; line < h; ++line )
{ {
const cv::Vec3b *srcrow = frame[line]; const cv::Vec3b *srcrow = currFrame[line];
if( !(line % 2) ) if( !(line % 2) )
{ {
for( int x = 0; x < w; x += 2 ) for( int x = 0; x < w; x += 2 )
@ -112,10 +116,123 @@ vpx_image Camera::getLastVPXImage()
} }
} }
} }
unlock();
return img; 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() Camera* Camera::getInstance()
{ {
return Widget::getInstance()->getCamera(); if (!instance)
instance = new Camera();
return instance;
} }

View File

@ -18,8 +18,13 @@
#define CAMERA_H #define CAMERA_H
#include <QImage> #include <QImage>
#include <QList>
#include <QMutex>
#include "vpx/vpx_image.h" #include "vpx/vpx_image.h"
#include "opencv2/opencv.hpp" #include "opencv2/opencv.hpp"
#include "src/videosource.h"
class CameraWorker;
/** /**
* This class is a wrapper to share a camera's captured video frames * 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 * the camera only when needed, and giving access to the last frames
**/ **/
class Camera class Camera : public VideoSource
{ {
Q_OBJECT
public: public:
Camera(); enum Prop {
BRIGHTNESS,
SATURATION,
CONTRAST,
HUE,
};
~Camera();
static Camera* getInstance(); ///< Returns the global widget's Camera instance 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 !) 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: private:
int refcount; ///< Number of users suscribed to the camera 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 #endif // CAMERA_H

View File

@ -18,17 +18,16 @@
#include "src/widget/camera.h" #include "src/widget/camera.h"
#include "ui_avsettings.h" #include "ui_avsettings.h"
AVForm::AVForm(Camera* cam) : AVForm::AVForm() :
GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png")) GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png"))
{ {
bodyUI = new Ui::AVSettings; bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this); bodyUI->setupUi(this);
camView = new SelfCamView(cam, this); //cam->setVideoMode(cam->getBestVideoMode());
bodyUI->videoGroup->layout()->addWidget(camView); camView = new VideoSurface(Camera::getInstance(), this);
camView->hide(); // hide by default
connect(bodyUI->testVideoBtn, &QPushButton::clicked, this, &AVForm::onTestVideoPressed); bodyUI->CamViewLayout->addWidget(camView);
} }
AVForm::~AVForm() AVForm::~AVForm()
@ -36,22 +35,44 @@ AVForm::~AVForm()
delete bodyUI; delete bodyUI;
} }
void AVForm::showTestVideo() void AVForm::present()
{ {
bodyUI->testVideoBtn->setText(tr("Hide video preview","On a button")); bodyUI->videoModescomboBox->clear();
camView->show(); 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")); Camera::getInstance()->setProp(Camera::CONTRAST, position / 100.0);
camView->close();
} }
void AVForm::onTestVideoPressed() void AVForm::on_SaturationSlider_sliderMoved(int position)
{ {
if (camView->isVisible()) Camera::getInstance()->setProp(Camera::SATURATION, position / 100.0);
closeTestVideo(); }
else
showTestVideo(); 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));
} }

View File

@ -18,7 +18,7 @@
#define AVFORM_H #define AVFORM_H
#include "genericsettings.h" #include "genericsettings.h"
#include "src/widget/selfcamview.h" #include "src/widget/videosurface.h"
#include <QGroupBox> #include <QGroupBox>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QPushButton> #include <QPushButton>
@ -33,20 +33,21 @@ class AVForm : public GenericForm
{ {
Q_OBJECT Q_OBJECT
public: public:
AVForm(Camera* cam); AVForm();
~AVForm(); ~AVForm();
virtual void present();
private slots: 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: private:
Ui::AVSettings *bodyUI; Ui::AVSettings *bodyUI;
VideoSurface* camView;
SelfCamView* camView;
void showTestVideo();
void closeTestVideo();
}; };
#endif #endif

View File

@ -6,27 +6,143 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>394</width>
<height>300</height> <height>391</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <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> <item>
<widget class="QGroupBox" name="videoGroup"> <widget class="QGroupBox" name="videoGroup">
<property name="title"> <property name="title">
<string>Video settings</string> <string>Video settings</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QFormLayout" name="formLayout">
<item> <property name="fieldGrowthPolicy">
<widget class="QPushButton" name="testVideoBtn"> <enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Show video preview</string> <string>Modes</string>
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -38,7 +154,7 @@
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>40</height> <height>75</height>
</size> </size>
</property> </property>
</spacer> </spacer>

View File

@ -31,13 +31,14 @@ GeneralForm::GeneralForm() :
bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6());
bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations()); bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations());
bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable());
bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray());
for (auto entry : SmileyPack::listSmileyPacks()) for (auto entry : SmileyPack::listSmileyPacks())
{ {
bodyUI->smileyPackBrowser->addItem(entry.first, entry.second); bodyUI->smileyPackBrowser->addItem(entry.first, entry.second);
} }
bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack())); bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack()));
bodyUI->cbUDPDisabled->setChecked(Settings::getInstance().getForceTCP()); bodyUI->cbUDPDisabled->setChecked(Settings::getInstance().getForceTCP());
bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr()); bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr());
int port = Settings::getInstance().getProxyPort(); int port = Settings::getInstance().getProxyPort();
@ -46,10 +47,11 @@ GeneralForm::GeneralForm() :
bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy()); bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy());
onUseProxyUpdated(); onUseProxyUpdated();
connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated); connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated);
connect(bodyUI->cbUseTranslations, &QCheckBox::stateChanged, this, &GeneralForm::onUseTranslationUpdated); connect(bodyUI->cbUseTranslations, &QCheckBox::stateChanged, this, &GeneralForm::onUseTranslationUpdated);
connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated); 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))); connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int)));
// new syntax can't handle overloaded signals... (at least not in a pretty way) // new syntax can't handle overloaded signals... (at least not in a pretty way)
connect(bodyUI->cbUDPDisabled, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated); connect(bodyUI->cbUDPDisabled, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated);
@ -78,6 +80,11 @@ void GeneralForm::onMakeToxPortableUpdated()
Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked()); Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked());
} }
void GeneralForm::onSetAutostartInTray()
{
Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked());
}
void GeneralForm::onSmileyBrowserIndexChanged(int index) void GeneralForm::onSmileyBrowserIndexChanged(int index)
{ {
QString filename = bodyUI->smileyPackBrowser->itemData(index).toString(); QString filename = bodyUI->smileyPackBrowser->itemData(index).toString();

View File

@ -36,6 +36,7 @@ private slots:
void onEnableIPv6Updated(); void onEnableIPv6Updated();
void onUseTranslationUpdated(); void onUseTranslationUpdated();
void onMakeToxPortableUpdated(); void onMakeToxPortableUpdated();
void onSetAutostartInTray();
void onSmileyBrowserIndexChanged(int index); void onSmileyBrowserIndexChanged(int index);
void onUDPUpdated(); void onUDPUpdated();
void onProxyAddrEdited(); void onProxyAddrEdited();

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>527</width> <width>527</width>
<height>367</height> <height>369</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -46,6 +46,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="startInTray">
<property name="text">
<string>Start in tray</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -27,7 +27,7 @@ public:
GenericForm(const QString &name, const QPixmap &icon) : formName(name), formIcon(icon) {;} GenericForm(const QString &name, const QPixmap &icon) : formName(name), formIcon(icon) {;}
~GenericForm() {;} ~GenericForm() {;}
virtual void updateContent() {;} virtual void present() {}
QString getFormName() {return formName;} QString getFormName() {return formName;}
QPixmap getFormIcon() {return formIcon;} QPixmap getFormIcon() {return formIcon;}

View File

@ -73,7 +73,7 @@ void IdentityForm::onStatusMessageEdited()
Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text()); Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text());
} }
void IdentityForm::updateContent() void IdentityForm::present()
{ {
toxId->setText(Core::getInstance()->getSelfId().toString()); toxId->setText(Core::getInstance()->getSelfId().toString());
} }

View File

@ -50,7 +50,7 @@ public:
void setUserName(const QString &name); void setUserName(const QString &name);
void setStatusMessage(const QString &msg); void setStatusMessage(const QString &msg);
virtual void updateContent(); virtual void present();
signals: signals:
void userNameChanged(QString); void userNameChanged(QString);

View File

@ -25,7 +25,7 @@
#include <QTabBar> #include <QTabBar>
#include <QStackedWidget> #include <QStackedWidget>
SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent) SettingsWidget::SettingsWidget(QWidget* parent)
: QWidget(parent) : QWidget(parent)
{ {
body = new QWidget(this); body = new QWidget(this);
@ -55,7 +55,7 @@ SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent)
GeneralForm *gfrm = new GeneralForm; GeneralForm *gfrm = new GeneralForm;
ifrm = new IdentityForm; ifrm = new IdentityForm;
PrivacyForm *pfrm = new PrivacyForm; PrivacyForm *pfrm = new PrivacyForm;
AVForm *avfrm = new AVForm(cam); AVForm *avfrm = new AVForm;
GenericForm *cfgForms[] = {gfrm, ifrm, pfrm, avfrm}; GenericForm *cfgForms[] = {gfrm, ifrm, pfrm, avfrm};
for (auto cfgForm : cfgForms) for (auto cfgForm : cfgForms)
@ -85,8 +85,8 @@ void SettingsWidget::show(Ui::MainWindow& ui)
void SettingsWidget::onTabChanged(int index) void SettingsWidget::onTabChanged(int index)
{ {
this->settingsWidgets->setCurrentIndex(index); this->settingsWidgets->setCurrentIndex(index);
GenericForm *currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index)); GenericForm* currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index));
currentWidget->updateContent(); currentWidget->present();
nameLabel->setText(currentWidget->getFormName()); nameLabel->setText(currentWidget->getFormName());
imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation)); imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation));
} }

View File

@ -35,7 +35,7 @@ class SettingsWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
SettingsWidget(Camera* cam, QWidget* parent = nullptr); SettingsWidget(QWidget* parent = nullptr);
~SettingsWidget(); ~SettingsWidget();
void show(Ui::MainWindow &ui); void show(Ui::MainWindow &ui);

View File

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

View File

@ -17,39 +17,42 @@
#ifndef SELFCAMVIEW_H #ifndef SELFCAMVIEW_H
#define SELFCAMVIEW_H #define SELFCAMVIEW_H
#include <QWidget> #include <QGLWidget>
class QCloseEvent; class QOpenGLBuffer;
class QShowEvent; class QOpenGLShaderProgram;
class QPainter;
class Camera;
class QLabel;
class QHBoxLayout;
class QTimer; class QTimer;
class VideoSource;
class SelfCamView : public QWidget class VideoSurface : public QGLWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
SelfCamView(Camera* Cam, QWidget *parent=0); VideoSurface(VideoSource* source, QWidget* parent=0);
~VideoSurface();
private slots: virtual void hideEvent(QHideEvent* ev);
void updateDisplay(); virtual void showEvent(QShowEvent* ev);
private:
void closeEvent(QCloseEvent*);
void showEvent(QShowEvent*);
void paint(QPainter *painter);
// QGLWidget interface
protected: protected:
void resizeEvent(QResizeEvent *e); virtual void initializeGL();
virtual void paintGL();
virtual void updateGL();
void update();
private: private:
QLabel *displayLabel; VideoSource* source;
QHBoxLayout* mainLayout; QOpenGLBuffer* pbo;
Camera* cam; QOpenGLShaderProgram* program;
QTimer* updateDisplayTimer; GLuint textureId;
int pboAllocSize;
QSize res;
bool uploadFrame;
bool hasSubscribed;
}; };
#endif // SELFCAMVIEW_H #endif // SELFCAMVIEW_H

View File

@ -27,7 +27,6 @@
#include "groupwidget.h" #include "groupwidget.h"
#include "form/groupchatform.h" #include "form/groupchatform.h"
#include "src/misc/style.h" #include "src/misc/style.h"
#include "selfcamview.h"
#include "friendlistwidget.h" #include "friendlistwidget.h"
#include "camera.h" #include "camera.h"
#include "form/chatform.h" #include "form/chatform.h"
@ -102,8 +101,7 @@ Widget::Widget(QWidget *parent)
ui->statusButton->setProperty("status", "offline"); ui->statusButton->setProperty("status", "offline");
Style::repolish(ui->statusButton); Style::repolish(ui->statusButton);
camera = new Camera; settingsWidget = new SettingsWidget();
settingsWidget = new SettingsWidget(camera);
// Disable some widgets until we're connected to the DHT // Disable some widgets until we're connected to the DHT
ui->statusButton->setEnabled(false); ui->statusButton->setEnabled(false);
@ -119,7 +117,7 @@ Widget::Widget(QWidget *parent)
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection"); qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
coreThread = new QThread(this); coreThread = new QThread(this);
core = new Core(camera, coreThread); core = new Core(Camera::getInstance(), coreThread);
core->moveToThread(coreThread); core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start); 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::groupMessageReceived, this, &Widget::onGroupMessageReceived);
connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged); connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged);
connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); 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(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int)));
connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(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(); return core->getUsername();
} }
Camera* Widget::getCamera()
{
return camera;
}
void Widget::onAvatarClicked() void Widget::onAvatarClicked()
{ {
QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath()); QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath());
@ -343,6 +337,14 @@ void Widget::onTransferClicked()
activeChatroomWidget = nullptr; activeChatroomWidget = nullptr;
} }
void Widget::onIconClick()
{
if(this->isHidden() == true)
this->show();
else
this->hide();
}
void Widget::onSettingsClicked() void Widget::onSettingsClicked()
{ {
hideMainForms(); hideMainForms();
@ -535,6 +537,26 @@ void Widget::newMessageAlert()
alSourcePlay(core->alMainSource); 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) void Widget::onFriendRequestReceived(const QString& userId, const QString& message)
{ {
FriendRequestDialog dialog(this, userId, message); FriendRequestDialog dialog(this, userId, message);

View File

@ -34,7 +34,7 @@ class GenericChatroomWidget;
class Group; class Group;
struct Friend; struct Friend;
class QSplitter; class QSplitter;
class SelfCamView; class VideoSurface;
class QMenu; class QMenu;
class Core; class Core;
class Camera; class Camera;
@ -104,6 +104,8 @@ private slots:
void setStatusBusy(); void setStatusBusy();
void onMessageSendResult(int friendId, const QString& message, int messageId); void onMessageSendResult(int friendId, const QString& message, int messageId);
void onGroupSendResult(int groupId, const QString& message, int result); void onGroupSendResult(int groupId, const QString& message, int result);
void playRingtone();
void onIconClick();
private: private:
void hideMainForms(); void hideMainForms();
@ -122,7 +124,6 @@ private:
static Widget* instance; static Widget* instance;
GenericChatroomWidget* activeChatroomWidget; GenericChatroomWidget* activeChatroomWidget;
FriendListWidget* contactListWidget; FriendListWidget* contactListWidget;
Camera* camera;
MaskablePixmapWidget* profilePicture; MaskablePixmapWidget* profilePicture;
bool notify(QObject *receiver, QEvent *event); bool notify(QObject *receiver, QEvent *event);
}; };

BIN
translations/pirate.qm Normal file

Binary file not shown.

545
translations/pirate.ts Normal file
View 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&apos;/Crystals o&apos; Far-Seein&apos; 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&apos; Far-Seein&apos; 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&apos; Far-Seein&apos; 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&apos; Far-Seein&apos; settings</translation>
</message>
<message>
<location filename="../widget/form/settings/avsettings.ui" line="26"/>
<source>Show video preview</source>
<translation type="unfinished">See crystals o&apos; Far-Seein&apos; 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&apos;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&apos;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&apos;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&apos; ain&apos;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&apos;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&apos;t give permission bury that treasure here! Find another location or abandon hope of keepin&apos; it.</translation>
</message>
</context>
<context>
<name>FilesForm</name>
<message>
<location filename="../widget/form/filesform.cpp" line="30"/>
<source>Transfered Files</source>
<comment>&quot;Headline&quot; 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&apos; 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&apos; 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&apos;</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&apos; box</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="74"/>
<source>Connection Settings</source>
<translation type="unfinished">Sailin&apos; 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&apos;</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&apos;</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&apos;</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&apos; Far-Seein&apos; 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 &apos;Online&apos;</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="90"/>
<source>Away</source>
<comment>Button to set your status to &apos;Away&apos;</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="92"/>
<source>Busy</source>
<comment>Button to set your status to &apos;Busy&apos;</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>&lt;Unknown&gt;</source>
<comment>Placeholder when we don&apos;t know someone&apos;s name in a group chat</comment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

Binary file not shown.

View File

@ -1,26 +1,60 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!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> <context>
<name>AVPage</name> <name>AVPage</name>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="132"/>
<source>Video Settings</source> <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> <translation>Параметри відео</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="136"/> <location filename="../widget/form/settings/avsettings.ui" line="26"/>
<location filename="../widget/settingsdialog.cpp" line="163"/>
<source>Show video preview</source> <source>Show video preview</source>
<comment>On a button</comment>
<translation>Показати вікно попереднього перегляду</translation> <translation>Показати вікно попереднього перегляду</translation>
</message> </message>
<message>
<location filename="../widget/settingsdialog.cpp" line="157"/>
<source>Hide video preview</source>
<comment>On a button</comment>
<translation>Приховати вікно попереднього перегляду</translation>
</message>
</context> </context>
<context> <context>
<name>AddFriendForm</name> <name>AddFriendForm</name>
@ -105,27 +139,42 @@
<context> <context>
<name>ChatForm</name> <name>ChatForm</name>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="88"/> <location filename="../widget/form/chatform.cpp" line="105"/>
<source>Send a file</source> <source>Send a file</source>
<translation>Надіслати файл</translation> <translation>Надіслати файл</translation>
</message> </message>
</context> </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&apos;t open it
Do you want to erase this profile ?</source>
<translation>Схоже, що ваш tox-профіль зашифровано, qTox не може його відкрити.
Бажаєте стерти цей профіль?</translation>
</message>
</context>
<context> <context>
<name>FileTransferInstance</name> <name>FileTransferInstance</name>
<message> <message>
<location filename="../filetransferinstance.cpp" line="205"/> <location filename="../filetransferinstance.cpp" line="208"/>
<source>Save a file</source> <source>Save a file</source>
<comment>Title of the file saving dialog</comment> <comment>Title of the file saving dialog</comment>
<translation>Зберегти файл</translation> <translation>Зберегти файл</translation>
</message> </message>
<message> <message>
<location filename="../filetransferinstance.cpp" line="216"/> <location filename="../filetransferinstance.cpp" line="219"/>
<source>Location not writable</source> <source>Location not writable</source>
<comment>Title of permissions popup</comment> <comment>Title of permissions popup</comment>
<translation>Немає прав на запис</translation> <translation>Немає прав на запис</translation>
</message> </message>
<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> <source>You do not have permission to write that location. Choose another, or cancel the save dialog.</source>
<comment>text of permissions popup</comment> <comment>text of permissions popup</comment>
<translation>Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу.</translation> <translation>Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу.</translation>
@ -189,71 +238,153 @@
<context> <context>
<name>FriendWidget</name> <name>FriendWidget</name>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="90"/> <location filename="../widget/friendwidget.cpp" line="48"/>
<source>Copy friend ID</source> <source>Copy friend ID</source>
<comment>Menu to copy the Tox ID of that friend</comment> <comment>Menu to copy the Tox ID of that friend</comment>
<translation>Копіювати дружній ID</translation> <translation>Копіювати дружній ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="91"/> <location filename="../widget/friendwidget.cpp" line="49"/>
<source>Invite in group</source> <source>Invite in group</source>
<comment>Menu to invite a friend in a groupchat</comment> <comment>Menu to invite a friend in a groupchat</comment>
<translation>Запросити до групи</translation> <translation>Запросити до групи</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="101"/> <location filename="../widget/friendwidget.cpp" line="59"/>
<source>Remove friend</source> <source>Remove friend</source>
<comment>Menu to remove the friend from our friendlist</comment> <comment>Menu to remove the friend from our friendlist</comment>
<translation>Вилучити з друзів</translation> <translation>Вилучити з друзів</translation>
</message> </message>
</context> </context>
<context>
<name>GeneralForm</name>
<message>
<location filename="../widget/form/settings/generalform.cpp" line="26"/>
<source>General Settings</source>
<translation>Основні параметри</translation>
</message>
</context>
<context> <context>
<name>GeneralPage</name> <name>GeneralPage</name>
<message> <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> <source>General Settings</source>
<translation>Основні параметри</translation> <translation>Основні параметри</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="34"/> <location filename="../widget/form/settings/generalsettings.ui" line="35"/>
<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"/>
<source>Use translations</source> <source>Use translations</source>
<comment>Text on a checkbox to enable translations</comment> <extracomment>Text on a checkbox to enable translations</extracomment>
<translation>Використовувати мову системи</translation> <translation>Використовувати мову системи</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="38"/> <location filename="../widget/form/settings/generalsettings.ui" line="42"/>
<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"/>
<source>Save settings to the working directory instead of the usual conf dir</source> <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> <translation>Зберігати налаштування в робочий теці</translation>
</message> </message>
<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> <source>Theme</source>
<translation>Графічна тема</translation> <translation>Графічна тема</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="49"/> <location filename="../widget/form/settings/generalsettings.ui" line="61"/>
<source>Smiley Pack</source> <source>Smiley Pack</source>
<extracomment>Text on smiley pack label</extracomment>
<translation>Графічний пакунок емоційних картинок</translation> <translation>Графічний пакунок емоційних картинок</translation>
</message> </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>
<context> <context>
<name>GenericChatForm</name> <name>GenericChatForm</name>
<message> <message>
<location filename="../widget/form/genericchatform.cpp" line="144"/> <location filename="../widget/form/genericchatform.cpp" line="149"/>
<location filename="../widget/form/genericchatform.cpp" line="150"/> <location filename="../widget/form/genericchatform.cpp" line="155"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Зберегти чат</translation> <translation>Зберегти чат</translation>
</message> </message>
@ -261,18 +392,17 @@
<context> <context>
<name>GroupChatForm</name> <name>GroupChatForm</name>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="45"/> <location filename="../widget/form/groupchatform.cpp" line="47"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<comment>Number of users in chat</comment> <comment>Number of users in chat</comment>
<translation>Користувачів у чаті: %1</translation> <translation>Користувачів у чаті: %1</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="84"/>
<source>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<translation>&lt;Невідомо&gt;</translation> <translation type="obsolete">&lt;Невідомо&gt;</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="91"/> <location filename="../widget/form/groupchatform.cpp" line="85"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>Користувачів у чаті: %1</translation> <translation>Користувачів у чаті: %1</translation>
</message> </message>
@ -280,52 +410,88 @@
<context> <context>
<name>GroupWidget</name> <name>GroupWidget</name>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="60"/> <location filename="../widget/groupwidget.cpp" line="39"/>
<location filename="../widget/groupwidget.cpp" line="102"/> <location filename="../widget/groupwidget.cpp" line="59"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>Користувачів у чаті: %1</translation> <translation>Користувачів у чаті: %1</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="62"/> <location filename="../widget/groupwidget.cpp" line="41"/>
<location filename="../widget/groupwidget.cpp" line="104"/> <location filename="../widget/groupwidget.cpp" line="61"/>
<source>0 users in chat</source> <source>0 users in chat</source>
<translation>Немає користувачів</translation> <translation>Немає користувачів</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="85"/> <location filename="../widget/groupwidget.cpp" line="48"/>
<source>Quit group</source> <source>Quit group</source>
<comment>Menu to quit a groupchat</comment> <comment>Menu to quit a groupchat</comment>
<translation>Вийти з групи</translation> <translation>Вийти з групи</translation>
</message> </message>
</context> </context>
<context>
<name>IdentityForm</name>
<message>
<location filename="../widget/form/settings/identityform.cpp" line="29"/>
<source>Your identity</source>
<translation>Ваш ідентифікатор</translation>
</message>
</context>
<context> <context>
<name>IdentityPage</name> <name>IdentityPage</name>
<message> <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">Ім&apos;я</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> <source>Public Information</source>
<translation>Публічна інформація</translation> <translation>Публічна інформація</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="83"/> <location filename="../widget/form/settings/identitysettings.ui" line="26"/>
<source>Name</source> <source>Name</source>
<comment>Username/nick</comment>
<translation>Ім&apos;я</translation> <translation>Ім&apos;я</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="85"/> <location filename="../widget/form/settings/identitysettings.ui" line="36"/>
<source>Status</source> <source>Status</source>
<comment>Status message</comment>
<translation>Статус</translation> <translation>Статус</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="95"/> <location filename="../widget/form/settings/identitysettings.ui" line="49"/>
<source>Tox ID</source> <source>Tox ID</source>
<translation>Tox ID</translation> <translation>Tox ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="96"/> <location filename="../widget/form/settings/identitysettings.ui" line="55"/>
<source>Your Tox ID</source> <source>Your Tox ID (click to copy)</source>
<translation>Ваш Tox ID</translation> <translation>Ваш Tox ID (клацніть аби скопіювати)</translation>
</message> </message>
</context> </context>
<context> <context>
@ -336,46 +502,54 @@
<translation>qTox</translation> <translation>qTox</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="1909"/> <location filename="../mainwindow.ui" line="859"/>
<source>Your name</source> <source>Your name</source>
<translation>Ваше ім&apos;я</translation> <translation>Ваше ім&apos;я</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="1991"/> <location filename="../mainwindow.ui" line="941"/>
<source>Your status</source> <source>Your status</source>
<translation>Ваш статус</translation> <translation>Ваш статус</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="2557"/> <location filename="../mainwindow.ui" line="1089"/>
<source>Add friends</source> <source>Add friends</source>
<translation>Додати друзів</translation> <translation>Додати друзів</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="2583"/> <location filename="../mainwindow.ui" line="1115"/>
<source>Create a group chat</source> <source>Create a group chat</source>
<translation>Створити груповий чат</translation> <translation>Створити груповий чат</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="2615"/> <location filename="../mainwindow.ui" line="1147"/>
<source>View completed file transfers</source> <source>View completed file transfers</source>
<translation>Переглянути завершені передачі файлів</translation> <translation>Переглянути завершені передачі файлів</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="2647"/> <location filename="../mainwindow.ui" line="1179"/>
<source>Change your settings</source> <source>Change your settings</source>
<translation>Змінити параметри</translation> <translation>Змінити параметри</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="3229"/> <location filename="../mainwindow.ui" line="1761"/>
<source>Close</source> <source>Close</source>
<translation>Закрити</translation> <translation>Закрити</translation>
</message> </message>
<message> <message>
<location filename="../mainwindow.ui" line="3232"/> <location filename="../mainwindow.ui" line="1764"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translation>Ctrl+Q</translation> <translation>Ctrl+Q</translation>
</message> </message>
</context> </context>
<context>
<name>PrivacyForm</name>
<message>
<location filename="../widget/form/settings/privacyform.cpp" line="21"/>
<source>Privacy settings</source>
<translation>Параметри приватності</translation>
</message>
</context>
<context> <context>
<name>SelfCamView</name> <name>SelfCamView</name>
<message> <message>
@ -388,95 +562,98 @@
<context> <context>
<name>SettingsDialog</name> <name>SettingsDialog</name>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="197"/>
<source>qTox Settings</source> <source>qTox Settings</source>
<translation>qTox - Параметри</translation> <translation type="obsolete">qTox - Параметри</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="224"/>
<source>General</source> <source>General</source>
<translation>Основні</translation> <translation type="obsolete">Основні</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="230"/>
<source>Identity</source> <source>Identity</source>
<translation>Ідентифікація</translation> <translation type="obsolete">Ідентифікація</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="236"/>
<source>Privacy</source> <source>Privacy</source>
<translation>Приватність</translation> <translation type="obsolete">Приватність</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="242"/>
<source>Audio/Video</source> <source>Audio/Video</source>
<translation>Аудіо/Відео</translation> <translation type="obsolete">Аудіо/Відео</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="251"/>
<source>Ok</source> <source>Ok</source>
<translation>Гаразд</translation> <translation type="obsolete">Гаразд</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="252"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Скасувати</translation> <translation type="obsolete">Скасувати</translation>
</message> </message>
<message> <message>
<location filename="../widget/settingsdialog.cpp" line="253"/>
<source>Apply</source> <source>Apply</source>
<translation>Застосувати</translation> <translation type="obsolete">Застосувати</translation>
</message> </message>
</context> </context>
<context> <context>
<name>Widget</name> <name>Widget</name>
<message> <message>
<location filename="../widget/widget.cpp" line="141"/> <location filename="../widget/widget.cpp" line="90"/>
<source>Online</source> <source>Online</source>
<comment>Button to set your status to &apos;Online&apos;</comment> <comment>Button to set your status to &apos;Online&apos;</comment>
<translation>В мережі</translation> <translation>В мережі</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="143"/> <location filename="../widget/widget.cpp" line="92"/>
<source>Away</source> <source>Away</source>
<comment>Button to set your status to &apos;Away&apos;</comment> <comment>Button to set your status to &apos;Away&apos;</comment>
<translation>Відійшов</translation> <translation>Відійшов</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="145"/> <location filename="../widget/widget.cpp" line="94"/>
<source>Busy</source> <source>Busy</source>
<comment>Button to set your status to &apos;Busy&apos;</comment> <comment>Button to set your status to &apos;Busy&apos;</comment>
<translation>Зайнятий</translation> <translation>Зайнятий</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="285"/> <location filename="../widget/widget.cpp" line="228"/>
<source>Choose a profile picture</source> <source>Choose a profile picture</source>
<translation>Оберіть зображення для профілю</translation> <translation>Оберіть зображення для профілю</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="292"/> <location filename="../widget/widget.cpp" line="235"/>
<location filename="../widget/widget.cpp" line="299"/> <location filename="../widget/widget.cpp" line="242"/>
<location filename="../widget/widget.cpp" line="320"/> <location filename="../widget/widget.cpp" line="263"/>
<source>Error</source> <source>Error</source>
<translation>Помилка</translation> <translation>Помилка</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="292"/> <location filename="../widget/widget.cpp" line="235"/>
<source>Unable to open this file</source> <source>Unable to open this file</source>
<translation>Неможливо відкрити цей файл</translation> <translation>Неможливо відкрити цей файл</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="299"/> <location filename="../widget/widget.cpp" line="242"/>
<source>Unable to read this image</source> <source>Unable to read this image</source>
<translation>Неможливо прочитати це зображення</translation> <translation>Неможливо прочитати це зображення</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="320"/> <location filename="../widget/widget.cpp" line="263"/>
<source>This image is too big</source> <source>This image is too big</source>
<translation>Зображення завелике</translation> <translation>Зображення завелике</translation>
</message> </message>
<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>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<comment>Placeholder when we don&apos;t know someone&apos;s name in a group chat</comment> <comment>Placeholder when we don&apos;t know someone&apos;s name in a group chat</comment>
<translation>&lt;Невідомо&gt;</translation> <translation>&lt;Невідомо&gt;</translation>