From 10c6f015f45092040c281bb90a65179f598a00b1 Mon Sep 17 00:00:00 2001 From: Christoph Schleifenbaum Date: Wed, 11 Apr 2012 20:12:48 +0200 Subject: Support for overlay-scrollbars on Mac On Lion scroll bars are within the scroll area itself and are not being shown as long as the user is not scrolling. This patch draws the new scroll bars and makes them fade away. Further it introduces a pixel metric checking for this behaviour. It's used by QAbstractScrollArea to put the viewport to the correct place. Task-number: QTBUG-21673 Change-Id: Id530265043549318ac420b392de6b8642deaa4c6 Reviewed-by: Sean Harmer Reviewed-by: Gabriel de Dietrich --- src/widgets/styles/qcommonstyle.cpp | 3 + src/widgets/styles/qmacstyle_mac.mm | 414 ++++++++++++++++++++++++++-- src/widgets/styles/qmacstyle_mac_p.h | 31 ++- src/widgets/styles/qstyle.cpp | 2 + src/widgets/styles/qstyle.h | 1 + src/widgets/widgets/qabstractscrollarea.cpp | 38 ++- 6 files changed, 449 insertions(+), 40 deletions(-) diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 3de94a8f17..8e750735f6 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -4444,6 +4444,9 @@ int QCommonStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWid case PM_ScrollView_ScrollBarSpacing: ret = 2 * proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); break; + case PM_ScrollView_ScrollBarOverlap: + ret = 0; + break; case PM_SubMenuOverlap: ret = -proxy()->pixelMetric(QStyle::PM_MenuPanelWidth, opt, widget); break; 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 #include #include +#include #include #include #include @@ -102,6 +103,35 @@ #include #include +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 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(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 scrollBarsSiblings(const QScrollBar *sb) +{ + const QAbstractScrollArea *sa = scrollBarsScrollArea(sb); + Q_ASSERT(sa != 0); + QList list = sa->findChildren(); + 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(w)))) return true; + } else if (as == AquaScrollBar) { + if (scrollBars.contains((const_cast(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(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(w) == defaultButton - || progressBars.contains(static_cast(w))) + || progressBars.contains(static_cast(w)) + || scrollBars.contains(static_cast(w))) return false; if (QPushButton *btn = qobject_cast(w)) { @@ -1689,6 +1762,12 @@ bool QMacStylePrivate::addWidget(QWidget *w) startAnimate(AquaProgressBar, w); return true; } + bool isScrollBar = (qobject_cast(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(w)) { stopAnimate(AquaProgressBar, w); + } else if (qobject_cast(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(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(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(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(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(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(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(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(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(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(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 siblings = + scrollBarsSiblings(qobject_cast(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(sibling)->update(); + } + + CGContextSaveGState(cg); + + [NSGraphicsContext setCurrentContext:[NSGraphicsContext + graphicsContextWithGraphicsPort:(CGContextRef)cg flipped:NO]]; + NSScroller *scroller = reinterpret_cast(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(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(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); + + } } } } diff --git a/src/widgets/styles/qmacstyle_mac_p.h b/src/widgets/styles/qmacstyle_mac_p.h index 8f7edd51d5..260411851f 100644 --- a/src/widgets/styles/qmacstyle_mac_p.h +++ b/src/widgets/styles/qmacstyle_mac_p.h @@ -155,13 +155,14 @@ public: static const int BevelButtonW; static const int BevelButtonH; static const int PushButtonContentPadding; - + static const qreal ScrollBarFadeOutDuration; + static const qreal ScrollBarFadeOutDelay; // Stuff from QAquaAnimate: bool addWidget(QWidget *); void removeWidget(QWidget *); - enum Animates { AquaPushButton, AquaProgressBar, AquaListViewItemOpen }; + enum Animates { AquaPushButton, AquaProgressBar, AquaListViewItemOpen, AquaScrollBar }; bool animatable(Animates, const QWidget *) const; void stopAnimate(Animates, QWidget *); void startAnimate(Animates, QWidget *); @@ -210,6 +211,28 @@ public: QPointer defaultButton; //default push buttons int timerID; QList > progressBars; //existing progress bars that need animation + QList > scrollBars; //existing scroll bars that need animation + + struct OverlayScrollBarInfo { + OverlayScrollBarInfo() + : lastValue(-1), + lastMinimum(-1), + lastMaximum(-1), + lastUpdate(QDateTime::currentDateTime()), + hovered(false), + lastHovered(QDateTime::fromTime_t(0)), + cleared(false) + {} + int lastValue; + int lastMinimum; + int lastMaximum; + QSize lastSize; + QDateTime lastUpdate; + bool hovered; + QDateTime lastHovered; + bool cleared; + }; + QMap scrollBarInfos; struct ButtonState { int frame; @@ -220,6 +243,10 @@ public: CFAbsoluteTime defaultButtonStart; QMacStyle *q; bool mouseDown; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + void* receiver; + void *nsscroller; +#endif }; QT_END_NAMESPACE diff --git a/src/widgets/styles/qstyle.cpp b/src/widgets/styles/qstyle.cpp index d218e855d4..ba70dafd90 100644 --- a/src/widgets/styles/qstyle.cpp +++ b/src/widgets/styles/qstyle.cpp @@ -1497,6 +1497,8 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, \value PM_ScrollView_ScrollBarSpacing Distance between frame and scrollbar with SH_ScrollView_FrameOnlyAroundContents set. + \value PM_ScrollView_ScrollBarOverlap Overlap between scroll bars and scroll content + \value PM_SubMenuOverlap The horizontal overlap between a submenu and its parent. diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h index f56919a37f..9be063ca03 100644 --- a/src/widgets/styles/qstyle.h +++ b/src/widgets/styles/qstyle.h @@ -553,6 +553,7 @@ public: PM_TabCloseIndicatorHeight, PM_ScrollView_ScrollBarSpacing, + PM_ScrollView_ScrollBarOverlap, PM_SubMenuOverlap, // do not add any values below/greater than this diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp index d97b03128e..faa6933e75 100644 --- a/src/widgets/widgets/qabstractscrollarea.cpp +++ b/src/widgets/widgets/qabstractscrollarea.cpp @@ -52,6 +52,7 @@ #include "qboxlayout.h" #include "qpainter.h" #include "qmargins.h" +#include "qheaderview.h" #include @@ -327,6 +328,11 @@ void QAbstractScrollAreaPrivate::layoutChildren() bool needv = (vbarpolicy == Qt::ScrollBarAlwaysOn || (vbarpolicy == Qt::ScrollBarAsNeeded && vbar->minimum() < vbar->maximum())); + QStyleOption opt(0); + opt.init(q); + const int scrollOverlap = q->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarOverlap, + &opt, q); + #ifdef Q_WS_MAC QWidget * const window = q->window(); @@ -365,8 +371,6 @@ void QAbstractScrollAreaPrivate::layoutChildren() const QSize extSize(vsbExt, hsbExt); const QRect widgetRect = q->rect(); - QStyleOption opt(0); - opt.init(q); const bool hasCornerWidget = (cornerWidget != 0); @@ -394,7 +398,7 @@ void QAbstractScrollAreaPrivate::layoutChildren() } #endif - QPoint cornerOffset(needv ? vsbExt : 0, needh ? hsbExt : 0); + QPoint cornerOffset((needv && scrollOverlap == 0) ? vsbExt : 0, (needh && scrollOverlap == 0) ? hsbExt : 0); QRect controlsRect; QRect viewportRect; @@ -403,7 +407,7 @@ void QAbstractScrollAreaPrivate::layoutChildren() if ((frameStyle != QFrame::NoFrame) && q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, q)) { controlsRect = widgetRect; - const int extra = q->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &opt, q); + const int extra = scrollOverlap; const QPoint cornerExtra(needv ? extra : 0, needh ? extra : 0); QRect frameRect = widgetRect; frameRect.adjust(0, 0, -cornerOffset.x() - cornerExtra.x(), -cornerOffset.y() - cornerExtra.y()); @@ -418,9 +422,11 @@ void QAbstractScrollAreaPrivate::layoutChildren() viewportRect = QRect(controlsRect.topLeft(), controlsRect.bottomRight() - cornerOffset); } + cornerOffset = QPoint(needv ? vsbExt : 0, needh ? hsbExt : 0); + // If we have a corner widget and are only showing one scroll bar, we need to move it // to make room for the corner widget. - if (hasCornerWidget && (needv || needh)) + if (hasCornerWidget && (needv || needh) && scrollOverlap == 0) cornerOffset = extPoint; #ifdef Q_WS_MAC @@ -436,7 +442,7 @@ void QAbstractScrollAreaPrivate::layoutChildren() // Some styles paints the corner if both scorllbars are showing and there is // no corner widget. Also, on the Mac we paint if there is a native // (transparent) sizegrip in the area where a corner widget would be. - if ((needv && needh && hasCornerWidget == false) + if ((needv && needh && hasCornerWidget == false && scrollOverlap == 0) || ((needv || needh) #ifdef Q_WS_MAC && hasMacSizeGrip @@ -455,8 +461,24 @@ void QAbstractScrollAreaPrivate::layoutChildren() reverseCornerPaintingRect = QRect(); #endif + // move the scrollbars away from top/left headers + int vHeaderRight = 0; + int hHeaderBottom = 0; + if (scrollOverlap > 0 && (needv || needh)) { + const QList headers = q->findChildren(); + if (headers.count() <= 2) { + Q_FOREACH (const QHeaderView *header, headers) { + const QRect geo = header->geometry(); + if (header->orientation() == Qt::Vertical && header->isVisible() && QStyle::visualRect(opt.direction, opt.rect, geo).left() <= opt.rect.width() / 2) + vHeaderRight = QStyle::visualRect(opt.direction, opt.rect, geo).right(); + else if (header->orientation() == Qt::Horizontal && header->isVisible() && geo.top() <= q->frameWidth()) + hHeaderBottom = geo.bottom(); + } + } + } + if (needh) { - QRect horizontalScrollBarRect(QPoint(controlsRect.left(), cornerPoint.y()), QPoint(cornerPoint.x() - 1, controlsRect.bottom())); + QRect horizontalScrollBarRect(QPoint(controlsRect.left() + vHeaderRight, cornerPoint.y()), QPoint(cornerPoint.x() - 1, controlsRect.bottom())); #ifdef Q_WS_MAC if (hasMacReverseSizeGrip) horizontalScrollBarRect.adjust(vsbExt, 0, 0, 0); @@ -466,7 +488,7 @@ void QAbstractScrollAreaPrivate::layoutChildren() } if (needv) { - const QRect verticalScrollBarRect (QPoint(cornerPoint.x(), controlsRect.top()), QPoint(controlsRect.right(), cornerPoint.y() - 1)); + const QRect verticalScrollBarRect (QPoint(cornerPoint.x(), controlsRect.top() + hHeaderBottom), QPoint(controlsRect.right(), cornerPoint.y() - 1)); scrollBarContainers[Qt::Vertical]->setGeometry(QStyle::visualRect(opt.direction, opt.rect, verticalScrollBarRect)); scrollBarContainers[Qt::Vertical]->raise(); } -- cgit v1.2.3