diff --git a/psi.doap b/psi.doap
index 6f04826f..1c7dcfb5 100644
--- a/psi.doap
+++ b/psi.doap
@@ -781,6 +781,16 @@
+
+
+
+
+ partial
+ No allowed reaction handling / everything is allowed
+ 0.2.1
+
+
+
diff --git a/src/chatviewcommon.h b/src/chatviewcommon.h
index 5a146318..1ee7c802 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 5ad45b27..6042891b 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 85e789cd..518792f3 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 1911a5ff..264708fa 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 041ef041..2b3d0093 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 18103b3b..fdaa9dd8 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 07a1dcd3..5adcff92 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 850a40b8..a2274563 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 3abe2f2c..b056c110 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 f7750ca6..39048590 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 205213ac..920fc0a6 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 23b38001..69283476 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 3fe78a2c..58cc6398 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 3e29674b..ea69ed90 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 589ff014..becdf58e 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 db4c0e2c..382b6395 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 14cea6f7..40bec215 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"]
});
diff --git a/version b/version
index b30b4c37..a28ef98e 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-1.5.2015 (2024-07-01, 89b5898c)
+1.5.2017 (2024-07-02, 0d37d256)