1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'master' into profiles

Holy shit first big merge of my career, I'm mildly amazed it even compiles

Conflicts:
	core.cpp
	widget/form/settingsform.cpp
	widget/form/settingsform.h
	widget/widget.cpp
This commit is contained in:
Dubslow 2014-09-05 03:53:00 -05:00
commit 7aab85251e
105 changed files with 7686 additions and 5880 deletions

View File

@ -15,31 +15,73 @@ However, it is not a fork.
- Tox DNS - Tox DNS
- Translations in various languages - Translations in various languages
<h2>Requirements</h2> <h2>Downloads</h2>
This client runs on Windows, Linux and Mac natively, but is not build regularly for Linux <br/> This client runs on Windows, Linux and Mac natively.<br/>
Linux users will have to compile the source code themselves if they want the latest updates.
<a href="https://jenkins.libtoxcore.so/job/tux3-toxgui-win32/lastSuccessfulBuild/artifact/toxgui-win32.zip">Windows download</a><br/> <a href="https://jenkins.libtoxcore.so/job/tux3-toxgui-win32/lastSuccessfulBuild/artifact/toxgui-win32.zip">Windows download</a><br/>
<a href="https://jenkins.libtoxcore.so/job/ToxGUI%20OS%20X/lastSuccessfulBuild/artifact/toxgui.dmg">Mac download </a><br/> <a href="https://jenkins.libtoxcore.so/job/ToxGUI%20OS%20X/lastSuccessfulBuild/artifact/qtox.dmg">Mac download </a><br/>
<a href="https://mega.co.nz/#!9l5B0QqZ!O2glB8XE_Tcf4zTub2WEk-_9Ra43RoeiFV-AQBKDZJU">Linux download (12st July 2014 20:30 GMT)</a><br/> <a href="https://jenkins.libtoxcore.so/job/qTox-linux-amd64/">Linux download</a> (click "Last successful artifacts")<br/>
Note that the Linux download has not been tested and may not be up to date.<br/>
<h3>Screenshots</h3> <h3>Screenshots</h3>
<h5>Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features</h5> <h5>Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features</h5>
<img src="http://i.imgur.com/mMUdr6u.png"/> <img src="http://i.imgur.com/mMUdr6u.png"/>
<img src="http://i.imgur.com/66ARBGC.png"/> <img src="http://i.imgur.com/66ARBGC.png"/>
<h3>Compiling</h3> <h3>Compiling on GNU-Linux</h3>
Compiling toxgui requires Qt 5.2 with the Qt Multimedia module and a C++11 compatible compiler. <h4>Acquiring dependencies</h4>
It also requires the toxcore and toxav libraries. Compiling qTox requires several dependencies, however these are easily installable
with your system's package manager. The step-by-step instructions assume Debian-style apt, but
it should be easy enough to get equivalent packages with yum or pacman.
To compile, first clone or download the qTox repository and open a terminal in the qTox folder. First, we need Qt 5.2 with a C++11 compatible compiler:
Then run the script bootstrap.sh (for Linux and Mac) or bootsrap.bat (for Windows) to download an up-to-date toxcore. ```bash
And finally run the commands "qmake" and "make" to start building qTox. sudo apt-get install build-essential qt5-qmake qt5-default
```
toxcore and toxav, the client-agnostic network code for Tox, has several dependencies
of its own (see <a href="https://github.com/irungentoo/toxcore/blob/master/INSTALL.md#unix">its installation guide</a> for more details):
```bash
sudo apt-get install libtool autotools-dev automake checkinstall check git yasm libopus-dev libvpx-dev
```
<h3>OSX Install Guide</h3> Finally, qTox itself requires OpenAL and OpenCV:
```bash
sudo apt-get install libopenal-dev libopencv-dev
```
<h4>Compilation</h4>
Having acquired all the dependencies, the following commands should get and compile qTox:
```bash
wget -O qtox.tgz https://github.com/tux3/qTox/archive/master.tar.gz
tar xvf qtox.tgz
cd qTox-master
./bootstrap.sh # This will automagically download and compile libsodium, toxcore, and toxav
qmake
make # Should compile to "qtox"
```
And that's it!
<h4>Building packages</h4>
qTox now has the experimental and probably-dodgy ability to package itself (in .deb
form natively, and .rpm form with <a href="http://joeyh.name/code/alien/">alien</a>).
After installing the required dependencies, run `bootstrap.sh` and then run the
`buildPackages.sh` script, found in the tools folder. It will automatically get the
packages necessary for building .debs, so be prepared to type your password for sudo.
<h3>OSX Easy Install</h3>
Since https://github.com/ReDetection/homebrew-qtox you can easily install qtox with homebrew
```bash
brew install --HEAD ReDetection/qtox/qtox
```
<h3>OSX Full Install Guide</h3>
<strong>This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'.</strong> <strong>This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'.</strong>

View File

@ -1,84 +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 "audiobuffer.h"
AudioBuffer::AudioBuffer() :
QIODevice(0)
{
open(QIODevice::ReadWrite);
}
AudioBuffer::~AudioBuffer()
{
close();
}
qint64 AudioBuffer::readData(char *data, qint64 len)
{
qint64 total;
bufferMutex.lock();
try {
total = qMin((qint64)buffer.size(), len);
memcpy(data, buffer.constData(), total);
buffer = buffer.mid(total);
}
catch (...)
{
bufferMutex.unlock();
return 0;
}
bufferMutex.unlock();
return total;
}
qint64 AudioBuffer::writeData(const char* data, qint64 len)
{
bufferMutex.lock();
try {
buffer.append(data, len);
}
catch (...)
{
bufferMutex.unlock();
return 0;
}
bufferMutex.unlock();
return len;
}
qint64 AudioBuffer::bytesAvailable() const
{
bufferMutex.lock();
long long size = buffer.size() + QIODevice::bytesAvailable();
bufferMutex.unlock();
return size;
}
qint64 AudioBuffer::bufferSize() const
{
bufferMutex.lock();
long long size = buffer.size();
bufferMutex.unlock();
return size;
}
void AudioBuffer::clear()
{
bufferMutex.lock();
buffer.clear();
bufferMutex.unlock();
}

View File

@ -10,8 +10,10 @@ INSTALL_DIR=libs
# just for convenience # just for convenience
BASE_DIR=${SCRIPT_DIR}/${INSTALL_DIR} BASE_DIR=${SCRIPT_DIR}/${INSTALL_DIR}
SODIUM_VER=0.7.0
# directory names of cloned repositories # directory names of cloned repositories
SODIUM_DIR=libsodium-0.5.0 SODIUM_DIR=libsodium-$SODIUM_VER
TOX_CORE_DIR=libtoxcore-latest TOX_CORE_DIR=libtoxcore-latest
# this boolean describes whether the installation of # this boolean describes whether the installation of
@ -19,6 +21,8 @@ TOX_CORE_DIR=libtoxcore-latest
# the default value is 'false' and will be set to 'true' # the default value is 'false' and will be set to 'true'
# if this script gets the parameter -t or --tox # if this script gets the parameter -t or --tox
TOX_ONLY=false TOX_ONLY=false
GLOBAL=false
KEEP=false
if [ -z "$BASE_DIR" ]; then if [ -z "$BASE_DIR" ]; then
echo "internal error detected!" echo "internal error detected!"
@ -41,9 +45,16 @@ fi
########## check input parameters ########## ########## check input parameters ##########
if [ $# -ge 1 ] ; then while [ $# -ge 1 ] ; do
if [ ${1} = "-t" -o ${1} = "--tox" ] ; then if [ ${1} = "-t" -o ${1} = "--tox" ] ; then
TOX_ONLY=true TOX_ONLY=true
shift
elif [ ${1} = "-g" -o ${1} = "--global" ] ; then
GLOBAL=true
shift
elif [ ${1} = "-k" -o ${1} = "--keep" ]; then
KEEP=true
shift
else else
if [ ${1} != "-h" -a ${1} != "--help" ] ; then if [ ${1} != "-h" -a ${1} != "--help" ] ; then
echo "[ERROR] Unknown parameter \"${1}\"" echo "[ERROR] Unknown parameter \"${1}\""
@ -54,21 +65,26 @@ if [ $# -ge 1 ] ; then
echo "Use this script to install/update libsodium and libtoxcore in ${INSTALL_DIR}" echo "Use this script to install/update libsodium and libtoxcore in ${INSTALL_DIR}"
echo "" echo ""
echo "usage:" echo "usage:"
echo " ${0} [-t|--tox|-h|--help]" echo " ${0} [-t|--tox|-h|--help|-g|--global|-k|--keep]"
echo "" echo ""
echo "parameters:" echo "parameters:"
echo " -h|--help: displays this help" echo " -h|--help : displays this help"
echo " -t|--tox : only install/update libtoxcore" echo " -t|--tox : only install/update libtoxcore"
echo " requires an already installed libsodium" echo " requires an already installed libsodium"
echo " -g|--global: installs libtox* and libsodium globally"
echo " (also disables local configure prefixes)"
echo " -k|--keep : does not delete the build directories afterwards"
echo "" echo ""
echo "example usages:" echo "example usages:"
echo " ${0} -- to install libsodium and libtoxcore" echo " ${0} -- to install libsodium and libtoxcore"
echo " ${0} -t -- to update already installed libtoxcore" echo " ${0} -t -- to update already installed libtoxcore"
exit 1 exit 1
fi fi
fi done
echo "Tox only: $TOX_ONLY"
echo "Global : $GLOBAL"
echo "Keep : $KEEP"
############### prepare step ############### ############### prepare step ###############
# create BASE_DIR directory if necessary # create BASE_DIR directory if necessary
@ -83,17 +99,29 @@ rm -rf ${BASE_DIR}/${TOX_CORE_DIR}
############### install step ############### ############### install step ###############
# clone current master of libsodium and switch to version 0.5.0 # clone current master of libsodium and switch to version $SODIUM_VER
# afterwards install libsodium to INSTALL_DIR # afterwards install libsodium to INSTALL_DIR
# skip the installation if TOX_ONLY is true # skip the installation if TOX_ONLY is true
if [[ $TOX_ONLY = "false" ]]; then if [[ $TOX_ONLY = "false" ]]; then
git clone git://github.com/jedisct1/libsodium.git ${BASE_DIR}/${SODIUM_DIR} git clone git://github.com/jedisct1/libsodium.git ${BASE_DIR}/${SODIUM_DIR}
pushd ${BASE_DIR}/${SODIUM_DIR} pushd ${BASE_DIR}/${SODIUM_DIR}
git checkout tags/0.5.0 git checkout tags/$SODIUM_VER
./autogen.sh ./autogen.sh
./configure --prefix=${BASE_DIR}/
if [[ $GLOBAL = "false" ]]; then
./configure --prefix=${BASE_DIR}/
else
./configure
fi
make -j2 check make -j2 check
make install
if [[ $GLOBAL = "false" ]]; then
make install
else
sudo make install
fi
popd popd
fi fi
@ -103,14 +131,25 @@ fi
git clone https://github.com/irungentoo/toxcore.git ${BASE_DIR}/${TOX_CORE_DIR} git clone https://github.com/irungentoo/toxcore.git ${BASE_DIR}/${TOX_CORE_DIR}
pushd ${BASE_DIR}/${TOX_CORE_DIR} pushd ${BASE_DIR}/${TOX_CORE_DIR}
./autogen.sh ./autogen.sh
./configure --prefix=${BASE_DIR}/ --with-libsodium-headers=${BASE_DIR}/include --with-libsodium-libs=${BASE_DIR}/lib if [[ $GLOBAL = "false" ]]; then
./configure --prefix=${BASE_DIR}/ --with-libsodium-headers=${BASE_DIR}/include --with-libsodium-libs=${BASE_DIR}/lib
else
./configure
fi
make -j2 make -j2
make install
if [[ $GLOBAL = "false" ]]; then
make install
else
sudo make install
fi
popd popd
############### cleanup step ############### ############### cleanup step ###############
# remove cloned repositories # remove cloned repositories
rm -rf ${BASE_DIR}/${SODIUM_DIR} if [[ $KEEP = "false" ]]; then
rm -rf ${BASE_DIR}/${TOX_CORE_DIR} rm -rf ${BASE_DIR}/${SODIUM_DIR}
rm -rf ${BASE_DIR}/${TOX_CORE_DIR}
fi

714
core.cpp
View File

@ -20,6 +20,9 @@
#include "settings.h" #include "settings.h"
#include "widget/widget.h" #include "widget/widget.h"
#include <ctime>
#include <functional>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -33,10 +36,6 @@ const QString Core::CONFIG_FILE_NAME = "data";
const QString Core::TOX_EXT = ".tox"; const QString Core::TOX_EXT = ".tox";
QList<ToxFile> Core::fileSendQueue; QList<ToxFile> Core::fileSendQueue;
QList<ToxFile> Core::fileRecvQueue; QList<ToxFile> Core::fileRecvQueue;
ToxCall Core::calls[TOXAV_MAX_CALLS];
const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4};
uint8_t* Core::videobuf;
int Core::videoBusyness;
Core::Core(Camera* cam, QThread *coreThread) : Core::Core(Camera* cam, QThread *coreThread) :
tox(nullptr), camera(cam) tox(nullptr), camera(cam)
@ -48,8 +47,6 @@ Core::Core(Camera* cam, QThread *coreThread) :
toxTimer->setSingleShot(true); toxTimer->setSingleShot(true);
//saveTimer = new QTimer(this); //saveTimer = new QTimer(this);
//saveTimer->start(TOX_SAVE_INTERVAL); //saveTimer->start(TOX_SAVE_INTERVAL);
//fileTimer = new QTimer(this);
//fileTimer->start(TOX_FILE_INTERVAL);
bootstrapTimer = new QTimer(this); bootstrapTimer = new QTimer(this);
bootstrapTimer->start(TOX_BOOTSTRAP_INTERVAL); bootstrapTimer->start(TOX_BOOTSTRAP_INTERVAL);
connect(toxTimer, &QTimer::timeout, this, &Core::process); connect(toxTimer, &QTimer::timeout, this, &Core::process);
@ -63,11 +60,32 @@ Core::Core(Camera* cam, QThread *coreThread) :
{ {
calls[i].sendAudioTimer = new QTimer(); calls[i].sendAudioTimer = new QTimer();
calls[i].sendVideoTimer = new QTimer(); calls[i].sendVideoTimer = new QTimer();
calls[i].audioBuffer.moveToThread(coreThread);
calls[i].sendAudioTimer->moveToThread(coreThread); calls[i].sendAudioTimer->moveToThread(coreThread);
calls[i].sendVideoTimer->moveToThread(coreThread); calls[i].sendVideoTimer->moveToThread(coreThread);
connect(calls[i].sendVideoTimer, &QTimer::timeout, [this,i](){sendCallVideo(i);}); connect(calls[i].sendVideoTimer, &QTimer::timeout, [this,i](){sendCallVideo(i);});
} }
// OpenAL init
alOutDev = alcOpenDevice(nullptr);
if (!alOutDev)
{
qWarning() << "Core: Cannot open output audio device";
}
else
{
alContext=alcCreateContext(alOutDev,nullptr);
if (!alcMakeContextCurrent(alContext))
{
qWarning() << "Core: Cannot create output audio context";
alcCloseDevice(alOutDev);
}
else
alGenSources(1, &alMainSource);
}
alInDev = alcCaptureOpenDevice(NULL,av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16,
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) / 1000);
if (!alInDev)
qWarning() << "Core: Cannot open input audio device";
} }
Core::~Core() Core::~Core()
@ -83,6 +101,16 @@ Core::~Core()
delete[] videobuf; delete[] videobuf;
videobuf=nullptr; videobuf=nullptr;
} }
if (alContext)
{
alcMakeContextCurrent(nullptr);
alcDestroyContext(alContext);
}
if (alOutDev)
alcCloseDevice(alOutDev);
if (alInDev)
alcCaptureCloseDevice(alInDev);
} }
void Core::start() void Core::start()
@ -93,12 +121,36 @@ void Core::start()
qDebug() << "Core starting with IPv6 enabled"; qDebug() << "Core starting with IPv6 enabled";
else else
qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly.";
tox = tox_new(enableIPv6);
Tox_Options toxOptions;
toxOptions.ipv6enabled = enableIPv6;
toxOptions.udp_disabled = 0;
toxOptions.proxy_enabled = false;
toxOptions.proxy_address[0] = 0;
toxOptions.proxy_port = 0;
tox = tox_new(&toxOptions);
if (tox == nullptr) if (tox == nullptr)
{ {
qCritical() << "Tox core failed to start"; if (enableIPv6) // Fallback to IPv4
emit failedToStart(); {
return; toxOptions.ipv6enabled = false;
tox = tox_new(&toxOptions);
if (tox == nullptr)
{
qCritical() << "Tox core failed to start";
emit failedToStart();
return;
}
else
qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly.";
}
else
{
qCritical() << "Tox core failed to start";
emit failedToStart();
return;
}
} }
toxav = toxav_new(tox, TOXAV_MAX_CALLS); toxav = toxav_new(tox, TOXAV_MAX_CALLS);
@ -109,6 +161,8 @@ void Core::start()
return; return;
} }
qsrand(time(nullptr));
// where do we find the data file? // where do we find the data file?
QString path; QString path;
{ // read data from whose profile? { // read data from whose profile?
@ -153,8 +207,8 @@ void Core::start()
toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this); toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this);
toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this); toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this);
toxav_register_audio_recv_callback(toxav, playCallAudio); toxav_register_audio_recv_callback(toxav, playCallAudio, this);
toxav_register_video_recv_callback(toxav, playCallVideo); toxav_register_video_recv_callback(toxav, playCallVideo, this);
uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE]; uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE];
tox_get_address(tox, friendAddress); tox_get_address(tox, friendAddress);
@ -261,7 +315,7 @@ void Core::onFileSendRequestCallback(Tox*, int32_t friendnumber, uint8_t filenum
fileRecvQueue.append(file); fileRecvQueue.append(file);
emit static_cast<Core*>(core)->fileReceiveRequested(fileRecvQueue.last()); emit static_cast<Core*>(core)->fileReceiveRequested(fileRecvQueue.last());
} }
void Core::onFileControlCallback(Tox*, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber,
uint8_t control_type, const uint8_t*, uint16_t, void *core) uint8_t control_type, const uint8_t*, uint16_t, void *core)
{ {
ToxFile* file{nullptr}; ToxFile* file{nullptr};
@ -292,12 +346,15 @@ void Core::onFileControlCallback(Tox*, int32_t friendnumber, uint8_t receive_sen
qWarning("Core::onFileControlCallback: No such file in queue"); qWarning("Core::onFileControlCallback: No such file in queue");
return; return;
} }
if (control_type == TOX_FILECONTROL_ACCEPT && receive_send == 1) if (receive_send == 1 && control_type == TOX_FILECONTROL_ACCEPT)
{ {
file->status = ToxFile::TRANSMITTING; file->status = ToxFile::TRANSMITTING;
emit static_cast<Core*>(core)->fileTransferAccepted(*file); emit static_cast<Core*>(core)->fileTransferAccepted(*file);
qDebug() << "Core: File control callback, file accepted"; qDebug() << "Core: File control callback, file accepted";
file->sendFuture = QtConcurrent::run(sendAllFileData, static_cast<Core*>(core), file); file->sendTimer = new QTimer(static_cast<Core*>(core));
connect(file->sendTimer, &QTimer::timeout, std::bind(sendAllFileData,static_cast<Core*>(core), file));
file->sendTimer->setSingleShot(true);
file->sendTimer->start(TOX_FILE_INTERVAL);
} }
else if (receive_send == 1 && control_type == TOX_FILECONTROL_KILL) else if (receive_send == 1 && control_type == TOX_FILECONTROL_KILL)
{ {
@ -305,8 +362,26 @@ void Core::onFileControlCallback(Tox*, int32_t friendnumber, uint8_t receive_sen
.arg(file->fileNum).arg(file->friendId); .arg(file->fileNum).arg(file->friendId);
file->status = ToxFile::STOPPED; file->status = ToxFile::STOPPED;
emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING); emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
file->sendFuture.waitForFinished(); // Wait for sendAllFileData to return before deleting the ToxFile // Wait for sendAllFileData to return before deleting the ToxFile, we MUST ensure this or we'll use after free
removeFileFromQueue(true, file->friendId, file->fileNum); if (file->sendTimer)
{
QThread::msleep(1);
qApp->processEvents();
if (file->sendTimer)
{
delete file->sendTimer;
file->sendTimer = nullptr;
}
}
removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
}
else if (receive_send == 1 && control_type == TOX_FILECONTROL_FINISHED)
{
qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 to friend %2 is complete")
.arg(file->fileNum).arg(file->friendId);
file->status = ToxFile::STOPPED;
emit static_cast<Core*>(core)->fileTransferFinished(*file);
removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
} }
else if (receive_send == 0 && control_type == TOX_FILECONTROL_KILL) else if (receive_send == 0 && control_type == TOX_FILECONTROL_KILL)
{ {
@ -314,7 +389,7 @@ void Core::onFileControlCallback(Tox*, int32_t friendnumber, uint8_t receive_sen
.arg(file->fileNum).arg(file->friendId); .arg(file->fileNum).arg(file->friendId);
file->status = ToxFile::STOPPED; file->status = ToxFile::STOPPED;
emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::RECEIVING); emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::RECEIVING);
removeFileFromQueue(false, file->friendId, file->fileNum); removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
} }
else if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED) else if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED)
{ {
@ -322,7 +397,9 @@ void Core::onFileControlCallback(Tox*, int32_t friendnumber, uint8_t receive_sen
.arg(file->fileNum).arg(file->friendId); .arg(file->fileNum).arg(file->friendId);
file->status = ToxFile::STOPPED; file->status = ToxFile::STOPPED;
emit static_cast<Core*>(core)->fileTransferFinished(*file); emit static_cast<Core*>(core)->fileTransferFinished(*file);
removeFileFromQueue(false, file->friendId, file->fileNum); // confirm receive is complete
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0);
removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
} }
else else
{ {
@ -376,6 +453,19 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag
if (friendId < 0) { if (friendId < 0) {
emit failedToAddFriend(userId); emit failedToAddFriend(userId);
} else { } else {
// Update our friendAddresses
bool found=false;
QList<QString>& friendAddresses = Settings::getInstance().friendAddresses;
for (QString& addr : friendAddresses)
{
if (addr.toUpper().contains(friendAddress))
{
addr = friendAddress;
found = true;
}
}
if (!found)
friendAddresses.append(friendAddress);
emit friendAdded(friendId, userId); emit friendAdded(friendId, userId);
} }
saveConfiguration(); saveConfiguration();
@ -445,7 +535,7 @@ void Core::pauseResumeFileSend(int friendId, int fileNum)
} }
if (!file) if (!file)
{ {
qWarning("Core::cancelFileSend: No such file in queue"); qWarning("Core::pauseResumeFileSend: No such file in queue");
return; return;
} }
if (file->status == ToxFile::TRANSMITTING) if (file->status == ToxFile::TRANSMITTING)
@ -515,7 +605,7 @@ void Core::cancelFileSend(int friendId, int fileNum)
file->status = ToxFile::STOPPED; file->status = ToxFile::STOPPED;
emit fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING); emit fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
file->sendFuture.waitForFinished(); // Wait until sendAllFileData returns before deleting while (file->sendTimer) QThread::msleep(1); // Wait until sendAllFileData returns before deleting
removeFileFromQueue(true, friendId, fileNum); removeFileFromQueue(true, friendId, fileNum);
} }
@ -696,25 +786,36 @@ void Core::onFileTransferFinished(ToxFile file)
void Core::bootstrapDht() void Core::bootstrapDht()
{ {
qDebug() << "Core: Bootstraping DHT";
const Settings& s = Settings::getInstance(); const Settings& s = Settings::getInstance();
QList<Settings::DhtServer> dhtServerList = s.getDhtServerList(); QList<Settings::DhtServer> dhtServerList = s.getDhtServerList();
static int j = 0;
int i=0;
int listSize = dhtServerList.size(); int listSize = dhtServerList.size();
while (i<5) static int j = qrand() % listSize, n=0;
// We couldn't connect after trying 6 different nodes, let's try something else
if (n>3)
{
qDebug() << "Core: We're having trouble connecting to the DHT, slowing down";
bootstrapTimer->setInterval(TOX_BOOTSTRAP_INTERVAL*(n-1));
}
else
qDebug() << "Core: Connecting to the DHT ...";
int i=0;
while (i < (2 - (n>3)))
{ {
const Settings::DhtServer& dhtServer = dhtServerList[j % listSize]; const Settings::DhtServer& dhtServer = dhtServerList[j % listSize];
if (tox_bootstrap_from_address(tox, dhtServer.address.toLatin1().data(), if (tox_bootstrap_from_address(tox, dhtServer.address.toLatin1().data(),
0, qToBigEndian(dhtServer.port), CUserId(dhtServer.userId).data()) == 1) qToBigEndian(dhtServer.port), CUserId(dhtServer.userId).data()) == 1)
qDebug() << QString("Core: Bootstraping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data() qDebug() << QString("Core: Bootstraping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data()
+QString(", port ")+QString().setNum(dhtServer.port); +QString(", port ")+QString().setNum(dhtServer.port);
else else
qDebug() << "Core: Error bootstraping from "+dhtServer.name; qDebug() << "Core: Error bootstraping from "+dhtServer.name;
tox_do(tox);
j++; j++;
i++; i++;
n++;
} }
} }
@ -840,8 +941,10 @@ void Core::saveConfiguration(const QString& path)
configurationFile.write(reinterpret_cast<char *>(data), fileSize); configurationFile.write(reinterpret_cast<char *>(data), fileSize);
configurationFile.commit(); configurationFile.commit();
delete[] data; delete[] data;
//configurationFile.close();
} }
qDebug() << "Core: writing settings";
Settings::getInstance().save();
} }
void Core::loadFriends() void Core::loadFriends()
@ -981,486 +1084,85 @@ void Core::removeFileFromQueue(bool sendQueue, int friendId, int fileId)
void Core::sendAllFileData(Core *core, ToxFile* file) void Core::sendAllFileData(Core *core, ToxFile* file)
{ {
while (file->bytesSent < file->filesize) if (file->status == ToxFile::PAUSED)
{ {
if (file->status == ToxFile::PAUSED) file->sendTimer->start(5+TOX_FILE_INTERVAL);
{ return;
QThread::sleep(5); }
continue; else if (file->status == ToxFile::STOPPED)
} {
else if (file->status == ToxFile::STOPPED) qWarning("Core::sendAllFileData: File is stopped");
{ file->sendTimer->disconnect();
qWarning("Core::sendAllFileData: File is stopped"); delete file->sendTimer;
return; file->sendTimer = nullptr;
} return;
emit core->fileTransferInfo(file->friendId, file->fileNum, file->filesize, file->bytesSent, ToxFile::SENDING); }
qApp->processEvents(); emit core->fileTransferInfo(file->friendId, file->fileNum, file->filesize, file->bytesSent, ToxFile::SENDING);
long long chunkSize = tox_file_data_size(core->tox, file->friendId); qApp->processEvents();
if (chunkSize == -1) long long chunkSize = tox_file_data_size(core->tox, file->friendId);
{ if (chunkSize == -1)
qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send"); {
file->status = ToxFile::STOPPED; qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send");
emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING); file->status = ToxFile::STOPPED;
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
removeFileFromQueue(true, file->friendId, file->fileNum); tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
return; removeFileFromQueue(true, file->friendId, file->fileNum);
} return;
qDebug() << "chunkSize: " << chunkSize; }
chunkSize = std::min(chunkSize, file->filesize); //qDebug() << "chunkSize: " << chunkSize;
uint8_t* data = new uint8_t[chunkSize]; chunkSize = std::min(chunkSize, file->filesize);
file->file->seek(file->bytesSent); uint8_t* data = new uint8_t[chunkSize];
int readSize = file->file->read((char*)data, chunkSize); file->file->seek(file->bytesSent);
if (readSize == -1) int readSize = file->file->read((char*)data, chunkSize);
{ if (readSize == -1)
qWarning() << QString("Core::sendAllFileData: Error reading from file: %1").arg(file->file->errorString()); {
delete[] data; qWarning() << QString("Core::sendAllFileData: Error reading from file: %1").arg(file->file->errorString());
QThread::msleep(5);
continue;
}
else if (readSize == 0)
{
qWarning() << QString("Core::sendAllFileData: Nothing to read from file: %1").arg(file->file->errorString());
delete[] data;
QThread::msleep(5);
continue;
}
if (tox_file_send_data(core->tox, file->friendId, file->fileNum, data, readSize) == -1)
{
//qWarning("Core::fileHeartbeat: Error sending data chunk");
core->process();
delete[] data;
QThread::msleep(5);
continue;
}
delete[] data; delete[] data;
file->bytesSent += readSize; file->status = ToxFile::STOPPED;
//qDebug() << QString("Core::fileHeartbeat: sent %1/%2 bytes").arg(file->bytesSent).arg(file->fileData.size()); emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
} tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
qDebug("Core::fileHeartbeat: Transfer finished"); removeFileFromQueue(true, file->friendId, file->fileNum);
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0);
file->status = ToxFile::STOPPED;
emit core->fileTransferFinished(*file);
removeFileFromQueue(true, file->friendId, file->fileNum);
}
void Core::onAvInvite(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV invite";
return; return;
} }
else if (readSize == 0)
int transType = toxav_get_peer_transmission_type(toxav, call_index, 0);
if (transType == TypeVideo)
{ {
qDebug() << QString("Core: AV invite from %1 with video").arg(friendId); qWarning() << QString("Core::sendAllFileData: Nothing to read from file: %1").arg(file->file->errorString());
emit static_cast<Core*>(core)->avInvite(friendId, call_index, true); delete[] data;
file->status = ToxFile::STOPPED;
emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
removeFileFromQueue(true, file->friendId, file->fileNum);
return;
}
if (tox_file_send_data(core->tox, file->friendId, file->fileNum, data, readSize) == -1)
{
//qWarning("Core::fileHeartbeat: Error sending data chunk");
//core->process();
delete[] data;
//QThread::msleep(1);
file->sendTimer->start(1+TOX_FILE_INTERVAL);
return;
}
delete[] data;
file->bytesSent += readSize;
//qDebug() << QString("Core::fileHeartbeat: sent %1/%2 bytes").arg(file->bytesSent).arg(file->fileData.size());
if (file->bytesSent < file->filesize)
{
file->sendTimer->start(TOX_FILE_INTERVAL);
return;
} }
else else
{ {
qDebug() << QString("Core: AV invite from %1 without video").arg(friendId); //qDebug("Core: File transfer finished");
emit static_cast<Core*>(core)->avInvite(friendId, call_index, false); file->sendTimer->disconnect();
delete file->sendTimer;
file->sendTimer = nullptr;
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0);
//emit core->fileTransferFinished(*file);
} }
} }
void Core::onAvStart(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV start";
return;
}
int transType = toxav_get_peer_transmission_type(toxav, call_index, 0);
if (transType == TypeVideo)
{
qDebug() << QString("Core: AV start from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
emit static_cast<Core*>(core)->avStart(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV start from %1 without video").arg(friendId);
prepareCall(friendId, call_index, toxav, false);
emit static_cast<Core*>(core)->avStart(friendId, call_index, false);
}
}
void Core::onAvCancel(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV cancel";
return;
}
qDebug() << QString("Core: AV cancel from %1").arg(friendId);
emit static_cast<Core*>(core)->avCancel(friendId, call_index);
}
void Core::onAvReject(void*, int32_t, void*)
{
qDebug() << "Core: AV reject";
}
void Core::onAvEnd(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV end";
return;
}
qDebug() << QString("Core: AV end from %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avEnd(friendId, call_index);
}
void Core::onAvRinging(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV ringing";
return;
}
if (calls[call_index].videoEnabled)
{
qDebug() << QString("Core: AV ringing with %1 with video").arg(friendId);
emit static_cast<Core*>(core)->avRinging(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV ringing with %1 without video").arg(friendId);
emit static_cast<Core*>(core)->avRinging(friendId, call_index, false);
}
}
void Core::onAvStarting(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV starting";
return;
}
int transType = toxav_get_peer_transmission_type(toxav, call_index, 0);
if (transType == TypeVideo)
{
qDebug() << QString("Core: AV starting from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV starting from %1 without video").arg(friendId);
prepareCall(friendId, call_index, toxav, false);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, false);
}
}
void Core::onAvEnding(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV ending";
return;
}
qDebug() << QString("Core: AV ending from %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avEnding(friendId, call_index);
}
void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV request timeout";
return;
}
qDebug() << QString("Core: AV request timeout with %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avRequestTimeout(friendId, call_index);
}
void Core::onAvPeerTimeout(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV peer timeout";
return;
}
qDebug() << QString("Core: AV peer timeout with %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avPeerTimeout(friendId, call_index);
}
void Core::onAvMediaChange(void*, int32_t, void*)
{
// HALP, PLS COMPLETE MEH
}
void Core::answerCall(int callId)
{
int friendId = toxav_get_peer_id(toxav, callId, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV answer peer ID";
return;
}
int transType = toxav_get_peer_transmission_type(toxav, callId, 0);
if (transType == TypeVideo)
{
qDebug() << QString("Core: answering call %1 with video").arg(callId);
toxav_answer(toxav, callId, TypeVideo);
}
else
{
qDebug() << QString("Core: answering call %1 without video").arg(callId);
toxav_answer(toxav, callId, TypeAudio);
}
}
void Core::hangupCall(int callId)
{
qDebug() << QString("Core: hanging up call %1").arg(callId);
calls[callId].active = false;
toxav_hangup(toxav, callId);
}
void Core::startCall(int friendId, bool video)
{
int callId;
if (video)
{
qDebug() << QString("Core: Starting new call with %1 with video").arg(friendId);
toxav_call(toxav, &callId, friendId, TypeVideo, TOXAV_RINGING_TIME);
calls[callId].videoEnabled=true;
}
else
{
qDebug() << QString("Core: Starting new call with %1 without video").arg(friendId);
toxav_call(toxav, &callId, friendId, TypeAudio, TOXAV_RINGING_TIME);
calls[callId].videoEnabled=false;
}
}
void Core::cancelCall(int callId, int friendId)
{
qDebug() << QString("Core: Cancelling call with %1").arg(friendId);
calls[callId].active = false;
toxav_cancel(toxav, callId, friendId, 0);
}
void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled)
{
qDebug() << QString("Core: preparing call %1").arg(callId);
calls[callId].callId = callId;
calls[callId].friendId = friendId;
calls[callId].codecSettings = av_DefaultSettings;
calls[callId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH;
calls[callId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
calls[callId].videoEnabled = videoEnabled;
toxav_prepare_transmission(toxav, callId, &calls[callId].codecSettings, videoEnabled);
// Prepare output
QAudioFormat format;
format.setSampleRate(calls[callId].codecSettings.audio_sample_rate);
format.setChannelCount(calls[callId].codecSettings.audio_channels);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if (!QAudioDeviceInfo::defaultOutputDevice().isFormatSupported(format))
{
calls[callId].audioOutput = nullptr;
qWarning() << "Core: Raw audio format not supported by output backend, cannot play audio.";
}
else if (calls[callId].audioOutput==nullptr)
{
calls[callId].audioOutput = new QAudioOutput(format);
calls[callId].audioOutput->setBufferSize(1900*30); // Make this bigger to get less underflows, but more latency
calls[callId].audioOutput->start(&calls[callId].audioBuffer);
int error = calls[callId].audioOutput->error();
if (error != QAudio::NoError)
{
qWarning() << QString("Core: Error %1 when starting audio output").arg(error);
}
}
// Start input
if (!QAudioDeviceInfo::defaultInputDevice().isFormatSupported(format))
{
calls[callId].audioInput = nullptr;
qWarning() << "Default input format not supported, cannot record audio";
}
else if (calls[callId].audioInput==nullptr)
{
calls[callId].audioInput = new QAudioInput(format);
calls[callId].audioInputDevice = calls[callId].audioInput->start();
}
// Go
calls[callId].active = true;
if (calls[callId].audioInput != nullptr)
{
calls[callId].sendAudioTimer->setInterval(2);
calls[callId].sendAudioTimer->setSingleShot(true);
connect(calls[callId].sendAudioTimer, &QTimer::timeout, [=](){sendCallAudio(callId,toxav);});
calls[callId].sendAudioTimer->start();
}
if (calls[callId].videoEnabled)
{
calls[callId].sendVideoTimer->setInterval(50);
calls[callId].sendVideoTimer->setSingleShot(true);
calls[callId].sendVideoTimer->start();
Widget::getInstance()->getCamera()->suscribe();
}
}
void Core::cleanupCall(int callId)
{
qDebug() << QString("Core: cleaning up call %1").arg(callId);
calls[callId].active = false;
disconnect(calls[callId].sendAudioTimer,0,0,0);
calls[callId].sendAudioTimer->stop();
calls[callId].sendVideoTimer->stop();
if (calls[callId].audioOutput != nullptr)
{
calls[callId].audioOutput->stop();
}
if (calls[callId].audioInput != nullptr)
{
calls[callId].audioInput->stop();
}
if (calls[callId].videoEnabled)
Widget::getInstance()->getCamera()->unsuscribe();
calls[callId].audioBuffer.clear();
}
void Core::playCallAudio(ToxAv*, int32_t callId, int16_t *data, int length)
{
if (!calls[callId].active)
return;
calls[callId].audioBuffer.write((char*)data, length*2);
int state = calls[callId].audioOutput->state();
if (state != QAudio::ActiveState)
{
qDebug() << QString("Core: Audio state is %1").arg(state);
calls[callId].audioOutput->start(&calls[callId].audioBuffer);
}
int error = calls[callId].audioOutput->error();
if (error != QAudio::NoError)
qWarning() << QString("Core::playCallAudio: Error: %1").arg(error);
}
void Core::sendCallAudio(int callId, ToxAv* toxav)
{
if (!calls[callId].active)
return;
int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000;
uint8_t buf[framesize*2], dest[framesize*2];
int bytesReady = calls[callId].audioInput->bytesReady();
if (bytesReady >= framesize*2)
{
calls[callId].audioInputDevice->read((char*)buf, framesize*2);
int result = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize);
if (result < 0)
{
qWarning() << QString("Core: Unable to prepare audio frame, error %1").arg(result);
calls[callId].sendAudioTimer->start();
return;
}
result = toxav_send_audio(toxav, callId, dest, result);
if (result < 0)
{
qWarning() << QString("Core: Unable to send audio frame, error %1").arg(result);
calls[callId].sendAudioTimer->start();
return;
}
calls[callId].sendAudioTimer->start();
}
else
calls[callId].sendAudioTimer->start();
}
void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img)
{
if (!calls[callId].active || !calls[callId].videoEnabled)
return;
if (videoBusyness >= 1)
qWarning() << "Core: playCallVideo: Busy, dropping current frame";
else
emit Widget::getInstance()->getCore()->videoFrameReceived(img);
vpx_img_free(img);
}
void Core::sendCallVideo(int callId)
{
if (!calls[callId].active || !calls[callId].videoEnabled)
return;
vpx_image frame = camera->getLastVPXImage();
if (frame.w && frame.h)
{
int result;
if((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0)
{
qDebug() << QString("Core: toxav_prepare_video_frame: error %1").arg(result);
vpx_img_free(&frame);
calls[callId].sendVideoTimer->start();
return;
}
if((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0)
qDebug() << QString("Core: toxav_send_video error: %1").arg(result);
vpx_img_free(&frame);
}
else
qDebug("Core::sendCallVideo: Invalid frame (bad camera ?)");
calls[callId].sendVideoTimer->start();
}
void Core::groupInviteFriend(int friendId, int groupId) void Core::groupInviteFriend(int friendId, int groupId)
{ {
tox_invite_friend(tox, friendId, groupId); tox_invite_friend(tox, friendId, groupId);
@ -1471,12 +1173,18 @@ void Core::createGroup()
emit emptyGroupCreated(tox_add_groupchat(tox)); emit emptyGroupCreated(tox_add_groupchat(tox));
} }
void Core::increaseVideoBusyness() QString Core::getFriendAddress(int friendNumber) const
{ {
videoBusyness++; // If we don't know the full address of the client, return just the id, otherwise get the full address
} uint8_t rawid[TOX_CLIENT_ID_SIZE];
tox_get_client_id(tox, friendNumber, rawid);
QByteArray data((char*)rawid,TOX_CLIENT_ID_SIZE);
QString id = data.toHex().toUpper();
void Core::decreaseVideoBusyness() QList<QString>& friendAddresses = Settings::getInstance().friendAddresses;
{ for (QString addr : friendAddresses)
videoBusyness--; if (addr.toUpper().contains(id))
return addr;
return id;
} }

49
core.h
View File

@ -17,11 +17,17 @@
#ifndef CORE_HPP #ifndef CORE_HPP
#define CORE_HPP #define CORE_HPP
#include "audiobuffer.h"
#include <tox/tox.h> #include <tox/tox.h>
#include <tox/toxav.h> #include <tox/toxav.h>
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include <cstdint> #include <cstdint>
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
@ -30,16 +36,12 @@
#include <QFile> #include <QFile>
#include <QList> #include <QList>
#include <QByteArray> #include <QByteArray>
#include <QFuture>
#include <QBuffer>
#include <QAudioOutput>
#include <QAudioInput>
#define TOXAV_MAX_CALLS 16 #define TOXAV_MAX_CALLS 16
#define GROUPCHAT_MAX_SIZE 32 #define GROUPCHAT_MAX_SIZE 32
#define TOX_SAVE_INTERVAL 30*1000 #define TOX_SAVE_INTERVAL 30*1000
#define TOX_FILE_INTERVAL 20 #define TOX_FILE_INTERVAL 0
#define TOX_BOOTSTRAP_INTERVAL 10*1000 #define TOX_BOOTSTRAP_INTERVAL 5*1000
#define TOXAV_RINGING_TIME 15 #define TOXAV_RINGING_TIME 15
// TODO: Put that in the settings // TODO: Put that in the settings
@ -76,7 +78,7 @@ struct ToxFile
ToxFile()=default; ToxFile()=default;
ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction) ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction)
: fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)}, : fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)},
bytesSent{0}, filesize{0}, status{STOPPED}, direction{Direction} {} bytesSent{0}, filesize{0}, status{STOPPED}, direction{Direction}, sendTimer{nullptr} {}
~ToxFile(){} ~ToxFile(){}
void setFilePath(QString path) {filePath=path; file->setFileName(path);} void setFilePath(QString path) {filePath=path; file->setFileName(path);}
bool open(bool write) {return write?file->open(QIODevice::ReadWrite):file->open(QIODevice::ReadOnly);} bool open(bool write) {return write?file->open(QIODevice::ReadWrite):file->open(QIODevice::ReadOnly);}
@ -90,22 +92,20 @@ struct ToxFile
long long filesize; long long filesize;
FileStatus status; FileStatus status;
FileDirection direction; FileDirection direction;
QFuture<void> sendFuture; QTimer* sendTimer;
}; };
struct ToxCall struct ToxCall
{ {
public: public:
AudioBuffer audioBuffer; ToxAvCSettings codecSettings;
QAudioOutput* audioOutput;
QAudioInput* audioInput;
QIODevice* audioInputDevice;
ToxAvCodecSettings codecSettings;
QTimer *sendAudioTimer, *sendVideoTimer; QTimer *sendAudioTimer, *sendVideoTimer;
int callId; int callId;
int friendId; int friendId;
bool videoEnabled; bool videoEnabled;
bool active; bool active;
bool muteMic;
ALuint alSource;
}; };
class Core : public QObject class Core : public QObject
@ -120,6 +120,7 @@ public:
int getGroupNumberPeers(int groupId) const; int getGroupNumberPeers(int groupId) const;
QString getGroupPeerName(int groupId, int peerId) const; QString getGroupPeerName(int groupId, int peerId) const;
QList<QString> getGroupPeerNames(int groupId) const; QList<QString> getGroupPeerNames(int groupId) const;
QString getFriendAddress(int friendNumber) const;
int joinGroupchat(int32_t friendnumber, const uint8_t* friend_group_public_key) const; int joinGroupchat(int32_t friendnumber, const uint8_t* friend_group_public_key) const;
void quitGroupChat(int groupId) const; void quitGroupChat(int groupId) const;
void dispatchVideoFrame(vpx_image img) const; void dispatchVideoFrame(vpx_image img) const;
@ -173,6 +174,8 @@ public slots:
void startCall(int friendId, bool video=false); void startCall(int friendId, bool video=false);
void cancelCall(int callId, int friendId); void cancelCall(int callId, int friendId);
void micMuteToggle(int callId);
signals: signals:
void connected(); void connected();
void disconnected(); void disconnected();
@ -239,7 +242,7 @@ signals:
void avEnding(int friendId, int callIndex); void avEnding(int friendId, int callIndex);
void avRequestTimeout(int friendId, int callIndex); void avRequestTimeout(int friendId, int callIndex);
void avPeerTimeout(int friendId, int callIndex); void avPeerTimeout(int friendId, int callIndex);
void avMediaChange(int friendId, int callIndex); void avMediaChange(int friendId, int callIndex, bool videoEnabled);
void videoFrameReceived(vpx_image* frame); void videoFrameReceived(vpx_image* frame);
@ -271,21 +274,22 @@ private:
static void onAvEnding(void* toxav, int32_t call_index, void* core); static void onAvEnding(void* toxav, int32_t call_index, void* core);
static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core); static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core);
static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core); static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core);
static void onAvMediaChange(void* toxav, int32_t call_index, void* core); static void onAvMediaChange(void *toxav, int32_t call_index, void* core);
static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled); static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled);
static void cleanupCall(int callId); static void cleanupCall(int callId);
static void playCallAudio(ToxAv *toxav, int32_t callId, int16_t *data, int length); // Callback static void playCallAudio(ToxAv *toxav, int32_t callId, int16_t *data, int samples, void *user_data); // Callback
static void sendCallAudio(int callId, ToxAv* toxav); static void sendCallAudio(int callId, ToxAv* toxav);
static void playCallVideo(ToxAv* toxav, int32_t callId, vpx_image_t* img); static void playAudioBuffer(int callId, int16_t *data, int samples, unsigned channels, int sampleRate);
static void playCallVideo(ToxAv* toxav, int32_t callId, vpx_image_t* img, void *user_data);
void sendCallVideo(int callId); void sendCallVideo(int callId);
void checkConnection(); void checkConnection();
void onBootstrapTimer(); void onBootstrapTimer();
void loadFriends(); void loadFriends();
static void sendAllFileData(Core* core, ToxFile* file);
static void sendAllFileData(Core* core, ToxFile* file);
static void removeFileFromQueue(bool sendQueue, int friendId, int fileId); static void removeFileFromQueue(bool sendQueue, int friendId, int fileId);
void checkLastOnline(int friendId); void checkLastOnline(int friendId);
@ -307,6 +311,11 @@ private:
static const int videobufsize; static const int videobufsize;
static uint8_t* videobuf; static uint8_t* videobuf;
static int videoBusyness; // Used to know when to drop frames static int videoBusyness; // Used to know when to drop frames
static ALCdevice* alOutDev, *alInDev;
static ALCcontext* alContext;
public:
static ALuint alMainSource;
}; };
#endif // CORE_HPP #endif // CORE_HPP

540
coreav.cpp Normal file
View File

@ -0,0 +1,540 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 "core.h"
#include "widget/widget.h"
ToxCall Core::calls[TOXAV_MAX_CALLS];
const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4};
uint8_t* Core::videobuf;
int Core::videoBusyness;
ALCdevice* Core::alOutDev, *Core::alInDev;
ALCcontext* Core::alContext;
ALuint Core::alMainSource;
void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled)
{
qDebug() << QString("Core: preparing call %1").arg(callId);
calls[callId].callId = callId;
calls[callId].friendId = friendId;
calls[callId].muteMic = false;
// the following three lines are also now redundant from startCall, but are
// necessary there for outbound and here for inbound
calls[callId].codecSettings = av_DefaultSettings;
calls[callId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH;
calls[callId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
calls[callId].videoEnabled = videoEnabled;
toxav_prepare_transmission(toxav, callId, av_jbufdc, av_VADd, videoEnabled);
// Audio
alGenSources(1, &calls[callId].alSource);
alcCaptureStart(alInDev);
// Go
calls[callId].active = true;
calls[callId].sendAudioTimer->setInterval(5);
calls[callId].sendAudioTimer->setSingleShot(true);
connect(calls[callId].sendAudioTimer, &QTimer::timeout, [=](){sendCallAudio(callId,toxav);});
calls[callId].sendAudioTimer->start();
calls[callId].sendVideoTimer->setInterval(50);
calls[callId].sendVideoTimer->setSingleShot(true);
if (calls[callId].videoEnabled)
{
calls[callId].sendVideoTimer->start();
Widget::getInstance()->getCamera()->suscribe();
}
}
void Core::onAvMediaChange(void* toxav, int32_t callId, void* core)
{
ToxAvCSettings settings;
toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &settings);
int friendId = toxav_get_peer_id((ToxAv*)toxav, callId, 0);
qWarning() << "Core: Received media change from friend "<<friendId;
if (settings.call_type == TypeAudio)
{
calls[callId].videoEnabled = false;
calls[callId].sendVideoTimer->stop();
Widget::getInstance()->getCamera()->unsuscribe();
emit ((Core*)core)->avMediaChange(friendId, callId, false);
}
else
{
Widget::getInstance()->getCamera()->suscribe();
calls[callId].videoEnabled = true;
calls[callId].sendVideoTimer->start();
emit ((Core*)core)->avMediaChange(friendId, callId, true);
}
}
void Core::answerCall(int callId)
{
int friendId = toxav_get_peer_id(toxav, callId, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV answer peer ID";
return;
}
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, callId, 0, transSettings);
if (err != ErrorNone)
{
qWarning() << "Core::answerCall: error getting call settings";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
{
qDebug() << QString("Core: answering call %1 with video").arg(callId);
toxav_answer(toxav, callId, transSettings);
}
else
{
qDebug() << QString("Core: answering call %1 without video").arg(callId);
toxav_answer(toxav, callId, transSettings);
}
delete transSettings;
}
void Core::hangupCall(int callId)
{
qDebug() << QString("Core: hanging up call %1").arg(callId);
calls[callId].active = false;
toxav_hangup(toxav, callId);
}
void Core::startCall(int friendId, bool video)
{
int callId;
ToxAvCSettings cSettings = av_DefaultSettings;
cSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH;
cSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
if (video)
{
qDebug() << QString("Core: Starting new call with %1 with video").arg(friendId);
cSettings.call_type = TypeVideo;
toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME);
calls[callId].videoEnabled=true;
}
else
{
qDebug() << QString("Core: Starting new call with %1 without video").arg(friendId);
cSettings.call_type = TypeAudio;
toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME);
calls[callId].videoEnabled=false;
}
}
void Core::cancelCall(int callId, int friendId)
{
qDebug() << QString("Core: Cancelling call with %1").arg(friendId);
calls[callId].active = false;
toxav_cancel(toxav, callId, friendId, 0);
}
void Core::cleanupCall(int callId)
{
qDebug() << QString("Core: cleaning up call %1").arg(callId);
calls[callId].active = false;
disconnect(calls[callId].sendAudioTimer,0,0,0);
calls[callId].sendAudioTimer->stop();
calls[callId].sendVideoTimer->stop();
if (calls[callId].videoEnabled)
Widget::getInstance()->getCamera()->unsuscribe();
alcCaptureStop(alInDev);
}
void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int samples, void *user_data)
{
Q_UNUSED(user_data);
if (!calls[callId].active)
return;
ToxAvCSettings dest;
if(toxav_get_peer_csettings(toxav, callId, 0, &dest) == 0)
playAudioBuffer(callId, data, samples, dest.audio_channels, dest.audio_sample_rate);
}
void Core::sendCallAudio(int callId, ToxAv* toxav)
{
if (!calls[callId].active)
return;
if (calls[callId].muteMic)
{
calls[callId].sendAudioTimer->start();
return;
}
int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000;
uint8_t buf[framesize*2], dest[framesize*2];
bool frame = false;
ALint samples;
alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if(samples >= framesize)
{
alcCaptureSamples(alInDev, buf, framesize);
frame = 1;
}
if(frame)
{
int r;
if((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0)
{
qDebug() << "Core: toxav_prepare_audio_frame error";
calls[callId].sendAudioTimer->start();
return;
}
if((r = toxav_send_audio(toxav, callId, dest, r)) < 0)
qDebug() << "Core: toxav_send_audio error";
}
calls[callId].sendAudioTimer->start();
}
void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_data)
{
Q_UNUSED(user_data);
if (!calls[callId].active || !calls[callId].videoEnabled)
return;
if (videoBusyness >= 1)
qWarning() << "Core: playCallVideo: Busy, dropping current frame";
else
emit Widget::getInstance()->getCore()->videoFrameReceived(img);
vpx_img_free(img);
}
void Core::sendCallVideo(int callId)
{
if (!calls[callId].active || !calls[callId].videoEnabled)
return;
vpx_image frame = camera->getLastVPXImage();
if (frame.w && frame.h)
{
int result;
if((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0)
{
qDebug() << QString("Core: toxav_prepare_video_frame: error %1").arg(result);
vpx_img_free(&frame);
calls[callId].sendVideoTimer->start();
return;
}
if((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0)
qDebug() << QString("Core: toxav_send_video error: %1").arg(result);
vpx_img_free(&frame);
}
else
{
qDebug("Core::sendCallVideo: Invalid frame (bad camera ?)");
}
calls[callId].sendVideoTimer->start();
}
void Core::increaseVideoBusyness()
{
videoBusyness++;
}
void Core::decreaseVideoBusyness()
{
videoBusyness--;
}
void Core::micMuteToggle(int callId)
{
calls[callId].muteMic = !calls[callId].muteMic;
}
void Core::onAvCancel(void* _toxav, int32_t callId, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, callId, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV cancel";
return;
}
qDebug() << QString("Core: AV cancel from %1").arg(friendId);
calls[callId].active = false;
emit static_cast<Core*>(core)->avCancel(friendId, callId);
}
void Core::onAvReject(void*, int32_t, void*)
{
qDebug() << "Core: AV reject";
}
void Core::onAvEnd(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV end";
return;
}
qDebug() << QString("Core: AV end from %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avEnd(friendId, call_index);
}
void Core::onAvRinging(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV ringing";
return;
}
if (calls[call_index].videoEnabled)
{
qDebug() << QString("Core: AV ringing with %1 with video").arg(friendId);
emit static_cast<Core*>(core)->avRinging(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV ringing with %1 without video").arg(friendId);
emit static_cast<Core*>(core)->avRinging(friendId, call_index, false);
}
}
void Core::onAvStarting(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV starting";
return;
}
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
{
qWarning() << "Core::onAvStarting: error getting call type";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
{
qDebug() << QString("Core: AV starting from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV starting from %1 without video").arg(friendId);
prepareCall(friendId, call_index, toxav, false);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, false);
}
delete transSettings;
}
void Core::onAvEnding(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV ending";
return;
}
qDebug() << QString("Core: AV ending from %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avEnding(friendId, call_index);
}
void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV request timeout";
return;
}
qDebug() << QString("Core: AV request timeout with %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avRequestTimeout(friendId, call_index);
}
void Core::onAvPeerTimeout(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV peer timeout";
return;
}
qDebug() << QString("Core: AV peer timeout with %1").arg(friendId);
cleanupCall(call_index);
emit static_cast<Core*>(core)->avPeerTimeout(friendId, call_index);
}
void Core::onAvInvite(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV invite";
return;
}
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
{
qWarning() << "Core::onAvInvite: error getting call type";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
{
qDebug() << QString("Core: AV invite from %1 with video").arg(friendId);
emit static_cast<Core*>(core)->avInvite(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV invite from %1 without video").arg(friendId);
emit static_cast<Core*>(core)->avInvite(friendId, call_index, false);
}
delete transSettings;
}
void Core::onAvStart(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV start";
return;
}
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
{
qWarning() << "Core::onAvStart: error getting call type";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
{
qDebug() << QString("Core: AV start from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
emit static_cast<Core*>(core)->avStart(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV start from %1 without video").arg(friendId);
prepareCall(friendId, call_index, toxav, false);
emit static_cast<Core*>(core)->avStart(friendId, call_index, false);
}
delete transSettings;
}
// This function's logic was shamelessly stolen from uTox
void Core::playAudioBuffer(int callId, int16_t *data, int samples, unsigned channels, int sampleRate)
{
if(!channels || channels > 2)
{
qWarning() << "Core::playAudioBuffer: trying to play on "<<channels<<" channels! Giving up.";
return;
}
ALuint bufid;
ALint processed, queued;
alGetSourcei(calls[callId].alSource, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(calls[callId].alSource, AL_BUFFERS_QUEUED, &queued);
alSourcei(calls[callId].alSource, AL_LOOPING, AL_FALSE);
if(processed)
{
ALuint bufids[processed];
alSourceUnqueueBuffers(calls[callId].alSource, processed, bufids);
alDeleteBuffers(processed - 1, bufids + 1);
bufid = bufids[0];
}
else if(queued < 16)
{
alGenBuffers(1, &bufid);
}
else
{
qDebug() << "Core: Dropped audio frame";
return;
}
alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data,
samples * 2 * channels, sampleRate);
alSourceQueueBuffers(calls[callId].alSource, 1, &bufid);
ALint state;
alGetSourcei(calls[callId].alSource, AL_SOURCE_STATE, &state);
if(state != AL_PLAYING)
{
alSourcePlay(calls[callId].alSource);
qDebug() << "Core: Starting audio source of call " << callId;
}
}

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
qtox (0.01pre-alpha-1) UNRELEASED; urgency=medium local package
* Initial release.
-- John Smith <barrdetwix@gmail.com> Sat, 30 Aug 2014 23:27:47 +0200

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

14
debian/control vendored Normal file
View File

@ -0,0 +1,14 @@
Source: qtox
Maintainer: John Smith <barrdetwix@gmail.com>
Section: misc
Priority: optional
Standards-Version: 3.9.5
Build-Depends: debhelper (>= 9), cdbs, qt5-qmake, qt5-default, libopenal-dev, libopencv-dev, libopus-dev
Package: qtox
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Tox client
qTox is a powerful Tox client that follows the Tox design guidelines.
Tox is a decentralized and encrypted replacement for Skype, supporting
chats, audio, and video calls.

28
debian/copyright vendored Normal file
View File

@ -0,0 +1,28 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: qtox
Upstream-Contact: John Smith <barrdetwix@gmail.com>
Source: https://github.com/tux3/qTox
Files: *
Copyright: 2014 John Smith <barrdetwix@gmail.com>
License: GPL-3+
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 (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 GNU General Public License for more
details.
.
You should have received a copy of the GNU General Public
License along with this package; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor,
Boston, MA 02110-1301 USA
.
On Debian systems, the full text of the GNU General Public
License version 3 can be found in the file
`/usr/share/common-licenses/GPL-3'.

6
debian/rules vendored Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/make -f
include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/qmake.mk
QMAKE=qmake STATICPKG=YES

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

View File

@ -23,7 +23,7 @@ Friend::Friend(int FriendId, QString UserId)
{ {
widget = new FriendWidget(friendId, userId); widget = new FriendWidget(friendId, userId);
chatForm = new ChatForm(this); chatForm = new ChatForm(this);
hasNewMessages = 0; hasNewEvents = 0;
friendStatus = Status::Offline; friendStatus = Status::Offline;
} }

View File

@ -36,7 +36,7 @@ public:
int friendId; int friendId;
QString userId; QString userId;
ChatForm* chatForm; ChatForm* chatForm;
int hasNewMessages; int hasNewEvents;
Status friendStatus; Status friendStatus;
QPixmap avatar; QPixmap avatar;
}; };

View File

@ -17,6 +17,7 @@
#include "friend.h" #include "friend.h"
#include "friendlist.h" #include "friendlist.h"
#include <QMenu> #include <QMenu>
#include <QDebug>
QList<Friend*> FriendList::friendList; QList<Friend*> FriendList::friendList;

View File

@ -58,7 +58,6 @@ int main(int argc, char *argv[])
* Most cameras use YUYV, implement YUYV -> YUV240 * Most cameras use YUYV, implement YUYV -> YUV240
* Sending large files (~380MB) "restarts" after ~10MB. Goes back to 0%, consumes twice as much ram (reloads the file?) * Sending large files (~380MB) "restarts" after ~10MB. Goes back to 0%, consumes twice as much ram (reloads the file?)
* => Don't load the whole file at once, load small chunks (25MB?) when needed, then free them and load the next * => Don't load the whole file at once, load small chunks (25MB?) when needed, then free them and load the next
* Sort the friend list by status, online first then busy then offline
* Don't do anything if a friend is disconnected, don't print to the chat * Don't do anything if a friend is disconnected, don't print to the chat
* Changing online/away/busy/offline by clicking the bubble * Changing online/away/busy/offline by clicking the bubble
* /me action messages * /me action messages

3262
mainwindow.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,12 +20,13 @@
# See the COPYING file for more details. # See the COPYING file for more details.
QT += core gui network multimedia multimediawidgets QT += core gui network xml
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtox TARGET = qtox
TEMPLATE = app TEMPLATE = app
FORMS += widget.ui FORMS += \
mainwindow.ui
CONFIG += c++11 CONFIG += c++11
TRANSLATIONS = translations/de.ts \ TRANSLATIONS = translations/de.ts \
@ -35,16 +36,33 @@ TRANSLATIONS = translations/de.ts \
RESOURCES += res.qrc RESOURCES += res.qrc
target.path = /usr/local/bin contains(JENKINS,YES) {
INSTALLS += target INCLUDEPATH += ./libs/include/
INCLUDEPATH += libs/include
win32 {
LIBS += $$PWD/libs/lib/libtoxav.a $$PWD/libs/lib/libopus.a $$PWD/libs/lib/libvpx.a $$PWD/libs/lib/libtoxcore.a -lws2_32 $$PWD/libs/lib/libsodium.a -lpthread
} else { } else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lsodium -lvpx INCLUDEPATH += libs/include
} }
# Rules for Windows, Mac OSX, and Linux
win32 {
LIBS += $$PWD/libs/lib/libtoxav.a $$PWD/libs/lib/libopus.a $$PWD/libs/lib/libvpx.a $$PWD/libs/lib/libopenal32.a $$PWD/libs/lib/libtoxcore.a -lws2_32 $$PWD/libs/lib/libsodium.a -lpthread -liphlpapi
} macx {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui
} else {
# If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package
contains(STATICPKG, YES) {
target.path = /usr/bin
INSTALLS += target
LIBS += -L$$PWD/libs/lib/ -Wl,-Bstatic -ltoxcore -ltoxav -lsodium -Wl,-Bdynamic -lopus -lvpx -lopenal -lopencv_core -lopencv_highgui
} else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui
}
contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a -lopencv_core -lopencv_highgui -lopenal
}
}
#### Static linux build #### Static linux build
#LIBS += -Wl,-Bstatic -ltoxcore -ltoxav -lsodium -lvpx -lopus \ #LIBS += -Wl,-Bstatic -ltoxcore -ltoxav -lsodium -lvpx -lopus \
# -lgstbase-0.10 -lgstreamer-0.10 -lgmodule-2.0 -lgstaudio-0.10 -lxml2 \ # -lgstbase-0.10 -lgstreamer-0.10 -lgmodule-2.0 -lgstaudio-0.10 -lxml2 \
@ -61,10 +79,6 @@ HEADERS += widget/form/addfriendform.h \
widget/form/settingsform.h \ widget/form/settingsform.h \
widget/form/filesform.h \ widget/form/filesform.h \
widget/tool/chattextedit.h \ widget/tool/chattextedit.h \
widget/tool/copyableelidelabel.h \
widget/tool/editablelabelwidget.h \
widget/tool/elidelabel.h \
widget/tool/esclineedit.h \
widget/tool/friendrequestdialog.h \ widget/tool/friendrequestdialog.h \
widget/filetransfertwidget.h \ widget/filetransfertwidget.h \
widget/friendwidget.h \ widget/friendwidget.h \
@ -78,12 +92,16 @@ HEADERS += widget/form/addfriendform.h \
friendlist.h \ friendlist.h \
cdata.h \ cdata.h \
cstring.h \ cstring.h \
audiobuffer.h \
widget/selfcamview.h \ widget/selfcamview.h \
widget/videosurface.h \
widget/camera.h \ widget/camera.h \
widget/netcamview.h \ widget/netcamview.h \
widget/tool/clickablelabel.h smileypack.h \
widget/emoticonswidget.h \
style.h \
widget/adjustingscrollarea.h \
widget/croppinglabel.h \
widget/friendlistwidget.h \
widget/genericchatroomwidget.h
SOURCES += \ SOURCES += \
widget/form/addfriendform.cpp \ widget/form/addfriendform.cpp \
@ -92,10 +110,6 @@ SOURCES += \
widget/form/settingsform.cpp \ widget/form/settingsform.cpp \
widget/form/filesform.cpp \ widget/form/filesform.cpp \
widget/tool/chattextedit.cpp \ widget/tool/chattextedit.cpp \
widget/tool/copyableelidelabel.cpp \
widget/tool/editablelabelwidget.cpp \
widget/tool/elidelabel.cpp \
widget/tool/esclineedit.cpp \
widget/tool/friendrequestdialog.cpp \ widget/tool/friendrequestdialog.cpp \
widget/filetransfertwidget.cpp \ widget/filetransfertwidget.cpp \
widget/friendwidget.cpp \ widget/friendwidget.cpp \
@ -110,9 +124,14 @@ SOURCES += \
settings.cpp \ settings.cpp \
cdata.cpp \ cdata.cpp \
cstring.cpp \ cstring.cpp \
audiobuffer.cpp \
widget/selfcamview.cpp \ widget/selfcamview.cpp \
widget/videosurface.cpp \
widget/camera.cpp \ widget/camera.cpp \
widget/netcamview.cpp \ widget/netcamview.cpp \
widget/tool/clickablelabel.cpp smileypack.cpp \
widget/emoticonswidget.cpp \
style.cpp \
widget/adjustingscrollarea.cpp \
widget/croppinglabel.cpp \
widget/friendlistwidget.cpp \
coreav.cpp \
widget/genericchatroomwidget.cpp

13
res.qrc
View File

@ -111,5 +111,18 @@
<file>ui/statusButton/menu_indicator.png</file> <file>ui/statusButton/menu_indicator.png</file>
<file>translations/de.qm</file> <file>translations/de.qm</file>
<file>translations/it.qm</file> <file>translations/it.qm</file>
<file>ui/emoticonWidget/dot_page.png</file>
<file>ui/emoticonWidget/dot_page_current.png</file>
<file>ui/emoticonWidget/emoticonWidget.css</file>
<file>ui/emoticonWidget/dot_page_hover.png</file>
<file>ui/volButton/volButton.png</file>
<file>ui/volButton/volButtonHover.png</file>
<file>ui/volButton/volButtonPressed.png</file>
<file>ui/micButton/micButton.png</file>
<file>ui/micButton/micButtonDisabled.png</file>
<file>ui/micButton/micButtonHover.png</file>
<file>ui/micButton/micButtonPressed.png</file>
<file>ui/micButton/micButton.css</file>
<file>ui/volButton/volButton.css</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -1,5 +1,5 @@
[DHT%20Server] [DHT%20Server]
dhtServerList\size=16 dhtServerList\size=9
dhtServerList\1\name=stqism dhtServerList\1\name=stqism
dhtServerList\1\userId=951C88B7E75C867418ACDB5D273821372BB5BD652740BCDF623A4FA293E75D2F dhtServerList\1\userId=951C88B7E75C867418ACDB5D273821372BB5BD652740BCDF623A4FA293E75D2F
dhtServerList\1\address=192.254.75.98 dhtServerList\1\address=192.254.75.98
@ -12,55 +12,27 @@ dhtServerList\3\name=stal
dhtServerList\3\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074 dhtServerList\3\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074
dhtServerList\3\address=23.226.230.47 dhtServerList\3\address=23.226.230.47
dhtServerList\3\port=33445 dhtServerList\3\port=33445
dhtServerList\4\name=ChauffeR dhtServerList\4\name=aitjcize
dhtServerList\4\userId=4FD54CFD426A338399767E56FD0F44F5E35FA8C38C8E87C8DC3FEAC0160F8E1 dhtServerList\4\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029
dhtServerList\4\address=37.187.20.216 dhtServerList\4\address=54.199.139.199
dhtServerList\4\port=33445 dhtServerList\4\port=33445
dhtServerList\5\name=aitjcize dhtServerList\5\name=astonex
dhtServerList\5\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029 dhtServerList\5\userId=B98A2CEAA6C6A2FADC2C3632D284318B60FE5375CCB41EFA081AB67F500C1B0B
dhtServerList\5\address=54.199.139.199 dhtServerList\5\address=37.59.102.176
dhtServerList\5\port=33445 dhtServerList\5\port=33445
dhtServerList\6\name=astonex dhtServerList\6\name=nurupo
dhtServerList\6\userId=7F31BFC93B8E4016A902144D0B110C3EA97CB7D43F1C4D21BCAE998A7C838821 dhtServerList\6\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67
dhtServerList\6\address=109.169.46.133 dhtServerList\6\address=192.210.149.121
dhtServerList\6\port=33445 dhtServerList\6\port=33445
dhtServerList\7\name=nurupo dhtServerList\7\name=mousey
dhtServerList\7\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67 dhtServerList\7\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331
dhtServerList\7\address=192.210.149.121 dhtServerList\7\address=37.187.46.132
dhtServerList\7\port=33445 dhtServerList\7\port=33445
dhtServerList\8\name=mousey dhtServerList\8\name=Proplex
dhtServerList\8\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331 dhtServerList\8\userId=7BE3951B97CA4B9ECDDA768E8C52BA19E9E2690AB584787BF4C90E04DBB75111
dhtServerList\8\address=37.187.46.132 dhtServerList\8\address=107.161.17.51
dhtServerList\8\port=33445 dhtServerList\8\port=33445
dhtServerList\9\name=zlacki NL dhtServerList\9\name=SylvieLorxu
dhtServerList\9\userId=CC2B02636A2ADBC2871D6EC57C5E9589D4FD5E6F98A14743A4B949914CF26D39 dhtServerList\9\userId=4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057
dhtServerList\9\address=5.39.218.35 dhtServerList\9\address=178.21.112.187
dhtServerList\9\port=33445 dhtServerList\9\port=33445
dhtServerList\10\name=zlacki RU #2
dhtServerList\10\userId=AE27E1E72ADA3DC423C60EEBACA241456174048BE76A283B41AD32D953182D49
dhtServerList\10\address=193.107.16.73
dhtServerList\10\port=33445
dhtServerList\11\name=platos
dhtServerList\11\userId=B24E2FB924AE66D023FE1E42A2EE3B432010206F751A2FFD3E297383ACF1572E
dhtServerList\11\address=66.175.223.88
dhtServerList\11\port=33445
dhtServerList\12\name=JmanGuy
dhtServerList\12\userId=20C797E098701A848B07D0384222416B0EFB60D08CECB925B860CAEAAB572067
dhtServerList\12\address=66.74.15.98
dhtServerList\12\port=33445
dhtServerList\13\name=anonymous
dhtServerList\13\userId=5CD7EB176C19A2FD840406CD56177BB8E75587BB366F7BB3004B19E3EDC04143
dhtServerList\13\address=192.184.81.118
dhtServerList\13\port=33445
dhtServerList\14\name=benwaffle
dhtServerList\14\userId=8E6667FF967EA30B3DC3DB57A4B533152476E7AAE090158B9C2D9DF58ECC7B78
dhtServerList\14\address=192.3.30.132
dhtServerList\14\port=33445
dhtServerList\15\name=zlacki RU #1
dhtServerList\15\userId=D59F99384592DE4C8AB9D534D5197DB90F4755CC9E975ED0C565E18468A1445B
dhtServerList\15\address=31.192.105.19
dhtServerList\15\port=33445
dhtServerList\16\name=zlacki US
dhtServerList\16\userId=9430A83211A7AD1C294711D069D587028CA0B4782FA43CB9B30008247A43C944
dhtServerList\16\address=69.42.220.58
dhtServerList\16\port=33445

View File

@ -15,6 +15,8 @@
*/ */
#include "settings.h" #include "settings.h"
#include "smileypack.h"
#include "widget/widget.h"
#include <QApplication> #include <QApplication>
#include <QDir> #include <QDir>
@ -22,6 +24,7 @@
#include <QSettings> #include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include <QDebug> #include <QDebug>
#include <QList>
const QString Settings::FILENAME = "settings.ini"; const QString Settings::FILENAME = "settings.ini";
bool Settings::makeToxPortable{false}; bool Settings::makeToxPortable{false};
@ -53,7 +56,7 @@ void Settings::load()
if (portableSettings.exists()) if (portableSettings.exists())
makeToxPortable=true; makeToxPortable=true;
QString filePath = getSettingsDirPath() + '/' + FILENAME; QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
//if no settings file exist -- use the default one //if no settings file exist -- use the default one
QFile file(filePath); QFile file(filePath);
@ -77,6 +80,16 @@ void Settings::load()
s.endArray(); s.endArray();
s.endGroup(); s.endGroup();
friendAddresses.clear();
s.beginGroup("Friends");
int size = s.beginReadArray("fullAddresses");
for (int i = 0; i < size; i ++) {
s.setArrayIndex(i);
friendAddresses.append(s.value("addr").toString());
}
s.endArray();
s.endGroup();
s.beginGroup("General"); s.beginGroup("General");
enableIPv6 = s.value("enableIPv6", true).toBool(); enableIPv6 = s.value("enableIPv6", true).toBool();
useTranslations = s.value("useTranslations", true).toBool(); useTranslations = s.value("useTranslations", true).toBool();
@ -93,7 +106,7 @@ void Settings::load()
s.beginGroup("GUI"); s.beginGroup("GUI");
enableSmoothAnimation = s.value("smoothAnimation", true).toBool(); enableSmoothAnimation = s.value("smoothAnimation", true).toBool();
smileyPack = s.value("smileyPack").toByteArray(); smileyPack = s.value("smileyPack", QString()).toString();
customEmojiFont = s.value("customEmojiFont", true).toBool(); customEmojiFont = s.value("customEmojiFont", true).toBool();
emojiFontFamily = s.value("emojiFontFamily", "DejaVu Sans").toString(); emojiFontFamily = s.value("emojiFontFamily", "DejaVu Sans").toString();
emojiFontPointSize = s.value("emojiFontPointSize", QApplication::font().pointSize()).toInt(); emojiFontPointSize = s.value("emojiFontPointSize", QApplication::font().pointSize()).toInt();
@ -101,18 +114,30 @@ void Settings::load()
secondColumnHandlePosFromRight = s.value("secondColumnHandlePosFromRight", 50).toInt(); secondColumnHandlePosFromRight = s.value("secondColumnHandlePosFromRight", 50).toInt();
timestampFormat = s.value("timestampFormat", "hh:mm").toString(); timestampFormat = s.value("timestampFormat", "hh:mm").toString();
minimizeOnClose = s.value("minimizeOnClose", false).toBool(); minimizeOnClose = s.value("minimizeOnClose", false).toBool();
useNativeStyle = s.value("nativeStyle", false).toBool();
useNativeDecoration = s.value("nativeDecoration", true).toBool();
s.endGroup();
s.beginGroup("State");
windowGeometry = s.value("windowGeometry", QByteArray()).toByteArray();
windowState = s.value("windowState", QByteArray()).toByteArray();
splitterState = s.value("splitterState", QByteArray()).toByteArray();
s.endGroup(); s.endGroup();
s.beginGroup("Privacy"); s.beginGroup("Privacy");
typingNotification = s.value("typingNotification", false).toBool(); typingNotification = s.value("typingNotification", false).toBool();
s.endGroup(); s.endGroup();
// try to set a smiley pack if none is selected
if (!SmileyPack::isValid(smileyPack) && !SmileyPack::listSmileyPacks().isEmpty())
smileyPack = SmileyPack::listSmileyPacks()[0].second;
loaded = true; loaded = true;
} }
void Settings::save() void Settings::save()
{ {
QString filePath = getSettingsDirPath() + '/' + FILENAME; QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
save(filePath); save(filePath);
} }
@ -134,6 +159,15 @@ void Settings::save(QString path)
s.endArray(); s.endArray();
s.endGroup(); s.endGroup();
s.beginGroup("Friends");
s.beginWriteArray("fullAddresses", friendAddresses.size());
for (int i = 0; i < friendAddresses.size(); i ++) {
s.setArrayIndex(i);
s.setValue("addr", friendAddresses[i]);
}
s.endArray();
s.endGroup();
s.beginGroup("General"); s.beginGroup("General");
s.setValue("enableIPv6", enableIPv6); s.setValue("enableIPv6", enableIPv6);
s.setValue("useTranslations",useTranslations); s.setValue("useTranslations",useTranslations);
@ -158,6 +192,14 @@ void Settings::save(QString path)
s.setValue("secondColumnHandlePosFromRight", secondColumnHandlePosFromRight); s.setValue("secondColumnHandlePosFromRight", secondColumnHandlePosFromRight);
s.setValue("timestampFormat", timestampFormat); s.setValue("timestampFormat", timestampFormat);
s.setValue("minimizeOnClose", minimizeOnClose); s.setValue("minimizeOnClose", minimizeOnClose);
s.setValue("nativeStyle", useNativeStyle);
s.setValue("nativeDecoration", useNativeDecoration);
s.endGroup();
s.beginGroup("State");
s.setValue("windowGeometry", windowGeometry);
s.setValue("windowState", windowState);
s.setValue("splitterState", splitterState);
s.endGroup(); s.endGroup();
s.beginGroup("Privacy"); s.beginGroup("Privacy");
@ -174,7 +216,7 @@ QString Settings::getSettingsDirPath()
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
#else #else
return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + '/' + "tox" + '/'; return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox");
#endif #endif
} }
@ -208,6 +250,8 @@ void Settings::setMakeToxPortable(bool newValue)
{ {
makeToxPortable = newValue; makeToxPortable = newValue;
save(FILENAME); // Commit to the portable file that we don't want to use it save(FILENAME); // Commit to the portable file that we don't want to use it
if (!newValue) // Update the new file right now if not already done
save();
} }
QString Settings::getCurrentProfile() const QString Settings::getCurrentProfile() const
@ -270,12 +314,12 @@ void Settings::setAnimationEnabled(bool newValue)
enableSmoothAnimation = newValue; enableSmoothAnimation = newValue;
} }
QByteArray Settings::getSmileyPack() const QString Settings::getSmileyPack() const
{ {
return smileyPack; return smileyPack;
} }
void Settings::setSmileyPack(const QByteArray &value) void Settings::setSmileyPack(const QString &value)
{ {
smileyPack = value; smileyPack = value;
emit smileyPackChanged(); emit smileyPackChanged();
@ -345,6 +389,56 @@ void Settings::setEmojiFontFamily(const QString &value)
emit emojiFontChanged(); emit emojiFontChanged();
} }
bool Settings::getUseNativeStyle() const
{
return useNativeStyle;
}
void Settings::setUseNativeStyle(bool value)
{
useNativeStyle = value;
}
bool Settings::getUseNativeDecoration() const
{
return useNativeDecoration;
}
void Settings::setUseNativeDecoration(bool value)
{
useNativeDecoration = value;
}
QByteArray Settings::getWindowGeometry() const
{
return windowGeometry;
}
void Settings::setWindowGeometry(const QByteArray &value)
{
windowGeometry = value;
}
QByteArray Settings::getWindowState() const
{
return windowState;
}
void Settings::setWindowState(const QByteArray &value)
{
windowState = value;
}
QByteArray Settings::getSplitterState() const
{
return splitterState;
}
void Settings::setSplitterState(const QByteArray &value)
{
splitterState = value;
}
bool Settings::isMinimizeOnCloseEnabled() const bool Settings::isMinimizeOnCloseEnabled() const
{ {
return minimizeOnClose; return minimizeOnClose;

View File

@ -86,8 +86,8 @@ public:
bool isAnimationEnabled() const; bool isAnimationEnabled() const;
void setAnimationEnabled(bool newValue); void setAnimationEnabled(bool newValue);
QByteArray getSmileyPack() const; QString getSmileyPack() const;
void setSmileyPack(const QByteArray &value); void setSmileyPack(const QString &value);
bool isCurstomEmojiFont() const; bool isCurstomEmojiFont() const;
void setCurstomEmojiFont(bool value); void setCurstomEmojiFont(bool value);
@ -115,16 +115,31 @@ public:
bool isTypingNotificationEnabled() const; bool isTypingNotificationEnabled() const;
void setTypingNotification(bool enabled); void setTypingNotification(bool enabled);
private: bool getUseNativeStyle() const;
Settings(); void setUseNativeStyle(bool value);
Settings(Settings &settings) = delete;
Settings& operator=(const Settings&) = delete;
bool getUseNativeDecoration() const;
void setUseNativeDecoration(bool value);
QByteArray getWindowGeometry() const;
void setWindowGeometry(const QByteArray &value);
QByteArray getWindowState() const;
void setWindowState(const QByteArray &value);
QByteArray getSplitterState() const;
void setSplitterState(const QByteArray &value);
public:
QList<QString> friendAddresses;
void save(); void save();
void save(QString path); void save(QString path);
void load(); void load();
private:
Settings();
Settings(Settings &settings) = delete;
Settings& operator=(const Settings&) = delete;
static const QString FILENAME; static const QString FILENAME;
@ -146,11 +161,16 @@ private:
// GUI // GUI
bool enableSmoothAnimation; bool enableSmoothAnimation;
QByteArray smileyPack; QString smileyPack;
bool customEmojiFont; bool customEmojiFont;
QString emojiFontFamily; QString emojiFontFamily;
int emojiFontPointSize; int emojiFontPointSize;
bool minimizeOnClose; bool minimizeOnClose;
bool useNativeStyle;
bool useNativeDecoration;
QByteArray windowGeometry;
QByteArray windowState;
QByteArray splitterState;
// ChatView // ChatView
int firstColumnHandlePos; int firstColumnHandlePos;

199
smileypack.cpp Normal file
View File

@ -0,0 +1,199 @@
/*
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 "smileypack.h"
#include "settings.h"
#include <QFileInfo>
#include <QFile>
#include <QtXml>
#include <QDebug>
SmileyPack::SmileyPack()
{
load(Settings::getInstance().getSmileyPack());
connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged);
}
SmileyPack& SmileyPack::getInstance()
{
static SmileyPack smileyPack;
return smileyPack;
}
QList<QPair<QString, QString> > SmileyPack::listSmileyPacks(const QString &path)
{
QList<QPair<QString, QString> > smileyPacks;
QDir dir(path);
foreach (const QString& subdirectory, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{
dir.cd(subdirectory);
QFileInfoList entries = dir.entryInfoList(QStringList() << "emoticons.xml", QDir::Files);
if (entries.size() > 0) // does it contain a file called emoticons.xml?
{
QString packageName = dir.dirName();
QString relPath = QDir(QCoreApplication::applicationDirPath()).relativeFilePath(entries[0].absoluteFilePath());
smileyPacks << QPair<QString, QString>(packageName, relPath);
}
dir.cdUp();
}
return smileyPacks;
}
bool SmileyPack::isValid(const QString &filename)
{
return QFile(filename).exists();
}
bool SmileyPack::load(const QString& filename)
{
// discard old data
filenameTable.clear();
imgCache.clear();
emoticons.clear();
path.clear();
// open emoticons.xml
QFile xmlFile(filename);
if(!xmlFile.open(QIODevice::ReadOnly))
return false; // cannot open file
/* parse the cfg file
* sample:
* <?xml version='1.0'?>
* <messaging-emoticon-map>
* <emoticon file="smile.png" >
* <string>:)</string>
* <string>:-)</string>
* </emoticon>
* <emoticon file="sad.png" >
* <string>:(</string>
* <string>:-(</string>
* </emoticon>
* </messaging-emoticon-map>
*/
path = QFileInfo(filename).absolutePath();
QDomDocument doc;
doc.setContent(xmlFile.readAll());
QDomNodeList emoticonElements = doc.elementsByTagName("emoticon");
for (int i = 0; i < emoticonElements.size(); ++i)
{
QString file = emoticonElements.at(i).attributes().namedItem("file").nodeValue();
QDomElement stringElement = emoticonElements.at(i).firstChildElement("string");
QStringList emoticonSet; // { ":)", ":-)" } etc.
while (!stringElement.isNull())
{
QString emoticon = stringElement.text();
filenameTable.insert(emoticon, file);
emoticonSet.push_back(emoticon);
cacheSmiley(file); // preload all smileys
stringElement = stringElement.nextSibling().toElement();
}
emoticons.push_back(emoticonSet);
}
// success!
return true;
}
QString SmileyPack::smileyfied(QString msg)
{
QRegExp exp("\\S+"); // matches words
int index = msg.indexOf(exp);
// if a word is key of a smiley, replace it by its corresponding image in Rich Text
while (index >= 0)
{
QString key = exp.cap();
if (filenameTable.contains(key))
{
QString imgRichText = getAsRichText(key);
msg.replace(index, key.length(), imgRichText);
index += imgRichText.length() - key.length();
}
index = msg.indexOf(exp, index + key.length());
}
return msg;
}
QList<QStringList> SmileyPack::getEmoticons() const
{
return emoticons;
}
QString SmileyPack::getAsRichText(const QString &key)
{
return "<img src=\"data:image/png;base64," % QString(getCachedSmiley(key).toBase64()) % "\">";
}
QIcon SmileyPack::getAsIcon(const QString &key)
{
QPixmap pm;
pm.loadFromData(getCachedSmiley(key), "PNG");
return QIcon(pm);
}
void SmileyPack::cacheSmiley(const QString &name)
{
QSize size(16, 16); // TODO: adapt to text size
QString filename = QDir(path).filePath(name);
QImage img(filename);
if (!img.isNull())
{
QImage scaledImg = img.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QByteArray scaledImgData;
QBuffer buffer(&scaledImgData);
scaledImg.save(&buffer, "PNG");
imgCache.insert(name, scaledImgData);
}
}
QByteArray SmileyPack::getCachedSmiley(const QString &key)
{
// valid key?
if (!filenameTable.contains(key))
return QByteArray();
// cache it if needed
QString file = filenameTable.value(key);
if (!imgCache.contains(file)) {
cacheSmiley(file);
}
return imgCache.value(file);
}
void SmileyPack::onSmileyPackChanged()
{
load(Settings::getInstance().getSmileyPack());
}

59
smileypack.h Normal file
View File

@ -0,0 +1,59 @@
/*
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 SMILEYPACK_H
#define SMILEYPACK_H
#include <QHash>
#include <QObject>
#include <QString>
#include <QStringList>
#define SMILEYPACK_DEFAULT_PATH "./smileys"
//maps emoticons to smileys
class SmileyPack : public QObject
{
Q_OBJECT
public:
static SmileyPack& getInstance();
static QList<QPair<QString, QString>> listSmileyPacks(const QString& path = SMILEYPACK_DEFAULT_PATH);
static bool isValid(const QString& filename);
bool load(const QString &filename);
QString smileyfied(QString msg);
QList<QStringList> getEmoticons() const;
QString getAsRichText(const QString& key);
QIcon getAsIcon(const QString& key);
private slots:
void onSmileyPackChanged();
private:
SmileyPack();
SmileyPack(SmileyPack&) = delete;
SmileyPack& operator=(const SmileyPack&) = delete;
void cacheSmiley(const QString& name);
QByteArray getCachedSmiley(const QString& key);
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
QHash<QString, QByteArray> imgCache; // (scaled) representation of a smiley ie. "happy.png" -> data
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... }
QString path; // directory containing the cfg and image files
};
#endif // SMILEYPACK_H

BIN
smileys/default/angry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
smileys/default/cool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
smileys/default/crying.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,85 @@
<?xml version='1.0'?>
<messaging-emoticon-map>
<emoticon file="happy.png">
<string>😊</string>
<string>:)</string>
<string>:-)</string>
</emoticon>
<emoticon file="cool.png">
<string>😎</string>
<string>8-)</string>
<string>8)</string>
</emoticon>
<emoticon file="stunned.png">
<string>😲</string>
<string>:O</string>
<string>:-O</string>
</emoticon>
<emoticon file="tongue.png">
<string>😋</string>
<string>:p</string>
<string>:P</string>
</emoticon>
<emoticon file="uncertain.png">
<string>😕</string>
<string>:/</string>
<string>:-/</string>
</emoticon>
<emoticon file="wink.png">
<string>😉</string>
<string>;)</string>
<string>;-)</string>
</emoticon>
<emoticon file="sad.png">
<string>😖</string>
<string>:(</string>
<string>:-(</string>
</emoticon>
<emoticon file="crying.png">
<string>😢</string>
<string>;(</string>
<string>;-(</string>
</emoticon>
<emoticon file="smile.png">
<string>😃</string>
<string>:D</string>
<string>:-D</string>
</emoticon>
<emoticon file="plain.png">
<string>😐</string>
<string>:|</string>
<string>:-|</string>
</emoticon>
<emoticon file="laugh.png">
<string>😄</string>
<string>;D</string>
<string>;-D</string>
</emoticon>
<emoticon file="angry.png">
<string>😠</string>
<string>:@</string>
</emoticon>
<emoticon file="scared.png">
<string>😨</string>
<string>D:</string>
</emoticon>
<emoticon file="laugh_closed_eyes">
<string>😆</string>
<string>xD</string>
<string>XD</string>
</emoticon>
</messaging-emoticon-map>

BIN
smileys/default/happy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
smileys/default/laugh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
smileys/default/plain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

889
smileys/default/raw.svg Normal file
View File

@ -0,0 +1,889 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="256"
id="svg2"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="raw.svg"
inkscape:export-filename="./raw.png"
inkscape:export-xdpi="360"
inkscape:export-ydpi="360">
<title
id="title4628">qTox</title>
<defs
id="defs4">
<linearGradient
id="linearGradient3131">
<stop
style="stop-color:#ffcc00;stop-opacity:1;"
offset="0"
id="stop3133" />
<stop
style="stop-color:#0063f6;stop-opacity:1;"
offset="1"
id="stop3135" />
</linearGradient>
<linearGradient
id="linearGradient3181">
<stop
style="stop-color:#ff2000;stop-opacity:1;"
offset="0"
id="stop3183" />
<stop
style="stop-color:#ffcc00;stop-opacity:1;"
offset="1"
id="stop3185" />
</linearGradient>
<linearGradient
id="linearGradient3881">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3883" />
<stop
style="stop-color:#505050;stop-opacity:1;"
offset="1"
id="stop3885" />
</linearGradient>
<filter
inkscape:collect="always"
id="filter3777">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-8">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-3" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3881"
id="linearGradient3887"
x1="11.216473"
y1="234.94836"
x2="14.142681"
y2="245.0323"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3881-1">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3883-2" />
<stop
style="stop-color:#505050;stop-opacity:1;"
offset="1"
id="stop3885-8" />
</linearGradient>
<linearGradient
gradientTransform="translate(-5.3934044,0.00862702)"
y2="245.0323"
x2="14.142681"
y1="234.94836"
x1="11.216473"
gradientUnits="userSpaceOnUse"
id="linearGradient3904"
xlink:href="#linearGradient3881-1"
inkscape:collect="always" />
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-5">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-0" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-5-2">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-0-5" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-5-2-6">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-0-5-3" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-56">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-5" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-4">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-4" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-4-5">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-4-7" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42-4">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6-6" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42-5">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6-4" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42-1">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6-0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3181"
id="linearGradient3187"
x1="14.965159"
y1="231.7083"
x2="14.965159"
y2="244.56258"
gradientUnits="userSpaceOnUse" />
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42-0">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6-8" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3131"
id="linearGradient3137"
x1="18.417803"
y1="245.66988"
x2="18.417803"
y2="219.33583"
gradientUnits="userSpaceOnUse" />
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3777-42-51">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.20338983"
id="feGaussianBlur3779-6-09" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="126.48175"
inkscape:cy="74.652167"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
gridtolerance="10"
inkscape:window-width="1920"
inkscape:window-height="1025"
inkscape:window-x="-2"
inkscape:window-y="-3"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="1"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="32px"
spacingy="32px" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>qTox</dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/3.0/" />
<dc:description>Created for the qTox project. Inspired by the &quot;Never mind!&quot; smiley pack.</dc:description>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-796.36218)">
<g
id="happy"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,1.8194901,823.65453)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,0.34633991,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,9.8832106,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czc"
transform="translate(0,796.36218)"
inkscape:connector-curvature="0"
id="path3809"
d="m 6.5178572,243.05357 c 0,0 2.8196608,6.69643 9.4642858,6.69643 6.644625,0 8.571429,-6.69643 8.571429,-6.69643"
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="cool"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,33.801736,823.74463)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-7"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-8)"
sodipodi:type="arc" />
<rect
ry="0.8035714"
y="1027.3845"
x="37.566963"
height="2.4541557"
width="20.714285"
id="rect3871"
style="fill:#000000;fill-opacity:1;stroke:none" />
<path
transform="matrix(2.2007051,0,0,1.1582659,15.153743,755.67017)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-6"
style="fill:url(#linearGradient3887);fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czc"
inkscape:connector-curvature="0"
id="path3809-8"
d="m 42.05798,1044.2158 c 0,0 1.872643,2.6558 5.991887,2.6558 4.119244,0 6.235452,-4.7392 6.235452,-4.7392"
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="matrix(2.2007051,0,0,1.1582659,27.023036,755.66017)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-6-5"
style="fill:url(#linearGradient3904);fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="stunned"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,65.76398,823.59407)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-3"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-5)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,64.29083,917.11755)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-0"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,73.8277,917.11755)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-9"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(1.5951602,0,0,1.0261609,60.548479,798.24781)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-9-5"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="tongue"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,97.820154,823.8709)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-3-0"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-5-2)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,96.347004,917.39438)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-0-7"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,105.88387,917.39438)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-9-3"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czcc"
transform="translate(0,796.36218)"
inkscape:connector-curvature="0"
id="path4009"
d="m 111.69643,244.57143 c 0,0 -0.45837,5.71428 3.21428,5.71428 3.67265,0 2.94643,-5.625 2.94643,-5.625 z"
style="fill:#ff3c00;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(0,796.36218)"
inkscape:connector-curvature="0"
id="path4007"
d="m 104.19643,244.48214 15.80357,0"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="wink"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,161.68181,823.65707)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-1"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-56)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.1914354,160.20866,986.17167)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-2"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(1.2347542,0,0,0.64987065,165.84267,876.83486)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-1"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czc"
inkscape:connector-curvature="0"
id="path3809-82-3"
d="m 169.22444,1041.5403 c 0,0 2.64109,3.5714 7.05357,3.5714 4.41248,0 7.14286,-3.6606 7.14286,-3.6606"
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="translate(192.12627,0.09009866)"
id="sad"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,1.8194901,823.65453)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-2"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-4)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,0.34633991,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-9"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,9.8832106,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-2"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czc"
inkscape:connector-curvature="0"
id="path3809-82"
d="m 9.0981694,1044.9324 c 0,0 2.6410896,-3.5714 7.0535716,-3.5714 4.412482,0 7.142857,3.6606 7.142857,3.6606"
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="crying"
inkscape:label="">
<path
sodipodi:type="arc"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-4-5)"
id="path2987-2-8"
sodipodi:cx="16"
sodipodi:cy="240"
sodipodi:rx="16"
sodipodi:ry="16"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
transform="matrix(0.88628187,0,0,0.88628187,225.85699,823.74463)" />
<path
sodipodi:nodetypes="czaac"
inkscape:connector-curvature="0"
id="path4283"
d="m 233.0569,1035.4408 c 0,0 -3.31266,1.7855 -3.58753,3.0023 -0.27489,1.2169 0.30632,2.1821 1.1672,2.5772 0.83161,0.3825 2.09139,-0.035 2.61925,-0.7703 0.93331,-1.2995 -0.19892,-4.8092 -0.19892,-4.8092 z"
style="fill:#007eff;fill-opacity:1;stroke:#000000;stroke-width:0.24999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:type="arc"
style="fill:#000000;fill-opacity:1;stroke:none"
id="path3781-9-1"
sodipodi:cx="12.142858"
sodipodi:cy="238.5"
sodipodi:rx="1.9642857"
sodipodi:ry="3.9285715"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
transform="matrix(1.2115763,0,0,0.63767169,220.9911,879.83187)" />
<path
sodipodi:type="arc"
style="fill:#000000;fill-opacity:1;stroke:none"
id="path3781-5-2-2"
sodipodi:cx="12.142858"
sodipodi:cy="238.5"
sodipodi:rx="1.9642857"
sodipodi:ry="3.9285715"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
transform="matrix(1.2115763,0,0,0.63767169,230.2492,879.83187)" />
<path
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="m 233.13567,1045.0225 c 0,0 2.64109,-3.5714 7.05357,-3.5714 4.41249,0 7.14286,3.6606 7.14286,3.6606"
id="path3809-82-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="czc" />
<path
sodipodi:nodetypes="czaac"
inkscape:connector-curvature="0"
id="path4283-1"
d="m 247.33865,1036.0177 c 0,0 -1.06279,3.61 -0.39172,4.6617 0.67107,1.0516 1.76583,1.318 2.65236,0.9844 0.857,-0.3216 1.4467,-1.5107 1.29603,-2.403 -0.26637,-1.5775 -3.55667,-3.2431 -3.55667,-3.2431 z"
style="fill:#007eff;fill-opacity:1;stroke:#000000;stroke-width:0.24999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
inkscape:label=""
id="smile"
transform="translate(-0.01141139,-31.91911)">
<path
transform="matrix(0.88628187,0,0,0.88628187,1.8194901,823.65453)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,0.34633991,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-26"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,9.8832106,917.17801)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-29"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czcc"
transform="translate(0,796.36218)"
inkscape:connector-curvature="0"
id="path3809-7"
d="m 6.5178572,243.05357 c 0,0 2.8196608,6.69643 9.4642858,6.69643 6.644625,0 8.571429,-6.69643 8.571429,-6.69643 z"
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:1.01859379px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 8.6777108,1042.4231 14.3101152,0"
id="path4459"
inkscape:connector-curvature="0" />
</g>
<g
inkscape:label="#"
id="plain"
transform="translate(-0.01141139,-31.91911)">
<path
transform="matrix(0.88628187,0,0,0.88628187,33.814236,823.55768)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19-4"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42-4)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,32.341086,917.08116)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-26-8"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,41.877957,917.08116)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-29-3"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 40.844992,1042.3262 13.792439,0"
id="path4459-9"
inkscape:connector-curvature="0" />
</g>
<g
inkscape:label=""
id="laugh"
transform="translate(-0.01141139,-31.91911)">
<path
transform="matrix(0.88628187,0,0,0.88628187,65.711639,823.5914)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19-5"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42-5)"
sodipodi:type="arc" />
<path
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:none"
d="m 75.320085,1029.2437 c -1.4357,0 -2.580995,1.2055 -2.580995,2.7168 0,0.4945 0.144275,0.9581 0.362245,1.3584 -0.01753,-0.091 -0.04528,-0.1765 -0.04528,-0.2717 0,-1.0104 1.000947,-1.8112 2.264031,-1.8112 1.263084,0 2.309311,0.8008 2.309311,1.8112 0,0.08 -0.03293,0.1495 -0.04528,0.2264 0.203088,-0.3899 0.362245,-0.8371 0.362245,-1.3131 0,-1.5113 -1.190576,-2.7168 -2.626275,-2.7168 z"
id="path3781-26-7" />
<path
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:none"
d="m 84.041385,1029.2437 c -1.435699,0 -2.580994,1.2055 -2.580994,2.7168 0,0.4584 0.127713,0.8883 0.316964,1.2679 0,-1.0105 1.000946,-1.8113 2.26403,-1.8113 1.263085,0 2.309312,0.8008 2.309312,1.8113 0.185411,-0.3766 0.316964,-0.8145 0.316964,-1.2679 0,-1.5113 -1.190576,-2.7168 -2.626276,-2.7168 z"
id="path3781-5-29-1" />
<path
sodipodi:nodetypes="czcc"
inkscape:connector-curvature="0"
id="path3809-7-5"
d="m 70.410006,1039.3526 c 0,0 2.819661,6.6965 9.464286,6.6965 6.644625,0 8.571429,-6.6965 8.571429,-6.6965 z"
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#000000;stroke:none"
d="m 71.931054,1041.3349 15.089286,0 -1.508928,2.1996 -11.568453,0 z"
id="path4562"
inkscape:connector-curvature="0" />
</g>
<g
id="angry"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,97.629508,791.79739)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19-2"
style="fill:url(#linearGradient3187);fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42-1)"
sodipodi:type="arc" />
<rect
ry="0.8035714"
y="1007.2888"
x="105.08295"
height="6.1593604"
width="13.196339"
id="rect3189"
style="fill:#f9f9f9;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1"
rx="0.8035714" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 105.46983,1010.3042 12.71794,0"
id="path4459-1"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2.03361535;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 114.11573,1000.6187 6.16645,-1.17671"
id="path4459-1-0"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2.03361535;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 103.85721,999.56622 6.20869,0.92818"
id="path4459-1-0-1"
inkscape:connector-curvature="0" />
</g>
<g
id="uncertain"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,129.83735,823.77759)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-3-0-2"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-5-2-6)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,128.3642,917.30107)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-0-7-4"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,137.90106,917.30107)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-9-3-1"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="csc"
inkscape:connector-curvature="0"
id="path3204"
d="m 140.66832,1045.9796 c 0,0 2.22896,-6.1582 5.84056,-6.352 3.35156,-0.1799 5.55738,1.9643 5.55738,1.9643"
style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="scared"
inkscape:label="">
<path
transform="matrix(0.88628187,0,0,0.88628187,129.83735,791.59902)"
d="m 32,240 c 0,8.83656 -7.163444,16 -16,16 -8.836556,0 -16,-7.16344 -16,-16 0,-8.83656 7.163444,-16 16,-16 8.836556,0 16,7.16344 16,16 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19-58"
style="fill:url(#linearGradient3137);fill-opacity:1;stroke:#000000;stroke-width:1.12830921000000006;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1.0;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42-0)"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,128.3642,885.1225)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-26-2"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.91334205,0,0,0.48070635,137.90107,885.1225)"
d="m 14.107143,238.5 c 0,2.16969 -0.87944,3.92857 -1.964285,3.92857 -1.084846,0 -1.964286,-1.75888 -1.964286,-3.92857 0,-2.16969 0.87944,-3.92857 1.964286,-3.92857 1.084845,0 1.964285,1.75888 1.964285,3.92857 z"
sodipodi:ry="3.9285715"
sodipodi:rx="1.9642857"
sodipodi:cy="238.5"
sodipodi:cx="12.142858"
id="path3781-5-29-6"
style="fill:#000000;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="szsszss"
inkscape:connector-curvature="0"
id="path3809-7-56"
d="m 137.06672,1013.3344 c 0.25114,-1.6566 2.15608,-5.1205 7.23695,-5.1205 5.08089,0 6.93799,3.9294 6.55424,5.1205 -0.13763,0.4272 -0.47655,0.8569 -1.31761,0.4379 -1.70599,-0.8499 -2.65516,-1.2076 -5.57799,-1.2572 -2.92284,-0.05 -4.14543,1.2631 -5.36636,1.7495 -1.22092,0.4865 -1.61867,-0.3403 -1.52923,-0.9302 z"
style="fill:#ffffff;stroke:#000000;stroke-width:0.76466024;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="laugh_closed_eyes"
inkscape:label="#g3182">
<path
transform="matrix(0.88628187,0,0,0.88628187,161.91268,791.71004)"
d="m 32,240 a 16,16 0 1 1 -32,0 16,16 0 1 1 32,0 z"
sodipodi:ry="16"
sodipodi:rx="16"
sodipodi:cy="240"
sodipodi:cx="16"
id="path2987-19-46"
style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:1.12830925;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3777-42-51)"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="czcc"
inkscape:connector-curvature="0"
id="path3809-7-7"
d="m 166.61105,1007.4713 c 0,0 2.81966,6.6964 9.46428,6.6964 6.64463,0 8.57143,-6.6964 8.57143,-6.6964 z"
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path3142"
d="m 167.85286,999.14099 5.21988,2.05271 -5.01109,1.8159"
style="fill:none;stroke:#000000;stroke-width:1.01681638px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path3142-8"
d="m 183.57572,999.08084 -5.21988,2.05276 5.01109,1.8159"
style="fill:none;stroke:#000000;stroke-width:1.01681638px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path4562-7"
d="m 168.16325,1009.489 15.08928,0 -1.50892,2.1996 -11.56846,0 z"
style="fill:#000000;stroke:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

BIN
smileys/default/sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
smileys/default/scared.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
smileys/default/smile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
smileys/default/stunned.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
smileys/default/tongue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
smileys/default/wink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

35
style.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
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 "style.h"
#include "settings.h"
#include <QFile>
#include <QDebug>
QString Style::get(const QString &filename)
{
if (!Settings::getInstance().getUseNativeStyle())
{
QFile file(filename);
if (file.open(QFile::ReadOnly | QFile::Text))
return file.readAll();
else
qWarning() << "Style " << filename << " not found";
}
return QString();
}

View File

@ -14,14 +14,17 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#include "clickablelabel.h" #ifndef STYLE_H
#define STYLE_H
ClickableLabel::ClickableLabel(QWidget *parent) : #include <QString>
QLabel(parent)
{
}
void ClickableLabel::mousePressEvent(QMouseEvent*) class Style
{ {
emit clicked(); public:
} static QString get(const QString& filename);
private:
Style();
};
#endif // STYLE_H

98
tools/buildPackages.sh Executable file
View File

@ -0,0 +1,98 @@
#!/bin/bash
# Config (Update me if needed !)
VERSION_UPSTREAM="0.01pre-alpha"
VERSION_PACKAGE="1"
PACKAGENAME="qtox"
UPSTREAM_URL="https://github.com/tux3/qTox/archive/master.tar.gz"
# Make some vars for convenience
VERNAME=$PACKAGENAME"_"$VERSION_UPSTREAM
FULLVERNAME=$VERNAME"-"$VERSION_PACKAGE
ARCHIVENAME=$VERNAME".orig.tar.gz"
# ARCHIVENAME > FULLVERNAME > VERNAME = PACKAGENAME+UPVER
# Get some args
OPT_SUDO=true
OPT_APT=true
OPT_KEEP=false
while [ $# -ge 1 ] ; do
if [ ${1} = "-s" -o ${1} = "--no-sudo" ] ; then
OPT_SUDO=false
shift
elif [ ${1} = "-a" -o ${1} = "--no-apt" ] ; then
OPT_APT=false
shift
elif [ ${1} = "-k" -o ${1} = "--keep" ]; then
OPT_KEEP=true
shift
else
if [ ${1} != "-h" -a ${1} != "--help" ] ; then
echo "[ERROR] Unknown parameter \"${1}\""
echo ""
fi
# print help
echo "Use this script to build qTox packages for Debian and Red Hat families"
echo ""
echo "usage:"
echo " ${0} [-h|--help|-k|--keep|-s|--no-sudo|-a|--no-apt]"
echo ""
echo "parameters:"
echo " -h|--help : displays this help"
echo " -s|--no-sudo: disables using sudo for apt and alien"
echo " -a|--no-apt : disables apt-get (used for build deps) entirely"
echo " -k|--keep : does not delete the build files afterwards"
echo ""
echo "example usages:"
echo " ${0} -- build packages, cleaning up trash and running sudo alien and apt-get"
echo " ${0} -s -k -- build packages, keeping build files and non-sudo alien and apt-get"
exit 1
fi
done
# Get the requried tools if needed
if [[ $OPT_APT = "true" ]]; then
echo "Installing missing tools (if any)..."
if [[ $EUID -ne 0 && $OPT_SUDO = "true" ]]; then
sudo apt-get install wget debhelper cdbs devscripts alien tar gzip build-essential
else
apt-get install wget debhelper cdbs devscripts alien tar gzip build-essential
fi
fi
mkdir -p .packages
cd .packages
# Cleanup
rm -r $VERNAME 2> /dev/null
rm $ARCHIVENAME 2> /dev/null
# Fectch sources and layout directories
wget -O $ARCHIVENAME $UPSTREAM_URL
tar xvf $ARCHIVENAME 2> /dev/null # Extracts to qTox-master
mv qTox-master $VERNAME
#tar cz $VERNAME > $ARCHIVENAME
# Build packages
cd $VERNAME
debuild -us -uc
cd ..
# alien warns that it should probably be run as root...
if [[ $EUID -ne 0 && $OPT_SUDO = "true" ]]; then
sudo alien ./$FULLVERNAME*.deb -r
else
alien ./$FULLVERNAME*.deb -r
fi
mv *.deb ..
mv -f *.rpm ..
if [[ $OPT_KEEP = "false" ]]; then
rm -r *
fi
cd ..
rmdir .packages 2> /dev/null # fails if non empty

View File

@ -1,75 +1,75 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.0" language="de_DE"> <TS version="2.1" language="de_DE">
<context> <context>
<name>AddFriendForm</name> <name>AddFriendForm</name>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="15"/> <location filename="../widget/form/addfriendform.cpp" line="32"/>
<source>Add Friends</source> <source>Add Friends</source>
<translation>Freunde hinzufügen</translation> <translation>Freunde hinzufügen</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="18"/> <location filename="../widget/form/addfriendform.cpp" line="35"/>
<source>Tox ID</source> <source>Tox ID</source>
<comment>Tox ID of the person you&apos;re sending a friend request to</comment> <comment>Tox ID of the person you&apos;re sending a friend request to</comment>
<translation>Tox ID</translation> <translation>Tox ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="19"/> <location filename="../widget/form/addfriendform.cpp" line="36"/>
<source>Message</source> <source>Message</source>
<comment>The message you send in friend requests</comment> <comment>The message you send in friend requests</comment>
<translation>Nachricht</translation> <translation>Nachricht</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="20"/> <location filename="../widget/form/addfriendform.cpp" line="37"/>
<source>Send friend request</source> <source>Send friend request</source>
<translation>Freundschaftseinladung versenden</translation> <translation>Freundschaftseinladung versenden</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="67"/> <location filename="../widget/form/addfriendform.cpp" line="38"/>
<source>Tox me maybe?</source> <source>Tox me maybe?</source>
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment> <comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
<translation>Lass uns Toxen!</translation> <translation>Lass uns Toxen!</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="75"/> <location filename="../widget/form/addfriendform.cpp" line="93"/>
<source>Please fill in a valid Tox ID</source> <source>Please fill in a valid Tox ID</source>
<comment>Tox ID of the friend you&apos;re sending a friend request to</comment> <comment>Tox ID of the friend you&apos;re sending a friend request to</comment>
<translation>Bitte gib eine gültige Tox ID ein</translation> <translation>Bitte gib eine gültige Tox ID ein</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="92"/> <location filename="../widget/form/addfriendform.cpp" line="110"/>
<source>This address does not exist</source> <source>This address does not exist</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="96"/> <location filename="../widget/form/addfriendform.cpp" line="114"/>
<source>Error while looking up DNS</source> <source>Error while looking up DNS</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Fehler beim Auflösen des DNS</translation> <translation>Fehler beim Auflösen des DNS</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="102"/> <location filename="../widget/form/addfriendform.cpp" line="120"/>
<source>Unexpected number of text records</source> <source>Unexpected number of text records</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Unererwartete Anzahl von Texteinträgen</translation> <translation>Unererwartete Anzahl von Texteinträgen</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="108"/> <location filename="../widget/form/addfriendform.cpp" line="126"/>
<source>Unexpected number of values in text record</source> <source>Unexpected number of values in text record</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Unerwartete Anzahl von Werten innerhalb des Texteintrages</translation> <translation>Unerwartete Anzahl von Werten innerhalb des Texteintrages</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="115"/> <location filename="../widget/form/addfriendform.cpp" line="133"/>
<source>The DNS lookup does not contain any Tox ID</source> <source>The DNS lookup does not contain any Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Der DNS Eintrag enthält keine gültige TOX ID</translation> <translation>Der DNS Eintrag enthält keine gültige TOX ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="121"/> <location filename="../widget/form/addfriendform.cpp" line="139"/>
<location filename="../widget/form/addfriendform.cpp" line="127"/> <location filename="../widget/form/addfriendform.cpp" line="145"/>
<source>The DNS lookup does not contain a valid Tox ID</source> <source>The DNS lookup does not contain a valid Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Der DNS Eintrag enthält keine gültige TOX ID</translation> <translation>Der DNS Eintrag enthält keine gültige TOX ID</translation>
@ -78,12 +78,12 @@
<context> <context>
<name>Camera</name> <name>Camera</name>
<message> <message>
<location filename="../widget/camera.cpp" line="145"/> <location filename="../widget/camera.cpp" line="161"/>
<source>Camera eror</source> <source>Camera eror</source>
<translation>Kamerafehler</translation> <translation>Kamerafehler</translation>
</message> </message>
<message> <message>
<location filename="../widget/camera.cpp" line="146"/> <location filename="../widget/camera.cpp" line="162"/>
<source>Camera format %1 not supported, can&apos;t use the camera</source> <source>Camera format %1 not supported, can&apos;t use the camera</source>
<translation>Kameraformat %1 wird nicht unterstützt. Die Kamera kann nicht verwendet werden</translation> <translation>Kameraformat %1 wird nicht unterstützt. Die Kamera kann nicht verwendet werden</translation>
</message> </message>
@ -91,13 +91,13 @@
<context> <context>
<name>ChatForm</name> <name>ChatForm</name>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="265"/> <location filename="../widget/form/chatform.cpp" line="283"/>
<source>Send a file</source> <source>Send a file</source>
<translation>Datei versenden</translation> <translation>Datei versenden</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="590"/> <location filename="../widget/form/chatform.cpp" line="620"/>
<location filename="../widget/form/chatform.cpp" line="596"/> <location filename="../widget/form/chatform.cpp" line="626"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Chatverlauf speichern</translation> <translation>Chatverlauf speichern</translation>
</message> </message>
@ -113,11 +113,42 @@
<context> <context>
<name>FileTransfertWidget</name> <name>FileTransfertWidget</name>
<message> <message>
<location filename="../widget/filetransfertwidget.cpp" line="249"/> <location filename="../widget/filetransfertwidget.cpp" line="281"/>
<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>Datei speichern</translation> <translation>Datei speichern</translation>
</message> </message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<source>Location not writable</source>
<comment>Title of permissions popup</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<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"></translation>
</message>
</context>
<context>
<name>FilesForm</name>
<message>
<location filename="../widget/form/filesform.cpp" line="25"/>
<source>Transfered Files</source>
<comment>&quot;Headline&quot; of the window</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="33"/>
<source>Downloads</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="34"/>
<source>Uploads</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>FriendRequestDialog</name> <name>FriendRequestDialog</name>
@ -158,19 +189,19 @@
<context> <context>
<name>FriendWidget</name> <name>FriendWidget</name>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="71"/> <location filename="../widget/friendwidget.cpp" line="86"/>
<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>Tox ID kopieren</translation> <translation>Tox ID kopieren</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="72"/> <location filename="../widget/friendwidget.cpp" line="87"/>
<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>In Gruppe einladen</translation> <translation>In Gruppe einladen</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="82"/> <location filename="../widget/friendwidget.cpp" line="97"/>
<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>Freund entfernen</translation> <translation>Freund entfernen</translation>
@ -179,23 +210,23 @@
<context> <context>
<name>GroupChatForm</name> <name>GroupChatForm</name>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="32"/> <location filename="../widget/form/groupchatform.cpp" line="49"/>
<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 Personen im Chat</translation> <translation>%1 Personen im Chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="155"/> <location filename="../widget/form/groupchatform.cpp" line="146"/>
<source>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<translation>&lt;Unbekannt&gt;</translation> <translation>&lt;Unbekannt&gt;</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="224"/> <location filename="../widget/form/groupchatform.cpp" line="215"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 Personen im Chat</translation> <translation>%1 Personen im Chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="243"/> <location filename="../widget/form/groupchatform.cpp" line="234"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Chatverlauf speichern</translation> <translation>Chatverlauf speichern</translation>
</message> </message>
@ -203,28 +234,76 @@
<context> <context>
<name>GroupWidget</name> <name>GroupWidget</name>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="38"/> <location filename="../widget/groupwidget.cpp" line="54"/>
<location filename="../widget/groupwidget.cpp" line="130"/> <location filename="../widget/groupwidget.cpp" line="141"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 Personen im Chat</translation> <translation>%1 Personen im Chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="40"/> <location filename="../widget/groupwidget.cpp" line="56"/>
<location filename="../widget/groupwidget.cpp" line="132"/> <location filename="../widget/groupwidget.cpp" line="143"/>
<source>0 users in chat</source> <source>0 users in chat</source>
<translation>0 Personen im Chat</translation> <translation>0 Personen im Chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="73"/> <location filename="../widget/groupwidget.cpp" line="84"/>
<source>Quit group</source> <source>Quit group</source>
<comment>Menu to quit a groupchat</comment> <comment>Menu to quit a groupchat</comment>
<translation>Gruppe verlassen</translation> <translation>Gruppe verlassen</translation>
</message> </message>
</context> </context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="20"/>
<source>qTox</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1918"/>
<source>Your name</source>
<translation type="unfinished">Dein Name</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2000"/>
<source>Your status</source>
<translation type="unfinished">Dein Status</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2566"/>
<source>Add friends</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2592"/>
<source>Create a group chat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2624"/>
<source>View completed file transfers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2656"/>
<source>Change your settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3238"/>
<source>Close</source>
<translation type="unfinished">Schließen</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3241"/>
<source>Ctrl+Q</source>
<translation type="unfinished">Strg+Q</translation>
</message>
</context>
<context> <context>
<name>SelfCamView</name> <name>SelfCamView</name>
<message> <message>
<location filename="../widget/selfcamview.cpp" line="16"/> <location filename="../widget/selfcamview.cpp" line="32"/>
<source>Tox video test</source> <source>Tox video test</source>
<comment>Title of the window to test the video/webcam</comment> <comment>Title of the window to test the video/webcam</comment>
<translation>Tox Video testen</translation> <translation>Tox Video testen</translation>
@ -233,83 +312,105 @@
<context> <context>
<name>SettingsForm</name> <name>SettingsForm</name>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="15"/> <location filename="../widget/form/settingsform.cpp" line="34"/>
<source>User Settings</source> <source>User Settings</source>
<comment>&quot;Headline&quot; of the window</comment> <comment>&quot;Headline&quot; of the window</comment>
<translation>Einstellungen</translation> <translation>Einstellungen</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="18"/> <location filename="../widget/form/settingsform.cpp" line="37"/>
<source>Name</source> <source>Name</source>
<comment>Username/nick</comment> <comment>Username/nick</comment>
<translation>Benutzername</translation> <translation>Benutzername</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="19"/> <location filename="../widget/form/settingsform.cpp" line="38"/>
<source>Status</source> <source>Status</source>
<comment>Status message</comment> <comment>Status message</comment>
<translation>Status</translation> <translation>Status</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="28"/> <location filename="../widget/form/settingsform.cpp" line="39"/>
<source>(click here to copy)</source>
<comment>Click on this text to copy TID to clipboard</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="47"/>
<source>Test video</source> <source>Test video</source>
<comment>Text on a button to test the video/webcam</comment> <comment>Text on a button to test the video/webcam</comment>
<translation>Video testen</translation> <translation>Video testen</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="29"/> <location filename="../widget/form/settingsform.cpp" line="48"/>
<source>Enable IPv6 (recommended)</source> <source>Enable IPv6 (recommended)</source>
<comment>Text on a checkbox to enable IPv6</comment> <comment>Text on a checkbox to enable IPv6</comment>
<translation>IPv6 aktivieren (empfohlen)</translation> <translation>IPv6 aktivieren (empfohlen)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="31"/> <location filename="../widget/form/settingsform.cpp" line="50"/>
<source>Use translations</source> <source>Use translations</source>
<comment>Text on a checkbox to enable translations</comment> <comment>Text on a checkbox to enable translations</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../widget/form/settingsform.cpp" line="52"/>
<source>Make Tox portable</source>
<comment>Text on a checkbox to make qTox a portable application</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="54"/>
<source>Save settings to the working directory instead of the usual conf dir</source>
<comment>describes makeToxPortable checkbox</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="56"/>
<source>Smiley Pack</source>
<comment>Text on smiley pack label</comment>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Widget</name> <name>Widget</name>
<message> <message>
<location filename="../widget.ui" line="26"/>
<source>Tox</source> <source>Tox</source>
<translation>Tox</translation> <translation type="vanished">Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="1935"/>
<source>Your name</source> <source>Your name</source>
<translation>Dein Name</translation> <translation type="vanished">Dein Name</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2017"/>
<source>Your status</source> <source>Your status</source>
<translation>Dein Status</translation> <translation type="vanished">Dein Status</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3293"/>
<source>Close</source> <source>Close</source>
<translation>Schließen</translation> <translation type="vanished">Schließen</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3296"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translation>Strg+Q</translation> <translation type="vanished">Strg+Q</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="128"/>
<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 type="obsolete">Online</translation> <translation type="unfinished">Online</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="130"/>
<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 type="obsolete">Abwesend</translation> <translation type="unfinished">Abwesend</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="132"/>
<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 type="obsolete">Beschäftigt</translation> <translation type="unfinished">Beschäftigt</translation>
</message> </message>
</context> </context>
</TS> </TS>

View File

@ -1,75 +1,75 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.0" language="fr_FR"> <TS version="2.1" language="fr_FR">
<context> <context>
<name>AddFriendForm</name> <name>AddFriendForm</name>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="15"/> <location filename="../widget/form/addfriendform.cpp" line="32"/>
<source>Add Friends</source> <source>Add Friends</source>
<translation>Ajouter des amis</translation> <translation>Ajouter des amis</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="18"/> <location filename="../widget/form/addfriendform.cpp" line="35"/>
<source>Tox ID</source> <source>Tox ID</source>
<comment>Tox ID of the person you&apos;re sending a friend request to</comment> <comment>Tox ID of the person you&apos;re sending a friend request to</comment>
<translation>ID Tox</translation> <translation>ID Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="19"/> <location filename="../widget/form/addfriendform.cpp" line="36"/>
<source>Message</source> <source>Message</source>
<comment>The message you send in friend requests</comment> <comment>The message you send in friend requests</comment>
<translation>Message</translation> <translation>Message</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="20"/> <location filename="../widget/form/addfriendform.cpp" line="37"/>
<source>Send friend request</source> <source>Send friend request</source>
<translation>Envoyer la demande d&apos;ami</translation> <translation>Envoyer la demande d&apos;ami</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="67"/> <location filename="../widget/form/addfriendform.cpp" line="38"/>
<source>Tox me maybe?</source> <source>Tox me maybe?</source>
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment> <comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
<translation>Je souhaiterais vous ajouter à mes contacts</translation> <translation>Je souhaiterais vous ajouter à mes contacts</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="75"/> <location filename="../widget/form/addfriendform.cpp" line="93"/>
<source>Please fill in a valid Tox ID</source> <source>Please fill in a valid Tox ID</source>
<comment>Tox ID of the friend you&apos;re sending a friend request to</comment> <comment>Tox ID of the friend you&apos;re sending a friend request to</comment>
<translation>Merci de remplir un ID Tox valide</translation> <translation>Merci de remplir un ID Tox valide</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="92"/> <location filename="../widget/form/addfriendform.cpp" line="110"/>
<source>This address does not exist</source> <source>This address does not exist</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="96"/> <location filename="../widget/form/addfriendform.cpp" line="114"/>
<source>Error while looking up DNS</source> <source>Error while looking up DNS</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Erreur en consultant le serveur DNS</translation> <translation>Erreur en consultant le serveur DNS</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="102"/> <location filename="../widget/form/addfriendform.cpp" line="120"/>
<source>Unexpected number of text records</source> <source>Unexpected number of text records</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Nombre d&apos;entrées texte innatendu</translation> <translation>Nombre d&apos;entrées texte innatendu</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="108"/> <location filename="../widget/form/addfriendform.cpp" line="126"/>
<source>Unexpected number of values in text record</source> <source>Unexpected number of values in text record</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Nombre d&apos;entrées numériques dans l&apos;entrée texte innatendu</translation> <translation>Nombre d&apos;entrées numériques dans l&apos;entrée texte innatendu</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="115"/> <location filename="../widget/form/addfriendform.cpp" line="133"/>
<source>The DNS lookup does not contain any Tox ID</source> <source>The DNS lookup does not contain any Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>La réponse DNS ne contient aucun ID Tox</translation> <translation>La réponse DNS ne contient aucun ID Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="121"/> <location filename="../widget/form/addfriendform.cpp" line="139"/>
<location filename="../widget/form/addfriendform.cpp" line="127"/> <location filename="../widget/form/addfriendform.cpp" line="145"/>
<source>The DNS lookup does not contain a valid Tox ID</source> <source>The DNS lookup does not contain a valid Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>La réponse DNS ne contient pas d&apos;ID Tox valide</translation> <translation>La réponse DNS ne contient pas d&apos;ID Tox valide</translation>
@ -78,12 +78,12 @@
<context> <context>
<name>Camera</name> <name>Camera</name>
<message> <message>
<location filename="../widget/camera.cpp" line="145"/> <location filename="../widget/camera.cpp" line="161"/>
<source>Camera eror</source> <source>Camera eror</source>
<translation>Erreur de caméra</translation> <translation>Erreur de caméra</translation>
</message> </message>
<message> <message>
<location filename="../widget/camera.cpp" line="146"/> <location filename="../widget/camera.cpp" line="162"/>
<source>Camera format %1 not supported, can&apos;t use the camera</source> <source>Camera format %1 not supported, can&apos;t use the camera</source>
<translation>Format %1 de la caméra non supporté, impossible de l&apos;utiliser</translation> <translation>Format %1 de la caméra non supporté, impossible de l&apos;utiliser</translation>
</message> </message>
@ -91,13 +91,13 @@
<context> <context>
<name>ChatForm</name> <name>ChatForm</name>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="265"/> <location filename="../widget/form/chatform.cpp" line="283"/>
<source>Send a file</source> <source>Send a file</source>
<translation>Envoyer un fichier</translation> <translation>Envoyer un fichier</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="590"/> <location filename="../widget/form/chatform.cpp" line="620"/>
<location filename="../widget/form/chatform.cpp" line="596"/> <location filename="../widget/form/chatform.cpp" line="626"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Sauvegarder l&apos;historique de conversation</translation> <translation>Sauvegarder l&apos;historique de conversation</translation>
</message> </message>
@ -113,11 +113,42 @@
<context> <context>
<name>FileTransfertWidget</name> <name>FileTransfertWidget</name>
<message> <message>
<location filename="../widget/filetransfertwidget.cpp" line="249"/> <location filename="../widget/filetransfertwidget.cpp" line="281"/>
<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>Sauvegarder un fichier</translation> <translation>Sauvegarder un fichier</translation>
</message> </message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<source>Location not writable</source>
<comment>Title of permissions popup</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<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"></translation>
</message>
</context>
<context>
<name>FilesForm</name>
<message>
<location filename="../widget/form/filesform.cpp" line="25"/>
<source>Transfered Files</source>
<comment>&quot;Headline&quot; of the window</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="33"/>
<source>Downloads</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="34"/>
<source>Uploads</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>FriendRequestDialog</name> <name>FriendRequestDialog</name>
@ -158,19 +189,19 @@
<context> <context>
<name>FriendWidget</name> <name>FriendWidget</name>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="71"/> <location filename="../widget/friendwidget.cpp" line="86"/>
<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>Copier l&apos;ID ami</translation> <translation>Copier l&apos;ID ami</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="72"/> <location filename="../widget/friendwidget.cpp" line="87"/>
<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>Inviter dans un groupe</translation> <translation>Inviter dans un groupe</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="82"/> <location filename="../widget/friendwidget.cpp" line="97"/>
<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>Supprimer ami</translation> <translation>Supprimer ami</translation>
@ -179,23 +210,23 @@
<context> <context>
<name>GroupChatForm</name> <name>GroupChatForm</name>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="32"/> <location filename="../widget/form/groupchatform.cpp" line="49"/>
<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 personnes</translation> <translation>%1 personnes</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="155"/> <location filename="../widget/form/groupchatform.cpp" line="146"/>
<source>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<translation>&lt;Inconnu&gt;</translation> <translation>&lt;Inconnu&gt;</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="224"/> <location filename="../widget/form/groupchatform.cpp" line="215"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 personnes</translation> <translation>%1 personnes</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="243"/> <location filename="../widget/form/groupchatform.cpp" line="234"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Sauvegarder l&apos;historique de conversation</translation> <translation>Sauvegarder l&apos;historique de conversation</translation>
</message> </message>
@ -203,28 +234,76 @@
<context> <context>
<name>GroupWidget</name> <name>GroupWidget</name>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="38"/> <location filename="../widget/groupwidget.cpp" line="54"/>
<location filename="../widget/groupwidget.cpp" line="130"/> <location filename="../widget/groupwidget.cpp" line="141"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 personnes</translation> <translation>%1 personnes</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="40"/> <location filename="../widget/groupwidget.cpp" line="56"/>
<location filename="../widget/groupwidget.cpp" line="132"/> <location filename="../widget/groupwidget.cpp" line="143"/>
<source>0 users in chat</source> <source>0 users in chat</source>
<translation>0 personnes</translation> <translation>0 personnes</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="73"/> <location filename="../widget/groupwidget.cpp" line="84"/>
<source>Quit group</source> <source>Quit group</source>
<comment>Menu to quit a groupchat</comment> <comment>Menu to quit a groupchat</comment>
<translation>Quitter le groupe</translation> <translation>Quitter le groupe</translation>
</message> </message>
</context> </context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="20"/>
<source>qTox</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1918"/>
<source>Your name</source>
<translation type="unfinished">Votre nom</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2000"/>
<source>Your status</source>
<translation type="unfinished">Votre status</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2566"/>
<source>Add friends</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2592"/>
<source>Create a group chat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2624"/>
<source>View completed file transfers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2656"/>
<source>Change your settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3238"/>
<source>Close</source>
<translation type="unfinished">Fermer</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3241"/>
<source>Ctrl+Q</source>
<translation type="unfinished">Ctrl+Q</translation>
</message>
</context>
<context> <context>
<name>SelfCamView</name> <name>SelfCamView</name>
<message> <message>
<location filename="../widget/selfcamview.cpp" line="16"/> <location filename="../widget/selfcamview.cpp" line="32"/>
<source>Tox video test</source> <source>Tox video test</source>
<comment>Title of the window to test the video/webcam</comment> <comment>Title of the window to test the video/webcam</comment>
<translation>Test vidéo Tox</translation> <translation>Test vidéo Tox</translation>
@ -233,83 +312,105 @@
<context> <context>
<name>SettingsForm</name> <name>SettingsForm</name>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="15"/> <location filename="../widget/form/settingsform.cpp" line="34"/>
<source>User Settings</source> <source>User Settings</source>
<comment>&quot;Headline&quot; of the window</comment> <comment>&quot;Headline&quot; of the window</comment>
<translation>Configuration</translation> <translation>Configuration</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="18"/> <location filename="../widget/form/settingsform.cpp" line="37"/>
<source>Name</source> <source>Name</source>
<comment>Username/nick</comment> <comment>Username/nick</comment>
<translation>Nom</translation> <translation>Nom</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="19"/> <location filename="../widget/form/settingsform.cpp" line="38"/>
<source>Status</source> <source>Status</source>
<comment>Status message</comment> <comment>Status message</comment>
<translation>Status</translation> <translation>Status</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="28"/> <location filename="../widget/form/settingsform.cpp" line="39"/>
<source>(click here to copy)</source>
<comment>Click on this text to copy TID to clipboard</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="47"/>
<source>Test video</source> <source>Test video</source>
<comment>Text on a button to test the video/webcam</comment> <comment>Text on a button to test the video/webcam</comment>
<translation>Tester la vidéo</translation> <translation>Tester la vidéo</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="29"/> <location filename="../widget/form/settingsform.cpp" line="48"/>
<source>Enable IPv6 (recommended)</source> <source>Enable IPv6 (recommended)</source>
<comment>Text on a checkbox to enable IPv6</comment> <comment>Text on a checkbox to enable IPv6</comment>
<translation>Activer IPv6 (recommandé)</translation> <translation>Activer IPv6 (recommandé)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="31"/> <location filename="../widget/form/settingsform.cpp" line="50"/>
<source>Use translations</source> <source>Use translations</source>
<comment>Text on a checkbox to enable translations</comment> <comment>Text on a checkbox to enable translations</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../widget/form/settingsform.cpp" line="52"/>
<source>Make Tox portable</source>
<comment>Text on a checkbox to make qTox a portable application</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="54"/>
<source>Save settings to the working directory instead of the usual conf dir</source>
<comment>describes makeToxPortable checkbox</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="56"/>
<source>Smiley Pack</source>
<comment>Text on smiley pack label</comment>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Widget</name> <name>Widget</name>
<message> <message>
<location filename="../widget.ui" line="26"/>
<source>Tox</source> <source>Tox</source>
<translation>Tox</translation> <translation type="vanished">Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="1935"/>
<source>Your name</source> <source>Your name</source>
<translation>Votre nom</translation> <translation type="vanished">Votre nom</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2017"/>
<source>Your status</source> <source>Your status</source>
<translation>Votre status</translation> <translation type="vanished">Votre status</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3293"/>
<source>Close</source> <source>Close</source>
<translation>Fermer</translation> <translation type="vanished">Fermer</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3296"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translation>Ctrl+Q</translation> <translation type="vanished">Ctrl+Q</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="128"/>
<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 type="obsolete">Connecté</translation> <translation type="unfinished">Connecté</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="130"/>
<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 type="obsolete">Indisponnible</translation> <translation type="unfinished">Indisponnible</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="132"/>
<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 type="obsolete">Occupé</translation> <translation type="unfinished">Occupé</translation>
</message> </message>
</context> </context>
</TS> </TS>

Binary file not shown.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.0" language="it_IT"> <TS version="2.1" language="it_IT">
<context> <context>
<name>AddFriendForm</name> <name>AddFriendForm</name>
<message> <message>
@ -26,50 +26,50 @@
<translation>Invia richiesta d&apos;amicizia</translation> <translation>Invia richiesta d&apos;amicizia</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="84"/> <location filename="../widget/form/addfriendform.cpp" line="38"/>
<source>Tox me maybe?</source> <source>Tox me maybe?</source>
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment> <comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
<translation>Permettimi di aggiungerti alla mia lista contatti</translation> <translation>Permettimi di aggiungerti alla mia lista contatti</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="92"/> <location filename="../widget/form/addfriendform.cpp" line="93"/>
<source>Please fill in a valid Tox ID</source> <source>Please fill in a valid Tox ID</source>
<comment>Tox ID of the friend you&apos;re sending a friend request to</comment> <comment>Tox ID of the friend you&apos;re sending a friend request to</comment>
<translation>Inserisci un Tox ID valido</translation> <translation>Inserisci un Tox ID valido</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="109"/> <location filename="../widget/form/addfriendform.cpp" line="110"/>
<source>This address does not exist</source> <source>This address does not exist</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Questo indirizzo non esiste</translation> <translation>Questo indirizzo non esiste</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="113"/> <location filename="../widget/form/addfriendform.cpp" line="114"/>
<source>Error while looking up DNS</source> <source>Error while looking up DNS</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Errore nel consultare il server DNS</translation> <translation>Errore nel consultare il server DNS</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="119"/> <location filename="../widget/form/addfriendform.cpp" line="120"/>
<source>Unexpected number of text records</source> <source>Unexpected number of text records</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Numero inaspettato di text-records</translation> <translation>Numero inaspettato di text-records</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="125"/> <location filename="../widget/form/addfriendform.cpp" line="126"/>
<source>Unexpected number of values in text record</source> <source>Unexpected number of values in text record</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Numero inaspettato di valori nel text-record</translation> <translation>Numero inaspettato di valori nel text-record</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="132"/> <location filename="../widget/form/addfriendform.cpp" line="133"/>
<source>The DNS lookup does not contain any Tox ID</source> <source>The DNS lookup does not contain any Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>La risposta del server DNS non contiene nessun Tox ID</translation> <translation>La risposta del server DNS non contiene nessun Tox ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="138"/> <location filename="../widget/form/addfriendform.cpp" line="139"/>
<location filename="../widget/form/addfriendform.cpp" line="144"/> <location filename="../widget/form/addfriendform.cpp" line="145"/>
<source>The DNS lookup does not contain a valid Tox ID</source> <source>The DNS lookup does not contain a valid Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>La risposta del server DNS non contiene un Tox ID valido</translation> <translation>La risposta del server DNS non contiene un Tox ID valido</translation>
@ -91,13 +91,13 @@
<context> <context>
<name>ChatForm</name> <name>ChatForm</name>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="291"/> <location filename="../widget/form/chatform.cpp" line="283"/>
<source>Send a file</source> <source>Send a file</source>
<translation>Invia un file</translation> <translation>Invia un file</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="616"/> <location filename="../widget/form/chatform.cpp" line="620"/>
<location filename="../widget/form/chatform.cpp" line="622"/> <location filename="../widget/form/chatform.cpp" line="626"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Salva il log della chat</translation> <translation>Salva il log della chat</translation>
</message> </message>
@ -113,11 +113,23 @@
<context> <context>
<name>FileTransfertWidget</name> <name>FileTransfertWidget</name>
<message> <message>
<location filename="../widget/filetransfertwidget.cpp" line="270"/> <location filename="../widget/filetransfertwidget.cpp" line="281"/>
<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>Salva file</translation> <translation>Salva file</translation>
</message> </message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<source>Location not writable</source>
<comment>Title of permissions popup</comment>
<translation>Errore</translation>
</message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<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>Non hai sufficienti permessi per scrivere in questa locazione. Scegli un&apos;altra posizione, o annulla il salvataggio.</translation>
</message>
</context> </context>
<context> <context>
<name>FilesForm</name> <name>FilesForm</name>
@ -125,15 +137,15 @@
<location filename="../widget/form/filesform.cpp" line="25"/> <location filename="../widget/form/filesform.cpp" line="25"/>
<source>Transfered Files</source> <source>Transfered Files</source>
<comment>&quot;Headline&quot; of the window</comment> <comment>&quot;Headline&quot; of the window</comment>
<translation>Files inviati</translation> <translation>Files Trasferiti</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/filesform.cpp" line="30"/> <location filename="../widget/form/filesform.cpp" line="33"/>
<source>Downloads</source> <source>Downloads</source>
<translation>Ricevuti</translation> <translation>Ricevuti</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/filesform.cpp" line="31"/> <location filename="../widget/form/filesform.cpp" line="34"/>
<source>Uploads</source> <source>Uploads</source>
<translation>Inviati</translation> <translation>Inviati</translation>
</message> </message>
@ -177,19 +189,19 @@
<context> <context>
<name>FriendWidget</name> <name>FriendWidget</name>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="87"/> <location filename="../widget/friendwidget.cpp" line="86"/>
<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>Copia Tox ID del contatto</translation> <translation>Copia Tox ID del contatto</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="88"/> <location filename="../widget/friendwidget.cpp" line="87"/>
<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>Invita nel gruppo</translation> <translation>Invita nel gruppo</translation>
</message> </message>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="98"/> <location filename="../widget/friendwidget.cpp" line="97"/>
<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>Rimuovi contatto</translation> <translation>Rimuovi contatto</translation>
@ -198,23 +210,23 @@
<context> <context>
<name>GroupChatForm</name> <name>GroupChatForm</name>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="48"/> <location filename="../widget/form/groupchatform.cpp" line="49"/>
<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 utenti in chat</translation> <translation>%1 utenti in chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="173"/> <location filename="../widget/form/groupchatform.cpp" line="146"/>
<source>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<translation>&lt;Sconosciuto&gt;</translation> <translation>&lt;Sconosciuto&gt;</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="242"/> <location filename="../widget/form/groupchatform.cpp" line="215"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 utenti in chat</translation> <translation>%1 utenti in chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="261"/> <location filename="../widget/form/groupchatform.cpp" line="234"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Salva il log della chat</translation> <translation>Salva il log della chat</translation>
</message> </message>
@ -223,23 +235,71 @@
<name>GroupWidget</name> <name>GroupWidget</name>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="54"/> <location filename="../widget/groupwidget.cpp" line="54"/>
<location filename="../widget/groupwidget.cpp" line="146"/> <location filename="../widget/groupwidget.cpp" line="141"/>
<source>%1 users in chat</source> <source>%1 users in chat</source>
<translation>%1 utenti in chat</translation> <translation>%1 utenti in chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="56"/> <location filename="../widget/groupwidget.cpp" line="56"/>
<location filename="../widget/groupwidget.cpp" line="148"/> <location filename="../widget/groupwidget.cpp" line="143"/>
<source>0 users in chat</source> <source>0 users in chat</source>
<translation>0 utenti in chat</translation> <translation>0 utenti in chat</translation>
</message> </message>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="89"/> <location filename="../widget/groupwidget.cpp" line="84"/>
<source>Quit group</source> <source>Quit group</source>
<comment>Menu to quit a groupchat</comment> <comment>Menu to quit a groupchat</comment>
<translation>Esci dal gruppo</translation> <translation>Esci dal gruppo</translation>
</message> </message>
</context> </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="1918"/>
<source>Your name</source>
<translation>qTox User</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2000"/>
<source>Your status</source>
<translation>Toxin on qTox</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2566"/>
<source>Add friends</source>
<translation>Aggiungi contatto</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2592"/>
<source>Create a group chat</source>
<translation>Crea un gruppo</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2624"/>
<source>View completed file transfers</source>
<translation>Visualizza i trasferimenti completati</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2656"/>
<source>Change your settings</source>
<translation>Cambia le impostazioni</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3238"/>
<source>Close</source>
<translation>Chiudi</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3241"/>
<source>Ctrl+Q</source>
<translation>Ctrl+Q</translation>
</message>
</context>
<context> <context>
<name>SelfCamView</name> <name>SelfCamView</name>
<message> <message>
@ -252,115 +312,129 @@
<context> <context>
<name>SettingsForm</name> <name>SettingsForm</name>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="31"/> <location filename="../widget/form/settingsform.cpp" line="34"/>
<source>User Settings</source> <source>User Settings</source>
<comment>&quot;Headline&quot; of the window</comment> <comment>&quot;Headline&quot; of the window</comment>
<translation>Impostazioni</translation> <translation>Impostazioni</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="34"/> <location filename="../widget/form/settingsform.cpp" line="37"/>
<source>Name</source> <source>Name</source>
<comment>Username/nick</comment> <comment>Username/nick</comment>
<translation>Nome</translation> <translation>Nome</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="35"/> <location filename="../widget/form/settingsform.cpp" line="38"/>
<source>Status</source> <source>Status</source>
<comment>Status message</comment> <comment>Status message</comment>
<translation>Stato</translation> <translation>Stato</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="36"/> <location filename="../widget/form/settingsform.cpp" line="39"/>
<source>(click here to copy)</source> <source>(click here to copy)</source>
<comment>Click on this text to copy TID to clipboard</comment> <comment>Click on this text to copy TID to clipboard</comment>
<translation>(clicca qui per copiare)</translation> <translation>(clicca qui per copiare)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="44"/> <location filename="../widget/form/settingsform.cpp" line="47"/>
<source>Test video</source> <source>Test video</source>
<comment>Text on a button to test the video/webcam</comment> <comment>Text on a button to test the video/webcam</comment>
<translation>Prova la webcam</translation> <translation>Prova la webcam</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="45"/> <location filename="../widget/form/settingsform.cpp" line="48"/>
<source>Enable IPv6 (recommended)</source> <source>Enable IPv6 (recommended)</source>
<comment>Text on a checkbox to enable IPv6</comment> <comment>Text on a checkbox to enable IPv6</comment>
<translation>Abilita IPv6 (consigliato)</translation> <translation>Abilita IPv6 (consigliato)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="47"/> <location filename="../widget/form/settingsform.cpp" line="50"/>
<source>Use translations</source> <source>Use translations</source>
<comment>Text on a checkbox to enable translations</comment> <comment>Text on a checkbox to enable translations</comment>
<translation>Abilita traduzioni</translation> <translation>Abilita traduzioni</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="49"/> <location filename="../widget/form/settingsform.cpp" line="52"/>
<source>Make Tox portable</source> <source>Make Tox portable</source>
<comment>Text on a checkbox to make qTox a portable application</comment> <comment>Text on a checkbox to make qTox a portable application</comment>
<translation>Rendi qTox portabile</translation> <translation>Rendi qTox portabile</translation>
</message> </message>
<message>
<location filename="../widget/form/settingsform.cpp" line="54"/>
<source>Save settings to the working directory instead of the usual conf dir</source>
<comment>describes makeToxPortable checkbox</comment>
<translation>Slava le impostazioni nella directory di lavoro corrente, invece della directory di default</translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="56"/>
<source>Smiley Pack</source>
<comment>Text on smiley pack label</comment>
<translation>Emoticons</translation>
</message>
<message>
<source>Select smiley pack</source>
<translation type="obsolete">Scegli pacchetto emoticons</translation>
</message>
</context> </context>
<context> <context>
<name>Widget</name> <name>Widget</name>
<message> <message>
<location filename="../widget.ui" line="42"/>
<source>Tox</source> <source>Tox</source>
<translation>Tox</translation> <translation type="obsolete">Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="1951"/>
<source>Your name</source> <source>Your name</source>
<translation>Tox User</translation> <translation type="obsolete">Tox User</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2033"/>
<source>Your status</source> <source>Your status</source>
<translation>Toxin on qTox</translation> <translation type="obsolete">Toxin on qTox</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2610"/>
<source>Add friends</source> <source>Add friends</source>
<translation>Aggiungi contatto</translation> <translation type="obsolete">Aggiungi contatto</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2652"/>
<source>Create a group chat</source> <source>Create a group chat</source>
<translation>Crea un gruppo</translation> <translation type="obsolete">Crea un gruppo</translation>
</message>
<message>
<source>View completed file transfers</source>
<translation type="obsolete">Visualizza i trasferimenti completati</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2700"/>
<source>(button inactive currently)</source> <source>(button inactive currently)</source>
<translation>(bottone attualmente inattivo)</translation> <translation type="obsolete">(bottone attualmente inattivo)</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2748"/>
<source>Change your settings</source> <source>Change your settings</source>
<translation>Cambia le impostazioni</translation> <translation type="obsolete">Cambia le impostazioni</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3321"/>
<source>Close</source> <source>Close</source>
<translation>Chiudi</translation> <translation type="obsolete">Chiudi</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3324"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translation>Ctrl+Q</translation> <translation type="obsolete">Ctrl+Q</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="128"/>
<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 type="obsolete">Online</translation> <translation>Online</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="130"/>
<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 type="obsolete">Assente</translation> <translation>Assente</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="132"/>
<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 type="obsolete">Occupato</translation> <translation>Occupato</translation>
</message> </message>
</context> </context>
</TS> </TS>

View File

@ -27,51 +27,51 @@
<translation>Отправить запрос на добавление в друзья</translation> <translation>Отправить запрос на добавление в друзья</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="84"/> <location filename="../widget/form/addfriendform.cpp" line="38"/>
<source>Tox me maybe?</source> <source>Tox me maybe?</source>
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment> <comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
<translatorcomment>Вот таким нехитрым и незамысловатым образом решаются сложные переводчиские проблемы</translatorcomment> <translatorcomment>Вот таким нехитрым и незамысловатым образом решаются сложные переводчиские проблемы</translatorcomment>
<translation>Добавь меня, а?</translation> <translation>Добавь меня, а?</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="92"/> <location filename="../widget/form/addfriendform.cpp" line="93"/>
<source>Please fill in a valid Tox ID</source> <source>Please fill in a valid Tox ID</source>
<comment>Tox ID of the friend you&apos;re sending a friend request to</comment> <comment>Tox ID of the friend you&apos;re sending a friend request to</comment>
<translation>Пожалуйста, введите корректный Tox ID</translation> <translation>Пожалуйста, введите корректный Tox ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="109"/> <location filename="../widget/form/addfriendform.cpp" line="110"/>
<source>This address does not exist</source> <source>This address does not exist</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Нет такого адреса</translation> <translation>Нет такого адреса</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="113"/> <location filename="../widget/form/addfriendform.cpp" line="114"/>
<source>Error while looking up DNS</source> <source>Error while looking up DNS</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment> <comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Ошибка при просмотре DNS</translation> <translation>Ошибка при просмотре DNS</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="119"/> <location filename="../widget/form/addfriendform.cpp" line="120"/>
<source>Unexpected number of text records</source> <source>Unexpected number of text records</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Непредвиденное количество текстовых записей</translation> <translation>Непредвиденное количество текстовых записей</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="125"/> <location filename="../widget/form/addfriendform.cpp" line="126"/>
<source>Unexpected number of values in text record</source> <source>Unexpected number of values in text record</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Непредвиденное количество значений в текстовой записи</translation> <translation>Непредвиденное количество значений в текстовой записи</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="132"/> <location filename="../widget/form/addfriendform.cpp" line="133"/>
<source>The DNS lookup does not contain any Tox ID</source> <source>The DNS lookup does not contain any Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>В ответе DNS ни одного Tox ID</translation> <translation>В ответе DNS ни одного Tox ID</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/addfriendform.cpp" line="138"/> <location filename="../widget/form/addfriendform.cpp" line="139"/>
<location filename="../widget/form/addfriendform.cpp" line="144"/> <location filename="../widget/form/addfriendform.cpp" line="145"/>
<source>The DNS lookup does not contain a valid Tox ID</source> <source>The DNS lookup does not contain a valid Tox ID</source>
<comment>Error with the DNS</comment> <comment>Error with the DNS</comment>
<translation>Ответ DNS не содержит корректных Tox ID</translation> <translation>Ответ DNS не содержит корректных Tox ID</translation>
@ -93,13 +93,13 @@
<context> <context>
<name>ChatForm</name> <name>ChatForm</name>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="291"/> <location filename="../widget/form/chatform.cpp" line="283"/>
<source>Send a file</source> <source>Send a file</source>
<translation>Отправить файл</translation> <translation>Отправить файл</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/chatform.cpp" line="616"/> <location filename="../widget/form/chatform.cpp" line="620"/>
<location filename="../widget/form/chatform.cpp" line="622"/> <location filename="../widget/form/chatform.cpp" line="626"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Сохранить лог чата</translation> <translation>Сохранить лог чата</translation>
</message> </message>
@ -115,11 +115,23 @@
<context> <context>
<name>FileTransfertWidget</name> <name>FileTransfertWidget</name>
<message> <message>
<location filename="../widget/filetransfertwidget.cpp" line="270"/> <location filename="../widget/filetransfertwidget.cpp" line="281"/>
<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>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<source>Location not writable</source>
<comment>Title of permissions popup</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/filetransfertwidget.cpp" line="292"/>
<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"></translation>
</message>
</context> </context>
<context> <context>
<name>FilesForm</name> <name>FilesForm</name>
@ -130,12 +142,12 @@
<translation>Переданные файлы</translation> <translation>Переданные файлы</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/filesform.cpp" line="30"/> <location filename="../widget/form/filesform.cpp" line="33"/>
<source>Downloads</source> <source>Downloads</source>
<translation>Загрузки</translation> <translation>Загрузки</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/filesform.cpp" line="31"/> <location filename="../widget/form/filesform.cpp" line="34"/>
<source>Uploads</source> <source>Uploads</source>
<translation>Выгрузки</translation> <translation>Выгрузки</translation>
</message> </message>
@ -181,19 +193,19 @@
<context> <context>
<name>FriendWidget</name> <name>FriendWidget</name>
<message> <message>
<location filename="../widget/friendwidget.cpp" line="87"/> <location filename="../widget/friendwidget.cpp" line="86"/>
<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="88"/> <location filename="../widget/friendwidget.cpp" line="87"/>
<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="98"/> <location filename="../widget/friendwidget.cpp" line="97"/>
<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>
@ -202,23 +214,23 @@
<context> <context>
<name>GroupChatForm</name> <name>GroupChatForm</name>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="48"/> <location filename="../widget/form/groupchatform.cpp" line="49"/>
<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="173"/> <location filename="../widget/form/groupchatform.cpp" line="146"/>
<source>&lt;Unknown&gt;</source> <source>&lt;Unknown&gt;</source>
<translation>&lt;Неизвестно&gt;</translation> <translation>&lt;Неизвестно&gt;</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/groupchatform.cpp" line="242"/> <location filename="../widget/form/groupchatform.cpp" line="215"/>
<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/form/groupchatform.cpp" line="261"/> <location filename="../widget/form/groupchatform.cpp" line="234"/>
<source>Save chat log</source> <source>Save chat log</source>
<translation>Сохранить лог чата</translation> <translation>Сохранить лог чата</translation>
</message> </message>
@ -226,24 +238,72 @@
<context> <context>
<name>GroupWidget</name> <name>GroupWidget</name>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="89"/> <location filename="../widget/groupwidget.cpp" line="84"/>
<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>
<message> <message>
<location filename="../widget/groupwidget.cpp" line="54"/> <location filename="../widget/groupwidget.cpp" line="54"/>
<location filename="../widget/groupwidget.cpp" line="146"/> <location filename="../widget/groupwidget.cpp" line="141"/>
<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="56"/> <location filename="../widget/groupwidget.cpp" line="56"/>
<location filename="../widget/groupwidget.cpp" line="148"/> <location filename="../widget/groupwidget.cpp" line="143"/>
<source>0 users in chat</source> <source>0 users in chat</source>
<translation>Ни одного пользователя в чате</translation> <translation>Ни одного пользователя в чате</translation>
</message> </message>
</context> </context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="20"/>
<source>qTox</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1918"/>
<source>Your name</source>
<translation type="unfinished">Ваше имя</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2000"/>
<source>Your status</source>
<translation type="unfinished">Ваш статус</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2566"/>
<source>Add friends</source>
<translation type="unfinished">Добавить друзей</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2592"/>
<source>Create a group chat</source>
<translation type="unfinished">Создать групповой чат</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2624"/>
<source>View completed file transfers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="2656"/>
<source>Change your settings</source>
<translation type="unfinished">Изменить ваши настройки</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3238"/>
<source>Close</source>
<translation type="unfinished">Закрыть</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="3241"/>
<source>Ctrl+Q</source>
<translation type="unfinished">Ctrl+Q</translation>
</message>
</context>
<context> <context>
<name>SelfCamView</name> <name>SelfCamView</name>
<message> <message>
@ -256,117 +316,123 @@
<context> <context>
<name>SettingsForm</name> <name>SettingsForm</name>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="31"/> <location filename="../widget/form/settingsform.cpp" line="34"/>
<source>User Settings</source> <source>User Settings</source>
<comment>&quot;Headline&quot; of the window</comment> <comment>&quot;Headline&quot; of the window</comment>
<translation>Пользовательские настройки</translation> <translation>Пользовательские настройки</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="34"/> <location filename="../widget/form/settingsform.cpp" line="37"/>
<source>Name</source> <source>Name</source>
<comment>Username/nick</comment> <comment>Username/nick</comment>
<translation>Имя</translation> <translation>Имя</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="35"/> <location filename="../widget/form/settingsform.cpp" line="38"/>
<source>Status</source> <source>Status</source>
<comment>Status message</comment> <comment>Status message</comment>
<translation>Статус</translation> <translation>Статус</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="36"/> <location filename="../widget/form/settingsform.cpp" line="39"/>
<source>(click here to copy)</source> <source>(click here to copy)</source>
<comment>Click on this text to copy TID to clipboard</comment> <comment>Click on this text to copy TID to clipboard</comment>
<translation>(нажмите здесь чтобы скопировать)</translation> <translation>(нажмите здесь чтобы скопировать)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="44"/> <location filename="../widget/form/settingsform.cpp" line="47"/>
<source>Test video</source> <source>Test video</source>
<comment>Text on a button to test the video/webcam</comment> <comment>Text on a button to test the video/webcam</comment>
<translation>Проверить видео</translation> <translation>Проверить видео</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="45"/> <location filename="../widget/form/settingsform.cpp" line="48"/>
<source>Enable IPv6 (recommended)</source> <source>Enable IPv6 (recommended)</source>
<comment>Text on a checkbox to enable IPv6</comment> <comment>Text on a checkbox to enable IPv6</comment>
<translation>Включить IPv6 (рекомендуется)</translation> <translation>Включить IPv6 (рекомендуется)</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="47"/> <location filename="../widget/form/settingsform.cpp" line="50"/>
<source>Use translations</source> <source>Use translations</source>
<comment>Text on a checkbox to enable translations</comment> <comment>Text on a checkbox to enable translations</comment>
<translatorcomment>Так гораздо понятнее, чем «использовать переводы»</translatorcomment> <translatorcomment>Так гораздо понятнее, чем «использовать переводы»</translatorcomment>
<translation>Русскоязычный интерфейс</translation> <translation>Русскоязычный интерфейс</translation>
</message> </message>
<message> <message>
<location filename="../widget/form/settingsform.cpp" line="49"/> <location filename="../widget/form/settingsform.cpp" line="52"/>
<source>Make Tox portable</source> <source>Make Tox portable</source>
<comment>Text on a checkbox to make qTox a portable application</comment> <comment>Text on a checkbox to make qTox a portable application</comment>
<translation>Портативный режим</translation> <translation>Портативный режим</translation>
</message> </message>
<message>
<location filename="../widget/form/settingsform.cpp" line="54"/>
<source>Save settings to the working directory instead of the usual conf dir</source>
<comment>describes makeToxPortable checkbox</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget/form/settingsform.cpp" line="56"/>
<source>Smiley Pack</source>
<comment>Text on smiley pack label</comment>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Widget</name> <name>Widget</name>
<message> <message>
<location filename="../widget.ui" line="42"/>
<source>Tox</source> <source>Tox</source>
<translation>Tox</translation> <translation type="vanished">Tox</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="1951"/>
<source>Your name</source> <source>Your name</source>
<translation>Ваше имя</translation> <translation type="vanished">Ваше имя</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2033"/>
<source>Your status</source> <source>Your status</source>
<translation>Ваш статус</translation> <translation type="vanished">Ваш статус</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2610"/>
<source>Add friends</source> <source>Add friends</source>
<translation>Добавить друзей</translation> <translation type="vanished">Добавить друзей</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2652"/>
<source>Create a group chat</source> <source>Create a group chat</source>
<translation>Создать групповой чат</translation> <translation type="vanished">Создать групповой чат</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2700"/>
<source>(button inactive currently)</source> <source>(button inactive currently)</source>
<translation>(кнопка на данный момент неактивна)</translation> <translation type="vanished">(кнопка на данный момент неактивна)</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="2748"/>
<source>Change your settings</source> <source>Change your settings</source>
<translation>Изменить ваши настройки</translation> <translation type="vanished">Изменить ваши настройки</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3321"/>
<source>Close</source> <source>Close</source>
<translation>Закрыть</translation> <translation type="vanished">Закрыть</translation>
</message> </message>
<message> <message>
<location filename="../widget.ui" line="3324"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translation>Ctrl+Q</translation> <translation type="vanished">Ctrl+Q</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="128"/>
<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 type="obsolete">В сети</translation> <translation type="unfinished">В сети</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="130"/>
<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>
<translatorcomment>Вероятно, это не столь долгое путешествие</translatorcomment> <translatorcomment>Вероятно, это не столь долгое путешествие</translatorcomment>
<translation type="obsolete">Отошёл</translation> <translation type="unfinished">Отошёл</translation>
</message> </message>
<message> <message>
<location filename="../widget/widget.cpp" line="132"/>
<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 type="obsolete">Занят</translation> <translation type="unfinished">Занят</translation>
</message> </message>
</context> </context>
</TS> </TS>

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

View File

@ -0,0 +1,40 @@
QPushButton
{
background-color: transparent;
background-repeat: none;
border: none;
width: 18px;
height: 18px;
}
QRadioButton::indicator
{
width: 10px;
height: 10px;
}
QRadioButton::indicator::unchecked
{
image: url(:/ui/emoticonWidget/dot_page.png);
}
QRadioButton::indicator:unchecked:hover
{
image: url(:/ui/emoticonWidget/dot_page_hover.png);
}
QRadioButton::indicator:unchecked:pressed
{
image: url(:/ui/emoticonWidget/dot_page_hover.png);
}
QRadioButton::indicator::checked
{
image: url(:/ui/emoticonWidget/dot_page_current.png);
}
QMenu
{
background-color: rgb(240,240,240); /* sets background of the menu */
border: 1px solid;
}

View File

@ -1,34 +1,34 @@
QScrollArea { QScrollArea {
background: #414141!important; background: #414141;
} }
QScrollBar:vertical { QScrollBar:vertical {
background: #414141!important; background: transparent;
width: 14px!important; width: 14px;
margin-top: 2px!important; margin-top: 2px;
margin-bottom: 2px!important; margin-bottom: 2px;
} }
QScrollBar:handle:vertical { QScrollBar:handle:vertical {
background: #1c1c1c!important; background: rgba(18, 18, 18, 204);
min-height: 20px!important; min-height: 20px;
border-radius: 3px!important; border-radius: 3px;
margin-left: 3px!important; margin-left: 3px;
margin-right: 1px!important; margin-right: 1px;
} }
QScrollBar:handle:vertical:hover { QScrollBar:handle:vertical:hover {
background: #2d2d2d!important; background: rgba(35, 35, 35, 204);
} }
QScrollBar:handle:vertical:pressed { QScrollBar:handle:vertical:pressed {
background: #171717!important; background: rgba(13, 13, 13, 204);
} }
QScrollBar:add-line:vertical {height: 0px!important;subcontrol-position: bottom!important;subcontrol-origin: margin!important;} QScrollBar:add-line:vertical {height: 0px;subcontrol-position: bottom;subcontrol-origin: margin;}
QScrollBar:sub-line:vertical {height: 0px!important;subcontrol-position: top!important;subcontrol-origin: margin!important;} QScrollBar:sub-line:vertical {height: 0px;subcontrol-position: top;subcontrol-origin: margin;}
QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical { QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical {
background: none!important; background: none;
} }

View File

@ -0,0 +1,38 @@
QPushButton#green
{
background-color: transparent;
background-image: url(":/ui/micButton/micButton.png");
background-repeat: none;
border: none;
width: 25px;
height: 20px;
}
QPushButton#green:hover
{
background-image:url(":/ui/micButton/micButtonHover.png");
}
QPushButton#red
{
background-color: transparent;
background-image: url(":/ui/micButton/micButtonPressed.png");
background-repeat: none;
border: none;
width: 25px;
height: 20px;
}
QPushButton#grey
{
background-color: transparent;
background-image: url(":/ui/micButton/micButtonDisabled.png");
background-repeat: none;
border: none;
width: 25px;
height: 20px;
}
QPushButton:focus {
outline: none;
}

BIN
ui/micButton/micButton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

View File

@ -0,0 +1,23 @@
QPushButton#green
{
background-color: transparent;
background-image: url(":/ui/volButton/volButton.png");
background-repeat: none;
border: none;
width: 25px;
height: 20px;
}
QPushButton#green:hover
{
background-image: url(":/ui/volButton/volButtonHover.png");
}
QPushButton#green:pressed
{
background-image: url(":/ui/volButton/volButtonPressed.png");
}
QPushButton:focus {
outline: none;
}

BIN
ui/volButton/volButton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

3357
widget.ui

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
/*
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 "adjustingscrollarea.h"
#include <QEvent>
#include <QLayout>
#include <QScrollBar>
#include <QDebug>
AdjustingScrollArea::AdjustingScrollArea(QWidget *parent) :
QScrollArea(parent)
{
}
void AdjustingScrollArea::resizeEvent(QResizeEvent *ev)
{
updateGeometry();
QScrollArea::resizeEvent(ev);
}
QSize AdjustingScrollArea::sizeHint() const
{
if (widget())
{
int scrollbarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
return widget()->sizeHint() + QSize(scrollbarWidth, 0);
}
return QScrollArea::sizeHint();
}

View File

@ -14,23 +14,23 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#ifndef CLICKABLELABEL_H #ifndef ADJUSTINGSCROLLAREA_H
#define CLICKABLELABEL_H #define ADJUSTINGSCROLLAREA_H
#include <QLabel> #include <QScrollArea>
class ClickableLabel : public QLabel class AdjustingScrollArea : public QScrollArea
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ClickableLabel(QWidget *parent = 0); explicit AdjustingScrollArea(QWidget *parent = 0);
virtual void resizeEvent(QResizeEvent *ev);
virtual QSize sizeHint() const override;
signals: signals:
void clicked();
protected: public slots:
void mousePressEvent ( QMouseEvent * event );
}; };
#endif // CLICKABLELABEL_H #endif // ADJUSTINGSCROLLAREA_H

View File

@ -15,59 +15,13 @@
*/ */
#include "camera.h" #include "camera.h"
#include <QVideoSurfaceFormat>
#include <QMessageBox> #include <QMessageBox>
#include <QVideoEncoderSettings>
#include <QVideoEncoderSettingsControl>
static inline void fromYCbCrToRGB( using namespace cv;
uint8_t Y, uint8_t Cb, uint8_t Cr,
uint8_t& R, uint8_t& G, uint8_t& B)
{
int r = Y + ((1436 * (Cr - 128)) >> 10),
g = Y - ((354 * (Cb - 128) + 732 * (Cr - 128)) >> 10),
b = Y + ((1814 * (Cb - 128)) >> 10);
if(r < 0) {
r = 0;
} else if(r > 255) {
r = 255;
}
if(g < 0) {
g = 0;
} else if(g > 255) {
g = 255;
}
if(b < 0) {
b = 0;
} else if(b > 255) {
b = 255;
}
R = static_cast<uint8_t>(r);
G = static_cast<uint8_t>(g);
B = static_cast<uint8_t>(b);
}
Camera::Camera() Camera::Camera()
: refcount{0}, camera{new QCamera} : refcount{0}
{ {
camera->setCaptureMode(QCamera::CaptureVideo);
camera->setViewfinder(this);
#if 0 // Crashes on Windows
QMediaService *m = camera->service();
QVideoEncoderSettingsControl *enc = m->requestControl<QVideoEncoderSettingsControl*>();
QVideoEncoderSettings sets = enc->videoSettings();
sets.setResolution(640, 480);
enc->setVideoSettings(sets);
#endif
connect(camera, SIGNAL(error(QCamera::Error)), this, SLOT(onCameraError(QCamera::Error)));
supportedFormats << QVideoFrame::Format_YUV420P << QVideoFrame::Format_YV12 << QVideoFrame::Format_RGB32;
} }
void Camera::suscribe() void Camera::suscribe()
@ -75,7 +29,7 @@ void Camera::suscribe()
if (refcount <= 0) if (refcount <= 0)
{ {
refcount = 1; refcount = 1;
camera->start(); cam.open(0);
} }
else else
refcount++; refcount++;
@ -87,226 +41,76 @@ void Camera::unsuscribe()
if (refcount <= 0) if (refcount <= 0)
{ {
camera->stop(); cam.release();
refcount = 0; refcount = 0;
} }
} }
QVideoFrame Camera::getLastFrame() Mat Camera::getLastFrame()
{ {
return lastFrame; Mat frame;
cam >> frame;
return frame;
} }
bool Camera::start(const QVideoSurfaceFormat &format)
{
if(supportedFormats.contains(format.pixelFormat()))
{
frameFormat = format.pixelFormat();
QAbstractVideoSurface::start(format);
return true;
}
else
{
QMessageBox::warning(0, "Camera error", "The camera only supports rare video formats, can't use it");
return false;
}
}
bool Camera::present(const QVideoFrame &frame)
{
QVideoFrame frameMap(frame); // Basically a const_cast because shallow copies
if (!frameMap.map(QAbstractVideoBuffer::ReadOnly))
{
qWarning() << "Camera::present: Unable to map frame";
return false;
}
int w = frameMap.width(), h = frameMap.height();
int bpl = frameMap.bytesPerLine(), size = frameMap.mappedBytes();
QVideoFrame frameCopy(size, QSize(w, h), bpl, frameMap.pixelFormat());
frameCopy.map(QAbstractVideoBuffer::WriteOnly);
memcpy(frameCopy.bits(), frameMap.bits(), size);
frameCopy.unmap();
lastFrame = frameCopy;
frameMap.unmap();
return true;
}
QList<QVideoFrame::PixelFormat> Camera::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
if (handleType == QAbstractVideoBuffer::NoHandle)
return supportedFormats;
else
return QList<QVideoFrame::PixelFormat>();
}
void Camera::onCameraError(QCamera::Error value)
{
QMessageBox::warning(0,"Camera error",QString("Error %1 : %2")
.arg(value).arg(camera->errorString()));
}
bool Camera::isFormatSupported(const QVideoSurfaceFormat& format) const
{
if (format.pixelFormat() == 0)
{
//QMessageBox::warning(0, "Camera eror","The camera's video format is not supported !");
return QAbstractVideoSurface::isFormatSupported(format);
}
else if(supportedFormats.contains(format.pixelFormat()))
{
return true;
}
else
{
QMessageBox::warning(0, tr("Camera eror"),
tr("Camera format %1 not supported, can't use the camera")
.arg(format.pixelFormat()));
return false;
}
}
QImage Camera::getLastImage() QImage Camera::getLastImage()
{ {
if (!lastFrame.map(QAbstractVideoBuffer::ReadOnly)) Mat3b src = getLastFrame();
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
for (int y = 0; y < src.rows; ++y)
{ {
qWarning() << "Camera::getLastImage: Error maping last frame"; const cv::Vec3b *srcrow = src[y];
return QImage(); 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);
} }
int w = lastFrame.width(), h = lastFrame.height(); return dest;
int bpl = lastFrame.bytesPerLine(), cxbpl = bpl/2;
QImage img(w, h, QImage::Format_RGB32);
if (frameFormat == QVideoFrame::Format_YUV420P)
{
uint8_t* yData = lastFrame.bits();
uint8_t* uData = yData + (bpl * h);
uint8_t* vData = uData + (bpl * h / 4);
for (int i = 0; i< h; i++)
{
uint32_t* scanline = (uint32_t*)img.scanLine(i);
for (int j=0; j < bpl; j++)
{
uint8_t Y = yData[i*bpl + j];
uint8_t U = uData[i/2*cxbpl + j/2];
uint8_t V = vData[i/2*cxbpl + j/2];
uint8_t R, G, B;
fromYCbCrToRGB(Y, U, V, R, G, B);
scanline[j] = (0xFF<<24) + (R<<16) + (G<<8) + B;
}
}
}
else if (frameFormat == QVideoFrame::Format_YV12)
{
uint8_t* yData = lastFrame.bits();
uint8_t* vData = yData + (bpl * h);
uint8_t* uData = vData + (bpl * h / 4);
for (int i = 0; i< h; i++)
{
uint32_t* scanline = (uint32_t*)img.scanLine(i);
for (int j=0; j < bpl; j++)
{
uint8_t Y = yData[i*bpl + j];
uint8_t U = uData[i/2*cxbpl + j/2];
uint8_t V = vData[i/2*cxbpl + j/2];
uint8_t R, G, B;
fromYCbCrToRGB(Y, U, V, R, G, B);
scanline[j] = (0xFF<<24) + (R<<16) + (G<<8) + B;
}
}
}
else if (frameFormat == QVideoFrame::Format_RGB32)
{
memcpy(img.bits(), lastFrame.bits(), bpl*h);
}
lastFrame.unmap();
return img;
} }
vpx_image Camera::getLastVPXImage() vpx_image Camera::getLastVPXImage()
{ {
Mat3b frame = getLastFrame();
vpx_image img; vpx_image img;
img.w = img.h = 0; int w = frame.size().width, h = frame.size().height;
if (!lastFrame.isValid())
return img;
if (!lastFrame.map(QAbstractVideoBuffer::ReadOnly))
{
qWarning() << "Camera::getLastVPXImage: Error maping last frame";
return img;
}
int w = lastFrame.width(), h = lastFrame.height();
int bpl = lastFrame.bytesPerLine();
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
if (frameFormat == QVideoFrame::Format_YUV420P) size_t i=0, j=0;
for( int line = 0; line < h; ++line )
{ {
uint8_t* yData = lastFrame.bits(); const cv::Vec3b *srcrow = frame[line];
uint8_t* uData = yData + (bpl * h); if( !(line % 2) )
uint8_t* vData = uData + (bpl * h / 4);
img.planes[VPX_PLANE_Y] = yData;
img.planes[VPX_PLANE_U] = uData;
img.planes[VPX_PLANE_V] = vData;
}
else if (frameFormat == QVideoFrame::Format_YV12)
{
uint8_t* yData = lastFrame.bits();
uint8_t* uData = yData + (bpl * h);
uint8_t* vData = uData + (bpl * h / 4);
img.planes[VPX_PLANE_Y] = yData;
img.planes[VPX_PLANE_U] = vData;
img.planes[VPX_PLANE_V] = uData;
}
else if (frameFormat == QVideoFrame::Format_RGB32 || frameFormat == QVideoFrame::Format_ARGB32)
{
qWarning() << "Camera::getLastVPXImage: Using experimental RGB32 conversion code";
uint8_t* rgb = lastFrame.bits();
size_t i=0, j=0;
for( size_t line = 0; line < h; ++line )
{ {
if( !(line % 2) ) for( int x = 0; x < w; x += 2 )
{ {
for( size_t x = 0; x < w; x += 2 ) uint8_t r = srcrow[x][2];
{ uint8_t g = srcrow[x][1];
uint8_t r = rgb[4 * i + 1]; uint8_t b = srcrow[x][0];
uint8_t g = rgb[4 * i + 2];
uint8_t b = rgb[4 * i + 3];
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16; img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
img.planes[VPX_PLANE_U][j] = ((-38*r + -74*g + 112*b) >> 8) + 128; img.planes[VPX_PLANE_V][j] = ((-38*r + -74*g + 112*b) >> 8) + 128;
img.planes[VPX_PLANE_V][j] = ((112*r + -94*g + -18*b) >> 8) + 128; img.planes[VPX_PLANE_U][j] = ((112*r + -94*g + -18*b) >> 8) + 128;
i++; i++;
j++; j++;
r = rgb[4 * i + 1]; r = srcrow[x+1][2];
g = rgb[4 * i + 2]; g = srcrow[x+1][1];
b = rgb[4 * i + 3]; b = srcrow[x+1][0];
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16; i++;
i++;
}
} }
else }
else
{
for( int x = 0; x < w; x += 1 )
{ {
for( size_t x = 0; x < w; x += 1 ) uint8_t r = srcrow[x][2];
{ uint8_t g = srcrow[x][1];
uint8_t r = rgb[4 * i + 1]; uint8_t b = srcrow[x][0];
uint8_t g = rgb[4 * i + 2];
uint8_t b = rgb[4 * i + 3];
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16; img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
i++; i++;
}
} }
} }
} }
lastFrame.unmap();
return img; return img;
} }

View File

@ -17,47 +17,29 @@
#ifndef CAMERA_H #ifndef CAMERA_H
#define CAMERA_H #define CAMERA_H
#include <QCamera> #include <QImage>
#include <QVideoFrame>
#include <QAbstractVideoSurface>
#include "vpx/vpx_image.h" #include "vpx/vpx_image.h"
#include "opencv2/opencv.hpp"
/** /**
* 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
* In Qt cameras normally only send their frames to a single output at a time * It allows objects to suscribe and unsuscribe to the stream, starting
* So you can't, for example, send the frames over the network * the camera only when needed, and giving access to the last frames
* and output them to a widget on the screen at the same time
*
* Instead this class allows objects to surscribe and unsuscribe, starting
* the camera only when needed, and giving access to the last frame
**/ **/
class Camera : private QAbstractVideoSurface class Camera
{ {
Q_OBJECT
public: public:
Camera(); Camera();
void suscribe(); ///< Call this once before trying to get frames void suscribe(); ///< Call this once before trying to get frames
void unsuscribe(); ///< Call this once when you don't need frames anymore void unsuscribe(); ///< Call this once when you don't need frames anymore
QVideoFrame getLastFrame(); ///< Get the last captured frame cv::Mat getLastFrame(); ///< Get the last captured frame
QImage getLastImage(); ///< Convert the last frame to a QImage (can be expensive !) 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 !)
bool isFormatSupported(const QVideoSurfaceFormat & format) const;
private slots:
void onCameraError(QCamera::Error value);
private:
bool start(const QVideoSurfaceFormat &format);
bool present(const QVideoFrame &frame);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const;
private: private:
int refcount; ///< Number of users suscribed to the camera int refcount; ///< Number of users suscribed to the camera
QCamera *camera; cv::VideoCapture cam; ///< OpenCV camera capture opbject
QVideoFrame lastFrame;
int frameFormat;
QList<QVideoFrame::PixelFormat> supportedFormats;
}; };
#endif // CAMERA_H #endif // CAMERA_H

142
widget/croppinglabel.cpp Normal file
View File

@ -0,0 +1,142 @@
/*
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 "croppinglabel.h"
#include <QResizeEvent>
CroppingLabel::CroppingLabel(QWidget* parent)
: QLabel(parent)
, blockPaintEvents(false)
, editable(false)
, elideMode(Qt::ElideRight)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
textEdit = new QLineEdit(this);
textEdit->hide();
installEventFilter(this);
textEdit->installEventFilter(this);
}
void CroppingLabel::setEditable(bool editable)
{
this->editable = editable;
if (editable)
setCursor(Qt::PointingHandCursor);
else
unsetCursor();
}
void CroppingLabel::setEdlideMode(Qt::TextElideMode elide)
{
elideMode = elide;
}
void CroppingLabel::setText(const QString& text)
{
origText = text.trimmed();
setElidedText();
}
void CroppingLabel::resizeEvent(QResizeEvent* ev)
{
setElidedText();
textEdit->resize(ev->size());
QLabel::resizeEvent(ev);
}
QSize CroppingLabel::sizeHint() const
{
return QSize(0, QLabel::sizeHint().height());
}
QSize CroppingLabel::minimumSizeHint() const
{
return QSize(fontMetrics().width("..."), QLabel::minimumSizeHint().height());
}
void CroppingLabel::mouseReleaseEvent(QMouseEvent *e)
{
if (editable)
showTextEdit();
emit clicked();
QLabel::mouseReleaseEvent(e);
}
bool CroppingLabel::eventFilter(QObject *obj, QEvent *e)
{
// catch paint events if needed
if (obj == this)
{
if (e->type() == QEvent::Paint && blockPaintEvents)
return true;
}
// events fired by the QLineEdit
if (obj == textEdit)
{
if (e->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
if (keyEvent->key() == Qt::Key_Return)
hideTextEdit(true);
if (keyEvent->key() == Qt::Key_Escape)
hideTextEdit(false);
}
if (e->type() == QEvent::FocusOut)
hideTextEdit(true);
}
return false;
}
void CroppingLabel::setElidedText()
{
QString elidedText = fontMetrics().elidedText(origText, elideMode, width());
if (elidedText != origText)
setToolTip(origText);
else
setToolTip(QString());
QLabel::setText(elidedText);
}
void CroppingLabel::hideTextEdit(bool acceptText)
{
if (acceptText)
{
emit textChanged(textEdit->text(), origText);
setText(textEdit->text());
}
textEdit->hide();
blockPaintEvents = false;
}
void CroppingLabel::showTextEdit()
{
blockPaintEvents = true;
textEdit->show();
textEdit->setFocus();
textEdit->setText(origText);
}

56
widget/croppinglabel.h Normal file
View File

@ -0,0 +1,56 @@
/*
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 CROPPINGLABEL_H
#define CROPPINGLABEL_H
#include <QLabel>
#include <QLineEdit>
class CroppingLabel : public QLabel
{
Q_OBJECT
public:
explicit CroppingLabel(QWidget *parent = 0);
void setEditable(bool editable);
void setEdlideMode(Qt::TextElideMode elide);
virtual void setText(const QString& text);
virtual void resizeEvent(QResizeEvent *ev);
virtual QSize sizeHint() const;
virtual QSize minimumSizeHint() const;
virtual void mouseReleaseEvent(QMouseEvent *e);
virtual bool eventFilter(QObject *obj, QEvent *e);
signals:
void textChanged(QString newText, QString oldText);
void clicked();
protected:
void setElidedText();
void hideTextEdit(bool acceptText);
void showTextEdit();
private:
QString origText;
QLineEdit* textEdit;
bool blockPaintEvents;
bool editable;
Qt::TextElideMode elideMode;
};
#endif // CROPPINGLABEL_H

136
widget/emoticonswidget.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
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 "emoticonswidget.h"
#include "smileypack.h"
#include "style.h"
#include <QPushButton>
#include <QRadioButton>
#include <QFile>
#include <QLayout>
#include <QGridLayout>
EmoticonsWidget::EmoticonsWidget(QWidget *parent) :
QMenu(parent)
{
setStyleSheet(Style::get(":/ui/emoticonWidget/emoticonWidget.css"));
setLayout(&layout);
layout.addWidget(&stack);
QWidget* pageButtonsContainer = new QWidget;
QHBoxLayout* buttonLayout = new QHBoxLayout;
pageButtonsContainer->setLayout(buttonLayout);
layout.addWidget(pageButtonsContainer);
const int maxCols = 5;
const int maxRows = 3;
const int itemsPerPage = maxRows * maxCols;
const QList<QStringList>& emoticons = SmileyPack::getInstance().getEmoticons();
int itemCount = emoticons.size();
int pageCount = (itemCount / itemsPerPage) + 1;
int currPage = 0;
int currItem = 0;
int row = 0;
int col = 0;
// create pages
buttonLayout->addStretch();
for (int i = 0; i < pageCount; i++)
{
QGridLayout* pageLayout = new QGridLayout;
pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), maxRows, 0);
pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, maxCols);
QWidget* page = new QWidget;
page->setLayout(pageLayout);
stack.addWidget(page);
// page buttons are only needed if there is more than 1 page
if (pageCount > 1)
{
QRadioButton* pageButton = new QRadioButton;
pageButton->setProperty("pageIndex", i);
pageButton->setChecked(i == 0);
buttonLayout->addWidget(pageButton);
connect(pageButton, &QRadioButton::clicked, this, &EmoticonsWidget::onPageButtonClicked);
}
}
buttonLayout->addStretch();
for (const QStringList& set : emoticons)
{
QPushButton* button = new QPushButton;
button->setIcon(SmileyPack::getInstance().getAsIcon(set[0]));
button->setToolTip(set.join(" "));
button->setProperty("sequence", set[0]);
button->setFlat(true);
connect(button, &QPushButton::clicked, this, &EmoticonsWidget::onSmileyClicked);
qobject_cast<QGridLayout*>(stack.widget(currPage)->layout())->addWidget(button, row, col);
col++;
currItem++;
// next row
if (col >= maxCols)
{
col = 0;
row++;
}
// next page
if (currItem >= itemsPerPage)
{
row = 0;
currItem = 0;
currPage++;
}
}
// calculates sizeHint
layout.activate();
}
void EmoticonsWidget::onSmileyClicked()
{
// hide the QMenu
QMenu::hide();
// emit insert emoticon
QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
if (sender)
emit insertEmoticon(' ' + sender->property("sequence").toString() + ' ');
}
void EmoticonsWidget::onPageButtonClicked()
{
QWidget* sender = qobject_cast<QRadioButton*>(QObject::sender());
if (sender)
{
int page = sender->property("pageIndex").toInt();
stack.setCurrentIndex(page);
}
}
QSize EmoticonsWidget::sizeHint() const
{
return layout.sizeHint();
}

View File

@ -14,29 +14,32 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#ifndef AUDIOBUFFER_H #ifndef EMOTICONSWIDGET_H
#define AUDIOBUFFER_H #define EMOTICONSWIDGET_H
#include <QIODevice> #include <QMenu>
#include <QByteArray> #include <QStackedWidget>
#include <QMutex> #include <QVBoxLayout>
class AudioBuffer : public QIODevice class EmoticonsWidget : public QMenu
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AudioBuffer(); explicit EmoticonsWidget(QWidget *parent = 0);
~AudioBuffer();
qint64 readData(char *data, qint64 maxlen); signals:
qint64 writeData(const char *data, qint64 len); void insertEmoticon(QString str);
qint64 bytesAvailable() const;
qint64 bufferSize() const; private slots:
void clear(); void onSmileyClicked();
void onPageButtonClicked();
private: private:
QByteArray buffer; QStackedWidget stack;
mutable QMutex bufferMutex; QVBoxLayout layout;
public:
virtual QSize sizeHint() const;
}; };
#endif // AUDIOBUFFER_H #endif // EMOTICONSWIDGET_H

View File

@ -18,6 +18,7 @@
#include "widget.h" #include "widget.h"
#include "core.h" #include "core.h"
#include "math.h" #include "math.h"
#include "style.h"
#include <QFileDialog> #include <QFileDialog>
#include <QPixmap> #include <QPixmap>
#include <QPainter> #include <QPainter>
@ -36,10 +37,7 @@ FileTransfertWidget::FileTransfertWidget(ToxFile File)
QFont prettysmall; QFont prettysmall;
prettysmall.setPixelSize(10); prettysmall.setPixelSize(10);
this->setObjectName("default"); this->setObjectName("default");
QFile f0(":/ui/fileTransferWidget/fileTransferWidget.css"); this->setStyleSheet(Style::get(":/ui/fileTransferWidget/fileTransferWidget.css"));
f0.open(QFile::ReadOnly | QFile::Text);
QTextStream fileTransfertWidgetStylesheet(&f0);
this->setStyleSheet(fileTransfertWidgetStylesheet.readAll());
QPalette greybg; QPalette greybg;
greybg.setColor(QPalette::Window, QColor(209,209,209)); greybg.setColor(QPalette::Window, QColor(209,209,209));
greybg.setColor(QPalette::Base, QColor(150,150,150)); greybg.setColor(QPalette::Base, QColor(150,150,150));
@ -71,20 +69,9 @@ FileTransfertWidget::FileTransfertWidget(ToxFile File)
buttonWidget->setAutoFillBackground(true); buttonWidget->setAutoFillBackground(true);
buttonWidget->setLayout(buttonLayout); buttonWidget->setLayout(buttonLayout);
QFile f1(":/ui/stopFileButton/style.css"); stopFileButtonStylesheet = Style::get(":/ui/stopFileButton/style.css");
f1.open(QFile::ReadOnly | QFile::Text); pauseFileButtonStylesheet = Style::get(":/ui/pauseFileButton/style.css");
QTextStream stopFileButtonStylesheetStream(&f1); acceptFileButtonStylesheet = Style::get(":/ui/acceptFileButton/style.css");
stopFileButtonStylesheet = stopFileButtonStylesheetStream.readAll();
QFile f2(":/ui/pauseFileButton/style.css");
f2.open(QFile::ReadOnly | QFile::Text);
QTextStream pauseFileButtonStylesheetStream(&f2);
pauseFileButtonStylesheet = pauseFileButtonStylesheetStream.readAll();
QFile f3(":/ui/acceptFileButton/style.css");
f3.open(QFile::ReadOnly | QFile::Text);
QTextStream acceptFileButtonStylesheetStream(&f3);
acceptFileButtonStylesheet = acceptFileButtonStylesheetStream.readAll();
topright->setStyleSheet(stopFileButtonStylesheet); topright->setStyleSheet(stopFileButtonStylesheet);
if (File.direction == ToxFile::SENDING) if (File.direction == ToxFile::SENDING)
@ -151,13 +138,13 @@ FileTransfertWidget::FileTransfertWidget(ToxFile File)
buttonLayout->setSpacing(0); buttonLayout->setSpacing(0);
} }
QString FileTransfertWidget::getHumanReadableSize(int size) QString FileTransfertWidget::getHumanReadableSize(unsigned long long size)
{ {
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"}; static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"};
int exp = 0; int exp = 0;
if (size) if (size)
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1)); exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1));
return QString().setNum(size / pow(1024, exp),'g',3).append(suffix[exp]); return QString().setNum(size / pow(1024, exp),'f',2).append(suffix[exp]);
} }
void FileTransfertWidget::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction) void FileTransfertWidget::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction)
@ -174,7 +161,7 @@ void FileTransfertWidget::onFileTransferInfo(int FriendId, int FileNum, int64_t
qWarning() << "FileTransfertWidget::onFileTransferInfo: Negative transfer speed !"; qWarning() << "FileTransfertWidget::onFileTransferInfo: Negative transfer speed !";
diff = 0; diff = 0;
} }
int rawspeed = diff / timediff; long rawspeed = diff / timediff;
speed->setText(getHumanReadableSize(rawspeed)+"/s"); speed->setText(getHumanReadableSize(rawspeed)+"/s");
size->setText(getHumanReadableSize(Filesize)); size->setText(getHumanReadableSize(Filesize));
if (!rawspeed) if (!rawspeed)
@ -184,10 +171,15 @@ void FileTransfertWidget::onFileTransferInfo(int FriendId, int FileNum, int64_t
etaTime = etaTime.addSecs(etaSecs); etaTime = etaTime.addSecs(etaSecs);
eta->setText(etaTime.toString("mm:ss")); eta->setText(etaTime.toString("mm:ss"));
if (!Filesize) if (!Filesize)
{
progress->setValue(0); progress->setValue(0);
qDebug() << QString("FT: received %1 bytes of an empty file, stop sending sequential devices, zetok!").arg(BytesSent);
}
else else
{
progress->setValue(BytesSent*100/Filesize); progress->setValue(BytesSent*100/Filesize);
qDebug() << QString("FT: received %1/%2 bytes, progress is %3%").arg(BytesSent).arg(Filesize).arg(BytesSent*100/Filesize); qDebug() << QString("FT: received %1/%2 bytes, progress is %3%").arg(BytesSent).arg(Filesize).arg(BytesSent*100/Filesize);
}
lastUpdate = newtime; lastUpdate = newtime;
lastBytesSent = BytesSent; lastBytesSent = BytesSent;
} }
@ -286,7 +278,7 @@ void FileTransfertWidget::acceptRecvRequest()
QString path; QString path;
while (true) while (true)
{ {
path = QFileDialog::getSaveFileName(0,tr("Save a file","Title of the file saving dialog"),QDir::currentPath()+'/'+filename->text()); path = QFileDialog::getSaveFileName(this, tr("Save a file","Title of the file saving dialog"), QDir::current().filePath(filename->text()));
if (path.isEmpty()) if (path.isEmpty())
return; return;
else else
@ -297,7 +289,7 @@ void FileTransfertWidget::acceptRecvRequest()
if (isWritable(path)) if (isWritable(path))
break; break;
else else
QMessageBox::warning(0, tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup")); QMessageBox::warning(this, tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
} }
} }

View File

@ -49,7 +49,7 @@ private slots:
void pauseResumeSend(); void pauseResumeSend();
private: private:
QString getHumanReadableSize(int size); QString getHumanReadableSize(unsigned long long size);
private: private:
QLabel *pic, *filename, *size, *speed, *eta; QLabel *pic, *filename, *size, *speed, *eta;

View File

@ -35,6 +35,7 @@ AddFriendForm::AddFriendForm() : dns(this)
toxIdLabel.setText(tr("Tox ID","Tox ID of the person you're sending a friend request to")); toxIdLabel.setText(tr("Tox ID","Tox ID of the person you're sending a friend request to"));
messageLabel.setText(tr("Message","The message you send in friend requests")); messageLabel.setText(tr("Message","The message you send in friend requests"));
sendButton.setText(tr("Send friend request")); sendButton.setText(tr("Send friend request"));
message.setPlaceholderText(tr("Tox me maybe?","Default message in friend requests if the field is left blank. Write something appropriate!"));
main->setLayout(&layout); main->setLayout(&layout);
layout.addWidget(&toxIdLabel); layout.addWidget(&toxIdLabel);
@ -56,7 +57,7 @@ AddFriendForm::~AddFriendForm()
main->deleteLater(); main->deleteLater();
} }
void AddFriendForm::show(Ui::Widget &ui) void AddFriendForm::show(Ui::MainWindow &ui)
{ {
ui.mainContent->layout()->addWidget(main); ui.mainContent->layout()->addWidget(main);
ui.mainHead->layout()->addWidget(head); ui.mainHead->layout()->addWidget(head);
@ -81,7 +82,7 @@ void AddFriendForm::showWarning(const QString &message) const
QString AddFriendForm::getMessage() const QString AddFriendForm::getMessage() const
{ {
const QString msg = message.toPlainText(); const QString msg = message.toPlainText();
return !msg.isEmpty() ? msg : tr("Tox me maybe?","Default message in friend requests if the field is left blank. Write something appropriate!"); return !msg.isEmpty() ? msg : message.placeholderText();
} }
void AddFriendForm::onSendTriggered() void AddFriendForm::onSendTriggered()

View File

@ -17,7 +17,7 @@
#ifndef ADDFRIENDFORM_H #ifndef ADDFRIENDFORM_H
#define ADDFRIENDFORM_H #define ADDFRIENDFORM_H
#include "ui_widget.h" #include "ui_mainwindow.h"
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QLabel> #include <QLabel>
@ -33,7 +33,7 @@ public:
AddFriendForm(); AddFriendForm();
~AddFriendForm(); ~AddFriendForm();
void show(Ui::Widget& ui); void show(Ui::MainWindow &ui);
bool isToxId(const QString& value) const; bool isToxId(const QString& value) const;
void showWarning(const QString& message) const; void showWarning(const QString& message) const;
QString getMessage() const; QString getMessage() const;

View File

@ -16,14 +16,20 @@
#include "chatform.h" #include "chatform.h"
#include "friend.h" #include "friend.h"
#include "smileypack.h"
#include "widget/friendwidget.h" #include "widget/friendwidget.h"
#include "widget/widget.h" #include "widget/widget.h"
#include "widget/filetransfertwidget.h" #include "widget/filetransfertwidget.h"
#include "widget/emoticonswidget.h"
#include "style.h"
#include <QFont> #include <QFont>
#include <QTime> #include <QTime>
#include <QScrollBar> #include <QScrollBar>
#include <QFileDialog> #include <QFileDialog>
#include <QMenu> #include <QMenu>
#include <QWidgetAction>
#include <QGridLayout>
#include <QMessageBox>
ChatForm::ChatForm(Friend* chatFriend) ChatForm::ChatForm(Friend* chatFriend)
: f(chatFriend), curRow{0}, lockSliderToBottom{true} : f(chatFriend), curRow{0}, lockSliderToBottom{true}
@ -31,12 +37,16 @@ ChatForm::ChatForm(Friend* chatFriend)
main = new QWidget(), head = new QWidget(), chatAreaWidget = new QWidget(); main = new QWidget(), head = new QWidget(), chatAreaWidget = new QWidget();
name = new QLabel(), avatar = new QLabel(), statusMessage = new QLabel(); name = new QLabel(), avatar = new QLabel(), statusMessage = new QLabel();
headLayout = new QHBoxLayout(), mainFootLayout = new QHBoxLayout(); headLayout = new QHBoxLayout(), mainFootLayout = new QHBoxLayout();
headTextLayout = new QVBoxLayout(), mainLayout = new QVBoxLayout(), footButtonsSmall = new QVBoxLayout(); headTextLayout = new QVBoxLayout(), mainLayout = new QVBoxLayout(),
footButtonsSmall = new QVBoxLayout(), volMicLayout = new QVBoxLayout();
mainChatLayout = new QGridLayout(); mainChatLayout = new QGridLayout();
msgEdit = new ChatTextEdit(); msgEdit = new ChatTextEdit();
sendButton = new QPushButton(), fileButton = new QPushButton(), emoteButton = new QPushButton(), callButton = new QPushButton(), videoButton = new QPushButton(); sendButton = new QPushButton(), fileButton = new QPushButton(), emoteButton = new QPushButton(),
callButton = new QPushButton(), videoButton = new QPushButton(),
volButton = new QPushButton(), micButton = new QPushButton();
chatArea = new QScrollArea(); chatArea = new QScrollArea();
netcam = new NetCamView(); netcam = new NetCamView();
audioInputFlag = false;
QFont bold; QFont bold;
bold.setBold(true); bold.setBold(true);
@ -49,16 +59,8 @@ ChatForm::ChatForm(Friend* chatFriend)
avatar->setPixmap(QPixmap(":/img/contact_dark.png")); avatar->setPixmap(QPixmap(":/img/contact_dark.png"));
chatAreaWidget->setLayout(mainChatLayout); chatAreaWidget->setLayout(mainChatLayout);
QString chatAreaStylesheet = ""; chatAreaWidget->setStyleSheet(Style::get(":/ui/chatArea/chatArea.css"));
try
{
QFile f(":/ui/chatArea/chatArea.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream chatAreaStylesheetStream(&f);
chatAreaStylesheet = chatAreaStylesheetStream.readAll();
}
catch (int e) {}
chatArea->setStyleSheet(chatAreaStylesheet);
chatArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); chatArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
chatArea->setWidgetResizable(true); chatArea->setWidgetResizable(true);
chatArea->setContextMenuPolicy(Qt::CustomContextMenu); chatArea->setContextMenuPolicy(Qt::CustomContextMenu);
@ -69,76 +71,43 @@ ChatForm::ChatForm(Friend* chatFriend)
footButtonsSmall->setSpacing(2); footButtonsSmall->setSpacing(2);
QString msgEditStylesheet = ""; msgEdit->setStyleSheet(Style::get(":/ui/msgEdit/msgEdit.css"));
try
{
QFile f(":/ui/msgEdit/msgEdit.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream msgEditStylesheetStream(&f);
msgEditStylesheet = msgEditStylesheetStream.readAll();
}
catch (int e) {}
msgEdit->setStyleSheet(msgEditStylesheet);
msgEdit->setFixedHeight(50); msgEdit->setFixedHeight(50);
msgEdit->setFrameStyle(QFrame::NoFrame); msgEdit->setFrameStyle(QFrame::NoFrame);
QString sendButtonStylesheet = ""; sendButton->setStyleSheet(Style::get(":/ui/sendButton/sendButton.css"));
try fileButton->setStyleSheet(Style::get(":/ui/fileButton/fileButton.css"));
{ emoteButton->setStyleSheet(Style::get(":/ui/emoteButton/emoteButton.css"));
QFile f(":/ui/sendButton/sendButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream sendButtonStylesheetStream(&f);
sendButtonStylesheet = sendButtonStylesheetStream.readAll();
}
catch (int e) {}
sendButton->setStyleSheet(sendButtonStylesheet);
QString fileButtonStylesheet = "";
try
{
QFile f(":/ui/fileButton/fileButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream fileButtonStylesheetStream(&f);
fileButtonStylesheet = fileButtonStylesheetStream.readAll();
}
catch (int e) {}
fileButton->setStyleSheet(fileButtonStylesheet);
QString emoteButtonStylesheet = "";
try
{
QFile f(":/ui/emoteButton/emoteButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream emoteButtonStylesheetStream(&f);
emoteButtonStylesheet = emoteButtonStylesheetStream.readAll();
}
catch (int e) {}
emoteButton->setStyleSheet(emoteButtonStylesheet);
QString callButtonStylesheet = "";
try
{
QFile f(":/ui/callButton/callButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream callButtonStylesheetStream(&f);
callButtonStylesheet = callButtonStylesheetStream.readAll();
}
catch (int e) {}
callButton->setObjectName("green"); callButton->setObjectName("green");
callButton->setStyleSheet(callButtonStylesheet); callButton->setStyleSheet(Style::get(":/ui/callButton/callButton.css"));
QString videoButtonStylesheet = ""; videoButton->setObjectName("green");
videoButton->setStyleSheet(Style::get(":/ui/videoButton/videoButton.css"));
QString volButtonStylesheet = "";
try try
{ {
QFile f(":/ui/videoButton/videoButton.css"); QFile f(":/ui/volButton/volButton.css");
f.open(QFile::ReadOnly | QFile::Text); f.open(QFile::ReadOnly | QFile::Text);
QTextStream videoButtonStylesheetStream(&f); QTextStream volButtonStylesheetStream(&f);
videoButtonStylesheet = videoButtonStylesheetStream.readAll(); volButtonStylesheet = volButtonStylesheetStream.readAll();
} }
catch (int e) {} catch (int e) {}
videoButton->setObjectName("green"); volButton->setObjectName("green");
videoButton->setStyleSheet(videoButtonStylesheet); volButton->setStyleSheet(volButtonStylesheet);
QString micButtonStylesheet = "";
try
{
QFile f(":/ui/micButton/micButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream micButtonStylesheetStream(&f);
micButtonStylesheet = micButtonStylesheetStream.readAll();
}
catch (int e) {}
micButton->setObjectName("green");
micButton->setStyleSheet(micButtonStylesheet);
main->setLayout(mainLayout); main->setLayout(mainLayout);
mainLayout->addWidget(chatArea); mainLayout->addWidget(chatArea);
@ -158,9 +127,13 @@ ChatForm::ChatForm(Friend* chatFriend)
headLayout->addWidget(avatar); headLayout->addWidget(avatar);
headLayout->addLayout(headTextLayout); headLayout->addLayout(headTextLayout);
headLayout->addStretch(); headLayout->addStretch();
headLayout->addLayout(volMicLayout);
headLayout->addWidget(callButton); headLayout->addWidget(callButton);
headLayout->addWidget(videoButton); headLayout->addWidget(videoButton);
volMicLayout->addWidget(micButton);
volMicLayout->addWidget(volButton);
headTextLayout->addStretch(); headTextLayout->addStretch();
headTextLayout->addWidget(name); headTextLayout->addWidget(name);
headTextLayout->addWidget(statusMessage); headTextLayout->addWidget(statusMessage);
@ -173,20 +146,22 @@ ChatForm::ChatForm(Friend* chatFriend)
sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
fileButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); fileButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
// callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
// videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
// msgEdit->setAttribute(Qt::WA_LayoutUsesWidgetRect); // msgEdit->setAttribute(Qt::WA_LayoutUsesWidgetRect);
// chatArea->setAttribute(Qt::WA_LayoutUsesWidgetRect); // chatArea->setAttribute(Qt::WA_LayoutUsesWidgetRect);
connect(Widget::getInstance()->getCore(), &Core::fileSendStarted, this, &ChatForm::startFileSend); connect(Widget::getInstance()->getCore(), &Core::fileSendStarted, this, &ChatForm::startFileSend);
connect(Widget::getInstance()->getCore(), &Core::videoFrameReceived, netcam, &NetCamView::updateDisplay); connect(Widget::getInstance()->getCore(), &Core::videoFrameReceived, netcam, &NetCamView::updateDisplay);
connect(sendButton, SIGNAL(clicked()), this, SLOT(onSendTriggered())); connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered);
connect(fileButton, SIGNAL(clicked()), this, SLOT(onAttachClicked())); connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked);
connect(callButton, SIGNAL(clicked()), this, SLOT(onCallTriggered())); connect(callButton, &QPushButton::clicked, this, &ChatForm::onCallTriggered);
connect(videoButton, SIGNAL(clicked()), this, SLOT(onVideoCallTriggered())); connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered);
connect(msgEdit, SIGNAL(enterPressed()), this, SLOT(onSendTriggered())); connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered);
connect(chatArea->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged())); connect(chatArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &ChatForm::onSliderRangeChanged);
connect(chatArea, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint))); connect(chatArea, &QScrollArea::customContextMenuRequested, this, &ChatForm::onChatContextMenuRequested);
connect(emoteButton, &QPushButton::clicked, this, &ChatForm::onEmoteButtonClicked);
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
} }
ChatForm::~ChatForm() ChatForm::~ChatForm()
@ -196,7 +171,7 @@ ChatForm::~ChatForm()
delete netcam; delete netcam;
} }
void ChatForm::show(Ui::Widget &ui) void ChatForm::show(Ui::MainWindow &ui)
{ {
ui.mainContent->layout()->addWidget(main); ui.mainContent->layout()->addWidget(main);
ui.mainHead->layout()->addWidget(head); ui.mainHead->layout()->addWidget(head);
@ -243,8 +218,6 @@ void ChatForm::addMessage(QString author, QString message, QString date)
void ChatForm::addMessage(QLabel* author, QLabel* message, QLabel* date) void ChatForm::addMessage(QLabel* author, QLabel* message, QLabel* date)
{ {
QPalette greentext;
greentext.setColor(QPalette::WindowText, QColor(61,204,61));
QScrollBar* scroll = chatArea->verticalScrollBar(); QScrollBar* scroll = chatArea->verticalScrollBar();
lockSliderToBottom = scroll && scroll->value() == scroll->maximum(); lockSliderToBottom = scroll && scroll->value() == scroll->maximum();
author->setAlignment(Qt::AlignTop | Qt::AlignRight); author->setAlignment(Qt::AlignTop | Qt::AlignRight);
@ -272,8 +245,24 @@ void ChatForm::addMessage(QLabel* author, QLabel* message, QLabel* date)
} }
else if (curRow)// onSaveLogClicked expects 0 or 3 QLabel per line else if (curRow)// onSaveLogClicked expects 0 or 3 QLabel per line
author->setText(""); author->setText("");
if (message->text()[0] == '>')
message->setPalette(greentext); QColor greentext(61,204,61);
QString fontTemplate = "<font color='%1'>%2</font>";
QString finalMessage;
QStringList messageLines = message->text().split("\n");
for (QString& s : messageLines)
{
if (QRegExp("^[ ]*>.*").exactMatch(s))
finalMessage += fontTemplate.arg(greentext.name(), s.replace(" ", "&nbsp;"));
else
finalMessage += s.replace(" ", "&nbsp;");
finalMessage += "<br>";
}
message->setText(finalMessage.left(finalMessage.length()-4));
message->setText(SmileyPack::getInstance().smileyfied(message->text()));
message->setTextFormat(Qt::RichText);
mainChatLayout->addWidget(author, curRow, 0); mainChatLayout->addWidget(author, curRow, 0);
mainChatLayout->addWidget(message, curRow, 1); mainChatLayout->addWidget(message, curRow, 1);
mainChatLayout->addWidget(date, curRow, 3); mainChatLayout->addWidget(date, curRow, 3);
@ -297,6 +286,12 @@ void ChatForm::onAttachClicked()
QFile file(path); QFile file(path);
if (!file.exists() || !file.open(QIODevice::ReadOnly)) if (!file.exists() || !file.open(QIODevice::ReadOnly))
return; return;
if (file.isSequential())
{
QMessageBox::critical(0, "Bad Idea", "You're trying to send a special (sequential) file, that's not going to work!");
return;
file.close();
}
long long filesize = file.size(); long long filesize = file.size();
file.close(); file.close();
QFileInfo fi(path); QFileInfo fi(path);
@ -308,13 +303,14 @@ void ChatForm::onSliderRangeChanged()
{ {
QScrollBar* scroll = chatArea->verticalScrollBar(); QScrollBar* scroll = chatArea->verticalScrollBar();
if (lockSliderToBottom) if (lockSliderToBottom)
scroll->setValue(scroll->maximum()); scroll->setValue(scroll->maximum());
} }
void ChatForm::startFileSend(ToxFile file) void ChatForm::startFileSend(ToxFile file)
{ {
if (file.friendId != f->friendId) if (file.friendId != f->friendId)
return; return;
QLabel *author = new QLabel(Widget::getInstance()->getUsername()); QLabel *author = new QLabel(Widget::getInstance()->getUsername());
QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm")); QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm"));
QScrollBar* scroll = chatArea->verticalScrollBar(); QScrollBar* scroll = chatArea->verticalScrollBar();
@ -351,6 +347,7 @@ void ChatForm::onFileRecvRequest(ToxFile file)
{ {
if (file.friendId != f->friendId) if (file.friendId != f->friendId)
return; return;
QLabel *author = new QLabel(f->getName()); QLabel *author = new QLabel(f->getName());
QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm")); QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm"));
QScrollBar* scroll = chatArea->verticalScrollBar(); QScrollBar* scroll = chatArea->verticalScrollBar();
@ -378,12 +375,21 @@ void ChatForm::onFileRecvRequest(ToxFile file)
connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransfertWidget::onFileTransferInfo); connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransfertWidget::onFileTransferInfo);
connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransfertWidget::onFileTransferCancelled); connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransfertWidget::onFileTransferCancelled);
connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransfertWidget::onFileTransferFinished); connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransfertWidget::onFileTransferFinished);
Widget* w = Widget::getInstance();
if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow())
{
w->newMessageAlert();
f->hasNewEvents=true;
f->widget->updateStatusLight();
}
} }
void ChatForm::onAvInvite(int FriendId, int CallId, bool video) void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
callId = CallId; callId = CallId;
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
@ -405,11 +411,11 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
} }
Widget* w = Widget::getInstance(); Widget* w = Widget::getInstance();
if (!w->isFriendWidgetCurActiveWidget(f)) if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow())
{ {
w->newMessageAlert(); w->newMessageAlert();
f->hasNewMessages=true; f->hasNewEvents=true;
w->updateFriendStatusLights(f->friendId); f->widget->updateStatusLight();
} }
} }
@ -417,6 +423,8 @@ void ChatForm::onAvStart(int FriendId, int CallId, bool video)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = true;
callId = CallId; callId = CallId;
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
@ -443,6 +451,10 @@ void ChatForm::onAvCancel(int FriendId, int)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -458,6 +470,10 @@ void ChatForm::onAvEnd(int FriendId, int)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -473,6 +489,7 @@ void ChatForm::onAvRinging(int FriendId, int CallId, bool video)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
callId = CallId; callId = CallId;
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
@ -498,6 +515,7 @@ void ChatForm::onAvStarting(int FriendId, int, bool video)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
if (video) if (video)
@ -523,6 +541,10 @@ void ChatForm::onAvEnding(int FriendId, int)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -540,6 +562,10 @@ void ChatForm::onAvRequestTimeout(int FriendId, int)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -557,6 +583,10 @@ void ChatForm::onAvPeerTimeout(int FriendId, int)
{ {
if (FriendId != f->friendId) if (FriendId != f->friendId)
return; return;
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -570,25 +600,43 @@ void ChatForm::onAvPeerTimeout(int FriendId, int)
netcam->hide(); netcam->hide();
} }
void ChatForm::onAvMediaChange(int, int, bool video)
{
if (video)
{
netcam->show();
}
else
{
netcam->hide();
}
}
void ChatForm::onAnswerCallTriggered() void ChatForm::onAnswerCallTriggered()
{ {
audioInputFlag = true;
emit answerCall(callId); emit answerCall(callId);
} }
void ChatForm::onHangupCallTriggered() void ChatForm::onHangupCallTriggered()
{ {
audioInputFlag = false;
emit hangupCall(callId); emit hangupCall(callId);
micButton->setObjectName("green");
micButton->style()->polish(micButton);
} }
void ChatForm::onCallTriggered() void ChatForm::onCallTriggered()
{ {
callButton->disconnect(); audioInputFlag = true;
videoButton->disconnect(); callButton->disconnect();
emit startCall(f->friendId); videoButton->disconnect();
emit startCall(f->friendId);
} }
void ChatForm::onVideoCallTriggered() void ChatForm::onVideoCallTriggered()
{ {
audioInputFlag = true;
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
emit startVideoCall(f->friendId, true); emit startVideoCall(f->friendId, true);
@ -596,6 +644,9 @@ void ChatForm::onVideoCallTriggered()
void ChatForm::onCancelCallTriggered() void ChatForm::onCancelCallTriggered()
{ {
audioInputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
callButton->disconnect(); callButton->disconnect();
videoButton->disconnect(); videoButton->disconnect();
callButton->setObjectName("green"); callButton->setObjectName("green");
@ -650,3 +701,48 @@ void ChatForm::onSaveLogClicked()
file.write(log.toUtf8()); file.write(log.toUtf8());
file.close(); file.close();
} }
void ChatForm::onEmoteButtonClicked()
{
// don't show the smiley selection widget if there are no smileys available
if (SmileyPack::getInstance().getEmoticons().empty())
return;
EmoticonsWidget widget;
connect(&widget, &EmoticonsWidget::insertEmoticon, this, &ChatForm::onEmoteInsertRequested);
QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
if (sender)
{
QPoint pos = -QPoint(widget.sizeHint().width() / 2, widget.sizeHint().height()) - QPoint(0, 10);
widget.exec(sender->mapToGlobal(pos));
}
}
void ChatForm::onEmoteInsertRequested(QString str)
{
// insert the emoticon
QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
if (sender)
msgEdit->insertPlainText(str);
msgEdit->setFocus(); // refocus so that we can continue typing
}
void ChatForm::onMicMuteToggle()
{
if (audioInputFlag == true)
{
emit micMuteToggle(callId);
if (micButton->objectName() == "red")
{
micButton->setObjectName("green");
micButton->style()->polish(micButton);
}
else
{
micButton->setObjectName("red");
micButton->style()->polish(micButton);
}
}
}

View File

@ -28,7 +28,7 @@
#include <QPoint> #include <QPoint>
#include "widget/tool/chattextedit.h" #include "widget/tool/chattextedit.h"
#include "ui_widget.h" #include "ui_mainwindow.h"
#include "core.h" #include "core.h"
#include "widget/netcamview.h" #include "widget/netcamview.h"
@ -43,7 +43,7 @@ class ChatForm : public QObject
public: public:
ChatForm(Friend* chatFriend); ChatForm(Friend* chatFriend);
~ChatForm(); ~ChatForm();
void show(Ui::Widget& ui); void show(Ui::MainWindow &ui);
void setName(QString newName); void setName(QString newName);
void setStatusMessage(QString newMessage); void setStatusMessage(QString newMessage);
void addFriendMessage(QString message); void addFriendMessage(QString message);
@ -58,6 +58,7 @@ signals:
void answerCall(int callId); void answerCall(int callId);
void hangupCall(int callId); void hangupCall(int callId);
void cancelCall(int callId, int friendId); void cancelCall(int callId, int friendId);
void micMuteToggle(int callId);
public slots: public slots:
void startFileSend(ToxFile file); void startFileSend(ToxFile file);
@ -71,6 +72,8 @@ public slots:
void onAvEnding(int FriendId, int CallId); void onAvEnding(int FriendId, int CallId);
void onAvRequestTimeout(int FriendId, int CallId); void onAvRequestTimeout(int FriendId, int CallId);
void onAvPeerTimeout(int FriendId, int CallId); void onAvPeerTimeout(int FriendId, int CallId);
void onAvMediaChange(int FriendId, int CallId, bool video);
void onMicMuteToggle();
private slots: private slots:
void onSendTriggered(); void onSendTriggered();
@ -83,21 +86,23 @@ private slots:
void onCancelCallTriggered(); void onCancelCallTriggered();
void onChatContextMenuRequested(QPoint pos); void onChatContextMenuRequested(QPoint pos);
void onSaveLogClicked(); void onSaveLogClicked();
void onEmoteButtonClicked();
void onEmoteInsertRequested(QString str);
private: private:
Friend* f; Friend* f;
QHBoxLayout *headLayout, *mainFootLayout; QHBoxLayout *headLayout, *mainFootLayout;
QVBoxLayout *headTextLayout, *mainLayout, *footButtonsSmall; QVBoxLayout *headTextLayout, *mainLayout, *footButtonsSmall, *volMicLayout;
QGridLayout *mainChatLayout; QGridLayout *mainChatLayout;
QLabel *avatar, *name, *statusMessage; QLabel *avatar, *name, *statusMessage;
ChatTextEdit *msgEdit; ChatTextEdit *msgEdit;
QPushButton *sendButton, *fileButton, *emoteButton, *callButton, *videoButton; QPushButton *sendButton, *fileButton, *emoteButton, *callButton, *videoButton, *volButton, *micButton;
QScrollArea *chatArea; QScrollArea *chatArea;
QWidget *main, *head, *chatAreaWidget; QWidget *main, *head, *chatAreaWidget;
QString previousName; QString previousName;
NetCamView* netcam; NetCamView* netcam;
int curRow; int curRow;
bool lockSliderToBottom; bool lockSliderToBottom, audioInputFlag;
int callId; int callId;
}; };

View File

@ -50,7 +50,7 @@ FilesForm::~FilesForm()
// I'm not too bummed about removing it // I'm not too bummed about removing it
} }
void FilesForm::show(Ui::Widget& ui) void FilesForm::show(Ui::MainWindow& ui)
{ {
ui.mainContent->layout()->addWidget(&main); ui.mainContent->layout()->addWidget(&main);
ui.mainHead->layout()->addWidget(head); ui.mainHead->layout()->addWidget(head);

View File

@ -17,7 +17,7 @@
#ifndef FILESFORM_H #ifndef FILESFORM_H
#define FILESFORM_H #define FILESFORM_H
#include "ui_widget.h" #include "ui_mainwindow.h"
#include <QListWidget> #include <QListWidget>
#include <QTabWidget> #include <QTabWidget>
@ -37,7 +37,7 @@ public:
FilesForm(); FilesForm();
~FilesForm(); ~FilesForm();
void show(Ui::Widget& ui); void show(Ui::MainWindow &ui);
public slots: public slots:
void onFileDownloadComplete(const QString& path); void onFileDownloadComplete(const QString& path);

View File

@ -20,6 +20,7 @@
#include "widget/widget.h" #include "widget/widget.h"
#include "friend.h" #include "friend.h"
#include "friendlist.h" #include "friendlist.h"
#include "style.h"
#include <QFont> #include <QFont>
#include <QTime> #include <QTime>
#include <QScrollBar> #include <QScrollBar>
@ -55,16 +56,8 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
namesList->setFont(small); namesList->setFont(small);
chatAreaWidget->setLayout(mainChatLayout); chatAreaWidget->setLayout(mainChatLayout);
QString chatAreaStylesheet = "";
try chatArea->setStyleSheet(Style::get(":/ui/chatArea/chatArea.css"));
{
QFile f(":/ui/chatArea/chatArea.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream chatAreaStylesheetStream(&f);
chatAreaStylesheet = chatAreaStylesheetStream.readAll();
}
catch (int e) {}
chatArea->setStyleSheet(chatAreaStylesheet);
chatArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); chatArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
chatArea->setWidgetResizable(true); chatArea->setWidgetResizable(true);
chatArea->setContextMenuPolicy(Qt::CustomContextMenu); chatArea->setContextMenuPolicy(Qt::CustomContextMenu);
@ -73,35 +66,15 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
mainChatLayout->setColumnStretch(1,1); mainChatLayout->setColumnStretch(1,1);
mainChatLayout->setSpacing(10); mainChatLayout->setSpacing(10);
QString msgEditStylesheet = "";
try
{
QFile f(":/ui/msgEdit/msgEdit.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream msgEditStylesheetStream(&f);
msgEditStylesheet = msgEditStylesheetStream.readAll();
}
catch (int e) {}
msgEdit->setObjectName("group"); msgEdit->setObjectName("group");
msgEdit->setStyleSheet(msgEditStylesheet); msgEdit->setStyleSheet(Style::get(":/ui/msgEdit/msgEdit.css"));
msgEdit->setFixedHeight(50); msgEdit->setFixedHeight(50);
msgEdit->setFrameStyle(QFrame::NoFrame); msgEdit->setFrameStyle(QFrame::NoFrame);
mainChatLayout->setColumnStretch(1,1); mainChatLayout->setColumnStretch(1,1);
mainChatLayout->setHorizontalSpacing(10); mainChatLayout->setHorizontalSpacing(10);
QString sendButtonStylesheet = ""; sendButton->setStyleSheet(Style::get(":/ui/sendButton/sendButton.css"));
try
{
QFile f(":/ui/sendButton/sendButton.css");
f.open(QFile::ReadOnly | QFile::Text);
QTextStream sendButtonStylesheetStream(&f);
sendButtonStylesheet = sendButtonStylesheetStream.readAll();
}
catch (int e) {}
sendButton->setStyleSheet(sendButtonStylesheet);
sendButton->setFixedSize(50, 50); sendButton->setFixedSize(50, 50);
main->setLayout(mainLayout); main->setLayout(mainLayout);
@ -142,7 +115,7 @@ GroupChatForm::~GroupChatForm()
delete main; delete main;
} }
void GroupChatForm::show(Ui::Widget &ui) void GroupChatForm::show(Ui::MainWindow &ui)
{ {
ui.mainContent->layout()->addWidget(main); ui.mainContent->layout()->addWidget(main);
ui.mainHead->layout()->addWidget(head); ui.mainHead->layout()->addWidget(head);

View File

@ -27,7 +27,7 @@
#include <QTime> #include <QTime>
#include "widget/tool/chattextedit.h" #include "widget/tool/chattextedit.h"
#include "ui_widget.h" #include "ui_mainwindow.h"
// Spacing in px inserted when the author of the last message changes // Spacing in px inserted when the author of the last message changes
#define AUTHOR_CHANGE_SPACING 5 #define AUTHOR_CHANGE_SPACING 5
@ -40,7 +40,7 @@ class GroupChatForm : public QObject
public: public:
GroupChatForm(Group* chatGroup); GroupChatForm(Group* chatGroup);
~GroupChatForm(); ~GroupChatForm();
void show(Ui::Widget& ui); void show(Ui::MainWindow &ui);
void setName(QString newName); void setName(QString newName);
void addGroupMessage(QString message, int peerId); void addGroupMessage(QString message, int peerId);
void addMessage(QString author, QString message, QString date=QTime::currentTime().toString("hh:mm")); void addMessage(QString author, QString message, QString date=QTime::currentTime().toString("hh:mm"));

View File

@ -17,9 +17,12 @@
#include "settingsform.h" #include "settingsform.h"
#include "widget/widget.h" #include "widget/widget.h"
#include "settings.h" #include "settings.h"
#include "smileypack.h"
#include <QFont> #include <QFont>
#include <QClipboard> #include <QClipboard>
#include <QApplication> #include <QApplication>
#include <QFileDialog>
#include <QDir>
SettingsForm::SettingsForm() SettingsForm::SettingsForm()
: QObject() : QObject()
@ -55,6 +58,12 @@ SettingsForm::SettingsForm()
useTranslations.setChecked(Settings::getInstance().getUseTranslations()); useTranslations.setChecked(Settings::getInstance().getUseTranslations());
makeToxPortable.setText(tr("Make Tox portable","Text on a checkbox to make qTox a portable application")); makeToxPortable.setText(tr("Make Tox portable","Text on a checkbox to make qTox a portable application"));
makeToxPortable.setChecked(Settings::getInstance().getMakeToxPortable()); makeToxPortable.setChecked(Settings::getInstance().getMakeToxPortable());
makeToxPortable.setToolTip(tr("Save settings to the working directory instead of the usual conf dir","describes makeToxPortable checkbox"));
smileyPackLabel.setText(tr("Smiley Pack", "Text on smiley pack label"));
for (auto entry : SmileyPack::listSmileyPacks())
smileyPackBrowser.addItem(entry.first, entry.second);
smileyPackBrowser.setCurrentIndex(smileyPackBrowser.findData(Settings::getInstance().getSmileyPack()));
main->setLayout(&layout); main->setLayout(&layout);
layout.addWidget(&idLabel); layout.addWidget(&idLabel);
@ -73,7 +82,9 @@ SettingsForm::SettingsForm()
layout.addWidget(&enableIPv6); layout.addWidget(&enableIPv6);
layout.addWidget(&useTranslations); layout.addWidget(&useTranslations);
layout.addWidget(&makeToxPortable); layout.addWidget(&makeToxPortable);
//layout.addStretch(); layout.addWidget(&smileyPackLabel);
layout.addWidget(&smileyPackBrowser);
layout.addStretch();
head->setLayout(&headLayout); head->setLayout(&headLayout);
headLayout.addWidget(&headLabel); headLayout.addWidget(&headLabel);
@ -87,6 +98,7 @@ SettingsForm::SettingsForm()
connect(&useTranslations, SIGNAL(stateChanged(int)), this, SLOT(onUseTranslationUpdated())); connect(&useTranslations, SIGNAL(stateChanged(int)), this, SLOT(onUseTranslationUpdated()));
connect(&makeToxPortable, SIGNAL(stateChanged(int)), this, SLOT(onMakeToxPortableUpdated())); connect(&makeToxPortable, SIGNAL(stateChanged(int)), this, SLOT(onMakeToxPortableUpdated()));
connect(&idLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked())); connect(&idLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(&smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int)));
} }
SettingsForm::~SettingsForm() SettingsForm::~SettingsForm()
@ -116,7 +128,7 @@ void SettingsForm::setFriendAddress(const QString& friendAddress)
id.setText(friendAddress); id.setText(friendAddress);
} }
void SettingsForm::show(Ui::Widget &ui) void SettingsForm::show(Ui::MainWindow &ui)
{ {
profiles.clear(); profiles.clear();
for (QString profile : searchProfiles()) for (QString profile : searchProfiles())
@ -198,3 +210,9 @@ void SettingsForm::onMakeToxPortableUpdated()
{ {
Settings::getInstance().setMakeToxPortable(makeToxPortable.isChecked()); Settings::getInstance().setMakeToxPortable(makeToxPortable.isChecked());
} }
void SettingsForm::onSmileyBrowserIndexChanged(int index)
{
QString filename = smileyPackBrowser.itemData(index).toString();
Settings::getInstance().setSmileyPack(filename);
}

View File

@ -31,9 +31,9 @@
#include <QFileInfo> #include <QFileInfo>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include "widget/tool/clickablelabel.h" #include "ui_mainwindow.h"
#include "ui_widget.h"
#include "widget/selfcamview.h" #include "widget/selfcamview.h"
#include "widget/croppinglabel.h"
#include "core.h" #include "core.h"
class SettingsForm : public QObject class SettingsForm : public QObject
@ -43,7 +43,7 @@ public:
SettingsForm(); SettingsForm();
~SettingsForm(); ~SettingsForm();
void show(Ui::Widget& ui); void show(Ui::MainWindow &ui);
static QList<QString> searchProfiles(); static QList<QString> searchProfiles();
public slots: public slots:
@ -58,12 +58,13 @@ private slots:
void onEnableIPv6Updated(); void onEnableIPv6Updated();
void onUseTranslationUpdated(); void onUseTranslationUpdated();
void onMakeToxPortableUpdated(); void onMakeToxPortableUpdated();
void onSmileyBrowserIndexChanged(int index);
void copyIdClicked(); void copyIdClicked();
private: private:
QLabel headLabel;/*, nameLabel, statusTextLabel;*/ QLabel headLabel, smileyPackLabel;
QTextEdit id; QTextEdit id;
ClickableLabel idLabel; CroppingLabel idLabel;
QLabel profilesLabel; QLabel profilesLabel;
QComboBox profiles; QComboBox profiles;
QPushButton loadConf, exportConf, delConf, importConf, videoTest; QPushButton loadConf, exportConf, delConf, importConf, videoTest;
@ -71,10 +72,8 @@ private:
QCheckBox enableIPv6, useTranslations, makeToxPortable; QCheckBox enableIPv6, useTranslations, makeToxPortable;
QVBoxLayout layout, headLayout; QVBoxLayout layout, headLayout;
QWidget *main, *head, *hboxcont1, *hboxcont2; QWidget *main, *head, *hboxcont1, *hboxcont2;
QComboBox smileyPackBrowser;
QString getSelectedSavePath(); QString getSelectedSavePath();
public:
//QLineEdit name, statusText;
}; };
#endif // SETTINGSFORM_H #endif // SETTINGSFORM_H

View File

@ -0,0 +1,67 @@
/*
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 "friendlistwidget.h"
#include <QDebug>
FriendListWidget::FriendListWidget(QWidget *parent) :
QWidget(parent)
{
mainLayout = new QGridLayout();
setLayout(mainLayout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
layout()->setSpacing(0);
layout()->setMargin(0);
groupLayout = new QVBoxLayout();
groupLayout->setSpacing(0);
groupLayout->setMargin(0);
for (Status s : {Status::Online, Status::Away, Status::Busy, Status::Offline})
{
QLayout *l = new QVBoxLayout();
l->setSpacing(0);
l->setMargin(0);
layouts[static_cast<int>(s)] = l;
}
mainLayout->addLayout(layouts[static_cast<int>(Status::Online)], 0, 0);
mainLayout->addLayout(groupLayout, 1, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Away)], 2, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Busy)], 3, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Offline)], 4, 0);
}
QLayout* FriendListWidget::getGroupLayout()
{
return groupLayout;
}
QLayout* FriendListWidget::getFriendLayout(Status s)
{
auto res = layouts.find(static_cast<int>(s));
if (res != layouts.end())
return res.value();
qDebug() << "Friend Status: " << static_cast<int>(s) << " not found!";
return layouts[static_cast<int>(Status::Online)];
}
void FriendListWidget::moveWidget(QWidget *w, Status s)
{
mainLayout->removeWidget(w);
getFriendLayout(s)->addWidget(w);
}

View File

@ -14,29 +14,31 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#ifndef VIDEOSURFACE_H #ifndef FRIENDLISTWIDGET_H
#define VIDEOSURFACE_H #define FRIENDLISTWIDGET_H
#include <QAbstractVideoSurface> #include <QWidget>
#include <QVideoSurfaceFormat> #include <QGridLayout>
#include "vpx/vpx_image.h" #include "core.h"
class VideoSurface : public QAbstractVideoSurface class FriendListWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
VideoSurface(); explicit FriendListWidget(QWidget *parent = 0);
bool start(const QVideoSurfaceFormat &format);
bool present(const QVideoFrame &frame); QLayout* getGroupLayout();
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const; QLayout* getFriendLayout(Status s);
void moveWidget(QWidget *w, Status s);
signals: signals:
// Slots MUST be called with a direct or blocking connection, or img may die before they return !
void videoFrameReady(vpx_image img); public slots:
private: private:
QVideoSurfaceFormat mVideoFormat; QHash<int, QLayout*> layouts;
vpx_image_t input; QLayout *groupLayout;
QGridLayout *mainLayout;
}; };
#endif // VIDEOSURFACE_H #endif // FRIENDLISTWIDGET_H

View File

@ -19,22 +19,24 @@
#include "grouplist.h" #include "grouplist.h"
#include "groupwidget.h" #include "groupwidget.h"
#include "widget.h" #include "widget.h"
#include "friendlist.h"
#include "friend.h"
#include <QContextMenuEvent> #include <QContextMenuEvent>
#include <QMenu> #include <QMenu>
FriendWidget::FriendWidget(int FriendId, QString id) FriendWidget::FriendWidget(int FriendId, QString id)
: friendId(FriendId) : friendId(FriendId)
{ {
this->setMouseTracking(true); setMouseTracking(true);
this->setAutoFillBackground(true); setAutoFillBackground(true);
this->setFixedWidth(225); setFixedHeight(55);
this->setFixedHeight(55); setLayout(&layout);
this->setLayout(&layout);
layout.setSpacing(0); layout.setSpacing(0);
layout.setMargin(0); layout.setMargin(0);
layout.setStretchFactor(this, 100); layout.setStretchFactor(this, 100);
textLayout.setSpacing(0); textLayout.setSpacing(0);
textLayout.setMargin(0); textLayout.setMargin(0);
setLayoutDirection(Qt::LeftToRight); // parent might have set Qt::RightToLeft
avatar.setPixmap(QPixmap(":img/contact.png")); avatar.setPixmap(QPixmap(":img/contact.png"));
name.setText(id); name.setText(id);
@ -62,22 +64,16 @@ FriendWidget::FriendWidget(int FriendId, QString id)
layout.addWidget(&avatar); layout.addWidget(&avatar);
layout.addSpacing(5); layout.addSpacing(5);
layout.addLayout(&textLayout); layout.addLayout(&textLayout);
layout.addStretch();
layout.addSpacing(5); layout.addSpacing(5);
layout.addWidget(&statusPic); layout.addWidget(&statusPic);
layout.addSpacing(5); layout.addSpacing(5);
isActiveWidget = 0; isActiveWidget = 0;
}
void FriendWidget::setNewFixedWidth(int newWidth) layout.invalidate();
{ layout.update();
this->setFixedWidth(newWidth); layout.activate();
} updateGeometry();
void FriendWidget::mouseReleaseEvent (QMouseEvent*)
{
emit friendWidgetClicked(this);
} }
void FriendWidget::contextMenuEvent(QContextMenuEvent * event) void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
@ -121,47 +117,6 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
} }
} }
void FriendWidget::mousePressEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) == Qt::LeftButton)
{
if (isActiveWidget)
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(250,250,250,255));
this->setPalette(pal);
}
else
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(85,85,85,255));
this->setPalette(pal);
}
}
}
void FriendWidget::enterEvent(QEvent*)
{
if (isActiveWidget != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(75,75,75,255));
lastColor = this->palette().background().color();
this->setPalette(pal);
}
}
void FriendWidget::leaveEvent(QEvent*)
{
if (isActiveWidget != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, lastColor);
this->setPalette(pal);
}
}
void FriendWidget::setAsActiveChatroom() void FriendWidget::setAsActiveChatroom()
{ {
isActiveWidget = 1; isActiveWidget = 1;
@ -199,3 +154,38 @@ void FriendWidget::setAsInactiveChatroom()
this->setPalette(pal3); this->setPalette(pal3);
avatar.setPixmap(QPixmap(":img/contact.png")); avatar.setPixmap(QPixmap(":img/contact.png"));
} }
void FriendWidget::updateStatusLight()
{
Friend* f = FriendList::findFriend(friendId);
Status status = f->friendStatus;
if (status == Status::Online && f->hasNewEvents == 0)
statusPic.setPixmap(QPixmap(":img/status/dot_online.png"));
else if (status == Status::Online && f->hasNewEvents == 1)
statusPic.setPixmap(QPixmap(":img/status/dot_online_notification.png"));
else if (status == Status::Away && f->hasNewEvents == 0)
statusPic.setPixmap(QPixmap(":img/status/dot_idle.png"));
else if (status == Status::Away && f->hasNewEvents == 1)
statusPic.setPixmap(QPixmap(":img/status/dot_idle_notification.png"));
else if (status == Status::Busy && f->hasNewEvents == 0)
statusPic.setPixmap(QPixmap(":img/status/dot_busy.png"));
else if (status == Status::Busy && f->hasNewEvents == 1)
statusPic.setPixmap(QPixmap(":img/status/dot_busy_notification.png"));
else if (status == Status::Offline && f->hasNewEvents == 0)
statusPic.setPixmap(QPixmap(":img/status/dot_away.png"));
else if (status == Status::Offline && f->hasNewEvents == 1)
statusPic.setPixmap(QPixmap(":img/status/dot_away_notification.png"));
}
void FriendWidget::setChatForm(Ui::MainWindow &ui)
{
Friend* f = FriendList::findFriend(friendId);
f->chatForm->show(ui);
}
void FriendWidget::resetEventFlags()
{
Friend* f = FriendList::findFriend(friendId);
f->hasNewEvents = 0;
}

View File

@ -22,19 +22,20 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
struct FriendWidget : public QWidget #include "genericchatroomwidget.h"
#include "croppinglabel.h"
struct FriendWidget : public GenericChatroomWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
FriendWidget(int FriendId, QString id); FriendWidget(int FriendId, QString id);
void mouseReleaseEvent (QMouseEvent* event);
void mousePressEvent(QMouseEvent *event);
void contextMenuEvent(QContextMenuEvent * event); void contextMenuEvent(QContextMenuEvent * event);
void enterEvent(QEvent* event);
void leaveEvent(QEvent* event);
void setAsActiveChatroom(); void setAsActiveChatroom();
void setAsInactiveChatroom(); void setAsInactiveChatroom();
void setNewFixedWidth(int newWidth); void updateStatusLight();
void setChatForm(Ui::MainWindow &);
void resetEventFlags();
signals: signals:
void friendWidgetClicked(FriendWidget* widget); void friendWidgetClicked(FriendWidget* widget);
@ -43,13 +44,8 @@ signals:
public: public:
int friendId; int friendId;
QLabel avatar, name, statusMessage, statusPic; QLabel avatar, statusPic;
QHBoxLayout layout; CroppingLabel name, statusMessage;
QVBoxLayout textLayout;
private:
QColor lastColor;
int isActiveWidget;
}; };
#endif // FRIENDWIDGET_H #endif // FRIENDWIDGET_H

View File

@ -0,0 +1,73 @@
/*
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 "genericchatroomwidget.h"
#include <QMouseEvent>
GenericChatroomWidget::GenericChatroomWidget(QWidget *parent) :
QWidget(parent)
{
}
int GenericChatroomWidget::isActive()
{
return isActiveWidget;
}
void GenericChatroomWidget::mousePressEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) == Qt::LeftButton)
{
if (isActive())
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(250,250,250,255));
this->setPalette(pal);
}
else
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(85,85,85,255));
this->setPalette(pal);
}
}
}
void GenericChatroomWidget::leaveEvent(QEvent *)
{
if (isActive() != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, lastColor);
this->setPalette(pal);
}
}
void GenericChatroomWidget::enterEvent(QEvent *)
{
if (isActive() != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(75,75,75,255));
lastColor = this->palette().background().color();
this->setPalette(pal);
}
}
void GenericChatroomWidget::mouseReleaseEvent(QMouseEvent*)
{
emit chatroomWidgetClicked(this);
}

View File

@ -0,0 +1,58 @@
/*
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 GENERICCHATROOMWIDGET_H
#define GENERICCHATROOMWIDGET_H
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
namespace Ui {
class MainWindow;
}
class GenericChatroomWidget : public QWidget
{
Q_OBJECT
public:
GenericChatroomWidget(QWidget *parent = 0);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent (QMouseEvent* event);
void leaveEvent(QEvent *);
void enterEvent(QEvent *);
virtual void setAsActiveChatroom(){;}
virtual void setAsInactiveChatroom(){;}
virtual void updateStatusLight(){;}
virtual void setChatForm(Ui::MainWindow &){;}
virtual void resetEventFlags(){;}
int isActive();
signals:
void chatroomWidgetClicked(GenericChatroomWidget* widget);
public slots:
protected:
int isActiveWidget;
QColor lastColor;
QHBoxLayout layout;
QVBoxLayout textLayout;
};
#endif // GENERICCHATROOMWIDGET_H

View File

@ -17,22 +17,26 @@
#include "groupwidget.h" #include "groupwidget.h"
#include "grouplist.h" #include "grouplist.h"
#include "group.h" #include "group.h"
#include "settings.h"
#include "widget/form/groupchatform.h"
#include <QPalette> #include <QPalette>
#include <QMenu> #include <QMenu>
#include <QContextMenuEvent> #include <QContextMenuEvent>
#include "ui_mainwindow.h"
GroupWidget::GroupWidget(int GroupId, QString Name) GroupWidget::GroupWidget(int GroupId, QString Name)
: groupId{GroupId} : groupId{GroupId}
{ {
this->setMouseTracking(true); setMouseTracking(true);
this->setAutoFillBackground(true); setAutoFillBackground(true);
this->setLayout(&layout); setLayout(&layout);
this->setFixedWidth(225); setFixedHeight(55);
this->setFixedHeight(55);
layout.setSpacing(0); layout.setSpacing(0);
layout.setMargin(0); layout.setMargin(0);
textLayout.setSpacing(0); textLayout.setSpacing(0);
textLayout.setMargin(0); textLayout.setMargin(0);
setLayoutDirection(Qt::LeftToRight); // parent might have set Qt::RightToLeft
avatar.setPixmap(QPixmap(":img/group.png")); avatar.setPixmap(QPixmap(":img/group.png"));
statusPic.setPixmap(QPixmap(":img/status/dot_online.png")); statusPic.setPixmap(QPixmap(":img/status/dot_online.png"));
@ -72,16 +76,6 @@ GroupWidget::GroupWidget(int GroupId, QString Name)
isActiveWidget = 0; isActiveWidget = 0;
} }
void GroupWidget::setNewFixedWidth(int newWidth)
{
this->setFixedWidth(newWidth);
}
void GroupWidget::mouseReleaseEvent (QMouseEvent*)
{
emit groupWidgetClicked(this);
}
void GroupWidget::contextMenuEvent(QContextMenuEvent * event) void GroupWidget::contextMenuEvent(QContextMenuEvent * event)
{ {
QPoint pos = event->globalPos(); QPoint pos = event->globalPos();
@ -99,46 +93,6 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event)
} }
} }
void GroupWidget::mousePressEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) == Qt::LeftButton)
{
if (isActiveWidget)
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(250,250,250,255));
this->setPalette(pal);
}
else
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(85,85,85,255));
this->setPalette(pal);
}
}
}
void GroupWidget::enterEvent(QEvent*)
{
if (isActiveWidget != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, QColor(75,75,75,255));
lastColor = this->palette().background().color();
this->setPalette(pal);
}
}
void GroupWidget::leaveEvent(QEvent*)
{
if (isActiveWidget != 1)
{
QPalette pal;
pal.setColor(QPalette::Background, lastColor);
this->setPalette(pal);
}
}
void GroupWidget::onUserListChanged() void GroupWidget::onUserListChanged()
{ {
Group* g = GroupList::findGroup(groupId); Group* g = GroupList::findGroup(groupId);
@ -185,3 +139,40 @@ void GroupWidget::setAsInactiveChatroom()
this->setPalette(pal3); this->setPalette(pal3);
avatar.setPixmap(QPixmap(":img/group.png")); avatar.setPixmap(QPixmap(":img/group.png"));
} }
void GroupWidget::updateStatusLight()
{
Group *g = GroupList::findGroup(groupId);
if (Settings::getInstance().getUseNativeDecoration())
{
if (g->hasNewMessages == 0)
{
statusPic.setPixmap(QPixmap(":img/status/dot_online.png"));
} else {
if (g->userWasMentioned == 0) statusPic.setPixmap(QPixmap(":img/status/dot_online_notification.png"));
else statusPic.setPixmap(QPixmap(":img/status/dot_online_notification.png"));
}
} else {
if (g->hasNewMessages == 0)
{
statusPic.setPixmap(QPixmap(":img/status/dot_groupchat.png"));
} else {
if (g->userWasMentioned == 0) statusPic.setPixmap(QPixmap(":img/status/dot_groupchat_newmessages.png"));
else statusPic.setPixmap(QPixmap(":img/status/dot_groupchat_notification.png"));
}
}
}
void GroupWidget::setChatForm(Ui::MainWindow &ui)
{
Group* g = GroupList::findGroup(groupId);
g->chatForm->show(ui);
}
void GroupWidget::resetEventFlags()
{
Group* g = GroupList::findGroup(groupId);
g->hasNewMessages = 0;
g->userWasMentioned = 0;
}

View File

@ -19,21 +19,20 @@
#include <QWidget> #include <QWidget>
#include <QLabel> #include <QLabel>
#include <QHBoxLayout> #include "genericchatroomwidget.h"
#include <QVBoxLayout>
class GroupWidget : public QWidget class GroupWidget : public GenericChatroomWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
GroupWidget(int GroupId, QString Name); GroupWidget(int GroupId, QString Name);
void onUserListChanged(); void onUserListChanged();
void mouseReleaseEvent (QMouseEvent* event);
void mousePressEvent(QMouseEvent *event);
void contextMenuEvent(QContextMenuEvent * event); void contextMenuEvent(QContextMenuEvent * event);
void enterEvent(QEvent* event); void setAsInactiveChatroom();
void leaveEvent(QEvent* event); void setAsActiveChatroom();
void updateStatusLight();
void setChatForm(Ui::MainWindow &);
void resetEventFlags();
signals: signals:
void groupWidgetClicked(GroupWidget* widget); void groupWidgetClicked(GroupWidget* widget);
@ -42,15 +41,6 @@ signals:
public: public:
int groupId; int groupId;
QLabel avatar, name, nusers, statusPic; QLabel avatar, name, nusers, statusPic;
QHBoxLayout layout;
QVBoxLayout textLayout;
void setAsInactiveChatroom();
void setAsActiveChatroom();
void setNewFixedWidth(int newWidth);
private:
QColor lastColor;
int isActiveWidget;
}; };
#endif // GROUPWIDGET_H #endif // GROUPWIDGET_H

View File

@ -89,8 +89,8 @@ QImage NetCamView::convert(vpx_image& frame)
QImage img(w, h, QImage::Format_RGB32); QImage img(w, h, QImage::Format_RGB32);
uint8_t* yData = frame.planes[VPX_PLANE_Y]; uint8_t* yData = frame.planes[VPX_PLANE_Y];
uint8_t* uData = frame.planes[VPX_PLANE_U]; uint8_t* uData = frame.planes[VPX_PLANE_V];
uint8_t* vData = frame.planes[VPX_PLANE_V]; uint8_t* vData = frame.planes[VPX_PLANE_U];
for (int i = 0; i< h; i++) for (int i = 0; i< h; i++)
{ {
uint32_t* scanline = (uint32_t*)img.scanLine(i); uint32_t* scanline = (uint32_t*)img.scanLine(i);

View File

@ -15,15 +15,13 @@
*/ */
#include "selfcamview.h" #include "selfcamview.h"
#include <QActionGroup>
#include <QMessageBox>
#include <QCloseEvent> #include <QCloseEvent>
#include <QShowEvent> #include <QShowEvent>
#include <QVideoFrame>
#include "videosurface.h"
#include "widget.h" #include "widget.h"
using namespace cv;
SelfCamView::SelfCamView(Camera* Cam, QWidget* parent) SelfCamView::SelfCamView(Camera* Cam, QWidget* parent)
: QWidget(parent), displayLabel{new QLabel}, : QWidget(parent), displayLabel{new QLabel},
mainLayout{new QHBoxLayout()}, cam(Cam) mainLayout{new QHBoxLayout()}, cam(Cam)

View File

@ -20,6 +20,7 @@
ChatTextEdit::ChatTextEdit(QWidget *parent) : ChatTextEdit::ChatTextEdit(QWidget *parent) :
QTextEdit(parent) QTextEdit(parent)
{ {
setPlaceholderText("Type your message here...");
} }
void ChatTextEdit::keyPressEvent(QKeyEvent * event) void ChatTextEdit::keyPressEvent(QKeyEvent * event)

View File

@ -1,47 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 "copyableelidelabel.h"
#include <QApplication>
#include <QMenu>
#include <QClipboard>
CopyableElideLabel::CopyableElideLabel(QWidget* parent) :
ElideLabel(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &CopyableElideLabel::customContextMenuRequested, this, &CopyableElideLabel::showContextMenu);
actionCopy = new QAction(CopyableElideLabel::tr("Copy"), this);
connect(actionCopy, &QAction::triggered, [this]() {
QApplication::clipboard()->setText(text());
});
}
void CopyableElideLabel::showContextMenu(const QPoint& pos)
{
if (text().length() == 0) {
return;
}
QPoint globalPos = mapToGlobal(pos);
QMenu contextMenu;
contextMenu.addAction(actionCopy);
contextMenu.exec(globalPos);
}

View File

@ -1,36 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 COPYABLEELIDELABEL_HPP
#define COPYABLEELIDELABEL_HPP
#include "elidelabel.h"
class CopyableElideLabel : public ElideLabel
{
Q_OBJECT
public:
explicit CopyableElideLabel(QWidget* parent = 0);
private:
QAction* actionCopy;
private slots:
void showContextMenu(const QPoint& pos);
};
#endif // COPYABLEELIDELABEL_HPP

View File

@ -1,117 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 "editablelabelwidget.h"
#include <QApplication>
#include <QEvent>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QVBoxLayout>
ClickableCopyableElideLabel::ClickableCopyableElideLabel(QWidget* parent) :
CopyableElideLabel(parent)
{
}
bool ClickableCopyableElideLabel::event(QEvent* event)
{
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
emit clicked();
}
} else if (event->type() == QEvent::Enter) {
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
} else if (event->type() == QEvent::Leave) {
QApplication::restoreOverrideCursor();
}
return CopyableElideLabel::event(event);
}
EditableLabelWidget::EditableLabelWidget(QWidget* parent) :
QStackedWidget(parent), isSubmitting(false)
{
label = new ClickableCopyableElideLabel(this);
connect(label, &ClickableCopyableElideLabel::clicked, this, &EditableLabelWidget::onLabelClicked);
lineEdit = new EscLineEdit(this);
lineEdit->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
lineEdit->setMinimumHeight(label->fontMetrics().lineSpacing() + LINE_SPACING_OFFSET);
// Set dark background for >windows
//QColor toxDarkAsMySoul(28,28,28);
//QPalette darkPal;
//darkPal.setColor(QPalette::Window, toxDarkAsMySoul);
//darkPal.setColor(QPalette::Base, toxDarkAsMySoul);
//lineEdit->setPalette(darkPal);
connect(lineEdit, &EscLineEdit::editingFinished, this, &EditableLabelWidget::onLabelChangeSubmited);
connect(lineEdit, &EscLineEdit::escPressed, this, &EditableLabelWidget::onLabelChangeCancelled);
addWidget(label);
addWidget(lineEdit);
setCurrentWidget(label);
}
void EditableLabelWidget::setText(const QString& text)
{
label->setText(text);
lineEdit->setText(text);
}
QString EditableLabelWidget::text()
{
return label->text();
}
void EditableLabelWidget::onLabelChangeSubmited()
{
if (isSubmitting) {
return;
}
isSubmitting = true;
QString oldText = label->text();
QString newText = lineEdit->text();
// `lineEdit->clearFocus()` triggers `onLabelChangeSubmited()`, we use `isSubmitting` as a workaround
lineEdit->clearFocus();
setCurrentWidget(label);
if (oldText != newText) {
label->setText(newText);
emit textChanged(newText, oldText);
}
isSubmitting = false;
}
void EditableLabelWidget::onLabelChangeCancelled()
{
// order of calls matters, since clearFocus() triggers EditableLabelWidget::onLabelChangeSubmited()
lineEdit->setText(label->text());
lineEdit->clearFocus();
setCurrentWidget(label);
}
void EditableLabelWidget::onLabelClicked()
{
setCurrentWidget(lineEdit);
lineEdit->setFocus();
}

View File

@ -1,66 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 EDITABLELABELWIDGET_HPP
#define EDITABLELABELWIDGET_HPP
#include "copyableelidelabel.h"
#include "esclineedit.h"
#include <QLineEdit>
#include <QStackedWidget>
class ClickableCopyableElideLabel : public CopyableElideLabel
{
Q_OBJECT
public:
explicit ClickableCopyableElideLabel(QWidget* parent = 0);
protected:
bool event(QEvent* event) Q_DECL_OVERRIDE;
signals:
void clicked();
};
class EditableLabelWidget : public QStackedWidget
{
Q_OBJECT
public:
explicit EditableLabelWidget(QWidget* parent = 0);
ClickableCopyableElideLabel* label;
EscLineEdit* lineEdit;
void setText(const QString& text);
QString text();
private:
static const int LINE_SPACING_OFFSET = 2;
bool isSubmitting;
private slots:
void onLabelChangeSubmited();
void onLabelChangeCancelled();
void onLabelClicked();
signals:
void textChanged(QString newText, QString oldText);
};
#endif // EDITABLELABELWIDGET_HPP

View File

@ -1,82 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 "elidelabel.h"
#include <QPainter>
#include <QEvent>
ElideLabel::ElideLabel(QWidget *parent) :
QLabel(parent), _textElide(false), _textElideMode(Qt::ElideNone), _showToolTipOnElide(false)
{
}
void ElideLabel::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
QPainter p(this);
QFontMetrics metrics(font());
if ((metrics.width(text()) > contentsRect().width()) && textElide()) {
QString elidedText = fontMetrics().elidedText(text(), textElideMode(), rect().width());
p.drawText(rect(), alignment(), elidedText);
} else {
QLabel::paintEvent(event);
}
}
bool ElideLabel::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
QFontMetrics metrics(font());
if ((metrics.width(text()) > contentsRect().width()) && textElide() && showToolTipOnElide()) {
setToolTip(text());
} else {
setToolTip("");
}
}
return QLabel::event(event);
}
void ElideLabel::setTextElide(bool set)
{
_textElide = set;
}
bool ElideLabel::textElide() const
{
return _textElide;
}
void ElideLabel::setTextElideMode(Qt::TextElideMode mode)
{
_textElideMode = mode;
}
Qt::TextElideMode ElideLabel::textElideMode() const
{
return _textElideMode;
}
void ElideLabel::setShowToolTipOnElide(bool show)
{
_showToolTipOnElide = show;
}
bool ElideLabel::showToolTipOnElide()
{
return _showToolTipOnElide;
}

View File

@ -1,50 +0,0 @@
/*
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
This file is part of Tox Qt GUI.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(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 ELIDELABEL_HPP
#define ELIDELABEL_HPP
#include <QLabel>
class ElideLabel : public QLabel
{
Q_OBJECT
public:
explicit ElideLabel(QWidget *parent = 0);
void setTextElide(bool set);
bool textElide() const;
void setTextElideMode(Qt::TextElideMode mode);
Qt::TextElideMode textElideMode() const;
void setShowToolTipOnElide(bool show);
bool showToolTipOnElide();
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
bool event(QEvent *e) Q_DECL_OVERRIDE;
private:
bool _textElide;
Qt::TextElideMode _textElideMode;
bool _showToolTipOnElide;
};
#endif // ELIDELABEL_HPP

Some files were not shown because too many files have changed in this diff Show More