qtquick: Ported the old DockWidgetQuick to the new architecture
Mostly anyway. Facing several blockers that need to be ported first, for example DockWidget::setWidget() is still receiving QWidget
This commit is contained in:
@@ -210,6 +210,8 @@ set(KDDW_QTQUICK_FRONTEND_SRCS
|
||||
qtquick/TestHelpers_qtquick.cpp
|
||||
qtquick/LayoutSaverInstantiator.cpp
|
||||
qtquick/LayoutSaverInstantiator.h
|
||||
# qtquick/views/DockWidget_qtquick.cpp
|
||||
# qtquick/views/DockWidget_qtquick.h
|
||||
qtquick/views/Frame_qtquick.cpp
|
||||
qtquick/views/Frame_qtquick.h
|
||||
qtquick/views/View_qtquick.cpp
|
||||
|
||||
@@ -10,9 +10,15 @@
|
||||
*/
|
||||
|
||||
#include "DockWidget_qtquick.h"
|
||||
#include "FrameworkWidgetFactory.h"
|
||||
|
||||
#include "controllers/TitleBar.h"
|
||||
#include "controllers/DockWidget.h"
|
||||
#include "controllers/Frame.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <QQuickItem>
|
||||
#include <QCloseEvent>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
* @file
|
||||
@@ -22,32 +28,34 @@
|
||||
*/
|
||||
|
||||
using namespace KDDockWidgets;
|
||||
using namespace KDDockWidgets::Controllers;
|
||||
using namespace KDDockWidgets::Views;
|
||||
|
||||
class DockWidget_qtquick::Private
|
||||
{
|
||||
public:
|
||||
Private(DockWidget_qtquick *q, Controllers::DockWidget *controller)
|
||||
: layout(new QVBoxLayout(q))
|
||||
, m_controller(controller)
|
||||
Private(DockWidget_qtquick *view, QQmlEngine *qmlengine)
|
||||
: q(view->m_controller)
|
||||
, m_visualItem(q->createItem(qmlengine,
|
||||
Config::self().frameworkWidgetFactory()->dockwidgetFilename().toString()))
|
||||
, m_qmlEngine(qmlengine)
|
||||
{
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// propagate the max-size constraints from the guest widget to the DockWidget
|
||||
layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
Q_ASSERT(m_visualItem);
|
||||
m_visualItem->setParent(view);
|
||||
m_visualItem->setParentItem(view);
|
||||
}
|
||||
|
||||
QVBoxLayout *const layout;
|
||||
Controllers::DockWidget *const m_controller;
|
||||
Controllers::DockWidget *const q;
|
||||
QQuickItem *const m_visualItem;
|
||||
QQmlEngine *const m_qmlEngine;
|
||||
};
|
||||
|
||||
DockWidget_qtquick::DockWidget_qtquick(Controllers::DockWidget *controller,
|
||||
Qt::WindowFlags windowFlags)
|
||||
: View_qtquick<QWidget>(controller, Type::DockWidget, nullptr, windowFlags)
|
||||
, d(new Private(this, controller))
|
||||
Qt::WindowFlags windowFlags, QQmlEngine *engine)
|
||||
: View_qtquick(controller, Type::DockWidget, nullptr, windowFlags)
|
||||
, d(new Private(this, engine ? engine : Config::self().qmlEngine()))
|
||||
{
|
||||
// To mimic what QtWidgets does when creating a new QWidget.
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
DockWidget_qtquick::~DockWidget_qtquick()
|
||||
@@ -55,38 +63,105 @@ DockWidget_qtquick::~DockWidget_qtquick()
|
||||
delete d;
|
||||
}
|
||||
|
||||
void DockWidget_qtquick::init()
|
||||
void DockWidget_qtquick::setWidget(const QString &qmlFilename)
|
||||
{
|
||||
connect(d->m_controller, &DockWidgetBase::widgetChanged, this, [this](QWidget *w) {
|
||||
d->layout->addWidget(w);
|
||||
});
|
||||
QQuickItem *guest = createItem(d->m_qmlEngine, qmlFilename);
|
||||
if (!guest)
|
||||
return;
|
||||
|
||||
setWidget(guest);
|
||||
}
|
||||
|
||||
Controllers::DockWidget *DockWidget_qtquick::dockWidget() const
|
||||
void DockWidget_qtquick::setWidget(QWidgetAdapter *widget)
|
||||
{
|
||||
return d->m_controller;
|
||||
widget->QWidgetAdapter::setParent(this);
|
||||
makeItemFillParent(widget);
|
||||
DockWidget::setWidget(widget);
|
||||
}
|
||||
|
||||
void DockWidget_qtquick::setWidget(QQuickItem *guest)
|
||||
{
|
||||
auto adapter = new View_qtquick(nullptr, Type::None, this);
|
||||
adapter->setIsWrapper();
|
||||
|
||||
// In case the user app needs to use them:
|
||||
adapter->setProperty("originalParent", QVariant::fromValue(guest->parent()));
|
||||
adapter->setProperty("originalParentItem", QVariant::fromValue(guest->parentItem()));
|
||||
|
||||
guest->setParentItem(adapter);
|
||||
guest->setParent(adapter);
|
||||
makeItemFillParent(guest);
|
||||
|
||||
setWidget(adapter);
|
||||
}
|
||||
|
||||
bool DockWidget_qtquick::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::ParentChange) {
|
||||
d->m_controller->onParentChanged();
|
||||
d->q->onParentChanged();
|
||||
Q_EMIT d->q->actualTitleBarChanged();
|
||||
} else if (e->type() == QEvent::Show) {
|
||||
d->m_controller->onShown(e->spontaneous());
|
||||
d->q->onShown(e->spontaneous());
|
||||
} else if (e->type() == QEvent::Hide) {
|
||||
d->m_controller->onHidden(e->spontaneous());
|
||||
d->q->onHidden(e->spontaneous());
|
||||
} else if (e->type() == QEvent::Close) {
|
||||
d->q->onCloseEvent(static_cast<QCloseEvent *>(e));
|
||||
}
|
||||
|
||||
return QWidget::event(e);
|
||||
return DockWidget_qtquick::event(e);
|
||||
}
|
||||
|
||||
void DockWidget_qtquick::closeEvent(QCloseEvent *e)
|
||||
QSize DockWidget_qtquick::minSize() const
|
||||
{
|
||||
d->m_controller->onCloseEvent(e);
|
||||
if (auto guestWidget = widget()) {
|
||||
// The guests min-size is the same as the widget's, there's no spacing or margins.
|
||||
return guestWidget->minimumSize();
|
||||
}
|
||||
|
||||
return View_qtquick::minSize();
|
||||
}
|
||||
|
||||
void DockWidget_qtquick::resizeEvent(QResizeEvent *e)
|
||||
QSize DockWidget_qtquick::maximumSize() const
|
||||
{
|
||||
d->m_controller->onResize(e->size());
|
||||
return QWidget::resizeEvent(e);
|
||||
if (auto guestWidget = widget()) {
|
||||
// The guests max-size is the same as the widget's, there's no spacing or margins.
|
||||
return guestWidget->maximumSize();
|
||||
}
|
||||
|
||||
return View_qtquick::maximumSize();
|
||||
}
|
||||
|
||||
QObject *DockWidget_qtquick::actualTitleBar() const
|
||||
{
|
||||
if (Controllers::Frame *frame = d->q->d->frame())
|
||||
return frame->actualTitleBar();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject *DockWidget_qtquick::actualTitleBarObj() const
|
||||
{
|
||||
return actualTitleBar();
|
||||
}
|
||||
|
||||
QQuickItem *DockWidget_qtquick::frameVisualItem() const
|
||||
{
|
||||
if (Controllers::Frame *frame = d->q->d->frame()) {
|
||||
return frame->visualItem();
|
||||
}
|
||||
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DockWidget_qtquick::onGeometryUpdated()
|
||||
{
|
||||
if (auto frame = qobject_cast<FrameQuick *>(DockWidgetBase::d->frame())) {
|
||||
frame->updateConstriants();
|
||||
frame->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
Frame *DockWidget_qtquick::frame() const
|
||||
{
|
||||
return qobject_cast<FrameQuick *>(DockWidgetBase::d->frame());
|
||||
}
|
||||
|
||||
@@ -16,51 +16,101 @@
|
||||
* @author Sérgio Martins \<sergio.martins@kdab.com\>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef KD_DOCKWIDGET_QUICK_H
|
||||
#define KD_DOCKWIDGET_QUICK_H
|
||||
|
||||
#include "controllers/DockWidget.h"
|
||||
#include "View_qtquick.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QCloseEvent;
|
||||
class QQmlEngine;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KDDockWidgets {
|
||||
|
||||
namespace Views {
|
||||
namespace Controllers {
|
||||
class Frame;
|
||||
class TitleBar;
|
||||
}
|
||||
|
||||
namespace Views {
|
||||
/**
|
||||
* @brief Represents a dock widget.
|
||||
*
|
||||
* Most of the interface lives in DockWidgetBase, to facilitate sharing with QtQuick.
|
||||
*/
|
||||
class DOCKS_EXPORT DockWidget_qtquick : public View_qtquick<QQuickItem>
|
||||
class DOCKS_EXPORT DockWidget_qtquick : public Views::View_qtquick
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QObject *actualTitleBar READ actualTitleBarObj NOTIFY actualTitleBarChanged)
|
||||
public:
|
||||
/**
|
||||
* @brief constructs a new DockWidget
|
||||
* @param uniqueName Mandatory name that should be unique between all DockWidget instances.
|
||||
* This name won't be user visible and just used internally for the save/restore.
|
||||
* Use setTitle() for user visible text.
|
||||
* @param uniqueName the name of the dockwidget, should be unique. Use title for user visible text.
|
||||
* @param options optional options controlling behaviour
|
||||
* @param layoutSaverOptions options regarding LayoutSaver behaviour
|
||||
* @param engine the QML engine this dock widget will be created on. If not specified then
|
||||
* Config::self().qmlEngine() will be used
|
||||
*
|
||||
* There's no parent argument. The DockWidget is either parented to FloatingWindow or MainWindow
|
||||
* when visible, or stays without a parent when hidden. This allows to support docking
|
||||
* to different main windows.
|
||||
* when visible, or stays without a parent when hidden.
|
||||
*/
|
||||
explicit DockWidget_qtquick(Controllers::DockWidget *controller,
|
||||
Qt::WindowFlags windowFlags = {});
|
||||
Qt::WindowFlags windowFlags = {},
|
||||
QQmlEngine *engine = nullptr);
|
||||
|
||||
///@brief destructor
|
||||
~DockWidget_qtquick() override;
|
||||
|
||||
Controllers::DockWidget *dockWidget() const;
|
||||
/// Sets the DockWidget's guest item
|
||||
/// Similar to DockWidgetBase::setWidget(QQuickItem*)
|
||||
void setWidget(const QString &qmlFilename);
|
||||
|
||||
/// @reimp
|
||||
// void setWidget(QQuickItem *widget);
|
||||
|
||||
/// @reimp
|
||||
Q_INVOKABLE void setWidget(QQuickItem *widget);
|
||||
|
||||
/// @reimp
|
||||
QSize minSize() const override;
|
||||
|
||||
/// @reimp
|
||||
QSize maximumSize() const override;
|
||||
|
||||
/// @brief Returns the title bar
|
||||
QObject *actualTitleBar() const;
|
||||
|
||||
/// @brief Returns the title bar
|
||||
/// Qt6 requires us to include TitleBar_p.h, so instead the Q_PROPERTY uses
|
||||
/// QObject so we don't include private headers in public headers
|
||||
QObject *actualTitleBarObj() const;
|
||||
|
||||
/// @brief Returns the visual item which represents Frame in the screen
|
||||
/// Equivalent to Frame::visualItem().
|
||||
QQuickItem *frameVisualItem() const;
|
||||
|
||||
///@internal
|
||||
Q_INVOKABLE KDDockWidgets::Controllers::Frame *frame() const;
|
||||
|
||||
/// @brief Called by QtQuick when min-size changes
|
||||
Q_INVOKABLE void onGeometryUpdated();
|
||||
|
||||
Q_SIGNALS:
|
||||
/// @brief The geometry of the frame container this dock widget is in changed
|
||||
/// For example, when dragging a dockwidget
|
||||
void frameGeometryChanged(QRect);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
private:
|
||||
|
||||
class Private;
|
||||
Private *const d;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
This file is part of KDDockWidgets.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sérgio Martins <sergio.martins@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#include "DockWidgetQuick.h"
|
||||
#include "FrameworkWidgetFactory.h"
|
||||
|
||||
#include "private/TitleBar_p.h"
|
||||
#include "private/DockWidget_p.h"
|
||||
#include "private/quick/FrameQuick_p.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <QQuickItem>
|
||||
#include <QCloseEvent>
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Represents a dock widget.
|
||||
*
|
||||
* @author Sérgio Martins \<sergio.martins@kdab.com\>
|
||||
*/
|
||||
|
||||
using namespace KDDockWidgets;
|
||||
|
||||
class DockWidgetQuick::Private
|
||||
{
|
||||
public:
|
||||
Private(DockWidgetQuick *dw, QQmlEngine *qmlengine)
|
||||
: q(dw)
|
||||
, m_visualItem(q->createItem(qmlengine,
|
||||
Config::self().frameworkWidgetFactory()->dockwidgetFilename().toString()))
|
||||
, m_qmlEngine(qmlengine)
|
||||
{
|
||||
Q_ASSERT(m_visualItem);
|
||||
m_visualItem->setParent(q);
|
||||
m_visualItem->setParentItem(q);
|
||||
}
|
||||
|
||||
DockWidgetBase *const q;
|
||||
QQuickItem *const m_visualItem;
|
||||
QQmlEngine *const m_qmlEngine;
|
||||
};
|
||||
|
||||
DockWidgetQuick::DockWidgetQuick(const QString &name, Options options,
|
||||
LayoutSaverOptions layoutSaverOptions, QQmlEngine *engine)
|
||||
: DockWidgetBase(name, options, layoutSaverOptions)
|
||||
, d(new Private(this, engine ? engine : Config::self().qmlEngine()))
|
||||
{
|
||||
// To mimic what QtWidgets does when creating a new QWidget.
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
DockWidgetQuick::~DockWidgetQuick()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
void DockWidgetQuick::setWidget(const QString &qmlFilename)
|
||||
{
|
||||
QQuickItem *guest = createItem(d->m_qmlEngine, qmlFilename);
|
||||
if (!guest)
|
||||
return;
|
||||
|
||||
setWidget(guest);
|
||||
}
|
||||
|
||||
void DockWidgetQuick::setWidget(QWidgetAdapter *widget)
|
||||
{
|
||||
widget->QWidgetAdapter::setParent(this);
|
||||
QWidgetAdapter::makeItemFillParent(widget);
|
||||
DockWidgetBase::setWidget(widget);
|
||||
}
|
||||
|
||||
void DockWidgetQuick::setWidget(QQuickItem *guest)
|
||||
{
|
||||
auto adapter = new QWidgetAdapter(this);
|
||||
adapter->setIsWrapper();
|
||||
|
||||
// In case the user app needs to use them:
|
||||
adapter->setProperty("originalParent", QVariant::fromValue(guest->parent()));
|
||||
adapter->setProperty("originalParentItem", QVariant::fromValue(guest->parentItem()));
|
||||
|
||||
guest->setParentItem(adapter);
|
||||
guest->setParent(adapter);
|
||||
QWidgetAdapter::makeItemFillParent(guest);
|
||||
|
||||
setWidget(adapter);
|
||||
}
|
||||
|
||||
bool DockWidgetQuick::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::ParentChange) {
|
||||
onParentChanged();
|
||||
Q_EMIT actualTitleBarChanged();
|
||||
} else if (e->type() == QEvent::Show) {
|
||||
onShown(e->spontaneous());
|
||||
} else if (e->type() == QEvent::Hide) {
|
||||
onHidden(e->spontaneous());
|
||||
} else if (e->type() == QEvent::Close) {
|
||||
onCloseEvent(static_cast<QCloseEvent *>(e));
|
||||
}
|
||||
|
||||
return DockWidgetBase::event(e);
|
||||
}
|
||||
|
||||
QSize DockWidgetQuick::minimumSize() const
|
||||
{
|
||||
if (QWidgetAdapter *guestWidget = widget()) {
|
||||
// The guests min-size is the same as the widget's, there's no spacing or margins.
|
||||
return guestWidget->minimumSize();
|
||||
}
|
||||
|
||||
return DockWidgetBase::minimumSize();
|
||||
}
|
||||
|
||||
QSize DockWidgetQuick::maximumSize() const
|
||||
{
|
||||
if (QWidgetAdapter *guestWidget = widget()) {
|
||||
// The guests max-size is the same as the widget's, there's no spacing or margins.
|
||||
return guestWidget->maximumSize();
|
||||
}
|
||||
|
||||
return DockWidgetBase::maximumSize();
|
||||
}
|
||||
|
||||
TitleBar *DockWidgetQuick::actualTitleBar() const
|
||||
{
|
||||
if (Frame *frame = DockWidgetBase::d->frame())
|
||||
return frame->actualTitleBar();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject *DockWidgetQuick::actualTitleBarObj() const
|
||||
{
|
||||
return actualTitleBar();
|
||||
}
|
||||
|
||||
QQuickItem *DockWidgetQuick::frameVisualItem() const
|
||||
{
|
||||
if (auto frame = qobject_cast<FrameQuick *>(DockWidgetBase::d->frame()))
|
||||
return frame->visualItem();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DockWidgetQuick::onGeometryUpdated()
|
||||
{
|
||||
if (auto frame = qobject_cast<FrameQuick *>(DockWidgetBase::d->frame())) {
|
||||
frame->updateConstriants();
|
||||
frame->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
Frame *DockWidgetQuick::frame() const
|
||||
{
|
||||
return qobject_cast<FrameQuick *>(DockWidgetBase::d->frame());
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
This file is part of KDDockWidgets.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sérgio Martins <sergio.martins@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Represents a dock widget.
|
||||
*
|
||||
* @author Sérgio Martins \<sergio.martins@kdab.com\>
|
||||
*/
|
||||
|
||||
#ifndef KD_DOCKWIDGET_QUICK_H
|
||||
#define KD_DOCKWIDGET_QUICK_H
|
||||
|
||||
#include "DockWidget.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QCloseEvent;
|
||||
class QQmlEngine;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KDDockWidgets {
|
||||
|
||||
class Frame;
|
||||
class TitleBar;
|
||||
|
||||
/**
|
||||
* @brief Represents a dock widget.
|
||||
*
|
||||
* Most of the interface lives in DockWidgetBase, to facilitate sharing with QtQuick.
|
||||
*/
|
||||
class DOCKS_EXPORT DockWidgetQuick : public DockWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QObject *actualTitleBar READ actualTitleBarObj NOTIFY actualTitleBarChanged)
|
||||
public:
|
||||
/**
|
||||
* @brief constructs a new DockWidget
|
||||
* @param uniqueName the name of the dockwidget, should be unique. Use title for user visible text.
|
||||
* @param options optional options controlling behaviour
|
||||
* @param layoutSaverOptions options regarding LayoutSaver behaviour
|
||||
* @param engine the QML engine this dock widget will be created on. If not specified then
|
||||
* Config::self().qmlEngine() will be used
|
||||
*
|
||||
* There's no parent argument. The DockWidget is either parented to FloatingWindow or MainWindow
|
||||
* when visible, or stays without a parent when hidden.
|
||||
*/
|
||||
explicit DockWidgetQuick(const QString &uniqueName, Options options = {},
|
||||
LayoutSaverOptions layoutSaverOptions = LayoutSaverOptions(),
|
||||
QQmlEngine *engine = nullptr);
|
||||
|
||||
///@brief destructor
|
||||
~DockWidgetQuick() override;
|
||||
|
||||
/// Sets the DockWidget's guest item
|
||||
/// Similar to DockWidgetBase::setWidget(QQuickItem*)
|
||||
void setWidget(const QString &qmlFilename);
|
||||
|
||||
/// @reimp
|
||||
void setWidget(QWidgetAdapter *widget) override;
|
||||
|
||||
/// @reimp
|
||||
Q_INVOKABLE void setWidget(QQuickItem *widget);
|
||||
|
||||
/// @reimp
|
||||
QSize minimumSize() const override;
|
||||
|
||||
/// @reimp
|
||||
QSize maximumSize() const override;
|
||||
|
||||
/// @brief Returns the title bar
|
||||
TitleBar *actualTitleBar() const;
|
||||
|
||||
/// @brief Returns the title bar
|
||||
/// Qt6 requires us to include TitleBar_p.h, so instead the Q_PROPERTY uses
|
||||
/// QObject so we don't include private headers in public headers
|
||||
QObject *actualTitleBarObj() const;
|
||||
|
||||
/// @brief Returns the visual item which represents Frame in the screen
|
||||
/// Equivalent to Frame::visualItem().
|
||||
QQuickItem *frameVisualItem() const;
|
||||
|
||||
///@internal
|
||||
Q_INVOKABLE KDDockWidgets::Frame *frame() const;
|
||||
|
||||
/// @brief Called by QtQuick when min-size changes
|
||||
Q_INVOKABLE void onGeometryUpdated();
|
||||
|
||||
Q_SIGNALS:
|
||||
/// @brief The geometry of the frame container this dock widget is in changed
|
||||
/// For example, when dragging a dockwidget
|
||||
void frameGeometryChanged(QRect);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
Private *const d;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user