mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2025-01-12 13:30:19 +00:00
parasite: Make flashing work better
The positioning of the highlight window was not reliable; instead just use a after-handler for the draw signal, in the same way that drag highlights are drawn by GTK+ itself. And copy the code for grabbing a widget via pointer from testgtk; that code is known to work.
This commit is contained in:
parent
01cb166819
commit
8f96238194
@ -20,223 +20,351 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "parasite.h"
|
||||
#include "widget-tree.h"
|
||||
|
||||
static void
|
||||
on_inspect_widget(GtkWidget *grab_window,
|
||||
GdkEventButton *event,
|
||||
ParasiteWindow *parasite)
|
||||
typedef struct
|
||||
{
|
||||
gdk_device_ungrab (gtk_get_current_event_device (), event->time);
|
||||
gtk_widget_hide(parasite->highlight_window);
|
||||
|
||||
if (parasite->selected_window != NULL)
|
||||
{
|
||||
GtkWidget *toplevel = NULL;
|
||||
GtkWidget *widget = NULL;
|
||||
|
||||
gdk_window_get_user_data(
|
||||
gdk_window_get_toplevel(parasite->selected_window),
|
||||
(gpointer*)&toplevel);
|
||||
|
||||
gdk_window_get_user_data (parasite->selected_window, (gpointer*)&widget);
|
||||
|
||||
if (toplevel)
|
||||
{
|
||||
parasite_widget_tree_scan (PARASITE_WIDGET_TREE (parasite->widget_tree),
|
||||
toplevel);
|
||||
}
|
||||
|
||||
if (widget)
|
||||
{
|
||||
parasite_widget_tree_select_object (PARASITE_WIDGET_TREE (parasite->widget_tree),
|
||||
G_OBJECT (widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
gint x;
|
||||
gint y;
|
||||
gboolean found;
|
||||
gboolean first;
|
||||
GtkWidget *res_widget;
|
||||
} FindWidgetData;
|
||||
|
||||
static void
|
||||
on_highlight_window_show (GtkWidget *window, ParasiteWindow *parasite)
|
||||
find_widget (GtkWidget *widget,
|
||||
FindWidgetData *data)
|
||||
{
|
||||
if (gtk_widget_is_composited (parasite->window))
|
||||
{
|
||||
gtk_widget_set_opacity (parasite->highlight_window, 0.2);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* TODO: Do something different when there's no compositing manager.
|
||||
* Draw a border or something.
|
||||
*/
|
||||
}
|
||||
}
|
||||
GtkAllocation new_allocation;
|
||||
gint x_offset = 0;
|
||||
gint y_offset = 0;
|
||||
|
||||
static void
|
||||
ensure_highlight_window (ParasiteWindow *parasite)
|
||||
{
|
||||
GdkRGBA color;
|
||||
gtk_widget_get_allocation (widget, &new_allocation);
|
||||
|
||||
if (parasite->highlight_window != NULL)
|
||||
if (data->found || !gtk_widget_get_mapped (widget))
|
||||
return;
|
||||
|
||||
color.red = 0;
|
||||
color.green = 0;
|
||||
color.blue = 65535;
|
||||
color.alpha = 1;
|
||||
/* Note that in the following code, we only count the
|
||||
* position as being inside a WINDOW widget if it is inside
|
||||
* widget->window; points that are outside of widget->window
|
||||
* but within the allocation are not counted. This is consistent
|
||||
* with the way we highlight drag targets.
|
||||
*/
|
||||
if (gtk_widget_get_has_window (widget))
|
||||
{
|
||||
new_allocation.x = 0;
|
||||
new_allocation.y = 0;
|
||||
}
|
||||
|
||||
parasite->highlight_window = gtk_window_new (GTK_WINDOW_POPUP);
|
||||
gtk_widget_override_background_color (parasite->highlight_window,
|
||||
GTK_STATE_NORMAL,
|
||||
&color);
|
||||
if (gtk_widget_get_parent (widget) && !data->first)
|
||||
{
|
||||
GdkWindow *window = gtk_widget_get_window (widget);
|
||||
while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
|
||||
{
|
||||
gint tx, ty, twidth, theight;
|
||||
|
||||
g_signal_connect (parasite->highlight_window, "show", G_CALLBACK (on_highlight_window_show), parasite);
|
||||
twidth = gdk_window_get_width (window);
|
||||
theight = gdk_window_get_height (window);
|
||||
|
||||
if (new_allocation.x < 0)
|
||||
{
|
||||
new_allocation.width += new_allocation.x;
|
||||
new_allocation.x = 0;
|
||||
}
|
||||
if (new_allocation.y < 0)
|
||||
{
|
||||
new_allocation.height += new_allocation.y;
|
||||
new_allocation.y = 0;
|
||||
}
|
||||
if (new_allocation.x + new_allocation.width > twidth)
|
||||
new_allocation.width = twidth - new_allocation.x;
|
||||
if (new_allocation.y + new_allocation.height > theight)
|
||||
new_allocation.height = theight - new_allocation.y;
|
||||
|
||||
gdk_window_get_position (window, &tx, &ty);
|
||||
new_allocation.x += tx;
|
||||
x_offset += tx;
|
||||
new_allocation.y += ty;
|
||||
y_offset += ty;
|
||||
|
||||
window = gdk_window_get_parent (window);
|
||||
}
|
||||
}
|
||||
|
||||
if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
|
||||
(data->x < new_allocation.x + new_allocation.width) &&
|
||||
(data->y < new_allocation.y + new_allocation.height))
|
||||
{
|
||||
/* First, check if the drag is in a valid drop site in
|
||||
* one of our children
|
||||
*/
|
||||
if (GTK_IS_CONTAINER (widget))
|
||||
{
|
||||
FindWidgetData new_data = *data;
|
||||
|
||||
new_data.x -= x_offset;
|
||||
new_data.y -= y_offset;
|
||||
new_data.found = FALSE;
|
||||
new_data.first = FALSE;
|
||||
|
||||
gtk_container_forall (GTK_CONTAINER (widget),
|
||||
(GtkCallback)find_widget,
|
||||
&new_data);
|
||||
|
||||
data->found = new_data.found;
|
||||
if (data->found)
|
||||
data->res_widget = new_data.res_widget;
|
||||
}
|
||||
|
||||
/* If not, and this widget is registered as a drop site, check to
|
||||
* emit "drag_motion" to check if we are actually in
|
||||
* a drop site.
|
||||
*/
|
||||
if (!data->found)
|
||||
{
|
||||
data->found = TRUE;
|
||||
data->res_widget = widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
find_widget_at_pointer (GdkDevice *device)
|
||||
{
|
||||
GtkWidget *widget = NULL;
|
||||
GdkWindow *pointer_window;
|
||||
gint x, y;
|
||||
FindWidgetData data;
|
||||
|
||||
pointer_window = gdk_device_get_window_at_position (device, NULL, NULL);
|
||||
|
||||
if (pointer_window)
|
||||
{
|
||||
gpointer widget_ptr;
|
||||
|
||||
gdk_window_get_user_data (pointer_window, &widget_ptr);
|
||||
widget = widget_ptr;
|
||||
}
|
||||
|
||||
if (widget)
|
||||
{
|
||||
gdk_window_get_device_position (gtk_widget_get_window (widget),
|
||||
device, &x, &y, NULL);
|
||||
|
||||
data.x = x;
|
||||
data.y = y;
|
||||
data.found = FALSE;
|
||||
data.first = TRUE;
|
||||
|
||||
find_widget (widget, &data);
|
||||
if (data.found)
|
||||
return data.res_widget;
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean draw_flash (GtkWidget *widget,
|
||||
cairo_t *cr,
|
||||
ParasiteWindow *parasite);
|
||||
|
||||
static void
|
||||
clear_flash (ParasiteWindow *parasite)
|
||||
{
|
||||
if (parasite->flash_widget)
|
||||
{
|
||||
gtk_widget_queue_draw (parasite->flash_widget);
|
||||
g_signal_handlers_disconnect_by_func (parasite->flash_widget, draw_flash, parasite);
|
||||
parasite->flash_widget = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_highlight_widget(GtkWidget *grab_window,
|
||||
GdkEventMotion *event,
|
||||
ParasiteWindow *parasite)
|
||||
start_flash (ParasiteWindow *parasite,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *selected_window;
|
||||
gint x, y, width, height;
|
||||
parasite->flash_count = 1;
|
||||
parasite->flash_widget = widget;
|
||||
g_signal_connect_after (widget, "draw", G_CALLBACK (draw_flash), parasite);
|
||||
gtk_widget_queue_draw (widget);
|
||||
}
|
||||
|
||||
ensure_highlight_window (parasite);
|
||||
static void
|
||||
on_inspect_widget (GtkWidget *button,
|
||||
GdkEvent *event,
|
||||
ParasiteWindow *parasite)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
|
||||
gtk_widget_hide (parasite->highlight_window);
|
||||
clear_flash (parasite);
|
||||
|
||||
selected_window = gdk_device_get_window_at_position (gtk_get_current_event_device (),
|
||||
NULL,
|
||||
NULL);
|
||||
if (selected_window == NULL)
|
||||
widget = find_widget_at_pointer (gdk_event_get_device (event));
|
||||
|
||||
if (widget == NULL)
|
||||
return;
|
||||
|
||||
parasite->selected_widget = widget;
|
||||
|
||||
parasite_widget_tree_scan (PARASITE_WIDGET_TREE (parasite->widget_tree),
|
||||
gtk_widget_get_toplevel (widget));
|
||||
|
||||
parasite_widget_tree_select_object (PARASITE_WIDGET_TREE (parasite->widget_tree),
|
||||
G_OBJECT (widget));
|
||||
}
|
||||
|
||||
static void
|
||||
on_highlight_widget (GtkWidget *button,
|
||||
GdkEvent *event,
|
||||
ParasiteWindow *parasite)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
|
||||
widget = find_widget_at_pointer (gdk_event_get_device (event));
|
||||
|
||||
if (widget == NULL)
|
||||
{
|
||||
/* This window isn't in-process. Ignore it. */
|
||||
parasite->selected_window = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (gdk_window_get_toplevel(selected_window) == gtk_widget_get_window(parasite->window))
|
||||
if (gtk_widget_get_toplevel (widget) == parasite->window)
|
||||
{
|
||||
/* Don't hilight things in the parasite window */
|
||||
parasite->selected_window = NULL;
|
||||
return;
|
||||
/* Don't hilight things in the parasite window */
|
||||
return;
|
||||
}
|
||||
|
||||
parasite->selected_window = selected_window;
|
||||
|
||||
gdk_window_get_origin (selected_window, &x, &y);
|
||||
width = gdk_window_get_width (selected_window);
|
||||
height = gdk_window_get_height (selected_window);
|
||||
|
||||
gtk_window_move (GTK_WINDOW (parasite->highlight_window), x, y);
|
||||
gtk_window_resize (GTK_WINDOW (parasite->highlight_window), width, height);
|
||||
gtk_widget_show (parasite->highlight_window);
|
||||
}
|
||||
|
||||
static void
|
||||
on_inspect_button_release (GtkWidget *button,
|
||||
GdkEventButton *event,
|
||||
ParasiteWindow *parasite)
|
||||
{
|
||||
GdkCursor *cursor;
|
||||
GdkEventMask events;
|
||||
|
||||
events = GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
|
||||
|
||||
if (parasite->grab_window == NULL)
|
||||
if (parasite->flash_widget == widget)
|
||||
{
|
||||
parasite->grab_window = gtk_window_new (GTK_WINDOW_POPUP);
|
||||
gtk_widget_show(parasite->grab_window);
|
||||
gtk_window_resize (GTK_WINDOW (parasite->grab_window), 1, 1);
|
||||
gtk_window_move (GTK_WINDOW (parasite->grab_window), -100, -100);
|
||||
gtk_widget_add_events (parasite->grab_window, events);
|
||||
|
||||
g_signal_connect (parasite->grab_window, "button_release_event", G_CALLBACK (on_inspect_widget), parasite);
|
||||
g_signal_connect (parasite->grab_window, "motion_notify_event", G_CALLBACK(on_highlight_widget), parasite);
|
||||
/* Already selected */
|
||||
return;
|
||||
}
|
||||
|
||||
cursor = gdk_cursor_new_for_display (gtk_widget_get_display (button), GDK_CROSSHAIR);
|
||||
gdk_device_grab (gtk_get_current_event_device (),
|
||||
gtk_widget_get_window (parasite->grab_window),
|
||||
GDK_OWNERSHIP_WINDOW,
|
||||
FALSE,
|
||||
events,
|
||||
cursor,
|
||||
event->time);
|
||||
g_object_unref (cursor);
|
||||
}
|
||||
|
||||
|
||||
GtkWidget *
|
||||
gtkparasite_inspect_button_new(ParasiteWindow *parasite)
|
||||
{
|
||||
GtkWidget *button;
|
||||
|
||||
button = gtk_button_new_from_icon_name ("find", GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text (button, "Inspect");
|
||||
g_signal_connect(G_OBJECT(button), "button_release_event",
|
||||
G_CALLBACK(on_inspect_button_release), parasite);
|
||||
|
||||
return button;
|
||||
clear_flash (parasite);
|
||||
start_flash (parasite, widget);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_flash_timeout(ParasiteWindow *parasite)
|
||||
property_query_event (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer data)
|
||||
{
|
||||
parasite->flash_count++;
|
||||
|
||||
if (parasite->flash_count == 8)
|
||||
if (event->type == GDK_BUTTON_RELEASE)
|
||||
{
|
||||
parasite->flash_cnx = 0;
|
||||
return FALSE;
|
||||
g_signal_handlers_disconnect_by_func (widget, property_query_event, data);
|
||||
gtk_grab_remove (widget);
|
||||
gdk_device_ungrab (gdk_event_get_device (event), GDK_CURRENT_TIME);
|
||||
on_inspect_widget (widget, event, data);
|
||||
}
|
||||
else if (event->type == GDK_MOTION_NOTIFY)
|
||||
{
|
||||
on_highlight_widget (widget, event, data);
|
||||
}
|
||||
|
||||
if (parasite->flash_count % 2 == 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_inspect (GtkWidget *button,
|
||||
ParasiteWindow *parasite)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
GdkDevice *device;
|
||||
GdkCursor *cursor;
|
||||
|
||||
g_signal_connect (button, "event",
|
||||
G_CALLBACK (property_query_event), parasite);
|
||||
|
||||
display = gtk_widget_get_display (button);
|
||||
cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);
|
||||
device = gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display));
|
||||
gdk_device_grab (device,
|
||||
gtk_widget_get_window (GTK_WIDGET (button)),
|
||||
GDK_OWNERSHIP_NONE, TRUE,
|
||||
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
|
||||
cursor, GDK_CURRENT_TIME);
|
||||
g_object_unref (cursor);
|
||||
gtk_grab_add (GTK_WIDGET (button));
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtkparasite_inspect_button_new (ParasiteWindow *parasite)
|
||||
{
|
||||
GtkWidget *button;
|
||||
|
||||
button = gtk_button_new_with_label ("Inspect");
|
||||
g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (on_inspect), parasite);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
draw_flash (GtkWidget *widget,
|
||||
cairo_t *cr,
|
||||
ParasiteWindow *parasite)
|
||||
{
|
||||
GtkAllocation alloc;
|
||||
|
||||
if (parasite->flash_count % 2 == 0)
|
||||
return FALSE;
|
||||
|
||||
if (GTK_IS_WINDOW (widget))
|
||||
{
|
||||
if (gtk_widget_get_visible(parasite->highlight_window))
|
||||
gtk_widget_hide(parasite->highlight_window);
|
||||
else
|
||||
gtk_widget_show(parasite->highlight_window);
|
||||
/* We don't want to draw the drag highlight around the
|
||||
* CSD window decorations
|
||||
*/
|
||||
gtk_widget_get_allocation (gtk_bin_get_child (GTK_BIN (widget)), &alloc);
|
||||
}
|
||||
else
|
||||
{
|
||||
alloc.x = 0;
|
||||
alloc.y = 0;
|
||||
alloc.width = gtk_widget_get_allocated_width (widget);
|
||||
alloc.height = gtk_widget_get_allocated_height (widget);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.2);
|
||||
cairo_rectangle (cr,
|
||||
alloc.x + 0.5, alloc.y + 0.5,
|
||||
alloc.width - 1, alloc.height - 1);
|
||||
cairo_fill (cr);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_flash_timeout (ParasiteWindow *parasite)
|
||||
{
|
||||
gtk_widget_queue_draw (parasite->flash_widget);
|
||||
|
||||
parasite->flash_count++;
|
||||
|
||||
if (parasite->flash_count == 6)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (parasite->flash_widget, draw_flash, parasite);
|
||||
parasite->flash_widget = NULL;
|
||||
parasite->flash_cnx = 0;
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
void
|
||||
gtkparasite_flash_widget(ParasiteWindow *parasite, GtkWidget *widget)
|
||||
gtkparasite_flash_widget (ParasiteWindow *parasite,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
gint x, y, width, height;
|
||||
GdkWindow *parent_window;
|
||||
if (parasite->flash_cnx != 0)
|
||||
return;
|
||||
|
||||
if (!gtk_widget_get_visible(widget) || !gtk_widget_get_mapped(widget))
|
||||
return;
|
||||
if (!gtk_widget_get_visible (widget) || !gtk_widget_get_mapped (widget))
|
||||
return;
|
||||
|
||||
ensure_highlight_window(parasite);
|
||||
|
||||
parent_window = gtk_widget_get_parent_window(widget);
|
||||
if (parent_window != NULL) {
|
||||
GtkAllocation alloc;
|
||||
gtk_widget_get_allocation(widget, &alloc);
|
||||
|
||||
gdk_window_get_origin(parent_window, &x, &y);
|
||||
x += alloc.x;
|
||||
y += alloc.y;
|
||||
|
||||
width = alloc.width;
|
||||
height = alloc.height;
|
||||
|
||||
gtk_window_move(GTK_WINDOW(parasite->highlight_window), x, y);
|
||||
gtk_window_resize(GTK_WINDOW(parasite->highlight_window), width, height);
|
||||
gtk_widget_show(parasite->highlight_window);
|
||||
|
||||
if (parasite->flash_cnx != 0)
|
||||
g_source_remove(parasite->flash_cnx);
|
||||
|
||||
parasite->flash_count = 0;
|
||||
parasite->flash_cnx = g_timeout_add(150, (GSourceFunc)on_flash_timeout,
|
||||
parasite);
|
||||
}
|
||||
start_flash (parasite, widget);
|
||||
parasite->flash_cnx = g_timeout_add (150, (GSourceFunc) on_flash_timeout, parasite);
|
||||
}
|
||||
|
||||
// vim: set et sw=4 ts=4:
|
||||
/* vim: set et sw=2 ts=2: */
|
||||
|
@ -43,12 +43,10 @@ typedef struct
|
||||
GtkWidget *widget_css_editor;
|
||||
GtkWidget *oh;
|
||||
|
||||
GtkWidget *grab_window;
|
||||
GtkWidget *highlight_window;
|
||||
|
||||
GtkWidget *widget_popup;
|
||||
|
||||
GdkWindow *selected_window;
|
||||
GtkWidget *selected_widget;
|
||||
GtkWidget *flash_widget;
|
||||
|
||||
int flash_count;
|
||||
int flash_cnx;
|
||||
|
@ -338,7 +338,7 @@ parasite_proplist_new (GtkWidget *widget_tree,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void
|
||||
gboolean
|
||||
parasite_proplist_set_object (ParasitePropList* pl, GObject *object)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
@ -347,6 +347,9 @@ parasite_proplist_set_object (ParasitePropList* pl, GObject *object)
|
||||
guint i;
|
||||
GList *l;
|
||||
|
||||
if (pl->priv->object == object)
|
||||
return FALSE;
|
||||
|
||||
pl->priv->object = object;
|
||||
|
||||
for (l = pl->priv->signal_cnxs; l != NULL; l = l->next)
|
||||
@ -369,11 +372,11 @@ parasite_proplist_set_object (ParasitePropList* pl, GObject *object)
|
||||
GtkWidget *parent;
|
||||
|
||||
if (!GTK_IS_WIDGET (object))
|
||||
return;
|
||||
return TRUE;
|
||||
|
||||
parent = gtk_widget_get_parent (GTK_WIDGET (object));
|
||||
if (!parent)
|
||||
return;
|
||||
return TRUE;
|
||||
|
||||
props = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &num_properties);
|
||||
}
|
||||
@ -409,6 +412,8 @@ parasite_proplist_set_object (ParasitePropList* pl, GObject *object)
|
||||
|
||||
g_free (signal_name);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,7 +51,7 @@ G_BEGIN_DECLS
|
||||
GType parasite_proplist_get_type (void);
|
||||
GtkWidget *parasite_proplist_new (GtkWidget *widget_tree,
|
||||
gboolean child_properties);
|
||||
void parasite_proplist_set_object (ParasitePropList *proplist,
|
||||
gboolean parasite_proplist_set_object (ParasitePropList *proplist,
|
||||
GObject *object);
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -42,7 +42,9 @@ on_widget_tree_selection_changed (ParasiteWidgetTree *widget_tree,
|
||||
|
||||
if (selected != NULL)
|
||||
{
|
||||
parasite_proplist_set_object (PARASITE_PROPLIST (parasite->prop_list), selected);
|
||||
if (!parasite_proplist_set_object (PARASITE_PROPLIST (parasite->prop_list), selected))
|
||||
return;
|
||||
|
||||
parasite_proplist_set_object (PARASITE_PROPLIST (parasite->child_prop_list), selected);
|
||||
parasite_objecthierarchy_set_object (PARASITE_OBJECTHIERARCHY (parasite->oh), selected);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user