nowplaying.cpp 9.24 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2009 Michael Pyne <mpyne@kde.org>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
17

18 19
#include "nowplaying.h"

20
#include <kiconloader.h>
Michael Pyne's avatar
Michael Pyne committed
21 22 23
#include <KLocalizedString>
#include <KIO/StoredTransferJob>
#include <KJobWidgets>
24

25
#include <QImage>
26
#include <QLayout>
27
#include <QEvent>
Michael Pyne's avatar
Michael Pyne committed
28
#include <QDrag>
29
#include <QTimer>
30
#include <QPoint>
Laurent Montel's avatar
Laurent Montel committed
31
#include <QFrame>
Laurent Montel's avatar
Laurent Montel committed
32 33 34 35 36
#include <QDropEvent>
#include <QLabel>
#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QMouseEvent>
Michael Pyne's avatar
Michael Pyne committed
37 38
#include <QUrl>
#include <QList>
Laurent Montel's avatar
Laurent Montel committed
39
#include <QTextDocument>
40
#include <QFontMetrics>
Michael Pyne's avatar
Michael Pyne committed
41
#include <QFontDatabase>
42
#include <QApplication>
43

44
#include "playlistcollection.h"
45
#include "playlistitem.h"
46
#include "coverinfo.h"
47
#include "covermanager.h"
48
#include "tag.h"
49
#include "collectionlist.h"
Michael Pyne's avatar
Michael Pyne committed
50
#include "juk_debug.h"
51 52

////////////////////////////////////////////////////////////////////////////////
53
// NowPlaying
54 55
////////////////////////////////////////////////////////////////////////////////

56 57
NowPlaying::NowPlaying(QWidget *parent, PlaylistCollection *collection) :
    QWidget(parent),
58
    m_observer(this, collection),
59 60
    // Also watch the collection
    m_collectionListObserver(this, CollectionList::instance()),
61
    m_collection(collection)
62
{
63
    setObjectName(QLatin1String("NowPlaying"));
64 65 66 67 68 69

    QHBoxLayout *layout = new QHBoxLayout(this);
    setLayout(layout);

    layout->setMargin(0);
    layout->setSpacing(3);
70 71 72

    // With HiDPI the text might actually be bigger... try to account for
    // that.
Michael Pyne's avatar
Michael Pyne committed
73
    const QFont defaultLargeFont(QFontDatabase::systemFont(QFontDatabase::TitleFont));
74 75
    const QFontMetrics fm(defaultLargeFont, this);

76 77
    const int coverIconHeight = qMax(64, fm.lineSpacing());
    setFixedHeight(coverIconHeight + 2);
78

79 80
    layout->addWidget(new CoverItem(this), 0);
    layout->addWidget(new TrackItem(this), 2);
81

82
    hide();
83 84
}

85
void NowPlaying::addItem(NowPlayingItem *item)
86
{
87
    m_items.append(item);
88 89
}

90 91 92 93 94
PlaylistCollection *NowPlaying::collection() const
{
    return m_collection;
}

95
void NowPlaying::slotUpdate(const FileHandle &file)
96
{
97
    m_file = file;
98 99
    if(file.isNull()) {
        hide();
100
        emit nowPlayingHidden();
101
        return;
102 103 104
    }
    else
        show();
105

106 107
    foreach(NowPlayingItem *item, m_items)
        item->update(file);
108 109
}

110 111 112 113 114 115
void NowPlaying::slotReloadCurrentItem()
{
    foreach(NowPlayingItem *item, m_items)
        item->update(m_file);
}

116 117
////////////////////////////////////////////////////////////////////////////////
// CoverItem
118 119
////////////////////////////////////////////////////////////////////////////////

120
CoverItem::CoverItem(NowPlaying *parent) :
121
    QLabel(parent),
122
    NowPlayingItem(parent)
123
{
124
    setObjectName(QLatin1String("CoverItem"));
125 126
    setFixedHeight(parent->height() - parent->layout()->margin() * 2);
    setMargin(1);
127
    setAcceptDrops(true);
128
}
129

130 131
void CoverItem::update(const FileHandle &file)
{
132 133
    m_file = file;

134
    if(!file.isNull() && file.coverInfo()->hasCover()) {
135
        show();
136 137 138 139 140 141 142 143 144 145 146 147 148

        const auto pixRatio = this->devicePixelRatioF();
        const QSizeF logicalSize = QSizeF(this->height(), this->height());
        const QSizeF scaledSize = logicalSize * pixRatio;
        QPixmap pix =
            file.coverInfo()->pixmap(CoverInfo::FullSize)
            .scaled(scaledSize.toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation);

        if (!qFuzzyCompare(pixRatio, 1.0)) {
            pix.setDevicePixelRatio(pixRatio);
        }

        setPixmap(pix);
149
    }
150 151
    else
        hide();
152 153
}

154
void CoverItem::mouseReleaseEvent(QMouseEvent *event)
155
{
156 157 158 159 160
    if(m_dragging) {
        m_dragging = false;
        return;
    }

161 162
    if(event->x() >= 0 && event->y() >= 0 &&
       event->x() < width() && event->y() < height() &&
163
       event->button() == Qt::LeftButton &&
164 165
       m_file.coverInfo()->hasCover())
    {
166
        m_file.coverInfo()->popup();
167
    }
168

169 170 171
    QLabel::mousePressEvent(event);
}

172 173 174 175 176 177 178 179 180 181 182 183
void CoverItem::mousePressEvent(QMouseEvent *e)
{
    m_dragging = false;
    m_dragStart = e->globalPos();
}

void CoverItem::mouseMoveEvent(QMouseEvent *e)
{
    if(m_dragging)
        return;

    QPoint diff = m_dragStart - e->globalPos();
184
    if(diff.manhattanLength() > QApplication::startDragDistance()) {
185 186 187 188 189

        // Start a drag.

        m_dragging = true;

Michael Pyne's avatar
Michael Pyne committed
190 191 192 193 194
        QDrag *drag = new QDrag(this);
        CoverDrag *data = new CoverDrag(m_file.coverInfo()->coverId());

        drag->setMimeData(data);
        drag->exec(Qt::CopyAction);
195 196 197
    }
}

198 199
void CoverItem::dragEnterEvent(QDragEnterEvent *e)
{
Michael Pyne's avatar
Michael Pyne committed
200
    e->setAccepted(CoverDrag::isCover(e->mimeData()) || e->mimeData()->hasUrls());
201 202 203 204 205
}

void CoverItem::dropEvent(QDropEvent *e)
{
    QImage image;
Michael Pyne's avatar
Michael Pyne committed
206
    QList<QUrl> urls;
207 208 209 210
    coverKey key;

    if(e->source() == this)
        return;
211

Michael Pyne's avatar
Michael Pyne committed
212 213 214
    key = CoverDrag::idFromData(e->mimeData());
    if(key != CoverManager::NoMatch) {
        m_file.coverInfo()->setCoverId(key);
215 216
        update(m_file);
    }
Michael Pyne's avatar
Michael Pyne committed
217 218
    else if(e->mimeData()->hasImage()) {
        m_file.coverInfo()->setCover(qvariant_cast<QImage>(e->mimeData()->imageData()));
219 220
        update(m_file);
    }
Michael Pyne's avatar
Michael Pyne committed
221 222 223 224 225
    else {
        urls = e->mimeData()->urls();
        if(urls.isEmpty())
            return;

226 227
        QString fileName;

Michael Pyne's avatar
Michael Pyne committed
228 229 230 231
        auto getJob = KIO::storedGet(urls.front());
        KJobWidgets::setWindow(getJob, this);
        if(getJob->exec()) {
            if(image.loadFromData(getJob->data())) {
232 233 234 235
                m_file.coverInfo()->setCover(image);
                update(m_file);
            }
            else
236
                qCCritical(JUK_LOG) << "Unable to load image from " << urls.front();
237 238
        }
        else
239
            qCCritical(JUK_LOG) << "Unable to download " << urls.front();
240 241 242
    }
}

243
////////////////////////////////////////////////////////////////////////////////
244
// TrackItem
245 246
////////////////////////////////////////////////////////////////////////////////

247
TrackItem::TrackItem(NowPlaying *parent) :
Dirk Mueller's avatar
Dirk Mueller committed
248
    QWidget(parent),
249
    NowPlayingItem(parent)
250
{
251
    setObjectName(QLatin1String("TrackItem"));
252 253 254 255
    setFixedHeight(parent->height() - parent->layout()->margin() * 2);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);

    QVBoxLayout *layout = new QVBoxLayout(this);
256

257 258
    m_label = new QLabel(this);
    m_label->setWordWrap(true);
259

Tim Beaulen's avatar
Tim Beaulen committed
260 261
    m_label->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::LinksAccessibleByKeyboard);

262
    layout->addStretch();
263
    layout->addWidget(m_label, 1);
264
    layout->addStretch();
265

Laurent Montel's avatar
Laurent Montel committed
266 267
    connect(m_label, SIGNAL(linkActivated(QString)), this,
            SLOT(slotOpenLink(QString)));
268 269 270 271 272

    // Ensure that if we're filtering results, that the filtering is cleared if we
    // hide the now playing bar so that the user can select tracks normally.

    connect(parent, SIGNAL(nowPlayingHidden()), SLOT(slotClearShowMore()));
273 274
}

275
void TrackItem::update(const FileHandle &file)
276
{
277
    m_file = file;
278 279 280 281 282
    QTimer::singleShot(0, this, SLOT(slotUpdate()));
}

void TrackItem::slotOpenLink(const QString &link)
{
283
    PlaylistCollection *collection = parentManager()->collection();
284 285 286 287 288 289 290 291 292 293

    if(link == "artist")
        collection->showMore(m_file.tag()->artist());
    else if(link == "album")
        collection->showMore(m_file.tag()->artist(), m_file.tag()->album());
    else if(link == "clear")
        collection->clearShowMore();

    update(m_file);
}
294

295 296
void TrackItem::slotUpdate()
{
297 298 299 300 301
    if(m_file.isNull()) {
        m_label->setText(QString());
        return;
    }

Michael Pyne's avatar
Michael Pyne committed
302 303 304
    QString title  = m_file.tag()->title().toHtmlEscaped();
    QString artist = m_file.tag()->artist().toHtmlEscaped();
    QString album  = m_file.tag()->album().toHtmlEscaped();
305
    QString separator = (artist.isNull() || album.isNull()) ? QString::null : QString(" - ");	//krazy:exclude=nullstrassign for old broken gcc
306 307 308 309 310 311 312

    // This block-o-nastiness makes the font smaller and smaller until it actually fits.

    int size = 4;
    QString format =
        "<font size=\"+%1\"><b>%2</b></font>"
        "<br />"
313 314
        "<font size=\"+%3\"><b><a href=\"artist\">%4</a>%5<a href=\"album\">%6</a></b>";

315
    if(parentManager()->collection()->showMoreActive())
316 317 318
        format.append(QString(" (<a href=\"clear\">%1</a>)").arg(i18n("back to playlist")));

    format.append("</font>");
319 320
    int parentHeight = parentManager()->contentsRect().height();
    int neededHeight = 0;
321 322 323 324 325

    do {
        m_label->setText(format.arg(size).arg(title).arg(size - 2)
                         .arg(artist).arg(separator).arg(album));
        --size;
326 327
        neededHeight = m_label->heightForWidth(m_label->width());
    } while(neededHeight > parentHeight && size >= -1);
328

329
    m_label->setFixedHeight(qMin(neededHeight, parentHeight));
330 331
}

332 333
void TrackItem::slotClearShowMore()
{
334
    PlaylistCollection *collection = parentManager()->collection();
335 336 337 338
    Q_ASSERT(collection);
    collection->clearShowMore();
}

339
// vim: set et sw=4 tw=0 sta: