diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..f8e2ee1c8 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qtox.pro b/qtox.pro index 7de0f7811..3c95c22ba 100644 --- a/qtox.pro +++ b/qtox.pro @@ -69,6 +69,23 @@ contains(ENABLE_SYSTRAY_UNITY_BACKEND, YES) { LIBS += -lgobject-2.0 -lappindicator -lgtk-x11-2.0 } +android { + ANDROID_TOOLCHAIN=/opt/android/toolchain-r9d-17/ + INCLUDEPATH += $$ANDROID_TOOLCHAIN/include/ + LIBS += -L$$ANDROID_TOOLCHAIN/lib + + DISABLE_PLATFORM_EXT=YES + DISABLE_FILTER_AUDIO=YES + + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + contains(ANDROID_TARGET_ARCH,armeabi) { + ANDROID_EXTRA_LIBS = \ + $$ANDROID_TOOLCHAIN/lib/libopenal.so \ + $$ANDROID_TOOLCHAIN/lib/libsodium.so + } +} + + contains(DISABLE_PLATFORM_EXT, YES) { } else { @@ -90,7 +107,7 @@ contains(JENKINS,YES) { # Rules for Windows, Mac OSX, and Linux win32 { RC_FILE = windows/qtox.rc - LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread + LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread LIBS += -L$$PWD/libs/lib -lopencv_core249 -lopencv_highgui249 -lopencv_imgproc249 -lOpenAL32 -lopus LIBS += -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -lws2_32 -liphlpapi -lz @@ -110,31 +127,38 @@ win32 { contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation } contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio } } 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 -ltoxencryptsave -ltoxdns -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 + android { + LIBS += -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns + LIBS += -lopencv_videoio -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc -lopencv_androidcamera + LIBS += -llibjpeg -llibwebp -llibpng -llibtiff -llibjasper -lIlmImf -lopencv_core + LIBS += -lopus -lvpx -lsodium -lopenal } else { - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc - } - - contains(DEFINES, QTOX_PLATFORM_EXT) { - LIBS += -lX11 -lXss - } - - contains(DEFINES, QTOX_FILTER_AUDIO) { + # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { - LIBS += -Wl,-Bstatic -lfilteraudio + target.path = /usr/bin + INSTALLS += target + LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -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 += -lfilteraudio + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -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/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s + contains(DEFINES, QTOX_PLATFORM_EXT) { + LIBS += -lX11 -lXss + } + + contains(DEFINES, QTOX_FILTER_AUDIO) { + contains(STATICPKG, YES) { + LIBS += -Wl,-Bstatic -lfilteraudio + } else { + LIBS += -lfilteraudio + } + } + + contains(JENKINS, YES) { + LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s + } } } } @@ -206,7 +230,10 @@ HEADERS += src/widget/form/addfriendform.h \ src/audio.h \ src/widget/callconfirmwidget.h \ src/widget/systemtrayicon.h \ - src/widget/systemtrayicon_private.h + src/widget/systemtrayicon_private.h \ + src/nexus.h \ + src/widget/gui.h \ + src/widget/androidgui.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -275,7 +302,10 @@ SOURCES += \ src/widget/form/settings/advancedform.cpp \ src/audio.cpp \ src/widget/callconfirmwidget.cpp \ - src/widget/systemtrayicon.cpp + src/widget/systemtrayicon.cpp \ + src/nexus.cpp \ + src/widget/gui.cpp \ + src/widget/androidgui.cpp contains(DEFINES, QTOX_FILTER_AUDIO) { HEADERS += src/audiofilterer.h diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 9c4fcc666..5f7b54f97 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -19,6 +19,7 @@ #include "src/misc/serialize.h" #include "src/misc/settings.h" #include "src/widget/widget.h" +#include "src/widget/gui.h" #include #include #include @@ -490,7 +491,7 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker() QDir updateDir(updateDirStr); if ((updateDir.exists() && QFile(updateDirStr+"flist").exists()) - || Widget::getInstance()->askQuestion(QObject::tr("Update", "The title of a message box"), + || GUI::askQuestion(QObject::tr("Update", "The title of a message box"), QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."), true, false)) { downloadUpdate(); diff --git a/src/core.cpp b/src/core.cpp index 4b82a1d5a..610077fda 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -15,10 +15,11 @@ */ #include "core.h" +#include "nexus.h" #include "misc/cdata.h" #include "misc/cstring.h" #include "misc/settings.h" -#include "widget/widget.h" +#include "widget/gui.h" #include "historykeeper.h" #include "src/audio.h" @@ -106,6 +107,7 @@ Core::~Core() { qDebug() << "Deleting Core"; + saveConfiguration(); toxTimer->stop(); coreThread->exit(0); while (coreThread->isRunning()) @@ -128,7 +130,7 @@ Core::~Core() Core* Core::getInstance() { - return Widget::getInstance()->getCore(); + return Nexus::getCore(); } void Core::make_tox() @@ -223,6 +225,8 @@ void Core::make_tox() void Core::start() { + qDebug() << "Core: Starting up"; + make_tox(); qsrand(time(nullptr)); @@ -255,7 +259,6 @@ void Core::start() { setStatusMessage(tr("Toxing on qTox")); // this also solves the not updating issue setUsername(tr("qTox User")); - QMetaObject::invokeMethod(Widget::getInstance(), "onSettingsClicked"); // update ui with new profile } tox_callback_friend_request(tox, onFriendRequest, this); @@ -1202,8 +1205,7 @@ bool Core::loadConfiguration(QString path) { configurationFile.close(); - QString profile; - QMetaObject::invokeMethod(Widget::getInstance(), "askProfiles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); + QString profile = Settings::getInstance().askProfiles(); if (!profile.isEmpty()) { @@ -1281,7 +1283,7 @@ void Core::switchConfiguration(const QString& profile) saveCurrentInformation(); // part of a hack, see core.h ready = false; - Widget::getInstance()->setEnabledThreadsafe(false); + GUI::setEnabled(false); clearPassword(ptMain); clearPassword(ptHistory); @@ -1301,7 +1303,7 @@ void Core::switchConfiguration(const QString& profile) start(); if (isReady()) - Widget::getInstance()->setEnabledThreadsafe(true); + GUI::setEnabled(true); } void Core::loadFriends() diff --git a/src/coreencryption.cpp b/src/coreencryption.cpp index 00e4d1a52..3f64fe7b3 100644 --- a/src/coreencryption.cpp +++ b/src/coreencryption.cpp @@ -19,7 +19,7 @@ /* was permanently moved here to handle encryption */ #include "core.h" -#include "src/widget/widget.h" +#include "src/widget/gui.h" #include #include #include "src/misc/settings.h" @@ -165,7 +165,7 @@ QByteArray Core::getSaltFromFile(QString filename) bool Core::loadEncryptedSave(QByteArray& data) { if (!Settings::getInstance().getEncryptTox()) - Widget::getInstance()->showWarningMsgBox(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); + GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); int error = -1; QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile())); @@ -190,7 +190,7 @@ bool Core::loadEncryptedSave(QByteArray& data) do { - QString pw = Widget::getInstance()->passwordDialog(tr("Change profile"), dialogtxt); + QString pw = GUI::passwordDialog(tr("Change profile"), dialogtxt); if (pw.isEmpty()) { @@ -216,7 +216,7 @@ void Core::checkEncryptedHistory() QByteArray salt = getSaltFromFile(path); if (exists && salt.size() == 0) { // maybe we should handle this better - Widget::getInstance()->showWarningMsgBox(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!")); + GUI::showWarning(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!")); Settings::getInstance().setEncryptLogs(false); Settings::getInstance().setEnableLogging(false); HistoryKeeper::resetInstance(); @@ -252,7 +252,7 @@ void Core::checkEncryptedHistory() bool error = true; do { - QString pw = Widget::getInstance()->passwordDialog(tr("Disable chat history"), dialogtxt); + QString pw = GUI::passwordDialog(tr("Disable chat history"), dialogtxt); if (pw.isEmpty()) { @@ -295,7 +295,7 @@ void Core::saveConfiguration(const QString& path) else fileSize = tox_size(tox); - if (fileSize > 0 && fileSize <= INT32_MAX) { + if (fileSize > 0 && fileSize <= std::numeric_limits::max()) { uint8_t *data = new uint8_t[fileSize]; if (encrypt) @@ -303,7 +303,7 @@ void Core::saveConfiguration(const QString& path) if (!pwsaltedkeys[ptMain]) { // probably zero chance event - Widget::getInstance()->showWarningMsgBox(tr("NO Password"), tr("Encryption is enabled, but there is no password! Encryption will be disabled.")); + GUI::showWarning(tr("NO Password"), tr("Encryption is enabled, but there is no password! Encryption will be disabled.")); Settings::getInstance().setEncryptTox(false); tox_save(tox, data); } diff --git a/src/friend.cpp b/src/friend.cpp index 856e2a58c..52f675cf5 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -18,7 +18,7 @@ #include "friendlist.h" #include "widget/friendwidget.h" #include "widget/form/chatform.h" -#include "widget/widget.h" +#include "widget/gui.h" #include "src/core.h" #include "src/misc/settings.h" @@ -52,7 +52,7 @@ void Friend::setName(QString name) chatForm->setName(name); if (widget->isActive()) - Widget::getInstance()->setWindowTitle(name); + GUI::setWindowTitle(name); } } @@ -65,7 +65,7 @@ void Friend::setAlias(QString name) chatForm->setName(dispName); if (widget->isActive()) - Widget::getInstance()->setWindowTitle(dispName); + GUI::setWindowTitle(dispName); } void Friend::setStatusMessage(QString message) diff --git a/src/group.cpp b/src/group.cpp index a75d933a2..c54601c44 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -20,7 +20,7 @@ #include "friendlist.h" #include "friend.h" #include "core.h" -#include "widget/widget.h" +#include "widget/gui.h" #include #include @@ -90,7 +90,7 @@ void Group::setName(const QString& name) chatForm->setName(name); if (widget->isActive()) - Widget::getInstance()->setWindowTitle(name); + GUI::setWindowTitle(name); } void Group::regeneratePeerList() diff --git a/src/main.cpp b/src/main.cpp index ad8c23342..43395a9f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ #include "widget/widget.h" #include "misc/settings.h" +#include "src/nexus.h" #include "src/ipc.h" #include "src/widget/toxuri.h" #include "src/widget/toxsave.h" @@ -70,6 +71,7 @@ int main(int argc, char *argv[]) parser.process(a); Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before + if (parser.isSet("p")) { QString profile = parser.value("p"); @@ -121,6 +123,9 @@ int main(int argc, char *argv[]) AutoUpdater::installLocalUpdate(); ///< NORETURN #endif + Nexus::getInstance().start(); + +#ifndef Q_OS_ANDROID // Inter-process communication IPC ipc; ipc.registerEventHandler(&toxURIEventHandler); @@ -175,17 +180,18 @@ int main(int argc, char *argv[]) if (!ipc.isCurrentOwner()) return EXIT_SUCCESS; } +#endif // Run a.setQuitOnLastWindowClosed(false); - Widget* w = Widget::getInstance(); int errorcode = a.exec(); - delete w; #ifdef LOG_TO_FILE delete logFile; logFile = nullptr; #endif + Nexus::destroyInstance(); + return errorcode; } diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 196202865..96b2b2c6e 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -18,6 +18,8 @@ #include "smileypack.h" #include "src/corestructs.h" #include "src/misc/db/plaindb.h" +#include "src/core.h" +#include "src/widget/gui.h" #include #include @@ -65,6 +67,70 @@ void Settings::switchProfile(const QString& profile) resetInstance(); } +QString Settings::detectProfile() +{ + QDir dir(getSettingsDirPath()); + QString path, profile = getCurrentProfile(); + path = dir.filePath(profile + Core::TOX_EXT); + QFile file(path); + if (profile.isEmpty() || !file.exists()) + { + 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.isEmpty()) + return ""; + else + { + switchProfile(profile); + return dir.filePath(profile + Core::TOX_EXT); + } + } + } + else + return path; +} + +QList Settings::searchProfiles() +{ + QList out; + QDir dir(getSettingsDirPath()); + dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); + dir.setNameFilters(QStringList("*.tox")); + for (QFileInfo file : dir.entryInfoList()) + out += file.completeBaseName(); + return out; +} + +QString Settings::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 = GUI::itemInputDialog(nullptr, + 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 + return ""; + else + return profile; +} + + void Settings::load() { if (loaded) diff --git a/src/misc/settings.h b/src/misc/settings.h index 391fe1c58..550b79840 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -33,6 +33,9 @@ public: static Settings& getInstance(); static void resetInstance(); void switchProfile(const QString& profile); + QString detectProfile(); + QList searchProfiles(); + QString askProfiles(); ~Settings() = default; void executeSettingsDialog(QWidget* parent); diff --git a/src/misc/style.cpp b/src/misc/style.cpp index ad6f93c7b..ea1318fd7 100644 --- a/src/misc/style.cpp +++ b/src/misc/style.cpp @@ -16,10 +16,7 @@ #include "style.h" #include "settings.h" - -#include "src/widget/widget.h" -#include "ui_mainwindow.h" -#include "src/widget/genericchatroomwidget.h" +#include "src/widget/gui.h" #include #include @@ -198,11 +195,9 @@ void Style::setThemeColor(QColor color) dict["@themeMediumDark"] = getColor(ThemeMediumDark).name(); dict["@themeMedium"] = getColor(ThemeMedium).name(); dict["@themeLight"] = getColor(ThemeLight).name(); - - applyTheme(); } void Style::applyTheme() { - Widget::getInstance()->reloadTheme(); + GUI::reloadTheme(); } diff --git a/src/nexus.cpp b/src/nexus.cpp new file mode 100644 index 000000000..9c80bcfcd --- /dev/null +++ b/src/nexus.cpp @@ -0,0 +1,142 @@ +#include "nexus.h" +#include "core.h" +#include "misc/settings.h" +#include "video/camera.h" +#include "widget/gui.h" +#include +#include + +#ifdef Q_OS_ANDROID +#include +#else +#include +#endif + +static Nexus* nexus{nullptr}; + +Nexus::Nexus(QObject *parent) : + QObject(parent), + core{nullptr}, + coreThread{nullptr}, + widget{nullptr}, + androidgui{nullptr}, + started{false} +{ +} + +Nexus::~Nexus() +{ + delete core; + delete coreThread; +#ifdef Q_OS_ANDROID + delete androidgui; +#else + delete widget; +#endif +} + +void Nexus::start() +{ + if (started) + return; + qDebug() << "Nexus: Starting up"; + + // Setup the environment + qRegisterMetaType("Status"); + qRegisterMetaType("vpx_image"); + qRegisterMetaType("uint8_t"); + qRegisterMetaType("uint16_t"); + qRegisterMetaType("const int16_t*"); + qRegisterMetaType("int32_t"); + qRegisterMetaType("int64_t"); + qRegisterMetaType("QPixmap"); + qRegisterMetaType("ToxFile"); + qRegisterMetaType("ToxFile::FileDirection"); + qRegisterMetaType("Core::PasswordType"); + + // Create Core + QString profilePath = Settings::getInstance().detectProfile(); + coreThread = new QThread(this); + coreThread->setObjectName("qTox Core"); + core = new Core(Camera::getInstance(), coreThread, profilePath); + core->moveToThread(coreThread); + connect(coreThread, &QThread::started, core, &Core::start); + + // Start GUI +#ifdef Q_OS_ANDROID + androidgui = new AndroidGUI; + androidgui->show(); +#else + widget = Widget::getInstance(); +#endif + GUI::getInstance(); + + // Connections +#ifndef Q_OS_ANDROID + connect(core, &Core::connected, widget, &Widget::onConnected); + connect(core, &Core::disconnected, widget, &Widget::onDisconnected); + connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore); + connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore); + connect(core, &Core::statusSet, widget, &Widget::onStatusSet); + connect(core, &Core::usernameSet, widget, &Widget::setUsername); + connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage); + connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded); + connect(core, &Core::friendAdded, widget, &Widget::addFriend); + connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed); + connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged); + connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged); + connect(core, &Core::friendStatusMessageChanged, widget, &Widget::onFriendStatusMessageChanged); + connect(core, &Core::friendRequestReceived, widget, &Widget::onFriendRequestReceived); + connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived); + connect(core, &Core::receiptRecieved, widget, &Widget::onReceiptRecieved); + connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived); + connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived); + connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChanged); + connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged); + connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated); + connect(core, &Core::avInvite, widget, &Widget::playRingtone); + connect(core, &Core::blockingClearContacts, widget, &Widget::clearContactsList, Qt::BlockingQueuedConnection); + connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged); + + connect(core, SIGNAL(messageSentResult(int,QString,int)), widget, SLOT(onMessageSendResult(int,QString,int))); + connect(core, SIGNAL(groupSentResult(int,QString,int)), widget, SLOT(onGroupSendResult(int,QString,int))); + + connect(widget, &Widget::statusSet, core, &Core::setStatus); + connect(widget, &Widget::friendRequested, core, &Core::requestFriendship); + connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); + connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration); +#endif + + // Start Core + coreThread->start(); + + started = true; +} + +Nexus& Nexus::getInstance() +{ + if (!nexus) + nexus = new Nexus; + return *nexus; +} + +void Nexus::destroyInstance() +{ + delete nexus; + nexus = nullptr; +} + +Core* Nexus::getCore() +{ + return getInstance().core; +} + +AndroidGUI* Nexus::getAndroidGUI() +{ + return getInstance().androidgui; +} + +Widget* Nexus::getDesktopGUI() +{ + return getInstance().widget; +} diff --git a/src/nexus.h b/src/nexus.h new file mode 100644 index 000000000..f198f21cc --- /dev/null +++ b/src/nexus.h @@ -0,0 +1,38 @@ +#ifndef NEXUS_H +#define NEXUS_H + +#include + +class QThread; +class Core; +class Widget; +class AndroidGUI; + +/// This class is in charge of connecting various systems together +/// and forwarding signals appropriately to the right objects +/// It is in charge of starting the GUI and the Core +class Nexus : public QObject +{ + Q_OBJECT +public: + void start(); ///< Will initialise the systems (GUI, Core, ...) + + static Nexus& getInstance(); + static void destroyInstance(); + static Core* getCore(); ///< Will return 0 if not started + static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started + static Widget* getDesktopGUI(); ///< Will return 0 if not started + +private: + explicit Nexus(QObject *parent = 0); + ~Nexus(); + +private: + Core* core; + QThread* coreThread; + Widget* widget; + AndroidGUI* androidgui; + bool started; +}; + +#endif // NEXUS_H diff --git a/src/widget/androidgui.cpp b/src/widget/androidgui.cpp new file mode 100644 index 000000000..7ae41d983 --- /dev/null +++ b/src/widget/androidgui.cpp @@ -0,0 +1,13 @@ +#include "androidgui.h" +#include + +AndroidGUI::AndroidGUI(QWidget *parent) : + QWidget(parent) +{ + l = new QLabel("qTox Android", this); +} + +AndroidGUI::~AndroidGUI() +{ + delete l; +} diff --git a/src/widget/androidgui.h b/src/widget/androidgui.h new file mode 100644 index 000000000..853483ce2 --- /dev/null +++ b/src/widget/androidgui.h @@ -0,0 +1,19 @@ +#ifndef ANDROIDGUI_H +#define ANDROIDGUI_H + +#include + +class QLabel; + +class AndroidGUI : public QWidget +{ + Q_OBJECT +public: + explicit AndroidGUI(QWidget *parent = 0); + ~AndroidGUI(); + +private: + QLabel* l; +}; + +#endif // ANDROIDGUI_H diff --git a/src/widget/callconfirmwidget.cpp b/src/widget/callconfirmwidget.cpp index 03cc7912f..a5663ec1a 100644 --- a/src/widget/callconfirmwidget.cpp +++ b/src/widget/callconfirmwidget.cpp @@ -1,5 +1,6 @@ #include "callconfirmwidget.h" -#include "widget.h" +#include "gui.h" +#include #include #include #include @@ -11,7 +12,7 @@ #include CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) : - QWidget(Widget::getInstance()), anchor(Anchor), + QWidget(GUI::getMainWidget()), anchor(Anchor), rectW{120}, rectH{85}, spikeW{30}, spikeH{15}, roundedFactor{20}, @@ -43,7 +44,7 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) : connect(buttonBox, &QDialogButtonBox::accepted, this, &CallConfirmWidget::accepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &CallConfirmWidget::rejected); - connect(Widget::getInstance(), &Widget::resized, this, &CallConfirmWidget::reposition); + connect(&GUI::getInstance(), &GUI::resized, this, &CallConfirmWidget::reposition); layout->setMargin(12); layout->addSpacing(spikeH); @@ -56,7 +57,7 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) : void CallConfirmWidget::reposition() { - Widget* w = Widget::getInstance(); + QWidget* w = GUI::getMainWidget(); QPoint pos = anchor->mapToGlobal({(anchor->width()-rectW)/2,anchor->height()})-w->mapToGlobal({0,0}); // We don't want the widget to overflow past the right of the screen diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 3c170821e..d3cefe8a7 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -27,6 +27,10 @@ #include #endif +#ifndef ALC_ALL_DEVICES_SPECIFIER +#define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER +#endif + AVForm::AVForm() : GenericForm(tr("Audio/Video"), QPixmap(":/img/settings/av.png")) { diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 333d9a561..4aca50b3b 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -359,4 +359,5 @@ void GeneralForm::onThemeColorChanged(int) int index = bodyUI->themeColorCBox->currentIndex(); Settings::getInstance().setThemeColor(index); Style::setThemeColor(index); + Style::applyTheme(); } diff --git a/src/widget/form/settings/identityform.cpp b/src/widget/form/settings/identityform.cpp index 8d067b606..a856af1b5 100644 --- a/src/widget/form/settings/identityform.cpp +++ b/src/widget/form/settings/identityform.cpp @@ -111,7 +111,7 @@ void IdentityForm::present() toxId->setText(Core::getInstance()->getSelfId().toString()); toxId->setCursorPosition(0); bodyUI->profiles->clear(); - for (QString profile : Widget::searchProfiles()) + for (QString profile : Settings::getInstance().searchProfiles()) bodyUI->profiles->addItem(profile); QString current = Settings::getInstance().getCurrentProfile(); if (current != "") diff --git a/src/widget/gui.cpp b/src/widget/gui.cpp new file mode 100644 index 000000000..d6b8a7dc0 --- /dev/null +++ b/src/widget/gui.cpp @@ -0,0 +1,290 @@ +#include "gui.h" +#include "src/nexus.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_ANDROID +#include "androidgui.h" +#else +#include "widget.h" +#endif + +GUI::GUI(QObject *parent) : + QObject(parent) +{ + assert(QThread::currentThread() == qApp->thread()); + +#ifndef Q_OS_ANDROID + assert(Nexus::getDesktopGUI()); + connect(Nexus::getDesktopGUI(), &Widget::resized, this, &GUI::resized); +#endif +} + +GUI& GUI::getInstance() +{ + static GUI gui; + return gui; +} + +// Implementation of the public clean interface + +void GUI::setEnabled(bool state) +{ + if (QThread::currentThread() == qApp->thread()) + { + getInstance()._setEnabled(state); + } + else + { + QMetaObject::invokeMethod(&getInstance(), "_setEnabled", Qt::BlockingQueuedConnection, + Q_ARG(bool, state)); + } +} + +void GUI::setWindowTitle(const QString& title) +{ + if (QThread::currentThread() == qApp->thread()) + { + getInstance()._setWindowTitle(title); + } + else + { + QMetaObject::invokeMethod(&getInstance(), "_setWindowTitle", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, title)); + } +} + +void GUI::reloadTheme() +{ + if (QThread::currentThread() == qApp->thread()) + { + getInstance()._reloadTheme(); + } + else + { + QMetaObject::invokeMethod(&getInstance(), "_reloadTheme", Qt::BlockingQueuedConnection); + } +} + +void GUI::showWarning(const QString& title, const QString& msg) +{ + if (QThread::currentThread() == qApp->thread()) + { + getInstance()._showWarning(title, msg); + } + else + { + QMetaObject::invokeMethod(&getInstance(), "_showWarning", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); + } +} + +void GUI::showInfo(const QString& title, const QString& msg) +{ + if (QThread::currentThread() == qApp->thread()) + { + getInstance()._showInfo(title, msg); + } + else + { + QMetaObject::invokeMethod(&getInstance(), "_showInfo", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); + } +} + +bool GUI::askQuestion(const QString& title, const QString& msg, + bool defaultAns, bool warning) +{ + if (QThread::currentThread() == qApp->thread()) + { + return getInstance()._askQuestion(title, msg, defaultAns, warning); + } + else + { + bool ret; + QMetaObject::invokeMethod(&getInstance(), "_askQuestion", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ret), + Q_ARG(const QString&, title), Q_ARG(const QString&, msg), + Q_ARG(bool, defaultAns), Q_ARG(bool, warning)); + return ret; + } +} + +QString GUI::itemInputDialog(QWidget * parent, const QString & title, + const QString & label, const QStringList & items, + int current, bool editable, bool * ok, + Qt::WindowFlags flags, + Qt::InputMethodHints hints) +{ + if (QThread::currentThread() == qApp->thread()) + { + return getInstance()._itemInputDialog(parent, title, label, items, current, editable, ok, flags, hints); + } + else + { + QString r; + QMetaObject::invokeMethod(&getInstance(), "_itemInputDialog", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, r), + Q_ARG(QWidget*, parent), Q_ARG(const QString&, title), + Q_ARG(const QString&,label), Q_ARG(const QStringList&, items), + Q_ARG(int, current), Q_ARG(bool, editable), Q_ARG(bool*, ok), + Q_ARG(Qt::WindowFlags, flags), Q_ARG(Qt::InputMethodHints, hints)); + return r; + } +} + +QString GUI::passwordDialog(const QString& cancel, const QString& body) +{ + if (QThread::currentThread() == qApp->thread()) + { + return getInstance()._passwordDialog(cancel, body); + } + else + { + QString r; + QMetaObject::invokeMethod(&getInstance(), "_passwordDialog", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, r), + Q_ARG(const QString&, cancel), Q_ARG(const QString&, body)); + return r; + } +} + +// Private implementations + +void GUI::_setEnabled(bool state) +{ +#ifdef Q_OS_ANDROID + Nexus::getAndroidGUI()->setEnabled(state); +#else + Nexus::getDesktopGUI()->setEnabled(state); +#endif +} + +void GUI::_setWindowTitle(const QString& title) +{ + if (title.isEmpty()) + getMainWidget()->setWindowTitle("qTox"); + else + getMainWidget()->setWindowTitle("qTox - " +title); +} + +void GUI::_reloadTheme() +{ +#ifndef Q_OS_ANDROID + Nexus::getDesktopGUI()->reloadTheme(); +#endif +} + +void GUI::_showWarning(const QString& title, const QString& msg) +{ + QMessageBox::warning(getMainWidget(), title, msg); +} + +void GUI::_showInfo(const QString& title, const QString& msg) +{ + QMessageBox::information(getMainWidget(), title, msg); +} + +bool GUI::_askQuestion(const QString& title, const QString& msg, + bool defaultAns, bool warning) +{ + if (warning) + { + QMessageBox::StandardButton def = QMessageBox::Cancel; + if (defaultAns) + def = QMessageBox::Ok; + return QMessageBox::warning(getMainWidget(), title, msg, QMessageBox::Ok | QMessageBox::Cancel, def) == QMessageBox::Ok; + } + else + { + QMessageBox::StandardButton def = QMessageBox::No; + if (defaultAns) + def = QMessageBox::Yes; + return QMessageBox::question(getMainWidget(), title, msg, QMessageBox::Yes | QMessageBox::No, def) == QMessageBox::Yes; + } +} + +QString GUI::_itemInputDialog(QWidget * parent, const QString & title, + const QString & label, const QStringList & items, + int current, bool editable, bool * ok, + Qt::WindowFlags flags, + Qt::InputMethodHints hints) +{ + return QInputDialog::getItem(parent, title, label, items, current, editable, ok, flags, hints); +} + +QString GUI::_passwordDialog(const QString& cancel, const QString& body) +{ + // we use a hack. It is considered that closing the dialog without explicitly clicking + // disable history is confusing. But we can't distinguish between clicking the cancel + // button and closing the dialog. So instead, we reverse the Ok and Cancel roles, + // so that nothing but explicitly clicking disable history closes the dialog + QString ret; + QInputDialog dialog; + dialog.setWindowTitle(tr("Enter your password")); + dialog.setOkButtonText(cancel); + dialog.setCancelButtonText(tr("Decrypt")); + dialog.setInputMode(QInputDialog::TextInput); + dialog.setTextEchoMode(QLineEdit::Password); + dialog.setLabelText(body); + + // problem with previous hack: the default button is disable history, not decrypt. + // use another hack to reverse the default buttons. + // http://www.qtcentre.org/threads/49924-Change-property-of-QInputDialog-button + QList l = dialog.findChildren(); + if (!l.isEmpty()) + { + QPushButton* ok = l.first()->button(QDialogButtonBox::Ok); + QPushButton* cancel = l.first()->button(QDialogButtonBox::Cancel); + if (ok && cancel) + { + ok->setAutoDefault(false); + ok->setDefault(false); + cancel->setAutoDefault(true); + cancel->setDefault(true); + } + else + qWarning() << "PasswordDialog: Missing button!"; + } + else + qWarning() << "PasswordDialog: No QDialogButtonBox!"; + + // using similar code, set QLabels to wrap + for (auto* label : dialog.findChildren()) + label->setWordWrap(true); + + while (true) + { + int val = dialog.exec(); + if (val == QDialog::Accepted) + return QString(); + else + { + ret = dialog.textValue(); + if (!ret.isEmpty()) + return ret; + } + dialog.setTextValue(""); + dialog.setLabelText(body + "\n\n" + tr("You must enter a non-empty password:")); + } +} + +// Other + +QWidget* GUI::getMainWidget() +{ + QWidget* maingui{nullptr}; +#ifdef Q_OS_ANDROID + maingui = Nexus::getAndroidGUI(); +#else + maingui = Nexus::getDesktopGUI(); +#endif + return maingui; +} diff --git a/src/widget/gui.h b/src/widget/gui.h new file mode 100644 index 000000000..70cc73a7c --- /dev/null +++ b/src/widget/gui.h @@ -0,0 +1,72 @@ +#ifndef GUI_H +#define GUI_H + +#include + +class QWidget; + +/// Abstracts the GUI from the target backend (AndroidGUI, DesktopGUI, ...) +/// All the functions exposed here are thread-safe +/// Prefer calling this class to calling a GUI backend directly +class GUI : public QObject +{ + Q_OBJECT +public: + static GUI& getInstance(); + /// Returns the main QWidget* of the application + static QWidget* getMainWidget(); + /// Will enable or disable the GUI. + /// A disabled GUI can't be interacted with by the user + static void setEnabled(bool state); + /// Change the title of the main window + /// This is usually always visible to the user + static void setWindowTitle(const QString& title); + /// Reloads the application theme and redraw the window + static void reloadTheme(); + /// Show a warning to the user, for example in a message box + static void showWarning(const QString& title, const QString& msg); + /// Show some text to the user, for example in a message box + static void showInfo(const QString& title, const QString& msg); + /// Asks the user a question, for example in a message box. + /// If warning is true, we will use a special warning style. + /// Returns the answer. + static bool askQuestion(const QString& title, const QString& msg, + bool defaultAns = false, bool warning = true); + /// Asks the user to input text and returns the answer. + /// The interface is equivalent to QInputDialog::getItem() + static QString itemInputDialog(QWidget * parent, const QString & title, + const QString & label, const QStringList & items, + int current = 0, bool editable = true, bool * ok = 0, + Qt::WindowFlags flags = 0, + Qt::InputMethodHints hints = Qt::ImhNone); + /// Asks the user to answer a password + /// cancel is the text on the cancel button and body + /// is descriptive text that will be shown to the user + static QString passwordDialog(const QString& cancel, const QString& body); + +signals: + /// Emitted when the GUI is resized on supported platforms + /// Guaranteed to work on desktop platforms + void resized(); + +private: + explicit GUI(QObject *parent = 0); + + // Private implementation, those must be called from the GUI thread +private slots: + void _setEnabled(bool state); + void _setWindowTitle(const QString& title); + void _reloadTheme(); + void _showWarning(const QString& title, const QString& msg); + void _showInfo(const QString& title, const QString& msg); + bool _askQuestion(const QString& title, const QString& msg, + bool defaultAns = false, bool warning = true); + QString _itemInputDialog(QWidget * parent, const QString & title, + const QString & label, const QStringList & items, + int current = 0, bool editable = true, bool * ok = 0, + Qt::WindowFlags flags = 0, + Qt::InputMethodHints inputMethodHints = Qt::ImhNone); + QString _passwordDialog(const QString& cancel, const QString& body); +}; + +#endif // GUI_H diff --git a/src/widget/systemtrayicon.cpp b/src/widget/systemtrayicon.cpp index a31929d3e..e33c0a22c 100644 --- a/src/widget/systemtrayicon.cpp +++ b/src/widget/systemtrayicon.cpp @@ -42,6 +42,11 @@ SystemTrayIcon::SystemTrayIcon() } } +SystemTrayIcon::~SystemTrayIcon() +{ + qDebug() << "Deleting SystemTrayIcon"; +} + QString SystemTrayIcon::extractIconToFile(QIcon icon, QString name) { QString iconPath; diff --git a/src/widget/systemtrayicon.h b/src/widget/systemtrayicon.h index aa5120cd0..540d96285 100644 --- a/src/widget/systemtrayicon.h +++ b/src/widget/systemtrayicon.h @@ -12,6 +12,7 @@ class SystemTrayIcon : public QObject Q_OBJECT public: SystemTrayIcon(); + ~SystemTrayIcon(); void setContextMenu(QMenu* menu); void show(); void hide(); diff --git a/src/widget/toxsave.cpp b/src/widget/toxsave.cpp index 9d14879db..8340b1e95 100644 --- a/src/widget/toxsave.cpp +++ b/src/widget/toxsave.cpp @@ -15,7 +15,7 @@ */ #include "toxsave.h" -#include "widget.h" +#include "gui.h" #include "src/core.h" #include "src/misc/settings.h" #include @@ -53,20 +53,19 @@ void handleToxSave(const QString& path) if (info.suffix() != "tox") { - QMessageBox::warning(Widget::getInstance(), - QObject::tr("Ignoring non-Tox file", "popup title"), - QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text")); + GUI::showWarning(QObject::tr("Ignoring non-Tox file", "popup title"), + QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text")); return; } QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); - if (QFileInfo(profilePath).exists() && !Widget::getInstance()->askQuestion(QObject::tr("Profile already exists", "import confirm title"), + if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"), QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile))) return; QFile::copy(path, profilePath); // no good way to update the ui from here... maybe we need a Widget:refreshUi() function... // such a thing would simplify other code as well I believe - QMessageBox::information(Widget::getInstance(), QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile)); + GUI::showInfo(QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile)); } diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index eb3999504..ce7c42ee1 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -36,6 +36,8 @@ #include "src/audio.h" #include "src/platform/timer.h" #include "systemtrayicon.h" +#include "src/nexus.h" +#include #include #include #include @@ -53,6 +55,12 @@ #include #include +#ifdef Q_OS_ANDROID +#define IS_ON_DESKTOP_GUI 0 +#else +#define IS_ON_DESKTOP_GUI 1 +#endif + void toxActivateEventHandler(const QByteArray& data) { if (data != "$activate") @@ -64,11 +72,11 @@ Widget *Widget::instance{nullptr}; Widget::Widget(QWidget *parent) : QMainWindow(parent), + icon{nullptr}, ui(new Ui::MainWindow), activeChatroomWidget{nullptr}, eventFlag(false), - eventIcon(false), - icon{nullptr} + eventIcon(false) { translator = new QTranslator; setTranslation(); @@ -116,6 +124,9 @@ void Widget::init() this, SLOT(onIconClick(QSystemTrayIcon::ActivationReason))); + icon->show(); + icon->hide(); + if (Settings::getInstance().getShowSystemTray()) { icon->show(); @@ -124,7 +135,6 @@ void Widget::init() } else this->show(); - } else { @@ -193,68 +203,16 @@ void Widget::init() ui->statusButton->setEnabled(false); Style::setThemeColor(Settings::getInstance().getThemeColor()); - Style::applyTheme(); - - qRegisterMetaType("Status"); - qRegisterMetaType("vpx_image"); - qRegisterMetaType("uint8_t"); - qRegisterMetaType("uint16_t"); - qRegisterMetaType("const int16_t*"); - qRegisterMetaType("int32_t"); - qRegisterMetaType("int64_t"); - qRegisterMetaType("QPixmap"); - qRegisterMetaType("ToxFile"); - qRegisterMetaType("ToxFile::FileDirection"); - qRegisterMetaType("Core::PasswordType"); - - QString profilePath = detectProfile(); - coreThread = new QThread(this); - coreThread->setObjectName("qTox Core"); - core = new Core(Camera::getInstance(), coreThread, profilePath); - core->moveToThread(coreThread); - connect(coreThread, &QThread::started, core, &Core::start); + reloadTheme(); filesForm = new FilesForm(); addFriendForm = new AddFriendForm; settingsWidget = new SettingsWidget(); - connect(settingsWidget, &SettingsWidget::setShowSystemTray, this, &Widget::onSetShowSystemTray); - - connect(core, &Core::connected, this, &Widget::onConnected); - connect(core, &Core::disconnected, this, &Widget::onDisconnected); - connect(core, &Core::failedToStart, this, &Widget::onFailedToStartCore); - connect(core, &Core::badProxy, this, &Widget::onBadProxyCore); - connect(core, &Core::statusSet, this, &Widget::onStatusSet); - connect(core, &Core::usernameSet, this, &Widget::setUsername); - connect(core, &Core::statusMessageSet, this, &Widget::setStatusMessage); - connect(core, &Core::selfAvatarChanged, this, &Widget::onSelfAvatarLoaded); + Core* core = Nexus::getCore(); connect(core, SIGNAL(fileDownloadFinished(const QString&)), filesForm, SLOT(onFileDownloadComplete(const QString&))); connect(core, SIGNAL(fileUploadFinished(const QString&)), filesForm, SLOT(onFileUploadComplete(const QString&))); - connect(core, &Core::friendAdded, this, &Widget::addFriend); - connect(core, &Core::failedToAddFriend, this, &Widget::addFriendFailed); - connect(core, &Core::friendUsernameChanged, this, &Widget::onFriendUsernameChanged); - connect(core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged); - connect(core, &Core::friendStatusMessageChanged, this, &Widget::onFriendStatusMessageChanged); - connect(core, &Core::friendRequestReceived, this, &Widget::onFriendRequestReceived); - connect(core, &Core::friendMessageReceived, this, &Widget::onFriendMessageReceived); - connect(core, &Core::receiptRecieved, this, &Widget::onReceiptRecieved); - connect(core, &Core::groupInviteReceived, this, &Widget::onGroupInviteReceived); - connect(core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived); - connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged); - connect(core, &Core::groupTitleChanged, this, &Widget::onGroupTitleChanged); - 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::friendTypingChanged, this, &Widget::onFriendTypingChanged); - - 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))); - - 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(settingsWidget, &SettingsWidget::setShowSystemTray, this, &Widget::onSetShowSystemTray); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(onAddClicked())); connect(ui->groupButton, SIGNAL(clicked()), this, SLOT(onGroupClicked())); connect(ui->transferButton, SIGNAL(clicked()), this, SLOT(onTransferClicked())); @@ -270,8 +228,6 @@ void Widget::init() connect(timer, &QTimer::timeout, this, &Widget::onUserAwayCheck); connect(timer, &QTimer::timeout, this, &Widget::onEventIconTick); - coreThread->start(); - addFriendForm->show(*ui); #if (AUTOUPDATE_ENABLED) @@ -317,10 +273,8 @@ void Widget::updateTrayIcon() Widget::~Widget() { - qDebug() << "Deleting Widget"; - core->saveConfiguration(); + qDebug() << "Widget: Deleting Widget"; AutoUpdater::abortUpdates(); - delete core; icon->hide(); hideMainForms(); delete settingsWidget; @@ -338,6 +292,8 @@ Widget::~Widget() Widget* Widget::getInstance() { + assert(IS_ON_DESKTOP_GUI); // Widget must only be used on Desktop platforms + if (!instance) { instance = new Widget(); @@ -346,11 +302,6 @@ Widget* Widget::getInstance() return instance; } -QThread* Widget::getCoreThread() -{ - return coreThread; -} - void Widget::closeEvent(QCloseEvent *event) { if (Settings::getInstance().getShowSystemTray() && Settings::getInstance().getCloseToTray() == true) @@ -386,72 +337,9 @@ void Widget::resizeEvent(QResizeEvent *event) emit resized(); } -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.isEmpty() || !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.isEmpty()) - return ""; - else - { - Settings::getInstance().switchProfile(profile); - return dir.filePath(profile + Core::TOX_EXT); - } - } - } - 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 - return ""; - else - return profile; -} - QString Widget::getUsername() { - return core->getUsername(); + return Nexus::getCore()->getUsername(); } void Widget::onAvatarClicked() @@ -495,7 +383,7 @@ void Widget::onAvatarClicked() return; } - core->setAvatar(TOX_AVATAR_FORMAT_PNG, bytes); + Nexus::getCore()->setAvatar(TOX_AVATAR_FORMAT_PNG, bytes); } void Widget::onSelfAvatarLoaded(const QPixmap& pic) @@ -591,7 +479,7 @@ void Widget::onAddClicked() void Widget::onGroupClicked() { - core->createGroup(); + Nexus::getCore()->createGroup(); } void Widget::onTransferClicked() @@ -664,7 +552,7 @@ void Widget::hideMainForms() void Widget::onUsernameChanged(const QString& newUsername, const QString& oldUsername) { setUsername(oldUsername); // restore old username until Core tells us to set it - core->setUsername(newUsername); + Nexus::getCore()->setUsername(newUsername); } void Widget::setUsername(const QString& username) @@ -681,7 +569,7 @@ void Widget::onStatusMessageChanged(const QString& newStatusMessage, const QStri { ui->statusLabel->setText(oldStatusMessage); // restore old status message until Core tells us to set it ui->statusLabel->setToolTip(oldStatusMessage); // for overlength messsages - core->setStatusMessage(newStatusMessage); + Nexus::getCore()->setStatusMessage(newStatusMessage); } void Widget::setStatusMessage(const QString &statusMessage) @@ -707,6 +595,7 @@ void Widget::addFriend(int friendId, const QString &userId) if (Settings::getInstance().getEnableLogging()) newfriend->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true); + Core* core = Nexus::getCore(); connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::onCompactChanged); connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); @@ -928,7 +817,7 @@ void Widget::removeFriend(Friend* f, bool fake) onAddClicked(); } FriendList::removeFriend(f->getFriendID(), fake); - core->removeFriend(f->getFriendID(), fake); + Nexus::getCore()->removeFriend(f->getFriendID(), fake); delete f; if (ui->mainHead->layout()->isEmpty()) onAddClicked(); @@ -959,7 +848,7 @@ void Widget::copyFriendIdToClipboard(int friendId) if (f != nullptr) { QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(core->getFriendAddress(f->getFriendID()), QClipboard::Clipboard); + clipboard->setText(Nexus::getCore()->getFriendAddress(f->getFriendID()), QClipboard::Clipboard); } } @@ -967,7 +856,7 @@ void Widget::onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray in { if (type == TOX_GROUPCHAT_TYPE_TEXT || type == TOX_GROUPCHAT_TYPE_AV) { - int groupId = core->joinGroupchat(friendId, type, (uint8_t*)invite.data(), invite.length()); + int groupId = Nexus::getCore()->joinGroupchat(friendId, type, (uint8_t*)invite.data(), invite.length()); if (groupId < 0) { qWarning() << "Widget::onGroupInviteReceived: Unable to accept group invite"; @@ -1014,7 +903,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha g = createGroup(groupnumber); } - QString name = core->getGroupPeerName(groupnumber, peernumber); + QString name = Nexus::getCore()->getGroupPeerName(groupnumber, peernumber); TOX_CHAT_CHANGE change = static_cast(Change); if (change == TOX_CHAT_CHANGE_PEER_ADD) { @@ -1033,7 +922,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha //g->chatForm->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "silver"); } else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed... - g->updatePeer(peernumber,core->getGroupPeerName(groupnumber, peernumber)); + g->updatePeer(peernumber,Nexus::getCore()->getGroupPeerName(groupnumber, peernumber)); } void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const QString& title) @@ -1056,7 +945,7 @@ void Widget::removeGroup(Group* g, bool fake) onAddClicked(); } GroupList::removeGroup(g->getGroupId(), fake); - core->removeGroup(g->getGroupId(), fake); + Nexus::getCore()->removeGroup(g->getGroupId(), fake); delete g; if (ui->mainHead->layout()->isEmpty()) onAddClicked(); @@ -1070,11 +959,6 @@ void Widget::removeGroup(int groupId) removeGroup(GroupList::findGroup(groupId)); } -Core *Widget::getCore() -{ - return core; -} - Group *Widget::createGroup(int groupId) { Group* g = GroupList::findGroup(groupId); @@ -1090,6 +974,7 @@ Group *Widget::createGroup(int groupId) layout->addWidget(newgroup->getGroupWidget()); newgroup->getGroupWidget()->updateStatusLight(); + Core* core = Nexus::getCore(); connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput())); @@ -1174,17 +1059,17 @@ void Widget::onEventIconTick() void Widget::setStatusOnline() { - core->setStatus(Status::Online); + Nexus::getCore()->setStatus(Status::Online); } void Widget::setStatusAway() { - core->setStatus(Status::Away); + Nexus::getCore()->setStatus(Status::Away); } void Widget::setStatusBusy() { - core->setStatus(Status::Busy); + Nexus::getCore()->setStatus(Status::Busy); } void Widget::onMessageSendResult(int friendId, const QString& message, int messageId) diff --git a/src/widget/widget.h b/src/widget/widget.h index 0a8307cbe..f43a301f7 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -54,14 +54,11 @@ public: explicit Widget(QWidget *parent = 0); void setCentralWidget(QWidget *widget, const QString &widgetName); QString getUsername(); - Core* getCore(); - QThread* getCoreThread(); Camera* getCamera(); static Widget* getInstance(); void newMessageAlert(GenericChatroomWidget* chat); bool isFriendWidgetCurActiveWidget(Friend* f); bool getIsWindowMinimized(); - static QList searchProfiles(); void clearContactsList(); void setTranslation(); void updateTrayIcon(); @@ -69,7 +66,6 @@ public: Q_INVOKABLE void setEnabledThreadsafe(bool enabled); Q_INVOKABLE bool askQuestion(const QString& title, const QString& msg, bool defaultAns = false, bool warning = true); Q_INVOKABLE QString passwordDialog(const QString& cancel, const QString& body); - Q_INVOKABLE QString askProfiles(); // hooray for threading hacks ~Widget(); @@ -86,6 +82,29 @@ public slots: void onSettingsClicked(); void setWindowTitle(const QString& title); void forceShow(); + void onConnected(); + void onDisconnected(); + void onStatusSet(Status status); + void onFailedToStartCore(); + void onBadProxyCore(); + void onSelfAvatarLoaded(const QPixmap &pic); + void setUsername(const QString& username); + void setStatusMessage(const QString &statusMessage); + void addFriend(int friendId, const QString& userId); + void addFriendFailed(const QString& userId, const QString& errorInfo = QString()); + void onFriendStatusChanged(int friendId, Status status); + void onFriendStatusMessageChanged(int friendId, const QString& message); + void onFriendUsernameChanged(int friendId, const QString& username); + void onFriendMessageReceived(int friendId, const QString& message, bool isAction); + void onFriendRequestReceived(const QString& userId, const QString& message); + void onReceiptRecieved(int friendId, int receipt); + void onEmptyGroupCreated(int groupId); + void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); + void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); + void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); + void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title); + void playRingtone(); + void onFriendTypingChanged(int friendId, bool isTyping); signals: void friendRequestAccepted(const QString& userId); @@ -98,34 +117,13 @@ signals: void resized(); private slots: - void onConnected(); - void onDisconnected(); - void onStatusSet(Status status); void onAddClicked(); void onGroupClicked(); void onTransferClicked(); - void onFailedToStartCore(); - void onBadProxyCore(); void onAvatarClicked(); - void onSelfAvatarLoaded(const QPixmap &pic); void onUsernameChanged(const QString& newUsername, const QString& oldUsername); void onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage); - void setUsername(const QString& username); - void setStatusMessage(const QString &statusMessage); - void addFriend(int friendId, const QString& userId); - void addFriendFailed(const QString& userId, const QString& errorInfo = QString()); - void onFriendStatusChanged(int friendId, Status status); - void onFriendStatusMessageChanged(int friendId, const QString& message); - void onFriendUsernameChanged(int friendId, const QString& username); void onChatroomWidgetClicked(GenericChatroomWidget *); - void onFriendMessageReceived(int friendId, const QString& message, bool isAction); - void onFriendRequestReceived(const QString& userId, const QString& message); - void onReceiptRecieved(int friendId, int receipt); - void onEmptyGroupCreated(int groupId); - void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); - void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); - void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); - void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title); void removeFriend(int friendId); void copyFriendIdToClipboard(int friendId); void removeGroup(int groupId); @@ -134,11 +132,9 @@ private slots: void setStatusBusy(); void onMessageSendResult(int friendId, const QString& message, int messageId); void onGroupSendResult(int groupId, const QString& message, int result); - void playRingtone(); void onIconClick(QSystemTrayIcon::ActivationReason); void onUserAwayCheck(); void onEventIconTick(); - void onFriendTypingChanged(int friendId, bool isTyping); void onSetShowSystemTray(bool newValue); void onSplitterMoved(int pos, int index); @@ -151,7 +147,6 @@ private: void removeGroup(Group* g, bool fake = false); void saveWindowGeometry(); void saveSplitterGeometry(); - QString detectProfile(); SystemTrayIcon *icon; QMenu *trayMenu; QAction *statusOnline, @@ -162,8 +157,6 @@ private: Ui::MainWindow *ui; QSplitter *centralLayout; QPoint dragPosition; - Core* core; - QThread* coreThread; AddFriendForm* addFriendForm; SettingsWidget* settingsWidget; FilesForm* filesForm;