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 f6fa4463c..610077fda 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -19,7 +19,7 @@ #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" @@ -259,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); @@ -1284,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); @@ -1304,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 69319f830..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()) { @@ -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/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 index 91b65530e..f8a6c1133 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -2,6 +2,7 @@ #include "core.h" #include "misc/settings.h" #include "video/camera.h" +#include "widget/gui.h" #include #include @@ -66,6 +67,7 @@ void Nexus::start() #else widget = Widget::getInstance(); #endif + GUI::getInstance(); // Connections #ifndef Q_OS_ANDROID @@ -119,3 +121,13 @@ 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 index f8b5dd4b5..7c84bb1bf 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -19,6 +19,8 @@ public: static Nexus& getInstance(); 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); 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/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/gui.cpp b/src/widget/gui.cpp index 76f77e8db..d6b8a7dc0 100644 --- a/src/widget/gui.cpp +++ b/src/widget/gui.cpp @@ -1,11 +1,30 @@ #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() @@ -14,6 +33,90 @@ GUI& GUI::getInstance() 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, @@ -37,6 +140,77 @@ QString GUI::itemInputDialog(QWidget * parent, const QString & title, } } +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, @@ -45,3 +219,72 @@ QString GUI::_itemInputDialog(QWidget * parent, const QString & title, { 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 index 7a7f70339..70cc73a7c 100644 --- a/src/widget/gui.h +++ b/src/widget/gui.h @@ -13,21 +13,60 @@ 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/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 3a5d5ee5c..445a9855b 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -117,6 +117,9 @@ void Widget::init() this, SLOT(onIconClick(QSystemTrayIcon::ActivationReason))); + icon->show(); + icon->hide(); + if (Settings::getInstance().getShowSystemTray()) { icon->show(); @@ -125,7 +128,6 @@ void Widget::init() } else this->show(); - } else { @@ -194,7 +196,7 @@ void Widget::init() ui->statusButton->setEnabled(false); Style::setThemeColor(Settings::getInstance().getThemeColor()); - Style::applyTheme(); + reloadTheme(); filesForm = new FilesForm(); addFriendForm = new AddFriendForm; @@ -264,7 +266,7 @@ void Widget::updateTrayIcon() Widget::~Widget() { - qDebug() << "Deleting Widget"; + qDebug() << "Widget: Deleting Widget"; AutoUpdater::abortUpdates(); icon->hide(); hideMainForms();