plasma-framework/widgets/icon.cpp
Matt Broadstone 4823fc0237 More refactoring in preparation for text layout code
svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=694895
2007-08-01 01:25:01 +00:00

819 lines
19 KiB
C++

/*
* Copyright (C) 2007 by Aaron Seigo <aseigo@kde.org>
* Copyright (C) 2007 by Riccardo Iaconelli <riccardo@kde.org>
* Copyright (C) 2007 by Matt Broadstone <mbroadst@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as
* published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "icon.h"
#include <QAction>
#include <QApplication>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
//#define BACKINGSTORE_BLUR_HACK
#ifdef BACKINGSTORE_BLUR_HACK
#include <private/qwindowsurface_p.h>
#endif
#include <KIcon>
#include <KImageEffect>
#include <KUrl>
#include <KRun>
#include <KMimeType>
#include <KDebug>
#include "phase.h"
#include "svg.h"
#include "effects/blur.cpp"
namespace Plasma
{
class PLASMA_EXPORT IconAction
{
public:
IconAction(Icon* icon, QAction* action);
void show();
void hide();
bool isVisible() const;
Phase::AnimId animationId() const;
QAction* action() const;
void paint(QPainter *painter) const;
bool event(QEvent::Type type, const QPointF &pos);
void setSelected(bool selected);
bool isSelected() const;
bool isHovered() const;
bool isPressed() const;
void setRect(const QRectF &rect);
QRectF rect() const;
private:
void rebuildPixmap();
Icon* m_icon;
QAction* m_action;
QPixmap m_pixmap;
QRectF m_rect;
bool m_hovered;
bool m_pressed;
bool m_selected;
bool m_visible;
Phase::AnimId m_animationId;
};
class Icon::Private
{
public:
Private()
: svg("widgets/iconbutton"),
svgElements(0),
size(128*1.1, 128*1.1),
iconSize(128, 128),
state(Private::NoState)
{
svg.setContentType(Plasma::Svg::ImageSet);
svg.resize(size);
//TODO: recheck when svg changes
checkSvgElements();
}
~Private()
{
qDeleteAll(cornerActions);
}
void drawBackground(QPainter *painter);
void drawForeground(QPainter *painter);
void drawIcon(QPainter *painter);
void drawText(QPainter *painter);
void checkSvgElements()
{
if (svg.elementExists("background")) {
svgElements |= SvgBackground;
}
if (svg.elementExists("background-hover")) {
svgElements |= SvgBackgroundHover;
}
if (svg.elementExists("background-pressed")) {
svgElements |= SvgBackgroundPressed;
}
if (svg.elementExists("foreground")) {
svgElements |= SvgForeground;
}
if (svg.elementExists("foreground-hover")) {
svgElements |= SvgForegroundHover;
}
if (svg.elementExists("foreground-pressed")) {
svgElements |= SvgForegroundPressed;
}
if (svg.elementExists("minibutton")) {
svgElements |= SvgMinibutton;
}
if (svg.elementExists("minibutton-hover")) {
svgElements |= SvgMinibuttonHover;
}
if (svg.elementExists("minibutton-pressed")) {
svgElements |= SvgMinibuttonPressed;
}
}
enum {
NoSvg = 0,
SvgBackground = 1,
SvgBackgroundHover = 2,
SvgBackgroundPressed = 4,
SvgForeground = 8,
SvgForegroundHover = 16,
SvgForegroundPressed = 32,
SvgMinibutton = 64,
SvgMinibuttonHover = 128,
SvgMinibuttonPressed = 256
};
enum ActionPosition {
TopLeft = 0,
TopRight,
BottomLeft,
BottomRight,
LastIconPosition
};
enum ButtonState
{
NoState,
HoverState,
PressedState
};
QString text;
QString infoText;
Svg svg;
int svgElements;
QSizeF size;
QSizeF iconSize;
QIcon icon;
ButtonState state;
QList<IconAction*> cornerActions;
};
IconAction::IconAction(Icon* icon, QAction *action)
: m_icon(icon),
m_action(action),
m_hovered(false),
m_pressed(false),
m_visible(false),
m_animationId(-1)
{
}
void IconAction::show()
{
if (m_animationId) {
Phase::self()->stopElementAnimation(m_animationId);
}
rebuildPixmap();
m_animationId = Phase::self()->animateElement(m_icon, Phase::ElementAppear);
Phase::self()->setAnimationPixmap(m_animationId, m_pixmap);
m_visible = true;
}
void IconAction::hide()
{
if (m_animationId) {
Phase::self()->stopElementAnimation(m_animationId);
}
rebuildPixmap();
m_animationId = Phase::self()->animateElement(m_icon, Phase::ElementDisappear);
Phase::self()->setAnimationPixmap(m_animationId, m_pixmap);
m_visible = false;
}
bool IconAction::isVisible() const
{
return m_visible;
}
bool IconAction::isPressed() const
{
return m_pressed;
}
bool IconAction::isHovered() const
{
return m_hovered;
}
void IconAction::setSelected(bool selected)
{
m_selected = selected;
}
bool IconAction::isSelected() const
{
return m_selected;
}
void IconAction::setRect(const QRectF &rect)
{
m_rect = rect;
}
QRectF IconAction::rect() const
{
return m_rect;
}
void IconAction::rebuildPixmap()
{
// Determine proper QIcon mode based on selection status
QIcon::Mode mode = QIcon::Normal;
if (m_selected) {
mode = QIcon::Selected;
}
// Draw everything
m_pixmap = QPixmap(26, 26);
m_pixmap.fill(Qt::transparent);
int element = Icon::Private::SvgMinibutton;
if (m_pressed) {
element = Icon::Private::SvgMinibuttonPressed;
} else if (m_hovered) {
element = Icon::Private::SvgMinibuttonHover;
}
QPainter painter(&m_pixmap);
m_icon->drawActionButtonBase(&painter, m_pixmap.size(), element);
m_action->icon().paint(&painter, 2, 2, 22, 22, Qt::AlignCenter, mode);
}
bool IconAction::event(QEvent::Type type, const QPointF &pos)
{
switch (type) {
case QEvent::MouseButtonPress: {
setSelected(m_rect.contains(pos));
return isSelected();
}
break;
case QEvent::MouseMove: {
bool wasSelected = isSelected();
bool active = m_rect.contains(pos);
setSelected(wasSelected && active);
return (wasSelected != isSelected()) || active;
}
break;
case QEvent::MouseButtonRelease: {
kDebug() << "IconAction::event got a QEvent::MouseButtonRelease, " << isSelected() << endl;
bool wasSelected = isSelected();
setSelected(false);
if (wasSelected) {
m_action->trigger();
}
return wasSelected;
}
break;
case QEvent::GraphicsSceneHoverEnter:
m_pressed = false;
m_hovered = true;
break;
case QEvent::GraphicsSceneHoverLeave:
m_pressed = false;
m_hovered = false;
break;
default:
break;
}
return false;
}
Phase::AnimId IconAction::animationId() const
{
return m_animationId;
}
QAction* IconAction::action() const
{
return m_action;
}
void IconAction::paint(QPainter *painter) const
{
painter->drawPixmap(m_rect.toRect(), Phase::self()->animationResult(m_animationId));
}
Icon::Icon(QGraphicsItem *parent)
: QGraphicsItem(parent),
d(new Private)
{
init();
}
Icon::Icon(const QString &text, QGraphicsItem *parent)
: QGraphicsItem(parent),
d(new Private)
{
setText(text);
init();
}
Icon::Icon(const QIcon &icon, const QString &text, QGraphicsItem *parent)
: QGraphicsItem(parent),
d(new Private)
{
setText(text);
setIcon(icon);
init();
}
Icon::~Icon()
{
delete d;
}
void Icon::init()
{
setAcceptedMouseButtons(Qt::LeftButton);
setAcceptsHoverEvents(true);
}
void Icon::addAction(QAction *action)
{
int count = d->cornerActions.count();
if (count > 3) {
kDebug() << "Icon::addAction(QAction*) no more room for more actions!" << endl;
}
IconAction* iconAction = new IconAction(this, action);
d->cornerActions.append(iconAction);
connect(action, SIGNAL(destroyed(QObject*)), this, SLOT(actionDestroyed(QObject*)));
//TODO: fewer magic numbers, please =) 38, 32, 6, etc... needs to go.
// at the very least static const ints with nice names.
switch (count) {
case Private::TopLeft:
iconAction->setRect(QRectF(6, 6, 32, 32));
break;
case Private::TopRight:
iconAction->setRect(QRectF(d->size.width() - 38, 6, 32, 32));
break;
case Private::BottomLeft:
iconAction->setRect(QRectF(6, d->size.height() - 38, 32, 32));
break;
case Private::BottomRight:
iconAction->setRect(QRectF(d->size.width() - 38, d->size.height() - 38, 32, 32));
break;
}
}
void Icon::actionDestroyed(QObject* action)
{
QList<IconAction*>::iterator it = d->cornerActions.begin();
while (it != d->cornerActions.end()) {
if ((*it)->action() == action) {
d->cornerActions.erase(it);
break;
}
}
update(); // redraw since an action has been deleted.
}
void Icon::calculateSize()
{
prepareGeometryChange();
QFontMetrics fm(QApplication::font()); // TODO: get the font somewhere more appropriate
QSizeF fmSize = fm.size(Qt::AlignHCenter | Qt::AlignTop, d->text);
int margin = 6; // hmmm
qreal height = d->iconSize.height() + (margin*2) + fmSize.height();
qreal width = margin + qMax(fmSize.width(), d->iconSize.width()) + margin;
d->size = QSizeF(width, height);
d->svg.resize(d->size);
int count = 0;
foreach (IconAction* iconAction, d->cornerActions) {
//TODO: fewer magic numbers, please =) 38, 32, 6, etc... needs to go.
// at the very least static const ints with nice names.
switch (count) {
case Private::TopLeft:
// top left never changes, so don't bother setting it
//iconAction->setRect(QRectF(6, 6, 32, 32));
break;
case Private::TopRight:
iconAction->setRect(QRectF(d->size.width() - 38, 6, 32, 32));
break;
case Private::BottomLeft:
iconAction->setRect(QRectF(6, d->size.height() - 38, 32, 32));
break;
case Private::BottomRight:
iconAction->setRect(QRectF(d->size.width() - 38, d->size.height() - 38, 32, 32));
break;
}
++count;
}
update();
}
QRectF Icon::boundingRect() const
{
return QRectF(QPointF(0, 0), d->size);
}
void Icon::Private::drawBackground(QPainter *painter)
{
QString element;
if (svgElements & Private::SvgBackground) {
element = "background";
}
switch (state) {
case Private::NoState:
break;
case Private::HoverState:
if (svgElements & Private::SvgBackgroundHover) {
element = "background-hover";
}
break;
case Private::PressedState:
if (svgElements & Private::SvgBackgroundPressed) {
element = "background-pressed";
} else if (svgElements & Private::SvgBackgroundHover) {
element = "background-hover";
}
break;
}
if (!element.isEmpty()) {
svg.paint(painter, 0, 0, element);
}
}
void Icon::Private::drawForeground(QPainter *painter)
{
QString element;
if (svgElements & Private::SvgForeground) {
element = "foreground";
}
switch (state) {
case Private::NoState:
break;
case Private::HoverState:
if (svgElements & Private::SvgForegroundHover) {
element = "foreground-hover";
}
break;
case Private::PressedState:
if (svgElements & Private::SvgForegroundPressed) {
element = "foreground-pressed";
} else if (svgElements & Private::SvgForegroundHover) {
element = "foreground-hover";
}
break;
}
if (!element.isEmpty()) {
svg.paint(painter, 0, 0, element);
}
}
void Icon::Private::drawIcon(QPainter *painter)
{
if (!icon.isNull()) {
qreal iw = iconSize.width();
qreal ih = iconSize.height();
qreal deltaX = (size.width() - iw) / 2;
qreal deltaY = (size.height() - ih) / 2 ;
if (state == Private::PressedState) {
painter->save();
painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->scale(0.95, 0.95);
deltaY *= 0.95;
deltaX *= 0.95;
painter->drawPixmap((int)(deltaX + (iw * .025)), (int)(deltaY + (ih * .025)),
icon.pixmap(iconSize.toSize()));
painter->restore();
} else {
painter->drawPixmap((int)deltaX, (int)deltaY, icon.pixmap(iconSize.toSize()));
}
}
}
void Icon::Private::drawText(QPainter *painter)
{
if (!text.isEmpty()) {
qreal offset = (iconSize.height() + 12); // TODO this shouldn't be hardcoded?
QRectF textRect(0, offset, size.width(), size.height() - offset);
QTextOption textOpt;
textOpt.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
textOpt.setWrapMode(QTextOption::WordWrap);
painter->setPen(Qt::white);
painter->drawText(textRect, text, textOpt);
}
}
void Icon::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
#ifdef BACKINGSTORE_BLUR_HACK
if (d->state == Private::HoverState && scene()) {
QList<QGraphicsView*> views = scene()->views();
if (views.count() > 0) {
QPixmap* pix = static_cast<QPixmap*>(views[0]->windowSurface()->paintDevice());
QImage image(boundingRect().size().toSize(), QImage::Format_ARGB32_Premultiplied);
{
QPainter p(&image);
p.drawPixmap(image.rect(), *pix, sceneBoundingRect());
}
expblur<16,7>(image, 8);
painter->drawImage(0, 0, image);
}
}
#endif
d->svg.resize(d->size);
d->drawBackground(painter);
d->drawIcon(painter);
d->drawForeground(painter);
// Draw corner actions
foreach (IconAction *action, d->cornerActions) {
if (action->animationId()) {
action->paint(painter);
}
}
// Draw text last because its overlayed
d->drawText(painter);
}
void Icon::drawActionButtonBase(QPainter* painter, const QSize &size, int element)
{
// Determine proper svg element
QString id;
if (d->svgElements & Private::SvgMinibutton) {
id = "minibutton";
}
if (element == Private::SvgMinibuttonPressed) {
if (d->svgElements & Private::SvgMinibuttonPressed) {
id = "minibutton-pressed";
} else if (d->svgElements & Private::SvgMinibuttonHover) {
id = "minibutton-hover";
}
} else if (element == Icon::Private::SvgMinibuttonHover &&
d->svgElements & Private::SvgMinibuttonHover) {
id = "minibutton-hover";
}
if (!id.isEmpty()) {
d->svg.resize(size);
d->svg.paint(painter, 0, 0, id);
}
}
void Icon::setText(const QString& text)
{
d->text = text;
calculateSize();
}
QString Icon::text() const
{
return d->text;
}
void Icon::setInfoText(const QString& text)
{
d->infoText = text;
calculateSize();
}
QString Icon::infoText() const
{
return d->infoText;
}
void Icon::setIcon(const QString& icon)
{
if (icon.isEmpty()) {
setIcon(QIcon());
return;
}
setIcon(KIcon(icon));
}
void Icon::setIcon(const QIcon& icon)
{
d->icon = icon;
calculateSize();
}
QSizeF Icon::iconSize() const
{
return d->iconSize;
}
void Icon::setIconSize(const QSizeF& s)
{
d->iconSize = s;
calculateSize();
}
void Icon::setIconSize(int w, int h)
{
setIconSize(QSizeF(w, h));
}
bool Icon::isDown()
{
return d->state == Private::PressedState;
}
void Icon::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
kDebug() << "Icon::mousePressEvent " << endl;
foreach (IconAction *action, d->cornerActions) {
action->event(event->type(), event->pos());
}
d->state = Private::PressedState;
QGraphicsItem::mousePressEvent(event);
update();
}
void Icon::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
bool inside = boundingRect().contains(event->pos());
kDebug() << "Icon::mouseReleaseEvent " << inside << endl;
Private::ButtonState was = d->state;
if (inside) {
d->state = Private::HoverState;
foreach (IconAction *action, d->cornerActions) {
if (action->event(event->type(), event->pos())) {
inside = false;
break;
}
}
} else {
d->state = Private::NoState;
}
if (was == Private::PressedState) {
emit pressed(false);
if (inside) {
emit clicked();
}
}
QGraphicsItem::mouseReleaseEvent(event);
update();
}
void Icon::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
foreach (IconAction *action, d->cornerActions) {
action->show();
action->event(event->type(), event->pos());
}
d->state = Private::HoverState;
QGraphicsItem::hoverEnterEvent(event);
}
void Icon::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
foreach (IconAction *action, d->cornerActions) {
action->hide();
action->event(event->type(), event->pos());
}
d->state = Private::NoState;
QGraphicsItem::hoverLeaveEvent(event);
}
void Icon::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (d->state == Private::PressedState) {
d->state = Private::HoverState;
}
QGraphicsItem::mouseMoveEvent(event);
}
QSizeF Icon::sizeHint() const
{
return d->size;
}
QSizeF Icon::minimumSize() const
{
return d->size; // probably should be more flexible on this =)
}
QSizeF Icon::maximumSize() const
{
return d->size;
}
Qt::Orientations Icon::expandingDirections() const
{
return Qt::Horizontal;
}
bool Icon::hasHeightForWidth() const
{
return true;
}
qreal Icon::heightForWidth(qreal w) const
{
return w; //FIXME: we shouldn't assume squareness but actually calculate based on text and what not
}
bool Icon::hasWidthForHeight() const
{
return true;
}
qreal Icon::widthForHeight(qreal h) const
{
return h; //FIXME: we shouldn't assume squareness but actually calculate based on text and what not
}
QRectF Icon::geometry() const
{
return boundingRect();
}
void Icon::setGeometry(const QRectF &r)
{
// TODO: this is wrong, but we should probably never call setGeometry anyway!
setIconSize(r.size());
setPos(r.x(),r.y());
}
} // namespace Plasma
#include "icon.moc"