File indexing completed on 2023-05-30 11:30:48

0001 /**
0002  * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
0003  * Copyright (C) 2009 Michael Pyne <mpyne@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify it under
0006  * the terms of the GNU General Public License as published by the Free Software
0007  * Foundation; either version 2 of the License, or (at your option) any later
0008  * version.
0009  *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU General Public License along with
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #include "nowplaying.h"
0019 
0020 #include <KIO/StoredTransferJob>
0021 #include <KJobWidgets>
0022 #include <KLocalizedString>
0023 #include <kiconloader.h>
0024 
0025 #include <QApplication>
0026 #include <QDrag>
0027 #include <QDragEnterEvent>
0028 #include <QDropEvent>
0029 #include <QFontDatabase>
0030 #include <QFontMetrics>
0031 #include <QImage>
0032 #include <QLabel>
0033 #include <QLayout>
0034 #include <QList>
0035 #include <QMouseEvent>
0036 #include <QPoint>
0037 #include <QTimer>
0038 #include <QUrl>
0039 #include <QVBoxLayout>
0040 
0041 #include "playlistcollection.h"
0042 #include "playlistitem.h"
0043 #include "coverinfo.h"
0044 #include "covermanager.h"
0045 #include "juktag.h"
0046 #include "collectionlist.h"
0047 #include "juk_debug.h"
0048 
0049 ////////////////////////////////////////////////////////////////////////////////
0050 // NowPlaying
0051 ////////////////////////////////////////////////////////////////////////////////
0052 
0053 NowPlaying::NowPlaying(QWidget *parent, PlaylistCollection *collection) :
0054     QWidget(parent),
0055     m_observer(this, collection),
0056     // Also watch the collection
0057     m_collectionListObserver(this, CollectionList::instance()),
0058     m_collection(collection)
0059 {
0060     setObjectName(QLatin1String("NowPlaying"));
0061 
0062     QHBoxLayout *layout = new QHBoxLayout(this);
0063     setLayout(layout);
0064 
0065     layout->setContentsMargins(0, 0, 0, 0);
0066     layout->setSpacing(3);
0067 
0068     // With HiDPI the text might actually be bigger... try to account for
0069     // that.
0070     const QFont defaultLargeFont(QFontDatabase::systemFont(QFontDatabase::TitleFont));
0071     const QFontMetrics fm(defaultLargeFont, this);
0072 
0073     const int coverIconHeight = qMax(64, fm.lineSpacing() * 2);
0074     setFixedHeight(coverIconHeight + 4);
0075 
0076     layout->addWidget(new CoverItem(this), 0);
0077     layout->addWidget(new TrackItem(this), 2);
0078 
0079     hide();
0080 }
0081 
0082 void NowPlaying::addItem(NowPlayingItem *item)
0083 {
0084     m_items.append(item);
0085 }
0086 
0087 PlaylistCollection *NowPlaying::collection() const
0088 {
0089     return m_collection;
0090 }
0091 
0092 void NowPlaying::slotUpdate(const FileHandle &file)
0093 {
0094     m_file = file;
0095     if(file.isNull()) {
0096         hide();
0097         emit nowPlayingHidden();
0098         return;
0099     }
0100     else
0101         show();
0102 
0103     foreach(NowPlayingItem *item, m_items)
0104         item->update(file);
0105 }
0106 
0107 void NowPlaying::slotReloadCurrentItem()
0108 {
0109     foreach(NowPlayingItem *item, m_items)
0110         item->update(m_file);
0111 }
0112 
0113 ////////////////////////////////////////////////////////////////////////////////
0114 // CoverItem
0115 ////////////////////////////////////////////////////////////////////////////////
0116 
0117 CoverItem::CoverItem(NowPlaying *parent) :
0118     QLabel(parent),
0119     NowPlayingItem(parent)
0120 {
0121     setObjectName(QLatin1String("CoverItem"));
0122     const QMargins margins = parent->layout()->contentsMargins();
0123 
0124     setFixedHeight(parent->height() - (margins.top() + margins.bottom()));
0125     setContentsMargins(1, 1, 1, 1);
0126     setAcceptDrops(true);
0127 }
0128 
0129 void CoverItem::update(const FileHandle &file)
0130 {
0131     m_file = file;
0132 
0133     if(!file.isNull() && file.coverInfo()->hasCover()) {
0134         show();
0135 
0136         const auto pixRatio = this->devicePixelRatioF();
0137         const QSizeF logicalSize = QSizeF(this->height(), this->height());
0138         const QSizeF scaledSize = logicalSize * pixRatio;
0139         QPixmap pix =
0140             file.coverInfo()->pixmap(CoverInfo::FullSize)
0141             .scaled(scaledSize.toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
0142 
0143         if (!qFuzzyCompare(pixRatio, 1.0)) {
0144             pix.setDevicePixelRatio(pixRatio);
0145         }
0146 
0147         setPixmap(pix);
0148     }
0149     else
0150         hide();
0151 }
0152 
0153 void CoverItem::mouseReleaseEvent(QMouseEvent *event)
0154 {
0155     if(m_dragging) {
0156         m_dragging = false;
0157         return;
0158     }
0159 
0160     if(event->x() >= 0 && event->y() >= 0 &&
0161        event->x() < width() && event->y() < height() &&
0162        event->button() == Qt::LeftButton &&
0163        m_file.coverInfo()->hasCover())
0164     {
0165         m_file.coverInfo()->popup();
0166     }
0167 
0168     QLabel::mousePressEvent(event);
0169 }
0170 
0171 void CoverItem::mousePressEvent(QMouseEvent *e)
0172 {
0173     m_dragging = false;
0174     m_dragStart = e->globalPos();
0175 }
0176 
0177 void CoverItem::mouseMoveEvent(QMouseEvent *e)
0178 {
0179     if(m_dragging)
0180         return;
0181 
0182     QPoint diff = m_dragStart - e->globalPos();
0183     if(diff.manhattanLength() > QApplication::startDragDistance()) {
0184 
0185         // Start a drag.
0186 
0187         m_dragging = true;
0188 
0189         QDrag *drag = new QDrag(this);
0190         CoverDrag *data = new CoverDrag(m_file.coverInfo()->coverId());
0191 
0192         drag->setMimeData(data);
0193         drag->exec(Qt::CopyAction);
0194     }
0195 }
0196 
0197 void CoverItem::dragEnterEvent(QDragEnterEvent *e)
0198 {
0199     e->setAccepted(CoverDrag::isCover(e->mimeData()) || e->mimeData()->hasUrls());
0200 }
0201 
0202 void CoverItem::dropEvent(QDropEvent *e)
0203 {
0204     QImage image;
0205     QList<QUrl> urls;
0206     coverKey key;
0207 
0208     if(e->source() == this)
0209         return;
0210 
0211     key = CoverDrag::idFromData(e->mimeData());
0212     if(key != CoverManager::NoMatch) {
0213         m_file.coverInfo()->setCoverId(key);
0214         update(m_file);
0215     }
0216     else if(e->mimeData()->hasImage()) {
0217         m_file.coverInfo()->setCover(qvariant_cast<QImage>(e->mimeData()->imageData()));
0218         update(m_file);
0219     }
0220     else {
0221         urls = e->mimeData()->urls();
0222         if(urls.isEmpty())
0223             return;
0224 
0225         auto getJob = KIO::storedGet(urls.front());
0226         KJobWidgets::setWindow(getJob, this);
0227         if(getJob->exec()) {
0228             if(image.loadFromData(getJob->data())) {
0229                 m_file.coverInfo()->setCover(image);
0230                 update(m_file);
0231             }
0232             else
0233                 qCCritical(JUK_LOG) << "Unable to load image from " << urls.front();
0234         }
0235         else
0236             qCCritical(JUK_LOG) << "Unable to download " << urls.front();
0237     }
0238 }
0239 
0240 ////////////////////////////////////////////////////////////////////////////////
0241 // TrackItem
0242 ////////////////////////////////////////////////////////////////////////////////
0243 
0244 TrackItem::TrackItem(NowPlaying *parent) :
0245     QWidget(parent),
0246     NowPlayingItem(parent)
0247 {
0248     setObjectName(QLatin1String("TrackItem"));
0249     const QMargins margins = parent->layout()->contentsMargins();
0250     setFixedHeight(parent->height() - (margins.top() + margins.bottom()));
0251     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
0252 
0253     QVBoxLayout *layout = new QVBoxLayout(this);
0254 
0255     m_label = new QLabel(this);
0256     m_label->setWordWrap(true);
0257 
0258     m_label->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::LinksAccessibleByKeyboard|Qt::TextSelectableByMouse);
0259 
0260     layout->addStretch();
0261     layout->addWidget(m_label, 1);
0262     layout->addStretch();
0263 
0264     connect(m_label, SIGNAL(linkActivated(QString)), this,
0265             SLOT(slotOpenLink(QString)));
0266 
0267     // Ensure that if we're filtering results, that the filtering is cleared if we
0268     // hide the now playing bar so that the user can select tracks normally.
0269 
0270     connect(parent, SIGNAL(nowPlayingHidden()), SLOT(slotClearShowMore()));
0271 }
0272 
0273 void TrackItem::update(const FileHandle &file)
0274 {
0275     m_file = file;
0276     QTimer::singleShot(0, this, SLOT(slotUpdate()));
0277 }
0278 
0279 void TrackItem::slotOpenLink(const QString &link)
0280 {
0281     PlaylistCollection *collection = parentManager()->collection();
0282 
0283     if(link == "artist")
0284         collection->showMore(m_file.tag()->artist());
0285     else if(link == "album")
0286         collection->showMore(QString(), m_file.tag()->album());
0287     else if(link == "clear")
0288         collection->clearShowMore();
0289 
0290     update(m_file);
0291 }
0292 
0293 void TrackItem::slotUpdate()
0294 {
0295     if(m_file.isNull()) {
0296         m_label->setText(QString());
0297         return;
0298     }
0299 
0300     const QString title  = m_file.tag()->title().toHtmlEscaped();
0301     const QString artist = m_file.tag()->artist().toHtmlEscaped();
0302     const QString album  = m_file.tag()->album().toHtmlEscaped();
0303     const QString separator =
0304         (artist.isEmpty() || album.isEmpty())
0305         ? QString() : QString(" - ");
0306 
0307     // This block-o-nastiness makes the font smaller and smaller until it actually fits.
0308 
0309     int size = 4;
0310     QString format =
0311         "<font size=\"+%1\"><b>%2</b></font>"
0312         "<br />"
0313         "<font size=\"+%3\"><b><a href=\"artist\">%4</a>%5<a href=\"album\">%6</a></b>";
0314 
0315     if(parentManager()->collection()->showMoreActive())
0316         format.append(QString(" (<a href=\"clear\">%1</a>)").arg(i18n("back to playlist")));
0317 
0318     format.append("</font>");
0319     int parentHeight = parentManager()->contentsRect().height();
0320     int neededHeight = 0;
0321 
0322     do {
0323         m_label->setText(format.arg(size).arg(title).arg(size - 2)
0324                          .arg(artist, separator, album));
0325         --size;
0326         neededHeight = m_label->heightForWidth(m_label->width());
0327     } while(neededHeight > parentHeight && size >= -1);
0328 
0329     m_label->setFixedHeight(qMin(neededHeight, parentHeight));
0330 }
0331 
0332 void TrackItem::slotClearShowMore()
0333 {
0334     PlaylistCollection *collection = parentManager()->collection();
0335     Q_ASSERT(collection);
0336     collection->clearShowMore();
0337 }
0338 
0339 // vim: set et sw=4 tw=0 sta: