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

Merge pull request #1 from dubslow/history

updated history encryption
This commit is contained in:
apprb 2014-10-17 21:34:41 +09:00
commit 753edf3c6f
35 changed files with 1180 additions and 259 deletions

View File

@ -10,7 +10,7 @@ INSTALL_DIR=libs
# just for convenience
BASE_DIR=${SCRIPT_DIR}/${INSTALL_DIR}
SODIUM_VER=0.7.0
SODIUM_VER=1.0.0
# directory names of cloned repositories
SODIUM_DIR=libsodium-$SODIUM_VER

4
debian/control vendored
View File

@ -3,11 +3,11 @@ Maintainer: John Smith <barrdetwix@gmail.com>
Section: misc
Priority: optional
Standards-Version: 3.9.5
Build-Depends: debhelper (>= 9), cdbs, qt5-qmake, libopenal-dev (>= 1:1.15.1), libopencv-dev (>= 2.4.8), libopus-dev (>= 1.0), qtbase5-dev (>= 5.2), sudo, autoconf, libtool, pkg-config, libvpx-dev
Build-Depends: debhelper (>= 9), cdbs, qt5-qmake, libopenal-dev (>= 1:1.14), libopencv-dev (>= 2.3), libopus-dev (>= 0.9), qtbase5-dev (>= 5.2), sudo, autoconf, libtool, pkg-config, libvpx-dev
Package: qtox
Architecture: any
Depends: libc6 (>= 2.17), libgcc1 (>= 1:4.1.1), libgl1-mesa-glx | libgl1, libopenal1 (>= 1.14), libopencv-core2.4, libopencv-highgui2.4, libopus0 (>= 1.0), libqt5core5a (>= 5.2), libqt5gui5 (>= 5.2), libqt5network5 (>= 5.0), libqt5widgets5 (>= 5.2), libqt5xml5 (>= 5.0), libstdc++6 (>= 4.9), libvpx1 (>= 1.0.0)
Depends: ${shlibs:Depends}, ${misc:Depends}, libgcc1 (>= 1:4.1.1), libgl1-mesa-glx | libgl1, libopenal1 (>= 1.14), libopus0 (>= 0.9), libqt5core5a (>= 5.2), libqt5gui5 (>= 5.2), libqt5network5 (>= 5.0), libqt5widgets5 (>= 5.2), libqt5xml5 (>= 5.0), libstdc++6 (>= 4.9), libvpx1 (>= 1.0.0)
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

View File

@ -9,4 +9,4 @@ Exec=qtox
Icon=qtox
Categories=InstantMessaging;;AudioVideo;Network;
Terminal=false
MimeType=x-scheme-handler/tox;
MimeType=x-scheme-handler/tox;application/x-tox;

View File

@ -71,11 +71,11 @@ win32 {
LIBS += -Wl,-Bdynamic -ltbb -lv4l1 -lv4l2 -lgnutls -lrtmp -lgnutls -lavformat -lavcodec -lavutil -lavfilter -lswscale -lusb-1.0
} else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxencryptsave -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -lvpx -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
}
contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a -lopencv_core -lopencv_highgui -lopenal
LIBS = ./libs/lib/libsodium.a ./libs/lib/libtoxcore.a ./libs/lib/libtoxav.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libvpx.a ./libs/lib/libopus.a -lopencv_core -lopencv_highgui -lopenal
}
}
}

View File

@ -21,6 +21,7 @@
#include "widget/widget.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <ctime>
#include <functional>
@ -39,12 +40,15 @@
#include <QMessageBox>
const QString Core::CONFIG_FILE_NAME = "data";
const QString Core::TOX_EXT = ".tox";
QList<ToxFile> Core::fileSendQueue;
QList<ToxFile> Core::fileRecvQueue;
Core::Core(Camera* cam, QThread *coreThread) :
tox(nullptr), camera(cam)
Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
tox(nullptr), camera(cam), loadPath(loadPath)
{
qDebug() << "Core: loading Tox from" << loadPath;
videobuf = new uint8_t[videobufsize];
videoBusyness=0;
@ -110,6 +114,8 @@ Core::~Core()
alcCloseDevice(alOutDev);
if (alInDev)
alcCaptureCloseDevice(alInDev);
clearPassword();
}
Core* Core::getInstance()
@ -117,12 +123,11 @@ Core* Core::getInstance()
return Widget::getInstance()->getCore();
}
void Core::start()
void Core::make_tox()
{
// IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options.
bool enableIPv6 = Settings::getInstance().getEnableIPv6();
bool forceTCP = Settings::getInstance().getForceTCP();
bool useProxy = Settings::getInstance().getUseProxy();
if (enableIPv6)
@ -181,7 +186,7 @@ void Core::start()
emit failedToStart();
}
return;
}
}
else
qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly.";
}
@ -205,15 +210,24 @@ void Core::start()
emit failedToStart();
return;
}
}
void Core::start()
{
make_tox();
qsrand(time(nullptr));
if (!loadConfiguration())
if (loadPath != "")
{
emit failedToStart();
tox_kill(tox);
tox = nullptr;
return;
if (!loadConfiguration(loadPath)) // loadPath is meaningless after this
{
emit failedToStart();
tox_kill(tox);
tox = nullptr;
return;
}
loadPath = "";
}
tox_callback_friend_request(tox, onFriendRequest, this);
@ -273,9 +287,9 @@ void Core::start()
* 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
* So I set the tolerance here at 25, and initial DCs should be very rare now.
* This should be able to go to 50 or 100 without affecting legitimate disconnects'
* downtime, but lets be conservative for now. Edit: now 40.
* downtime, but lets be conservative for now. Edit: now ~~40~~ 30.
*/
#define CORE_DISCONNECT_TOLERANCE 40
#define CORE_DISCONNECT_TOLERANCE 30
void Core::process()
{
@ -574,7 +588,7 @@ void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive
uint64_t resumePos = *reinterpret_cast<const uint64_t*>(data);
if (resumePos >= file->filesize)
if (resumePos >= (unsigned)file->filesize)
{
qWarning() << "Core::onFileControlCallback: invalid resume position";
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); // don't sure about it
@ -627,8 +641,8 @@ void Core::onAvatarInfoCallback(Tox*, int32_t friendnumber, uint8_t format,
{
qDebug() << "Core: Got null avatar info from" << core->getFriendUsername(friendnumber);
emit core->friendAvatarRemoved(friendnumber);
QFile::remove(QDir(Settings::getInstance().getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".png"));
QFile::remove(QDir(Settings::getInstance().getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".hash"));
QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".png"));
QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".hash"));
}
else
{
@ -918,6 +932,8 @@ void Core::acceptFileRecvRequest(int friendId, int fileNum, QString path)
void Core::removeFriend(int friendId)
{
if (!tox)
return;
if (tox_del_friend(tox, friendId) == -1) {
emit failedToRemoveFriend(friendId);
} else {
@ -928,6 +944,8 @@ void Core::removeFriend(int friendId)
void Core::removeGroup(int groupId)
{
if (!tox)
return;
tox_del_groupchat(tox, groupId);
}
@ -949,8 +967,8 @@ void Core::setUsername(const QString& username)
if (tox_set_name(tox, cUsername.data(), cUsername.size()) == -1) {
emit failedToSetUsername(username);
} else {
saveConfiguration();
emit usernameSet(username);
saveConfiguration();
}
}
@ -981,6 +999,13 @@ ToxID Core::getSelfId()
return ToxID::fromString(CFriendAddress::toString(friendAddress));
}
QString Core::getIDString()
{
return getSelfId().toString().left(12);
// 12 is the smallest multiple of four such that
// 16^n > 10^10 (which is roughly the planet's population)
}
QString Core::getStatusMessage()
{
int size = tox_get_self_status_message_size(tox);
@ -1038,11 +1063,25 @@ void Core::onFileTransferFinished(ToxFile file)
emit fileDownloadFinished(file.filePath);
}
bool Core::loadConfiguration()
QString Core::sanitize(QString name)
{
QString path = QDir(Settings::getSettingsDirPath()).filePath(CONFIG_FILE_NAME);
// these are pretty much Windows banned filename characters
QList<QChar> banned = {'/', '\\', ':', '<', '>', '"', '|', '?', '*'};
for (QChar c : banned)
name.replace(c, '_');
// also remove leading and trailing periods
if (name[0] == '.')
name[0] = '_';
if (name.endsWith('.'))
name[name.length()-1] = '_';
return name;
}
bool Core::loadConfiguration(QString path)
{
// setting the profile is now the responsibility of the caller
QFile configurationFile(path);
qDebug() << "Core::loadConfiguration: reading from " << path;
if (!configurationFile.exists()) {
qWarning() << "The Tox configuration file was not found";
@ -1064,14 +1103,27 @@ bool Core::loadConfiguration()
}
else if (error == 1) // Encrypted data save
{
qWarning() << "Core: Can not open encrypted tox save";
if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted profile"),
tr("Your tox profile seems to be encrypted, qTox can't open it\nDo you want to erase this profile ?"),
QMessageBox::Ok | QMessageBox::Cancel))
if (!pwsaltedkey)
{
qWarning() << "Core: Couldn't open encrypted save, giving up";
configurationFile.close();
return false;
qWarning() << "Core: Can not open encrypted tox save";
if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted profile"),
tr("Your tox profile seems to be encrypted, qTox can't open it\nDo you want to erase this profile ?"),
QMessageBox::Ok | QMessageBox::Cancel))
{
qWarning() << "Core: Couldn't open encrypted save, giving up";
configurationFile.close();
return false;
}
}
else
{ /*
while (error != 0)
{
error = tox_encrypted_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size(), pwsaltedkey, TOX_HASH_LENGTH);
emit blockingGetPassword();
if (!pwsaltedkey)
// we need a way to start core without any profile
} */
}
}
}
@ -1093,42 +1145,108 @@ bool Core::loadConfiguration()
void Core::saveConfiguration()
{
Settings::getInstance().save();
QString dir = Settings::getSettingsDirPath();
QDir directory(dir);
if (!directory.exists() && !directory.mkpath(directory.absolutePath())) {
qCritical() << "Error while creating directory " << dir;
return;
}
QString profile = Settings::getInstance().getCurrentProfile();
if (profile == "")
{ // no profile active; this should only happen on startup, if at all
profile = sanitize(getUsername());
if (profile == "") // happens on creation of a new Tox ID
profile = getIDString();
Settings::getInstance().setCurrentProfile(profile);
}
QString path = directory.filePath(profile + TOX_EXT);
saveConfiguration(path);
}
void Core::saveConfiguration(const QString& path)
{
if (!tox)
{
qWarning() << "Core::saveConfiguration: Tox not started, aborting!";
return;
}
QString path = Settings::getSettingsDirPath();
Settings::getInstance().save();
QDir directory(path);
if (!directory.exists() && !directory.mkpath(directory.absolutePath())) {
qCritical() << "Error while creating directory " << path;
return;
}
path = directory.filePath(CONFIG_FILE_NAME);
QSaveFile configurationFile(path);
if (!configurationFile.open(QIODevice::WriteOnly)) {
qCritical() << "File " << path << " cannot be opened";
return;
}
qDebug() << "Core: Saving";
qDebug() << "Core: writing tox_save to " << path;
uint32_t fileSize;
if (Settings::getInstance().getEncryptTox())
fileSize = tox_encrypted_size(tox);
else
fileSize = tox_size(tox);
uint32_t fileSize = tox_size(tox);
if (fileSize > 0 && fileSize <= INT32_MAX) {
uint8_t *data = new uint8_t[fileSize];
tox_save(tox, data);
if (Settings::getInstance().getEncryptTox())
{
if (!pwsaltedkey)
emit blockingGetPassword();
//if (!pwsaltedkey)
// revert to unsaved...? or maybe we shouldn't even try to get a pw from here ^
int ret = tox_encrypted_save(tox, data, pwsaltedkey, TOX_HASH_LENGTH);
if (ret == -1)
{
qCritical() << "Core::saveConfiguration: encryption of save file failed!!!";
return;
}
}
else
tox_save(tox, data);
configurationFile.write(reinterpret_cast<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
}
void Core::switchConfiguration(const QString& profile)
{
if (profile.isEmpty())
{
qWarning() << "Core: got null profile to switch to, not switching";
return;
}
else
qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration();
clearPassword();
toxTimer->stop();
if (tox) {
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
emit selfAvatarChanged(QPixmap(":/img/contact_dark.png"));
emit blockingClearContacts(); // we need this to block, but signals are required for thread safety
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().setCurrentProfile(profile);
start();
}
void Core::loadFriends()
{
const uint32_t friendCount = tox_count_friendlist(tox);
@ -1405,3 +1523,54 @@ QList<CString> Core::splitMessage(const QString &message)
return splittedMsgs;
}
void Core::setPassword(QString& password)
{
if (password.isEmpty())
{
clearPassword();
return;
}
if (!pwsaltedkey)
pwsaltedkey = new uint8_t[tox_pass_key_length()];
CString str(password);
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkey);
password.clear();
}
void Core::clearPassword()
{
if (pwsaltedkey)
{
delete[] pwsaltedkey;
pwsaltedkey = nullptr;
}
}
QByteArray Core::encryptData(const QByteArray& data)
{
if (!pwsaltedkey)
return QByteArray();
uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()];
if (tox_pass_key_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkey, encrypted) == -1)
{
qWarning() << "Core::encryptData: encryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + tox_pass_encryption_extra_length());
}
QByteArray Core::decryptData(const QByteArray& data)
{
if (!pwsaltedkey)
return QByteArray();
int sz = data.size() - tox_pass_encryption_extra_length();
uint8_t decrypted[sz];
if (tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkey, decrypted) != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(decrypted), sz);
}

View File

@ -34,9 +34,13 @@ class Core : public QObject
{
Q_OBJECT
public:
explicit Core(Camera* cam, QThread* coreThread);
explicit Core(Camera* cam, QThread* coreThread, QString initialLoadPath);
static Core* getInstance(); ///< Returns the global widget's Core instance
~Core();
static const QString TOX_EXT;
static const QString CONFIG_FILE_NAME;
static QString sanitize(QString name);
int getGroupNumberPeers(int groupId) const;
QString getGroupPeerName(int groupId, int peerId) const;
@ -48,6 +52,9 @@ public:
void dispatchVideoFrame(vpx_image img) const;
void saveConfiguration();
void saveConfiguration(const QString& path);
QString getIDString();
QString getUsername();
QString getStatusMessage();
@ -56,10 +63,13 @@ public:
void increaseVideoBusyness();
void decreaseVideoBusyness();
bool anyActiveCalls();
public slots:
void start();
void process();
void bootstrapDht();
void switchConfiguration(const QString& profile);
void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message);
@ -94,9 +104,16 @@ public slots:
void micMuteToggle(int callId);
void setPassword(QString& password);
void clearPassword();
QByteArray encryptData(const QByteArray& data);
QByteArray decryptData(const QByteArray& data);
signals:
void connected();
void disconnected();
void blockingClearContacts();
void blockingGetPassword();
void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(int friendId, const QString& message, bool isAction);
@ -209,7 +226,8 @@ private:
bool checkConnection();
bool loadConfiguration(); // Returns false for a critical error, true otherwise
bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise
void make_tox();
void loadFriends();
static void sendAllFileData(Core* core, ToxFile* file);
@ -225,14 +243,15 @@ private slots:
private:
Tox* tox;
ToxAv* toxav;
QTimer *toxTimer, *fileTimer, *bootstrapTimer; //, *saveTimer;
QTimer *toxTimer, *fileTimer; //, *saveTimer;
Camera* camera;
QString loadPath; // meaningless after start() is called
QList<DhtServer> dhtServerList;
int dhtServerId;
static QList<ToxFile> fileSendQueue, fileRecvQueue;
static ToxCall calls[];
uint8_t* pwsaltedkey = nullptr; // use the pw's hash as the "pw"
static const QString CONFIG_FILE_NAME;
static const int videobufsize;
static uint8_t* videobuf;
static int videoBusyness; // Used to know when to drop frames

View File

@ -28,6 +28,14 @@ ALCdevice* Core::alOutDev, *Core::alInDev;
ALCcontext* Core::alContext;
ALuint Core::alMainSource;
bool Core::anyActiveCalls()
{
for (auto& call : calls)
if (call.active)
return true;
return false;
}
void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled)
{
qDebug() << QString("Core: preparing call %1").arg(callId);

View File

@ -24,7 +24,7 @@
#include <QDebug>
#include <QPainter>
#define CONTENT_WIDTH 250
#define MAX_CONTENT_WIDTH 250
#define MAX_PREVIEW_SIZE 25*1024*1024
uint FileTransferInstance::Idconter = 0;
@ -43,9 +43,10 @@ FileTransferInstance::FileTransferInstance(ToxFile File)
// update this whenever you change the font in innerStyle.css
QFontMetrics fm(Style::getFont(Style::Small));
filenameElided = fm.elidedText(filename, Qt::ElideRight, CONTENT_WIDTH);
filenameElided = fm.elidedText(filename, Qt::ElideRight, MAX_CONTENT_WIDTH);
size = getHumanReadableSize(File.filesize);
contentPrefWidth = std::max(fm.width(filenameElided), fm.width(size));
speed = "0B/s";
eta = "00:00";
@ -57,7 +58,7 @@ FileTransferInstance::FileTransferInstance(ToxFile File)
File.file->seek(0);
if (preview.loadFromData(File.file->readAll()))
{
pic = preview.scaledToHeight(50);
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
}
File.file->seek(0);
@ -127,7 +128,7 @@ void FileTransferInstance::onFileTransferFinished(ToxFile File)
{
if (preview.loadFromData(previewFile.readAll()))
{
pic = preview.scaledToHeight(50);
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
previewFile.close();
}
@ -377,7 +378,8 @@ QString FileTransferInstance::draw2ButtonsForm(const QString &type, const QImage
QString imgBstr = "<img src=\"data:ftrans." + widgetId + ".btnB/png;base64," + QImage2base64(imgB) + "\">";
QString content;
QString progrBar = "<img src=\"data:progressbar." + widgetId + "/png;base64," + QImage2base64(drawProgressBarImg(double(lastBytesSent)/totalBytes, CONTENT_WIDTH, 9)) + "\">";
QString progrBar = "<img src=\"data:progressbar." + widgetId + "/png;base64," +
QImage2base64(drawProgressBarImg(double(lastBytesSent)/totalBytes, MAX_CONTENT_WIDTH, 9)) + "\">";
content = "<p>" + filenameElided + "</p>";
content += "<table cellspacing=\"0\"><tr>";
@ -421,11 +423,14 @@ QString FileTransferInstance::wrapIntoForm(const QString& content, const QString
res += "<div class=button>" + imgLeftA + "<br>" + imgLeftB + "</div>\n";
res += "</td>\n";
res += insertMiniature(type);
res += "<td width=" + QString::number(CONTENT_WIDTH + 30) + ">\n";
res += "<td width=" + QString::number(contentPrefWidth) + ">\n";
res += "<div class=" + type + ">";
res += content;
res += "</div>\n";
res += "</td>\n";
res += "<td width=3>\n";
res += "<div class=" + type + "></div>\n";
res += "</td>\n";
res += "<td>\n";
res += "<div class=button>" + imgAstr + "<br>" + imgBstr + "</div>\n";
res += "</td>\n";

View File

@ -78,6 +78,7 @@ private:
long long lastBytesSent, totalBytes;
int fileNum;
int friendId;
int contentPrefWidth;
QString savePath;
ToxFile::FileDirection direction;
QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet;

View File

@ -44,9 +44,8 @@ HistoryKeeper *HistoryKeeper::getInstance()
if (encrypted)
{
QString key = "plainKey"; // FIXME: ask user about it
path = QDir(Settings::getInstance().getSettingsDirPath()).filePath("qtox_history.encrypted");
dbIntf = new EncryptedDb(path, key);
dbIntf = new EncryptedDb(path);
historyInstance = new HistoryKeeper(dbIntf);
return historyInstance;

View File

@ -16,6 +16,7 @@
#include "encrypteddb.h"
#include "src/misc/settings.h"
#include "src/core.h"
#include <tox/toxencryptsave.h>
@ -23,17 +24,9 @@
#include <QDebug>
#include <QSqlError>
EncryptedDb::EncryptedDb(const QString &fname, const QString &key) :
EncryptedDb::EncryptedDb(const QString &fname) :
PlainDb(":memory:"), encrFile(fname)
{
encrkey = new u_int8_t[tox_pass_key_length()];
QByteArray key_ba;
key_ba.append(key);
// tox_derive_key_from_pass(reinterpret_cast<uint8_t*>(key_ba.data()), key_ba.size(), encrkey);
passwd = "test";
qDebug() << QByteArray::fromRawData(reinterpret_cast<char *>(encrkey), tox_pass_key_length()).toBase64();
plainChunkSize = 1024;
encryptedChunkSize = plainChunkSize + tox_pass_encryption_extra_length();
@ -60,8 +53,7 @@ EncryptedDb::EncryptedDb(const QString &fname, const QString &key) :
EncryptedDb::~EncryptedDb()
{
encrFile.close();
delete encrkey;
encrFile.close(); // what if program is killed without being able to clean up?
}
QSqlQuery EncryptedDb::exec(const QString &query)
@ -85,7 +77,7 @@ QList<QString> EncryptedDb::decryptFile()
while (!encrFile.atEnd())
{
QByteArray encrChunk = encrFile.read(encryptedChunkSize);
buffer = decrypt(encrChunk);
buffer = Core::getInstance()->decryptData(encrChunk);
fileContent += buffer;
}
@ -114,44 +106,14 @@ void EncryptedDb::appendToEncrypted(const QString &sql)
{
QByteArray filledChunk = buffer.left(plainChunkSize);
encrFile.seek(chunkPosition * encryptedChunkSize);
encrFile.write(encrypt(filledChunk));
encrFile.write(Core::getInstance()->encryptData(filledChunk));
buffer = buffer.right(buffer.size() - plainChunkSize);
chunkPosition++;
}
encrFile.seek(chunkPosition * encryptedChunkSize);
encrFile.write(encrypt(buffer));
encrFile.write(Core::getInstance()->encryptData(buffer));
encrFile.flush();
qDebug() << sql;
}
QByteArray EncryptedDb::encrypt(QByteArray data)
{
int encrSize = data.size() + tox_pass_encryption_extra_length();
int plainSize = data.size();
uint8_t *out = new u_int8_t[encrSize];
// int state = tox_pass_key_encrypt(reinterpret_cast<uint8_t*>(data.data()), plainSize, encrkey, out);
int state = tox_pass_encrypt(reinterpret_cast<uint8_t*>(data.data()), plainSize,
reinterpret_cast<uint8_t*>(passwd.data()), passwd.size(), out);
qDebug() << state;
QByteArray ret = QByteArray::fromRawData(reinterpret_cast<const char*>(out), encrSize);
return ret;
}
QByteArray EncryptedDb::decrypt(QByteArray data)
{
int encrSize = data.size();
int plainSize = data.size() - tox_pass_encryption_extra_length();
uint8_t *out = new u_int8_t[plainSize];
// int state = tox_pass_key_decrypt(reinterpret_cast<uint8_t*>(data.data()), encrSize, encrkey, out);
int state = tox_pass_decrypt(reinterpret_cast<uint8_t*>(data.data()), encrSize,
reinterpret_cast<uint8_t*>(passwd.data()), passwd.size(), out);
qDebug() << state << encrSize << plainSize;
QByteArray ret = QByteArray::fromRawData(reinterpret_cast<const char*>(out), plainSize);
return ret;
}

View File

@ -25,22 +25,17 @@
class EncryptedDb : public PlainDb
{
public:
EncryptedDb(const QString& fname, const QString &key);
EncryptedDb(const QString& fname);
virtual ~EncryptedDb();
virtual QSqlQuery exec(const QString &query);
virtual bool save();
private:
QByteArray encrypt(QByteArray data);
QByteArray decrypt(QByteArray data);
QList<QString> decryptFile();
void appendToEncrypted(const QString &sql);
u_int8_t *encrkey;
QFile encrFile;
QByteArray passwd;
qint64 plainChunkSize;
qint64 encryptedChunkSize;

View File

@ -115,6 +115,8 @@ void Settings::load()
useProxy = s.value("useProxy", false).toBool();
proxyAddr = s.value("proxyAddr", "").toString();
proxyPort = s.value("proxyPort", 0).toInt();
currentProfile = s.value("currentProfile", "").toString();
autoAwayTime = s.value("autoAwayTime", 10).toInt();
s.endGroup();
s.beginGroup("Widgets");
@ -135,6 +137,8 @@ void Settings::load()
timestampFormat = s.value("timestampFormat", "hh:mm").toString();
minimizeOnClose = s.value("minimizeOnClose", false).toBool();
useNativeStyle = s.value("nativeStyle", false).toBool();
style = s.value("style", "None").toString();
statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool();
s.endGroup();
s.beginGroup("State");
@ -221,6 +225,8 @@ void Settings::save(QString path)
s.setValue("forceTCP", forceTCP);
s.setValue("proxyAddr", proxyAddr);
s.setValue("proxyPort", proxyPort);
s.setValue("currentProfile", currentProfile);
s.setValue("autoAwayTime", autoAwayTime);
s.endGroup();
s.beginGroup("Widgets");
@ -241,6 +247,8 @@ void Settings::save(QString path)
s.setValue("timestampFormat", timestampFormat);
s.setValue("minimizeOnClose", minimizeOnClose);
s.setValue("nativeStyle", useNativeStyle);
s.setValue("style",style);
s.setValue("statusChangeNotificationEnabled", statusChangeNotificationEnabled);
s.endGroup();
s.beginGroup("State");
@ -360,11 +368,31 @@ bool Settings::getAutostartInTray() const
return autostartInTray;
}
QString Settings::getStyle() const
{
return style;
}
void Settings::setStyle(const QString& newStyle)
{
style = newStyle;
}
void Settings::setAutostartInTray(bool newValue)
{
autostartInTray = newValue;
}
bool Settings::getStatusChangeNotificationEnabled() const
{
return statusChangeNotificationEnabled;
}
void Settings::setStatusChangeNotificationEnabled(bool newValue)
{
statusChangeNotificationEnabled = newValue;
}
bool Settings::getUseTranslations() const
{
return useTranslations;
@ -414,6 +442,16 @@ void Settings::setProxyPort(int newValue)
proxyPort = newValue;
}
QString Settings::getCurrentProfile() const
{
return currentProfile;
}
void Settings::setCurrentProfile(QString profile)
{
currentProfile = profile;
}
bool Settings::getEnableLogging() const
{
return enableLogging;
@ -434,6 +472,28 @@ void Settings::setEncryptLogs(bool newValue)
encryptLogs = newValue;
}
bool Settings::getEncryptTox() const
{
return encryptTox;
}
void Settings::setEncryptTox(bool newValue)
{
encryptTox = newValue;
}
int Settings::getAutoAwayTime() const
{
return autoAwayTime;
}
void Settings::setAutoAwayTime(int newValue)
{
if (newValue < 0)
newValue = 10;
autoAwayTime = newValue;
}
void Settings::setWidgetData(const QString& uniqueName, const QByteArray& data)
{
widgetSettings[uniqueName] = data;

View File

@ -51,6 +51,12 @@ public:
bool getAutostartInTray() const;
void setAutostartInTray(bool newValue);
QString getStyle() const;
void setStyle(const QString& newValue);
QString getCurrentProfile() const;
void setCurrentProfile(QString profile);
bool getUseTranslations() const;
void setUseTranslations(bool newValue);
@ -73,6 +79,12 @@ public:
bool getEncryptLogs() const;
void setEncryptLogs(bool newValue);
bool getEncryptTox() const;
void setEncryptTox(bool newValue);
int getAutoAwayTime() const;
void setAutoAwayTime(int newValue);
QPixmap getSavedAvatar(const QString& ownerId);
void saveAvatar(QPixmap& pic, const QString& ownerId);
@ -129,10 +141,14 @@ public:
bool isMinimizeOnCloseEnabled() const;
void setMinimizeOnClose(bool newValue);
bool getStatusChangeNotificationEnabled() const;
void setStatusChangeNotificationEnabled(bool newValue);
// Privacy
bool isTypingNotificationEnabled() const;
void setTypingNotification(bool enabled);
// State
bool getUseNativeStyle() const;
void setUseNativeStyle(bool value);
@ -176,8 +192,13 @@ private:
QString proxyAddr;
int proxyPort;
QString currentProfile;
bool enableLogging;
bool encryptLogs;
bool encryptTox;
int autoAwayTime;
QHash<QString, QByteArray> widgetSettings;
@ -192,11 +213,13 @@ private:
QByteArray windowGeometry;
QByteArray windowState;
QByteArray splitterState;
QString style;
// ChatView
int firstColumnHandlePos;
int secondColumnHandlePosFromRight;
QString timestampFormat;
bool statusChangeNotificationEnabled;
// Privacy
bool typingNotification;

View File

@ -127,12 +127,21 @@ bool SmileyPack::load(const QString& filename)
{
QString emoticon = stringElement.text();
filenameTable.insert(emoticon, file);
emoticonSet.push_back(emoticon);
cacheSmiley(file); // preload all smileys
QPixmap pm;
pm.loadFromData(getCachedSmiley(emoticon), "PNG");
if(pm.size().width() > 0)
emoticonSet.push_back(emoticon);
stringElement = stringElement.nextSibling().toElement();
}
emoticons.push_back(emoticonSet);
if(emoticonSet.size() > 0)
emoticons.push_back(emoticonSet);
}
// success!
@ -176,7 +185,6 @@ QIcon SmileyPack::getAsIcon(const QString &key)
{
QPixmap pm;
pm.loadFromData(getCachedSmiley(key), "PNG");
return QIcon(pm);
}

View File

@ -34,7 +34,6 @@ Camera::Camera()
connect(workerThread, &QThread::started, worker, &CameraWorker::onStart);
connect(workerThread, &QThread::finished, worker, &CameraWorker::deleteLater);
connect(workerThread, &QThread::deleteLater, worker, &CameraWorker::deleteLater);
connect(worker, &CameraWorker::started, this, &Camera::onWorkerStarted);
connect(worker, &CameraWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable);
connect(worker, &CameraWorker::resProbingFinished, this, &Camera::onResProbingFinished);

View File

@ -118,6 +118,11 @@ void ChatAreaWidget::insertMessage(ChatAction *msgAction)
messages.append(msgAction);
}
int ChatAreaWidget::getNumberOfMessages()
{
return messages.size();
}
void ChatAreaWidget::onSliderRangeChanged()
{
QScrollBar* scroll = verticalScrollBar();

View File

@ -34,6 +34,7 @@ public:
int nameColWidth() {return nameWidth;}
void setNameColWidth(int w);
int getNumberOfMessages();
public slots:
void clearChatArea();

View File

@ -132,6 +132,11 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
}
int GenericChatForm::getNumberOfMessages()
{
return chatWidget->getNumberOfMessages();
}
void GenericChatForm::setName(const QString &newName)
{
nameLabel->setText(newName);

View File

@ -48,6 +48,7 @@ public:
virtual void show(Ui::MainWindow &ui);
void addMessage(const QString &author, const QString &message, bool isAction, const QDateTime &datetime);
void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime);
int getNumberOfMessages();
signals:
void sendMessage(int, QString);

View File

@ -21,24 +21,39 @@
#include "src/misc/settings.h"
#include "src/misc/smileypack.h"
#include <QMessageBox>
#include <QStyleFactory>
GeneralForm::GeneralForm() :
GeneralForm::GeneralForm(SettingsWidget *myParent) :
GenericForm(tr("General Settings"), QPixmap(":/img/settings/general.png"))
{
parent = myParent;
bodyUI = new Ui::GeneralSettings;
bodyUI->setupUi(this);
bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6());
bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations());
bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable());
bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray());
bodyUI->statusChangesCheckbox->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled());
for (auto entry : SmileyPack::listSmileyPacks())
{
bodyUI->smileyPackBrowser->addItem(entry.first, entry.second);
}
bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack()));
reloadSmiles();
bodyUI->styleBrowser->addItems(QStyleFactory::keys());
bodyUI->styleBrowser->addItem("None");
if(QStyleFactory::keys().contains(Settings::getInstance().getStyle()))
bodyUI->styleBrowser->setCurrentText(Settings::getInstance().getStyle());
else
bodyUI->styleBrowser->setCurrentText("None");
bodyUI->autoAwaySpinBox->setValue(Settings::getInstance().getAutoAwayTime());
bodyUI->cbUDPDisabled->setChecked(Settings::getInstance().getForceTCP());
bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr());
int port = Settings::getInstance().getProxyPort();
@ -52,12 +67,15 @@ GeneralForm::GeneralForm() :
connect(bodyUI->cbUseTranslations, &QCheckBox::stateChanged, this, &GeneralForm::onUseTranslationUpdated);
connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated);
connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray);
connect(bodyUI->statusChangesCheckbox, &QCheckBox::stateChanged, this, &GeneralForm::onSetStatusChange);
connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int)));
// new syntax can't handle overloaded signals... (at least not in a pretty way)
connect(bodyUI->cbUDPDisabled, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated);
connect(bodyUI->proxyAddr, &QLineEdit::editingFinished, this, &GeneralForm::onProxyAddrEdited);
connect(bodyUI->proxyPort, SIGNAL(valueChanged(int)), this, SLOT(onProxyPortEdited(int)));
connect(bodyUI->cbUseProxy, &QCheckBox::stateChanged, this, &GeneralForm::onUseProxyUpdated);
connect(bodyUI->styleBrowser, SIGNAL(currentTextChanged(QString)), this, SLOT(onStyleSelected(QString)));
connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged()));
}
GeneralForm::~GeneralForm()
@ -85,10 +103,30 @@ void GeneralForm::onSetAutostartInTray()
Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked());
}
void GeneralForm::onStyleSelected(QString style)
{
Settings::getInstance().setStyle(style);
this->setStyle(QStyleFactory::create(style));
parent->setBodyHeadStyle(style);
}
void GeneralForm::onAutoAwayChanged()
{
int minutes = bodyUI->autoAwaySpinBox->value();
Settings::getInstance().setAutoAwayTime(minutes);
Widget::getInstance()->setIdleTimer(minutes);
}
void GeneralForm::onSetStatusChange()
{
Settings::getInstance().setStatusChangeNotificationEnabled(bodyUI->statusChangesCheckbox->isChecked());
}
void GeneralForm::onSmileyBrowserIndexChanged(int index)
{
QString filename = bodyUI->smileyPackBrowser->itemData(index).toString();
Settings::getInstance().setSmileyPack(filename);
reloadSmiles();
}
void GeneralForm::onUDPUpdated()
@ -119,3 +157,26 @@ void GeneralForm::onUseProxyUpdated()
bodyUI->proxyPort->setEnabled(state);
Settings::getInstance().setUseProxy(state);
}
void GeneralForm::reloadSmiles()
{
QList<QStringList> emoticons = SmileyPack::getInstance().getEmoticons();
QStringList smiles;
smiles << ":)" << ";)" << ":p" << ":O" << ":["; //just in case...
for(int i = 0; i < emoticons.size(); i++)
smiles.push_front(emoticons.at(i).first());
int pixSize = 30;
bodyUI->smile1->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[0]).pixmap(pixSize, pixSize));
bodyUI->smile2->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[1]).pixmap(pixSize, pixSize));
bodyUI->smile3->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[2]).pixmap(pixSize, pixSize));
bodyUI->smile4->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[3]).pixmap(pixSize, pixSize));
bodyUI->smile5->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[4]).pixmap(pixSize, pixSize));
bodyUI->smile1->setToolTip(smiles[0]);
bodyUI->smile2->setToolTip(smiles[1]);
bodyUI->smile3->setToolTip(smiles[2]);
bodyUI->smile4->setToolTip(smiles[3]);
bodyUI->smile5->setToolTip(smiles[4]);
}

View File

@ -18,8 +18,6 @@
#define GENERALFORM_H
#include "genericsettings.h"
#include <QComboBox>
#include <QCheckBox>
namespace Ui {
class GeneralSettings;
@ -29,7 +27,7 @@ class GeneralForm : public GenericForm
{
Q_OBJECT
public:
GeneralForm();
GeneralForm(SettingsWidget *parent);
~GeneralForm();
private slots:
@ -42,9 +40,15 @@ private slots:
void onProxyAddrEdited();
void onProxyPortEdited(int port);
void onUseProxyUpdated();
void onStyleSelected(QString style);
void onSetStatusChange();
void onAutoAwayChanged();
private:
Ui::GeneralSettings *bodyUI;
void reloadSmiles();
SettingsWidget *parent;
};
#endif

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>527</width>
<height>369</height>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
@ -53,6 +53,49 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="statusChangesCheckbox">
<property name="text">
<string>Show contacts' status changes</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item alignment="Qt::AlignLeft">
<widget class="QLabel" name="autoAwayLabel">
<property name="toolTip">
<string>Provided in minutes</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Auto away after:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="autoAwaySpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> minutes</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>600</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -72,6 +115,70 @@
<item>
<widget class="QComboBox" name="smileyPackBrowser"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="smile1">
<property name="toolTip">
<string>:)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="smile2">
<property name="toolTip">
<string>;)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="smile3">
<property name="toolTip">
<string>:p</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="smile4">
<property name="toolTip">
<string>:O</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="smile5">
<property name="toolTip">
<string>:'(</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="styleLabel">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="styleBrowser"/>
</item>
</layout>
</widget>
</item>

View File

@ -18,11 +18,16 @@
#include "ui_identitysettings.h"
#include "identityform.h"
#include "src/widget/form/settingswidget.h"
#include "src/misc/settings.h"
#include "src/widget/croppinglabel.h"
#include "src/widget/widget.h"
#include <QLabel>
#include <QLineEdit>
#include <QApplication>
#include <QClipboard>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
IdentityForm::IdentityForm() :
GenericForm(tr("Your identity"), QPixmap(":/img/settings/identity.png"))
@ -38,7 +43,7 @@ IdentityForm::IdentityForm() :
// toxId->setTextInteractionFlags(Qt::TextSelectableByMouse);
toxId->setReadOnly(true);
// toxId->setFrameStyle(QFrame::NoFrame);
toxId->setFrame(false);
// toxId->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// toxId->setFixedHeight(toxId->document()->size().height()*2);
toxId->setFont(small);
@ -49,6 +54,11 @@ IdentityForm::IdentityForm() :
connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited()));
connect(bodyUI->statusMessage, SIGNAL(editingFinished()), this, SLOT(onStatusMessageEdited()));
connect(bodyUI->loadButton, &QPushButton::clicked, this, &IdentityForm::onLoadClicked);
connect(bodyUI->renameButton, &QPushButton::clicked, this, &IdentityForm::onRenameClicked);
connect(bodyUI->exportButton, &QPushButton::clicked, this, &IdentityForm::onExportClicked);
connect(bodyUI->deleteButton, &QPushButton::clicked, this, &IdentityForm::onDeleteClicked);
connect(bodyUI->importButton, &QPushButton::clicked, this, &IdentityForm::onImportClicked);
}
IdentityForm::~IdentityForm()
@ -61,6 +71,7 @@ void IdentityForm::copyIdClicked()
QString txt = toxId->text();
txt.replace('\n',"");
QApplication::clipboard()->setText(txt);
toxId->setCursorPosition(0);
}
void IdentityForm::onUserNameEdited()
@ -76,6 +87,13 @@ void IdentityForm::onStatusMessageEdited()
void IdentityForm::present()
{
toxId->setText(Core::getInstance()->getSelfId().toString());
toxId->setCursorPosition(0);
bodyUI->profiles->clear();
for (QString profile : Widget::searchProfiles())
bodyUI->profiles->addItem(profile);
QString current = Settings::getInstance().getCurrentProfile();
if (current != "")
bodyUI->profiles->setCurrentText(current);
}
void IdentityForm::setUserName(const QString &name)
@ -87,3 +105,80 @@ void IdentityForm::setStatusMessage(const QString &msg)
{
bodyUI->statusMessage->setText(msg);
}
void IdentityForm::onLoadClicked()
{
if (bodyUI->profiles->currentText() != Settings::getInstance().getCurrentProfile())
{
if (Core::getInstance()->anyActiveCalls())
QMessageBox::warning(this, tr("Call active", "popup title"),
tr("You can't switch profiles while a call is active!", "popup text"));
else
emit Widget::getInstance()->changeProfile(bodyUI->profiles->currentText());
// I think by directly calling the function, I may have been causing thread issues
}
}
void IdentityForm::onRenameClicked()
{
QString cur = bodyUI->profiles->currentText();
QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur);
QString name = QInputDialog::getText(this, title, title+":");
if (name != "")
{
name = Core::sanitize(name);
QDir dir(Settings::getSettingsDirPath());
QFile::rename(dir.filePath(cur+Core::TOX_EXT), dir.filePath(name+Core::TOX_EXT));
bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name);
Settings::getInstance().setCurrentProfile(name);
}
}
void IdentityForm::onExportClicked()
{
QString current = bodyUI->profiles->currentText() + Core::TOX_EXT;
QString path = QFileDialog::getSaveFileName(this, tr("Export profile", "save dialog title"),
QDir::home().filePath(current),
tr("Tox save file (*.tox)", "save dialog filter"));
if (!path.isEmpty())
QFile::copy(QDir(Settings::getSettingsDirPath()).filePath(current), path);
}
void IdentityForm::onDeleteClicked()
{
if (Settings::getInstance().getCurrentProfile() == bodyUI->profiles->currentText())
{
QMessageBox::warning(this, tr("Profile currently loaded","current profile deletion warning title"), tr("This profile is currently in use. Please load a different profile before deleting this one.","current profile deletion warning text"));
}
else
{
QMessageBox::StandardButton resp = QMessageBox::question(this,
tr("Deletion imminent!","deletion confirmation title"), tr("Are you sure you want to delete this profile?","deletion confirmation text"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (resp == QMessageBox::Yes)
{
QFile::remove(QDir(Settings::getSettingsDirPath()).filePath(bodyUI->profiles->currentText()+Core::TOX_EXT));
bodyUI->profiles->removeItem(bodyUI->profiles->currentIndex());
bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile());
}
}
}
void IdentityForm::onImportClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Import profile", "import dialog title"), QDir::homePath(), tr("Tox save file (*.tox)", "import dialog filter"));
if (path.isEmpty())
return;
QFileInfo info(path);
if (info.suffix() != "tox")
{
QMessageBox::warning(this, tr("Ignoring non-Tox file", "popup title"), tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
return;
}
QString profile = info.completeBaseName();
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
QFile::copy(path, profilePath);
bodyUI->profiles->addItem(profile);
}

View File

@ -60,6 +60,11 @@ private slots:
void copyIdClicked();
void onUserNameEdited();
void onStatusMessageEdited();
void onLoadClicked();
void onRenameClicked();
void onExportClicked();
void onDeleteClicked();
void onImportClicked();
private:
Ui::IdentitySettings* bodyUI;

View File

@ -59,6 +59,71 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="profilesGroup">
<property name="title">
<string>Profiles</string>
</property>
<layout class="QVBoxLayout" name="profilesVLayout">
<item>
<layout class="QHBoxLayout" name="profilesComboLayout">
<item>
<widget class="QLabel" name="profilesLabel">
<property name="text">
<string>Available profiles:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="profiles" />
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="profilesButtonsLayout">
<item>
<widget class="QPushButton" name="loadButton">
<property name="text">
<string comment="load profile button">Load</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="renameButton">
<property name="text">
<string comment="rename profile button">Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportButton">
<property name="text">
<string comment="export profile button">Export</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string comment="delete profile button">Delete</string>
</property>
<property name="toolTip">
<string comment="delete profile button tooltip">This is useful to remain safe on public computers</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="importButton">
<property name="text">
<string comment="import profile button">Import a profile</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@ -52,7 +52,7 @@ SettingsWidget::SettingsWidget(QWidget* parent)
tabBar = new QTabBar;
bodyLayout->addWidget(tabBar);
GeneralForm *gfrm = new GeneralForm;
GeneralForm *gfrm = new GeneralForm(this);
ifrm = new IdentityForm;
PrivacyForm *pfrm = new PrivacyForm;
AVForm *avfrm = new AVForm;
@ -73,6 +73,12 @@ SettingsWidget::~SettingsWidget()
{
}
void SettingsWidget::setBodyHeadStyle(QString style)
{
head->setStyle(QStyleFactory::create(style));
body->setStyle(QStyleFactory::create(style));
}
void SettingsWidget::show(Ui::MainWindow& ui)
{
ui.mainContent->layout()->addWidget(body);

View File

@ -19,6 +19,8 @@
#include <QHBoxLayout>
#include <QPushButton>
#include <QStyleFactory>
class Camera;
class GenericForm;
class GeneralForm;
@ -40,6 +42,7 @@ public:
void show(Ui::MainWindow &ui);
IdentityForm *getIdentityForm() {return ifrm;}
void setBodyHeadStyle(QString style);
private slots:
void onTabChanged(int);

View File

@ -41,9 +41,13 @@
#include <QClipboard>
#include <QThread>
#include <QFileDialog>
#include <QInputDialog>
#include <QTimer>
#include <QStyleFactory>
#include "src/historykeeper.h"
#include <tox/tox.h>
Widget *Widget::instance{nullptr};
Widget::Widget(QWidget *parent)
@ -74,7 +78,16 @@ Widget::Widget(QWidget *parent)
ui->mainHead->setLayout(new QVBoxLayout());
ui->mainHead->layout()->setMargin(0);
ui->mainHead->layout()->setSpacing(0);
ui->mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css"));
if(QStyleFactory::keys().contains(Settings::getInstance().getStyle())
&& Settings::getInstance().getStyle() != "None")
{
ui->mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle()));
ui->mainContent->setStyle(QStyleFactory::create(Settings::getInstance().getStyle()));
}
ui->mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css"));
ui->mainContent->setStyleSheet(Style::getStylesheet(":ui/settings/mainContent.css"));
contactListWidget = new FriendListWidget();
@ -107,6 +120,11 @@ Widget::Widget(QWidget *parent)
// Disable some widgets until we're connected to the DHT
ui->statusButton->setEnabled(false);
idleTimer = new QTimer();
int mins = Settings::getInstance().getAutoAwayTime();
if (mins > 0)
idleTimer->start(mins * 1000*60);
qRegisterMetaType<Status>("Status");
qRegisterMetaType<vpx_image>("vpx_image");
qRegisterMetaType<uint8_t>("uint8_t");
@ -117,8 +135,9 @@ Widget::Widget(QWidget *parent)
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
QString profilePath = detectProfile();
coreThread = new QThread(this);
core = new Core(Camera::getInstance(), coreThread);
core = new Core(Camera::getInstance(), coreThread, profilePath);
core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start);
@ -134,7 +153,6 @@ Widget::Widget(QWidget *parent)
connect(core, SIGNAL(fileUploadFinished(const QString&)), &filesForm, SLOT(onFileUploadComplete(const QString&)));
connect(core, &Core::friendAdded, this, &Widget::addFriend);
connect(core, &Core::failedToAddFriend, this, &Widget::addFriendFailed);
connect(core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged);
connect(core, &Core::friendUsernameChanged, this, &Widget::onFriendUsernameChanged);
connect(core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged);
connect(core, &Core::friendStatusMessageChanged, this, &Widget::onFriendStatusMessageChanged);
@ -145,6 +163,8 @@ Widget::Widget(QWidget *parent)
connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged);
connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, this, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection);
connect(core, &Core::blockingGetPassword, this, &Widget::getPassword, Qt::BlockingQueuedConnection);
connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int)));
connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(int,QString,int)));
@ -152,6 +172,7 @@ Widget::Widget(QWidget *parent)
connect(this, &Widget::statusSet, core, &Core::setStatus);
connect(this, &Widget::friendRequested, core, &Core::requestFriendship);
connect(this, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
connect(this, &Widget::changeProfile, core, &Core::switchConfiguration);
connect(ui->addButton, SIGNAL(clicked()), this, SLOT(onAddClicked()));
connect(ui->groupButton, SIGNAL(clicked()), this, SLOT(onGroupClicked()));
@ -166,6 +187,7 @@ Widget::Widget(QWidget *parent)
connect(setStatusAway, SIGNAL(triggered()), this, SLOT(setStatusAway()));
connect(setStatusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy()));
connect(&friendForm, SIGNAL(friendRequested(QString,QString)), this, SIGNAL(friendRequested(QString,QString)));
connect(idleTimer, &QTimer::timeout, this, &Widget::onUserAway);
coreThread->start();
@ -212,6 +234,74 @@ void Widget::closeEvent(QCloseEvent *event)
QWidget::closeEvent(event);
}
QString Widget::detectProfile()
{
QDir dir(Settings::getSettingsDirPath());
QString path, profile = Settings::getInstance().getCurrentProfile();
path = dir.filePath(profile + Core::TOX_EXT);
QFile file(path);
if (profile == "" || !file.exists())
{
Settings::getInstance().setCurrentProfile("");
#if 1 // deprecation attempt
// if the last profile doesn't exist, fall back to old "data"
path = dir.filePath(Core::CONFIG_FILE_NAME);
QFile file(path);
if (file.exists())
return path;
else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data
return path;
else
#endif
{
profile = askProfiles();
if (profile != "")
return dir.filePath(profile + Core::TOX_EXT);
else
return "";
}
}
else
return path;
}
QList<QString> Widget::searchProfiles()
{
QList<QString> out;
QDir dir(Settings::getSettingsDirPath());
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
dir.setNameFilters(QStringList("*.tox"));
for(QFileInfo file : dir.entryInfoList())
out += file.completeBaseName();
return out;
}
QString Widget::askProfiles()
{ // TODO: allow user to create new Tox ID, even if a profile already exists
QList<QString> profiles = searchProfiles();
if (profiles.empty()) return "";
bool ok;
QString profile = QInputDialog::getItem(this,
tr("Choose a profile"),
tr("Please choose which identity to use"),
profiles,
0, // which slot to start on
false, // if the user can enter their own input
&ok);
if (!ok) // user cancelled
{
qApp->quit();
return "";
}
else
return profile;
}
void Widget::setIdleTimer(int minutes)
{
idleTimer->start(minutes * 1000*60);
}
QString Widget::getUsername()
{
return core->getUsername();
@ -220,7 +310,7 @@ QString Widget::getUsername()
void Widget::onAvatarClicked()
{
QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath());
if (filename == "")
if (filename.isEmpty())
return;
QFile file(filename);
file.open(QIODevice::ReadOnly);
@ -455,6 +545,25 @@ void Widget::onFriendStatusChanged(int friendId, Status status)
f->friendStatus = status;
f->widget->updateStatusLight();
//won't print the message if there were no messages before
if(f->chatForm->getNumberOfMessages() != 0
&& Settings::getInstance().getStatusChangeNotificationEnabled() == true)
{
QString fStatus = "";
switch(f->friendStatus){
case Status::Away:
fStatus = tr("away", "contact status"); break;
case Status::Busy:
fStatus = tr("busy", "contact status"); break;
case Status::Offline:
fStatus = tr("offline", "contact status"); break;
default:
fStatus = tr("online", "contact status"); break;
}
f->chatForm->addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"").arg(f->getName()).arg(fStatus),
"white", QDateTime::currentDateTime());
}
}
void Widget::onFriendStatusMessageChanged(int friendId, const QString& message)
@ -572,19 +681,31 @@ void Widget::onFriendRequestReceived(const QString& userId, const QString& messa
emit friendRequestAccepted(userId);
}
void Widget::removeFriend(int friendId)
void Widget::removeFriend(Friend* f)
{
Friend* f = FriendList::findFriend(friendId);
f->widget->setAsInactiveChatroom();
if (static_cast<GenericChatroomWidget*>(f->widget) == activeChatroomWidget)
activeChatroomWidget = nullptr;
FriendList::removeFriend(friendId);
core->removeFriend(friendId);
FriendList::removeFriend(f->friendId);
core->removeFriend(f->friendId);
delete f;
if (ui->mainHead->layout()->isEmpty())
onAddClicked();
}
void Widget::removeFriend(int friendId)
{
removeFriend(FriendList::findFriend(friendId));
}
void Widget::clearContactsList()
{
for (Friend* f : FriendList::friendList)
removeFriend(f);
for (Group* g : GroupList::groupList)
removeGroup(g);
}
void Widget::copyFriendIdToClipboard(int friendId)
{
Friend* f = FriendList::findFriend(friendId);
@ -648,19 +769,23 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha
g->updatePeer(peernumber,core->getGroupPeerName(groupnumber, peernumber));
}
void Widget::removeGroup(int groupId)
void Widget::removeGroup(Group* g)
{
Group* g = GroupList::findGroup(groupId);
g->widget->setAsInactiveChatroom();
if (static_cast<GenericChatroomWidget*>(g->widget) == activeChatroomWidget)
activeChatroomWidget = nullptr;
GroupList::removeGroup(groupId);
core->removeGroup(groupId);
GroupList::removeGroup(g->groupId);
core->removeGroup(g->groupId);
delete g;
if (ui->mainHead->layout()->isEmpty())
onAddClicked();
}
void Widget::removeGroup(int groupId)
{
removeGroup(GroupList::findGroup(groupId));
}
Core *Widget::getCore()
{
return core;
@ -706,18 +831,49 @@ bool Widget::isFriendWidgetCurActiveWidget(Friend* f)
bool Widget::event(QEvent * e)
{
if (e->type() == QEvent::WindowActivate)
{
if (activeChatroomWidget != nullptr)
{
activeChatroomWidget->resetEventFlags();
activeChatroomWidget->updateStatusLight();
}
switch(e->type()) {
case QEvent::WindowActivate:
if (activeChatroomWidget != nullptr)
{
activeChatroomWidget->resetEventFlags();
activeChatroomWidget->updateStatusLight();
}
// http://qt-project.org/faq/answer/how_can_i_detect_a_period_of_no_user_interaction
// Detecting global inactivity, like Skype, is possible but not via Qt:
// http://stackoverflow.com/a/21905027/1497645
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::Wheel:
case QEvent::KeyPress:
case QEvent::KeyRelease:
if (autoAwayActive)
{
qDebug() << "Widget: auto away deactivated";
autoAwayActive = false;
emit statusSet(Status::Online);
int mins = Settings::getInstance().getAutoAwayTime();
if (mins > 0)
idleTimer->start(mins * 1000*60);
}
default:
break;
}
return QWidget::event(e);
}
void Widget::onUserAway()
{
if (Settings::getInstance().getAutoAwayTime() > 0
&& ui->statusButton->property("status").toString() == "online") // leave user-set statuses in place
{
qDebug() << "Widget: auto away activated";
emit statusSet(Status::Away);
autoAwayActive = true;
}
idleTimer->stop();
}
void Widget::setStatusOnline()
{
core->setStatus(Status::Online);
@ -754,3 +910,12 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result)
if (result == -1)
g->chatForm->addSystemInfoMessage("Message failed to send", "red", QDateTime::currentDateTime());
}
void Widget::getPassword()
{
//QString password = QInputDialog();
//if (password.isEmpty())
// core->clearPassword();
//else
// core->setPassword(password);
}

View File

@ -40,6 +40,7 @@ class Core;
class Camera;
class FriendListWidget;
class MaskablePixmapWidget;
class QTimer;
class Widget : public QMainWindow
{
@ -56,6 +57,9 @@ public:
void newMessageAlert();
bool isFriendWidgetCurActiveWidget(Friend* f);
bool getIsWindowMinimized();
static QList<QString> searchProfiles();
void clearContactsList();
void setIdleTimer(int minutes);
~Widget();
virtual void closeEvent(QCloseEvent *event);
@ -67,6 +71,7 @@ signals:
void statusSelected(Status status);
void usernameChanged(const QString& username);
void statusMessageChanged(const QString& statusMessage);
void changeProfile(const QString& profile);
private slots:
void onConnected();
@ -106,13 +111,18 @@ private slots:
void onGroupSendResult(int groupId, const QString& message, int result);
void playRingtone();
void onIconClick();
void onUserAway();
void getPassword();
private:
void hideMainForms();
virtual bool event(QEvent * e);
Group* createGroup(int groupId);
void removeFriend(Friend* f);
void removeGroup(Group* g);
QString askProfiles();
QString detectProfile();
private:
Ui::MainWindow *ui;
QSplitter *centralLayout;
QPoint dragPosition;
@ -126,6 +136,8 @@ private:
FriendListWidget* contactListWidget;
MaskablePixmapWidget* profilePicture;
bool notify(QObject *receiver, QEvent *event);
bool autoAwayActive = false;
QTimer* idleTimer;
};
#endif // WIDGET_H

View File

@ -87,7 +87,7 @@ mv qTox-master $VERNAME
# Build packages
cd $VERNAME
./bootstrap.sh --local
./bootstrap.sh -t
debuild -us -uc -aamd64
debuild -us -uc -ai386
cd ..

Binary file not shown.

View File

@ -4,116 +4,144 @@
<context>
<name>AVForm</name>
<message>
<location filename="../widget/form/settings/avform.cpp" line="22"/>
<location filename="../src/widget/form/settings/avform.cpp" line="22"/>
<source>Audio/Video settings</source>
<translation>Impostazioni Audio/Video</translation>
</message>
<message>
<location filename="../widget/form/settings/avform.cpp" line="41"/>
<source>Hide video preview</source>
<comment>On a button</comment>
<translation>Ferma webcam</translation>
</message>
<message>
<location filename="../widget/form/settings/avform.cpp" line="47"/>
<source>Show video preview</source>
<comment>On a button</comment>
<translation>Prova webcam</translation>
</message>
</context>
<context>
<name>AVSettings</name>
<message>
<location filename="../widget/form/settings/avsettings.ui" line="14"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="14"/>
<source>Form</source>
<translation>Form</translation>
</message>
<message>
<location filename="../widget/form/settings/avsettings.ui" line="20"/>
<location filename="../src/widget/form/settings/avsettings.ui" line="20"/>
<source>Volume Settings (Stubs)</source>
<translation>Impostazioni Volume (Stub)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="26"/>
<source>Playback</source>
<translation>Altoparlanti</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="40"/>
<source>Microphone</source>
<translation>Microfono</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="57"/>
<source>Video settings</source>
<translation>Impostazioni Video</translation>
</message>
<message>
<location filename="../widget/form/settings/avsettings.ui" line="26"/>
<source>Show video preview</source>
<translation>Prova webcam</translation>
<location filename="../src/widget/form/settings/avsettings.ui" line="66"/>
<source>Modes</source>
<translation>Modalità</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="83"/>
<source>Hue</source>
<translation>Colore</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="97"/>
<source>Brightness</source>
<translation>Luminoistà</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="111"/>
<source>Saturation</source>
<translation>Saturazione</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="125"/>
<source>Contrast</source>
<translation>Contrasto</translation>
</message>
<message>
<location filename="../src/widget/form/settings/avsettings.ui" line="139"/>
<source>Preview</source>
<translation>Anteprima</translation>
</message>
</context>
<context>
<name>AddFriendForm</name>
<message>
<location filename="../widget/form/addfriendform.cpp" line="34"/>
<location filename="../src/widget/form/addfriendform.cpp" line="34"/>
<source>Add Friends</source>
<translation>Aggiungi Contatto</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="37"/>
<location filename="../src/widget/form/addfriendform.cpp" line="37"/>
<source>Tox ID</source>
<comment>Tox ID of the person you&apos;re sending a friend request to</comment>
<translation>Tox ID</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="38"/>
<location filename="../src/widget/form/addfriendform.cpp" line="38"/>
<source>Message</source>
<comment>The message you send in friend requests</comment>
<translation>Messaggio</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="39"/>
<location filename="../src/widget/form/addfriendform.cpp" line="39"/>
<source>Send friend request</source>
<translation>Invia richiesta d&apos;amicizia</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="40"/>
<location filename="../src/widget/form/addfriendform.cpp" line="40"/>
<source>Tox me maybe?</source>
<comment>Default message in friend requests if the field is left blank. Write something appropriate!</comment>
<translation>Permettimi di aggiungerti alla mia lista contatti</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="96"/>
<location filename="../src/widget/form/addfriendform.cpp" line="96"/>
<source>Please fill in a valid Tox ID</source>
<comment>Tox ID of the friend you&apos;re sending a friend request to</comment>
<translation>Inserisci un Tox ID valido</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="99"/>
<location filename="../src/widget/form/addfriendform.cpp" line="99"/>
<source>You can&apos;t add yourself as a friend!</source>
<comment>When trying to add your own Tox ID as friend</comment>
<translation>Non puoi aggiungere te stesso come contatto!</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="116"/>
<location filename="../src/widget/form/addfriendform.cpp" line="116"/>
<source>This address does not exist</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Questo indirizzo non esiste</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="120"/>
<location filename="../src/widget/form/addfriendform.cpp" line="120"/>
<source>Error while looking up DNS</source>
<comment>The DNS gives the Tox ID associated to toxme.se addresses</comment>
<translation>Errore nel consultare il server DNS</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="126"/>
<location filename="../src/widget/form/addfriendform.cpp" line="126"/>
<source>Unexpected number of text records</source>
<comment>Error with the DNS</comment>
<translation>Numero inaspettato di text-records</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="132"/>
<location filename="../src/widget/form/addfriendform.cpp" line="132"/>
<source>Unexpected number of values in text record</source>
<comment>Error with the DNS</comment>
<translation>Numero inaspettato di valori nel text-record</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="139"/>
<location filename="../src/widget/form/addfriendform.cpp" line="139"/>
<source>The DNS lookup does not contain any Tox ID</source>
<comment>Error with the DNS</comment>
<translation>La risposta del server DNS non contiene nessun Tox ID</translation>
</message>
<message>
<location filename="../widget/form/addfriendform.cpp" line="145"/>
<location filename="../widget/form/addfriendform.cpp" line="151"/>
<location filename="../src/widget/form/addfriendform.cpp" line="145"/>
<location filename="../src/widget/form/addfriendform.cpp" line="151"/>
<source>The DNS lookup does not contain a valid Tox ID</source>
<comment>Error with the DNS</comment>
<translation>La risposta del server DNS non contiene un Tox ID valido</translation>
@ -122,7 +150,7 @@
<context>
<name>ChatForm</name>
<message>
<location filename="../widget/form/chatform.cpp" line="105"/>
<location filename="../src/widget/form/chatform.cpp" line="105"/>
<source>Send a file</source>
<translation>Invia un file</translation>
</message>
@ -130,12 +158,12 @@
<context>
<name>Core</name>
<message>
<location filename="../core.cpp" line="1068"/>
<location filename="../src/core.cpp" line="1104"/>
<source>Encrypted profile</source>
<translation>Profilo criptato</translation>
</message>
<message>
<location filename="../core.cpp" line="1069"/>
<location filename="../src/core.cpp" line="1105"/>
<source>Your tox profile seems to be encrypted, qTox can&apos;t open it
Do you want to erase this profile ?</source>
<translation>Il tuo profilo Tox sembra essere criptato, qTox non può aprirlo\nVuoi eliminare questo profilo?</translation>
@ -144,19 +172,19 @@ Do you want to erase this profile ?</source>
<context>
<name>FileTransferInstance</name>
<message>
<location filename="../filetransferinstance.cpp" line="208"/>
<location filename="../src/filetransferinstance.cpp" line="209"/>
<source>Save a file</source>
<comment>Title of the file saving dialog</comment>
<translation>Salva file</translation>
</message>
<message>
<location filename="../filetransferinstance.cpp" line="219"/>
<location filename="../src/filetransferinstance.cpp" line="220"/>
<source>Location not writable</source>
<comment>Title of permissions popup</comment>
<translation>Errore</translation>
</message>
<message>
<location filename="../filetransferinstance.cpp" line="219"/>
<location filename="../src/filetransferinstance.cpp" line="220"/>
<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>
@ -165,18 +193,18 @@ Do you want to erase this profile ?</source>
<context>
<name>FilesForm</name>
<message>
<location filename="../widget/form/filesform.cpp" line="30"/>
<location filename="../src/widget/form/filesform.cpp" line="30"/>
<source>Transfered Files</source>
<comment>&quot;Headline&quot; of the window</comment>
<translation>Files Trasferiti</translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="38"/>
<location filename="../src/widget/form/filesform.cpp" line="38"/>
<source>Downloads</source>
<translation>Ricevuti</translation>
</message>
<message>
<location filename="../widget/form/filesform.cpp" line="39"/>
<location filename="../src/widget/form/filesform.cpp" line="39"/>
<source>Uploads</source>
<translation>Inviati</translation>
</message>
@ -184,34 +212,34 @@ Do you want to erase this profile ?</source>
<context>
<name>FriendRequestDialog</name>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="30"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="30"/>
<source>Friend request</source>
<comment>Title of the window to aceept/deny a friend request</comment>
<translation>Richiesta d&apos;amicizia</translation>
</message>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="32"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="32"/>
<source>Someone wants to make friends with you</source>
<translation>Qualcuno vuole chattare con te</translation>
</message>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="33"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="33"/>
<source>User ID:</source>
<translation>ID Utente:</translation>
</message>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="37"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="37"/>
<source>Friend request message:</source>
<translation>Messaggio della richiesta d&apos;amicizia:</translation>
</message>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="44"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="44"/>
<source>Accept</source>
<comment>Accept a friend request</comment>
<translation>Accetta</translation>
</message>
<message>
<location filename="../widget/tool/friendrequestdialog.cpp" line="45"/>
<location filename="../src/widget/tool/friendrequestdialog.cpp" line="45"/>
<source>Reject</source>
<comment>Reject a friend request</comment>
<translation>Rifiuta</translation>
@ -220,19 +248,19 @@ Do you want to erase this profile ?</source>
<context>
<name>FriendWidget</name>
<message>
<location filename="../widget/friendwidget.cpp" line="48"/>
<location filename="../src/widget/friendwidget.cpp" line="48"/>
<source>Copy friend ID</source>
<comment>Menu to copy the Tox ID of that friend</comment>
<translation>Copia Tox ID del contatto</translation>
</message>
<message>
<location filename="../widget/friendwidget.cpp" line="49"/>
<location filename="../src/widget/friendwidget.cpp" line="49"/>
<source>Invite in group</source>
<comment>Menu to invite a friend in a groupchat</comment>
<translation>Invita nel gruppo</translation>
</message>
<message>
<location filename="../widget/friendwidget.cpp" line="59"/>
<location filename="../src/widget/friendwidget.cpp" line="59"/>
<source>Remove friend</source>
<comment>Menu to remove the friend from our friendlist</comment>
<translation>Rimuovi contatto</translation>
@ -241,7 +269,7 @@ Do you want to erase this profile ?</source>
<context>
<name>GeneralForm</name>
<message>
<location filename="../widget/form/settings/generalform.cpp" line="26"/>
<location filename="../src/widget/form/settings/generalform.cpp" line="26"/>
<source>General Settings</source>
<translation>Impostazioni Generali</translation>
</message>
@ -249,79 +277,84 @@ Do you want to erase this profile ?</source>
<context>
<name>GeneralSettings</name>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="14"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="14"/>
<source>Form</source>
<translation>Form</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="29"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="29"/>
<source>General Settings</source>
<translation>Impostazioni Generali</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="74"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="81"/>
<source>Connection Settings</source>
<translation>Impostazioni Connessione</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="80"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="87"/>
<source>Enable IPv6 (recommended)</source>
<extracomment>Text on a checkbox to enable IPv6</extracomment>
<translation>Abilita IPv6 (consigliato)</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="35"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="35"/>
<source>Use translations</source>
<extracomment>Text on a checkbox to enable translations</extracomment>
<translation>Abilita traduzioni</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="42"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="42"/>
<source>Save settings to the working directory instead of the usual conf dir</source>
<extracomment>describes makeToxPortable checkbox</extracomment>
<translation>Slava le impostazioni nella directory di lavoro corrente, invece della directory di default</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="45"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="45"/>
<source>Make Tox portable</source>
<translation>Rendi qTox portabile</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="55"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="52"/>
<source>Start in tray</source>
<translation>Avvia minimizzato</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="62"/>
<source>Theme</source>
<translation>Impostazioni Tema</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="61"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="68"/>
<source>Smiley Pack</source>
<extracomment>Text on smiley pack label</extracomment>
<translation>Emoticons</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="97"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="104"/>
<source>Use proxy (SOCKS5)</source>
<translation>Usa proxy (SOCKS5)</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="106"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="113"/>
<source>Address</source>
<extracomment>Text on proxy addr label</extracomment>
<translation>IP</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="116"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="123"/>
<source>Port</source>
<extracomment>Text on proxy port label</extracomment>
<translation>Porta</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="90"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="97"/>
<source>Disable UDP (not recommended)</source>
<extracomment>Text on checkbox to disable UDP</extracomment>
<translation>Disabilita connessioni UDP (non raccomandato)</translation>
</message>
<message>
<location filename="../widget/form/settings/generalsettings.ui" line="87"/>
<location filename="../src/widget/form/settings/generalsettings.ui" line="94"/>
<source>This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary.</source>
<extracomment>force tcp checkbox tooltip</extracomment>
<translation>Questo permette di usare qTox con Tor; tuttavia aggiunge carico alla rete Tox, quindi usalo solo se necessario.</translation>
@ -330,8 +363,8 @@ Do you want to erase this profile ?</source>
<context>
<name>GenericChatForm</name>
<message>
<location filename="../widget/form/genericchatform.cpp" line="149"/>
<location filename="../widget/form/genericchatform.cpp" line="155"/>
<location filename="../src/widget/form/genericchatform.cpp" line="149"/>
<location filename="../src/widget/form/genericchatform.cpp" line="155"/>
<source>Save chat log</source>
<translation>Salva il log della chat</translation>
</message>
@ -339,13 +372,13 @@ Do you want to erase this profile ?</source>
<context>
<name>GroupChatForm</name>
<message>
<location filename="../widget/form/groupchatform.cpp" line="47"/>
<location filename="../src/widget/form/groupchatform.cpp" line="47"/>
<source>%1 users in chat</source>
<comment>Number of users in chat</comment>
<translation>%1 utenti in chat</translation>
</message>
<message>
<location filename="../widget/form/groupchatform.cpp" line="85"/>
<location filename="../src/widget/form/groupchatform.cpp" line="85"/>
<source>%1 users in chat</source>
<translation>%1 utenti in chat</translation>
</message>
@ -353,19 +386,19 @@ Do you want to erase this profile ?</source>
<context>
<name>GroupWidget</name>
<message>
<location filename="../widget/groupwidget.cpp" line="39"/>
<location filename="../widget/groupwidget.cpp" line="59"/>
<location filename="../src/widget/groupwidget.cpp" line="39"/>
<location filename="../src/widget/groupwidget.cpp" line="59"/>
<source>%1 users in chat</source>
<translation>%1 utenti in chat</translation>
</message>
<message>
<location filename="../widget/groupwidget.cpp" line="41"/>
<location filename="../widget/groupwidget.cpp" line="61"/>
<location filename="../src/widget/groupwidget.cpp" line="41"/>
<location filename="../src/widget/groupwidget.cpp" line="61"/>
<source>0 users in chat</source>
<translation>0 utenti in chat</translation>
</message>
<message>
<location filename="../widget/groupwidget.cpp" line="48"/>
<location filename="../src/widget/groupwidget.cpp" line="48"/>
<source>Quit group</source>
<comment>Menu to quit a groupchat</comment>
<translation>Esci dal gruppo</translation>
@ -374,88 +407,188 @@ Do you want to erase this profile ?</source>
<context>
<name>IdentityForm</name>
<message>
<location filename="../widget/form/settings/identityform.cpp" line="29"/>
<location filename="../src/widget/form/settings/identityform.cpp" line="33"/>
<source>Your identity</source>
<translation>Il tuo profilo</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="117"/>
<source>Rename &quot;%1&quot;</source>
<comment>renaming a profile</comment>
<translation>Rinomina &quot;%1&quot;</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="132"/>
<source>Export profile</source>
<comment>save dialog title</comment>
<translation>Esporta profilo</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="134"/>
<source>Tox save file (*.tox)</source>
<comment>save dialog filter</comment>
<translation>Tox save file (*.tox)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="143"/>
<source>Profile currently loaded</source>
<comment>current profile deletion warning title</comment>
<translation>Profilo attualmente in uso</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="143"/>
<source>This profile is currently in use. Please load a different profile before deleting this one.</source>
<comment>current profile deletion warning text</comment>
<translation>Questo profilo è attualmente in uso. Per favore carica un profilo differente prima di eliminare questo.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="148"/>
<source>Deletion imminent!</source>
<comment>deletion confirmation title</comment>
<translation>Eliminazione imminente!</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="148"/>
<source>Are you sure you want to delete this profile?</source>
<comment>deletion confirmation text</comment>
<translation>Sei sicuro di voler eliminare questo profilo?</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="160"/>
<source>Import profile</source>
<comment>import dialog title</comment>
<translation>Importa profilo</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identityform.cpp" line="160"/>
<source>Tox save file (*.tox)</source>
<comment>import dialog filter</comment>
<translation>Tox save file (*.tox)</translation>
</message>
</context>
<context>
<name>IdentitySettings</name>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="14"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="14"/>
<source>Form</source>
<translation>Form</translation>
</message>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="20"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="20"/>
<source>Public Information</source>
<translation>Informazioni Pubbliche</translation>
</message>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="26"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="26"/>
<source>Name</source>
<translation>Nome</translation>
</message>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="36"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="36"/>
<source>Status</source>
<translation>Stato</translation>
</message>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="49"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="49"/>
<source>Tox ID</source>
<translation>Tox ID</translation>
</message>
<message>
<location filename="../widget/form/settings/identitysettings.ui" line="55"/>
<location filename="../src/widget/form/settings/identitysettings.ui" line="55"/>
<source>Your Tox ID (click to copy)</source>
<translation>(clicca qui per copiare)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="65"/>
<source>Profiles</source>
<translation>Profili</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="73"/>
<source>Available profiles:</source>
<translation>Profili disponibili:</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="87"/>
<source>Load</source>
<comment>load profile button</comment>
<translation>Carica</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="94"/>
<source>Rename</source>
<comment>rename profile button</comment>
<translation>Rinomina</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="101"/>
<source>Export</source>
<comment>export profile button</comment>
<translation>Esporta</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="108"/>
<source>Delete</source>
<comment>delete profile button</comment>
<translation>Elimina</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="111"/>
<source>This is useful to remain safe on public computers</source>
<comment>delete profile button tooltip</comment>
<translation>Utile per preservare la tua sicurezza su computer pubblici</translation>
</message>
<message>
<location filename="../src/widget/form/settings/identitysettings.ui" line="120"/>
<source>Import a profile</source>
<comment>import profile button</comment>
<translation>Importa un profilo</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="20"/>
<location filename="../src/mainwindow.ui" line="20"/>
<source>qTox</source>
<translation>qTox</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="859"/>
<location filename="../src/mainwindow.ui" line="859"/>
<source>Your name</source>
<translation>qTox User</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="941"/>
<location filename="../src/mainwindow.ui" line="941"/>
<source>Your status</source>
<translation>Toxing on qTox</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1089"/>
<location filename="../src/mainwindow.ui" line="1089"/>
<source>Add friends</source>
<translation>Aggiungi contatto</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1115"/>
<location filename="../src/mainwindow.ui" line="1115"/>
<source>Create a group chat</source>
<translation>Crea un gruppo</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1147"/>
<location filename="../src/mainwindow.ui" line="1147"/>
<source>View completed file transfers</source>
<translation>Visualizza i trasferimenti completati</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1179"/>
<location filename="../src/mainwindow.ui" line="1179"/>
<source>Change your settings</source>
<translation>Cambia le impostazioni</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1761"/>
<location filename="../src/mainwindow.ui" line="1761"/>
<source>Close</source>
<translation>Chiudi</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="1764"/>
<location filename="../src/mainwindow.ui" line="1764"/>
<source>Ctrl+Q</source>
<translation>Ctrl+Q</translation>
</message>
@ -463,80 +596,81 @@ Do you want to erase this profile ?</source>
<context>
<name>PrivacyForm</name>
<message>
<location filename="../widget/form/settings/privacyform.cpp" line="21"/>
<location filename="../src/widget/form/settings/privacyform.cpp" line="21"/>
<source>Privacy settings</source>
<translation>Impostazioni privacy</translation>
</message>
</context>
<context>
<name>SelfCamView</name>
<message>
<location filename="../widget/selfcamview.cpp" line="33"/>
<source>Tox video test</source>
<comment>Title of the window to test the video/webcam</comment>
<translation>qTox video test</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<location filename="../widget/widget.cpp" line="88"/>
<location filename="../src/widget/widget.cpp" line="90"/>
<source>Online</source>
<comment>Button to set your status to &apos;Online&apos;</comment>
<translation>Online</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="90"/>
<location filename="../src/widget/widget.cpp" line="92"/>
<source>Away</source>
<comment>Button to set your status to &apos;Away&apos;</comment>
<translation>Assente</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="92"/>
<location filename="../src/widget/widget.cpp" line="94"/>
<source>Busy</source>
<comment>Button to set your status to &apos;Busy&apos;</comment>
<translation>Occupato</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="225"/>
<location filename="../src/widget/widget.cpp" line="264"/>
<source>Choose a profile</source>
<translation>Scegli un profilo</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="265"/>
<source>Please choose which identity to use</source>
<translation>Per favore scegli quale identità usare</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="286"/>
<source>Choose a profile picture</source>
<translation>Scegli un&apos;immagine per il profilo</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="232"/>
<location filename="../widget/widget.cpp" line="239"/>
<location filename="../widget/widget.cpp" line="260"/>
<location filename="../src/widget/widget.cpp" line="293"/>
<location filename="../src/widget/widget.cpp" line="300"/>
<location filename="../src/widget/widget.cpp" line="321"/>
<source>Error</source>
<translation>Errore</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="232"/>
<location filename="../src/widget/widget.cpp" line="293"/>
<source>Unable to open this file</source>
<translation>Impossibile aprire il file</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="239"/>
<location filename="../src/widget/widget.cpp" line="300"/>
<source>Unable to read this image</source>
<translation>Impossibile leggere l&apos;immagine</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="260"/>
<location filename="../src/widget/widget.cpp" line="321"/>
<source>This image is too big</source>
<translation>L&apos;immagine è troppo grande</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="287"/>
<location filename="../src/widget/widget.cpp" line="348"/>
<source>Toxcore failed to start, the application will terminate after you close this message.</source>
<translation>Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio.</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="296"/>
<location filename="../src/widget/widget.cpp" line="357"/>
<source>toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart.</source>
<comment>popup text</comment>
<translation>Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma.</translation>
</message>
<message>
<location filename="../widget/widget.cpp" line="611"/>
<location filename="../src/widget/widget.cpp" line="712"/>
<source>&lt;Unknown&gt;</source>
<comment>Placeholder when we don&apos;t know someone&apos;s name in a group chat</comment>
<translation>&lt;Sconosciuto&gt;</translation>

View File

@ -41,7 +41,7 @@ div.green {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 0px;
margin-right: 12px;
margin-right: 0px;
color: @white;
background-color: @green;
font: @small;
@ -51,7 +51,7 @@ div.silver {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 0px;
margin-right: 12px;
margin-right: 0px;
color: @black;
background-color: @lightGrey;
font: @small;
@ -61,7 +61,7 @@ div.red {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 0px;
margin-right: 12px;
margin-right: 0px;
color: @white;
background-color: @red;
font: @small;

View File

@ -11,7 +11,11 @@ QLabel
QGroupBox::title
{
color: black;
background-color: white;
}
QGroupBox
{
background-color: white;
}
QWidget