Version v4.05 is released!
[LameXP.git] / src / Model_FileList.cpp
blob4d1e5596aa1703f2e2e7b5a80109105a8423430e
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 #include "Model_FileList.h"
24 #include "Global.h"
26 #include <QFileInfo>
27 #include <QDir>
28 #include <QFile>
29 #include <QTextCodec>
30 #include <QTextStream>
31 #include <QInputDialog>
33 #define EXPAND(STR) QString(STR).leftJustified(96, ' ')
34 #define CHECK_HDR(STR,NAM) (!(STR).compare((NAM), Qt::CaseInsensitive))
35 #define MAKE_KEY(PATH) (QDir::fromNativeSeparators(PATH).toLower())
37 ////////////////////////////////////////////////////////////
38 // Constructor & Destructor
39 ////////////////////////////////////////////////////////////
41 FileListModel::FileListModel(void)
43 m_blockUpdates(false),
44 m_fileIcon(":/icons/page_white_cd.png")
48 FileListModel::~FileListModel(void)
52 ////////////////////////////////////////////////////////////
53 // Public Functions
54 ////////////////////////////////////////////////////////////
56 int FileListModel::columnCount(const QModelIndex &parent) const
58 return 2;
61 int FileListModel::rowCount(const QModelIndex &parent) const
63 return m_fileList.count();
66 QVariant FileListModel::data(const QModelIndex &index, int role) const
68 if((role == Qt::DisplayRole || role == Qt::ToolTipRole) && index.row() < m_fileList.count() && index.row() >= 0)
70 switch(index.column())
72 case 0:
73 return m_fileStore.value(m_fileList.at(index.row())).fileName();
74 break;
75 case 1:
76 return QDir::toNativeSeparators(m_fileStore.value(m_fileList.at(index.row())).filePath());
77 break;
78 default:
79 return QVariant();
80 break;
83 else if(role == Qt::DecorationRole && index.column() == 0)
85 return m_fileIcon;
87 else
89 return QVariant();
93 QVariant FileListModel::headerData(int section, Qt::Orientation orientation, int role) const
95 if(role == Qt::DisplayRole)
97 if(orientation == Qt::Horizontal)
99 switch(section)
101 case 0:
102 return QVariant(tr("Title"));
103 break;
104 case 1:
105 return QVariant(tr("Full Path"));
106 break;
107 default:
108 return QVariant();
109 break;
112 else
114 if(m_fileList.count() < 10)
116 return QVariant(QString().sprintf("%d", section + 1));
118 else if(m_fileList.count() < 100)
120 return QVariant(QString().sprintf("%02d", section + 1));
122 else if(m_fileList.count() < 1000)
124 return QVariant(QString().sprintf("%03d", section + 1));
126 else
128 return QVariant(QString().sprintf("%04d", section + 1));
132 else
134 return QVariant();
138 void FileListModel::addFile(const QString &filePath)
140 QFileInfo fileInfo(filePath);
141 const QString key = MAKE_KEY(fileInfo.canonicalFilePath());
142 const bool flag = (!m_blockUpdates);
144 if(!m_fileStore.contains(key))
146 if(flag) beginInsertRows(QModelIndex(), m_fileList.count(), m_fileList.count());
147 m_fileStore.insert(key, AudioFileModel(fileInfo.canonicalFilePath(), fileInfo.baseName()));
148 m_fileList.append(key);
149 if(flag) endInsertRows();
150 emit rowAppended();
154 void FileListModel::addFile(const AudioFileModel &file)
156 const QString key = MAKE_KEY(file.filePath());
157 const bool flag = (!m_blockUpdates);
159 if(!m_fileStore.contains(key))
161 if(flag) beginInsertRows(QModelIndex(), m_fileList.count(), m_fileList.count());
162 m_fileStore.insert(key, file);
163 m_fileList.append(key);
164 if(flag) endInsertRows();
165 emit rowAppended();
169 bool FileListModel::removeFile(const QModelIndex &index)
171 if(index.row() >= 0 && index.row() < m_fileList.count())
173 beginResetModel();
174 m_fileStore.remove(m_fileList.at(index.row()));
175 m_fileList.removeAt(index.row());
176 endResetModel();
177 return true;
179 else
181 return false;
185 void FileListModel::clearFiles(void)
187 beginResetModel();
188 m_fileList.clear();
189 m_fileStore.clear();
190 endResetModel();
193 bool FileListModel::moveFile(const QModelIndex &index, int delta)
195 if(delta != 0 && index.row() >= 0 && index.row() < m_fileList.count() && index.row() + delta >= 0 && index.row() + delta < m_fileList.count())
197 beginResetModel();
198 m_fileList.move(index.row(), index.row() + delta);
199 endResetModel();
200 return true;
202 else
204 return false;
208 AudioFileModel FileListModel::getFile(const QModelIndex &index)
210 if(index.row() >= 0 && index.row() < m_fileList.count())
212 return m_fileStore.value(m_fileList.at(index.row()));
214 else
216 return AudioFileModel();
220 AudioFileModel &FileListModel::operator[] (const QModelIndex &index)
222 const QString key = m_fileList.at(index.row());
223 return m_fileStore[key];
226 bool FileListModel::setFile(const QModelIndex &index, const AudioFileModel &audioFile)
228 if(index.row() >= 0 && index.row() < m_fileList.count())
230 const QString oldKey = m_fileList.at(index.row());
231 const QString newKey = MAKE_KEY(audioFile.filePath());
233 beginResetModel();
234 m_fileList.replace(index.row(), newKey);
235 m_fileStore.remove(oldKey);
236 m_fileStore.insert(newKey, audioFile);
237 endResetModel();
238 return true;
240 else
242 return false;
246 int FileListModel::exportToCsv(const QString &outFile)
248 const int nFiles = m_fileList.count();
250 bool havePosition = false, haveTitle = false, haveArtist = false, haveAlbum = false, haveGenre = false, haveYear = false, haveComment = false;
252 for(int i = 0; i < nFiles; i++)
254 AudioFileModel current = m_fileStore.value(m_fileList.at(i));
256 if(current.filePosition() > 0) havePosition = true;
257 if(!current.fileName().isEmpty()) haveTitle = true;
258 if(!current.fileArtist().isEmpty()) haveArtist = true;
259 if(!current.fileAlbum().isEmpty()) haveAlbum = true;
260 if(!current.fileGenre().isEmpty()) haveGenre = true;
261 if(current.fileYear() > 0) haveYear = true;
262 if(!current.fileComment().isEmpty()) haveComment = true;
265 if(!(haveTitle || haveArtist || haveAlbum || haveGenre || haveYear || haveComment))
267 return CsvError_NoTags;
270 QFile file(outFile);
272 if(!file.open(QIODevice::WriteOnly))
274 return CsvError_FileOpen;
276 else
278 QStringList line;
280 if(havePosition) line << "POSITION";
281 if(haveTitle) line << "TITLE";
282 if(haveArtist) line << "ARTIST";
283 if(haveAlbum) line << "ALBUM";
284 if(haveGenre) line << "GENRE";
285 if(haveYear) line << "YEAR";
286 if(haveComment) line << "COMMENT";
288 if(file.write(line.join(";").append("\r\n").toUtf8().prepend("\xef\xbb\xbf")) < 1)
290 file.close();
291 return CsvError_FileWrite;
295 for(int i = 0; i < nFiles; i++)
297 QStringList line;
298 AudioFileModel current = m_fileStore.value(m_fileList.at(i));
300 if(havePosition) line << QString::number(current.filePosition());
301 if(haveTitle) line << current.fileName().trimmed();
302 if(haveArtist) line << current.fileArtist().trimmed();
303 if(haveAlbum) line << current.fileAlbum().trimmed();
304 if(haveGenre) line << current.fileGenre().trimmed();
305 if(haveYear) line << QString::number(current.fileYear());
306 if(haveComment) line << current.fileComment().trimmed();
308 if(file.write(line.replaceInStrings(";", ",").join(";").append("\r\n").toUtf8()) < 1)
310 file.close();
311 return CsvError_FileWrite;
315 file.close();
316 return CsvError_OK;
319 int FileListModel::importFromCsv(QWidget *parent, const QString &inFile)
321 QFile file(inFile);
322 if(!file.open(QIODevice::ReadOnly))
324 return CsvError_FileOpen;
327 QTextCodec *codec = NULL;
328 QByteArray bomCheck = file.peek(16);
330 if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
332 codec = QTextCodec::codecForName("UTF-8");
334 else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
336 codec = QTextCodec::codecForName("UTF-16LE");
338 else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
340 codec = QTextCodec::codecForName("UTF-16BE");
342 else
344 const QString systemDefault = tr("(System Default)");
346 QStringList codecList;
347 codecList.append(systemDefault);
348 codecList.append(lamexp_available_codepages());
350 QInputDialog *input = new QInputDialog(parent);
351 input->setLabelText(EXPAND(tr("Select ANSI Codepage for CSV file:")));
352 input->setOkButtonText(tr("OK"));
353 input->setCancelButtonText(tr("Cancel"));
354 input->setTextEchoMode(QLineEdit::Normal);
355 input->setComboBoxItems(codecList);
357 if(input->exec() < 1)
359 LAMEXP_DELETE(input);
360 return CsvError_Aborted;
363 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
365 qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
366 codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
368 else
370 qDebug("Going to use the system's default codec!");
371 codec = QTextCodec::codecForName("System");
374 LAMEXP_DELETE(input);
377 bomCheck.clear();
379 //----------------------//
381 QTextStream stream(&file);
382 stream.setAutoDetectUnicode(false);
383 stream.setCodec(codec);
385 QString headerLine = stream.readLine().simplified();
387 while(headerLine.isEmpty())
389 if(stream.atEnd())
391 qWarning("The file appears to be empty!");
392 return CsvError_FileRead;
394 qWarning("Skipping a blank line at beginning of CSV file!");
395 headerLine = stream.readLine().simplified();
398 QStringList header = headerLine.split(";", QString::KeepEmptyParts);
400 const int nCols = header.count();
401 const int nFiles = m_fileList.count();
403 if(nCols < 1)
405 qWarning("Header appears to be empty!");
406 return CsvError_FileRead;
409 bool *ignore = new bool[nCols];
410 memset(ignore, 0, sizeof(bool) * nCols);
412 for(int i = 0; i < nCols; i++)
414 if((header[i] = header[i].trimmed()).isEmpty())
416 ignore[i] = true;
420 //----------------------//
422 for(int i = 0; i < nFiles; i++)
424 if(stream.atEnd())
426 LAMEXP_DELETE_ARRAY(ignore);
427 return CsvError_Incomplete;
430 QString line = stream.readLine().simplified();
432 if(line.isEmpty())
434 qWarning("Skipping a blank line in CSV file!");
435 continue;
438 QStringList data = line.split(";", QString::KeepEmptyParts);
440 if(data.count() < header.count())
442 qWarning("Skipping an incomplete line in CSV file!");
443 continue;
446 const QString key = m_fileList[i];
448 for(int j = 0; j < nCols; j++)
450 if(ignore[j])
452 continue;
454 else if(CHECK_HDR(header.at(j), "POSITION"))
456 bool ok = false;
457 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
458 if(ok) m_fileStore[key].setFilePosition(temp);
460 else if(CHECK_HDR(header.at(j), "TITLE"))
462 QString temp = data.at(j).trimmed();
463 if(!temp.isEmpty()) m_fileStore[key].setFileName(temp);
465 else if(CHECK_HDR(header.at(j), "ARTIST"))
467 QString temp = data.at(j).trimmed();
468 if(!temp.isEmpty()) m_fileStore[key].setFileArtist(temp);
470 else if(CHECK_HDR(header.at(j), "ALBUM"))
472 QString temp = data.at(j).trimmed();
473 if(!temp.isEmpty()) m_fileStore[key].setFileAlbum(temp);
475 else if(CHECK_HDR(header.at(j), "GENRE"))
477 QString temp = data.at(j).trimmed();
478 if(!temp.isEmpty()) m_fileStore[key].setFileGenre(temp);
480 else if(CHECK_HDR(header.at(j), "YEAR"))
482 bool ok = false;
483 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
484 if(ok) m_fileStore[key].setFileYear(temp);
486 else if(CHECK_HDR(header.at(j), "COMMENT"))
488 QString temp = data.at(j).trimmed();
489 if(!temp.isEmpty()) m_fileStore[key].setFileComment(temp);
491 else
493 qWarning("Unkonw field '%s' will be ignored!", header.at(j).toUtf8().constData());
494 ignore[j] = true;
496 if(!checkArray(ignore, false, nCols))
498 qWarning("No known fields left, aborting!");
499 return CsvError_NoTags;
505 //----------------------//
507 LAMEXP_DELETE_ARRAY(ignore);
508 return CsvError_OK;
511 bool FileListModel::checkArray(const bool *a, const bool val, size_t len)
513 for(size_t i = 0; i < len; i++)
515 if(a[i] == val) return true;
518 return false;