#include "TextEdit.hpp" #include #include "Utf8Util.hpp" namespace jdi { namespace { /** @brief 判断字符是否为 UTF-16 高位代理。 */ bool IsHighSurrogate(wchar_t c) { return c >= 0xD800 && c <= 0xDBFF; } /** @brief 判断字符是否为 UTF-16 低位代理。 */ bool IsLowSurrogate(wchar_t c) { return c >= 0xDC00 && c <= 0xDFFF; } /** @brief 判断字符是否符合数值输入(数字与科学计数法符号)。 */ bool IsNumberChar(wchar_t c) { return (c >= L'0' && c <= L'9') || c == L'.' || c == L'-' || c == L'+' || c == L'e' || c == L'E'; } /** @brief 判断字符是否为十六进制数字。 */ bool IsHexDigit(wchar_t c) { return (c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f') || (c >= L'A' && c <= L'F'); } /** @brief 判断字符是否为十进制数字。 */ bool IsDigit(wchar_t c) { return c >= L'0' && c <= L'9'; } } bool TextEdit::AcceptsCharacter(EditFilter filter, wchar_t character) { switch (filter) { case EditFilter::NUMBER: return IsNumberChar(character); case EditFilter::HEX: return IsHexDigit(character) || character == L'#'; case EditFilter::BYTE: return IsDigit(character); case EditFilter::ANY: default: return character >= L' ' && character != 0x7F; } } void TextEdit::Begin(const std::wstring& text, bool selectAll, EditFilter filter, std::size_t maxLength) { m_active = true; m_text = text; m_filter = filter; m_maxLength = maxLength; m_caret = m_text.size(); m_anchor = selectAll ? 0 : m_text.size(); m_fresh = selectAll && !m_text.empty(); } void TextEdit::End() { m_active = false; m_fresh = false; m_dragging = false; m_text.clear(); m_caret = 0; m_anchor = 0; m_filter = EditFilter::ANY; m_maxLength = 0; } std::size_t TextEdit::ClampOffset(std::size_t offset) const { if (offset > m_text.size()) { offset = m_text.size(); } // 避免光标落在代理对中间(否则渲染与删除都会错位)。 if (offset > 0 && offset < m_text.size() && IsLowSurrogate(m_text[offset]) && IsHighSurrogate(m_text[offset - 1])) { --offset; } return offset; } std::size_t TextEdit::StepLeft(std::size_t offset) const { if (offset == 0) { return 0; } --offset; if (offset > 0 && IsLowSurrogate(m_text[offset]) && IsHighSurrogate(m_text[offset - 1])) { --offset; } return offset; } std::size_t TextEdit::StepRight(std::size_t offset) const { if (offset >= m_text.size()) { return m_text.size(); } ++offset; if (offset < m_text.size() && IsLowSurrogate(m_text[offset]) && IsHighSurrogate(m_text[offset - 1])) { ++offset; } return offset; } bool TextEdit::ReplaceSelection(const std::wstring& replacement) { const bool hadSelection = HasSelection(); const std::size_t begin = SelectionBegin(); const std::size_t end = SelectionEnd(); std::wstring merged = m_text.substr(0, begin) + replacement + m_text.substr(end); if (m_maxLength > 0 && merged.size() > m_maxLength) { merged.resize(m_maxLength); } const bool changed = (merged != m_text) || hadSelection; m_text = std::move(merged); m_caret = ClampOffset(begin + replacement.size()); m_anchor = m_caret; m_fresh = false; return changed; } bool TextEdit::Insert(wchar_t character) { if (!m_active || character == 0) { return false; } if (!AcceptsCharacter(m_filter, character)) { // ⚠️ 必须返回 false(= 内容未变化):调用方会把返回值当作「是否已变化」用于重绘与回写, // 返回 true 会让被过滤器拒绝的按键显示成“已输入”。 return false; } return ReplaceSelection(std::wstring(1, character)); } bool TextEdit::Backspace() { if (!m_active) { return false; } if (HasSelection()) { // 存在选区时退格清除整个选区。 return ReplaceSelection(std::wstring()); } if (m_caret == 0) { m_fresh = false; return false; } const std::size_t previous = StepLeft(m_caret); const std::size_t removed = m_caret - previous; m_text.erase(previous, removed); m_caret = previous; m_anchor = previous; m_fresh = false; return true; } bool TextEdit::DeleteForward() { if (!m_active) { return false; } if (HasSelection()) { // 存在选区时删除键清除整个选区。 return ReplaceSelection(std::wstring()); } if (m_caret >= m_text.size()) { m_fresh = false; return false; } const std::size_t next = StepRight(m_caret); m_text.erase(m_caret, next - m_caret); m_anchor = m_caret; m_fresh = false; return true; } bool TextEdit::MoveCaret(int delta, bool extendSelection) { if (!m_active || delta == 0) { return false; } // 未按 Shift 且存在选区时,左右键先「收拢」到选区的对应边缘(常规输入框行为): // 左键跳到选区左端、右键跳到选区右端,而不是在光标处再移动一格。 if (!extendSelection && HasSelection()) { const std::size_t target = (delta < 0) ? SelectionBegin() : SelectionEnd(); m_caret = target; m_anchor = target; m_fresh = false; return true; } std::size_t offset = m_caret; const int steps = (delta > 0) ? delta : -delta; for (int index = 0; index < steps; ++index) { offset = (delta > 0) ? StepRight(offset) : StepLeft(offset); } offset = ClampOffset(offset); if (offset == m_caret) { return false; } m_caret = offset; if (!extendSelection) { m_anchor = offset; } m_fresh = false; return true; } bool TextEdit::MoveHome(bool extendSelection) { if (!m_active) { return false; } // 未按 Shift 时 Home 必须收拢已有选区(例如整段选中后按 Home 应取消全选)。 if (!extendSelection) { const bool changed = HasSelection() || (m_caret != 0); m_caret = 0; m_anchor = 0; m_fresh = false; return changed; } if (m_caret == 0) { return false; } m_caret = 0; m_fresh = false; return true; } bool TextEdit::MoveEnd(bool extendSelection) { if (!m_active) { return false; } // 同上:End 在未按 Shift 时收拢到行尾。 if (!extendSelection) { const bool changed = HasSelection() || (m_caret != m_text.size()); m_caret = m_text.size(); m_anchor = m_caret; m_fresh = false; return changed; } if (m_caret == m_text.size()) { return false; } m_caret = m_text.size(); m_fresh = false; return true; } void TextEdit::SelectAll() { m_anchor = 0; m_caret = m_text.size(); m_fresh = !m_text.empty(); } bool TextEdit::DeselectToEnd() { if (!m_active) { return false; } const bool changed = HasSelection() || (m_caret != m_text.size()); m_caret = m_text.size(); m_anchor = m_caret; m_fresh = false; return changed; } void TextEdit::BeginDrag(std::size_t offset) { if (!m_active) { return; } const std::size_t target = ClampOffset(offset); m_caret = target; m_anchor = target; m_fresh = false; m_dragging = true; } bool TextEdit::UpdateDrag(std::size_t offset) { if (!m_active || !m_dragging) { return false; } const std::size_t target = ClampOffset(offset); if (target == m_caret) { return false; } m_caret = target; m_fresh = false; return true; } bool TextEdit::CopyToClipboard(HWND owner) const { if (m_text.empty()) { return false; } const std::wstring payload = HasSelection() ? m_text.substr(SelectionBegin(), SelectionEnd() - SelectionBegin()) : m_text; return CopyTextToClipboard(owner, payload); } bool TextEdit::CutToClipboard(HWND owner) { if (m_text.empty()) { return false; } const bool copied = CopyToClipboard(owner); if (!copied) { return false; } if (HasSelection()) { return ReplaceSelection(std::wstring()); } // ⚠️ 没有选区时 Ctrl+X 只复制、**不清空**:与常规输入框一致。 // 旧实现会 `m_text.clear()` ⇒ 用户想复制一个阈值/色号却把整个值删掉了。 return false; } bool TextEdit::PasteFromClipboard(HWND owner) { if (!m_active) { return false; } std::wstring pasted; if (!PasteTextFromClipboard(owner, pasted)) { return false; } // 按当前过滤策略清洗粘贴内容,避免数值框被灌入非法字符。 std::wstring cleaned; cleaned.reserve(pasted.size()); for (const wchar_t c : pasted) { if (AcceptsCharacter(m_filter, c)) { cleaned.push_back(c); } } if (cleaned.empty()) { return false; } return ReplaceSelection(cleaned); } } // namespace jdi