Moved remaining popups to classes and some cleanup
This commit is contained in:
parent
f5eac69c20
commit
a237757e5e
@ -13,6 +13,8 @@ from gi.repository import GLib
|
||||
from .controller_data import Controller_Data
|
||||
from .mixins.signals_mixins import SignalsMixins
|
||||
|
||||
from widgets.popups.message_popup_widget import MessagePopupWidget
|
||||
from widgets.popups.path_menu_popup_widget import PathMenuPopupWidget
|
||||
from widgets.popups.plugins_popup_widget import PluginsPopupWidget
|
||||
from widgets.popups.io_popup_widget import IOPopupWidget
|
||||
|
||||
@ -56,6 +58,8 @@ class Controller(UI, SignalsMixins, Controller_Data):
|
||||
# NOTE: Really we will move these to the UI/(New) Window 'base' controller
|
||||
# after we're done cleaning and refactoring to use fewer mixins.
|
||||
def _load_widgets(self):
|
||||
MessagePopupWidget()
|
||||
PathMenuPopupWidget()
|
||||
PluginsPopupWidget()
|
||||
IOPopupWidget()
|
||||
ContextMenuWidget()
|
||||
@ -68,7 +72,6 @@ class Controller(UI, SignalsMixins, Controller_Data):
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
||||
event_system.subscribe("get_current_state", self.get_current_state)
|
||||
event_system.subscribe("display_message", self.display_message)
|
||||
event_system.subscribe("go_to_path", self.go_to_path)
|
||||
event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
|
||||
# NOTE: Needs to be moved (probably just to file actions class) after reducing mixins usage
|
||||
@ -171,7 +174,8 @@ class Controller(UI, SignalsMixins, Controller_Data):
|
||||
event_system.emit("show_io_popup")
|
||||
if action == "plugins_popup":
|
||||
event_system.emit("show_plugins_popup")
|
||||
|
||||
if action == "messages_popup":
|
||||
event_system.emit("show_messages_popup")
|
||||
|
||||
|
||||
@endpoint_registry.register(rule="go_home")
|
||||
|
@ -55,7 +55,6 @@ class Controller_Data:
|
||||
|
||||
self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn")
|
||||
self.warning_alert = self.builder.get_object("warning_alert")
|
||||
self.path_menu = self.builder.get_object("path_menu")
|
||||
self.path_entry = self.builder.get_object("path_entry")
|
||||
|
||||
self.bottom_size_label = self.builder.get_object("bottom_size_label")
|
||||
|
@ -1,55 +0,0 @@
|
||||
# Python imports
|
||||
import traceback
|
||||
import time
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class ExceptionHookMixin:
|
||||
""" ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """
|
||||
|
||||
def custom_except_hook(self, exec_type, value, _traceback):
|
||||
trace = ''.join(traceback.format_tb(_traceback))
|
||||
data = f"Exec Type: {exec_type} <--> Value: {value}\n\n{trace}\n\n\n\n"
|
||||
start_itr = self.message_buffer.get_start_iter()
|
||||
self.message_buffer.place_cursor(start_itr)
|
||||
self.display_message(self.error_color, data)
|
||||
|
||||
def display_message(self, type, text, seconds=None):
|
||||
self.message_buffer.insert_at_cursor(text)
|
||||
self.message_popup_widget.popup()
|
||||
if seconds:
|
||||
self.hide_message_timeout(seconds)
|
||||
|
||||
@threaded
|
||||
def hide_message_timeout(self, seconds=3):
|
||||
time.sleep(seconds)
|
||||
GLib.idle_add(self.message_popup_widget.popdown)
|
||||
|
||||
def save_debug_alerts(self, widget=None, eve=None):
|
||||
start_itr, end_itr = self.message_buffer.get_bounds()
|
||||
save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \
|
||||
action = Gtk.FileChooserAction.SAVE, \
|
||||
buttons = (Gtk.STOCK_CANCEL, \
|
||||
Gtk.ResponseType.CANCEL, \
|
||||
Gtk.STOCK_SAVE, \
|
||||
Gtk.ResponseType.OK))
|
||||
|
||||
text = self.message_buffer.get_text(start_itr, end_itr, False)
|
||||
resp = save_location_prompt.run()
|
||||
if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT):
|
||||
pass
|
||||
elif resp == Gtk.ResponseType.OK:
|
||||
target = save_location_prompt.get_filename();
|
||||
with open(target, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
save_location_prompt.destroy()
|
@ -1,13 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Gtk imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
class ShowHideMixin:
|
||||
def show_messages_popup(self, type, text, seconds=None):
|
||||
self.message_popup_widget.popup()
|
@ -1,7 +1,6 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from .exception_hook_mixin import ExceptionHookMixin
|
||||
from .signals.file_action_signals_mixin import FileActionSignalsMixin
|
||||
from .signals.ipc_signals_mixin import IPCSignalsMixin
|
||||
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
|
||||
@ -11,5 +10,5 @@ from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
|
||||
|
||||
|
||||
|
||||
class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin):
|
||||
class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin):
|
||||
...
|
||||
|
@ -167,9 +167,9 @@ class TabMixin(GridMixin):
|
||||
show_path_menu = True
|
||||
|
||||
if not show_path_menu:
|
||||
self.path_menu.popdown()
|
||||
event_system.emit("hide_path_menu")
|
||||
else:
|
||||
self.path_menu.popup()
|
||||
event_system.emit("show_path_menu")
|
||||
widget.grab_focus_without_selecting()
|
||||
widget.set_position(-1)
|
||||
|
||||
@ -194,7 +194,7 @@ class TabMixin(GridMixin):
|
||||
path_entry.set_text(path)
|
||||
path_entry.grab_focus_without_selecting()
|
||||
path_entry.set_position(-1)
|
||||
self.path_menu.popdown()
|
||||
event_system.emit("hide_path_menu")
|
||||
|
||||
def show_hide_hidden_files(self):
|
||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||
|
@ -188,7 +188,7 @@ class WindowMixin(TabMixin):
|
||||
|
||||
def grid_icon_single_click(self, icons_grid, eve):
|
||||
try:
|
||||
self.path_menu.popdown()
|
||||
event_system.emit("hide_path_menu")
|
||||
wid, tid = icons_grid.get_name().split("|")
|
||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||
self.set_path_text(wid, tid)
|
||||
|
@ -3,10 +3,11 @@
|
||||
# Gtk imports
|
||||
|
||||
# Application imports
|
||||
from .mixins.show_hide_mixin import ShowHideMixin
|
||||
from .mixins.ui.pane_mixin import PaneMixin
|
||||
from .mixins.ui.window_mixin import WindowMixin
|
||||
|
||||
|
||||
class UI(PaneMixin, WindowMixin, ShowHideMixin):
|
||||
|
||||
|
||||
class UI(PaneMixin, WindowMixin):
|
||||
...
|
||||
|
@ -9,6 +9,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class AboutWidget:
|
||||
"""docstring for AboutWidget."""
|
||||
|
||||
|
@ -9,6 +9,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class AppchooserWidget:
|
||||
"""docstring for AppchooserWidget."""
|
||||
|
||||
|
@ -9,6 +9,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class ContextMenuWidget(Gtk.Menu):
|
||||
"""docstring for ContextMenuWidget"""
|
||||
|
||||
|
@ -10,6 +10,8 @@ from gi.repository import GLib
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class FileExistsWidget:
|
||||
"""docstring for FileExistsWidget."""
|
||||
|
||||
|
@ -12,6 +12,8 @@ from gi.repository import GdkPixbuf
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IconGridWidget(Gtk.IconView):
|
||||
"""docstring for IconGridWidget"""
|
||||
|
||||
|
@ -11,6 +11,8 @@ from gi.repository import GdkPixbuf
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IconTreeWidget(Gtk.TreeView):
|
||||
"""docstring for IconTreeWidget"""
|
||||
|
||||
|
@ -9,6 +9,8 @@ from gi.repository import Gio
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IOWidget(Gtk.Box):
|
||||
"""docstring for IOWidget"""
|
||||
|
||||
|
@ -11,6 +11,8 @@ from gi.repository import Gdk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class NewFileWidget:
|
||||
"""docstring for NewFileWidget."""
|
||||
|
||||
|
@ -9,6 +9,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IOPopupWidget(Gtk.Popover):
|
||||
"""docstring for IOPopupWidget."""
|
||||
|
||||
@ -29,12 +31,15 @@ class IOPopupWidget(Gtk.Popover):
|
||||
self.set_relative_to(io_button)
|
||||
self.set_modal(True)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(320, 280)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_io_popup", self.show_io_popup)
|
||||
|
||||
def _load_widgets(self):
|
||||
vbox = Gtk.Box()
|
||||
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.builder.expose_object(f"io_list", vbox)
|
||||
self.add(vbox)
|
||||
|
||||
|
@ -0,0 +1,114 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
import traceback
|
||||
import time
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class MessagePopupWidget(Gtk.Popover):
|
||||
""" MessagePopupWidget custom exception hook viewer to reroute to log Gtk text area too. """
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super(MessagePopupWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
|
||||
self.builder.expose_object(f"message_popup_widget", self)
|
||||
self._message_buffer = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_relative_to( self.builder.get_object(f"main_menu_bar") )
|
||||
self.set_modal(True)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(620, 580)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_messages_popup", self.show_messages_popup)
|
||||
event_system.subscribe("hide_messages_popup", self.hide_messages_popup)
|
||||
|
||||
|
||||
def _load_widgets(self):
|
||||
self._message_buffer = Gtk.TextBuffer()
|
||||
vbox = Gtk.Box()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
message_text_view = Gtk.TextView.new_with_buffer(self._message_buffer)
|
||||
|
||||
button = Gtk.Button(label="Save As")
|
||||
button.set_image( Gtk.Image(stock=Gtk.STOCK_SAVE_AS) )
|
||||
button.connect("released", self.save_debug_alerts)
|
||||
button.set_always_show_image(True)
|
||||
|
||||
vbox.set_vexpand(True)
|
||||
vbox.set_hexpand(True)
|
||||
scroll_window.set_vexpand(True)
|
||||
scroll_window.set_hexpand(True)
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.builder.expose_object(f"message_popup_widget", self)
|
||||
self.builder.expose_object(f"message_text_view", message_text_view)
|
||||
|
||||
vbox.add(button)
|
||||
scroll_window.add(message_text_view)
|
||||
vbox.add(scroll_window)
|
||||
self.add(vbox)
|
||||
|
||||
def show_messages_popup(self):
|
||||
self.popup()
|
||||
|
||||
def hide_messages_popup(self):
|
||||
self.popup()
|
||||
|
||||
|
||||
def custom_except_hook(self, exec_type, value, _traceback):
|
||||
trace = ''.join(traceback.format_tb(_traceback))
|
||||
data = f"Exec Type: {exec_type} <--> Value: {value}\n\n{trace}\n\n\n\n"
|
||||
start_itr = self._message_buffer.get_start_iter()
|
||||
self._message_buffer.place_cursor(start_itr)
|
||||
self.display_message(self.error_color, data)
|
||||
|
||||
def display_message(self, type, text, seconds=None):
|
||||
self._message_buffer.insert_at_cursor(text)
|
||||
self.popup()
|
||||
if seconds:
|
||||
self.hide_message_timeout(seconds)
|
||||
|
||||
@threaded
|
||||
def hide_message_timeout(self, seconds=3):
|
||||
time.sleep(seconds)
|
||||
# GLib.idle_add(self.message_popup_widget.popdown)
|
||||
GLib.idle_add(event_system.emit, ("hide_messages_popup"))
|
||||
|
||||
def save_debug_alerts(self, widget=None, eve=None):
|
||||
start_itr, end_itr = self._message_buffer.get_bounds()
|
||||
save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", settings.get_main_window(), \
|
||||
action = Gtk.FileChooserAction.SAVE, \
|
||||
buttons = (Gtk.STOCK_CANCEL, \
|
||||
Gtk.ResponseType.CANCEL, \
|
||||
Gtk.STOCK_SAVE, \
|
||||
Gtk.ResponseType.OK))
|
||||
|
||||
text = self._message_buffer.get_text(start_itr, end_itr, False)
|
||||
resp = save_location_prompt.run()
|
||||
if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT):
|
||||
pass
|
||||
elif resp == Gtk.ResponseType.OK:
|
||||
target = save_location_prompt.get_filename();
|
||||
with open(target, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
save_location_prompt.destroy()
|
@ -0,0 +1,57 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class PathMenuPopupWidget(Gtk.Popover):
|
||||
"""docstring for PathMenuPopupWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(PathMenuPopupWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
|
||||
self.builder.expose_object(f"path_menu", self)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
path_entry = self.builder.get_object(f"path_entry")
|
||||
self.set_relative_to(path_entry)
|
||||
self.set_modal(False)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(240, 420)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_path_menu", self.show_path_menu)
|
||||
event_system.subscribe("hide_path_menu", self.hide_path_menu)
|
||||
|
||||
def _load_widgets(self):
|
||||
path_menu_buttons = Gtk.ButtonBox()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
view_port = Gtk.Viewport()
|
||||
|
||||
scroll_window.set_vexpand(True)
|
||||
scroll_window.set_hexpand(True)
|
||||
path_menu_buttons.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.builder.expose_object(f"path_menu_buttons", path_menu_buttons)
|
||||
view_port.add(path_menu_buttons)
|
||||
scroll_window.add(view_port)
|
||||
self.add(scroll_window)
|
||||
|
||||
def show_path_menu(self, widget=None, eve=None):
|
||||
self.popup()
|
||||
|
||||
def hide_path_menu(self, widget=None, eve=None):
|
||||
self.popdown()
|
@ -9,6 +9,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class PluginsPopupWidget(Gtk.Popover):
|
||||
"""docstring for PluginsPopupWidget."""
|
||||
|
||||
@ -37,6 +39,8 @@ class PluginsPopupWidget(Gtk.Popover):
|
||||
|
||||
def _load_widgets(self):
|
||||
vbox = Gtk.Box()
|
||||
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.builder.expose_object(f"plugin_control_list", vbox)
|
||||
self.add(vbox)
|
||||
|
||||
|
@ -11,6 +11,8 @@ from gi.repository import Gdk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class RenameWidget:
|
||||
"""docstring for RenameWidget."""
|
||||
|
||||
|
@ -8,6 +8,8 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class TabHeaderWidget(Gtk.ButtonBox):
|
||||
"""docstring for TabHeaderWidget"""
|
||||
|
||||
|
@ -32,7 +32,6 @@
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-stop</property>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="message_buffer"/>
|
||||
<object class="GtkFileChooserDialog" id="save_load_dialog">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
@ -254,11 +253,12 @@
|
||||
<child>
|
||||
<object class="GtkImageMenuItem">
|
||||
<property name="label">Show Errors</property>
|
||||
<property name="name">messages_popup</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="image">image3</property>
|
||||
<property name="use-stock">False</property>
|
||||
<signal name="button-release-event" handler="show_messages_popup" swapped="no"/>
|
||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@ -901,94 +901,6 @@
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkPopover" id="message_popup_widget">
|
||||
<property name="width-request">320</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="relative-to">main_menu_bar</property>
|
||||
<property name="position">bottom</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="button-release-event" handler="save_debug_alerts" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="height-request">600</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="overlay-scrolling">False</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="message_text_view">
|
||||
<property name="name">message_view</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="cursor-visible">False</property>
|
||||
<property name="buffer">message_buffer</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkPopover" id="path_menu">
|
||||
<property name="width-request">240</property>
|
||||
<property name="height-request">420</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="relative-to">path_entry</property>
|
||||
<property name="position">bottom</property>
|
||||
<property name="modal">False</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="overlay-scrolling">False</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="path_menu_buttons">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="layout-style">start</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMessageDialog" id="warning_alert">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
|
Loading…
Reference in New Issue
Block a user