all repos — mgba @ 5efacfa097d75f5fcd0672f8b98a1bcc36e4ccbb

mGBA Game Boy Advance Emulator

src/platform/qt/LibraryModel.cpp (view raw)

  1/* Copyright (c) 2013-2016 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "LibraryModel.h"
  7
  8#include <QFontMetrics>
  9
 10#include <mgba-util/vfs.h>
 11
 12using namespace QGBA;
 13
 14Q_DECLARE_METATYPE(mLibraryEntry);
 15
 16QMap<QString, LibraryModel::LibraryHandle*> LibraryModel::s_handles;
 17QMap<QString, LibraryModel::LibraryColumn> LibraryModel::s_columns;
 18
 19LibraryModel::LibraryModel(const QString& path, QObject* parent)
 20	: QAbstractItemModel(parent)
 21{
 22	if (s_columns.empty()) {
 23		s_columns["name"] = {
 24			tr("Name"),
 25			[](const mLibraryEntry& e) -> QString {
 26				if (e.title) {
 27					return QString::fromUtf8(e.title);
 28				}
 29				return QString::fromUtf8(e.filename);
 30			}
 31		};
 32		s_columns["filename"] = {
 33			tr("Filename"),
 34			[](const mLibraryEntry& e) -> QString {
 35				return QString::fromUtf8(e.filename);
 36			}
 37		};
 38		s_columns["size"] = {
 39			tr("Size"),
 40			[](const mLibraryEntry& e) -> QString {
 41				double size = e.filesize;
 42				QString unit = "B";
 43				if (size >= 1024.0) {
 44					size /= 1024.0;
 45					unit = "kiB";
 46				}
 47				if (size >= 1024.0) {
 48					size /= 1024.0;
 49					unit = "MiB";
 50				}
 51				return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit);
 52			},
 53			Qt::AlignRight
 54		};
 55		s_columns["platform"] = {
 56			tr("Platform"),
 57			[](const mLibraryEntry& e) -> QString {
 58				int platform = e.platform;
 59				switch (platform) {
 60#ifdef M_CORE_GBA
 61				case PLATFORM_GBA:
 62					return tr("GBA");
 63#endif
 64#ifdef M_CORE_GB
 65				case PLATFORM_GB:
 66					return tr("GB");
 67#endif
 68#ifdef M_CORE_DS
 69				case PLATFORM_DS:
 70					return tr("DS");
 71#endif
 72				default:
 73					return tr("?");
 74				}
 75			}
 76		};
 77		s_columns["location"] = {
 78			tr("Location"),
 79			[](const mLibraryEntry& e) -> QString {
 80				return QString::fromUtf8(e.base);
 81			}
 82		};
 83		s_columns["crc32"] = {
 84			tr("CRC32"),
 85			[](const mLibraryEntry& e) -> QString {
 86				return QString("%0").arg(e.crc32, 8, 16, QChar('0'));
 87			}
 88		};
 89	}
 90	if (!path.isNull()) {
 91		if (s_handles.contains(path)) {
 92			m_library = s_handles[path];
 93			m_library->ref();
 94		} else {
 95			m_library = new LibraryHandle(mLibraryLoad(path.toUtf8().constData()), path);
 96			if (m_library->library) {
 97				s_handles[path] = m_library;
 98			} else {
 99				delete m_library;
100				m_library = new LibraryHandle(mLibraryCreateEmpty());
101			}
102		}
103	} else {
104		m_library = new LibraryHandle(mLibraryCreateEmpty());
105	}
106	mLibraryListingInit(&m_listings, 0);
107	memset(&m_constraints, 0, sizeof(m_constraints));
108	m_constraints.platform = PLATFORM_NONE;
109	m_columns.append(s_columns["name"]);
110	m_columns.append(s_columns["location"]);
111	m_columns.append(s_columns["platform"]);
112	m_columns.append(s_columns["size"]);
113	m_columns.append(s_columns["crc32"]);
114
115	connect(m_library->loader, SIGNAL(directoryLoaded(const QString&)), this, SLOT(directoryLoaded(const QString&)));
116}
117
118LibraryModel::~LibraryModel() {
119	clearConstraints();
120	mLibraryListingDeinit(&m_listings);
121	if (!m_library->deref()) {
122		s_handles.remove(m_library->path);
123		delete m_library;
124	}
125}
126
127void LibraryModel::loadDirectory(const QString& path) {
128	m_queue.append(path);
129	QMetaObject::invokeMethod(m_library->loader, "loadDirectory", Q_ARG(const QString&, path));
130}
131
132bool LibraryModel::entryAt(int row, mLibraryEntry* out) const {
133	if (mLibraryListingSize(&m_listings) <= row) {
134		return false;
135	}
136	*out = *mLibraryListingGetConstPointer(&m_listings, row);
137	return true;
138}
139
140VFile* LibraryModel::openVFile(const QModelIndex& index) const {
141	mLibraryEntry entry;
142	if (!entryAt(index.row(), &entry)) {
143		return nullptr;
144	}
145	return mLibraryOpenVFile(m_library->library, &entry);
146}
147
148QString LibraryModel::filename(const QModelIndex& index) const {
149	mLibraryEntry entry;
150	if (!entryAt(index.row(), &entry)) {
151		return QString();
152	}
153	return QString::fromUtf8(entry.filename);
154}
155
156QString LibraryModel::location(const QModelIndex& index) const {
157	mLibraryEntry entry;
158	if (!entryAt(index.row(), &entry)) {
159		return QString();
160	}
161	return QString::fromUtf8(entry.base);
162}
163
164QVariant LibraryModel::data(const QModelIndex& index, int role) const {
165	if (!index.isValid()) {
166		return QVariant();
167	}
168	mLibraryEntry entry;
169	if (!entryAt(index.row(), &entry)) {
170		return QVariant();
171	}
172	if (role == Qt::UserRole) {
173		return QVariant::fromValue(entry);
174	}
175	if (index.column() >= m_columns.count()) {
176		return QVariant();
177	}
178	switch (role) {
179	case Qt::DisplayRole:
180		return m_columns[index.column()].value(entry);
181	case Qt::SizeHintRole: {
182		QFontMetrics fm((QFont()));
183		return fm.size(Qt::TextSingleLine, m_columns[index.column()].value(entry));
184	}
185	case Qt::TextAlignmentRole:
186		return m_columns[index.column()].alignment;
187	default:
188		return QVariant();
189	}
190}
191
192QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
193	if (role != Qt::DisplayRole) {
194		return QAbstractItemModel::headerData(section, orientation, role);
195	}
196	if (orientation == Qt::Horizontal) {
197		if (section >= m_columns.count()) {
198			return QVariant();
199		}
200		return m_columns[section].name;
201	}
202	return section;
203}
204
205QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
206	if (parent.isValid()) {
207		return QModelIndex();
208	}
209	return createIndex(row, column, nullptr);
210}
211
212QModelIndex LibraryModel::parent(const QModelIndex&) const {
213	return QModelIndex();
214}
215
216int LibraryModel::columnCount(const QModelIndex& parent) const {
217	if (parent.isValid()) {
218		return 0;
219	}
220	return m_columns.count();
221}
222
223int LibraryModel::rowCount(const QModelIndex& parent) const {
224	if (parent.isValid()) {
225		return 0;
226	}
227	return mLibraryCount(m_library->library, &m_constraints);
228}
229
230void LibraryModel::attachGameDB(const NoIntroDB* gameDB) {
231	mLibraryAttachGameDB(m_library->library, gameDB);
232}
233
234void LibraryModel::constrainBase(const QString& path) {
235	clearConstraints();
236	if (m_constraints.base) {
237		free(const_cast<char*>(m_constraints.base));
238	}
239	m_constraints.base = strdup(path.toUtf8().constData());
240	reload();
241}
242
243void LibraryModel::clearConstraints() {
244	if (m_constraints.base) {
245		free(const_cast<char*>(m_constraints.base));
246	}
247	if (m_constraints.filename) {
248		free(const_cast<char*>(m_constraints.filename));
249	}
250	if (m_constraints.title) {
251		free(const_cast<char*>(m_constraints.title));
252	}
253	memset(&m_constraints, 0, sizeof(m_constraints));
254	size_t i;
255	for (i = 0; i < mLibraryListingSize(&m_listings); ++i) {
256		mLibraryEntryFree(mLibraryListingGetPointer(&m_listings, i));
257	}
258	mLibraryListingClear(&m_listings);
259}
260
261void LibraryModel::reload() {
262	mLibraryGetEntries(m_library->library, &m_listings, 0, 0, m_constraints.base ? &m_constraints : nullptr);
263}
264
265void LibraryModel::directoryLoaded(const QString& path) {
266	m_queue.removeOne(path);
267	beginResetModel();
268	endResetModel();
269	if (m_queue.empty()) {
270		emit doneLoading();
271	}
272}
273
274LibraryModel::LibraryColumn::LibraryColumn() {
275}
276
277LibraryModel::LibraryColumn::LibraryColumn(const QString& name, std::function<QString(const mLibraryEntry&)> value, int alignment)
278	: name(name)
279	, value(value)
280	, alignment(alignment)
281{
282}
283
284LibraryModel::LibraryHandle::LibraryHandle(mLibrary* lib, const QString& p)
285	: library(lib)
286	, loader(new LibraryLoader(library))
287	, path(p)
288	, m_ref(1)
289{
290	if (!library) {
291		return;
292	}
293	loader->moveToThread(&m_loaderThread);
294	m_loaderThread.setObjectName("Library Loader Thread");
295	m_loaderThread.start();
296}
297
298LibraryModel::LibraryHandle::~LibraryHandle() {
299	m_loaderThread.quit();
300	m_loaderThread.wait();
301	if (library) {
302		mLibraryDestroy(library);
303	}
304}
305
306void LibraryModel::LibraryHandle::ref() {
307	++m_ref;
308}
309
310bool LibraryModel::LibraryHandle::deref() {
311	--m_ref;
312	return m_ref > 0;
313}
314
315LibraryLoader::LibraryLoader(mLibrary* library, QObject* parent)
316	: QObject(parent)
317	, m_library(library)
318{
319}
320
321void LibraryLoader::loadDirectory(const QString& path) {
322	mLibraryLoadDirectory(m_library, path.toUtf8().constData());
323	emit directoryLoaded(path);
324}