summaryrefslogtreecommitdiffstats
path: root/src/widgets/styles/qmacstyle_mac.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/styles/qmacstyle_mac.mm')
-rw-r--r--src/widgets/styles/qmacstyle_mac.mm414
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);
+
+ }
}
}
}