Mac: refactor scrollbar animations
Get rid of QWidget-centric QMacStyle::eventFilter() and implement the fade out animations for scrollbars using QNumberStyleAnimation-based QFadeOutAnimation. Change-Id: I2000fa50d46b153e981ceafc12a53932a196382e Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
This commit is contained in:
parent
020196d16b
commit
3c2bfbff5f
@ -5016,6 +5016,9 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget
|
||||
case SH_RequestSoftwareInputPanel:
|
||||
ret = RSIP_OnMouseClickAndAlreadyFocused;
|
||||
break;
|
||||
case SH_ScrollBar_Transient:
|
||||
ret = false;
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
break;
|
||||
|
@ -125,9 +125,6 @@ public:
|
||||
Qt::Orientation orientation, const QStyleOption *option = 0,
|
||||
const QWidget *widget = 0) const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(QMacStyle)
|
||||
Q_DECLARE_PRIVATE(QMacStyle)
|
||||
|
@ -127,9 +127,9 @@ QMacStylePrivate *mPrivate;
|
||||
{
|
||||
Q_UNUSED(notification);
|
||||
QEvent event(QEvent::StyleChange);
|
||||
Q_FOREACH (const QObject* target, mPrivate->animationTargets()) {
|
||||
if (target)
|
||||
QCoreApplication::sendEvent(const_cast<QObject*>(target), &event);
|
||||
foreach (QWidget *widget, QApplication::allWidgets()) {
|
||||
if (QScrollBar *scrollBar = qobject_cast<QScrollBar *>(widget))
|
||||
QCoreApplication::sendEvent(scrollBar, &event);
|
||||
}
|
||||
}
|
||||
@end
|
||||
@ -178,38 +178,6 @@ 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
|
||||
@ -1678,24 +1646,6 @@ QMacStylePrivate::QMacStylePrivate()
|
||||
|
||||
}
|
||||
|
||||
bool QMacStylePrivate::addWidget(QWidget *w)
|
||||
{
|
||||
//already knew of it
|
||||
if (w == defaultButton || animation(w))
|
||||
return false;
|
||||
|
||||
Q_Q(QMacStyle);
|
||||
if (qobject_cast<QScrollBar *>(w)) {
|
||||
w->installEventFilter(q);
|
||||
return true;
|
||||
}
|
||||
if (w->isWindow()) {
|
||||
w->installEventFilter(q);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ThemeDrawState QMacStylePrivate::getDrawState(QStyle::State flags)
|
||||
{
|
||||
ThemeDrawState tds = kThemeStateActive;
|
||||
@ -1713,73 +1663,6 @@ ThemeDrawState QMacStylePrivate::getDrawState(QStyle::State flags)
|
||||
return tds;
|
||||
}
|
||||
|
||||
/*! \reimp */
|
||||
bool QMacStyle::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
//animate
|
||||
Q_D(QMacStyle);
|
||||
if (QScrollBar *sb = qobject_cast<QScrollBar *>(o)) {
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
|
||||
// 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) {
|
||||
QMacStylePrivate::OverlayScrollBarInfo& info = d->scrollBarInfos[sb];
|
||||
const qint64 dt = QDateTime::currentMSecsSinceEpoch();
|
||||
const qreal elapsed = dt - info.lastUpdate;
|
||||
const CGFloat opacity = 1.0 - qMax(0.0, (elapsed - QMacStylePrivate::ScrollBarFadeOutDelay)
|
||||
/ QMacStylePrivate::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 = dt;
|
||||
info.lastHovered = info.lastUpdate;
|
||||
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
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void QMacStylePrivate::drawColorlessButton(const HIRect &macRect, HIThemeButtonDrawInfo *bdi,
|
||||
QPainter *p, const QStyleOption *opt) const
|
||||
{
|
||||
@ -2006,8 +1889,6 @@ void QMacStyle::unpolish(QApplication *)
|
||||
|
||||
void QMacStyle::polish(QWidget* w)
|
||||
{
|
||||
Q_D(QMacStyle);
|
||||
d->addWidget(w);
|
||||
if (qt_mac_is_metal(w) && !w->testAttribute(Qt::WA_SetPalette)) {
|
||||
// Set a clear brush so that the metal shines through.
|
||||
QPalette pal = w->palette();
|
||||
@ -2059,13 +1940,13 @@ void QMacStyle::polish(QWidget* w)
|
||||
|
||||
if (qobject_cast<QScrollBar*>(w)) {
|
||||
w->setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
w->setAttribute(Qt::WA_Hover, true);
|
||||
w->setMouseTracking(true);
|
||||
}
|
||||
}
|
||||
|
||||
void QMacStyle::unpolish(QWidget* w)
|
||||
{
|
||||
Q_D(QMacStyle);
|
||||
if ((qobject_cast<QMenu*>(w) || qt_mac_is_metal(w)) && !w->testAttribute(Qt::WA_SetPalette)) {
|
||||
QPalette pal = qApp->palette(w);
|
||||
w->setPalette(pal);
|
||||
@ -2093,8 +1974,8 @@ void QMacStyle::unpolish(QWidget* w)
|
||||
|
||||
if (qobject_cast<QScrollBar*>(w)) {
|
||||
w->setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
w->setAttribute(Qt::WA_Hover, false);
|
||||
w->setMouseTracking(false);
|
||||
d->scrollBarInfos.remove(w);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2303,8 +2184,7 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW
|
||||
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))) {
|
||||
[NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay) {
|
||||
switch (d->aquaSizeConstrain(opt, widget)) {
|
||||
case QAquaSizeUnknown:
|
||||
case QAquaSizeLarge:
|
||||
@ -2831,6 +2711,12 @@ int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w
|
||||
case SH_DockWidget_ButtonsHaveFrame:
|
||||
ret = false;
|
||||
break;
|
||||
case SH_ScrollBar_Transient:
|
||||
ret = QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7;
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
|
||||
ret &= [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
ret = QCommonStyle::styleHint(sh, opt, w, hret);
|
||||
break;
|
||||
@ -5023,59 +4909,65 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
|
||||
}
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
|
||||
const qint64 dt = QDateTime::currentMSecsSinceEpoch();
|
||||
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 = dt;
|
||||
showSiblings = true;
|
||||
}
|
||||
if (cc == CC_ScrollBar && proxy()->styleHint(SH_ScrollBar_Transient)) {
|
||||
QObject *styleObject = opt->styleObject;
|
||||
int oldPos = styleObject->property("_q_stylepos").toInt();
|
||||
int oldMin = styleObject->property("_q_stylemin").toInt();
|
||||
int oldMax = styleObject->property("_q_stylemax").toInt();
|
||||
QRect oldRect = styleObject->property("_q_stylerect").toRect();
|
||||
int oldState = styleObject->property("_q_stylestate").toInt();
|
||||
uint oldActiveControls = styleObject->property("_q_stylecontrols").toUInt();
|
||||
|
||||
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 = dt;
|
||||
}
|
||||
// a scrollbar is transient when the the scrollbar itself and
|
||||
// its sibling are both inactive (ie. not pressed/hovered/moved)
|
||||
bool transient = !opt->activeSubControls && !(slider->state & State_On);
|
||||
|
||||
qreal elapsed = dt - info.lastHovered;
|
||||
CGFloat opacity = 1.0 - qMax(0.0,
|
||||
(elapsed - QMacStylePrivate::ScrollBarFadeOutDelay) /
|
||||
QMacStylePrivate::ScrollBarFadeOutDuration);
|
||||
const bool isHorizontal = slider->orientation == Qt::Horizontal;
|
||||
CGFloat opacity = 0.0;
|
||||
if (!transient ||
|
||||
oldPos != slider->sliderPosition ||
|
||||
oldMin != slider->minimum ||
|
||||
oldMax != slider->maximum ||
|
||||
oldRect != slider->rect ||
|
||||
oldState != slider->state ||
|
||||
oldActiveControls != slider->activeSubControls) {
|
||||
|
||||
if (info.hovered) {
|
||||
info.lastHovered = dt;
|
||||
info.lastUpdate = dt;
|
||||
// if the scrollbar is transient or its attributes, geometry or
|
||||
// state has changed, the opacity is reset back to 100% opaque
|
||||
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;
|
||||
|
||||
styleObject->setProperty("_q_stylepos", slider->sliderPosition);
|
||||
styleObject->setProperty("_q_stylemin", slider->minimum);
|
||||
styleObject->setProperty("_q_stylemax", slider->maximum);
|
||||
styleObject->setProperty("_q_stylerect", slider->rect);
|
||||
styleObject->setProperty("_q_stylestate", static_cast<int>(slider->state));
|
||||
styleObject->setProperty("_q_stylecontrols", static_cast<uint>(slider->activeSubControls));
|
||||
|
||||
if (transient) {
|
||||
QFadeOutAnimation *anim = qobject_cast<QFadeOutAnimation *>(d->animation(styleObject));
|
||||
if (!anim) {
|
||||
anim = new QFadeOutAnimation(styleObject);
|
||||
d->startAnimation(anim);
|
||||
} else {
|
||||
// the scrollbar was already fading out while the
|
||||
// state changed -> restart the fade out animation
|
||||
anim->setCurrentTime(0);
|
||||
}
|
||||
} else {
|
||||
d->stopAnimation(styleObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
QFadeOutAnimation *anim = qobject_cast<QFadeOutAnimation *>(d->animation(styleObject));
|
||||
if (anim) {
|
||||
// once a scrollbar was active (hovered/pressed), it retains
|
||||
// the active look even if it's no longer active while fading out
|
||||
if (oldActiveControls)
|
||||
anim->setActive(true);
|
||||
opacity = anim->currentValue();
|
||||
}
|
||||
|
||||
const bool isHorizontal = slider->orientation == Qt::Horizontal;
|
||||
|
||||
CGContextSaveGState(cg);
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
|
||||
@ -5085,7 +4977,7 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
|
||||
[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 QColor bgColor = opt->palette.color(QPalette::Base);
|
||||
const bool isDarkBg = bgColor.red() < 128 && bgColor.green() < 128 &&
|
||||
bgColor.blue() < 128;
|
||||
if (isDarkBg)
|
||||
@ -5102,7 +4994,7 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
|
||||
[scroller setScrollerStyle:NSScrollerStyleOverlay];
|
||||
|
||||
// first we draw only the track, by using a disabled scroller
|
||||
if (opacity > 0.0) {
|
||||
if (opt->activeSubControls || (anim && anim->wasActive())) {
|
||||
CGContextBeginTransparencyLayerWithRect(cg, qt_hirectForQRect(slider->rect),
|
||||
NULL);
|
||||
CGContextSetAlpha(cg, opacity);
|
||||
@ -5115,21 +5007,6 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
|
||||
CGContextEndTransparencyLayer(cg);
|
||||
}
|
||||
|
||||
// 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 = dt - info.lastUpdate;
|
||||
opacity = 1.0 - qMax(0.0, (elapsed - QMacStylePrivate::ScrollBarFadeOutDelay) /
|
||||
QMacStylePrivate::ScrollBarFadeOutDuration);
|
||||
info.cleared = opacity <= 0.0;
|
||||
|
||||
if (info.animating && info.cleared) {
|
||||
d->stopAnimation(slider->styleObject);
|
||||
info.animating = false;
|
||||
} else if (!info.animating && !info.cleared) {
|
||||
d->startAnimation(new QStyleAnimation(slider->styleObject));
|
||||
info.animating = true;
|
||||
}
|
||||
|
||||
CGContextBeginTransparencyLayerWithRect(cg, qt_hirectForQRect(slider->rect), NULL);
|
||||
CGContextSetAlpha(cg, opacity);
|
||||
|
||||
@ -5170,6 +5047,8 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
d->stopAnimation(opt->styleObject);
|
||||
|
||||
HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg,
|
||||
kHIThemeOrientationNormal);
|
||||
if (cc == CC_Slider && slider->subControls & SC_SliderTickmarks) {
|
||||
|
@ -158,9 +158,6 @@ public:
|
||||
static const qreal ScrollBarFadeOutDuration;
|
||||
static const qreal ScrollBarFadeOutDelay;
|
||||
|
||||
// Stuff from QAquaAnimate:
|
||||
bool addWidget(QWidget *);
|
||||
|
||||
enum Animates { AquaPushButton, AquaProgressBar, AquaListViewItemOpen, AquaScrollBar };
|
||||
static ThemeDrawState getDrawState(QStyle::State flags);
|
||||
QAquaWidgetSize aquaSizeConstrain(const QStyleOption *option, const QWidget *widg,
|
||||
@ -201,29 +198,6 @@ public:
|
||||
mutable QPointer<QObject> defaultButton;
|
||||
mutable QPointer<QObject> autoDefaultButton;
|
||||
|
||||
struct OverlayScrollBarInfo {
|
||||
OverlayScrollBarInfo()
|
||||
: lastValue(-1),
|
||||
lastMinimum(-1),
|
||||
lastMaximum(-1),
|
||||
lastUpdate(QDateTime::currentMSecsSinceEpoch()),
|
||||
hovered(false),
|
||||
lastHovered(0),
|
||||
cleared(false),
|
||||
animating(false)
|
||||
{}
|
||||
int lastValue;
|
||||
int lastMinimum;
|
||||
int lastMaximum;
|
||||
QSize lastSize;
|
||||
qint64 lastUpdate;
|
||||
bool hovered;
|
||||
qint64 lastHovered;
|
||||
bool cleared;
|
||||
bool animating;
|
||||
};
|
||||
mutable QMap<const QWidget*, OverlayScrollBarInfo> scrollBarInfos;
|
||||
|
||||
struct ButtonState {
|
||||
int frame;
|
||||
enum { ButtonDark, ButtonLight } dir;
|
||||
@ -237,6 +211,34 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
class QFadeOutAnimation : public QNumberStyleAnimation
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QFadeOutAnimation(QObject *target) : QNumberStyleAnimation(target), _active(false)
|
||||
{
|
||||
setDuration(QMacStylePrivate::ScrollBarFadeOutDelay + QMacStylePrivate::ScrollBarFadeOutDuration);
|
||||
setDelay(QMacStylePrivate::ScrollBarFadeOutDelay);
|
||||
setStartValue(1.0);
|
||||
setEndValue(0.0);
|
||||
}
|
||||
|
||||
bool wasActive() const { return _active; }
|
||||
void setActive(bool active) { _active = active; }
|
||||
|
||||
private slots:
|
||||
void updateCurrentTime(int time)
|
||||
{
|
||||
QNumberStyleAnimation::updateCurrentTime(time);
|
||||
if (qFuzzyIsNull(currentValue()))
|
||||
target()->setProperty("visible", false);
|
||||
}
|
||||
|
||||
private:
|
||||
bool _active;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QMACSTYLE_MAC_P_H
|
||||
|
@ -1887,6 +1887,9 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
|
||||
\value SH_RequestSoftwareInputPanel Determines when a software input panel should
|
||||
be requested by input widgets. Returns an enum of type QStyle::RequestSoftwareInputPanel.
|
||||
|
||||
\value SH_ScrollBar_Transient Determines if the style supports transient scroll bars. Transient
|
||||
scroll bars appear when the content is scrolled and disappear when they are no longer needed.
|
||||
|
||||
\sa styleHint()
|
||||
*/
|
||||
|
||||
|
@ -698,6 +698,7 @@ public:
|
||||
SH_DockWidget_ButtonsHaveFrame,
|
||||
SH_ToolButtonStyle,
|
||||
SH_RequestSoftwareInputPanel,
|
||||
SH_ScrollBar_Transient,
|
||||
// Add new style hint values here
|
||||
|
||||
SH_CustomBase = 0xf0000000
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "qabstractscrollarea_p.h"
|
||||
#include "qscrollbar_p.h"
|
||||
#include <qwidget.h>
|
||||
|
||||
#include <private/qapplication_p.h>
|
||||
@ -183,6 +184,7 @@ QAbstractScrollAreaScrollBarContainer::QAbstractScrollAreaScrollBarContainer(Qt:
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(scrollBar);
|
||||
layout->setSizeConstraint(QLayout::SetMaximumSize);
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
@ -266,6 +268,8 @@ void QAbstractScrollAreaPrivate::replaceScrollBar(QScrollBar *scrollBar,
|
||||
scrollBar->setSliderPosition(oldBar->sliderPosition());
|
||||
scrollBar->setTracking(oldBar->hasTracking());
|
||||
scrollBar->setValue(oldBar->value());
|
||||
scrollBar->installEventFilter(q);
|
||||
oldBar->removeEventFilter(q);
|
||||
delete oldBar;
|
||||
|
||||
QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
|
||||
@ -286,6 +290,7 @@ void QAbstractScrollAreaPrivate::init()
|
||||
hbar = scrollBarContainers[Qt::Horizontal]->scrollBar;
|
||||
hbar->setRange(0,0);
|
||||
scrollBarContainers[Qt::Horizontal]->setVisible(false);
|
||||
hbar->installEventFilter(q);
|
||||
QObject::connect(hbar, SIGNAL(valueChanged(int)), q, SLOT(_q_hslide(int)));
|
||||
QObject::connect(hbar, SIGNAL(rangeChanged(int,int)), q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection);
|
||||
scrollBarContainers[Qt::Vertical] = new QAbstractScrollAreaScrollBarContainer(Qt::Vertical, q);
|
||||
@ -293,6 +298,7 @@ void QAbstractScrollAreaPrivate::init()
|
||||
vbar = scrollBarContainers[Qt::Vertical]->scrollBar;
|
||||
vbar->setRange(0,0);
|
||||
scrollBarContainers[Qt::Vertical]->setVisible(false);
|
||||
vbar->installEventFilter(q);
|
||||
QObject::connect(vbar, SIGNAL(valueChanged(int)), q, SLOT(_q_vslide(int)));
|
||||
QObject::connect(vbar, SIGNAL(rangeChanged(int,int)), q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection);
|
||||
viewportFilter.reset(new QAbstractScrollAreaFilter(this));
|
||||
@ -323,10 +329,10 @@ void QAbstractScrollAreaPrivate::layoutChildren()
|
||||
{
|
||||
Q_Q(QAbstractScrollArea);
|
||||
bool needh = (hbarpolicy == Qt::ScrollBarAlwaysOn
|
||||
|| (hbarpolicy == Qt::ScrollBarAsNeeded && hbar->minimum() < hbar->maximum()));
|
||||
|| (hbarpolicy == Qt::ScrollBarAsNeeded && hbar->minimum() < hbar->maximum() && !hbar->sizeHint().isEmpty()));
|
||||
|
||||
bool needv = (vbarpolicy == Qt::ScrollBarAlwaysOn
|
||||
|| (vbarpolicy == Qt::ScrollBarAsNeeded && vbar->minimum() < vbar->maximum()));
|
||||
|| (vbarpolicy == Qt::ScrollBarAsNeeded && vbar->minimum() < vbar->maximum() && !vbar->sizeHint().isEmpty()));
|
||||
|
||||
QStyleOption opt(0);
|
||||
opt.init(q);
|
||||
@ -648,6 +654,7 @@ void QAbstractScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
|
||||
d->layoutChildren();
|
||||
if (oldPolicy != d->vbarpolicy)
|
||||
d->scrollBarPolicyChanged(Qt::Vertical, d->vbarpolicy);
|
||||
d->setScrollBarTransient(d->vbar, policy == Qt::ScrollBarAsNeeded);
|
||||
}
|
||||
|
||||
|
||||
@ -709,6 +716,7 @@ void QAbstractScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy polic
|
||||
d->layoutChildren();
|
||||
if (oldPolicy != d->hbarpolicy)
|
||||
d->scrollBarPolicyChanged(Qt::Horizontal, d->hbarpolicy);
|
||||
d->setScrollBarTransient(d->hbar, policy == Qt::ScrollBarAsNeeded);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -921,6 +929,20 @@ void QAbstractScrollArea::setViewportMargins(const QMargins &margins)
|
||||
margins.right(), margins.bottom());
|
||||
}
|
||||
|
||||
/*! \internal */
|
||||
bool QAbstractScrollArea::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
Q_D(QAbstractScrollArea);
|
||||
if ((o == d->hbar || o == d->vbar) && (e->type() == QEvent::HoverEnter || e->type() == QEvent::HoverLeave)) {
|
||||
Qt::ScrollBarPolicy policy = o == d->hbar ? d->vbarpolicy : d->hbarpolicy;
|
||||
if (policy == Qt::ScrollBarAsNeeded) {
|
||||
QScrollBar *sibling = o == d->hbar ? d->vbar : d->hbar;
|
||||
d->setScrollBarTransient(sibling, e->type() == QEvent::HoverLeave);
|
||||
}
|
||||
}
|
||||
return QFrame::eventFilter(o, e);
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn bool QAbstractScrollArea::event(QEvent *event)
|
||||
|
||||
@ -1421,12 +1443,26 @@ bool QAbstractScrollAreaPrivate::canStartScrollingAt( const QPoint &startPos )
|
||||
return true;
|
||||
}
|
||||
|
||||
void QAbstractScrollAreaPrivate::flashScrollBars()
|
||||
{
|
||||
if (hbarpolicy == Qt::ScrollBarAsNeeded)
|
||||
hbar->d_func()->flash();
|
||||
if (vbarpolicy == Qt::ScrollBarAsNeeded)
|
||||
vbar->d_func()->flash();
|
||||
}
|
||||
|
||||
void QAbstractScrollAreaPrivate::setScrollBarTransient(QScrollBar *scrollBar, bool transient)
|
||||
{
|
||||
scrollBar->d_func()->setTransient(transient);
|
||||
}
|
||||
|
||||
void QAbstractScrollAreaPrivate::_q_hslide(int x)
|
||||
{
|
||||
Q_Q(QAbstractScrollArea);
|
||||
int dx = xoffset - x;
|
||||
xoffset = x;
|
||||
q->scrollContentsBy(dx, 0);
|
||||
flashScrollBars();
|
||||
}
|
||||
|
||||
void QAbstractScrollAreaPrivate::_q_vslide(int y)
|
||||
@ -1435,6 +1471,7 @@ void QAbstractScrollAreaPrivate::_q_vslide(int y)
|
||||
int dy = yoffset - y;
|
||||
yoffset = y;
|
||||
q->scrollContentsBy(0, dy);
|
||||
flashScrollBars();
|
||||
}
|
||||
|
||||
void QAbstractScrollAreaPrivate::_q_showOrHideScrollBars()
|
||||
|
@ -96,6 +96,7 @@ protected:
|
||||
void setViewportMargins(int left, int top, int right, int bottom);
|
||||
void setViewportMargins(const QMargins &margins);
|
||||
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
bool event(QEvent *);
|
||||
virtual bool viewportEvent(QEvent *);
|
||||
|
||||
|
@ -92,6 +92,9 @@ public:
|
||||
virtual void scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy) {}
|
||||
bool canStartScrollingAt( const QPoint &startPos );
|
||||
|
||||
void flashScrollBars();
|
||||
void setScrollBarTransient(QScrollBar *scrollBar, bool transient);
|
||||
|
||||
void _q_hslide(int);
|
||||
void _q_vslide(int);
|
||||
void _q_showOrHideScrollBars();
|
||||
|
@ -331,7 +331,7 @@ bool QScrollArea::eventFilter(QObject *o, QEvent *e)
|
||||
if (o == d->widget && e->type() == QEvent::Resize)
|
||||
d->updateScrollBars();
|
||||
|
||||
return false;
|
||||
return QAbstractScrollArea::eventFilter(o, e);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -55,7 +55,7 @@
|
||||
#include "qaccessible.h"
|
||||
#endif
|
||||
#include <limits.h>
|
||||
#include "qabstractslider_p.h"
|
||||
#include "qscrollbar_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -201,26 +201,6 @@ QT_BEGIN_NAMESPACE
|
||||
\sa QScrollArea, QSlider, QDial, QSpinBox, {fowler}{GUI Design Handbook: Scroll Bar}, {Sliders Example}
|
||||
*/
|
||||
|
||||
class QScrollBarPrivate : public QAbstractSliderPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QScrollBar)
|
||||
public:
|
||||
QStyle::SubControl pressedControl;
|
||||
bool pointerOutsidePressedControl;
|
||||
|
||||
int clickOffset, snapBackPosition;
|
||||
|
||||
void activateControl(uint control, int threshold = 500);
|
||||
void stopRepeatAction();
|
||||
int pixelPosToRangeValue(int pos) const;
|
||||
void init();
|
||||
bool updateHoverControl(const QPoint &pos);
|
||||
QStyle::SubControl newHoverControl(const QPoint &pos);
|
||||
|
||||
QStyle::SubControl hoverControl;
|
||||
QRect hoverRect;
|
||||
};
|
||||
|
||||
bool QScrollBarPrivate::updateHoverControl(const QPoint &pos)
|
||||
{
|
||||
Q_Q(QScrollBar);
|
||||
@ -249,6 +229,29 @@ QStyle::SubControl QScrollBarPrivate::newHoverControl(const QPoint &pos)
|
||||
return hoverControl;
|
||||
}
|
||||
|
||||
void QScrollBarPrivate::setTransient(bool value)
|
||||
{
|
||||
Q_Q(QScrollBar);
|
||||
if (transient != value) {
|
||||
transient = value;
|
||||
if (transient) {
|
||||
if (q->isVisible() && q->style()->styleHint(QStyle::SH_ScrollBar_Transient))
|
||||
q->update();
|
||||
} else if (!q->isVisible()) {
|
||||
q->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QScrollBarPrivate::flash()
|
||||
{
|
||||
Q_Q(QScrollBar);
|
||||
if (!flashed && q->style()->styleHint(QStyle::SH_ScrollBar_Transient)) {
|
||||
flashed = true;
|
||||
q->show();
|
||||
}
|
||||
}
|
||||
|
||||
void QScrollBarPrivate::activateControl(uint control, int threshold)
|
||||
{
|
||||
QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
|
||||
@ -322,6 +325,8 @@ void QScrollBar::initStyleOption(QStyleOptionSlider *option) const
|
||||
option->upsideDown = d->invertedAppearance;
|
||||
if (d->orientation == Qt::Horizontal)
|
||||
option->state |= QStyle::State_Horizontal;
|
||||
if (d->flashed || !d->transient)
|
||||
option->state |= QStyle::State_On;
|
||||
}
|
||||
|
||||
|
||||
@ -379,6 +384,8 @@ void QScrollBarPrivate::init()
|
||||
invertedControls = true;
|
||||
pressedControl = hoverControl = QStyle::SC_None;
|
||||
pointerOutsidePressedControl = false;
|
||||
transient = q->style()->styleHint(QStyle::SH_ScrollBar_Transient);
|
||||
flashed = false;
|
||||
q->setFocusPolicy(Qt::NoFocus);
|
||||
QSizePolicy sp(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::Slider);
|
||||
if (orientation == Qt::Vertical)
|
||||
@ -476,6 +483,9 @@ bool QScrollBar::event(QEvent *event)
|
||||
if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
|
||||
d_func()->updateHoverControl(he->pos());
|
||||
break;
|
||||
case QEvent::StyleChange:
|
||||
d_func()->setTransient(style()->styleHint(QStyle::SH_ScrollBar_Transient));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -521,6 +531,10 @@ void QScrollBar::paintEvent(QPaintEvent *)
|
||||
opt.activeSubControls = (QStyle::SubControl)d->hoverControl;
|
||||
}
|
||||
style()->drawComplexControl(QStyle::CC_ScrollBar, &opt, &p, this);
|
||||
if (d->flashed && style()->styleHint(QStyle::SH_ScrollBar_Transient)) {
|
||||
d->flashed = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -83,6 +83,7 @@ protected:
|
||||
|
||||
|
||||
private:
|
||||
friend class QAbstractScrollAreaPrivate;
|
||||
friend Q_WIDGETS_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *scrollBar);
|
||||
|
||||
Q_DISABLE_COPY(QScrollBar)
|
||||
|
89
src/widgets/widgets/qscrollbar_p.h
Normal file
89
src/widgets/widgets/qscrollbar_p.h
Normal file
@ -0,0 +1,89 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSCROLLBAR_P_H
|
||||
#define QSCROLLBAR_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "private/qabstractslider_p.h"
|
||||
#include "qstyle.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QScrollBarPrivate : public QAbstractSliderPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QScrollBar)
|
||||
public:
|
||||
QStyle::SubControl pressedControl;
|
||||
bool pointerOutsidePressedControl;
|
||||
|
||||
int clickOffset, snapBackPosition;
|
||||
|
||||
void activateControl(uint control, int threshold = 500);
|
||||
void stopRepeatAction();
|
||||
int pixelPosToRangeValue(int pos) const;
|
||||
void init();
|
||||
bool updateHoverControl(const QPoint &pos);
|
||||
QStyle::SubControl newHoverControl(const QPoint &pos);
|
||||
|
||||
QStyle::SubControl hoverControl;
|
||||
QRect hoverRect;
|
||||
|
||||
bool transient;
|
||||
void setTransient(bool value);
|
||||
|
||||
bool flashed;
|
||||
void flash();
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QSCROLLBAR_P_H
|
@ -46,6 +46,7 @@ HEADERS += \
|
||||
widgets/qradiobutton.h \
|
||||
widgets/qrubberband.h \
|
||||
widgets/qscrollbar.h \
|
||||
widgets/qscrollbar_p.h \
|
||||
widgets/qscrollarea_p.h \
|
||||
widgets/qsizegrip.h \
|
||||
widgets/qslider.h \
|
||||
|
@ -1585,12 +1585,15 @@ void tst_QListWidget::task217070_scrollbarsAdjusted()
|
||||
v.setUniformItemSizes(true);
|
||||
v.resize(160,100);
|
||||
QTest::qWait(50);
|
||||
QScrollBar *hbar = v.horizontalScrollBar();
|
||||
QScrollBar *vbar = v.verticalScrollBar();
|
||||
QVERIFY(hbar && vbar);
|
||||
for(int f=150; f>90 ; f--) {
|
||||
v.resize(f,100);
|
||||
QTest::qWait(30);
|
||||
QVERIFY(v.verticalScrollBar()->isVisible());
|
||||
//the vertical scrollbar must not be visible.
|
||||
QVERIFY(!v.horizontalScrollBar()->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || vbar->isVisible());
|
||||
//the horizontal scrollbar must not be visible.
|
||||
QVERIFY(!hbar->isVisible());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2696,7 +2696,7 @@ void tst_QTableView::indexAt_data()
|
||||
<< 0 << 0; // expected
|
||||
|
||||
QTest::newRow("no hidden, no span, scroll (5,0), at (20,20)")
|
||||
<< 10 << 10 // dim
|
||||
<< 20 << 20 // dim
|
||||
<< 40 << 40 // size
|
||||
<< -1 << -1 // hide
|
||||
<< -1 << -1 // pos
|
||||
@ -2706,7 +2706,7 @@ void tst_QTableView::indexAt_data()
|
||||
<< 0 << 5; // expected
|
||||
|
||||
QTest::newRow("no hidden, no span, scroll (0,5), at (20,20)")
|
||||
<< 10 << 10 // dim
|
||||
<< 20 << 20 // dim
|
||||
<< 40 << 40 // size
|
||||
<< -1 << -1 // hide
|
||||
<< -1 << -1 // pos
|
||||
@ -2716,7 +2716,7 @@ void tst_QTableView::indexAt_data()
|
||||
<< 5 << 0; // expected
|
||||
|
||||
QTest::newRow("no hidden, no span, scroll (5,5), at (20,20)")
|
||||
<< 10 << 10 // dim
|
||||
<< 20 << 20 // dim
|
||||
<< 40 << 40 // size
|
||||
<< -1 << -1 // hide
|
||||
<< -1 << -1 // pos
|
||||
|
@ -2105,40 +2105,40 @@ void tst_QMdiArea::updateScrollBars()
|
||||
|
||||
QScrollBar *hbar = mdiArea.horizontalScrollBar();
|
||||
QVERIFY(hbar);
|
||||
QVERIFY(!hbar->isVisible());
|
||||
QVERIFY(hbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !hbar->isVisible());
|
||||
|
||||
QScrollBar *vbar = mdiArea.verticalScrollBar();
|
||||
QVERIFY(vbar);
|
||||
QVERIFY(!vbar->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !vbar->isVisible());
|
||||
|
||||
// Move sub-window 2 away.
|
||||
subWindow2->move(10000, 10000);
|
||||
qApp->processEvents();
|
||||
QVERIFY(hbar->isVisible());
|
||||
QVERIFY(vbar->isVisible());
|
||||
QVERIFY(hbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || hbar->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || vbar->isVisible());
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Maximize sub-window 1 and make sure we don't have any scroll bars.
|
||||
subWindow1->showMaximized();
|
||||
qApp->processEvents();
|
||||
QVERIFY(subWindow1->isMaximized());
|
||||
QVERIFY(!hbar->isVisible());
|
||||
QVERIFY(!vbar->isVisible());
|
||||
QVERIFY(hbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !hbar->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !vbar->isVisible());
|
||||
|
||||
// We still shouldn't get any scroll bars.
|
||||
mdiArea.resize(mdiArea.size() - QSize(20, 20));
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&mdiArea));
|
||||
qApp->processEvents();
|
||||
QVERIFY(subWindow1->isMaximized());
|
||||
QVERIFY(!hbar->isVisible());
|
||||
QVERIFY(!vbar->isVisible());
|
||||
QVERIFY(hbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !hbar->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || !vbar->isVisible());
|
||||
|
||||
// Restore sub-window 1 and make sure we have scroll bars again.
|
||||
subWindow1->showNormal();
|
||||
qApp->processEvents();
|
||||
QVERIFY(!subWindow1->isMaximized());
|
||||
QVERIFY(hbar->isVisible());
|
||||
QVERIFY(vbar->isVisible());
|
||||
QVERIFY(hbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || hbar->isVisible());
|
||||
QVERIFY(vbar->style()->styleHint(QStyle::SH_ScrollBar_Transient) || vbar->isVisible());
|
||||
if (i == 0) {
|
||||
// Now, do the same when the viewport is scrolled.
|
||||
hbar->setValue(1000);
|
||||
|
Loading…
x
Reference in New Issue
Block a user