Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

View File

@@ -0,0 +1,179 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QMouseEvent>
#include <QtDebug>
#include "autoexpandingtreeview.h"
#include "core/mimedata.h"
const int AutoExpandingTreeView::kRowsToShow = 50;
AutoExpandingTreeView::AutoExpandingTreeView(QWidget *parent)
: QTreeView(parent),
auto_open_(true),
expand_on_reset_(true),
add_on_double_click_(true),
ignore_next_click_(false)
{
setExpandsOnDoubleClick(false);
setAnimated(true);
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(ItemExpanded(QModelIndex)));
connect(this, SIGNAL(clicked(QModelIndex)), SLOT(ItemClicked(QModelIndex)));
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex)));
}
void AutoExpandingTreeView::reset() {
QTreeView::reset();
// Expand nodes in the tree until we have about 50 rows visible in the view
if (auto_open_ && expand_on_reset_) {
RecursivelyExpand(rootIndex());
}
}
void AutoExpandingTreeView::RecursivelyExpand(const QModelIndex &index) {
int rows = model()->rowCount(index);
RecursivelyExpand(index, &rows);
}
bool AutoExpandingTreeView::RecursivelyExpand(const QModelIndex &index, int *count) {
if (!CanRecursivelyExpand(index))
return true;
if (model()->canFetchMore(index))
model()->fetchMore(index);
int children = model()->rowCount(index);
if (*count + children > kRowsToShow)
return false;
expand(index);
*count += children;
for (int i = 0 ; i < children ; ++i) {
if (!RecursivelyExpand(model()->index(i, 0, index), count))
return false;
}
return true;
}
void AutoExpandingTreeView::ItemExpanded(const QModelIndex &index) {
if (model()->rowCount(index) == 1 && auto_open_)
expand(model()->index(0, 0, index));
}
void AutoExpandingTreeView::ItemClicked(const QModelIndex &index) {
if (ignore_next_click_) {
ignore_next_click_ = false;
return;
}
setExpanded(index, !isExpanded(index));
}
void AutoExpandingTreeView::ItemDoubleClicked(const QModelIndex &index) {
ignore_next_click_ = true;
if (add_on_double_click_) {
QMimeData *data = model()->mimeData(QModelIndexList() << index);
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
mime_data->from_doubleclick_ = true;
}
emit AddToPlaylistSignal(data);
}
}
void AutoExpandingTreeView::mousePressEvent(QMouseEvent *event) {
if (event->modifiers() != Qt::NoModifier) {
ignore_next_click_ = true;
}
QTreeView::mousePressEvent(event);
//enqueue to playlist with middleClick
if (event->button() == Qt::MidButton) {
QMimeData *data = model()->mimeData(selectedIndexes());
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
mime_data->enqueue_now_ = true;
}
emit AddToPlaylistSignal(data);
}
}
void AutoExpandingTreeView::mouseDoubleClickEvent(QMouseEvent *event) {
State p_state = state();
QModelIndex index = indexAt(event->pos());
QTreeView::mouseDoubleClickEvent(event);
// If the p_state was the "AnimatingState", then the base class's
// "mouseDoubleClickEvent" method just did nothing, hence the
// "doubleClicked" signal is not emitted. So let's do it ourselves.
if (index.isValid() && p_state == AnimatingState) {
emit doubleClicked(index);
}
}
void AutoExpandingTreeView::keyPressEvent(QKeyEvent *e) {
QModelIndex index = currentIndex();
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (currentIndex().isValid())
emit doubleClicked(currentIndex());
e->accept();
break;
case Qt::Key_Backspace:
case Qt::Key_Escape:
emit FocusOnFilterSignal(e);
e->accept();
break;
case Qt::Key_Left:
// Set focus on the root of the current branch
if (index.isValid() && index.parent() != rootIndex() &&
(!isExpanded(index) || model()->rowCount(index) == 0)) {
setCurrentIndex(index.parent());
setFocus();
e->accept();
}
break;
}
QTreeView::keyPressEvent(e);
}
void AutoExpandingTreeView::UpAndFocus() {
setCurrentIndex(moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier));
setFocus();
}
void AutoExpandingTreeView::DownAndFocus() {
setCurrentIndex(moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier));
setFocus();
}

View File

@@ -0,0 +1,77 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AUTOEXPANDINGTREEVIEW_H
#define AUTOEXPANDINGTREEVIEW_H
#include "config.h"
#include <QTreeView>
class AutoExpandingTreeView : public QTreeView {
Q_OBJECT
public:
AutoExpandingTreeView(QWidget *parent = nullptr);
static const int kRowsToShow;
void SetAutoOpen(bool v) { auto_open_ = v; }
void SetExpandOnReset(bool v) { expand_on_reset_ = v; }
void SetAddOnDoubleClick(bool v) { add_on_double_click_ = v; }
public slots:
void RecursivelyExpand(const QModelIndex &index);
void UpAndFocus();
void DownAndFocus();
signals:
void AddToPlaylistSignal(QMimeData *data);
void FocusOnFilterSignal(QKeyEvent *event);
protected:
// QAbstractItemView
void reset();
// QWidget
void mousePressEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
virtual bool CanRecursivelyExpand(const QModelIndex &index) const { return true; }
private slots:
void ItemExpanded(const QModelIndex &index);
void ItemClicked(const QModelIndex &index);
void ItemDoubleClicked(const QModelIndex &index);
private:
bool RecursivelyExpand(const QModelIndex &index, int *count);
private:
bool auto_open_;
bool expand_on_reset_;
bool add_on_double_click_;
bool ignore_next_click_;
};
#endif // AUTOEXPANDINGTREEVIEW_H

View File

@@ -0,0 +1,83 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QHBoxLayout>
#include <QMovie>
#include "core/logging.h"
#include "busyindicator.h"
BusyIndicator::BusyIndicator(const QString &text, QWidget* parent)
: QWidget(parent) {
Init(text);
}
BusyIndicator::BusyIndicator(QWidget* parent)
: QWidget(parent) {
Init(QString::null);
}
void BusyIndicator::Init(const QString &text) {
//qLog(Debug) << __PRETTY_FUNCTION__;
movie_ = new QMovie(":pictures/spinner.gif"),
label_ = new QLabel;
QLabel *icon = new QLabel;
icon->setMovie(movie_);
icon->setMinimumSize(16, 16);
label_->setWordWrap(true);
label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(icon);
layout->addSpacing(6);
layout->addWidget(label_);
set_text(text);
}
BusyIndicator::~BusyIndicator() {
delete movie_;
}
void BusyIndicator::showEvent(QShowEvent *) {
movie_->start();
}
void BusyIndicator::hideEvent(QHideEvent *) {
movie_->stop();
}
void BusyIndicator::set_text(const QString &text) {
label_->setText(text);
label_->setVisible(!text.isEmpty());
}
QString BusyIndicator::text() const {
return label_->text();
}

View File

@@ -0,0 +1,55 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BUSYINDICATOR_H
#define BUSYINDICATOR_H
#include "config.h"
#include <QLabel>
class QMovie;
class BusyIndicator : public QWidget {
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE set_text)
public:
explicit BusyIndicator(const QString &text, QWidget *parent = nullptr);
explicit BusyIndicator(QWidget *parent = nullptr);
~BusyIndicator();
QString text() const;
void set_text(const QString &text);
protected:
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
private:
void Init(const QString &text);
private:
QMovie *movie_;
QLabel *label_;
};
#endif // BUSYINDICATOR_H

View File

@@ -0,0 +1,30 @@
/* This file is part of Strawberry.
Copyright 2010, Andrea Decorte <adecorte@gmail.com>
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "clickablelabel.h"
ClickableLabel::ClickableLabel(QWidget *parent)
: QLabel(parent)
{
}
void ClickableLabel::mousePressEvent(QMouseEvent *event) {
emit Clicked();
QLabel::mousePressEvent(event);
}

View File

@@ -0,0 +1,38 @@
/* This file is part of Strawberry.
Copyright 2010, Andrea Decorte <adecorte@gmail.com>
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLICKABLELABEL_H
#define CLICKABLELABEL_H
#include "config.h"
#include <QLabel>
class ClickableLabel : public QLabel {
Q_OBJECT
public:
ClickableLabel(QWidget *parent = nullptr);
signals:
void Clicked();
protected:
void mousePressEvent(QMouseEvent* event);
};
#endif // CLICKABLELABEL_H

177
src/widgets/didyoumean.cpp Normal file
View File

@@ -0,0 +1,177 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QToolButton>
#include "didyoumean.h"
#include "core/logging.h"
const int DidYouMean::kPadding = 3;
DidYouMean::DidYouMean(QWidget *buddy, QWidget *parent)
: QWidget(parent, Qt::ToolTip),
buddy_(buddy),
close_(new QToolButton(this)),
normal_font_(font()),
correction_font_(font()),
press_enter_font_(font()) {
// Close icon
close_->setToolTip(tr("Close"));
close_->setIcon(QIcon(":/qt-project.org/styles/macstyle/images/closedock-16.png"));
close_->setIconSize(QSize(16, 16));
connect(close_, SIGNAL(clicked()), SLOT(hide()));
// Cursors
setCursor(Qt::PointingHandCursor);
close_->setCursor(Qt::ArrowCursor);
// Fonts
correction_font_.setBold(true);
press_enter_font_.setBold(true);
press_enter_font_.setPointSizeF(7.5);
hide();
buddy_->installEventFilter(this);
// Texts
did_you_mean_ = tr("Did you mean") + ": ";
press_enter_ = "(" + tr("press enter") + ")";
// Texts' sizes
did_you_mean_size_ = QFontMetrics(normal_font_).width(did_you_mean_);
press_enter_size_ = QFontMetrics(press_enter_font_).width(press_enter_);
}
bool DidYouMean::eventFilter(QObject *object, QEvent *event) {
if (object != buddy_) {
return QObject::eventFilter(object, event);
}
switch (event->type()) {
case QEvent::Move:
case QEvent::Resize:
if (isVisible()) {
UpdateGeometry();
}
break;
case QEvent::KeyPress:
if (!isVisible()) {
break;
}
switch (static_cast<QKeyEvent*>(event)->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
emit Accepted(correction_);
// fallthrough
case Qt::Key_Escape:
hide();
return true;
default:
break;
}
break;
case QEvent::FocusOut:
case QEvent::WindowDeactivate:
hide();
break;
default:
break;
}
return QObject::eventFilter(object, event);
}
void DidYouMean::showEvent(QShowEvent*) {
UpdateGeometry();
}
void DidYouMean::UpdateGeometry() {
const int text_height = fontMetrics().height();
const int height = text_height + kPadding * 2;
move(buddy_->mapToGlobal(buddy_->rect().bottomLeft()));
// Resize to len(text to display) + total number of padding added +
// size(close button), so the "Did you mean" widget is always fully displayed
resize(QSize(did_you_mean_size_ + QFontMetrics(correction_font_).width(correction_ + " ") + press_enter_size_ + kPadding * 6 + close_->width(), height));
close_->move(kPadding, kPadding);
close_->resize(text_height, text_height);
}
void DidYouMean::paintEvent(QPaintEvent*) {
QPainter p(this);
// Draw the background
QColor bg(palette().color(QPalette::Inactive, QPalette::ToolTipBase));
p.fillRect(0, 0, width()-1, height()-1, bg);
// Border
p.setPen(Qt::black);
p.drawRect(0, 0, width()-1, height()-1);
// Text rectangle
QRect text_rect(kPadding + close_->width() + kPadding, kPadding, rect().width() - kPadding, rect().height() - kPadding);
// Text
p.setFont(normal_font_);
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, did_you_mean_);
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(did_you_mean_));
p.setFont(correction_font_);
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, correction_);
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(correction_ + " "));
p.setPen(palette().color(QPalette::Disabled, QPalette::Text));
p.setFont(press_enter_font_);
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, press_enter_);
}
void DidYouMean::SetCorrection(const QString &correction) {
correction_ = correction;
UpdateGeometry();
update();
}
void DidYouMean::Show(const QString &correction) {
SetCorrection(correction);
show();
}
void DidYouMean::mouseReleaseEvent(QMouseEvent *e) {
emit Accepted(correction_);
hide();
}

73
src/widgets/didyoumean.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DIDYOUMEAN_H
#define DIDYOUMEAN_H
#include "config.h"
#include <QWidget>
class QToolButton;
class DidYouMean : public QWidget {
Q_OBJECT
public:
DidYouMean(QWidget *buddy, QWidget *parent);
static const int kPadding;
public slots:
void SetCorrection(const QString& correction);
void Show(const QString& correction);
signals:
void Accepted(const QString& correction);
protected:
void paintEvent(QPaintEvent*);
void showEvent(QShowEvent*);
void mouseReleaseEvent(QMouseEvent *e);
bool eventFilter(QObject *object, QEvent *event);
private:
void UpdateGeometry();
private:
QWidget *buddy_;
QString correction_;
QToolButton *close_;
QFont normal_font_;
QFont correction_font_;
QFont press_enter_font_;
QString did_you_mean_;
QString press_enter_;
// Size of the text to display, according to QFonts above.
// Stored here to avoid to recompute them each time
int did_you_mean_size_;
int press_enter_size_;
};
#endif // DIDYOUMEAN_H

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "elidedlabel.h"
ElidedLabel::ElidedLabel(QWidget *parent) : QLabel(parent) {}
void ElidedLabel::SetText(const QString& text) {
text_ = text;
UpdateText();
}
void ElidedLabel::resizeEvent(QResizeEvent *) {
UpdateText();
}
void ElidedLabel::UpdateText() {
setText(fontMetrics().elidedText(text_, Qt::ElideRight, width() - 5));
}

47
src/widgets/elidedlabel.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ELIDEDLABEL_H
#define ELIDEDLABEL_H
#include "config.h"
#include <QLabel>
class ElidedLabel : public QLabel {
Q_OBJECT
public:
ElidedLabel(QWidget *parent = nullptr);
public slots:
void SetText(const QString &text);
protected:
void resizeEvent(QResizeEvent *e);
private:
void UpdateText();
private:
QString text_;
};
#endif // ELIDEDLABEL_H

View File

@@ -0,0 +1,731 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "fancytabwidget.h"
#include "stylehelper.h"
#include "core/logging.h"
#include <QDebug>
#include <QAnimationGroup>
#include <QColorDialog>
#include <QHBoxLayout>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QSignalMapper>
#include <QSplitter>
#include <QStackedLayout>
#include <QStyleOptionTabV3>
#include <QToolButton>
#include <QToolTip>
#include <QVBoxLayout>
#include <QCommonStyle>
using namespace Core;
using namespace Internal;
const int FancyTabBar::m_rounding = 22;
const int FancyTabBar::m_textPadding = 4;
void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption* option, QPainter* p, const QWidget* widget) const {
const QStyleOptionTabV3* v_opt = qstyleoption_cast<const QStyleOptionTabV3*>(option);
if (element != CE_TabBarTab || !v_opt) {
QProxyStyle::drawControl(element, option, p, widget);
return;
}
const QRect rect = v_opt->rect;
const bool selected = v_opt->state & State_Selected;
const bool vertical_tabs = v_opt->shape == QTabBar::RoundedWest;
const QString text = v_opt->text;
if (selected) {
//background
p->save();
QLinearGradient grad(rect.topLeft(), rect.topRight());
grad.setColorAt(0, QColor(255, 255, 255, 140));
grad.setColorAt(1, QColor(255, 255, 255, 210));
p->fillRect(rect.adjusted(0, 0, 0, -1), grad);
p->restore();
//shadows
p->setPen(QColor(0, 0, 0, 110));
p->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1));
p->drawLine(rect.bottomLeft(), rect.bottomRight());
p->setPen(QColor(0, 0, 0, 40));
p->drawLine(rect.topLeft(), rect.bottomLeft());
//highlights
p->setPen(QColor(255, 255, 255, 50));
p->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2));
p->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1));
p->setPen(QColor(255, 255, 255, 40));
p->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight());
p->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1));
p->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1));
}
QTransform m;
if (vertical_tabs) {
m = QTransform::fromTranslate(rect.left(), rect.bottom());
m.rotate(-90);
} else {
m = QTransform::fromTranslate(rect.left(), rect.top());
}
const QRect draw_rect(QPoint(0, 0), m.mapRect(rect).size());
p->save();
p->setTransform(m);
QRect icon_rect(QPoint(8, 0), v_opt->iconSize);
QRect text_rect(icon_rect.topRight() + QPoint(4, 0), draw_rect.size());
text_rect.setRight(draw_rect.width());
icon_rect.translate(0, (draw_rect.height() - icon_rect.height()) / 2);
QFont boldFont(p->font());
boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
boldFont.setBold(true);
p->setFont(boldFont);
p->setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110));
int textFlags = Qt::AlignHCenter | Qt::AlignVCenter;
p->drawText(text_rect, textFlags, text);
p->setPen(selected ? QColor(60, 60, 60) : Utils::StyleHelper::panelTextColor());
#ifndef Q_WS_MAC
if (widget) {
const QString fader_key = "tab_" + text + "_fader";
const QString animation_key = "tab_" + text + "_animation";
const QString tab_hover = widget->property("tab_hover").toString();
int fader = widget->property(fader_key.toUtf8().constData()).toInt();
QPropertyAnimation* animation = widget->property(animation_key.toUtf8().constData()).value<QPropertyAnimation*>();
if (!animation) {
QWidget* mut_widget = const_cast<QWidget*>(widget);
fader = 0;
mut_widget->setProperty(fader_key.toUtf8().constData(), fader);
animation = new QPropertyAnimation(mut_widget, fader_key.toUtf8(), mut_widget);
connect(animation, SIGNAL(valueChanged(QVariant)), mut_widget, SLOT(update()));
mut_widget->setProperty(animation_key.toUtf8().constData(), QVariant::fromValue(animation));
}
if (text == tab_hover) {
if (animation->state() != QAbstractAnimation::Running && fader != 40) {
animation->stop();
animation->setDuration(80);
animation->setEndValue(40);
animation->start();
}
} else {
if (animation->state() != QAbstractAnimation::Running && fader != 0) {
animation->stop();
animation->setDuration(160);
animation->setEndValue(0);
animation->start();
}
}
if (!selected) {
p->save();
QLinearGradient grad(draw_rect.topLeft(), vertical_tabs ? draw_rect.bottomLeft() : draw_rect.topRight());
grad.setColorAt(0, Qt::transparent);
grad.setColorAt(0.5, QColor(255, 255, 255, fader));
grad.setColorAt(1, Qt::transparent);
p->fillRect(draw_rect, grad);
p->setPen(QPen(grad, 1.0));
p->drawLine(draw_rect.topLeft(), vertical_tabs ? draw_rect.bottomLeft() : draw_rect.topRight());
p->drawLine(draw_rect.bottomRight(), vertical_tabs ? draw_rect.topRight() : draw_rect.bottomLeft());
p->restore();
}
}
#endif
Utils::StyleHelper::drawIconWithShadow(v_opt->icon, icon_rect, p, QIcon::Normal);
p->drawText(text_rect.translated(0, -1), textFlags, text);
p->restore();
}
void FancyTabProxyStyle::polish(QWidget* widget) {
if (QString(widget->metaObject()->className()) == "QTabBar") {
widget->setMouseTracking(true);
widget->installEventFilter(this);
}
QProxyStyle::polish(widget);
}
void FancyTabProxyStyle::polish(QApplication* app) {
QProxyStyle::polish(app);
}
void FancyTabProxyStyle::polish(QPalette& palette) {
QProxyStyle::polish(palette);
}
bool FancyTabProxyStyle::eventFilter(QObject* o, QEvent* e) {
QTabBar* bar = qobject_cast<QTabBar*>(o);
if (bar && (e->type() == QEvent::MouseMove || e->type() == QEvent::Leave)) {
QMouseEvent* event = static_cast<QMouseEvent*>(e);
const QString old_hovered_tab = bar->property("tab_hover").toString();
const QString hovered_tab = e->type() == QEvent::Leave ? QString() : bar->tabText(bar->tabAt(event->pos()));
bar->setProperty("tab_hover", hovered_tab);
if (old_hovered_tab != hovered_tab)
bar->update();
}
return false;
}
FancyTab::FancyTab(QWidget* tabbar)
: QWidget(tabbar), tabbar(tabbar), m_fader(0)
{
animator.setPropertyName("fader");
animator.setTargetObject(this);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
}
void FancyTab::fadeIn()
{
animator.stop();
animator.setDuration(80);
animator.setEndValue(40);
animator.start();
}
void FancyTab::fadeOut()
{
animator.stop();
animator.setDuration(160);
animator.setEndValue(0);
animator.start();
}
void FancyTab::setFader(float value)
{
m_fader = value;
tabbar->update();
}
FancyTabBar::FancyTabBar(QWidget *parent)
: QWidget(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
setStyle(new QCommonStyle);
setMinimumWidth(qMax(2 * m_rounding, 40));
setAttribute(Qt::WA_Hover, true);
setFocusPolicy(Qt::NoFocus);
setMouseTracking(true); // Needed for hover events
m_triggerTimer.setSingleShot(true);
QVBoxLayout* layout = new QVBoxLayout;
layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding));
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
// We use a zerotimer to keep the sidebar responsive
connect(&m_triggerTimer, SIGNAL(timeout()), this, SLOT(emitCurrentIndex()));
}
FancyTabBar::~FancyTabBar()
{
delete style();
}
QSize FancyTab::sizeHint() const {
QFont boldFont(font());
boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
boldFont.setBold(true);
QFontMetrics fm(boldFont);
int spacing = 8;
int width = 60 + spacing + 2;
int iconHeight = 32;
QSize ret(width, iconHeight + spacing + fm.height());
return ret;
}
QSize FancyTabBar::tabSizeHint(bool minimum) const
{
QFont boldFont(font());
boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
boldFont.setBold(true);
QFontMetrics fm(boldFont);
int spacing = 8;
int width = 60 + spacing + 2;
int iconHeight = minimum ? 0 : 32;
return QSize(width, iconHeight + spacing + fm.height());
}
void FancyTabBar::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter p(this);
for (int i = 0; i < count(); ++i)
if (i != currentIndex())
paintTab(&p, i);
// paint active tab last, since it overlaps the neighbors
if (currentIndex() != -1)
paintTab(&p, currentIndex());
}
bool FancyTab::event(QEvent* event)
{
if (event->type() == QEvent::ToolTip) {
QFontMetrics metrics (font());
int text_width = metrics.width(text);
if (text_width > sizeHint().width()) {
// The text is elided: show the tooltip
QHelpEvent* he = static_cast<QHelpEvent*>(event);
QToolTip::showText(he->globalPos(), text);
} else {
QToolTip::hideText();
}
return true;
}
return QWidget::event(event);
}
void FancyTab::enterEvent(QEvent*)
{
fadeIn();
}
void FancyTab::leaveEvent(QEvent*)
{
fadeOut();
}
QSize FancyTabBar::sizeHint() const
{
QSize sh = tabSizeHint();
return QSize(sh.width(), sh.height() * m_tabs.count());
}
QSize FancyTabBar::minimumSizeHint() const
{
QSize sh = tabSizeHint(true);
return QSize(sh.width(), sh.height() * m_tabs.count());
}
QRect FancyTabBar::tabRect(int index) const
{
return m_tabs[index]->geometry();
}
QString FancyTabBar::tabToolTip(int index) const {
return m_tabs[index]->toolTip();
}
void FancyTabBar::setTabToolTip(int index, const QString& toolTip) {
m_tabs[index]->setToolTip(toolTip);
}
// This keeps the sidebar responsive since
// we get a repaint before loading the
// mode itself
void FancyTabBar::emitCurrentIndex()
{
emit currentChanged(m_currentIndex);
}
void FancyTabBar::mousePressEvent(QMouseEvent *e)
{
e->accept();
for (int index = 0; index < m_tabs.count(); ++index) {
if (tabRect(index).contains(e->pos())) {
m_currentIndex = index;
update();
m_triggerTimer.start(0);
break;
}
}
}
void FancyTabBar::addTab(const QIcon& icon, const QString& label) {
FancyTab *tab = new FancyTab(this);
tab->icon = icon;
tab->text = label;
tab->setToolTip(label);
m_tabs.append(tab);
qobject_cast<QVBoxLayout*>(layout())->insertWidget(layout()->count()-1, tab);
}
void FancyTabBar::addSpacer(int size) {
qobject_cast<QVBoxLayout*>(layout())->insertSpacerItem(layout()->count()-1,
new QSpacerItem(0, size, QSizePolicy::Fixed, QSizePolicy::Maximum));
}
void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
{
if (!validIndex(tabIndex)) {
qWarning("invalid index");
return;
}
painter->save();
QRect rect = tabRect(tabIndex);
bool selected = (tabIndex == m_currentIndex);
if (selected) {
//background
painter->save();
QLinearGradient grad(rect.topLeft(), rect.topRight());
grad.setColorAt(0, QColor(255, 255, 255, 140));
grad.setColorAt(1, QColor(255, 255, 255, 210));
painter->fillRect(rect.adjusted(0, 0, 0, -1), grad);
painter->restore();
//shadows
painter->setPen(QColor(0, 0, 0, 110));
painter->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1));
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
painter->setPen(QColor(0, 0, 0, 40));
painter->drawLine(rect.topLeft(), rect.bottomLeft());
//highlights
painter->setPen(QColor(255, 255, 255, 50));
painter->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2));
painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1));
painter->setPen(QColor(255, 255, 255, 40));
painter->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight());
painter->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1));
painter->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1));
}
QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), Qt::ElideRight, width()));
QRect tabTextRect(tabRect(tabIndex));
QRect tabIconRect(tabTextRect);
tabIconRect.adjust(+4, +4, -4, -4);
tabTextRect.translate(0, -2);
QFont boldFont(painter->font());
boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
boldFont.setBold(true);
painter->setFont(boldFont);
painter->setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110));
int textFlags = Qt::AlignCenter | Qt::AlignBottom;
painter->drawText(tabTextRect, textFlags, tabText);
painter->setPen(selected ? QColor(60, 60, 60) : Utils::StyleHelper::panelTextColor());
#ifndef Q_WS_MAC
if (!selected) {
painter->save();
int fader = int(m_tabs[tabIndex]->fader());
QLinearGradient grad(rect.topLeft(), rect.topRight());
grad.setColorAt(0, Qt::transparent);
grad.setColorAt(0.5, QColor(255, 255, 255, fader));
grad.setColorAt(1, Qt::transparent);
painter->fillRect(rect, grad);
painter->setPen(QPen(grad, 1.0));
painter->drawLine(rect.topLeft(), rect.topRight());
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
painter->restore();
}
#endif
const int textHeight = painter->fontMetrics().height();
tabIconRect.adjust(0, 4, 0, -textHeight);
Utils::StyleHelper::drawIconWithShadow(tabIcon(tabIndex), tabIconRect, painter, QIcon::Normal);
painter->translate(0, -1);
painter->drawText(tabTextRect, textFlags, tabText);
painter->restore();
}
void FancyTabBar::setCurrentIndex(int index) {
m_currentIndex = index;
update();
emit currentChanged(m_currentIndex);
}
//////
// FancyColorButton
//////
class FancyColorButton : public QWidget
{
public:
FancyColorButton(QWidget *parent)
: m_parent(parent)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
}
void mousePressEvent(QMouseEvent *ev)
{
if (ev->modifiers() & Qt::ShiftModifier)
Utils::StyleHelper::setBaseColor(QColorDialog::getColor(Utils::StyleHelper::requestedBaseColor(), m_parent));
}
private:
QWidget *m_parent;
};
//////
// FancyTabWidget
//////
FancyTabWidget::FancyTabWidget(QWidget* parent)
: QWidget(parent),
mode_(Mode_None),
tab_bar_(nullptr),
stack_(new QStackedLayout),
side_widget_(new QWidget),
side_layout_(new QVBoxLayout),
top_layout_(new QVBoxLayout),
use_background_(false),
menu_(nullptr),
proxy_style_(new FancyTabProxyStyle) {
side_layout_->setSpacing(0);
side_layout_->setMargin(0);
side_layout_->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding));
side_widget_->setLayout(side_layout_);
side_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
top_layout_->setMargin(0);
top_layout_->setSpacing(0);
top_layout_->addLayout(stack_);
QHBoxLayout* main_layout = new QHBoxLayout;
main_layout->setMargin(0);
main_layout->setSpacing(1);
main_layout->addWidget(side_widget_);
main_layout->addLayout(top_layout_);
setLayout(main_layout);
}
void FancyTabWidget::AddTab(QWidget* tab, const QIcon& icon, const QString& label) {
stack_->addWidget(tab);
items_ << Item(icon, label);
}
void FancyTabWidget::AddSpacer(int size) {
items_ << Item(size);
}
void FancyTabWidget::SetBackgroundPixmap(const QPixmap& pixmap) {
background_pixmap_ = pixmap;
update();
}
void FancyTabWidget::paintEvent(QPaintEvent*) {
if (!use_background_) return;
QPainter painter(this);
QRect rect = side_widget_->rect().adjusted(0, 0, 1, 0);
rect = style()->visualRect(layoutDirection(), geometry(), rect);
Utils::StyleHelper::verticalGradient(&painter, rect, rect);
if (!background_pixmap_.isNull()) {
QRect pixmap_rect(background_pixmap_.rect());
pixmap_rect.moveTo(rect.topLeft());
while (pixmap_rect.top() < rect.bottom()) {
QRect source_rect(pixmap_rect.intersected(rect));
source_rect.moveTo(0, 0);
painter.drawPixmap(pixmap_rect.topLeft(), background_pixmap_, source_rect);
pixmap_rect.moveTop(pixmap_rect.bottom() - 10);
}
}
painter.setPen(Utils::StyleHelper::borderColor());
painter.drawLine(rect.topRight(), rect.bottomRight());
QColor light = Utils::StyleHelper::sidebarHighlight();
painter.setPen(light);
painter.drawLine(rect.bottomLeft(), rect.bottomRight());
}
int FancyTabWidget::current_index() const {
return stack_->currentIndex();
}
void FancyTabWidget::SetCurrentIndex(int index) {
if (FancyTabBar* bar = qobject_cast<FancyTabBar*>(tab_bar_)) {
bar->setCurrentIndex(index);
} else if (QTabBar* bar = qobject_cast<QTabBar*>(tab_bar_)) {
bar->setCurrentIndex(index);
} else {
stack_->setCurrentIndex(index);
}
}
void FancyTabWidget::SetCurrentWidget(QWidget* widget) {
SetCurrentIndex(stack_->indexOf(widget));
}
void FancyTabWidget::ShowWidget(int index) {
stack_->setCurrentIndex(index);
emit CurrentChanged(index);
}
void FancyTabWidget::AddBottomWidget(QWidget* widget) {
top_layout_->addWidget(widget);
}
void FancyTabWidget::SetMode(Mode mode) {
// Remove previous tab bar
delete tab_bar_;
tab_bar_ = nullptr;
use_background_ = false;
// Create new tab bar
switch (mode) {
case Mode_None:
default:
qLog(Warning) << "Unknown fancy tab mode" << mode;
// fallthrough
case Mode_LargeSidebar: {
FancyTabBar* bar = new FancyTabBar(this);
side_layout_->insertWidget(0, bar);
tab_bar_ = bar;
for (const Item& item : items_) {
if (item.type_ == Item::Type_Spacer)
bar->addSpacer(item.spacer_size_);
else
bar->addTab(item.tab_icon_, item.tab_label_);
}
bar->setCurrentIndex(stack_->currentIndex());
connect(bar, SIGNAL(currentChanged(int)), SLOT(ShowWidget(int)));
use_background_ = true;
break;
}
case Mode_Tabs:
MakeTabBar(QTabBar::RoundedNorth, true, false, false);
break;
case Mode_IconOnlyTabs:
MakeTabBar(QTabBar::RoundedNorth, false, true, false);
break;
case Mode_SmallSidebar:
MakeTabBar(QTabBar::RoundedWest, true, true, true);
use_background_ = true;
break;
case Mode_PlainSidebar:
MakeTabBar(QTabBar::RoundedWest, true, true, false);
break;
}
tab_bar_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
mode_ = mode;
emit ModeChanged(mode);
update();
}
void FancyTabWidget::contextMenuEvent(QContextMenuEvent* e) {
if (!menu_) {
menu_ = new QMenu(this);
QSignalMapper* mapper = new QSignalMapper(this);
QActionGroup* group = new QActionGroup(this);
AddMenuItem(mapper, group, tr("Large sidebar"), Mode_LargeSidebar);
AddMenuItem(mapper, group, tr("Small sidebar"), Mode_SmallSidebar);
AddMenuItem(mapper, group, tr("Plain sidebar"), Mode_PlainSidebar);
AddMenuItem(mapper, group, tr("Tabs on top"), Mode_Tabs);
AddMenuItem(mapper, group, tr("Icons on top"), Mode_IconOnlyTabs);
menu_->addActions(group->actions());
connect(mapper, SIGNAL(mapped(int)), SLOT(SetMode(int)));
}
menu_->popup(e->globalPos());
}
void FancyTabWidget::AddMenuItem(QSignalMapper* mapper, QActionGroup* group,
const QString& text, Mode mode) {
QAction* action = group->addAction(text);
action->setCheckable(true);
mapper->setMapping(action, mode);
connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
if (mode == mode_) action->setChecked(true);
}
void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons,
bool fancy) {
QTabBar* bar = new QTabBar(this);
bar->setShape(shape);
bar->setDocumentMode(true);
bar->setUsesScrollButtons(true);
bar->setElideMode(Qt::ElideRight);
if (shape == QTabBar::RoundedWest) {
bar->setIconSize(QSize(22, 22));
}
if (fancy) {
bar->setStyle(proxy_style_.get());
}
if (shape == QTabBar::RoundedNorth)
top_layout_->insertWidget(0, bar);
else
side_layout_->insertWidget(0, bar);
for (const Item& item : items_) {
if (item.type_ != Item::Type_Tab) continue;
QString label = item.tab_label_;
if (shape == QTabBar::RoundedWest) {
label = QFontMetrics(font()).elidedText(label, Qt::ElideMiddle, 100);
}
int tab_id = -1;
if (icons && text)
tab_id = bar->addTab(item.tab_icon_, label);
else if (icons)
tab_id = bar->addTab(item.tab_icon_, QString());
else if (text)
tab_id = bar->addTab(label);
bar->setTabToolTip(tab_id, item.tab_label_);
}
bar->setCurrentIndex(stack_->currentIndex());
connect(bar, SIGNAL(currentChanged(int)), SLOT(ShowWidget(int)));
tab_bar_ = bar;
}

View File

@@ -0,0 +1,234 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef FANCYTABWIDGET_H
#define FANCYTABWIDGET_H
#include <memory>
#include <QIcon>
#include <QPropertyAnimation>
#include <QProxyStyle>
#include <QTabBar>
#include <QTimer>
#include <QWidget>
class QActionGroup;
class QMenu;
class QPainter;
class QSignalMapper;
class QStackedLayout;
class QStatusBar;
class QVBoxLayout;
namespace Core {
namespace Internal {
class FancyTabProxyStyle : public QProxyStyle {
Q_OBJECT
public:
void drawControl(ControlElement element, const QStyleOption* option,
QPainter* painter, const QWidget* widget) const;
void polish(QWidget* widget);
void polish(QApplication* app);
void polish(QPalette& palette);
protected:
bool eventFilter(QObject* o, QEvent* e);
};
class FancyTab : public QWidget {
Q_OBJECT
Q_PROPERTY(float fader READ fader WRITE setFader)
public:
FancyTab(QWidget *tabbar);
float fader() { return m_fader; }
void setFader(float value);
QSize sizeHint() const;
void fadeIn();
void fadeOut();
QIcon icon;
QString text;
protected:
bool event(QEvent *);
void enterEvent(QEvent *);
void leaveEvent(QEvent *);
private:
QPropertyAnimation animator;
QWidget *tabbar;
float m_fader;
};
class FancyTabBar : public QWidget
{
Q_OBJECT
public:
FancyTabBar(QWidget* parent = nullptr);
~FancyTabBar();
void paintEvent(QPaintEvent *event);
void paintTab(QPainter *painter, int tabIndex) const;
void mousePressEvent(QMouseEvent *);
bool validIndex(int index) const { return index >= 0 && index < m_tabs.count(); }
QSize sizeHint() const;
QSize minimumSizeHint() const;
void addTab(const QIcon &icon, const QString &label);
void addSpacer(int size = 40);
void removeTab(int index) {
FancyTab *tab = m_tabs.takeAt(index);
delete tab;
}
void setCurrentIndex(int index);
int currentIndex() const { return m_currentIndex; }
void setTabToolTip(int index, const QString& toolTip);
QString tabToolTip(int index) const;
QIcon tabIcon(int index) const {return m_tabs.at(index)->icon; }
QString tabText(int index) const { return m_tabs.at(index)->text; }
int count() const {return m_tabs.count(); }
QRect tabRect(int index) const;
signals:
void currentChanged(int);
public slots:
void emitCurrentIndex();
private:
static const int m_rounding;
static const int m_textPadding;
int m_currentIndex;
QList<FancyTab*> m_tabs;
QTimer m_triggerTimer;
QSize tabSizeHint(bool minimum = false) const;
};
class FancyTabWidget : public QWidget {
Q_OBJECT
public:
FancyTabWidget(QWidget* parent = nullptr);
// Values are persisted - only add to the end
enum Mode {
Mode_None = 0,
Mode_LargeSidebar = 1,
Mode_SmallSidebar = 2,
Mode_Tabs = 3,
Mode_IconOnlyTabs = 4,
Mode_PlainSidebar = 5,
};
struct Item {
Item(const QIcon& icon, const QString& label)
: type_(Type_Tab), tab_label_(label), tab_icon_(icon), spacer_size_(0) {}
Item(int size) : type_(Type_Spacer), spacer_size_(size) {}
enum Type {
Type_Tab,
Type_Spacer,
};
Type type_;
QString tab_label_;
QIcon tab_icon_;
int spacer_size_;
};
void AddTab(QWidget *tab, const QIcon &icon, const QString &label);
void AddSpacer(int size = 40);
void SetBackgroundPixmap(const QPixmap& pixmap);
void AddBottomWidget(QWidget* widget);
int current_index() const;
Mode mode() const { return mode_; }
public slots:
void SetCurrentIndex(int index);
void SetCurrentWidget(QWidget* widget);
void SetMode(Mode mode);
void SetMode(int mode) { SetMode(Mode(mode)); }
signals:
void CurrentChanged(int index);
void ModeChanged(FancyTabWidget::Mode mode);
protected:
void paintEvent(QPaintEvent *event);
void contextMenuEvent(QContextMenuEvent* e);
private slots:
void ShowWidget(int index);
private:
void MakeTabBar(QTabBar::Shape shape, bool text, bool icons, bool fancy);
void AddMenuItem(QSignalMapper* mapper, QActionGroup* group,
const QString& text, Mode mode);
Mode mode_;
QList<Item> items_;
QWidget* tab_bar_;
QStackedLayout* stack_;
QPixmap background_pixmap_;
QWidget* side_widget_;
QVBoxLayout* side_layout_;
QVBoxLayout* top_layout_;
bool use_background_;
QMenu* menu_;
std::unique_ptr<FancyTabProxyStyle> proxy_style_;
};
} // namespace Internal
} // namespace Core
Q_DECLARE_METATYPE(QPropertyAnimation*);
using Core::Internal::FancyTab;
using Core::Internal::FancyTabBar;
using Core::Internal::FancyTabWidget;
#endif // FANCYTABWIDGET_H

View File

@@ -0,0 +1,75 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2013, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "favoritewidget.h"
#include <QPaintEvent>
#include <QMouseEvent>
#include <QSize>
#include <QStyle>
#include <QStylePainter>
#include "core/logging.h"
const int FavoriteWidget::kStarSize = 15;
FavoriteWidget::FavoriteWidget(int tab_index, bool favorite, QWidget *parent)
: QWidget(parent),
tab_index_(tab_index),
favorite_(favorite),
on_(":/icons/64x64/star.png"),
off_(":/icons/64x64/star-grey.png"),
rect_(0, 0, kStarSize, kStarSize) {}
void FavoriteWidget::SetFavorite(bool favorite) {
if (favorite_ != favorite) {
favorite_ = favorite;
update();
emit FavoriteStateChanged(tab_index_, favorite_);
}
}
QSize FavoriteWidget::sizeHint() const {
const int frame_width = 1 + style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
return QSize(kStarSize + frame_width * 2, kStarSize + frame_width * 2);
}
void FavoriteWidget::paintEvent(QPaintEvent *e) {
QStylePainter p(this);
if (favorite_) {
p.drawPixmap(rect_, on_);
}
else {
p.drawPixmap(rect_, off_);
}
}
void FavoriteWidget::mouseReleaseEvent(QMouseEvent *e) {
favorite_ = !favorite_;
update();
emit FavoriteStateChanged(tab_index_, favorite_);
}

View File

@@ -0,0 +1,56 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2013, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QWidget>
class QPaintEvent;
class QMouseEvent;
class FavoriteWidget : public QWidget {
Q_OBJECT
public:
FavoriteWidget(int tab_id, bool favorite = false, QWidget *parent = nullptr);
// Change the value if different from the current one and then update display
// and emit FavoriteStateChanged signal
void SetFavorite(bool favorite);
QSize sizeHint() const;
signals:
void FavoriteStateChanged(int, bool);
protected:
void paintEvent(QPaintEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
private:
static const int kStarSize;
// The playlist's id this widget belongs to
int tab_index_;
bool favorite_;
QPixmap on_;
QPixmap off_;
QRect rect_;
};

270
src/widgets/fileview.cpp Normal file
View File

@@ -0,0 +1,270 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QKeyEvent>
#include <QFileSystemModel>
#include <QMessageBox>
#include <QScrollBar>
#include "fileview.h"
#include "ui_fileview.h"
#include "core/deletefiles.h"
#include "core/filesystemmusicstorage.h"
#include "core/mimedata.h"
#include "core/iconloader.h"
#include "core/mainwindow.h" // for filter information
#ifdef HAVE_GSTREAMER
#include "dialogs/organiseerrordialog.h"
#endif
const char *FileView::kFileFilter =
"*.mp3 *.ogg *.flac *.mpc *.m4a *.aac *.wma "
"*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf "
"*.asx *.asxini *.cue *.ape *.wv *.mka *.opus "
"*.oga *.mka *.mp2";
FileView::FileView(QWidget *parent)
: QWidget(parent),
ui_(new Ui_FileView),
model_(nullptr),
undo_stack_(new QUndoStack(this)),
task_manager_(nullptr),
storage_(new FilesystemMusicStorage("/"))
{
ui_->setupUi(this);
// Icons
ui_->back->setIcon(IconLoader::Load("go-previous"));
ui_->forward->setIcon(IconLoader::Load("go-next"));
ui_->home->setIcon(IconLoader::Load("go-home"));
ui_->up->setIcon(IconLoader::Load("go-up"));
connect(ui_->back, SIGNAL(clicked()), undo_stack_, SLOT(undo()));
connect(ui_->forward, SIGNAL(clicked()), undo_stack_, SLOT(redo()));
connect(ui_->home, SIGNAL(clicked()), SLOT(FileHome()));
connect(ui_->up, SIGNAL(clicked()), SLOT(FileUp()));
connect(ui_->path, SIGNAL(textChanged(QString)), SLOT(ChangeFilePath(QString)));
connect(undo_stack_, SIGNAL(canUndoChanged(bool)), ui_->back, SLOT(setEnabled(bool)));
connect(undo_stack_, SIGNAL(canRedoChanged(bool)), ui_->forward, SLOT(setEnabled(bool)));
connect(ui_->list, SIGNAL(activated(QModelIndex)), SLOT(ItemActivated(QModelIndex)));
connect(ui_->list, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClick(QModelIndex)));
connect(ui_->list, SIGNAL(AddToPlaylist(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*)));
connect(ui_->list, SIGNAL(CopyToCollection(QList<QUrl>)), SIGNAL(CopyToCollection(QList<QUrl>)));
connect(ui_->list, SIGNAL(MoveToCollection(QList<QUrl>)), SIGNAL(MoveToCollection(QList<QUrl>)));
connect(ui_->list, SIGNAL(CopyToDevice(QList<QUrl>)), SIGNAL(CopyToDevice(QList<QUrl>)));
#ifdef HAVE_GSTREAMER
connect(ui_->list, SIGNAL(Delete(QStringList)), SLOT(Delete(QStringList)));
#endif
connect(ui_->list, SIGNAL(EditTags(QList<QUrl>)), SIGNAL(EditTags(QList<QUrl>)));
QString filter(FileView::kFileFilter);
filter_list_ << filter.split(" ");
}
FileView::~FileView() {
delete ui_;
}
void FileView::SetPath(const QString &path) {
if (!model_)
lazy_set_path_ = path;
else
ChangeFilePathWithoutUndo(path);
}
void FileView::SetTaskManager(TaskManager *task_manager) {
task_manager_ = task_manager;
}
void FileView::FileUp() {
QDir dir(model_->rootDirectory());
dir.cdUp();
// Is this the same as going back? If so just go back, so we can keep the
// view scroll position.
if (undo_stack_->canUndo()) {
const UndoCommand *last_dir = static_cast<const UndoCommand*>(undo_stack_->command(undo_stack_->index()-1));
if (last_dir->undo_path() == dir.path()) {
undo_stack_->undo();
return;
}
}
ChangeFilePath(dir.path());
}
void FileView::FileHome() {
ChangeFilePath(QDir::homePath());
}
void FileView::ChangeFilePath(const QString &new_path_native) {
QString new_path = QDir::fromNativeSeparators(new_path_native);
QFileInfo info(new_path);
if (!info.exists() || !info.isDir())
return;
QString old_path(model_->rootPath());
if (old_path == new_path)
return;
undo_stack_->push(new UndoCommand(this, new_path));
}
void FileView::ChangeFilePathWithoutUndo(const QString &new_path) {
ui_->list->setRootIndex(model_->setRootPath(new_path));
ui_->path->setText(QDir::toNativeSeparators(new_path));
QDir dir(new_path);
ui_->up->setEnabled(dir.cdUp());
emit PathChanged(new_path);
}
void FileView::ItemActivated(const QModelIndex &index) {
if (model_->isDir(index))
ChangeFilePath(model_->filePath(index));
}
void FileView::ItemDoubleClick(const QModelIndex &index) {
if (model_->isDir(index))
return;
QString file_path = model_->filePath(index);
MimeData *data = new MimeData;
data->from_doubleclick_ = true;
data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(file_path));
data->name_for_new_playlist_ = file_path;
emit AddToPlaylist(data);
}
FileView::UndoCommand::UndoCommand(FileView *view, const QString &new_path)
: view_(view) {
old_state_.path = view->model_->rootPath();
old_state_.scroll_pos = view_->ui_->list->verticalScrollBar()->value();
old_state_.index = view_->ui_->list->currentIndex();
new_state_.path = new_path;
}
void FileView::UndoCommand::redo() {
view_->ChangeFilePathWithoutUndo(new_state_.path);
if (new_state_.scroll_pos != -1) {
view_->ui_->list->setCurrentIndex(new_state_.index);
view_->ui_->list->verticalScrollBar()->setValue(new_state_.scroll_pos);
}
}
void FileView::UndoCommand::undo() {
new_state_.scroll_pos = view_->ui_->list->verticalScrollBar()->value();
new_state_.index = view_->ui_->list->currentIndex();
view_->ChangeFilePathWithoutUndo(old_state_.path);
view_->ui_->list->setCurrentIndex(old_state_.index);
view_->ui_->list->verticalScrollBar()->setValue(old_state_.scroll_pos);
}
#ifdef HAVE_GSTREAMER
void FileView::Delete(const QStringList &filenames) {
if (filenames.isEmpty())
return;
if (QMessageBox::warning(this, tr("Delete files"),
tr("These files will be deleted from disk, are you sure you want to continue?"),
QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes)
return;
DeleteFiles *delete_files = new DeleteFiles(task_manager_, storage_);
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
delete_files->Start(filenames);
}
void FileView::DeleteFinished(const SongList &songs_with_errors) {
if (songs_with_errors.isEmpty()) return;
OrganiseErrorDialog *dialog = new OrganiseErrorDialog(this);
dialog->Show(OrganiseErrorDialog::Type_Delete, songs_with_errors);
// It deletes itself when the user closes it
}
#endif
void FileView::showEvent(QShowEvent *e) {
QWidget::showEvent(e);
if (model_) return;
model_ = new QFileSystemModel(this);
model_->setNameFilters(filter_list_);
// if an item fails the filter, hide it
model_->setNameFilterDisables(false);
ui_->list->setModel(model_);
ChangeFilePathWithoutUndo(QDir::homePath());
if (!lazy_set_path_.isEmpty()) ChangeFilePathWithoutUndo(lazy_set_path_);
}
void FileView::keyPressEvent(QKeyEvent *e) {
switch (e->key()) {
case Qt::Key_Back:
case Qt::Key_Backspace:
ui_->up->click();
break;
case Qt::Key_Enter:
case Qt::Key_Return:
ItemDoubleClick(ui_->list->currentIndex());
break;
}
QWidget::keyPressEvent(e);
}

119
src/widgets/fileview.h Normal file
View File

@@ -0,0 +1,119 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILEVIEW_H
#define FILEVIEW_H
#include "config.h"
#include <memory>
#include <QWidget>
#include <QUndoCommand>
#include <QUrl>
#include <QModelIndex>
#include "core/song.h"
class FilesystemMusicStorage;
class MusicStorage;
class TaskManager;
class Ui_FileView;
class QFileSystemModel;
class QUndoStack;
class FileView : public QWidget {
Q_OBJECT
public:
FileView(QWidget *parent = nullptr);
~FileView();
static const char *kFileFilter;
void SetPath(const QString &path);
void SetTaskManager(TaskManager *task_manager);
void showEvent(QShowEvent*);
void keyPressEvent(QKeyEvent *e);
signals:
void PathChanged(const QString &path);
void AddToPlaylist(QMimeData *data);
void CopyToCollection(const QList<QUrl> &urls);
void MoveToCollection(const QList<QUrl> &urls);
void CopyToDevice(const QList<QUrl> &urls);
void EditTags(const QList<QUrl> &urls);
private slots:
void FileUp();
void FileHome();
void ChangeFilePath(const QString &new_path);
void ItemActivated(const QModelIndex &index);
void ItemDoubleClick(const QModelIndex &index);
#ifdef HAVE_GSTREAMER
void Delete(const QStringList &filenames);
void DeleteFinished(const SongList &songs_with_errors);
#endif
private:
void ChangeFilePathWithoutUndo(const QString &new_path);
private:
class UndoCommand : public QUndoCommand {
public:
UndoCommand(FileView *view, const QString &new_path);
QString undo_path() const { return old_state_.path; }
void undo();
void redo();
private:
struct State {
State() : scroll_pos(-1) {}
QString path;
QModelIndex index;
int scroll_pos;
};
FileView *view_;
State old_state_;
State new_state_;
};
Ui_FileView *ui_;
QFileSystemModel *model_;
QUndoStack *undo_stack_;
TaskManager *task_manager_;
std::shared_ptr<MusicStorage> storage_;
QString lazy_set_path_;
QStringList filter_list_;
};
#endif // FILEVIEW_H

124
src/widgets/fileview.ui Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileView</class>
<widget class="QWidget" name="FileView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="back">
<property name="enabled">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="forward">
<property name="enabled">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="up">
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="home">
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="path"/>
</item>
</layout>
</item>
<item>
<widget class="FileViewList" name="list">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileViewList</class>
<extends>QListView</extends>
<header>widgets/fileviewlist.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,186 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QContextMenuEvent>
#include <QFileSystemModel>
#include <QMenu>
#include <QtDebug>
#include "fileviewlist.h"
#include "core/mimedata.h"
#include "core/utilities.h"
#include "core/logging.h"
#include "core/iconloader.h"
FileViewList::FileViewList(QWidget *parent)
: QListView(parent),
menu_(new QMenu(this))
{
//qLog(Debug) << __PRETTY_FUNCTION__;
menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylistSlot()));
menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(LoadSlot()));
menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylistSlot()));
menu_->addSeparator();
menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(CopyToCollectionSlot()));
menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(MoveToCollectionSlot()));
menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDeviceSlot()));
menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(DeleteSlot()));
menu_->addSeparator();
menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTagsSlot()));
menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
setAttribute(Qt::WA_MacShowFocusRect, false);
}
void FileViewList::contextMenuEvent(QContextMenuEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
menu_selection_ = selectionModel()->selection();
menu_->popup(e->globalPos());
e->accept();
}
QList<QUrl> FileViewList::UrlListFromSelection() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
QList<QUrl> urls;
for (const QModelIndex& index : menu_selection_.indexes()) {
if (index.column() == 0)
urls << QUrl::fromLocalFile(static_cast<QFileSystemModel*>(model())->fileInfo(index).canonicalFilePath());
}
return urls;
}
MimeData *FileViewList::MimeDataFromSelection() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
MimeData *data = new MimeData;
data->setUrls(UrlListFromSelection());
QList<QString> filenames = FilenamesFromSelection();
// if just one folder selected - use it's path as the new playlist's name
if (filenames.size() == 1 && QFileInfo(filenames.first()).isDir()) {
data->name_for_new_playlist_ = filenames.first();
// otherwise, use the current root path
}
else {
data->name_for_new_playlist_ = static_cast<QFileSystemModel*>(model())->rootPath();
}
return data;
}
QStringList FileViewList::FilenamesFromSelection() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
QStringList filenames;
for (const QModelIndex& index : menu_selection_.indexes()) {
if (index.column() == 0)
filenames << static_cast<QFileSystemModel*>(model())->filePath(index);
}
return filenames;
}
void FileViewList::LoadSlot() {
//qLog(Debug) << __PRETTY_FUNCTION__;
MimeData *data = MimeDataFromSelection();
data->clear_first_ = true;
emit AddToPlaylist(data);
}
void FileViewList::AddToPlaylistSlot() {
//qLog(Debug) << __PRETTY_FUNCTION__;
emit AddToPlaylist(MimeDataFromSelection());
}
void FileViewList::OpenInNewPlaylistSlot() {
MimeData *data = MimeDataFromSelection();
data->open_in_new_playlist_ = true;
emit AddToPlaylist(data);
}
void FileViewList::CopyToCollectionSlot() {
emit CopyToCollection(UrlListFromSelection());
}
void FileViewList::MoveToCollectionSlot() {
emit MoveToCollection(UrlListFromSelection());
}
void FileViewList::CopyToDeviceSlot() {
//qLog(Debug) << __PRETTY_FUNCTION__;
emit CopyToDevice(UrlListFromSelection());
}
void FileViewList::DeleteSlot() {
//qLog(Debug) << __PRETTY_FUNCTION__;
emit Delete(FilenamesFromSelection());
}
void FileViewList::EditTagsSlot() {
//qLog(Debug) << __PRETTY_FUNCTION__;
emit EditTags(UrlListFromSelection());
}
void FileViewList::mousePressEvent(QMouseEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch (e->button()) {
case Qt::XButton1:
emit Back();
break;
case Qt::XButton2:
emit Forward();
break;
// enqueue to playlist with middleClick
case Qt::MidButton: {
QListView::mousePressEvent(e);
// we need to update the menu selection
menu_selection_ = selectionModel()->selection();
MimeData *data = new MimeData;
data->setUrls(UrlListFromSelection());
data->enqueue_now_ = true;
emit AddToPlaylist(data);
break;
}
default:
QListView::mousePressEvent(e);
break;
}
}
void FileViewList::ShowInBrowser() {
//qLog(Debug) << __PRETTY_FUNCTION__;
Utilities::OpenInFileBrowser(UrlListFromSelection());
}

View File

@@ -0,0 +1,73 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILEVIEWLIST_H
#define FILEVIEWLIST_H
#include "config.h"
#include <QListView>
#include <QUrl>
class MimeData;
class FileViewList : public QListView {
Q_OBJECT
public:
FileViewList(QWidget* parent = nullptr);
void mousePressEvent(QMouseEvent *e);
signals:
void AddToPlaylist(QMimeData *data);
void CopyToCollection(const QList<QUrl>& urls);
void MoveToCollection(const QList<QUrl>& urls);
void CopyToDevice(const QList<QUrl>& urls);
void Delete(const QStringList& filenames);
void EditTags(const QList<QUrl>& urls);
void Back();
void Forward();
protected:
void contextMenuEvent(QContextMenuEvent *e);
private slots:
void LoadSlot();
void AddToPlaylistSlot();
void OpenInNewPlaylistSlot();
void CopyToCollectionSlot();
void MoveToCollectionSlot();
void CopyToDeviceSlot();
void DeleteSlot();
void EditTagsSlot();
void ShowInBrowser();
QStringList FilenamesFromSelection() const;
QList<QUrl> UrlListFromSelection() const;
MimeData *MimeDataFromSelection() const;
private:
QMenu *menu_;
QItemSelection menu_selection_;
};
#endif // FILEVIEWLIST_H

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "forcescrollperpixel.h"
#include <QAbstractItemView>
#include <QEvent>
#include <QScrollBar>
ForceScrollPerPixel::ForceScrollPerPixel(QAbstractItemView *item_view, QObject *parent)
: QObject(parent), item_view_(item_view) {
item_view_->installEventFilter(this);
}
bool ForceScrollPerPixel::eventFilter(QObject *object, QEvent *event) {
if (object == item_view_ &&
event->type() != QEvent::Destroy &&
event->type() != QEvent::WinIdChange) {
//event->type() != QEvent::AccessibilityPrepare)
item_view_->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
item_view_->verticalScrollBar()->setSingleStep(20);
}
return QObject::eventFilter(object, event);
}

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FORCESCROLLPERPIXEL_H
#define FORCESCROLLPERPIXEL_H
#include "config.h"
#include <QObject>
class QAbstractItemView;
// Some KDE styles override the ScrollMode property of QAbstractItemViews.
// This helper class forces the mode back to ScrollPerPixel.
class ForceScrollPerPixel : public QObject {
public:
ForceScrollPerPixel(QAbstractItemView *item_view, QObject *parent = nullptr);
protected:
bool eventFilter(QObject *object, QEvent *event);
private:
QAbstractItemView *item_view_;
};
#endif // FORCESCROLLPERPIXEL_H

View File

@@ -0,0 +1,220 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QLinearGradient>
#include <QPainter>
#include "freespacebar.h"
#include "core/utilities.h"
const int FreeSpaceBar::kBarHeight = 20;
const int FreeSpaceBar::kBarBorderRadius = 8;
const int FreeSpaceBar::kMarkerSpacing = 32;
const int FreeSpaceBar::kLabelBoxSize = 12;
const int FreeSpaceBar::kLabelBoxPadding = 4;
const int FreeSpaceBar::kLabelSpacing = 16;
const QRgb FreeSpaceBar::kColorBg1 = qRgb(214, 207, 200);
const QRgb FreeSpaceBar::kColorBg2 = qRgb(234, 226, 218);
const QRgb FreeSpaceBar::kColorAdd1 = qRgb(136, 180, 229);
const QRgb FreeSpaceBar::kColorAdd2 = qRgb(72, 146, 229);
const QRgb FreeSpaceBar::kColorBar1 = qRgb(250, 148, 76);
const QRgb FreeSpaceBar::kColorBar2 = qRgb(214, 102, 24);
const QRgb FreeSpaceBar::kColorBorder = qRgb(174, 168, 162);
FreeSpaceBar::FreeSpaceBar(QWidget *parent)
: QWidget(parent),
free_(100),
additional_(0),
total_(100),
free_text_(tr("Available")),
additional_text_(tr("New songs")),
used_text_(tr("Used"))
{
setMinimumHeight(sizeHint().height());
}
QSize FreeSpaceBar::sizeHint() const {
return QSize(150, kBarHeight + kLabelBoxPadding + fontMetrics().height());
}
void FreeSpaceBar::paintEvent(QPaintEvent *) {
// Geometry
QRect bar_rect(rect());
bar_rect.setHeight(kBarHeight);
QRect reflection_rect(bar_rect);
reflection_rect.moveTop(reflection_rect.bottom());
QRect labels_rect(rect());
labels_rect.setTop(labels_rect.top() + kBarHeight + kLabelBoxPadding);
// Draw the reflection
// Create the reflected pixmap
QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied);
reflection.fill(palette().color(QPalette::Background).rgba());
QPainter p(&reflection);
// Set up the transformation
QTransform transform;
transform.scale(1.0, -1.0);
transform.translate(0.0, -reflection.height());
p.setTransform(transform);
// Draw the bar
DrawBar(&p, QRect(QPoint(0, 0), reflection.size()));
// Make it fade out towards the bottom
QLinearGradient fade_gradient(reflection.rect().topLeft(), reflection.rect().bottomLeft());
fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0));
fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128));
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(reflection.rect(), fade_gradient);
p.end();
// Draw on the widget
p.begin(this);
DrawBar(&p, bar_rect);
p.drawImage(reflection_rect, reflection);
DrawText(&p, labels_rect);
}
void FreeSpaceBar::DrawBar(QPainter* p, const QRect &r) {
p->setRenderHint(QPainter::Antialiasing, true);
p->setRenderHint(QPainter::HighQualityAntialiasing, true);
QRect bar_rect(r);
bar_rect.setWidth(float(bar_rect.width()) * (float(total_ - free_) / total_));
QLinearGradient background_gradient(r.topLeft(), r.bottomLeft());
background_gradient.setColorAt(0, kColorBg1);
background_gradient.setColorAt(1, kColorBg2);
QLinearGradient bar_gradient(bar_rect.topLeft(), bar_rect.bottomLeft());
bar_gradient.setColorAt(0, kColorBar1);
bar_gradient.setColorAt(1, kColorBar2);
// Draw the background
p->setPen(Qt::NoPen);
p->setBrush(background_gradient);
p->drawRoundedRect(r, kBarBorderRadius, kBarBorderRadius);
// Create a path to use for clipping the bars
QPainterPath clip_path;
clip_path.addRoundedRect(r, kBarBorderRadius, kBarBorderRadius);
p->setClipPath(clip_path);
// Draw any additional space
if (additional_) {
QRect additional_rect(bar_rect);
additional_rect.setLeft(bar_rect.right());
additional_rect.setWidth(float(r.width()) * (float(qMin(free_, additional_)) / total_) + 1);
QLinearGradient additional_gradient(additional_rect.topLeft(), additional_rect.bottomLeft());
additional_gradient.setColorAt(0, kColorAdd1);
additional_gradient.setColorAt(1, kColorAdd2);
p->fillRect(additional_rect, additional_gradient);
}
// Draw the bar foreground
p->fillRect(bar_rect, bar_gradient);
// Draw a border
p->setClipping(false);
p->setPen(kColorBorder);
p->setBrush(Qt::NoBrush);
p->drawRoundedRect(r, kBarBorderRadius, kBarBorderRadius);
// Draw marker lines over the top every few pixels
p->setOpacity(0.35);
p->setRenderHint(QPainter::Antialiasing, false);
p->setPen(QPen(palette().color(QPalette::Light), 1.0));
for (int x = r.left() + kMarkerSpacing ; x < r.right() ; x += kMarkerSpacing) {
p->drawLine(x, r.top() + 2, x, r.bottom() - 2);
}
p->setOpacity(1.0);
}
void FreeSpaceBar::DrawText(QPainter* p, const QRect &r) {
QFont small_font(font());
small_font.setPointSize(small_font.pointSize() - 1);
small_font.setBold(true);
QFontMetrics small_metrics(small_font);
p->setFont(small_font);
// Work out the geometry for the text
QList<Label> labels;
labels << Label(TextForSize(used_text_, total_ - free_), kColorBar1);
if (additional_)
labels << Label(TextForSize(additional_text_, additional_), kColorAdd1);
labels << Label(TextForSize(free_text_, free_ - additional_), kColorBg2);
int text_width = 0;
for (const Label &label : labels) {
text_width += kLabelBoxSize + kLabelBoxPadding + kLabelSpacing + small_metrics.width(label.text);
}
// Draw the text
int x = (r.width() - text_width) / 2;
p->setRenderHint(QPainter::Antialiasing, false);
for (const Label &label : labels) {
const bool light = palette().color(QPalette::Base).value() > 128;
QRect box(x, r.top() + (r.height() - kLabelBoxSize)/2, kLabelBoxSize, kLabelBoxSize);
p->setPen(label.color.darker());
p->setBrush(label.color);
p->drawRect(box);
QRect text(x + kLabelBoxSize + kLabelBoxPadding, r.top(), small_metrics.width(label.text), r.height());
p->setPen(light ? label.color.darker() : label.color);
p->drawText(text, Qt::AlignCenter, label.text);
x += kLabelBoxSize + kLabelBoxPadding + kLabelSpacing + small_metrics.width(label.text);
}
}
QString FreeSpaceBar::TextForSize(const QString &prefix, qint64 size) const {
QString ret;
if (size > 0)
ret = Utilities::PrettySize(size);
else if (size < 0)
ret = "-" + Utilities::PrettySize(-size);
else
ret = "0 MB";
if (!prefix.isEmpty()) ret.prepend(prefix + " ");
return ret;
}

View File

@@ -0,0 +1,85 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FREESPACEBAR_H
#define FREESPACEBAR_H
#include "config.h"
#include <QWidget>
class FreeSpaceBar : public QWidget {
Q_OBJECT
public:
FreeSpaceBar(QWidget *parent = nullptr);
static const int kBarHeight;
static const int kBarBorderRadius;
static const int kMarkerSpacing;
static const int kLabelBoxSize;
static const int kLabelBoxPadding;
static const int kLabelSpacing;
static const QRgb kColorBg1;
static const QRgb kColorBg2;
static const QRgb kColorAdd1;
static const QRgb kColorAdd2;
static const QRgb kColorBar1;
static const QRgb kColorBar2;
static const QRgb kColorBorder;
void set_free_bytes(qint64 bytes) { free_ = bytes; update(); }
void set_additional_bytes(qint64 bytes) { additional_ = bytes; update(); }
void set_total_bytes(qint64 bytes) { total_ = bytes; update(); }
void set_free_text(const QString& text) { free_text_ = text; update(); }
void set_additional_text(const QString& text) { additional_text_ = text; update(); }
void set_used_text(const QString& text) { used_text_ = text; update(); }
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent*);
private:
struct Label {
Label(const QString &t, const QColor &c) : text(t), color(c) {}
QString text;
QColor color;
};
QString TextForSize(const QString &prefix, qint64 size) const;
void DrawBar(QPainter *p, const QRect &r);
void DrawText(QPainter *p, const QRect &r);
private:
qint64 free_;
qint64 additional_;
qint64 total_;
QString free_text_;
QString additional_text_;
QString used_text_;
};
#endif // FREESPACEBAR_H

View File

@@ -0,0 +1,363 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QPainter>
#include <QPaintEvent>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include <QtDebug>
#include "groupediconview.h"
#include "core/multisortfilterproxy.h"
const int GroupedIconView::kBarThickness = 2;
const int GroupedIconView::kBarMarginTop = 3;
GroupedIconView::GroupedIconView(QWidget *parent)
: QListView(parent),
proxy_model_(new MultiSortFilterProxy(this)),
default_header_height_(fontMetrics().height() +
kBarMarginTop + kBarThickness),
header_spacing_(10),
header_indent_(5),
item_indent_(10),
header_text_("%1")
{
setFlow(LeftToRight);
setViewMode(IconMode);
setResizeMode(Adjust);
setWordWrap(true);
setDragEnabled(false);
proxy_model_->AddSortSpec(Role_Group);
proxy_model_->setDynamicSortFilter(true);
connect(proxy_model_, SIGNAL(modelReset()), SLOT(LayoutItems()));
}
void GroupedIconView::AddSortSpec(int role, Qt::SortOrder order) {
proxy_model_->AddSortSpec(role, order);
}
void GroupedIconView::setModel(QAbstractItemModel *model) {
proxy_model_->setSourceModel(model);
proxy_model_->sort(0);
QListView::setModel(proxy_model_);
LayoutItems();
}
int GroupedIconView::header_height() const {
return default_header_height_;
}
void GroupedIconView::DrawHeader(QPainter *painter, const QRect &rect, const QFont &font, const QPalette &palette, const QString &text) {
painter->save();
// Bold font
QFont bold_font(font);
bold_font.setBold(true);
QFontMetrics metrics(bold_font);
QRect text_rect(rect);
text_rect.setHeight(metrics.height());
text_rect.moveTop(rect.top() + (rect.height() - text_rect.height() - kBarThickness - kBarMarginTop) / 2);
text_rect.setLeft(text_rect.left() + 3);
// Draw text
painter->setFont(bold_font);
painter->drawText(text_rect, text);
// Draw a line underneath
const QPoint start(rect.left(), text_rect.bottom() + kBarMarginTop);
const QPoint end(rect.right(), start.y());
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(QPen(palette.color(QPalette::Disabled, QPalette::Text),
kBarThickness, Qt::SolidLine, Qt::RoundCap));
painter->setOpacity(0.5);
painter->drawLine(start, end);
painter->restore();
}
void GroupedIconView::resizeEvent(QResizeEvent *e) {
QListView::resizeEvent(e);
LayoutItems();
}
void GroupedIconView::rowsInserted(const QModelIndex &parent, int start, int end) {
QListView::rowsInserted(parent, start, end);
LayoutItems();
}
void GroupedIconView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &) {
QListView::dataChanged(topLeft, bottomRight);
LayoutItems();
}
void GroupedIconView::LayoutItems() {
if (!model())
return;
const int count = model()->rowCount();
QString last_group;
QPoint next_position(0, 0);
int max_row_height = 0;
visual_rects_.clear();
visual_rects_.reserve(count);
headers_.clear();
for (int i = 0; i < count; ++i) {
const QModelIndex index(model()->index(i, 0));
const QString group = index.data(Role_Group).toString();
const QSize size(rectForIndex(index).size());
// Is this the first item in a new group?
if (group != last_group) {
// Add the group header.
Header header;
header.y = next_position.y() + max_row_height + header_indent_;
header.first_row = i;
header.text = group;
if (!last_group.isNull()) {
header.y += header_spacing_;
}
headers_ << header;
// Remember this group so we don't add it again.
last_group = group;
// Move the next item immediately below the header.
next_position.setX(0);
next_position.setY(header.y + header_height() + header_indent_ + header_spacing_);
max_row_height = 0;
}
// Take into account padding and spacing
QPoint this_position(next_position);
if (this_position.x() == 0) {
this_position.setX(this_position.x() + item_indent_);
} else {
this_position.setX(this_position.x() + spacing());
}
// Should this item wrap?
if (next_position.x() != 0 && this_position.x() + size.width() >= viewport()->width()) {
next_position.setX(0);
next_position.setY(next_position.y() + max_row_height);
this_position = next_position;
this_position.setX(this_position.x() + item_indent_);
max_row_height = 0;
}
// Set this item's geometry
visual_rects_.append(QRect(this_position, size));
// Update next index
next_position.setX(this_position.x() + size.width());
max_row_height = qMax(max_row_height, size.height());
}
verticalScrollBar()->setRange(0, next_position.y() + max_row_height - viewport()->height());
update();
}
QRect GroupedIconView::visualRect(const QModelIndex &index) const {
if (index.row() < 0 || index.row() >= visual_rects_.count())
return QRect();
return visual_rects_[index.row()].translated(-horizontalOffset(), -verticalOffset());
}
QModelIndex GroupedIconView::indexAt(const QPoint &p) const {
const QPoint viewport_p = p + QPoint(horizontalOffset(), verticalOffset());
const int count = visual_rects_.count();
for (int i=0 ; i<count ; ++i) {
if (visual_rects_[i].contains(viewport_p)) {
return model()->index(i, 0);
}
}
return QModelIndex();
}
void GroupedIconView::paintEvent(QPaintEvent *e) {
// This code was adapted from QListView::paintEvent(), changed to use the
// visualRect() of items, and to draw headers.
QStyleOptionViewItemV4 option(viewOptions());
if (isWrapping())
option.features = QStyleOptionViewItemV2::WrapText;
option.locale = locale();
option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
option.widget = this;
QPainter painter(viewport());
const QRect viewport_rect(e->rect().translated(horizontalOffset(), verticalOffset()));
QVector<QModelIndex> toBeRendered = IntersectingItems(viewport_rect);
const QModelIndex current = currentIndex();
const QAbstractItemModel *itemModel = model();
const QItemSelectionModel *selections = selectionModel();
const bool focus = (hasFocus() || viewport()->hasFocus()) && current.isValid();
const QStyle::State state = option.state;
const QAbstractItemView::State viewState = this->state();
const bool enabled = (state & QStyle::State_Enabled) != 0;
int maxSize = (flow() == TopToBottom) ? viewport()->size().width() - 2 * spacing() : viewport()->size().height() - 2 * spacing();
QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd();
for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) {
if (!it->isValid()) {
continue;
}
option.rect = visualRect(*it);
if (flow() == TopToBottom)
option.rect.setWidth(qMin(maxSize, option.rect.width()));
else
option.rect.setHeight(qMin(maxSize, option.rect.height()));
option.state = state;
if (selections && selections->isSelected(*it))
option.state |= QStyle::State_Selected;
if (enabled) {
QPalette::ColorGroup cg;
if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) {
option.state &= ~QStyle::State_Enabled;
cg = QPalette::Disabled;
}
else {
cg = QPalette::Normal;
}
option.palette.setCurrentColorGroup(cg);
}
if (focus && current == *it) {
option.state |= QStyle::State_HasFocus;
if (viewState == EditingState)
option.state |= QStyle::State_Editing;
}
itemDelegate()->paint(&painter, option, *it);
}
// Draw headers
for (const Header& header : headers_) {
const QRect header_rect = QRect(header_indent_, header.y, viewport()->width() - header_indent_ * 2, header_height());
// Is this header contained in the area we're drawing?
if (!header_rect.intersects(viewport_rect)) {
continue;
}
// Draw the header
DrawHeader(&painter,
header_rect.translated(-horizontalOffset(), -verticalOffset()),
font(),
palette(),
model()->index(header.first_row, 0).data(Role_Group).toString());
}
}
void GroupedIconView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) {
QVector<QModelIndex> indexes(IntersectingItems(rect.translated(horizontalOffset(), verticalOffset())));
QItemSelection selection;
for (const QModelIndex &index : indexes) {
selection << QItemSelectionRange(index);
}
selectionModel()->select(selection, command);
}
QVector<QModelIndex> GroupedIconView::IntersectingItems(const QRect &rect) const {
QVector<QModelIndex> ret;
const int count = visual_rects_.count();
for (int i = 0; i < count; ++i) {
if (rect.intersects(visual_rects_[i])) {
ret.append(model()->index(i, 0));
}
}
return ret;
}
QRegion GroupedIconView::visualRegionForSelection(const QItemSelection &selection) const {
QRegion ret;
for (const QModelIndex &index : selection.indexes()) {
ret += visual_rects_[index.row()];
}
return ret;
}
QModelIndex GroupedIconView::moveCursor(CursorAction action, Qt::KeyboardModifiers) {
if (model()->rowCount() == 0) {
return QModelIndex();
}
int ret = currentIndex().row();
if (ret == -1) {
ret = 0;
}
switch (action) {
case MoveUp: ret = IndexAboveOrBelow(ret, -1); break;
case MovePrevious:
case MoveLeft: ret --; break;
case MoveDown: ret = IndexAboveOrBelow(ret, +1); break;
case MoveNext:
case MoveRight: ret ++; break;
case MovePageUp:
case MoveHome: ret = 0; break;
case MovePageDown:
case MoveEnd: ret = model()->rowCount() - 1; break;
}
return model()->index(qBound(0, ret, model()->rowCount()), 0);
}
int GroupedIconView::IndexAboveOrBelow(int index, int d) const {
const QRect orig_rect(visual_rects_[index]);
while (index >= 0 && index < visual_rects_.count()) {
const QRect rect(visual_rects_[index]);
const QPoint center(rect.center());
if ((center.y() <= orig_rect.top() || center.y() >= orig_rect.bottom()) && center.x() >= orig_rect.left() && center.x() <= orig_rect.right()) {
return index;
}
index += d;
}
return index;
}

View File

@@ -0,0 +1,116 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GROUPEDICONVIEW_H
#define GROUPEDICONVIEW_H
#include "config.h"
#include <QListView>
class MultiSortFilterProxy;
class GroupedIconView : public QListView {
Q_OBJECT
// Vertical space separating a header from the items above and below it.
Q_PROPERTY(int header_spacing READ header_spacing WRITE set_header_spacing)
// Horizontal space separating a header from the left and right edges of the widget.
Q_PROPERTY(int header_indent READ header_indent WRITE set_header_indent)
// Horizontal space separating an item from the left and right edges of the widget.
Q_PROPERTY(int item_indent READ item_indent WRITE set_item_indent)
// The text of each group's header. Must contain "%1".
Q_PROPERTY(QString header_text READ header_text WRITE set_header_text);
public:
GroupedIconView(QWidget *parent = nullptr);
enum Role {
Role_Group = 1158300,
};
void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder);
int header_spacing() const { return header_spacing_; }
int header_indent() const { return header_indent_; }
int item_indent() const { return item_indent_; }
const QString &header_text() const { return header_text_;}
void set_header_spacing(int value) { header_spacing_ = value; }
void set_header_indent(int value) { header_indent_ = value; }
void set_item_indent(int value) { item_indent_ = value; }
void set_header_text(const QString &value) { header_text_ = value; }
// QAbstractItemView
QModelIndex moveCursor(CursorAction action, Qt::KeyboardModifiers modifiers);
void setModel(QAbstractItemModel *model);
static void DrawHeader(QPainter *painter, const QRect &rect, const QFont &font, const QPalette &palette, const QString &text);
protected:
virtual int header_height() const;
// QWidget
void paintEvent(QPaintEvent *e);
void resizeEvent(QResizeEvent *e);
// QAbstractItemView
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int>& = QVector<int>());
QModelIndex indexAt(const QPoint &p) const;
void rowsInserted(const QModelIndex &parent, int start, int end);
void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command);
QRect visualRect(const QModelIndex &index) const;
QRegion visualRegionForSelection(const QItemSelection &selection) const;
private slots:
void LayoutItems();
private:
static const int kBarThickness;
static const int kBarMarginTop;
struct Header {
int y;
int first_row;
QString text;
};
// Returns the items that are wholly or partially inside the rect.
QVector<QModelIndex> IntersectingItems(const QRect &rect) const;
// Returns the index of the item above (d=-1) or below (d=+1) the given item.
int IndexAboveOrBelow(int index, int d) const;
MultiSortFilterProxy *proxy_model_;
QVector<QRect> visual_rects_;
QVector<Header> headers_;
const int default_header_height_;
int header_spacing_;
int header_indent_;
int item_indent_;
QString header_text_;
};
#endif // GROUPEDICONVIEW_H

216
src/widgets/lineedit.cpp Normal file
View File

@@ -0,0 +1,216 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QPainter>
#include <QPaintEvent>
#include <QStyle>
#include <QStyleOption>
#include <QToolButton>
#include <QtDebug>
#include "lineedit.h"
ExtendedEditor::ExtendedEditor(QWidget *widget, int extra_right_padding, bool draw_hint)
: LineEditInterface(widget),
has_clear_button_(true),
clear_button_(new QToolButton(widget)),
reset_button_(new QToolButton(widget)),
extra_right_padding_(extra_right_padding),
draw_hint_(draw_hint),
font_point_size_(widget->font().pointSizeF() - 1),
is_rtl_(false) {
clear_button_->setIcon(IconLoader::Load("edit-clear-locationbar-ltr"));
clear_button_->setIconSize(QSize(16, 16));
clear_button_->setCursor(Qt::ArrowCursor);
clear_button_->setStyleSheet("QToolButton { border: none; padding: 0px; }");
clear_button_->setToolTip(widget->tr("Clear"));
clear_button_->setFocusPolicy(Qt::NoFocus);
QStyleOption opt;
opt.initFrom(widget);
reset_button_->setIcon(widget->style()->standardIcon(QStyle::SP_DialogResetButton, &opt, widget));
reset_button_->setIconSize(QSize(16, 16));
reset_button_->setCursor(Qt::ArrowCursor);
reset_button_->setStyleSheet("QToolButton { border: none; padding: 0px; }");
reset_button_->setToolTip(widget->tr("Reset"));
reset_button_->setFocusPolicy(Qt::NoFocus);
reset_button_->hide();
widget->connect(clear_button_, SIGNAL(clicked()), widget, SLOT(clear()));
widget->connect(clear_button_, SIGNAL(clicked()), widget, SLOT(setFocus()));
UpdateButtonGeometry();
}
void ExtendedEditor::set_hint(const QString& hint) {
hint_ = hint;
widget_->update();
}
void ExtendedEditor::set_clear_button(bool visible) {
has_clear_button_ = visible;
clear_button_->setVisible(visible);
UpdateButtonGeometry();
}
bool ExtendedEditor::has_reset_button() const {
return reset_button_->isVisible();
}
void ExtendedEditor::set_reset_button(bool visible) {
reset_button_->setVisible(visible);
UpdateButtonGeometry();
}
void ExtendedEditor::UpdateButtonGeometry() {
const int frame_width = widget_->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
const int left = frame_width + 1 + (has_clear_button() ? clear_button_->sizeHint().width() : 0);
const int right = frame_width + 1 + (has_reset_button() ? reset_button_->sizeHint().width() : 0);
widget_->setStyleSheet(
QString("QLineEdit { padding-left: %1px; padding-right: %2px; }").arg(left).arg(right));
QSize msz = widget_->minimumSizeHint();
widget_->setMinimumSize(msz.width() + (clear_button_->sizeHint().width() + frame_width + 1) * 2 + extra_right_padding_, qMax(msz.height(), clear_button_->sizeHint().height() + frame_width * 2 + 2));
}
void ExtendedEditor::Paint(QPaintDevice *device) {
if (!widget_->hasFocus() && is_empty() && !hint_.isEmpty()) {
clear_button_->hide();
if (draw_hint_) {
QPainter p(device);
QFont font;
font.setBold(false);
font.setPointSizeF(font_point_size_);
QFontMetrics m(font);
const int kBorder = (device->height() - m.height()) / 2;
p.setPen(widget_->palette().color(QPalette::Disabled, QPalette::Text));
p.setFont(font);
QRect r(5, kBorder, device->width() - 10, device->height() - kBorder*2);
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, m.elidedText(hint_, Qt::ElideRight, r.width()));
}
}
else {
clear_button_->setVisible(has_clear_button_);
}
}
void ExtendedEditor::Resize() {
const QSize sz = clear_button_->sizeHint();
const int frame_width = widget_->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
const int y = (widget_->rect().height() - sz.height()) / 2;
clear_button_->move(frame_width, y);
if (!is_rtl_) {
reset_button_->move(widget_->width() - frame_width - sz.width() - extra_right_padding_, y);
}
else {
reset_button_->move((has_clear_button() ? sz.width() + 4 : 0) + frame_width, y);
}
}
LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent), ExtendedEditor(this) {
connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset()));
connect(this, SIGNAL(textChanged(QString)), SLOT(text_changed(QString)));
}
void LineEdit::text_changed(const QString& text) {
if (text.isEmpty()) {
// Consider empty string as LTR
set_rtl(false);
}
else {
// For some reason Qt will detect any text with LTR at the end as LTR, so
// instead
// compare only the first character
set_rtl(QString(text.at(0)).isRightToLeft());
}
Resize();
}
void LineEdit::paintEvent(QPaintEvent *e) {
QLineEdit::paintEvent(e);
Paint(this);
}
void LineEdit::resizeEvent(QResizeEvent *e) {
QLineEdit::resizeEvent(e);
Resize();
}
TextEdit::TextEdit(QWidget *parent)
: QPlainTextEdit(parent),
ExtendedEditor(this)
{
connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset()));
connect(this, SIGNAL(textChanged()), viewport(), SLOT(update())); // To clear the hint
}
void TextEdit::paintEvent(QPaintEvent *e) {
QPlainTextEdit::paintEvent(e);
Paint(viewport());
}
void TextEdit::resizeEvent(QResizeEvent *e) {
QPlainTextEdit::resizeEvent(e);
Resize();
}
SpinBox::SpinBox(QWidget *parent)
: QSpinBox(parent),
ExtendedEditor(this, 14, false)
{
connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset()));
}
void SpinBox::paintEvent(QPaintEvent *e) {
QSpinBox::paintEvent(e);
Paint(this);
}
void SpinBox::resizeEvent(QResizeEvent *e) {
QSpinBox::resizeEvent(e);
Resize();
}
QString SpinBox::textFromValue(int val) const {
if (val <= 0 && !hint_.isEmpty())
return "-";
return QSpinBox::textFromValue(val);
}

178
src/widgets/lineedit.h Normal file
View File

@@ -0,0 +1,178 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LINEEDIT_H
#define LINEEDIT_H
#include "config.h"
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QSpinBox>
#include "core/iconloader.h"
class QToolButton;
class LineEditInterface {
public:
LineEditInterface(QWidget *widget) : widget_(widget) {}
QWidget *widget() const { return widget_; }
virtual ~LineEditInterface() {}
virtual void clear() { set_text(QString()); }
virtual void set_focus() = 0;
virtual QString text() const = 0;
virtual void set_text(const QString& text) = 0;
virtual QString hint() const = 0;
virtual void set_hint(const QString& hint) = 0;
virtual void clear_hint() = 0;
virtual void set_enabled(bool enabled) = 0;
protected:
QWidget *widget_;
};
class ExtendedEditor : public LineEditInterface {
public:
ExtendedEditor(QWidget *widget, int extra_right_padding = 0, bool draw_hint = true);
virtual ~ExtendedEditor() {}
virtual bool is_empty() const { return text().isEmpty(); }
QString hint() const { return hint_; }
void set_hint(const QString& hint);
void clear_hint() { set_hint(QString()); }
bool has_clear_button() const { return has_clear_button_; }
void set_clear_button(bool visible);
bool has_reset_button() const;
void set_reset_button(bool visible);
qreal font_point_size() const { return font_point_size_; }
void set_font_point_size(qreal size) { font_point_size_ = size; }
protected:
void Paint(QPaintDevice *device);
void Resize();
private:
void UpdateButtonGeometry();
protected:
QString hint_;
bool has_clear_button_;
QToolButton *clear_button_;
QToolButton *reset_button_;
int extra_right_padding_;
bool draw_hint_;
qreal font_point_size_;
bool is_rtl_;
};
class LineEdit : public QLineEdit, public ExtendedEditor {
Q_OBJECT
Q_PROPERTY(QString hint READ hint WRITE set_hint);
Q_PROPERTY(qreal font_point_size READ font_point_size WRITE set_font_point_size);
Q_PROPERTY(bool has_clear_button READ has_clear_button WRITE set_clear_button);
Q_PROPERTY(bool has_reset_button READ has_reset_button WRITE set_reset_button);
public:
LineEdit(QWidget *parent = nullptr);
// ExtendedEditor
void set_focus() { QLineEdit::setFocus(); }
QString text() const { return QLineEdit::text(); }
void set_text(const QString& text) { QLineEdit::setText(text); }
void set_enabled(bool enabled) { QLineEdit::setEnabled(enabled); }
protected:
void paintEvent(QPaintEvent*);
void resizeEvent(QResizeEvent*);
private:
bool is_rtl() const { return is_rtl_; }
void set_rtl(bool rtl) { is_rtl_ = rtl; }
private slots:
void text_changed(const QString& text);
signals:
void Reset();
};
class TextEdit : public QPlainTextEdit, public ExtendedEditor {
Q_OBJECT
Q_PROPERTY(QString hint READ hint WRITE set_hint);
Q_PROPERTY(bool has_clear_button READ has_clear_button WRITE set_clear_button);
Q_PROPERTY(bool has_reset_button READ has_reset_button WRITE set_reset_button);
public:
TextEdit(QWidget *parent = nullptr);
// ExtendedEditor
void set_focus() { QPlainTextEdit::setFocus(); }
QString text() const { return QPlainTextEdit::toPlainText(); }
void set_text(const QString& text) { QPlainTextEdit::setPlainText(text); }
void set_enabled(bool enabled) { QPlainTextEdit::setEnabled(enabled); }
protected:
void paintEvent(QPaintEvent*);
void resizeEvent(QResizeEvent*);
signals:
void Reset();
};
class SpinBox : public QSpinBox, public ExtendedEditor {
Q_OBJECT
Q_PROPERTY(QString hint READ hint WRITE set_hint);
Q_PROPERTY(bool has_clear_button READ has_clear_button WRITE set_clear_button);
Q_PROPERTY(bool has_reset_button READ has_reset_button WRITE set_reset_button);
public:
SpinBox(QWidget *parent = nullptr);
// QSpinBox
QString textFromValue(int val) const;
// ExtendedEditor
bool is_empty() const { return text().isEmpty() || text() == "0"; }
void set_focus() { QSpinBox::setFocus(); }
QString text() const { return QSpinBox::text(); }
void set_text(const QString& text) { QSpinBox::setValue(text.toInt()); }
void set_enabled(bool enabled) { QSpinBox::setEnabled(enabled); }
protected:
void paintEvent(QPaintEvent*);
void resizeEvent(QResizeEvent*);
signals:
void Reset();
};
#endif // LINEEDIT_H

View File

@@ -0,0 +1,57 @@
/*
Strawberry Music Player
This file was part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <QKeyEvent>
#include "linetextedit.h"
LineTextEdit::LineTextEdit(QWidget *parent)
: QTextEdit(parent)
{
setWordWrapMode(QTextOption::NoWrap);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setTabChangesFocus(true);
sizePolicy().setVerticalPolicy(QSizePolicy::Fixed);
}
QSize LineTextEdit::sizeHint() const {
QFontMetrics fm(font());
static const int kMargin = 5;
int h = 2 * kMargin + qMax(fm.height(), 14);
int w = 2 * kMargin + fm.width("W") * 15;
return QSize(w, h);
}
QSize LineTextEdit::minimumSizeHint() const {
return sizeHint();
}
void LineTextEdit::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
e->ignore();
} else {
QTextEdit::keyPressEvent(e);
}
}

View File

@@ -0,0 +1,41 @@
/*
Strawberry Music Player
This file was part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LINETEXTEDIT_H
#define LINETEXTEDIT_H
#include "config.h"
#include <QTextEdit>
class LineTextEdit : public QTextEdit {
Q_OBJECT
public:
LineTextEdit(QWidget *parent = nullptr);
QSize sizeHint() const;
QSize minimumSizeHint() const;
protected:
void keyPressEvent(QKeyEvent *e);
};
#endif // LINETEXTEDIT_H

View File

@@ -0,0 +1,94 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "multiloadingindicator.h"
#include "core/taskmanager.h"
#include "widgets/busyindicator.h"
#include <QHBoxLayout>
#include <QPainter>
const int MultiLoadingIndicator::kVerticalPadding = 4;
const int MultiLoadingIndicator::kHorizontalPadding = 6;
const int MultiLoadingIndicator::kSpacing = 6;
MultiLoadingIndicator::MultiLoadingIndicator(QWidget *parent)
: QWidget(parent),
spinner_(new BusyIndicator(this))
{
spinner_->move(kHorizontalPadding, kVerticalPadding);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
}
QSize MultiLoadingIndicator::sizeHint() const {
const int width = kHorizontalPadding * 2 + spinner_->sizeHint().width() + kSpacing + fontMetrics().width(text_);
const int height = kVerticalPadding * 2 + qMax(spinner_->sizeHint().height(), fontMetrics().height());
return QSize(width, height);
}
void MultiLoadingIndicator::SetTaskManager(TaskManager* task_manager) {
task_manager_ = task_manager;
connect(task_manager_, SIGNAL(TasksChanged()), SLOT(UpdateText()));
}
void MultiLoadingIndicator::UpdateText() {
QList<TaskManager::Task> tasks = task_manager_->GetTasks();
QStringList strings;
for (const TaskManager::Task& task : tasks) {
QString task_text(task.name);
task_text[0] = task_text[0].toLower();
if (task.progress_max) {
int percentage = float(task.progress) / task.progress_max * 100;
task_text += QString(" %1%").arg(percentage);
}
strings << task_text;
}
text_ = strings.join(", ");
if (!text_.isEmpty()) {
text_[0] = text_[0].toUpper();
text_ += "...";
}
emit TaskCountChange(tasks.count());
update();
updateGeometry();
}
void MultiLoadingIndicator::paintEvent(QPaintEvent*) {
QPainter p(this);
const QRect text_rect(
kHorizontalPadding + spinner_->sizeHint().width() + kSpacing, kVerticalPadding,
width() - kHorizontalPadding * 2 - spinner_->sizeHint().width() - kSpacing,
height() - kVerticalPadding * 2);
p.drawText(text_rect, Qt::TextSingleLine | Qt::AlignLeft, fontMetrics().elidedText(text_, Qt::ElideRight, text_rect.width()));
}

View File

@@ -0,0 +1,62 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MULTILOADINGINDICATOR_H
#define MULTILOADINGINDICATOR_H
#include "config.h"
#include <QWidget>
class BusyIndicator;
class TaskManager;
class MultiLoadingIndicator : public QWidget {
Q_OBJECT
public:
MultiLoadingIndicator(QWidget *parent = nullptr);
static const int kVerticalPadding;
static const int kHorizontalPadding;
static const int kSpacing;
void SetTaskManager(TaskManager* task_manager);
QSize sizeHint() const;
signals:
void TaskCountChange(int tasks);
protected:
void paintEvent(QPaintEvent *);
private slots:
void UpdateText();
private:
TaskManager *task_manager_;
BusyIndicator *spinner_;
QString text_;
};
#endif // MULTILOADINGINDICATOR_H

358
src/widgets/osd.cpp Normal file
View File

@@ -0,0 +1,358 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QCoreApplication>
#include <QtDebug>
#include <QSettings>
#include "osd.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/systemtrayicon.h"
#include "covermanager/currentartloader.h"
#include "osdpretty.h"
#ifdef HAVE_DBUS
# include "dbus/notification.h"
#endif
const char *OSD::kSettingsGroup = "OSD";
OSD::OSD(SystemTrayIcon *tray_icon, Application *app, QObject *parent)
: QObject(parent),
tray_icon_(tray_icon),
app_(app),
timeout_msec_(5000),
behaviour_(Native),
show_on_volume_change_(false),
show_art_(true),
show_on_play_mode_change_(true),
show_on_pause_(true),
use_custom_text_(false),
custom_text1_(QString()),
custom_text2_(QString()),
preview_mode_(false),
force_show_next_(false),
ignore_next_stopped_(false),
pretty_popup_(new OSDPretty(OSDPretty::Mode_Popup))
{
connect(app_->current_art_loader(), SIGNAL(ThumbnailLoaded(Song,QString,QImage)), SLOT(AlbumArtLoaded(Song,QString,QImage)));
ReloadSettings();
Init();
}
OSD::~OSD() {
delete pretty_popup_;
}
void OSD::ReloadSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
behaviour_ = OSD::Behaviour(s.value("Behaviour", Native).toInt());
timeout_msec_ = s.value("Timeout", 5000).toInt();
show_on_volume_change_ = s.value("ShowOnVolumeChange", false).toBool();
show_art_ = s.value("ShowArt", true).toBool();
show_on_play_mode_change_ = s.value("ShowOnPlayModeChange", true).toBool();
show_on_pause_ = s.value("ShowOnPausePlayback", true).toBool();
use_custom_text_ = s.value(("CustomTextEnabled"), false).toBool();
custom_text1_ = s.value("CustomText1").toString();
custom_text2_ = s.value("CustomText2").toString();
if (!SupportsNativeNotifications() && behaviour_ == Native)
behaviour_ = Pretty;
if (!SupportsTrayPopups() && behaviour_ == TrayPopup) behaviour_ = Disabled;
ReloadPrettyOSDSettings();
}
// Reload just Pretty OSD settings, not everything
void OSD::ReloadPrettyOSDSettings() {
pretty_popup_->set_popup_duration(timeout_msec_);
pretty_popup_->ReloadSettings();
}
void OSD::ReshowCurrentSong() {
force_show_next_ = true;
AlbumArtLoaded(last_song_, last_image_uri_, last_image_);
}
void OSD::AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image) {
// Don't change tray icon details if it's a preview
if (!preview_mode_) {
tray_icon_->SetNowPlaying(song, uri);
}
last_song_ = song;
last_image_ = image;
last_image_uri_ = uri;
QStringList message_parts;
QString summary;
if (!use_custom_text_) {
summary = song.PrettyTitle();
if (!song.artist().isEmpty())
summary = QString("%1 - %2").arg(song.artist(), summary);
if (!song.album().isEmpty())
message_parts << song.album();
if (song.disc() > 0)
message_parts << tr("disc %1").arg(song.disc());
if (song.track() > 0)
message_parts << tr("track %1").arg(song.track());
}
else {
QRegExp variable_replacer("[%][a-z]+[%]");
summary = custom_text1_;
QString message(custom_text2_);
// Replace the first line
int pos = 0;
variable_replacer.indexIn(custom_text1_);
while ((pos = variable_replacer.indexIn(custom_text1_, pos)) != -1) {
QStringList captured = variable_replacer.capturedTexts();
summary.replace(captured[0], ReplaceVariable(captured[0], song));
pos += variable_replacer.matchedLength();
}
// Replace the second line
pos = 0;
variable_replacer.indexIn(custom_text2_);
while ((pos = variable_replacer.indexIn(custom_text2_, pos)) != -1) {
QStringList captured = variable_replacer.capturedTexts();
message.replace(captured[0], ReplaceVariable(captured[0], song));
pos += variable_replacer.matchedLength();
}
message_parts << message;
}
if (show_art_) {
ShowMessage(summary, message_parts.join(", "), "notification-audio-play", image);
}
else {
ShowMessage(summary, message_parts.join(", "), "notification-audio-play", QImage());
}
// Reload the saved settings if they were changed for preview
if (preview_mode_) {
ReloadSettings();
preview_mode_ = false;
}
}
void OSD::Paused() {
if (show_on_pause_) {
ShowMessage(QCoreApplication::applicationName(), tr("Paused"));
}
}
void OSD::Stopped() {
tray_icon_->ClearNowPlaying();
if (ignore_next_stopped_) {
ignore_next_stopped_ = false;
return;
}
ShowMessage(QCoreApplication::applicationName(), tr("Stopped"));
}
void OSD::StopAfterToggle(bool stop) {
ShowMessage(
QCoreApplication::applicationName(),
tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
}
void OSD::PlaylistFinished() {
// We get a PlaylistFinished followed by a Stopped from the player
ignore_next_stopped_ = true;
ShowMessage(QCoreApplication::applicationName(), tr("Playlist finished"));
}
void OSD::VolumeChanged(int value) {
if (!show_on_volume_change_) return;
ShowMessage(QCoreApplication::applicationName(), tr("Volume %1%").arg(value));
}
void OSD::ShowMessage(const QString &summary, const QString &message, const QString &icon, const QImage &image) {
if (pretty_popup_->toggle_mode()) {
pretty_popup_->ShowMessage(summary, message, image);
}
else {
switch (behaviour_) {
case Native:
if (image.isNull()) {
ShowMessageNative(summary, message, icon, QImage());
} else {
ShowMessageNative(summary, message, QString(), image);
}
break;
#ifndef Q_OS_DARWIN
case TrayPopup:
tray_icon_->ShowPopup(summary, message, timeout_msec_);
break;
#endif
case Disabled:
if (!force_show_next_) break;
force_show_next_ = false;
// fallthrough
case Pretty:
pretty_popup_->ShowMessage(summary, message, image);
break;
default:
break;
}
}
}
#ifndef HAVE_DBUS
void OSD::CallFinished(QDBusPendingCallWatcher*) {}
#endif
void OSD::ShuffleModeChanged(PlaylistSequence::ShuffleMode mode) {
if (show_on_play_mode_change_) {
QString current_mode = QString();
switch (mode) {
case PlaylistSequence::Shuffle_Off: current_mode = tr("Don't shuffle"); break;
case PlaylistSequence::Shuffle_All: current_mode = tr("Shuffle all"); break;
case PlaylistSequence::Shuffle_InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
case PlaylistSequence::Shuffle_Albums: current_mode = tr("Shuffle albums"); break;
}
ShowMessage(QCoreApplication::applicationName(), current_mode);
}
}
void OSD::RepeatModeChanged(PlaylistSequence::RepeatMode mode) {
if (show_on_play_mode_change_) {
QString current_mode = QString();
switch (mode) {
case PlaylistSequence::Repeat_Off: current_mode = tr("Don't repeat"); break;
case PlaylistSequence::Repeat_Track: current_mode = tr("Repeat track"); break;
case PlaylistSequence::Repeat_Album: current_mode = tr("Repeat album"); break;
case PlaylistSequence::Repeat_Playlist: current_mode = tr("Repeat playlist"); break;
case PlaylistSequence::Repeat_OneByOne: current_mode = tr("Stop after every track"); break;
case PlaylistSequence::Repeat_Intro: current_mode = tr("Intro tracks"); break;
}
ShowMessage(QCoreApplication::applicationName(), current_mode);
}
}
QString OSD::ReplaceVariable(const QString &variable, const Song &song) {
QString return_value;
if (variable == "%artist%") {
return song.artist();
}
else if (variable == "%album%") {
return song.album();
}
else if (variable == "%title%") {
return song.PrettyTitle();
}
else if (variable == "%albumartist%") {
return song.effective_albumartist();
}
else if (variable == "%year%") {
return song.PrettyYear();
}
else if (variable == "%composer%") {
return song.composer();
}
else if (variable == "%performer%") {
return song.performer();
}
else if (variable == "%grouping%") {
return song.grouping();
}
else if (variable == "%length%") {
return song.PrettyLength();
}
else if (variable == "%disc%") {
return return_value.setNum(song.disc());
}
else if (variable == "%track%") {
return return_value.setNum(song.track());
}
else if (variable == "%genre%") {
return song.genre();
}
else if (variable == "%playcount%") {
return return_value.setNum(song.playcount());
}
else if (variable == "%skipcount%") {
return return_value.setNum(song.skipcount());
}
else if (variable == "%filename%") {
return song.basefilename();
}
else if (variable == "%newline%") {
// We need different strings depending on notification type
switch (behaviour_) {
case Native:
#ifdef Q_OS_DARWIN
return "\n";
#endif
#ifdef Q_OS_LINUX
return "<br/>";
#endif
#ifdef Q_OS_WIN32
// Other OS don't support native notifications
qLog(Debug) << "New line not supported by this notification type under Windows";
return "";
#endif
case TrayPopup:
qLog(Debug) << "New line not supported by this notification type";
return "";
case Pretty:
default:
// When notifications are disabled, we force the PrettyOSD
return "<br/>";
}
}
//if the variable is not recognized, just return it
return variable;
}
void OSD::ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song) {
behaviour_ = type;
custom_text1_ = line1;
custom_text2_ = line2;
if (!use_custom_text_) use_custom_text_ = true;
// We want to reload the settings, but we can't do this here because the cover art loading is asynch
preview_mode_ = true;
AlbumArtLoaded(song, QString(), QImage());
}
void OSD::SetPrettyOSDToggleMode(bool toggle) {
pretty_popup_->set_toggle_mode(toggle);
}

131
src/widgets/osd.h Normal file
View File

@@ -0,0 +1,131 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef OSD_H
#define OSD_H
#include "config.h"
#include <memory>
#include <QDateTime>
#include <QImage>
#include <QObject>
#include "engine/engine_fwd.h"
#include "core/song.h"
#include "playlist/playlistsequence.h"
class Application;
class OrgFreedesktopNotificationsInterface;
class OSDPretty;
class SystemTrayIcon;
class QDBusPendingCallWatcher;
#ifdef HAVE_DBUS
#include <QDBusArgument>
QDBusArgument& operator<< (QDBusArgument &arg, const QImage &image);
const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image);
#endif
class OSD : public QObject {
Q_OBJECT
public:
OSD(SystemTrayIcon *tray_icon, Application *app, QObject *parent = nullptr);
~OSD();
static const char *kSettingsGroup;
enum Behaviour {
Disabled = 0,
Native,
TrayPopup,
Pretty,
};
// Implemented in the OS-specific files
static bool SupportsNativeNotifications();
static bool SupportsTrayPopups();
void ReloadPrettyOSDSettings();
void SetPrettyOSDToggleMode(bool toggle);
public slots:
void ReloadSettings();
void Paused();
void Stopped();
void StopAfterToggle(bool stop);
void PlaylistFinished();
void VolumeChanged(int value);
void RepeatModeChanged(PlaylistSequence::RepeatMode mode);
void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode);
void ReshowCurrentSong();
void ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song);
private:
void ShowMessage(const QString &summary, const QString &message = QString(), const QString &icon = QString(), const QImage &image = QImage());
// These are implemented in the OS-specific files
void Init();
void ShowMessageNative(const QString &summary, const QString &message, const QString &icon = QString(), const QImage &image = QImage());
QString ReplaceVariable(const QString &variable, const Song &song);
private slots:
void CallFinished(QDBusPendingCallWatcher *watcher);
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
private:
SystemTrayIcon *tray_icon_;
Application *app_;
int timeout_msec_;
Behaviour behaviour_;
bool show_on_volume_change_;
bool show_art_;
bool show_on_play_mode_change_;
bool show_on_pause_;
bool use_custom_text_;
QString custom_text1_;
QString custom_text2_;
bool preview_mode_;
bool force_show_next_;
bool ignore_next_stopped_;
OSDPretty *pretty_popup_;
Song last_song_;
QString last_image_uri_;
QImage last_image_;
#ifdef HAVE_DBUS
std::unique_ptr<OrgFreedesktopNotificationsInterface> interface_;
uint notification_id_;
QDateTime last_notification_time_;
#endif
};
#endif // OSD_H

70
src/widgets/osd_mac.mm Normal file
View File

@@ -0,0 +1,70 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osd.h"
#include <QBuffer>
#include <QByteArray>
#include <QFile>
#include <QtDebug>
#include "core/scoped_nsobject.h"
namespace {
bool NotificationCenterSupported() {
return NSClassFromString(@"NSUserNotificationCenter");
}
void SendNotificationCenterMessage(NSString* title, NSString* subtitle) {
Class clazz = NSClassFromString(@"NSUserNotificationCenter");
id notification_center = [clazz defaultUserNotificationCenter];
Class user_notification_class = NSClassFromString(@"NSUserNotification");
id notification = [[user_notification_class alloc] init];
[notification setTitle:title];
[notification setSubtitle:subtitle];
[notification_center deliverNotification:notification];
}
}
void OSD::Init() {}
bool OSD::SupportsNativeNotifications() {
return NotificationCenterSupported();
}
bool OSD::SupportsTrayPopups() { return false; }
void OSD::ShowMessageNative(const QString& summary, const QString& message,
const QString& icon, const QImage& image) {
Q_UNUSED(icon);
if (NotificationCenterSupported()) {
scoped_nsobject<NSString> mac_message(
[[NSString alloc] initWithUTF8String:message.toUtf8().constData()]);
scoped_nsobject<NSString> mac_summary(
[[NSString alloc] initWithUTF8String:summary.toUtf8().constData()]);
SendNotificationCenterMessage(mac_summary.get(), mac_message.get());
}
}

41
src/widgets/osd_win.cpp Normal file
View File

@@ -0,0 +1,41 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osd.h"
#include "core/logging.h"
#include <QtDebug>
void OSD::Init() {
}
bool OSD::SupportsNativeNotifications() {
return false;
}
bool OSD::SupportsTrayPopups() {
return true;
}
void OSD::ShowMessageNative(const QString&, const QString&, const QString&, const QImage&) {
qLog(Warning) << "not implemented";
}

163
src/widgets/osd_x11.cpp Normal file
View File

@@ -0,0 +1,163 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osd.h"
#include <memory>
#include <QtDebug>
#include "config.h"
#include "core/logging.h"
#ifdef HAVE_DBUS
#include "dbus/notification.h"
#include <QCoreApplication>
#include <QTextDocument>
QDBusArgument& operator<<(QDBusArgument& arg, const QImage& image) {
if (image.isNull()) {
// Sometimes this gets called with a null QImage for no obvious reason.
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
#else
// ABGR -> GBAR
QImage i(scaled.size(), scaled.format());
for (int y = 0; y < i.height(); ++y) {
QRgb* p = (QRgb*)scaled.scanLine(y);
QRgb* q = (QRgb*)i.scanLine(y);
QRgb* end = p + scaled.width();
while (p < end) {
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
p++;
q++;
}
}
#endif
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
arg << i.depth() / channels;
arg << channels;
arg << QByteArray(reinterpret_cast<const char*>(i.bits()), i.byteCount());
arg.endStructure();
return arg;
}
const QDBusArgument& operator>>(const QDBusArgument& arg, QImage& image) {
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
}
#endif // HAVE_DBUS
void OSD::Init() {
#ifdef HAVE_DBUS
notification_id_ = 0;
interface_.reset(new OrgFreedesktopNotificationsInterface(
OrgFreedesktopNotificationsInterface::staticInterfaceName(),
"/org/freedesktop/Notifications",
QDBusConnection::sessionBus()));
if (!interface_->isValid()) {
qLog(Warning) << "Error connecting to notifications service.";
}
#endif // HAVE_DBUS
}
bool OSD::SupportsNativeNotifications() {
#ifdef HAVE_DBUS
return true;
#else
return false;
#endif
}
bool OSD::SupportsTrayPopups() { return true; }
void OSD::ShowMessageNative(const QString& summary, const QString& message,
const QString& icon, const QImage& image) {
#ifdef HAVE_DBUS
if (!interface_) return;
QVariantMap hints;
if (!image.isNull()) {
hints["image_data"] = QVariant(image);
}
int id = 0;
if (last_notification_time_.secsTo(QDateTime::currentDateTime()) * 1000
< timeout_msec_) {
// Reuse the existing popup if it's still open. The reason we don't always
// reuse the popup is because the notification daemon on KDE4 won't re-show
// the bubble if it's already gone to the tray. See issue #118
id = notification_id_;
}
QDBusPendingReply<uint> reply = interface_->Notify(
QCoreApplication::applicationName(),
id,
icon,
summary,
message,
QStringList(),
hints,
timeout_msec_);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(CallFinished(QDBusPendingCallWatcher*)));
#else // HAVE_DBUS
qLog(Warning) << "not implemented";
#endif // HAVE_DBUS
}
#ifdef HAVE_DBUS
void OSD::CallFinished(QDBusPendingCallWatcher* watcher) {
std::unique_ptr<QDBusPendingCallWatcher> w(watcher);
QDBusPendingReply<uint> reply = *watcher;
if (reply.isError()) {
qLog(Warning) << "Error sending notification" << reply.error().name();
return;
}
uint id = reply.value();
if (id != 0) {
notification_id_ = id;
last_notification_time_ = QDateTime::currentDateTime();
}
}
#endif

468
src/widgets/osdpretty.cpp Normal file
View File

@@ -0,0 +1,468 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osdpretty.h"
#include "ui_osdpretty.h"
#include <QApplication>
#include <QBitmap>
#include <QColor>
#include <QDesktopWidget>
#include <QLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QSettings>
#include <QTimer>
#include <QTimeLine>
#include <QtDebug>
#ifdef Q_WS_X11
# include <QX11Info>
#endif
#ifdef Q_OS_WIN32
# include "qtwin.h"
# include <windows.h>
#endif
const char *OSDPretty::kSettingsGroup = "OSDPretty";
const int OSDPretty::kDropShadowSize = 13;
const int OSDPretty::kBorderRadius = 10;
const int OSDPretty::kMaxIconSize = 100;
const int OSDPretty::kSnapProximity = 20;
const QRgb OSDPretty::kPresetBlue = qRgb(102, 150, 227);
const QRgb OSDPretty::kPresetOrange = qRgb(254, 156, 67);
OSDPretty::OSDPretty(Mode mode, QWidget *parent)
: QWidget(parent),
ui_(new Ui_OSDPretty),
mode_(mode),
background_color_(kPresetBlue),
background_opacity_(0.85),
popup_display_(0),
font_(QFont()),
disable_duration_(false),
timeout_(new QTimer(this)),
fading_enabled_(false),
fader_(new QTimeLine(300, this)),
toggle_mode_(false)
{
Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint;
setWindowFlags(flags);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_X11NetWmWindowTypeNotification, true);
setAttribute(Qt::WA_ShowWithoutActivating, true);
ui_->setupUi(this);
#ifdef Q_OS_WIN32
// Don't show the window in the taskbar. Qt::ToolTip does this too, but it
// adds an extra ugly shadow.
int ex_style = GetWindowLong(winId(), GWL_EXSTYLE);
ex_style |= WS_EX_NOACTIVATE;
SetWindowLong(winId(), GWL_EXSTYLE, ex_style);
#endif
// Mode settings
switch (mode_) {
case Mode_Popup:
setCursor(QCursor(Qt::ArrowCursor));
break;
case Mode_Draggable:
setCursor(QCursor(Qt::OpenHandCursor));
break;
}
// Timeout
timeout_->setSingleShot(true);
timeout_->setInterval(5000);
connect(timeout_, SIGNAL(timeout()), SLOT(hide()));
ui_->icon->setMaximumSize(kMaxIconSize, kMaxIconSize);
// Fader
connect(fader_, SIGNAL(valueChanged(qreal)), SLOT(FaderValueChanged(qreal)));
connect(fader_, SIGNAL(finished()), SLOT(FaderFinished()));
#ifdef Q_OS_WIN32
set_fading_enabled(true);
#endif
// Load the show edges and corners
QImage shadow_edge(":pictures/osd_shadow_edge.png");
QImage shadow_corner(":pictures/osd_shadow_corner.png");
for (int i = 0; i < 4; ++i) {
QTransform rotation = QTransform().rotate(90 * i);
shadow_edge_[i] = QPixmap::fromImage(shadow_edge.transformed(rotation));
shadow_corner_[i] = QPixmap::fromImage(shadow_corner.transformed(rotation));
}
background_ = QPixmap(":pictures/osd_background.png");
// Set the margins to allow for the drop shadow
QBoxLayout *l = static_cast<QBoxLayout*>(layout());
int margin = l->margin() + kDropShadowSize;
l->setMargin(margin);
// Get current screen resolution
QRect screenResolution = QApplication::desktop()->screenGeometry();
// Leave 200 px for icon
ui_->summary->setMaximumWidth(screenResolution.width()-200);
ui_->message->setMaximumWidth(screenResolution.width()-200);
// Set maximum size for the OSD, a little margin here too
setMaximumSize(screenResolution.width()-100,screenResolution.height()-100);
// Don't load settings here, they will be reloaded anyway on creation
}
OSDPretty::~OSDPretty() {
delete ui_;
}
bool OSDPretty::IsTransparencyAvailable() {
#ifdef Q_WS_X11
return QX11Info::isCompositingManagerRunning();
#endif
return true;
}
void OSDPretty::Load() {
QSettings s;
s.beginGroup(kSettingsGroup);
foreground_color_ = QColor(s.value("foreground_color", 0).toInt());
background_color_ = QColor(s.value("background_color", kPresetBlue).toInt());
background_opacity_ = s.value("background_opacity", 0.85).toDouble();
popup_display_ = s.value("popup_display", -1).toInt();
popup_pos_ = s.value("popup_pos", QPoint(0, 0)).toPoint();
font_.fromString(s.value("font", "Verdana,9,-1,5,50,0,0,0,0,0").toString());
disable_duration_ = s.value("disable_duration", false).toBool();
set_font(font());
set_foreground_color(foreground_color());
}
void OSDPretty::ReloadSettings() {
Load();
if (isVisible()) update();
}
QRect OSDPretty::BoxBorder() const {
return rect().adjusted(kDropShadowSize, kDropShadowSize, -kDropShadowSize, -kDropShadowSize);
}
void OSDPretty::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::HighQualityAntialiasing);
QRect box(BoxBorder());
// Shadow corners
const int kShadowCornerSize = kDropShadowSize + kBorderRadius;
p.drawPixmap(0, 0, shadow_corner_[0]);
p.drawPixmap(width() - kShadowCornerSize, 0, shadow_corner_[1]);
p.drawPixmap(width() - kShadowCornerSize, height() - kShadowCornerSize, shadow_corner_[2]);
p.drawPixmap(0, height() - kShadowCornerSize, shadow_corner_[3]);
// Shadow edges
p.drawTiledPixmap(kShadowCornerSize, 0, width() - kShadowCornerSize*2, kDropShadowSize, shadow_edge_[0]);
p.drawTiledPixmap(width() - kDropShadowSize, kShadowCornerSize, kDropShadowSize, height() - kShadowCornerSize*2, shadow_edge_[1]);
p.drawTiledPixmap(kShadowCornerSize, height() - kDropShadowSize, width() - kShadowCornerSize*2, kDropShadowSize, shadow_edge_[2]);
p.drawTiledPixmap(0, kShadowCornerSize, kDropShadowSize, height() - kShadowCornerSize*2, shadow_edge_[3]);
// Box background
p.setBrush(background_color_);
p.setPen(QPen());
p.setOpacity(background_opacity_);
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
// Background pattern
QPainterPath background_path;
background_path.addRoundedRect(box, kBorderRadius, kBorderRadius);
p.setClipPath(background_path);
p.setOpacity(1.0);
p.drawPixmap(box.right() - background_.width(), box.bottom() - background_.height(), background_);
p.setClipping(false);
// Gradient overlay
QLinearGradient gradient(0, 0, 0, height());
gradient.setColorAt(0, QColor(255, 255, 255, 130));
gradient.setColorAt(1, QColor(255, 255, 255, 50));
p.setBrush(gradient);
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
// Box border
p.setBrush(QBrush());
p.setPen(QPen(background_color_.darker(150), 2));
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
}
void OSDPretty::SetMessage(const QString& summary, const QString& message, const QImage &image) {
if (!image.isNull()) {
QImage scaled_image = image.scaled(kMaxIconSize, kMaxIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
ui_->icon->setPixmap(QPixmap::fromImage(scaled_image));
ui_->icon->show();
}
else {
ui_->icon->hide();
}
ui_->summary->setText(summary);
ui_->message->setText(message);
if (isVisible()) Reposition();
}
// Set the desired message and then show the OSD
void OSDPretty::ShowMessage(const QString &summary, const QString &message, const QImage &image) {
SetMessage(summary, message, image);
if (isVisible() && mode_ == Mode_Popup) {
// The OSD is already visible, toggle or restart the timer
if (toggle_mode()) {
set_toggle_mode(false);
// If timeout is disabled, timer hadn't been started
if (!disable_duration())
timeout_->stop();
hide();
}
else {
if (!disable_duration())
timeout_->start(); // Restart the timer
}
}
else {
if (toggle_mode())
set_toggle_mode(false);
// The OSD is not visible, show it
show();
}
}
void OSDPretty::showEvent(QShowEvent *e) {
setWindowOpacity(fading_enabled_ ? 0.0 : 1.0);
QWidget::showEvent(e);
Reposition();
if (fading_enabled_) {
fader_->setDirection(QTimeLine::Forward);
fader_->start(); // Timeout will be started in FaderFinished
}
else if (mode_ == Mode_Popup) {
if (!disable_duration())
timeout_->start();
// Ensures it is above when showing the preview
raise();
}
}
void OSDPretty::setVisible(bool visible) {
if (!visible && fading_enabled_ && fader_->direction() == QTimeLine::Forward) {
fader_->setDirection(QTimeLine::Backward);
fader_->start();
}
else {
QWidget::setVisible(visible);
}
}
void OSDPretty::FaderFinished() {
if (fader_->direction() == QTimeLine::Backward)
hide();
else if (mode_ == Mode_Popup && !disable_duration())
timeout_->start();
}
void OSDPretty::FaderValueChanged(qreal value) {
setWindowOpacity(value);
}
void OSDPretty::Reposition() {
QDesktopWidget *desktop = QApplication::desktop();
// Make the OSD the proper size
layout()->activate();
resize(sizeHint());
// Work out where to place the OSD. -1 for x or y means "on the right or
// bottom edge".
QRect geometry(desktop->availableGeometry(popup_display_));
int x = popup_pos_.x() < 0 ? geometry.right() - width() : geometry.left() + popup_pos_.x();
int y = popup_pos_.y() < 0 ? geometry.bottom() - height() : geometry.top() + popup_pos_.y();
move(qBound(0, x, geometry.right() - width()), qBound(0, y, geometry.bottom() - height()));
// Create a mask for the actual area of the OSD
QBitmap mask(size());
mask.clear();
QPainter p(&mask);
p.setBrush(Qt::color1);
p.drawRoundedRect(BoxBorder().adjusted(-1, -1, 0, 0), kBorderRadius, kBorderRadius);
p.end();
// If there's no compositing window manager running then we have to set an
// XShape mask.
if (IsTransparencyAvailable())
clearMask();
else {
setMask(mask);
}
#ifdef Q_OS_WIN32
// On windows, enable blurbehind on the masked area
QtWin::enableBlurBehindWindow(this, true, QRegion(mask));
#endif
}
void OSDPretty::enterEvent(QEvent *) {
if (mode_ == Mode_Popup)
setWindowOpacity(0.25);
}
void OSDPretty::leaveEvent(QEvent *) {
setWindowOpacity(1.0);
}
void OSDPretty::mousePressEvent(QMouseEvent *e) {
if (mode_ == Mode_Popup)
hide();
else {
original_window_pos_ = pos();
drag_start_pos_ = e->globalPos();
}
}
void OSDPretty::mouseMoveEvent(QMouseEvent *e) {
if (mode_ == Mode_Draggable) {
QPoint delta = e->globalPos() - drag_start_pos_;
QPoint new_pos = original_window_pos_ + delta;
// Keep it to the bounds of the desktop
QDesktopWidget *desktop = QApplication::desktop();
QRect geometry(desktop->availableGeometry(e->globalPos()));
new_pos.setX(qBound(geometry.left(), new_pos.x(), geometry.right() - width()));
new_pos.setY(qBound(geometry.top(), new_pos.y(), geometry.bottom() - height()));
// Snap to center
int snap_x = geometry.center().x() - width() / 2;
if (new_pos.x() > snap_x - kSnapProximity && new_pos.x() < snap_x + kSnapProximity) {
new_pos.setX(snap_x);
}
move(new_pos);
popup_display_ = current_display();
popup_pos_ = current_pos();
}
}
QPoint OSDPretty::current_pos() const {
QDesktopWidget *desktop = QApplication::desktop();
QRect geometry(desktop->availableGeometry(current_display()));
int x = pos().x() >= geometry.right() - width() ? -1 : pos().x() - geometry.left();
int y = pos().y() >= geometry.bottom() - height() ? -1 : pos().y() - geometry.top();
return QPoint(x, y);
}
int OSDPretty::current_display() const {
QDesktopWidget *desktop = QApplication::desktop();
return desktop->screenNumber(pos());
}
void OSDPretty::set_background_color(QRgb color) {
background_color_ = color;
if (isVisible()) update();
}
void OSDPretty::set_background_opacity(qreal opacity) {
background_opacity_ = opacity;
if (isVisible()) update();
}
void OSDPretty::set_foreground_color(QRgb color) {
foreground_color_ = QColor(color);
QPalette p;
p.setColor(QPalette::WindowText, foreground_color_);
ui_->summary->setPalette(p);
ui_->message->setPalette(p);
}
void OSDPretty::set_popup_duration(int msec) {
timeout_->setInterval(msec);
}
void OSDPretty::mouseReleaseEvent(QMouseEvent *) {
if (mode_ == Mode_Draggable) {
popup_display_ = current_display();
popup_pos_ = current_pos();
}
}
void OSDPretty::set_font(QFont font) {
font_ = font;
// Update the UI
ui_->summary->setFont(font);
ui_->message->setFont(font);
// Now adjust OSD size so everything fits
ui_->verticalLayout->activate();
resize(sizeHint());
// Update the position after font change
Reposition();
}

149
src/widgets/osdpretty.h Normal file
View File

@@ -0,0 +1,149 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef OSDPRETTY_H
#define OSDPRETTY_H
#include "config.h"
#include <QWidget>
class Ui_OSDPretty;
class QTimeLine;
class OSDPretty : public QWidget {
Q_OBJECT
public:
enum Mode {
Mode_Popup,
Mode_Draggable,
};
OSDPretty(Mode mode, QWidget *parent = nullptr);
~OSDPretty();
static const char *kSettingsGroup;
static const int kDropShadowSize;
static const int kBorderRadius;
static const int kMaxIconSize;
static const int kSnapProximity;
static const QRgb kPresetBlue;
static const QRgb kPresetOrange;
static bool IsTransparencyAvailable();
void SetMessage(const QString &summary, const QString& message, const QImage &image);
void ShowMessage(const QString &summary, const QString& message, const QImage &image);
// Controls the fader. This is enabled by default on Windows.
void set_fading_enabled(bool enabled) { fading_enabled_ = enabled; }
// Popup duration in seconds. Only used in Mode_Popup.
void set_popup_duration(int msec);
// These will get overwritten when ReloadSettings() is called
void set_foreground_color(QRgb color);
void set_background_color(QRgb color);
void set_background_opacity(qreal opacity);
void set_font(QFont font);
QRgb foreground_color() const { return foreground_color_.rgb(); }
QRgb background_color() const { return background_color_.rgb(); }
qreal background_opacity() const { return background_opacity_; }
int popup_display() const { return popup_display_; }
QPoint popup_pos() const { return popup_pos_; }
QFont font() const { return font_; }
bool disable_duration() const { return disable_duration_; }
// When the user has been moving the popup, use these to get its current
// position and screen. Note that these return invalid values if the popup
// is hidden.
int current_display() const;
QPoint current_pos() const;
// QWidget
void setVisible(bool visible);
bool toggle_mode() const { return toggle_mode_; }
void set_toggle_mode(bool toggle_mode) { toggle_mode_ = toggle_mode; }
public slots:
void ReloadSettings();
protected:
void paintEvent(QPaintEvent *);
void enterEvent(QEvent *);
void leaveEvent(QEvent *);
void mousePressEvent(QMouseEvent *);
void showEvent(QShowEvent *);
void mouseMoveEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
private:
void Reposition();
void Load();
QRect BoxBorder() const;
private slots:
void FaderValueChanged(qreal value);
void FaderFinished();
private:
Ui_OSDPretty *ui_;
Mode mode_;
// Settings loaded from QSettings
QColor foreground_color_;
QColor background_color_;
float background_opacity_;
int popup_display_; // -1 for default
QPoint popup_pos_;
QFont font_;
// The OSD is kept always on top until you click (no timer)
bool disable_duration_;
// Cached pixmaps
QPixmap shadow_edge_[4];
QPixmap shadow_corner_[4];
QPixmap background_;
// For dragging the OSD
QPoint original_window_pos_;
QPoint drag_start_pos_;
// For timeout of notification
QTimer *timeout_;
// For fading
bool fading_enabled_;
QTimeLine *fader_;
// Toggling requested, we have to show or hide the OSD
bool toggle_mode_;
};
#endif // OSDPRETTY_H

99
src/widgets/osdpretty.ui Normal file
View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OSDPretty</class>
<widget class="QWidget" name="OSDPretty">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>80</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">OSDPretty {
background-color: transparent;
}
#summary {
font-weight: bold;
font-size: larger;
}
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QLabel" name="icon"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="summary">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="message">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,603 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QMenu>
#include <QMovie>
#include <QPainter>
#include <QPaintEvent>
#include <QSettings>
#include <QSignalMapper>
#include <QTextDocument>
#include <QTimeLine>
#include <QtDebug>
#include "playingwidget.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/iconloader.h"
#include "collection/collectionbackend.h"
#include "covermanager/albumcoverloader.h"
#include "covermanager/coverproviders.h"
#include "covermanager/currentartloader.h"
#include "covermanager/albumcoverchoicecontroller.h"
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
// Space between the cover and the details in small mode
const int PlayingWidget::kPadding = 2;
// Width of the transparent to black gradient above and below the text in large
// mode
const int PlayingWidget::kGradientHead = 40;
const int PlayingWidget::kGradientTail = 20;
// Maximum height of the cover in large mode, and offset between the
// bottom of the cover and bottom of the widget
const int PlayingWidget::kMaxCoverSize = 260;
const int PlayingWidget::kBottomOffset = 0;
// Border for large mode
const int PlayingWidget::kTopBorder = 4;
PlayingWidget::PlayingWidget(QWidget *parent)
: QWidget(parent),
app_(nullptr),
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
//mode_(SmallSongDetails),
menu_(new QMenu(this)),
fit_cover_width_action_(nullptr),
enabled_(false),
visible_(false),
active_(false),
small_ideal_height_(0),
fit_width_(false),
show_hide_animation_(new QTimeLine(500, this)),
fade_animation_(new QTimeLine(1000, this)),
details_(new QTextDocument(this)),
previous_track_opacity_(0.0),
downloading_covers_(false) {
//qLog(Debug) << __PRETTY_FUNCTION__;
enabled_ = false;
visible_ = false;
active_ = false;
// Load settings
QSettings s;
s.beginGroup(kSettingsGroup);
mode_ = Mode(s.value("mode", LargeSongDetails).toInt());
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", false).toBool());
fit_width_ = s.value("fit_cover_width", false).toBool();
// Accept drops for setting album art
setAcceptDrops(true);
// Context menu
QActionGroup *mode_group = new QActionGroup(this);
QSignalMapper *mode_mapper = new QSignalMapper(this);
connect(mode_mapper, SIGNAL(mapped(int)), SLOT(SetMode(int)));
CreateModeAction(SmallSongDetails, tr("Small album cover"), mode_group, mode_mapper);
CreateModeAction(LargeSongDetails, tr("Large album cover"), mode_group, mode_mapper);
menu_->addActions(mode_group->actions());
fit_cover_width_action_ = menu_->addAction(tr("Fit cover to width"));
fit_cover_width_action_->setCheckable(true);
fit_cover_width_action_->setEnabled(true);
connect(fit_cover_width_action_, SIGNAL(toggled(bool)), SLOT(FitCoverWidth(bool)));
fit_cover_width_action_->setChecked(fit_width_);
menu_->addSeparator();
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
// Here we add the search automatically action, too!
actions.append(album_cover_choice_controller_->search_cover_auto_action());
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
menu_->addActions(actions);
menu_->addSeparator();
// Animations
connect(show_hide_animation_, SIGNAL(frameChanged(int)), SLOT(SetHeight(int)));
setMaximumHeight(0);
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
// add placeholder text to get the correct height
if (mode_ == LargeSongDetails) {
details_->setDefaultStyleSheet(
"p {"
" font-size: small;"
" color: black;"
"}");
details_->setHtml(QString("<p align=center><i></i><br/><br/></p>"));
}
UpdateHeight();
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
}
PlayingWidget::~PlayingWidget() {
//qLog(Debug) << __PRETTY_FUNCTION__;
}
void PlayingWidget::SetApplication(Application *app) {
//qLog(Debug) << __PRETTY_FUNCTION__;
app_ = app;
album_cover_choice_controller_->SetApplication(app_);
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
}
void PlayingWidget::CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper* mapper) {
QAction* action = new QAction(text, group);
action->setCheckable(true);
mapper->setMapping(action, mode);
connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
if (mode == mode_) action->setChecked(true);
}
void PlayingWidget::set_ideal_height(int height) {
//qLog(Debug) << __PRETTY_FUNCTION__;
small_ideal_height_ = height;
UpdateHeight();
}
QSize PlayingWidget::sizeHint() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return QSize(cover_loader_options_.desired_height_, total_height_);
}
void PlayingWidget::UpdateHeight() {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch (mode_) {
case SmallSongDetails:
cover_loader_options_.desired_height_ = small_ideal_height_;
total_height_ = small_ideal_height_;
break;
case LargeSongDetails:
if (fit_width_) cover_loader_options_.desired_height_ = width();
else cover_loader_options_.desired_height_ = qMin(kMaxCoverSize, width());
total_height_ = kTopBorder + cover_loader_options_.desired_height_ + kBottomOffset + details_->size().height();
break;
}
// Update the animation settings and resize the widget now if we're visible
show_hide_animation_->setFrameRange(0, total_height_);
if (visible_ && show_hide_animation_->state() != QTimeLine::Running) setMaximumHeight(total_height_);
// Re-scale the current image
if (metadata_.is_valid()) {
ScaleCover();
}
// Tell Qt we've changed size
updateGeometry();
}
void PlayingWidget::Stopped() {
//qLog(Debug) << __PRETTY_FUNCTION__;
active_ = false;
SetVisible(false);
}
void PlayingWidget::UpdateDetailsText() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QString html;
switch (mode_) {
case SmallSongDetails:
details_->setTextWidth(-1);
details_->setDefaultStyleSheet("");
html += "<p>";
break;
case LargeSongDetails:
details_->setTextWidth(cover_loader_options_.desired_height_);
if (fit_width_) {
details_->setDefaultStyleSheet(
"p {"
" font-size: small;"
"}");
}
else {
details_->setDefaultStyleSheet(
"p {"
" font-size: small;"
" color: black;"
"}");
}
html += "<p align=center>";
break;
}
// TODO: Make this configurable
html += QString("<i>%1</i><br/>%2<br/>%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped());
html += "</p>";
details_->setHtml(html);
// if something spans multiple lines the height needs to change
if (mode_ == LargeSongDetails) UpdateHeight();
}
void PlayingWidget::ScaleCover() {
//qLog(Debug) << __PRETTY_FUNCTION__;
cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
update();
}
void PlayingWidget::AlbumArtLoaded(const Song &metadata, const QString &, const QImage &image) {
//qLog(Debug) << __PRETTY_FUNCTION__;
active_ = true;
metadata_ = metadata;
downloading_covers_ = false;
SetImage(image);
// Search for cover automatically?
GetCoverAutomatically();
}
void PlayingWidget::SetImage(const QImage &image) {
//qLog(Debug) << __PRETTY_FUNCTION__;
active_ = true;
if (visible_) {
// Cache the current pixmap so we can fade between them
previous_track_ = QPixmap(size());
previous_track_.fill(palette().background().color());
previous_track_opacity_ = 1.0;
QPainter p(&previous_track_);
DrawContents(&p);
p.end();
}
original_ = image;
UpdateDetailsText();
ScaleCover();
if (enabled_ == true) SetVisible(true);
// Were we waiting for this cover to load before we started fading?
if (!previous_track_.isNull()) {
fade_animation_->start();
}
}
void PlayingWidget::SetHeight(int height) {
//qLog(Debug) << __PRETTY_FUNCTION__;
setMaximumHeight(height);
}
void PlayingWidget::SetVisible(bool visible) {
//qLog(Debug) << __PRETTY_FUNCTION__ << visible;
//if (enabled_ == false) return;
if (visible == visible_) return;
visible_ = visible;
show_hide_animation_->setDirection(visible ? QTimeLine::Forward : QTimeLine::Backward);
show_hide_animation_->start();
}
void PlayingWidget::paintEvent(QPaintEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
QPainter p(this);
DrawContents(&p);
// Draw the previous track's image if we're fading
if (!previous_track_.isNull()) {
p.setOpacity(previous_track_opacity_);
p.drawPixmap(0, 0, previous_track_);
}
}
void PlayingWidget::DrawContents(QPainter *p) {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch (mode_) {
case SmallSongDetails:
// Draw the cover
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, cover_);
if (downloading_covers_) {
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
}
// Draw the details
p->translate(small_ideal_height_ + kPadding, 0);
details_->drawContents(p);
p->translate(-small_ideal_height_ - kPadding, 0);
break;
case LargeSongDetails:
// Work out how high the text is going to be
const int text_height = details_->size().height();
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2;
if (!fit_width_) {
// Draw the black background
//p->fillRect(QRect(0, kTopBorder, width(), height() - kTopBorder), Qt::black);
}
// Draw the cover
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, cover_);
if (downloading_covers_) {
p->drawPixmap(x_offset + 45, 35, 16, 16, spinner_animation_->currentPixmap());
}
// Draw the text below
p->translate(x_offset, height() - text_height);
details_->drawContents(p);
p->translate(-x_offset, -height() + text_height);
break;
}
}
void PlayingWidget::FadePreviousTrack(qreal value) {
//qLog(Debug) << __PRETTY_FUNCTION__;
previous_track_opacity_ = value;
if (qFuzzyCompare(previous_track_opacity_, qreal(0.0))) {
previous_track_ = QPixmap();
}
update();
}
void PlayingWidget::SetMode(int mode) {
mode_ = Mode(mode);
if (mode_ == SmallSongDetails) {
fit_cover_width_action_->setEnabled(false);
}
else {
fit_cover_width_action_->setEnabled(true);
}
UpdateHeight();
UpdateDetailsText();
update();
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("mode", mode_);
}
void PlayingWidget::resizeEvent(QResizeEvent* e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (visible_ && e->oldSize() != e->size()) {
if (mode_ == LargeSongDetails) {
UpdateHeight();
UpdateDetailsText();
}
}
}
void PlayingWidget::contextMenuEvent(QContextMenuEvent* e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// show the menu
menu_->popup(mapToGlobal(e->pos()));
}
void PlayingWidget::mouseReleaseEvent(QMouseEvent*) {
// Same behaviour as right-click > Show Fullsize
}
void PlayingWidget::FitCoverWidth(bool fit) {
//qLog(Debug) << __PRETTY_FUNCTION__;
fit_width_ = fit;
UpdateHeight();
update();
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("fit_cover_width", fit_width_);
}
void PlayingWidget::LoadCoverFromFile() {
album_cover_choice_controller_->LoadCoverFromFile(&metadata_);
}
void PlayingWidget::LoadCoverFromURL() {
album_cover_choice_controller_->LoadCoverFromURL(&metadata_);
}
void PlayingWidget::SearchForCover() {
album_cover_choice_controller_->SearchForCover(&metadata_);
}
void PlayingWidget::SaveCoverToFile() {
album_cover_choice_controller_->SaveCoverToFile(metadata_, original_);
}
void PlayingWidget::UnsetCover() {
album_cover_choice_controller_->UnsetCover(&metadata_);
}
void PlayingWidget::ShowCover() {
album_cover_choice_controller_->ShowCover(metadata_);
}
void PlayingWidget::SearchCoverAutomatically() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
// Search for cover automatically?
GetCoverAutomatically();
}
void PlayingWidget::dragEnterEvent(QDragEnterEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (AlbumCoverChoiceController::CanAcceptDrag(e)) {
e->acceptProposedAction();
}
QWidget::dragEnterEvent(e);
}
void PlayingWidget::dropEvent(QDropEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
album_cover_choice_controller_->SaveCover(&metadata_, e);
QWidget::dropEvent(e);
}
bool PlayingWidget::GetCoverAutomatically() {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Search for cover automatically?
bool search =
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
!metadata_.has_manually_unset_cover() &&
metadata_.art_automatic().isEmpty() && metadata_.art_manual().isEmpty() &&
!metadata_.artist().isEmpty() && !metadata_.album().isEmpty();
if (search) {
//qLog(Debug) << "GetCoverAutomatically";
downloading_covers_ = true;
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
// Show a spinner animation
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
spinner_animation_->start();
update();
}
return search;
}
void PlayingWidget::AutomaticCoverSearchDone() {
//qLog(Debug) << __PRETTY_FUNCTION__;
downloading_covers_ = false;
spinner_animation_.reset();
update();
}
void PlayingWidget::SetEnabled() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (enabled_ == true) return;
if ((active_ == true) && (visible_ == false)) SetVisible(true);
enabled_ = true;
}
void PlayingWidget::SetDisabled() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (enabled_ == false) return;
if (visible_ == true) SetVisible(false);
enabled_ = false;
}

152
src/widgets/playingwidget.h Normal file
View File

@@ -0,0 +1,152 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYINGWIDGET_H
#define PLAYINGWIDGET_H
#include "config.h"
#include <memory>
#include <QWidget>
#include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
class AlbumCoverChoiceController;
class Application;
class QAction;
class QActionGroup;
class QMenu;
class QMovie;
class QSignalMapper;
class QTextDocument;
class QTimeLine;
class PlayingWidget : public QWidget {
Q_OBJECT
public:
PlayingWidget(QWidget *parent = nullptr);
~PlayingWidget();
static const char *kSettingsGroup;
static const int kPadding;
static const int kGradientHead;
static const int kGradientTail;
static const int kMaxCoverSize;
static const int kBottomOffset;
static const int kTopBorder;
// Values are saved in QSettings
enum Mode {
SmallSongDetails = 0,
LargeSongDetails = 1,
};
void SetApplication(Application *app);
void SetEnabled();
void SetDisabled();
void set_ideal_height(int height);
QSize sizeHint() const;
signals:
void ShowAboveStatusBarChanged(bool above);
public slots:
void Stopped();
protected:
void paintEvent(QPaintEvent *e);
void resizeEvent(QResizeEvent*);
void contextMenuEvent(QContextMenuEvent *e);
void mouseReleaseEvent(QMouseEvent*);
void dragEnterEvent(QDragEnterEvent *e);
void dropEvent(QDropEvent *e);
private slots:
void SetMode(int mode);
void FitCoverWidth(bool fit);
void AlbumArtLoaded(const Song &metadata, const QString &uri, const QImage &image);
void SetVisible(bool visible);
void SetHeight(int height);
void FadePreviousTrack(qreal value);
void LoadCoverFromFile();
void SaveCoverToFile();
void LoadCoverFromURL();
void SearchForCover();
void UnsetCover();
void ShowCover();
void SearchCoverAutomatically();
void AutomaticCoverSearchDone();
private:
void CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper);
void UpdateDetailsText();
void UpdateHeight();
void DrawContents(QPainter *p);
void SetImage(const QImage &image);
void ScaleCover();
bool GetCoverAutomatically();
private:
Application *app_;
AlbumCoverChoiceController *album_cover_choice_controller_;
Mode mode_;
QMenu *menu_;
QAction *fit_cover_width_action_;
bool enabled_;
bool visible_;
bool active_;
int small_ideal_height_;
AlbumCoverLoaderOptions cover_loader_options_;
int total_height_;
bool fit_width_;
QTimeLine *show_hide_animation_;
QTimeLine *fade_animation_;
// Information about the current track
Song metadata_;
QPixmap cover_;
// A copy of the original, unscaled album cover.
QImage original_;
QTextDocument *details_;
// Holds the last track while we're fading to the new track
QPixmap previous_track_;
qreal previous_track_opacity_;
std::unique_ptr<QMovie> spinner_animation_;
bool downloading_covers_;
};
#endif // PLAYINGWIDGET_H

245
src/widgets/prettyimage.cpp Normal file
View File

@@ -0,0 +1,245 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "prettyimage.h"
#include <QApplication>
#include <QContextMenuEvent>
#include <QDesktopWidget>
#include <QDir>
#include <QFileDialog>
#include <QFuture>
#include <QLabel>
#include <QMenu>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPainter>
#include <QScrollArea>
#include <QSettings>
#include <QtConcurrentRun>
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/iconloader.h"
const int PrettyImage::kTotalHeight = 200;
const int PrettyImage::kReflectionHeight = 40;
const int PrettyImage::kImageHeight = PrettyImage::kTotalHeight - PrettyImage::kReflectionHeight;
const int PrettyImage::kMaxImageWidth = 300;
const char *PrettyImage::kSettingsGroup = "PrettyImageView";
PrettyImage::PrettyImage(const QUrl& url, QNetworkAccessManager* network, QWidget* parent)
: QWidget(parent),
network_(network),
state_(State_WaitingForLazyLoad),
url_(url),
menu_(nullptr) {
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
LazyLoad();
}
void PrettyImage::LazyLoad() {
if (state_ != State_WaitingForLazyLoad) return;
// Start fetching the image
QNetworkReply* reply = network_->get(QNetworkRequest(url_));
RedirectFollower* follower = new RedirectFollower(reply);
state_ = State_Fetching;
NewClosure(follower, SIGNAL(finished()), this, SLOT(ImageFetched(RedirectFollower*)), follower);
}
QSize PrettyImage::image_size() const {
if (state_ != State_Finished) return QSize(kImageHeight * 1.6, kImageHeight);
QSize ret = image_.size();
ret.scale(kMaxImageWidth, kImageHeight, Qt::KeepAspectRatio);
return ret;
}
QSize PrettyImage::sizeHint() const {
return QSize(image_size().width(), kTotalHeight);
}
void PrettyImage::ImageFetched(RedirectFollower* follower) {
follower->deleteLater();
QNetworkReply* reply = follower->reply();
reply->deleteLater();
QImage image = QImage::fromData(reply->readAll());
if (image.isNull()) {
qLog(Debug) << "Image failed to load" << reply->request().url() << reply->error();
deleteLater();
} else {
state_ = State_CreatingThumbnail;
image_ = image;
QFuture<QImage> future = QtConcurrent::run(image_, &QImage::scaled, image_size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
NewClosure(future, this, SLOT(ImageScaled(QFuture<QImage>)), future);
}
}
void PrettyImage::ImageScaled(QFuture<QImage> future) {
thumbnail_ = QPixmap::fromImage(future.result());
state_ = State_Finished;
updateGeometry();
update();
emit Loaded();
}
void PrettyImage::paintEvent(QPaintEvent* ) {
// Draw at the bottom of our area
QRect image_rect(QPoint(0, 0), image_size());
image_rect.moveBottom(kImageHeight);
QPainter p(this);
// Draw the main image
DrawThumbnail(&p, image_rect);
// Draw the reflection
// Figure out where to draw it
QRect reflection_rect(image_rect);
reflection_rect.moveTop(image_rect.bottom());
// Create the reflected pixmap
QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied);
reflection.fill(palette().color(QPalette::Base).rgba());
QPainter reflection_painter(&reflection);
// Set up the transformation
QTransform transform;
transform.scale(1.0, -1.0);
transform.translate(0.0, -reflection_rect.height());
reflection_painter.setTransform(transform);
QRect fade_rect(reflection.rect().bottomLeft() - QPoint(0, kReflectionHeight), reflection.rect().bottomRight());
// Draw the reflection into the buffer
DrawThumbnail(&reflection_painter, reflection.rect());
// Make it fade out towards the bottom
QLinearGradient fade_gradient(fade_rect.topLeft(), fade_rect.bottomLeft());
fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0));
fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128));
reflection_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
reflection_painter.fillRect(fade_rect, fade_gradient);
reflection_painter.end();
// Draw the reflection on the image
p.drawImage(reflection_rect, reflection);
}
void PrettyImage::DrawThumbnail(QPainter* p, const QRect& rect) {
switch (state_) {
case State_WaitingForLazyLoad:
case State_Fetching:
case State_CreatingThumbnail:
p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
p->drawText(rect, Qt::AlignHCenter | Qt::AlignBottom, tr("Loading..."));
break;
case State_Finished:
p->drawPixmap(rect, thumbnail_);
break;
}
}
void PrettyImage::contextMenuEvent(QContextMenuEvent* e) {
if (e->pos().y() >= kImageHeight) return;
if (!menu_) {
menu_ = new QMenu(this);
menu_->addAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this, SLOT(ShowFullsize()));
menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...", this, SLOT(SaveAs()));
}
menu_->popup(e->globalPos());
}
void PrettyImage::ShowFullsize() {
// Work out how large to make the window, based on the size of the screen
QRect desktop_rect(QApplication::desktop()->availableGeometry(this));
QSize window_size(qMin(desktop_rect.width() - 20, image_.width()), qMin(desktop_rect.height() - 20, image_.height()));
// Create the window
QScrollArea* window = new QScrollArea;
window->setAttribute(Qt::WA_DeleteOnClose, true);
window->setWindowTitle(tr("Strawberry image viewer"));
window->resize(window_size);
// Create the label that displays the image
QLabel* label = new QLabel(window);
label->setPixmap(QPixmap::fromImage(image_));
// Show the label in the window
window->setWidget(label);
window->setFrameShape(QFrame::NoFrame);
window->show();
}
void PrettyImage::SaveAs() {
QString filename = QFileInfo(url_.path()).fileName();
if (filename.isEmpty()) filename = "artwork.jpg";
QSettings s;
s.beginGroup(kSettingsGroup);
QString last_save_dir = s.value("last_save_dir", QDir::homePath()).toString();
QString path = last_save_dir.isEmpty() ? QDir::homePath() : last_save_dir;
QFileInfo path_info(path);
if (path_info.isDir()) {
path += "/" + filename;
}
else {
path = path_info.path() + "/" + filename;
}
filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
if (filename.isEmpty()) return;
image_.save(filename);
s.setValue("last_save_dir", last_save_dir);
}

90
src/widgets/prettyimage.h Normal file
View File

@@ -0,0 +1,90 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PRETTYIMAGE_H
#define PRETTYIMAGE_H
#include "config.h"
#include <QFuture>
#include <QUrl>
#include <QWidget>
class QMenu;
class QNetworkAccessManager;
class RedirectFollower;
class PrettyImage : public QWidget {
Q_OBJECT
public:
PrettyImage(const QUrl& url, QNetworkAccessManager* network, QWidget* parent = nullptr);
static const int kTotalHeight;
static const int kReflectionHeight;
static const int kImageHeight;
static const int kMaxImageWidth;
static const char* kSettingsGroup;
QSize sizeHint() const;
QSize image_size() const;
signals:
void Loaded();
public slots:
void LazyLoad();
void SaveAs();
void ShowFullsize();
protected:
void contextMenuEvent(QContextMenuEvent*);
void paintEvent(QPaintEvent*);
private slots:
void ImageFetched(RedirectFollower* reply);
void ImageScaled(QFuture<QImage> future);
private:
enum State {
State_WaitingForLazyLoad,
State_Fetching,
State_CreatingThumbnail,
State_Finished,
};
void DrawThumbnail(QPainter* p, const QRect& rect);
private:
QNetworkAccessManager* network_;
State state_;
QUrl url_;
QImage image_;
QPixmap thumbnail_;
QMenu* menu_;
QString last_save_dir_;
};
#endif // PRETTYIMAGE_H

View File

@@ -0,0 +1,179 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "prettyimage.h"
#include "prettyimageview.h"
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPropertyAnimation>
#include <QScrollBar>
#include <QTimer>
#include <QtDebug>
PrettyImageView::PrettyImageView(QNetworkAccessManager* network, QWidget* parent)
: QScrollArea(parent),
network_(network),
container_(new QWidget(this)),
layout_(new QHBoxLayout(container_)),
current_index_(-1),
scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this)),
recursion_filter_(false)
{
setWidget(container_);
setWidgetResizable(true);
setMinimumHeight(PrettyImage::kTotalHeight + 10);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setFrameShape(QFrame::NoFrame);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_animation_->setDuration(250);
scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
layout_->setContentsMargins(6, 6, 6, 6);
layout_->setSpacing(6);
layout_->addSpacing(200);
layout_->addSpacing(200);
container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
}
bool PrettyImageView::eventFilter(QObject* obj, QEvent* event) {
// Work around infinite recursion in QScrollArea resizes.
if (recursion_filter_) {
return false;
}
recursion_filter_ = true;
bool ret = QScrollArea::eventFilter(obj, event);
recursion_filter_ = false;
return ret;
}
void PrettyImageView::AddImage(const QUrl &url) {
PrettyImage *image = new PrettyImage(url, network_, container_);
connect(image, SIGNAL(destroyed()), SLOT(ScrollToCurrent()));
connect(image, SIGNAL(Loaded()), SLOT(ScrollToCurrent()));
layout_->insertWidget(layout_->count() - 1, image);
if (current_index_ == -1) ScrollTo(0);
}
void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) {
// Find the image that was clicked on
QWidget* widget = container_->childAt(container_->mapFrom(this, e->pos()));
if (!widget) return;
// Get the index of that image
const int index = layout_->indexOf(widget) - 1;
if (index == -1) return;
if (index == current_index_) {
// Show the image fullsize
PrettyImage* pretty_image = qobject_cast<PrettyImage*>(widget);
if (pretty_image) {
pretty_image->ShowFullsize();
}
}
else {
// Scroll to the image
ScrollTo(index);
}
}
void PrettyImageView::ScrollTo(int index, bool smooth) {
current_index_ = qBound(0, index, layout_->count() - 3);
const int layout_index = current_index_ + 1;
const QWidget* target_widget = layout_->itemAt(layout_index)->widget();
if (!target_widget) return;
const int current_x = horizontalScrollBar()->value();
const int target_x = target_widget->geometry().center().x() - width() / 2;
if (current_x == target_x) return;
if (smooth) {
scroll_animation_->setStartValue(current_x);
scroll_animation_->setEndValue(target_x);
scroll_animation_->start();
}
else {
scroll_animation_->stop();
horizontalScrollBar()->setValue(target_x);
}
}
void PrettyImageView::ScrollToCurrent() {
ScrollTo(current_index_);
}
void PrettyImageView::ScrollBarReleased() {
// Find the nearest widget to where the scroll bar was released
const int current_x = horizontalScrollBar()->value() + width() / 2;
int layout_index = 1;
for (; layout_index<layout_->count() - 1 ; ++layout_index) {
const QWidget* widget = layout_->itemAt(layout_index)->widget();
if (widget && widget->geometry().right() > current_x) {
break;
}
}
ScrollTo(layout_index - 1);
}
void PrettyImageView::ScrollBarAction(int action) {
switch (action) {
case QAbstractSlider::SliderSingleStepAdd:
case QAbstractSlider::SliderPageStepAdd:
ScrollTo(current_index_ + 1);
break;
case QAbstractSlider::SliderSingleStepSub:
case QAbstractSlider::SliderPageStepSub:
ScrollTo(current_index_ - 1);
break;
}
}
void PrettyImageView::resizeEvent(QResizeEvent* e) {
QScrollArea::resizeEvent(e);
ScrollTo(current_index_, false);
}
void PrettyImageView::wheelEvent(QWheelEvent* e) {
const int d = e->delta() > 0 ? -1 : 1;
ScrollTo(current_index_ + d, true);
}

View File

@@ -0,0 +1,74 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PRETTYIMAGEVIEW_H
#define PRETTYIMAGEVIEW_H
#include "config.h"
#include <QMap>
#include <QScrollArea>
#include <QUrl>
class QHBoxLayout;
class QMenu;
class QNetworkAccessManager;
class QNetworkReply;
class QPropertyAnimation;
class QTimeLine;
class PrettyImageView : public QScrollArea {
Q_OBJECT
public:
PrettyImageView(QNetworkAccessManager* network, QWidget* parent = nullptr);
static const char* kSettingsGroup;
public slots:
void AddImage(const QUrl& url);
protected:
void mouseReleaseEvent(QMouseEvent*);
void resizeEvent(QResizeEvent* e);
void wheelEvent(QWheelEvent* e);
private slots:
void ScrollBarReleased();
void ScrollBarAction(int action);
void ScrollTo(int index, bool smooth = true);
void ScrollToCurrent();
private:
bool eventFilter(QObject*, QEvent*);
QNetworkAccessManager* network_;
QWidget* container_;
QHBoxLayout* layout_;
int current_index_;
QPropertyAnimation* scroll_animation_;
bool recursion_filter_;
};
#endif // PRETTYIMAGEVIEW_H

View File

@@ -0,0 +1,50 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "progressitemdelegate.h"
#include <QApplication>
ProgressItemDelegate::ProgressItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
bool ok = false;
int progress = index.data().toInt(&ok);
if (ok) {
QStyleOptionProgressBar opt;
opt.rect = option.rect;
opt.minimum = 0;
opt.maximum = 100;
opt.progress = progress;
opt.text = QString::number(progress) + "%";
opt.textVisible = true;
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PROGRESSITEMDELEGATE_H
#define PROGRESSITEMDELEGATE_H
#include "config.h"
#include <QStyledItemDelegate>
class ProgressItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
ProgressItemDelegate(QObject* parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
#endif // PROGRESSITEMDELEGATE_H

View File

@@ -0,0 +1,154 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "ratingwidget.h"
#include <QMouseEvent>
#include <QStyleOptionFrameV3>
#include <QStylePainter>
#include <QtDebug>
const int RatingPainter::kStarCount;
const int RatingPainter::kStarSize;
RatingPainter::RatingPainter() {
// Load the base pixmaps
QPixmap on(":/icons/64x64/star.png");
QPixmap off(":/icons/64x64/star-grey.png");
// Generate the 10 states, better to do it now than on the fly
for (int i = 0; i < kStarCount * 2 + 1; ++i) {
const float rating = float(i) / 2.0;
// Clear the pixmap
stars_[i] = QPixmap(kStarSize * kStarCount, kStarSize);
stars_[i].fill(Qt::transparent);
QPainter p(&stars_[i]);
// Draw the stars
int x = 0;
for (int i = 0; i < kStarCount; ++i, x += kStarSize) {
const QRect rect(x, 0, kStarSize, kStarSize);
if (rating - 0.25 <= i) {
// Totally empty
p.drawPixmap(rect, off);
} else if (rating - 0.75 <= i) {
// Half full
const QRect target_left(rect.x(), rect.y(), kStarSize/2, kStarSize);
const QRect target_right(rect.x() + kStarSize/2, rect.y(), kStarSize/2, kStarSize);
const QRect source_left(0, 0, kStarSize/2, kStarSize);
const QRect source_right(kStarSize/2, 0, kStarSize/2, kStarSize);
p.drawPixmap(target_left, on, source_left);
p.drawPixmap(target_right, off, source_right);
} else {
// Totally full
p.drawPixmap(rect, on);
}
}
}
}
QRect RatingPainter::Contents(const QRect& rect) {
const int width = kStarSize * kStarCount;
const int x = rect.x() + (rect.width() - width) / 2;
return QRect(x, rect.y(), width, rect.height());
}
double RatingPainter::RatingForPos(const QPoint& pos, const QRect& rect) {
const QRect contents = Contents(rect);
const double raw = double(pos.x() - contents.left()) / contents.width();
// Round to the nearest 0.1
return double(int(raw * kStarCount * 2 + 0.5)) / (kStarCount * 2);
}
void RatingPainter::Paint(QPainter* painter, const QRect& rect, float rating) const {
QSize size(qMin(kStarSize*kStarCount, rect.width()), qMin(kStarSize, rect.height()));
QPoint pos(rect.center() - QPoint(size.width() / 2, size.height() / 2));
rating *= kStarCount;
// Draw the stars
const int star = qBound(0, int(rating*2.0 + 0.5), kStarCount*2);
painter->drawPixmap(QRect(pos, size), stars_[star], QRect(QPoint(0,0), size));
}
RatingWidget::RatingWidget(QWidget* parent)
: QWidget(parent),
rating_(0.0),
hover_rating_(-1.0)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
setMouseTracking(true);
}
QSize RatingWidget::sizeHint() const {
const int frame_width = 1 + style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
return QSize(RatingPainter::kStarSize * (RatingPainter::kStarCount+2) + frame_width*2,
RatingPainter::kStarSize + frame_width*2);
}
void RatingWidget::set_rating(float rating) {
rating_ = rating;
update();
}
void RatingWidget::paintEvent(QPaintEvent* e) {
QStylePainter p(this);
// Draw the background
QStyleOptionFrameV3 opt;
opt.initFrom(this);
opt.state |= QStyle::State_Sunken;
opt.frameShape = QFrame::StyledPanel;
opt.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
opt.midLineWidth = 0;
p.drawPrimitive(QStyle::PE_PanelLineEdit, opt);
// Draw the stars
painter_.Paint(&p, rect(), hover_rating_ == -1.0 ? rating_ : hover_rating_);
}
void RatingWidget::mousePressEvent(QMouseEvent* e) {
rating_ = RatingPainter::RatingForPos(e->pos(), rect());
emit RatingChanged(rating_);
}
void RatingWidget::mouseMoveEvent(QMouseEvent* e) {
hover_rating_ = RatingPainter::RatingForPos(e->pos(), rect());
update();
}
void RatingWidget::leaveEvent(QEvent*) {
hover_rating_ = -1.0;
update();
}

View File

@@ -0,0 +1,70 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef RATINGWIDGET_H
#define RATINGWIDGET_H
#include <QFrame>
#include <QPixmap>
class RatingPainter {
public:
RatingPainter();
static const int kStarCount = 5;
static const int kStarSize = 15;
static QRect Contents(const QRect& rect);
static double RatingForPos(const QPoint& pos, const QRect& rect);
void Paint(QPainter* painter, const QRect& rect, float rating) const;
private:
QPixmap stars_[kStarCount*2+1];
};
class RatingWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(float rating READ rating WRITE set_rating);
public:
RatingWidget(QWidget* parent = nullptr);
QSize sizeHint() const;
float rating() const { return rating_; }
void set_rating(float rating);
signals:
void RatingChanged(float rating);
protected:
void paintEvent(QPaintEvent*);
void mousePressEvent(QMouseEvent* e);
void mouseMoveEvent(QMouseEvent* e);
void leaveEvent(QEvent*);
private:
RatingPainter painter_;
float rating_;
float hover_rating_;
};
#endif // RATINGWIDGET_H

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, Andrea Decorte <adecorte@gmail.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "renametablineedit.h"
#include <QKeyEvent>
RenameTabLineEdit::RenameTabLineEdit(QWidget *parent) :
QLineEdit(parent)
{
}
void RenameTabLineEdit::keyPressEvent (QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
e->accept();
emit EditingCanceled();
}
else {
QLineEdit::keyPressEvent(e);
}
}
void RenameTabLineEdit::focusOutEvent(QFocusEvent *e) {
//if the user hasn't explicitly accepted, discard the value
emit EditingCanceled();
//we don't call the default event since it will trigger editingFished()
}

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, Andrea Decorte <adecorte@gmail.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef RENAMETABLINEEDIT_H
#define RENAMETABLINEEDIT_H
#include <QLineEdit>
class RenameTabLineEdit : public QLineEdit {
Q_OBJECT
public:
RenameTabLineEdit(QWidget* parent = nullptr);
signals:
void EditingCanceled();
public slots:
protected:
void focusOutEvent(QFocusEvent* e);
void keyPressEvent(QKeyEvent* e);
};
#endif // RENAMETABLINEEDIT_H

View File

@@ -0,0 +1,392 @@
/***************************************************************************
amarokslider.cpp - description
-------------------
begin : Dec 15 2003
copyright : (C) 2003 by Mark Kretschmann
email : markey@web.de
copyright : (C) 2005 by Gábor Lehel
email : illissius@gmail.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "sliderwidget.h"
#include <QApplication>
#include <QBitmap>
#include <QBrush>
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QTimer>
#include <QStyle>
#include <QMenu>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QStyleOptionSlider>
Amarok::Slider::Slider(Qt::Orientation orientation, QWidget* parent, uint max)
: QSlider(orientation, parent),
m_sliding(false),
m_outside(false),
m_prevValue(0) {
setRange(0, max);
}
void Amarok::Slider::wheelEvent(QWheelEvent* e) {
if (orientation() == Qt::Vertical) {
// Will be handled by the parent widget
e->ignore();
return;
}
// Position Slider (horizontal)
int step = e->delta() * 1500 / 18;
int nval = qBound(minimum(), QSlider::value() + step, maximum());
QSlider::setValue(nval);
emit sliderReleased(value());
}
void Amarok::Slider::mouseMoveEvent(QMouseEvent *e) {
if (m_sliding) {
// feels better, but using set value of 20 is bad of course
QRect rect(-20, -20, width() + 40, height() + 40);
if (orientation() == Qt::Horizontal && !rect.contains(e->pos())) {
if (!m_outside) QSlider::setValue(m_prevValue);
m_outside = true;
}
else {
m_outside = false;
slideEvent(e);
emit sliderMoved(value());
}
}
else
QSlider::mouseMoveEvent(e);
}
void Amarok::Slider::slideEvent(QMouseEvent* e) {
QStyleOptionSlider option;
initStyleOption(&option);
QRect sliderRect(style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this));
QSlider::setValue(
orientation() == Qt::Horizontal
? ((QApplication::layoutDirection() == Qt::RightToLeft)
? QStyle::sliderValueFromPosition(
minimum(), maximum(),
width() - (e->pos().x() - sliderRect.width() / 2),
width() + sliderRect.width(), true)
: QStyle::sliderValueFromPosition(
minimum(), maximum(),
e->pos().x() - sliderRect.width() / 2,
width() - sliderRect.width()))
: QStyle::sliderValueFromPosition(
minimum(), maximum(), e->pos().y() - sliderRect.height() / 2,
height() - sliderRect.height()));
}
void Amarok::Slider::mousePressEvent(QMouseEvent* e) {
QStyleOptionSlider option;
initStyleOption(&option);
QRect sliderRect(style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this));
m_sliding = true;
m_prevValue = QSlider::value();
if (!sliderRect.contains(e->pos())) mouseMoveEvent(e);
}
void Amarok::Slider::mouseReleaseEvent(QMouseEvent*) {
if (!m_outside && QSlider::value() != m_prevValue)
emit sliderReleased(value());
m_sliding = false;
m_outside = false;
}
void Amarok::Slider::setValue(int newValue) {
// don't adjust the slider while the user is dragging it!
if (!m_sliding || m_outside)
QSlider::setValue(adjustValue(newValue));
else
m_prevValue = newValue;
}
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS PrettySlider
//////////////////////////////////////////////////////////////////////////////////////////
#define THICKNESS 7
#define MARGIN 3
Amarok::PrettySlider::PrettySlider(Qt::Orientation orientation, SliderMode mode, QWidget* parent, uint max)
: Amarok::Slider(orientation, parent, max), m_mode(mode) {
if (m_mode == Pretty) {
setFocusPolicy(Qt::NoFocus);
}
}
void Amarok::PrettySlider::mousePressEvent(QMouseEvent* e) {
Amarok::Slider::mousePressEvent(e);
slideEvent(e);
}
void Amarok::PrettySlider::slideEvent(QMouseEvent* e) {
if (m_mode == Pretty)
QSlider::setValue(
orientation() == Qt::Horizontal
? QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2)
: QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().y(), height() - 2));
else
Amarok::Slider::slideEvent(e);
}
namespace Amarok {
namespace ColorScheme {
extern QColor Background;
extern QColor Foreground;
}
}
#if 0
/** these functions aren't required in our fixed size world,
but they may become useful one day **/
QSize
Amarok::PrettySlider::minimumSizeHint() const
{
return sizeHint();
}
QSize
Amarok::PrettySlider::sizeHint() const
{
constPolish();
return (orientation() == Horizontal
? QSize( maxValue(), THICKNESS + MARGIN )
: QSize( THICKNESS + MARGIN, maxValue() )).expandedTo( QApplit ication::globalStrut() );
}
#endif
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS VolumeSlider
//////////////////////////////////////////////////////////////////////////////////////////
Amarok::VolumeSlider::VolumeSlider(QWidget* parent, uint max)
: Amarok::Slider(Qt::Horizontal, parent, max),
m_animCount(0),
m_animTimer(new QTimer(this)),
m_pixmapInset(QPixmap(drawVolumePixmap ())) {
setFocusPolicy(Qt::NoFocus);
// Store theme colors to check theme change at paintEvent
m_previous_theme_text_color = palette().color(QPalette::WindowText);
m_previous_theme_highlight_color = palette().color(QPalette::Highlight);
drawVolumeSliderHandle();
generateGradient();
setMinimumWidth(m_pixmapInset.width());
setMinimumHeight(m_pixmapInset.height());
connect(m_animTimer, SIGNAL(timeout()), this, SLOT(slotAnimTimer()));
}
void Amarok::VolumeSlider::generateGradient() {
const QImage mask(":/pictures/volumeslider-gradient.png");
QImage gradient_image(mask.size(), QImage::Format_ARGB32_Premultiplied);
QPainter p(&gradient_image);
QLinearGradient gradient(gradient_image.rect().topLeft(), gradient_image.rect().topRight());
gradient.setColorAt(0, palette().color(QPalette::Background));
gradient.setColorAt(1, palette().color(QPalette::Highlight));
p.fillRect(gradient_image.rect(), QBrush(gradient));
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(0, 0, mask);
p.end();
m_pixmapGradient = QPixmap::fromImage(gradient_image);
}
void Amarok::VolumeSlider::slotAnimTimer() // SLOT
{
if (m_animEnter) {
m_animCount++;
update();
if (m_animCount == ANIM_MAX - 1) m_animTimer->stop();
}
else {
m_animCount--;
update();
if (m_animCount == 0) m_animTimer->stop();
}
}
void Amarok::VolumeSlider::mousePressEvent(QMouseEvent* e) {
if (e->button() != Qt::RightButton) {
Amarok::Slider::mousePressEvent(e);
slideEvent(e);
}
}
void Amarok::VolumeSlider::contextMenuEvent(QContextMenuEvent* e) {
QMap<QAction*, int> values;
QMenu menu;
menu.setTitle("Volume");
values[menu.addAction("100%")] = 100;
values[menu.addAction("80%")] = 80;
values[menu.addAction("60%")] = 60;
values[menu.addAction("40%")] = 40;
values[menu.addAction("20%")] = 20;
values[menu.addAction("0%")] = 0;
QAction* ret = menu.exec(mapToGlobal(e->pos()));
if (ret) {
QSlider::setValue(values[ret]);
emit sliderReleased(values[ret]);
}
}
void Amarok::VolumeSlider::slideEvent(QMouseEvent* e) {
QSlider::setValue(QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2));
}
void Amarok::VolumeSlider::wheelEvent(QWheelEvent* e) {
const uint step = e->delta() / (e->orientation() == Qt::Vertical ? 30 : -30);
QSlider::setValue(QSlider::value() + step);
emit sliderReleased(value());
}
void Amarok::VolumeSlider::paintEvent(QPaintEvent*) {
QPainter p(this);
const int padding = 7;
const int offset = int(double((width() - 2 * padding) * value()) / maximum());
// If theme changed since last paintEvent, redraw the volume pixmap with new theme colors
if (m_previous_theme_text_color != palette().color(QPalette::WindowText)) {
m_pixmapInset = drawVolumePixmap();
m_previous_theme_text_color = palette().color(QPalette::WindowText);
}
if (m_previous_theme_highlight_color != palette().color(QPalette::Highlight)) {
drawVolumeSliderHandle();
m_previous_theme_highlight_color = palette().color(QPalette::Highlight);
}
p.drawPixmap(0, 0, m_pixmapGradient, 0, 0, offset + padding, 0);
p.drawPixmap(0, 0, m_pixmapInset);
p.drawPixmap(offset - m_handlePixmaps[0].width() / 2 + padding, 0, m_handlePixmaps[m_animCount]);
// Draw percentage number
QStyleOptionViewItem opt;
p.setPen(opt.palette.color(QPalette::Normal, QPalette::Text));
QFont vol_font(opt.font);
vol_font.setPixelSize(9);
p.setFont(vol_font);
const QRect rect(0, 0, 34, 15);
p.drawText(rect, Qt::AlignRight | Qt::AlignVCenter, QString::number(value()) + '%');
}
void Amarok::VolumeSlider::enterEvent(QEvent*) {
m_animEnter = true;
m_animCount = 0;
m_animTimer->start(ANIM_INTERVAL);
}
void Amarok::VolumeSlider::leaveEvent(QEvent*) {
// This can happen if you enter and leave the widget quickly
if (m_animCount == 0) m_animCount = 1;
m_animEnter = false;
m_animTimer->start(ANIM_INTERVAL);
}
void Amarok::VolumeSlider::paletteChange(const QPalette&) {
generateGradient();
}
QPixmap Amarok::VolumeSlider::drawVolumePixmap () const {
QPixmap pixmap(112, 36);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
QPen pen(palette().color(QPalette::WindowText), 0.3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// Draw volume control pixmap
QPolygon poly;
poly << QPoint(6, 21) << QPoint(104, 21)
<< QPoint(104, 7) << QPoint(6, 16)
<< QPoint(6, 21);
QPainterPath path;
path.addPolygon(poly);
painter.drawPolygon(poly);
painter.drawLine(6, 29, 104, 29);
// Return QPixmap
return pixmap;
}
void Amarok::VolumeSlider::drawVolumeSliderHandle() {
QImage pixmapHandle(":/pictures/volumeslider-handle.png");
QImage pixmapHandleGlow(":/pictures/volumeslider-handle_glow.png");
QImage pixmapHandleGlow_image(pixmapHandleGlow.size(), QImage::Format_ARGB32_Premultiplied);
QPainter painter(&pixmapHandleGlow_image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// repaint volume slider handle glow image with theme highlight color
painter.fillRect(pixmapHandleGlow_image.rect(), QBrush(palette().color(QPalette::Highlight)));
painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
painter.drawImage(0, 0, pixmapHandleGlow);
// Overlay the volume slider handle image
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
painter.drawImage(0, 0, pixmapHandle);
// BEGIN Calculate handle animation pixmaps for mouse-over effect
float opacity = 0.0;
const float step = 1.0 / ANIM_MAX;
QImage dst;
m_handlePixmaps.clear();
for (int i = 0; i < ANIM_MAX; ++i) {
dst = pixmapHandle.copy();
QPainter p(&dst);
p.setOpacity(opacity);
p.drawImage(0, 0, pixmapHandleGlow_image);
p.end();
m_handlePixmaps.append(QPixmap::fromImage(dst));
opacity += step;
}
// END
}

141
src/widgets/sliderwidget.h Normal file
View File

@@ -0,0 +1,141 @@
/***************************************************************************
amarokslider.h - description
-------------------
begin : Dec 15 2003
copyright : (C) 2003 by Mark Kretschmann
email : markey@web.de
copyright : (C) 2005 by Gábor Lehel
email : illissius@gmail.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef AMAROKSLIDER_H
#define AMAROKSLIDER_H
#include <QPixmap>
#include <QSlider>
#include <QList>
class QPalette;
class QTimer;
namespace Amarok {
class Slider : public QSlider {
Q_OBJECT
public:
Slider(Qt::Orientation, QWidget*, uint max = 0);
virtual void setValue(int);
// WARNING non-virtual - and thus only really intended for internal use
// this is a major flaw in the class presently, however it suits our
// current needs fine
int value() const { return adjustValue(QSlider::value()); }
signals:
// we emit this when the user has specifically changed the slider
// so connect to it if valueChanged() is too generic
// Qt also emits valueChanged( int )
void sliderReleased(int);
protected:
virtual void wheelEvent(QWheelEvent*);
virtual void mouseMoveEvent(QMouseEvent*);
virtual void mouseReleaseEvent(QMouseEvent*);
virtual void mousePressEvent(QMouseEvent*);
virtual void slideEvent(QMouseEvent*);
bool m_sliding;
/// we flip the value for vertical sliders
int adjustValue(int v) const {
int mp = (minimum() + maximum()) / 2;
return orientation() == Qt::Vertical ? mp - (v - mp) : v;
}
private:
bool m_outside;
int m_prevValue;
Slider(const Slider&); // undefined
Slider& operator=(const Slider&); // undefined
};
class PrettySlider : public Slider {
Q_OBJECT
public:
typedef enum {
Normal, // Same behavior as Slider *unless* there's a moodbar
Pretty
} SliderMode;
PrettySlider(Qt::Orientation orientation, SliderMode mode, QWidget* parent,
uint max = 0);
protected:
virtual void slideEvent(QMouseEvent*);
virtual void mousePressEvent(QMouseEvent*);
private:
PrettySlider(const PrettySlider&); // undefined
PrettySlider& operator=(const PrettySlider&); // undefined
SliderMode m_mode;
};
class VolumeSlider : public Slider {
Q_OBJECT
public:
VolumeSlider(QWidget* parent, uint max = 0);
protected:
virtual void paintEvent(QPaintEvent*);
virtual void enterEvent(QEvent*);
virtual void leaveEvent(QEvent*);
virtual void paletteChange(const QPalette&);
virtual void slideEvent(QMouseEvent*);
virtual void mousePressEvent(QMouseEvent*);
virtual void contextMenuEvent(QContextMenuEvent*);
virtual void wheelEvent(QWheelEvent* e);
private slots:
virtual void slotAnimTimer();
private:
void generateGradient();
QPixmap drawVolumePixmap() const;
void drawVolumeSliderHandle();
VolumeSlider(const VolumeSlider&); // undefined
VolumeSlider& operator=(const VolumeSlider&); // undefined
////////////////////////////////////////////////////////////////
static const int ANIM_INTERVAL = 18;
static const int ANIM_MAX = 18;
bool m_animEnter;
int m_animCount;
QTimer* m_animTimer;
QPixmap m_pixmapInset;
QPixmap m_pixmapGradient;
QColor m_previous_theme_text_color;
QColor m_previous_theme_highlight_color;
QList<QPixmap> m_handlePixmaps;
};
}
#endif

625
src/widgets/statusview.cpp Normal file
View File

@@ -0,0 +1,625 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <unistd.h>
#include <QApplication>
#include <QFile>
#include <QTextDocument>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QPainter>
#include <QPaintEvent>
#include <QMovie>
#include <QTimeLine>
#include "statusview.h"
#include "core/utilities.h"
#include "core/logging.h"
#include "core/song.h"
#include "core/application.h"
#include "core/player.h"
#include "core/mainwindow.h"
#include "collection/collection.h"
#include "collection/collectionbackend.h"
#include "collection/collectionview.h"
#include "collection/collectionviewcontainer.h"
#include "covermanager/albumcoverloader.h"
#include "covermanager/coverproviders.h"
#include "covermanager/currentartloader.h"
#include "covermanager/albumcoverchoicecontroller.h"
#include "engine/enginetype.h"
#include "engine/enginebase.h"
const char *StatusView::kSettingsGroup = "StatusView";
StatusView::StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent) :
QWidget(parent),
layout_(new QVBoxLayout),
scrollarea_(new QScrollArea),
container_layout_(new QVBoxLayout),
container_widget_(new QWidget),
widget_stopped_ (nullptr),
widget_playing_ (nullptr),
layout_playing_(nullptr),
layout_stopped_(nullptr),
label_stopped_top_ (nullptr),
label_stopped_logo_(nullptr),
label_stopped_text_(nullptr),
label_playing_top_(nullptr),
label_playing_album_(nullptr),
label_playing_text_(nullptr),
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
show_hide_animation_(new QTimeLine(500, this)),
fade_animation_(new QTimeLine(1000, this)),
image_blank_(""),
image_nosong_(":/icons/full/strawberry.png"),
widgetstate_(None),
menu_(new QMenu(this))
{
//qLog(Debug) << __PRETTY_FUNCTION__;
collectionview_ = collectionviewcontainer->view();
connect(collectionview_, SIGNAL(TotalSongCountUpdated_()), this, SLOT(UpdateNoSong()));
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
cover_loader_options_.desired_height_ = 300;
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_blank_));
CreateWidget();
NoSongWidget();
NoSong();
AddActions();
}
StatusView::~StatusView() {
}
void StatusView::AddActions() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
// Here we add the search automatically action, too!
actions.append(album_cover_choice_controller_->search_cover_auto_action());
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
menu_->addActions(actions);
menu_->addSeparator();
}
void StatusView::CreateWidget() {
//qLog(Debug) << __PRETTY_FUNCTION__;
setLayout(layout_);
setStyleSheet("background-color: white;");
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
layout_->setContentsMargins(0, 0, 0, 0);
layout_->setSpacing(6);
layout_->addWidget(scrollarea_);
scrollarea_->setWidget(container_widget_);
scrollarea_->setWidgetResizable(true);
scrollarea_->setStyleSheet("background-color: white;");
scrollarea_->setVisible(true);
container_widget_->setLayout(container_layout_);
container_widget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
container_widget_->setBackgroundRole(QPalette::Base);
container_layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
container_layout_->setContentsMargins(0, 0, 0, 0);
container_layout_->setSpacing(6);
container_layout_->addStretch();
}
void StatusView::SetApplication(Application *app) {
//qLog(Debug) << __PRETTY_FUNCTION__;
app_ = app;
album_cover_choice_controller_->SetApplication(app_);
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
}
void StatusView::NoSongWidget() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (widgetstate_ == Playing) {
container_layout_->removeWidget(widget_playing_);
widget_playing_->setVisible(false);
delete label_playing_top_;
delete label_playing_album_;
delete label_playing_text_;
delete layout_playing_;
delete widget_playing_;
}
widget_stopped_ = new QWidget;
layout_stopped_ = new QVBoxLayout;
label_stopped_top_ = new QLabel;
label_stopped_logo_ = new QLabel;
label_stopped_text_ = new QLabel;
layout_stopped_->addWidget(label_stopped_top_);
layout_stopped_->addWidget(label_stopped_logo_);
layout_stopped_->addWidget(label_stopped_text_);
layout_stopped_->addStretch();
label_stopped_top_->setFixedHeight(40);
label_stopped_top_->setFixedWidth(300);
label_stopped_top_->setAlignment(Qt::AlignTop & Qt::AlignCenter);
widget_stopped_->setVisible(true);
widget_stopped_->setLayout(layout_stopped_);
container_layout_->insertWidget(0, widget_stopped_);
widgetstate_ = Stopped;
}
void StatusView::SongWidget() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (widgetstate_ == Stopped) {
container_layout_->removeWidget(widget_stopped_);
widget_stopped_->setVisible(false);
delete label_stopped_top_ ;
delete label_stopped_logo_;
delete label_stopped_text_;
delete layout_stopped_;
delete widget_stopped_;
}
widget_playing_ = new QWidget;
widget_playing_ = new QWidget;
layout_playing_ = new QVBoxLayout;
label_playing_top_ = new QLabel;
label_playing_album_ = new QLabel;
label_playing_text_ = new QLabel;
layout_playing_->addWidget(label_playing_top_);
layout_playing_->addWidget(label_playing_album_);
layout_playing_->addWidget(label_playing_text_);
layout_playing_->addStretch();
label_playing_top_->setAlignment(Qt::AlignTop & Qt::AlignCenter);
label_playing_top_->setFixedHeight(40);
label_playing_top_->setFixedWidth(300);
label_playing_top_->setWordWrap(true);
label_playing_text_->setAlignment(Qt::AlignTop);
label_playing_text_->setFixedWidth(300);
label_playing_text_->setWordWrap(true);
label_playing_album_->setFixedHeight(300);
label_playing_album_->setFixedWidth(300);
label_playing_album_->setStyleSheet("background-color: transparent;");
label_playing_album_->installEventFilter(this);
widget_playing_->setVisible(true);
widget_playing_->setLayout(layout_playing_);
container_layout_->insertWidget(0, widget_playing_);
QFile stylesheet(":/style/statusview.css");
if (stylesheet.open(QIODevice::ReadOnly)) {
setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
label_playing_text_->setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
}
widgetstate_ = Playing;
}
void StatusView::SwitchWidgets(WidgetState state) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (widgetstate_ == None) NoSongWidget();
if ((state == Stopped) && (widgetstate_ != Stopped)) {
NoSongWidget();
}
if ((widgetstate_ != Playing) && (state == Playing)) {
SongWidget();
}
}
void StatusView::UpdateSong(const Song &song) {
//qLog(Debug) << __PRETTY_FUNCTION__;
SwitchWidgets(Playing);
const Song *song_ = &song;
const QueryOptions opt;
CollectionBackend::AlbumList albumlist;
Engine::EngineType enginetype = app_->player()->engine()->type();
QString EngineName = EngineNameFromType(enginetype);
label_playing_top_->setText("");
label_playing_text_->setText("");
QString html;
QString html_albums;
html += QString("<b>%1 - %2</b><br/>%3").arg(song_->PrettyTitle().toHtmlEscaped(), song_->artist().toHtmlEscaped(), song_->album().toHtmlEscaped());
label_playing_top_->setText(html);
html = "";
html += QString("Filetype: %1<br />\n").arg(song_->TextForFiletype());
html += QString("Length: %1<br />\n").arg(Utilities::PrettyTimeNanosec(song.length_nanosec()));
html += QString("Bitrate: %1 kbps<br />\n").arg(song_->bitrate());
html += QString("Samplerate: %1 hz / %2 bit<br />\n").arg(song_->samplerate()).arg(song_->bitdepth());
if (enginetype != Engine::EngineType::None) {
html += QString("<br />");
html += QString("Engine: %1<br />").arg(EngineName);
}
html += QString("<br />");
html_albums += QString("<b>Albums by %1:</b>").arg( song_->artist().toHtmlEscaped() );
albumlist = app_->collection_backend()->GetAlbumsByArtist(song_->artist(), opt);
html_albums += QString("<ul>");
int i=0;
for (CollectionBackend::Album album : albumlist) {
i++;
html_albums += QString("<li>%1</li>\n").arg(album.album_name.toHtmlEscaped());
}
html_albums += QString("</ul>");
html_albums += QString("");
if (i > 1) html += html_albums;
label_playing_text_->setText(html);
}
void StatusView::NoSong() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QString html;
QImage image_logo(":/icons/full/strawberry.png");
QImage image_logo_scaled = image_logo.scaled(300, 300, Qt::KeepAspectRatio);
QPixmap pixmap_logo(QPixmap::fromImage(image_logo_scaled));
SwitchWidgets(Stopped);
label_stopped_top_->setText("<b>No Track Playing</b>");
label_stopped_logo_->setPixmap(pixmap_logo);
html += QString(
"<html>\n"
"<head>\n"
"<style type=\"text/css\">:/style/statusview.css</style>\n"
"</head>\n"
"<body>\n"
"%1 songs<br />\n"
"%2 artists<br />\n"
"%3 albums<br />\n"
"</body>\n"
"</html>\n"
)
.arg(collectionview_->TotalSongs())
.arg(collectionview_->TotalArtists())
.arg(collectionview_->TotalAlbums())
;
label_stopped_text_->setText(html);
}
void StatusView::SongChanged(const Song &song) {
//qLog(Debug) << __PRETTY_FUNCTION__;
stopped_ = false;
metadata_ = song;
const Song *song_ = &song;
UpdateSong(*song_);
update();
}
void StatusView::SongFinished() {
//qLog(Debug) << __PRETTY_FUNCTION__;
stopped_ = true;
SetImage(image_blank_);
}
bool StatusView::eventFilter(QObject *object, QEvent *event) {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch(event->type()) {
case QEvent::Paint:{
handlePaintEvent(object, event);
}
default:{
return QObject::eventFilter(object, event);
}
}
return(true);
}
void StatusView::handlePaintEvent(QObject *object, QEvent *event) {
//qLog(Debug) << __PRETTY_FUNCTION__ << object->objectName();
if (object == label_playing_album_) {
paintEvent_album(event);
}
return;
}
void StatusView::paintEvent_album(QEvent *event) {
//qLog(Debug) << __PRETTY_FUNCTION__;
QPainter p(label_playing_album_);
DrawImage(&p);
// Draw the previous track's image if we're fading
if (!pixmap_previous_.isNull()) {
p.setOpacity(pixmap_previous_opacity_);
p.drawPixmap(0, 0, pixmap_previous_);
}
}
void StatusView::DrawImage(QPainter *p) {
//qLog(Debug) << __PRETTY_FUNCTION__;
p->drawPixmap(0, 0, 300, 300, pixmap_current_);
if ((downloading_covers_) && (spinner_animation_ != nullptr)) {
p->drawPixmap(50, 50, 16, 16, spinner_animation_->currentPixmap());
}
}
void StatusView::FadePreviousTrack(qreal value) {
//qLog(Debug) << __PRETTY_FUNCTION__;
pixmap_previous_opacity_ = value;
if (qFuzzyCompare(pixmap_previous_opacity_, qreal(0.0))) {
pixmap_previous_ = QPixmap();
}
update();
if ((value == 0) && (stopped_ == true)) {
SwitchWidgets(Stopped);
NoSong();
}
}
void StatusView::contextMenuEvent(QContextMenuEvent *e) {
// show the menu
menu_->popup(mapToGlobal(e->pos()));
}
void StatusView::mouseReleaseEvent(QMouseEvent *) {
//qLog(Debug) << __PRETTY_FUNCTION__;
}
void StatusView::dragEnterEvent(QDragEnterEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
QWidget::dragEnterEvent(e);
}
void StatusView::dropEvent(QDropEvent *e) {
//qLog(Debug) << __PRETTY_FUNCTION__;
QWidget::dropEvent(e);
}
void StatusView::ScaleCover() {
//qLog(Debug) << __PRETTY_FUNCTION__;
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
update();
}
void StatusView::AlbumArtLoaded(const Song &metadata, const QString&, const QImage &image) {
//qLog(Debug) << __PRETTY_FUNCTION__;
SwitchWidgets(Playing);
label_playing_album_->clear();
metadata_ = metadata;
downloading_covers_ = false;
SetImage(image);
// Search for cover automatically?
GetCoverAutomatically();
}
void StatusView::SetImage(const QImage &image) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Cache the current pixmap so we can fade between them
pixmap_previous_ = QPixmap(size());
pixmap_previous_.fill(palette().background().color());
pixmap_previous_opacity_ = 1.0;
QPainter p(&pixmap_previous_);
DrawImage(&p);
p.end();
original_ = image;
ScaleCover();
// Were we waiting for this cover to load before we started fading?
if (!pixmap_previous_.isNull() && fade_animation_ != nullptr) {
fade_animation_->start();
}
}
bool StatusView::GetCoverAutomatically() {
//qLog(Debug) << __PRETTY_FUNCTION__;
SwitchWidgets(Playing);
// Search for cover automatically?
bool search =
!metadata_.has_manually_unset_cover() &&
metadata_.art_automatic().isEmpty() &&
metadata_.art_manual().isEmpty() &&
!metadata_.artist().isEmpty() &&
!metadata_.album().isEmpty();
if (search) {
qLog(Debug) << "GetCoverAutomatically";
downloading_covers_ = true;
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
// Show a spinner animation
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
spinner_animation_->start();
update();
}
return search;
}
void StatusView::AutomaticCoverSearchDone() {
//qLog(Debug) << __PRETTY_FUNCTION__;
downloading_covers_ = false;
spinner_animation_.reset();
update();
}
void StatusView::UpdateNoSong() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (widgetstate_ == Playing) return;
NoSong();
}
void StatusView::LoadCoverFromFile() {
album_cover_choice_controller_->LoadCoverFromFile(&metadata_);
}
void StatusView::LoadCoverFromURL() {
album_cover_choice_controller_->LoadCoverFromURL(&metadata_);
}
void StatusView::SearchForCover() {
album_cover_choice_controller_->SearchForCover(&metadata_);
}
void StatusView::SaveCoverToFile() {
album_cover_choice_controller_->SaveCoverToFile(metadata_, original_);
}
void StatusView::UnsetCover() {
album_cover_choice_controller_->UnsetCover(&metadata_);
}
void StatusView::ShowCover() {
album_cover_choice_controller_->ShowCover(metadata_);
}
void StatusView::SearchCoverAutomatically() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
// Search for cover automatically?
GetCoverAutomatically();
}

173
src/widgets/statusview.h Normal file
View File

@@ -0,0 +1,173 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STATUSVIEW_H
#define STATUSVIEW_H
#include "config.h"
#include <QApplication>
#include "core/song.h"
#include "core/database.h"
#include "core/application.h"
#include "collection/collectionbackend.h"
#include "collection/collectionview.h"
#include "collection/collectionviewcontainer.h"
#include "covermanager/albumcoverloaderoptions.h"
class QVBoxLayout;
class QHBoxLayout;
class QScrollArea;
class QWidget;
class QTimeLine;
class QLabel;
class QImage;
class QPixmap;
class QPainter;
class Application;
class CollectionView;
class CollectionBackend;
class StatusView : public QWidget {
Q_OBJECT
public:
StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent = nullptr);
~StatusView();
static const char* kSettingsGroup;
static const int kPadding;
static const int kGradientHead;
static const int kGradientTail;
static const int kMaxCoverSize;
static const int kBottomOffset;
static const int kTopBorder;
void SetApplication(Application *app);
public slots:
void SongChanged(const Song &song);
void SongFinished();
void AlbumArtLoaded(const Song& metadata, const QString &uri, const QImage &image);
void FadePreviousTrack(qreal value);
void LoadCoverFromFile();
void SaveCoverToFile();
void LoadCoverFromURL();
void SearchForCover();
void UnsetCover();
void ShowCover();
void SearchCoverAutomatically();
void AutomaticCoverSearchDone();
private:
QVBoxLayout *layout_;
QScrollArea *scrollarea_;
QVBoxLayout *container_layout_;
QWidget *container_widget_;
QWidget *widget_stopped_;
QWidget *widget_playing_;
QVBoxLayout *layout_playing_;
QVBoxLayout *layout_stopped_;
QLabel *label_stopped_top_;
QLabel *label_stopped_logo_;
QLabel *label_stopped_text_;
QLabel *label_playing_top_;
QLabel *label_playing_album_;
QLabel *label_playing_text_;
QPixmap *pixmap_album_;
QPainter *painter_album_;
Song song_;
CollectionView *collectionview_;
AlbumCoverLoaderOptions cover_loader_options_;
QImage original_;
void CreateWidget();
void NoSongWidget();
void SongWidget();
void AddActions();
void SetImage(const QImage &image);
void DrawImage(QPainter *p);
void ScaleCover();
bool GetCoverAutomatically();
Application *app_;
AlbumCoverChoiceController *album_cover_choice_controller_;
QAction *fit_cover_width_action_;
bool visible_;
int small_ideal_height_;
int total_height_;
bool fit_width_;
QTimeLine *show_hide_animation_;
QTimeLine *fade_animation_;
QImage image_blank_;
QImage image_nosong_;
// Information about the current track
Song metadata_;
QPixmap pixmap_current_;
// Holds the last track while we're fading to the new track
QPixmap pixmap_previous_;
qreal pixmap_previous_opacity_;
std::unique_ptr<QMovie> spinner_animation_;
bool downloading_covers_;
bool stopped_;
bool playing_;
enum WidgetState {
None = 0,
Playing,
Stopped
};
WidgetState widgetstate_;
QMenu* menu_;
protected:
bool eventFilter(QObject *, QEvent *);
void handlePaintEvent(QObject *object, QEvent *event);
void paintEvent_album(QEvent *event);
void contextMenuEvent(QContextMenuEvent *e);
void mouseReleaseEvent(QMouseEvent *);
void dragEnterEvent(QDragEnterEvent *e);
void dropEvent(QDropEvent *e);
void UpdateSong(const Song &song);
void NoSong();
void SwitchWidgets(WidgetState state);
private slots:
void UpdateNoSong();
};
#endif // STATUSVIEW_H

View File

@@ -0,0 +1,41 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "stickyslider.h"
StickySlider::StickySlider(QWidget *parent)
: QSlider(parent),
sticky_center_(-1),
sticky_threshold_(10)
{
}
void StickySlider::mouseMoveEvent(QMouseEvent *e) {
QSlider::mouseMoveEvent(e);
if (sticky_center_ == -1) return;
const int v = sliderPosition();
if (v <= sticky_center_ + sticky_threshold_ && v >= sticky_center_ - sticky_threshold_) setSliderPosition(sticky_center_);
}

View File

@@ -0,0 +1,49 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STICKYSLIDER_H
#define STICKYSLIDER_H
#include "config.h"
#include <QSlider>
class StickySlider : public QSlider {
Q_OBJECT
Q_PROPERTY(int sticky_center READ sticky_center WRITE set_sticky_center);
Q_PROPERTY(int sticky_threshold READ sticky_threshold WRITE set_sticky_threshold);
public:
StickySlider(QWidget* parent = nullptr);
int sticky_center() const { return sticky_center_; }
int sticky_threshold() const { return sticky_threshold_; }
void set_sticky_center(int center) { sticky_center_ = center; }
void set_sticky_threshold(int threshold) { sticky_threshold_ = threshold; }
protected:
void mouseMoveEvent(QMouseEvent* e);
private:
int sticky_center_;
int sticky_threshold_;
};
#endif // STICKYSLIDER_H

View File

@@ -0,0 +1,320 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "stretchheaderview.h"
#include "core/logging.h"
#include <QDataStream>
#include <algorithm>
#include <cmath>
#include <numeric>
const int StretchHeaderView::kMinimumColumnWidth = 10;
const int StretchHeaderView::kMagicNumber = 0x502c950f;
StretchHeaderView::StretchHeaderView(Qt::Orientation orientation, QWidget* parent)
: QHeaderView(orientation, parent),
stretch_enabled_(false),
in_mouse_move_event_(false)
{
connect(this, SIGNAL(sectionResized(int,int,int)), SLOT(SectionResized(int,int,int)));
}
void StretchHeaderView::setModel(QAbstractItemModel* model) {
QHeaderView::setModel(model);
if (stretch_enabled_) {
column_widths_.resize(count());
std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count());
}
}
void StretchHeaderView::NormaliseWidths(const QList<int>& sections) {
if (!stretch_enabled_) return;
const ColumnWidthType total_sum = std::accumulate(column_widths_.begin(), column_widths_.end(), 0.0);
ColumnWidthType selected_sum = total_sum;
if (!sections.isEmpty()) {
selected_sum = 0.0;
for (int i=0 ; i<count() ; ++i)
if (sections.contains(i))
selected_sum += column_widths_[i];
}
if (total_sum != 0.0 && !qFuzzyCompare(total_sum, 1.0)) {
const ColumnWidthType mult = (selected_sum + (1.0 - total_sum)) / selected_sum;
for (int i=0 ; i<column_widths_.count() ; ++i) {
if (sections.isEmpty() || sections.contains(i))
column_widths_[i] *= mult;
}
}
}
void StretchHeaderView::UpdateWidths(const QList<int>& sections) {
if (!stretch_enabled_)
return;
ColumnWidthType total_w = 0.0;
for (int i = 0; i < column_widths_.count(); ++i) {
const ColumnWidthType w = column_widths_[i];
int pixels = w * width();
if (pixels != 0 && total_w - int(total_w) > 0.5)
pixels ++;
total_w += w;
if (!sections.isEmpty() && !sections.contains(i))
continue;
if (pixels == 0 && !isSectionHidden(i))
hideSection(i);
else if (pixels != 0 && isSectionHidden(i)) {
showSection(i);
}
if (pixels != 0)
resizeSection(i, pixels);
}
}
void StretchHeaderView::HideSection(int logical) {
// Would this hide the last section?
bool all_hidden = true;
for (int i=0 ; i<count() ; ++i) {
if (i != logical && !isSectionHidden(i) && sectionSize(i) > 0) {
all_hidden = false;
break;
}
}
if (all_hidden) {
return;
}
if (!stretch_enabled_) {
hideSection(logical);
return;
}
column_widths_[logical] = 0.0;
NormaliseWidths();
UpdateWidths();
}
void StretchHeaderView::ShowSection(int logical) {
if (!stretch_enabled_) {
showSection(logical);
return;
}
// How many sections are visible already?
int visible_count = 0;
for (int i=0 ; i<count() ; ++i) {
if (!isSectionHidden(i))
visible_count ++;
}
column_widths_[logical] =
visible_count == 0 ? 1.0 : 1.0 / visible_count;
NormaliseWidths();
UpdateWidths();
}
void StretchHeaderView::SetSectionHidden(int logical, bool hidden) {
if (hidden) {
HideSection(logical);
}
else {
ShowSection(logical);
}
}
void StretchHeaderView::resizeEvent(QResizeEvent* event) {
QHeaderView::resizeEvent(event);
if (!stretch_enabled_) return;
UpdateWidths();
}
void StretchHeaderView::mouseMoveEvent(QMouseEvent* e) {
in_mouse_move_event_ = true;
QHeaderView::mouseMoveEvent(e);
in_mouse_move_event_ = false;
}
void StretchHeaderView::SectionResized(int logical, int, int new_size) {
if (!stretch_enabled_) return;
if (in_mouse_move_event_) {
// Update this section's proportional width
column_widths_[logical] = ColumnWidthType(new_size) / width();
// Find the visible sections to the right of the section that's being resized
int visual = visualIndex(logical);
QList<int> logical_sections_to_resize;
for (int i = 0; i < count(); ++i) {
if (!isSectionHidden(i) && visualIndex(i) > visual)
logical_sections_to_resize << i;
}
// Resize just those columns
if (!logical_sections_to_resize.isEmpty()) {
in_mouse_move_event_ = false;
UpdateWidths(logical_sections_to_resize);
NormaliseWidths(logical_sections_to_resize);
in_mouse_move_event_ = true;
}
}
}
void StretchHeaderView::ToggleStretchEnabled() {
SetStretchEnabled(!is_stretch_enabled());
}
void StretchHeaderView::SetStretchEnabled(bool enabled) {
stretch_enabled_ = enabled;
if (enabled) {
// Initialise the list of widths from the current state of the widget
column_widths_.resize(count());
for (int i = 0; i < count(); ++i) {
column_widths_[i] = ColumnWidthType(sectionSize(i)) / width();
}
// Stretch the columns to fill the widget
NormaliseWidths();
UpdateWidths();
}
emit StretchEnabledChanged(enabled);
}
void StretchHeaderView::SetColumnWidth(int logical, ColumnWidthType width) {
if (!stretch_enabled_) return;
column_widths_[logical] = width;
QList<int> other_columns;
for (int i=0 ; i<count() ; ++i)
if (!isSectionHidden(i) && i != logical)
other_columns << i;
NormaliseWidths(other_columns);
}
bool StretchHeaderView::RestoreState(const QByteArray& data) {
QDataStream s(data);
s.setVersion(QDataStream::Qt_4_6);
int magic_number = 0;
s >> magic_number;
if (magic_number != kMagicNumber || s.atEnd()) {
return false;
}
QList<int> pixel_widths;
QList<int> visual_indices;
int sort_indicator_order = Qt::AscendingOrder;
int sort_indicator_section = 0;
s >> stretch_enabled_;
s >> pixel_widths;
s >> visual_indices;
s >> column_widths_;
s >> sort_indicator_order;
s >> sort_indicator_section;
setSortIndicator(sort_indicator_section, Qt::SortOrder(sort_indicator_order));
const int persisted_column_count = qMin(qMin(visual_indices.count(), pixel_widths.count()), column_widths_.count());
// Set column visible state, visual indices and, if we're not in stretch mode,
// pixel widths.
for (int i = 0; i < count() && i < persisted_column_count; ++i) {
setSectionHidden(i, pixel_widths[i] <= kMinimumColumnWidth);
moveSection(visualIndex(visual_indices[i]), i);
if (!stretch_enabled_) {
resizeSection(i, pixel_widths[i]);
}
}
// Have we added more columns since the last time?
while (column_widths_.count() < count()) {
column_widths_ << 0;
}
if (stretch_enabled_) {
// In stretch mode, we've already set the proportional column widths so apply
// them now.
UpdateWidths();
}
emit StretchEnabledChanged(stretch_enabled_);
return true;
}
QByteArray StretchHeaderView::SaveState() const {
QByteArray ret;
QDataStream s(&ret, QIODevice::WriteOnly);
QList<int> pixel_widths;
QList<int> visual_indices;
for (int i = 0; i < count(); ++i) {
pixel_widths << sectionSize(i);
visual_indices << logicalIndex(i);
}
s.setVersion(QDataStream::Qt_4_6);
s << kMagicNumber;
s << stretch_enabled_;
s << pixel_widths;
s << visual_indices;
s << column_widths_;
s << int(sortIndicatorOrder());
s << sortIndicatorSection();
return ret;
}

View File

@@ -0,0 +1,96 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STRETCHHEADERVIEW_H
#define STRETCHHEADERVIEW_H
#include "config.h"
#include <QHeaderView>
class StretchHeaderView : public QHeaderView {
Q_OBJECT
public:
StretchHeaderView(Qt::Orientation orientation, QWidget* parent = nullptr);
typedef double ColumnWidthType;
static const int kMinimumColumnWidth;
static const int kMagicNumber;
void setModel(QAbstractItemModel* model);
// Serialises the proportional and actual column widths. Use these instead
// of QHeaderView::restoreState and QHeaderView::saveState to persist the
// proportional values directly and avoid floating point errors over time.
bool RestoreState(const QByteArray& data);
QByteArray SaveState() const;
// Hides a section and resizes all other sections to fill the gap. Does
// nothing if you try to hide the last section.
void HideSection(int logical);
// Shows a section and resizes all other sections to make room.
void ShowSection(int logical);
// Calls either HideSection or ShowSection.
void SetSectionHidden(int logical, bool hidden);
// Sets the width of the given column and resizes other columns appropriately.
// width is the proportion of the entire width from 0.0 to 1.0.
void SetColumnWidth(int logical, ColumnWidthType width);
bool is_stretch_enabled() const { return stretch_enabled_; }
public slots:
// Changes the stretch mode. Enabling stretch mode will initialise the
// proportional column widths from the current state of the header.
void ToggleStretchEnabled();
void SetStretchEnabled(bool enabled);
signals:
// Emitted when the stretch mode is changed.
void StretchEnabledChanged(bool enabled);
protected:
// QWidget
void mouseMoveEvent(QMouseEvent* e);
void resizeEvent(QResizeEvent* event);
private:
// Scales column_widths_ values so the total is 1.0.
void NormaliseWidths(const QList<int>& sections = QList<int>());
// Resizes the actual columns to make them match the proportional values
// in column_widths_.
void UpdateWidths(const QList<int>& sections = QList<int>());
private slots:
void SectionResized(int logical, int old_size, int new_size);
private:
bool stretch_enabled_;
QVector<ColumnWidthType> column_widths_;
bool in_mouse_move_event_;
};
#endif // STRETCHHEADERVIEW_H

242
src/widgets/stylehelper.cpp Normal file
View File

@@ -0,0 +1,242 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "stylehelper.h"
#include <QtGui/QPixmapCache>
#include <QtGui/QWidget>
#include <QtCore/QRect>
#include <QtGui/QPainter>
#include <QtGui/QApplication>
#include <QtGui/QPalette>
#include <QtGui/QStyleOption>
#include <QtCore/QObject>
// Clamps float color values within (0, 255)
static int clamp(float x)
{
const int val = x > 255 ? 255 : static_cast<int>(x);
return val < 0 ? 0 : val;
}
namespace Utils {
qreal StyleHelper::sidebarFontSize()
{
#if defined(Q_WS_MAC)
return 10;
#else
return 7.5;
#endif
}
QColor StyleHelper::panelTextColor(bool lightColored)
{
if (!lightColored)
return Qt::white;
else
return Qt::black;
}
// Invalid by default, setBaseColor needs to be called at least once
QColor StyleHelper::m_baseColor;
QColor StyleHelper::m_requestedBaseColor;
QColor StyleHelper::baseColor(bool lightColored)
{
if (!lightColored)
return m_baseColor;
else
return m_baseColor.lighter(230);
}
QColor StyleHelper::highlightColor(bool lightColored)
{
QColor result = baseColor(lightColored);
if (!lightColored)
result.setHsv(result.hue(),
clamp(result.saturation()),
clamp(result.value() * 1.16));
else
result.setHsv(result.hue(),
clamp(result.saturation()),
clamp(result.value() * 1.06));
return result;
}
QColor StyleHelper::shadowColor(bool lightColored)
{
QColor result = baseColor(lightColored);
result.setHsv(result.hue(),
clamp(result.saturation() * 1.1),
clamp(result.value() * 0.70));
return result;
}
QColor StyleHelper::borderColor(bool lightColored)
{
QColor result = baseColor(lightColored);
result.setHsv(result.hue(),
result.saturation(),
result.value() / 2);
return result;
}
// We try to ensure that the actual color used are within
// reasonalbe bounds while generating the actual baseColor
// from the users request.
void StyleHelper::setBaseColor(const QColor &newcolor)
{
m_requestedBaseColor = newcolor;
QColor color;
color.setHsv(newcolor.hue(),
newcolor.saturation() * 0.7,
64 + newcolor.value() / 3);
if (color.isValid() && color != m_baseColor) {
m_baseColor = color;
for (QWidget* w : QApplication::topLevelWidgets()) {
w->update();
}
}
}
static void verticalGradientHelper(QPainter *p, const QRect &spanRect, const QRect &rect, bool lightColored)
{
QColor highlight = StyleHelper::highlightColor(lightColored);
QColor shadow = StyleHelper::shadowColor(lightColored);
QLinearGradient grad(spanRect.topRight(), spanRect.topLeft());
grad.setColorAt(0, highlight.lighter(117));
grad.setColorAt(1, shadow.darker(109));
p->fillRect(rect, grad);
QColor light(255, 255, 255, 80);
p->setPen(light);
p->drawLine(rect.topRight() - QPoint(1, 0), rect.bottomRight() - QPoint(1, 0));
QColor dark(0, 0, 0, 90);
p->setPen(dark);
p->drawLine(rect.topLeft(), rect.bottomLeft());
}
void StyleHelper::verticalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored)
{
if (StyleHelper::usePixmapCache()) {
QString key;
QColor keyColor = baseColor(lightColored);
key.sprintf("mh_vertical %d %d %d %d %d",
spanRect.width(), spanRect.height(), clipRect.width(),
clipRect.height(), keyColor.rgb());;
QPixmap pixmap;
if (!QPixmapCache::find(key, pixmap)) {
pixmap = QPixmap(clipRect.size());
QPainter p(&pixmap);
QRect rect(0, 0, clipRect.width(), clipRect.height());
verticalGradientHelper(&p, spanRect, rect, lightColored);
p.end();
QPixmapCache::insert(key, pixmap);
}
painter->drawPixmap(clipRect.topLeft(), pixmap);
} else {
verticalGradientHelper(painter, spanRect, clipRect, lightColored);
}
}
// Draws a cached pixmap with shadow
void StyleHelper::drawIconWithShadow(const QIcon &icon, const QRect &rect,
QPainter *p, QIcon::Mode iconMode, int radius, const QColor &color, const QPoint &offset)
{
QPixmap cache;
QString pixmapName = QString("icon %0 %1 %2").arg(icon.cacheKey()).arg(iconMode).arg(rect.height());
if (!QPixmapCache::find(pixmapName, cache)) {
QPixmap px = icon.pixmap(rect.size());
cache = QPixmap(px.size() + QSize(radius * 2, radius * 2));
cache.fill(Qt::transparent);
QPainter cachePainter(&cache);
if (iconMode == QIcon::Disabled) {
QImage im = px.toImage().convertToFormat(QImage::Format_ARGB32);
for (int y=0; y<im.height(); ++y) {
QRgb *scanLine = (QRgb*)im.scanLine(y);
for (int x=0; x<im.width(); ++x) {
QRgb pixel = *scanLine;
char intensity = qGray(pixel);
*scanLine = qRgba(intensity, intensity, intensity, qAlpha(pixel));
++scanLine;
}
}
px = QPixmap::fromImage(im);
}
// Draw shadow
QImage tmp(px.size() + QSize(radius * 2, radius * 2 + 1), QImage::Format_ARGB32_Premultiplied);
tmp.fill(Qt::transparent);
QPainter tmpPainter(&tmp);
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
tmpPainter.drawPixmap(QPoint(radius, radius), px);
tmpPainter.end();
// blur the alpha channel
QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
blurred.fill(Qt::transparent);
QPainter blurPainter(&blurred);
qt_blurImage(&blurPainter, tmp, radius, false, true);
blurPainter.end();
tmp = blurred;
// blacken the image...
tmpPainter.begin(&tmp);
tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
tmpPainter.fillRect(tmp.rect(), color);
tmpPainter.end();
tmpPainter.begin(&tmp);
tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
tmpPainter.fillRect(tmp.rect(), color);
tmpPainter.end();
// draw the blurred drop shadow...
cachePainter.drawImage(QRect(0, 0, cache.rect().width(), cache.rect().height()), tmp);
// Draw the actual pixmap...
cachePainter.drawPixmap(QPoint(radius, radius) + offset, px);
QPixmapCache::insert(pixmapName, cache);
}
QRect targetRect = cache.rect();
targetRect.moveCenter(rect.center());
p->drawPixmap(targetRect.topLeft() - offset, cache);
}
} // namespace Utils

85
src/widgets/stylehelper.h Normal file
View File

@@ -0,0 +1,85 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef STYLEHELPER_H
#define STYLEHELPER_H
#include <QtGui/QColor>
#include <QtGui/QStyle>
#include "core/qt_blurimage.h"
QT_BEGIN_NAMESPACE
class QPalette;
class QPainter;
class QRect;
QT_END_NAMESPACE
// Helper class holding all custom color values
namespace Utils {
class StyleHelper
{
public:
static const unsigned int DEFAULT_BASE_COLOR = 0x666666;
// Height of the project explorer navigation bar
static qreal sidebarFontSize();
// This is our color table, all colors derive from baseColor
static QColor requestedBaseColor() { return m_requestedBaseColor; }
static QColor baseColor(bool lightColored = false);
static QColor panelTextColor(bool lightColored = false);
static QColor highlightColor(bool lightColored = false);
static QColor shadowColor(bool lightColored = false);
static QColor borderColor(bool lightColored = false);
static QColor sidebarHighlight() { return QColor(255, 255, 255, 40); }
static QColor sidebarShadow() { return QColor(0, 0, 0, 40); }
// Sets the base color and makes sure all top level widgets are updated
static void setBaseColor(const QColor &color);
// Gradients used for panels
static void verticalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false);
static bool usePixmapCache() { return true; }
static void drawIconWithShadow(const QIcon &icon, const QRect &rect, QPainter *p, QIcon::Mode iconMode,
int radius = 3, const QColor &color = QColor(0, 0, 0, 130),
const QPoint &offset = QPoint(1, -2));
private:
static QColor m_baseColor;
static QColor m_requestedBaseColor;
};
} // namespace Utils
using Utils::StyleHelper;
#endif // STYLEHELPER_H

178
src/widgets/trackslider.cpp Normal file
View File

@@ -0,0 +1,178 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "trackslider.h"
#include "ui_trackslider.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include <QSettings>
const char* TrackSlider::kSettingsGroup = "MainWindow";
TrackSlider::TrackSlider(QWidget* parent)
: QWidget(parent),
ui_(new Ui_TrackSlider),
setting_value_(false),
show_remaining_time_(true),
slider_maximum_value_(0)
{
ui_->setupUi(this);
UpdateLabelWidth();
// Load settings
QSettings s;
s.beginGroup(kSettingsGroup);
show_remaining_time_ = s.value("show_remaining_time").toBool();
connect(ui_->slider, SIGNAL(sliderMoved(int)), SIGNAL(ValueChanged(int)));
connect(ui_->slider, SIGNAL(valueChanged(int)), SLOT(ValueMaybeChanged(int)));
connect(ui_->remaining, SIGNAL(Clicked()), SLOT(ToggleTimeDisplay()));
}
TrackSlider::~TrackSlider() {
delete ui_;
}
void TrackSlider::SetApplication(Application* app) {
}
void TrackSlider::UpdateLabelWidth() {
// We set the label's minimum size so it won't resize itself when the user
// is dragging the slider.
UpdateLabelWidth(ui_->elapsed, "0:00:00");
UpdateLabelWidth(ui_->remaining, "-0:00:00");
}
void TrackSlider::UpdateLabelWidth(QLabel* label, const QString& text) {
QString old_text = label->text();
label->setText(text);
label->setMinimumWidth(0);
int width = label->sizeHint().width();
label->setText(old_text);
label->setMinimumWidth(width);
}
QSize TrackSlider::sizeHint() const {
int width = 500;
width += ui_->elapsed->sizeHint().width();
width += ui_->remaining->sizeHint().width();
int height = qMax(ui_->slider->sizeHint().height(), ui_->elapsed->sizeHint().height());
return QSize(width, height);
}
void TrackSlider::SetValue(int elapsed, int total) {
setting_value_ = true; // This is so we don't emit from QAbstractSlider::valueChanged
ui_->slider->setMaximum(total);
ui_->slider->setValue(elapsed);
setting_value_ = false;
UpdateTimes(elapsed / kMsecPerSec);
}
void TrackSlider::UpdateTimes(int elapsed) {
ui_->elapsed->setText(Utilities::PrettyTime(elapsed));
//update normally if showing remaining time
if (show_remaining_time_) {
ui_->remaining->setText("-" + Utilities::PrettyTime((ui_->slider->maximum() / kMsecPerSec) - elapsed));
}
else {
// check if slider maximum value is changed before updating
if (slider_maximum_value_ != ui_->slider->maximum()) {
slider_maximum_value_ = ui_->slider->maximum();
ui_->remaining->setText(Utilities::PrettyTime((ui_->slider->maximum() / kMsecPerSec)));
}
}
setEnabled(true);
}
void TrackSlider::SetStopped() {
setEnabled(false);
ui_->elapsed->setText("0:00:00");
ui_->remaining->setText("0:00:00");
setting_value_ = true;
ui_->slider->setValue(0);
slider_maximum_value_ = 0;
setting_value_ = false;
}
void TrackSlider::SetCanSeek(bool can_seek) {
ui_->slider->setEnabled(can_seek);
}
void TrackSlider::Seek(int gap) {
if (ui_->slider->isEnabled())
ui_->slider->setValue(ui_->slider->value() + gap * kMsecPerSec);
}
void TrackSlider::ValueMaybeChanged(int value) {
if (setting_value_) return;
UpdateTimes(value / kMsecPerSec);
emit ValueChangedSeconds(value / kMsecPerSec);
}
bool TrackSlider::event(QEvent* e) {
switch (e->type()) {
case QEvent::ApplicationFontChange:
case QEvent::StyleChange:
UpdateLabelWidth();
break;
default:
break;
}
return false;
}
void TrackSlider::ToggleTimeDisplay() {
show_remaining_time_ = !show_remaining_time_;
if (!show_remaining_time_) {
//we set the value to -1 because the label must be updated
slider_maximum_value_ = -1;
}
UpdateTimes(ui_->slider->value() / kMsecPerSec);
// save this setting
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("show_remaining_time", show_remaining_time_);
}

77
src/widgets/trackslider.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TRACKSLIDER_H
#define TRACKSLIDER_H
#include "config.h"
#include <QWidget>
class QLabel;
class Application;
class Ui_TrackSlider;
class TrackSlider : public QWidget {
Q_OBJECT
public:
TrackSlider(QWidget* parent = nullptr);
~TrackSlider();
void SetApplication(Application* app);
// QWidget
QSize sizeHint() const;
// QObject
bool event(QEvent *);
static const char* kSettingsGroup;
public slots:
void SetValue(int elapsed, int total);
void SetStopped();
void SetCanSeek(bool can_seek);
void Seek(int gap);
signals:
void ValueChanged(int value);
void ValueChangedSeconds(int value);
private slots:
void ValueMaybeChanged(int value);
void ToggleTimeDisplay();
private:
void UpdateTimes(int elapsed);
void UpdateLabelWidth();
void UpdateLabelWidth(QLabel* label, const QString& text);
private:
Ui_TrackSlider* ui_;
bool setting_value_;
bool show_remaining_time_;
int slider_maximum_value_; //we cache it to avoid unnecessary updates
};
#endif // TRACKSLIDER_H

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TrackSlider</class>
<widget class="QWidget" name="TrackSlider">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>517</width>
<height>33</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="elapsed">
<property name="text">
<string>0:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="TrackSliderSlider" name="slider">
<property name="singleStep">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="ClickableLabel" name="remaining">
<property name="toolTip">
<string>Click to toggle between remaining time and total time</string>
</property>
<property name="text">
<string>0:00:00</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TrackSliderSlider</class>
<extends>QSlider</extends>
<header>widgets/tracksliderslider.h</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>widgets/clickablelabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,174 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "tracksliderpopup.h"
#include "core/qt_blurimage.h"
#include <QBitmap>
#include <QCoreApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QTimer>
#include <QWheelEvent>
#include <QtDebug>
const int TrackSliderPopup::kTextMargin = 4;
const int TrackSliderPopup::kPointLength = 16;
const int TrackSliderPopup::kPointWidth = 4;
const int TrackSliderPopup::kBorderRadius = 4;
const qreal TrackSliderPopup::kBlurRadius = 20.0;
TrackSliderPopup::TrackSliderPopup(QWidget* parent)
: QWidget(parent),
font_metrics_(fontMetrics()),
small_font_metrics_(fontMetrics())
{
setAttribute(Qt::WA_TransparentForMouseEvents);
setMouseTracking(true);
font_.setPointSizeF(7.5);
font_.setBold(true);
small_font_.setPointSizeF(7.5);
font_metrics_ = QFontMetrics(font_);
small_font_metrics_ = QFontMetrics(small_font_);
}
void TrackSliderPopup::SetText(const QString& text) {
text_ = text;
UpdatePixmap();
}
void TrackSliderPopup::SetSmallText(const QString& text) {
small_text_ = text;
UpdatePixmap();
}
void TrackSliderPopup::SetPopupPosition(const QPoint& pos) {
pos_ = pos;
UpdatePosition();
}
void TrackSliderPopup::paintEvent(QPaintEvent*) {
QPainter p(this);
p.drawPixmap(0, 0, pixmap_);
}
void TrackSliderPopup::UpdatePixmap() {
const int text_width = qMax(font_metrics_.width(text_), small_font_metrics_.width(small_text_));
const QRect text_rect1(kBlurRadius + kTextMargin, kBlurRadius + kTextMargin, text_width + 2, font_metrics_.height());
const QRect text_rect2(kBlurRadius + kTextMargin, text_rect1.bottom(), text_width, small_font_metrics_.height());
const int bubble_bottom = text_rect2.bottom() + kTextMargin;
const QRect total_rect(0, 0, text_rect1.right() + kBlurRadius + kTextMargin, kBlurRadius + bubble_bottom + kPointLength);
const QRect bubble_rect(kBlurRadius, kBlurRadius, total_rect.width() - kBlurRadius * 2, bubble_bottom - kBlurRadius);
if (background_cache_.size() != total_rect.size()) {
const QColor highlight(palette().color(QPalette::Active, QPalette::Highlight));
const QColor bg_color_1(highlight.lighter(110));
const QColor bg_color_2(highlight.darker(120));
QPolygon pointy;
pointy << QPoint(total_rect.width()/2 - kPointWidth, bubble_bottom)
<< QPoint(total_rect.width()/2, total_rect.bottom() - kBlurRadius)
<< QPoint(total_rect.width()/2 + kPointWidth, bubble_bottom);
QPolygon inner_pointy;
inner_pointy << QPoint(pointy[0].x() + 1, pointy[0].y() - 1)
<< QPoint(pointy[1].x(), pointy[1].y() - 1)
<< QPoint(pointy[2].x() - 1, pointy[2].y() - 1);
background_cache_ = QPixmap(total_rect.size());
background_cache_.fill(Qt::transparent);
QPainter p(&background_cache_);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::HighQualityAntialiasing);
// Draw the shadow to a different image
QImage blur_source(total_rect.size(), QImage::Format_ARGB32);
blur_source.fill(Qt::transparent);
QPainter blur_painter(&blur_source);
blur_painter.setRenderHint(QPainter::Antialiasing);
blur_painter.setRenderHint(QPainter::HighQualityAntialiasing);
blur_painter.setBrush(bg_color_2);
blur_painter.drawRoundedRect(bubble_rect, kBorderRadius, kBorderRadius);
blur_painter.drawPolygon(pointy);
// Fade the shadow out towards the bottom
QLinearGradient fade_gradient(QPoint(0, bubble_bottom), QPoint(0, bubble_bottom + kPointLength));
fade_gradient.setColorAt(0.0, QColor(255, 0, 0, 0));
fade_gradient.setColorAt(1.0, QColor(255, 0, 0, 255));
blur_painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
blur_painter.fillRect(total_rect, fade_gradient);
blur_painter.end();
p.save();
qt_blurImage(&p, blur_source, kBlurRadius, true, false);
p.restore();
// Outer bubble
p.setPen(Qt::NoPen);
p.setBrush(bg_color_2);
p.drawRoundedRect(bubble_rect, kBorderRadius, kBorderRadius);
// Outer pointy
p.drawPolygon(pointy);
// Inner bubble
p.setBrush(bg_color_1);
p.drawRoundedRect(bubble_rect.adjusted(1, 1, -1, -1), kBorderRadius, kBorderRadius);
// Inner pointy
p.drawPolygon(inner_pointy);
}
pixmap_ = QPixmap(total_rect.size());
pixmap_.fill(Qt::transparent);
QPainter p(&pixmap_);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::HighQualityAntialiasing);
// Background
p.drawPixmap(total_rect.topLeft(), background_cache_);
// Text
p.setPen(palette().color(QPalette::HighlightedText));
p.setFont(font_);
p.drawText(text_rect1, Qt::AlignHCenter, text_);
p.setFont(small_font_);
p.setOpacity(0.65);
p.drawText(text_rect2, Qt::AlignHCenter, small_text_);
p.end();
resize(pixmap_.size());
UpdatePosition();
update();
}
void TrackSliderPopup::UpdatePosition() {
move(pos_.x() - pixmap_.width() / 2,
pos_.y() - pixmap_.height() + kBlurRadius);
}

View File

@@ -0,0 +1,66 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TRACKSLIDERPOPUP_H
#define TRACKSLIDERPOPUP_H
#include "config.h"
#include <QWidget>
class TrackSliderPopup : public QWidget {
Q_OBJECT
public:
TrackSliderPopup(QWidget* parent);
public slots:
void SetText(const QString& text);
void SetSmallText(const QString& small_text);
void SetPopupPosition(const QPoint& pos);
protected:
void paintEvent(QPaintEvent*);
private:
static const int kTextMargin;
static const int kPointLength;
static const int kPointWidth;
static const int kBorderRadius;
static const qreal kBlurRadius;
void UpdatePixmap();
void UpdatePosition();
void SendMouseEventToParent(QMouseEvent* e);
private:
QString text_;
QString small_text_;
QPoint pos_;
QFont font_;
QFont small_font_;
QFontMetrics font_metrics_;
QFontMetrics small_font_metrics_;
QPixmap pixmap_;
QPixmap background_cache_;
};
#endif // TRACKSLIDERPOPUP_H

View File

@@ -0,0 +1,132 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "tracksliderpopup.h"
#include "tracksliderslider.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include <QMouseEvent>
#include <QStyle>
#include <QStyleOptionSlider>
#include <QtDebug>
#include <QWheelEvent>
TrackSliderSlider::TrackSliderSlider(QWidget* parent)
: QSlider(parent),
popup_(new TrackSliderPopup(window())),
mouse_hover_seconds_(0)
{
setMouseTracking(true);
connect(this, SIGNAL(valueChanged(int)), SLOT(UpdateDeltaTime()));
}
void TrackSliderSlider::mousePressEvent(QMouseEvent* e) {
// QSlider asks QStyle which mouse button should do what (absolute move or
// page step). We force our own behaviour here because it makes more sense
// for a music player IMO.
Qt::MouseButton new_button = e->button();
if (e->button() == Qt::LeftButton) {
int abs_buttons = style()->styleHint(QStyle::SH_Slider_AbsoluteSetButtons);
if (abs_buttons & Qt::LeftButton)
new_button = Qt::LeftButton;
else if (abs_buttons & Qt::MidButton)
new_button = Qt::MidButton;
else if (abs_buttons & Qt::RightButton)
new_button = Qt::RightButton;
}
QMouseEvent new_event(e->type(), e->pos(), new_button, new_button, e->modifiers());
QSlider::mousePressEvent(&new_event);
if (new_event.isAccepted())
e->accept();
}
void TrackSliderSlider::mouseReleaseEvent(QMouseEvent* e) {
QSlider::mouseReleaseEvent(e);
}
void TrackSliderSlider::mouseMoveEvent(QMouseEvent* e) {
QSlider::mouseMoveEvent(e);
// Borrowed from QSliderPrivate::pixelPosToRangeValue
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
int slider_length = sr.width();
int slider_min = gr.x();
int slider_max = gr.right() - slider_length + 1;
mouse_hover_seconds_ = QStyle::sliderValueFromPosition(minimum() / kMsecPerSec, maximum() / kMsecPerSec, e->x() - slider_length / 2 - slider_min + 1, slider_max - slider_min);
popup_->SetText(Utilities::PrettyTime(mouse_hover_seconds_));
UpdateDeltaTime();
popup_->SetPopupPosition(mapTo(window(), QPoint(e->x(), rect().center().y())));
}
void TrackSliderSlider::wheelEvent(QWheelEvent *e) {
if (e->delta() < 0) {
emit SeekBackward();
}
else {
emit SeekForward();
}
e->accept();
}
void TrackSliderSlider::enterEvent(QEvent* e) {
QSlider::enterEvent(e);
if (isEnabled()) {
popup_->show();
}
}
void TrackSliderSlider::leaveEvent(QEvent* e) {
QSlider::leaveEvent(e);
popup_->hide();
}
void TrackSliderSlider::keyPressEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Down) {
emit SeekBackward();
event->accept();
}
else if (event->key() == Qt::Key_Right || event->key() == Qt::Key_Up) {
emit SeekForward();
event->accept();
}
else {
QSlider::keyPressEvent(event);
}
}
void TrackSliderSlider::UpdateDeltaTime() {
if (popup_->isVisible()) {
int delta_seconds = mouse_hover_seconds_ - value();
popup_->SetSmallText(Utilities::PrettyTimeDelta(delta_seconds));
}
}

View File

@@ -0,0 +1,59 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TRACKSLIDERSLIDER_H
#define TRACKSLIDERSLIDER_H
#include "config.h"
#include <QSlider>
class TrackSliderPopup;
// It's the slider inside the TrackSliderSlider
class TrackSliderSlider : public QSlider {
Q_OBJECT
public:
TrackSliderSlider(QWidget* parent = nullptr);
signals:
void SeekForward();
void SeekBackward();
protected:
void mousePressEvent(QMouseEvent* e);
void mouseReleaseEvent(QMouseEvent* e);
void mouseMoveEvent(QMouseEvent* e);
void wheelEvent(QWheelEvent *e);
void enterEvent(QEvent*);
void leaveEvent(QEvent*);
void keyPressEvent(QKeyEvent* event);
private slots:
void UpdateDeltaTime();
private:
TrackSliderPopup* popup_;
int mouse_hover_seconds_;
};
#endif // TRACKSLIDERSLIDER_H

View File

@@ -0,0 +1,168 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "widgetfadehelper.h"
#include "core/qt_blurimage.h"
#include <QResizeEvent>
#include <QPainter>
#include <QTimeLine>
#include <QtDebug>
const int WidgetFadeHelper::kLoadingPadding = 9;
const int WidgetFadeHelper::kLoadingBorderRadius = 10;
WidgetFadeHelper::WidgetFadeHelper(QWidget* parent, int msec)
: QWidget(parent),
parent_(parent),
blur_timeline_(new QTimeLine(msec, this)),
fade_timeline_(new QTimeLine(msec, this))
{
parent->installEventFilter(this);
connect(blur_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
connect(fade_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
connect(fade_timeline_, SIGNAL(finished()), SLOT(FadeFinished()));
hide();
}
bool WidgetFadeHelper::eventFilter(QObject* obj, QEvent* event) {
// We're only interested in our parent's resize events
if (obj != parent_ || event->type() != QEvent::Resize) return false;
// Don't care if we're hidden
if (!isVisible()) return false;
QResizeEvent* re = static_cast<QResizeEvent*>(event);
if (re->oldSize() == re->size()) {
// Ignore phoney resize events
return false;
}
// Get a new capture of the parent
hide();
CaptureParent();
show();
return false;
}
void WidgetFadeHelper::StartBlur() {
CaptureParent();
// Cover the parent
raise();
show();
// Start the timeline
blur_timeline_->stop();
blur_timeline_->start();
setAttribute(Qt::WA_TransparentForMouseEvents, false);
}
void WidgetFadeHelper::CaptureParent() {
// Take a "screenshot" of the window
original_pixmap_ = QPixmap::grabWidget(parent_);
QImage original_image = original_pixmap_.toImage();
// Blur it
QImage blurred(original_image.size(), QImage::Format_ARGB32_Premultiplied);
blurred.fill(Qt::transparent);
QPainter blur_painter(&blurred);
blur_painter.save();
qt_blurImage(&blur_painter, original_image, 10.0, true, false);
blur_painter.restore();
// Draw some loading text over the top
QFont loading_font(font());
loading_font.setBold(true);
QFontMetrics loading_font_metrics(loading_font);
const QString loading_text = tr("Loading...");
const QSize loading_size(kLoadingPadding*2 + loading_font_metrics.width(loading_text), kLoadingPadding*2 + loading_font_metrics.height());
const QRect loading_rect((blurred.width() - loading_size.width()) / 2, 100, loading_size.width(), loading_size.height());
blur_painter.setRenderHint(QPainter::Antialiasing);
blur_painter.setRenderHint(QPainter::HighQualityAntialiasing);
blur_painter.translate(0.5, 0.5);
blur_painter.setPen(QColor(200, 200, 200, 255));
blur_painter.setBrush(QColor(200, 200, 200, 192));
blur_painter.drawRoundedRect(loading_rect, kLoadingBorderRadius, kLoadingBorderRadius);
blur_painter.setPen(palette().brush(QPalette::Text).color());
blur_painter.setFont(loading_font);
blur_painter.drawText(loading_rect.translated(-1, -1), Qt::AlignCenter, loading_text);
blur_painter.translate(-0.5, -0.5);
blur_painter.end();
blurred_pixmap_ = QPixmap::fromImage(blurred);
resize(parent_->size());
}
void WidgetFadeHelper::StartFade() {
if (blur_timeline_->state() == QTimeLine::Running) {
// Blur timeline is still running, so we need render the current state
// into a new pixmap.
QPixmap pixmap(original_pixmap_);
QPainter painter(&pixmap);
painter.setOpacity(blur_timeline_->currentValue());
painter.drawPixmap(0, 0, blurred_pixmap_);
painter.end();
blurred_pixmap_ = pixmap;
}
blur_timeline_->stop();
original_pixmap_ = QPixmap();
// Start the timeline
fade_timeline_->stop();
fade_timeline_->start();
setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
void WidgetFadeHelper::paintEvent(QPaintEvent* ) {
QPainter p(this);
if (fade_timeline_->state() != QTimeLine::Running) {
// We're fading in the blur
p.drawPixmap(0, 0, original_pixmap_);
p.setOpacity(blur_timeline_->currentValue());
}
else {
// Fading out the blur into the new image
p.setOpacity(1.0 - fade_timeline_->currentValue());
}
p.drawPixmap(0, 0, blurred_pixmap_);
}
void WidgetFadeHelper::FadeFinished() {
hide();
original_pixmap_ = QPixmap();
blurred_pixmap_ = QPixmap();
}

View File

@@ -0,0 +1,62 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WIDGETFADEHELPER_H
#define WIDGETFADEHELPER_H
#include "config.h"
#include <QWidget>
class QTimeLine;
class WidgetFadeHelper : public QWidget {
Q_OBJECT
public:
WidgetFadeHelper(QWidget* parent, int msec = 500);
public slots:
void StartBlur();
void StartFade();
protected:
void paintEvent(QPaintEvent*);
bool eventFilter(QObject* obj, QEvent* event);
private slots:
void FadeFinished();
private:
void CaptureParent();
private:
static const int kLoadingPadding;
static const int kLoadingBorderRadius;
QWidget* parent_;
QTimeLine* blur_timeline_;
QTimeLine* fade_timeline_;
QPixmap original_pixmap_;
QPixmap blurred_pixmap_;
};
#endif // WIDGETFADEHELPER_H