File indexing completed on 2024-04-14 04:43:18

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 "cddaheaderwidget.h"
0009 
0010 #include <QDebug>
0011 #include <QFileDialog>
0012 #include <QMenu>
0013 #include <qnamespace.h>
0014 
0015 CDDAHeaderWidget ::CDDAHeaderWidget(CDDAModel *cddaModel, QWidget *parent, const int cover_size_min, const int cover_size_max, const int padding)
0016     : QWidget(parent)
0017 {
0018     cdda_model = cddaModel;
0019     if (!cdda_model) {
0020         qDebug() << "CDDAModel is NULL!";
0021         return;
0022     }
0023     connect(cdda_model, SIGNAL(modelReset()), this, SLOT(update()));
0024 
0025     setup_actions();
0026 
0027     this->cover_size_min = cover_size_min;
0028     this->cover_size_max = cover_size_max;
0029     this->padding = padding;
0030 
0031     setMouseTracking(true);
0032     cursor_on_cover = false;
0033     cursor_on_artist_label = false;
0034     cursor_on_title_label = false;
0035     cursor_on_year_label = false;
0036 
0037     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(context_menu(const QPoint &)));
0038 
0039     setContextMenuPolicy(Qt::CustomContextMenu);
0040 
0041     setMinimumSize(QSize(cover_size_min + (padding * 2), cover_size_min + (padding * 2)));
0042 
0043     update();
0044 }
0045 
0046 CDDAHeaderWidget::~CDDAHeaderWidget()
0047 {
0048     delete action_collection;
0049 }
0050 
0051 QSize CDDAHeaderWidget::sizeHint() const
0052 {
0053     return QSize(cover_size_min + (padding * 2), cover_size_min + (padding * 2));
0054 }
0055 
0056 bool CDDAHeaderWidget::isEnabled() const
0057 {
0058     return enabled;
0059 }
0060 
0061 void CDDAHeaderWidget::setEnabled(bool enabled)
0062 {
0063     this->enabled = enabled;
0064     repaint();
0065 }
0066 
0067 void CDDAHeaderWidget::paintEvent(QPaintEvent *event)
0068 {
0069     Q_UNUSED(event);
0070 
0071     QPainter painter;
0072 
0073     painter.begin(this);
0074 
0075     if (enabled) {
0076         const bool vertical = this->frameGeometry().width() > this->frameGeometry().height();
0077         QImage scaled_cd_case =
0078             cd_case.scaled(vertical ? QSize(qMin(rect().height() - padding * 2, cover_size_max), qMin(rect().height() - padding * 2, cover_size_max))
0079                                     : QSize(qMin(rect().width() - padding * 2, cover_size_max), qMin(rect().width() - padding * 2, cover_size_max)),
0080                            Qt::KeepAspectRatio,
0081                            Qt::SmoothTransformation);
0082 
0083         int xOffset = padding;
0084         int yOffset = padding;
0085 
0086         // QImage scaled_cover = cover.scaled(cover_size, cover_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0087         cover_rect = QRect(xOffset, yOffset, scaled_cd_case.width(), scaled_cd_case.height());
0088         painter.drawImage(QPoint(xOffset, yOffset), scaled_cd_case);
0089 
0090         if (vertical) {
0091             xOffset += scaled_cd_case.width() + padding;
0092         } else {
0093             yOffset += scaled_cd_case.height();
0094         }
0095 
0096         painter.setBrush(palette().text());
0097 
0098         QFont font(QApplication::font());
0099 
0100         int pixelSize = font.pixelSize() == -1 ? (font.pointSize() * qApp->devicePixelRatio() + 36) / 72 : font.pixelSize();
0101         int width = rect().width() - (xOffset + 1);
0102         font.setPixelSize((int)((((double)pixelSize) * 2) + 0.5));
0103         font.setBold(true);
0104         font.setUnderline(cursor_on_artist_label || cursor_on_title_label || cursor_on_year_label);
0105         painter.setFont(font);
0106         artist_label_rect = QRect(QPoint(xOffset, yOffset + 10), painter.fontMetrics().boundingRect(cdda_model->artist()).size());
0107         yOffset += painter.fontMetrics().lineSpacing() + 10;
0108         painter.drawText(xOffset, yOffset, painter.fontMetrics().elidedText(cdda_model->artist(), Qt::ElideRight, width));
0109 
0110         font.setPixelSize((int)((((double)pixelSize) * 1.6) + 0.5));
0111         font.setBold(true);
0112         font.setItalic(true);
0113         painter.setFont(font);
0114         title_label_rect = QRect(QPoint(xOffset, yOffset + 10), painter.fontMetrics().boundingRect(cdda_model->title()).size());
0115         yOffset += (int)((((double)pixelSize) * 1.6) + 0.5) + 10;
0116         painter.drawText(xOffset, yOffset, painter.fontMetrics().elidedText(cdda_model->title(), Qt::ElideRight, width));
0117 
0118         if (!cdda_model->year().isEmpty()) {
0119             font.setItalic(false);
0120             font.setUnderline(false);
0121             painter.setFont(font);
0122             painter.drawText(xOffset + title_label_rect.width(), yOffset, QString(" (%1)").arg(cdda_model->year()));
0123             year_label_rect = QRect(QPoint(xOffset + title_label_rect.width(), title_label_rect.y()),
0124                                     painter.fontMetrics().boundingRect(QString(" (%1)").arg(cdda_model->year())).size());
0125         }
0126 
0127         if (cdda_model->cdNum() > 0) {
0128             font.setItalic(false);
0129             font.setUnderline(false);
0130             font.setPixelSize((int)((((double)pixelSize)) + 0.5));
0131             painter.setFont(font);
0132             yOffset += painter.fontMetrics().lineSpacing() + 10;
0133             painter.drawText(xOffset, yOffset, QString("[%1%2]").arg(i18n("CD Number: ")).arg(cdda_model->cdNum()));
0134         }
0135 
0136     } else { // disabled
0137 
0138         QFont font(QApplication::font());
0139         if (font.pixelSize() == -1) {
0140             font.setPointSizeF(font.pointSizeF() * 1.6);
0141         } else {
0142             font.setPixelSize(font.pixelSize() * 1.6);
0143         }
0144         font.setBold(true);
0145         font.setItalic(true);
0146         painter.setFont(font);
0147         painter.drawText(rect(), Qt::AlignCenter | Qt::AlignVCenter, i18n("No audio CD detected"));
0148     }
0149 
0150     painter.end();
0151 }
0152 
0153 void CDDAHeaderWidget::construct_cd_case()
0154 {
0155     QImage cdcase_wo_latches = QImage(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("audex/images/cdcase_wo_latches.png")));
0156     QImage latches = QImage(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("audex/images/latches.png")));
0157     QImage cdcase_covered = QImage(cdcase_wo_latches.size(), QImage::Format_ARGB32_Premultiplied);
0158 
0159     QPainter cover_painter(&cdcase_covered);
0160     cover_painter.setRenderHint(QPainter::Antialiasing);
0161 
0162     cover_painter.setCompositionMode(QPainter::CompositionMode_Source);
0163     cover_painter.fillRect(cdcase_covered.rect(), Qt::transparent);
0164 
0165     cover_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
0166     cover_painter.drawImage(0, 0, cdcase_wo_latches);
0167 
0168     cover_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
0169     if (!cdda_model->cover().isNull())
0170         cover_painter.drawImage(125, 15, cdda_model->cover().scaled(QSize(1110, 1080), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
0171 
0172     cover_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
0173     cover_painter.drawImage(259, 0, latches);
0174 
0175     cover_painter.end();
0176 
0177     this->cd_case = cdcase_covered;
0178 }
0179 
0180 void CDDAHeaderWidget::mouseMoveEvent(QMouseEvent *event)
0181 {
0182     if (cover_rect.contains(event->pos())) {
0183         if (!cursor_on_cover) {
0184             QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
0185             cursor_on_cover = true;
0186         }
0187     } else if (artist_label_rect.contains(event->pos())) {
0188         if (!cursor_on_artist_label) {
0189             QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
0190             cursor_on_artist_label = true;
0191             repaint();
0192         }
0193     } else if (title_label_rect.contains(event->pos())) {
0194         if (!cursor_on_title_label) {
0195             QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
0196             cursor_on_title_label = true;
0197             repaint();
0198         }
0199     } else if (year_label_rect.contains(event->pos())) {
0200         if (!cursor_on_year_label) {
0201             QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
0202             cursor_on_year_label = true;
0203             repaint();
0204         }
0205     } else {
0206         QApplication::restoreOverrideCursor();
0207         if (cursor_on_cover) {
0208             cursor_on_cover = false;
0209         } else if (cursor_on_artist_label) {
0210             cursor_on_artist_label = false;
0211             repaint();
0212         } else if (cursor_on_title_label) {
0213             cursor_on_title_label = false;
0214             repaint();
0215         } else if (cursor_on_year_label) {
0216             cursor_on_year_label = false;
0217             repaint();
0218         }
0219     }
0220 }
0221 
0222 void CDDAHeaderWidget::mousePressEvent(QMouseEvent *event)
0223 {
0224     if (event->button() == Qt::LeftButton) {
0225         if (cursor_on_cover) {
0226             QApplication::restoreOverrideCursor();
0227             if (cdda_model->isCoverEmpty()) {
0228                 load();
0229             } else {
0230                 view_cover();
0231             }
0232         }
0233         if (cursor_on_artist_label || cursor_on_title_label || cursor_on_year_label) {
0234             QApplication::restoreOverrideCursor();
0235             edit_data();
0236         }
0237     }
0238 }
0239 
0240 void CDDAHeaderWidget::update()
0241 {
0242     // action_collection->action("fetch")->setEnabled(!cdda_model->empty());
0243 
0244     bool activate = false;
0245     if (!cdda_model->cover().isNull()) {
0246         activate = true;
0247     }
0248 
0249     action_collection->action("save")->setEnabled(activate);
0250     action_collection->action("view")->setEnabled(activate);
0251     action_collection->action("remove")->setEnabled(activate);
0252 
0253     construct_cd_case();
0254     repaint();
0255 }
0256 
0257 void CDDAHeaderWidget::load()
0258 {
0259     qDebug() << "Supported cover image file MIME types:" << cdda_model->coverSupportedMimeTypeList();
0260     QString filename = QFileDialog::getOpenFileName(this, i18n("Load Cover"), QDir::homePath(), cdda_model->coverSupportedMimeTypeList());
0261     if (!filename.isEmpty()) {
0262         if (!cdda_model->setCover(filename)) {
0263             ErrorDialog::show(this, cdda_model->lastError().message(), cdda_model->lastError().details());
0264         }
0265     }
0266 }
0267 
0268 void CDDAHeaderWidget::save()
0269 {
0270     QString filename = QFileDialog::getSaveFileName(this, i18n("Save Cover"), QDir::homePath() + '/' + cdda_model->title() + ".jpg");
0271     if (!filename.isEmpty()) {
0272         if (!cdda_model->saveCoverToFile(filename)) {
0273             ErrorDialog::show(this, cdda_model->lastError().message(), cdda_model->lastError().details());
0274         }
0275     }
0276 }
0277 
0278 void CDDAHeaderWidget::view_cover()
0279 {
0280     QString filename = tmp_dir.path() + QString("%1.jpeg").arg(cdda_model->cover().cacheKey(), 8, 16);
0281     cdda_model->saveCoverToFile(filename);
0282 
0283     qDebug() << "Open" << filename;
0284 
0285     QDesktopServices::openUrl(QUrl(filename));
0286 }
0287 
0288 void CDDAHeaderWidget::remove()
0289 {
0290     cdda_model->clearCover();
0291     update();
0292 }
0293 
0294 void CDDAHeaderWidget::edit_data()
0295 {
0296     QApplication::restoreOverrideCursor();
0297     cursor_on_artist_label = false;
0298     cursor_on_title_label = false;
0299     cursor_on_year_label = false;
0300 
0301     auto *dialog = new CDDAHeaderDataDialog(cdda_model, this);
0302 
0303     if (dialog->exec() != QDialog::Accepted) {
0304         delete dialog;
0305         return;
0306     }
0307     delete dialog;
0308     update();
0309     Q_EMIT headerDataChanged();
0310 }
0311 
0312 void CDDAHeaderWidget::set_cover(const QByteArray &cover)
0313 {
0314     if (!cover.isEmpty())
0315         cdda_model->setCover(cover);
0316     // action_collection->action("fetch")->setEnabled(true);
0317     update();
0318 }
0319 
0320 void CDDAHeaderWidget::context_menu(const QPoint &point)
0321 {
0322     qDebug() << "context menu requested at point" << point;
0323     if (cursor_on_cover) {
0324         QApplication::restoreOverrideCursor();
0325         cursor_on_cover = false;
0326         QMenu contextMenu(this);
0327         auto *mevent = new QMouseEvent(QEvent::MouseButtonPress, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier);
0328         contextMenu.clear();
0329         // contextMenu.addAction(action_collection->action("fetch"));
0330         contextMenu.addAction(action_collection->action("load"));
0331         contextMenu.addAction(action_collection->action("save"));
0332         contextMenu.addSeparator();
0333         contextMenu.addAction(action_collection->action("view"));
0334         contextMenu.addSeparator();
0335         contextMenu.addAction(action_collection->action("remove"));
0336         contextMenu.exec(mevent->globalPos());
0337     }
0338 }
0339 
0340 void CDDAHeaderWidget::setup_actions()
0341 {
0342     action_collection = new KActionCollection(this);
0343 
0344     // auto *fetchCoverAction = new QAction(this);
0345     // fetchCoverAction->setText(i18n("Fetch cover from Google..."));
0346     // action_collection->addAction("fetch", fetchCoverAction);
0347     // connect(fetchCoverAction, SIGNAL(triggered(bool)), this, SLOT(google()));
0348 
0349     auto *loadCoverAction = new QAction(this);
0350     loadCoverAction->setText(i18n("Set Custom Cover..."));
0351     action_collection->addAction("load", loadCoverAction);
0352     connect(loadCoverAction, SIGNAL(triggered(bool)), this, SLOT(load()));
0353 
0354     auto *saveCoverAction = new QAction(this);
0355     saveCoverAction->setText(i18n("Save Cover To File..."));
0356     action_collection->addAction("save", saveCoverAction);
0357     connect(saveCoverAction, SIGNAL(triggered(bool)), this, SLOT(save()));
0358 
0359     auto *viewCoverAction = new QAction(this);
0360     viewCoverAction->setText(i18n("Show Full Size Cover..."));
0361     action_collection->addAction("view", viewCoverAction);
0362     connect(viewCoverAction, SIGNAL(triggered(bool)), this, SLOT(view_cover()));
0363 
0364     auto *removeCoverAction = new QAction(this);
0365     removeCoverAction->setText(i18n("Remove Cover"));
0366     action_collection->addAction("remove", removeCoverAction);
0367     connect(removeCoverAction, SIGNAL(triggered(bool)), this, SLOT(remove()));
0368 }