1 #include "cbmdosfsmodel.h"
2 #include "cbmdosfilemimedata.h"
3 #include "editoperationcheck.h"
4 #include "v1541commander.h"
5 #include "petsciistr.h"
6 #include "settings.h"
7 #include "utils.h"
8 
9 #include <QFont>
10 #include <QList>
11 #include <QRegularExpression>
12 #include <QTemporaryDir>
13 #include <QUrl>
14 
15 #include <1541img/cbmdosfs.h>
16 #include <1541img/cbmdosvfs.h>
17 #include <1541img/cbmdosvfseventargs.h>
18 #include <1541img/cbmdosfile.h>
19 #include <1541img/event.h>
20 #include <1541img/petscii.h>
21 
evhdl(void * receiver,int id,const void * sender,const void * args)22 static void evhdl(void *receiver, int id, const void *sender, const void *args)
23 {
24     (void) id;
25     (void) sender;
26 
27     CbmdosFsModel *model = (CbmdosFsModel *)receiver;
28     const CbmdosVfsEventArgs *eventArgs = (const CbmdosVfsEventArgs *)args;
29     model->fsChanged(eventArgs);
30 }
31 
32 class CbmdosFsModel::priv
33 {
34     public:
35 	priv();
36 	~priv();
37 	QTemporaryDir *tmpDir;
38 	CbmdosFs *fs;
39 	QSizeF itemSize;
40 };
41 
priv()42 CbmdosFsModel::priv::priv() :
43     tmpDir(0), fs(0), itemSize()
44 {}
45 
~priv()46 CbmdosFsModel::priv::~priv()
47 {
48     delete tmpDir;
49 }
50 
CbmdosFsModel(QObject * parent)51 CbmdosFsModel::CbmdosFsModel(QObject *parent)
52     : QAbstractListModel(parent)
53 {
54     d = new priv();
55 
56     connect(&cmdr, &V1541Commander::lowerCaseChanged,
57             this, [this](bool lowerCase){
58                 (void) lowerCase;
59                 emit dataChanged(createIndex(0, 0),
60                         createIndex(rowCount()-1, 0),
61                         QVector<int>(Qt::DisplayRole));
62             });
63 }
64 
~CbmdosFsModel()65 CbmdosFsModel::~CbmdosFsModel()
66 {
67     if (d->fs)
68     {
69 	CbmdosVfs *vfs = CbmdosFs_vfs(d->fs);
70 	Event_unregister(CbmdosVfs_changedEvent(vfs), this, evhdl);
71     }
72     delete d;
73 }
74 
fsChanged(const CbmdosVfsEventArgs * args)75 void CbmdosFsModel::fsChanged(const CbmdosVfsEventArgs *args)
76 {
77     emit modified();
78     switch (args->what)
79     {
80 	case CbmdosVfsEventArgs::CVE_FILECHANGED:
81 	    {
82 		QModelIndex pos = createIndex(args->filepos + 1, 0);
83 		emit dataChanged(pos, pos, QVector<int>(Qt::DisplayRole));
84 	    }
85 	    break;
86 
87 	case CbmdosVfsEventArgs::CVE_FILEMOVED:
88 	    if (args->targetpos > args->filepos)
89 	    {
90 		QModelIndex from = createIndex(args->filepos + 1, 0);
91 		QModelIndex to = createIndex(args->targetpos + 1, 0);
92 		emit dataChanged(from, to, QVector<int>(Qt::DisplayRole));
93 		emit selectedIndexChanged(to,
94 			QItemSelectionModel::ClearAndSelect);
95 	    }
96 	    else
97 	    {
98 		QModelIndex from = createIndex(args->targetpos + 1, 0);
99 		QModelIndex to = createIndex(args->filepos + 1, 0);
100 		emit dataChanged(from, to, QVector<int>(Qt::DisplayRole));
101 		emit selectedIndexChanged(from,
102 			QItemSelectionModel::ClearAndSelect);
103 	    }
104 	    break;
105 
106 	case CbmdosVfsEventArgs::CVE_NAMECHANGED:
107 	case CbmdosVfsEventArgs::CVE_IDCHANGED:
108 	case CbmdosVfsEventArgs::CVE_DOSVERCHANGED:
109 	    {
110 		QModelIndex pos = createIndex(0, 0);
111 		emit dataChanged(pos, pos, QVector<int>(Qt::DisplayRole));
112 	    }
113 	    break;
114 
115 	case CbmdosVfsEventArgs::CVE_FILEDELETED:
116 	    {
117 		endRemoveRows();
118 		QModelIndex last = createIndex(rowCount() - 1, 0);
119 		emit dataChanged(last, last, QVector<int>(Qt::DisplayRole));
120 	    }
121 	    break;
122 
123 	case CbmdosVfsEventArgs::CVE_FILEADDED:
124 	    {
125 		endInsertRows();
126 		QModelIndex last = createIndex(rowCount() - 1, 0);
127 		emit dataChanged(last, last, QVector<int>(Qt::DisplayRole));
128 		QModelIndex at = createIndex(args->filepos + 1, 0);
129 		emit selectedIndexChanged(at,
130 			QItemSelectionModel::ClearAndSelect);
131 	    }
132 	    break;
133 
134 	default:
135 	    break;
136     }
137 }
138 
fs() const139 CbmdosFs *CbmdosFsModel::fs() const
140 {
141     return d->fs;
142 }
143 
setFs(CbmdosFs * fs)144 void CbmdosFsModel::setFs(CbmdosFs *fs)
145 {
146     if (d->fs)
147     {
148 	CbmdosVfs *vfs = CbmdosFs_vfs(d->fs);
149 	Event_unregister(CbmdosVfs_changedEvent(vfs), this, evhdl);
150 	beginRemoveRows(QModelIndex(), 0, CbmdosVfs_fileCount(vfs)+1);
151 	d->fs = 0;
152 	endRemoveRows();
153     }
154     if (fs)
155     {
156 	CbmdosVfs *vfs = CbmdosFs_vfs(fs);
157 	beginInsertRows(QModelIndex(), 0, CbmdosVfs_fileCount(vfs)+1);
158 	d->fs = fs;
159 	Event_register(CbmdosVfs_changedEvent(vfs), this, evhdl);
160 	endInsertRows();
161     }
162 }
163 
deleteAt(const QModelIndex & at)164 void CbmdosFsModel::deleteAt(const QModelIndex &at)
165 {
166     if (at.row() > 0 && at.row() < rowCount() - 1)
167     {
168 	beginRemoveRows(QModelIndex(), at.row(), at.row());
169 	CbmdosVfs_deleteAt(CbmdosFs_vfs(d->fs), at.row() - 1);
170     }
171 }
172 
addFile(const QModelIndex & at,CbmdosFile * newFile)173 void CbmdosFsModel::addFile(const QModelIndex &at, CbmdosFile *newFile)
174 {
175     EditOperationCheck check(newFile);
176     emit checkEditOperation(check);
177     if (!check.allowed())
178     {
179 	CbmdosFile_destroy(newFile);
180 	return;
181     }
182     if (at.isValid() && at.row() > 0)
183     {
184 	beginInsertRows(QModelIndex(), at.row(), at.row());
185 	CbmdosVfs_insert(CbmdosFs_vfs(d->fs), newFile, at.row() - 1);
186     }
187     else
188     {
189 	beginInsertRows(QModelIndex(), rowCount() - 1, rowCount() - 1);
190 	CbmdosVfs_append(CbmdosFs_vfs(d->fs), newFile);
191     }
192 }
193 
setItemSize(QSizeF size)194 void CbmdosFsModel::setItemSize(QSizeF size)
195 {
196     d->itemSize = size;
197 }
198 
tmpDir() const199 const QTemporaryDir *CbmdosFsModel::tmpDir() const
200 {
201     if (!d->tmpDir) d->tmpDir = new QTemporaryDir();
202     return d->tmpDir;
203 }
204 
rowCount(const QModelIndex & parent) const205 int CbmdosFsModel::rowCount(const QModelIndex &parent) const
206 {
207     (void) parent;
208     if (!d->fs) return 0;
209     const CbmdosVfs *vfs = CbmdosFs_rvfs(d->fs);
210     return CbmdosVfs_fileCount(vfs) + 2;
211 }
212 
data(const QModelIndex & index,int role) const213 QVariant CbmdosFsModel::data(const QModelIndex &index, int role) const
214 {
215     uint8_t buffer[28];
216 
217     if (role == Qt::FontRole)
218     {
219 	return cmdr.c64font();
220     }
221 
222     if (role == Qt::SizeHintRole)
223     {
224 	return d->itemSize;
225     }
226 
227     if (!d->fs) return QVariant();
228 
229     if (role != Qt::DisplayRole)
230     {
231 	return QVariant();
232     }
233 
234     int row = index.row();
235     int rowcount = rowCount();
236     const CbmdosVfs *vfs = CbmdosFs_rvfs(d->fs);
237 
238     if (row > 0 && row < rowcount - 1)
239     {
240 	const CbmdosFile *file = CbmdosVfs_rfile(vfs, row-1);
241 	CbmdosFile_getDirLine(file, buffer);
242 	PetsciiStr dirLine((char *)buffer, 28);
243 	return dirLine.toString(cmdr.settings().lowercase());
244     }
245 
246     if (row == 0)
247     {
248 	QString heading("0 ");
249 	CbmdosVfs_getDirHeader(vfs, buffer);
250 	PetsciiStr dirHeader((char *)buffer, 24);
251 	heading.append(dirHeader.toString(cmdr.settings().lowercase(), true));
252 	return heading;
253     }
254 
255     CbmdosFs_getFreeBlocksLine(d->fs, buffer);
256     PetsciiStr freeLine((char *)buffer, 16);
257     return freeLine.toString(cmdr.settings().lowercase());
258 }
259 
flags(const QModelIndex & index) const260 Qt::ItemFlags CbmdosFsModel::flags(const QModelIndex &index) const
261 {
262     if (!d->fs) return Qt::ItemNeverHasChildren;
263     if (!index.isValid()) return Qt::ItemIsDropEnabled;
264 
265     int row = index.row();
266     int rowcount = rowCount();
267     if (row > 0 && row < rowcount - 1)
268     {
269 	return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled
270 	    | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
271     }
272 
273     return Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
274 }
275 
mimeTypes() const276 QStringList CbmdosFsModel::mimeTypes() const
277 {
278     return QStringList({
279 	    CbmdosFileMimeData::internalFormat(),
280 	    "text/uri-list"});
281 }
282 
mimeData(const QModelIndexList & indexes) const283 QMimeData *CbmdosFsModel::mimeData(const QModelIndexList &indexes) const
284 {
285     if (indexes.empty()) return 0;
286     CbmdosFileMimeData *mimeData = new CbmdosFileMimeData(this);
287     CbmdosVfs *vfs = CbmdosFs_vfs(d->fs);
288     for (QModelIndexList::const_iterator i = indexes.cbegin();
289 	    i != indexes.cend(); ++i)
290     {
291 	if (i->isValid())
292 	{
293 	    int row = i->row();
294 	    if (row > 0 && row < rowCount() - 1)
295 	    {
296 		mimeData->addFile(CbmdosVfs_rfile(vfs, row-1), row-1);
297 	    }
298 	}
299     }
300     if (mimeData->files().empty())
301     {
302 	delete mimeData;
303 	return 0;
304     }
305     return mimeData;
306 }
307 
supportedDropActions() const308 Qt::DropActions CbmdosFsModel::supportedDropActions() const
309 {
310     return Qt::MoveAction|Qt::CopyAction;
311 }
312 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)313 bool CbmdosFsModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
314 	int row, int column, const QModelIndex &parent)
315 {
316     (void) parent;
317     if (!d->fs) return false;
318     if (CbmdosFs_status(d->fs) & CFS_BROKEN) return false;
319     const CbmdosFileMimeData *fileData =
320 	qobject_cast<const CbmdosFileMimeData *>(data);
321     if (fileData)
322     {
323 	if (action == Qt::MoveAction)
324 	{
325 	    if (row < 0 || column < 0) return false;
326 	    if (row < 1) ++row;
327 	    int to = row - 1;
328 	    QList<int> fromRows;
329 	    for (int r = 0; r < rowCount() - 2; ++r) fromRows.append(r);
330 	    CbmdosVfs *vfs = CbmdosFs_vfs(d->fs);
331 	    for (int i = 0; i < fileData->filePositions().size(); ++i)
332 	    {
333 		int from = fromRows.at(fileData->filePositions().at(i));
334 		if (to > from) --to;
335 		CbmdosVfs_move(vfs, to, from);
336 		int oldfrom = fromRows.at(to);
337 		fromRows.removeAt(to);
338 		fromRows.insert(from, oldfrom);
339 		++to;
340 	    }
341 	    return true;
342 	}
343 	if (action == Qt::CopyAction)
344 	{
345 	    if (fileData->files().empty()) return false;
346 	    for (int i = 0; i < fileData->files().size(); ++i)
347 	    {
348 		CbmdosFile *copy = CbmdosFile_clone(fileData->files().at(i));
349 		addFile(createIndex(row+i, column), copy);
350 	    }
351 	    return true;
352 	}
353     }
354     else if (data->hasUrls())
355     {
356 	QList<QUrl> urls = data->urls();
357 	int fileno = 0;
358 	for (QList<QUrl>::const_iterator i = urls.cbegin();
359 		i != urls.cend(); ++i)
360 	{
361 	    if (i->isLocalFile())
362 	    {
363 		QFileInfo file(i->toLocalFile());
364 		FILE *fp = qfopen(file.canonicalFilePath(), "rb");
365 		if (fp)
366 		{
367 		    CbmdosFile *newFile = CbmdosFile_create();
368 		    char name[17];
369 		    QByteArray basename = file.completeBaseName().toUtf8();
370 		    size_t nameLen = petscii_fromUtf8(name, 17,
371 			    basename.data(), basename.size(), PC_UPPER, 1, 0);
372 		    if(--nameLen > 16) nameLen = 16;
373 		    CbmdosFile_setName(newFile, name, nameLen);
374 		    QString ext = file.suffix().toLower();
375 		    if (ext == "seq" || QRegularExpression("^s\\d{2}$")
376 			    .match(ext).hasMatch())
377 		    {
378 			CbmdosFile_setType(newFile, CbmdosFileType::CFT_SEQ);
379 		    }
380 		    else if (ext == "usr" || QRegularExpression("^u\\d{2}$").
381 			    match(ext).hasMatch())
382 		    {
383 			CbmdosFile_setType(newFile, CbmdosFileType::CFT_USR);
384 		    }
385 		    else if (ext == "prg" || QRegularExpression("^p\\d{2}$").
386 			    match(ext).hasMatch())
387 		    {
388 			CbmdosFile_setType(newFile, CbmdosFileType::CFT_PRG);
389 		    }
390 		    else if (ext == "rel" || QRegularExpression("^r\\d{2}$").
391 			    match(ext).hasMatch())
392 		    {
393 			CbmdosFile_setType(newFile, CbmdosFileType::CFT_REL);
394 		    }
395 		    else
396 		    {
397 			CbmdosFile_setType(newFile,
398 				cmdr.settings().defaultImportType());
399 		    }
400 		    int rc = CbmdosFile_import(newFile, fp);
401 		    fclose(fp);
402 		    if (rc < 0)
403 		    {
404 			CbmdosFile_destroy(newFile);
405 			break;
406 		    }
407 		    addFile(createIndex(row + (fileno++), column), newFile);
408 		}
409 	    }
410 	}
411 	return fileno > 0;
412     }
413     return false;
414 }
415