diff --git a/dlls/riched20/editor.c b/dlls/riched20/editor.c index d9bcea84f74..e512f2f9ed6 100644 --- a/dlls/riched20/editor.c +++ b/dlls/riched20/editor.c @@ -2333,52 +2333,70 @@ done: return hr == S_OK; } -static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars) +static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out ) { - LPDATAOBJECT dataObj = NULL; - HRESULT hr = S_OK; + IDataObject *data = NULL; + HRESULT hr = S_OK; - if (editor->cPasswordMask) - return FALSE; /* Copying or Cutting masked text isn't allowed */ + if (editor->lpOleCallback) + { + CHARRANGE range; + range.cpMin = ME_GetCursorOfs( start ); + range.cpMax = range.cpMin + chars; + hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data ); + } - if(editor->lpOleCallback) - { - CHARRANGE range; - range.cpMin = ME_GetCursorOfs(start); - range.cpMax = range.cpMin + nChars; - hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj); - } - if(FAILED(hr) || !dataObj) - hr = ME_GetDataObject(editor, start, nChars, &dataObj); - if(SUCCEEDED(hr)) { - hr = OleSetClipboard(dataObj); - IDataObject_Release(dataObj); - } - return SUCCEEDED(hr); + if (FAILED( hr ) || !data) + hr = ME_GetDataObject( editor, start, chars, &data ); + + if (SUCCEEDED( hr )) + { + if (data_out) + *data_out = data; + else + { + hr = OleSetClipboard( data ); + IDataObject_Release( data ); + } + } + + return hr; } -static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut) +HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count, + IDataObject **data_out ) { - BOOL result; - int offs, num_chars; - int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars); - ME_Cursor *sel_start = &editor->pCursors[start_cursor]; + HRESULT hr; if (cut && (editor->styleFlags & ES_READONLY)) { - MessageBeep(MB_ICONERROR); - return FALSE; + return E_ACCESSDENIED; } - num_chars -= offs; - result = ME_Copy(editor, sel_start, num_chars); - if (result && cut) + hr = editor_copy( editor, start, count, data_out ); + if (SUCCEEDED(hr) && cut) { - ME_InternalDeleteText(editor, sel_start, num_chars, FALSE); - ME_CommitUndo(editor); - ME_UpdateRepaint(editor, TRUE); + ME_InternalDeleteText( editor, start, count, FALSE ); + ME_CommitUndo( editor ); + ME_UpdateRepaint( editor, TRUE ); } - return result; + return hr; +} + +static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut ) +{ + HRESULT hr; + int offs, count; + int start_cursor = ME_GetSelectionOfs( editor, &offs, &count ); + ME_Cursor *sel_start = &editor->pCursors[start_cursor]; + + if (editor->cPasswordMask) return FALSE; + + count -= offs; + hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL ); + if (FAILED( hr )) MessageBeep( MB_ICONERROR ); + + return SUCCEEDED( hr ); } /* helper to send a msg filter notification */ diff --git a/dlls/riched20/editor.h b/dlls/riched20/editor.h index 0503a671ea1..fba9616ab4f 100644 --- a/dlls/riched20/editor.h +++ b/dlls/riched20/editor.h @@ -270,6 +270,8 @@ void ME_StreamInFill(ME_InStream *stream) DECLSPEC_HIDDEN; extern BOOL me_debug DECLSPEC_HIDDEN; void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len) DECLSPEC_HIDDEN; int set_selection( ME_TextEditor *editor, int to, int from ) DECLSPEC_HIDDEN; +HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count, + IDataObject **data_out ) DECLSPEC_HIDDEN; ME_Paragraph *editor_first_para( ME_TextEditor *editor ) DECLSPEC_HIDDEN; /* table.c */ diff --git a/dlls/riched20/richole.c b/dlls/riched20/richole.c index 923984e96b1..21e92e95cd1 100644 --- a/dlls/riched20/richole.c +++ b/dlls/riched20/richole.c @@ -2594,28 +2594,53 @@ static HRESULT WINAPI ITextRange_fnDelete(ITextRange *me, LONG unit, LONG count, return E_NOTIMPL; } +static HRESULT textrange_copy_or_cut( ITextRange *range, ME_TextEditor *editor, BOOL cut, VARIANT *v ) +{ + LONG start, end; + ME_Cursor cursor; + IDataObject **data_out = NULL; + + ITextRange_GetStart( range, &start ); + ITextRange_GetEnd( range, &end ); + if (start == end) + { + /* If the range is empty, all text is copied */ + LONG prev_end = end; + ITextRange_SetEnd( range, MAXLONG ); + start = 0; + ITextRange_GetEnd( range, &end ); + ITextRange_SetEnd( range, prev_end ); + } + cursor_from_char_ofs( editor, start, &cursor ); + + if (v && V_VT(v) == (VT_UNKNOWN | VT_BYREF) && V_UNKNOWNREF( v )) + data_out = (IDataObject **)V_UNKNOWNREF( v ); + + return editor_copy_or_cut( editor, cut, &cursor, end - start, data_out ); +} + static HRESULT WINAPI ITextRange_fnCut(ITextRange *me, VARIANT *v) { ITextRangeImpl *This = impl_from_ITextRange(me); - FIXME("(%p)->(%p): stub\n", This, v); + TRACE("(%p)->(%p)\n", This, v); if (!This->child.reole) return CO_E_RELEASED; - return E_NOTIMPL; + return textrange_copy_or_cut(me, This->child.reole->editor, TRUE, v); } static HRESULT WINAPI ITextRange_fnCopy(ITextRange *me, VARIANT *v) { ITextRangeImpl *This = impl_from_ITextRange(me); - FIXME("(%p)->(%p): stub\n", This, v); + TRACE("(%p)->(%p)\n", This, v); if (!This->child.reole) return CO_E_RELEASED; - return E_NOTIMPL; + return textrange_copy_or_cut(me, This->child.reole->editor, FALSE, v); } static HRESULT WINAPI ITextRange_fnPaste(ITextRange *me, VARIANT *v, LONG format) @@ -5365,25 +5390,35 @@ static HRESULT WINAPI ITextSelection_fnDelete(ITextSelection *me, LONG unit, LON static HRESULT WINAPI ITextSelection_fnCut(ITextSelection *me, VARIANT *v) { ITextSelectionImpl *This = impl_from_ITextSelection(me); + ITextRange *range = NULL; + HRESULT hr; - FIXME("(%p)->(%p): stub\n", This, v); + TRACE("(%p)->(%p): stub\n", This, v); if (!This->reOle) return CO_E_RELEASED; - return E_NOTIMPL; + ITextSelection_QueryInterface(me, &IID_ITextRange, (void**)&range); + hr = textrange_copy_or_cut(range, This->reOle->editor, TRUE, v); + ITextRange_Release(range); + return hr; } static HRESULT WINAPI ITextSelection_fnCopy(ITextSelection *me, VARIANT *v) { ITextSelectionImpl *This = impl_from_ITextSelection(me); + ITextRange *range = NULL; + HRESULT hr; - FIXME("(%p)->(%p): stub\n", This, v); + TRACE("(%p)->(%p)\n", This, v); if (!This->reOle) return CO_E_RELEASED; - return E_NOTIMPL; + ITextSelection_QueryInterface(me, &IID_ITextRange, (void**)&range); + hr = textrange_copy_or_cut(range, This->reOle->editor, FALSE, v); + ITextRange_Release(range); + return hr; } static HRESULT WINAPI ITextSelection_fnPaste(ITextSelection *me, VARIANT *v, LONG format) diff --git a/dlls/riched20/tests/richole.c b/dlls/riched20/tests/richole.c index 16ff1fafa67..a5f439aece7 100644 --- a/dlls/riched20/tests/richole.c +++ b/dlls/riched20/tests/richole.c @@ -4012,6 +4012,79 @@ static void test_character_movement(void) ITextRange_Release(range); } +#define CLIPBOARD_RANGE_CONTAINS(range, start, end, expected) _clipboard_range_contains(range, start, end, expected, __LINE__, 0); +#define TODO_CLIPBOARD_RANGE_CONTAINS(range, start, end, expected) _clipboard_range_contains(range, start, end, expected, __LINE__, 1); +static void _clipboard_range_contains(ITextRange *range, LONG start, LONG end, const char *expected, int line, int todo) +{ + HRESULT hr; + BOOL clipboard_open; + HGLOBAL global; + const char *clipboard_text; + + hr = ITextRange_SetRange(range, start, end); + ok_(__FILE__,line)(SUCCEEDED(hr), "SetRange failed: 0x%08x\n", hr); + hr = ITextRange_Copy(range, NULL); + ok_(__FILE__,line)(hr == S_OK, "Copy failed: 0x%08x\n", hr); + + clipboard_open = OpenClipboard(NULL); + ok_(__FILE__,line)(clipboard_open, "OpenClipboard failed: %d\n", GetLastError()); + global = GetClipboardData(CF_TEXT); + ok_(__FILE__,line)(global != NULL, "GetClipboardData failed: %p\n", global); + clipboard_text = GlobalLock(global); + ok_(__FILE__,line)(clipboard_text != NULL, "GlobalLock failed: %p\n", clipboard_text); + todo_wine_if(todo) ok_(__FILE__,line)(!strcmp(expected, clipboard_text), "unexpected contents: %s\n", wine_dbgstr_a(clipboard_text)); + GlobalUnlock(global); + CloseClipboard(); +} + +static void test_clipboard(void) +{ + static const char text_in[] = "ab\n c"; + IRichEditOle *reole = NULL; + ITextDocument *doc = NULL; + ITextRange *range; + ITextSelection *selection; + HRESULT hr; + HWND hwnd; + + create_interfaces(&hwnd, &reole, &doc, &selection); + SendMessageA(hwnd, WM_SETTEXT, 0, (LPARAM)text_in); + + hr = ITextDocument_Range(doc, 0, 0, &range); + ok(hr == S_OK, "got 0x%08x\n", hr); + + CLIPBOARD_RANGE_CONTAINS(range, 0, 5, "ab\r\n c") + CLIPBOARD_RANGE_CONTAINS(range, 0, 0, "ab\r\n c") + CLIPBOARD_RANGE_CONTAINS(range, 1, 1, "ab\r\n c") + CLIPBOARD_RANGE_CONTAINS(range, 0, 1, "a") + CLIPBOARD_RANGE_CONTAINS(range, 5, 6, "") + + /* Setting password char does not stop Copy */ + SendMessageA(hwnd, EM_SETPASSWORDCHAR, '*', 0); + CLIPBOARD_RANGE_CONTAINS(range, 0, 1, "a") + + /* Cut can be undone */ + hr = ITextRange_SetRange(range, 0, 1); + ok(SUCCEEDED(hr), "SetRange failed: 0x%08x\n", hr); + hr = ITextRange_Cut(range, NULL); + ok(hr == S_OK, "Cut failed: 0x%08x\n", hr); + CLIPBOARD_RANGE_CONTAINS(range, 0, 4, "b\r\n c"); + hr = ITextDocument_Undo(doc, 1, NULL); + todo_wine ok(hr == S_OK, "Undo failed: 0x%08x\n", hr); + TODO_CLIPBOARD_RANGE_CONTAINS(range, 0, 5, "ab\r\n c"); + + /* Cannot cut when read-only */ + SendMessageA(hwnd, EM_SETREADONLY, TRUE, 0); + hr = ITextRange_SetRange(range, 0, 1); + ok(SUCCEEDED(hr), "SetRange failed: 0x%08x\n", hr); + hr = ITextRange_Cut(range, NULL); + ok(hr == E_ACCESSDENIED, "got 0x%08x\n", hr); + + release_interfaces(&hwnd, &reole, &doc, NULL); + ITextSelection_Release(selection); + ITextRange_Release(range); +} + START_TEST(richole) { /* Must explicitly LoadLibrary(). The test has no references to functions in @@ -4052,4 +4125,5 @@ START_TEST(richole) test_Expand(); test_MoveEnd_story(); test_character_movement(); + test_clipboard(); }