feat: Added goto line popup to text editors (#1984)
Since the popup is fairly small I opted for a straight addition parallel to the find/replace. To make code more clear the functions that create each popup were coalesced and made their interface simpler. That forced a reorganization of the data processing which translates to a larger number of changes than usual. Most of those changes are just moving some action from one function to another. The old method to identify popups using the size and position of the window was dropped in favor of one based on child windows and using their names for a much easier and robust identification. Added specialized functions to text editor to jump to a line or to given coordinates with a simple interface that simplifies older code that performed the same task. Because this PR modifies heavily the same code as the previous PR (1983) it is also included here to make merging easier. --------- Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
parent
093310a9e5
commit
8d123da847
@ -321,7 +321,6 @@ public:
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FindReplaceHandler;
|
class FindReplaceHandler;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -334,6 +333,10 @@ public:
|
|||||||
FindReplaceHandler *GetFindReplaceHandler() { return &mFindReplaceHandler; }
|
FindReplaceHandler *GetFindReplaceHandler() { return &mFindReplaceHandler; }
|
||||||
int GetTotalLines() const { return (int)mLines.size(); }
|
int GetTotalLines() const { return (int)mLines.size(); }
|
||||||
bool IsOverwrite() const { return mOverwrite; }
|
bool IsOverwrite() const { return mOverwrite; }
|
||||||
|
void SetTopMarginChanged(int newMargin) {
|
||||||
|
mNewTopMargin = newMargin;
|
||||||
|
mTopMarginChanged = true;
|
||||||
|
}
|
||||||
void setFocusAtCoords(const Coordinates &coords) {
|
void setFocusAtCoords(const Coordinates &coords) {
|
||||||
mFocusAtCoords = coords;
|
mFocusAtCoords = coords;
|
||||||
mUpdateFocus = true;
|
mUpdateFocus = true;
|
||||||
@ -576,6 +579,8 @@ private:
|
|||||||
int mUndoIndex;
|
int mUndoIndex;
|
||||||
bool mScrollToBottom;
|
bool mScrollToBottom;
|
||||||
float mTopMargin;
|
float mTopMargin;
|
||||||
|
float mNewTopMargin;
|
||||||
|
bool mTopMarginChanged=false;
|
||||||
|
|
||||||
int mTabSize;
|
int mTabSize;
|
||||||
bool mOverwrite;
|
bool mOverwrite;
|
||||||
|
@ -1129,63 +1129,43 @@ void TextEditor::Render() {
|
|||||||
EnsureCursorVisible();
|
EnsureCursorVisible();
|
||||||
mScrollToCursor = false;
|
mScrollToCursor = false;
|
||||||
}
|
}
|
||||||
|
if (mTopMarginChanged) {
|
||||||
ImGuiPopupFlags_ popup_flags = ImGuiPopupFlags_None;
|
mTopMarginChanged = false;
|
||||||
ImGuiContext& g = *GImGui;
|
if (mTopMargin == 0)
|
||||||
auto oldTopMargin = mTopMargin;
|
|
||||||
auto popupStack = g.OpenPopupStack;
|
|
||||||
if (popupStack.Size > 0) {
|
|
||||||
for (int n = 0; n < popupStack.Size; n++){
|
|
||||||
if (auto window = popupStack[n].Window; window != nullptr) {
|
|
||||||
if (window->Size.x == mFindReplaceHandler.GetFindWindowSize().x &&
|
|
||||||
window->Size.y == mFindReplaceHandler.GetFindWindowSize().y &&
|
|
||||||
window->Pos.x == mFindReplaceHandler.GetFindWindowPos().x &&
|
|
||||||
window->Pos.y == mFindReplaceHandler.GetFindWindowPos().y) {
|
|
||||||
mTopMargin = mFindReplaceHandler.GetFindWindowSize().y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mTopMargin = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (mTopMargin != oldTopMargin) {
|
|
||||||
if (oldTopMargin == 0)
|
|
||||||
m_savedScrollY = ImGui::GetScrollY();
|
m_savedScrollY = ImGui::GetScrollY();
|
||||||
auto window = ImGui::GetCurrentWindow();
|
auto window = ImGui::GetCurrentWindow();
|
||||||
auto maxScroll = window->ScrollMax.y;
|
auto maxScroll = window->ScrollMax.y;
|
||||||
if (maxScroll > 0) {
|
if (maxScroll > 0) {
|
||||||
float lineCount;
|
float lineCount;
|
||||||
float pixelCount;
|
float pixelCount;
|
||||||
if (mTopMargin > oldTopMargin) {
|
if (mNewTopMargin > mTopMargin) {
|
||||||
pixelCount = mTopMargin - oldTopMargin;
|
pixelCount = mNewTopMargin - mTopMargin;
|
||||||
lineCount = pixelCount / mCharAdvance.y;
|
lineCount = pixelCount / mCharAdvance.y;
|
||||||
} else if (mTopMargin > 0) {
|
} else if (mNewTopMargin > 0) {
|
||||||
pixelCount = oldTopMargin - mTopMargin;
|
pixelCount = mTopMargin - mNewTopMargin;
|
||||||
lineCount = pixelCount / mCharAdvance.y;
|
lineCount = pixelCount / mCharAdvance.y;
|
||||||
} else {
|
} else {
|
||||||
pixelCount = oldTopMargin;
|
pixelCount = mTopMargin;
|
||||||
lineCount = std::round(m_linesAdded);
|
lineCount = std::round(m_linesAdded);
|
||||||
}
|
}
|
||||||
auto state = mState;
|
auto state = mState;
|
||||||
auto oldScrollY = ImGui::GetScrollY();
|
auto oldScrollY = ImGui::GetScrollY();
|
||||||
int lineCountInt;
|
int lineCountInt;
|
||||||
|
|
||||||
if (mTopMargin > oldTopMargin) {
|
if (mNewTopMargin > mTopMargin) {
|
||||||
lineCountInt = std::round(lineCount + m_linesAdded - std::floor(m_linesAdded));
|
lineCountInt = std::round(lineCount + m_linesAdded - std::floor(m_linesAdded));
|
||||||
} else
|
} else
|
||||||
lineCountInt = std::round(lineCount);
|
lineCountInt = std::round(lineCount);
|
||||||
for (int i = 0; i < lineCountInt; i++) {
|
for (int i = 0; i < lineCountInt; i++) {
|
||||||
if (mTopMargin > oldTopMargin)
|
if (mNewTopMargin > mTopMargin)
|
||||||
mLines.insert(mLines.begin() + mLines.size(), Line());
|
mLines.insert(mLines.begin() + mLines.size(), Line());
|
||||||
else
|
else
|
||||||
mLines.erase(mLines.begin() + mLines.size() - 1);
|
mLines.erase(mLines.begin() + mLines.size() - 1);
|
||||||
}
|
}
|
||||||
if (mTopMargin > oldTopMargin) {
|
if (mNewTopMargin > mTopMargin) {
|
||||||
m_linesAdded += lineCount;
|
m_linesAdded += lineCount;
|
||||||
m_pixelsAdded += pixelCount;
|
m_pixelsAdded += pixelCount;
|
||||||
} else if (mTopMargin > 0) {
|
} else if (mNewTopMargin > 0) {
|
||||||
m_linesAdded -= lineCount;
|
m_linesAdded -= lineCount;
|
||||||
m_pixelsAdded -= pixelCount;
|
m_pixelsAdded -= pixelCount;
|
||||||
} else {
|
} else {
|
||||||
@ -1193,9 +1173,9 @@ void TextEditor::Render() {
|
|||||||
m_pixelsAdded = 0;
|
m_pixelsAdded = 0;
|
||||||
}
|
}
|
||||||
if (oldScrollY + pixelCount < maxScroll) {
|
if (oldScrollY + pixelCount < maxScroll) {
|
||||||
if (mTopMargin > oldTopMargin)
|
if (mNewTopMargin > mTopMargin)
|
||||||
m_shiftedScrollY = oldScrollY + pixelCount;
|
m_shiftedScrollY = oldScrollY + pixelCount;
|
||||||
else if (mTopMargin > 0)
|
else if (mNewTopMargin > 0)
|
||||||
m_shiftedScrollY = oldScrollY - pixelCount;
|
m_shiftedScrollY = oldScrollY - pixelCount;
|
||||||
else if (ImGui::GetScrollY() == m_shiftedScrollY)
|
else if (ImGui::GetScrollY() == m_shiftedScrollY)
|
||||||
m_shiftedScrollY = m_savedScrollY;
|
m_shiftedScrollY = m_savedScrollY;
|
||||||
@ -1203,9 +1183,10 @@ void TextEditor::Render() {
|
|||||||
m_shiftedScrollY = ImGui::GetScrollY() - pixelCount;
|
m_shiftedScrollY = ImGui::GetScrollY() - pixelCount;
|
||||||
ImGui::SetScrollY(m_shiftedScrollY);
|
ImGui::SetScrollY(m_shiftedScrollY);
|
||||||
} else {
|
} else {
|
||||||
if (mTopMargin > oldTopMargin)
|
if (mNewTopMargin > mTopMargin)
|
||||||
mScrollToBottom = true;
|
mScrollToBottom = true;
|
||||||
}
|
}
|
||||||
|
mTopMargin = mNewTopMargin;
|
||||||
mState = state;
|
mState = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1600,6 +1581,7 @@ void TextEditor::DeleteSelection() {
|
|||||||
void TextEditor::JumpToLine(int line) {
|
void TextEditor::JumpToLine(int line) {
|
||||||
auto newPos = Coordinates(line, 0);
|
auto newPos = Coordinates(line, 0);
|
||||||
JumpToCoords(newPos);
|
JumpToCoords(newPos);
|
||||||
|
|
||||||
setFocusAtCoords(newPos);
|
setFocusAtCoords(newPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1607,6 +1589,7 @@ void TextEditor::JumpToCoords(const Coordinates &aNewPos) {
|
|||||||
SetSelection(aNewPos, aNewPos);
|
SetSelection(aNewPos, aNewPos);
|
||||||
SetCursorPosition(aNewPos);
|
SetCursorPosition(aNewPos);
|
||||||
EnsureCursorVisible();
|
EnsureCursorVisible();
|
||||||
|
|
||||||
setFocusAtCoords(aNewPos);
|
setFocusAtCoords(aNewPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,8 +73,9 @@ namespace hex::plugin::builtin {
|
|||||||
return ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
|
return ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
|
||||||
std::string preprocessText(const std::string &code);
|
std::string preprocessText(const std::string &code);
|
||||||
|
void setPopupWindowHeight(u32 height) { m_popupWindowHeight = height; }
|
||||||
|
u32 getPopupWindowHeight() const { return m_popupWindowHeight; }
|
||||||
|
|
||||||
struct VirtualFile {
|
struct VirtualFile {
|
||||||
std::fs::path path;
|
std::fs::path path;
|
||||||
@ -282,12 +283,16 @@ namespace hex::plugin::builtin {
|
|||||||
bool m_parentHighlightingEnabled = true;
|
bool m_parentHighlightingEnabled = true;
|
||||||
bool m_replaceMode = false;
|
bool m_replaceMode = false;
|
||||||
bool m_openFindReplacePopUp = false;
|
bool m_openFindReplacePopUp = false;
|
||||||
|
bool m_openGotoLinePopUp = false;
|
||||||
std::map<std::fs::path, std::string> m_patternNames;
|
std::map<std::fs::path, std::string> m_patternNames;
|
||||||
|
|
||||||
ImRect m_textEditorHoverBox;
|
ImRect m_textEditorHoverBox;
|
||||||
ImRect m_consoleHoverBox;
|
ImRect m_consoleHoverBox;
|
||||||
std::string m_focusedSubWindowName;
|
std::string m_focusedSubWindowName;
|
||||||
|
float m_popupWindowHeight = 0;
|
||||||
|
float m_popupWindowHeightChange = 0;
|
||||||
|
bool m_frPopupIsClosed = true;
|
||||||
|
bool m_gotoPopupIsClosed = true;
|
||||||
|
|
||||||
static inline std::array<std::string,256> m_findHistory;
|
static inline std::array<std::string,256> m_findHistory;
|
||||||
static inline u32 m_findHistorySize = 0;
|
static inline u32 m_findHistorySize = 0;
|
||||||
@ -306,7 +311,8 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
void drawPatternTooltip(pl::ptrn::Pattern *pattern);
|
void drawPatternTooltip(pl::ptrn::Pattern *pattern);
|
||||||
|
|
||||||
void drawFindReplaceDialog(TextEditor *textEditor, std::string &findWord, bool &requestFocus, u64 &position, u64 &count, bool &updateCount, bool canReplace);
|
void drawTextEditorFindReplacePopup(TextEditor *textEditor);
|
||||||
|
void drawTextEditorGotoLinePopup(TextEditor *textEditor);
|
||||||
|
|
||||||
void historyInsert(std::array<std::string, 256> &history, u32 &size, u32 &index, const std::string &value);
|
void historyInsert(std::array<std::string, 256> &history, u32 &size, u32 &index, const std::string &value);
|
||||||
|
|
||||||
@ -317,6 +323,7 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
TextEditor *getEditorFromFocusedWindow();
|
TextEditor *getEditorFromFocusedWindow();
|
||||||
void setupFindReplace(TextEditor *editor);
|
void setupFindReplace(TextEditor *editor);
|
||||||
|
void setupGotoLine(TextEditor *editor);
|
||||||
|
|
||||||
void registerEvents();
|
void registerEvents();
|
||||||
void registerMenuItems();
|
void registerMenuItems();
|
||||||
|
@ -935,6 +935,7 @@
|
|||||||
"hex.builtin.view.pattern_editor.evaluating": "Evaluating...",
|
"hex.builtin.view.pattern_editor.evaluating": "Evaluating...",
|
||||||
"hex.builtin.view.pattern_editor.find_hint": "Find",
|
"hex.builtin.view.pattern_editor.find_hint": "Find",
|
||||||
"hex.builtin.view.pattern_editor.find_hint_history": " for history)",
|
"hex.builtin.view.pattern_editor.find_hint_history": " for history)",
|
||||||
|
"hex.builtin.view.pattern_editor.goto_line": "Goto line: ",
|
||||||
"hex.builtin.view.pattern_editor.menu.edit.place_pattern": "Place pattern...",
|
"hex.builtin.view.pattern_editor.menu.edit.place_pattern": "Place pattern...",
|
||||||
"hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin": "Built-in Type",
|
"hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin": "Built-in Type",
|
||||||
"hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array": "Array",
|
"hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array": "Array",
|
||||||
@ -945,6 +946,7 @@
|
|||||||
"hex.builtin.view.pattern_editor.menu.find": "Find...",
|
"hex.builtin.view.pattern_editor.menu.find": "Find...",
|
||||||
"hex.builtin.view.pattern_editor.menu.find_next": "Find Next",
|
"hex.builtin.view.pattern_editor.menu.find_next": "Find Next",
|
||||||
"hex.builtin.view.pattern_editor.menu.find_previous": "Find Previous",
|
"hex.builtin.view.pattern_editor.menu.find_previous": "Find Previous",
|
||||||
|
"hex.builtin.view.pattern_editor.menu.goto_line": "Goto line...",
|
||||||
"hex.builtin.view.pattern_editor.menu.replace": "Replace...",
|
"hex.builtin.view.pattern_editor.menu.replace": "Replace...",
|
||||||
"hex.builtin.view.pattern_editor.menu.replace_next": "Replace Next",
|
"hex.builtin.view.pattern_editor.menu.replace_next": "Replace Next",
|
||||||
"hex.builtin.view.pattern_editor.menu.replace_previous": "Replace Previous",
|
"hex.builtin.view.pattern_editor.menu.replace_previous": "Replace Previous",
|
||||||
@ -961,6 +963,7 @@
|
|||||||
"hex.builtin.view.pattern_editor.sections.view": "View content",
|
"hex.builtin.view.pattern_editor.sections.view": "View content",
|
||||||
"hex.builtin.view.pattern_editor.sections.export": "Export content",
|
"hex.builtin.view.pattern_editor.sections.export": "Export content",
|
||||||
"hex.builtin.view.pattern_editor.settings": "Settings",
|
"hex.builtin.view.pattern_editor.settings": "Settings",
|
||||||
|
"hex.builtin.view.pattern_editor.shortcut.goto_line": "Goto line ...",
|
||||||
"hex.builtin.view.pattern_editor.shortcut.find": "Search ...",
|
"hex.builtin.view.pattern_editor.shortcut.find": "Search ...",
|
||||||
"hex.builtin.view.pattern_editor.shortcut.replace": "Replace ...",
|
"hex.builtin.view.pattern_editor.shortcut.replace": "Replace ...",
|
||||||
"hex.builtin.view.pattern_editor.shortcut.find_next": "Find Next",
|
"hex.builtin.view.pattern_editor.shortcut.find_next": "Find Next",
|
||||||
|
@ -238,98 +238,82 @@ namespace hex::plugin::builtin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ViewPatternEditor::setupFindReplace(TextEditor *editor) {
|
void ViewPatternEditor::setupFindReplace(TextEditor *editor) {
|
||||||
|
if (editor == nullptr)
|
||||||
// Context menu entries that open the find/replace popup
|
return;
|
||||||
ImGui::PushID(editor);
|
|
||||||
static std::string findWord;
|
|
||||||
static bool requestFocus = false;
|
|
||||||
static u64 position = 0;
|
|
||||||
static u64 count = 0;
|
|
||||||
static bool updateCount = false;
|
|
||||||
TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler();
|
|
||||||
static bool canReplace = true;
|
|
||||||
if (m_openFindReplacePopUp) {
|
if (m_openFindReplacePopUp) {
|
||||||
m_openFindReplacePopUp = false;
|
m_openFindReplacePopUp = false;
|
||||||
// Place the popup at the top right of the window
|
|
||||||
auto windowSize = ImGui::GetWindowSize();
|
|
||||||
auto style = ImGui::GetStyle();
|
auto style = ImGui::GetStyle();
|
||||||
|
|
||||||
// Set the scrollbar size only if it is visible
|
|
||||||
float scrollbarSize = 0;
|
|
||||||
|
|
||||||
// Calculate the number of lines to display in the text editor
|
|
||||||
auto totalTextHeight = editor->GetTotalLines() * editor->GetCharAdvance().y;
|
|
||||||
|
|
||||||
// Compare it to the window height
|
|
||||||
if (totalTextHeight > windowSize.y)
|
|
||||||
scrollbarSize = style.ScrollbarSize;
|
|
||||||
|
|
||||||
auto labelSize = ImGui::CalcTextSize(ICON_VS_WHOLE_WORD);
|
auto labelSize = ImGui::CalcTextSize(ICON_VS_WHOLE_WORD);
|
||||||
|
|
||||||
// Six icon buttons
|
// Six icon buttons
|
||||||
auto popupSize = ImVec2({(labelSize.x + style.FramePadding.x * 2.0F) * 6.0F,
|
auto popupSizeX = (labelSize.x + style.FramePadding.x * 2.0F) * 6.0F;
|
||||||
labelSize.y + style.FramePadding.y * 2.0F + style.WindowPadding.y + 3 });
|
|
||||||
|
|
||||||
// 2 * 11 spacings in between elements
|
// 2 * 9 spacings in between elements
|
||||||
popupSize.x += style.FramePadding.x * 22.0F;
|
popupSizeX += style.FramePadding.x * 18.0F;
|
||||||
|
|
||||||
// Text input fields are set to 12 characters wide
|
// Text input fields are set to 12 characters wide
|
||||||
popupSize.x += ImGui::GetFontSize() * 12.0F;
|
popupSizeX += ImGui::GetFontSize() * 12.0F;
|
||||||
|
|
||||||
// IndexOfCount text
|
// IndexOfCount text
|
||||||
popupSize.x += ImGui::CalcTextSize("2000 of 2000").x + 2.0F;
|
popupSizeX += ImGui::CalcTextSize("2000 of 2000").x + 2.0F;
|
||||||
popupSize.x += scrollbarSize;
|
popupSizeX += style.FramePadding.x * 2.0F;
|
||||||
|
|
||||||
// Position of popup relative to parent window
|
// Position of popup relative to parent window
|
||||||
ImVec2 windowPosForPopup = ImGui::GetWindowPos();
|
ImVec2 windowPosForPopup;
|
||||||
|
windowPosForPopup.x = m_textEditorHoverBox.Max.x - style.ScrollbarSize - popupSizeX;
|
||||||
|
|
||||||
// Not the window height but the content height
|
if (m_focusedSubWindowName.contains(consoleView))
|
||||||
windowPosForPopup.y += popupSize.y;
|
windowPosForPopup.y = m_consoleHoverBox.Min.y;
|
||||||
|
else if (m_focusedSubWindowName.contains(textEditorView))
|
||||||
|
windowPosForPopup.y = m_textEditorHoverBox.Min.y;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
ImGui::SetNextWindowPos(windowPosForPopup);
|
||||||
|
ImGui::OpenPopup("##text_editor_view_find_replace_popup");
|
||||||
|
}
|
||||||
|
drawTextEditorFindReplacePopup(editor);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPatternEditor::setupGotoLine(TextEditor *editor) {
|
||||||
|
|
||||||
|
// Context menu entries that open the goto line popup
|
||||||
|
if (m_openGotoLinePopUp) {
|
||||||
|
m_openGotoLinePopUp = false;
|
||||||
|
// Place the popup at the top right of the window
|
||||||
|
auto style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
|
||||||
|
auto labelSize = ImGui::CalcTextSize("hex.builtin.view.pattern_editor.goto_line"_lang);
|
||||||
|
|
||||||
|
auto popupSizeX = (labelSize.x + style.FramePadding.x * 2.0F);
|
||||||
|
|
||||||
|
|
||||||
|
// Text input fields are set to 8 characters wide
|
||||||
|
popupSizeX += ImGui::CalcTextSize("00000000").x;
|
||||||
|
|
||||||
|
popupSizeX += style.WindowPadding.x * 2.0F;
|
||||||
|
// Position of popup relative to parent window
|
||||||
|
ImVec2 windowPosForPopup;
|
||||||
|
|
||||||
// Add remaining window height
|
|
||||||
popupSize.y += style.WindowPadding.y + 1;
|
|
||||||
|
|
||||||
// Move to the right so as not to overlap the scrollbar
|
// Move to the right so as not to overlap the scrollbar
|
||||||
windowPosForPopup.x += windowSize.x - popupSize.x;
|
windowPosForPopup.x = m_textEditorHoverBox.Max.x - style.ScrollbarSize - popupSizeX;
|
||||||
findReplaceHandler->SetFindWindowPos(windowPosForPopup);
|
|
||||||
|
|
||||||
if (m_replaceMode) {
|
|
||||||
// Remove one window padding
|
|
||||||
popupSize.y -= style.WindowPadding.y;
|
|
||||||
// Add the replace window height
|
|
||||||
popupSize.y *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_focusedSubWindowName.contains(consoleView)) {
|
if (m_focusedSubWindowName.contains(consoleView)) {
|
||||||
windowPosForPopup.y = m_consoleHoverBox.Min.y;
|
windowPosForPopup.y = m_consoleHoverBox.Min.y;
|
||||||
canReplace = false;
|
} else if (m_focusedSubWindowName.contains(textEditorView)) {
|
||||||
} else if (m_focusedSubWindowName.contains(textEditorView))
|
windowPosForPopup.y = m_textEditorHoverBox.Min.y;
|
||||||
canReplace = true;
|
} else {
|
||||||
|
return;
|
||||||
ImGui::SetNextWindowPos(windowPosForPopup);
|
|
||||||
ImGui::SetNextWindowSize(popupSize);
|
|
||||||
ImGui::OpenPopup("##pattern_editor_find_replace");
|
|
||||||
|
|
||||||
findReplaceHandler->resetMatches();
|
|
||||||
|
|
||||||
// Use selection as find word if there is one, otherwise use the word under the cursor
|
|
||||||
if (!editor->HasSelection())
|
|
||||||
editor->SelectWordUnderCursor();
|
|
||||||
|
|
||||||
findWord = editor->GetSelectedText();
|
|
||||||
|
|
||||||
// Find all matches to findWord
|
|
||||||
findReplaceHandler->FindAllMatches(editor,findWord);
|
|
||||||
position = findReplaceHandler->FindPosition(editor,editor->GetCursorPosition(), true);
|
|
||||||
count = findReplaceHandler->GetMatches().size();
|
|
||||||
findReplaceHandler->SetFindWord(editor,findWord);
|
|
||||||
requestFocus = true;
|
|
||||||
updateCount = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawFindReplaceDialog(editor, findWord, requestFocus, position, count, updateCount, canReplace);
|
ImGui::SetNextWindowPos(windowPosForPopup);
|
||||||
|
ImGui::OpenPopup("##text_editor_view_goto_line_popup");
|
||||||
|
|
||||||
ImGui::PopID();
|
}
|
||||||
|
drawTextEditorGotoLinePopup(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewPatternEditor::drawContent() {
|
void ViewPatternEditor::drawContent() {
|
||||||
@ -359,11 +343,11 @@ namespace hex::plugin::builtin {
|
|||||||
m_consoleHoverBox = ImRect(ImVec2(windowPosition.x,windowPosition.y+textEditorSize.y),windowPosition+availableSize);
|
m_consoleHoverBox = ImRect(ImVec2(windowPosition.x,windowPosition.y+textEditorSize.y),windowPosition+availableSize);
|
||||||
TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler();
|
TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler();
|
||||||
if (m_textEditor.RaiseContextMenu()) {
|
if (m_textEditor.RaiseContextMenu()) {
|
||||||
ImGui::OpenPopup("##pattern_editor_context_menu");
|
ImGui::OpenPopup("##text_editor_context_menu");
|
||||||
m_textEditor.ClearRaiseContextMenu();
|
m_textEditor.ClearRaiseContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginPopup("##pattern_editor_context_menu")) {
|
if (ImGui::BeginPopup("##text_editor_context_menu")) {
|
||||||
// no shortcut for this
|
// no shortcut for this
|
||||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.pattern_file"_lang, nullptr, false))
|
if (ImGui::MenuItem("hex.builtin.menu.file.import.pattern_file"_lang, nullptr, false))
|
||||||
m_importPatternFile();
|
m_importPatternFile();
|
||||||
@ -422,14 +406,19 @@ namespace hex::plugin::builtin {
|
|||||||
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace_all"_lang, "",false,!findReplaceHandler->GetReplaceWord().empty()))
|
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace_all"_lang, "",false,!findReplaceHandler->GetReplaceWord().empty()))
|
||||||
findReplaceHandler->ReplaceAll(&m_textEditor);
|
findReplaceHandler->ReplaceAll(&m_textEditor);
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.goto_line"_lang, Shortcut(ALT + Keys::G).toString().c_str()))
|
||||||
|
m_openGotoLinePopUp = true;
|
||||||
|
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false))
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false))
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr)
|
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) {
|
||||||
setupFindReplace(editor);
|
setupFindReplace(editor);
|
||||||
|
setupGotoLine(editor);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Button("##settings_drag_bar", ImVec2(ImGui::GetContentRegionAvail().x, 2_scaled));
|
ImGui::Button("##settings_drag_bar", ImVec2(ImGui::GetContentRegionAvail().x, 2_scaled));
|
||||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
|
||||||
@ -640,20 +629,52 @@ namespace hex::plugin::builtin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewPatternEditor::drawFindReplaceDialog(TextEditor *textEditor, std::string &findWord, bool &requestFocus, u64 &position, u64 &count, bool &updateCount, bool canReplace) {
|
void ViewPatternEditor::drawTextEditorFindReplacePopup(TextEditor *textEditor) {
|
||||||
|
ImGuiWindowFlags popupFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar;
|
||||||
|
if (ImGui::BeginPopup("##text_editor_view_find_replace_popup", popupFlags)) {
|
||||||
|
static std::string findWord;
|
||||||
|
static bool requestFocus = false;
|
||||||
|
static u64 position = 0;
|
||||||
|
static u64 count = 0;
|
||||||
|
static bool updateCount = false;
|
||||||
|
static bool canReplace = true;
|
||||||
TextEditor::FindReplaceHandler *findReplaceHandler = textEditor->GetFindReplaceHandler();
|
TextEditor::FindReplaceHandler *findReplaceHandler = textEditor->GetFindReplaceHandler();
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
findReplaceHandler->resetMatches();
|
||||||
|
|
||||||
|
// Use selection as find word if there is one, otherwise use the word under the cursor
|
||||||
|
if (!textEditor->HasSelection())
|
||||||
|
textEditor->SelectWordUnderCursor();
|
||||||
|
|
||||||
|
findWord = textEditor->GetSelectedText();
|
||||||
|
|
||||||
|
// Find all matches to findWord
|
||||||
|
findReplaceHandler->FindAllMatches(textEditor, findWord);
|
||||||
|
position = findReplaceHandler->FindPosition(textEditor, textEditor->GetCursorPosition(), true);
|
||||||
|
count = findReplaceHandler->GetMatches().size();
|
||||||
|
findReplaceHandler->SetFindWord(textEditor, findWord);
|
||||||
|
requestFocus = true;
|
||||||
|
updateCount = true;
|
||||||
|
if (m_focusedSubWindowName.contains(consoleView))
|
||||||
|
canReplace = false;
|
||||||
|
else if (m_focusedSubWindowName.contains(textEditorView))
|
||||||
|
canReplace = true;
|
||||||
|
}
|
||||||
bool enter = ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false);
|
bool enter = ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false);
|
||||||
bool upArrow = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad8, false);
|
bool upArrow = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad8, false);
|
||||||
bool downArrow = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad2, false);
|
bool downArrow = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad2, false);
|
||||||
bool shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
bool shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
||||||
bool alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt);
|
bool alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt);
|
||||||
|
std::string childName;
|
||||||
|
if (m_focusedSubWindowName.contains(consoleView))
|
||||||
|
childName = "##console_find_replace";
|
||||||
|
else if (m_focusedSubWindowName.contains(textEditorView))
|
||||||
|
childName = "##text_editor_find_replace";
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
ImGui::BeginChild(childName.c_str(), ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY);
|
||||||
|
|
||||||
if (ImGui::BeginPopup("##pattern_editor_find_replace")) {
|
if (ImGui::BeginTable("##text_editor_find_replace_table", 2, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH)) {
|
||||||
|
|
||||||
if (ImGui::BeginTable("##pattern_editor_find_replace_table", 2,
|
|
||||||
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH)) {
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
static bool requestFocusFind = false;
|
static bool requestFocusFind = false;
|
||||||
@ -904,9 +925,8 @@ namespace hex::plugin::builtin {
|
|||||||
requestFocusFind = true;
|
requestFocusFind = true;
|
||||||
updateCount = true;
|
updateCount = true;
|
||||||
}
|
}
|
||||||
findReplaceHandler->SetFindWindowSize(ImGui::GetWindowSize());
|
}
|
||||||
} else
|
ImGui::EndTable();
|
||||||
findReplaceHandler->SetFindWindowSize(ImGui::GetWindowSize());
|
|
||||||
|
|
||||||
if ((ImGui::IsKeyPressed(ImGuiKey_F3, false)) || downArrowFind || upArrowFind || enterPressedFind) {
|
if ((ImGui::IsKeyPressed(ImGuiKey_F3, false)) || downArrowFind || upArrowFind || enterPressedFind) {
|
||||||
historyInsert(m_findHistory, m_findHistorySize, m_findHistoryIndex, findWord);
|
historyInsert(m_findHistory, m_findHistorySize, m_findHistoryIndex, findWord);
|
||||||
@ -918,14 +938,92 @@ namespace hex::plugin::builtin {
|
|||||||
requestFocusFind = true;
|
requestFocusFind = true;
|
||||||
enterPressedFind = false;
|
enterPressedFind = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
}
|
||||||
// Escape key to close the popup
|
// Escape key to close the popup
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false))
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
|
||||||
|
m_popupWindowHeight = 0;
|
||||||
|
m_textEditor.SetTopMarginChanged(0);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
if (m_focusedSubWindowName.contains(textEditorView)) {
|
||||||
|
if (auto window = ImGui::GetCurrentWindow(); window != nullptr) {
|
||||||
|
auto height = window->Size.y;
|
||||||
|
auto heightChange = height - m_popupWindowHeight;
|
||||||
|
auto heightChangeChange = heightChange - m_popupWindowHeightChange;
|
||||||
|
if (std::fabs(heightChange) < 0.5 && std::fabs(heightChangeChange) > 1.0) {
|
||||||
|
m_textEditor.SetTopMarginChanged(height);
|
||||||
|
}
|
||||||
|
m_popupWindowHeightChange = heightChange;
|
||||||
|
m_popupWindowHeight = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
|
m_frPopupIsClosed = false;
|
||||||
|
} else if (!m_frPopupIsClosed) {
|
||||||
|
m_frPopupIsClosed = true;
|
||||||
|
m_popupWindowHeight = 0;
|
||||||
|
m_textEditor.SetTopMarginChanged(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPatternEditor::drawTextEditorGotoLinePopup(TextEditor *textEditor) {
|
||||||
|
ImGuiWindowFlags popupFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar;
|
||||||
|
if (ImGui::BeginPopup("##text_editor_view_goto_line_popup", popupFlags)) {
|
||||||
|
std::string childName;
|
||||||
|
if (m_focusedSubWindowName.contains(consoleView))
|
||||||
|
childName = "##console_goto_line";
|
||||||
|
else if (m_focusedSubWindowName.contains(textEditorView))
|
||||||
|
childName = "##text_editor_goto_line";
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
ImGui::BeginChild(childName.c_str(), ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY);
|
||||||
|
bool enter = ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false);
|
||||||
|
static i32 line = 1;
|
||||||
|
|
||||||
|
std::string hint = "hex.builtin.view.pattern_editor.goto_line"_lang.operator std::string();
|
||||||
|
ImGui::TextUnformatted(hint.c_str());
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::PushItemWidth(ImGui::CalcTextSize("00000000").x);
|
||||||
|
|
||||||
|
if (ImGui::InputInt("###text_editor_goto_line", &line, 0, 0, ImGuiInputTextFlags_CharsDecimal)) {
|
||||||
|
enter = false;
|
||||||
|
}
|
||||||
|
ImGui::SetKeyboardFocusHere(-1);
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
if (enter) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
if (line < 0)
|
||||||
|
line = textEditor->GetTotalLines() + line + 1;
|
||||||
|
line = std::clamp(line, 1, textEditor->GetTotalLines());
|
||||||
|
textEditor->JumpToLine(line-1);
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
|
||||||
|
m_popupWindowHeight = 0;
|
||||||
|
m_textEditor.SetTopMarginChanged(0);
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
if (m_focusedSubWindowName.contains(textEditorView)) {
|
||||||
|
if (auto window = ImGui::GetCurrentWindow(); window != nullptr) {
|
||||||
|
auto height = window->Size.y;
|
||||||
|
auto heightChange = height - m_popupWindowHeight;
|
||||||
|
auto heightChangeChange = heightChange - m_popupWindowHeightChange;
|
||||||
|
if (std::fabs(heightChange) < 0.5 && std::fabs(heightChangeChange) > 1.0) {
|
||||||
|
m_textEditor.SetTopMarginChanged(height);
|
||||||
|
}
|
||||||
|
m_popupWindowHeightChange = heightChange;
|
||||||
|
m_popupWindowHeight = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
m_gotoPopupIsClosed = false;
|
||||||
|
} else if (!m_gotoPopupIsClosed) {
|
||||||
|
m_gotoPopupIsClosed = true;
|
||||||
|
m_popupWindowHeight = 0;
|
||||||
|
m_textEditor.SetTopMarginChanged(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,23 +1055,34 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.find_previous"_lang, Shortcut(SHIFT + Keys::F3).toString().c_str(),false,!findReplaceHandler->GetFindWord().empty()))
|
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.find_previous"_lang, Shortcut(SHIFT + Keys::F3).toString().c_str(),false,!findReplaceHandler->GetFindWord().empty()))
|
||||||
findReplaceHandler->FindMatch(&m_consoleEditor,false);
|
findReplaceHandler->FindMatch(&m_consoleEditor,false);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.goto_line"_lang, Shortcut(ALT + Keys::G).toString().c_str()))
|
||||||
|
m_openGotoLinePopUp = true;
|
||||||
|
|
||||||
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
if (m_consoleNeedsUpdate) {
|
if (m_consoleNeedsUpdate) {
|
||||||
std::scoped_lock lock(m_logMutex);
|
std::scoped_lock lock(m_logMutex);
|
||||||
|
bool skipNewLine = false;
|
||||||
auto lineCount = m_consoleEditor.GetTextLines().size() - 1;
|
auto lineCount = m_consoleEditor.GetTextLines().size();
|
||||||
if (m_console->size() < lineCount) {
|
if (m_console->size() < lineCount || (lineCount == 1 && m_consoleEditor.GetLineText(0).empty())) {
|
||||||
m_consoleEditor.SetText("");
|
m_consoleEditor.SetText("");
|
||||||
lineCount = 0;
|
lineCount = 0;
|
||||||
|
skipNewLine = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_consoleEditor.SetCursorPosition({ int(lineCount + 1), 0 });
|
m_consoleEditor.JumpToLine(lineCount);
|
||||||
|
|
||||||
const auto linesToAdd = m_console->size() - lineCount;
|
const auto linesToAdd = m_console->size() - lineCount;
|
||||||
|
|
||||||
|
|
||||||
for (size_t i = 0; i < linesToAdd; i += 1) {
|
for (size_t i = 0; i < linesToAdd; i += 1) {
|
||||||
m_consoleEditor.InsertText(m_console->at(lineCount + i));
|
if (!skipNewLine)
|
||||||
m_consoleEditor.InsertText("\n");
|
m_consoleEditor.InsertText("\n");
|
||||||
|
skipNewLine = false;
|
||||||
|
m_consoleEditor.InsertText(m_console->at(lineCount + i));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_consoleNeedsUpdate = false;
|
m_consoleNeedsUpdate = false;
|
||||||
@ -1326,7 +1435,7 @@ namespace hex::plugin::builtin {
|
|||||||
m_resetDebuggerVariables = false;
|
m_resetDebuggerVariables = false;
|
||||||
|
|
||||||
if (pauseLine.has_value())
|
if (pauseLine.has_value())
|
||||||
m_textEditor.SetCursorPosition({ int(pauseLine.value() - 1), 0 });
|
m_textEditor.JumpToLine(pauseLine.value() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &currScope = evaluator->getScope(-m_debuggerScopeIndex);
|
const auto &currScope = evaluator->getScope(-m_debuggerScopeIndex);
|
||||||
@ -1786,7 +1895,7 @@ namespace hex::plugin::builtin {
|
|||||||
m_resetDebuggerVariables = true;
|
m_resetDebuggerVariables = true;
|
||||||
auto optPauseLine = runtime.getInternals().evaluator->getPauseLine();
|
auto optPauseLine = runtime.getInternals().evaluator->getPauseLine();
|
||||||
if (optPauseLine.has_value())
|
if (optPauseLine.has_value())
|
||||||
m_textEditor.SetCursorPosition({ static_cast<int>(optPauseLine.value())-1, 0 });
|
m_textEditor.JumpToLine(optPauseLine.value() - 1);
|
||||||
while (*m_breakpointHit) {
|
while (*m_breakpointHit) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100LL));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100LL));
|
||||||
}
|
}
|
||||||
@ -1968,7 +2077,7 @@ namespace hex::plugin::builtin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ViewPatternEditor::appendEditorText(const std::string &text) {
|
void ViewPatternEditor::appendEditorText(const std::string &text) {
|
||||||
m_textEditor.SetCursorPosition(TextEditor::Coordinates { m_textEditor.GetTotalLines(), 0 });
|
m_textEditor.JumpToLine(m_textEditor.GetTotalLines());
|
||||||
m_textEditor.InsertText(hex::format("\n{0}", text));
|
m_textEditor.InsertText(hex::format("\n{0}", text));
|
||||||
m_triggerEvaluation = true;
|
m_triggerEvaluation = true;
|
||||||
}
|
}
|
||||||
@ -2183,6 +2292,10 @@ namespace hex::plugin::builtin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ShortcutManager::addShortcut(this, CTRL + Keys::G + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.goto_line", [this] {
|
||||||
|
m_openGotoLinePopUp = true;
|
||||||
|
});
|
||||||
|
|
||||||
ShortcutManager::addShortcut(this, CTRL + Keys::F + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.find", [this] {
|
ShortcutManager::addShortcut(this, CTRL + Keys::F + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.find", [this] {
|
||||||
m_openFindReplacePopUp = true;
|
m_openFindReplacePopUp = true;
|
||||||
m_replaceMode = false;
|
m_replaceMode = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user