File indexing completed on 2021-12-21 13:27:55

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