#include "commons.h" #define DEFINE_GUIDXXX(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ const GUID CDECL name \ = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } DEFINE_GUIDXXX(IID_ITextDocument,0x8CC497C0,0xA1DF,0x11CE,0x80,0x98, 0x00,0xAA,0x00,0x47,0xBE,0x5D); RichEdit::RichEdit(HWND hwnd) : hwnd(NULL), ole(NULL), textDocument(NULL), stopped(0), undoEnabled(TRUE) { SetHWND(hwnd); } RichEdit::~RichEdit() { SetHWND(NULL); } bool RichEdit::IsValid() const { return ole != NULL; } HWND RichEdit::GetHWND() const { return hwnd; } void RichEdit::SetHWND(HWND hwnd) { if (textDocument != NULL) { textDocument->Release(); textDocument = NULL; } if (ole != NULL) { ole->Release(); ole = NULL; } this->hwnd = hwnd; if (hwnd == NULL) return; SendMessage(EM_GETOLEINTERFACE, 0, (LPARAM) &ole); if (ole == NULL) return; if (ole->QueryInterface(IID_ITextDocument, (void**) &textDocument) != S_OK) textDocument = NULL; } LRESULT RichEdit::SendMessage(UINT Msg, WPARAM wParam, LPARAM lParam) const { return ::SendMessage(hwnd, Msg, wParam, lParam); } bool RichEdit::IsReadOnly() const { return (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_READONLY) == ES_READONLY; } void RichEdit::SuspendUndo() { if (textDocument != NULL) { textDocument->Undo(tomSuspend, NULL); undoEnabled = FALSE; } } void RichEdit::ResumeUndo() { if (textDocument != NULL) { textDocument->Undo(tomResume, NULL); undoEnabled = TRUE; } } void RichEdit::Stop() { stopped++; if (stopped != 1) return; SuspendUndo(); // HideCaret(hwnd); SendMessage(WM_SETREDRAW, FALSE, 0); SendMessage(EM_GETSCROLLPOS, 0, (LPARAM) &old_scroll_pos); SendMessage(EM_EXGETSEL, 0, (LPARAM) &old_sel); GetCaretPos(&caretPos); old_mask = SendMessage(EM_GETEVENTMASK, 0, 0); SendMessage(EM_SETEVENTMASK, 0, old_mask & ~ENM_CHANGE); inverse = (old_sel.cpMin >= LOWORD(SendMessage(EM_CHARFROMPOS, 0, (LPARAM) &caretPos))); } void RichEdit::Start() { stopped--; if (stopped < 0) { stopped = 0; return; } else if (stopped > 0) return; if (inverse) { LONG tmp = old_sel.cpMin; old_sel.cpMin = old_sel.cpMax; old_sel.cpMax = tmp; } SendMessage(EM_SETEVENTMASK, 0, old_mask); SendMessage(EM_EXSETSEL, 0, (LPARAM) &old_sel); SendMessage(EM_SETSCROLLPOS, 0, (LPARAM) &old_scroll_pos); SendMessage(WM_SETREDRAW, TRUE, 0); InvalidateRect(hwnd, NULL, FALSE); // ShowCaret(hwnd); ResumeUndo(); } BOOL RichEdit::IsStopped() { return stopped > 0; } int RichEdit::GetCharFromPos(const POINT &pt) { return LOWORD(SendMessage(EM_CHARFROMPOS, 0, (LPARAM) &pt)); } int RichEdit::GetLineCount() const { return SendMessage(EM_GETLINECOUNT, 0, 0); } void RichEdit::GetLine(int line, TCHAR *text, size_t text_len) const { *((WORD*)text) = WORD(text_len - 1); unsigned size = (unsigned) SendMessage(EM_GETLINE, (WPARAM) line, (LPARAM) text); // Sometimes it likes to return size = lineLen+1, adding an \n at the end, so we remove it here // to make both implementations return same size int lineLen = GetLineLength(line); size = (unsigned) max(0, min((int)text_len - 1, min((int) size, lineLen))); text[size] = _T('\0'); } int RichEdit::GetLineLength(int line) const { return SendMessage(EM_LINELENGTH, GetFirstCharOfLine(line), 0); } int RichEdit::GetFirstCharOfLine(int line) const { return SendMessage(EM_LINEINDEX, (WPARAM) line, 0); } int RichEdit::GetLineFromChar(int charPos) const { return SendMessage(EM_LINEFROMCHAR, charPos, 0); } CHARRANGE RichEdit::GetSel() const { CHARRANGE sel; SendMessage(EM_EXGETSEL, 0, (LPARAM) &sel); return sel; } void RichEdit::SetSel(int start, int end) { CHARRANGE sel = { start, end }; SetSel(sel); } void RichEdit::SetSel(const CHARRANGE &sel) { SendMessage(EM_EXSETSEL, 0, (LPARAM) &sel); } int RichEdit::GetTextLength() const { return GetWindowTextLength(hwnd); } TCHAR *RichEdit::GetText(int start, int end) const { if (end <= start) end = GetTextLength(); if (textDocument != NULL) { ITextRange *range; if (textDocument->Range(start, end, &range) != S_OK) return mir_tstrdup(_T("")); BSTR text = NULL; if (range->GetText(&text) != S_OK || text == NULL) { range->Release(); return mir_tstrdup(_T("")); } TCHAR *ret = mir_u2t(text); SysFreeString(text); range->Release(); return ret; } else { int len = GetTextLength(); TCHAR *tmp = (TCHAR *) mir_alloc(len * sizeof(TCHAR)); GetWindowText(hwnd, tmp, len); tmp[len] = 0; TCHAR *ret = (TCHAR *) mir_alloc((end - start + 1) * sizeof(TCHAR)); memmove(ret, &tmp[start], (end - start) * sizeof(TCHAR)); ret[end - start] = 0; mir_free(tmp); return ret; } } void RichEdit::ReplaceSel(const TCHAR *new_text) { if (stopped) { CHARRANGE sel = GetSel(); ResumeUndo(); SendMessage(EM_REPLACESEL, undoEnabled, (LPARAM) new_text); SuspendUndo(); FixSel(&old_sel, sel, lstrlen(new_text)); SendMessage(WM_SETREDRAW, FALSE, 0); SendMessage(EM_SETEVENTMASK, 0, old_mask & ~ENM_CHANGE); } else { SendMessage(EM_REPLACESEL, undoEnabled, (LPARAM) new_text); } } int RichEdit::Replace(int start, int end, const TCHAR *new_text) { CHARRANGE sel = GetSel(); CHARRANGE replace_sel = { start, end }; SetSel(replace_sel); ReplaceSel(new_text); int dif = FixSel(&sel, replace_sel, lstrlen(new_text)); SetSel(sel); return dif; } int RichEdit::Insert(int pos, const TCHAR *text) { CHARRANGE sel = GetSel(); CHARRANGE replace_sel = { pos, pos }; SetSel(replace_sel); ReplaceSel(text); int dif = FixSel(&sel, replace_sel, lstrlen(text)); SetSel(sel); return dif; } int RichEdit::Delete(int start, int end) { CHARRANGE sel = GetSel(); CHARRANGE replace_sel = { start, end }; SetSel(replace_sel); ReplaceSel(_T("")); int dif = FixSel(&sel, replace_sel, 0); SetSel(sel); return dif; } int RichEdit::FixSel(CHARRANGE *to_fix, CHARRANGE sel_changed, int new_len) { int dif = new_len - (sel_changed.cpMax - sel_changed.cpMin); if (to_fix->cpMax <= sel_changed.cpMin) return dif; int newMax = sel_changed.cpMax + dif; if (to_fix->cpMin >= sel_changed.cpMax) to_fix->cpMin += dif; else if (to_fix->cpMin >= newMax) // For dif < 0, pos beetween sel_changed.cpMax + dif and sel_changed.cpMax to_fix->cpMin = newMax; if (to_fix->cpMax >= sel_changed.cpMax) to_fix->cpMax += dif; else if (to_fix->cpMax >= newMax) // For dif < 0, pos beetween sel_changed.cpMax + dif and sel_changed.cpMax to_fix->cpMax = newMax; return dif; }