OSDN Git Service

Updated Monkey's Audio binary to v4.11 (2013-01-20), including STDERR flush fix.
[lamexp/LameXP.git] / src / Model_FileList.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2013 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 #define MAKE_KEY(PATH) (QDir::fromNativeSeparators(PATH).toLower())
36
37 ////////////////////////////////////////////////////////////
38 // Constructor & Destructor
39 ////////////////////////////////////////////////////////////
40
41 FileListModel::FileListModel(void)
42 :
43         m_blockUpdates(false),
44         m_fileIcon(":/icons/page_white_cd.png")
45 {
46 }
47
48 FileListModel::~FileListModel(void)
49 {
50 }
51
52 ////////////////////////////////////////////////////////////
53 // Public Functions
54 ////////////////////////////////////////////////////////////
55
56 int FileListModel::columnCount(const QModelIndex &parent) const
57 {
58         return 2;
59 }
60
61 int FileListModel::rowCount(const QModelIndex &parent) const
62 {
63         return m_fileList.count();
64 }
65
66 QVariant FileListModel::data(const QModelIndex &index, int role) const
67 {
68         if((role == Qt::DisplayRole || role == Qt::ToolTipRole) && index.row() < m_fileList.count() && index.row() >= 0)
69         {
70                 switch(index.column())
71                 {
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;
81                 }               
82         }
83         else if(role == Qt::DecorationRole && index.column() == 0)
84         {
85                 return m_fileIcon;
86         }
87         else
88         {
89                 return QVariant();
90         }
91 }
92
93 QVariant FileListModel::headerData(int section, Qt::Orientation orientation, int role) const
94 {
95         if(role == Qt::DisplayRole)
96         {
97                 if(orientation == Qt::Horizontal)
98                 {
99                         switch(section)
100                         {
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;
110                         }
111                 }
112                 else
113                 {
114                         if(m_fileList.count() < 10)
115                         {
116                                 return QVariant(QString().sprintf("%d", section + 1));
117                         }
118                         else if(m_fileList.count() < 100)
119                         {
120                                 return QVariant(QString().sprintf("%02d", section + 1));
121                         }
122                         else if(m_fileList.count() < 1000)
123                         {
124                                 return QVariant(QString().sprintf("%03d", section + 1));
125                         }
126                         else
127                         {
128                                 return QVariant(QString().sprintf("%04d", section + 1));
129                         }
130                 }
131         }
132         else
133         {
134                 return QVariant();
135         }
136 }
137
138 void FileListModel::addFile(const QString &filePath)
139 {
140         QFileInfo fileInfo(filePath);
141         const QString key = MAKE_KEY(fileInfo.canonicalFilePath()); 
142         const bool flag = (!m_blockUpdates);
143
144         if(!m_fileStore.contains(key))
145         {
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();
151         }
152 }
153
154 void FileListModel::addFile(const AudioFileModel &file)
155 {
156         const QString key = MAKE_KEY(file.filePath()); 
157         const bool flag = (!m_blockUpdates);
158
159         if(!m_fileStore.contains(key))
160         {
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();
166         }
167 }
168
169 bool FileListModel::removeFile(const QModelIndex &index)
170 {
171         if(index.row() >= 0 && index.row() < m_fileList.count())
172         {
173                 beginResetModel();
174                 m_fileStore.remove(m_fileList.at(index.row()));
175                 m_fileList.removeAt(index.row());
176                 endResetModel();
177                 return true;
178         }
179         else
180         {
181                 return false;
182         }
183 }
184
185 void FileListModel::clearFiles(void)
186 {
187         beginResetModel();
188         m_fileList.clear();
189         m_fileStore.clear();
190         endResetModel();
191 }
192
193 bool FileListModel::moveFile(const QModelIndex &index, int delta)
194 {
195         if(delta != 0 && index.row() >= 0 && index.row() < m_fileList.count() && index.row() + delta >= 0 && index.row() + delta < m_fileList.count())
196         {
197                 beginResetModel();
198                 m_fileList.move(index.row(), index.row() + delta);
199                 endResetModel();
200                 return true;
201         }
202         else
203         {
204                 return false;
205         }
206 }
207
208 AudioFileModel FileListModel::getFile(const QModelIndex &index)
209 {
210         if(index.row() >= 0 && index.row() < m_fileList.count())
211         {
212                 return m_fileStore.value(m_fileList.at(index.row()));
213         }
214         else
215         {
216                 return AudioFileModel();
217         }
218 }
219
220 AudioFileModel &FileListModel::operator[] (const QModelIndex &index)
221 {
222         const QString key = m_fileList.at(index.row());
223         return m_fileStore[key];
224 }
225
226 bool FileListModel::setFile(const QModelIndex &index, const AudioFileModel &audioFile)
227 {
228         if(index.row() >= 0 && index.row() < m_fileList.count())
229         {
230                 const QString oldKey = m_fileList.at(index.row());
231                 const QString newKey = MAKE_KEY(audioFile.filePath());
232                 
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;
239         }
240         else
241         {
242                 return false;
243         }
244 }
245
246 int FileListModel::exportToCsv(const QString &outFile)
247 {
248         const int nFiles = m_fileList.count();
249         
250         bool havePosition = false, haveTitle = false, haveArtist = false, haveAlbum = false, haveGenre = false, haveYear = false, haveComment = false;
251         
252         for(int i = 0; i < nFiles; i++)
253         {
254                 AudioFileModel current = m_fileStore.value(m_fileList.at(i));
255                 
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;
263         }
264
265         if(!(haveTitle || haveArtist || haveAlbum || haveGenre || haveYear || haveComment))
266         {
267                 return CsvError_NoTags;
268         }
269
270         QFile file(outFile);
271
272         if(!file.open(QIODevice::WriteOnly))
273         {
274                 return CsvError_FileOpen;
275         }
276         else
277         {
278                 QStringList line;
279                 
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";
287
288                 if(file.write(line.join(";").append("\r\n").toUtf8().prepend("\xef\xbb\xbf")) < 1)
289                 {
290                         file.close();
291                         return CsvError_FileWrite;
292                 }
293         }
294
295         for(int i = 0; i < nFiles; i++)
296         {
297                 QStringList line;
298                 AudioFileModel current = m_fileStore.value(m_fileList.at(i));
299                 
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();
307
308                 if(file.write(line.replaceInStrings(";", ",").join(";").append("\r\n").toUtf8()) < 1)
309                 {
310                         file.close();
311                         return CsvError_FileWrite;
312                 }
313         }
314
315         file.close();
316         return CsvError_OK;
317 }
318
319 int FileListModel::importFromCsv(QWidget *parent, const QString &inFile)
320 {
321         QFile file(inFile);
322         if(!file.open(QIODevice::ReadOnly))
323         {
324                 return CsvError_FileOpen;
325         }
326
327         QTextCodec *codec = NULL;
328         QByteArray bomCheck = file.peek(16);
329
330         if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
331         {
332                 codec = QTextCodec::codecForName("UTF-8");
333         }
334         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
335         {
336                 codec = QTextCodec::codecForName("UTF-16LE");
337         }
338         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
339         {
340                 codec = QTextCodec::codecForName("UTF-16BE");
341         }
342         else
343         {
344                 const QString systemDefault = tr("(System Default)");
345
346                 QStringList codecList;
347                 codecList.append(systemDefault);
348                 codecList.append(lamexp_available_codepages());
349
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);
356         
357                 if(input->exec() < 1)
358                 {
359                         LAMEXP_DELETE(input);
360                         return CsvError_Aborted;
361                 }
362         
363                 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
364                 {
365                         qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
366                         codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
367                 }
368                 else
369                 {
370                         qDebug("Going to use the system's default codec!");
371                         codec = QTextCodec::codecForName("System");
372                 }
373
374                 LAMEXP_DELETE(input);
375         }
376
377         bomCheck.clear();
378
379         //----------------------//
380
381         QTextStream stream(&file);
382         stream.setAutoDetectUnicode(false);
383         stream.setCodec(codec);
384
385         QString headerLine = stream.readLine().simplified();
386
387         while(headerLine.isEmpty())
388         {
389                 if(stream.atEnd())
390                 {
391                         qWarning("The file appears to be empty!");
392                         return CsvError_FileRead;
393                 }
394                 qWarning("Skipping a blank line at beginning of CSV file!");
395                 headerLine = stream.readLine().simplified();
396         }
397
398         QStringList header = headerLine.split(";", QString::KeepEmptyParts);
399
400         const int nCols = header.count();
401         const int nFiles = m_fileList.count();
402
403         if(nCols < 1)
404         {
405                 qWarning("Header appears to be empty!");
406                 return CsvError_FileRead;
407         }
408
409         bool *ignore = new bool[nCols];
410         memset(ignore, 0, sizeof(bool) * nCols);
411
412         for(int i = 0; i < nCols; i++)
413         {
414                 if((header[i] = header[i].trimmed()).isEmpty())
415                 {
416                         ignore[i] = true;
417                 }
418         }
419
420         //----------------------//
421
422         for(int i = 0; i < nFiles; i++)
423         {
424                 if(stream.atEnd())
425                 {
426                         LAMEXP_DELETE_ARRAY(ignore);
427                         return CsvError_Incomplete;
428                 }
429                 
430                 QString line = stream.readLine().simplified();
431                 
432                 if(line.isEmpty())
433                 {
434                         qWarning("Skipping a blank line in CSV file!");
435                         continue;
436                 }
437                 
438                 QStringList data = line.split(";", QString::KeepEmptyParts);
439
440                 if(data.count() < header.count())
441                 {
442                         qWarning("Skipping an incomplete line in CSV file!");
443                         continue;
444                 }
445
446                 const QString key = m_fileList[i];
447
448                 for(int j = 0; j < nCols; j++)
449                 {
450                         if(ignore[j])
451                         {
452                                 continue;
453                         }
454                         else if(CHECK_HDR(header.at(j), "POSITION"))
455                         {
456                                 bool ok = false;
457                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
458                                 if(ok) m_fileStore[key].setFilePosition(temp);
459                         }
460                         else if(CHECK_HDR(header.at(j), "TITLE"))
461                         {
462                                 QString temp = data.at(j).trimmed();
463                                 if(!temp.isEmpty()) m_fileStore[key].setFileName(temp);
464                         }
465                         else if(CHECK_HDR(header.at(j), "ARTIST"))
466                         {
467                                 QString temp = data.at(j).trimmed();
468                                 if(!temp.isEmpty()) m_fileStore[key].setFileArtist(temp);
469                         }
470                         else if(CHECK_HDR(header.at(j), "ALBUM"))
471                         {
472                                 QString temp = data.at(j).trimmed();
473                                 if(!temp.isEmpty()) m_fileStore[key].setFileAlbum(temp);
474                         }
475                         else if(CHECK_HDR(header.at(j), "GENRE"))
476                         {
477                                 QString temp = data.at(j).trimmed();
478                                 if(!temp.isEmpty()) m_fileStore[key].setFileGenre(temp);
479                         }
480                         else if(CHECK_HDR(header.at(j), "YEAR"))
481                         {
482                                 bool ok = false;
483                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
484                                 if(ok) m_fileStore[key].setFileYear(temp);
485                         }
486                         else if(CHECK_HDR(header.at(j), "COMMENT"))
487                         {
488                                 QString temp = data.at(j).trimmed();
489                                 if(!temp.isEmpty()) m_fileStore[key].setFileComment(temp);
490                         }
491                         else
492                         {
493                                 qWarning("Unkonw field '%s' will be ignored!", header.at(j).toUtf8().constData());
494                                 ignore[j] = true;
495                                 
496                                 if(!checkArray(ignore, false, nCols))
497                                 {
498                                         qWarning("No known fields left, aborting!");
499                                         return CsvError_NoTags;
500                                 }
501                         }
502                 }
503         }
504
505         //----------------------//
506
507         LAMEXP_DELETE_ARRAY(ignore);
508         return CsvError_OK;
509 }
510
511 bool FileListModel::checkArray(const bool *a, const bool val, size_t len)
512 {
513         for(size_t i = 0; i < len; i++)
514         {
515                 if(a[i] == val) return true;
516         }
517
518         return false;
519 }