diff --git a/src/chatviewcommon.h b/src/chatviewcommon.h index 5a1463180..1ee7c802d 100644 --- a/src/chatviewcommon.h +++ b/src/chatviewcommon.h @@ -30,8 +30,6 @@ class QWidget; class ChatViewCommon { public: enum UserType { LocalParty, RemoteParty, Participant }; - enum class Feature { Reactions = 0x1 }; - Q_DECLARE_FLAGS(Features, Feature) ChatViewCommon() : _nickNumber(0) { } void setLooks(QWidget *); @@ -64,11 +62,8 @@ class ChatViewCommon { // to be called from UI stuff. return list of reactions to send over network QSet onReactionSwitched(const QString &senderNickname, const QString &messageId, const QString &reaction); - Features features(); - protected: QDateTime _lastMsgTime; - Features _featuers; private: QList &generatePalette(); diff --git a/src/chatviewtheme.cpp b/src/chatviewtheme.cpp index 5ad45b272..6042891b3 100644 --- a/src/chatviewtheme.cpp +++ b/src/chatviewtheme.cpp @@ -70,7 +70,7 @@ ChatViewThemePrivate::ChatViewThemePrivate(ChatViewThemeProvider *provider) : Th ChatViewThemePrivate::~ChatViewThemePrivate() { - qDebug("ChatViewThemePrivate::~ChatViewThemePrivate"); + // qDebug("ChatViewThemePrivate::~ChatViewThemePrivate"); delete wv; } @@ -447,6 +447,11 @@ void ChatViewJSLoader::setMetaData(const QVariantMap &map) if (!v.isEmpty()) { theme->homeUrl = v; } + + vl = map["features"].toStringList(); + if (vl.count()) { + theme->features = vl; + } } void ChatViewJSLoader::finishThemeLoading() diff --git a/src/chatviewthemeprovider.cpp b/src/chatviewthemeprovider.cpp index 85e789cd9..518792f3c 100644 --- a/src/chatviewthemeprovider.cpp +++ b/src/chatviewthemeprovider.cpp @@ -85,49 +85,63 @@ Theme ChatViewThemeProvider::theme(const QString &id) } /** - * @brief Load theme from settings or classic on failure. - * Signal themeChanged when necessary + * @brief Load theme from settings or New Classic on failure. + * Signals themeChanged if different theme was loaded before. * * @return false on failure to load any theme */ -bool ChatViewThemeProvider::loadCurrent() +PsiThemeProvider::LoadRestult ChatViewThemeProvider::loadCurrent() { QString loadedId = curTheme.id(); QString themeName = PsiOptions::instance()->getOption(optionString()).toString(); if (!loadedId.isEmpty() && loadedId == themeName) { - return true; // already loaded. nothing todo + return LoadSuccess; // already loaded. nothing todo } - Theme t(theme(themeName)); - if (!t.exists()) { - if (themeName != QLatin1String("psi/classic")) { + auto newClassic = QStringLiteral("psi/new_classic"); + curLoadingTheme = theme(themeName); // may trigger destructor of prev curLoadingTheme + if (!curLoadingTheme.exists()) { + if (themeName != newClassic) { qDebug("Invalid theme id: %s", qPrintable(themeName)); - qDebug("fallback to classic chatview theme"); - PsiOptions::instance()->setOption(optionString(), QLatin1String("psi/classic")); - return loadCurrent(); + qDebug("fallback to new_classic chatview theme"); + PsiOptions::instance()->setOption(optionString(), newClassic); + curLoadingTheme = theme(newClassic); + } else { + qDebug("New Classic theme failed to load. No fallback.."); + return LoadFailure; } - qDebug("Classic theme failed to load. No fallback.."); - return false; } - bool startedLoading = t.load([this, t, loadedId](bool success) { - if (!success && t.id() != QLatin1String("psi/classic")) { - qDebug("Failed to load theme \"%s\"", qPrintable(t.id())); - qDebug("fallback to classic chatview theme"); - PsiOptions::instance()->setOption(optionString(), QLatin1String("psi/classic")); - loadCurrent(); - } else if (success) { + bool startedLoading = curLoadingTheme.load([this, loadedId, newClassic](bool success) { + auto t = curLoadingTheme; + curLoadingTheme = {}; + if (success) { curTheme = t; if (t.id() != loadedId) { emit themeChanged(); } - } // else it was already classic + return; + } + if (t.id() != newClassic) { + qDebug("Failed to load theme \"%s\"", qPrintable(t.id())); + qDebug("fallback to classic chatview theme"); + PsiOptions::instance()->setOption(optionString(), newClassic); + loadCurrent(); + } else { + // nowhere to fallback + emit currentLoadFailed(); + } }); - return startedLoading; // does not really matter. may fail later on loading + if (startedLoading) { + return LoadInProgress; // does not really matter. may fail later on loading + } + return LoadFailure; } void ChatViewThemeProvider::unloadCurrent() { curTheme = Theme(); } +void ChatViewThemeProvider::cancelCurrentLoading() { curLoadingTheme = {}; /* should call destructor */ } + Theme ChatViewThemeProvider::current() const { return curTheme; } void ChatViewThemeProvider::setCurrentTheme(const QString &id) diff --git a/src/chatviewthemeprovider.h b/src/chatviewthemeprovider.h index 1911a5ff5..264708fab 100644 --- a/src/chatviewthemeprovider.h +++ b/src/chatviewthemeprovider.h @@ -37,9 +37,10 @@ class ChatViewThemeProvider : public PsiThemeProvider { const QStringList themeIds() const; Theme theme(const QString &id); - bool loadCurrent(); - void unloadCurrent(); - Theme current() const; // currently loaded theme + LoadRestult loadCurrent(); + void unloadCurrent(); + void cancelCurrentLoading(); + Theme current() const; // currently loaded theme void setCurrentTheme(const QString &); virtual int screenshotWidth() const { return 512; } // hack @@ -54,11 +55,9 @@ class ChatViewThemeProvider : public PsiThemeProvider { protected: virtual const char *optionString() const { return "options.ui.chat.theme"; } -signals: - void themeChanged(); - private: Theme curTheme; + Theme curLoadingTheme; // load-in-progress theme to replace cutTheme in success }; class GroupChatViewThemeProvider : public ChatViewThemeProvider { diff --git a/src/psiaccount.cpp b/src/psiaccount.cpp index 041ef0411..2b3d0093d 100644 --- a/src/psiaccount.cpp +++ b/src/psiaccount.cpp @@ -56,6 +56,7 @@ #include "fileutil.h" #include "geolocationdlg.h" #include "iris/bsocket.h" +#include "psithememanager.h" #include "xmpp/xmpp-im/xmpp_vcard4.h" #ifdef GOOGLE_FT #include "googleftmanager.h" @@ -1075,6 +1076,12 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, TabManage // another hack. We rather should have PsiMedia single instance as a member of PsiCon connect(MediaDeviceWatcher::instance(), &MediaDeviceWatcher::availibityChanged, this, &PsiAccount::updateFeatures); +#ifdef WEBKIT + connect(d->psi->themeManager()->provider("chatview"), &PsiThemeProvider::themeChanged, this, + &PsiAccount::updateFeatures); + connect(d->psi->themeManager()->provider("groupchatview"), &PsiThemeProvider::themeChanged, this, + &PsiAccount::updateFeatures); +#endif #ifdef FILETRANSFER d->client->setFileTransferEnabled(true); #else @@ -1547,22 +1554,30 @@ void PsiAccount::updateFeatures() #endif #ifdef USE_PEP - features << "http://jabber.org/protocol/mood" - << "http://jabber.org/protocol/activity"; - features << "http://jabber.org/protocol/tune" - << "http://jabber.org/protocol/geoloc"; - features << "urn:xmpp:avatar:data" - << "urn:xmpp:avatar:metadata"; + features << QLatin1String("http://jabber.org/protocol/mood") << QLatin1String("http://jabber.org/protocol/activity") + << QLatin1String("http://jabber.org/protocol/tune") << QLatin1String("http://jabber.org/protocol/geoloc") + << QLatin1String("urn:xmpp:avatar:data") << QLatin1String("urn:xmpp:avatar:metadata"); #endif if (AvCallManager::isSupported()) { - features << "urn:xmpp:jingle:transports:ice-udp:1"; - features << "urn:xmpp:jingle:transports:ice:0"; - features << "urn:xmpp:jingle:apps:rtp:1"; - features << "urn:xmpp:jingle:apps:rtp:audio"; - features << "urn:xmpp:jingle:apps:rtp:video"; + features << QLatin1String("urn:xmpp:jingle:transports:ice-udp:1"); + features << QLatin1String("urn:xmpp:jingle:transports:ice:0"); + features << QLatin1String("urn:xmpp:jingle:apps:rtp:1"); + features << QLatin1String("urn:xmpp:jingle:apps:rtp:audio"); + features << QLatin1String("urn:xmpp:jingle:apps:rtp:video"); } - features << "jabber:x:conference"; // allow direct invites + features << QLatin1String("jabber:x:conference"); // allow direct invites + +#ifdef WEBKIT + auto gcTheme = psi()->themeManager()->provider("groupchatview")->current(); + auto chatTheme = psi()->themeManager()->provider("chatview")->current(); + if (gcTheme && chatTheme) { + auto themeFeatures = gcTheme.features() + chatTheme.features(); + if (themeFeatures.contains(QStringLiteral("reactions"))) { + features << QLatin1String("urn:xmpp:reactions:0"); + } + } +#endif // TODO reset hash d->client->setFeatures(Features(features)); diff --git a/src/psicon.cpp b/src/psicon.cpp index 18103b3bf..fdaa9dd8d 100644 --- a/src/psicon.cpp +++ b/src/psicon.cpp @@ -558,11 +558,22 @@ bool PsiCon::init() d->themeManager->registerProvider(new GroupChatViewThemeProvider(this), true); #endif - if (!d->themeManager->loadAll()) { + const auto reportThemeError = [this]() { QMessageBox::critical(nullptr, tr("Error"), - tr("Unable to load theme! Please make sure Psi is properly installed.")); + tr("Unable to load \"%1\" theme! Please make sure Psi is properly installed.") + .arg(d->themeManager->failedId())); + }; + auto themeLoadResult = d->themeManager->loadAll(); + if (themeLoadResult == PsiThemeProvider::LoadFailure) { + reportThemeError(); result = false; } + if (themeLoadResult == PsiThemeProvider::LoadInProgress) { + connect(d->themeManager, &PsiThemeManager::currentLoadFailed, this, [this, reportThemeError]() { + reportThemeError(); + closeProgram(); + }); + } if (!d->actionList) d->actionList = new PsiActionList(this); diff --git a/src/psithememanager.cpp b/src/psithememanager.cpp index 07a1dcd36..5adcff921 100644 --- a/src/psithememanager.cpp +++ b/src/psithememanager.cpp @@ -28,6 +28,9 @@ class PsiThemeManager::Private { public: QMap providers; QSet required; + QString failedId; + QString errorString; + PsiThemeProvider::LoadRestult loadResult = PsiThemeProvider::LoadNotStarted; }; //--------------------------------------------------------- @@ -61,13 +64,58 @@ PsiThemeProvider *PsiThemeManager::provider(const QString &type) { return d->pro QList PsiThemeManager::registeredProviders() const { return d->providers.values(); } -bool PsiThemeManager::loadAll() +PsiThemeProvider::LoadRestult PsiThemeManager::loadAll() { - const auto &types = d->providers.keys(); - for (const QString &type : types) { - if (!d->providers[type]->loadCurrent() && d->required.contains(type)) { - return false; + d->loadResult = PsiThemeProvider::LoadInProgress; + QObject *ctx = nullptr; + auto pending = std::shared_ptr> { new QList() }; + auto cleanup = [this, pending](QObject *ctx, PsiThemeProvider *provider, bool failure) { + if (failure) { + d->failedId = QLatin1String(provider->type()); + for (auto p : *pending) { + p->cancelCurrentLoading(); + p->disconnect(ctx); + } + ctx->deleteLater(); + d->loadResult = PsiThemeProvider::LoadFailure; + emit currentLoadFailed(); + return; } + pending->removeOne(provider); + provider->disconnect(ctx); + if (pending->isEmpty()) { + ctx->deleteLater(); + d->loadResult = PsiThemeProvider::LoadSuccess; + emit currentLoadSuccess(); + } + }; + for (auto it = d->providers.begin(); it != d->providers.end(); ++it) { + auto provider = it.value(); + auto required = d->required.contains(it.key()); + auto status = provider->loadCurrent(); + if (status == PsiThemeProvider::LoadFailure && required) { + d->failedId = QLatin1String(provider->type()); + d->loadResult = PsiThemeProvider::LoadFailure; + return PsiThemeProvider::LoadFailure; + } + if (status == PsiThemeProvider::LoadSuccess) { + continue; + } + // in progress + pending->append(provider); + + if (!ctx) { + ctx = new QObject(this); + } + connect( + provider, &PsiThemeProvider::themeChanged, ctx, + [ctx, provider, cleanup]() { cleanup(ctx, provider, false); }, Qt::SingleShotConnection); + connect( + provider, &PsiThemeProvider::currentLoadFailed, ctx, + [ctx, provider, required, cleanup]() { cleanup(ctx, provider, required); }, Qt::SingleShotConnection); } - return true; + d->loadResult = pending->isEmpty() ? PsiThemeProvider::LoadSuccess : PsiThemeProvider::LoadInProgress; + return d->loadResult; } + +QString PsiThemeManager::failedId() const { return d->failedId; } diff --git a/src/psithememanager.h b/src/psithememanager.h index 850a40b8e..a2274563d 100644 --- a/src/psithememanager.h +++ b/src/psithememanager.h @@ -32,12 +32,18 @@ class PsiThemeManager : public QObject { PsiThemeManager(QObject *parent); ~PsiThemeManager(); - void registerProvider(PsiThemeProvider *provider, bool required = false); - PsiThemeProvider *unregisterProvider(const QString &type); - PsiThemeProvider *provider(const QString &type); - QList registeredProviders() const; - bool loadAll(); - + void registerProvider(PsiThemeProvider *provider, bool required = false); + PsiThemeProvider *unregisterProvider(const QString &type); + PsiThemeProvider *provider(const QString &type); + QList registeredProviders() const; + PsiThemeProvider::LoadRestult loadAll(); + QString failedId() const; + +signals: + void currentLoadSuccess(); + void currentLoadFailed(); + +private: class Private; Private *d; }; diff --git a/src/psithememodel.cpp b/src/psithememodel.cpp index 3abe2f2c3..b056c110e 100644 --- a/src/psithememodel.cpp +++ b/src/psithememodel.cpp @@ -82,6 +82,7 @@ struct PsiThemeModel::Loader { ti.authors = theme.authors(); ti.creation = theme.creation(); ti.homeUrl = theme.homeUrl(); + ti.features = theme.features(); ti.hasPreview = theme.hasPreview(); ti.isValid = true; diff --git a/src/psithememodel.h b/src/psithememodel.h index f7750ca6d..39048590c 100644 --- a/src/psithememodel.h +++ b/src/psithememodel.h @@ -36,6 +36,7 @@ struct ThemeItemInfo { QStringList authors; QString creation; QString homeUrl; + QStringList features; bool hasPreview; bool isValid = false; diff --git a/src/psithemeprovider.h b/src/psithemeprovider.h index 205213acc..920fc0a68 100644 --- a/src/psithemeprovider.h +++ b/src/psithemeprovider.h @@ -23,7 +23,6 @@ #include "theme.h" #include -#include class PsiCon; @@ -33,6 +32,8 @@ class PsiThemeProvider : public QObject { PsiCon *_psi; public: + enum LoadRestult { LoadNotStarted, LoadSuccess, LoadFailure, LoadInProgress }; + PsiThemeProvider(PsiCon *parent); inline PsiCon *psi() const { return _psi; } @@ -40,8 +41,9 @@ class PsiThemeProvider : public QObject { virtual const char *type() const = 0; virtual Theme theme(const QString &id) = 0; // make new theme virtual const QStringList themeIds() const = 0; - virtual bool loadCurrent() = 0; + virtual LoadRestult loadCurrent() = 0; virtual void unloadCurrent() = 0; + virtual void cancelCurrentLoading() = 0; virtual Theme current() const = 0; virtual void setCurrentTheme(const QString &) = 0; @@ -52,6 +54,10 @@ class PsiThemeProvider : public QObject { virtual QString optionsDescription() const = 0; static QString themePath(const QString &name); + +signals: + void themeChanged(); + void currentLoadFailed(); // loading of the current therme has failed }; #endif // PSITHEMEPROVIDER_H diff --git a/src/theme.cpp b/src/theme.cpp index 23b380013..69283476a 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -187,6 +187,8 @@ const QString &Theme::creation() const { return d->creation; } const QString &Theme::homeUrl() const { return d->homeUrl; } +const QStringList &Theme::features() const { return d->features; } + PsiThemeProvider *Theme::themeProvider() const { return d->provider; } /** diff --git a/src/theme.h b/src/theme.h index 3fe78a2c8..58cc63989 100644 --- a/src/theme.h +++ b/src/theme.h @@ -62,8 +62,9 @@ class Theme { Theme &operator=(const Theme &other); virtual ~Theme(); - bool isValid() const; - State state() const; + bool isValid() const; + inline operator bool() const { return isValid(); } + State state() const; // previously virtual bool exists(); @@ -91,6 +92,7 @@ class Theme { const QStringList &authors() const; const QString &creation() const; const QString &homeUrl() const; + const QStringList &features() const; PsiThemeProvider *themeProvider() const; const QString &filePath() const; diff --git a/src/theme_p.h b/src/theme_p.h index 3e29674bd..ea69ed904 100644 --- a/src/theme_p.h +++ b/src/theme_p.h @@ -33,7 +33,7 @@ class ThemePrivate : public QSharedData { // metadata QString id, name, version, description, creation, homeUrl; - QStringList authors; + QStringList authors, features; QHash info; // runtime info diff --git a/src/webview.cpp b/src/webview.cpp index 589ff014e..becdf58e3 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -73,7 +73,7 @@ WebView::WebView(QWidget *parent) : connectPageActions(); } -WebView::~WebView() { qDebug("WebView::~WebView"); } +WebView::~WebView() { /*qDebug("WebView::~WebView");*/ } void WebView::linkClickedEvent(const QUrl &url) { diff --git a/themes/chatview/psi/bubble/index.html b/themes/chatview/psi/bubble/index.html index db4c0e2c7..382b63950 100644 --- a/themes/chatview/psi/bubble/index.html +++ b/themes/chatview/psi/bubble/index.html @@ -136,10 +136,10 @@ } } else { items = [ - { text: "Delete", action: ()=>{ shared.session.deleteMessage(msgNode.id); } }, - { text: "Reply", action: ()=>{ onReplyClicked(msgNode); } }, + { text: "Delete", action: ()=>{ shared.session.deleteMessage(msgNode.id); } }, + { text: "Reply", action: ()=>{ onReplyClicked(msgNode); } }, { text: "Forward", action: ()=>{ shared.session.forwardMessage(msgNode.id); } }, - { text: "Copy", action: ()=>{ shared.session.copyMessage(msgNode.id); } } + { text: "Copy", action: ()=>{ shared.session.copyMessage(msgNode.id); } } ] } return items; diff --git a/themes/chatview/psi/bubble/load.js b/themes/chatview/psi/bubble/load.js index 14cea6f7f..40bec2150 100644 --- a/themes/chatview/psi/bubble/load.js +++ b/themes/chatview/psi/bubble/load.js @@ -3,5 +3,6 @@ srvLoader.setMetaData({ version: "1.0", authors: ["Sergei Ilinykh "], description: "Bubble style.", - url: "https://psi-im.org" + url: "https://psi-im.org", + features: ["reactions"] });