BTabView: implement drawing tabs on all sides.
Signed-off-by: Adrien Destugues <[email protected]>
This commit is contained in:
committed by
Adrien Destugues
parent
173e8195a4
commit
cb13fce878
@@ -86,6 +86,13 @@ private:
|
||||
|
||||
class BTabView : public BView {
|
||||
public:
|
||||
enum tab_side {
|
||||
kLeftSide = 1 << 0,
|
||||
kRightSide = 1 << 1,
|
||||
kTopSide = 1 << 2,
|
||||
kBottomSide = 1 << 3
|
||||
};
|
||||
|
||||
BTabView(const char* name,
|
||||
button_width width = B_WIDTH_AS_USUAL,
|
||||
uint32 flags = B_FULL_UPDATE_ON_RESIZE
|
||||
@@ -165,6 +172,9 @@ public:
|
||||
virtual void SetBorder(border_style borderStyle);
|
||||
border_style Border() const;
|
||||
|
||||
virtual void SetTabSide(tab_side tabSide);
|
||||
tab_side TabSide() const;
|
||||
|
||||
BView* ContainerView() const;
|
||||
|
||||
int32 CountTabs() const;
|
||||
@@ -172,7 +182,6 @@ public:
|
||||
|
||||
private:
|
||||
// FBC padding and forbidden methods
|
||||
virtual void _ReservedTabView2();
|
||||
virtual void _ReservedTabView3();
|
||||
virtual void _ReservedTabView4();
|
||||
virtual void _ReservedTabView5();
|
||||
@@ -205,8 +214,9 @@ private:
|
||||
int32 fFocus;
|
||||
float fTabOffset;
|
||||
border_style fBorderStyle;
|
||||
tab_side fTabSide;
|
||||
|
||||
uint32 _reserved[10];
|
||||
uint32 _reserved[9];
|
||||
};
|
||||
|
||||
#endif // _TAB_VIEW_H
|
||||
|
||||
+278
-56
@@ -243,20 +243,74 @@ BTab::DrawFocusMark(BView* owner, BRect frame)
|
||||
owner->SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
|
||||
|
||||
float offset = IsSelected() ? 3 : 2;
|
||||
owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
|
||||
frame.bottom - offset),
|
||||
BPoint((frame.left + frame.right + width) / 2.0,
|
||||
frame.bottom - offset));
|
||||
switch (fTabView->TabSide()) {
|
||||
case BTabView::kTopSide:
|
||||
owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
|
||||
frame.bottom - offset),
|
||||
BPoint((frame.left + frame.right + width) / 2.0,
|
||||
frame.bottom - offset));
|
||||
break;
|
||||
case BTabView::kBottomSide:
|
||||
owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
|
||||
frame.top + offset),
|
||||
BPoint((frame.left + frame.right + width) / 2.0,
|
||||
frame.top + offset));
|
||||
break;
|
||||
case BTabView::kLeftSide:
|
||||
owner->StrokeLine(BPoint(frame.right - offset,
|
||||
(frame.top + frame.bottom - width) / 2.0),
|
||||
BPoint(frame.right - offset,
|
||||
(frame.top + frame.bottom + width) / 2.0));
|
||||
break;
|
||||
case BTabView::kRightSide:
|
||||
owner->StrokeLine(BPoint(frame.left + offset,
|
||||
(frame.top + frame.bottom - width) / 2.0),
|
||||
BPoint(frame.left + offset,
|
||||
(frame.top + frame.bottom + width) / 2.0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BTab::DrawLabel(BView* owner, BRect frame)
|
||||
{
|
||||
float rotation;
|
||||
BPoint center(frame.left + frame.Width() / 2,
|
||||
frame.top + frame.Height() / 2);
|
||||
switch (fTabView->TabSide()) {
|
||||
case BTabView::kTopSide:
|
||||
case BTabView::kBottomSide:
|
||||
rotation = 0.0f;
|
||||
break;
|
||||
case BTabView::kLeftSide:
|
||||
rotation = 270.0f;
|
||||
break;
|
||||
case BTabView::kRightSide:
|
||||
rotation = 90.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rotation != 0.0f) {
|
||||
// DrawLabel doesn't allow rendering rotated text
|
||||
// rotate frame first and BAffineTransform will handle the rotation
|
||||
// we can't give "unrotated" frame because it comes from
|
||||
// BTabView::TabFrame and it is also used to handle mouse clicks
|
||||
BRect originalFrame(frame);
|
||||
frame.top = center.y - originalFrame.Width() / 2;
|
||||
frame.bottom = center.y + originalFrame.Width() / 2;
|
||||
frame.left = center.x - originalFrame.Height() / 2;
|
||||
frame.right = center.x + originalFrame.Height() / 2;
|
||||
}
|
||||
|
||||
BAffineTransform transform;
|
||||
transform.RotateBy(center, rotation * M_PI / 180.0f);
|
||||
owner->SetTransform(transform);
|
||||
be_control_look->DrawLabel(owner, Label(), frame, frame,
|
||||
ui_color(B_PANEL_BACKGROUND_COLOR),
|
||||
IsEnabled() ? 0 : BPrivate::BControlLook::B_DISABLED,
|
||||
BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
|
||||
owner->SetTransform(BAffineTransform());
|
||||
}
|
||||
|
||||
|
||||
@@ -264,22 +318,47 @@ void
|
||||
BTab::DrawTab(BView* owner, BRect frame, tab_position position, bool full)
|
||||
{
|
||||
rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
|
||||
uint32 borders = BControlLook::B_TOP_BORDER
|
||||
| BControlLook::B_BOTTOM_BORDER;
|
||||
uint32 borders;
|
||||
if (fTabView->TabSide() == BTabView::kTopSide
|
||||
|| fTabView->TabSide() == BTabView::kBottomSide) {
|
||||
borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
|
||||
|
||||
if (frame.left == owner->Bounds().left)
|
||||
borders |= BControlLook::B_LEFT_BORDER;
|
||||
if (frame.left == owner->Bounds().left)
|
||||
borders |= BControlLook::B_LEFT_BORDER;
|
||||
|
||||
if (frame.right == owner->Bounds().right)
|
||||
borders |= BControlLook::B_RIGHT_BORDER;
|
||||
if (frame.right == owner->Bounds().right)
|
||||
borders |= BControlLook::B_RIGHT_BORDER;
|
||||
} else if (fTabView->TabSide() == BTabView::kLeftSide
|
||||
|| fTabView->TabSide() == BTabView::kRightSide) {
|
||||
borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
|
||||
|
||||
if (frame.top == owner->Bounds().top)
|
||||
borders |= BControlLook::B_TOP_BORDER;
|
||||
|
||||
if (frame.bottom == owner->Bounds().bottom)
|
||||
borders |= BControlLook::B_BOTTOM_BORDER;
|
||||
}
|
||||
|
||||
if (position == B_TAB_FRONT) {
|
||||
frame.bottom -= 1.0f;
|
||||
switch (fTabView->TabSide()) {
|
||||
case BTabView::kTopSide:
|
||||
frame.bottom -= 1.0f;
|
||||
break;
|
||||
case BTabView::kBottomSide:
|
||||
frame.top += 1.0f;
|
||||
break;
|
||||
case BTabView::kLeftSide:
|
||||
frame.right -= 1.0f;
|
||||
break;
|
||||
case BTabView::kRightSide:
|
||||
frame.left += 1.0f;
|
||||
break;
|
||||
}
|
||||
be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0,
|
||||
borders);
|
||||
borders, fTabView->TabSide());
|
||||
} else {
|
||||
be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0,
|
||||
borders);
|
||||
borders, fTabView->TabSide());
|
||||
}
|
||||
|
||||
DrawLabel(owner, frame);
|
||||
@@ -365,6 +444,9 @@ BTabView::BTabView(BMessage* archive)
|
||||
if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK)
|
||||
fBorderStyle = B_FANCY_BORDER;
|
||||
|
||||
if (archive->FindInt32("_TabSide", (int32*)&fTabSide) != B_OK)
|
||||
fTabSide = kTopSide;
|
||||
|
||||
int32 i = 0;
|
||||
BMessage tabMsg;
|
||||
|
||||
@@ -426,6 +508,8 @@ BTabView::Archive(BMessage* archive, bool deep) const
|
||||
result = archive->AddInt32("_sel", fSelection);
|
||||
if (result == B_OK && fBorderStyle != B_FANCY_BORDER)
|
||||
result = archive->AddInt32("_border_style", fBorderStyle);
|
||||
if (result == B_OK && fTabSide != kTopSide)
|
||||
result = archive->AddInt32("_TabSide", fTabSide);
|
||||
|
||||
if (result == B_OK && deep) {
|
||||
for (int32 i = 0; i < CountTabs(); i++) {
|
||||
@@ -642,9 +726,6 @@ BTabView::KeyDown(const char* bytes, int32 numBytes)
|
||||
void
|
||||
BTabView::MouseDown(BPoint where)
|
||||
{
|
||||
if (where.y > fTabHeight)
|
||||
return;
|
||||
|
||||
for (int32 i = 0; i < CountTabs(); i++) {
|
||||
if (TabFrame(i).Contains(where)
|
||||
&& i != Selection()) {
|
||||
@@ -800,28 +881,64 @@ BTabView::Draw(BRect updateRect)
|
||||
BRect
|
||||
BTabView::DrawTabs()
|
||||
{
|
||||
// draw an inactive tab frame behind all tabs
|
||||
BRect bounds(Bounds());
|
||||
bounds.bottom = fTabHeight;
|
||||
BRect tabsBounds;
|
||||
uint32 borders;
|
||||
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
|
||||
uint32 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
|
||||
if (fBorderStyle == B_NO_BORDER) {
|
||||
// removes left border that is an artifact of DrawInactiveTab()
|
||||
bounds.left -= 1;
|
||||
} else
|
||||
borders |= BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
|
||||
if (fTabSide == kTopSide || fTabSide == kBottomSide) {
|
||||
if (fTabSide == kTopSide)
|
||||
bounds.bottom = fTabHeight;
|
||||
else
|
||||
bounds.top = bounds.bottom - fTabHeight;
|
||||
tabsBounds = bounds;
|
||||
// make a copy for later
|
||||
|
||||
// DrawInactiveTab draws 2px border
|
||||
// draw a little wider tab frame to align B_PLAIN_BORDER with it
|
||||
if (fBorderStyle == B_PLAIN_BORDER) {
|
||||
bounds.left -= 1;
|
||||
bounds.right += 1;
|
||||
// draw an inactive tab frame behind all tabs
|
||||
borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
|
||||
if (fBorderStyle == B_NO_BORDER) {
|
||||
// removes left border that is an artifact of DrawInactiveTab()
|
||||
bounds.left -= 1;
|
||||
} else {
|
||||
borders |= BControlLook::B_LEFT_BORDER
|
||||
| BControlLook::B_RIGHT_BORDER;
|
||||
}
|
||||
|
||||
// DrawInactiveTab draws 2px border
|
||||
// draw a little wider tab frame to align B_PLAIN_BORDER with it
|
||||
if (fBorderStyle == B_PLAIN_BORDER) {
|
||||
bounds.left -= 1;
|
||||
bounds.right += 1;
|
||||
}
|
||||
} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
|
||||
if (fTabSide == kLeftSide)
|
||||
bounds.right = fTabHeight;
|
||||
else
|
||||
bounds.left = bounds.right - fTabHeight;
|
||||
tabsBounds = bounds;
|
||||
// make a copy for later
|
||||
|
||||
// draw an inactive tab frame behind all tabs
|
||||
borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
|
||||
if (fBorderStyle == B_NO_BORDER) {
|
||||
// removes top border that is an artifact of DrawInactiveTab()
|
||||
bounds.top -= 1;
|
||||
} else {
|
||||
borders |= BControlLook::B_TOP_BORDER
|
||||
| BControlLook::B_BOTTOM_BORDER;
|
||||
}
|
||||
|
||||
// DrawInactiveTab draws 2px border
|
||||
// draw a little wider tab frame to align B_PLAIN_BORDER with it
|
||||
if (fBorderStyle == B_PLAIN_BORDER) {
|
||||
bounds.top -= 1;
|
||||
bounds.bottom += 1;
|
||||
}
|
||||
}
|
||||
|
||||
be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0, borders);
|
||||
be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0,
|
||||
borders, fTabSide);
|
||||
|
||||
// draw the tabs on top of the inactive tab bounds
|
||||
float right = 0.0f;
|
||||
BRect activeTabFrame;
|
||||
int32 tabCount = CountTabs();
|
||||
for (int32 i = 0; i < tabCount; i++) {
|
||||
@@ -830,19 +947,29 @@ BTabView::DrawTabs()
|
||||
activeTabFrame = tabFrame;
|
||||
|
||||
TabAt(i)->DrawTab(this, tabFrame,
|
||||
i == fSelection ? B_TAB_FRONT : (i == 0) ? B_TAB_FIRST : B_TAB_ANY,
|
||||
i == fSelection ? B_TAB_FRONT :
|
||||
(i == 0) ? B_TAB_FIRST : B_TAB_ANY,
|
||||
i + 1 != fSelection);
|
||||
right = tabFrame.right;
|
||||
}
|
||||
|
||||
if (right < bounds.right) {
|
||||
float last = 0.0f;
|
||||
float lastTab = 0.0f;
|
||||
if (fTabSide == kTopSide || fTabSide == kBottomSide) {
|
||||
lastTab = TabFrame(tabCount - 1).right;
|
||||
last = bounds.right;
|
||||
tabsBounds.left = tabsBounds.right = lastTab;
|
||||
borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
|
||||
} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
|
||||
lastTab = TabFrame(tabCount - 1).bottom;
|
||||
last = bounds.bottom;
|
||||
tabsBounds.top = tabsBounds.bottom = lastTab;
|
||||
borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
|
||||
}
|
||||
|
||||
if (lastTab < last) {
|
||||
// draw a 1px right border on the last tab
|
||||
bounds = Bounds();
|
||||
bounds.left = bounds.right = right;
|
||||
bounds.bottom = fTabHeight;
|
||||
borders = BControlLook::B_TOP_BORDER;
|
||||
be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0,
|
||||
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
|
||||
be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0,
|
||||
borders, fTabSide);
|
||||
}
|
||||
|
||||
return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
|
||||
@@ -853,15 +980,32 @@ void
|
||||
BTabView::DrawBox(BRect selectedTabRect)
|
||||
{
|
||||
BRect rect(Bounds());
|
||||
rect.top = fTabHeight;
|
||||
uint32 bordersToDraw = BControlLook::B_ALL_BORDERS;
|
||||
switch (fTabSide) {
|
||||
case kTopSide:
|
||||
bordersToDraw &= ~BControlLook::B_TOP_BORDER;
|
||||
rect.top = fTabHeight;
|
||||
break;
|
||||
case kBottomSide:
|
||||
bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER;
|
||||
rect.bottom -= fTabHeight;
|
||||
break;
|
||||
case kLeftSide:
|
||||
bordersToDraw &= ~BControlLook::B_LEFT_BORDER;
|
||||
rect.left = fTabHeight;
|
||||
break;
|
||||
case kRightSide:
|
||||
bordersToDraw &= ~BControlLook::B_RIGHT_BORDER;
|
||||
rect.right -= fTabHeight;
|
||||
break;
|
||||
}
|
||||
|
||||
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
|
||||
if (fBorderStyle == B_FANCY_BORDER) {
|
||||
be_control_look->DrawGroupFrame(this, rect, rect, base,
|
||||
BControlLook::B_ALL_BORDERS & ~BControlLook::B_TOP_BORDER);
|
||||
} else if (fBorderStyle == B_PLAIN_BORDER) {
|
||||
if (fBorderStyle == B_FANCY_BORDER)
|
||||
be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw);
|
||||
else if (fBorderStyle == B_PLAIN_BORDER) {
|
||||
be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER,
|
||||
0, BControlLook::B_ALL_BORDERS & ~BControlLook::B_TOP_BORDER);
|
||||
0, bordersToDraw);
|
||||
} else
|
||||
; // B_NO_BORDER draws no box
|
||||
}
|
||||
@@ -876,6 +1020,7 @@ BTabView::TabFrame(int32 index) const
|
||||
float width = 100.0f;
|
||||
float height = fTabHeight;
|
||||
float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
|
||||
BRect bounds(Bounds());
|
||||
|
||||
switch (fTabWidthSetting) {
|
||||
case B_WIDTH_FROM_LABEL:
|
||||
@@ -885,9 +1030,25 @@ BTabView::TabFrame(int32 index) const
|
||||
x += StringWidth(TabAt(i)->Label()) + 20.0f;
|
||||
}
|
||||
|
||||
return BRect(offset + x, 0.0f,
|
||||
offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
|
||||
height);
|
||||
switch (fTabSide) {
|
||||
case kTopSide:
|
||||
return BRect(offset + x, 0.0f,
|
||||
offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
|
||||
height);
|
||||
case kBottomSide:
|
||||
return BRect(offset + x, bounds.bottom - height,
|
||||
offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
|
||||
bounds.bottom);
|
||||
case kLeftSide:
|
||||
return BRect(0.0f, offset + x, height, offset + x
|
||||
+ StringWidth(TabAt(index)->Label()) + 20.0f);
|
||||
case kRightSide:
|
||||
return BRect(bounds.right - height, offset + x,
|
||||
bounds.right, offset + x
|
||||
+ StringWidth(TabAt(index)->Label()) + 20.0f);
|
||||
default:
|
||||
return BRect();
|
||||
}
|
||||
}
|
||||
|
||||
case B_WIDTH_FROM_WIDEST:
|
||||
@@ -901,8 +1062,22 @@ BTabView::TabFrame(int32 index) const
|
||||
|
||||
case B_WIDTH_AS_USUAL:
|
||||
default:
|
||||
return BRect(offset + index * width, 0.0f,
|
||||
offset + index * width + width, height);
|
||||
switch (fTabSide) {
|
||||
case kTopSide:
|
||||
return BRect(offset + index * width, 0.0f,
|
||||
offset + index * width + width, height);
|
||||
case kBottomSide:
|
||||
return BRect(offset + index * width, bounds.bottom - height,
|
||||
offset + index * width + width, bounds.bottom);
|
||||
case kLeftSide:
|
||||
return BRect(0.0f, offset + index * width, height,
|
||||
offset + index * width + width);
|
||||
case kRightSide:
|
||||
return BRect(bounds.right - height, offset + index * width,
|
||||
bounds.right, offset + index * width + width);
|
||||
default:
|
||||
return BRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,6 +1328,24 @@ BTabView::Border() const
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BTabView::SetTabSide(tab_side tabSide)
|
||||
{
|
||||
if (fTabSide == tabSide)
|
||||
return;
|
||||
|
||||
fTabSide = tabSide;
|
||||
_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
|
||||
}
|
||||
|
||||
|
||||
BTabView::tab_side
|
||||
BTabView::TabSide() const
|
||||
{
|
||||
return fTabSide;
|
||||
}
|
||||
|
||||
|
||||
BView*
|
||||
BTabView::ContainerView() const
|
||||
{
|
||||
@@ -1188,6 +1381,7 @@ BTabView::_InitObject(bool layouted, button_width width)
|
||||
fFocus = -1;
|
||||
fTabOffset = 0.0f;
|
||||
fBorderStyle = B_FANCY_BORDER;
|
||||
fTabSide = kTopSide;
|
||||
|
||||
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
|
||||
SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
|
||||
@@ -1292,13 +1486,42 @@ BTabView::_LayoutContainerView(bool layouted)
|
||||
}
|
||||
BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
|
||||
if (layout != NULL) {
|
||||
layout->SetInsets(borderWidth, borderWidth + TabHeight()
|
||||
- topBorderOffset, borderWidth, borderWidth);
|
||||
float inset = borderWidth + TabHeight() - topBorderOffset;
|
||||
switch (fTabSide) {
|
||||
case kTopSide:
|
||||
layout->SetInsets(borderWidth, inset, borderWidth,
|
||||
borderWidth);
|
||||
break;
|
||||
case kBottomSide:
|
||||
layout->SetInsets(borderWidth, borderWidth, borderWidth,
|
||||
inset);
|
||||
break;
|
||||
case kLeftSide:
|
||||
layout->SetInsets(inset, borderWidth, borderWidth,
|
||||
borderWidth);
|
||||
break;
|
||||
case kRightSide:
|
||||
layout->SetInsets(borderWidth, borderWidth, inset,
|
||||
borderWidth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BRect bounds = Bounds();
|
||||
|
||||
bounds.top += TabHeight();
|
||||
switch (fTabSide) {
|
||||
case kTopSide:
|
||||
bounds.top += TabHeight();
|
||||
break;
|
||||
case kBottomSide:
|
||||
bounds.bottom -= TabHeight();
|
||||
break;
|
||||
case kLeftSide:
|
||||
bounds.left += TabHeight();
|
||||
break;
|
||||
case kRightSide:
|
||||
bounds.right -= TabHeight();
|
||||
break;
|
||||
}
|
||||
bounds.InsetBy(borderWidth, borderWidth);
|
||||
|
||||
fContainerView->MoveTo(bounds.left, bounds.top);
|
||||
@@ -1310,7 +1533,6 @@ BTabView::_LayoutContainerView(bool layouted)
|
||||
// #pragma mark - FBC and forbidden
|
||||
|
||||
|
||||
void BTabView::_ReservedTabView2() {}
|
||||
void BTabView::_ReservedTabView3() {}
|
||||
void BTabView::_ReservedTabView4() {}
|
||||
void BTabView::_ReservedTabView5() {}
|
||||
|
||||
Reference in New Issue
Block a user