/* * Copyright 2007 by Aaron Seigo * Copyright 2007 by Riccardo Iaconelli * Copyright 2007 by Matt Broadstone * Copyright 2006-2007 Fredrik Höglund * Copyright 2007 by Marco Martin * Copyright 2008 by Alexis Ménard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU 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 "icon_p.h" #include #include #include #include #include #include #include //#define BACKINGSTORE_BLUR_HACK #ifdef BACKINGSTORE_BLUR_HACK #include #include "effects/blur.cpp" #endif #include #include #include #include #include #include #include #include #include #include #include "animator.h" #include "svg.h" namespace Plasma { Icon::Private::Private(Icon *i) : q(i), iconSvg(0), iconSize(48, 48), states(Private::NoState), orientation(Qt::Vertical), numDisplayLines(2), invertLayout(false), drawBg(false), action(0) { m_hoverAnimId = -1; m_hoverAlpha = 20/255; } Icon::Private::~Private() { qDeleteAll(cornerActions); delete iconSvg; } void Icon::readColors() { d->textColor = Plasma::Theme::defaultTheme()->color(Theme::TextColor); d->shadowColor = Plasma::Theme::defaultTheme()->color(Theme::BackgroundColor); } 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) { Animator::self()->stopElementAnimation(m_animationId); } rebuildPixmap(); m_animationId = Animator::self()->animateElement(m_icon, Animator::AppearAnimation); Animator::self()->setInitialPixmap(m_animationId, m_pixmap); m_visible = true; } void IconAction::hide() { if (m_animationId) { Animator::self()->stopElementAnimation(m_animationId); } rebuildPixmap(); m_animationId = Animator::self()->animateElement(m_icon, Animator::DisappearAnimation); Animator::self()->setInitialPixmap(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::Minibutton; if (m_pressed) { element = Icon::Private::MinibuttonPressed; } else if (m_hovered) { element = Icon::Private::MinibuttonHover; } 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) { if (m_icon->size().width() < m_rect.width()*2.0 || m_icon->size().height() < m_rect.height()*2.0) { return false; } switch (type) { case QEvent::GraphicsSceneMousePress: { setSelected(m_rect.contains(pos)); return isSelected(); } break; case QEvent::GraphicsSceneMouseMove: { bool wasSelected = isSelected(); bool active = m_rect.contains(pos); setSelected(wasSelected && active); return (wasSelected != isSelected()) || active; } break; case QEvent::GraphicsSceneMouseRelease: { // kDebug() << "IconAction::event got a QEvent::MouseButtonRelease, " << isSelected(); 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; } int IconAction::animationId() const { return m_animationId; } QAction* IconAction::action() const { return m_action; } void IconAction::paint(QPainter *painter) const { if (m_icon->size().width() < m_rect.width()*2.0 || m_icon->size().height() < m_rect.height()*2.0) { return; } QPixmap animPixmap = Animator::self()->currentPixmap(m_animationId); if (m_visible && animPixmap.isNull()) { painter->drawPixmap(m_rect.toRect(), m_pixmap); } else { painter->drawPixmap(m_rect.toRect(), animPixmap); } } Icon::Icon(QGraphicsItem *parent) : QGraphicsWidget(parent), d(new Private(this)) { init(); } Icon::Icon(const QString &text, QGraphicsItem *parent) : QGraphicsWidget(parent), d(new Private(this)) { setText(text); init(); } Icon::Icon(const QIcon &icon, const QString &text, QGraphicsItem *parent) : QGraphicsWidget(parent), d(new Private(this)) { setText(text); setIcon(icon); init(); } Icon::~Icon() { delete d; } void Icon::init() { readColors(); connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(readColors())); // setAcceptedMouseButtons(Qt::LeftButton); setAcceptsHoverEvents(true); int focusHMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin); int focusVMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin); // Margins for horizontal mode (list views, tree views, table views) d->setHorizontalMargin(Private::TextMargin, focusHMargin, focusVMargin); d->setHorizontalMargin(Private::IconMargin, focusHMargin, focusVMargin); d->setHorizontalMargin(Private::ItemMargin, 0, 0); // Margins for vertical mode (icon views) d->setVerticalMargin(Private::TextMargin, 6, 2); d->setVerticalMargin(Private::IconMargin, focusHMargin, focusVMargin); d->setVerticalMargin(Private::ItemMargin, 0, 0); d->setActiveMargins(); d->currentSize = QSizeF(-1, -1); //setDrawStandardBackground(false); } void Icon::addIconAction(QAction *action) { int count = d->cornerActions.count(); if (count > 3) { kDebug() << "no more room for more actions!"; } IconAction* iconAction = new IconAction(this, action); d->cornerActions.append(iconAction); connect(action, SIGNAL(destroyed(QObject*)), this, SLOT(actionDestroyed(QObject*))); iconAction->setRect(d->actionRect((Private::ActionPosition)count)); } void Icon::setAction(QAction *action) { if (d->action) { disconnect(d->action, 0, this, 0); disconnect(this, 0, d->action, 0); } d->action = action; if (action) { connect(action, SIGNAL(changed()), this, SLOT(syncToAction())); connect(this, SIGNAL(clicked()), action, SLOT(trigger())); d->syncToAction(); } } QAction* Icon::action() const { return d->action; } void Icon::actionDestroyed(QObject* action) { QList::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. } int Icon::numDisplayLines() { return d->numDisplayLines; } void Icon::setNumDisplayLines(int numLines) { if(numLines > d->maxDisplayLines) { d->numDisplayLines = d->maxDisplayLines; } else { d->numDisplayLines = numLines; } } void Icon::setDrawBackground(bool draw) { if (d->drawBg != draw) { d->drawBg = draw; update(); } } bool Icon::drawBackground() const { return d->drawBg; } QPainterPath Icon::shape() const { if (d->currentSize.width() < 1) { return QGraphicsItem::shape(); } return roundedRectangle(QRectF(QPointF(0.0, 0.0), d->currentSize).adjusted(-2, -2, 2, 2), 10.0); } QSizeF Icon::Private::displaySizeHint(const QStyleOptionGraphicsItem *option, const qreal width) const { if (text.isEmpty() && infoText.isEmpty()) { return QSizeF( .0, .0 ); } QString label = text; // const qreal maxWidth = (orientation == Qt::Vertical) ? iconSize.width() + 10 : 32757; // NOTE: find a way to use the other layoutText, it currently returns nominal width, when // we actually need the actual width. qreal textWidth = width - horizontalMargin[Private::TextMargin].left - horizontalMargin[Private::TextMargin].right; //allow only five lines of text const qreal maxHeight = numDisplayLines*Plasma::Theme::defaultTheme()->fontMetrics().lineSpacing(); // To compute the nominal size for the label + info, we'll just append // the information string to the label if (!infoText.isEmpty()) { label += QString(QChar::LineSeparator) + infoText; } QTextLayout layout; setLayoutOptions(layout, option); QSizeF size = layoutText(layout, option, label, QSizeF(textWidth, maxHeight)); return addMargin(size, TextMargin); } void Icon::layoutIcons(const QStyleOptionGraphicsItem *option) { if (size() == d->currentSize) { return; } d->currentSize = size(); d->setActiveMargins(); //calculate icon size based on the available space qreal iconWidth; if (d->orientation == Qt::Vertical) { qreal heightAvail; //if there is text resize the icon in order to make room for the text if (!d->text.isEmpty() || !d->infoText.isEmpty()) { heightAvail = d->currentSize.height() - d->displaySizeHint(option, d->currentSize.width()).height() - d->verticalMargin[Private::TextMargin].top - d->verticalMargin[Private::TextMargin].bottom; //never make a label higher than half the total height heightAvail = qMax(heightAvail, d->currentSize.height()/2); } else { heightAvail = d->currentSize.height(); } //aspect ratio very "tall" if (d->currentSize.width() < heightAvail) { iconWidth = d->currentSize.width() - d->horizontalMargin[Private::IconMargin].left - d->horizontalMargin[Private::IconMargin].right; } else { iconWidth = heightAvail - d->verticalMargin[Private::IconMargin].top - d->verticalMargin[Private::IconMargin].bottom; } } else { //Horizontal layout QFontMetricsF fm(font()); //if there is text resize the icon in order to make room for the text if (d->text.isEmpty() && d->infoText.isEmpty()) { // with no text, we just take up the whole geometry iconWidth = d->currentSize.width() - d->horizontalMargin[Private::IconMargin].left - d->horizontalMargin[Private::IconMargin].right; } else { iconWidth = d->currentSize.height() - d->verticalMargin[Private::IconMargin].top - d->verticalMargin[Private::IconMargin].bottom; } } d->iconSize = QSizeF(iconWidth, iconWidth); int count = 0; foreach (IconAction* iconAction, d->cornerActions) { iconAction->setRect(d->actionRect((Private::ActionPosition)count)); ++count; } } void Icon::setSvg(const QString &svgFilePath, const QString &elementId) { if (!d->iconSvg) { d->iconSvg = new Plasma::Svg(); } d->iconSvg->setImagePath(svgFilePath); d->iconSvgElement = elementId; } void Icon::hoverEffect(bool show) { if (show) { d->states |= Private::HoverState; } if (d->m_hoverAnimId == -1 && !d->drawBg) { // we do this only when we don't do the anim, because // this gets set at animation end when we are animating if (!show) { d->states &= ~Private::HoverState; } return; } d->m_fadeIn = show; const int FadeInDuration = 150; if (d->m_hoverAnimId != -1) { Animator::self()->stopCustomAnimation(d->m_hoverAnimId); } d->m_hoverAnimId = Animator::self()->customAnimation(40 / (1000 / FadeInDuration), FadeInDuration, Animator::EaseOutCurve, this, "hoverAnimationUpdate"); } void Icon::hoverAnimationUpdate(qreal progress) { if (d->m_fadeIn) { d->m_hoverAlpha = progress; } else { // If we mouse leaves before the fade in is done, fade out from where we were, // not from fully faded in qreal new_alpha = d->m_fadeIn ? progress : 1 - progress; d->m_hoverAlpha = qMin(new_alpha, d->m_hoverAlpha); } if (progress == 1) { d->m_hoverAnimId = -1; } if (!d->m_fadeIn && progress == 1 ) { d->states &= ~Private::HoverState; } update(); } void Icon::Private::drawBackground(QPainter *painter, IconState state) { if (!drawBg) { return; } bool darkShadow = shadowColor.value() < 128; QColor shadow = shadowColor; QColor border = textColor; switch (state) { case Private::HoverState: shadow.setHsv(shadow.hue(), shadow.saturation(), shadow.value() + (int)(darkShadow ? 50 * m_hoverAlpha: -50 * m_hoverAlpha), 200 + (int)m_hoverAlpha * 55); // opacity break; case Private::PressedState: shadow.setHsv(shadow.hue(), shadow.saturation(), shadow.value() + (darkShadow?(int)(50*m_hoverAlpha):(int)(-50*m_hoverAlpha)), 204); //80% opacity break; default: break; } border.setAlphaF(.2); shadow.setAlphaF(.6); painter->save(); painter->translate(0.5, 0.5); painter->setRenderHint(QPainter::Antialiasing); painter->setBrush(shadow); painter->setPen(QPen(border, 1)); painter->drawPath(roundedRectangle(QRectF(QPointF(1, 1), QSize((int)currentSize.width()-2, (int)currentSize.height()-2)), 5.0)); painter->restore(); } QPixmap Icon::Private::decoration(const QStyleOptionGraphicsItem *option, bool useHoverEffect) { QPixmap result; QIcon::Mode mode = option->state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled; QIcon::State state = option->state & QStyle::State_Open ? QIcon::On : QIcon::Off; if (iconSvg) { if (iconSvgPixmap.size() != iconSize.toSize()) { QImage img(iconSize.toSize(), QImage::Format_ARGB32_Premultiplied); { img.fill(0); QPainter p(&img); iconSvg->resize(iconSize); iconSvg->paint(&p, img.rect(), iconSvgElement); } iconSvgPixmap = QPixmap::fromImage(img); } result = iconSvgPixmap; } else { const QSize size = icon.actualSize(iconSize.toSize(), mode, state); result = icon.pixmap(size, mode, state); } // We disable the iconeffect here since we cannot get it into sync with // the fade animation. TODO: Enable it when animations are switched off if (!result.isNull() && useHoverEffect && !drawBg) { KIconEffect *effect = KIconLoader::global()->iconEffect(); // Note that in KIconLoader terminology, active = hover. // We're assuming that the icon group is desktop/filemanager, since this // is KFileItemDelegate. if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) { result = effect->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState); } } return result; } QPointF Icon::Private::iconPosition(const QStyleOptionGraphicsItem *option, const QPixmap &pixmap) const { const QRectF itemRect = subtractMargin(option->rect, Private::ItemMargin); // Compute the nominal decoration rectangle const QSizeF size = addMargin(iconSize, Private::IconMargin); Qt::LayoutDirection direction = iconDirection(option); //alignment depends from orientation and option->direction Qt::Alignment alignment; if (text.isEmpty() && infoText.isEmpty()) { alignment = Qt::AlignCenter; }else if (orientation == Qt::Vertical) { alignment = Qt::Alignment(Qt::AlignHCenter | Qt::AlignTop); //Horizontal }else{ alignment = QStyle::visualAlignment(direction, Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter)); } const QRect iconRect = QStyle::alignedRect(direction, alignment, size.toSize(), itemRect.toRect()); // Position the pixmap in the center of the rectangle QRect pixmapRect = pixmap.rect(); pixmapRect.moveCenter(iconRect.center()); // add a gimmicky margin of 5px to y, TEMP TEMP TEMP // pixmapRect = pixmapRect.adjusted(0, 5, 0, 0); return QPointF(pixmapRect.topLeft()); } QRectF Icon::Private::labelRectangle(const QStyleOptionGraphicsItem *option, const QPixmap &icon, const QString &string) const { Q_UNUSED(string) if (icon.isNull()) { return option->rect; } const QSizeF decoSize = addMargin(iconSize, Private::IconMargin); const QRectF itemRect = subtractMargin(option->rect, Private::ItemMargin); QRectF textArea(QPointF(0, 0), itemRect.size()); if (orientation == Qt::Vertical) { textArea.setTop(decoSize.height() + 1); } else { //Horizontal textArea.setLeft(decoSize.width() + 1); } textArea.translate(itemRect.topLeft()); return QRectF(QStyle::visualRect(iconDirection(option), option->rect, textArea.toRect())); } // Lays the text out in a rectangle no larger than constraints, eliding it as necessary QSizeF Icon::Private::layoutText(QTextLayout &layout, const QStyleOptionGraphicsItem *option, const QString &text, const QSizeF &constraints) const { const QSizeF size = layoutText(layout, text, constraints.width()); if (size.width() > constraints.width() || size.height() > constraints.height()) { const QString elided = elidedText(layout, option, constraints); return layoutText(layout, elided, constraints.width()); } return size; } // Lays the text out in a rectangle no wider than maxWidth QSizeF Icon::Private::layoutText(QTextLayout &layout, const QString &text, qreal maxWidth) const { QFontMetricsF metrics(layout.font()); qreal leading = metrics.leading(); qreal height = 0.0; qreal widthUsed = 0.0; QTextLine line; layout.setText(text); layout.beginLayout(); while ((line = layout.createLine()).isValid()) { line.setLineWidth(maxWidth); height += leading; line.setPosition(QPointF(0.0, height)); height += line.height(); widthUsed = qMax(widthUsed, line.naturalTextWidth()); } layout.endLayout(); return QSizeF(widthUsed, height); } // Elides the text in the layout, by iterating over each line in the layout, eliding // or word breaking the line if it's wider than the max width, and finally adding an // ellipses at the end of the last line, if there are more lines than will fit within // the vertical size constraints. QString Icon::Private::elidedText(QTextLayout &layout, const QStyleOptionGraphicsItem *option, const QSizeF &size) const { Q_UNUSED(option) QFontMetricsF metrics(layout.font()); const QString text = layout.text(); qreal maxWidth = size.width(); qreal maxHeight = size.height(); qreal height = 0; // Elide each line that has already been laid out in the layout. QString elided; elided.reserve(text.length()); for (int i = 0; i < layout.lineCount(); i++) { QTextLine line = layout.lineAt(i); int start = line.textStart(); int length = line.textLength(); height += metrics.leading(); if (height + line.height() + metrics.lineSpacing() > maxHeight) { // Unfortunately, if the line ends because of a line separator, elidedText() will be too // clever and keep adding lines until it finds one that's too wide. if (line.naturalTextWidth() < maxWidth && start+length > 0 && text[start + length - 1] == QChar::LineSeparator) elided += text.mid(start, length - 1); else elided += metrics.elidedText(text.mid(start), Qt::ElideRight, maxWidth); break; } else if (line.naturalTextWidth() > maxWidth) elided += metrics.elidedText(text.mid(start, length), Qt::ElideRight, maxWidth); else elided += text.mid(start, length); height += line.height(); } return elided; } void Icon::Private::layoutTextItems(const QStyleOptionGraphicsItem *option, const QPixmap &icon, QTextLayout *labelLayout, QTextLayout *infoLayout, QRectF *textBoundingRect) const { bool showInformation = false; setLayoutOptions(*labelLayout, option); QFontMetricsF fm(labelLayout->font()); const QRectF textArea = labelRectangle(option, icon, text); QRectF textRect = subtractMargin(textArea, Private::TextMargin); //kDebug() << this << "text area" << textArea << "text rect" << textRect; // Sizes and constraints for the different text parts QSizeF maxLabelSize = textRect.size(); QSizeF maxInfoSize = textRect.size(); QSizeF labelSize; QSizeF infoSize; // If we have additional info text, and there's space for at least two lines of text, // adjust the max label size to make room for at least one line of the info text if (!infoText.isEmpty() && textRect.height() >= fm.lineSpacing() * 2) { infoLayout->setFont(labelLayout->font()); infoLayout->setTextOption(labelLayout->textOption()); maxLabelSize.rheight() -= fm.lineSpacing(); showInformation = true; } // Lay out the label text, and adjust the max info size based on the label size labelSize = layoutText(*labelLayout, option, text, maxLabelSize); maxInfoSize.rheight() -= labelSize.height(); // Lay out the info text if (showInformation) { infoSize = layoutText(*infoLayout, option, infoText, maxInfoSize); } else { infoSize = QSizeF(0, 0); } // Compute the bounding rect of the text const Qt::Alignment alignment = labelLayout->textOption().alignment(); const QSizeF size(qMax(labelSize.width(), infoSize.width()), labelSize.height() + infoSize.height()); *textBoundingRect = QStyle::alignedRect(iconDirection(option), alignment, size.toSize(), textRect.toRect()); // Compute the positions where we should draw the layouts labelLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y())); infoLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y() + labelSize.height())); //kDebug() << "final position is" << labelLayout->position(); } QBrush Icon::Private::foregroundBrush(const QStyleOptionGraphicsItem *option) const { const QPalette::ColorGroup group = option->state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; // Always use the highlight color for selected items if (option->state & QStyle::State_Selected) { return option->palette.brush(group, QPalette::HighlightedText); } return option->palette.brush(group, QPalette::Text); } QBrush Icon::Private::backgroundBrush(const QStyleOptionGraphicsItem *option) const { const QPalette::ColorGroup group = option->state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; QBrush background(Qt::NoBrush); // Always use the highlight color for selected items if (option->state & QStyle::State_Selected) { background = option->palette.brush(group, QPalette::Highlight); } return background; } void Icon::Private::drawTextItems(QPainter *painter, const QStyleOptionGraphicsItem *option, const QTextLayout &labelLayout, const QTextLayout &infoLayout) const { Q_UNUSED(option) painter->save(); painter->setPen(textColor); // the translation prevents odd rounding errors in labelLayout.position() // when applied to the canvas painter->translate(0.5, 0.5); labelLayout.draw(painter, QPointF()); if (!infoLayout.text().isEmpty()) { painter->setPen(textColor); infoLayout.draw(painter, QPointF()); } painter->restore(); } void Icon::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(widget) #ifdef BACKINGSTORE_BLUR_HACK if (d->state == Private::HoverState && scene()) { QList views = scene()->views(); if (views.count() > 0) { QPixmap* pix = static_cast(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 //Lay out the main icon and action icons layoutIcons(option); // Compute the metrics, and lay out the text items // ======================================================================== Private::IconState state = Private::NoState; if (d->states & Private::ManualPressedState) { state = Private::PressedState; } else if (d->states & Private::PressedState) { if (d->states & Private::HoverState) { state = Private::PressedState; } } else if (d->states & Private::HoverState) { state = Private::HoverState; } QPixmap icon = d->decoration(option, state != Private::NoState); const QPointF iconPos = d->iconPosition(option, icon); d->drawBackground(painter, state); // draw icon if (!icon.isNull()) { painter->drawPixmap(iconPos, icon); } // Draw corner actions foreach (const IconAction *action, d->cornerActions) { if (action->animationId()) { action->paint(painter); } } // Draw text last because its overlayed QTextLayout labelLayout, infoLayout; QRectF textBoundingRect; d->layoutTextItems(option, icon, &labelLayout, &infoLayout, &textBoundingRect); d->drawTextItems(painter, option, labelLayout, infoLayout); } void Icon::drawActionButtonBase(QPainter* painter, const QSize &size, int element) { qreal radius = size.width()/2; QRadialGradient gradient(radius, radius, radius, radius, radius); int alpha; if (element == Private::MinibuttonPressed) { alpha = 255; } else if (element == Private::MinibuttonHover) { alpha = 200; } else { alpha = 160; } gradient.setColorAt(0, QColor::fromRgb(d->textColor.red(), d->textColor.green(), d->textColor.blue(), alpha)); gradient.setColorAt(1, QColor::fromRgb(d->textColor.red(), d->textColor.green(), d->textColor.blue(), 0)); painter->setBrush(gradient); painter->setPen(Qt::NoPen); painter->drawEllipse(QRectF(QPointF(.0, .0), size)); } void Icon::setText(const QString& text) { d->text = text; // cause a relayout d->currentSize = QSizeF(-1, -1); } QString Icon::text() const { return d->text; } void Icon::setInfoText(const QString& text) { d->infoText = text; // cause a relayout d->currentSize = QSizeF(-1, -1); } QString Icon::infoText() const { return d->infoText; } QIcon Icon::icon() const { return d->icon; } void Icon::setIcon(const QString& icon) { if (icon.isEmpty()) { setIcon(QIcon()); return; } setIcon(KIcon(icon)); } void Icon::setIcon(const QIcon& icon) { d->icon = icon; } QSizeF Icon::iconSize() const { return d->iconSize; } bool Icon::isDown() { return d->states & Private::PressedState; } void Icon::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() != Qt::LeftButton) { QGraphicsWidget::mousePressEvent(event); return; } d->states |= Private::PressedState; d->clickStartPos = scenePos(); bool handled = false; foreach (IconAction *action, d->cornerActions) { handled = action->event(event->type(), event->pos()); if (handled) { break; } } if (!handled) { emit pressed(true); } update(); } void Icon::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (~d->states & Private::PressedState) { QGraphicsWidget::mouseMoveEvent(event); return; } if (boundingRect().contains(event->pos())) { if (~d->states & Private::HoverState) { d->states |= Private::HoverState; update(); } } else { if (d->states & Private::HoverState) { d->states &= ~Private::HoverState; update(); } } } void Icon::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (~d->states & Private::PressedState) { QGraphicsWidget::mouseMoveEvent(event); return; } d->states &= ~Private::PressedState; //don't pass click when the mouse was moved bool handled = d->clickStartPos != scenePos(); if (!handled) { foreach (IconAction *action, d->cornerActions) { if (action->event(event->type(), event->pos())) { handled = true; break; } } } if (!handled) { if (boundingRect().contains(event->pos())) { emit clicked(); if (KGlobalSettings::singleClick()) { emit activated(); } } emit pressed(false); } update(); } void Icon::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event) emit doubleClicked(); if (!KGlobalSettings::singleClick()) { emit activated(); } } void Icon::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { foreach (IconAction *action, d->cornerActions) { action->show(); action->event(event->type(), event->pos()); } hoverEffect(true); update(); QGraphicsWidget::hoverEnterEvent(event); } void Icon::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { foreach (IconAction *action, d->cornerActions) { action->hide(); action->event(event->type(), event->pos()); } // d->states &= ~Private::HoverState; // Will be set once progress is zero again ... hoverEffect(false); update(); QGraphicsWidget::hoverLeaveEvent(event); } void Icon::setPressed(bool pressed) { if (pressed) { d->states |= Private::ManualPressedState; d->states |= Private::PressedState; } else { d->states &= ~Private::ManualPressedState; d->states &= ~Private::PressedState; } update(); } void Icon::setUnpressed() { setPressed(false); } void Icon::Private::syncToAction() { if (!action) { return; } //we don't get told *what* changed, just that something changed //so we update everything we care about q->setIcon(action->icon()); q->setText(action->iconText()); q->setEnabled(action->isEnabled()); //TODO use action's tooltip too emit q->changed(); } void Icon::setOrientation(Qt::Orientation orientation) { d->orientation = orientation; resize(sizeFromIconSize(d->iconSize.width())); } void Icon::invertLayout(bool invert) { d->invertLayout = invert; } bool Icon::invertedLayout() const { return d->invertLayout; } QSizeF Icon::sizeFromIconSize(const qreal iconWidth) const { if (d->text.isEmpty() && d->infoText.isEmpty()) { //no text, less calculations return d->addMargin(d->addMargin(QSizeF(iconWidth, iconWidth), Private::IconMargin), Private::ItemMargin); } QFontMetricsF fm = Plasma::Theme::defaultTheme()->fontMetrics(); qreal width = 0; if (d->orientation == Qt::Vertical) { // make room for at most 14 characters width = qMax(fm.width(d->text.left(12)), fm.width(d->infoText.left(12))) + fm.width("xx") + d->horizontalMargin[Private::TextMargin].left + d->horizontalMargin[Private::TextMargin].right; width = qMax(width, iconWidth + d->horizontalMargin[Private::IconMargin].left + d->horizontalMargin[Private::IconMargin].right); } else { width = iconWidth + d->horizontalMargin[Private::IconMargin].left + d->horizontalMargin[Private::IconMargin].right + qMax(fm.width(d->text), fm.width(d->infoText)) + fm.width("xx") + d->horizontalMargin[Private::TextMargin].left + d->horizontalMargin[Private::TextMargin].right; } qreal height; qreal textHeight; QStyleOptionGraphicsItem option; option.state = QStyle::State_None; option.rect = boundingRect().toRect(); textHeight = d->displaySizeHint(&option, width).height(); if (d->orientation == Qt::Vertical) { height = iconWidth + textHeight + d->verticalMargin[Private::TextMargin].top + d->verticalMargin[Private::TextMargin].bottom + d->verticalMargin[Private::IconMargin].top + d->verticalMargin[Private::IconMargin].bottom; } else { //Horizontal height = qMax(iconWidth + d->verticalMargin[Private::IconMargin].top + d->verticalMargin[Private::IconMargin].bottom, textHeight + d->verticalMargin[Private::TextMargin].top + d->verticalMargin[Private::TextMargin].bottom); } return d->addMargin(QSizeF(width, height), Private::ItemMargin); } } // namespace Plasma #include "icon.moc"