OSDN Git Service

Update MediaInfo binaries to v0.7.53 (2012-01-24), compiled with ICL 12.1.6 and MSVC...
[lamexp/LameXP.git] / src / Model_FileList.cpp
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.
14 //
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.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Model_FileList.h"
23
24 #include "Global.h"
25
26 #include <QFileInfo>
27 #include <QDir>
28 #include <QFile>
29 #include <QTextCodec>
30 #include <QTextStream>
31 #include <QInputDialog>
32
33 #define EXPAND(STR) QString(STR).leftJustified(96, ' ')
34 #define CHECK_HDR(STR,NAM) (!(STR).compare((NAM), Qt::CaseInsensitive))
35
36 ////////////////////////////////////////////////////////////
37 // Constructor & Destructor
38 ////////////////////////////////////////////////////////////
39
40 FileListModel::FileListModel(void)
41 :
42         m_fileIcon(":/icons/page_white_cd.png")
43 {
44 }
45
46 FileListModel::~FileListModel(void)
47 {
48 }
49
50 ////////////////////////////////////////////////////////////
51 // Public Functions
52 ////////////////////////////////////////////////////////////
53
54 int FileListModel::columnCount(const QModelIndex &parent) const
55 {
56         return 2;
57 }
58
59 int FileListModel::rowCount(const QModelIndex &parent) const
60 {
61         return m_fileList.count();
62 }
63
64 QVariant FileListModel::data(const QModelIndex &index, int role) const
65 {
66         if((role == Qt::DisplayRole || role == Qt::ToolTipRole) && index.row() < m_fileList.count() && index.row() >= 0)
67         {
68                 switch(index.column())
69                 {
70                 case 0:
71                         return m_fileList.at(index.row()).fileName();
72                         break;
73                 case 1:
74                         return QDir::toNativeSeparators(m_fileList.at(index.row()).filePath());
75                         break;
76                 default:
77                         return QVariant();
78                         break;
79                 }               
80         }
81         else if(role == Qt::DecorationRole && index.column() == 0)
82         {
83                 return m_fileIcon;
84         }
85         else
86         {
87                 return QVariant();
88         }
89 }
90
91 QVariant FileListModel::headerData(int section, Qt::Orientation orientation, int role) const
92 {
93         if(role == Qt::DisplayRole)
94         {
95                 if(orientation == Qt::Horizontal)
96                 {
97                         switch(section)
98                         {
99                         case 0:
100                                 return QVariant(tr("Title"));
101                                 break;
102                         case 1:
103                                 return QVariant(tr("Full Path"));
104                                 break;
105                         default:
106                                 return QVariant();
107                                 break;
108                         }
109                 }
110                 else
111                 {
112                         if(m_fileList.count() < 10)
113                         {
114                                 return QVariant(QString().sprintf("%d", section + 1));
115                         }
116                         else if(m_fileList.count() < 100)
117                         {
118                                 return QVariant(QString().sprintf("%02d", section + 1));
119                         }
120                         else if(m_fileList.count() < 1000)
121                         {
122                                 return QVariant(QString().sprintf("%03d", section + 1));
123                         }
124                         else
125                         {
126                                 return QVariant(QString().sprintf("%04d", section + 1));
127                         }
128                 }
129         }
130         else
131         {
132                 return QVariant();
133         }
134 }
135
136 void FileListModel::addFile(const QString &filePath)
137 {
138         QFileInfo fileInfo(filePath);
139
140         for(int i = 0; i < m_fileList.count(); i++)
141         {
142                 if(m_fileList.at(i).filePath().compare(fileInfo.canonicalFilePath(), Qt::CaseInsensitive) == 0)
143                 {
144                         return;
145                 }
146         }
147         
148         beginResetModel();
149         m_fileList.append(AudioFileModel(fileInfo.canonicalFilePath(), fileInfo.baseName()));
150         endResetModel();
151 }
152
153 void FileListModel::addFile(const AudioFileModel &file)
154 {
155         for(int i = 0; i < m_fileList.count(); i++)
156         {
157                 if(m_fileList.at(i).filePath().compare(file.filePath(), Qt::CaseInsensitive) == 0)
158                 {
159                         return;
160                 }
161         }
162         
163         beginResetModel();
164         m_fileList.append(file);
165         endResetModel();
166 }
167
168 bool FileListModel::removeFile(const QModelIndex &index)
169 {
170         if(index.row() >= 0 && index.row() < m_fileList.count())
171         {
172                 beginResetModel();
173                 m_fileList.removeAt(index.row());
174                 endResetModel();
175                 return true;
176         }
177         else
178         {
179                 return false;
180         }
181 }
182
183 void FileListModel::clearFiles(void)
184 {
185         beginResetModel();
186         m_fileList.clear();
187         endResetModel();
188 }
189
190 bool FileListModel::moveFile(const QModelIndex &index, int delta)
191 {
192         if(delta != 0 && index.row() >= 0 && index.row() < m_fileList.count() && index.row() + delta >= 0 && index.row() + delta < m_fileList.count())
193         {
194                 beginResetModel();
195                 m_fileList.move(index.row(), index.row() + delta);
196                 endResetModel();
197                 return true;
198         }
199         else
200         {
201                 return false;
202         }
203 }
204
205 AudioFileModel FileListModel::getFile(const QModelIndex &index)
206 {
207         if(index.row() >= 0 && index.row() < m_fileList.count())
208         {
209                 return m_fileList.at(index.row());
210         }
211         else
212         {
213                 return AudioFileModel();
214         }
215 }
216
217 AudioFileModel &FileListModel::operator[] (const QModelIndex &index)
218 {
219         return m_fileList[index.row()];
220 }
221
222 bool FileListModel::setFile(const QModelIndex &index, const AudioFileModel &audioFile)
223 {
224         if(index.row() >= 0 && index.row() < m_fileList.count())
225         {
226                 beginResetModel();
227                 m_fileList.replace(index.row(), audioFile);
228                 endResetModel();
229                 return true;
230         }
231         else
232         {
233                 return false;
234         }
235 }
236
237 int FileListModel::exportToCsv(const QString &outFile)
238 {
239         const int nFiles = m_fileList.count();
240         
241         bool havePosition = false, haveTitle = false, haveArtist = false, haveAlbum = false, haveGenre = false, haveYear = false, haveComment = false;
242         
243         for(int i = 0; i < nFiles; i++)
244         {
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;
252         }
253
254         if(!(haveTitle || haveArtist || haveAlbum || haveGenre || haveYear || haveComment))
255         {
256                 return CsvError_NoTags;
257         }
258
259         QFile file(outFile);
260
261         if(!file.open(QIODevice::WriteOnly))
262         {
263                 return CsvError_FileOpen;
264         }
265         else
266         {
267                 QStringList line;
268                 
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";
276
277                 if(file.write(line.join(";").append("\r\n").toUtf8().prepend("\xef\xbb\xbf")) < 1)
278                 {
279                         file.close();
280                         return CsvError_FileWrite;
281                 }
282         }
283
284         for(int i = 0; i < nFiles; i++)
285         {
286                 QStringList line;
287                 
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();
295
296                 if(file.write(line.replaceInStrings(";", ",").join(";").append("\r\n").toUtf8()) < 1)
297                 {
298                         file.close();
299                         return CsvError_FileWrite;
300                 }
301         }
302
303         file.close();
304         return CsvError_OK;
305 }
306
307 int FileListModel::importFromCsv(QWidget *parent, const QString &inFile)
308 {
309         QFile file(inFile);
310         if(!file.open(QIODevice::ReadOnly))
311         {
312                 return CsvError_FileOpen;
313         }
314
315         QTextCodec *codec = NULL;
316         QByteArray bomCheck = file.peek(16);
317
318         if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
319         {
320                 codec = QTextCodec::codecForName("UTF-8");
321         }
322         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
323         {
324                 codec = QTextCodec::codecForName("UTF-16LE");
325         }
326         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
327         {
328                 codec = QTextCodec::codecForName("UTF-16BE");
329         }
330         else
331         {
332                 const QString systemDefault = tr("(System Default)");
333
334                 QStringList codecList;
335                 codecList.append(systemDefault);
336                 codecList.append(lamexp_available_codepages());
337
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);
344         
345                 if(input->exec() < 1)
346                 {
347                         LAMEXP_DELETE(input);
348                         return CsvError_Aborted;
349                 }
350         
351                 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
352                 {
353                         qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
354                         codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
355                 }
356                 else
357                 {
358                         qDebug("Going to use the system's default codec!");
359                         codec = QTextCodec::codecForName("System");
360                 }
361
362                 LAMEXP_DELETE(input);
363         }
364
365         bomCheck.clear();
366
367         //----------------------//
368
369         QTextStream stream(&file);
370         stream.setAutoDetectUnicode(false);
371         stream.setCodec(codec);
372
373         QString headerLine = stream.readLine().simplified();
374
375         while(headerLine.isEmpty())
376         {
377                 if(stream.atEnd())
378                 {
379                         qWarning("The file appears to be empty!");
380                         return CsvError_FileRead;
381                 }
382                 qWarning("Skipping a blank line at beginning of CSV file!");
383                 headerLine = stream.readLine().simplified();
384         }
385
386         QStringList header = headerLine.split(";", QString::KeepEmptyParts);
387
388         const int nCols = header.count();
389         const int nFiles = m_fileList.count();
390
391         if(nCols < 1)
392         {
393                 qWarning("Header appears to be empty!");
394                 return CsvError_FileRead;
395         }
396
397         bool *ignore = new bool[nCols];
398         memset(ignore, 0, sizeof(bool) * nCols);
399
400         for(int i = 0; i < nCols; i++)
401         {
402                 if((header[i] = header[i].trimmed()).isEmpty())
403                 {
404                         ignore[i] = true;
405                 }
406         }
407
408         //----------------------//
409
410         for(int i = 0; i < nFiles; i++)
411         {
412                 if(stream.atEnd())
413                 {
414                         LAMEXP_DELETE_ARRAY(ignore);
415                         return CsvError_Incomplete;
416                 }
417                 
418                 QString line = stream.readLine().simplified();
419                 
420                 if(line.isEmpty())
421                 {
422                         qWarning("Skipping a blank line in CSV file!");
423                         continue;
424                 }
425                 
426                 QStringList data = line.split(";", QString::KeepEmptyParts);
427
428                 if(data.count() < header.count())
429                 {
430                         qWarning("Skipping an incomplete line in CSV file!");
431                         continue;
432                 }
433
434                 for(int j = 0; j < nCols; j++)
435                 {
436                         if(ignore[j])
437                         {
438                                 continue;
439                         }
440                         else if(CHECK_HDR(header.at(j), "POSITION"))
441                         {
442                                 bool ok = false;
443                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
444                                 if(ok) m_fileList[i].setFilePosition(temp);
445                         }
446                         else if(CHECK_HDR(header.at(j), "TITLE"))
447                         {
448                                 QString temp = data.at(j).trimmed();
449                                 if(!temp.isEmpty()) m_fileList[i].setFileName(temp);
450                         }
451                         else if(CHECK_HDR(header.at(j), "ARTIST"))
452                         {
453                                 QString temp = data.at(j).trimmed();
454                                 if(!temp.isEmpty()) m_fileList[i].setFileArtist(temp);
455                         }
456                         else if(CHECK_HDR(header.at(j), "ALBUM"))
457                         {
458                                 QString temp = data.at(j).trimmed();
459                                 if(!temp.isEmpty()) m_fileList[i].setFileAlbum(temp);
460                         }
461                         else if(CHECK_HDR(header.at(j), "GENRE"))
462                         {
463                                 QString temp = data.at(j).trimmed();
464                                 if(!temp.isEmpty()) m_fileList[i].setFileGenre(temp);
465                         }
466                         else if(CHECK_HDR(header.at(j), "YEAR"))
467                         {
468                                 bool ok = false;
469                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
470                                 if(ok) m_fileList[i].setFileYear(temp);
471                         }
472                         else if(CHECK_HDR(header.at(j), "COMMENT"))
473                         {
474                                 QString temp = data.at(j).trimmed();
475                                 if(!temp.isEmpty()) m_fileList[i].setFileComment(temp);
476                         }
477                         else
478                         {
479                                 qWarning("Unkonw field '%s' will be ignored!", header.at(j).toUtf8().constData());
480                                 ignore[j] = true;
481                                 
482                                 if(!checkArray(ignore, false, nCols))
483                                 {
484                                         qWarning("No known fields left, aborting!");
485                                         return CsvError_NoTags;
486                                 }
487                         }
488                 }
489         }
490
491         //----------------------//
492
493         LAMEXP_DELETE_ARRAY(ignore);
494         return CsvError_OK;
495 }
496
497 bool FileListModel::checkArray(const bool *a, const bool val, size_t len)
498 {
499         for(size_t i = 0; i < len; i++)
500         {
501                 if(a[i] == val) return true;
502         }
503
504         return false;
505 }