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 default:
69 return tr("?");
70 }
71 }
72 };
73 s_columns["location"] = {
74 tr("Location"),
75 [](const mLibraryEntry& e) -> QString {
76 return QString::fromUtf8(e.base);
77 }
78 };
79 s_columns["crc32"] = {
80 tr("CRC32"),
81 [](const mLibraryEntry& e) -> QString {
82 return QString("%0").arg(e.crc32, 8, 16, QChar('0'));
83 }
84 };
85 }
86 if (!path.isNull()) {
87 if (s_handles.contains(path)) {
88 m_library = s_handles[path];
89 m_library->ref();
90 } else {
91 m_library = new LibraryHandle(mLibraryLoad(path.toUtf8().constData()), path);
92 if (m_library->library) {
93 s_handles[path] = m_library;
94 } else {
95 delete m_library;
96 m_library = new LibraryHandle(mLibraryCreateEmpty());
97 }
98 }
99 } else {
100 m_library = new LibraryHandle(mLibraryCreateEmpty());
101 }
102 mLibraryListingInit(&m_listings, 0);
103 memset(&m_constraints, 0, sizeof(m_constraints));
104 m_constraints.platform = PLATFORM_NONE;
105 m_columns.append(s_columns["name"]);
106 m_columns.append(s_columns["location"]);
107 m_columns.append(s_columns["platform"]);
108 m_columns.append(s_columns["size"]);
109 m_columns.append(s_columns["crc32"]);
110
111 connect(m_library->loader, SIGNAL(directoryLoaded(const QString&)), this, SLOT(directoryLoaded(const QString&)));
112}
113
114LibraryModel::~LibraryModel() {
115 clearConstraints();
116 mLibraryListingDeinit(&m_listings);
117 if (!m_library->deref()) {
118 s_handles.remove(m_library->path);
119 delete m_library;
120 }
121}
122
123void LibraryModel::loadDirectory(const QString& path) {
124 m_queue.append(path);
125 QMetaObject::invokeMethod(m_library->loader, "loadDirectory", Q_ARG(const QString&, path));
126}
127
128bool LibraryModel::entryAt(int row, mLibraryEntry* out) const {
129 if (mLibraryListingSize(&m_listings) <= row) {
130 return false;
131 }
132 *out = *mLibraryListingGetConstPointer(&m_listings, row);
133 return true;
134}
135
136VFile* LibraryModel::openVFile(const QModelIndex& index) const {
137 mLibraryEntry entry;
138 if (!entryAt(index.row(), &entry)) {
139 return nullptr;
140 }
141 return mLibraryOpenVFile(m_library->library, &entry);
142}
143
144QString LibraryModel::filename(const QModelIndex& index) const {
145 mLibraryEntry entry;
146 if (!entryAt(index.row(), &entry)) {
147 return QString();
148 }
149 return QString::fromUtf8(entry.filename);
150}
151
152QString LibraryModel::location(const QModelIndex& index) const {
153 mLibraryEntry entry;
154 if (!entryAt(index.row(), &entry)) {
155 return QString();
156 }
157 return QString::fromUtf8(entry.base);
158}
159
160QVariant LibraryModel::data(const QModelIndex& index, int role) const {
161 if (!index.isValid()) {
162 return QVariant();
163 }
164 mLibraryEntry entry;
165 if (!entryAt(index.row(), &entry)) {
166 return QVariant();
167 }
168 if (role == Qt::UserRole) {
169 return QVariant::fromValue(entry);
170 }
171 if (index.column() >= m_columns.count()) {
172 return QVariant();
173 }
174 switch (role) {
175 case Qt::DisplayRole:
176 return m_columns[index.column()].value(entry);
177 case Qt::SizeHintRole: {
178 QFontMetrics fm((QFont()));
179 return fm.size(Qt::TextSingleLine, m_columns[index.column()].value(entry));
180 }
181 case Qt::TextAlignmentRole:
182 return m_columns[index.column()].alignment;
183 default:
184 return QVariant();
185 }
186}
187
188QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
189 if (role != Qt::DisplayRole) {
190 return QAbstractItemModel::headerData(section, orientation, role);
191 }
192 if (orientation == Qt::Horizontal) {
193 if (section >= m_columns.count()) {
194 return QVariant();
195 }
196 return m_columns[section].name;
197 }
198 return section;
199}
200
201QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
202 if (parent.isValid()) {
203 return QModelIndex();
204 }
205 return createIndex(row, column, nullptr);
206}
207
208QModelIndex LibraryModel::parent(const QModelIndex&) const {
209 return QModelIndex();
210}
211
212int LibraryModel::columnCount(const QModelIndex& parent) const {
213 if (parent.isValid()) {
214 return 0;
215 }
216 return m_columns.count();
217}
218
219int LibraryModel::rowCount(const QModelIndex& parent) const {
220 if (parent.isValid()) {
221 return 0;
222 }
223 return mLibraryCount(m_library->library, &m_constraints);
224}
225
226void LibraryModel::attachGameDB(const NoIntroDB* gameDB) {
227 mLibraryAttachGameDB(m_library->library, gameDB);
228}
229
230void LibraryModel::constrainBase(const QString& path) {
231 clearConstraints();
232 if (m_constraints.base) {
233 free(const_cast<char*>(m_constraints.base));
234 }
235 m_constraints.base = strdup(path.toUtf8().constData());
236 reload();
237}
238
239void LibraryModel::clearConstraints() {
240 if (m_constraints.base) {
241 free(const_cast<char*>(m_constraints.base));
242 }
243 if (m_constraints.filename) {
244 free(const_cast<char*>(m_constraints.filename));
245 }
246 if (m_constraints.title) {
247 free(const_cast<char*>(m_constraints.title));
248 }
249 memset(&m_constraints, 0, sizeof(m_constraints));
250 size_t i;
251 for (i = 0; i < mLibraryListingSize(&m_listings); ++i) {
252 mLibraryEntryFree(mLibraryListingGetPointer(&m_listings, i));
253 }
254 mLibraryListingClear(&m_listings);
255}
256
257void LibraryModel::reload() {
258 mLibraryGetEntries(m_library->library, &m_listings, 0, 0, m_constraints.base ? &m_constraints : nullptr);
259}
260
261void LibraryModel::directoryLoaded(const QString& path) {
262 m_queue.removeOne(path);
263 beginResetModel();
264 endResetModel();
265 if (m_queue.empty()) {
266 emit doneLoading();
267 }
268}
269
270LibraryModel::LibraryColumn::LibraryColumn() {
271}
272
273LibraryModel::LibraryColumn::LibraryColumn(const QString& name, std::function<QString(const mLibraryEntry&)> value, int alignment)
274 : name(name)
275 , value(value)
276 , alignment(alignment)
277{
278}
279
280LibraryModel::LibraryHandle::LibraryHandle(mLibrary* lib, const QString& p)
281 : library(lib)
282 , loader(new LibraryLoader(library))
283 , path(p)
284 , m_ref(1)
285{
286 if (!library) {
287 return;
288 }
289 loader->moveToThread(&m_loaderThread);
290 m_loaderThread.setObjectName("Library Loader Thread");
291 m_loaderThread.start();
292}
293
294LibraryModel::LibraryHandle::~LibraryHandle() {
295 m_loaderThread.quit();
296 m_loaderThread.wait();
297 if (library) {
298 mLibraryDestroy(library);
299 }
300}
301
302void LibraryModel::LibraryHandle::ref() {
303 ++m_ref;
304}
305
306bool LibraryModel::LibraryHandle::deref() {
307 --m_ref;
308 return m_ref > 0;
309}
310
311LibraryLoader::LibraryLoader(mLibrary* library, QObject* parent)
312 : QObject(parent)
313 , m_library(library)
314{
315}
316
317void LibraryLoader::loadDirectory(const QString& path) {
318 mLibraryLoadDirectory(m_library, path.toUtf8().constData());
319 emit directoryLoaded(path);
320}