forked from openlp/openlp
cleanup, add settings tab
This commit is contained in:
parent
578e0fea3f
commit
d54ef615b4
@ -51,10 +51,13 @@ class MediaController(object):
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.isActive = False
|
||||
self.canBackground = False
|
||||
self.state = MediaState.Off
|
||||
self.hasOwnWidget = False
|
||||
self.audio_extensions_list = []
|
||||
self.video_extensions_list = []
|
||||
|
||||
def setup(self, display):
|
||||
def setup(self, display, hasAudio):
|
||||
"""
|
||||
Create the related widgets for the current display
|
||||
"""
|
||||
|
@ -54,11 +54,11 @@ class MediaManager(object):
|
||||
self.curDisplayMediaController = {}
|
||||
#Create Backend Controllers
|
||||
if WebkitController.is_available():
|
||||
self.backends['webkit'] = WebkitController(self)
|
||||
self.backends[u'Webkit'] = WebkitController(self)
|
||||
if PhononController.is_available():
|
||||
self.backends['phonon'] = PhononController(self)
|
||||
self.backends[u'Phonon'] = PhononController(self)
|
||||
if VlcController.is_available():
|
||||
self.backends['vlc'] = VlcController(self)
|
||||
self.backends[u'Vlc'] = VlcController(self)
|
||||
#Timer for video state
|
||||
self.Timer = QtCore.QTimer()
|
||||
self.Timer.setInterval(200)
|
||||
@ -117,21 +117,26 @@ class MediaManager(object):
|
||||
After a new display is configured, all media related widget
|
||||
will be created too
|
||||
"""
|
||||
hasAudio = True
|
||||
if not self.withLivePreview and \
|
||||
display == self.parent.liveController.previewDisplay:
|
||||
return
|
||||
if display == self.parent.previewController.previewDisplay or \
|
||||
display == self.parent.liveController.previewDisplay:
|
||||
hasAudio = False
|
||||
for backend in self.backends.values():
|
||||
backend.setup(display)
|
||||
backend.setup(display, hasAudio)
|
||||
|
||||
def resize(self, controller):
|
||||
"""
|
||||
After Mainwindow changes or Splitter moved all related media
|
||||
widgets have to be resized
|
||||
"""
|
||||
pass
|
||||
#TODO
|
||||
#for display in self.curDisplayMediaController.keys():
|
||||
# self.curDisplayMediaController[display].resize(display, controller)
|
||||
for display in self.curDisplayMediaController.keys():
|
||||
if display == self.parent.previewController.previewDisplay or \
|
||||
display == self.parent.liveController.previewDisplay:
|
||||
display.resize(display.parent.slidePreview.size())
|
||||
self.curDisplayMediaController[display].resize(display, controller)
|
||||
|
||||
def video(self, msg):
|
||||
"""
|
||||
@ -154,18 +159,23 @@ class MediaManager(object):
|
||||
if self.withLivePreview:
|
||||
display = controller.previewDisplay
|
||||
if self.check_file_type(display, videoPath, False):
|
||||
#check size of all media_widgets
|
||||
self.resize(controller)
|
||||
self.curDisplayMediaController[display] \
|
||||
.load(display, videoPath, volume)
|
||||
display = controller.display
|
||||
if self.check_file_type(display, videoPath, isBackground):
|
||||
#check size of all media_widgets
|
||||
self.resize(controller)
|
||||
isValid = self.curDisplayMediaController[display] \
|
||||
.load(display, videoPath, volume)
|
||||
controller.display.webLoaded = True
|
||||
else:
|
||||
display = controller.previewDisplay
|
||||
self.check_file_type(display, videoPath, isBackground)
|
||||
isValid = self.curDisplayMediaController[display] \
|
||||
.load(display, videoPath, volume)
|
||||
if self.check_file_type(display, videoPath, isBackground):
|
||||
#check size of all media_widgets
|
||||
self.resize(controller)
|
||||
isValid = self.curDisplayMediaController[display] \
|
||||
.load(display, videoPath, volume)
|
||||
if not isValid:
|
||||
#Media could not be loaded correctly
|
||||
critical_error_message_box(
|
||||
@ -173,10 +183,8 @@ class MediaManager(object):
|
||||
unicode(translate('MediaPlugin.MediaItem',
|
||||
'Unsupported File')))
|
||||
return
|
||||
#check size of all media_widgets
|
||||
self.resize(controller)
|
||||
# controller.display.webLoaded = True
|
||||
#now start playing
|
||||
print self.curDisplayMediaController[display]
|
||||
self.video_play(controller)
|
||||
|
||||
def check_file_type(self, display, videoPath, isBackground):
|
||||
@ -184,20 +192,30 @@ class MediaManager(object):
|
||||
Used to choose the right media backend type
|
||||
from the prioritized backend list
|
||||
"""
|
||||
if videoPath.endswith(u'.swf') or isBackground:
|
||||
self.curDisplayMediaController[display] = self.backends['webkit']
|
||||
elif QtCore.QSettings().value(u'media/use webkit',
|
||||
QtCore.QVariant(True)).toInt()[0] == 0:
|
||||
self.curDisplayMediaController[display] = self.backends['webkit']
|
||||
elif QtCore.QSettings().value(u'media/use vlc',
|
||||
QtCore.QVariant(True)).toInt()[0] == 0:
|
||||
self.curDisplayMediaController[display] = self.backends['vlc']
|
||||
elif QtCore.QSettings().value(u'media/use phonon',
|
||||
QtCore.QVariant(True)).toInt()[0] == 0:
|
||||
self.curDisplayMediaController[display] = self.backends['phonon']
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
usedBackends = QtCore.QSettings().value(u'media/backends',
|
||||
QtCore.QVariant(u'Webkit')).toString().split(u',')
|
||||
media_path = QtCore.QFileInfo(videoPath)
|
||||
if media_path.isFile():
|
||||
suffix = u'*.%s' % media_path.suffix()
|
||||
for title in usedBackends:
|
||||
backend = self.backends[str(title)]
|
||||
if suffix in backend.video_extensions_list:
|
||||
if isBackground:
|
||||
if backend.canBackground:
|
||||
self.curDisplayMediaController[display] = backend
|
||||
return True
|
||||
else:
|
||||
self.curDisplayMediaController[display] = backend
|
||||
return True
|
||||
return False
|
||||
# # Special FileType Check
|
||||
# if videoPath.endswith(u'.swf') or isBackground:
|
||||
# self.curDisplayMediaController[display] = self.backends[u'Webkit']
|
||||
# else:
|
||||
# # search extension in available backends
|
||||
# # currently only use the first available backend
|
||||
# self.curDisplayMediaController[display] = self.backends[str(usedBackends[0])]
|
||||
# return True
|
||||
|
||||
def video_play(self, controller):
|
||||
"""
|
||||
@ -324,3 +342,19 @@ class MediaManager(object):
|
||||
self.curDisplayMediaController[display].play(display)
|
||||
self.curDisplayMediaController[display] \
|
||||
.set_visible(display, True)
|
||||
|
||||
def get_audio_extensions_list(self):
|
||||
audio_list = []
|
||||
for backend in self.backends.values():
|
||||
for item in backend.audio_extensions_list:
|
||||
if not item in audio_list:
|
||||
audio_list.append(item)
|
||||
return audio_list
|
||||
|
||||
def get_video_extensions_list(self):
|
||||
video_list = []
|
||||
for backend in self.backends.values():
|
||||
for item in backend.video_extensions_list:
|
||||
if not item in video_list:
|
||||
video_list.append(item)
|
||||
return video_list
|
||||
|
@ -104,25 +104,36 @@ class MediaTab(SettingsTab):
|
||||
|
||||
def onUsePhononCheckBoxChanged(self, check_state):
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.usePhonon = self.backendOrderlistWidget.count()
|
||||
self.usePhonon = True
|
||||
if u'Phonon' not in self.usedBackends:
|
||||
self.usedBackends.append(u'Phonon')
|
||||
else:
|
||||
self.usePhonon = -1
|
||||
self.usePhonon = False
|
||||
self.usedBackends.takeAt(self.usedBackends.indexOf(u'Phonon'))
|
||||
self.updateBackendList()
|
||||
|
||||
def onUseVlcCheckBoxChanged(self, check_state):
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.useVlc = self.backendOrderlistWidget.count()
|
||||
self.useVlc = True
|
||||
if u'Vlc' not in self.usedBackends:
|
||||
self.usedBackends.append(u'Vlc')
|
||||
else:
|
||||
self.useVlc = -1
|
||||
self.useVlc = False
|
||||
self.usedBackends.takeAt(self.usedBackends.indexOf(u'Vlc'))
|
||||
self.updateBackendList()
|
||||
|
||||
def updateBackendList(self):
|
||||
self.backendOrderlistWidget.clear()
|
||||
for backend in self.usedBackends:
|
||||
self.backendOrderlistWidget.addItem(backend)
|
||||
|
||||
def onOrderingUpButtonPressed(self):
|
||||
currentRow = self.backendOrderlistWidget.currentRow()
|
||||
if currentRow > 0:
|
||||
item = self.backendOrderlistWidget.takeItem(currentRow)
|
||||
self.backendOrderlistWidget.insertItem(currentRow-1, item)
|
||||
self.backendOrderlistWidget.setCurrentRow(currentRow-1)
|
||||
self.updateOrdering()
|
||||
self.usedBackends.move(currentRow, currentRow-1)
|
||||
|
||||
def onOrderingDownButtonPressed(self):
|
||||
currentRow = self.backendOrderlistWidget.currentRow()
|
||||
@ -130,60 +141,25 @@ class MediaTab(SettingsTab):
|
||||
item = self.backendOrderlistWidget.takeItem(currentRow)
|
||||
self.backendOrderlistWidget.insertItem(currentRow+1, item)
|
||||
self.backendOrderlistWidget.setCurrentRow(currentRow+1)
|
||||
self.updateOrdering()
|
||||
|
||||
def updateOrdering(self):
|
||||
for num in range (0, self.backendOrderlistWidget.count()):
|
||||
item = self.backendOrderlistWidget.item(num)
|
||||
if item.text == u'Webkit':
|
||||
self.useWebkit = num
|
||||
elif item.text == u'Phonon':
|
||||
self.usePhonon = num
|
||||
elif item.text == u'Vlc':
|
||||
self.useVlc = num
|
||||
|
||||
def updateBackendList(self):
|
||||
self.backendOrderlistWidget.clear()
|
||||
for num in range(0, 3):
|
||||
if self.useWebkit == num:
|
||||
self.backendOrderlistWidget.addItem(u'Webkit')
|
||||
elif self.usePhonon == num:
|
||||
self.backendOrderlistWidget.addItem(u'Phonon')
|
||||
elif self.useVlc == num:
|
||||
self.backendOrderlistWidget.addItem(u'Vlc')
|
||||
self.usedBackends.move(currentRow, currentRow+1)
|
||||
|
||||
def load(self):
|
||||
self.useWebkit = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/use webkit',
|
||||
QtCore.QVariant(True)).toInt()[0]
|
||||
self.usePhonon = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/use phonon',
|
||||
QtCore.QVariant(True)).toInt()[0]
|
||||
self.useVlc = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/use vlc',
|
||||
QtCore.QVariant(True)).toInt()[0]
|
||||
self.usePhononCheckBox.setChecked((self.usePhonon != -1))
|
||||
self.useVlcCheckBox.setChecked((self.useVlc != -1))
|
||||
self.usedBackends = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/backends',
|
||||
QtCore.QVariant(u'Webkit')).toString().split(u',')
|
||||
self.useWebkit = u'Webkit' in self.usedBackends
|
||||
self.usePhonon = u'Phonon' in self.usedBackends
|
||||
self.useVlc = u'Vlc' in self.usedBackends
|
||||
self.usePhononCheckBox.setChecked(self.usePhonon)
|
||||
self.useVlcCheckBox.setChecked(self.useVlc)
|
||||
self.updateBackendList()
|
||||
|
||||
def save(self):
|
||||
changedValues = False
|
||||
oldUseWebkit = QtCore.QSettings().value(
|
||||
u'media/use webkit', QtCore.QVariant(True)).toInt()[0]
|
||||
if oldUseWebkit != self.useWebkit:
|
||||
QtCore.QSettings().setValue(self.settingsSection + u'/use webkit',
|
||||
QtCore.QVariant(self.useWebkit))
|
||||
changedValues = True
|
||||
oldUsePhonon = QtCore.QSettings().value(
|
||||
u'media/use phonon', QtCore.QVariant(True)).toInt()[0]
|
||||
if oldUsePhonon != self.usePhonon:
|
||||
QtCore.QSettings().setValue(self.settingsSection + u'/use phonon',
|
||||
QtCore.QVariant(self.usePhonon))
|
||||
changedValues = True
|
||||
oldUseVlc = QtCore.QSettings().value(
|
||||
u'media/use vlc', QtCore.QVariant(True)).toInt()[0]
|
||||
if oldUseVlc != self.useVlc:
|
||||
QtCore.QSettings().setValue(self.settingsSection + u'/use vlc',
|
||||
QtCore.QVariant(self.useVlc))
|
||||
changedValues = True
|
||||
if changedValues:
|
||||
oldBackendString = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/backends',
|
||||
QtCore.QVariant(True)).toString()
|
||||
newBackendString = self.usedBackends.join(u',')
|
||||
if oldBackendString != newBackendString:
|
||||
QtCore.QSettings().setValue(self.settingsSection + u'/backends',
|
||||
QtCore.QVariant(newBackendString))
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
|
@ -26,6 +26,7 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
@ -59,8 +60,37 @@ class PhononController(MediaController):
|
||||
u'video/x-matroska': [u'.mpv', u'.mkv'],
|
||||
u'video/x-wmv': [u'.wmv'],
|
||||
u'video/x-ms-wmv': [u'.wmv']}
|
||||
mimetypes.init()
|
||||
for mimetype in Phonon.BackendCapabilities.availableMimeTypes():
|
||||
mimetype = unicode(mimetype)
|
||||
if mimetype.startswith(u'audio/'):
|
||||
self._addToList(self.audio_extensions_list, mimetype)
|
||||
elif mimetype.startswith(u'video/'):
|
||||
self._addToList(self.video_extensions_list, mimetype)
|
||||
|
||||
def setup(self, display):
|
||||
def _addToList(self, list, mimetype):
|
||||
# Add all extensions which mimetypes provides us for supported types.
|
||||
extensions = mimetypes.guess_all_extensions(unicode(mimetype))
|
||||
for extension in extensions:
|
||||
ext = u'*%s' % extension
|
||||
if ext not in list:
|
||||
list.append(ext)
|
||||
self.parent.parent.serviceManager.supportedSuffixes(extension[1:])
|
||||
log.info(u'MediaPlugin: %s extensions: %s' % (mimetype,
|
||||
u' '.join(extensions)))
|
||||
# Add extensions for this mimetype from self.additional_extensions.
|
||||
# This hack clears mimetypes' and operating system's shortcomings
|
||||
# by providing possibly missing extensions.
|
||||
if mimetype in self.additional_extensions.keys():
|
||||
for extension in self.additional_extensions[mimetype]:
|
||||
ext = u'*%s' % extension
|
||||
if ext not in list:
|
||||
list.append(ext)
|
||||
self.parent.parent.serviceManager.supportedSuffixes(extension[1:])
|
||||
log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype,
|
||||
u' '.join(self.additional_extensions[mimetype])))
|
||||
|
||||
def setup(self, display, hasAudio):
|
||||
display.phononWidget = Phonon.VideoWidget(display)
|
||||
display.phononWidget.setVisible(False)
|
||||
display.phononWidget.resize(display.size())
|
||||
|
@ -46,11 +46,37 @@ class VlcController(MediaController):
|
||||
def __init__(self, parent):
|
||||
MediaController.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.video_extensions_list = [
|
||||
u'*.3gp'
|
||||
, u'*.asf', u'*.wmv'
|
||||
, u'*.au'
|
||||
, u'*.avi'
|
||||
, u'*.flv'
|
||||
, u'*.mov'
|
||||
, u'*.mp4'
|
||||
, u'*.ogm', u'*.ogg'
|
||||
, u'*.mkv', u'*.mka'
|
||||
, u'*.ts', u'*.mpg'
|
||||
, u'*.mpg', u'*.mp3', u'*.mp2'
|
||||
, u'*.nsc'
|
||||
, u'*.nsv'
|
||||
, u'*.nut'
|
||||
, u'*.ra', u'*.ram', u'*.rm', u'*.rv' ,u'*.rmbv'
|
||||
, u'*.a52', u'*.dts', u'*.aac', u'*.flac' ,u'*.dv', u'*.vid'
|
||||
, u'*.tta', u'*.tac'
|
||||
, u'*.ty'
|
||||
, u'*.wav', u'*.dts'
|
||||
, u'*.xa'
|
||||
, u'*.iso'
|
||||
]
|
||||
|
||||
def setup(self, display):
|
||||
def setup(self, display, hasAudio):
|
||||
display.vlcWidget = QtGui.QFrame(display)
|
||||
# creating a basic vlc instance
|
||||
display.vlcInstance = vlc.Instance()
|
||||
if hasAudio:
|
||||
display.vlcInstance = vlc.Instance()
|
||||
else:
|
||||
display.vlcInstance = vlc.Instance('--no-audio')
|
||||
display.vlcInstance.set_log_verbosity(2)
|
||||
# creating an empty vlc media player
|
||||
display.vlcMediaPlayer = display.vlcInstance.media_player_new()
|
||||
|
@ -42,13 +42,33 @@ class WebkitController(MediaController):
|
||||
MediaController.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.isFlash = False
|
||||
self.additional_extensions = {
|
||||
u'video/shockwave': [u'.swf']}
|
||||
self.canBackground = True
|
||||
self.video_extensions_list = [
|
||||
u'*.3gp'
|
||||
, u'*.3gpp'
|
||||
, u'*.3g2'
|
||||
, u'*.3gpp2'
|
||||
, u'*.aac'
|
||||
, u'*.flv'
|
||||
, u'*.f4a'
|
||||
, u'*.f4b'
|
||||
, u'*.f4p'
|
||||
, u'*.f4v'
|
||||
, u'*.mov'
|
||||
, u'*.m4a'
|
||||
, u'*.m4b'
|
||||
, u'*.m4p'
|
||||
, u'*.m4v'
|
||||
, u'*.mkv'
|
||||
, u'*.mp4'
|
||||
, u'*.mp3'
|
||||
, u'*.ogg'
|
||||
, u'*.ogv'
|
||||
, u'*.webm'
|
||||
, u'*.swf', u'*.mpg', u'*.wmv'
|
||||
]
|
||||
|
||||
def setup(self, display):
|
||||
# if display == self.parent.previewController.previewDisplay or \
|
||||
# display == self.parent.liveController.previewDisplay:
|
||||
# display.webView.resize(display.size())
|
||||
def setup(self, display, hasAudio):
|
||||
display.webView.raise_()
|
||||
self.hasOwnWidget = False
|
||||
|
||||
@ -57,21 +77,7 @@ class WebkitController(MediaController):
|
||||
return True
|
||||
|
||||
def get_supported_file_types(self):
|
||||
self.supported_file_types = ['avi']
|
||||
self.additional_extensions = {
|
||||
u'audio/ac3': [u'.ac3'],
|
||||
u'audio/flac': [u'.flac'],
|
||||
u'audio/x-m4a': [u'.m4a'],
|
||||
u'audio/midi': [u'.mid', u'.midi'],
|
||||
u'audio/x-mp3': [u'.mp3'],
|
||||
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
|
||||
u'audio/qcelp': [u'.qcp'],
|
||||
u'audio/x-wma': [u'.wma'],
|
||||
u'audio/x-ms-wma': [u'.wma'],
|
||||
u'video/x-flv': [u'.flv'],
|
||||
u'video/x-matroska': [u'.mpv', u'.mkv'],
|
||||
u'video/x-wmv': [u'.wmv'],
|
||||
u'video/x-ms-wmv': [u'.wmv']}
|
||||
pass
|
||||
|
||||
def load(self, display, path, volume):
|
||||
log.debug(u'load vid in Webkit Controller')
|
||||
|
@ -25,9 +25,6 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
|
||||
from PyQt4.phonon import Phonon
|
||||
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||
from openlp.plugins.media.lib import MediaMediaItem, MediaTab, MediaManager
|
||||
@ -45,52 +42,11 @@ class MediaPlugin(Plugin):
|
||||
self.icon = build_icon(self.icon_path)
|
||||
# passed with drag and drop messages
|
||||
self.dnd_id = u'Media'
|
||||
self.additional_extensions = {
|
||||
u'audio/ac3': [u'.ac3'],
|
||||
u'audio/flac': [u'.flac'],
|
||||
u'audio/x-m4a': [u'.m4a'],
|
||||
u'audio/midi': [u'.mid', u'.midi'],
|
||||
u'audio/x-mp3': [u'.mp3'],
|
||||
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
|
||||
u'audio/qcelp': [u'.qcp'],
|
||||
u'audio/x-wma': [u'.wma'],
|
||||
u'audio/x-ms-wma': [u'.wma'],
|
||||
u'video/x-flv': [u'.flv'],
|
||||
u'video/x-matroska': [u'.mpv', u'.mkv'],
|
||||
u'video/x-wmv': [u'.wmv'],
|
||||
u'video/x-ms-wmv': [u'.wmv']}
|
||||
self.audio_extensions_list = []
|
||||
self.video_extensions_list = []
|
||||
mimetypes.init()
|
||||
for mimetype in Phonon.BackendCapabilities.availableMimeTypes():
|
||||
mimetype = unicode(mimetype)
|
||||
if mimetype.startswith(u'audio/'):
|
||||
self._addToList(self.audio_extensions_list, mimetype)
|
||||
elif mimetype.startswith(u'video/'):
|
||||
self._addToList(self.video_extensions_list, mimetype)
|
||||
self.mediaManager = MediaManager(self)
|
||||
|
||||
def _addToList(self, list, mimetype):
|
||||
# Add all extensions which mimetypes provides us for supported types.
|
||||
extensions = mimetypes.guess_all_extensions(unicode(mimetype))
|
||||
for extension in extensions:
|
||||
ext = u'*%s' % extension
|
||||
if ext not in list:
|
||||
list.append(ext)
|
||||
self.serviceManager.supportedSuffixes(extension[1:])
|
||||
log.info(u'MediaPlugin: %s extensions: %s' % (mimetype,
|
||||
u' '.join(extensions)))
|
||||
# Add extensions for this mimetype from self.additional_extensions.
|
||||
# This hack clears mimetypes' and operating system's shortcomings
|
||||
# by providing possibly missing extensions.
|
||||
if mimetype in self.additional_extensions.keys():
|
||||
for extension in self.additional_extensions[mimetype]:
|
||||
ext = u'*%s' % extension
|
||||
if ext not in list:
|
||||
list.append(ext)
|
||||
self.serviceManager.supportedSuffixes(extension[1:])
|
||||
log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype,
|
||||
u' '.join(self.additional_extensions[mimetype])))
|
||||
self.audio_extensions_list = \
|
||||
self.mediaManager.get_audio_extensions_list()
|
||||
self.video_extensions_list = \
|
||||
self.mediaManager.get_video_extensions_list()
|
||||
|
||||
def about(self):
|
||||
about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
|
||||
|
Loading…
Reference in New Issue
Block a user