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

Merge branch 'pr423'

This commit is contained in:
Tux3 / Mlkj / !Lev.uXFMLA 2014-10-23 18:47:35 +02:00
commit f1601f389f
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
45 changed files with 1918 additions and 131 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

View File

@ -20,7 +20,7 @@
# See the COPYING file for more details.
QT += core gui network xml opengl
QT += core gui network xml opengl sql
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtox
@ -29,7 +29,12 @@ FORMS += \
src/mainwindow.ui \
src/widget/form/settings/generalsettings.ui \
src/widget/form/settings/avsettings.ui \
src/widget/form/settings/identitysettings.ui
src/widget/form/settings/identitysettings.ui \
src/widget/form/settings/privacysettings.ui \
src/widget/form/loadhistorydialog.ui \
src/widget/form/inputpassworddialog.ui \
src/widget/form/setpassworddialog.ui
CONFIG += c++11
TRANSLATIONS = translations/de.ts \
@ -55,28 +60,28 @@ contains(JENKINS,YES) {
# Rules for Windows, Mac OSX, and Linux
win32 {
LIBS += -liphlpapi -L$$PWD/libs/lib -ltoxav -ltoxcore -lvpx -lpthread
LIBS += -liphlpapi -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lopencv_core248 -lopencv_highgui248 -lopencv_imgproc248 -lOpenAL32 -lopus
LIBS += -lz -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32
} else {
macx {
ICON = img/icons/qtox.icns
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui
ICON = img/icons/qtox.icns
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -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/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic
LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic
LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
} else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -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/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
}
}
}
@ -140,6 +145,13 @@ HEADERS += src/widget/form/addfriendform.h \
src/videosource.h \
src/cameraworker.h \
src/widget/videosurface.h \
src/widget/form/loadhistorydialog.h \
src/historykeeper.h \
src/misc/db/genericddinterface.h \
src/misc/db/plaindb.h \
src/misc/db/encrypteddb.h \
src/widget/form/inputpassworddialog.h \
src/widget/form/setpassworddialog.h \
src/widget/form/tabcompleter.h
SOURCES += \
@ -189,5 +201,12 @@ SOURCES += \
src/widget/maskablepixmapwidget.cpp \
src/cameraworker.cpp \
src/widget/videosurface.cpp \
src/widget/form/loadhistorydialog.cpp \
src/historykeeper.cpp \
src/misc/db/genericddinterface.cpp \
src/misc/db/plaindb.cpp \
src/misc/db/encrypteddb.cpp \
src/widget/form/inputpassworddialog.cpp \
src/widget/form/setpassworddialog.cpp \
src/netvideosource.cpp \
src/widget/form/tabcompleter.cpp

View File

@ -19,8 +19,10 @@
#include "misc/cstring.h"
#include "misc/settings.h"
#include "widget/widget.h"
#include "historykeeper.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <ctime>
#include <functional>
@ -51,6 +53,9 @@ Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
videobuf = new uint8_t[videobufsize];
videoBusyness=0;
for (int i = 0; i < ptCounter; i++)
pwsaltedkey[i] = nullptr;
toxTimer = new QTimer(this);
toxTimer->setSingleShot(true);
connect(toxTimer, &QTimer::timeout, this, &Core::process);
@ -113,6 +118,9 @@ Core::~Core()
alcCloseDevice(alOutDev);
if (alInDev)
alcCaptureCloseDevice(alInDev);
clearPassword(Core::ptMain);
clearPassword(Core::ptHistory);
}
Core* Core::getInstance()
@ -1092,28 +1100,104 @@ bool Core::loadConfiguration(QString path)
return true;
}
if (Settings::getInstance().getEncryptTox())
{
while (!isPasswordSet(ptMain))
{
emit blockingGetPassword(tr("Tox datafile decryption password"), ptMain);
if (!isPasswordSet(ptMain))
QMessageBox::warning(nullptr, tr("Password error"), tr("Failed to setup password.\nEmpty password."));
}
}
qint64 fileSize = configurationFile.size();
if (fileSize > 0) {
QByteArray data = configurationFile.readAll();
int error = tox_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size());
if (error < 0)
{
qWarning() << "Core: tox_load failed with error "<<error;
qWarning() << "Core: tox_load failed with error "<< error;
}
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))
do
{
qWarning() << "Core: Couldn't open encrypted save, giving up";
configurationFile.close();
return false;
}
while (!isPasswordSet(ptMain))
{
emit blockingGetPassword(tr("Tox datafile decryption password"), ptMain);
if (!isPasswordSet(ptMain))
QMessageBox::warning(nullptr, tr("Password error"), tr("Failed to setup password.\nEmpty password."));
}
error = tox_encrypted_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size(),
reinterpret_cast<uint8_t *>(barePassword[ptMain].data()), barePassword[ptMain].size());
if (error != 0)
{
QMessageBox msgb;
QPushButton *tryAgain = msgb.addButton(tr("Try Again"), QMessageBox::AcceptRole);
QPushButton *cancel = msgb.addButton(tr("Change profile"), QMessageBox::RejectRole);
QPushButton *wipe = msgb.addButton(tr("Reinit current profile"), QMessageBox::ActionRole);
msgb.setDefaultButton(tryAgain);
msgb.setWindowTitle(tr("Password error"));
msgb.setText(tr("Wrong password has been entered"));
// msgb.setInformativeText(tr(""));
msgb.exec();
if (msgb.clickedButton() == tryAgain)
{
clearPassword(ptMain);
} else if (msgb.clickedButton() == cancel)
{
configurationFile.close();
return false;
} else if (msgb.clickedButton() == wipe)
{
clearPassword(ptMain);
Settings::getInstance().setEncryptTox(false);
error = 0;
}
} else {
Settings::getInstance().setEncryptTox(true);
}
} while (error != 0);
}
}
// tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs())
{
bool error = true;
do
{
while (!isPasswordSet(ptHistory))
{
emit blockingGetPassword(tr("History Log decpytion password"), Core::ptHistory);
if (!isPasswordSet(ptHistory))
QMessageBox::warning(nullptr, tr("Password error"), tr("Failed to setup password.\nEmpty password."));
}
if (!HistoryKeeper::checkPassword())
{
if (QMessageBox::Ok == QMessageBox::warning(nullptr, tr("Encrypted log"),
tr("Your history encrypted with different password\nDo you want to try another password?"),
QMessageBox::Ok | QMessageBox::Cancel))
{
error = true;
clearPassword(ptHistory);
} else {
error = false;
clearPassword(ptHistory);
QMessageBox::warning(nullptr, tr("Loggin"), tr("Due to incorret password logging will be disabled"));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
}
} else {
error = false;
}
} while (error);
}
configurationFile.close();
// set GUI with user and statusmsg
@ -1143,22 +1227,18 @@ void Core::saveConfiguration()
}
QString profile = Settings::getInstance().getCurrentProfile();
//qDebug() << "saveConf read profile: " << profile;
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();
//qDebug() << "saveConf: read sanitized user as " << profile;
Settings::getInstance().setCurrentProfile(profile);
}
QString path = dir + QDir::separator() + profile + TOX_EXT;
QFileInfo info(path);
// if (!info.exists()) // fall back to old school 'data'
// { //path = dir + QDir::separator() + CONFIG_FILE_NAME;
// qDebug() << "Core:" << path << " does not exist";
// }
QString path = directory.filePath(profile + TOX_EXT);
saveConfiguration(path);
}
@ -1180,10 +1260,36 @@ void Core::saveConfiguration(const QString& path)
}
qDebug() << "Core: writing tox_save to " << path;
uint32_t fileSize = tox_size(tox);
uint32_t fileSize;
if (Settings::getInstance().getEncryptTox())
fileSize = tox_encrypted_size(tox);
else
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 (!isPasswordSet(ptMain))
{
// probably zero chance event
QMessageBox::warning(nullptr, tr("NO Password"), tr("Will be saved without encryption!"));
tox_save(tox, data);
} else {
int ret = tox_encrypted_save(tox, data, reinterpret_cast<uint8_t *>(barePassword[ptMain].data()),
barePassword[ptMain].size());
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;
@ -1196,8 +1302,10 @@ void Core::switchConfiguration(const QString& profile)
qDebug() << "Core: creating new Id";
else
qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration();
saveConfiguration();
clearPassword(ptMain);
clearPassword(ptHistory);
toxTimer->stop();
if (tox) {
@ -1214,7 +1322,8 @@ void Core::switchConfiguration(const QString& profile)
else
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().setCurrentProfile(profile);
HistoryKeeper::getInstance()->resetInstance();
start();
}
@ -1494,3 +1603,71 @@ QList<CString> Core::splitMessage(const QString &message)
return splittedMsgs;
}
void Core::setPassword(QString& password, PasswordType passtype)
{
if (password.isEmpty())
{
clearPassword(passtype);
return;
}
if (!pwsaltedkey[passtype])
pwsaltedkey[passtype] = new uint8_t[tox_pass_key_length()];
CString str(password);
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkey[passtype]);
barePassword[passtype].clear();
barePassword[passtype].append(password);
password.clear();
}
void Core::clearPassword(PasswordType passtype)
{
if (pwsaltedkey[passtype])
{
delete[] pwsaltedkey[passtype];
pwsaltedkey[passtype] = nullptr;
barePassword[passtype].clear();
}
}
QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkey[passtype])
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[passtype], encrypted) == -1)
if (tox_pass_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(barePassword[passtype].data()), barePassword[passtype].size(), 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, PasswordType passtype)
{
if (!pwsaltedkey[passtype])
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[passtype], decrypted) != sz)
if (tox_pass_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(barePassword[passtype].data()), barePassword[passtype].size(), decrypted) != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(decrypted), sz);
}
bool Core::isPasswordSet(PasswordType passtype)
{
if (pwsaltedkey[passtype])
return true;
return false;
}

View File

@ -35,6 +35,8 @@ class Core : public QObject
{
Q_OBJECT
public:
enum PasswordType {ptMain = 0, ptHistory, ptCounter};
explicit Core(Camera* cam, QThread* coreThread, QString initialLoadPath);
static Core* getInstance(); ///< Returns the global widget's Core instance
~Core();
@ -66,6 +68,7 @@ public:
void decreaseVideoBusyness();
bool anyActiveCalls();
bool isPasswordSet(PasswordType passtype);
public slots:
void start();
@ -106,10 +109,16 @@ public slots:
void micMuteToggle(int callId);
void setPassword(QString& password, PasswordType passtype);
void clearPassword(PasswordType passtype);
QByteArray encryptData(const QByteArray& data, PasswordType passtype);
QByteArray decryptData(const QByteArray& data, PasswordType passtype);
signals:
void connected();
void disconnected();
void blockingClearContacts();
void blockingGetPassword(QString info, int passtype);
void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(int friendId, const QString& message, bool isAction);
@ -246,6 +255,9 @@ private:
static QList<ToxFile> fileSendQueue, fileRecvQueue;
static ToxCall calls[];
uint8_t* pwsaltedkey[PasswordType::ptCounter]; // use the pw's hash as the "pw"
QByteArray barePassword[PasswordType::ptCounter]; // to be deleted after tox_pass_key_decrypt/tox_pass_key_encrypt fix
static const int videobufsize;
static uint8_t* videobuf;
static int videoBusyness; // Used to know when to drop frames

277
src/historykeeper.cpp Normal file
View File

@ -0,0 +1,277 @@
/*
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 "historykeeper.h"
#include "misc/settings.h"
#include "core.h"
#include <QSqlError>
#include <QFile>
#include <QDir>
#include <QSqlQuery>
#include <QVariant>
#include <QDebug>
#include <QTemporaryFile>
#include "misc/db/plaindb.h"
#include "misc/db/encrypteddb.h"
static HistoryKeeper *historyInstance = nullptr;
HistoryKeeper *HistoryKeeper::getInstance()
{
if (historyInstance == nullptr)
{
QList<QString> initLst;
initLst.push_back(QString("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, ") +
QString("chat_id INTEGER NOT NULL, sender INTERGER NOT NULL, message TEXT NOT NULL);"));
initLst.push_back(QString("CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT UNIQUE NOT NULL);"));
initLst.push_back(QString("CREATE TABLE IF NOT EXISTS chats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, ctype INTEGER NOT NULL);"));
QString path(":memory:");
GenericDdInterface *dbIntf;
if (Settings::getInstance().getEnableLogging())
{
bool encrypted = Settings::getInstance().getEncryptLogs();
if (encrypted)
{
path = getHistoryPath();
dbIntf = new EncryptedDb(path, initLst);
historyInstance = new HistoryKeeper(dbIntf);
return historyInstance;
} else {
path = getHistoryPath();
}
}
dbIntf = new PlainDb(path, initLst);
historyInstance = new HistoryKeeper(dbIntf);
}
return historyInstance;
}
bool HistoryKeeper::checkPassword()
{
if (Settings::getInstance().getEnableLogging())
{
if (Settings::getInstance().getEncryptLogs())
{
QString dbpath = getHistoryPath();
return EncryptedDb::check(dbpath);
} else {
return true;
}
} else {
return true;
}
}
HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
db(db_)
{
/*
DB format
chats:
* name -> id map
id -- auto-incrementing number
name -- chat's name (for user to user conversation it is opposite user public key)
ctype -- chat type, reserved for group chats
alisases:
* user_id -> id map
id -- auto-incrementing number
name -- user's public key
history:
id -- auto-incrementing number
timestamp
chat_id -- current chat ID (resolves from chats table)
sender -- sender's ID (resolves from aliases table)
message
*/
updateChatsID();
updateAliases();
}
HistoryKeeper::~HistoryKeeper()
{
delete db;
}
void HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt)
{
int chat_id = getChatID(chat, ctSingle).first;
int sender_id = getAliasID(sender);
db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message)") +
QString("VALUES (%1, %2, %3, '%4');")
.arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
}
QList<HistoryKeeper::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::ChatType ct, const QString &chat,
const QDateTime &time_from, const QDateTime &time_to)
{
QList<HistMessage> res;
qint64 time64_from = time_from.toMSecsSinceEpoch();
qint64 time64_to = time_to.toMSecsSinceEpoch();
int chat_id = getChatID(chat, ct).first;
QSqlQuery dbAnswer;
if (ct == ctSingle)
{
dbAnswer = db->exec(QString("SELECT timestamp, user_id, message FROM history INNER JOIN aliases ON history.sender = aliases.id ") +
QString("AND timestamp BETWEEN %1 AND %2 AND chat_id = %3;")
.arg(time64_from).arg(time64_to).arg(chat_id));
} else {
// no groupchats yet
}
while (dbAnswer.next())
{
QString sender = dbAnswer.value(1).toString();
QString message = unWrapMessage(dbAnswer.value(2).toString());
qint64 timeInt = dbAnswer.value(0).toLongLong();
QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt);
res.push_back({sender,message,time});
}
return res;
}
QString HistoryKeeper::wrapMessage(const QString &str)
{
QString wrappedMessage(str);
wrappedMessage.replace("'", "''");
return wrappedMessage;
}
QString HistoryKeeper::unWrapMessage(const QString &str)
{
QString unWrappedMessage(str);
unWrappedMessage.replace("''", "'");
return unWrappedMessage;
}
void HistoryKeeper::updateChatsID()
{
auto dbAnswer = db->exec(QString("SELECT * FROM chats;"));
chats.clear();
while (dbAnswer.next())
{
QString name = dbAnswer.value(1).toString();
int id = dbAnswer.value(0).toInt();
ChatType ctype = convertToChatType(dbAnswer.value(2).toInt());
chats[name] = {id, ctype};
}
}
void HistoryKeeper::updateAliases()
{
auto dbAnswer = db->exec(QString("SELECT * FROM aliases;"));
aliases.clear();
while (dbAnswer.next())
{
QString user_id = dbAnswer.value(1).toString();
int id = dbAnswer.value(0).toInt();
aliases[user_id] = id;
}
}
QPair<int, HistoryKeeper::ChatType> HistoryKeeper::getChatID(const QString &id_str, ChatType ct)
{
auto it = chats.find(id_str);
if (it != chats.end())
return it.value();
db->exec(QString("INSERT INTO chats (name, ctype) VALUES ('%1', '%2');").arg(id_str).arg(ct));
updateChatsID();
return getChatID(id_str, ct);
}
int HistoryKeeper::getAliasID(const QString &id_str)
{
auto it = aliases.find(id_str);
if (it != aliases.end())
return it.value();
db->exec(QString("INSERT INTO aliases (user_id) VALUES ('%1');").arg(id_str));
updateAliases();
return getAliasID(id_str);
}
void HistoryKeeper::resetInstance()
{
if (historyInstance == nullptr)
return;
delete historyInstance;
historyInstance = nullptr;
}
void HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt)
{
Q_UNUSED(chat)
Q_UNUSED(message)
Q_UNUSED(sender)
Q_UNUSED(dt)
// no groupchats yet
}
HistoryKeeper::ChatType HistoryKeeper::convertToChatType(int ct)
{
if (ct < 0 || ct > 1)
return ctSingle;
return static_cast<ChatType>(ct);
}
QString HistoryKeeper::getHistoryPath()
{
QDir baseDir(Settings::getInstance().getSettingsDirPath());
QString currentProfile = Settings::getInstance().getCurrentProfile();
if (Settings::getInstance().getEncryptLogs())
return baseDir.filePath(currentProfile + ".qtox_history.encrypted");
else
return baseDir.filePath(currentProfile + ".qtox_history");
}
void HistoryKeeper::renameHistory(QString from, QString to)
{
resetInstance();
QFile fileEnc(QDir(Settings::getInstance().getSettingsDirPath()).filePath(from + ".qtox_history.encrypted"));
if (fileEnc.exists())
fileEnc.rename(QDir(Settings::getInstance().getSettingsDirPath()).filePath(to + ".qtox_history.encrypted"));
QFile filePlain(QDir(Settings::getInstance().getSettingsDirPath()).filePath(from + ".qtox_history"));
if (filePlain.exists())
filePlain.rename(QDir(Settings::getInstance().getSettingsDirPath()).filePath(to + ".qtox_history"));
}

71
src/historykeeper.h Normal file
View File

@ -0,0 +1,71 @@
/*
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 HISTORYKEEPER_H
#define HISTORYKEEPER_H
#include <QMap>
#include <QList>
#include <QDateTime>
class GenericDdInterface;
class HistoryKeeper
{
public:
enum ChatType {ctSingle = 0, ctGroup};
struct HistMessage
{
QString sender;
QString message;
QDateTime timestamp;
};
virtual ~HistoryKeeper();
static HistoryKeeper* getInstance();
static void resetInstance();
static QString getHistoryPath();
static bool checkPassword();
static void renameHistory(QString from, QString to);
void addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt);
void addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt);
QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
private:
HistoryKeeper(GenericDdInterface *db_);
HistoryKeeper(HistoryKeeper &hk) = delete;
HistoryKeeper& operator=(const HistoryKeeper&) = delete;
void updateChatsID();
void updateAliases();
QPair<int, ChatType> getChatID(const QString &id_str, ChatType ct);
int getAliasID(const QString &id_str);
QString wrapMessage(const QString &str);
QString unWrapMessage(const QString &str);
ChatType convertToChatType(int);
GenericDdInterface *db;
QMap<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats;
bool isEncrypted;
};
#endif // HISTORYKEEPER_H

172
src/misc/db/encrypteddb.cpp Normal file
View File

@ -0,0 +1,172 @@
/*
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 "encrypteddb.h"
#include "src/misc/settings.h"
#include "src/core.h"
#include <tox/toxencryptsave.h>
#include <QSqlQuery>
#include <QDebug>
#include <QSqlError>
qint64 EncryptedDb::plainChunkSize = 4096;
qint64 EncryptedDb::encryptedChunkSize = EncryptedDb::plainChunkSize + tox_pass_encryption_extra_length();
EncryptedDb::EncryptedDb(const QString &fname, QList<QString> initList) :
PlainDb(":memory:", initList), encrFile(fname)
{
QByteArray fileContent;
if (pullFileContent())
{
chunkPosition = encrFile.size() / encryptedChunkSize;
encrFile.seek(0);
fileContent = encrFile.readAll();
} else {
qWarning() << "corrupted history log file will be wiped!";
chunkPosition = 0;
}
encrFile.close();
encrFile.open(QIODevice::WriteOnly);
encrFile.write(fileContent);
encrFile.flush();
}
EncryptedDb::~EncryptedDb()
{
encrFile.close(); // Q: what if program is killed without being able to clean up?
// A: cleanup isn't necessary, everything handled int appendToEncrypted(..) function
}
QSqlQuery EncryptedDb::exec(const QString &query)
{
QSqlQuery retQSqlQuery = PlainDb::exec(query);
if (query.startsWith("INSERT", Qt::CaseInsensitive))
appendToEncrypted(query);
return retQSqlQuery;
}
bool EncryptedDb::pullFileContent()
{
encrFile.open(QIODevice::ReadOnly);
QByteArray fileContent;
while (!encrFile.atEnd())
{
QByteArray encrChunk = encrFile.read(encryptedChunkSize);
buffer = Core::getInstance()->decryptData(encrChunk, Core::ptHistory);
if (buffer.size() > 0)
{
fileContent += buffer;
} else {
qWarning() << "Encrypted history log is corrupted: can't decrypt";
buffer = QByteArray();
return false;
}
}
QList<QByteArray> splittedBA = fileContent.split('\n');
QList<QString> sqlCmds;
for (auto ba_line : splittedBA)
{
QString line = QByteArray::fromBase64(ba_line);
if (line.size() == 0)
continue;
bool isGoodLine = false;
if (line.startsWith("CREATE", Qt::CaseInsensitive) || line.startsWith("INSERT", Qt::CaseInsensitive))
{
if (line.endsWith(");"))
{
sqlCmds.append(line);
isGoodLine = true;
}
}
if (!isGoodLine)
{
qWarning() << "Encrypted history log is corrupted: errors in content";
buffer = QByteArray();
return false;
}
}
for (auto line : sqlCmds)
{
QSqlQuery r = PlainDb::exec(line);
}
return true;
}
void EncryptedDb::appendToEncrypted(const QString &sql)
{
QByteArray b64Str;
b64Str.append(sql);
b64Str = b64Str.toBase64(); // much easier to parse strings like this from file
buffer += b64Str + "\n";
while (buffer.size() > plainChunkSize)
{
QByteArray filledChunk = buffer.left(plainChunkSize);
encrFile.seek(chunkPosition * encryptedChunkSize);
QByteArray encr = Core::getInstance()->encryptData(filledChunk, Core::ptHistory);
if (encr.size() > 0)
{
encrFile.write(encr);
}
buffer = buffer.right(buffer.size() - plainChunkSize);
chunkPosition++;
}
encrFile.seek(chunkPosition * encryptedChunkSize);
QByteArray encr = Core::getInstance()->encryptData(buffer, Core::ptHistory);
if (encr.size() > 0)
{
encrFile.write(encr);
}
encrFile.flush();
}
bool EncryptedDb::check(const QString &fname)
{
QFile file(fname);
file.open(QIODevice::ReadOnly);
bool state = true;
if (file.size() > 0)
{
QByteArray encrChunk = file.read(encryptedChunkSize);
QByteArray buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory);
if (buf.size() == 0)
{
state = false;
}
} else {
file.close();
file.open(QIODevice::WriteOnly);
}
file.close();
return state;
}

47
src/misc/db/encrypteddb.h Normal file
View File

@ -0,0 +1,47 @@
/*
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 ENCRYPTEDDB_H
#define ENCRYPTEDDB_H
#include "plaindb.h"
#include <QList>
#include <QFile>
class EncryptedDb : public PlainDb
{
public:
EncryptedDb(const QString& fname, QList<QString> initList);
virtual ~EncryptedDb();
virtual QSqlQuery exec(const QString &query);
static bool check(const QString &fname);
private:
bool pullFileContent();
void appendToEncrypted(const QString &sql);
QFile encrFile;
static qint64 plainChunkSize;
static qint64 encryptedChunkSize;
qint64 chunkPosition;
QByteArray buffer;
};
#endif // ENCRYPTEDDB_H

View File

@ -0,0 +1,21 @@
/*
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 "genericddinterface.h"
GenericDdInterface::~GenericDdInterface()
{
}

View File

@ -0,0 +1,31 @@
/*
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 GENERICDDINTERFACE_H
#define GENERICDDINTERFACE_H
class QSqlQuery;
class QString;
class GenericDdInterface
{
public:
virtual ~GenericDdInterface();
virtual QSqlQuery exec(const QString &query) = 0;
};
#endif // GENERICDDINTERFACE_H

51
src/misc/db/plaindb.cpp Normal file
View File

@ -0,0 +1,51 @@
/*
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 "plaindb.h"
#include <QDebug>
#include <QSqlQuery>
#include <QString>
PlainDb::PlainDb(const QString &db_name, QList<QString> initList)
{
db = new QSqlDatabase();
*db = QSqlDatabase::addDatabase("QSQLITE");
db->setDatabaseName(db_name);
if (!db->open())
{
qWarning() << QString("Can't open file: %1, history will not be saved!").arg(db_name);
db->setDatabaseName(":memory:");
db->open();
}
for (const QString &cmd : initList)
db->exec(cmd);
}
PlainDb::~PlainDb()
{
db->close();
QString dbConName = db->connectionName();
delete db;
QSqlDatabase::removeDatabase(dbConName);
}
QSqlQuery PlainDb::exec(const QString &query)
{
return db->exec(query);
}

36
src/misc/db/plaindb.h Normal file
View File

@ -0,0 +1,36 @@
/*
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 PLAINDB_H
#define PLAINDB_H
#include "genericddinterface.h"
#include <QSqlDatabase>
class PlainDb : public GenericDdInterface
{
public:
PlainDb(const QString &db_name, QList<QString> initList);
virtual ~PlainDb();
virtual QSqlQuery exec(const QString &query);
private:
QSqlDatabase *db;
};
#endif // PLAINDB_H

View File

@ -152,6 +152,9 @@ void Settings::load()
s.beginGroup("Privacy");
typingNotification = s.value("typingNotification", false).toBool();
enableLogging = s.value("enableLogging", false).toBool();
encryptLogs = s.value("encryptLogs", false).toBool();
encryptTox = s.value("encryptTox", false).toBool();
s.endGroup();
s.beginGroup("AutoAccept");
@ -268,6 +271,9 @@ void Settings::save(QString path)
s.beginGroup("Privacy");
s.setValue("typingNotification", typingNotification);
s.setValue("enableLogging", enableLogging);
s.setValue("encryptLogs", encryptLogs);
s.setValue("encryptTox", encryptTox);
s.endGroup();
s.beginGroup("AutoAccept");
@ -506,6 +512,16 @@ 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;

View File

@ -85,6 +85,9 @@ public:
bool getEncryptLogs() const;
void setEncryptLogs(bool newValue);
bool getEncryptTox() const;
void setEncryptTox(bool newValue);
int getAutoAwayTime() const;
void setAutoAwayTime(int newValue);
@ -207,6 +210,7 @@ private:
bool enableLogging;
bool encryptLogs;
bool encryptTox;
int autoAwayTime;

View File

@ -152,6 +152,7 @@ QTextTable *ChatAreaWidget::getMsgTable()
QTextCursor tc = textCursor();
tc.movePosition(QTextCursor::End);
QTextTable *chatTextTable = tc.insertTable(1, 5, *tableFrmt);
return chatTextTable;
@ -167,3 +168,24 @@ void ChatAreaWidget::setNameColWidth(int w)
nameWidth = w;
}
void ChatAreaWidget::clearChatArea()
{
QList<ChatAction*> newMsgs;
for (ChatAction* message : messages)
{
if (message->isInteractive())
{
newMsgs.append(message);
} else {
delete message;
}
}
messages.clear();
this->clear();
for (ChatAction* message : newMsgs)
{
insertMessage(message);
}
}

View File

@ -30,11 +30,15 @@ public:
explicit ChatAreaWidget(QWidget *parent = 0);
virtual ~ChatAreaWidget();
void insertMessage(ChatAction *msgAction);
QList<ChatAction*>& getMesages() {return messages;}
int nameColWidth() {return nameWidth;}
void setNameColWidth(int w);
int getNumberOfMessages();
public slots:
void clearChatArea();
signals:
void onFileTranfertInterract(QString widgetName, QString buttonName);

View File

@ -24,6 +24,8 @@
#include <QDragEnterEvent>
#include <QBitmap>
#include "chatform.h"
#include "src/historykeeper.h"
#include "src/widget/form/loadhistorydialog.h"
#include "src/friend.h"
#include "src/widget/friendwidget.h"
#include "src/filetransferinstance.h"
@ -57,6 +59,8 @@ ChatForm::ChatForm(Friend* chatFriend)
headTextLayout->addStretch();
headTextLayout->setSpacing(0);
menu.addAction(tr("Load History..."), this, SLOT(onLoadHistory()));
connect(Core::getInstance(), &Core::fileSendStarted, this, &ChatForm::startFileSend);
connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered);
connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked);
@ -87,15 +91,18 @@ void ChatForm::onSendTriggered()
if (msg.isEmpty())
return;
QString name = Widget::getInstance()->getUsername();
if (msg.startsWith("/me"))
QDateTime timestamp = QDateTime::currentDateTime();
HistoryKeeper::getInstance()->addChatEntry(f->userId, msg, Core::getInstance()->getSelfId().publicKey, timestamp);
if (msg.startsWith("/me "))
{
msg = msg.right(msg.length() - 4);
addMessage(name, msg, true);
addMessage(name, msg, true, timestamp);
emit sendAction(f->friendId, msg);
}
else
{
addMessage(name, msg, false);
addMessage(name, msg, false, timestamp);
emit sendMessage(f->friendId, msg);
}
msgEdit->clear();
@ -502,7 +509,7 @@ void ChatForm::onFileSendFailed(int FriendId, const QString &fname)
if (FriendId != f->friendId)
return;
addSystemInfoMessage("File: \"" + fname + "\" failed to send.", "red");
addSystemInfoMessage("File: \"" + fname + "\" failed to send.", "red", QDateTime::currentDateTime());
}
void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic)
@ -540,3 +547,61 @@ void ChatForm::onAvatarRemoved(int FriendId)
avatar->setPixmap(QPixmap(":/img/contact_dark.png"), Qt::transparent);
}
void ChatForm::onLoadHistory()
{
LoadHistoryDialog dlg;
if (dlg.exec())
{
QDateTime fromTime = dlg.getFromDate();
QDateTime toTime = QDateTime::currentDateTime();
if (fromTime > toTime)
return;
if (earliestMessage)
{
if (*earliestMessage < fromTime)
return;
if (*earliestMessage < toTime)
{
toTime = *earliestMessage;
toTime = toTime.addMSecs(-1);
}
}
auto msgs = HistoryKeeper::getInstance()->getChatHistory(HistoryKeeper::ctSingle, f->userId, fromTime, toTime);
QString storedPrevName = previousName;
previousName = "";
QList<ChatAction*> historyMessages;
for (const auto &it : msgs)
{
QString name = f->getName();
if (it.sender == Core::getInstance()->getSelfId().publicKey)
name = Core::getInstance()->getUsername();
ChatAction *ca = genMessageActionAction(name, it.message, false, it.timestamp.toLocalTime());
historyMessages.append(ca);
}
previousName = storedPrevName;
for (ChatAction *ca : chatWidget->getMesages())
historyMessages.append(ca);
int savedSliderPos = chatWidget->verticalScrollBar()->maximum() - chatWidget->verticalScrollBar()->value();
chatWidget->getMesages().clear();
chatWidget->clear();
if (earliestMessage != nullptr)
*earliestMessage = fromTime;
for (ChatAction *ca : historyMessages)
chatWidget->insertMessage(ca);
savedSliderPos = chatWidget->verticalScrollBar()->maximum() - savedSliderPos;
chatWidget->verticalScrollBar()->setValue(savedSliderPos);
}
}

View File

@ -69,6 +69,7 @@ private slots:
void onCancelCallTriggered();
void onFileTansBtnClicked(QString widgetName, QString buttonName);
void onFileSendFailed(int FriendId, const QString &fname);
void onLoadHistory();
protected:
// drag & drop

View File

@ -31,7 +31,8 @@
#include "src/widget/maskablepixmapwidget.h"
GenericChatForm::GenericChatForm(QWidget *parent) :
QWidget(parent)
QWidget(parent),
earliestMessage(nullptr)
{
curRow = 0;
@ -116,6 +117,10 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
fileButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
menu.addAction(tr("Save chat log"), this, SLOT(onSaveLogClicked()));
menu.addAction(tr("Clear displayed messages"), this, SLOT(clearChatArea(bool)));
menu.addSeparator();
connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked()));
connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
@ -147,8 +152,6 @@ void GenericChatForm::onChatContextMenuRequested(QPoint pos)
{
QWidget* sender = (QWidget*)QObject::sender();
pos = sender->mapToGlobal(pos);
QMenu menu;
menu.addAction(tr("Save chat log"), this, SLOT(onSaveLogClicked()));
menu.exec(pos);
}
@ -169,29 +172,10 @@ void GenericChatForm::onSaveLogClicked()
file.close();
}
void GenericChatForm::addMessage(QString author, QString message, bool isAction, QDateTime datetime)
void GenericChatForm::addMessage(const QString &author, const QString &message, bool isAction, const QDateTime &datetime)
{
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
bool isMe = (author == Widget::getInstance()->getUsername());
if (!isAction && message.startsWith("/me"))
{ // always render actions regardless of what core thinks
isAction = true;
message = message.right(message.length()-4);
}
if (isAction)
{
chatWidget->insertMessage(new ActionAction (getElidedName(author), message, date, isMe));
previousName = ""; // next msg has a name regardless
return;
}
else if (previousName == author)
chatWidget->insertMessage(new MessageAction("", message, date, isMe));
else
chatWidget->insertMessage(new MessageAction(getElidedName(author), message, date, isMe));
previousName = author;
ChatAction *ca = genMessageActionAction(author, message, isAction, datetime);
chatWidget->insertMessage(ca);
}
void GenericChatForm::addAlertMessage(QString author, QString message, QDateTime datetime)
@ -235,10 +219,8 @@ void GenericChatForm::focusInput()
void GenericChatForm::addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime)
{
previousName = "";
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
chatWidget->insertMessage(new SystemMessageAction(message, type, date));
ChatAction *ca = genSystemInfoAction(message, type, datetime);
chatWidget->insertMessage(ca);
}
QString GenericChatForm::getElidedName(const QString& name)
@ -248,3 +230,58 @@ QString GenericChatForm::getElidedName(const QString& name)
return fm.elidedText(name, Qt::ElideRight, chatWidget->nameColWidth());
}
void GenericChatForm::clearChatArea(bool notinform)
{
chatWidget->clearChatArea();
previousName = "";
if (!notinform)
addSystemInfoMessage(tr("Cleared"), "white", QDateTime::currentDateTime());
if (earliestMessage)
{
delete earliestMessage;
earliestMessage = nullptr;
}
}
ChatAction* GenericChatForm::genMessageActionAction(const QString &author, QString message, bool isAction, const QDateTime &datetime)
{
if (earliestMessage == nullptr)
{
earliestMessage = new QDateTime(datetime);
}
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
bool isMe = (author == Widget::getInstance()->getUsername());
if (!isAction && message.startsWith("/me "))
{ // always render actions regardless of what core thinks
isAction = true;
message = message.right(message.length()-4);
}
if (isAction)
{
previousName = ""; // next msg has a name regardless
return (new ActionAction (getElidedName(author), message, date, isMe));
}
ChatAction *res;
if (previousName == author)
res = (new MessageAction("", message, date, isMe));
else
res = (new MessageAction(getElidedName(author), message, date, isMe));
previousName = author;
return res;
}
ChatAction* GenericChatForm::genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime)
{
previousName = "";
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
return (new SystemMessageAction(message, type, date));
}

View File

@ -20,6 +20,7 @@
#include <QWidget>
#include <QPoint>
#include <QDateTime>
#include <QMenu>
// Spacing in px inserted when the author of the last message changes
#define AUTHOR_CHANGE_SPACING 5 // why the hell is this a thing? surely the different font is enough?
@ -31,6 +32,7 @@ class CroppingLabel;
class ChatTextEdit;
class ChatAreaWidget;
class MaskablePixmapWidget;
class ChatAction;
namespace Ui {
class MainWindow;
@ -44,11 +46,11 @@ public:
virtual void setName(const QString &newName);
virtual void show(Ui::MainWindow &ui);
void addMessage(QString author, QString message, bool isAction = false, QDateTime datetime=QDateTime::currentDateTime());
void addAlertMessage(QString author, QString message, QDateTime datetime=QDateTime::currentDateTime());
void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime=QDateTime::currentDateTime());
void addMessage(const QString &author, const QString &message, bool isAction, const QDateTime &datetime);
void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime);
void addAlertMessage(QString author, QString message, QDateTime datetime);
int getNumberOfMessages();
signals:
void sendMessage(int, QString);
void sendAction(int, QString);
@ -61,10 +63,14 @@ protected slots:
void onSaveLogClicked();
void onEmoteButtonClicked();
void onEmoteInsertRequested(QString str);
void clearChatArea(bool);
protected:
QString getElidedName(const QString& name);
ChatAction* genMessageActionAction(const QString &author, QString message, bool isAction, const QDateTime &datetime);
ChatAction* genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime);
QMenu menu;
CroppingLabel *nameLabel;
MaskablePixmapWidget *avatar;
QWidget *headWidget;
@ -75,6 +81,7 @@ protected:
QString previousName;
ChatAreaWidget *chatWidget;
int curRow;
QDateTime *earliestMessage;
};
#endif // GENERICCHATFORM_H

View File

@ -26,6 +26,7 @@
#include <QPushButton>
#include <QMimeData>
#include <QDragEnterEvent>
#include "src/historykeeper.h"
GroupChatForm::GroupChatForm(Group* chatGroup)
: group(chatGroup)

View File

@ -0,0 +1,38 @@
/*
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 "inputpassworddialog.h"
#include "ui_inputpassworddialog.h"
InputPasswordDialog::InputPasswordDialog(QString title, QWidget *parent) :
QDialog(parent),
ui(new Ui::InputPasswordDialog)
{
ui->setupUi(this);
if (title != QString())
setWindowTitle(title);
}
InputPasswordDialog::~InputPasswordDialog()
{
delete ui;
}
QString InputPasswordDialog::getPassword()
{
return ui->passwordLineEdit->text();
}

View File

@ -0,0 +1,40 @@
/*
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 INPUTPASSWORDDIALOG_H
#define INPUTPASSWORDDIALOG_H
#include <QDialog>
namespace Ui {
class InputPasswordDialog;
}
class InputPasswordDialog : public QDialog
{
Q_OBJECT
public:
explicit InputPasswordDialog(QString title = QString(), QWidget *parent = 0);
~InputPasswordDialog();
QString getPassword();
private:
Ui::InputPasswordDialog *ui;
};
#endif // INPUTPASSWORDDIALOG_H

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InputPasswordDialog</class>
<widget class="QDialog" name="InputPasswordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>123</height>
</rect>
</property>
<property name="windowTitle">
<string>Password Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Input password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>InputPasswordDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>InputPasswordDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,36 @@
/*
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 "loadhistorydialog.h"
#include "ui_loadhistorydialog.h"
LoadHistoryDialog::LoadHistoryDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::LoadHistoryDialog)
{
ui->setupUi(this);
}
LoadHistoryDialog::~LoadHistoryDialog()
{
delete ui;
}
QDateTime LoadHistoryDialog::getFromDate()
{
QDateTime res(ui->fromDate->selectedDate());
return res;
}

View File

@ -0,0 +1,41 @@
/*
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 LOADHISTORYDIALOG_H
#define LOADHISTORYDIALOG_H
#include <QDialog>
#include <QDateTime>
namespace Ui {
class LoadHistoryDialog;
}
class LoadHistoryDialog : public QDialog
{
Q_OBJECT
public:
explicit LoadHistoryDialog(QWidget *parent = 0);
~LoadHistoryDialog();
QDateTime getFromDate();
private:
Ui::LoadHistoryDialog *ui;
};
#endif // LOADHISTORYDIALOG_H

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoadHistoryDialog</class>
<widget class="QDialog" name="LoadHistoryDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>347</width>
<height>264</height>
</rect>
</property>
<property name="windowTitle">
<string>Load History Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="fromLabel">
<property name="text">
<string>Load history from:</string>
</property>
</widget>
</item>
<item>
<widget class="QCalendarWidget" name="fromDate">
<property name="gridVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LoadHistoryDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LoadHistoryDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,47 @@
/*
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 "setpassworddialog.h"
#include "ui_setpassworddialog.h"
#include <QPushButton>
SetPasswordDialog::SetPasswordDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SetPasswordDialog)
{
ui->setupUi(this);
connect(ui->passwordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit()));
connect(ui->repasswordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit()));
}
SetPasswordDialog::~SetPasswordDialog()
{
delete ui;
}
void SetPasswordDialog::onPasswordEdit()
{
if (ui->passwordlineEdit->text() == ui->repasswordlineEdit->text())
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
else
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
QString SetPasswordDialog::getPassword()
{
return ui->passwordlineEdit->text();
}

View File

@ -0,0 +1,42 @@
/*
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 SETPASSWORDDIALOG_H
#define SETPASSWORDDIALOG_H
#include <QDialog>
namespace Ui {
class SetPasswordDialog;
}
class SetPasswordDialog : public QDialog
{
Q_OBJECT
public:
explicit SetPasswordDialog(QWidget *parent = 0);
~SetPasswordDialog();
QString getPassword();
private slots:
void onPasswordEdit();
private:
Ui::SetPasswordDialog *ui;
};
#endif // SETPASSWORDDIALOG_H

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SetPasswordDialog</class>
<widget class="QDialog" name="SetPasswordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>434</width>
<height>175</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Type Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Repeat Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="repasswordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SetPasswordDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SetPasswordDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -21,6 +21,7 @@
#include "src/misc/settings.h"
#include "src/widget/croppinglabel.h"
#include "src/widget/widget.h"
#include "src/historykeeper.h"
#include "src/misc/style.h"
#include <QLabel>
#include <QLineEdit>
@ -133,6 +134,7 @@ void IdentityForm::onRenameClicked()
{
QFile::rename(dir.filePath(cur+Core::TOX_EXT), file);
bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name);
HistoryKeeper::renameHistory(cur, name);
Settings::getInstance().setCurrentProfile(name);
break;
}

View File

@ -15,13 +15,121 @@
*/
#include "privacyform.h"
#include "ui_privacysettings.h"
#include "src/widget/form/settingswidget.h"
#include "src/misc/settings.h"
#include "src/historykeeper.h"
#include "src/core.h"
#include "src/widget/widget.h"
#include "src/widget/form/setpassworddialog.h"
#include <QMessageBox>
PrivacyForm::PrivacyForm() :
GenericForm(tr("Privacy"), QPixmap(":/img/settings/privacy.png"))
{
bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this);
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated()));
connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated()));
connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated()));
}
PrivacyForm::~PrivacyForm()
{
delete bodyUI;
}
void PrivacyForm::onEnableLoggingUpdated()
{
Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked());
bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked());
HistoryKeeper::getInstance()->resetInstance();
}
void PrivacyForm::onTypingNotificationEnabledUpdated()
{
Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked());
}
void PrivacyForm::onEncryptLogsUpdated()
{
bool encrytionState = bodyUI->cbEncryptHistory->isChecked();
if (encrytionState)
{
if (!Core::getInstance()->isPasswordSet(Core::ptHistory))
{
SetPasswordDialog dialog;
if (dialog.exec())
{
QString pswd = dialog.getPassword();
if (pswd.size() == 0)
encrytionState = false;
Core::getInstance()->setPassword(pswd, Core::ptHistory);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptHistory);
}
}
}
Settings::getInstance().setEncryptLogs(encrytionState);
if (encrytionState && !HistoryKeeper::checkPassword())
{
if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted log"),
tr("You already have history log file encrypted with different password\nDo you want to delete old history file?"),
QMessageBox::Ok | QMessageBox::Cancel))
{
// TODO: ask user about reencryption with new password
encrytionState = false;
}
}
Settings::getInstance().setEncryptLogs(encrytionState);
bodyUI->cbEncryptHistory->setChecked(encrytionState);
if (encrytionState)
HistoryKeeper::resetInstance();
if (!Settings::getInstance().getEncryptLogs())
Core::getInstance()->clearPassword(Core::ptHistory);
}
void PrivacyForm::onEncryptToxUpdated()
{
bool encrytionState = bodyUI->cbEncryptTox->isChecked();
if (encrytionState)
{
if (!Core::getInstance()->isPasswordSet(Core::ptMain))
{
SetPasswordDialog dialog;
if (dialog.exec())
{
QString pswd = dialog.getPassword();
if (pswd.size() == 0)
encrytionState = false;
Core::getInstance()->setPassword(pswd, Core::ptMain);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptMain);
}
}
}
bodyUI->cbEncryptTox->setChecked(encrytionState);
Settings::getInstance().setEncryptTox(encrytionState);
if (!Settings::getInstance().getEncryptTox())
Core::getInstance()->clearPassword(Core::ptMain);
}

View File

@ -19,6 +19,10 @@
#include "genericsettings.h"
namespace Ui {
class PrivacySettings;
}
class PrivacyForm : public GenericForm
{
Q_OBJECT
@ -26,8 +30,15 @@ public:
PrivacyForm();
~PrivacyForm();
private slots:
void onEnableLoggingUpdated();
void onTypingNotificationEnabledUpdated();
void onEncryptLogsUpdated();
void onEncryptToxUpdated();
private:
Ui::PrivacySettings* bodyUI;
};
#endif

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PrivacySettings</class>
<widget class="QWidget" name="PrivacySettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="cbTypingNotification">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Typing Notification</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbKeepHistory">
<property name="toolTip">
<string extracomment="History keeping still under developing. Log format changin is possible."/>
</property>
<property name="text">
<string>Keep History (unstable)</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="encryptionGroup">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Encryption</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt Tox datafile</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="text">
<string>Encrypt History</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -22,21 +22,6 @@ ActionAction::ActionAction(const QString &author, QString message, const QString
{
}
void ActionAction::setup(QTextCursor cursor, QTextEdit *)
{
// When this function is called, we're supposed to only update ourselve when needed
// Nobody should ask us to do anything with our content, we're on our own
// Except we never udpate on our own, so we can safely free our resources
(void) cursor;
message.clear();
message.squeeze();
name.clear();
name.squeeze();
date.clear();
date.squeeze();
}
QString ActionAction::getName()
{
return QString("<div class=action>*</div>");

View File

@ -26,7 +26,7 @@ public:
virtual ~ActionAction(){;}
virtual QString getMessage();
virtual QString getName();
virtual void setup(QTextCursor cursor, QTextEdit*) override;
virtual void setup(QTextCursor, QTextEdit*) override {;}
private:
QString message;

View File

@ -33,6 +33,7 @@ public:
virtual QString getName();
virtual QString getMessage() = 0;
virtual QString getDate();
virtual bool isInteractive(){return false;}
protected:
QString toHtmlChars(const QString &str);

View File

@ -74,15 +74,15 @@ void FileTransferAction::updateHtml()
// restore old slider value
edit->verticalScrollBar()->setValue(vSliderVal);
}
// Free our ressources if we'll never need to update again
bool FileTransferAction::isInteractive()
{
if (w->getState() == FileTransferInstance::TransfState::tsCanceled
|| w->getState() == FileTransferInstance::TransfState::tsFinished)
{
name.clear();
name.squeeze();
date.clear();
date.squeeze();
cur = QTextCursor();
return false;
}
return true;
}

View File

@ -27,6 +27,7 @@ public:
virtual ~FileTransferAction();
virtual QString getMessage();
virtual void setup(QTextCursor cursor, QTextEdit* textEdit) override;
virtual bool isInteractive();
private slots:
void updateHtml();

View File

@ -23,21 +23,6 @@ MessageAction::MessageAction(const QString &author, const QString &message, cons
{
}
void MessageAction::setup(QTextCursor cursor, QTextEdit *)
{
// When this function is called, we're supposed to only update ourselve when needed
// Nobody should ask us to do anything with our content, we're on our own
// Except we never udpate on our own, so we can safely free our resources
(void) cursor;
message.clear();
message.squeeze();
name.clear();
name.squeeze();
date.clear();
date.squeeze();
}
QString MessageAction::getMessage(QString div)
{
QString message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message));

View File

@ -26,7 +26,7 @@ public:
virtual ~MessageAction(){;}
virtual QString getMessage();
virtual QString getMessage(QString div);
virtual void setup(QTextCursor cursor, QTextEdit*) override;
virtual void setup(QTextCursor, QTextEdit*) override {;}
protected:
QString message;

View File

@ -27,20 +27,3 @@ QString SystemMessageAction::getMessage()
{
return QString("<table width=100%><tr><td align=center><div class=" + type + ">" + message + "</td><tr></div></table>");
}
void SystemMessageAction::setup(QTextCursor cursor, QTextEdit *)
{
// When this function is called, we're supposed to only update ourselve when needed
// Nobody should ask us to do anything with our content, we're on our own
// Except we never udpate on our own, so we can safely free our resources
(void) cursor;
message.clear();
message.squeeze();
name.clear();
name.squeeze();
date.clear();
date.squeeze();
type.clear();
type.squeeze();
}

View File

@ -24,7 +24,7 @@ class SystemMessageAction : public ChatAction
public:
SystemMessageAction(const QString &message, const QString& type, const QString &date);
virtual ~SystemMessageAction(){;}
virtual void setup(QTextCursor cursor, QTextEdit*) override;
virtual void setup(QTextCursor, QTextEdit*) override {;}
virtual QString getName() {return QString();}
virtual QString getMessage();

View File

@ -45,8 +45,9 @@
#include <QTimer>
#include <QStyleFactory>
#include <QTranslator>
#include "src/historykeeper.h"
#include <tox/tox.h>
#include "form/inputpassworddialog.h"
Widget *Widget::instance{nullptr};
@ -181,6 +182,7 @@ void Widget::init()
qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<Core::PasswordType>("Core::PasswordType");
QString profilePath = detectProfile();
coreThread = new QThread(this);
@ -215,6 +217,7 @@ void Widget::init()
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)));
@ -710,7 +713,13 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool
if (!f)
return;
f->chatForm->addMessage(f->getName(), message, isAction);
QDateTime timestamp = QDateTime::currentDateTime();
f->chatForm->addMessage(f->getName(), message, isAction, timestamp);
if (isAction)
HistoryKeeper::getInstance()->addChatEntry(f->userId, "/me " + message, f->userId, timestamp);
else
HistoryKeeper::getInstance()->addChatEntry(f->userId, message, f->userId, timestamp);
if (activeChatroomWidget != nullptr)
{
@ -831,9 +840,9 @@ void Widget::onGroupMessageReceived(int groupnumber, const QString& message, con
QString name = core->getUsername();
bool targeted = (author != name) && message.contains(name, Qt::CaseInsensitive);
if (targeted)
g->chatForm->addAlertMessage(author, message);
g->chatForm->addAlertMessage(author, message, QDateTime::currentDateTime());
else
g->chatForm->addMessage(author, message);
g->chatForm->addMessage(author, message, false, QDateTime::currentDateTime());
if ((static_cast<GenericChatroomWidget*>(g->widget) != activeChatroomWidget) || isMinimized() || !isActiveWindow())
{
@ -1004,7 +1013,7 @@ void Widget::onMessageSendResult(int friendId, const QString& message, int messa
return;
if (!messageId)
f->chatForm->addSystemInfoMessage("Message failed to send", "red");
f->chatForm->addSystemInfoMessage("Message failed to send", "red", QDateTime::currentDateTime());
}
void Widget::onGroupSendResult(int groupId, const QString& message, int result)
@ -1015,5 +1024,19 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result)
return;
if (result == -1)
g->chatForm->addSystemInfoMessage("Message failed to send", "red");
g->chatForm->addSystemInfoMessage("Message failed to send", "red", QDateTime::currentDateTime());
}
void Widget::getPassword(QString info, int passtype)
{
Core::PasswordType pt = static_cast<Core::PasswordType>(passtype);
InputPasswordDialog dialog(info);
if (dialog.exec())
{
QString pswd = dialog.getPassword();
if (pswd.isEmpty())
core->clearPassword(pt);
else
core->setPassword(pswd, pt);
}
}

View File

@ -98,7 +98,7 @@ private slots:
void setStatusMessage(const QString &statusMessage);
void addFriend(int friendId, const QString& userId);
void addFriendFailed(const QString& userId);
void onFriendStatusChanged(int friendId, Status status);
void onFriendStatusChanged(int friendId, Status status);
void onFriendStatusMessageChanged(int friendId, const QString& message);
void onFriendUsernameChanged(int friendId, const QString& username);
void onChatroomWidgetClicked(GenericChatroomWidget *);
@ -119,6 +119,7 @@ private slots:
void playRingtone();
void onIconClick(QSystemTrayIcon::ActivationReason);
void onUserAway();
void getPassword(QString info, int passtype);
private:
void init();