mirror of
https://github.com/cookiengineer/audacity
synced 2025-07-04 22:49:07 +02:00
All UIHandle::Preview() return same cursors, messages as HitTest...
... except TrackSelectHandle. Cursor changes to a hand only after button-down. HitTests give a UIHandle, whenever they also give any cursor or status, even when it's unsafe to click and drag; Click override is reponsible for cancelling. SelectHandle::Preview introduces much duplication, but the original in the hit test will later be deleted.
This commit is contained in:
parent
3a8280c562
commit
d2fbca83b2
@ -155,10 +155,12 @@ UIHandle::Result StretchHandle::Click
|
|||||||
{
|
{
|
||||||
using namespace RefreshCode;
|
using namespace RefreshCode;
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
if ( unsafe )
|
||||||
|
return Cancelled;
|
||||||
|
|
||||||
const wxMouseEvent &event = evt.event;
|
const wxMouseEvent &event = evt.event;
|
||||||
|
|
||||||
if (unsafe ||
|
if (event.LeftDClick() ||
|
||||||
event.LeftDClick() ||
|
|
||||||
!event.LeftDown() ||
|
!event.LeftDown() ||
|
||||||
evt.pCell == NULL)
|
evt.pCell == NULL)
|
||||||
return Cancelled;
|
return Cancelled;
|
||||||
|
@ -50,12 +50,7 @@ HitTestPreview CutlineHandle::HitPreview(bool cutline, bool unsafe)
|
|||||||
HitTestResult CutlineHandle::HitAnywhere(const AudacityProject *pProject, bool cutline)
|
HitTestResult CutlineHandle::HitAnywhere(const AudacityProject *pProject, bool cutline)
|
||||||
{
|
{
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
return {
|
return { HitPreview(cutline, unsafe), &Instance() };
|
||||||
HitPreview(cutline, unsafe),
|
|
||||||
(unsafe
|
|
||||||
? NULL
|
|
||||||
: &Instance())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@ -131,6 +126,11 @@ CutlineHandle::~CutlineHandle()
|
|||||||
UIHandle::Result CutlineHandle::Click
|
UIHandle::Result CutlineHandle::Click
|
||||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
using namespace RefreshCode;
|
||||||
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
if ( unsafe )
|
||||||
|
return Cancelled;
|
||||||
|
|
||||||
const wxMouseEvent &event = evt.event;
|
const wxMouseEvent &event = evt.event;
|
||||||
ViewInfo &viewInfo = pProject->GetViewInfo();
|
ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||||
const auto pTrack = static_cast<Track*>(evt.pCell.get());
|
const auto pTrack = static_cast<Track*>(evt.pCell.get());
|
||||||
@ -138,12 +138,8 @@ UIHandle::Result CutlineHandle::Click
|
|||||||
// Can affect the track by merging clips, expanding a cutline, or
|
// Can affect the track by merging clips, expanding a cutline, or
|
||||||
// deleting a cutline.
|
// deleting a cutline.
|
||||||
// All the change is done at button-down. Button-up just commits the undo item.
|
// All the change is done at button-down. Button-up just commits the undo item.
|
||||||
using namespace RefreshCode;
|
|
||||||
|
|
||||||
/// Someone has just clicked the mouse. What do we do?
|
/// Someone has just clicked the mouse. What do we do?
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
|
||||||
if (unsafe)
|
|
||||||
return Cancelled;
|
|
||||||
|
|
||||||
WaveTrackLocation capturedTrackLocation;
|
WaveTrackLocation capturedTrackLocation;
|
||||||
|
|
||||||
@ -222,9 +218,10 @@ UIHandle::Result CutlineHandle::Drag
|
|||||||
}
|
}
|
||||||
|
|
||||||
HitTestPreview CutlineHandle::Preview
|
HitTestPreview CutlineHandle::Preview
|
||||||
(const TrackPanelMouseState &, const AudacityProject *)
|
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
return HitPreview(mbCutline, false);
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
return HitPreview( mbCutline, unsafe );
|
||||||
}
|
}
|
||||||
|
|
||||||
UIHandle::Result CutlineHandle::Release
|
UIHandle::Result CutlineHandle::Release
|
||||||
|
@ -67,12 +67,7 @@ HitTestResult SampleHandle::HitAnywhere
|
|||||||
(const wxMouseState &state, const AudacityProject *pProject)
|
(const wxMouseState &state, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
return {
|
return { HitPreview(state, pProject, unsafe), &Instance() };
|
||||||
HitPreview(state, pProject, unsafe),
|
|
||||||
(unsafe
|
|
||||||
? NULL
|
|
||||||
: &Instance())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -206,17 +201,17 @@ namespace {
|
|||||||
UIHandle::Result SampleHandle::Click
|
UIHandle::Result SampleHandle::Click
|
||||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
using namespace RefreshCode;
|
||||||
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
if ( unsafe )
|
||||||
|
return Cancelled;
|
||||||
|
|
||||||
const wxMouseEvent &event = evt.event;
|
const wxMouseEvent &event = evt.event;
|
||||||
const wxRect &rect = evt.rect;
|
const wxRect &rect = evt.rect;
|
||||||
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||||
const auto pTrack = std::static_pointer_cast<WaveTrack>(evt.pCell);
|
const auto pTrack = std::static_pointer_cast<WaveTrack>(evt.pCell);
|
||||||
|
|
||||||
using namespace RefreshCode;
|
|
||||||
|
|
||||||
/// Someone has just clicked the mouse. What do we do?
|
/// Someone has just clicked the mouse. What do we do?
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
|
||||||
if (unsafe)
|
|
||||||
return Cancelled;
|
|
||||||
if (!IsSampleEditingPossible(
|
if (!IsSampleEditingPossible(
|
||||||
event, rect, viewInfo, pTrack.get(), rect.width))
|
event, rect, viewInfo, pTrack.get(), rect.width))
|
||||||
return Cancelled;
|
return Cancelled;
|
||||||
@ -419,7 +414,8 @@ UIHandle::Result SampleHandle::Drag
|
|||||||
HitTestPreview SampleHandle::Preview
|
HitTestPreview SampleHandle::Preview
|
||||||
(const TrackPanelMouseState &st, const AudacityProject *pProject)
|
(const TrackPanelMouseState &st, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
return HitPreview(st.state, pProject, false);
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
return HitPreview(st.state, pProject, unsafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
UIHandle::Result SampleHandle::Release
|
UIHandle::Result SampleHandle::Release
|
||||||
|
@ -54,12 +54,7 @@ HitTestPreview EnvelopeHandle::HitPreview(const AudacityProject *pProject, bool
|
|||||||
HitTestResult EnvelopeHandle::HitAnywhere(const AudacityProject *pProject)
|
HitTestResult EnvelopeHandle::HitAnywhere(const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
return {
|
return { HitPreview(pProject, unsafe), &Instance() };
|
||||||
HitPreview(pProject, unsafe),
|
|
||||||
(unsafe
|
|
||||||
? NULL
|
|
||||||
: &Instance())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -182,15 +177,15 @@ EnvelopeHandle::~EnvelopeHandle()
|
|||||||
UIHandle::Result EnvelopeHandle::Click
|
UIHandle::Result EnvelopeHandle::Click
|
||||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
using namespace RefreshCode;
|
||||||
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
if ( unsafe )
|
||||||
|
return Cancelled;
|
||||||
|
|
||||||
const wxMouseEvent &event = evt.event;
|
const wxMouseEvent &event = evt.event;
|
||||||
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||||
const auto pTrack = static_cast<Track*>(evt.pCell.get());
|
const auto pTrack = static_cast<Track*>(evt.pCell.get());
|
||||||
|
|
||||||
using namespace RefreshCode;
|
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
|
||||||
if (unsafe)
|
|
||||||
return Cancelled;
|
|
||||||
|
|
||||||
if (pTrack->GetKind() == Track::Wave) {
|
if (pTrack->GetKind() == Track::Wave) {
|
||||||
WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
|
WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
|
||||||
if (wt->GetDisplay() != WaveTrack::Waveform)
|
if (wt->GetDisplay() != WaveTrack::Waveform)
|
||||||
@ -257,7 +252,8 @@ UIHandle::Result EnvelopeHandle::Drag
|
|||||||
HitTestPreview EnvelopeHandle::Preview
|
HitTestPreview EnvelopeHandle::Preview
|
||||||
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
return HitPreview(pProject, false);
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
return HitPreview(pProject, unsafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
UIHandle::Result EnvelopeHandle::Release
|
UIHandle::Result EnvelopeHandle::Release
|
||||||
|
@ -49,6 +49,11 @@ enum {
|
|||||||
|
|
||||||
// #define SPECTRAL_EDITING_ESC_KEY
|
// #define SPECTRAL_EDITING_ESC_KEY
|
||||||
|
|
||||||
|
bool SelectHandle::IsClicked() const
|
||||||
|
{
|
||||||
|
return mSelectionStateChanger.get();
|
||||||
|
}
|
||||||
|
|
||||||
SelectHandle::SelectHandle()
|
SelectHandle::SelectHandle()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -888,14 +893,95 @@ UIHandle::Result SelectHandle::Drag
|
|||||||
}
|
}
|
||||||
|
|
||||||
HitTestPreview SelectHandle::Preview
|
HitTestPreview SelectHandle::Preview
|
||||||
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
(const TrackPanelMouseState &st, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
auto pTrack = mpTrack.lock();
|
||||||
|
if (!pTrack)
|
||||||
|
return {};
|
||||||
|
|
||||||
wxString tip;
|
wxString tip;
|
||||||
wxCursor *pCursor;
|
wxCursor *pCursor = SelectCursor();
|
||||||
SetTipAndCursorForBoundary
|
if ( IsClicked() )
|
||||||
(SelectionBoundary(mSelectionBoundary),
|
// Use same cursor as at the clck
|
||||||
(mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
|
SetTipAndCursorForBoundary
|
||||||
tip, pCursor);
|
(SelectionBoundary(mSelectionBoundary),
|
||||||
|
(mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
|
||||||
|
tip, pCursor);
|
||||||
|
else {
|
||||||
|
// Choose one of many cursors for mouse-over
|
||||||
|
|
||||||
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||||
|
|
||||||
|
const bool bMultiToolMode =
|
||||||
|
pProject->GetToolsToolBar()->IsDown(multiTool);
|
||||||
|
|
||||||
|
//In Multi-tool mode, give multitool prompt if no-special-hit.
|
||||||
|
if (bMultiToolMode) {
|
||||||
|
// Look up the current key binding for Preferences.
|
||||||
|
// (Don't assume it's the default!)
|
||||||
|
wxString keyStr
|
||||||
|
(pProject->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
|
||||||
|
if (keyStr.IsEmpty())
|
||||||
|
// No keyboard preference defined for opening Preferences dialog
|
||||||
|
/* i18n-hint: These are the names of a menu and a command in that menu */
|
||||||
|
keyStr = _("Edit, Preferences...");
|
||||||
|
else
|
||||||
|
keyStr = KeyStringDisplay(keyStr);
|
||||||
|
/* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
|
||||||
|
tip = wxString::Format(
|
||||||
|
_("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
|
||||||
|
keyStr.c_str());
|
||||||
|
// Later in this function we may point to some other string instead.
|
||||||
|
if (!pTrack->GetSelected() ||
|
||||||
|
!viewInfo.bAdjustSelectionEdges)
|
||||||
|
;
|
||||||
|
else {
|
||||||
|
const auto &state = st.state;
|
||||||
|
const wxRect &rect = st.rect;
|
||||||
|
const bool bShiftDown = state.ShiftDown();
|
||||||
|
const bool bCtrlDown = state.ControlDown();
|
||||||
|
const bool bModifierDown = bShiftDown || bCtrlDown;
|
||||||
|
|
||||||
|
// If not shift-down and not snapping center, then
|
||||||
|
// choose boundaries only in snapping tolerance,
|
||||||
|
// and may choose center.
|
||||||
|
SelectionBoundary boundary =
|
||||||
|
ChooseBoundary(viewInfo, state, pTrack.get(), rect, !bModifierDown, !bModifierDown);
|
||||||
|
|
||||||
|
SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// This is a vestige of an idea in the prototype version.
|
||||||
|
// Center would snap without mouse button down, click would pin the center
|
||||||
|
// and drag width.
|
||||||
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
||||||
|
if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
|
||||||
|
isSpectralSelectionTrack(pTrack)) {
|
||||||
|
// Not shift-down, but center frequency snapping toggle is on
|
||||||
|
tip = _("Click and drag to set frequency bandwidth.");
|
||||||
|
pCursor = &*envelopeCursor;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
|
||||||
|
;
|
||||||
|
else {
|
||||||
|
const auto &state = st.state;
|
||||||
|
const wxRect &rect = st.rect;
|
||||||
|
const bool bShiftDown = state.ShiftDown();
|
||||||
|
const bool bCtrlDown = state.ControlDown();
|
||||||
|
const bool bModifierDown = bShiftDown || bCtrlDown;
|
||||||
|
SelectionBoundary boundary = ChooseBoundary(
|
||||||
|
viewInfo, state, pTrack.get(), rect, !bModifierDown, !bModifierDown);
|
||||||
|
SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
MaySetOnDemandTip(pTrack.get(), tip);
|
||||||
|
}
|
||||||
if (tip == "") {
|
if (tip == "") {
|
||||||
const auto ttb = pProject->GetToolsToolBar();
|
const auto ttb = pProject->GetToolsToolBar();
|
||||||
if (ttb)
|
if (ttb)
|
||||||
|
@ -45,6 +45,8 @@ public:
|
|||||||
|
|
||||||
virtual ~SelectHandle();
|
virtual ~SelectHandle();
|
||||||
|
|
||||||
|
bool IsClicked() const;
|
||||||
|
|
||||||
virtual Result Click
|
virtual Result Click
|
||||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
||||||
|
|
||||||
|
@ -54,12 +54,7 @@ HitTestResult TimeShiftHandle::HitAnywhere(const AudacityProject *pProject)
|
|||||||
// After all that, it still may be unsafe to drag.
|
// After all that, it still may be unsafe to drag.
|
||||||
// Even if so, make an informative cursor change from default to "banned."
|
// Even if so, make an informative cursor change from default to "banned."
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
return {
|
return { HitPreview(pProject, unsafe), &Instance() };
|
||||||
HitPreview(pProject, unsafe),
|
|
||||||
(unsafe
|
|
||||||
? NULL
|
|
||||||
: &Instance())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HitTestResult TimeShiftHandle::HitTest
|
HitTestResult TimeShiftHandle::HitTest
|
||||||
@ -413,18 +408,17 @@ void TimeShiftHandle::DoSlideHorizontal
|
|||||||
UIHandle::Result TimeShiftHandle::Click
|
UIHandle::Result TimeShiftHandle::Click
|
||||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
using namespace RefreshCode;
|
||||||
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
if ( unsafe )
|
||||||
|
return Cancelled;
|
||||||
|
|
||||||
const wxMouseEvent &event = evt.event;
|
const wxMouseEvent &event = evt.event;
|
||||||
const wxRect &rect = evt.rect;
|
const wxRect &rect = evt.rect;
|
||||||
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||||
|
|
||||||
const auto pTrack = std::static_pointer_cast<Track>(evt.pCell);
|
const auto pTrack = std::static_pointer_cast<Track>(evt.pCell);
|
||||||
|
|
||||||
using namespace RefreshCode;
|
|
||||||
|
|
||||||
const bool unsafe = pProject->IsAudioActive();
|
|
||||||
if (unsafe)
|
|
||||||
return Cancelled;
|
|
||||||
|
|
||||||
TrackList *const trackList = pProject->GetTracks();
|
TrackList *const trackList = pProject->GetTracks();
|
||||||
|
|
||||||
mClipMoveState.clear();
|
mClipMoveState.clear();
|
||||||
@ -771,7 +765,10 @@ UIHandle::Result TimeShiftHandle::Drag
|
|||||||
HitTestPreview TimeShiftHandle::Preview
|
HitTestPreview TimeShiftHandle::Preview
|
||||||
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
(const TrackPanelMouseState &, const AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
return HitPreview(pProject, false);
|
// After all that, it still may be unsafe to drag.
|
||||||
|
// Even if so, make an informative cursor change from default to "banned."
|
||||||
|
const bool unsafe = pProject->IsAudioActive();
|
||||||
|
return HitPreview(pProject, unsafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
UIHandle::Result TimeShiftHandle::Release
|
UIHandle::Result TimeShiftHandle::Release
|
||||||
|
@ -83,6 +83,9 @@ TrackSelectHandle::~TrackSelectHandle()
|
|||||||
UIHandle::Result TrackSelectHandle::Click
|
UIHandle::Result TrackSelectHandle::Click
|
||||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||||
{
|
{
|
||||||
|
// If unsafe to drag, still, it does harmlessly change the selected track
|
||||||
|
// set on button down.
|
||||||
|
|
||||||
using namespace RefreshCode;
|
using namespace RefreshCode;
|
||||||
Result result = RefreshNone;
|
Result result = RefreshNone;
|
||||||
|
|
||||||
@ -165,19 +168,30 @@ UIHandle::Result TrackSelectHandle::Drag
|
|||||||
HitTestPreview TrackSelectHandle::Preview
|
HitTestPreview TrackSelectHandle::Preview
|
||||||
(const TrackPanelMouseState &, const AudacityProject *project)
|
(const TrackPanelMouseState &, const AudacityProject *project)
|
||||||
{
|
{
|
||||||
// Note that this differs from HitPreview.
|
const auto trackCount = project->GetTrackPanel()->GetTrackCount();
|
||||||
|
if (mpTrack) {
|
||||||
|
// Has been clicked
|
||||||
|
static auto disabledCursor =
|
||||||
|
::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
|
||||||
|
static wxCursor rearrangeCursor{ wxCURSOR_HAND };
|
||||||
|
|
||||||
static auto disabledCursor =
|
const bool unsafe = GetActiveProject()->IsAudioActive();
|
||||||
::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
|
return {
|
||||||
static wxCursor rearrangeCursor{ wxCURSOR_HAND };
|
Message(trackCount),
|
||||||
|
(unsafe
|
||||||
const bool unsafe = GetActiveProject()->IsAudioActive();
|
? &*disabledCursor
|
||||||
return {
|
: &rearrangeCursor)
|
||||||
Message(project->GetTrackPanel()->GetTrackCount()),
|
};
|
||||||
(unsafe
|
}
|
||||||
? &*disabledCursor
|
else {
|
||||||
: &rearrangeCursor)
|
// Only mouse-over
|
||||||
};
|
// Don't test safety, because the click to change selection is allowed
|
||||||
|
static wxCursor arrowCursor{ wxCURSOR_ARROW };
|
||||||
|
return {
|
||||||
|
Message(trackCount),
|
||||||
|
&arrowCursor
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UIHandle::Result TrackSelectHandle::Release
|
UIHandle::Result TrackSelectHandle::Release
|
||||||
|
Loading…
x
Reference in New Issue
Block a user