File indexing completed on 2024-04-21 12:23:52

0001 /* AUDEX CDDA EXTRACTOR
0002  * SPDX-FileCopyrightText: Copyright (C) 2007 Marco Nelles
0003  * <https://userbase.kde.org/Audex>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "cddamodel.h"
0009 #include <QRegularExpression>
0010 
0011 CDDAModel::CDDAModel(QObject *parent)
0012     : QAbstractTableModel(parent)
0013 {
0014     p_cdio = nullptr;
0015     device_file.clear();
0016     udi.clear();
0017 
0018     devices = new CDDADevices(this);
0019     if (!devices) {
0020         qDebug() << "Unable to create devices object. low mem?";
0021         error = Error(i18n("Unable to create devices object."),
0022                       i18n("This is an internal error. Check your hardware. If all okay please make bug report."),
0023                       Error::ERROR,
0024                       this);
0025         return;
0026     }
0027     connect(devices, SIGNAL(audioDiscDetected(const QString &)), this, SLOT(new_audio_disc_available(const QString &)));
0028     connect(devices, SIGNAL(audioDiscRemoved(const QString &)), this, SLOT(audio_disc_removed(const QString &)));
0029 
0030     cddb = new KCDDB::Client();
0031     if (!cddb) {
0032         qDebug() << "Unable to create KCDDB object. Low mem?";
0033         error = Error(i18n("Unable to create KCDDB object."),
0034                       i18n("This is an internal error. Check your hardware. If all okay please make bug report."),
0035                       Error::ERROR,
0036                       this);
0037         return;
0038     }
0039     connect(cddb, SIGNAL(finished(KCDDB::Result)), this, SLOT(lookup_cddb_done(KCDDB::Result)));
0040 
0041     cddb_transaction_pending = false;
0042 
0043     cd_info.clear();
0044     modified = false;
0045     p_empty = true;
0046 
0047     QTimer::singleShot(200, devices, SLOT(scanBus()));
0048 }
0049 
0050 CDDAModel::~CDDAModel()
0051 {
0052     delete cddb;
0053     delete devices;
0054 
0055     if (p_cdio)
0056         delete p_cdio;
0057 }
0058 
0059 int CDDAModel::rowCount(const QModelIndex &parent) const
0060 {
0061     if (!p_cdio)
0062         return 0;
0063     return parent.isValid() ? 0 : p_cdio->numOfTracks();
0064 }
0065 
0066 int CDDAModel::columnCount(const QModelIndex &parent) const
0067 {
0068     Q_UNUSED(parent);
0069     return CDDA_MODEL_COLUMN_COUNT;
0070 }
0071 
0072 QVariant CDDAModel::data(const QModelIndex &index, int role) const
0073 {
0074     if (!p_cdio)
0075         return QVariant();
0076 
0077     if (!index.isValid())
0078         return QVariant();
0079 
0080     if ((index.row() < 0) || (index.row() >= numOfTracks()))
0081         return QVariant();
0082 
0083     if (role == Qt::TextAlignmentRole)
0084         return int(Qt::AlignLeft | Qt::AlignVCenter);
0085 
0086     /*if (role == Qt::ForegroundRole) {
0087       switch (index.column()) {
0088         case CDDA_MODEL_COLUMN_ARTIST_INDEX :
0089               if (!isTrackArtistValid(index.row()+1)) return qVariantFromValue(QColor(Qt::gray));
0090               break;
0091         case CDDA_MODEL_COLUMN_TITLE_INDEX :
0092               if (!isTrackTitleValid(index.row()+1)) return qVariantFromValue(QColor(Qt::gray));
0093               break;
0094       }
0095     }*/
0096 
0097     if ((role == Qt::DisplayRole) || (role == Qt::CheckStateRole && index.column() == CDDA_MODEL_COLUMN_RIP_INDEX) || (role == CDDA_MODEL_INTERNAL_ROLE)
0098         || (role == Qt::EditRole)) {
0099         switch (index.column()) {
0100         case CDDA_MODEL_COLUMN_RIP_INDEX:
0101             if (role == Qt::CheckStateRole) {
0102                 return isTrackInSelection(index.row() + 1) ? Qt::Checked : Qt::Unchecked;
0103             }
0104             break;
0105         case CDDA_MODEL_COLUMN_TRACK_INDEX:
0106             return index.row() + 1 + (trackOffset() > 1 ? trackOffset() : 0);
0107         case CDDA_MODEL_COLUMN_LENGTH_INDEX:
0108             if (role == CDDA_MODEL_INTERNAL_ROLE)
0109                 return lengthOfTrack(index.row() + 1);
0110             else
0111                 return QString("%1:%2").arg(lengthOfTrack(index.row() + 1) / 60, 2, 10, QChar('0')).arg(lengthOfTrack(index.row() + 1) % 60, 2, 10, QChar('0'));
0112         case CDDA_MODEL_COLUMN_ARTIST_INDEX:
0113             if (isAudioTrack(index.row() + 1)) {
0114                 QString a = cd_info.track(index.row()).get(KCDDB::Artist).toString();
0115                 return a;
0116             }
0117             break;
0118         case CDDA_MODEL_COLUMN_TITLE_INDEX:
0119             if (isAudioTrack(index.row() + 1)) {
0120                 QString t = cd_info.track(index.row()).get(KCDDB::Title).toString();
0121                 if (t.isEmpty())
0122                     return i18n("Track %1", index.row() + 1);
0123                 return t;
0124             }
0125             break;
0126         default:;
0127         }
0128     }
0129 
0130     return QVariant();
0131 }
0132 
0133 bool CDDAModel::setData(const QModelIndex &index, const QVariant &value, int role)
0134 {
0135     if (!p_cdio)
0136         return false;
0137 
0138     if (!index.isValid())
0139         return false;
0140 
0141     if ((index.row() < 0) || (index.row() >= numOfTracks()))
0142         return false;
0143 
0144     if (role == Qt::EditRole) {
0145         bool changed = false;
0146         switch (index.column()) {
0147         case CDDA_MODEL_COLUMN_ARTIST_INDEX:
0148             if (value != cd_info.track(index.row()).get(KCDDB::Artist)) {
0149                 cd_info.track(index.row()).set(KCDDB::Artist, value);
0150                 changed = true;
0151             }
0152             break;
0153         case CDDA_MODEL_COLUMN_TITLE_INDEX:
0154             if (value != cd_info.track(index.row()).get(KCDDB::Title)) {
0155                 cd_info.track(index.row()).set(KCDDB::Title, value);
0156                 changed = true;
0157             }
0158             break;
0159         default:
0160             return false;
0161         }
0162         if (changed) {
0163             Q_EMIT dataChanged(index, index);
0164             modify();
0165         }
0166         return changed;
0167     }
0168 
0169     return false;
0170 }
0171 
0172 QVariant CDDAModel::headerData(int section, Qt::Orientation orientation, int role) const
0173 {
0174     Q_UNUSED(orientation);
0175 
0176     if (orientation == Qt::Horizontal) {
0177         switch (role) {
0178         case Qt::DisplayRole:
0179             switch (section) {
0180             case CDDA_MODEL_COLUMN_RIP_INDEX:
0181                 return CDDA_MODEL_COLUMN_RIP_LABEL;
0182             case CDDA_MODEL_COLUMN_TRACK_INDEX:
0183                 return CDDA_MODEL_COLUMN_TRACK_LABEL;
0184             case CDDA_MODEL_COLUMN_LENGTH_INDEX:
0185                 return CDDA_MODEL_COLUMN_LENGTH_LABEL;
0186             case CDDA_MODEL_COLUMN_ARTIST_INDEX:
0187                 return CDDA_MODEL_COLUMN_ARTIST_LABEL;
0188             case CDDA_MODEL_COLUMN_TITLE_INDEX:
0189                 return CDDA_MODEL_COLUMN_TITLE_LABEL;
0190             default:;
0191             }
0192             break;
0193         case Qt::TextAlignmentRole:
0194             return Qt::AlignLeft;
0195         default:;
0196         }
0197     } else if (orientation == Qt::Vertical) {
0198         if (role == Qt::DisplayRole) {
0199             return QVariant(section + 1);
0200         }
0201     }
0202 
0203     return QVariant();
0204 }
0205 
0206 Qt::ItemFlags CDDAModel::flags(const QModelIndex &index) const
0207 {
0208     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0209     if ((index.column() == CDDA_MODEL_COLUMN_ARTIST_INDEX) || (index.column() == CDDA_MODEL_COLUMN_TITLE_INDEX)) {
0210         flags |= Qt::ItemIsEditable;
0211     }
0212     if (!isAudioTrack(index.row() + 1))
0213         return Qt::ItemIsEnabled;
0214     return flags;
0215 }
0216 
0217 void CDDAModel::setArtist(const QString &a)
0218 {
0219     if (!p_cdio)
0220         return;
0221     if (a != cd_info.get(KCDDB::Artist).toString()) {
0222         beginResetModel();
0223         cd_info.set(KCDDB::Artist, a);
0224         modify();
0225         endResetModel();
0226     }
0227 }
0228 
0229 const QString CDDAModel::artist() const
0230 {
0231     if (!p_cdio)
0232         return QString();
0233     QString a = cd_info.get(KCDDB::Artist).toString();
0234     return a;
0235 }
0236 
0237 void CDDAModel::setTitle(const QString &t)
0238 {
0239     if (!p_cdio)
0240         return;
0241     if (t != cd_info.get(KCDDB::Title).toString()) {
0242         beginResetModel();
0243         cd_info.set(KCDDB::Title, t);
0244         modify();
0245         endResetModel();
0246     }
0247 }
0248 
0249 const QString CDDAModel::title() const
0250 {
0251     if (!p_cdio)
0252         return QString();
0253     QString t = cd_info.get(KCDDB::Title).toString();
0254     return t;
0255 }
0256 
0257 void CDDAModel::setCategory(const QString &c)
0258 {
0259     if (!p_cdio)
0260         return;
0261 
0262     QStringList validCategories;
0263     validCategories << "blues"
0264                     << "classical"
0265                     << "country"
0266                     << "data"
0267                     << "folk"
0268                     << "jazz"
0269                     << "misc"
0270                     << "newage"
0271                     << "reggae"
0272                     << "rock"
0273                     << "soundtrack";
0274     if (!validCategories.contains(c))
0275         return;
0276 
0277     if (c != cd_info.get(KCDDB::Category).toString()) {
0278         beginResetModel();
0279         cd_info.set(KCDDB::Category, c);
0280         modify();
0281         endResetModel();
0282     }
0283 }
0284 
0285 const QString CDDAModel::category() const
0286 {
0287     if (!p_cdio)
0288         return QString();
0289     return cd_info.get(KCDDB::Category).toString();
0290 }
0291 
0292 void CDDAModel::setGenre(const QString &g)
0293 {
0294     if (!p_cdio)
0295         return;
0296     if (g != cd_info.get(KCDDB::Genre).toString()) {
0297         beginResetModel();
0298         cd_info.set(KCDDB::Genre, g);
0299         modify();
0300         endResetModel();
0301     }
0302 }
0303 
0304 const QString CDDAModel::genre() const
0305 {
0306     if (!p_cdio)
0307         return QString();
0308     return cd_info.get(KCDDB::Genre).toString();
0309 }
0310 
0311 void CDDAModel::setYear(const QString &year)
0312 {
0313     if (!p_cdio)
0314         return;
0315     if (year != cd_info.get(KCDDB::Year).toString()) {
0316         beginResetModel();
0317         cd_info.set(KCDDB::Year, year);
0318         modify();
0319         endResetModel();
0320     }
0321 }
0322 
0323 const QString CDDAModel::year() const
0324 {
0325     if (!p_cdio)
0326         return QString();
0327     return cd_info.get(KCDDB::Year).toString();
0328 }
0329 
0330 void CDDAModel::setExtendedData(const QStringList &e)
0331 {
0332     if (!p_cdio)
0333         return;
0334     if (e != cd_info.get(KCDDB::Comment).toStringList()) {
0335         beginResetModel();
0336         cd_info.set(KCDDB::Comment, e);
0337         modify();
0338         endResetModel();
0339     }
0340 }
0341 
0342 const QStringList CDDAModel::extendedData() const
0343 {
0344     if (!p_cdio)
0345         return QStringList();
0346     return cd_info.get(KCDDB::Comment).toStringList();
0347 }
0348 
0349 void CDDAModel::setCDNum(const int n)
0350 {
0351     if (!p_cdio)
0352         return;
0353     if (n != cd_info.get("DNO").toInt()) {
0354         beginResetModel();
0355         cd_info.set("DNO", n);
0356         modify();
0357         endResetModel();
0358     }
0359 }
0360 
0361 int CDDAModel::cdNum() const
0362 {
0363     if (!p_cdio)
0364         return -1;
0365     if (!isMultiCD())
0366         return 0;
0367     return cd_info.get("DNO").toInt();
0368 }
0369 
0370 void CDDAModel::setTrackOffset(const int n)
0371 {
0372     if (!p_cdio)
0373         return;
0374     if (n != cd_info.get("DTRACKOFFSET").toInt()) {
0375         beginResetModel();
0376         cd_info.set("DTRACKOFFSET", n);
0377         modify();
0378         endResetModel();
0379     }
0380 }
0381 
0382 int CDDAModel::trackOffset() const
0383 {
0384     if (!p_cdio)
0385         return -1;
0386     return cd_info.get("DTRACKOFFSET").toInt();
0387 }
0388 
0389 int CDDAModel::guessMultiCD(QString &newTitle) const
0390 {
0391     if (!p_cdio)
0392         return -1;
0393 
0394     QString t = cd_info.get(KCDDB::Title).toString();
0395     static QRegularExpression rx1("[\\(|\\[]* *([c|C][d|D]|[d|D][i|I][s|S][k|c|K|C]) *[0-9]* *[\\)|\\]]* *$");
0396     int i = t.indexOf(rx1);
0397     if (i >= 0) {
0398         QString frac = t.mid(i);
0399         static QRegularExpression rx2("(\\d+)");
0400         auto match = rx2.match(frac);
0401         bool ok;
0402         int cdnum = match.captured(0).toInt(&ok);
0403         if (ok) {
0404             if (cdnum < 0)
0405                 return -1;
0406             if (cdnum == 0)
0407                 cdnum = 1;
0408             newTitle = t.left(i).trimmed();
0409             return cdnum;
0410         }
0411     }
0412     return -1;
0413 }
0414 
0415 void CDDAModel::setMultiCD(const bool multi)
0416 {
0417     if (!p_cdio)
0418         return;
0419     if (multi != cd_info.get("DMULTICD").toBool()) {
0420         beginResetModel();
0421         cd_info.set("DMULTICD", multi);
0422         modify();
0423         endResetModel();
0424     }
0425 }
0426 
0427 bool CDDAModel::isMultiCD() const
0428 {
0429     if (!p_cdio)
0430         return false;
0431     return cd_info.get("DMULTICD").toBool();
0432 }
0433 
0434 void CDDAModel::setCustomData(const QString &type, const QVariant &data)
0435 {
0436     if (!p_cdio)
0437         return;
0438     if (data != cd_info.get(type)) {
0439         beginResetModel();
0440         cd_info.set(type, data);
0441         modify();
0442         endResetModel();
0443     }
0444 }
0445 
0446 const QVariant CDDAModel::customData(const QString &type) const
0447 {
0448     if (!p_cdio)
0449         return QVariant();
0450     return cd_info.get(type);
0451 }
0452 
0453 void CDDAModel::setCustomDataPerTrack(const int n, const QString &type, const QVariant &data)
0454 {
0455     if (!p_cdio)
0456         return;
0457     if (data != cd_info.track(n).get(type)) {
0458         beginResetModel();
0459         cd_info.track(n).set(type, data);
0460         modify();
0461         endResetModel();
0462     }
0463 }
0464 
0465 const QVariant CDDAModel::getCustomDataPerTrack(const int n, const QString &type)
0466 {
0467     if (!p_cdio)
0468         return QVariant();
0469     return cd_info.track(n).get(type);
0470 }
0471 
0472 const QImage &CDDAModel::cover() const
0473 {
0474     return p_cover;
0475 }
0476 
0477 bool CDDAModel::setCover(const QByteArray &data)
0478 {
0479     if (p_cover.load(data)) {
0480         beginResetModel();
0481         endResetModel();
0482         return true;
0483     }
0484     return false;
0485 }
0486 
0487 bool CDDAModel::setCover(const QString &filename)
0488 {
0489     if (p_cover.load(filename)) {
0490         beginResetModel();
0491         endResetModel();
0492         return true;
0493     }
0494     return false;
0495 }
0496 
0497 bool CDDAModel::saveCoverToFile(const QString &filename)
0498 {
0499     if (p_cover.save(filename)) {
0500         return true;
0501     }
0502     return false;
0503 }
0504 
0505 bool CDDAModel::isCoverEmpty() const
0506 {
0507     return p_cover.isNull();
0508 }
0509 
0510 void CDDAModel::clearCover()
0511 {
0512     beginResetModel();
0513     p_cover = QImage();
0514     endResetModel();
0515 }
0516 
0517 const QString CDDAModel::coverSupportedMimeTypeList() const
0518 {
0519     QList<QByteArray> supp_list = QImageReader::supportedImageFormats();
0520     QMap<QString, QStringList> map;
0521     QMimeDatabase db;
0522     QMimeType mime = db.mimeTypeForData(QByteArray(""));
0523     for (int i = 0; i < supp_list.count(); ++i) {
0524         map[db.mimeTypeForUrl("dummy." + QString(supp_list[i]).toLower()).comment()].append("*." + QString(supp_list[i]).toLower());
0525     }
0526     QString result = i18n("Common image formats") + " (*.jpg *.jpeg *.png *.bmp *.tif *.tiff *.gif *.avif)";
0527     QMap<QString, QStringList>::const_iterator i = map.constBegin();
0528     while (i != map.constEnd()) {
0529         if (i.key() == mime.comment()) {
0530             ++i;
0531             continue;
0532         }
0533         result += ";;";
0534         QStringList extensions = i.value();
0535         extensions.removeDuplicates();
0536         result += i.key() + " (" + extensions.join(" ") + ')';
0537         ++i;
0538     }
0539     return result;
0540 }
0541 
0542 bool CDDAModel::guessVarious() const
0543 {
0544     if (!p_cdio)
0545         return false;
0546     QString a;
0547     for (int i = 0; i < cd_info.numberOfTracks(); ++i) {
0548         if ((i > 0) && (cd_info.track(i).get(KCDDB::Artist).toString().toLower() != a.toLower()))
0549             return true;
0550         a = cd_info.track(i).get(KCDDB::Artist).toString();
0551     }
0552     return false;
0553 }
0554 
0555 void CDDAModel::setVarious(bool various)
0556 {
0557     if (!p_cdio)
0558         return;
0559     if (various != cd_info.get("DVARIOUS").toBool()) {
0560         cd_info.set("DVARIOUS", various);
0561         modify();
0562     }
0563 }
0564 
0565 bool CDDAModel::isVarious()
0566 {
0567     if (!p_cdio)
0568         return false;
0569     return cd_info.get("DVARIOUS").toBool();
0570 }
0571 
0572 void CDDAModel::swapArtistAndTitleOfTracks()
0573 {
0574     if (!p_cdio)
0575         return;
0576 
0577     beginResetModel();
0578     for (int i = 0; i < cd_info.numberOfTracks(); ++i) {
0579         QVariant tmp = cd_info.track(i).get(KCDDB::Artist);
0580         cd_info.track(i).set(KCDDB::Artist, cd_info.track(i).get(KCDDB::Title));
0581         cd_info.track(i).set(KCDDB::Title, tmp);
0582     }
0583     modified = true;
0584     endResetModel();
0585     Q_EMIT cddbDataModified();
0586 }
0587 
0588 void CDDAModel::swapArtistAndTitle()
0589 {
0590     if (!p_cdio)
0591         return;
0592     QVariant tmp = cd_info.get(KCDDB::Title);
0593     beginResetModel();
0594     cd_info.set(KCDDB::Title, cd_info.get(KCDDB::Artist));
0595     cd_info.set(KCDDB::Artist, tmp);
0596     modified = true;
0597     endResetModel();
0598     Q_EMIT cddbDataModified();
0599 }
0600 
0601 void CDDAModel::splitTitleOfTracks(const QString &divider)
0602 {
0603     if (!p_cdio)
0604         return;
0605 
0606     beginResetModel();
0607     for (int i = 0; i < cd_info.numberOfTracks(); ++i) {
0608         int splitPos = cd_info.track(i).get(KCDDB::Title).toString().indexOf(divider);
0609         if (splitPos >= 0) {
0610             // split
0611             QString title = cd_info.track(i).get(KCDDB::Title).toString().mid(splitPos + divider.length());
0612             QString artist = cd_info.track(i).get(KCDDB::Title).toString().left(splitPos);
0613             cd_info.track(i).set(KCDDB::Artist, artist);
0614             cd_info.track(i).set(KCDDB::Title, title);
0615         }
0616     }
0617     modified = true;
0618     Q_EMIT cddbDataModified();
0619     endResetModel();
0620 }
0621 
0622 void CDDAModel::capitalizeTracks()
0623 {
0624     if (!p_cdio)
0625         return;
0626 
0627     beginResetModel();
0628     for (int i = 0; i < cd_info.numberOfTracks(); ++i) {
0629         cd_info.track(i).set(KCDDB::Artist, capitalize(cd_info.track(i).get(KCDDB::Artist).toString()));
0630         cd_info.track(i).set(KCDDB::Title, capitalize(cd_info.track(i).get(KCDDB::Title).toString()));
0631     }
0632     modified = true;
0633     endResetModel();
0634     Q_EMIT cddbDataModified();
0635 }
0636 
0637 void CDDAModel::capitalizeHeader()
0638 {
0639     if (!p_cdio)
0640         return;
0641 
0642     beginResetModel();
0643     cd_info.set(KCDDB::Artist, capitalize(cd_info.get(KCDDB::Artist).toString()));
0644     cd_info.set(KCDDB::Title, capitalize(cd_info.get(KCDDB::Title).toString()));
0645     modified = true;
0646     endResetModel();
0647     Q_EMIT cddbDataModified();
0648 }
0649 
0650 void CDDAModel::setTitleArtistsFromHeader()
0651 {
0652     if (!p_cdio)
0653         return;
0654 
0655     beginResetModel();
0656     for (int i = 0; i < cd_info.numberOfTracks(); ++i) {
0657         cd_info.track(i).set(KCDDB::Artist, cd_info.get(KCDDB::Artist));
0658     }
0659     modified = true;
0660     endResetModel();
0661     Q_EMIT cddbDataModified();
0662 }
0663 
0664 int CDDAModel::numOfTracks() const
0665 {
0666     if (!p_cdio)
0667         return 0;
0668     return p_cdio->numOfTracks();
0669 }
0670 
0671 int CDDAModel::numOfAudioTracks() const
0672 {
0673     int c = 0;
0674     for (int i = 1; i <= numOfTracks(); ++i) {
0675         if (isAudioTrack(i))
0676             ++c;
0677     }
0678     return c;
0679 }
0680 
0681 int CDDAModel::numOfAudioTracksInSelection() const
0682 {
0683     return sel_tracks.count();
0684 }
0685 
0686 int CDDAModel::length() const
0687 {
0688     if (!p_cdio)
0689         return 0;
0690     return p_cdio->length();
0691 }
0692 
0693 int CDDAModel::lengthOfAudioTracks() const
0694 {
0695     int c = 0;
0696     for (int i = 1; i <= numOfTracks(); ++i) {
0697         if (isAudioTrack(i))
0698             c += lengthOfTrack(i);
0699     }
0700     return c;
0701 }
0702 
0703 int CDDAModel::lengthOfAudioTracksInSelection() const
0704 {
0705     QSet<int>::ConstIterator it(sel_tracks.begin()), end(sel_tracks.end());
0706     int l = 0;
0707     for (; it != end; ++it) {
0708         if (isAudioTrack(*it))
0709             l += lengthOfTrack(*it);
0710     }
0711     return l;
0712 }
0713 
0714 int CDDAModel::lengthOfTrack(int n) const
0715 {
0716     if (!p_cdio)
0717         return 0;
0718     return p_cdio->lengthOfTrack(n);
0719 }
0720 
0721 const QList<quint32> CDDAModel::discSignature() const
0722 {
0723     if (!p_cdio)
0724         return QList<quint32>();
0725     return p_cdio->discSignature();
0726 }
0727 
0728 bool CDDAModel::isAudioTrack(int n) const
0729 {
0730     if (!p_cdio)
0731         return false;
0732     return p_cdio->isAudioTrack(n);
0733 }
0734 
0735 void CDDAModel::clear()
0736 {
0737     beginResetModel();
0738     cd_info.clear();
0739     clearCover();
0740     endResetModel();
0741 }
0742 
0743 void CDDAModel::toggle(int row)
0744 {
0745     p_toggle(row + 1);
0746 
0747     Q_EMIT hasSelection(0 != sel_tracks.count());
0748     Q_EMIT selectionChanged(sel_tracks.count());
0749 }
0750 
0751 bool CDDAModel::isTrackInSelection(int n) const
0752 {
0753     return sel_tracks.contains(n);
0754 }
0755 
0756 void CDDAModel::invertSelection()
0757 {
0758     for (int i = 1; i <= numOfTracks(); ++i) {
0759         if (isAudioTrack(i))
0760             p_toggle(i);
0761     }
0762     Q_EMIT hasSelection(0 != sel_tracks.count());
0763     Q_EMIT selectionChanged(sel_tracks.count());
0764 }
0765 
0766 void CDDAModel::selectAll()
0767 {
0768     sel_tracks.clear();
0769     invertSelection();
0770 }
0771 
0772 void CDDAModel::selectNone()
0773 {
0774     sel_tracks.clear();
0775     Q_EMIT hasSelection(false);
0776     Q_EMIT selectionChanged(0);
0777 }
0778 
0779 bool CDDAModel::isModified() const
0780 {
0781     return modified;
0782 }
0783 
0784 void CDDAModel::confirm()
0785 {
0786     modified = false;
0787 }
0788 
0789 Error CDDAModel::lastError() const
0790 {
0791     return error;
0792 }
0793 
0794 void CDDAModel::lookupCDDB()
0795 {
0796     if (!p_cdio)
0797         return;
0798 
0799     qDebug() << "lookupCDDB called";
0800 
0801     if (cddb_transaction_pending) {
0802         qDebug() << "CDDB transaction already in progress.";
0803         return;
0804     }
0805     cddb_transaction_pending = true;
0806 
0807     Q_EMIT cddbLookupStarted();
0808 
0809     cddb->config().reparse();
0810     cddb->setBlockingMode(false);
0811     cddb->lookup(p_cdio->discSignature());
0812 }
0813 
0814 bool CDDAModel::submitCDDB()
0815 {
0816     if (!p_cdio)
0817         return true;
0818 
0819     qDebug() << "submitCDDB called";
0820 
0821     if (cddb_transaction_pending) {
0822         qDebug() << "CDDB transaction already in progress.";
0823         error = Error(i18n("CDDB transaction already in progress."),
0824                       i18n("A CDDB transaction is already in progress. Please wait until it has finished and try again."),
0825                       Error::ERROR,
0826                       this);
0827         return false;
0828     }
0829 
0830     cddb_transaction_pending = true;
0831 
0832     cddb->config().reparse();
0833     cddb->setBlockingMode(true);
0834     if (category().isEmpty()) {
0835         setCategory("rock");
0836     }
0837     KCDDB::Result result = cddb->submit(cd_info, p_cdio->discSignature());
0838 
0839     if (result != KCDDB::Success) {
0840         switch (result) {
0841         case KCDDB::ServerError:
0842             error = Error(KCDDB::resultToString(result),
0843                           i18n("There is an error with the CDDB server. Please wait or contact the administrator of the CDDB server."),
0844                           Error::ERROR,
0845                           this);
0846             break;
0847         case KCDDB::HostNotFound:
0848             error = Error(KCDDB::resultToString(result),
0849                           i18n("Cannot find the CDDB server. Check your network. Maybe the CDDB server is offline."),
0850                           Error::ERROR,
0851                           this);
0852             break;
0853         case KCDDB::NoResponse:
0854             error = Error(KCDDB::resultToString(result),
0855                           i18n("Please wait, maybe the server is busy, or contact the CDDB server administrator."),
0856                           Error::ERROR,
0857                           this);
0858             break;
0859         case KCDDB::CannotSave:
0860             error = Error(KCDDB::resultToString(result), i18n("Please contact the CDDB server administrator."), Error::ERROR, this);
0861             break;
0862         case KCDDB::InvalidCategory:
0863             error = Error(KCDDB::resultToString(result), i18n("This should not happen. Please make a bug report."), Error::ERROR, this);
0864             break;
0865         case KCDDB::UnknownError:;
0866         case KCDDB::NoRecordFound:;
0867         case KCDDB::MultipleRecordFound:;
0868         case KCDDB::Success:;
0869         default:
0870             error = Error(KCDDB::resultToString(result), i18n("Please make a bug report and contact the CDDB server administrator."), Error::ERROR, this);
0871             break;
0872         }
0873         return false;
0874     }
0875 
0876     error = Error();
0877 
0878     confirm();
0879 
0880     cddb_transaction_pending = false;
0881 
0882     Q_EMIT cddbDataSubmited(true);
0883 
0884     return true;
0885 }
0886 
0887 void CDDAModel::eject()
0888 {
0889     devices->eject(udi);
0890 }
0891 
0892 void CDDAModel::new_audio_disc_available(const QString &udi)
0893 {
0894     if (p_cdio)
0895         return;
0896 
0897     device_file = devices->blockDevice(udi);
0898     this->udi = udi;
0899 
0900     p_cdio = new CDDACDIO(this);
0901     if (!p_cdio) {
0902         qDebug() << "Unable to create cdio class. low mem?";
0903         error = Error(i18n("Unable to create CDIO object."),
0904                       i18n("This is an internal error. Check your hardware. If all okay please make bug report."),
0905                       Error::ERROR,
0906                       this);
0907         return;
0908     }
0909     p_cdio->init(device_file);
0910 
0911     qDebug() << "new audio disc detected (" << udi << ", " << device_file << ")";
0912 
0913     clear();
0914     confirm();
0915 
0916     sel_tracks.clear();
0917     for (int i = 1; i <= p_cdio->numOfTracks(); ++i) {
0918         if (isAudioTrack(i))
0919             sel_tracks.insert(i);
0920     }
0921 
0922     Q_EMIT hasSelection(0 != sel_tracks.size());
0923 
0924     Q_EMIT audioDiscDetected();
0925 }
0926 
0927 void CDDAModel::audio_disc_removed(const QString &udi)
0928 {
0929     qDebug() << "audio disc removed (" << udi << ")";
0930 
0931     device_file.clear();
0932     this->udi.clear();
0933 
0934     if (p_cdio)
0935         delete p_cdio;
0936     p_cdio = nullptr;
0937 
0938     Q_EMIT audioDiscRemoved();
0939 }
0940 
0941 void CDDAModel::disc_information_modified()
0942 {
0943     qDebug() << "disc info changed";
0944 
0945     beginResetModel();
0946     set_default_values();
0947     setVarious(guessVarious());
0948     endResetModel();
0949 }
0950 
0951 void CDDAModel::lookup_cddb_done(KCDDB::Result result)
0952 {
0953     if ((result != KCDDB::Success) && (result != KCDDB::MultipleRecordFound)) {
0954         switch (result) {
0955         case KCDDB::ServerError:
0956             error = Error(KCDDB::resultToString(result),
0957                           i18n("There is an error with the CDDB server. Please wait or contact the administrator of the CDDB server."),
0958                           Error::ERROR,
0959                           this);
0960             break;
0961         case KCDDB::HostNotFound:
0962             error = Error(KCDDB::resultToString(result),
0963                           i18n("Cannot find the CDDB server. Check your network. Maybe the CDDB server is offline."),
0964                           Error::ERROR,
0965                           this);
0966             break;
0967         case KCDDB::NoResponse:
0968             error = Error(KCDDB::resultToString(result),
0969                           i18n("Please wait, maybe the server is busy, or contact the CDDB server administrator."),
0970                           Error::ERROR,
0971                           this);
0972             break;
0973         case KCDDB::InvalidCategory:
0974             error = Error(KCDDB::resultToString(result), i18n("This should not happen. Please make a bug report."), Error::ERROR, this);
0975             break;
0976         case KCDDB::UnknownError:
0977             error = Error(KCDDB::resultToString(result), i18n("Please make a bug report and contact the CDDB server administrator."), Error::ERROR, this);
0978             break;
0979         case KCDDB::NoRecordFound:;
0980         case KCDDB::MultipleRecordFound:;
0981         case KCDDB::Success:;
0982         default:
0983             error = Error(KCDDB::resultToString(result), i18n("This means no data found in the CDDB database."), Error::ERROR, this);
0984         }
0985         Q_EMIT cddbLookupDone(false);
0986         return;
0987     }
0988 
0989     KCDDB::CDInfo info = cddb->lookupResponse().constFirst();
0990     if (cddb->lookupResponse().count() > 1) {
0991         KCDDB::CDInfoList cddb_info = cddb->lookupResponse();
0992         KCDDB::CDInfoList::iterator it;
0993         QStringList list;
0994         for (it = cddb_info.begin(); it != cddb_info.end(); ++it) {
0995             list.append(QString("%1, %2, %3, %4")
0996                             .arg((it->get(KCDDB::Artist).toString()),
0997                                  it->get(KCDDB::Title).toString(),
0998                                  it->get(KCDDB::Genre).toString(),
0999                                  it->get(KCDDB::Year).toString()));
1000         }
1001 
1002         bool ok = false;
1003         // Uses a ComboBox, could use UseListViewForComboBoxItems if necessary
1004         QString res = QInputDialog::getItem(nullptr, i18n("Select CDDB Entry"), i18n("Select a CDDB entry:"), list, 0, false, &ok, {});
1005 
1006         if (ok) {
1007             // The user selected an item and pressed OK
1008             int c = 0;
1009             for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) {
1010                 if (*it == res)
1011                     break;
1012                 c++;
1013             }
1014             if (c < cddb_info.size())
1015                 info = cddb_info[c];
1016         } else {
1017             Q_EMIT cddbLookupDone(true);
1018             return;
1019             // user pressed cancel
1020         }
1021     }
1022 
1023     beginResetModel();
1024 
1025     cd_info = info;
1026     set_default_values();
1027     setVarious(guessVarious());
1028     if (isVarious() && QLatin1String("Various") == artist()) {
1029         setArtist(i18n("Various Artists"));
1030     }
1031 
1032     QString newTitle;
1033     int cdnum = guessMultiCD(newTitle);
1034     if (cdnum > 0) {
1035         setMultiCD(true);
1036         setCDNum(cdnum);
1037         setTitle(newTitle);
1038     }
1039     endResetModel();
1040 
1041     cddb_transaction_pending = false;
1042 
1043     p_empty = false;
1044 
1045     Q_EMIT cddbLookupDone(true);
1046 }
1047 
1048 void CDDAModel::p_toggle(const unsigned int track)
1049 {
1050     if (sel_tracks.contains(track)) {
1051         sel_tracks.remove(track);
1052     } else {
1053         sel_tracks.insert(track);
1054     }
1055 }
1056 
1057 const QString CDDAModel::capitalize(const QString &s)
1058 {
1059     QStringList stringlist = s.split(' ', Qt::SkipEmptyParts);
1060     for (int i = 0; i < stringlist.count(); i++) {
1061         QString string = stringlist[i].toLower();
1062         int j = 0;
1063         while (((string[j] == '(') || (string[j] == '[') || (string[j] == '{')) && (j < string.length()))
1064             j++;
1065         string[j] = string[j].toUpper();
1066         stringlist[i] = string;
1067     }
1068     return stringlist.join(" ");
1069 }
1070 
1071 void CDDAModel::set_default_values()
1072 {
1073     if (cd_info.get(KCDDB::Year).toString().isEmpty())
1074         cd_info.set(KCDDB::Year, QString("%1").arg(QDate::currentDate().year()));
1075     cd_info.set("DNO", 1);
1076     cd_info.set("DTRACKOFFSET", 1);
1077     cd_info.set("DMULTICD", false);
1078 }
1079 
1080 void CDDAModel::modify()
1081 {
1082     modified = true;
1083     p_empty = false;
1084     Q_EMIT cddbDataModified();
1085 }