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}