/**************************************************************************** ** ** Qtitan Library by Developer Machines (Microsoft-Ribbon implementation for Qt.C++) ** ** Copyright (c) 2009-2015 Developer Machines (http://www.devmachines.com) ** ALL RIGHTS RESERVED ** ** The entire contents of this file is protected by copyright law and ** international treaties. Unauthorized reproduction, reverse-engineering ** and distribution of all or any portion of the code contained in this ** file is strictly prohibited and may result in severe civil and ** criminal penalties and will be prosecuted to the maximum extent ** possible under the law. ** ** RESTRICTIONS ** ** THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED ** FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE ** COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE ** AVAILABLE TO OTHER INDIVIDUALS WITHOUT WRITTEN CONSENT ** AND PERMISSION FROM DEVELOPER MACHINES ** ** CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON ** ADDITIONAL RESTRICTIONS. ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "QtnRibbonToolTip.h" #include "QtnRibbonStyle.h" #include "QtnRibbonPrivate.h" #ifdef DEBUG_MEMORY_ENABLED #include "QtitanMSVSDebug.h" #endif QTITAN_USE_NAMESPACE struct QEffects { enum Direction { LeftScroll = 0x0001, RightScroll = 0x0002, UpScroll = 0x0004, DownScroll = 0x0008 }; typedef uint DirFlags; }; extern void Q_GUI_EXPORT qScrollEffect(QWidget*, QEffects::DirFlags dir = QEffects::DownScroll, int time = -1); extern void Q_GUI_EXPORT qFadeEffect(QWidget*, int time = -1); QTITAN_BEGIN_NAMESPACE class RibbonToolTipPrivate : public QObject { public: QTN_DECLARE_PUBLIC(RibbonToolTip) public: explicit RibbonToolTipPrivate(); public: void updateTool(); public: int m_margin; int m_indent; int m_maxTipWidth; QBasicTimer m_hideTimer, m_expireTimer; bool m_fadingOut; QWidget* m_widget; QRect m_rect; QString m_text; QString m_textTitle; QIcon m_icon; static RibbonToolTip* m_instance; static RibbonToolTip::WrapMode m_wrapMode; static bool m_showIcon; QLabel* m_label; }; class RibbonKeyTipPrivate : public QObject { public: QTN_DECLARE_PUBLIC(RibbonKeyTip) public: explicit RibbonKeyTipPrivate(); public: QString strTip_; QString strCaption_; QString strPrefix_; QWidget* owner_; QAction* action_; QPoint pt_; bool enabled_; bool visible_; QWidget* bars_; uint align_; bool explicit_; }; QTITAN_END_NAMESPACE RibbonToolTip* RibbonToolTipPrivate::m_instance = Q_NULL; RibbonToolTip::WrapMode RibbonToolTipPrivate::m_wrapMode = RibbonToolTip::NoWrap; bool RibbonToolTipPrivate::m_showIcon = true; RibbonToolTipPrivate::RibbonToolTipPrivate() { m_margin = 0; m_fadingOut = false; m_widget = Q_NULL; m_indent = -1; m_maxTipWidth = 160; m_label = Q_NULL; } void RibbonToolTipPrivate::updateTool() { QTN_P(RibbonToolTip); if (m_label) { m_label->setText(m_text); m_label->setWordWrap(/*Qt::mightBeRichText(m_text)*/true); m_label->adjustSize(); } p.updateGeometry(); p.update(p.contentsRect()); } /* RibbonToolTip */ /*! \brief Constructs RibbonToolTip object with the given \a parent. Parameter \a title - is a title of the tooltip, \a text - is a text of the tooltip. */ RibbonToolTip::RibbonToolTip(const QString& title, const QString& text, const QIcon& icon, QWidget* parent) : QFrame(parent, Qt::ToolTip | Qt::BypassGraphicsProxyWidget) { delete RibbonToolTipPrivate::m_instance; RibbonToolTipPrivate::m_instance = this; QTN_INIT_PRIVATE(RibbonToolTip); QTN_D(RibbonToolTip); if (d.m_label) { d.m_label->setForegroundRole(QPalette::ToolTipText); d.m_label->setBackgroundRole(QPalette::ToolTipBase); d.m_label->setPalette(QToolTip::palette()); d.m_label->setFont(QToolTip::font()); d.m_label->ensurePolished(); d.m_label->setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, 0, this)); d.m_label->setFrameStyle(QFrame::NoFrame); } setForegroundRole(QPalette::ToolTipText); setBackgroundRole(QPalette::ToolTipBase); setPalette(QToolTip::palette()); setFont(QToolTip::font()); ensurePolished(); setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, 0, this)); setFrameStyle(QFrame::NoFrame); qApp->installEventFilter(this); setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / 255.0); setMouseTracking(true); d.m_fadingOut = false; setIcon(isIconVisible() ? icon : QIcon()); reuseTip(title, text); } /*! \brief Destructor of the RibbonToolTip object. */ RibbonToolTip::~RibbonToolTip() { RibbonToolTipPrivate::m_instance = Q_NULL; QTN_FINI_PRIVATE(); } RibbonToolTip* RibbonToolTip::instance() { return RibbonToolTipPrivate::m_instance; } /*! \brief Shows tooltip in \a pos position. Parameter \a title - is a title of the tooltip, \a text - is a text of the tooltip, \a icon - the icon of the tooltip. Parameter \a widget - reference to the widget for calculating the position of tooltip. */ void RibbonToolTip::showToolTip(const QPoint &pos, const QString& title, const QString& text, const QIcon& icon, QWidget* w) { QRect rect; // a tip does already exist if (RibbonToolTipPrivate::m_instance && RibbonToolTipPrivate::m_instance->isVisible()) { // empty text means hide current tip if (text.isEmpty()) { RibbonToolTipPrivate::m_instance->hideTip(); return; } else if (!RibbonToolTipPrivate::m_instance->fadingOut()) { // If the tip has changed, reuse the one // that is showing (removes flickering) QPoint localPos = pos; if (w) localPos = w->mapFromGlobal(pos); if (RibbonToolTipPrivate::m_instance->tipChanged(localPos, text, title, w)) { RibbonToolTipPrivate::m_instance->setIcon(isIconVisible() ? icon : QIcon()); RibbonToolTipPrivate::m_instance->reuseTip(title, text); RibbonToolTipPrivate::m_instance->setTipRect(w, rect); RibbonToolTipPrivate::m_instance->placeTip(pos, w); } return; } } // no tip can be reused, create new tip: if (!text.isEmpty()) { #ifndef Q_WS_WIN new RibbonToolTip(title, text, icon, w); // sets TipLabel::m_pInstanceto itself #else // On windows, we can't use the widget as parent otherwise the window will be // raised when the tooltip will be shown QScreen* screenAtPos = QGuiApplication::screenAt(RibbonToolTip::getTipScreen(pos, w)); new RibbonToolTip(title, text, icon, screenAtPos); #endif RibbonToolTipPrivate::m_instance->setTipRect(w, rect); RibbonToolTipPrivate::m_instance->placeTip(pos, w); RibbonToolTipPrivate::m_instance->setObjectName("qtntooltip_label"); #if !defined(QT_NO_EFFECTS) && !defined(Q_WS_MAC) if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip)) qFadeEffect(RibbonToolTipPrivate::m_instance); else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip)) qScrollEffect(RibbonToolTipPrivate::m_instance); else RibbonToolTipPrivate::m_instance->show(); #else RibbonToolTipPrivate::m_instance->show(); #endif } } /*! \brief Hides the tooltip window. */ void RibbonToolTip::hideToolTip() { showToolTip(QPoint(), QString(), QString(), QIcon()); } /*! \brief Returns visibility of the tooltip at the moment. */ bool RibbonToolTip::isToolTipVisible() { return (RibbonToolTipPrivate::m_instance != 0 && RibbonToolTipPrivate::m_instance->isVisible()); } /* QString RibbonToolTip::textToolTip() { if (RibbonToolTipPrivate::m_instance) return RibbonToolTipPrivate::m_instance->text(); return QString(); } */ /*! Sets the wrap \a mode for the tolltip's text. */ void RibbonToolTip::setWrapMode(WrapMode mode) { RibbonToolTipPrivate::m_wrapMode = mode; } /*! Returns the wrap mode for the tooltip's text. */ RibbonToolTip::WrapMode RibbonToolTip::wordWrap() { return RibbonToolTipPrivate::m_wrapMode; } /*! Sets the visibility of the tooltip's icon to \a visible. Method is static. */ void RibbonToolTip::setIconVisible(bool visible) { RibbonToolTipPrivate::m_showIcon = visible; } /*! Returns the visibility of the tooltip's icon. Method is static. */ bool RibbonToolTip::isIconVisible() { return RibbonToolTipPrivate::m_showIcon; } /*! Returns the margin \a mode for the tolltip's text in pixels. */ int RibbonToolTip::margin() const { QTN_D(const RibbonToolTip); return d.m_margin; } /*! Sets the margin \a mode for the tolltip's text in in pixels. */ void RibbonToolTip::setMargin(int margin) { QTN_D(RibbonToolTip); if (d.m_margin == margin) return; d.m_margin = margin; d.updateTool(); } void RibbonToolTip::setTitle(const QString& text) { QTN_D(RibbonToolTip); if (d.m_textTitle == text) return; d.m_textTitle = text; d.updateTool(); } const QString& RibbonToolTip::title() const { QTN_D(const RibbonToolTip); return d.m_textTitle; } void RibbonToolTip::setText(const QString& text) { QTN_D(RibbonToolTip); if (d.m_text == text) return; d.m_text = text; d.updateTool(); } const QString& RibbonToolTip::text() const { QTN_D(const RibbonToolTip); return d.m_text; } void RibbonToolTip::setIcon(const QIcon& icon) { QTN_D(RibbonToolTip); d.m_icon = icon; d.updateTool(); } const QIcon& RibbonToolTip::icon() const { QTN_D(const RibbonToolTip); return d.m_icon; } void RibbonToolTip::restartExpireTimer() { QTN_D(RibbonToolTip); int time = 10000 + 40 * qMax(0, text().length()-100); d.m_expireTimer.start(time, this); d.m_hideTimer.stop(); } void RibbonToolTip::reuseTip(const QString& textTitle, const QString& text) { setText(text); setTitle(textTitle); QFontMetrics fm(font()); QSize extra(1, 0); // Make it look good with the default RibbonToolTip font on Mac, which has a small descent. if (fm.descent() == 2 && fm.ascent() >= 11) ++extra.rheight(); resize(sizeHint() + extra); restartExpireTimer(); } /*! \reimp */ QSize RibbonToolTip::sizeHint() const { QTN_D(const RibbonToolTip); QFontMetrics fm = fontMetrics(); QRect rcMargin(margin(), margin(), margin(), margin()); QSize szMargin(3 + rcMargin.left() + rcMargin.right() + 3, 3 + rcMargin.top() + rcMargin.bottom() + 3); int flags = Qt::TextExpandTabs | Qt::TextHideMnemonic; bool drawTitle = !d.m_textTitle.isEmpty(); bool drawImage = !d.m_icon.isNull(); bool drawImageTop = true; QSize szImage(0, 0); QSize szTitle(0, 0); if (drawTitle) { QFont titleFont(font()); titleFont.setBold(true); QFontMetrics titleFM(titleFont); int w = titleFM.averageCharWidth() * 80; QRect rcTitle = titleFM.boundingRect(0, 0, w, 2000, flags | Qt::TextSingleLine, d.m_textTitle); szTitle = QSize(rcTitle.width(), rcTitle.height() + 20); } if (drawImage) { QSize szIcon(0, 0); if (!d.m_icon.availableSizes().isEmpty()) szIcon = d.m_icon.availableSizes().first(); drawImageTop = (szIcon.height() <= 16); if (drawImageTop) { if (!drawTitle) szImage.setWidth(szIcon.height() + 3); else szTitle.setWidth(szTitle.width() + szIcon.width() + 1); } else szImage.setWidth(szIcon.width() + 5); szImage.setHeight(szIcon.height()); } QSize szText; if (!d.m_label) { int nMaxTipWidth = d.m_maxTipWidth; QRect rcText(QPoint(0, 0), QSize(nMaxTipWidth - szMargin.width(), 0)); rcText = fm.boundingRect(rcText.left(), rcText.top(), rcText.width(), rcText.height(), flags | Qt::TextWordWrap, d.m_text); szText = rcText.size(); } else { szText = d.m_label->sizeHint(); } QSize sz(0, 0); sz.setHeight(qMax(szImage.height(), szText.height())); sz.setWidth(szImage.width() + szText.width()); if (drawTitle) { sz.setWidth(qMax(sz.width(), szTitle.width())); sz.setHeight(sz.height() + szTitle.height()); } sz += szMargin; return sz; } /*! \reimp */ void RibbonToolTip::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QTN_D(RibbonToolTip); QStylePainter p(this); drawFrame(&p); QStyleOptionFrame opt; opt.initFrom(this); p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); QRect rc = contentsRect(); rc.adjust(margin(), margin(), -margin(), -margin()); rc.adjust(3, 3, -3, -3); QRect rcTitle(rc.left() + 2, rc.top() + 2, rc.right() - 2, rc.bottom()); int flags = Qt::TextExpandTabs | Qt::TextHideMnemonic; bool drawTitle = !d.m_textTitle.isEmpty(); bool drawImage = !d.m_icon.isNull(); bool drawImageTop = true; QSize szImage(0, 0); if (drawImage) { if (!d.m_icon.availableSizes().isEmpty()) szImage = d.m_icon.availableSizes().first(); drawImageTop = (szImage.height() <= 16); if (drawImageTop) { QPoint ptIcon = rc.topLeft(); QPixmap pm = d.m_icon.pixmap(szImage, QIcon::Normal, QIcon::On); p.drawPixmap(ptIcon, pm); if (drawTitle) rcTitle.setLeft(rcTitle.left()+szImage.width() + 1); else rc.setLeft(rc.left()+szImage.width() + 3); } } if (drawTitle) { QFont oldFont = p.font(); QFont fnt = oldFont; fnt.setBold(true); p.setFont(fnt); style()->drawItemText(&p, rcTitle, flags | Qt::TextSingleLine, opt.palette, isEnabled(), d.m_textTitle, QPalette::ToolTipText); p.setFont(oldFont); rc.setTop(rc.top() + p.boundingRect(rcTitle, flags | Qt::TextSingleLine, d.m_textTitle).height()); rc.adjust(0, 12, 0, 0); } if (drawImage && !drawImageTop) { QPoint ptIcon = rc.topLeft(); QPixmap pm = d.m_icon.pixmap(szImage, QIcon::Normal, QIcon::On); p.drawPixmap(ptIcon, pm); rc.setLeft(rc.left() + szImage.width() + 4); } if (d.m_label) d.m_label->move(rc.topLeft()); else style()->drawItemText(&p, rc, flags | Qt::TextWordWrap, opt.palette, isEnabled(), d.m_text, QPalette::ToolTipText); p.end(); } /*! \reimp */ void RibbonToolTip::resizeEvent(QResizeEvent* event) { QStyleHintReturnMask frameMask; QStyleOption option; option.initFrom(this); if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) setMask(frameMask.region); QFrame::resizeEvent(event); } /*! \reimp */ void RibbonToolTip::mouseMoveEvent(QMouseEvent* event) { QTN_D(RibbonToolTip); if (d.m_rect.isNull()) return; QPoint pos = event->globalPos(); if (d.m_widget) pos = d.m_widget->mapFromGlobal(pos); if (!d.m_rect.contains(pos)) hideTip(); QFrame::mouseMoveEvent(event); } void RibbonToolTip::hideTip() { QTN_D(RibbonToolTip); if (!d.m_hideTimer.isActive()) d.m_hideTimer.start(300, this); } void RibbonToolTip::closeToolTip() { close(); // to trigger QEvent::Close which stops the animation deleteLater(); } void RibbonToolTip::setTipRect(QWidget* w, const QRect& r) { QTN_D(RibbonToolTip); if (!d.m_rect.isNull() && !w) qWarning("RibbonToolTip::setTipRect: Cannot pass null widget if rect is set"); else { d.m_widget = w; d.m_rect = r; } } /*! \reimp */ void RibbonToolTip::timerEvent(QTimerEvent* event) { QTN_D(RibbonToolTip); if (event->timerId() == d.m_hideTimer.timerId() || event->timerId() == d.m_expireTimer.timerId()) { d.m_hideTimer.stop(); d.m_expireTimer.stop(); closeToolTip(); } } /*! \reimp */ bool RibbonToolTip::eventFilter(QObject *o, QEvent* event) { QTN_D(RibbonToolTip); switch (event->type()) { case QEvent::Leave: hideTip(); break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::Wheel: closeToolTip(); break; case QEvent::MouseMove: if (o == d.m_widget && !d.m_rect.isNull() && !d.m_rect.contains(static_cast(event)->pos())) hideTip(); default: break; } return false; } int RibbonToolTip::getTipScreen(const QPoint& pos, QWidget* w) { if (QGuiApplication::screens().size() > 1) { QScreen* screenAtPos = QGuiApplication::screenAt(pos); return QGuiApplication::screens().indexOf(screenAtPos); } else { QScreen* screenAtWidget = QGuiApplication::screenAt(w->geometry().center()); return QGuiApplication::screens().indexOf(screenAtWidget); } } void RibbonToolTip::placeTip(const QPoint& pos, QWidget* w) { int screenNumber = getTipScreen(pos, w); QList screens = QGuiApplication::screens(); QRect screen; if (screenNumber >= 0 && screenNumber < screens.size()) { screen = screens[screenNumber]->geometry(); } else { // Fallback to primary screen if the screen number is invalid QScreen* primaryScreen = QGuiApplication::primaryScreen(); if (primaryScreen) { screen = primaryScreen->geometry(); } } QPoint p = pos; if (p.x() + this->width() > screen.x() + screen.width()) p.rx() -= 4 + this->width(); if (p.y() + this->height() > screen.y() + screen.height()) p.ry() -= 24 + this->height(); if (p.y() < screen.y()) p.setY(screen.y()); if (p.x() + this->width() > screen.x() + screen.width()) p.setX(screen.x() + screen.width() - this->width()); if (p.x() < screen.x()) p.setX(screen.x()); if (p.y() + this->height() > screen.y() + screen.height()) p.setY(screen.y() + screen.height() - this->height()); this->move(p); } bool RibbonToolTip::tipChanged(const QPoint& pos, const QString& text, const QString& textTitle, QObject* o) { QTN_D(RibbonToolTip); if (RibbonToolTipPrivate::m_instance->text() != text) return true; if (RibbonToolTipPrivate::m_instance->title() != textTitle) return true; if (o != d.m_widget) return true; if (!d.m_rect.isNull()) return !d.m_rect.contains(pos); else return false; } bool RibbonToolTip::fadingOut() const { QTN_D(const RibbonToolTip); return d.m_fadingOut; } /* RibbonKeyTipPrivate */ RibbonKeyTipPrivate::RibbonKeyTipPrivate() { owner_ = Q_NULL; action_ = Q_NULL; bars_ = Q_NULL; enabled_ = false; align_ = 0; visible_ = true; explicit_ = false; } /*! \class Qtitan::RibbonKeyTip \internal */ RibbonKeyTip::RibbonKeyTip(QWidget* bars, QWidget* owner, const QString& caption, const QPoint& pt, uint align, bool enabled, QAction* action) : QFrame(bars, Qt::ToolTip | Qt::BypassGraphicsProxyWidget) { QTN_INIT_PRIVATE(RibbonKeyTip); QTN_D(RibbonKeyTip); d.action_ = action; d.owner_ = owner; d.strCaption_ = caption; d.bars_ = bars; d.pt_ = pt; d.enabled_ = enabled; d.align_ = align; setEnabled(enabled); setForegroundRole(QPalette::ToolTipText); setBackgroundRole(QPalette::ToolTipBase); QPalette pal = QToolTip::palette(); pal.setColor(QPalette::Light, pal.color(QPalette::ToolTipText)); setPalette(pal); setFont(QToolTip::font()); ensurePolished(); if (!enabled) setWindowOpacity(0.5); } RibbonKeyTip::~RibbonKeyTip() { QTN_FINI_PRIVATE(); } QPoint RibbonKeyTip::posTip() const { QTN_D(const RibbonKeyTip); return d.pt_; } void RibbonKeyTip::setVisibleTip(bool visible) { QTN_D(RibbonKeyTip); d.visible_ = visible; } bool RibbonKeyTip::isVisibleTip() const { QTN_D(const RibbonKeyTip); return d.visible_; } bool RibbonKeyTip::isEnabledTip() const { QTN_D(const RibbonKeyTip); return d.enabled_; } QWidget* RibbonKeyTip::getBars() const { QTN_D(const RibbonKeyTip); return d.bars_; } uint RibbonKeyTip::getAlign() const { QTN_D(const RibbonKeyTip); return d.align_; } void RibbonKeyTip::setExplicit(bool exp) { QTN_D(RibbonKeyTip); d.explicit_ = exp; } bool RibbonKeyTip::isExplicit() const { QTN_D(const RibbonKeyTip); return d.explicit_; } QString RibbonKeyTip::getStringTip() const { QTN_D(const RibbonKeyTip); return d.strTip_; } void RibbonKeyTip::setStringTip(const QString& str) { QTN_D(RibbonKeyTip); d.strTip_ = str; } QString RibbonKeyTip::getCaption() const { QTN_D(const RibbonKeyTip); return d.strCaption_; } void RibbonKeyTip::setCaption(const QString& str) { QTN_D(RibbonKeyTip); d.strCaption_ = str; } QString RibbonKeyTip::getPrefix() const { QTN_D(const RibbonKeyTip); return d.strPrefix_; } void RibbonKeyTip::setPrefix(const QString& pref) { QTN_D(RibbonKeyTip); d.strPrefix_ = pref; } QWidget* RibbonKeyTip::getOwner() const { QTN_D(const RibbonKeyTip); return d.owner_; } QAction* RibbonKeyTip::getAction() const { QTN_D(const RibbonKeyTip); return d.action_; } void RibbonKeyTip::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QTN_D(RibbonKeyTip); QPainter p(this); QStyleOption opt; opt.initFrom(this); if (d.enabled_) opt.state |= QStyle::State_Enabled; else opt.state &= ~QStyle::State_Enabled; style()->drawPrimitive((QStyle::PrimitiveElement)RibbonStyle::PE_RibbonKeyTip, &opt, &p, this); style()->drawItemText(&p, opt.rect, Qt::AlignVCenter | Qt::AlignCenter | Qt::TextSingleLine | Qt::TextWordWrap, opt.palette, d.enabled_, d.strTip_, QPalette::ToolTipText); } void RibbonKeyTip::resizeEvent(QResizeEvent* event) { Q_UNUSED(event); QBitmap maskBitmap(width(), height()); maskBitmap.clear(); QPainter p(&maskBitmap); p.setBrush(Qt::black); #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) p.drawRoundedRect(0, 0, width(), height(), 2, 2, Qt::AbsoluteSize); #else p.drawRoundedRect(0, 0, width()-1, height()-1, 2, 2, Qt::AbsoluteSize); #endif p.end(); setMask(maskBitmap); }