From Wang Rui, "The submission includes some fixes for osgQt library and osgQtWidgets example: (1) QTextEdit now works with mouse/drag events, (2) scrollbars will change when OSG window is resizing, (3) improve rendering efficiency of QGraphicsViewAdapter so that it works with complex Qt UI, (4) add new setBackgroundWidget() method to indicate a 'background widget', which will ignore mouse/key events on it and pass them to the 3D scene."
git-svn-id: http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk@14482 16af8721-9629-0410-8352-f15c8da7e697
This commit is contained in:
@@ -107,25 +107,25 @@ protected:
|
||||
};
|
||||
|
||||
|
||||
//We would need to document the following somewhere in order to guide people on
|
||||
//We would need to document the following somewhere in order to guide people on
|
||||
//what they need to use...
|
||||
//
|
||||
//----------------------------------------------
|
||||
//There are two angles to consider.
|
||||
//
|
||||
//1. If someone wants a widget in their Qt app to be an OSG-rendered scene, they
|
||||
//need GraphicsWindowQt (in the osgViewerQtContext example) or QOSGWidget (in the
|
||||
//osgViewerQt example). These two allow both OSG and Qt to manage their threads
|
||||
//in a way which is optimal to them. We've used QOSGWidget in the past and had
|
||||
//trouble when Qt tried to overlay other widgets over the QOSGWidget (since OSG
|
||||
//did its rendering independently of Qt, it would overwrite what Qt had drawn). I
|
||||
//haven't tried GraphicsWindowQt yet, but I expect since it uses QGLWidget, it
|
||||
//will result in Qt knowing when OSG has drawn and be able to do overlays at the
|
||||
//1. If someone wants a widget in their Qt app to be an OSG-rendered scene, they
|
||||
//need GraphicsWindowQt (in the osgViewerQtContext example) or QOSGWidget (in the
|
||||
//osgViewerQt example). These two allow both OSG and Qt to manage their threads
|
||||
//in a way which is optimal to them. We've used QOSGWidget in the past and had
|
||||
//trouble when Qt tried to overlay other widgets over the QOSGWidget (since OSG
|
||||
//did its rendering independently of Qt, it would overwrite what Qt had drawn). I
|
||||
//haven't tried GraphicsWindowQt yet, but I expect since it uses QGLWidget, it
|
||||
//will result in Qt knowing when OSG has drawn and be able to do overlays at the
|
||||
//right time. Eventually GraphicsWindowQt can be brought into osgViewer I imagine...
|
||||
//
|
||||
//2. If someone wants to bring Qt widgets inside their OSG scene (to do HUDs or
|
||||
//an interface on a computer screen which is inside the 3D scene, or even
|
||||
//floating Qt widgets, for example). That's where QGraphicsViewAdapter +
|
||||
//2. If someone wants to bring Qt widgets inside their OSG scene (to do HUDs or
|
||||
//an interface on a computer screen which is inside the 3D scene, or even
|
||||
//floating Qt widgets, for example). That's where QGraphicsViewAdapter +
|
||||
//QWidgetImage will be useful.
|
||||
//----------------------------------------------
|
||||
|
||||
@@ -160,7 +160,7 @@ int main(int argc, char **argv)
|
||||
bool useLabel = false;
|
||||
if (arguments.read("--useLabel")) useLabel = true;
|
||||
|
||||
// true = make a Qt window with the same content to compare to
|
||||
// true = make a Qt window with the same content to compare to
|
||||
// QWebViewImage/QWidgetImage
|
||||
// false = use QWebViewImage/QWidgetImage (depending on useWidgetImage)
|
||||
bool sanityCheck = false;
|
||||
@@ -183,9 +183,9 @@ int main(int argc, char **argv)
|
||||
//-------------------------------------------------------------------
|
||||
// QWebViewImage test
|
||||
//-------------------------------------------------------------------
|
||||
// Note: When the last few issues with QWidgetImage are fixed,
|
||||
// QWebViewImage and this if() {} section can be removed since
|
||||
// QWidgetImage can display a QWebView just like QWebViewImage. Use
|
||||
// Note: When the last few issues with QWidgetImage are fixed,
|
||||
// QWebViewImage and this if() {} section can be removed since
|
||||
// QWidgetImage can display a QWebView just like QWebViewImage. Use
|
||||
// --useWidgetImage --useBrowser to see that in action.
|
||||
|
||||
if (!sanityCheck)
|
||||
@@ -193,7 +193,7 @@ int main(int argc, char **argv)
|
||||
osg::ref_ptr<osgQt::QWebViewImage> image = new osgQt::QWebViewImage;
|
||||
|
||||
if (arguments.argc()>1) image->navigateTo((arguments[1]));
|
||||
else image->navigateTo("http://www.youtube.com/");
|
||||
else image->navigateTo("http://www.openscenegraph.org/");
|
||||
|
||||
osgWidget::GeometryHints hints(osg::Vec3(0.0f,0.0f,0.0f),
|
||||
osg::Vec3(1.0f,0.0f,0.0f),
|
||||
@@ -208,7 +208,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sanity check, do the same thing as QGraphicsViewAdapter but in
|
||||
// Sanity check, do the same thing as QGraphicsViewAdapter but in
|
||||
// a separate Qt window.
|
||||
QWebPage* webPage = new QWebPage;
|
||||
webPage->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
|
||||
@@ -218,7 +218,7 @@ int main(int argc, char **argv)
|
||||
webView->setPage(webPage);
|
||||
|
||||
if (arguments.argc()>1) webView->load(QUrl(arguments[1]));
|
||||
else webView->load(QUrl("http://www.youtube.com/"));
|
||||
else webView->load(QUrl("http://www.openscenegraph.org/"));
|
||||
|
||||
QGraphicsScene* graphicsScene = new QGraphicsScene;
|
||||
graphicsScene->addWidget(webView);
|
||||
@@ -240,14 +240,16 @@ int main(int argc, char **argv)
|
||||
// QWidgetImage test
|
||||
//-------------------------------------------------------------------
|
||||
// QWidgetImage still has some issues, some examples are:
|
||||
//
|
||||
// 1. Editing in the QTextEdit doesn't work. Also when started with
|
||||
// --useBrowser, editing in the search field on YouTube doesn't
|
||||
// work. But that same search field when using QWebViewImage
|
||||
// works... And editing in the text field in the pop-up getInteger
|
||||
// dialog works too. All these cases use QGraphicsViewAdapter
|
||||
//
|
||||
// 1. Editing in the QTextEdit doesn't work. Also when started with
|
||||
// --useBrowser, editing in the search field on YouTube doesn't
|
||||
// work. But that same search field when using QWebViewImage
|
||||
// works... And editing in the text field in the pop-up getInteger
|
||||
// dialog works too. All these cases use QGraphicsViewAdapter
|
||||
// under the hood, so why do some work and others don't?
|
||||
//
|
||||
// <<< FIXED, need TextEditorInteraction >>>
|
||||
//
|
||||
// a) osgQtBrowser --useWidgetImage [--fullscreen] (optional)
|
||||
// b) Try to click in the QTextEdit and type, or to select text
|
||||
// and drag-and-drop it somewhere else in the QTextEdit. These
|
||||
@@ -259,11 +261,11 @@ int main(int argc, char **argv)
|
||||
// g) osgQtBrowser
|
||||
// h) Try the operation in f), it works.
|
||||
//
|
||||
// 2. Operations on floating windows (--numFloatingWindows 1 or more).
|
||||
// Moving by dragging the titlebar, clicking the close button,
|
||||
// resizing them, none of these work. I wonder if it's because the
|
||||
// OS manages those functions (they're functions of the window
|
||||
// decorations) so we need to do something special for that? But
|
||||
// 2. Operations on floating windows (--numFloatingWindows 1 or more).
|
||||
// Moving by dragging the titlebar, clicking the close button,
|
||||
// resizing them, none of these work. I wonder if it's because the
|
||||
// OS manages those functions (they're functions of the window
|
||||
// decorations) so we need to do something special for that? But
|
||||
// in --sanityCheck mode they work.
|
||||
//
|
||||
// a) osgQtBrowser --useWidgetImage --numFloatingWindows 1 [--fullscreen]
|
||||
@@ -272,35 +274,37 @@ int main(int argc, char **argv)
|
||||
// c) osgQtBrowser --useWidgetImage --numFloatingWindows 1 --sanityCheck
|
||||
// d) Try the operations in b), all they work.
|
||||
// e) osgQtBrowser --useWidgetImage [--fullscreen]
|
||||
// f) Click the button so that the getInteger() dialog is
|
||||
// displayed, then try to move that dialog or close it with the
|
||||
// f) Click the button so that the getInteger() dialog is
|
||||
// displayed, then try to move that dialog or close it with the
|
||||
// close button, these don't work.
|
||||
// g) osgQtBrowser --useWidgetImage --sanityCheck
|
||||
// h) Try the operation in f), it works.
|
||||
//
|
||||
// 3. (Minor) The QGraphicsView's scrollbars don't appear when
|
||||
// using QWidgetImage or QWebViewImage. QGraphicsView is a
|
||||
// 3. (Minor) The QGraphicsView's scrollbars don't appear when
|
||||
// using QWidgetImage or QWebViewImage. QGraphicsView is a
|
||||
// QAbstractScrollArea and it should display scrollbars as soon as
|
||||
// the scene is too large to fit the view.
|
||||
//
|
||||
// <<< FIXED >>>
|
||||
//
|
||||
// a) osgQtBrowser --useWidgetImage --fullscreen
|
||||
// b) Resize the OSG window so it's smaller than the QTextEdit.
|
||||
// Scrollbars should appear but don't.
|
||||
// c) osgQtBrowser --useWidgetImage --sanityCheck
|
||||
// d) Try the operation in b), scrollbars appear. Even if you have
|
||||
// floating windows (by clicking the button or by adding
|
||||
// --numFloatingWindows 1) and move them outside the view,
|
||||
// scrollbars appear too. You can't test that case in OSG for
|
||||
// d) Try the operation in b), scrollbars appear. Even if you have
|
||||
// floating windows (by clicking the button or by adding
|
||||
// --numFloatingWindows 1) and move them outside the view,
|
||||
// scrollbars appear too. You can't test that case in OSG for
|
||||
// now because of problem 2 above, but that's pretty cool.
|
||||
//
|
||||
// 4. (Minor) In sanity check mode, the widget added to the
|
||||
// 4. (Minor) In sanity check mode, the widget added to the
|
||||
// QGraphicsView is centered. With QGraphicsViewAdapter, it is not.
|
||||
//
|
||||
// a) osgQtBrowser --useWidgetImage [--fullscreen]
|
||||
// b) The QTextEdit and button are not in the center of the image
|
||||
// generated by the QGraphicsViewAdapter.
|
||||
// c) osgQtBrowser --useWidgetImage --sanityCheck
|
||||
// d) The QTextEdit and button are in the center of the
|
||||
// d) The QTextEdit and button are in the center of the
|
||||
// QGraphicsView.
|
||||
|
||||
|
||||
@@ -346,7 +350,7 @@ int main(int argc, char **argv)
|
||||
{
|
||||
QTextEdit* textEdit = new QTextEdit(text);
|
||||
textEdit->setReadOnly(false);
|
||||
textEdit->setTextInteractionFlags(Qt::TextEditable);
|
||||
textEdit->setTextInteractionFlags(Qt::TextEditorInteraction);
|
||||
|
||||
QPalette palette = textEdit->palette();
|
||||
palette.setColor(QPalette::Highlight, Qt::darkBlue);
|
||||
@@ -388,7 +392,7 @@ int main(int argc, char **argv)
|
||||
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
mt->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
|
||||
|
||||
osgViewer::InteractiveImageHandler* handler;
|
||||
osgViewer::InteractiveImageHandler* handler;
|
||||
if (inScene)
|
||||
{
|
||||
mt->setMatrix(osg::Matrix::rotate(osg::Vec3(0,1,0), osg::Vec3(0,0,1)));
|
||||
@@ -398,9 +402,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else // fullscreen
|
||||
{
|
||||
// The HUD camera's viewport needs to follow the size of the
|
||||
// The HUD camera's viewport needs to follow the size of the
|
||||
// window. MyInteractiveImageHandler will make sure of this.
|
||||
// As for the quad and the camera's projection, setting the
|
||||
// As for the quad and the camera's projection, setting the
|
||||
// projection resize policy to FIXED takes care of them, so
|
||||
// they can stay the same: (0,1,0,1) with a quad that fits.
|
||||
|
||||
@@ -429,13 +433,13 @@ int main(int argc, char **argv)
|
||||
overlay->addChild(mt);
|
||||
|
||||
root->addChild(overlay);
|
||||
|
||||
|
||||
quad->setEventCallback(handler);
|
||||
quad->setCullCallback(handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sanity check, do the same thing as QWidgetImage and
|
||||
// Sanity check, do the same thing as QWidgetImage and
|
||||
// QGraphicsViewAdapter but in a separate Qt window.
|
||||
|
||||
graphicsScene = new QGraphicsScene;
|
||||
|
||||
@@ -52,6 +52,7 @@ class OSGQT_EXPORT QGraphicsViewAdapter : public QObject
|
||||
|
||||
void clearWriteBuffer();
|
||||
|
||||
bool requiresRendering() const { return _requiresRendering; }
|
||||
void render();
|
||||
|
||||
void assignImage(unsigned int i);
|
||||
@@ -61,6 +62,12 @@ class OSGQT_EXPORT QGraphicsViewAdapter : public QObject
|
||||
void setBackgroundColor(QColor color) { _backgroundColor = color; }
|
||||
QColor getBackgroundColor() const { return _backgroundColor; }
|
||||
|
||||
/** The 'background widget' will ignore mouse/keyboard events and let following handlers handle them
|
||||
It is mainly used for integrating scene graph and full-screen UIs
|
||||
*/
|
||||
void setBackgroundWidget(QWidget* w) { _backgroundWidget = w; }
|
||||
QWidget* getBackgroundWidget() { return _backgroundWidget; }
|
||||
|
||||
QGraphicsView* getQGraphicsView() { return _graphicsView; }
|
||||
QGraphicsScene* getQGraphicsScene() { return _graphicsScene; }
|
||||
|
||||
@@ -71,6 +78,7 @@ class OSGQT_EXPORT QGraphicsViewAdapter : public QObject
|
||||
QWidget* getWidgetAt(const QPoint& pos);
|
||||
|
||||
osg::observer_ptr<osg::Image> _image;
|
||||
QWidget* _backgroundWidget;
|
||||
|
||||
int _previousButtonMask;
|
||||
int _previousMouseX;
|
||||
@@ -78,6 +86,7 @@ class OSGQT_EXPORT QGraphicsViewAdapter : public QObject
|
||||
int _previousQtMouseX;
|
||||
int _previousQtMouseY;
|
||||
bool _previousSentEvent;
|
||||
bool _requiresRendering;
|
||||
|
||||
int _width;
|
||||
int _height;
|
||||
|
||||
@@ -66,9 +66,12 @@ class QWebViewImage : public osgWidget::BrowserImage
|
||||
|
||||
void render()
|
||||
{
|
||||
_adapter->render();
|
||||
if (_adapter->requiresRendering()) _adapter->render();
|
||||
}
|
||||
|
||||
virtual bool requiresUpdateCall() const { return true; }
|
||||
virtual void update( osg::NodeVisitor* nv ) { render(); }
|
||||
|
||||
virtual bool sendFocusHint(bool focus)
|
||||
{
|
||||
QFocusEvent event(focus ? QEvent::FocusIn : QEvent::FocusOut, Qt::OtherFocusReason);
|
||||
|
||||
@@ -29,6 +29,8 @@ class OSGQT_EXPORT QWidgetImage : public osg::Image
|
||||
QWidget* getQWidget() { return _widget; }
|
||||
QGraphicsViewAdapter* getQGraphicsViewAdapter() { return _adapter; }
|
||||
|
||||
virtual bool requiresUpdateCall() const { return true; }
|
||||
virtual void update( osg::NodeVisitor* nv ) { render(); }
|
||||
|
||||
void clearWriteBuffer();
|
||||
|
||||
|
||||
@@ -67,9 +67,13 @@ const QImage::Format s_imageFormat = QImage::Format_ARGB32_Premultiplied;
|
||||
|
||||
QGraphicsViewAdapter::QGraphicsViewAdapter(osg::Image* image, QWidget* widget):
|
||||
_image(image),
|
||||
_backgroundWidget(0),
|
||||
_previousMouseX(-1),
|
||||
_previousMouseY(-1),
|
||||
_previousQtMouseX(-1),
|
||||
_previousQtMouseY(-1),
|
||||
_previousSentEvent(false),
|
||||
_requiresRendering(false),
|
||||
_qtKeyModifiers(Qt::NoModifier),
|
||||
_backgroundColor(255, 255, 255),
|
||||
_widget(widget)
|
||||
@@ -115,13 +119,13 @@ QGraphicsViewAdapter::QGraphicsViewAdapter(osg::Image* image, QWidget* widget):
|
||||
void QGraphicsViewAdapter::repaintRequestedSlot(const QList<QRectF>&)
|
||||
{
|
||||
// OSG_NOTICE<<"QGraphicsViewAdapter::repaintRequestedSlot"<<std::endl;
|
||||
render();
|
||||
_requiresRendering = true;
|
||||
}
|
||||
|
||||
void QGraphicsViewAdapter::repaintRequestedSlot(const QRectF&)
|
||||
{
|
||||
// OSG_NOTICE<<"QGraphicsViewAdapter::repaintRequestedSlot"<<std::endl;
|
||||
render();
|
||||
_requiresRendering = true;
|
||||
}
|
||||
|
||||
void QGraphicsViewAdapter::customEvent ( QEvent * event )
|
||||
@@ -281,9 +285,9 @@ QWidget* QGraphicsViewAdapter::getWidgetAt(const QPoint& pos)
|
||||
}
|
||||
|
||||
QGraphicsItem* item = _graphicsView->itemAt(pos);
|
||||
if(item && item->contains(item->mapFromScene(pos)))
|
||||
if(item /*&& item->contains(item->mapFromScene(pos))*/)
|
||||
{
|
||||
QGraphicsProxyWidget* p = dynamic_cast<QGraphicsProxyWidget*>(item);
|
||||
QGraphicsProxyWidget* p = qgraphicsitem_cast<QGraphicsProxyWidget*>(item);
|
||||
if(p)
|
||||
{
|
||||
childAt = p->widget();
|
||||
@@ -292,6 +296,14 @@ QWidget* QGraphicsViewAdapter::getWidgetAt(const QPoint& pos)
|
||||
{
|
||||
childAt = c;
|
||||
}
|
||||
|
||||
// Widgets like QTextEdit will automatically add child scroll area widgets
|
||||
// that will be selected by childAt(), we have to change to parents at that moment
|
||||
// Hardcoded by the internal widget's name 'qt_scrollarea_viewport' at present
|
||||
if (childAt->objectName() == "qt_scrollarea_viewport")
|
||||
{
|
||||
childAt = childAt->parentWidget();
|
||||
}
|
||||
return childAt;
|
||||
}
|
||||
}
|
||||
@@ -305,7 +317,17 @@ bool QGraphicsViewAdapter::sendPointerEvent(int x, int y, int buttonMask)
|
||||
|
||||
QPoint pos(_previousQtMouseX, _previousQtMouseY);
|
||||
|
||||
if (getWidgetAt(pos) != NULL || (_previousSentEvent && buttonMask != 0))
|
||||
QWidget* targetWidget = getWidgetAt(pos);
|
||||
OSG_INFO << "Get " << (targetWidget ? targetWidget->metaObject()->className() : std::string("NULL"))
|
||||
<< " at global pos " << x << ", " << y << std::endl;
|
||||
|
||||
if (_backgroundWidget && _backgroundWidget == targetWidget)
|
||||
{
|
||||
// Mouse is at background widget, so ignore such events
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetWidget != NULL || (_previousSentEvent && buttonMask != 0))
|
||||
{
|
||||
QCoreApplication::postEvent(this, new MyQPointerEvent(x,y,buttonMask));
|
||||
OSG_INFO<<"sendPointerEvent("<<x<<", "<<y<<") sent"<<std::endl;
|
||||
@@ -342,6 +364,7 @@ bool QGraphicsViewAdapter::handlePointerEvent(int x, int y, int buttonMask)
|
||||
(rightButtonPressed ? Qt::RightButton : Qt::NoButton);
|
||||
|
||||
const QPoint globalPos(x, y);
|
||||
QWidget* targetWidget = getWidgetAt(globalPos);
|
||||
|
||||
if (buttonMask != _previousButtonMask)
|
||||
{
|
||||
@@ -363,7 +386,6 @@ bool QGraphicsViewAdapter::handlePointerEvent(int x, int y, int buttonMask)
|
||||
eventType = rightButtonPressed ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease ;
|
||||
if(!rightButtonPressed)
|
||||
{
|
||||
QWidget* targetWidget = getWidgetAt(globalPos);
|
||||
if(targetWidget)
|
||||
{
|
||||
QPoint localPos = targetWidget->mapFromGlobal(globalPos);
|
||||
@@ -376,10 +398,11 @@ bool QGraphicsViewAdapter::handlePointerEvent(int x, int y, int buttonMask)
|
||||
if (eventType==QEvent::MouseButtonPress)
|
||||
{
|
||||
_image->sendFocusHint(true);
|
||||
if (targetWidget) targetWidget->setFocus(Qt::MouseFocusReason);
|
||||
}
|
||||
|
||||
QMouseEvent event(eventType, globalPos, qtButton, qtMouseButtons, 0);
|
||||
QCoreApplication::sendEvent(_graphicsView->viewport(), &event );
|
||||
QCoreApplication::sendEvent(_graphicsView->viewport(), &event);
|
||||
|
||||
_previousButtonMask = buttonMask;
|
||||
}
|
||||
@@ -398,7 +421,14 @@ bool QGraphicsViewAdapter::handlePointerEvent(int x, int y, int buttonMask)
|
||||
bool QGraphicsViewAdapter::sendKeyEvent(int key, bool keyDown)
|
||||
{
|
||||
QPoint pos(_previousQtMouseX, _previousQtMouseY);
|
||||
if (getWidgetAt(pos) != NULL)
|
||||
QWidget* targetWidget = getWidgetAt(pos);
|
||||
if (_backgroundWidget && _backgroundWidget == targetWidget)
|
||||
{
|
||||
// Mouse is at background widget, so ignore such events
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetWidget != NULL)
|
||||
{
|
||||
QCoreApplication::postEvent(this, new MyQKeyEvent(key,keyDown));
|
||||
return true;
|
||||
@@ -489,6 +519,7 @@ void QGraphicsViewAdapter::render()
|
||||
{
|
||||
OSG_INFO<<"Current write = "<<_currentWrite<<std::endl;
|
||||
QImage& image = _qimages[_currentWrite];
|
||||
_requiresRendering = false;
|
||||
|
||||
// If we got a resize, act on it, first by resizing the view, then the current image
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ void QWidgetImage::clearWriteBuffer()
|
||||
|
||||
void QWidgetImage::render()
|
||||
{
|
||||
_adapter->render();
|
||||
if (_adapter->requiresRendering()) _adapter->render();
|
||||
}
|
||||
|
||||
void QWidgetImage::scaleImage(int s,int t,int /*r*/, GLenum /*newDataType*/)
|
||||
|
||||
@@ -719,6 +719,12 @@ InteractiveImageHandler::InteractiveImageHandler(osg::Image* image, osg::Texture
|
||||
bool InteractiveImageHandler::mousePosition(osgViewer::View* view, osg::NodeVisitor* nv, const osgGA::GUIEventAdapter& ea, int& x, int &y) const
|
||||
{
|
||||
if (!view) return false;
|
||||
if (_fullscreen)
|
||||
{
|
||||
x = ea.getX();
|
||||
y = ea.getY();
|
||||
return true;
|
||||
}
|
||||
|
||||
osgUtil::LineSegmentIntersector::Intersections intersections;
|
||||
bool foundIntersection = (nv==0) ? view->computeIntersections(ea, intersections) :
|
||||
|
||||
Reference in New Issue
Block a user