1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
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.
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"
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))
36 ////////////////////////////////////////////////////////////
37 // Constructor & Destructor
38 ////////////////////////////////////////////////////////////
40 FileListModel::FileListModel(void)
42 m_fileIcon(":/icons/page_white_cd.png")
46 FileListModel::~FileListModel(void)
50 ////////////////////////////////////////////////////////////
52 ////////////////////////////////////////////////////////////
54 int FileListModel::columnCount(const QModelIndex
&parent
) const
59 int FileListModel::rowCount(const QModelIndex
&parent
) const
61 return m_fileList
.count();
64 QVariant
FileListModel::data(const QModelIndex
&index
, int role
) const
66 if((role
== Qt::DisplayRole
|| role
== Qt::ToolTipRole
) && index
.row() < m_fileList
.count() && index
.row() >= 0)
68 switch(index
.column())
71 return m_fileList
.at(index
.row()).fileName();
74 return QDir::toNativeSeparators(m_fileList
.at(index
.row()).filePath());
81 else if(role
== Qt::DecorationRole
&& index
.column() == 0)
91 QVariant
FileListModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
93 if(role
== Qt::DisplayRole
)
95 if(orientation
== Qt::Horizontal
)
100 return QVariant(tr("Title"));
103 return QVariant(tr("Full Path"));
112 if(m_fileList
.count() < 10)
114 return QVariant(QString().sprintf("%d", section
+ 1));
116 else if(m_fileList
.count() < 100)
118 return QVariant(QString().sprintf("%02d", section
+ 1));
120 else if(m_fileList
.count() < 1000)
122 return QVariant(QString().sprintf("%03d", section
+ 1));
126 return QVariant(QString().sprintf("%04d", section
+ 1));
136 void FileListModel::addFile(const QString
&filePath
)
138 QFileInfo
fileInfo(filePath
);
140 for(int i
= 0; i
< m_fileList
.count(); i
++)
142 if(m_fileList
.at(i
).filePath().compare(fileInfo
.canonicalFilePath(), Qt::CaseInsensitive
) == 0)
149 m_fileList
.append(AudioFileModel(fileInfo
.canonicalFilePath(), fileInfo
.baseName()));
153 void FileListModel::addFile(const AudioFileModel
&file
)
155 for(int i
= 0; i
< m_fileList
.count(); i
++)
157 if(m_fileList
.at(i
).filePath().compare(file
.filePath(), Qt::CaseInsensitive
) == 0)
164 m_fileList
.append(file
);
168 bool FileListModel::removeFile(const QModelIndex
&index
)
170 if(index
.row() >= 0 && index
.row() < m_fileList
.count())
173 m_fileList
.removeAt(index
.row());
183 void FileListModel::clearFiles(void)
190 bool FileListModel::moveFile(const QModelIndex
&index
, int delta
)
192 if(delta
!= 0 && index
.row() >= 0 && index
.row() < m_fileList
.count() && index
.row() + delta
>= 0 && index
.row() + delta
< m_fileList
.count())
195 m_fileList
.move(index
.row(), index
.row() + delta
);
205 AudioFileModel
FileListModel::getFile(const QModelIndex
&index
)
207 if(index
.row() >= 0 && index
.row() < m_fileList
.count())
209 return m_fileList
.at(index
.row());
213 return AudioFileModel();
217 AudioFileModel
&FileListModel::operator[] (const QModelIndex
&index
)
219 return m_fileList
[index
.row()];
222 bool FileListModel::setFile(const QModelIndex
&index
, const AudioFileModel
&audioFile
)
224 if(index
.row() >= 0 && index
.row() < m_fileList
.count())
227 m_fileList
.replace(index
.row(), audioFile
);
237 int FileListModel::exportToCsv(const QString
&outFile
)
239 const int nFiles
= m_fileList
.count();
241 bool havePosition
= false, haveTitle
= false, haveArtist
= false, haveAlbum
= false, haveGenre
= false, haveYear
= false, haveComment
= false;
243 for(int i
= 0; i
< nFiles
; i
++)
245 if(m_fileList
.at(i
).filePosition() > 0) havePosition
= true;
246 if(!m_fileList
.at(i
).fileName().isEmpty()) haveTitle
= true;
247 if(!m_fileList
.at(i
).fileArtist().isEmpty()) haveArtist
= true;
248 if(!m_fileList
.at(i
).fileAlbum().isEmpty()) haveAlbum
= true;
249 if(!m_fileList
.at(i
).fileGenre().isEmpty()) haveGenre
= true;
250 if(m_fileList
.at(i
).fileYear() > 0) haveYear
= true;
251 if(!m_fileList
.at(i
).fileComment().isEmpty()) haveComment
= true;
254 if(!(haveTitle
|| haveArtist
|| haveAlbum
|| haveGenre
|| haveYear
|| haveComment
))
256 return CsvError_NoTags
;
261 if(!file
.open(QIODevice::WriteOnly
))
263 return CsvError_FileOpen
;
269 if(havePosition
) line
<< "POSITION";
270 if(haveTitle
) line
<< "TITLE";
271 if(haveArtist
) line
<< "ARTIST";
272 if(haveAlbum
) line
<< "ALBUM";
273 if(haveGenre
) line
<< "GENRE";
274 if(haveYear
) line
<< "YEAR";
275 if(haveComment
) line
<< "COMMENT";
277 if(file
.write(line
.join(";").append("\r\n").toUtf8().prepend("\xef\xbb\xbf")) < 1)
280 return CsvError_FileWrite
;
284 for(int i
= 0; i
< nFiles
; i
++)
288 if(havePosition
) line
<< QString::number(m_fileList
.at(i
).filePosition());
289 if(haveTitle
) line
<< m_fileList
.at(i
).fileName().trimmed();
290 if(haveArtist
) line
<< m_fileList
.at(i
).fileArtist().trimmed();
291 if(haveAlbum
) line
<< m_fileList
.at(i
).fileAlbum().trimmed();
292 if(haveGenre
) line
<< m_fileList
.at(i
).fileGenre().trimmed();
293 if(haveYear
) line
<< QString::number(m_fileList
.at(i
).fileYear());
294 if(haveComment
) line
<< m_fileList
.at(i
).fileComment().trimmed();
296 if(file
.write(line
.replaceInStrings(";", ",").join(";").append("\r\n").toUtf8()) < 1)
299 return CsvError_FileWrite
;
307 int FileListModel::importFromCsv(QWidget
*parent
, const QString
&inFile
)
310 if(!file
.open(QIODevice::ReadOnly
))
312 return CsvError_FileOpen
;
315 QTextCodec
*codec
= NULL
;
316 QByteArray bomCheck
= file
.peek(16);
318 if((!bomCheck
.isEmpty()) && bomCheck
.startsWith("\xef\xbb\xbf"))
320 codec
= QTextCodec::codecForName("UTF-8");
322 else if((!bomCheck
.isEmpty()) && bomCheck
.startsWith("\xff\xfe"))
324 codec
= QTextCodec::codecForName("UTF-16LE");
326 else if((!bomCheck
.isEmpty()) && bomCheck
.startsWith("\xfe\xff"))
328 codec
= QTextCodec::codecForName("UTF-16BE");
332 const QString systemDefault
= tr("(System Default)");
334 QStringList codecList
;
335 codecList
.append(systemDefault
);
336 codecList
.append(lamexp_available_codepages());
338 QInputDialog
*input
= new QInputDialog(parent
);
339 input
->setLabelText(EXPAND(tr("Select ANSI Codepage for CSV file:")));
340 input
->setOkButtonText(tr("OK"));
341 input
->setCancelButtonText(tr("Cancel"));
342 input
->setTextEchoMode(QLineEdit::Normal
);
343 input
->setComboBoxItems(codecList
);
345 if(input
->exec() < 1)
347 LAMEXP_DELETE(input
);
348 return CsvError_Aborted
;
351 if(input
->textValue().compare(systemDefault
, Qt::CaseInsensitive
))
353 qDebug("User-selected codec is: %s", input
->textValue().toLatin1().constData());
354 codec
= QTextCodec::codecForName(input
->textValue().toLatin1().constData());
358 qDebug("Going to use the system's default codec!");
359 codec
= QTextCodec::codecForName("System");
362 LAMEXP_DELETE(input
);
367 //----------------------//
369 QTextStream
stream(&file
);
370 stream
.setAutoDetectUnicode(false);
371 stream
.setCodec(codec
);
373 QString headerLine
= stream
.readLine().simplified();
375 while(headerLine
.isEmpty())
379 qWarning("The file appears to be empty!");
380 return CsvError_FileRead
;
382 qWarning("Skipping a blank line at beginning of CSV file!");
383 headerLine
= stream
.readLine().simplified();
386 QStringList header
= headerLine
.split(";", QString::KeepEmptyParts
);
388 const int nCols
= header
.count();
389 const int nFiles
= m_fileList
.count();
393 qWarning("Header appears to be empty!");
394 return CsvError_FileRead
;
397 bool *ignore
= new bool[nCols
];
398 memset(ignore
, 0, sizeof(bool) * nCols
);
400 for(int i
= 0; i
< nCols
; i
++)
402 if((header
[i
] = header
[i
].trimmed()).isEmpty())
408 //----------------------//
410 for(int i
= 0; i
< nFiles
; i
++)
414 LAMEXP_DELETE_ARRAY(ignore
);
415 return CsvError_Incomplete
;
418 QString line
= stream
.readLine().simplified();
422 qWarning("Skipping a blank line in CSV file!");
426 QStringList data
= line
.split(";", QString::KeepEmptyParts
);
428 if(data
.count() < header
.count())
430 qWarning("Skipping an incomplete line in CSV file!");
434 for(int j
= 0; j
< nCols
; j
++)
440 else if(CHECK_HDR(header
.at(j
), "POSITION"))
443 unsigned int temp
= data
.at(j
).trimmed().toUInt(&ok
);
444 if(ok
) m_fileList
[i
].setFilePosition(temp
);
446 else if(CHECK_HDR(header
.at(j
), "TITLE"))
448 QString temp
= data
.at(j
).trimmed();
449 if(!temp
.isEmpty()) m_fileList
[i
].setFileName(temp
);
451 else if(CHECK_HDR(header
.at(j
), "ARTIST"))
453 QString temp
= data
.at(j
).trimmed();
454 if(!temp
.isEmpty()) m_fileList
[i
].setFileArtist(temp
);
456 else if(CHECK_HDR(header
.at(j
), "ALBUM"))
458 QString temp
= data
.at(j
).trimmed();
459 if(!temp
.isEmpty()) m_fileList
[i
].setFileAlbum(temp
);
461 else if(CHECK_HDR(header
.at(j
), "GENRE"))
463 QString temp
= data
.at(j
).trimmed();
464 if(!temp
.isEmpty()) m_fileList
[i
].setFileGenre(temp
);
466 else if(CHECK_HDR(header
.at(j
), "YEAR"))
469 unsigned int temp
= data
.at(j
).trimmed().toUInt(&ok
);
470 if(ok
) m_fileList
[i
].setFileYear(temp
);
472 else if(CHECK_HDR(header
.at(j
), "COMMENT"))
474 QString temp
= data
.at(j
).trimmed();
475 if(!temp
.isEmpty()) m_fileList
[i
].setFileComment(temp
);
479 qWarning("Unkonw field '%s' will be ignored!", header
.at(j
).toUtf8().constData());
482 if(!checkArray(ignore
, false, nCols
))
484 qWarning("No known fields left, aborting!");
485 return CsvError_NoTags
;
491 //----------------------//
493 LAMEXP_DELETE_ARRAY(ignore
);
497 bool FileListModel::checkArray(const bool *a
, const bool val
, size_t len
)
499 for(size_t i
= 0; i
< len
; i
++)
501 if(a
[i
] == val
) return true;