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["filename"] = {
24 tr("Filename"),
25 [](const mLibraryEntry& e) -> QString {
26 return QString::fromUtf8(e.filename);
27 }
28 };
29 s_columns["size"] = {
30 tr("Size"),
31 [](const mLibraryEntry& e) -> QString {
32 double size = e.filesize;
33 QString unit = "B";
34 if (size > 1024.0) {
35 size /= 1024.0;
36 unit = "kiB";
37 }
38 if (size > 1024.0) {
39 size /= 1024.0;
40 unit = "MiB";
41 }
42 return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit);
43 }
44 };
45 s_columns["platform"] = {
46 tr("Platform"),
47 [](const mLibraryEntry& e) -> QString {
48 int platform = e.platform;
49 switch (platform) {
50#ifdef M_CORE_GBA
51 case PLATFORM_GBA:
52 return tr("GBA");
53#endif
54#ifdef M_CORE_GB
55 case PLATFORM_GB:
56 return tr("GB");
57#endif
58 default:
59 return tr("?");
60 }
61 }
62 };
63 s_columns["location"] = {
64 tr("Location"),
65 [](const mLibraryEntry& e) -> QString {
66 return QString::fromUtf8(e.base);
67 }
68 };
69 }
70 if (!path.isNull()) {
71 if (s_handles.contains(path)) {
72 m_library = s_handles[path];
73 m_library->ref();
74 } else {
75 m_library = new LibraryHandle(mLibraryLoad(path.toUtf8().constData()), path);
76 s_handles[path] = m_library;
77 }
78 } else {
79 m_library = new LibraryHandle(mLibraryCreateEmpty());
80 }
81 memset(&m_constraints, 0, sizeof(m_constraints));
82 m_constraints.platform = PLATFORM_NONE;
83 m_columns.append(s_columns["filename"]);
84 m_columns.append(s_columns["location"]);
85 m_columns.append(s_columns["platform"]);
86 m_columns.append(s_columns["size"]);
87
88 connect(m_library->loader, SIGNAL(directoryLoaded(const QString&)), this, SLOT(directoryLoaded(const QString&)));
89}
90
91LibraryModel::~LibraryModel() {
92 clearConstraints();
93 if (!m_library->deref()) {
94 s_handles.remove(m_library->path);
95 delete m_library;
96 }
97}
98
99void LibraryModel::loadDirectory(const QString& path) {
100 m_queue.append(path);
101 QMetaObject::invokeMethod(m_library->loader, "loadDirectory", Q_ARG(const QString&, path));
102}
103
104bool LibraryModel::entryAt(int row, mLibraryEntry* out) const {
105 mLibraryListing entries;
106 mLibraryListingInit(&entries, 0);
107 if (!mLibraryGetEntries(m_library->library, &entries, 1, row, &m_constraints)) {
108 mLibraryListingDeinit(&entries);
109 return false;
110 }
111 *out = *mLibraryListingGetPointer(&entries, 0);
112 mLibraryListingDeinit(&entries);
113 return true;
114}
115
116VFile* LibraryModel::openVFile(const QModelIndex& index) const {
117 mLibraryEntry entry;
118 if (!entryAt(index.row(), &entry)) {
119 return nullptr;
120 }
121 return mLibraryOpenVFile(m_library->library, &entry);
122}
123
124QString LibraryModel::filename(const QModelIndex& index) const {
125 mLibraryEntry entry;
126 if (!entryAt(index.row(), &entry)) {
127 return QString();
128 }
129 return QString::fromUtf8(entry.filename);
130}
131
132QString LibraryModel::location(const QModelIndex& index) const {
133 mLibraryEntry entry;
134 if (!entryAt(index.row(), &entry)) {
135 return QString();
136 }
137 return QString::fromUtf8(entry.base);
138}
139
140QVariant LibraryModel::data(const QModelIndex& index, int role) const {
141 if (!index.isValid()) {
142 return QVariant();
143 }
144 mLibraryEntry entry;
145 if (!entryAt(index.row(), &entry)) {
146 return QVariant();
147 }
148 if (role == Qt::UserRole) {
149 return QVariant::fromValue(entry);
150 }
151 if (index.column() >= m_columns.count()) {
152 return QVariant();
153 }
154 switch (role) {
155 case Qt::DisplayRole:
156 return m_columns[index.column()].value(entry);
157 case Qt::SizeHintRole: {
158 QFontMetrics fm((QFont()));
159 return fm.size(Qt::TextSingleLine, m_columns[index.column()].value(entry));
160 }
161 default:
162 return QVariant();
163 }
164}
165
166QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
167 if (role != Qt::DisplayRole) {
168 return QAbstractItemModel::headerData(section, orientation, role);
169 }
170 if (orientation == Qt::Horizontal) {
171 if (section >= m_columns.count()) {
172 return QVariant();
173 }
174 return m_columns[section].name;
175 }
176 return section;
177}
178
179QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
180 if (parent.isValid()) {
181 return QModelIndex();
182 }
183 return createIndex(row, column, nullptr);
184}
185
186QModelIndex LibraryModel::parent(const QModelIndex&) const {
187 return QModelIndex();
188}
189
190int LibraryModel::columnCount(const QModelIndex& parent) const {
191 if (parent.isValid()) {
192 return 0;
193 }
194 return m_columns.count();
195}
196
197int LibraryModel::rowCount(const QModelIndex& parent) const {
198 if (parent.isValid()) {
199 return 0;
200 }
201 return mLibraryCount(m_library->library, &m_constraints);
202}
203
204void LibraryModel::constrainBase(const QString& path) {
205 if (m_constraints.base) {
206 free(const_cast<char*>(m_constraints.base));
207 }
208 m_constraints.base = strdup(path.toUtf8().constData());
209}
210
211void LibraryModel::clearConstraints() {
212 if (m_constraints.base) {
213 free(const_cast<char*>(m_constraints.base));
214 }
215 if (m_constraints.filename) {
216 free(const_cast<char*>(m_constraints.filename));
217 }
218 if (m_constraints.title) {
219 free(const_cast<char*>(m_constraints.title));
220 }
221 memset(&m_constraints, 0, sizeof(m_constraints));
222}
223
224void LibraryModel::directoryLoaded(const QString& path) {
225 m_queue.removeOne(path);
226 beginResetModel();
227 endResetModel();
228 if (m_queue.empty()) {
229 emit doneLoading();
230 }
231}
232
233
234LibraryModel::LibraryHandle::LibraryHandle(mLibrary* lib, const QString& p)
235 : library(lib)
236 , loader(new LibraryLoader(library))
237 , path(p)
238 , m_ref(1)
239{
240 if (!library) {
241 return;
242 }
243 loader->moveToThread(&m_loaderThread);
244 m_loaderThread.setObjectName("Library Loader Thread");
245 m_loaderThread.start();
246}
247
248LibraryModel::LibraryHandle::~LibraryHandle() {
249 m_loaderThread.quit();
250 m_loaderThread.wait();
251 mLibraryDestroy(library);
252}
253
254void LibraryModel::LibraryHandle::ref() {
255 ++m_ref;
256}
257
258bool LibraryModel::LibraryHandle::deref() {
259 --m_ref;
260 return m_ref > 0;
261}
262
263LibraryLoader::LibraryLoader(mLibrary* library, QObject* parent)
264 : QObject(parent)
265 , m_library(library)
266{
267}
268
269void LibraryLoader::loadDirectory(const QString& path) {
270 mLibraryLoadDirectory(m_library, path.toUtf8().constData());
271 emit directoryLoaded(path);
272}