diff --git a/audio/ToxicIncomingCall.pcm b/audio/ToxicIncomingCall.pcm new file mode 100644 index 000000000..8239c74cf Binary files /dev/null and b/audio/ToxicIncomingCall.pcm differ diff --git a/qtox.pro b/qtox.pro index 63b952c15..e85d7e6ce 100644 --- a/qtox.pro +++ b/qtox.pro @@ -20,7 +20,7 @@ # See the COPYING file for more details. -QT += core gui network xml +QT += core gui network xml opengl greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = qtox @@ -36,6 +36,7 @@ TRANSLATIONS = translations/de.ts \ translations/fr.ts \ translations/it.ts \ translations/ru.ts \ + translations/pirate.ts \ translations/pl.ts \ translations/fi.ts \ translations/mannol.ts \ @@ -67,7 +68,7 @@ win32 { LIBS += -Wl,-Bdynamic -ltbb -lv4l1 -lv4l2 -lgnutls -lrtmp -lgnutls -lavformat -lavcodec -lavutil -lavfilter -lswscale -lusb-1.0 } else { - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc } contains(JENKINS, YES) { @@ -110,7 +111,6 @@ HEADERS += src/widget/form/addfriendform.h \ src/friendlist.h \ src/misc/cdata.h \ src/misc/cstring.h \ - src/widget/selfcamview.h \ src/widget/camera.h \ src/widget/netcamview.h \ src/misc/smileypack.h \ @@ -131,7 +131,10 @@ HEADERS += src/widget/form/addfriendform.h \ src/widget/tool/chatactions/filetransferaction.h \ src/widget/tool/chatactions/systemmessageaction.h \ src/widget/tool/chatactions/actionaction.h \ - src/widget/maskablepixmapwidget.h + src/widget/maskablepixmapwidget.h \ + src/videosource.h \ + src/cameraworker.h \ + src/widget/videosurface.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -157,7 +160,6 @@ SOURCES += \ src/misc/settings.cpp \ src/misc/cdata.cpp \ src/misc/cstring.cpp \ - src/widget/selfcamview.cpp \ src/widget/camera.cpp \ src/widget/netcamview.cpp \ src/misc/smileypack.cpp \ @@ -177,4 +179,6 @@ SOURCES += \ src/widget/tool/chatactions/filetransferaction.cpp \ src/widget/tool/chatactions/systemmessageaction.cpp \ src/widget/tool/chatactions/actionaction.cpp \ - src/widget/maskablepixmapwidget.cpp + src/widget/maskablepixmapwidget.cpp \ + src/cameraworker.cpp \ + src/widget/videosurface.cpp diff --git a/res.qrc b/res.qrc index 4ef12f901..375312b1d 100644 --- a/res.qrc +++ b/res.qrc @@ -123,6 +123,7 @@ ui/fileTransferInstance/emptyRGreenFileButton.png ui/fileTransferInstance/emptyRRedFileButton.png audio/notification.pcm + audio/ToxicIncomingCall.pcm img/settings/general.png img/settings/identity.png img/settings/privacy.png @@ -138,5 +139,6 @@ ui/window/statusPanel.css ui/settings/mainContent.css ui/settings/mainHead.css + translations/pirate.qm diff --git a/src/cameraworker.cpp b/src/cameraworker.cpp new file mode 100644 index 000000000..b0c964bc5 --- /dev/null +++ b/src/cameraworker.cpp @@ -0,0 +1,205 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include + +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 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; +} diff --git a/src/cameraworker.h b/src/cameraworker.h new file mode 100644 index 000000000..47fb10f87 --- /dev/null +++ b/src/cameraworker.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include +#include +#include +#include + +#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 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 queue; + QTimer* clock; + cv::VideoCapture cam; + cv::Mat3b frame; + int camIndex; + QMap props; + QList resolutions; + int refCount; +}; + +#endif // CAMERAWORKER_H diff --git a/src/coreav.cpp b/src/coreav.cpp index fa4ce5cd8..b7aae0103 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -57,7 +57,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled if (calls[callId].videoEnabled) { calls[callId].sendVideoTimer->start(); - Camera::getInstance()->suscribe(); + Camera::getInstance()->subscribe(); } } @@ -73,12 +73,12 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) { calls[callId].videoEnabled = false; calls[callId].sendVideoTimer->stop(); - Camera::getInstance()->unsuscribe(); + Camera::getInstance()->unsubscribe(); emit ((Core*)core)->avMediaChange(friendId, callId, false); } else { - Camera::getInstance()->suscribe(); + Camera::getInstance()->subscribe(); calls[callId].videoEnabled = true; calls[callId].sendVideoTimer->start(); emit ((Core*)core)->avMediaChange(friendId, callId, true); @@ -161,7 +161,7 @@ void Core::cleanupCall(int callId) calls[callId].sendAudioTimer->stop(); calls[callId].sendVideoTimer->stop(); if (calls[callId].videoEnabled) - Camera::getInstance()->unsuscribe(); + Camera::getInstance()->unsubscribe(); alcCaptureStop(alInDev); } diff --git a/src/main.cpp b/src/main.cpp index e21c1c647..1d447dbf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include int main(int argc, char *argv[]) @@ -47,7 +48,24 @@ int main(int argc, char *argv[]) QFontDatabase::addApplicationFont("://DejaVuSans.ttf"); Widget* w = Widget::getInstance(); - w->show(); + if (QSystemTrayIcon::isSystemTrayAvailable() == false) + { + qWarning() << "No system tray detected!"; + w->show(); + } + else + { + QSystemTrayIcon *icon = new QSystemTrayIcon(w); + QObject::connect(icon, + SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + w, + SLOT(onIconClick())); + icon->setIcon(w->windowIcon()); + icon->show(); + if(Settings::getInstance().getAutostartInTray() == false) + w->show(); + } + int errorcode = a.exec(); diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 545684272..6b1ca0ed0 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -1,8 +1,8 @@ /* Copyright (C) 2013 by Maxim Biro - + This file is part of Tox Qt GUI. - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -10,7 +10,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - + See the COPYING file for more details. */ @@ -110,6 +110,7 @@ void Settings::load() enableIPv6 = s.value("enableIPv6", true).toBool(); useTranslations = s.value("useTranslations", true).toBool(); makeToxPortable = s.value("makeToxPortable", false).toBool(); + autostartInTray = s.value("autostartInTray", false).toBool(); forceTCP = s.value("forceTCP", false).toBool(); useProxy = s.value("useProxy", false).toBool(); proxyAddr = s.value("proxyAddr", "").toString(); @@ -213,6 +214,7 @@ void Settings::save(QString path) s.setValue("enableIPv6", enableIPv6); s.setValue("useTranslations",useTranslations); s.setValue("makeToxPortable",makeToxPortable); + s.setValue("autostartInTray",autostartInTray); s.setValue("useProxy", useProxy); s.setValue("forceTCP", forceTCP); s.setValue("proxyAddr", proxyAddr); @@ -349,6 +351,16 @@ void Settings::setMakeToxPortable(bool newValue) save(); } +bool Settings::getAutostartInTray() const +{ + return autostartInTray; +} + +void Settings::setAutostartInTray(bool newValue) +{ + autostartInTray = newValue; +} + bool Settings::getUseTranslations() const { return useTranslations; diff --git a/src/misc/settings.h b/src/misc/settings.h index 7c43987d6..9278fff8b 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -1,8 +1,8 @@ /* Copyright (C) 2013 by Maxim Biro - + This file is part of Tox Qt GUI. - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -10,7 +10,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - + See the COPYING file for more details. */ @@ -49,12 +49,15 @@ public: bool getMakeToxPortable() const; void setMakeToxPortable(bool newValue); + bool getAutostartInTray() const; + void setAutostartInTray(bool newValue); + bool getUseTranslations() const; void setUseTranslations(bool newValue); bool getForceTCP() const; void setForceTCP(bool newValue); - + QString getProxyAddr() const; void setProxyAddr(const QString& newValue); @@ -165,7 +168,8 @@ private: bool enableIPv6; bool useTranslations; static bool makeToxPortable; - + bool autostartInTray; + bool forceTCP; bool useProxy; diff --git a/src/videosource.h b/src/videosource.h new file mode 100644 index 000000000..f6c11592c --- /dev/null +++ b/src/videosource.h @@ -0,0 +1,27 @@ +#ifndef VIDEOSOURCE_H +#define VIDEOSOURCE_H + +#include +#include + +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 diff --git a/src/widget/camera.cpp b/src/widget/camera.cpp index dab371005..5bf07e6df 100644 --- a/src/widget/camera.cpp +++ b/src/widget/camera.cpp @@ -16,68 +16,72 @@ #include "camera.h" #include "widget.h" +#include "src/cameraworker.h" +#include +#include -using namespace cv; +Camera* Camera::instance = nullptr; Camera::Camera() - : refcount{0} + : refcount(0) + , workerThread(nullptr) + , worker(nullptr) { + worker = new CameraWorker(0); + workerThread = new QThread(); + + worker->moveToThread(workerThread); + + connect(workerThread, &QThread::started, worker, &CameraWorker::onStart); + connect(workerThread, &QThread::finished, worker, &CameraWorker::deleteLater); + connect(workerThread, &QThread::deleteLater, worker, &CameraWorker::deleteLater); + connect(worker, &CameraWorker::started, this, &Camera::onWorkerStarted); + connect(worker, &CameraWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable); + connect(worker, &CameraWorker::resProbingFinished, this, &Camera::onResProbingFinished); + workerThread->start(); } -void Camera::suscribe() +void Camera::onWorkerStarted() +{ + worker->probeResolutions(); +} + +Camera::~Camera() +{ + workerThread->exit(); + workerThread->deleteLater(); +} + +void Camera::subscribe() { if (refcount <= 0) - { - refcount = 1; - cam.open(0); - } - else - refcount++; + worker->resume(); + + refcount++; } -void Camera::unsuscribe() +void Camera::unsubscribe() { refcount--; if (refcount <= 0) { - cam.release(); + worker->suspend(); refcount = 0; } } -Mat Camera::getLastFrame() -{ - Mat frame; - cam >> frame; - return frame; -} - -QImage Camera::getLastImage() -{ - Mat3b src = getLastFrame(); - QImage dest(src.cols, src.rows, QImage::Format_ARGB32); - for (int y = 0; y < src.rows; ++y) - { - const cv::Vec3b *srcrow = src[y]; - QRgb *destrow = (QRgb*)dest.scanLine(y); - for (int x = 0; x < src.cols; ++x) - destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255); - } - return dest; -} - vpx_image Camera::getLastVPXImage() { - Mat3b frame = getLastFrame(); + lock(); vpx_image img; - int w = frame.size().width, h = frame.size().height; + int w = currFrame.size().width, h = currFrame.size().height; vpx_img_alloc(&img, VPX_IMG_FMT_I420, w, h, 1); // I420 == YUV420P, same as YV12 with U and V switched size_t i=0, j=0; for( int line = 0; line < h; ++line ) { - const cv::Vec3b *srcrow = frame[line]; + const cv::Vec3b *srcrow = currFrame[line]; if( !(line % 2) ) { for( int x = 0; x < w; x += 2 ) @@ -112,10 +116,123 @@ vpx_image Camera::getLastVPXImage() } } } + unlock(); return img; } +QList 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 res) +{ + resolutions = res; +} + +void *Camera::getData() +{ + return currFrame.data; +} + +int Camera::getDataSize() +{ + return currFrame.total() * currFrame.channels(); +} + +void Camera::lock() +{ + mutex.lock(); + + if (worker->hasFrame()) + currFrame = worker->dequeueFrame(); +} + +void Camera::unlock() +{ + mutex.unlock(); +} + +QSize Camera::resolution() +{ + return QSize(currFrame.cols, currFrame.rows); +} + Camera* Camera::getInstance() { - return Widget::getInstance()->getCamera(); + if (!instance) + instance = new Camera(); + + return instance; } diff --git a/src/widget/camera.h b/src/widget/camera.h index e48307d41..b63740191 100644 --- a/src/widget/camera.h +++ b/src/widget/camera.h @@ -18,8 +18,13 @@ #define CAMERA_H #include +#include +#include #include "vpx/vpx_image.h" #include "opencv2/opencv.hpp" +#include "src/videosource.h" + +class CameraWorker; /** * This class is a wrapper to share a camera's captured video frames @@ -27,20 +32,60 @@ * the camera only when needed, and giving access to the last frames **/ -class Camera +class Camera : public VideoSource { + Q_OBJECT public: - Camera(); + enum Prop { + BRIGHTNESS, + SATURATION, + CONTRAST, + HUE, + }; + + ~Camera(); + static Camera* getInstance(); ///< Returns the global widget's Camera instance - void suscribe(); ///< Call this once before trying to get frames - void unsuscribe(); ///< Call this once when you don't need frames anymore - cv::Mat getLastFrame(); ///< Get the last captured frame - QImage getLastImage(); ///< Convert the last frame to a QImage (can be expensive !) vpx_image getLastVPXImage(); ///< Convert the last frame to a vpx_image (can be expensive !) + QList getSupportedResolutions(); + QSize getBestVideoMode(); + + void setResolution(QSize res); + QSize getResolution(); + + void setProp(Prop prop, double val); + double getProp(Prop prop); + + // VideoSource interface + virtual void *getData(); + virtual int getDataSize(); + virtual void lock(); + virtual void unlock(); + virtual QSize resolution(); + virtual void subscribe(); + virtual void unsubscribe(); + +protected: + Camera(); + private: int refcount; ///< Number of users suscribed to the camera - cv::VideoCapture cam; ///< OpenCV camera capture opbject + cv::Mat3b currFrame; + QMutex mutex; + + QThread* workerThread; + CameraWorker* worker; + + QList resolutions; + + static Camera* instance; + +private slots: + void onWorkerStarted(); + void onNewFrameAvailable(); + void onResProbingFinished(QList res); + }; #endif // CAMERA_H diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 826406dd3..082097b5c 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -18,17 +18,16 @@ #include "src/widget/camera.h" #include "ui_avsettings.h" -AVForm::AVForm(Camera* cam) : +AVForm::AVForm() : GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png")) { bodyUI = new Ui::AVSettings; bodyUI->setupUi(this); - camView = new SelfCamView(cam, this); - bodyUI->videoGroup->layout()->addWidget(camView); - camView->hide(); // hide by default + //cam->setVideoMode(cam->getBestVideoMode()); + camView = new VideoSurface(Camera::getInstance(), this); - connect(bodyUI->testVideoBtn, &QPushButton::clicked, this, &AVForm::onTestVideoPressed); + bodyUI->CamViewLayout->addWidget(camView); } AVForm::~AVForm() @@ -36,22 +35,44 @@ AVForm::~AVForm() delete bodyUI; } -void AVForm::showTestVideo() +void AVForm::present() { - bodyUI->testVideoBtn->setText(tr("Hide video preview","On a button")); - camView->show(); + bodyUI->videoModescomboBox->clear(); + QList res = Camera::getInstance()->getSupportedResolutions(); + for (QSize r : res) + bodyUI->videoModescomboBox->addItem(QString("%1x%2").arg(QString::number(r.width()),QString::number(r.height()))); + + bodyUI->ContrastSlider->setValue(Camera::getInstance()->getProp(Camera::CONTRAST)*100); + bodyUI->BrightnessSlider->setValue(Camera::getInstance()->getProp(Camera::BRIGHTNESS)*100); + bodyUI->SaturationSlider->setValue(Camera::getInstance()->getProp(Camera::SATURATION)*100); + bodyUI->HueSlider->setValue(Camera::getInstance()->getProp(Camera::HUE)*100); } -void AVForm::closeTestVideo() +void AVForm::on_ContrastSlider_sliderMoved(int position) { - bodyUI->testVideoBtn->setText(tr("Show video preview","On a button")); - camView->close(); + Camera::getInstance()->setProp(Camera::CONTRAST, position / 100.0); } -void AVForm::onTestVideoPressed() +void AVForm::on_SaturationSlider_sliderMoved(int position) { - if (camView->isVisible()) - closeTestVideo(); - else - showTestVideo(); + Camera::getInstance()->setProp(Camera::SATURATION, position / 100.0); +} + +void AVForm::on_BrightnessSlider_sliderMoved(int position) +{ + Camera::getInstance()->setProp(Camera::BRIGHTNESS, position / 100.0); +} + +void AVForm::on_HueSlider_sliderMoved(int position) +{ + Camera::getInstance()->setProp(Camera::HUE, position / 100.0); +} + +void AVForm::on_videoModescomboBox_currentIndexChanged(const QString &arg1) +{ + QStringList resStr = arg1.split("x"); + int w = resStr[0].toInt(); + int h = resStr[0].toInt(); + + Camera::getInstance()->setResolution(QSize(w,h)); } diff --git a/src/widget/form/settings/avform.h b/src/widget/form/settings/avform.h index 9e53fafd4..0bcc953b7 100644 --- a/src/widget/form/settings/avform.h +++ b/src/widget/form/settings/avform.h @@ -18,7 +18,7 @@ #define AVFORM_H #include "genericsettings.h" -#include "src/widget/selfcamview.h" +#include "src/widget/videosurface.h" #include #include #include @@ -33,20 +33,21 @@ class AVForm : public GenericForm { Q_OBJECT public: - AVForm(Camera* cam); + AVForm(); ~AVForm(); + virtual void present(); private slots: - void onTestVideoPressed(); + + void on_ContrastSlider_sliderMoved(int position); + void on_SaturationSlider_sliderMoved(int position); + void on_BrightnessSlider_sliderMoved(int position); + void on_HueSlider_sliderMoved(int position); + void on_videoModescomboBox_currentIndexChanged(const QString &arg1); private: Ui::AVSettings *bodyUI; - - SelfCamView* camView; - - void showTestVideo(); - void closeTestVideo(); - + VideoSurface* camView; }; #endif diff --git a/src/widget/form/settings/avsettings.ui b/src/widget/form/settings/avsettings.ui index 77fbb18b4..20ebbf43e 100644 --- a/src/widget/form/settings/avsettings.ui +++ b/src/widget/form/settings/avsettings.ui @@ -6,27 +6,143 @@ 0 0 - 400 - 300 + 394 + 391 Form + + + + Volume Settings (Stubs) + + + + + + Playback + + + + + + + Qt::Horizontal + + + + + + + Microphone + + + + + + + Qt::Horizontal + + + + + + Video settings - - - + + + QFormLayout::ExpandingFieldsGrow + + + - Show video preview + Modes + + + + + 0 + 0 + + + + + + + + Hue + + + + + + + Qt::Horizontal + + + + + + + Brightness + + + + + + + Qt::Horizontal + + + + + + + Saturation + + + + + + + Qt::Horizontal + + + + + + + Contrast + + + + + + + Qt::Horizontal + + + + + + + Preview + + + + + + @@ -38,7 +154,7 @@ 20 - 40 + 75 diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index e85e33bdb..3ac8bcf70 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -31,13 +31,14 @@ GeneralForm::GeneralForm() : bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations()); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); + bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray()); for (auto entry : SmileyPack::listSmileyPacks()) { bodyUI->smileyPackBrowser->addItem(entry.first, entry.second); } bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack())); - + bodyUI->cbUDPDisabled->setChecked(Settings::getInstance().getForceTCP()); bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr()); int port = Settings::getInstance().getProxyPort(); @@ -46,10 +47,11 @@ GeneralForm::GeneralForm() : bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy()); onUseProxyUpdated(); - + connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated); connect(bodyUI->cbUseTranslations, &QCheckBox::stateChanged, this, &GeneralForm::onUseTranslationUpdated); connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated); + connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray); connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int))); // new syntax can't handle overloaded signals... (at least not in a pretty way) connect(bodyUI->cbUDPDisabled, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated); @@ -78,6 +80,11 @@ void GeneralForm::onMakeToxPortableUpdated() Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked()); } +void GeneralForm::onSetAutostartInTray() +{ + Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked()); +} + void GeneralForm::onSmileyBrowserIndexChanged(int index) { QString filename = bodyUI->smileyPackBrowser->itemData(index).toString(); diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index ddfde9522..224c6ee26 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -36,6 +36,7 @@ private slots: void onEnableIPv6Updated(); void onUseTranslationUpdated(); void onMakeToxPortableUpdated(); + void onSetAutostartInTray(); void onSmileyBrowserIndexChanged(int index); void onUDPUpdated(); void onProxyAddrEdited(); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 653f15204..e0ffb3ab4 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -7,7 +7,7 @@ 0 0 527 - 367 + 369 @@ -46,6 +46,13 @@ + + + + Start in tray + + + diff --git a/src/widget/form/settings/genericsettings.h b/src/widget/form/settings/genericsettings.h index fb667e1fd..3e2d54feb 100644 --- a/src/widget/form/settings/genericsettings.h +++ b/src/widget/form/settings/genericsettings.h @@ -27,7 +27,7 @@ public: GenericForm(const QString &name, const QPixmap &icon) : formName(name), formIcon(icon) {;} ~GenericForm() {;} - virtual void updateContent() {;} + virtual void present() {} QString getFormName() {return formName;} QPixmap getFormIcon() {return formIcon;} diff --git a/src/widget/form/settings/identityform.cpp b/src/widget/form/settings/identityform.cpp index 1324fcf14..b8369945e 100644 --- a/src/widget/form/settings/identityform.cpp +++ b/src/widget/form/settings/identityform.cpp @@ -73,7 +73,7 @@ void IdentityForm::onStatusMessageEdited() Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text()); } -void IdentityForm::updateContent() +void IdentityForm::present() { toxId->setText(Core::getInstance()->getSelfId().toString()); } diff --git a/src/widget/form/settings/identityform.h b/src/widget/form/settings/identityform.h index 2a4b07a77..abbbf3a2c 100644 --- a/src/widget/form/settings/identityform.h +++ b/src/widget/form/settings/identityform.h @@ -50,7 +50,7 @@ public: void setUserName(const QString &name); void setStatusMessage(const QString &msg); - virtual void updateContent(); + virtual void present(); signals: void userNameChanged(QString); diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp index 1244e2026..b5f33a3bf 100644 --- a/src/widget/form/settingswidget.cpp +++ b/src/widget/form/settingswidget.cpp @@ -25,7 +25,7 @@ #include #include -SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent) +SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) { body = new QWidget(this); @@ -55,7 +55,7 @@ SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent) GeneralForm *gfrm = new GeneralForm; ifrm = new IdentityForm; PrivacyForm *pfrm = new PrivacyForm; - AVForm *avfrm = new AVForm(cam); + AVForm *avfrm = new AVForm; GenericForm *cfgForms[] = {gfrm, ifrm, pfrm, avfrm}; for (auto cfgForm : cfgForms) @@ -85,8 +85,8 @@ void SettingsWidget::show(Ui::MainWindow& ui) void SettingsWidget::onTabChanged(int index) { this->settingsWidgets->setCurrentIndex(index); - GenericForm *currentWidget = static_cast(this->settingsWidgets->widget(index)); - currentWidget->updateContent(); + GenericForm* currentWidget = static_cast(this->settingsWidgets->widget(index)); + currentWidget->present(); nameLabel->setText(currentWidget->getFormName()); imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation)); } diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h index 431e52c1c..6dca68b82 100644 --- a/src/widget/form/settingswidget.h +++ b/src/widget/form/settingswidget.h @@ -35,7 +35,7 @@ class SettingsWidget : public QWidget { Q_OBJECT public: - SettingsWidget(Camera* cam, QWidget* parent = nullptr); + SettingsWidget(QWidget* parent = nullptr); ~SettingsWidget(); void show(Ui::MainWindow &ui); diff --git a/src/widget/selfcamview.cpp b/src/widget/selfcamview.cpp deleted file mode 100644 index 7e9c3d252..000000000 --- a/src/widget/selfcamview.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2014 by Project Tox - - 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 -#include -#include -#include -#include -#include - -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(); -} diff --git a/src/widget/videosurface.cpp b/src/widget/videosurface.cpp new file mode 100644 index 000000000..bd49b49c0 --- /dev/null +++ b/src/widget/videosurface.cpp @@ -0,0 +1,197 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include +#include +#include + +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(); +} + + diff --git a/src/widget/selfcamview.h b/src/widget/videosurface.h similarity index 53% rename from src/widget/selfcamview.h rename to src/widget/videosurface.h index 26a8b315c..7de304682 100644 --- a/src/widget/selfcamview.h +++ b/src/widget/videosurface.h @@ -17,39 +17,42 @@ #ifndef SELFCAMVIEW_H #define SELFCAMVIEW_H -#include +#include -class QCloseEvent; -class QShowEvent; -class QPainter; -class Camera; -class QLabel; -class QHBoxLayout; +class QOpenGLBuffer; +class QOpenGLShaderProgram; class QTimer; +class VideoSource; -class SelfCamView : public QWidget +class VideoSurface : public QGLWidget { Q_OBJECT public: - SelfCamView(Camera* Cam, QWidget *parent=0); + VideoSurface(VideoSource* source, QWidget* parent=0); + ~VideoSurface(); -private slots: - void updateDisplay(); - -private: - void closeEvent(QCloseEvent*); - void showEvent(QShowEvent*); - void paint(QPainter *painter); + virtual void hideEvent(QHideEvent* ev); + virtual void showEvent(QShowEvent* ev); + // QGLWidget interface protected: - void resizeEvent(QResizeEvent *e); + virtual void initializeGL(); + virtual void paintGL(); + virtual void updateGL(); + + void update(); private: - QLabel *displayLabel; - QHBoxLayout* mainLayout; - Camera* cam; - QTimer* updateDisplayTimer; + VideoSource* source; + QOpenGLBuffer* pbo; + QOpenGLShaderProgram* program; + GLuint textureId; + int pboAllocSize; + QSize res; + bool uploadFrame; + bool hasSubscribed; + }; #endif // SELFCAMVIEW_H diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index d4a5f174d..ecf653947 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -27,7 +27,6 @@ #include "groupwidget.h" #include "form/groupchatform.h" #include "src/misc/style.h" -#include "selfcamview.h" #include "friendlistwidget.h" #include "camera.h" #include "form/chatform.h" @@ -102,8 +101,7 @@ Widget::Widget(QWidget *parent) ui->statusButton->setProperty("status", "offline"); Style::repolish(ui->statusButton); - camera = new Camera; - settingsWidget = new SettingsWidget(camera); + settingsWidget = new SettingsWidget(); // Disable some widgets until we're connected to the DHT ui->statusButton->setEnabled(false); @@ -119,7 +117,7 @@ Widget::Widget(QWidget *parent) qRegisterMetaType("ToxFile::FileDirection"); coreThread = new QThread(this); - core = new Core(camera, coreThread); + core = new Core(Camera::getInstance(), coreThread); core->moveToThread(coreThread); connect(coreThread, &QThread::started, core, &Core::start); @@ -145,6 +143,7 @@ Widget::Widget(QWidget *parent) connect(core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived); connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged); connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); + connect(core, &Core::avInvite, this, &Widget::playRingtone); connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int))); connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(int,QString,int))); @@ -217,11 +216,6 @@ QString Widget::getUsername() return core->getUsername(); } -Camera* Widget::getCamera() -{ - return camera; -} - void Widget::onAvatarClicked() { QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath()); @@ -343,6 +337,14 @@ void Widget::onTransferClicked() activeChatroomWidget = nullptr; } +void Widget::onIconClick() +{ + if(this->isHidden() == true) + this->show(); + else + this->hide(); +} + void Widget::onSettingsClicked() { hideMainForms(); @@ -535,6 +537,26 @@ void Widget::newMessageAlert() alSourcePlay(core->alMainSource); } +void Widget::playRingtone() +{ + QApplication::alert(this); + + static QFile sndFile1(":audio/ToxicIncomingCall.pcm"); // for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever + static QByteArray sndData1; + if (sndData1.isEmpty()) + { + sndFile1.open(QIODevice::ReadOnly); + sndData1 = sndFile1.readAll(); + sndFile1.close(); + } + + ALuint buffer; + alGenBuffers(1, &buffer); + alBufferData(buffer, AL_FORMAT_MONO16, sndData1.data(), sndData1.size(), 44100); + alSourcei(core->alMainSource, AL_BUFFER, buffer); + alSourcePlay(core->alMainSource); +} + void Widget::onFriendRequestReceived(const QString& userId, const QString& message) { FriendRequestDialog dialog(this, userId, message); diff --git a/src/widget/widget.h b/src/widget/widget.h index 240582bef..45953a206 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -34,7 +34,7 @@ class GenericChatroomWidget; class Group; struct Friend; class QSplitter; -class SelfCamView; +class VideoSurface; class QMenu; class Core; class Camera; @@ -104,6 +104,8 @@ private slots: void setStatusBusy(); void onMessageSendResult(int friendId, const QString& message, int messageId); void onGroupSendResult(int groupId, const QString& message, int result); + void playRingtone(); + void onIconClick(); private: void hideMainForms(); @@ -122,7 +124,6 @@ private: static Widget* instance; GenericChatroomWidget* activeChatroomWidget; FriendListWidget* contactListWidget; - Camera* camera; MaskablePixmapWidget* profilePicture; bool notify(QObject *receiver, QEvent *event); }; diff --git a/translations/pirate.qm b/translations/pirate.qm new file mode 100644 index 000000000..c245f536e Binary files /dev/null and b/translations/pirate.qm differ diff --git a/translations/pirate.ts b/translations/pirate.ts new file mode 100644 index 000000000..85a149a95 --- /dev/null +++ b/translations/pirate.ts @@ -0,0 +1,545 @@ + + + + + AVForm + + + Audio/Video settings + Hollerin'/Crystals o' Far-Seein' settings + + + + Hide video preview + On a button + Hide crystals o' Far-Seein' preview + + + + Show video preview + On a button + See crystals o' Far-Seein' preview + + + + AVSettings + + + Form + + + + + Video settings + Crystals o' Far-Seein' settings + + + + Show video preview + See crystals o' Far-Seein' preview + + + + AddFriendForm + + + Add Friends + Recruit yer crew + + + + Tox ID + Tox ID of the person you're sending a friend request to + Tox ID + + + + Message + The message you send in friend requests + Parchment Bottle of recruiting + + + + Send friend request + Send recruitment letter + + + + Tox me maybe? + Default message in friend requests if the field is left blank. Write something appropriate! + Tox, arrr? + + + + Please fill in a valid Tox ID + Tox ID of the friend you're sending a friend request to + Throw in here Tox ID, but a rightful one! + + + + You can't add yourself as a friend! + When trying to add your own Tox ID as friend + Yer cannot add yerself in yer crew! + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Recruitment address yer usin' ain't there + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + DNS witchery errored upon looking + + + + Unexpected number of text records + Error with the DNS + All numbery that DNS brought upon was unexpectable + + + + Unexpected number of values in text record + Error with the DNS + All things + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + + + + + ChatForm + + + Send a file + Send treasure + + + + Core + + + Encrypted profile + + + + + Your tox profile seems to be encrypted, qTox can't open it +Do you want to erase this profile ? + + + + + FileTransferInstance + + + Save a file + Title of the file saving dialog + Receive treasure + + + + Location not writable + Title of permissions popup + Cannot bury treasure here + + + + You do not have permission to write that location. Choose another, or cancel the save dialog. + text of permissions popup + Captain did't give permission bury that treasure here! Find another location or abandon hope of keepin' it. + + + + FilesForm + + + Transfered Files + "Headline" of the window + Treasures given/received + + + + Downloads + Received + + + + Uploads + Sent + + + + FriendRequestDialog + + + Friend request + Title of the window to aceept/deny a friend request + Recruitment + + + + Someone wants to make friends with you + + + + + User ID: + + + + + Friend request message: + + + + + Accept + Accept a friend request + Arr + + + + Reject + Reject a friend request + No + + + + FriendWidget + + + Copy friend ID + Menu to copy the Tox ID of that friend + + + + + Invite in group + Menu to invite a friend in a groupchat + Recruit in group + + + + Remove friend + Menu to remove the friend from our friendlist + Throw out traitor + + + + GeneralForm + + + General Settings + O' ship settings + + + + GeneralSettings + + + Form + + + + + General Settings + O' ship settings + + + + Use translations + Text on a checkbox to enable translations + Linguistics use + + + + Save settings to the working directory instead of the usual conf dir + describes makeToxPortable checkbox + + + + + Make Tox portable + + + + + Theme + Lookin' + + + + Smiley Pack + Text on smiley pack label + Teeth showin' box + + + + Connection Settings + Sailin' setting + + + + Enable IPv6 (recommended) + Text on a checkbox to enable IPv6 + Use IPv6 (good for ya) + + + + This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. + force tcp checkbox tooltip + + + + + Disable UDP (not recommended) + Text on checkbox to disable UDP + + + + + Use proxy (SOCKS5) + + + + + Address + Text on proxy addr label + + + + + Port + Text on proxy port label + + + + + GenericChatForm + + + + Save chat log + Write log of yer talkin' + + + + GroupChatForm + + + %1 users in chat + Number of users in chat + %1 mates in chat + + + + %1 users in chat + %1 mates in chat + + + + GroupWidget + + + + %1 users in chat + %1 mates in chat + + + + + 0 users in chat + + + + + Quit group + Menu to quit a groupchat + + + + + IdentityForm + + + Your identity + Yer self + + + + IdentitySettings + + + Form + + + + + Public Information + + + + + Name + + + + + Status + + + + + Tox ID + + + + + Your Tox ID (click to copy) + Yer Tox ID (hit to steal) + + + + MainWindow + + + qTox + qTox + + + + Your name + Yer name + + + + Your status + Yer status + + + + Add friends + Recruit crew + + + + Create a group chat + Make a chat with yer mates + + + + View completed file transfers + See treasures given and received + + + + Change your settings + Change yer setting' + + + + Close + + + + + Ctrl+Q + + + + + PrivacyForm + + + Privacy settings + Secrecy settin' + + + + SelfCamView + + + Tox video test + Title of the window to test the video/webcam + Tox crystals o' Far-Seein' test + + + + Widget + + + Online + Button to set your status to 'Online' + + + + + Away + Button to set your status to 'Away' + + + + + Busy + Button to set your status to 'Busy' + + + + + Choose a profile picture + Choose yer flag + + + + + + Error + + + + + Unable to open this file + Treasure cannot be opened + + + + Unable to read this image + Cannot see treasure + + + + This image is too big + Yer flag too big + + + + Toxcore failed to start, the application will terminate after you close this message. + + + + + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. + popup text + + + + + <Unknown> + Placeholder when we don't know someone's name in a group chat + + + + diff --git a/translations/uk.qm b/translations/uk.qm index 53c004e9b..80b128b79 100644 Binary files a/translations/uk.qm and b/translations/uk.qm differ diff --git a/translations/uk.ts b/translations/uk.ts index 70d143cdc..79d617223 100644 --- a/translations/uk.ts +++ b/translations/uk.ts @@ -1,26 +1,60 @@ - + + + AVForm + + + Audio/Video settings + Параметри аудіо/відео + + + + Hide video preview + On a button + Приховати вікно перегляду + + + + Show video preview + On a button + Показати вікно перегляду + + AVPage - Video Settings + Параметри відео + + + Show video preview + On a button + Показати вікно попереднього перегляду + + + Hide video preview + On a button + Приховати вікно попереднього перегляду + + + + AVSettings + + + Form + Form + + + + Video settings Параметри відео - - + Show video preview - On a button Показати вікно попереднього перегляду - - - Hide video preview - On a button - Приховати вікно попереднього перегляду - AddFriendForm @@ -105,27 +139,42 @@ ChatForm - + Send a file Надіслати файл + + Core + + + Encrypted profile + Зашифрований профіль + + + + Your tox profile seems to be encrypted, qTox can't open it +Do you want to erase this profile ? + Схоже, що ваш tox-профіль зашифровано, qTox не може його відкрити. +Бажаєте стерти цей профіль? + + FileTransferInstance - + Save a file Title of the file saving dialog Зберегти файл - + Location not writable Title of permissions popup Немає прав на запис - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу. @@ -189,71 +238,153 @@ FriendWidget - + Copy friend ID Menu to copy the Tox ID of that friend Копіювати дружній ID - + Invite in group Menu to invite a friend in a groupchat Запросити до групи - + Remove friend Menu to remove the friend from our friendlist Вилучити з друзів + + GeneralForm + + + General Settings + Основні параметри + + GeneralPage - + General Settings + Основні параметри + + + Enable IPv6 (recommended) + Text on a checkbox to enable IPv6 + Дозволити IPv6 (рекомендовано) + + + Use translations + Text on a checkbox to enable translations + Використовувати мову системи + + + Make Tox portable + Text on a checkbox to make qTox a portable application + Портативний запуск + + + Save settings to the working directory instead of the usual conf dir + describes makeToxPortable checkbox + Зберігати налаштування в робочий теці + + + Theme + Графічна тема + + + Smiley Pack + Графічний пакунок емоційних картинок + + + + GeneralSettings + + + Form + Form + + + General Settings Основні параметри - - Enable IPv6 (recommended) - Text on a checkbox to enable IPv6 - Дозволити IPv6 (рекомендовано) - - - + Use translations - Text on a checkbox to enable translations + Text on a checkbox to enable translations Використовувати мову системи - - Make Tox portable - Text on a checkbox to make qTox a portable application - Портативний запуск - - - + Save settings to the working directory instead of the usual conf dir - describes makeToxPortable checkbox + describes makeToxPortable checkbox Зберігати налаштування в робочий теці - + + Make Tox portable + Портативний запуск + + + Theme Графічна тема - + Smiley Pack + Text on smiley pack label Графічний пакунок емоційних картинок + + + Connection Settings + Параметри підключення + + + + Enable IPv6 (recommended) + Text on a checkbox to enable IPv6 + Дозволити IPv6 (рекомендовано) + + + + This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. + force tcp checkbox tooltip + Це дозволяє використовувати tox, наприклад, поверх протоколу Tor. Але це збульшує навантаження на мережу, тому використовуйте лише в разі необхідності. + + + + Disable UDP (not recommended) + Text on checkbox to disable UDP + Вимкнути IPv6 (не рекомендовано) + + + + Use proxy (SOCKS5) + Використовувати проксі (SOCKS5) + + + + Address + Text on proxy addr label + Адреса + + + + Port + Text on proxy port label + Порт + GenericChatForm - - + + Save chat log Зберегти чат @@ -261,18 +392,17 @@ GroupChatForm - + %1 users in chat Number of users in chat Користувачів у чаті: %1 - <Unknown> - <Невідомо> + <Невідомо> - + %1 users in chat Користувачів у чаті: %1 @@ -280,52 +410,88 @@ GroupWidget - - + + %1 users in chat Користувачів у чаті: %1 - - + + 0 users in chat Немає користувачів - + Quit group Menu to quit a groupchat Вийти з групи + + IdentityForm + + + Your identity + Ваш ідентифікатор + + IdentityPage - + Public Information + Публічна інформація + + + Name + Username/nick + Ім'я + + + Status + Status message + Статус + + + Tox ID + Tox ID + + + Your Tox ID + Ваш Tox ID + + + + IdentitySettings + + + Form + Form + + + Public Information Публічна інформація - + Name - Username/nick Ім'я - + Status - Status message Статус - + Tox ID Tox ID - - Your Tox ID - Ваш Tox ID + + Your Tox ID (click to copy) + Ваш Tox ID (клацніть аби скопіювати) @@ -336,46 +502,54 @@ qTox - + Your name Ваше ім'я - + Your status Ваш статус - + Add friends Додати друзів - + Create a group chat Створити груповий чат - + View completed file transfers Переглянути завершені передачі файлів - + Change your settings Змінити параметри - + Close Закрити - + Ctrl+Q Ctrl+Q + + PrivacyForm + + + Privacy settings + Параметри приватності + + SelfCamView @@ -388,95 +562,98 @@ SettingsDialog - qTox – Settings - qTox - Параметри + qTox - Параметри - General - Основні + Основні - Identity - Ідентифікація + Ідентифікація - Privacy - Приватність + Приватність - Audio/Video - Аудіо/Відео + Аудіо/Відео - Ok - Гаразд + Гаразд - Cancel - Скасувати + Скасувати - Apply - Застосувати + Застосувати Widget - + Online Button to set your status to 'Online' В мережі - + Away Button to set your status to 'Away' Відійшов - + Busy Button to set your status to 'Busy' Зайнятий - + Choose a profile picture Оберіть зображення для профілю - - - + + + Error Помилка - + Unable to open this file Неможливо відкрити цей файл - + Unable to read this image Неможливо прочитати це зображення - + This image is too big Зображення завелике - + + Toxcore failed to start, the application will terminate after you close this message. + Помилка запуску ядра tox, програма буде завершена після закриття цього повідомлення. + + + + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. + popup text + Помилка запуску ядра tox із поточними параметрами проксі. qTox не працює; змініть параметри і перезапустіть. + + + <Unknown> Placeholder when we don't know someone's name in a group chat <Невідомо>