forked from qt-creator/qt-creator
QmlDesigner: Add effect maker composition node color value
Since no StudioControls ColorEditor exists, using the HelperWidgets one and adding dummy context and backend to get it to work. Task-number: QDS-10404 Change-Id: Ifc1506b4b1f761b6abf4144791f5b0397a90cdf0 Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -24,10 +24,10 @@ Item {
|
|||||||
valueLoader.source = "ValueVec4.qml"
|
valueLoader.source = "ValueVec4.qml"
|
||||||
else if (uniformType === "bool")
|
else if (uniformType === "bool")
|
||||||
valueLoader.source = "ValueBool.qml"
|
valueLoader.source = "ValueBool.qml"
|
||||||
// else if (uniformType === "color") // TODO
|
else if (uniformType === "color")
|
||||||
// valueLoader.sourceComponent = colorValue
|
valueLoader.source = "ValueColor.qml"
|
||||||
// else if (uniformType === "image") // TODO
|
// else if (uniformType === "image") // TODO
|
||||||
// valueLoader.sourceComponent = imageValue
|
// valueLoader.source = valueImage
|
||||||
else
|
else
|
||||||
valueLoader.source = "ValueFloat.qml"
|
valueLoader.source = "ValueFloat.qml"
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,18 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuickDesignerTheme
|
import QtQuickDesignerTheme
|
||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
|
||||||
import StudioTheme 1.0 as StudioTheme
|
import StudioTheme 1.0 as StudioTheme
|
||||||
import EffectMakerBackend
|
import EffectMakerBackend
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: itemPane
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
// TODO
|
HelperWidgets.ColorEditor {
|
||||||
|
backendValue: uniformBackendValue
|
||||||
|
|
||||||
|
onValueChanged: uniformValue = convertColorToString(color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -720,6 +720,7 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
compositionnode.cpp compositionnode.h
|
compositionnode.cpp compositionnode.h
|
||||||
uniform.cpp uniform.h
|
uniform.cpp uniform.h
|
||||||
effectutils.cpp effectutils.h
|
effectutils.cpp effectutils.h
|
||||||
|
coloreditorcontextobject.cpp coloreditorcontextobject.h
|
||||||
)
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
|
@@ -0,0 +1,185 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "coloreditorcontextobject.h"
|
||||||
|
|
||||||
|
#include <abstractview.h>
|
||||||
|
#include <bindingproperty.h>
|
||||||
|
#include <documentmanager.h>
|
||||||
|
#include <nodemetainfo.h>
|
||||||
|
#include <rewritingexception.h>
|
||||||
|
#include <qmldesignerplugin.h>
|
||||||
|
#include <qmlmodelnodeproxy.h>
|
||||||
|
#include <qmlobjectnode.h>
|
||||||
|
#include <qmltimeline.h>
|
||||||
|
#include <qmltimelinekeyframegroup.h>
|
||||||
|
#include <variantproperty.h>
|
||||||
|
|
||||||
|
#include <coreplugin/messagebox.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QCursor>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
ColorEditorContextObject::ColorEditorContextObject(QQmlContext *context, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_qmlContext(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ColorEditorContextObject::convertColorToString(const QVariant &color)
|
||||||
|
{
|
||||||
|
QString colorString;
|
||||||
|
QColor theColor;
|
||||||
|
if (color.canConvert(QVariant::Color)) {
|
||||||
|
theColor = color.value<QColor>();
|
||||||
|
} else if (color.canConvert(QVariant::Vector3D)) {
|
||||||
|
auto vec = color.value<QVector3D>();
|
||||||
|
theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
colorString = theColor.name(QColor::HexArgb);
|
||||||
|
|
||||||
|
return colorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this method is used by the ColorEditor helper widget, check if at all needed?
|
||||||
|
QColor ColorEditorContextObject::colorFromString(const QString &colorString)
|
||||||
|
{
|
||||||
|
return colorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ColorEditorContextObject::majorVersion() const
|
||||||
|
{
|
||||||
|
return m_majorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setMajorVersion(int majorVersion)
|
||||||
|
{
|
||||||
|
if (m_majorVersion == majorVersion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_majorVersion = majorVersion;
|
||||||
|
|
||||||
|
emit majorVersionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setStateName(const QString &newStateName)
|
||||||
|
{
|
||||||
|
if (newStateName == m_stateName)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_stateName = newStateName;
|
||||||
|
emit stateNameChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setAllStateNames(const QStringList &allStates)
|
||||||
|
{
|
||||||
|
if (allStates == m_allStateNames)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_allStateNames = allStates;
|
||||||
|
emit allStateNamesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setIsBaseState(bool newIsBaseState)
|
||||||
|
{
|
||||||
|
if (newIsBaseState == m_isBaseState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_isBaseState = newIsBaseState;
|
||||||
|
emit isBaseStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setSelectionChanged(bool newSelectionChanged)
|
||||||
|
{
|
||||||
|
if (newSelectionChanged == m_selectionChanged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_selectionChanged = newSelectionChanged;
|
||||||
|
emit selectionChangedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues)
|
||||||
|
{
|
||||||
|
if (newBackendValues == m_backendValues)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_backendValues = newBackendValues;
|
||||||
|
emit backendValuesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::setModel(Model *model)
|
||||||
|
{
|
||||||
|
m_model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::triggerSelectionChanged()
|
||||||
|
{
|
||||||
|
setSelectionChanged(!m_selectionChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::hideCursor()
|
||||||
|
{
|
||||||
|
if (QApplication::overrideCursor())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
|
||||||
|
|
||||||
|
if (QWidget *w = QApplication::activeWindow())
|
||||||
|
m_lastPos = QCursor::pos(w->screen());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::restoreCursor()
|
||||||
|
{
|
||||||
|
if (!QApplication::overrideCursor())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
if (QWidget *w = QApplication::activeWindow())
|
||||||
|
QCursor::setPos(w->screen(), m_lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorEditorContextObject::holdCursorInPlace()
|
||||||
|
{
|
||||||
|
if (!QApplication::overrideCursor())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (QWidget *w = QApplication::activeWindow())
|
||||||
|
QCursor::setPos(w->screen(), m_lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ColorEditorContextObject::devicePixelRatio()
|
||||||
|
{
|
||||||
|
if (QWidget *w = QApplication::activeWindow())
|
||||||
|
return w->devicePixelRatio();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ColorEditorContextObject::allStatesForId(const QString &id)
|
||||||
|
{
|
||||||
|
if (m_model && m_model->rewriterView()) {
|
||||||
|
const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
|
||||||
|
if (node.isValid())
|
||||||
|
return node.allStateNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorEditorContextObject::isBlocked(const QString &) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // QmlDesigner
|
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <model.h>
|
||||||
|
#include <modelnode.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QQmlPropertyMap>
|
||||||
|
#include <QQmlComponent>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
class ColorEditorContextObject : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged)
|
||||||
|
Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged)
|
||||||
|
Q_PROPERTY(int possibleTypeIndex READ possibleTypeIndex NOTIFY possibleTypeIndexChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged)
|
||||||
|
Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ColorEditorContextObject(QQmlContext *context, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QString stateName() const { return m_stateName; }
|
||||||
|
QStringList allStateNames() const { return m_allStateNames; }
|
||||||
|
int possibleTypeIndex() const { return m_possibleTypeIndex; }
|
||||||
|
|
||||||
|
bool isBaseState() const { return m_isBaseState; }
|
||||||
|
bool selectionChanged() const { return m_selectionChanged; }
|
||||||
|
|
||||||
|
QQmlPropertyMap *backendValues() const { return m_backendValues; }
|
||||||
|
|
||||||
|
Q_INVOKABLE QString convertColorToString(const QVariant &color);
|
||||||
|
Q_INVOKABLE QColor colorFromString(const QString &colorString);
|
||||||
|
|
||||||
|
Q_INVOKABLE void hideCursor();
|
||||||
|
Q_INVOKABLE void restoreCursor();
|
||||||
|
Q_INVOKABLE void holdCursorInPlace();
|
||||||
|
|
||||||
|
Q_INVOKABLE int devicePixelRatio();
|
||||||
|
|
||||||
|
Q_INVOKABLE QStringList allStatesForId(const QString &id);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isBlocked(const QString &propName) const;
|
||||||
|
|
||||||
|
int majorVersion() const;
|
||||||
|
void setMajorVersion(int majorVersion);
|
||||||
|
|
||||||
|
void setStateName(const QString &newStateName);
|
||||||
|
void setAllStateNames(const QStringList &allStates);
|
||||||
|
void setIsBaseState(bool newIsBaseState);
|
||||||
|
void setSelectionChanged(bool newSelectionChanged);
|
||||||
|
void setBackendValues(QQmlPropertyMap *newBackendValues);
|
||||||
|
void setModel(QmlDesigner::Model *model);
|
||||||
|
|
||||||
|
void triggerSelectionChanged();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void stateNameChanged();
|
||||||
|
void allStateNamesChanged();
|
||||||
|
void possibleTypeIndexChanged();
|
||||||
|
void isBaseStateChanged();
|
||||||
|
void selectionChangedChanged();
|
||||||
|
void backendValuesChanged();
|
||||||
|
void majorVersionChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updatePossibleTypeIndex();
|
||||||
|
|
||||||
|
QQmlContext *m_qmlContext = nullptr;
|
||||||
|
|
||||||
|
QString m_stateName;
|
||||||
|
QStringList m_allStateNames;
|
||||||
|
int m_possibleTypeIndex = -1;
|
||||||
|
QString m_currentType;
|
||||||
|
|
||||||
|
int m_majorVersion = 1;
|
||||||
|
|
||||||
|
QQmlPropertyMap *m_backendValues = nullptr;
|
||||||
|
Model *m_model = nullptr;
|
||||||
|
|
||||||
|
QPoint m_lastPos;
|
||||||
|
|
||||||
|
bool m_isBaseState = false;
|
||||||
|
bool m_selectionChanged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // QmlDesigner
|
@@ -20,6 +20,7 @@ QHash<int, QByteArray> EffectMakerUniformsModel::roleNames() const
|
|||||||
roles[NameRole] = "uniformName";
|
roles[NameRole] = "uniformName";
|
||||||
roles[DescriptionRole] = "uniformDescription";
|
roles[DescriptionRole] = "uniformDescription";
|
||||||
roles[ValueRole] = "uniformValue";
|
roles[ValueRole] = "uniformValue";
|
||||||
|
roles[BackendValueRole] = "uniformBackendValue";
|
||||||
roles[DefaultValueRole] = "uniformDefaultValue";
|
roles[DefaultValueRole] = "uniformDefaultValue";
|
||||||
roles[MinValueRole] = "uniformMinValue";
|
roles[MinValueRole] = "uniformMinValue";
|
||||||
roles[MaxValueRole] = "uniformMaxValue";
|
roles[MaxValueRole] = "uniformMaxValue";
|
||||||
|
@@ -30,6 +30,7 @@ private:
|
|||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
DescriptionRole,
|
DescriptionRole,
|
||||||
ValueRole,
|
ValueRole,
|
||||||
|
BackendValueRole,
|
||||||
DefaultValueRole,
|
DefaultValueRole,
|
||||||
MaxValueRole,
|
MaxValueRole,
|
||||||
MinValueRole,
|
MinValueRole,
|
||||||
|
@@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
#include "effectmakerwidget.h"
|
#include "effectmakerwidget.h"
|
||||||
|
|
||||||
|
#include "coloreditorcontextobject.h"
|
||||||
#include "effectmakermodel.h"
|
#include "effectmakermodel.h"
|
||||||
#include "effectmakernodesmodel.h"
|
#include "effectmakernodesmodel.h"
|
||||||
#include "effectmakerview.h"
|
#include "effectmakerview.h"
|
||||||
#include "qmldesignerconstants.h"
|
#include "qmldesignerconstants.h"
|
||||||
#include "qmldesignerplugin.h"
|
#include "qmldesignerplugin.h"
|
||||||
|
#include "qqmlcontext.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
@@ -65,6 +67,10 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
|
|||||||
{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
|
{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
|
||||||
{"rootView", QVariant::fromValue(this)}});
|
{"rootView", QVariant::fromValue(this)}});
|
||||||
|
|
||||||
|
auto colorEditorCtx = new ColorEditorContextObject(m_quickWidget->rootContext());
|
||||||
|
m_quickWidget->rootContext()->setContextObject(colorEditorCtx);
|
||||||
|
m_quickWidget->rootContext()->setContextProperty("modelNodeBackend", {});
|
||||||
|
|
||||||
// init the first load of the QML UI elements
|
// init the first load of the QML UI elements
|
||||||
reloadQmlSource();
|
reloadQmlSource();
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include "uniform.h"
|
#include "uniform.h"
|
||||||
|
|
||||||
|
#include "propertyeditorvalue.h"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
@@ -38,6 +40,9 @@ Uniform::Uniform(const QJsonObject &propObj)
|
|||||||
maxValue = propObj.value("maxValue").toString();
|
maxValue = propObj.value("maxValue").toString();
|
||||||
|
|
||||||
setValueData(value, defaultValue, minValue, maxValue);
|
setValueData(value, defaultValue, minValue, maxValue);
|
||||||
|
|
||||||
|
m_backendValue = new PropertyEditorValue(this);
|
||||||
|
m_backendValue->setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Uniform::type() const
|
QString Uniform::type() const
|
||||||
@@ -70,6 +75,11 @@ QVariant Uniform::value() const
|
|||||||
return m_value;
|
return m_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant Uniform::backendValue() const
|
||||||
|
{
|
||||||
|
return QVariant::fromValue(m_backendValue);
|
||||||
|
}
|
||||||
|
|
||||||
void Uniform::setValue(const QVariant &newValue)
|
void Uniform::setValue(const QVariant &newValue)
|
||||||
{
|
{
|
||||||
if (m_value != newValue) {
|
if (m_value != newValue) {
|
||||||
|
@@ -12,6 +12,8 @@ QT_FORWARD_DECLARE_CLASS(QVector2D)
|
|||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
class PropertyEditorValue;
|
||||||
|
|
||||||
class Uniform : public QObject
|
class Uniform : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -19,6 +21,7 @@ class Uniform : public QObject
|
|||||||
Q_PROPERTY(QString uniformName MEMBER m_name CONSTANT)
|
Q_PROPERTY(QString uniformName MEMBER m_name CONSTANT)
|
||||||
Q_PROPERTY(QString uniformType READ type CONSTANT)
|
Q_PROPERTY(QString uniformType READ type CONSTANT)
|
||||||
Q_PROPERTY(QVariant uniformValue READ value WRITE setValue NOTIFY uniformValueChanged)
|
Q_PROPERTY(QVariant uniformValue READ value WRITE setValue NOTIFY uniformValueChanged)
|
||||||
|
Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged)
|
||||||
Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT)
|
Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT)
|
||||||
Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT)
|
Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT)
|
||||||
|
|
||||||
@@ -43,6 +46,8 @@ public:
|
|||||||
QVariant value() const;
|
QVariant value() const;
|
||||||
void setValue(const QVariant &newValue);
|
void setValue(const QVariant &newValue);
|
||||||
|
|
||||||
|
QVariant backendValue() const;
|
||||||
|
|
||||||
QVariant defaultValue() const;
|
QVariant defaultValue() const;
|
||||||
|
|
||||||
QVariant minValue() const;
|
QVariant minValue() const;
|
||||||
@@ -62,6 +67,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void uniformValueChanged();
|
void uniformValueChanged();
|
||||||
|
void uniformBackendValueChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString mipmapPropertyName(const QString &name) const;
|
QString mipmapPropertyName(const QString &name) const;
|
||||||
@@ -85,6 +91,7 @@ private:
|
|||||||
bool m_useCustomValue = false;
|
bool m_useCustomValue = false;
|
||||||
bool m_enabled = true;
|
bool m_enabled = true;
|
||||||
bool m_enableMipmap = false;
|
bool m_enableMipmap = false;
|
||||||
|
PropertyEditorValue *m_backendValue = nullptr;
|
||||||
|
|
||||||
bool operator==(const Uniform &rhs) const noexcept
|
bool operator==(const Uniform &rhs) const noexcept
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user