diff --git a/qTox.desktop b/qTox.desktop index afd74b440..298a11771 100644 --- a/qTox.desktop +++ b/qTox.desktop @@ -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; diff --git a/src/core.cpp b/src/core.cpp index dd8445dee..325028b03 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -39,12 +39,15 @@ #include const QString Core::CONFIG_FILE_NAME = "data"; +const QString Core::TOX_EXT = ".tox"; QList Core::fileSendQueue; QList 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; @@ -117,12 +120,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 +183,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 +207,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 +284,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 +585,7 @@ void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive uint64_t resumePos = *reinterpret_cast(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 +638,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 +929,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 +941,8 @@ void Core::removeFriend(int friendId) void Core::removeGroup(int groupId) { + if (!tox) + return; tox_del_groupchat(tox, groupId); } @@ -949,8 +964,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 +996,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 +1060,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 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"; @@ -1093,32 +1129,51 @@ 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(); + //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"; +// } + + 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 = tox_size(tox); if (fileSize > 0 && fileSize <= INT32_MAX) { uint8_t *data = new uint8_t[fileSize]; @@ -1129,6 +1184,34 @@ void Core::saveConfiguration() } } +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(); + + 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); diff --git a/src/core.h b/src/core.h index 9c67da6f6..189f4060a 100644 --- a/src/core.h +++ b/src/core.h @@ -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); @@ -97,6 +107,7 @@ public slots: signals: void connected(); void disconnected(); + void blockingClearContacts(); void friendRequestReceived(const QString& userId, const QString& message); void friendMessageReceived(int friendId, const QString& message, bool isAction); @@ -209,7 +220,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 +237,14 @@ 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 dhtServerList; int dhtServerId; static QList fileSendQueue, fileRecvQueue; static ToxCall calls[]; - static const QString CONFIG_FILE_NAME; static const int videobufsize; static uint8_t* videobuf; static int videoBusyness; // Used to know when to drop frames diff --git a/src/coreav.cpp b/src/coreav.cpp index b7aae0103..516d08833 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -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); diff --git a/src/filetransferinstance.cpp b/src/filetransferinstance.cpp index f3301e58a..158ddd65c 100644 --- a/src/filetransferinstance.cpp +++ b/src/filetransferinstance.cpp @@ -24,7 +24,7 @@ #include #include -#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 = ""; QString content; - QString progrBar = ""; + QString progrBar = ""; content = "

" + filenameElided + "

"; content += ""; @@ -421,11 +423,14 @@ QString FileTransferInstance::wrapIntoForm(const QString& content, const QString res += "
" + imgLeftA + "
" + imgLeftB + "
\n"; res += "\n"; res += insertMiniature(type); - res += "\n"; + res += "\n"; res += "\n"; diff --git a/src/filetransferinstance.h b/src/filetransferinstance.h index f32c10f92..a4f6524ee 100644 --- a/src/filetransferinstance.h +++ b/src/filetransferinstance.h @@ -78,6 +78,7 @@ private: long long lastBytesSent, totalBytes; int fileNum; int friendId; + int contentPrefWidth; QString savePath; ToxFile::FileDirection direction; QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet; diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 77a2deee1..47fccab47 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -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"); @@ -220,6 +222,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"); @@ -422,6 +426,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; @@ -442,6 +456,18 @@ void Settings::setEncryptLogs(bool newValue) encryptLogs = 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; diff --git a/src/misc/settings.h b/src/misc/settings.h index f7b574e6f..9ff506ba3 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -55,6 +55,9 @@ public: QString getStyle() const; void setStyle(const QString& newValue); + QString getCurrentProfile() const; + void setCurrentProfile(QString profile); + bool getUseTranslations() const; void setUseTranslations(bool newValue); @@ -76,6 +79,9 @@ public: bool getEncryptLogs() const; void setEncryptLogs(bool newValue); + int getAutoAwayTime() const; + void setAutoAwayTime(int newValue); + QPixmap getSavedAvatar(const QString& ownerId); void saveAvatar(QPixmap& pic, const QString& ownerId); @@ -179,9 +185,13 @@ private: QString proxyAddr; int proxyPort; + QString currentProfile; + bool enableLogging; bool encryptLogs; + int autoAwayTime; + QHash widgetSettings; // GUI diff --git a/src/misc/smileypack.cpp b/src/misc/smileypack.cpp index 57a754c76..f3230c3ea 100644 --- a/src/misc/smileypack.cpp +++ b/src/misc/smileypack.cpp @@ -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); } diff --git a/src/widget/camera.cpp b/src/widget/camera.cpp index 5bf07e6df..b652d7033 100644 --- a/src/widget/camera.cpp +++ b/src/widget/camera.cpp @@ -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); diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index bca44effc..b47d0d45a 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -23,11 +23,13 @@ #include #include -GeneralForm::GeneralForm() : +GeneralForm::GeneralForm(SettingsWidget *myParent) : GenericForm(tr("General Settings"), QPixmap(":/img/settings/general.png")) { bodyUI = new Ui::GeneralSettings; bodyUI->setupUi(this); + + parent = myParent; bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); bodyUI->cbUseTranslations->setChecked(Settings::getInstance().getUseTranslations()); @@ -39,9 +41,11 @@ GeneralForm::GeneralForm() : 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 @@ -98,12 +102,14 @@ void GeneralForm::onStyleSelected(QString style) { Settings::getInstance().setStyle(style); this->setStyle(QStyleFactory::create(style)); + parent->setStyle(style); } void GeneralForm::onSmileyBrowserIndexChanged(int index) { QString filename = bodyUI->smileyPackBrowser->itemData(index).toString(); Settings::getInstance().setSmileyPack(filename); + reloadSmiles(); } void GeneralForm::onUDPUpdated() @@ -134,3 +140,26 @@ void GeneralForm::onUseProxyUpdated() bodyUI->proxyPort->setEnabled(state); Settings::getInstance().setUseProxy(state); } + +void GeneralForm::reloadSmiles() +{ + QList 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]); +} diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index 19fb2f62e..4f2458590 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -18,8 +18,6 @@ #define GENERALFORM_H #include "genericsettings.h" -#include -#include namespace Ui { class GeneralSettings; @@ -29,7 +27,7 @@ class GeneralForm : public GenericForm { Q_OBJECT public: - GeneralForm(); + GeneralForm(SettingsWidget *parent); ~GeneralForm(); private slots: @@ -46,6 +44,8 @@ private slots: private: Ui::GeneralSettings *bodyUI; + void reloadSmiles(); + SettingsWidget *parent; }; #endif diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index e4542a7d6..d0d31b622 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -72,6 +72,60 @@ + + + + + + :) + + + + + + + + + + ;) + + + + + + + + + + :p + + + + + + + + + + :O + + + + + + + + + + :'( + + + + + + + + diff --git a/src/widget/form/settings/identityform.cpp b/src/widget/form/settings/identityform.cpp index b8369945e..d61a9fb18 100644 --- a/src/widget/form/settings/identityform.cpp +++ b/src/widget/form/settings/identityform.cpp @@ -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 #include #include #include +#include +#include +#include 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,73 @@ 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); + QString profile = info.completeBaseName(); + QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); + QFile::copy(path, profilePath); + bodyUI->profiles->addItem(profile); + Core::getInstance()->switchConfiguration(profile); +} diff --git a/src/widget/form/settings/identityform.h b/src/widget/form/settings/identityform.h index abbbf3a2c..fa3759903 100644 --- a/src/widget/form/settings/identityform.h +++ b/src/widget/form/settings/identityform.h @@ -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; diff --git a/src/widget/form/settings/identitysettings.ui b/src/widget/form/settings/identitysettings.ui index 67ee1a1ac..437495795 100644 --- a/src/widget/form/settings/identitysettings.ui +++ b/src/widget/form/settings/identitysettings.ui @@ -59,6 +59,71 @@ + + + + Profiles + + + + + + + + Available profiles: + + + + + + + + + + + + + + Load + + + + + + + Rename + + + + + + + Export + + + + + + + Delete + + + This is useful to remain safe on public computers + + + + + + + + + Import a profile + + + + + + diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp index b5f33a3bf..8bb378c79 100644 --- a/src/widget/form/settingswidget.cpp +++ b/src/widget/form/settingswidget.cpp @@ -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::setStyle(QString style) +{ + body->setStyle(QStyleFactory::create(style)); + head->setStyle(QStyleFactory::create(style)); +} + void SettingsWidget::show(Ui::MainWindow& ui) { ui.mainContent->layout()->addWidget(body); diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h index 6dca68b82..03d3646c2 100644 --- a/src/widget/form/settingswidget.h +++ b/src/widget/form/settingswidget.h @@ -19,6 +19,8 @@ #include #include +#include + class Camera; class GenericForm; class GeneralForm; @@ -40,6 +42,7 @@ public: void show(Ui::MainWindow &ui); IdentityForm *getIdentityForm() {return ifrm;} + void setStyle(QString style); private slots: void onTabChanged(int); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 2ac27ab1c..af4aca88b 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include @@ -111,11 +113,16 @@ Widget::Widget(QWidget *parent) ui->statusButton->setProperty("status", "offline"); Style::repolish(ui->statusButton); - settingsWidget = new SettingsWidget(); + settingsWidget = new SettingsWidget(this); // 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"); qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); @@ -126,8 +133,9 @@ Widget::Widget(QWidget *parent) qRegisterMetaType("ToxFile"); qRegisterMetaType("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); @@ -154,6 +162,7 @@ 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, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int))); connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(int,QString,int))); @@ -161,6 +170,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())); @@ -175,6 +185,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(); @@ -221,6 +232,69 @@ 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 Widget::searchProfiles() +{ + QList 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 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; +} + QString Widget::getUsername() { return core->getUsername(); @@ -575,19 +649,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(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); @@ -651,19 +737,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(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; @@ -709,18 +799,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); diff --git a/src/widget/widget.h b/src/widget/widget.h index 45953a206..4f50a133c 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -40,6 +40,7 @@ class Core; class Camera; class FriendListWidget; class MaskablePixmapWidget; +class QTimer; class Widget : public QMainWindow { @@ -56,6 +57,8 @@ public: void newMessageAlert(); bool isFriendWidgetCurActiveWidget(Friend* f); bool getIsWindowMinimized(); + static QList searchProfiles(); + void clearContactsList(); ~Widget(); virtual void closeEvent(QCloseEvent *event); @@ -67,6 +70,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 +110,17 @@ private slots: void onGroupSendResult(int groupId, const QString& message, int result); void playRingtone(); void onIconClick(); + void onUserAway(); 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 +134,8 @@ private: FriendListWidget* contactListWidget; MaskablePixmapWidget* profilePicture; bool notify(QObject *receiver, QEvent *event); + bool autoAwayActive = false; + QTimer* idleTimer; }; #endif // WIDGET_H diff --git a/translations/it.qm b/translations/it.qm index ace34002b..631dfcb58 100644 Binary files a/translations/it.qm and b/translations/it.qm differ diff --git a/translations/it.ts b/translations/it.ts index 114976d15..8b927746c 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -4,116 +4,144 @@ AVForm - + Audio/Video settings Impostazioni Audio/Video - - - Hide video preview - On a button - Ferma webcam - - - - Show video preview - On a button - Prova webcam - AVSettings - + Form Form - + + Volume Settings (Stubs) + Impostazioni Volume (Stub) + + + + Playback + Altoparlanti + + + + Microphone + Microfono + + + Video settings Impostazioni Video - - Show video preview - Prova webcam + + Modes + Modalità + + + + Hue + Colore + + + + Brightness + Luminoistà + + + + Saturation + Saturazione + + + + Contrast + Contrasto + + + + Preview + Anteprima AddFriendForm - + Add Friends Aggiungi Contatto - + Tox ID Tox ID of the person you're sending a friend request to Tox ID - + Message The message you send in friend requests Messaggio - + Send friend request Invia richiesta d'amicizia - + Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Permettimi di aggiungerti alla mia lista contatti - + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to Inserisci un Tox ID valido - + You can't add yourself as a friend! When trying to add your own Tox ID as friend Non puoi aggiungere te stesso come contatto! - + This address does not exist The DNS gives the Tox ID associated to toxme.se addresses Questo indirizzo non esiste - + Error while looking up DNS The DNS gives the Tox ID associated to toxme.se addresses Errore nel consultare il server DNS - + Unexpected number of text records Error with the DNS Numero inaspettato di text-records - + Unexpected number of values in text record Error with the DNS Numero inaspettato di valori nel text-record - + The DNS lookup does not contain any Tox ID Error with the DNS La risposta del server DNS non contiene nessun Tox ID - - + + The DNS lookup does not contain a valid Tox ID Error with the DNS La risposta del server DNS non contiene un Tox ID valido @@ -122,7 +150,7 @@ ChatForm - + Send a file Invia un file @@ -130,12 +158,12 @@ Core - + Encrypted profile Profilo criptato - + Your tox profile seems to be encrypted, qTox can't open it Do you want to erase this profile ? Il tuo profilo Tox sembra essere criptato, qTox non può aprirlo\nVuoi eliminare questo profilo? @@ -144,19 +172,19 @@ Do you want to erase this profile ? FileTransferInstance - + Save a file Title of the file saving dialog Salva file - + Location not writable Title of permissions popup Errore - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Non hai sufficienti permessi per scrivere in questa locazione. Scegli un'altra posizione, o annulla il salvataggio. @@ -165,18 +193,18 @@ Do you want to erase this profile ? FilesForm - + Transfered Files "Headline" of the window Files Trasferiti - + Downloads Ricevuti - + Uploads Inviati @@ -184,34 +212,34 @@ Do you want to erase this profile ? FriendRequestDialog - + Friend request Title of the window to aceept/deny a friend request Richiesta d'amicizia - + Someone wants to make friends with you Qualcuno vuole chattare con te - + User ID: ID Utente: - + Friend request message: Messaggio della richiesta d'amicizia: - + Accept Accept a friend request Accetta - + Reject Reject a friend request Rifiuta @@ -220,19 +248,19 @@ Do you want to erase this profile ? FriendWidget - + Copy friend ID Menu to copy the Tox ID of that friend Copia Tox ID del contatto - + Invite in group Menu to invite a friend in a groupchat Invita nel gruppo - + Remove friend Menu to remove the friend from our friendlist Rimuovi contatto @@ -241,7 +269,7 @@ Do you want to erase this profile ? GeneralForm - + General Settings Impostazioni Generali @@ -249,79 +277,84 @@ Do you want to erase this profile ? GeneralSettings - + Form Form - + General Settings Impostazioni Generali - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) - + Use translations Text on a checkbox to enable translations Abilita traduzioni - + Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Slava le impostazioni nella directory di lavoro corrente, invece della directory di default - + Make Tox portable Rendi qTox portabile - + + Start in tray + Avvia minimizzato + + + Theme Impostazioni Tema - + Smiley Pack Text on smiley pack label Emoticons - + Use proxy (SOCKS5) Usa proxy (SOCKS5) - + Address Text on proxy addr label IP - + Port Text on proxy port label Porta - + Disable UDP (not recommended) Text on checkbox to disable UDP Disabilita connessioni UDP (non raccomandato) - + This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. force tcp checkbox tooltip Questo permette di usare qTox con Tor; tuttavia aggiunge carico alla rete Tox, quindi usalo solo se necessario. @@ -330,8 +363,8 @@ Do you want to erase this profile ? GenericChatForm - - + + Save chat log Salva il log della chat @@ -339,13 +372,13 @@ Do you want to erase this profile ? GroupChatForm - + %1 users in chat Number of users in chat %1 utenti in chat - + %1 users in chat %1 utenti in chat @@ -353,19 +386,19 @@ Do you want to erase this profile ? GroupWidget - - + + %1 users in chat %1 utenti in chat - - + + 0 users in chat 0 utenti in chat - + Quit group Menu to quit a groupchat Esci dal gruppo @@ -374,88 +407,188 @@ Do you want to erase this profile ? IdentityForm - + Your identity Il tuo profilo + + + Rename "%1" + renaming a profile + Rinomina "%1" + + + + Export profile + save dialog title + Esporta profilo + + + + Tox save file (*.tox) + save dialog filter + Tox save file (*.tox) + + + + Profile currently loaded + current profile deletion warning title + Profilo attualmente in uso + + + + This profile is currently in use. Please load a different profile before deleting this one. + current profile deletion warning text + Questo profilo è attualmente in uso. Per favore carica un profilo differente prima di eliminare questo. + + + + Deletion imminent! + deletion confirmation title + Eliminazione imminente! + + + + Are you sure you want to delete this profile? + deletion confirmation text + Sei sicuro di voler eliminare questo profilo? + + + + Import profile + import dialog title + Importa profilo + + + + Tox save file (*.tox) + import dialog filter + Tox save file (*.tox) + IdentitySettings - + Form Form - + Public Information Informazioni Pubbliche - + Name Nome - + Status Stato - + Tox ID Tox ID - + Your Tox ID (click to copy) (clicca qui per copiare) + + + Profiles + Profili + + + + Available profiles: + Profili disponibili: + + + + Load + load profile button + Carica + + + + Rename + rename profile button + Rinomina + + + + Export + export profile button + Esporta + + + + Delete + delete profile button + Elimina + + + + This is useful to remain safe on public computers + delete profile button tooltip + Utile per preservare la tua sicurezza su computer pubblici + + + + Import a profile + import profile button + Importa un profilo + MainWindow - + qTox qTox - + Your name qTox User - + Your status Toxing on qTox - + Add friends Aggiungi contatto - + Create a group chat Crea un gruppo - + View completed file transfers Visualizza i trasferimenti completati - + Change your settings Cambia le impostazioni - + Close Chiudi - + Ctrl+Q Ctrl+Q @@ -463,80 +596,81 @@ Do you want to erase this profile ? PrivacyForm - + Privacy settings Impostazioni privacy - - SelfCamView - - - Tox video test - Title of the window to test the video/webcam - qTox video test - - Widget - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + + Choose a profile + Scegli un profilo + + + + Please choose which identity to use + Per favore scegli quale identità usare + + + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> diff --git a/ui/chatArea/innerStyle.css b/ui/chatArea/innerStyle.css index 823c8dfbe..b5c537362 100644 --- a/ui/chatArea/innerStyle.css +++ b/ui/chatArea/innerStyle.css @@ -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;
\n"; + res += "\n"; res += "
"; res += content; res += "
\n"; res += "
\n"; + res += "
\n"; + res += "
\n"; res += "
" + imgAstr + "
" + imgBstr + "
\n"; res += "