Initial commit.
This commit is contained in:
179
src/widgets/autoexpandingtreeview.cpp
Normal file
179
src/widgets/autoexpandingtreeview.cpp
Normal 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();
|
||||
}
|
||||
|
||||
77
src/widgets/autoexpandingtreeview.h
Normal file
77
src/widgets/autoexpandingtreeview.h
Normal 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
|
||||
|
||||
83
src/widgets/busyindicator.cpp
Normal file
83
src/widgets/busyindicator.cpp
Normal 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();
|
||||
}
|
||||
|
||||
55
src/widgets/busyindicator.h
Normal file
55
src/widgets/busyindicator.h
Normal 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
|
||||
|
||||
30
src/widgets/clickablelabel.cpp
Normal file
30
src/widgets/clickablelabel.cpp
Normal 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);
|
||||
}
|
||||
38
src/widgets/clickablelabel.h
Normal file
38
src/widgets/clickablelabel.h
Normal 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
177
src/widgets/didyoumean.cpp
Normal 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
73
src/widgets/didyoumean.h
Normal 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
|
||||
38
src/widgets/elidedlabel.cpp
Normal file
38
src/widgets/elidedlabel.cpp
Normal 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
47
src/widgets/elidedlabel.h
Normal 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
|
||||
731
src/widgets/fancytabwidget.cpp
Normal file
731
src/widgets/fancytabwidget.cpp
Normal 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;
|
||||
}
|
||||
234
src/widgets/fancytabwidget.h
Normal file
234
src/widgets/fancytabwidget.h
Normal 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
|
||||
75
src/widgets/favoritewidget.cpp
Normal file
75
src/widgets/favoritewidget.cpp
Normal 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_);
|
||||
}
|
||||
56
src/widgets/favoritewidget.h
Normal file
56
src/widgets/favoritewidget.h
Normal 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
270
src/widgets/fileview.cpp
Normal 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
119
src/widgets/fileview.h
Normal 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
124
src/widgets/fileview.ui
Normal 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>
|
||||
186
src/widgets/fileviewlist.cpp
Normal file
186
src/widgets/fileviewlist.cpp
Normal 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());
|
||||
}
|
||||
73
src/widgets/fileviewlist.h
Normal file
73
src/widgets/fileviewlist.h
Normal 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
|
||||
|
||||
45
src/widgets/forcescrollperpixel.cpp
Normal file
45
src/widgets/forcescrollperpixel.cpp
Normal 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);
|
||||
}
|
||||
43
src/widgets/forcescrollperpixel.h
Normal file
43
src/widgets/forcescrollperpixel.h
Normal 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
|
||||
220
src/widgets/freespacebar.cpp
Normal file
220
src/widgets/freespacebar.cpp
Normal 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;
|
||||
|
||||
}
|
||||
85
src/widgets/freespacebar.h
Normal file
85
src/widgets/freespacebar.h
Normal 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
|
||||
363
src/widgets/groupediconview.cpp
Normal file
363
src/widgets/groupediconview.cpp
Normal 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;
|
||||
}
|
||||
116
src/widgets/groupediconview.h
Normal file
116
src/widgets/groupediconview.h
Normal 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
216
src/widgets/lineedit.cpp
Normal 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
178
src/widgets/lineedit.h
Normal 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
|
||||
57
src/widgets/linetextedit.cpp
Normal file
57
src/widgets/linetextedit.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
41
src/widgets/linetextedit.h
Normal file
41
src/widgets/linetextedit.h
Normal 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
|
||||
94
src/widgets/multiloadingindicator.cpp
Normal file
94
src/widgets/multiloadingindicator.cpp
Normal 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()));
|
||||
|
||||
}
|
||||
|
||||
62
src/widgets/multiloadingindicator.h
Normal file
62
src/widgets/multiloadingindicator.h
Normal 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
358
src/widgets/osd.cpp
Normal 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
131
src/widgets/osd.h
Normal 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
70
src/widgets/osd_mac.mm
Normal 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
41
src/widgets/osd_win.cpp
Normal 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
163
src/widgets/osd_x11.cpp
Normal 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
468
src/widgets/osdpretty.cpp
Normal 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
149
src/widgets/osdpretty.h
Normal 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
99
src/widgets/osdpretty.ui
Normal 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>
|
||||
603
src/widgets/playingwidget.cpp
Normal file
603
src/widgets/playingwidget.cpp
Normal 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
152
src/widgets/playingwidget.h
Normal 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
245
src/widgets/prettyimage.cpp
Normal 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
90
src/widgets/prettyimage.h
Normal 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
|
||||
|
||||
179
src/widgets/prettyimageview.cpp
Normal file
179
src/widgets/prettyimageview.cpp
Normal 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);
|
||||
}
|
||||
|
||||
74
src/widgets/prettyimageview.h
Normal file
74
src/widgets/prettyimageview.h
Normal 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
|
||||
|
||||
50
src/widgets/progressitemdelegate.cpp
Normal file
50
src/widgets/progressitemdelegate.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
37
src/widgets/progressitemdelegate.h
Normal file
37
src/widgets/progressitemdelegate.h
Normal 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
|
||||
|
||||
154
src/widgets/ratingwidget.cpp
Normal file
154
src/widgets/ratingwidget.cpp
Normal 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();
|
||||
}
|
||||
|
||||
70
src/widgets/ratingwidget.h
Normal file
70
src/widgets/ratingwidget.h
Normal 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
|
||||
|
||||
45
src/widgets/renametablineedit.cpp
Normal file
45
src/widgets/renametablineedit.cpp
Normal 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()
|
||||
}
|
||||
|
||||
43
src/widgets/renametablineedit.h
Normal file
43
src/widgets/renametablineedit.h
Normal 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
|
||||
|
||||
392
src/widgets/sliderwidget.cpp
Normal file
392
src/widgets/sliderwidget.cpp
Normal 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
141
src/widgets/sliderwidget.h
Normal 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
625
src/widgets/statusview.cpp
Normal 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
173
src/widgets/statusview.h
Normal 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
|
||||
41
src/widgets/stickyslider.cpp
Normal file
41
src/widgets/stickyslider.cpp
Normal 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_);
|
||||
|
||||
}
|
||||
49
src/widgets/stickyslider.h
Normal file
49
src/widgets/stickyslider.h
Normal 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
|
||||
320
src/widgets/stretchheaderview.cpp
Normal file
320
src/widgets/stretchheaderview.cpp
Normal 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;
|
||||
|
||||
}
|
||||
96
src/widgets/stretchheaderview.h
Normal file
96
src/widgets/stretchheaderview.h
Normal 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
242
src/widgets/stylehelper.cpp
Normal 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
85
src/widgets/stylehelper.h
Normal 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
178
src/widgets/trackslider.cpp
Normal 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
77
src/widgets/trackslider.h
Normal 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
|
||||
69
src/widgets/trackslider.ui
Normal file
69
src/widgets/trackslider.ui
Normal 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>
|
||||
174
src/widgets/tracksliderpopup.cpp
Normal file
174
src/widgets/tracksliderpopup.cpp
Normal 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);
|
||||
}
|
||||
66
src/widgets/tracksliderpopup.h
Normal file
66
src/widgets/tracksliderpopup.h
Normal 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
|
||||
132
src/widgets/tracksliderslider.cpp
Normal file
132
src/widgets/tracksliderslider.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
59
src/widgets/tracksliderslider.h
Normal file
59
src/widgets/tracksliderslider.h
Normal 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
|
||||
168
src/widgets/widgetfadehelper.cpp
Normal file
168
src/widgets/widgetfadehelper.cpp
Normal 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();
|
||||
}
|
||||
62
src/widgets/widgetfadehelper.h
Normal file
62
src/widgets/widgetfadehelper.h
Normal 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
|
||||
Reference in New Issue
Block a user