diff options
Diffstat (limited to 'src/widgets/styles/qmacstyle_mac.mm')
-rw-r--r-- | src/widgets/styles/qmacstyle_mac.mm | 414 |
1 files changed, 384 insertions, 30 deletions
diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm index 662f0e2a60..67a3fd176e 100644 --- a/src/widgets/styles/qmacstyle_mac.mm +++ b/src/widgets/styles/qmacstyle_mac.mm @@ -82,6 +82,7 @@ #include <qpushbutton.h> #include <qradiobutton.h> #include <qrubberband.h> +#include <qscrollbar.h> #include <qsizegrip.h> #include <qspinbox.h> #include <qsplitter.h> @@ -102,6 +103,35 @@ #include <private/qstylehelper_p.h> #include <qpa/qplatformfontdatabase.h> +QT_USE_NAMESPACE + +@interface NotificationReceiver : NSObject { +QMacStylePrivate *mPrivate; +} +- (id)initWithPrivate:(QMacStylePrivate *)priv; +- (void)scrollBarStyleDidChange:(NSNotification *)notification; +@end + + +@implementation NotificationReceiver +- (id)initWithPrivate:(QMacStylePrivate *)priv +{ + self = [super init]; + mPrivate = priv; + return self; +} + +- (void)scrollBarStyleDidChange:(NSNotification *)notification +{ + Q_UNUSED(notification); + QEvent event(QEvent::StyleChange); + Q_FOREACH (QPointer<QWidget> sb, mPrivate->scrollBars) { + if (sb) + QCoreApplication::sendEvent(sb, &event); + } +} +@end + QT_BEGIN_NAMESPACE // The following constants are used for adjusting the size @@ -115,6 +145,8 @@ const int QMacStylePrivate::SmallButtonH = 30; const int QMacStylePrivate::BevelButtonW = 50; const int QMacStylePrivate::BevelButtonH = 22; const int QMacStylePrivate::PushButtonContentPadding = 6; +const qreal QMacStylePrivate::ScrollBarFadeOutDuration = 200.0; +const qreal QMacStylePrivate::ScrollBarFadeOutDelay = 450.0; // These colors specify the titlebar gradient colors on // Leopard. Ideally we should get them from the system. @@ -144,6 +176,38 @@ static bool isVerticalTabs(const QTabBar::Shape shape) { || shape == QTabBar::TriangularWest); } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 +/*! + Returns the QAbstractScrollArea the scroll bar \a sb is in. If \a sb is not + inside of a QAbstractScrollArea, this returns 0. + \internal + */ +static const QAbstractScrollArea *scrollBarsScrollArea(const QScrollBar *sb) +{ + const QWidget *w = sb; + const QAbstractScrollArea *sa = 0; + while (w != 0 && sa == 0) { + sa = qobject_cast<const QAbstractScrollArea *>(w); + w = w->parentWidget(); + } + return sa; +} + +/*! + For a scroll bar \a sb within a scroll area, this function returns all other scroll + bars within the same scroll area. + \internal + */ +static QList<const QScrollBar *> scrollBarsSiblings(const QScrollBar *sb) +{ + const QAbstractScrollArea *sa = scrollBarsScrollArea(sb); + Q_ASSERT(sa != 0); + QList<const QScrollBar *> list = sa->findChildren<const QScrollBar *>(); + list.removeOne(sb); + return list; +} +#endif + void drawTabCloseButton(QPainter *p, bool hover, bool active, bool selected) { // draw background circle @@ -1640,6 +1704,9 @@ bool QMacStylePrivate::animatable(QMacStylePrivate::Animates as, const QWidget * } else if (as == AquaProgressBar) { if (progressBars.contains((const_cast<QWidget *>(w)))) return true; + } else if (as == AquaScrollBar) { + if (scrollBars.contains((const_cast<QWidget *>(w)))) + return true; } return false; } @@ -1652,6 +1719,9 @@ void QMacStylePrivate::stopAnimate(QMacStylePrivate::Animates as, QWidget *w) tmp->update(); } else if (as == AquaProgressBar) { progressBars.removeAll(w); + } else if (as == AquaScrollBar) { + scrollBars.removeAll(w); + scrollBarInfos.remove(w); } } @@ -1661,12 +1731,14 @@ void QMacStylePrivate::startAnimate(QMacStylePrivate::Animates as, QWidget *w) defaultButton = static_cast<QPushButton *>(w); else if (as == AquaProgressBar) progressBars.append(w); + else if (as == AquaScrollBar) + scrollBars.append(w); startAnimationTimer(); } void QMacStylePrivate::startAnimationTimer() { - if ((defaultButton || !progressBars.isEmpty()) && timerID <= -1) + if ((defaultButton || !progressBars.isEmpty() || !scrollBars.isEmpty()) && timerID <= -1) timerID = startTimer(animateSpeed(AquaListViewItemOpen)); } @@ -1674,7 +1746,8 @@ bool QMacStylePrivate::addWidget(QWidget *w) { //already knew of it if (static_cast<QPushButton*>(w) == defaultButton - || progressBars.contains(static_cast<QProgressBar*>(w))) + || progressBars.contains(static_cast<QProgressBar*>(w)) + || scrollBars.contains(static_cast<QScrollBar*>(w))) return false; if (QPushButton *btn = qobject_cast<QPushButton *>(w)) { @@ -1689,6 +1762,12 @@ bool QMacStylePrivate::addWidget(QWidget *w) startAnimate(AquaProgressBar, w); return true; } + bool isScrollBar = (qobject_cast<QScrollBar *>(w)); + if (isScrollBar) { + w->installEventFilter(this); + startAnimate(AquaScrollBar, w); + return true; + } } if (w->isWindow()) { w->installEventFilter(this); @@ -1704,6 +1783,8 @@ void QMacStylePrivate::removeWidget(QWidget *w) stopAnimate(AquaPushButton, btn); } else if (qobject_cast<QProgressBar *>(w)) { stopAnimate(AquaProgressBar, w); + } else if (qobject_cast<QScrollBar *>(w)) { + stopAnimate(AquaScrollBar, w); } } @@ -1755,6 +1836,30 @@ void QMacStylePrivate::timerEvent(QTimerEvent *) animated += i; } } + if (!scrollBars.isEmpty()) { + int i = 0; + while (i < scrollBars.size()) { + QWidget *maybeScroll = scrollBars.at(i); + if (!maybeScroll) { + scrollBars.removeAt(i); + } else { + if (QScrollBar *sb = qobject_cast<QScrollBar *>(maybeScroll)) { + const OverlayScrollBarInfo& info = scrollBarInfos[sb]; + const QDateTime dt = QDateTime::currentDateTime(); + const qreal elapsed = qMax(info.lastHovered.msecsTo(dt), + info.lastUpdate.msecsTo(dt)); + const CGFloat opacity = 1.0 - qMax(0.0, (elapsed - ScrollBarFadeOutDelay) / + ScrollBarFadeOutDuration); + if ((opacity > 0.0 || !info.cleared) && (elapsed > ScrollBarFadeOutDelay)) { + if (doAnimate(AquaScrollBar)) + sb->update(); + } + } + ++i; + ++animated; + } + } + } if (animated <= 0) { killTimer(timerID); timerID = -1; @@ -1776,6 +1881,63 @@ bool QMacStylePrivate::eventFilter(QObject *o, QEvent *e) case QEvent::Hide: progressBars.removeAll(pb); } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + } else if (QScrollBar *sb = qobject_cast<QScrollBar *>(o)) { + // take care of fading out overlaying scrollbars (and only those!) when inactive + const QAbstractScrollArea *scrollArea = scrollBarsScrollArea(sb); + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7 && + [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay && scrollArea) { + OverlayScrollBarInfo& info = scrollBarInfos[sb]; + const qreal elapsed = info.lastUpdate.msecsTo(QDateTime::currentDateTime()); + const CGFloat opacity = 1.0 - qMax(0.0, (elapsed - ScrollBarFadeOutDelay) + / ScrollBarFadeOutDuration); + switch (e->type()) { + case QEvent::MouseMove: + // whenever the mouse moves on a not 100% transparent scroll bar, + // the fade out is stopped and it's set to 100% opaque + if (opacity > 0.0) { + info.hovered = true; + info.lastUpdate = QDateTime::currentDateTime(); + info.lastHovered = QDateTime::currentDateTime(); + sb->update(); + break; + } + + // fall through + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + // all mouse events which happens on a transparent scroll bar are + // translated and passed to the scroll area's viewport + if (opacity <= 0.0) { + QMouseEvent* mouse = static_cast<QMouseEvent *>(e); + QWidget* viewport = scrollArea->viewport(); + const QPoint scrollAreaPos = sb->mapTo(scrollArea, mouse->pos()); + const QPoint viewportPos = viewport->mapFromParent(scrollAreaPos); + QMouseEvent me(mouse->type(), viewportPos, mouse->windowPos(), + mouse->globalPos(), mouse->button(), mouse->buttons(), + mouse->modifiers()); + QCoreApplication::sendEvent(viewport, &me); + mouse->setAccepted(me.isAccepted()); + return true; + } + break; + case QEvent::Leave: + case QEvent::WindowDeactivate: + // mouse leave and window deactivate sets the scrollbar to not-hovered + // -> triggers fade out + info.hovered = false; + break; + if (!info.hovered) { + e->setAccepted(false); + return true; + } + break; + default: + break; + } + } +#endif } else if (QPushButton *btn = qobject_cast<QPushButton *>(o)) { switch (e->type()) { default: @@ -1943,10 +2105,29 @@ QMacStyle::QMacStyle() : QWindowsStyle() { d = new QMacStylePrivate(this); + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + d->receiver = [[NotificationReceiver alloc] initWithPrivate:d]; + NotificationReceiver *receiver = static_cast<NotificationReceiver *>(d->receiver); + + [[NSNotificationCenter defaultCenter] addObserver:receiver + selector:@selector(scrollBarStyleDidChange:) + name:NSPreferredScrollerStyleDidChangeNotification + object:nil]; + + d->nsscroller = [[NSScroller alloc] init]; +#endif } QMacStyle::~QMacStyle() { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + [d->nsscroller release]; + + NotificationReceiver *receiver = static_cast<NotificationReceiver *>(d->receiver); + [[NSNotificationCenter defaultCenter] removeObserver:receiver]; +#endif + delete qt_mac_backgroundPattern; qt_mac_backgroundPattern = 0; delete d; @@ -2086,6 +2267,11 @@ void QMacStyle::polish(QWidget* w) rubber->setAttribute(Qt::WA_PaintOnScreen, false); rubber->setAttribute(Qt::WA_NoSystemBackground, false); } + + if (qobject_cast<QScrollBar*>(w)) { + w->setAttribute(Qt::WA_OpaquePaintEvent, false); + w->setMouseTracking(true); + } } void QMacStyle::unpolish(QWidget* w) @@ -2115,6 +2301,11 @@ void QMacStyle::unpolish(QWidget* w) frame->setAttribute(Qt::WA_NoSystemBackground, true); QWindowsStyle::unpolish(w); + + if (qobject_cast<QScrollBar*>(w)) { + w->setAttribute(Qt::WA_OpaquePaintEvent, true); + w->setMouseTracking(false); + } } int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const @@ -2281,6 +2472,23 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW } break; case PM_ScrollBarExtent: { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7 && + [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay && + scrollBarsScrollArea(qobject_cast<const QScrollBar *>(widget))) { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + ret = 9; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + ret = 7; + break; + } + break; + } +#endif switch (d->aquaSizeConstrain(opt, widget)) { case QAquaSizeUnknown: case QAquaSizeLarge: @@ -2472,6 +2680,15 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW ret = 0; } break; + case PM_ScrollView_ScrollBarOverlap: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + ret = (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7 && + [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay) ? + pixelMetric(PM_ScrollBarExtent, opt, widget) : 0; +#else + ret = 0; +#endif + break; default: ret = QWindowsStyle::pixelMetric(metric, opt, widget); break; @@ -4868,38 +5085,175 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex #endif } - HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg, - kHIThemeOrientationNormal); - if (cc == CC_Slider && slider->subControls & SC_SliderTickmarks) { - if (qt_mac_is_metal(widget)) { - if (tdi.enableState == kThemeTrackInactive) - tdi.enableState = kThemeTrackActive; // Looks more Cocoa-like +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7 && + [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay && + scrollBarsScrollArea(qobject_cast<const QScrollBar *>(widget)) && + cc == CC_ScrollBar) { + QMacStylePrivate::OverlayScrollBarInfo& info = d->scrollBarInfos[widget]; + bool showSiblings = false; + if (info.lastValue != slider->sliderPosition || + info.lastMinimum != slider->minimum || + info.lastMaximum != slider->maximum || + info.lastSize != slider->rect.size()) { + info.lastValue = slider->sliderPosition; + info.lastMinimum = slider->minimum; + info.lastSize = slider->rect.size(); + info.lastMaximum = slider->maximum; + info.lastUpdate = QDateTime::currentDateTime(); + showSiblings = true; + } + + const QList<const QScrollBar *> siblings = + scrollBarsSiblings(qobject_cast<const QScrollBar *>(widget)); + // keep last update (last change of value) time of all siblings in sync + Q_FOREACH (const QScrollBar *sibling, siblings) { + info.lastUpdate = qMax(info.lastUpdate, + d->scrollBarInfos.value(sibling).lastUpdate); + info.cleared = false; + if (d->scrollBarInfos.value(sibling).hovered) + info.lastUpdate = QDateTime::currentDateTime(); } - int interval = slider->tickInterval; - if (interval == 0) { - interval = slider->pageStep; - if (interval == 0) - interval = slider->singleStep; - if (interval == 0) - interval = 1; + + qreal elapsed = info.lastHovered.msecsTo(QDateTime::currentDateTime()); + CGFloat opacity = 1.0 - qMax(0.0, + (elapsed - QMacStylePrivate::ScrollBarFadeOutDelay) / + QMacStylePrivate::ScrollBarFadeOutDuration); + const bool isHorizontal = slider->orientation == Qt::Horizontal; + + if (info.hovered) { + info.lastHovered = QDateTime::currentDateTime(); + info.lastUpdate = QDateTime::currentDateTime(); + opacity = 1.0; + // if the current scroll bar is hovered, none of the others might fade out + Q_FOREACH (const QScrollBar *sibling, siblings) { + d->scrollBarInfos[sibling].lastUpdate = info.lastUpdate; + } + } + + // when one scroll bar was changed, all its siblings need a redraw as well, since + // either both scroll bars within a scroll area shall be visible or none + if (showSiblings) { + Q_FOREACH (const QScrollBar *sibling, siblings) + const_cast<QScrollBar *>(sibling)->update(); + } + + CGContextSaveGState(cg); + + [NSGraphicsContext setCurrentContext:[NSGraphicsContext + graphicsContextWithGraphicsPort:(CGContextRef)cg flipped:NO]]; + NSScroller *scroller = reinterpret_cast<NSScroller*>(d->nsscroller); + [scroller initWithFrame:NSMakeRect(0, 0, slider->rect.width(), slider->rect.height())]; + // mac os behaviour: as soon as one color channel is >= 128, + // the bg is considered bright, scroller is dark + const QColor bgColor = widget->palette().color(QPalette::Base); + const bool isDarkBg = bgColor.red() < 128 && bgColor.green() < 128 && + bgColor.blue() < 128; + if (isDarkBg) + [scroller setKnobStyle:NSScrollerKnobStyleLight]; + + [scroller setControlSize:(tdi.kind == kThemeSmallScrollBar ? NSMiniControlSize + : NSRegularControlSize)]; + if (isHorizontal) + [scroller setBounds:NSMakeRect(0, -1, + slider->rect.width(), slider->rect.height())]; + else + [scroller setBounds:NSMakeRect(-1, 0, + slider->rect.width(), slider->rect.height())]; + [scroller setScrollerStyle:NSScrollerStyleOverlay]; + + // first we draw only the track, by using a disabled scroller + if (opacity > 0.0) { + CGContextBeginTransparencyLayerWithRect(cg, qt_hirectForQRect(slider->rect), + NULL); + CGContextSetAlpha(cg, opacity); + + [scroller setFrame:NSMakeRect(0, 0, slider->rect.width(), slider->rect.height())]; + [scroller setEnabled:NO]; + [scroller displayRectIgnoringOpacity:[scroller bounds] + inContext:[NSGraphicsContext currentContext]]; + + CGContextEndTransparencyLayer(cg); } - int numMarks = 1 + ((slider->maximum - slider->minimum) / interval); - - if (tdi.trackInfo.slider.thumbDir == kThemeThumbPlain) { - // They asked for both, so we'll give it to them. - tdi.trackInfo.slider.thumbDir = kThemeThumbDownward; - HIThemeDrawTrackTickMarks(&tdi, numMarks, - cg, - kHIThemeOrientationNormal); - tdi.trackInfo.slider.thumbDir = kThemeThumbUpward; - HIThemeDrawTrackTickMarks(&tdi, numMarks, - cg, - kHIThemeOrientationNormal); + + // afterwards we draw the knob, since we cannot drow the know w/o the track, + // we simulate a scrollbar with a knob from 0.0 to 1.0 + elapsed = info.lastUpdate.msecsTo(QDateTime::currentDateTime()); + opacity = 1.0 - qMax(0.0, (elapsed - QMacStylePrivate::ScrollBarFadeOutDelay) / + QMacStylePrivate::ScrollBarFadeOutDuration); + info.cleared = opacity <= 0.0; + + CGContextBeginTransparencyLayerWithRect(cg, qt_hirectForQRect(slider->rect), NULL); + CGContextSetAlpha(cg, opacity); + + const qreal length = slider->maximum - slider->minimum + slider->pageStep; + const qreal proportion = slider->pageStep / length; + qreal value = (slider->sliderValue - slider->minimum) / length; + if (isHorizontal && slider->direction == Qt::RightToLeft) + value = 1.0 - value - proportion; + + [scroller setEnabled:(slider->state & State_Enabled) ? YES : NO]; + [scroller setKnobProportion:1.0]; + + const int minKnobWidth = 26; + + if (isHorizontal) { + const qreal plannedWidth = proportion * slider->rect.width(); + const qreal width = qMax<qreal>(minKnobWidth, plannedWidth); + const qreal totalWidth = slider->rect.width() + plannedWidth - width; + [scroller setFrame:NSMakeRect(0, 0, width, slider->rect.height())]; + CGContextTranslateCTM(cg, value * totalWidth, 0); } else { - HIThemeDrawTrackTickMarks(&tdi, numMarks, - cg, - kHIThemeOrientationNormal); + const qreal plannedHeight = proportion * slider->rect.height(); + const qreal height = qMax<qreal>(minKnobWidth, plannedHeight); + const qreal totalHeight = slider->rect.height() + plannedHeight - height; + [scroller setFrame:NSMakeRect(0, 0, slider->rect.width(), height)]; + CGContextTranslateCTM(cg, 0, value * totalHeight); + } + if (value > 0.0) { + [scroller layout]; + [scroller displayRectIgnoringOpacity:[scroller bounds] + inContext:[NSGraphicsContext currentContext]]; + } + CGContextEndTransparencyLayer(cg); + CGContextRestoreGState(cg); + } else +#endif + { + HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg, + kHIThemeOrientationNormal); + if (cc == CC_Slider && slider->subControls & SC_SliderTickmarks) { + if (qt_mac_is_metal(widget)) { + if (tdi.enableState == kThemeTrackInactive) + tdi.enableState = kThemeTrackActive; // Looks more Cocoa-like + } + int interval = slider->tickInterval; + if (interval == 0) { + interval = slider->pageStep; + if (interval == 0) + interval = slider->singleStep; + if (interval == 0) + interval = 1; + } + int numMarks = 1 + ((slider->maximum - slider->minimum) / interval); + + if (tdi.trackInfo.slider.thumbDir == kThemeThumbPlain) { + // They asked for both, so we'll give it to them. + tdi.trackInfo.slider.thumbDir = kThemeThumbDownward; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + tdi.trackInfo.slider.thumbDir = kThemeThumbUpward; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + } else { + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + + } } } } |