1 /*
2  *  Kaidan - A user-friendly XMPP client for every device!
3  *
4  *  Copyright (C) 2016-2021 Kaidan developers and contributors
5  *  (see the LICENSE file for a full list of copyright authors)
6  *
7  *  Kaidan is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  In addition, as a special exception, the author of Kaidan gives
13  *  permission to link the code of its release with the OpenSSL
14  *  project's "OpenSSL" library (or with modified versions of it that
15  *  use the same license as the "OpenSSL" library), and distribute the
16  *  linked executables. You must obey the GNU General Public License in
17  *  all respects for all of the code used other than "OpenSSL". If you
18  *  modify this file, you may extend this exception to your version of
19  *  the file, but you are not obligated to do so.  If you do not wish to
20  *  do so, delete this exception statement from your version.
21  *
22  *  Kaidan is distributed in the hope that it will be useful,
23  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
24  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  *  GNU General Public License for more details.
26  *
27  *  You should have received a copy of the GNU General Public License
28  *  along with Kaidan.  If not, see <http://www.gnu.org/licenses/>.
29  */
30 
31 #pragma once
32 
33 #include <QAbstractListModel>
34 #include <QList>
35 
36 #include <functional>
37 
38 template <typename T>
39 class MediaSettingModel : public QAbstractListModel
40 {
41 public:
42 	enum CustomRoles {
43 		ValueRole = Qt::UserRole,
44 		DescriptionRole
45 	};
46 
47 	using ToString = std::function<QString(const T &, const void *userData)>;
48 
49 	using QAbstractListModel::QAbstractListModel;
50 
51 	explicit MediaSettingModel(MediaSettingModel::ToString toString,
52 		const void *userData = nullptr, QObject *parent = nullptr)
QAbstractListModel(parent)53 		: QAbstractListModel(parent)
54 		  , m_toString(toString)
55 		  , m_userData(userData) {
56 	}
57 
58 	int rowCount(const QModelIndex &parent = QModelIndex()) const override {
59 		return parent == QModelIndex() ? m_values.count() : 0;
60 	}
61 
62 	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
63 		if (hasIndex(index.row(), index.column(), index.parent())) {
64 			switch (role) {
65 			case MediaSettingModel::CustomRoles::ValueRole:
66 				return QVariant::fromValue(m_values[index.row()]);
67 			case MediaSettingModel::CustomRoles::DescriptionRole:
68 				return toString(m_values[index.row()]);
69 			}
70 		}
71 
72 		return { };
73 	}
74 
roleNames()75 	QHash<int, QByteArray> roleNames() const override {
76 		static const QHash<int, QByteArray> roles {
77 			{ MediaSettingModel::CustomRoles::ValueRole, QByteArrayLiteral("value") },
78 			{ MediaSettingModel::CustomRoles::DescriptionRole, QByteArrayLiteral("description") }
79 		};
80 
81 		return roles;
82 	}
83 
toString()84 	MediaSettingModel::ToString toString() const {
85 		return m_toString;
86 	}
87 
setToString(MediaSettingModel::ToString toString)88 	void setToString(MediaSettingModel::ToString toString) {
89 		m_toString = toString;
90 
91 		emit toStringChanged();
92 
93 		const int count = rowCount();
94 
95 		if (count > 0) {
96 			emit dataChanged(index(0, 0), index(count -1, 0));
97 		}
98 	}
99 
userData()100 	const void *userData() const {
101 		return m_userData;
102 	}
103 
setUserData(const void * userData)104 	void setUserData(const void *userData) {
105 		if (m_userData == userData) {
106 			return;
107 		}
108 
109 		m_userData = userData;
110 
111 		emit userDataChanged();
112 
113 		const int count = rowCount();
114 
115 		if (count > 0) {
116 			emit dataChanged(index(0, 0), index(count -1, 0));
117 		}
118 	}
119 
values()120 	QList<T> values() const {
121 		return m_values;
122 	}
123 
setValues(const QList<T> & values)124 	void setValues(const QList<T> &values) {
125 		if (m_values == values) {
126 			return;
127 		}
128 
129 		const int newCurrentIndex = m_currentIndex != -1 ? values.indexOf(currentValue()) : -1;
130 		const bool curIdxChanged = newCurrentIndex != m_currentIndex;
131 
132 		beginResetModel();
133 		m_values = values;
134 		m_currentIndex = newCurrentIndex;
135 		endResetModel();
136 
137 		emit valuesChanged();
138 
139 		if (curIdxChanged) {
140 			emit currentIndexChanged();
141 		}
142 	}
143 
currentIndex()144 	int currentIndex() const {
145 		return m_currentIndex;
146 	}
147 
setCurrentIndex(int currentIndex)148 	void setCurrentIndex(int currentIndex) {
149 		if (currentIndex < 0 || currentIndex >= m_values.count()
150 			|| m_currentIndex == currentIndex) {
151 			return;
152 		}
153 
154 		m_currentIndex = currentIndex;
155 		emit currentIndexChanged();
156 	}
157 
currentValue()158 	T currentValue() const {
159 		return m_currentIndex >= 0 && m_currentIndex < m_values.count()
160 			       ? m_values[m_currentIndex]
161 			       : T();
162 	}
163 
setCurrentValue(const T & currentValue)164 	void setCurrentValue(const T &currentValue) {
165 		setCurrentIndex(indexOf(currentValue));
166 	}
167 
currentDescription()168 	QString currentDescription() const {
169 		return m_currentIndex >= 0 && m_currentIndex < m_values.count()
170 			       ? toString(currentValue())
171 			       : QString();
172 	}
173 
setValuesAndCurrentIndex(const QList<T> & values,int currentIndex)174 	void setValuesAndCurrentIndex(const QList<T> &values, int currentIndex) {
175 		if (m_values == values && m_currentIndex == currentIndex) {
176 			return;
177 		}
178 
179 		beginResetModel();
180 		m_values = values;
181 		m_currentIndex = currentIndex >= 0 && currentIndex < m_values.count()
182 					 ? currentIndex
183 					 : -1;
184 		endResetModel();
185 
186 		emit valuesChanged();
187 		emit currentIndexChanged();
188 	}
189 
setValuesAndCurrentValue(const QList<T> & values,const T & currentValue)190 	void setValuesAndCurrentValue(const QList<T> &values, const T &currentValue) {
191 		setValuesAndCurrentIndex(values, values.indexOf(currentValue));
192 	}
193 
194 	// Invokables
clear()195 	virtual void clear() {
196 		beginResetModel();
197 		m_currentIndex = -1;
198 		m_values.clear();
199 		endResetModel();
200 
201 		emit valuesChanged();
202 		emit currentIndexChanged();
203 	}
204 
indexOf(const T & value)205 	virtual int indexOf(const T &value) const {
206 		return m_values.indexOf(value);
207 	}
208 
value(int index)209 	virtual T value(int index) const {
210 		if (index < 0 || index >= m_values.count()) {
211 			return { };
212 		}
213 
214 		return m_values[index];
215 	}
216 
description(int index)217 	virtual QString description(int index) const {
218 		if (index < 0 || index >= m_values.count()) {
219 			return { };
220 		}
221 
222 		return toString(m_values[index]);
223 	}
224 
toString(const T & value)225 	virtual QString toString(const T &value) const {
226 		if (m_toString) {
227 			return m_toString(value, m_userData);
228 		}
229 
230 		return QVariant::fromValue(value).toString();
231 	}
232 
233 	// Signals
234 	virtual void toStringChanged() = 0;
235 	virtual void userDataChanged() = 0;
236 	virtual void valuesChanged() = 0;
237 	virtual void currentIndexChanged() = 0;
238 
239 private:
240 	MediaSettingModel::ToString m_toString;
241 	const void *m_userData = nullptr;
242 	int m_currentIndex = -1;
243 	QList<T> m_values;
244 };
245 
246 #define DECL_MEDIA_SETTING_MODEL(NAME, TYPE, TO_STRING)										\
247 class MediaSettings##NAME##Model : public MediaSettingModel<TYPE> {								\
248 	Q_OBJECT														\
249 																\
250 	Q_PROPERTY(MediaSettings##NAME##Model::ToString toString READ toString WRITE setToString NOTIFY toStringChanged)	\
251 	Q_PROPERTY(const void *userData READ userData WRITE setUserData NOTIFY userDataChanged)					\
252 	Q_PROPERTY(QList<TYPE> values READ values WRITE setValues NOTIFY valuesChanged)						\
253 	Q_PROPERTY(int rowCount READ rowCount NOTIFY valuesChanged)								\
254 	Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)				\
255 	Q_PROPERTY(TYPE currentValue READ currentValue WRITE setCurrentValue NOTIFY currentIndexChanged)			\
256 	Q_PROPERTY(QString currentDescription READ currentDescription NOTIFY currentIndexChanged)				\
257 																\
258 	using MSMT = MediaSettingModel<TYPE>;											\
259 																\
260 public:																\
261 	using MSMT::MSMT;													\
262 	explicit MediaSettings##NAME##Model(const void *userData, QObject *parent = nullptr)					\
263 		: MSMT(TO_STRING, userData, parent)										\
264 	{ }															\
265 																\
266 	explicit MediaSettings##NAME##Model(QObject *parent = nullptr)								\
267 		: MSMT(parent)													\
268 	{ }															\
269 																\
270 	using MSMT::toString;													\
271 	Q_INVOKABLE void clear() override { MSMT::clear(); }									\
272 	Q_INVOKABLE int indexOf(const TYPE &value) const override { return MSMT::indexOf(value); }				\
273 	Q_INVOKABLE TYPE value(int index) const override { return MSMT::value(index); }						\
274 	Q_INVOKABLE QString description(int index) const override { return MSMT::description(index); }				\
275 	Q_INVOKABLE QString toString(const TYPE &value) const override { return MSMT::toString(value); }			\
276 																\
277 Q_SIGNALS:															\
278 	void toStringChanged() override;											\
279 	void userDataChanged() override;											\
280 	void valuesChanged() override;												\
281 	void currentIndexChanged() override;											\
282 }
283